| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152 |
1x
1x
1x
1x
1x
1x
1x
1x
17x
17x
17x
17x
17x
15x
15x
12x
11x
11x
17x
17x
17x
5x
5x
14x
5x
17x
17x
15x
15x
19x
17x
17x
17x
1x
17x
15x
15x
1x
1x
1x
16x
16x
16x
16x
15x
19x
19x
19x
1x
18x
17x
15x
| /**
*
* @module libs/fs/inject
* @createdAt 2017-06-12
*
* @copyright Copyright (c) 2017 Zhonglei Qiu
* @license Licensed under the MIT license.
*/
var fs = require('fs')
var escapeRegExp = require('../lang/escapeRegExp')
var EOL = '\n'
var TAGS_MAP = {
hash: ['## ', ' ##', '## ', ' ##'],
docs: ['/*# ', ' #*/', '/*# ', ' #*/'],
html: ['<!--# ', ' #-->', '<!--# ', ' #-->'],
loose: ['# ', ' #', '# ', ' #'] // 最宽松的模式
}
var TAGS_FILE_EXTENSIONS = {
hash: ['gitignore', 'sh', 'bash', 'npmignore'],
docs: ['js', 'jsx', 'css', 'sass', 'ts', 'tsx', 'json'],
html: ['html', 'md']
}
var TAG_START_KEYWORD = 'INJECT_START'
var TAG_END_KEYWORD = 'INJECT_END'
/**
* 根据文件 file 中的注释,注入 data 中对应的内容
*
* GROUP | EXTENSIONS | TAGS
* --------|--------------------------------------------------------------
* hash | gitignore, sh, bash | ['## ', ' ##', '## ', ' ##']
* docs | js, jsx, css, sass, ts, tsx, json | (文档注释加 "#")
* html | html, md | ['<!--# ', ' #-->', '<!--# ', ' #-->']
* loose | 支持上面所有的后缀 | ['# ', ' #', '# ', ' #']
*
* @param {string} file 要注入的文件的文件路径
* @param {Object} data 要注入的内容
* @param {Object} [options] 选项
* @param {string|Array<string>} [options.tags] [tagStartLeft, tagStartRight, tagEndLeft, tagEndRight]
* @param {boolean} [options.autoPrefixSpaces = true] 自动根据最后一个注释前的空格给每一行都添加相同的空格
* @param {boolean} [options.returnContent = false] 返回注入的内容,而不是直接注入
* @param {boolean} [options.append = false] 是否将内容 append 到原区块之后,而不是整体替换
* @example
* bash 中可以这样写: (type 默认是 string,可以不写,另外支持 file,这时 key 对应的 value 是文件地址)
*
* ## INJECT_START {"type": "string", "key": "ignores"} ##
* ## INJECT_END ##
*
* 或
*
* ## INJECT_START ignores ##
* ## INJECT_END ##
*
* @return {number} 返回注入成功的数量
*/
module.exports = function inject(file, data, options) {
options = options || {}
if (!('autoPrefixSpaces' in options)) options.autoPrefixSpaces = true
var content = fs.readFileSync(file).toString()
var counter = {count: 0}
var tags = getTags(file, options)
var regexp = buildRegExp(tags, TAG_START_KEYWORD, TAG_END_KEYWORD)
var newContent = content.replace(regexp, replaceContent(data, counter, options))
if (options.returnContent) return newContent
if (newContent !== content) fs.writeFileSync(file, newContent)
return counter.count
}
function getTags(file, options) {
var tagsKey = options.tags
var tags = tagsKey
if (!tagsKey) {
var extension = file.split('.').pop()
tagsKey = Object.keys(TAGS_FILE_EXTENSIONS).find(function(key) {
return TAGS_FILE_EXTENSIONS[key].indexOf(extension) >= 0
})
// if (!tagsKey) throw new Error('Can not judge the tags from current file extension')
if (!tagsKey) tagsKey = 'loose' // 无法判断出就使用 loose 模式
}
if (typeof tagsKey === 'string') tags = TAGS_MAP[tagsKey]
if (!Array.isArray(tags) || tags.length !== 4) throw new Error('Tags options should be Array and contains 4 string items')
return tags
}
function replaceContent(data, counter, options) {
return function(raw, startLine, jsonString, oldValue, endLine, spaces) {
var json = checkJsonString(jsonString)
var type = json.type || 'string'
var dataValue = data[json.key] || ''
if (typeof dataValue === 'function') {
dataValue = dataValue(oldValue)
}
var replaceValue
switch (type) {
case 'string':
replaceValue = dataValue
break
case 'file':
replaceValue = fs.readFileSync(dataValue).toString()
break
default:
throw new Error('Not supported inject type<' + data.type + '>')
}
counter.count++
var eol = options.eol == null ? EOL : options.eol
return startLine.trim()
+ (options.append ? oldValue : eol)
+ prefixSpaces(replaceValue, spaces, options.autoPrefixSpaces) + (replaceValue && eol)
+ endLine
}
}
function prefixSpaces(content, spaces, enabled) {
if (!content || !enabled) return content
return spaces + content.replace(/\r?\n/g, '$&' + spaces)
}
function checkJsonString(raw) {
var json
var str = raw.trim()
try {
json = str[0] === '{' ? JSON.parse(str) : {key: str}
} catch (e) {
throw new Error('Inject config<' + raw + '> is not a valid json string')
}
if (!json.hasOwnProperty('key')) throw new Error('Inject config<' + raw + '> should contains "key" field')
return json
}
function buildRegExp(tags, startKeyword, endKeyword) {
return new RegExp(
'(\\S*' + escapeRegExp(tags[0] + startKeyword) + '\\s*(.*)\\s*' + escapeRegExp(tags[1]) + '\\S*)'
+ '([\\s\\S]*?)'
// 保持前导的空格或 tab 一样多
+ '(([ ]*)\\S*' + escapeRegExp(tags[2] + endKeyword + tags[3]) + '\\S*)',
'g'
)
}
|