UNPKG

mmir-tooling

Version:
354 lines (287 loc) 15.7 kB
import { StateOptions , StateModelBuildEntry , StateModelsBuildOption , StateModelMode , StateModelEntry , StateBuildOptions , BuildAppConfig , DirectoriesInfo , ResourceConfig , RuntimeConfiguration } from '../index.d'; import path from 'path'; import fs from 'fs-extra'; import _ from 'lodash'; import fileUtils from '../utils/filepath-utils'; import appConfigUtils from '../utils/module-config-init'; import directoriesUtil from '../tools/directories-utils'; import optionUtils from '../tools/option-utils'; import logUtils from '../utils/log-utils'; import { WebpackAppConfig } from '../index-webpack.d'; const log = logUtils.log; const warn = logUtils.warn; const DEFAULT_MODE = 'extended'; function readDir(dir: string, list: StateModelBuildEntry[], options: StateModelsBuildOption): void { const files = fs.readdirSync(dir); const dirs = []; //sort file list to prioritize 'dialog.xml' and 'input.xml' (i.e. put at the beginning of the list) const re = /^(dialog|input)\.xml$/i files.sort(function(f1, f2){ if(f1 === f2) return 0; if(re.test(f1)) return -1; if(re.test(f2)) return 1; return f1.localeCompare(f2); }); // log('read dir "'+dir+'" -> ', files);//DEBU files.forEach(function(p){ const absPath = path.join(dir, p); if(fileUtils.isDirectory(absPath)){ dirs.push(absPath); return false; } else if(/\.xml$/i.test(p)) { const normalized = fileUtils.normalizePath(absPath); let defModel: RegExpExecArray, id: string, modId: string; if(/^(dialog|input)(DescriptionSCXML)?\.xml$/i.test(p)){// BACKWARDS COMPATIBILITY: do also accept old/deprecated file names ...DescriptionSCXML.xml // -> state models for input- or dialog-manager defModel = /^(dialog|input)/i.exec(p); id = defModel[1].toLowerCase(); modId = id === 'dialog'? 'mmirf/dialogManager' : 'mmirf/inputManager'; } else { // -> custom state models const opt = options && options[id]; id = opt && opt.id? opt.id : path.basename(p, path.extname(p)); } const opt: StateModelBuildEntry = options && options[id]; if(opt && (opt.exclude || opt.file)){ //-> ignore/exclude this scxml! // (if was not exlcuded, but file was specified -> will be added later in addFromOptions(..)) log('scxml-utils.addFromDirectory(): excluding scxml file for '+id+' model at "'+normalized+'"!');//DEBUG return;//////////////////// EARLY EXIT ////////////////// } if(opt && opt.id && opt.id !== id){ warn('scxml-utils.addFromDirectory(): ignoring option id "'+opt.id+'", using default id "'+id+'" for ', normalized); } if(!modId){ if(opt && opt.moduleId){ modId = opt.moduleId; } else { log('scxml-utils.addFromDirectory(): no moduleId specified for "'+p+'", using ID "'+id+'" as module ID!'); modId = id; } } else if(defModel && opt && opt.moduleId && opt.moduleId !== modId){ warn('scxml-utils.addFromDirectory(): ignoring option moduleId "'+opt.moduleId+'" for state model ID "'+id+'", using default moduleId "'+modId+'" instead!'); } if(contains(list, id, modId)){ warn('scxml-utils.addFromDirectory(): entry for ID '+id+' (module ID '+modId+') already exists in state model-list, ignoring state model in file '+normalized); return;//////////////////// EARLY EXIT ////////////////// } list.push({ id: id, moduleId: modId, file: normalized, mode: opt && opt.mode? opt.mode : DEFAULT_MODE, ignoreErrors: opt && typeof opt.ignoreErrors === 'boolean'? opt.ignoreErrors : void(0), force: opt && typeof opt.force === 'boolean'? opt.force : void(0), strict: opt && typeof opt.strict === 'boolean'? opt.strict : void(0) }); } else { log('scxml-utils.addFromDirectory(): ignoring non-XML file '+absPath);//DEBUG } }); // 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(stateModels: StateModelsBuildOption, list: StateModelBuildEntry[], appRootDir: string): void { let s: StateModelBuildEntry, entry: StateModelBuildEntry, moduleId: string; for(var id in stateModels){ s = stateModels[id]; if(s && s.file && !s.exclude){ entry = _.cloneDeep(s); if(!path.isAbsolute(entry.file)){ entry.file = path.resolve(appRootDir, entry.file); } entry.file = fileUtils.normalizePath(entry.file); if(entry.id && entry.id !== id){ warn('scxml-utils.addFromOptions(): entry from modelOptions 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(!entry.moduleId){ if(entry.id === 'dialog'){ entry.moduleId = 'mmirf/dialogManager'; } else if(entry.id === 'input'){ entry.moduleId = 'mmirf/inputManager'; } else { log('scxml-utils.addFromOptions(): entry from modelOptions for ID "'+id+'" has has no module ID set, using ID "'+id+'" as module ID!'); entry.moduleId = id; } } moduleId = entry.moduleId; if(!contains(list, id, moduleId)){ // log('scxml-utils.addFromOptions(): adding ', entry);//DEBU list.push(entry) } else { warn('scxml-utils.addFromOptions(): entry from modelOptions for ID '+id+' (module ID '+moduleId+') already exists in state model-list, ignoring entry!');//FIXME proper webpack error/warning } } // else {//DEBU // log('scxml-utils.addFromOptions(): entry for '+id+' has no file set -> ignore ', s);//DEBU // } } } /** * add (minimal) default state models for 'dialog' and/or 'input' * * @param kind the mode for the create default models, specified and other than 'minimal', a warning will be printed * @param list INOUT parameter: the list of state model entries, to which the created default models will be added * @param _appRootDir this path to app's root directory */ function addDefaults(kind: StateModelMode | 'minimal' | '', list: StateModelBuildEntry[], _appRootDir: string): void { //TODO support other types/kinds than "minimal" engines if(kind && kind !== 'minimal'){ warn('WARN scxml-utils: only support "minimal" for default input- and dialog-engine!'); } const inputEngine: StateModelBuildEntry = { id: 'input', moduleId: 'mmirf/inputManager', mode: 'extended', file: fileUtils.normalizePath(path.resolve(__dirname, '..', 'defaultValues/inputEngine.scxml')) }; const dialogEngine: StateModelBuildEntry = { id: 'dialog', moduleId: 'mmirf/dialogManager', mode: 'extended', file: fileUtils.normalizePath(path.resolve(__dirname, '..', 'defaultValues/dialogEngine.scxml')) }; list.push(inputEngine, dialogEngine); } function contains(list: StateModelBuildEntry[], id: string, moduleId: string){ return list.findIndex(function(item){ return item.id === id || item.moduleId === moduleId; }) !== -1; } function toAliasPath(stateModel: StateModelEntry): string { return path.normalize(stateModel.file); } function toAliasId(stateModel: StateModelBuildEntry): string { return 'mmirf/state/'+stateModel.id; } export = { /** * [description] * @param {ScxmlOptions} options the SCXML model options where * options.path: REQUIRED the directory from which to add the scxml models, and has the following structure: * <directory>/../dialog.xml * <directory>/../input.xml * ... * options.models: OPTIONAL a map of SCXML model IDs, i.e. {[scxmlID: string]: ScxmlOption} with specific options for compiling the corresponding SCXML model: * options.models[id].exclude {Boolean}: OPTIONAL if <code>true</code>, the corresponding SCXML model will be completely excluded, i.e. no executable state model will be compiled * from the corresponding SCXML state model definition * options.models[id].mode {"extended" | "simple"}: OPTIONAL run SCXML modle in "simple" or "extended" mode, * DEFAULT: "extended" * @param {String} appRootDir the root directory of the app (against which relative paths will be resolved) * @param {Array<ScxmlEntry>} [stateModels] OPTIONAL list of ScxmlEntry objects, to which the new entries (read from the options.directory) will be added * if omitted, a new list will be created and returned. * ScxmlEntry.id {String}: the SCXML engine ID (one of "input" or "dialog") * ScxmlEntry.file {String}: the path to the SCXML file (from which the executable state model will be created) * ScxmlEntry.mode {"extended" | "simple"}: run SCXML modle in "simple" or "extended" mode, DEFAULT: "extended" * @return {Array<ScxmlEntry>} the list of ScxmlEntry objects */ scxmlFromDir: function(options: StateOptions, appRootDir: string, stateModels: StateModelEntry[]): StateModelBuildEntry[] { var dir = options.path; if(!path.isAbsolute(dir)){ dir = path.resolve(appRootDir, dir); } var list = stateModels || []; readDir(dir, list, options.models); return list; }, scxmlFromOptions: function(options: StateOptions, appRootDir: string, stateModels: StateModelEntry[]): StateModelBuildEntry[] { var models = options.models; var list = stateModels || []; addFromOptions(models, list, appRootDir); return list; }, scxmlDefaults: function(options: StateBuildOptions, appRootDir: string, stateModels: StateModelEntry[]): StateModelBuildEntry[] { const kind = options && options.defaultType; var list = stateModels || []; addDefaults(kind, list, appRootDir); return list; }, /** * apply the "global" options from `options` or default values to the entries * from `stateModels` if its corresponding options-field is not explicitly specified. * * @param {ScxmlOptions} options the state-models options * @param {{Array<ScxmlEntry>}} stateModels * @return {{Array<ScxmlEntry>}} */ applyDefaultOptions: function(options: StateOptions, stateModels: StateModelEntry[]): StateModelBuildEntry[] { stateModels.forEach(function(st){ [ {name: 'mode', defaultValue: DEFAULT_MODE}, {name: 'ignoreErrors', defaultValue: false}, {name: 'force', defaultValue: false}, {name: 'strict', defaultValue: true} ].forEach(function(fieldInfo){ optionUtils.applySetting(fieldInfo.name, st, options, fieldInfo.defaultValue); }); }); return stateModels; }, /** * add SCXML models to (webpack) app build configuration * * @param {Array<ScxmlEntry>} stateModels list of ScxmlEntry objects: * stateModel.id {String}: the SCXML id ("dialog" | "input") * stateModel.file {String}: the path to the SCXML file (from which the executable SCXML model will be created) * @param {[type]} appConfig the app configuration to which the SCXML models will be added * @param {[type]} directories the directories.json representation * @param {ResourcesConfig} resources the resources configuration * @param {[type]} _runtimeConfiguration the configuration.json representation */ addStatesToAppConfig: function(stateModels: StateModelBuildEntry[], appConfig: WebpackAppConfig, directories: DirectoriesInfo, resources: ResourceConfig, _runtimeConfiguration: RuntimeConfiguration): void { if(!stateModels || stateModels.length < 1){ return; } if(!appConfig.paths){ appConfig.paths = {}; } //use scion runtime for compiled SCXML models instead of scion compiler/runtime resources.paths['mmirf/scion'] = resources.paths['mmirf/scionRuntime']; // log('scxml-utils.addStateModelsToAppConfig(): set mmirf/scion module implementation to ', appConfig.paths['mmirf/scion']);//DEBU if(!appConfig.config){ appConfig.config = {}; } var stateIds = new Set(); stateModels.forEach(function(s){ if(stateIds.has(s.id)){ warn('scxml-utils: there already is a state model for "'+s.id+'", omitting state model from: "'+s.file+'"'); return; } stateIds.add(s.id); var aliasId = toAliasId(s); appConfigUtils.addIncludeModule(appConfig, aliasId, toAliasPath(s)); directoriesUtil.addStateModel(directories, aliasId); if((appConfig as BuildAppConfig).includeStateModelXmls){ directoriesUtil.addStateModelXml(directories, aliasId); } var configId = s.moduleId; if(!appConfig.config[configId]){ appConfig.config[configId] = {}; } if(appConfig.config[configId].modelUri){ if(appConfig.config[configId].modelUri !== aliasId){ warn('scxml-utils: state model for "'+s.id+'" is already set to "'+appConfig.config[configId].modelUri+'", omitting configuration to load from module ID "'+aliasId+'"'); } // else {//DEBU // log(' scxml-utils: SCXML model for "'+s.id+'" is already set to "'+aliasId+'", do nothing ...');//DEBU // } } else { // log(' scxml-utils: setting SCXML model for "'+s.id+'" to "'+aliasId+'", do nothing ...');//DEBU appConfig.config[configId].modelUri = aliasId; } if(!appConfig.config[configId].mode){ // log(' scxml-utils: setting mode for SCXML model "'+s.id+'" to default mode: ', JSON.stringify(s.mode || DEFAULT_MODE));//DEBU appConfig.config[configId].mode = s.mode || DEFAULT_MODE; } else if(s.mode) { warn('scxml-utils: SCXML model mode for "'+s.id+'" is already set to "'+appConfig.config[configId].mode+'", omitting mode-setting ', JSON.stringify(s.mode)); } }); } };