UNPKG

mmir-tooling

Version:
370 lines (369 loc) 22.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; const path_1 = __importDefault(require("path")); const fs_extra_1 = __importDefault(require("fs-extra")); const lodash_1 = __importDefault(require("lodash")); const filepath_utils_1 = __importDefault(require("../utils/filepath-utils")); const module_config_init_1 = __importDefault(require("../utils/module-config-init")); const directories_utils_1 = __importDefault(require("../tools/directories-utils")); const settings_utils_1 = __importDefault(require("../tools/settings-utils")); const settings_utils_2 = __importDefault(require("../tools/settings-utils")); const option_utils_1 = __importDefault(require("../tools/option-utils")); const log_utils_1 = __importDefault(require("../utils/log-utils")); const log = log_utils_1.default.log; const warn = log_utils_1.default.warn; function readDir(dir, list, options) { const files = fs_extra_1.default.readdirSync(dir); const dirs = []; // log('read dir "'+dir+'" -> ', files); files.forEach(function (p) { const absPath = path_1.default.join(dir, p); if (filepath_utils_1.default.isDirectory(absPath)) { dirs.push(absPath); return false; } else if (/grammar\.js(on)?$/i.test(p)) { const normalized = filepath_utils_1.default.normalizePath(absPath); const m = /\/([^/]+)\/grammar\.js(on)?$/i.exec(normalized); const id = m[1]; const opt = options && options[id]; if (opt && (opt.exclude || opt.file)) { //-> ignore/exclude this grammar! log('grammar-utils.addFromDirectory(): excluding grammar ' + id + ' at "' + normalized + '"!'); //DEBUG return; //////////////////// EARLY EXIT ////////////////// } list.push({ id: id, file: normalized, fileType: settings_utils_1.default.getFileType(normalized), engine: opt && opt.engine ? opt.engine : void (0), ignore: opt && opt.ignore ? true : false, async: opt && opt.async ? true : void (0), initPhrase: opt && opt.initPhrase ? opt.initPhrase : void (0), asyncCompile: opt && typeof opt.asyncCompile === 'boolean' ? opt.asyncCompile : void (0), force: opt && typeof opt.force === 'boolean' ? opt.force : void (0), strict: opt && typeof opt.strict === 'boolean' ? opt.strict : void (0) }); } }); // log('read sub-dirs -> ', dirs); var size = dirs.length; if (size > 0) { for (var i = 0; i < size; ++i) { readDir(dirs[i], list, options); } } } function addFromOptions(grammars, list, appRootDir) { var g, entry; for (var id in grammars) { g = grammars[id]; if (g && g.file && !g.exclude) { entry = lodash_1.default.cloneDeep(g); if (!path_1.default.isAbsolute(entry.file)) { entry.file = path_1.default.resolve(appRootDir, entry.file); } entry.file = filepath_utils_1.default.normalizePath(entry.file); entry.fileType = entry.fileType || settings_utils_1.default.getFileType(entry.file); if (entry.id && entry.id !== id) { warn('grammar-utils.addFromOptions(): entry from grammarOptions for ID "' + id + '" has differing field id with value "' + entry.id + '", overwritting the id field with "' + id + '"!'); //FIXME proper webpack error/warning } entry.id = id; //TODO verify existence of entry.file? if (!contains(list, id)) { // log('grammar-utils.addFromOptions(): adding ', entry);//DEBU list.push(entry); } else { warn('grammar-utils.addFromOptions(): entry from grammarOptions for ID ' + id + ' already exists in grammar-list, ignoring entry!'); //FIXME proper webpack error/warning } } // else {//DEBU // log('grammar-utils.addFromOptions(): entry for '+id+' has no file set -> ignore ', g);//DEBU // } } } function isAsyncGramarExecEntry(g) { return !!(g && g.id); } function parseRuntimeConfigurationForOptions(options, config) { if (!options) { return null; } // console.log('START parseRuntimeConfigurationForOptions: ', config , '\n -> \n', options); if (config) { let val, gopt; const CONFIG_ASYNC_EXEC_GRAMMAR = settings_utils_2.default.configEntryAsyncExecGrammar; if (val = config[CONFIG_ASYNC_EXEC_GRAMMAR]) { if (val === true) { if (isApplyRuntimeConfigOption(options, null, true, 'encountered runtime setting true for "' + CONFIG_ASYNC_EXEC_GRAMMAR + '"')) { if (options === true || !options) { options = {}; } options.async = true; } } else if (Array.isArray(val)) { if (isApplyRuntimeConfigOption(options, null, true, 'encountered list for runtime setting "' + CONFIG_ASYNC_EXEC_GRAMMAR + '"')) { if (options === true || !options) { options = {}; } if (!options.grammars) { options.grammars = {}; } gopt = options.grammars; val.forEach(function (grammarEntry) { const grammarId = typeof grammarEntry === 'string' ? grammarEntry : grammarEntry.id; if (isApplyRuntimeConfigOption(gopt[grammarId], grammarId, true, 'encountered list entry "' + grammarId + '" for runtime setting "' + CONFIG_ASYNC_EXEC_GRAMMAR + '"')) { if (gopt[grammarId] === true || !gopt[grammarId]) { gopt[grammarId] = {}; } gopt[grammarId].async = true; if (isAsyncGramarExecEntry(grammarEntry) && grammarEntry.phrase) { gopt[grammarId].initPhrase = grammarEntry.phrase; } } }); } } else { warn('grammar-utils.parseRuntimeConfigurationForOptions(): cannot convert runtime setting for "' + CONFIG_ASYNC_EXEC_GRAMMAR + '": must be Array<string> or true, but encountered ', val); //FIXME proper webpack error/warning } } const CONFIG_IGNORE_GRAMMAR = settings_utils_2.default.configEntryIgnoreGrammar; if (val = config[CONFIG_IGNORE_GRAMMAR]) { if (val === true) { if (isApplyRuntimeConfigOption(options, null, false, 'encountered runtime setting true for "' + CONFIG_IGNORE_GRAMMAR + '"')) { if (options === true || !options) { options = {}; } options.ignore = true; } } else if (Array.isArray(val)) { if (isApplyRuntimeConfigOption(options, null, false, 'encountered list for runtime setting "' + CONFIG_IGNORE_GRAMMAR + '"')) { if (options === true || !options) { options = {}; } if (!options.grammars) { options.grammars = {}; } gopt = options.grammars; val.forEach(function (grammarEntry) { const grammarId = typeof grammarEntry === 'string' ? grammarEntry : grammarEntry.id; if (isApplyRuntimeConfigOption(gopt[grammarId], grammarId, false, 'encountered list entry "' + grammarId + '" for runtime setting "' + CONFIG_IGNORE_GRAMMAR + '"')) { if (gopt[grammarId] === true || !gopt[grammarId]) { gopt[grammarId] = {}; } gopt[grammarId].ignore = true; if (gopt[grammarId].async) { warn('grammar-utils.parseRuntimeConfigurationForOptions(): trying to apply runtime setting "' + CONFIG_IGNORE_GRAMMAR + '" for list entry "' + grammarId + '" to GrammarOptions[' + grammarId + '], but option is already set to async=true: ignore-setting will have no effect!'); //FIXME proper webpack error/warning } } }); } } else { warn('grammar-utils.parseRuntimeConfigurationForOptions(): cannot convert runtime setting for "' + CONFIG_IGNORE_GRAMMAR + '": must be Array<string> or true, but encountered ', val); //FIXME proper webpack error/warning } } const CONFIG_GRAMMAR_DISABLE_STRICT_MODE = settings_utils_2.default.configEntryDisableGrammarStrictMode; if (val = config[CONFIG_GRAMMAR_DISABLE_STRICT_MODE]) { if (val === true) { if (isApplyRuntimeConfigOption(options, null, true, 'encountered runtime setting true for "' + CONFIG_GRAMMAR_DISABLE_STRICT_MODE + '"')) { if (options === true || !options) { options = {}; } options.strict = false; } } } // console.log('DONE parseRuntimeConfigurationForOptions: ', config , '\n -> \n', options); } return options; } /** * HELPER check, if runtime-configuration setting should/can be applied to GrammarOption * * Prints warning, in case setting can/should not be applied. * * @param {GrammarOption} options the GrammarOption, either the root GrammarOptions, or a GrammarOptions.grammar[id] object * @param {string | null} optionId the ID, if options is a GrammarOptions.grammar[id] object * @param {boolean | null} invalidIgnoreValue if NULL, do no check, otherwise the invalid value for GrammarOption.ignore, i.e. in which case the runtime setting can NOT be applied * @param {string} descRuntimeSetting description for the runtime setting, for print the warning-message in case the runtime setting cannot be applied * @return {Boolean} whether or not the runtime setting can/should be applied */ function isApplyRuntimeConfigOption(options, optionId, invalidIgnoreValue, descRuntimeSetting) { let errDetails; if (options === false) { errDetails = ' is set to false'; } else { if (!options) { return true; } else if (invalidIgnoreValue !== null && options.ignore === invalidIgnoreValue) { errDetails = '.ignore is set to ' + (!invalidIgnoreValue); } else if (options.exclude === true) { errDetails = '.exclude is set to true'; } else { return true; } } warn('grammar-utils.parseRuntimeConfigurationForOptions(): ' + descRuntimeSetting + ', but GrammarOptions' + (optionId ? '[' + optionId + ']' : '') + errDetails + ' -> ignoring runtime setting!'); //FIXME proper webpack error/warning return false; } function contains(grammarList, id) { return grammarList.findIndex(function (item) { return item.id === id; }) !== -1; } function toAliasPath(grammar) { return path_1.default.normalize(grammar.file); //DISABLED: do keep file-extension to ensure that module is found regardless of webpack resolve-configuration//.replace(/\.json$/i, ''); } function toAliasId(grammar) { return 'mmirf/grammar/' + grammar.id; //FIXME formalize IDs for loading views in webpack (?) } module.exports = { /** * parse directories for JSON grammars and create/return GrammarEntry list * * @param {GrammarOptions} options the grammar options where * options.path: REQUIRED the directory from which to add the grammars, and has the following structure: * <directory>/<grammar-id-1>/grammar.json * <directory>/<grammar-id-2>/grammar.json * ... * options.grammars: OPTIONAL a map of grammar IDs, i.e. {[grammarID: string]: GrammarOption} with specific options for compiling the corresponding JSON grammar: * options.grammars[id].engine {"jscc" | "jison" | "pegjs"}: OPTIONAL the Grammar engine that will be used to compile the executable grammar. * DEFAULT: "jscc" * options.grammars[id].async {Boolean}: OPTIONAL if <code>true</code>, and the execution environment supports Workers, then the grammar will be loaded * in a Worker on app start-up, i.e. execution will be asynchronously in a worker-thread * options.grammars[id].exclude {Boolean}: OPTIONAL if <code>true</code>, the corresponding grammar will be completely excluded, i.e. no executable grammar will be compiled * from the corresponding JSON grammar * options.grammars[id].ignore {Boolean}: OPTIONAL if <code>true</code>, the grammar will not be loaded * (and registered) when the the app is initialized, i.e. needs to be * "manually" loaded/initialized by app implementation and/or other mechanisms. * If omitted or <code>false</code>, the grammar will be loaded on start-up of the app, * and then will be available e.g. via <code>mmir.semantic.interpret(<input phrase string>, <grammar-id>)</code>. * @param {String} appRootDir the root directory of the app (against which relative paths will be resolved) * @param {Array<GrammarEntry>} [grammarList] OPTIONAL list of GrammarEntry objects, to which the new entries (read from the options.directory) will be added * if omitted, a new list will be created and returned. * GrammarEntry.id {String}: the grammar id (usually the language code, e.g. "en" or "de") * GrammarEntry.file {String}: the path to the JSON grammar (from which the executable grammar will be created) * GrammarEntry.engine {"jscc" | "jison" | "pegjs"}: OPTIONAL the Grammar engine that will be used to compile the executable grammar. * DEFAULT: "jscc" * GrammarEntry.ignore {Boolean}: OPTIONAL if <code>true</code>, the grammar will not be loaded * (and registered) when the the app is initialized, i.e. needs to be * "manually" loaded/initialized by app implementation and/or other mechanisms. * If omitted or <code>false</code>, the grammar will be loaded on start-up of the app, * and then will be available e.g. via <code>mmir.semantic.interpret(<input phrase string>, <grammar-id>)</code>. * GrammarEntry.async {Boolean}: OPTIONAL if <code>true</code>, and the execution environment supports Workers, then the grammar will be loaded * in a Worker on app start-up, i.e. execution will be asynchronously in a worker-thread * GrammarEntry.initPhrase {String}: OPTIONAL an initalization phrase that will be executed, if grammar is set for async-execution * GrammarEntry.asyncCompile {Boolean}: OPTIONAL if <code>true</code>, and the build environment supports Workers, then the grammar will be compiled * in a Worker (during build) * @return {Array<GrammarEntry>} the list of GrammarEntry objects */ jsonGrammarsFromDir: function (options, appRootDir, grammarList) { var dir = options.path; if (!path_1.default.isAbsolute(dir)) { dir = path_1.default.resolve(appRootDir, dir); } var list = grammarList || []; readDir(dir, list, options.grammars); return list; }, /** * add grammars from options.grammar map {[grammarID: string]: GrammarOption}, if the GrammarOption has a <code>file</code> field set. * @param {GrammarOptions} options the grammar options with field options.grammars * @param {String} appRootDir the root directory of the app (against which relative paths will be resolved) * @param {{Array<GrammarEntry>}} [grammarList] OPTIONAL * @return {{Array<GrammarEntry>}} */ jsonGrammarsFromOptions: function (options, appRootDir, grammarList) { var grammars = options.grammars; var list = grammarList || []; addFromOptions(grammars, list, appRootDir); return list; }, /** * parse RuntimeConfiguration for grammar-related settings and "convert" them to the corresponding GrammarOptions * @param {GrammarOptions} options the grammar options * @param {RuntimeConfiguration} config the runtime configuration settings * @return {GrammarOptions} the grammar options with new/modified options from RuntimeConfiguration */ parseRuntimeConfigurationForOptions: parseRuntimeConfigurationForOptions, /** * apply the "global" options from `options` or default values to the entries * from `grammarList` if its corresponding options-field is not explicitly specified. * * @param {GrammarOptions} options the grammar options * @param {{Array<GrammarEntry>}} grammarList * @return {{Array<GrammarEntry>}} */ applyDefaultOptions: function (options, grammarList) { grammarList.forEach(function (g) { [ { name: 'engine', defaultValue: 'jscc' }, { name: 'ignore', defaultValue: false }, { name: 'async', defaultValue: false }, { name: 'initPhrase', defaultValue: void (0) }, { name: 'asyncCompile', defaultValue: void (0) }, { name: 'force', defaultValue: false }, { name: 'strict', defaultValue: true } ].forEach(function (fieldInfo) { option_utils_1.default.applySetting(fieldInfo.name, g, options, fieldInfo.defaultValue); }); }); return grammarList; }, /** * add grammars to (webpack) app build configuration * * @param {Array<GrammarEntry>} grammars list of GrammarEntry objects: * grammar.id {String}: the grammar id (usually the language code, e.g. "en" or "de") * grammar.file {String}: the path to the JSON grammar (from which the executable grammar will be created) * grammar.ignore {Boolean}: OPTIONAL if <code>true</code>, the grammar will not be loaded * (and registered) when the the app is initialized, i.e. needs to be * "manually" loaded/initialized by app implementation and/or other mechanisms. * If omitted or <code>false</code>, the grammar will be loaded on start-up of the app, * and then will be available e.g. via <code>mmir.semantic.interpret(<input phrase string>, <grammar-id>)</code>. * @param {[type]} appConfig the app configuration to which the grammars will be added * @param {[type]} directories the directories.json representation * @param {ResourcesConfig} _resources the resources configuration * @param {[type]} runtimeConfiguration the configuration.json representation */ addGrammarsToAppConfig: function (grammars, appConfig, directories, _resources, runtimeConfiguration) { if (!grammars || grammars.length < 1) { return; } grammars.forEach(function (g) { if (g.ignore) { //add configuration entry to avoid loading of grammar module on app start-up: settings_utils_1.default.setGrammarIgnored(runtimeConfiguration, g.id); } if (g.async) { //add configuration entry initializing grammar for async-execution: var entry = g.initPhrase ? { id: g.id, phrase: g.initPhrase } : g.id; settings_utils_1.default.setGrammarAsyncExec(runtimeConfiguration, entry); //add alias information, but do not require inclusion in "main thread script" // (i.e. inclusion only mandatory in async-exec-Worker script): module_config_init_1.default.registerModuleId(appConfig, toAliasId(g), toAliasPath(g)); //include internal mmir-lib module asyncGrammar for async-grammar-execution: module_config_init_1.default.addIncludeModule(appConfig, 'mmirf/asyncGrammar'); } else { //do add alias information (for require'ing via ID instead of relative/absolute file path), // and register module ID for inclusion in "main thread script" module_config_init_1.default.addIncludeModule(appConfig, toAliasId(g), toAliasPath(g)); } directories_utils_1.default.addGrammar(directories, toAliasId(g)); }); }, toAliasId: toAliasId, toAliasPath: toAliasPath };