UNPKG

zero-ai

Version:
345 lines (337 loc) 10.6 kB
const fs = require('fs'); const Immutable = require('immutable'); const U = require('underscore'); const __CX = require('./ai.under.fn.cx.evaluate'); const __FX = require('./ai.under.fn.fx.terminal'); const __IT = require('./ai.uncork.fn.it.feature'); const __IO = require('./ai.export.interface.io'); const __LOG = require('./ai.unified.fn._.logging'); const __STR = require('./ai.export.interface.fn.string'); const _parseButtons = (value) => { if (value.startsWith('(') && value.endsWith(')')) { // 分组的情况 } else { value = value.split('-'); const buttons = []; value.forEach((eachVal) => { const button = {}; const eachVals = eachVal.split(':'); button.text = eachVals[0]; if (eachVals[1]) { button.key = `btn${__STR.strFirstUpper(eachVals[1])}`; } if (eachVals[2]) { button.type = eachVals[2]; } buttons.push(button); }); return buttons; } }; const _parseTopbar = (key, value = "") => { const textes = value.split(','); const ret = {}; const config = {}; textes.forEach(text => { if (0 <= text.indexOf('=')) { const kv = text.split('='); const key = kv[0].trim(); let value = kv[1].trim(); if ("buttons" === key) { value = _parseButtons(value); } config[key] = value; } }); ret[`_${key}`] = config; return ret; }; const _parseTabs = (key, value = "") => { const textes = value.split(','); const ret = {}; const items = []; textes.forEach((each, index) => { const item = {}; item.tab = each; item.key = `tabItem${index}`; items.push(item); }); ret[`_${key}`] = {items}; return ret; }; const _parseNav = (key, value = "") => { const textes = value.split(','); const ret = {}; const items = []; textes.forEach(each => { const item = {}; if (0 <= each.indexOf('-')) { const meta = each.split('-'); item.text = meta[0]; item.uri = meta[1]; } else { item.text = each; } items.push(item); }); if (0 < items.length && items[0]) { items[0].uri = '$MAIN$'; } if (1 < items.length && items[items.length - 1]) { items[items.length - 1].uri = '$SELF$'; } items.forEach((item, index) => { item.key = `lnkNav${index}`; }); ret[`_${key}`] = items; return ret; }; const _parser = { NAV: _parseNav, TOPBAR: _parseTopbar, TABS: _parseTabs }; const _parseLine = (line = "") => { // 以#号为配置项 if (0 <= line.indexOf('#')) { const kv = line.split('#'); const prefix = kv[0]; const value = kv[1]; const type = prefix.toUpperCase(); if (_parser[type]) { return _parser[type](prefix, value); } } else { __LOG.warn(`不合法格式(不包含#)跳过配置行${line}`) } return {}; }; const parseUi = (lines = []) => { const result = {}; lines.forEach(line => { const kv = _parseLine(line); Object.assign(result, kv); }); return result; }; const _parseValue = (value = "") => { // 是否数值 if (value) { if ("true" === value.toString().toLowerCase() || "false" === value.toString().toLowerCase()) { value = "true" === value.toString().toLowerCase(); } else if (value.startsWith("[")) { value = JSON.parse(value); } else { const reg = /^([1-9]\d*|-[1-9]\d*)$/; if (reg.test(value)) { value = parseInt(value, 10); } } } return value; }; const _parseExpr = (item = "") => { let ret = item; if (item) { item = item.toString().trim(); if (0 < item.indexOf('=')) { const kv = item.split('='); kv[1] = _parseValue(kv[1]); ret = [kv[0], kv[1]]; } else { ret = [item, item]; } } return ret; }; const _parseRel = (lines = []) => { const array = []; lines.forEach(line => { const parsed = _parseExpr(line.trim()); array.push(parsed); }); return array; }; const _parseOption = (item = "", name = "option") => { const root = __IO.ioRoot(); const prop = parseZero(root + "/commander/" + name + ".zero", ["P;"]); const kv = _parseExpr(item); const pair = {}; pair[kv[0]] = kv[1]; if (prop[kv[0]]) pair[prop[kv[0]]] = kv[1]; delete pair[undefined]; return pair; }; const _parseArray = (lines = []) => { const fields = lines.shift(); const fieldArr = fields.split(','); const result = []; lines.forEach(line => { const valueArr = line.toString().trim().split(','); const entity = {}; __IT.itPair(fieldArr, valueArr, (first, second) => { first = first ? first.trim() : first; second = second ? second.trim() : second; if (first.startsWith('option')) { const optionValue = _parseOption(second); if (0 < Object.keys(optionValue).length) { entity[first] = optionValue; } } else { entity[first] = second; } }); result.push(entity); }); return result; }; const _parseProp = (lines) => { const data = {}; lines.forEach(line => { const parsed = _parseExpr(line.trim()); data[parsed[0]] = parsed[1]; }); return data; }; const _parseKv = (lines = []) => { let content = ""; lines.forEach(line => content += line); const pairs = content.split(','); const result = {}; pairs.forEach(pair => { const input = pair.trim(); const kv = _parseExpr(input); result[kv[0].trim()] = kv[1]; }); return result; }; const PARSER = { "P;": _parseProp, "P": _parseProp, "A;": _parseArray, "A": _parseArray, "KV;": _parseKv, "KV": _parseKv, "UI;": parseUi, "UI": parseUi, "R;": _parseRel, "R": _parseRel }; /** * ## `Ec.parseZero` * * ### 1. 基本介绍 * * 解析路径中的特殊文件,文件后缀使用`.zero`,作为当前工具的核心配置数据,文件内容通常如下(`UTF-8`方式读取内容): * * ```shell * PREFIX; * <Content> * ``` * * 此处`PREFIX`则是第二参数,表示不同的文件类型,不同文件类型的解析方式有所区别。 * * ### 2. 文件类型 * * |前缀|含义| * |:---|:---| * |`P;`|属性文件专用集,用于描述基础属性文件内容。| * |`KV;`|`P;`的不换行模式,直接以`,`为每一个键值对的匹配项,去`\n`换行符过后统一解析,不支持`ENUM`枚举信息。| * |`R;`|关系专用解析文件,可解析关系数据文件(后期可增强)。| * |`A;`|命令解析专用,表格解析器,解析以`\t`为分隔符的表格,然后解析尾部`optionX`部分为配置选项。| * |`UI;`|Jsx文件专用解析器,根据文件模板解析数据。| * |`J;`|Java文件专用解析器,解析Java源代码文件数据专用。| * * @memberOf module:_epic * @param {String} path 解析文件的路径 * @param {Array} fileTypes 文件前缀类型 * @returns {Any} 最终解析出来的数据内容 */ const parseZero = (path, fileTypes = ['J;', 'P;', 'A;', 'KV;', "UI;", 'R;']) => { if (fs.existsSync(path)) { const content = fs.readFileSync(path, "utf-8").trim(); const lines = content.split(/\r?\n/g); //for darwin, it is \n; for win32, it is \r\n. const fileType = lines.shift(); __CX.cxEnum(fileType, fileTypes); const parser = PARSER[fileType]; __FX.fxError(!U.isFunction(parser), 10003, fileType); return parser(lines); } else { __FX.fxError(10009, path); } }; /** * ## `Ec.parseArgs` * * @memberOf module:_epic * @param {Number} ensure 参数允许的长度解析,如果长度不合法,则直接退出 * @returns {Object} 解析成功后的数据信息 */ const parseArgs = (ensure) => { const inputArgs = process.argv.splice(3); if (ensure === inputArgs.length) { const config = {}; let key = undefined; let value = undefined; for (let idx = 0; idx <= inputArgs.length; idx++) { if (0 === idx % 2) { key = inputArgs[idx]; } else { value = inputArgs[idx]; } if (key && value) { config[key] = value; key = undefined; value = undefined; } } return config; } else { __LOG.error(`参数丢失,期望参数: ${ensure / 2} 个.`); process.exit(); } }; const parseInput = (required = []) => { const inputArgs = process.argv.splice(3); const config = {}; let key = undefined; let value = undefined; for (let idx = 0; idx <= inputArgs.length; idx++) { if (0 === idx % 2) { key = inputArgs[idx]; } else { value = inputArgs[idx]; } if (key && value) { config[key] = value; key = undefined; value = undefined; } } const $keys = Immutable.fromJS(Object.keys(config)); __IT.itArray(required, (each) => { const checked = 1 < each.length && (!($keys.contains(each[0]) || $keys.contains(each[1]))); __FX.fxError(checked, 10006, each); }); return config; }; const parseFormat = (args = {}, pairs = []) => { const actual = {}; pairs.forEach(item => __FX.fxContinue(U.isArray(item), () => { const arg0 = item[0]; const arg1 = item[1]; let finalKey = arg0.length > arg1.length ? arg0 : arg1; finalKey = finalKey.toString().replace(/-/g, ''); const dft = item[2]; __IT.itObject(args, (key, value) => __FX.fxContinue(arg0 === key || arg1 === key, () => { actual[finalKey] = value; })); __FX.fxContinue(!args.hasOwnProperty(arg0) && !args.hasOwnProperty(arg1) && undefined !== dft, () => { actual[finalKey] = dft; }); })); __LOG.info(`Zero AI 加载输入参数:\n${JSON.stringify(actual, null, 4).blue}`); return actual; }; module.exports = { parseInput, parseFormat, parseArgs, parseZero, };