mora-scripts
Version:
Some collection scripts by myself
155 lines (134 loc) • 5.3 kB
JavaScript
/**
*
* @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 replace = replaceContent(data, counter, options)
var newContent = content
.replace(buildRegExp(tags, ' ' + TAG_START_KEYWORD + ' ', ' ' + TAG_END_KEYWORD + ' '), replace)
.replace(buildRegExp(tags, 'region ' + TAG_START_KEYWORD + ' ', 'endregion ' + TAG_END_KEYWORD + ' '), replace)
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'
)
}