UNPKG

adcutil

Version:

Utilities tools for Askia Design Control

529 lines (469 loc) 18.5 kB
// Filesystem var fs = require('fs'), // Path helper pathHelper = require('path'), // Util util = require('util'), // cli-color clc = require('cli-color'), // Zip lib Zip = require('JSZip'); exports = module.exports; // Application name exports.APP_NAME = 'ADCUtil'; // Preferences exports.PREFERENCES_FILE_NAME = 'preferences.json'; // Common // File name of the config.xml exports.CONFIG_FILE_NAME = 'config.xml'; // File name of the readme.md exports.README_FILE_NAME = 'readme.md'; // Path of the unit tests directory exports.UNIT_TEST_DIR_PATH = "tests/units"; // Path of the fixtures directory exports.FIXTIRES_DIR_PATH = "tests/fixtures"; // Validator // Path to the XML Lint program exports.XML_LINT_PATH = '/lib/libxml/xmllint.exe'; // Path to the XSD schema file to validate the ADC config.xml exports.SCHEMA_PATH = '/schema/'; // Name of the schema to validate the config file exports.SCHEMA_CONFIG = 'config.xsd'; // Name of the schema to validate the unit test file exports.SCHEMA_TEST_UNIT = 'UnitTests.xsd'; // Path to the directory of the ADCUnit program exports.ADC_UNIT_DIR_PATH = '/lib/adxshell_' + ((process.arch === 'x64') ? 'x64' : 'x86') + '/'; // ADCUnit.exe exports.ADC_UNIT_PROCESS_NAME = 'ADXShell.exe'; // Name of the `resources` directory exports.RESOURCES_DIR_NAME = "resources"; // Name of the directory `dynamic` exports.DYNAMIC_DIR_NAME = "dynamic"; // Name of the directory `static` exports.STATIC_DIR_NAME = "static"; // Name of the directory `share` exports.SHARE_DIR_NAME = "share"; // File name which contains the list of files to ignore exports.ADC_IGNORE_FILE_NAME = "ADCIgnore"; // Ignore file list exports.adcIgnoreFiles = ""; // Rules to ignore files exports.adcIgnoreFilesRules = undefined; // Generator // Path of the templates directory exports.TEMPLATES_PATH = '/templates/adc/'; exports.DEFAULT_TEMPLATE_NAME = 'blank'; // Builder // Path to the bin directory of an ADC exports.ADC_BIN_PATH = '/bin/'; /** * Error messages */ exports.messages = { error : { // Common noSuchFileOrDirectory : "No such file or directory `%s`", // Validator missingArgPath : "missing required argument `path`", noConfigFile : "cannot find the `Config.xml` file in the directory", fileExtensionForbidden : "File extension `%s` is forbidden", duplicateConstraints : "Duplicate constraints on `%s`", invalidConstraintAttribute : "The constraint on `%s` doesn't accept the `%s` attribute", noRuleOnConstraint : "The constraint on `%s` require at least one rule", requireConstraintOn : "A constraint on `%s` is require", tooManyEmptyCondition : "Too many outputs with empty condition: %s", noResourcesDirectory : "Cannot find the `resources` directory", dynamicFileRequire : "At least one dynamic file is require for the `%s` output, or set the attribute `defaultGeneration=true` in the output node", cannotFindDirectory : "Cannot find the `%s` directory", cannotFindFileInDirectory : "Output: `%s`. Cannot find the file `%s` in the `%s` directory", typeCouldNotBeDynamic : "Output: `%s`. Type `%s` could not be dynamic (`%s`)", attributeNotOverridable : "Output: `%s`. Attribute `%s` of the `%s` content could not be override", yieldRequireForBinary : "Output: `%s`. `yield` node require for the binary content `%s` or set his position to `none`", duplicateAttributeNode : "Output: `%s`. Duplicate `%s` attribute node in content `%s`", missingInfoNode : "The config.xml must contains the `info` node as a child of the xml root element", missingOrEmptyNameNode : "The node `name` in `info` doesn't exist or is empty", // Generator missingNameArgument : "The `name` parameter is require", missingOutputArgument : "The --output path is require", directoryAlreadyExist : "The directory `%s` already exists.", incorrectADCName : "Incorrect ADC name. The name of the ADC should only contains letters, digits, spaces, `_,-,.` characters", cannotFoundTemplate : "Cannot found the `%s` template", // Builder validationFailed : "Validation failed", buildFailed : "Build failed with errors.", // Show noOutputDefinedForShow : "Please specify the name of the output you want to show, using the option -o or --output.", noFixtureDefinedForShow : "Please specify the name of the fixture you want to use, using the option -f or --fixture.", // Configurator invalidPathArg : "Invalid `path` argument" }, warning : { // Validator untrustExtension : "Untrust extension of the file `%s`", duplicateOutputCondition : "Duplicate conditions in outputs `%s` and `%s`", attributeNodeWillBeIgnored : "Output: `%s`. `attribute` nodes will be ignored for the `%s` content (`%s`)", attributeNodeAndDynamicContent : "Output: `%s`. `attribute` nodes will be ignored for dynamic content (`%s`)", attributeNodeAndYieldNode : "Output: `%s`. `attribute` nodes will be ignored when using `yield` (`%s`)", javascriptUseWithoutBrowserCheck : "Output: `%s`. It's recommended to test the `Browser.Support(\"Javascript\") in the condition node, before to use `javascript` content.", flashUseWithoutBrowserCheck : "Output: `%s`. It's recommended to test the `Browser.Support(\"Flash\") in the condition node, before to use `flash` content.", noHTMLFallBack : "It's recommended to have at least one fallback with HTML only", noProperties : "It's recommended to define at least one properties" }, success : { // Validator pathValidate : "ADC path validation done", directoryStructureValidate : "ADC directory structure validation done", fileExtensionValidate : "File extension validation done", xsdValidate : "XSD validation done", xmlInitialize : "Config.xml parsing done", xmlInfoValidate : "Config#info validation done", xmlInfoConstraintsValidate : "Config#info#constraints validation done", xmlOutputsValidate : "Config#outputs validation done", xmlPropertiesValidate : "Config#properties validation done", adcUnitSucceed : "ADC Unit tests succeeded", // Generator adcStructureGenerated : "Project structure\r\n\r\n%s\r\n\r\nADC `%s` was successfully generated in `%s`\r\n", // Builder buildSucceed : "ADC file was successfully generated.\r\nOutput: file:///%s", buildSucceedWithWarning: "ADC file was successfully generated with %d warnings.\r\nOutput: file:///%s" }, message : { // Validator runningADCUnit : 'Running ADC Unit tests', runningAutoUnit : 'Running the auto-generated ADC Unit tests', validationFinishedIn : "\r\nValidations finished in %d milliseconds", validationReport : "\r\n%d/%d validations runs, %d success, %d warnings, %d failures, %d skipped", // Preferences noPreferences : 'No preferences defined' } }; /** * Write an error output in the console * @param {String} text Text to write in the console */ exports.writeError = function writeError(text) { console.error(clc.red.bold("[ERROR]: " + text)); }; /** * Write a warning output in the console * @param {String} text Text to write in the console */ exports.writeWarning = function writeWarning(text) { console.log(clc.yellowBright("[WARNING]: " + util.format.apply(null, arguments))); }; /** * Write a success output in the console * @param {String} text Text to write in the console */ exports.writeSuccess = function writeSuccess(text) { console.log(clc.greenBright("[SUCCESS]: " + util.format.apply(null, arguments))); }; /** * Write an arbitrary message in the console without specific prefix * @param {String} text Text to write in the console */ exports.writeMessage = function writeMessage(text) { console.log(util.format.apply(null, arguments)); }; /** * Get a new zip object */ exports.getNewZip = function getNewZip() { return new Zip(); }; /** * Format the date for xml. * If no date in arg, use the current date * @param {Date} [date] Date to format */ exports.formatXmlDate = function formatXmlDate(date) { (date = date || new Date()); return padStr(date.getFullYear()) + '-' + padStr(1 + date.getMonth()) + '-' + padStr(date.getDate()); }; /** * Pad the number with one 0 when < 10 * @param {Number} i Number to pad * @return {String} */ function padStr(i) { return (i < 10) ? "0" + i : "" + i; } /** * Test if a directory exists * @param {String} path Path of the directory * @param {Function} callback Callback function with err, exists arguments */ exports.dirExists = function dirExists (path, callback) { fs.stat(path, function(err) { // errno 2 -- ENOENT, No such file or directory if (err && err.errno === 2) { callback(null, false); } else { callback(err, err ? false : true); } }); }; /** * Indicates if the file should be ignore * * @param {String} filename Name of the file * @return {Boolean} True when should be ignored */ exports.isIgnoreFile = function isIgnoreFile(filename) { if (!exports.adcIgnoreFiles) { exports.adcIgnoreFiles = fs.readFileSync(pathHelper.resolve(__dirname, "../" + exports.ADC_IGNORE_FILE_NAME), 'utf8'); } if (!exports.adcIgnoreFilesRules) { var lines = exports.adcIgnoreFiles.split('\n'), rgExp = []; lines.forEach(function (line) { line = line.replace(/(#.*)/g, ''); line = line.replace(/\s/g, ''); line = line.replace(/\r/g, ''); if (!line) return; line = line.replace(/\./g, "\\."); line = line.replace(/-/g, "\\-"); line = line.replace(/\*/g, ".*"); rgExp.push(line); }); exports.adcIgnoreFilesRules = new RegExp("(" + rgExp.join("|") + ")$", "gi"); } return exports.adcIgnoreFilesRules.test(filename); }; /** * Return the entire directory structure * * [ * { * name : 'folder', * sub : [ * { * name : 'sub folder', * sub : [] * }, * { * name : 'sub folder 2' * sub : [ * 'file', * 'file2' * ] * } * ] * } * ] * * @param {String} path Path of the root directory * @param {Function} callback Callback function */ exports.getDirStructure = function getDirStructure(path, callback) { fs.stat(path, function verifyRoot(err, stat) { if (err) { return callback(err); } if (!stat.isDirectory()) { return callback(new Error("path: " + path + " is not a directory")); } function record(root, file, struct, cb) { var fullPath = root + '/' + file, stat; try { stat = fs.statSync(fullPath); } catch(err) { if (cb) cb(); return; } if (!stat.isDirectory()) { struct.push(file); } else { struct.push({ name : file, sub : [] }); // Recurse var files = fs.readdirSync(fullPath), lastStruct = struct[struct.length -1].sub; if (files && Array.isArray(files)) { files.forEach(function (f) { record(fullPath, f, lastStruct); }); } } if (cb) cb(); } // Read through all the files in this directory var structure = [], files = fs.readdirSync(path), treat = 0, rootLength = files && files.length; if (!files || !Array.isArray(files) || !rootLength) { callback(null, structure); } function incrementTreat() { treat++; if (treat === rootLength) { callback(null, structure); } } files.forEach(function (file) { record(path, file, structure, incrementTreat); }); }); }; /** * Returns the list of templates directory * * It searches in the user data folder, the program data folder and the installation program folder * * @param {Function} callback Callback * @param {Error} callback.err Error * @param {Object[]} callback.dirs List of template * @param {String} callback.dirs[].name Name of the template * @param {String} callback.dirs[].path Path of the template directory */ exports.getTemplateList = function getTemplateList(callback) { // 1. Get the templates from the application path // 2. Get the templates from the PROGRAM_DATA path // 3. Get the templates from the USER_DATA path var result = [], map = {}; function addFiles(parent, files) { for (var i = 0, l = files.length; i < l; i++) { var fullPath = pathHelper.join(parent, files[i]), stat = fs.statSync(fullPath), name, lowerName, dir; if (stat.isDirectory()) { name = pathHelper.basename(files[i]); lowerName = name.toLowerCase(); dir = { name : name, path : fullPath }; if (lowerName in map) { result[map[lowerName]] = dir; } else { map[lowerName] = result.length; result.push(dir); } } } } // 1. var sysTemplatePath = pathHelper.resolve(__dirname, '../../'); sysTemplatePath = pathHelper.join(sysTemplatePath, exports.TEMPLATES_PATH); fs.readdir(sysTemplatePath, function (err, files) { if (!err) { addFiles(sysTemplatePath, files); } // 2. var programDataPath = process.env.ALLUSERSPROFILE || process.env.ProgramData || ''; programDataPath = pathHelper.join(programDataPath, exports.APP_NAME , exports.TEMPLATES_PATH); fs.readdir(programDataPath, function (err, files) { if (!err) { addFiles(programDataPath, files); } // 3. var userDataPath = process.env.APPDATA || ''; userDataPath = pathHelper.join(userDataPath, exports.APP_NAME , exports.TEMPLATES_PATH); fs.readdir(userDataPath, function (err, files) { if (!err) { addFiles(userDataPath, files); } callback(null, result); }); }); }); }; /** * Returns the path of the template according to his name * * It searches in the user data folder, the program data folder and the installation program folder * * @param {Function} callback Callback * @param {Error} callback.err Error * @param {String} callback.path Path of the template */ exports.getTemplatePath = function getTemplatePath(name, callback) { // 1. Search in the USER_DATA path // 2. Search in the PROGRAM_DATA path // 3. Search in the installation path // 1. var userDataPath = process.env.APPDATA || ''; userDataPath = pathHelper.join(userDataPath, exports.APP_NAME , exports.TEMPLATES_PATH, name); exports.dirExists(userDataPath, function (err, exist) { if (exist) { callback(null, userDataPath); return; } // 2. var programDataPath = process.env.ALLUSERSPROFILE || process.env.ProgramData || ''; programDataPath = pathHelper.join(programDataPath, exports.APP_NAME , exports.TEMPLATES_PATH, name); exports.dirExists(programDataPath, function (err, exist) { if (exist) { callback(null, programDataPath); return; } // 3. var sysTemplatePath = pathHelper.resolve(__dirname, '../../'); sysTemplatePath = pathHelper.join(sysTemplatePath, exports.TEMPLATES_PATH, name); exports.dirExists(sysTemplatePath, function (err, exist) { if (exist) { callback(null, sysTemplatePath); return; } callback(new Error(util.format(exports.messages.error.cannotFoundTemplate, name)), null); }); }); }); }; /** * Create a new sequence of function to call * @param {Array} sequence Array of function to call one by one * @param {Function} callback Callback function to execute at the end of the sequence * @param {Object} [scope] Scope of function to execute (this) * @constructor */ function Sequence(sequence, callback, scope) { this.current = -1; this.sequence = sequence; this.callback = callback; this.scope = scope; } /** * Return the index of the next function to execute * @return {Number} */ Sequence.prototype.nextIndex = function nextIndex() { if (!this.sequence || !Array.isArray(this.sequence) || !this.sequence.length) { return -1; } var i = (this.current + 1), l = this.sequence.length; for (;i < l; i++) { if (typeof this.sequence[i] === 'function') { return i; } } return -1; }; /** * Indicates if there is another function to call in the sequence stack * @returns {boolean} */ Sequence.prototype.hasNext = function hasNext() { return (this.nextIndex() !== -1); }; /** * Execute the next function * @param {Error} err Error */ Sequence.prototype.resume = function resume(err) { var index = this.nextIndex(); if (index === -1 || err) { if (typeof this.callback === 'function') { this.callback.call(this.scope, err); } return; } this.current = index; this.sequence[this.current].call(this.scope); }; exports.Sequence = Sequence;