diff --git a/.babelrc b/.babelrc
index 92a857cf..094c0592 100644
--- a/.babelrc
+++ b/.babelrc
@@ -5,7 +5,7 @@
"chrome": 40,
"firefox": 35,
"edge": 14,
- "node": "6.5",
+ "node": "6.5"
},
"modules": false,
"useBuiltIns": true
diff --git a/.eslintignore b/.eslintignore
index 1034aa8a..83279ae8 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,2 +1 @@
-src/core/lib/**
-src/core/config/MetaConfig.js
\ No newline at end of file
+src/core/vendor/**
\ No newline at end of file
diff --git a/.eslintrc.json b/.eslintrc.json
index d63e35e8..5d0a6331 100755
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -89,7 +89,6 @@
"globals": {
"$": false,
"jQuery": false,
- "moment": false,
"log": false,
"COMPILE_TIME": false,
diff --git a/.gitignore b/.gitignore
index 79c28325..31b15bd9 100755
--- a/.gitignore
+++ b/.gitignore
@@ -5,5 +5,4 @@ build
docs/*
!docs/*.conf.json
!docs/*.ico
-.vscode
-src/core/config/MetaConfig.js
\ No newline at end of file
+.vscode
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 0cb60d89..805f6b45 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,6 @@
language: node_js
node_js:
- - "8.4"
+ - node
install: npm install
before_script:
- npm install -g grunt
diff --git a/Gruntfile.js b/Gruntfile.js
index 7a9890f1..4b0eef0e 100755
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -4,7 +4,8 @@ const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const NodeExternals = require("webpack-node-externals");
const Inliner = require("web-resource-inliner");
-const fs = require("fs");
+const glob = require("glob");
+const path = require("path");
/**
* Grunt configuration for building the app in various formats.
@@ -21,15 +22,15 @@ module.exports = function (grunt) {
// Tasks
grunt.registerTask("dev",
"A persistent task which creates a development build whenever source files are modified.",
- ["clean:dev", "concurrent:dev"]);
+ ["clean:dev", "clean:config", "webpack-dev-server:start"]);
grunt.registerTask("node",
"Compiles CyberChef into a single NodeJS module.",
- ["clean:node", "webpack:metaConf", "webpack:node", "chmod:build"]);
+ ["clean:node", "clean:config", "webpack:node", "chmod:build"]);
grunt.registerTask("test",
"A task which runs all the tests in test/tests.",
- ["clean:test", "webpack:metaConf", "webpack:tests", "execute:test"]);
+ ["exec:tests"]);
grunt.registerTask("docs",
"Compiles documentation in the /docs directory.",
@@ -37,7 +38,7 @@ module.exports = function (grunt) {
grunt.registerTask("prod",
"Creates a production-ready build. Use the --msg flag to add a compile message.",
- ["eslint", "clean:prod", "webpack:metaConf", "webpack:web", "inline", "chmod"]);
+ ["eslint", "clean:prod", "clean:config", "webpack:web", "inline", "chmod"]);
grunt.registerTask("default",
"Lints the code base",
@@ -62,9 +63,7 @@ module.exports = function (grunt) {
grunt.loadNpmTasks("grunt-contrib-copy");
grunt.loadNpmTasks("grunt-chmod");
grunt.loadNpmTasks("grunt-exec");
- grunt.loadNpmTasks("grunt-execute");
grunt.loadNpmTasks("grunt-accessibility");
- grunt.loadNpmTasks("grunt-concurrent");
// Project configuration
@@ -118,12 +117,12 @@ module.exports = function (grunt) {
* Generates an entry list for all the modules.
*/
function listEntryModules() {
- const path = "./src/core/config/modules/";
let entryModules = {};
- fs.readdirSync(path).forEach(file => {
- if (file !== "Default.js" && file !== "OpModules.js")
- entryModules[file.split(".js")[0]] = path + file;
+ glob.sync("./src/core/config/modules/*.mjs").forEach(file => {
+ const basename = path.basename(file);
+ if (basename !== "Default.mjs" && basename !== "OpModules.mjs")
+ entryModules[basename.split(".mjs")[0]] = path.resolve(file);
});
return entryModules;
@@ -132,9 +131,9 @@ module.exports = function (grunt) {
grunt.initConfig({
clean: {
dev: ["build/dev/*"],
- prod: ["build/prod/*", "src/core/config/MetaConfig.js"],
- test: ["build/test/*", "src/core/config/MetaConfig.js"],
- node: ["build/node/*", "src/core/config/MetaConfig.js"],
+ prod: ["build/prod/*"],
+ node: ["build/node/*"],
+ config: ["src/core/config/OperationConfig.json", "src/core/config/modules/*"],
docs: ["docs/*", "!docs/*.conf.json", "!docs/*.ico", "!docs/*.png"],
inlineScripts: ["build/prod/scripts.js"],
},
@@ -143,10 +142,10 @@ module.exports = function (grunt) {
configFile: "./.eslintrc.json"
},
configs: ["Gruntfile.js"],
- core: ["src/core/**/*.js", "!src/core/lib/**/*", "!src/core/config/MetaConfig.js"],
- web: ["src/web/**/*.js"],
- node: ["src/node/**/*.js"],
- tests: ["test/**/*.js"],
+ core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*"],
+ web: ["src/web/**/*.{js,mjs}"],
+ node: ["src/node/**/*.{js,mjs}"],
+ tests: ["test/**/*.{js,mjs}"],
},
jsdoc: {
options: {
@@ -159,17 +158,11 @@ module.exports = function (grunt) {
all: {
src: [
"src/**/*.js",
- "!src/core/lib/**/*",
- "!src/core/config/MetaConfig.js"
+ "src/**/*.mjs",
+ "!src/core/vendor/**/*"
],
}
},
- concurrent: {
- options: {
- logConcurrentOutput: true
- },
- dev: ["webpack:metaConfDev", "webpack-dev-server:start"]
- },
accessibility: {
options: {
accessibilityLevel: "WCAG2A",
@@ -184,39 +177,6 @@ module.exports = function (grunt) {
},
webpack: {
options: webpackConfig,
- metaConf: {
- mode: "production",
- target: "node",
- entry: [
- "babel-polyfill",
- "./src/core/config/OperationConfig.js"
- ],
- output: {
- filename: "MetaConfig.js",
- path: __dirname + "/src/core/config/",
- library: "MetaConfig",
- libraryTarget: "commonjs2",
- libraryExport: "default"
- },
- externals: [NodeExternals()],
- },
- metaConfDev: {
- mode: "development",
- target: "node",
- entry: [
- "babel-polyfill",
- "./src/core/config/OperationConfig.js"
- ],
- output: {
- filename: "MetaConfig.js",
- path: __dirname + "/src/core/config/",
- library: "MetaConfig",
- libraryTarget: "commonjs2",
- libraryExport: "default"
- },
- externals: [NodeExternals()],
- watch: true
- },
web: {
mode: "production",
target: "web",
@@ -229,7 +189,7 @@ module.exports = function (grunt) {
},
resolve: {
alias: {
- "./config/modules/OpModules.js": "./config/modules/Default.js"
+ "./config/modules/OpModules": "./config/modules/Default"
}
},
plugins: [
@@ -279,7 +239,7 @@ module.exports = function (grunt) {
tests: {
mode: "development",
target: "node",
- entry: "./test/index.js",
+ entry: "./test/index.mjs",
externals: [NodeExternals()],
output: {
filename: "index.js",
@@ -292,7 +252,7 @@ module.exports = function (grunt) {
node: {
mode: "production",
target: "node",
- entry: "./src/node/index.js",
+ entry: "./src/node/index.mjs",
externals: [NodeExternals()],
output: {
filename: "CyberChef.js",
@@ -330,7 +290,7 @@ module.exports = function (grunt) {
}, moduleEntryPoints),
resolve: {
alias: {
- "./config/modules/OpModules.js": "./config/modules/Default.js"
+ "./config/modules/OpModules": "./config/modules/Default"
}
},
plugins: [
@@ -401,10 +361,10 @@ module.exports = function (grunt) {
},
sitemap: {
command: "node build/prod/sitemap.js > build/prod/sitemap.xml"
+ },
+ tests: {
+ command: "node --experimental-modules test/index.mjs"
}
},
- execute: {
- test: "build/test/index.js"
- },
});
};
diff --git a/package-lock.json b/package-lock.json
index 8a220bc7..0354d97d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5300,26 +5300,6 @@
"shelljs": "0.5.3"
}
},
- "grunt-concurrent": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/grunt-concurrent/-/grunt-concurrent-2.3.1.tgz",
- "integrity": "sha1-Hj2zjM71o9oRleYdYx/n4yE0TSM=",
- "dev": true,
- "requires": {
- "arrify": "1.0.1",
- "async": "1.5.2",
- "indent-string": "2.1.0",
- "pad-stream": "1.2.0"
- },
- "dependencies": {
- "async": {
- "version": "1.5.2",
- "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
- "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
- "dev": true
- }
- }
- },
"grunt-contrib-clean": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-1.1.0.tgz",
@@ -5460,12 +5440,6 @@
"integrity": "sha512-cgAlreXf3muSYS5LzW0Cc4xHK03BjFOYk0MqCQ/MZ3k1Xz2GU7D+IAJg4UKicxpO+XdONJdx/NJ6kpy2wI+uHg==",
"dev": true
},
- "grunt-execute": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/grunt-execute/-/grunt-execute-0.2.2.tgz",
- "integrity": "sha1-TpRf5XlZzA3neZCDtrQq7ZYWNQo=",
- "dev": true
- },
"grunt-jsdoc": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/grunt-jsdoc/-/grunt-jsdoc-2.2.1.tgz",
@@ -8419,19 +8393,6 @@
"integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==",
"dev": true
},
- "pad-stream": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/pad-stream/-/pad-stream-1.2.0.tgz",
- "integrity": "sha1-Yx3Mn3mBC3BZZeid7eps/w/B38k=",
- "dev": true,
- "requires": {
- "meow": "3.7.0",
- "pumpify": "1.3.5",
- "repeating": "2.0.1",
- "split2": "1.1.1",
- "through2": "2.0.3"
- }
- },
"pako": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
@@ -11046,15 +11007,6 @@
"resolved": "https://registry.npmjs.org/split.js/-/split.js-1.3.5.tgz",
"integrity": "sha1-YuLOZtLPkcx3SqXwdJ/yUTgDn1A="
},
- "split2": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/split2/-/split2-1.1.1.tgz",
- "integrity": "sha1-Fi2bGIZfAqsvKtlYVSLbm1TEgfk=",
- "dev": true,
- "requires": {
- "through2": "2.0.3"
- }
- },
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -12198,15 +12150,6 @@
"integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==",
"dev": true
},
- "val-loader": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/val-loader/-/val-loader-1.1.0.tgz",
- "integrity": "sha512-8m62XF42FcfrBBl02rtDY9hQhDcDczrEcr60/aSMxlzJiXAcbAimRPvsDoDa5QcGAusOgOmVTpFtK5EbfZdDwA==",
- "dev": true,
- "requires": {
- "loader-utils": "1.1.0"
- }
- },
"valid-data-url": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-0.1.4.tgz",
@@ -12709,6 +12652,12 @@
"integrity": "sha1-Iyxi7GCSsQBjWj0p2DwXRxKN+b0=",
"dev": true
},
+ "webpack-shell-plugin": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/webpack-shell-plugin/-/webpack-shell-plugin-0.5.0.tgz",
+ "integrity": "sha1-Kbih2A3erg3bEOcpZn9yhlPCx0I=",
+ "dev": true
+ },
"webpack-sources": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.0.1.tgz",
diff --git a/package.json b/package.json
index 6d9e75b2..119d915b 100644
--- a/package.json
+++ b/package.json
@@ -41,12 +41,10 @@
"grunt": ">=1.0.2",
"grunt-accessibility": "~6.0.0",
"grunt-chmod": "~1.1.1",
- "grunt-concurrent": "^2.3.1",
"grunt-contrib-clean": "~1.1.0",
"grunt-contrib-copy": "~1.0.0",
"grunt-eslint": "^20.1.0",
"grunt-exec": "~3.0.0",
- "grunt-execute": "^0.2.2",
"grunt-jsdoc": "^2.2.1",
"grunt-webpack": "^3.0.2",
"html-webpack-plugin": "^3.0.4",
@@ -61,11 +59,11 @@
"sitemap": "^1.13.0",
"style-loader": "^0.20.2",
"url-loader": "^0.6.2",
- "val-loader": "^1.1.0",
"web-resource-inliner": "^4.2.1",
"webpack": "^4.0.1",
"webpack-dev-server": "^3.1.0",
"webpack-node-externals": "^1.6.0",
+ "webpack-shell-plugin": "^0.5.0",
"worker-loader": "^1.1.1"
},
"dependencies": {
diff --git a/src/core/Chef.js b/src/core/Chef.js
deleted file mode 100755
index aba3f4f7..00000000
--- a/src/core/Chef.js
+++ /dev/null
@@ -1,165 +0,0 @@
-import Dish from "./Dish.js";
-import Recipe from "./Recipe.js";
-
-
-/**
- * The main controller for CyberChef.
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2016
- * @license Apache-2.0
- *
- * @class
- */
-const Chef = function() {
- this.dish = new Dish();
-};
-
-
-/**
- * Runs the recipe over the input.
- *
- * @param {string|ArrayBuffer} input - The input data as a string or ArrayBuffer
- * @param {Object[]} recipeConfig - The recipe configuration object
- * @param {Object} options - The options object storing various user choices
- * @param {boolean} options.attempHighlight - Whether or not to attempt highlighting
- * @param {number} progress - The position in the recipe to start from
- * @param {number} [step] - Whether to only execute one operation in the recipe
- *
- * @returns {Object} response
- * @returns {string} response.result - The output of the recipe
- * @returns {string} response.type - The data type of the result
- * @returns {number} response.progress - The position that we have got to in the recipe
- * @returns {number} response.duration - The number of ms it took to execute the recipe
- * @returns {number} response.error - The error object thrown by a failed operation (false if no error)
-*/
-Chef.prototype.bake = async function(input, recipeConfig, options, progress, step) {
- log.debug("Chef baking");
- const startTime = new Date().getTime(),
- recipe = new Recipe(recipeConfig),
- containsFc = recipe.containsFlowControl(),
- notUTF8 = options && options.hasOwnProperty("treatAsUtf8") && !options.treatAsUtf8;
- let error = false;
-
- if (containsFc && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false);
-
- // Clean up progress
- if (progress >= recipeConfig.length) {
- progress = 0;
- }
-
- if (step) {
- // Unset breakpoint on this step
- recipe.setBreakpoint(progress, false);
- // Set breakpoint on next step
- recipe.setBreakpoint(progress + 1, true);
- }
-
- // If stepping with flow control, we have to start from the beginning
- // but still want to skip all previous breakpoints
- if (progress > 0 && containsFc) {
- recipe.removeBreaksUpTo(progress);
- progress = 0;
- }
-
- // If starting from scratch, load data
- if (progress === 0) {
- const type = input instanceof ArrayBuffer ? Dish.ARRAY_BUFFER : Dish.STRING;
- this.dish.set(input, type);
- }
-
- try {
- progress = await recipe.execute(this.dish, progress);
- } catch (err) {
- log.error(err);
- error = {
- displayStr: err.displayStr,
- };
- progress = err.progress;
- }
-
- // Depending on the size of the output, we may send it back as a string or an ArrayBuffer.
- // This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file.
- // The threshold is specified in KiB.
- const threshold = (options.ioDisplayThreshold || 1024) * 1024;
- const returnType = this.dish.size() > threshold ? Dish.ARRAY_BUFFER : Dish.STRING;
-
- return {
- result: this.dish.type === Dish.HTML ?
- this.dish.get(Dish.HTML, notUTF8) :
- this.dish.get(returnType, notUTF8),
- type: Dish.enumLookup(this.dish.type),
- progress: progress,
- duration: new Date().getTime() - startTime,
- error: error
- };
-};
-
-
-/**
- * When a browser tab is unfocused and the browser has to run lots of dynamic content in other tabs,
- * it swaps out the memory for that tab. If the CyberChef tab has been unfocused for more than a
- * minute, we run a silent bake which will force the browser to load and cache all the relevant
- * JavaScript code needed to do a real bake.
- *
- * This will stop baking taking a long time when the CyberChef browser tab has been unfocused for a
- * long time and the browser has swapped out all its memory.
- *
- * The output will not be modified (hence "silent" bake).
- *
- * This will only actually execute the recipe if auto-bake is enabled, otherwise it will just load
- * the recipe, ingredients and dish.
- *
- * @param {Object[]} recipeConfig - The recipe configuration object
- * @returns {number} The time it took to run the silent bake in milliseconds.
-*/
-Chef.prototype.silentBake = function(recipeConfig) {
- log.debug("Running silent bake");
-
- let startTime = new Date().getTime(),
- recipe = new Recipe(recipeConfig),
- dish = new Dish("", Dish.STRING);
-
- try {
- recipe.execute(dish);
- } catch (err) {
- // Suppress all errors
- }
- return new Date().getTime() - startTime;
-};
-
-
-/**
- * Calculates highlight offsets if possible.
- *
- * @param {Object[]} recipeConfig
- * @param {string} direction
- * @param {Object} pos - The position object for the highlight.
- * @param {number} pos.start - The start offset.
- * @param {number} pos.end - The end offset.
- * @returns {Object}
- */
-Chef.prototype.calculateHighlights = function(recipeConfig, direction, pos) {
- const recipe = new Recipe(recipeConfig);
- const highlights = recipe.generateHighlightList();
-
- if (!highlights) return false;
-
- for (let i = 0; i < highlights.length; i++) {
- // Remove multiple highlights before processing again
- pos = [pos[0]];
-
- const func = direction === "forward" ? highlights[i].f : highlights[i].b;
-
- if (typeof func == "function") {
- pos = func(pos, highlights[i].args);
- }
- }
-
- return {
- pos: pos,
- direction: direction
- };
-};
-
-export default Chef;
diff --git a/src/core/Chef.mjs b/src/core/Chef.mjs
new file mode 100755
index 00000000..c7338184
--- /dev/null
+++ b/src/core/Chef.mjs
@@ -0,0 +1,172 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Dish from "./Dish";
+import Recipe from "./Recipe";
+import log from "loglevel";
+
+/**
+ * The main controller for CyberChef.
+ */
+class Chef {
+
+ /**
+ * Chef constructor
+ */
+ constructor() {
+ this.dish = new Dish();
+ }
+
+
+ /**
+ * Runs the recipe over the input.
+ *
+ * @param {string|ArrayBuffer} input - The input data as a string or ArrayBuffer
+ * @param {Object[]} recipeConfig - The recipe configuration object
+ * @param {Object} options - The options object storing various user choices
+ * @param {boolean} options.attempHighlight - Whether or not to attempt highlighting
+ * @param {number} progress - The position in the recipe to start from
+ * @param {number} [step] - Whether to only execute one operation in the recipe
+ *
+ * @returns {Object} response
+ * @returns {string} response.result - The output of the recipe
+ * @returns {string} response.type - The data type of the result
+ * @returns {number} response.progress - The position that we have got to in the recipe
+ * @returns {number} response.duration - The number of ms it took to execute the recipe
+ * @returns {number} response.error - The error object thrown by a failed operation (false if no error)
+ */
+ async bake(input, recipeConfig, options, progress, step) {
+ log.debug("Chef baking");
+ const startTime = new Date().getTime(),
+ recipe = new Recipe(recipeConfig),
+ containsFc = recipe.containsFlowControl(),
+ notUTF8 = options && options.hasOwnProperty("treatAsUtf8") && !options.treatAsUtf8;
+ let error = false;
+
+ if (containsFc && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false);
+
+ // Clean up progress
+ if (progress >= recipeConfig.length) {
+ progress = 0;
+ }
+
+ if (step) {
+ // Unset breakpoint on this step
+ recipe.setBreakpoint(progress, false);
+ // Set breakpoint on next step
+ recipe.setBreakpoint(progress + 1, true);
+ }
+
+ // If stepping with flow control, we have to start from the beginning
+ // but still want to skip all previous breakpoints
+ if (progress > 0 && containsFc) {
+ recipe.removeBreaksUpTo(progress);
+ progress = 0;
+ }
+
+ // If starting from scratch, load data
+ if (progress === 0) {
+ const type = input instanceof ArrayBuffer ? Dish.ARRAY_BUFFER : Dish.STRING;
+ this.dish.set(input, type);
+ }
+
+ try {
+ progress = await recipe.execute(this.dish, progress);
+ } catch (err) {
+ log.error(err);
+ error = {
+ displayStr: err.displayStr,
+ };
+ progress = err.progress;
+ }
+
+ // Depending on the size of the output, we may send it back as a string or an ArrayBuffer.
+ // This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file.
+ // The threshold is specified in KiB.
+ const threshold = (options.ioDisplayThreshold || 1024) * 1024;
+ const returnType = this.dish.size > threshold ? Dish.ARRAY_BUFFER : Dish.STRING;
+
+ return {
+ result: this.dish.type === Dish.HTML ?
+ this.dish.get(Dish.HTML, notUTF8) :
+ this.dish.get(returnType, notUTF8),
+ type: Dish.enumLookup(this.dish.type),
+ progress: progress,
+ duration: new Date().getTime() - startTime,
+ error: error
+ };
+ }
+
+
+ /**
+ * When a browser tab is unfocused and the browser has to run lots of dynamic content in other tabs,
+ * it swaps out the memory for that tab. If the CyberChef tab has been unfocused for more than a
+ * minute, we run a silent bake which will force the browser to load and cache all the relevant
+ * JavaScript code needed to do a real bake.
+ *
+ * This will stop baking taking a long time when the CyberChef browser tab has been unfocused for a
+ * long time and the browser has swapped out all its memory.
+ *
+ * The output will not be modified (hence "silent" bake).
+ *
+ * This will only actually execute the recipe if auto-bake is enabled, otherwise it will just load
+ * the recipe, ingredients and dish.
+ *
+ * @param {Object[]} recipeConfig - The recipe configuration object
+ * @returns {number} The time it took to run the silent bake in milliseconds.
+ */
+ silentBake(recipeConfig) {
+ log.debug("Running silent bake");
+
+ let startTime = new Date().getTime(),
+ recipe = new Recipe(recipeConfig),
+ dish = new Dish("", Dish.STRING);
+
+ try {
+ recipe.execute(dish);
+ } catch (err) {
+ // Suppress all errors
+ }
+ return new Date().getTime() - startTime;
+ }
+
+
+ /**
+ * Calculates highlight offsets if possible.
+ *
+ * @param {Object[]} recipeConfig
+ * @param {string} direction
+ * @param {Object} pos - The position object for the highlight.
+ * @param {number} pos.start - The start offset.
+ * @param {number} pos.end - The end offset.
+ * @returns {Object}
+ */
+ calculateHighlights(recipeConfig, direction, pos) {
+ const recipe = new Recipe(recipeConfig);
+ const highlights = recipe.generateHighlightList();
+
+ if (!highlights) return false;
+
+ for (let i = 0; i < highlights.length; i++) {
+ // Remove multiple highlights before processing again
+ pos = [pos[0]];
+
+ const func = direction === "forward" ? highlights[i].f : highlights[i].b;
+
+ if (typeof func == "function") {
+ pos = func(pos, highlights[i].args);
+ }
+ }
+
+ return {
+ pos: pos,
+ direction: direction
+ };
+ }
+
+}
+
+export default Chef;
diff --git a/src/core/ChefWorker.js b/src/core/ChefWorker.js
index a091e09c..3ef65a95 100644
--- a/src/core/ChefWorker.js
+++ b/src/core/ChefWorker.js
@@ -7,9 +7,9 @@
*/
import "babel-polyfill";
-import Chef from "./Chef.js";
-import OperationConfig from "./config/MetaConfig.js";
-import OpModules from "./config/modules/Default.js";
+import Chef from "./Chef";
+import OperationConfig from "./config/OperationConfig.json";
+import OpModules from "./config/modules/Default";
// Add ">" to the start of all log messages in the Chef Worker
import loglevelMessagePrefix from "loglevel-message-prefix";
diff --git a/src/core/Dish.js b/src/core/Dish.js
deleted file mode 100755
index f0093e81..00000000
--- a/src/core/Dish.js
+++ /dev/null
@@ -1,274 +0,0 @@
-import Utils from "./Utils.js";
-import BigNumber from "bignumber.js";
-
-/**
- * The data being operated on by each operation.
- *
- * @author n1474335 [n1474335@gmail.com]
- * @author Matt C [matt@artemisbot.uk]
- * @copyright Crown Copyright 2016
- * @license Apache-2.0
- *
- * @class
- * @param {byteArray|string|number|ArrayBuffer|BigNumber} value - The value of the input data.
- * @param {number} type - The data type of value, see Dish enums.
- */
-const Dish = function(value, type) {
- this.value = value || typeof value === "string" ? value : null;
- this.type = type || Dish.BYTE_ARRAY;
-};
-
-
-/**
- * Dish data type enum for byte arrays.
- * @readonly
- * @enum
- */
-Dish.BYTE_ARRAY = 0;
-/**
- * Dish data type enum for strings.
- * @readonly
- * @enum
- */
-Dish.STRING = 1;
-/**
- * Dish data type enum for numbers.
- * @readonly
- * @enum
- */
-Dish.NUMBER = 2;
-/**
- * Dish data type enum for HTML.
- * @readonly
- * @enum
- */
-Dish.HTML = 3;
-/**
- * Dish data type enum for ArrayBuffers.
- * @readonly
- * @enum
- */
-Dish.ARRAY_BUFFER = 4;
-/**
- * Dish data type enum for BigNumbers.
- * @readonly
- * @enum
- */
-Dish.BIG_NUMBER = 5;
-
-
-/**
- * Returns the data type enum for the given type string.
- *
- * @static
- * @param {string} typeStr - The name of the data type.
- * @returns {number} The data type enum value.
- */
-Dish.typeEnum = function(typeStr) {
- switch (typeStr.toLowerCase()) {
- case "bytearray":
- case "byte array":
- return Dish.BYTE_ARRAY;
- case "string":
- return Dish.STRING;
- case "number":
- return Dish.NUMBER;
- case "html":
- return Dish.HTML;
- case "arraybuffer":
- case "array buffer":
- return Dish.ARRAY_BUFFER;
- case "bignumber":
- case "big number":
- return Dish.BIG_NUMBER;
- default:
- throw "Invalid data type string. No matching enum.";
- }
-};
-
-
-/**
- * Returns the data type string for the given type enum.
- *
- * @static
- * @param {number} typeEnum - The enum value of the data type.
- * @returns {string} The data type as a string.
- */
-Dish.enumLookup = function(typeEnum) {
- switch (typeEnum) {
- case Dish.BYTE_ARRAY:
- return "byteArray";
- case Dish.STRING:
- return "string";
- case Dish.NUMBER:
- return "number";
- case Dish.HTML:
- return "html";
- case Dish.ARRAY_BUFFER:
- return "ArrayBuffer";
- case Dish.BIG_NUMBER:
- return "BigNumber";
- default:
- throw "Invalid data type enum. No matching type.";
- }
-};
-
-
-/**
- * Sets the data value and type and then validates them.
- *
- * @param {byteArray|string|number|ArrayBuffer|BigNumber} value - The value of the input data.
- * @param {number} type - The data type of value, see Dish enums.
- */
-Dish.prototype.set = function(value, type) {
- log.debug("Dish type: " + Dish.enumLookup(type));
- this.value = value;
- this.type = type;
-
- if (!this.valid()) {
- const sample = Utils.truncate(JSON.stringify(this.value), 13);
- throw "Data is not a valid " + Dish.enumLookup(type) + ": " + sample;
- }
-};
-
-
-/**
- * Returns the value of the data in the type format specified.
- *
- * @param {number} type - The data type of value, see Dish enums.
- * @param {boolean} [notUTF8] - Do not treat strings as UTF8.
- * @returns {byteArray|string|number|ArrayBuffer|BigNumber} The value of the output data.
- */
-Dish.prototype.get = function(type, notUTF8) {
- if (this.type !== type) {
- this.translate(type, notUTF8);
- }
- return this.value;
-};
-
-
-/**
- * Translates the data to the given type format.
- *
- * @param {number} toType - The data type of value, see Dish enums.
- * @param {boolean} [notUTF8] - Do not treat strings as UTF8.
- */
-Dish.prototype.translate = function(toType, notUTF8) {
- log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`);
- const byteArrayToStr = notUTF8 ? Utils.byteArrayToChars : Utils.byteArrayToUtf8;
-
- // Convert data to intermediate byteArray type
- switch (this.type) {
- case Dish.STRING:
- this.value = this.value ? Utils.strToByteArray(this.value) : [];
- break;
- case Dish.NUMBER:
- this.value = typeof this.value == "number" ? Utils.strToByteArray(this.value.toString()) : [];
- break;
- case Dish.HTML:
- this.value = this.value ? Utils.strToByteArray(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : [];
- break;
- case Dish.ARRAY_BUFFER:
- // Array.from() would be nicer here, but it's slightly slower
- this.value = Array.prototype.slice.call(new Uint8Array(this.value));
- break;
- case Dish.BIG_NUMBER:
- this.value = this.value instanceof BigNumber ? Utils.strToByteArray(this.value.toFixed()) : [];
- break;
- default:
- break;
- }
-
- this.type = Dish.BYTE_ARRAY;
-
- // Convert from byteArray to toType
- switch (toType) {
- case Dish.STRING:
- case Dish.HTML:
- this.value = this.value ? byteArrayToStr(this.value) : "";
- this.type = Dish.STRING;
- break;
- case Dish.NUMBER:
- this.value = this.value ? parseFloat(byteArrayToStr(this.value)) : 0;
- this.type = Dish.NUMBER;
- break;
- case Dish.ARRAY_BUFFER:
- this.value = new Uint8Array(this.value).buffer;
- this.type = Dish.ARRAY_BUFFER;
- break;
- case Dish.BIG_NUMBER:
- try {
- this.value = new BigNumber(byteArrayToStr(this.value));
- } catch (err) {
- this.value = new BigNumber(NaN);
- }
- this.type = Dish.BIG_NUMBER;
- break;
- default:
- break;
- }
-};
-
-
-/**
- * Validates that the value is the type that has been specified.
- * May have to disable parts of BYTE_ARRAY validation if it effects performance.
- *
- * @returns {boolean} Whether the data is valid or not.
-*/
-Dish.prototype.valid = function() {
- switch (this.type) {
- case Dish.BYTE_ARRAY:
- if (!(this.value instanceof Array)) {
- return false;
- }
-
- // Check that every value is a number between 0 - 255
- for (let i = 0; i < this.value.length; i++) {
- if (typeof this.value[i] !== "number" ||
- this.value[i] < 0 ||
- this.value[i] > 255) {
- return false;
- }
- }
- return true;
- case Dish.STRING:
- case Dish.HTML:
- return typeof this.value === "string";
- case Dish.NUMBER:
- return typeof this.value === "number";
- case Dish.ARRAY_BUFFER:
- return this.value instanceof ArrayBuffer;
- case Dish.BIG_NUMBER:
- return this.value instanceof BigNumber;
- default:
- return false;
- }
-};
-
-
-/**
- * Determines how much space the Dish takes up.
- * Numbers in JavaScript are 64-bit floating point, however for the purposes of the Dish,
- * we measure how many bytes are taken up when the number is written as a string.
- *
- * @returns {number}
-*/
-Dish.prototype.size = function() {
- switch (this.type) {
- case Dish.BYTE_ARRAY:
- case Dish.STRING:
- case Dish.HTML:
- return this.value.length;
- case Dish.NUMBER:
- case Dish.BIG_NUMBER:
- return this.value.toString().length;
- case Dish.ARRAY_BUFFER:
- return this.value.byteLength;
- default:
- return -1;
- }
-};
-
-
-export default Dish;
diff --git a/src/core/Dish.mjs b/src/core/Dish.mjs
new file mode 100755
index 00000000..792395c1
--- /dev/null
+++ b/src/core/Dish.mjs
@@ -0,0 +1,293 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @author Matt C [matt@artemisbot.uk]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Utils from "./Utils";
+import BigNumber from "bignumber.js";
+import log from "loglevel";
+
+/**
+ * The data being operated on by each operation.
+ */
+class Dish {
+
+ /**
+ * Dish constructor
+ *
+ * @param {byteArray|string|number|ArrayBuffer|BigNumber} [value=null]
+ * - The value of the input data.
+ * @param {number} [type=Dish.BYTE_ARRAY]
+ * - The data type of value, see Dish enums.
+ */
+ constructor(value=null, type=Dish.BYTE_ARRAY) {
+ this.value = value;
+ this.type = type;
+ }
+
+
+ /**
+ * Returns the data type enum for the given type string.
+ *
+ * @param {string} typeStr - The name of the data type.
+ * @returns {number} The data type enum value.
+ */
+ static typeEnum(typeStr) {
+ switch (typeStr.toLowerCase()) {
+ case "bytearray":
+ case "byte array":
+ return Dish.BYTE_ARRAY;
+ case "string":
+ return Dish.STRING;
+ case "number":
+ return Dish.NUMBER;
+ case "html":
+ return Dish.HTML;
+ case "arraybuffer":
+ case "array buffer":
+ return Dish.ARRAY_BUFFER;
+ case "bignumber":
+ case "big number":
+ return Dish.BIG_NUMBER;
+ default:
+ throw "Invalid data type string. No matching enum.";
+ }
+ }
+
+
+ /**
+ * Returns the data type string for the given type enum.
+ *
+ * @param {number} typeEnum - The enum value of the data type.
+ * @returns {string} The data type as a string.
+ */
+ static enumLookup(typeEnum) {
+ switch (typeEnum) {
+ case Dish.BYTE_ARRAY:
+ return "byteArray";
+ case Dish.STRING:
+ return "string";
+ case Dish.NUMBER:
+ return "number";
+ case Dish.HTML:
+ return "html";
+ case Dish.ARRAY_BUFFER:
+ return "ArrayBuffer";
+ case Dish.BIG_NUMBER:
+ return "BigNumber";
+ default:
+ throw "Invalid data type enum. No matching type.";
+ }
+ }
+
+
+ /**
+ * Sets the data value and type and then validates them.
+ *
+ * @param {byteArray|string|number|ArrayBuffer|BigNumber} value
+ * - The value of the input data.
+ * @param {number} type
+ * - The data type of value, see Dish enums.
+ */
+ set(value, type) {
+ if (typeof type === "string") {
+ type = Dish.typeEnum(type);
+ }
+
+ log.debug("Dish type: " + Dish.enumLookup(type));
+ this.value = value;
+ this.type = type;
+
+ if (!this.valid()) {
+ const sample = Utils.truncate(JSON.stringify(this.value), 13);
+ throw "Data is not a valid " + Dish.enumLookup(type) + ": " + sample;
+ }
+ }
+
+
+ /**
+ * Returns the value of the data in the type format specified.
+ *
+ * @param {number} type - The data type of value, see Dish enums.
+ * @param {boolean} [notUTF8=false] - Do not treat strings as UTF8.
+ * @returns {byteArray|string|number|ArrayBuffer|BigNumber}
+ * The value of the output data.
+ */
+ get(type, notUTF8=false) {
+ if (typeof type === "string") {
+ type = Dish.typeEnum(type);
+ }
+ if (this.type !== type) {
+ this.translate(type, notUTF8);
+ }
+ return this.value;
+ }
+
+
+ /**
+ * Translates the data to the given type format.
+ *
+ * @param {number} toType - The data type of value, see Dish enums.
+ * @param {boolean} [notUTF8=false] - Do not treat strings as UTF8.
+ */
+ translate(toType, notUTF8=false) {
+ log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`);
+ const byteArrayToStr = notUTF8 ? Utils.byteArrayToChars : Utils.byteArrayToUtf8;
+
+ // Convert data to intermediate byteArray type
+ switch (this.type) {
+ case Dish.STRING:
+ this.value = this.value ? Utils.strToByteArray(this.value) : [];
+ break;
+ case Dish.NUMBER:
+ this.value = typeof this.value == "number" ? Utils.strToByteArray(this.value.toString()) : [];
+ break;
+ case Dish.HTML:
+ this.value = this.value ? Utils.strToByteArray(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : [];
+ break;
+ case Dish.ARRAY_BUFFER:
+ // Array.from() would be nicer here, but it's slightly slower
+ this.value = Array.prototype.slice.call(new Uint8Array(this.value));
+ break;
+ case Dish.BIG_NUMBER:
+ this.value = this.value instanceof BigNumber ? Utils.strToByteArray(this.value.toFixed()) : [];
+ break;
+ default:
+ break;
+ }
+
+ this.type = Dish.BYTE_ARRAY;
+
+ // Convert from byteArray to toType
+ switch (toType) {
+ case Dish.STRING:
+ case Dish.HTML:
+ this.value = this.value ? byteArrayToStr(this.value) : "";
+ this.type = Dish.STRING;
+ break;
+ case Dish.NUMBER:
+ this.value = this.value ? parseFloat(byteArrayToStr(this.value)) : 0;
+ this.type = Dish.NUMBER;
+ break;
+ case Dish.ARRAY_BUFFER:
+ this.value = new Uint8Array(this.value).buffer;
+ this.type = Dish.ARRAY_BUFFER;
+ break;
+ case Dish.BIG_NUMBER:
+ try {
+ this.value = new BigNumber(byteArrayToStr(this.value));
+ } catch (err) {
+ this.value = new BigNumber(NaN);
+ }
+ this.type = Dish.BIG_NUMBER;
+ break;
+ default:
+ break;
+ }
+ }
+
+
+ /**
+ * Validates that the value is the type that has been specified.
+ * May have to disable parts of BYTE_ARRAY validation if it effects performance.
+ *
+ * @returns {boolean} Whether the data is valid or not.
+ */
+ valid() {
+ switch (this.type) {
+ case Dish.BYTE_ARRAY:
+ if (!(this.value instanceof Array)) {
+ return false;
+ }
+
+ // Check that every value is a number between 0 - 255
+ for (let i = 0; i < this.value.length; i++) {
+ if (typeof this.value[i] !== "number" ||
+ this.value[i] < 0 ||
+ this.value[i] > 255) {
+ return false;
+ }
+ }
+ return true;
+ case Dish.STRING:
+ case Dish.HTML:
+ return typeof this.value === "string";
+ case Dish.NUMBER:
+ return typeof this.value === "number";
+ case Dish.ARRAY_BUFFER:
+ return this.value instanceof ArrayBuffer;
+ case Dish.BIG_NUMBER:
+ return this.value instanceof BigNumber;
+ default:
+ return false;
+ }
+ }
+
+
+ /**
+ * Determines how much space the Dish takes up.
+ * Numbers in JavaScript are 64-bit floating point, however for the purposes of the Dish,
+ * we measure how many bytes are taken up when the number is written as a string.
+ *
+ * @returns {number}
+ */
+ get size() {
+ switch (this.type) {
+ case Dish.BYTE_ARRAY:
+ case Dish.STRING:
+ case Dish.HTML:
+ return this.value.length;
+ case Dish.NUMBER:
+ case Dish.BIG_NUMBER:
+ return this.value.toString().length;
+ case Dish.ARRAY_BUFFER:
+ return this.value.byteLength;
+ default:
+ return -1;
+ }
+ }
+
+}
+
+
+/**
+ * Dish data type enum for byte arrays.
+ * @readonly
+ * @enum
+ */
+Dish.BYTE_ARRAY = 0;
+/**
+ * Dish data type enum for strings.
+ * @readonly
+ * @enum
+ */
+Dish.STRING = 1;
+/**
+ * Dish data type enum for numbers.
+ * @readonly
+ * @enum
+ */
+Dish.NUMBER = 2;
+/**
+ * Dish data type enum for HTML.
+ * @readonly
+ * @enum
+ */
+Dish.HTML = 3;
+/**
+ * Dish data type enum for ArrayBuffers.
+ * @readonly
+ * @enum
+ */
+Dish.ARRAY_BUFFER = 4;
+/**
+ * Dish data type enum for BigNumbers.
+ * @readonly
+ * @enum
+ */
+Dish.BIG_NUMBER = 5;
+
+
+export default Dish;
diff --git a/src/core/FlowControl.js b/src/core/FlowControl.js
index fd2c0785..daa1c343 100755
--- a/src/core/FlowControl.js
+++ b/src/core/FlowControl.js
@@ -1,5 +1,5 @@
-import Recipe from "./Recipe.js";
-import Dish from "./Dish.js";
+import Recipe from "./Recipe";
+import Dish from "./Dish";
/**
@@ -27,7 +27,7 @@ const FlowControl = {
inputType = opList[state.progress].inputType,
outputType = opList[state.progress].outputType,
input = state.dish.get(inputType),
- ings = opList[state.progress].getIngValues(),
+ ings = opList[state.progress].ingValues,
splitDelim = ings[0],
mergeDelim = ings[1],
ignoreErrors = ings[2],
@@ -41,7 +41,7 @@ const FlowControl = {
// Create subOpList for each tranche to operate on
// (all remaining operations unless we encounter a Merge)
for (i = state.progress + 1; i < opList.length; i++) {
- if (opList[i].name === "Merge" && !opList[i].isDisabled()) {
+ if (opList[i].name === "Merge" && !opList[i].disabled) {
break;
} else {
subOpList.push(opList[i]);
@@ -57,7 +57,7 @@ const FlowControl = {
recipe.addOperations(subOpList);
// Take a deep(ish) copy of the ingredient values
- const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.getIngValues())));
+ const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.ingValues)));
// Run recipe over each tranche
for (i = 0; i < inputs.length; i++) {
@@ -65,7 +65,7 @@ const FlowControl = {
// Baseline ing values for each tranche so that registers are reset
subOpList.forEach((op, i) => {
- op.setIngValues(JSON.parse(JSON.stringify(ingValues[i])));
+ op.ingValues = JSON.parse(JSON.stringify(ingValues[i]));
});
const dish = new Dish(inputs[i], inputType);
@@ -112,7 +112,7 @@ const FlowControl = {
* @returns {Object} The updated state of the recipe.
*/
runRegister: function(state) {
- const ings = state.opList[state.progress].getIngValues(),
+ const ings = state.opList[state.progress].ingValues,
extractorStr = ings[0],
i = ings[1],
m = ings[2];
@@ -150,9 +150,9 @@ const FlowControl = {
// Step through all subsequent ops and replace registers in args with extracted content
for (let i = state.progress + 1; i < state.opList.length; i++) {
- if (state.opList[i].isDisabled()) continue;
+ if (state.opList[i].disabled) continue;
- let args = state.opList[i].getIngValues();
+ let args = state.opList[i].ingValues;
args = args.map(arg => {
if (typeof arg !== "string" && typeof arg !== "object") return arg;
@@ -181,7 +181,7 @@ const FlowControl = {
* @returns {Object} The updated state of the recipe.
*/
runJump: function(state) {
- const ings = state.opList[state.progress].getIngValues(),
+ const ings = state.opList[state.progress].ingValues,
label = ings[0],
maxJumps = ings[1],
jmpIndex = FlowControl._getLabelIndex(label, state);
@@ -209,7 +209,7 @@ const FlowControl = {
* @returns {Object} The updated state of the recipe.
*/
runCondJump: function(state) {
- const ings = state.opList[state.progress].getIngValues(),
+ const ings = state.opList[state.progress].ingValues,
dish = state.dish,
regexStr = ings[0],
invert = ings[1],
@@ -276,7 +276,7 @@ const FlowControl = {
for (let o = 0; o < state.opList.length; o++) {
let operation = state.opList[o];
if (operation.name === "Label"){
- let ings = operation.getIngValues();
+ let ings = operation.ingValues;
if (name === ings[0]) {
return o;
}
diff --git a/src/core/Ingredient.js b/src/core/Ingredient.js
deleted file mode 100755
index e8d8a8cc..00000000
--- a/src/core/Ingredient.js
+++ /dev/null
@@ -1,92 +0,0 @@
-import Utils from "./Utils.js";
-
-
-/**
- * The arguments to operations.
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2016
- * @license Apache-2.0
- *
- * @class
- * @param {Object} ingredientConfig
- */
-const Ingredient = function(ingredientConfig) {
- this.name = "";
- this.type = "";
- this.value = null;
-
- if (ingredientConfig) {
- this._parseConfig(ingredientConfig);
- }
-};
-
-
-/**
- * Reads and parses the given config.
- *
- * @private
- * @param {Object} ingredientConfig
- */
-Ingredient.prototype._parseConfig = function(ingredientConfig) {
- this.name = ingredientConfig.name;
- this.type = ingredientConfig.type;
-};
-
-
-/**
- * Returns the value of the Ingredient as it should be displayed in a recipe config.
- *
- * @returns {*}
- */
-Ingredient.prototype.getConfig = function() {
- return this.value;
-};
-
-
-/**
- * Sets the value of the Ingredient.
- *
- * @param {*} value
- */
-Ingredient.prototype.setValue = function(value) {
- this.value = Ingredient.prepare(value, this.type);
-};
-
-
-/**
- * Most values will be strings when they are entered. This function converts them to the correct
- * type.
- *
- * @static
- * @param {*} data
- * @param {string} type - The name of the data type.
-*/
-Ingredient.prepare = function(data, type) {
- let number;
-
- switch (type) {
- case "binaryString":
- case "binaryShortString":
- case "editableOption":
- return Utils.parseEscapedChars(data);
- case "byteArray":
- if (typeof data == "string") {
- data = data.replace(/\s+/g, "");
- return Utils.fromHex(data);
- } else {
- return data;
- }
- case "number":
- number = parseFloat(data);
- if (isNaN(number)) {
- const sample = Utils.truncate(data.toString(), 10);
- throw "Invalid ingredient value. Not a number: " + sample;
- }
- return number;
- default:
- return data;
- }
-};
-
-export default Ingredient;
diff --git a/src/core/Ingredient.mjs b/src/core/Ingredient.mjs
new file mode 100755
index 00000000..af96beaf
--- /dev/null
+++ b/src/core/Ingredient.mjs
@@ -0,0 +1,109 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Utils from "./Utils";
+
+/**
+ * The arguments to operations.
+ */
+class Ingredient {
+
+ /**
+ * Ingredient constructor
+ *
+ * @param {Object} ingredientConfig
+ */
+ constructor(ingredientConfig) {
+ this.name = "";
+ this.type = "";
+ this._value = null;
+
+ if (ingredientConfig) {
+ this._parseConfig(ingredientConfig);
+ }
+ }
+
+
+ /**
+ * Reads and parses the given config.
+ *
+ * @private
+ * @param {Object} ingredientConfig
+ */
+ _parseConfig(ingredientConfig) {
+ this.name = ingredientConfig.name;
+ this.type = ingredientConfig.type;
+ this.defaultValue = ingredientConfig.value;
+ }
+
+
+ /**
+ * Returns the value of the Ingredient as it should be displayed in a recipe config.
+ *
+ * @returns {*}
+ */
+ get config() {
+ return this._value;
+ }
+
+
+ /**
+ * Sets the value of the Ingredient.
+ *
+ * @param {*} value
+ */
+ set value(value) {
+ this._value = Ingredient.prepare(value, this.type);
+ }
+
+
+ /**
+ * Gets the value of the Ingredient.
+ *
+ * @returns {*}
+ */
+ get value() {
+ return this._value;
+ }
+
+
+ /**
+ * Most values will be strings when they are entered. This function converts them to the correct
+ * type.
+ *
+ * @param {*} data
+ * @param {string} type - The name of the data type.
+ */
+ static prepare(data, type) {
+ let number;
+
+ switch (type) {
+ case "binaryString":
+ case "binaryShortString":
+ case "editableOption":
+ return Utils.parseEscapedChars(data);
+ case "byteArray":
+ if (typeof data == "string") {
+ data = data.replace(/\s+/g, "");
+ return Utils.fromHex(data);
+ } else {
+ return data;
+ }
+ case "number":
+ number = parseFloat(data);
+ if (isNaN(number)) {
+ const sample = Utils.truncate(data.toString(), 10);
+ throw "Invalid ingredient value. Not a number: " + sample;
+ }
+ return number;
+ default:
+ return data;
+ }
+ }
+
+}
+
+export default Ingredient;
diff --git a/src/core/Operation.js b/src/core/Operation.js
deleted file mode 100755
index d4ee21d3..00000000
--- a/src/core/Operation.js
+++ /dev/null
@@ -1,174 +0,0 @@
-import Dish from "./Dish.js";
-import Ingredient from "./Ingredient.js";
-import OperationConfig from "./config/MetaConfig.js";
-import OpModules from "./config/modules/OpModules.js";
-
-
-/**
- * The Operation specified by the user to be run.
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2016
- * @license Apache-2.0
- *
- * @class
- * @param {string} operationName
- */
-const Operation = function(operationName) {
- this.name = operationName;
- this.module = "";
- this.description = "";
- this.inputType = -1;
- this.outputType = -1;
- this.run = null;
- this.highlight = null;
- this.highlightReverse = null;
- this.breakpoint = false;
- this.disabled = false;
- this.ingList = [];
-
- if (OperationConfig.hasOwnProperty(this.name)) {
- this._parseConfig(OperationConfig[this.name]);
- }
-};
-
-
-/**
- * Reads and parses the given config.
- *
- * @private
- * @param {Object} operationConfig
- */
-Operation.prototype._parseConfig = function(operationConfig) {
- this.module = operationConfig.module;
- this.description = operationConfig.description;
- this.inputType = Dish.typeEnum(operationConfig.inputType);
- this.outputType = Dish.typeEnum(operationConfig.outputType);
- this.highlight = operationConfig.highlight;
- this.highlightReverse = operationConfig.highlightReverse;
- this.flowControl = operationConfig.flowControl;
- this.run = OpModules[this.module][this.name];
-
- for (let a = 0; a < operationConfig.args.length; a++) {
- const ingredientConfig = operationConfig.args[a];
- const ingredient = new Ingredient(ingredientConfig);
- this.addIngredient(ingredient);
- }
-
- if (this.highlight === "func") {
- this.highlight = OpModules[this.module][`${this.name}-highlight`];
- }
-
- if (this.highlightReverse === "func") {
- this.highlightReverse = OpModules[this.module][`${this.name}-highlightReverse`];
- }
-};
-
-
-/**
- * Returns the value of the Operation as it should be displayed in a recipe config.
- *
- * @returns {Object}
- */
-Operation.prototype.getConfig = function() {
- const ingredientConfig = [];
-
- for (let o = 0; o < this.ingList.length; o++) {
- ingredientConfig.push(this.ingList[o].getConfig());
- }
-
- const operationConfig = {
- "op": this.name,
- "args": ingredientConfig
- };
-
- return operationConfig;
-};
-
-
-/**
- * Adds a new Ingredient to this Operation.
- *
- * @param {Ingredient} ingredient
- */
-Operation.prototype.addIngredient = function(ingredient) {
- this.ingList.push(ingredient);
-};
-
-
-/**
- * Set the Ingredient values for this Operation.
- *
- * @param {Object[]} ingValues
- */
-Operation.prototype.setIngValues = function(ingValues) {
- for (let i = 0; i < ingValues.length; i++) {
- this.ingList[i].setValue(ingValues[i]);
- }
-};
-
-
-/**
- * Get the Ingredient values for this Operation.
- *
- * @returns {Object[]}
- */
-Operation.prototype.getIngValues = function() {
- const ingValues = [];
- for (let i = 0; i < this.ingList.length; i++) {
- ingValues.push(this.ingList[i].value);
- }
- return ingValues;
-};
-
-
-/**
- * Set whether this Operation has a breakpoint.
- *
- * @param {boolean} value
- */
-Operation.prototype.setBreakpoint = function(value) {
- this.breakpoint = !!value;
-};
-
-
-/**
- * Returns true if this Operation has a breakpoint set.
- *
- * @returns {boolean}
- */
-Operation.prototype.isBreakpoint = function() {
- return this.breakpoint;
-};
-
-
-/**
- * Set whether this Operation is disabled.
- *
- * @param {boolean} value
- */
-Operation.prototype.setDisabled = function(value) {
- this.disabled = !!value;
-};
-
-
-/**
- * Returns true if this Operation is disabled.
- *
- * @returns {boolean}
- */
-Operation.prototype.isDisabled = function() {
- return this.disabled;
-};
-
-
-/**
- * Returns true if this Operation is a flow control.
- *
- * @returns {boolean}
- */
-Operation.prototype.isFlowControl = function() {
- return this.flowControl;
-};
-
-export default Operation;
diff --git a/src/core/Operation.mjs b/src/core/Operation.mjs
new file mode 100755
index 00000000..30ad71e4
--- /dev/null
+++ b/src/core/Operation.mjs
@@ -0,0 +1,239 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Dish from "./Dish";
+import Ingredient from "./Ingredient";
+
+/**
+ * The Operation specified by the user to be run.
+ */
+class Operation {
+
+ /**
+ * Operation constructor
+ */
+ constructor() {
+ // Private fields
+ this._inputType = -1;
+ this._outputType = -1;
+ this._breakpoint = false;
+ this._disabled = false;
+ this._flowControl = false;
+ this._ingList = [];
+
+ // Public fields
+ this.name = "";
+ this.module = "";
+ this.description = "";
+ }
+
+
+ /**
+ * Interface for operation runner
+ *
+ * @param {*} input
+ * @param {Object[]} args
+ * @returns {*}
+ */
+ run(input, args) {
+ return input;
+ }
+
+
+ /**
+ * Interface for forward highlighter
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlight(pos, args) {
+ return false;
+ }
+
+
+ /**
+ * Interface for reverse highlighter
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlightReverse(pos, args) {
+ return false;
+ }
+
+
+ /**
+ * Sets the input type as a Dish enum.
+ *
+ * @param {string} typeStr
+ */
+ set inputType(typeStr) {
+ this._inputType = Dish.typeEnum(typeStr);
+ }
+
+
+ /**
+ * Gets the input type as a readable string.
+ *
+ * @returns {string}
+ */
+ get inputType() {
+ return Dish.enumLookup(this._inputType);
+ }
+
+
+ /**
+ * Sets the output type as a Dish enum.
+ *
+ * @param {string} typeStr
+ */
+ set outputType(typeStr) {
+ this._outputType = Dish.typeEnum(typeStr);
+ }
+
+
+ /**
+ * Gets the output type as a readable string.
+ *
+ * @returns {string}
+ */
+ get outputType() {
+ return Dish.enumLookup(this._outputType);
+ }
+
+
+ /**
+ * Sets the args for the current operation.
+ *
+ * @param {Object[]} conf
+ */
+ set args(conf) {
+ conf.forEach(arg => {
+ const ingredient = new Ingredient(arg);
+ this.addIngredient(ingredient);
+ });
+ }
+
+
+ /**
+ * Gets the args for the current operation.
+ *
+ * @param {Object[]} conf
+ */
+ get args() {
+ return this._ingList.map(ing => {
+ return {
+ name: ing.name,
+ type: ing.type,
+ value: ing.defaultValue
+ };
+ });
+ }
+
+
+ /**
+ * Returns the value of the Operation as it should be displayed in a recipe config.
+ *
+ * @returns {Object}
+ */
+ get config() {
+ return {
+ "op": this.name,
+ "args": this._ingList.map(ing => ing.conf)
+ };
+ }
+
+
+ /**
+ * Adds a new Ingredient to this Operation.
+ *
+ * @param {Ingredient} ingredient
+ */
+ addIngredient(ingredient) {
+ this._ingList.push(ingredient);
+ }
+
+
+ /**
+ * Set the Ingredient values for this Operation.
+ *
+ * @param {Object[]} ingValues
+ */
+ set ingValues(ingValues) {
+ ingValues.forEach((val, i) => {
+ this._ingList[i].value = val;
+ });
+ }
+
+
+ /**
+ * Get the Ingredient values for this Operation.
+ *
+ * @returns {Object[]}
+ */
+ get ingValues() {
+ return this._ingList.map(ing => ing.value);
+ }
+
+
+ /**
+ * Set whether this Operation has a breakpoint.
+ *
+ * @param {boolean} value
+ */
+ set breakpoint(value) {
+ this._breakpoint = !!value;
+ }
+
+
+ /**
+ * Returns true if this Operation has a breakpoint set.
+ *
+ * @returns {boolean}
+ */
+ get breakpoint() {
+ return this._breakpoint;
+ }
+
+
+ /**
+ * Set whether this Operation is disabled.
+ *
+ * @param {boolean} value
+ */
+ set disabled(value) {
+ this._disabled = !!value;
+ }
+
+
+ /**
+ * Returns true if this Operation is disabled.
+ *
+ * @returns {boolean}
+ */
+ get disabled() {
+ return this._disabled;
+ }
+
+
+ /**
+ * Returns true if this Operation is a flow control.
+ *
+ * @returns {boolean}
+ */
+ get flowControl() {
+ return this._flowControl;
+ }
+
+}
+
+export default Operation;
diff --git a/src/core/Recipe.js b/src/core/Recipe.js
deleted file mode 100755
index 9305c32d..00000000
--- a/src/core/Recipe.js
+++ /dev/null
@@ -1,264 +0,0 @@
-import Operation from "./Operation.js";
-
-
-/**
- * The Recipe controls a list of Operations and the Dish they operate on.
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2016
- * @license Apache-2.0
- *
- * @class
- * @param {Object} recipeConfig
- */
-const Recipe = function(recipeConfig) {
- this.opList = [];
-
- if (recipeConfig) {
- this._parseConfig(recipeConfig);
- }
-};
-
-
-/**
- * Reads and parses the given config.
- *
- * @private
- * @param {Object} recipeConfig
- */
-Recipe.prototype._parseConfig = function(recipeConfig) {
- for (let c = 0; c < recipeConfig.length; c++) {
- const operationName = recipeConfig[c].op;
- const operation = new Operation(operationName);
- operation.setIngValues(recipeConfig[c].args);
- operation.setBreakpoint(recipeConfig[c].breakpoint);
- operation.setDisabled(recipeConfig[c].disabled);
- this.addOperation(operation);
- }
-};
-
-
-/**
- * Returns the value of the Recipe as it should be displayed in a recipe config.
- *
- * @returns {*}
- */
-Recipe.prototype.getConfig = function() {
- const recipeConfig = [];
-
- for (let o = 0; o < this.opList.length; o++) {
- recipeConfig.push(this.opList[o].getConfig());
- }
-
- return recipeConfig;
-};
-
-
-/**
- * Adds a new Operation to this Recipe.
- *
- * @param {Operation} operation
- */
-Recipe.prototype.addOperation = function(operation) {
- this.opList.push(operation);
-};
-
-
-/**
- * Adds a list of Operations to this Recipe.
- *
- * @param {Operation[]} operations
- */
-Recipe.prototype.addOperations = function(operations) {
- this.opList = this.opList.concat(operations);
-};
-
-
-/**
- * Set a breakpoint on a specified Operation.
- *
- * @param {number} position - The index of the Operation
- * @param {boolean} value
- */
-Recipe.prototype.setBreakpoint = function(position, value) {
- try {
- this.opList[position].setBreakpoint(value);
- } catch (err) {
- // Ignore index error
- }
-};
-
-
-/**
- * Remove breakpoints on all Operations in the Recipe up to the specified position. Used by Flow
- * Control Fork operation.
- *
- * @param {number} pos
- */
-Recipe.prototype.removeBreaksUpTo = function(pos) {
- for (let i = 0; i < pos; i++) {
- this.opList[i].setBreakpoint(false);
- }
-};
-
-
-/**
- * Returns true if there is an Flow Control Operation in this Recipe.
- *
- * @returns {boolean}
- */
-Recipe.prototype.containsFlowControl = function() {
- for (let i = 0; i < this.opList.length; i++) {
- if (this.opList[i].isFlowControl()) return true;
- }
- return false;
-};
-
-
-/**
- * Returns the index of the last Operation index that will be executed, taking into account disabled
- * Operations and breakpoints.
- *
- * @param {number} [startIndex=0] - The index to start searching from
- * @returns (number}
- */
-Recipe.prototype.lastOpIndex = function(startIndex) {
- let i = startIndex + 1 || 0,
- op;
-
- for (; i < this.opList.length; i++) {
- op = this.opList[i];
- if (op.isDisabled()) return i-1;
- if (op.isBreakpoint()) return i-1;
- }
-
- return i-1;
-};
-
-
-/**
- * Executes each operation in the recipe over the given Dish.
- *
- * @param {Dish} dish
- * @param {number} [startFrom=0] - The index of the Operation to start executing from
- * @param {number} [forkState={}] - If this is a forked recipe, the state of the recipe up to this point
- * @returns {number} - The final progress through the recipe
- */
-Recipe.prototype.execute = async function(dish, startFrom = 0, forkState = {}) {
- let op, input, output,
- numJumps = 0,
- numRegisters = forkState.numRegisters || 0;
-
- log.debug(`[*] Executing recipe of ${this.opList.length} operations, starting at ${startFrom}`);
-
- for (let i = startFrom; i < this.opList.length; i++) {
- op = this.opList[i];
- log.debug(`[${i}] ${op.name} ${JSON.stringify(op.getIngValues())}`);
- if (op.isDisabled()) {
- log.debug("Operation is disabled, skipping");
- continue;
- }
- if (op.isBreakpoint()) {
- log.debug("Pausing at breakpoint");
- return i;
- }
-
- try {
- input = dish.get(op.inputType);
- log.debug("Executing operation");
-
- if (op.isFlowControl()) {
- // Package up the current state
- let state = {
- "progress": i,
- "dish": dish,
- "opList": this.opList,
- "numJumps": numJumps,
- "numRegisters": numRegisters,
- "forkOffset": forkState.forkOffset || 0
- };
-
- state = await op.run(state);
- i = state.progress;
- numJumps = state.numJumps;
- numRegisters = state.numRegisters;
- } else {
- output = await op.run(input, op.getIngValues());
- dish.set(output, op.outputType);
- }
- } catch (err) {
- const e = typeof err == "string" ? { message: err } : err;
-
- e.progress = i;
- if (e.fileName) {
- e.displayStr = op.name + " - " + e.name + " in " +
- e.fileName + " on line " + e.lineNumber +
- ".
Message: " + (e.displayStr || e.message);
- } else {
- e.displayStr = op.name + " - " + (e.displayStr || e.message);
- }
-
- throw e;
- }
- }
-
- log.debug("Recipe complete");
- return this.opList.length;
-};
-
-
-/**
- * Returns the recipe configuration in string format.
- *
- * @returns {string}
- */
-Recipe.prototype.toString = function() {
- return JSON.stringify(this.getConfig());
-};
-
-
-/**
- * Creates a Recipe from a given configuration string.
- *
- * @param {string} recipeStr
- */
-Recipe.prototype.fromString = function(recipeStr) {
- const recipeConfig = JSON.parse(recipeStr);
- this._parseConfig(recipeConfig);
-};
-
-
-/**
- * Generates a list of all the highlight functions assigned to operations in the recipe, if the
- * entire recipe supports highlighting.
- *
- * @returns {Object[]} highlights
- * @returns {function} highlights[].f
- * @returns {function} highlights[].b
- * @returns {Object[]} highlights[].args
- */
-Recipe.prototype.generateHighlightList = function() {
- const highlights = [];
-
- for (let i = 0; i < this.opList.length; i++) {
- let op = this.opList[i];
- if (op.isDisabled()) continue;
-
- // If any breakpoints are set, do not attempt to highlight
- if (op.isBreakpoint()) return false;
-
- // If any of the operations do not support highlighting, fail immediately.
- if (op.highlight === false || op.highlight === undefined) return false;
-
- highlights.push({
- f: op.highlight,
- b: op.highlightReverse,
- args: op.getIngValues()
- });
- }
-
- return highlights;
-};
-
-
-export default Recipe;
diff --git a/src/core/Recipe.mjs b/src/core/Recipe.mjs
new file mode 100755
index 00000000..ce03f8bf
--- /dev/null
+++ b/src/core/Recipe.mjs
@@ -0,0 +1,250 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+// import Operation from "./Operation.js";
+import OpModules from "./config/modules/OpModules";
+import OperationConfig from "./config/OperationConfig.json";
+import log from "loglevel";
+
+/**
+ * The Recipe controls a list of Operations and the Dish they operate on.
+ */
+class Recipe {
+
+ /**
+ * Recipe constructor
+ *
+ * @param {Object} recipeConfig
+ */
+ constructor(recipeConfig) {
+ this.opList = [];
+
+ if (recipeConfig) {
+ this._parseConfig(recipeConfig);
+ }
+ }
+
+
+ /**
+ * Reads and parses the given config.
+ *
+ * @private
+ * @param {Object} recipeConfig
+ */
+ _parseConfig(recipeConfig) {
+ for (let c = 0; c < recipeConfig.length; c++) {
+ const operationName = recipeConfig[c].op;
+ const opConf = OperationConfig[operationName];
+ const opObj = OpModules[opConf.module][operationName];
+ const operation = new opObj();
+ operation.ingValues = recipeConfig[c].args;
+ operation.breakpoint = recipeConfig[c].breakpoint;
+ operation.disabled = recipeConfig[c].disabled;
+ this.addOperation(operation);
+ }
+ }
+
+
+ /**
+ * Returns the value of the Recipe as it should be displayed in a recipe config.
+ *
+ * @returns {Object[]}
+ */
+ get config() {
+ return this.opList.map(op => op.config);
+ }
+
+
+ /**
+ * Adds a new Operation to this Recipe.
+ *
+ * @param {Operation} operation
+ */
+ addOperation(operation) {
+ this.opList.push(operation);
+ }
+
+
+ /**
+ * Adds a list of Operations to this Recipe.
+ *
+ * @param {Operation[]} operations
+ */
+ addOperations(operations) {
+ this.opList = this.opList.concat(operations);
+ }
+
+
+ /**
+ * Set a breakpoint on a specified Operation.
+ *
+ * @param {number} position - The index of the Operation
+ * @param {boolean} value
+ */
+ setBreakpoint(position, value) {
+ try {
+ this.opList[position].breakpoint = value;
+ } catch (err) {
+ // Ignore index error
+ }
+ }
+
+
+ /**
+ * Remove breakpoints on all Operations in the Recipe up to the specified position. Used by Flow
+ * Control Fork operation.
+ *
+ * @param {number} pos
+ */
+ removeBreaksUpTo(pos) {
+ for (let i = 0; i < pos; i++) {
+ this.opList[i].breakpoint = false;
+ }
+ }
+
+
+ /**
+ * Returns true if there is an Flow Control Operation in this Recipe.
+ *
+ * @returns {boolean}
+ */
+ containsFlowControl() {
+ return this.opList.reduce((acc, curr) => {
+ return acc || curr.flowControl;
+ }, false);
+ }
+
+
+ /**
+ * Executes each operation in the recipe over the given Dish.
+ *
+ * @param {Dish} dish
+ * @param {number} [startFrom=0]
+ * - The index of the Operation to start executing from
+ * @param {number} [forkState={}]
+ * - If this is a forked recipe, the state of the recipe up to this point
+ * @returns {number}
+ * - The final progress through the recipe
+ */
+ async execute(dish, startFrom=0, forkState={}) {
+ let op, input, output,
+ numJumps = 0,
+ numRegisters = forkState.numRegisters || 0;
+
+ log.debug(`[*] Executing recipe of ${this.opList.length} operations, starting at ${startFrom}`);
+
+ for (let i = startFrom; i < this.opList.length; i++) {
+ op = this.opList[i];
+ log.debug(`[${i}] ${op.name} ${JSON.stringify(op.ingValues)}`);
+ if (op.disabled) {
+ log.debug("Operation is disabled, skipping");
+ continue;
+ }
+ if (op.breakpoint) {
+ log.debug("Pausing at breakpoint");
+ return i;
+ }
+
+ try {
+ input = dish.get(op.inputType);
+ log.debug("Executing operation");
+
+ if (op.flowControl) {
+ // Package up the current state
+ let state = {
+ "progress": i,
+ "dish": dish,
+ "opList": this.opList,
+ "numJumps": numJumps,
+ "numRegisters": numRegisters,
+ "forkOffset": forkState.forkOffset || 0
+ };
+
+ state = await op.run(state);
+ i = state.progress;
+ numJumps = state.numJumps;
+ numRegisters = state.numRegisters;
+ } else {
+ output = await op.run(input, op.ingValues);
+ dish.set(output, op.outputType);
+ }
+ } catch (err) {
+ const e = typeof err == "string" ? { message: err } : err;
+
+ e.progress = i;
+ if (e.fileName) {
+ e.displayStr = op.name + " - " + e.name + " in " +
+ e.fileName + " on line " + e.lineNumber +
+ ".
Message: " + (e.displayStr || e.message);
+ } else {
+ e.displayStr = op.name + " - " + (e.displayStr || e.message);
+ }
+
+ throw e;
+ }
+ }
+
+ log.debug("Recipe complete");
+ return this.opList.length;
+ }
+
+
+ /**
+ * Returns the recipe configuration in string format.
+ *
+ * @returns {string}
+ */
+ toString() {
+ return JSON.stringify(this.config);
+ }
+
+
+ /**
+ * Creates a Recipe from a given configuration string.
+ *
+ * @param {string} recipeStr
+ */
+ fromString(recipeStr) {
+ const recipeConfig = JSON.parse(recipeStr);
+ this._parseConfig(recipeConfig);
+ }
+
+
+ /**
+ * Generates a list of all the highlight functions assigned to operations in the recipe, if the
+ * entire recipe supports highlighting.
+ *
+ * @returns {Object[]} highlights
+ * @returns {function} highlights[].f
+ * @returns {function} highlights[].b
+ * @returns {Object[]} highlights[].args
+ */
+ generateHighlightList() {
+ const highlights = [];
+
+ for (let i = 0; i < this.opList.length; i++) {
+ let op = this.opList[i];
+ if (op.disabled) continue;
+
+ // If any breakpoints are set, do not attempt to highlight
+ if (op.breakpoint) return false;
+
+ // If any of the operations do not support highlighting, fail immediately.
+ if (op.highlight === false || op.highlight === undefined) return false;
+
+ highlights.push({
+ f: op.highlight,
+ b: op.highlightReverse,
+ args: op.ingValues
+ });
+ }
+
+ return highlights;
+ }
+
+}
+
+export default Recipe;
diff --git a/src/core/Utils.js b/src/core/Utils.mjs
similarity index 91%
rename from src/core/Utils.js
rename to src/core/Utils.mjs
index 6d80c21b..116d856c 100755
--- a/src/core/Utils.js
+++ b/src/core/Utils.mjs
@@ -1,17 +1,17 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
import utf8 from "utf8";
import moment from "moment-timezone";
/**
* Utility functions for use in operations, the core framework and the stage.
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2016
- * @license Apache-2.0
- *
- * @namespace
*/
-const Utils = {
+class Utils {
/**
* Translates an ordinal into a character.
@@ -23,7 +23,7 @@ const Utils = {
* // returns 'a'
* Utils.chr(97);
*/
- chr: function(o) {
+ static chr(o) {
// Detect astral symbols
// Thanks to @mathiasbynens for this solution
// https://mathiasbynens.be/notes/javascript-unicode
@@ -35,7 +35,7 @@ const Utils = {
}
return String.fromCharCode(o);
- },
+ }
/**
@@ -48,7 +48,7 @@ const Utils = {
* // returns 97
* Utils.ord('a');
*/
- ord: function(c) {
+ static ord(c) {
// Detect astral symbols
// Thanks to @mathiasbynens for this solution
// https://mathiasbynens.be/notes/javascript-unicode
@@ -62,7 +62,7 @@ const Utils = {
}
return c.charCodeAt(0);
- },
+ }
/**
@@ -88,8 +88,7 @@ const Utils = {
* // returns ["t", "e", "s", "t", 1, 1, 1, 1]
* Utils.padBytesRight("test", 8, 1);
*/
- padBytesRight: function(arr, numBytes, padByte) {
- padByte = padByte || 0;
+ static padBytesRight(arr, numBytes, padByte=0) {
const paddedBytes = new Array(numBytes);
paddedBytes.fill(padByte);
@@ -98,7 +97,7 @@ const Utils = {
});
return paddedBytes;
- },
+ }
/**
@@ -116,13 +115,12 @@ const Utils = {
* // returns "A long s-"
* Utils.truncate("A long string", 9, "-");
*/
- truncate: function(str, max, suffix) {
- suffix = suffix || "...";
+ static truncate(str, max, suffix="...") {
if (str.length > max) {
str = str.slice(0, max - suffix.length) + suffix;
}
return str;
- },
+ }
/**
@@ -139,11 +137,10 @@ const Utils = {
* // returns "6e"
* Utils.hex(110);
*/
- hex: function(c, length) {
+ static hex(c, length=2) {
c = typeof c == "string" ? Utils.ord(c) : c;
- length = length || 2;
return c.toString(16).padStart(length, "0");
- },
+ }
/**
@@ -160,11 +157,10 @@ const Utils = {
* // returns "01101110"
* Utils.bin(110);
*/
- bin: function(c, length) {
+ static bin(c, length=8) {
c = typeof c == "string" ? Utils.ord(c) : c;
- length = length || 8;
return c.toString(2).padStart(length, "0");
- },
+ }
/**
@@ -174,7 +170,7 @@ const Utils = {
* @param {boolean} [preserveWs=false] - Whether or not to print whitespace.
* @returns {string}
*/
- printable: function(str, preserveWs) {
+ static printable(str, preserveWs=false) {
if (ENVIRONMENT_IS_WEB() && window.app && !window.app.options.treatAsUtf8) {
str = Utils.byteArrayToChars(Utils.strToByteArray(str));
}
@@ -185,7 +181,7 @@ const Utils = {
str = str.replace(re, ".");
if (!preserveWs) str = str.replace(wsRe, ".");
return str;
- },
+ }
/**
@@ -201,7 +197,7 @@ const Utils = {
* // returns "\n"
* Utils.parseEscapedChars("\\n");
*/
- parseEscapedChars: function(str) {
+ static parseEscapedChars(str) {
return str.replace(/(\\)?\\([bfnrtv0'"]|x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]{1,6}\})/g, function(m, a, b) {
if (a === "\\") return "\\"+b;
switch (b[0]) {
@@ -232,7 +228,7 @@ const Utils = {
return String.fromCharCode(parseInt(b.substr(1), 16));
}
});
- },
+ }
/**
@@ -246,9 +242,9 @@ const Utils = {
* // returns "\[example\]"
* Utils.escapeRegex("[example]");
*/
- escapeRegex: function(str) {
+ static escapeRegex(str) {
return str.replace(/([.*+?^=!:${}()|[\]/\\])/g, "\\$1");
- },
+ }
/**
@@ -267,7 +263,7 @@ const Utils = {
* // returns ["a", "b", "c", "d", "0", "-", "3"]
* Utils.expandAlphRange("a-d0\\-3")
*/
- expandAlphRange: function(alphStr) {
+ static expandAlphRange(alphStr) {
const alphArr = [];
for (let i = 0; i < alphStr.length; i++) {
@@ -291,7 +287,7 @@ const Utils = {
}
}
return alphArr;
- },
+ }
/**
@@ -312,7 +308,7 @@ const Utils = {
* // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
* Utils.convertToByteArray("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64");
*/
- convertToByteArray: function(str, type) {
+ static convertToByteArray(str, type) {
switch (type.toLowerCase()) {
case "hex":
return Utils.fromHex(str);
@@ -324,7 +320,7 @@ const Utils = {
default:
return Utils.strToByteArray(str);
}
- },
+ }
/**
@@ -345,7 +341,7 @@ const Utils = {
* // returns "ÐдÑавÑÑвÑйÑе"
* Utils.convertToByteString("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64");
*/
- convertToByteString: function(str, type) {
+ static convertToByteString(str, type) {
switch (type.toLowerCase()) {
case "hex":
return Utils.byteArrayToChars(Utils.fromHex(str));
@@ -357,7 +353,7 @@ const Utils = {
default:
return str;
}
- },
+ }
/**
@@ -374,7 +370,7 @@ const Utils = {
* // returns [228,189,160,229,165,189]
* Utils.strToByteArray("你好");
*/
- strToByteArray: function(str) {
+ static strToByteArray(str) {
const byteArray = new Array(str.length);
let i = str.length, b;
while (i--) {
@@ -384,7 +380,7 @@ const Utils = {
if (b > 255) return Utils.strToUtf8ByteArray(str);
}
return byteArray;
- },
+ }
/**
@@ -400,7 +396,7 @@ const Utils = {
* // returns [228,189,160,229,165,189]
* Utils.strToUtf8ByteArray("你好");
*/
- strToUtf8ByteArray: function(str) {
+ static strToUtf8ByteArray(str) {
const utf8Str = utf8.encode(str);
if (str.length !== utf8Str.length) {
@@ -412,7 +408,7 @@ const Utils = {
}
return Utils.strToByteArray(utf8Str);
- },
+ }
/**
@@ -428,7 +424,7 @@ const Utils = {
* // returns [20320,22909]
* Utils.strToCharcode("你好");
*/
- strToCharcode: function(str) {
+ static strToCharcode(str) {
const charcode = [];
for (let i = 0; i < str.length; i++) {
@@ -446,7 +442,7 @@ const Utils = {
}
return charcode;
- },
+ }
/**
@@ -462,7 +458,7 @@ const Utils = {
* // returns "你好"
* Utils.byteArrayToUtf8([228,189,160,229,165,189]);
*/
- byteArrayToUtf8: function(byteArray) {
+ static byteArrayToUtf8(byteArray) {
const str = Utils.byteArrayToChars(byteArray);
try {
const utf8Str = utf8.decode(str);
@@ -479,7 +475,7 @@ const Utils = {
// If it fails, treat it as ANSI
return str;
}
- },
+ }
/**
@@ -495,14 +491,14 @@ const Utils = {
* // returns "你好"
* Utils.byteArrayToChars([20320,22909]);
*/
- byteArrayToChars: function(byteArray) {
+ static byteArrayToChars(byteArray) {
if (!byteArray) return "";
let str = "";
for (let i = 0; i < byteArray.length;) {
str += String.fromCharCode(byteArray[i++]);
}
return str;
- },
+ }
/**
@@ -516,17 +512,17 @@ const Utils = {
* // returns "hello"
* Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer);
*/
- arrayBufferToStr: function(arrayBuffer, utf8=true) {
+ static arrayBufferToStr(arrayBuffer, utf8=true) {
const byteArray = Array.prototype.slice.call(new Uint8Array(arrayBuffer));
return utf8 ? Utils.byteArrayToUtf8(byteArray) : Utils.byteArrayToChars(byteArray);
- },
+ }
/**
* Base64's the input byte array using the given alphabet, returning a string.
*
* @param {byteArray|Uint8Array|string} data
- * @param {string} [alphabet]
+ * @param {string} [alphabet="A-Za-z0-9+/="]
* @returns {string}
*
* @example
@@ -536,15 +532,14 @@ const Utils = {
* // returns "SGVsbG8="
* Utils.toBase64("Hello");
*/
- toBase64: function(data, alphabet) {
+ static toBase64(data, alphabet="A-Za-z0-9+/=") {
if (!data) return "";
if (typeof data == "string") {
data = Utils.strToByteArray(data);
}
- alphabet = alphabet ?
- Utils.expandAlphRange(alphabet).join("") :
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+ alphabet = Utils.expandAlphRange(alphabet).join("");
+
let output = "",
chr1, chr2, chr3,
enc1, enc2, enc3, enc4,
@@ -571,14 +566,14 @@ const Utils = {
}
return output;
- },
+ }
/**
* UnBase64's the input string using the given alphabet, returning a byte array.
*
* @param {byteArray} data
- * @param {string} [alphabet]
+ * @param {string} [alphabet="A-Za-z0-9+/="]
* @param {string} [returnType="string"] - Either "string" or "byteArray"
* @param {boolean} [removeNonAlphChars=true]
* @returns {byteArray}
@@ -590,18 +585,12 @@ const Utils = {
* // returns [72, 101, 108, 108, 111]
* Utils.fromBase64("SGVsbG8=", null, "byteArray");
*/
- fromBase64: function(data, alphabet, returnType, removeNonAlphChars) {
- returnType = returnType || "string";
-
+ static fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", removeNonAlphChars=true) {
if (!data) {
return returnType === "string" ? "" : [];
}
- alphabet = alphabet ?
- Utils.expandAlphRange(alphabet).join("") :
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
- if (removeNonAlphChars === undefined)
- removeNonAlphChars = true;
+ alphabet = Utils.expandAlphRange(alphabet).join("");
let output = [],
chr1, chr2, chr3,
@@ -638,7 +627,7 @@ const Utils = {
}
return returnType === "string" ? Utils.byteArrayToUtf8(output) : output;
- },
+ }
/**
@@ -656,11 +645,9 @@ const Utils = {
* // returns "0a:14:1e"
* Utils.toHex([10,20,30], ":");
*/
- toHex: function(data, delim, padding) {
+ static toHex(data, delim=" ", padding=2) {
if (!data) return "";
- delim = typeof delim == "string" ? delim : " ";
- padding = padding || 2;
let output = "";
for (let i = 0; i < data.length; i++) {
@@ -675,7 +662,7 @@ const Utils = {
return output.slice(0, -delim.length);
else
return output;
- },
+ }
/**
@@ -688,7 +675,7 @@ const Utils = {
* // returns "0a141e"
* Utils.toHex([10,20,30]);
*/
- toHexFast: function(data) {
+ static toHexFast(data) {
if (!data) return "";
const output = [];
@@ -699,7 +686,7 @@ const Utils = {
}
return output.join("");
- },
+ }
/**
@@ -717,11 +704,10 @@ const Utils = {
* // returns [10,20,30]
* Utils.fromHex("0a:14:1e", "Colon");
*/
- fromHex: function(data, delim, byteLen) {
+ static fromHex(data, delim, byteLen=2) {
delim = delim || (data.indexOf(" ") >= 0 ? "Space" : "None");
- byteLen = byteLen || 2;
if (delim !== "None") {
- const delimRegex = Utils.regexRep[delim];
+ const delimRegex = Utils.regexRep(delim);
data = data.replace(delimRegex, "");
}
@@ -730,7 +716,7 @@ const Utils = {
output.push(parseInt(data.substr(i, byteLen), 16));
}
return output;
- },
+ }
/**
@@ -743,8 +729,7 @@ const Utils = {
* // returns [["head1", "head2"], ["data1", "data2"]]
* Utils.parseCSV("head1,head2\ndata1,data2");
*/
- parseCSV: function(data) {
-
+ static parseCSV(data) {
let b,
ignoreNext = false,
inString = false,
@@ -783,26 +768,27 @@ const Utils = {
}
return lines;
- },
+ }
/**
* Removes all HTML (or XML) tags from the input string.
*
* @param {string} htmlStr
- * @param {boolean} removeScriptAndStyle - Flag to specify whether to remove entire script or style blocks
+ * @param {boolean} [removeScriptAndStyle=false]
+ * - Flag to specify whether to remove entire script or style blocks
* @returns {string}
*
* @example
* // returns "Test"
* Utils.stripHtmlTags("