UNPKG

s7-scl-gen

Version:

S7 PLC SCL 代码自动生成

275 lines (242 loc) 8.7 kB
#!/usr/bin/env node import { posix, basename, dirname } from 'node:path'; import nodemon from 'nodemon'; import { context, get_rules, convert } from './index.js'; import 'node:assert/strict'; import { cp, access, mkdir } from 'node:fs/promises'; import 'iconv-lite'; function toArr(any) { return any == null ? [] : Array.isArray(any) ? any : [any]; } function toVal(out, key, val, opts) { var x, old=out[key], nxt=( !!~opts.string.indexOf(key) ? (val == null || val === true ? '' : String(val)) : typeof val === 'boolean' ? val : !!~opts.boolean.indexOf(key) ? (val === 'false' ? false : val === 'true' || (out._.push((x = +val,x * 0 === 0) ? x : val),!!val)) : (x = +val,x * 0 === 0) ? x : val ); out[key] = old == null ? nxt : (Array.isArray(old) ? old.concat(nxt) : [old, nxt]); } function mri (args, opts) { args = args || []; opts = opts || {}; var k, arr, arg, name, val, out={ _:[] }; var i=0, j=0, idx=0, len=args.length; const alibi = opts.alias !== void 0; const strict = opts.unknown !== void 0; const defaults = opts.default !== void 0; opts.alias = opts.alias || {}; opts.string = toArr(opts.string); opts.boolean = toArr(opts.boolean); if (alibi) { for (k in opts.alias) { arr = opts.alias[k] = toArr(opts.alias[k]); for (i=0; i < arr.length; i++) { (opts.alias[arr[i]] = arr.concat(k)).splice(i, 1); } } } for (i=opts.boolean.length; i-- > 0;) { arr = opts.alias[opts.boolean[i]] || []; for (j=arr.length; j-- > 0;) opts.boolean.push(arr[j]); } for (i=opts.string.length; i-- > 0;) { arr = opts.alias[opts.string[i]] || []; for (j=arr.length; j-- > 0;) opts.string.push(arr[j]); } if (defaults) { for (k in opts.default) { name = typeof opts.default[k]; arr = opts.alias[k] = opts.alias[k] || []; if (opts[name] !== void 0) { opts[name].push(k); for (i=0; i < arr.length; i++) { opts[name].push(arr[i]); } } } } const keys = strict ? Object.keys(opts.alias) : []; for (i=0; i < len; i++) { arg = args[i]; if (arg === '--') { out._ = out._.concat(args.slice(++i)); break; } for (j=0; j < arg.length; j++) { if (arg.charCodeAt(j) !== 45) break; // "-" } if (j === 0) { out._.push(arg); } else if (arg.substring(j, j + 3) === 'no-') { name = arg.substring(j + 3); if (strict && !~keys.indexOf(name)) { return opts.unknown(arg); } out[name] = false; } else { for (idx=j+1; idx < arg.length; idx++) { if (arg.charCodeAt(idx) === 61) break; // "=" } name = arg.substring(j, idx); val = arg.substring(++idx) || (i+1 === len || (''+args[i+1]).charCodeAt(0) === 45 || args[++i]); arr = (j === 2 ? [name] : name); for (idx=0; idx < arr.length; idx++) { name = arr[idx]; if (strict && !~keys.indexOf(name)) return opts.unknown('-'.repeat(j) + name); toVal(out, name, (idx + 1 < arr.length) || val, opts); } } } if (defaults) { for (k in opts.default) { if (out[k] === void 0) { out[k] = opts.default[k]; } } } if (alibi) { for (k in out) { arr = opts.alias[k] || []; while (arr.length > 0) { out[arr.shift()] = out[k]; } } } return out; } posix.join(import.meta.dirname.replace(/\\/g, '/').replace(/\\/g, '/'), ".."); process.cwd().replace(/\\/g, '/'); async function prepare_dir(dir) { const parents = dirname(dir); await access(parents).catch(async () => { await prepare_dir(parents); }); await access(dir).catch(async () => { await mkdir(dir).catch( err => { if (err.code !== 'EEXIST') console.log(err); } ); }); } async function _copy(src, dst) { if (typeof src !== 'string' || typeof dst !== 'string') return; const d = dst.endsWith('/') ? dst + basename(src) : dst; await prepare_dir(dirname(d)); await cp(src, d, { recursive: true }); } /** * Copy files * When the target is a folder, it ends with '/' * @param {string} src * @param {string|string[]} dst */ async function copy_file(src, dst) { if (Array.isArray(dst)) { // Asynchronous sequential execution for (const item of dst) { await _copy(src, item); } } else { await _copy(src, dst); } } function show_help() { console.log(`usage: s7scl [subcommand] [path] [options] subcommand 子命令: convert | conv 转换配置为SCL,省略时的默认子命令 watch | monitor 监视配置文件并及时生成SCL help 打印本帮助 gcl | init | template 在当前目录下产生一个配置目录,内含样板配置文件 默认目录名为GCL,也可以用path参数指定为其它目录 path 参数: 指示要解析的YAML文件所在的目录,默认为 "." 即省略时为当前目录 options: --version | -V | -v 显示版本号,会忽略任何 subcommand 子命令 --help | -H 打印本帮助,会忽略任何 subcommand 子命令 --output-zyml 转换时同时输出无注释的配置文件(后缀为.zyml) --no-convert 不进行SCL转换 --no-copy 不进行文件复制 --silent | -s | -S 不输出过程信息 --line-ending 输出文件的换行符: CRLF LF --OE 输出文件的编码: gbk utf8 等 --rules 指定依照规则转换,后面跟规则文件的路径。这时 path 参数将被忽略 例子: s7scl 转换当前目录下的配置文件 s7scl conv programs/GCL 转换programs/GCL子目录下的配置文件 s7scl gcl 在当前目录下建立一个名为GCL配置目录,内含样板配置文件 s7scl gcl MyGCL 在当前目录下建立一个名为MyGCL配置目录,内含样板配置文件 s7scl --rules ./rules.yaml 依照规则文件进行转换 `); } const argv = mri(process.argv.slice(2), { boolean: ['help', 'version', 'silent', 'zyml-only', 'output-zyml'], alias: { H: 'help', V: ['v', 'version'], Z: ['z', 'zyml-only'], S: ['s', 'silent'], } }); const [cmd = 'convert', path] = argv._; const output_zyml = argv['output-zyml']; if (output_zyml) context.output_zyml = output_zyml; const no_convert = argv['no-convert']; if (no_convert) context.no_convert = no_convert; const no_copy = argv['no-copy']; if (no_copy) context.no_copy = no_copy; const silent = argv.silent; if (silent) context.silent = silent; const encoding = argv.OE; if (encoding) context.OE = encoding; const line_ending = argv['line-ending']; if (line_ending) context.line_ending = line_ending; const rules_file = argv.rules; if (argv.version) { console.log(`v${context.version}`); } else if (argv.help) { show_help(); } else if (cmd === 'convert' || cmd === 'conv') { const base_path = process.cwd().replace(/\\/g, '/'); if (rules_file) { const tasks = await get_rules(rules_file); for (const { path, rules } of tasks) { process.chdir(posix.join(base_path, path)); context.work_path = process.cwd().replace(/\\/g, '/'); await convert({ rules }); } } else { if (path) { process.chdir(path); context.work_path = process.cwd().replace(/\\/g, '/'); } await convert(); } no_convert || silent || console.log("\nAll GCL files have been converted to SCL files! 所有GCL文件已转换成SCL文件。"); } else if (cmd === 'watch' || cmd === 'monitor') { process.chdir(path ?? '.'); nodemon({ restartable: "rs", verbose: !silent, script: posix.join(context.module_path, 'lib', 'cli.js'), ext: 'yaml,scl' }); nodemon.on('start', () => { console.log('s7-scl-gen has started'); }).on('quit', () => { console.log('s7-scl-gen has quit'); process.exit(); }).on('restart', (files) => { console.log('s7-scl-gen restarted due to: ', files); }); } else if (cmd === 'gcl' || cmd === 'init' || cmd === 'template') { const distance = posix.join(context.work_path, path ?? 'GCL'); await copy_file(posix.join(context.module_path, 'example'), distance); await copy_file(posix.join(context.module_path, 'README.md'), `${distance}/`); const fullname_dst = posix.join(context.work_path, distance); const readme = posix.join(fullname_dst, 'README.md'); console.log(`Generated configuration folder ${fullname_dst}. 已生成配置文件夹 ${fullname_dst}。\nSee instructions in ${readme}. 可以参阅 ${readme} 内的说明。`); } else { show_help(); }