Explorar o código

ESM: Ported case converters, generic beautifier and syntax highlighting

Matt C %!s(int64=7) %!d(string=hai) anos
pai
achega
905bc6699e

+ 31 - 0
src/core/lib/Code.mjs

@@ -0,0 +1,31 @@
+/**
+ * Code functions.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ *
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ *
+ */
+
+/**
+ * This tries to rename variable names in a code snippet according to a function.
+ *
+ * @param {string} input
+ * @param {function} replacer - this function will be fed the token which should be renamed.
+ * @returns {string}
+ */
+export function replaceVariableNames(input, replacer) {
+    const tokenRegex = /\\"|"(?:\\"|[^"])*"|(\b[a-z0-9\-_]+\b)/ig;
+
+    return input.replace(tokenRegex, (...args) => {
+        const match = args[0],
+            quotes = args[1];
+
+        if (!quotes) {
+            return match;
+        } else {
+            return replacer(match);
+        }
+    });
+}

+ 161 - 0
src/core/operations/GenericCodeBeautify.mjs

@@ -0,0 +1,161 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+
+/**
+ * Generic Code Beautify operation
+ */
+class GenericCodeBeautify extends Operation {
+
+    /**
+     * GenericCodeBeautify constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Generic Code Beautify";
+        this.module = "Code";
+        this.description = "Attempts to pretty print C-style languages such as C, C++, C#, Java, PHP, JavaScript etc.<br><br>This will not do a perfect job, and the resulting code may not work any more. This operation is designed purely to make obfuscated or minified code more easy to read and understand.<br><br>Things which will not work properly:<ul><li>For loop formatting</li><li>Do-While loop formatting</li><li>Switch/Case indentation</li><li>Certain bit shift operators</li></ul>";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const preservedTokens = [];
+        let code = input,
+            t = 0,
+            m;
+
+        // Remove strings
+        const sstrings = /'([^'\\]|\\.)*'/g;
+        while ((m = sstrings.exec(code))) {
+            code = preserveToken(code, m, t++);
+            sstrings.lastIndex = m.index;
+        }
+
+        const dstrings = /"([^"\\]|\\.)*"/g;
+        while ((m = dstrings.exec(code))) {
+            code = preserveToken(code, m, t++);
+            dstrings.lastIndex = m.index;
+        }
+
+        // Remove comments
+        const scomments = /\/\/[^\n\r]*/g;
+        while ((m = scomments.exec(code))) {
+            code = preserveToken(code, m, t++);
+            scomments.lastIndex = m.index;
+        }
+
+        const mcomments = /\/\*[\s\S]*?\*\//gm;
+        while ((m = mcomments.exec(code))) {
+            code = preserveToken(code, m, t++);
+            mcomments.lastIndex = m.index;
+        }
+
+        const hcomments = /(^|\n)#[^\n\r#]+/g;
+        while ((m = hcomments.exec(code))) {
+            code = preserveToken(code, m, t++);
+            hcomments.lastIndex = m.index;
+        }
+
+        // Remove regexes
+        const regexes = /\/.*?[^\\]\/[gim]{0,3}/gi;
+        while ((m = regexes.exec(code))) {
+            code = preserveToken(code, m, t++);
+            regexes.lastIndex = m.index;
+        }
+
+        code = code
+            // Create newlines after ;
+            .replace(/;/g, ";\n")
+            // Create newlines after { and around }
+            .replace(/{/g, "{\n")
+            .replace(/}/g, "\n}\n")
+            // Remove carriage returns
+            .replace(/\r/g, "")
+            // Remove all indentation
+            .replace(/^\s+/g, "")
+            .replace(/\n\s+/g, "\n")
+            // Remove trailing spaces
+            .replace(/\s*$/g, "")
+            .replace(/\n{/g, "{");
+
+        // Indent
+        let i = 0,
+            level = 0,
+            indent;
+        while (i < code.length) {
+            switch (code[i]) {
+                case "{":
+                    level++;
+                    break;
+                case "\n":
+                    if (i+1 >= code.length) break;
+
+                    if (code[i+1] === "}") level--;
+                    indent = (level >= 0) ? Array(level*4+1).join(" ") : "";
+
+                    code = code.substring(0, i+1) + indent + code.substring(i+1);
+                    if (level > 0) i += level*4;
+                    break;
+            }
+            i++;
+        }
+
+        code = code
+            // Add strategic spaces
+            .replace(/\s*([!<>=+-/*]?)=\s*/g, " $1= ")
+            .replace(/\s*<([=]?)\s*/g, " <$1 ")
+            .replace(/\s*>([=]?)\s*/g, " >$1 ")
+            .replace(/([^+])\+([^+=])/g, "$1 + $2")
+            .replace(/([^-])-([^-=])/g, "$1 - $2")
+            .replace(/([^*])\*([^*=])/g, "$1 * $2")
+            .replace(/([^/])\/([^/=])/g, "$1 / $2")
+            .replace(/\s*,\s*/g, ", ")
+            .replace(/\s*{/g, " {")
+            .replace(/}\n/g, "}\n\n")
+            // Hacky horribleness
+            .replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)\s*\n([^{])/gim, "$1 ($2)\n    $3")
+            .replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)([^{])/gim, "$1 ($2) $3")
+            .replace(/else\s*\n([^{])/gim, "else\n    $1")
+            .replace(/else\s+([^{])/gim, "else $1")
+            // Remove strategic spaces
+            .replace(/\s+;/g, ";")
+            .replace(/\{\s+\}/g, "{}")
+            .replace(/\[\s+\]/g, "[]")
+            .replace(/}\s*(else|catch|except|finally|elif|elseif|else if)/gi, "} $1");
+
+        // Replace preserved tokens
+        const ptokens = /###preservedToken(\d+)###/g;
+        while ((m = ptokens.exec(code))) {
+            const ti = parseInt(m[1], 10);
+            code = code.substring(0, m.index) + preservedTokens[ti] + code.substring(m.index + m[0].length);
+            ptokens.lastIndex = m.index;
+        }
+
+        return code;
+
+        /**
+         * Replaces a matched token with a placeholder value.
+         */
+        function preserveToken(str, match, t) {
+            preservedTokens[t] = match[0];
+            return str.substring(0, match.index) +
+                "###preservedToken" + t + "###" +
+                str.substring(match.index + match[0].length);
+        }
+    }
+
+}
+
+export default GenericCodeBeautify;

+ 78 - 0
src/core/operations/SyntaxHighlighter.mjs

@@ -0,0 +1,78 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import hljs from "highlight.js";
+
+/**
+ * Syntax highlighter operation
+ */
+class SyntaxHighlighter extends Operation {
+
+    /**
+     * SyntaxHighlighter constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Syntax highlighter";
+        this.module = "Code";
+        this.description = "Adds syntax highlighting to a range of source code languages. Note that this will not indent the code. Use one of the 'Beautify' operations for that.";
+        this.inputType = "string";
+        this.outputType = "html";
+        this.args = [
+            {
+                "name": "Language",
+                "type": "option",
+                "value": ["auto detect"].concat(hljs.listLanguages())
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {html}
+     */
+    run(input, args) {
+        const language = args[0];
+
+        if (language === "auto detect") {
+            return hljs.highlightAuto(input).value;
+        }
+
+        return hljs.highlight(language, input, true).value;
+    }
+
+    /**
+     * Highlight Syntax highlighter
+     *
+     * @param {Object[]} pos
+     * @param {number} pos[].start
+     * @param {number} pos[].end
+     * @param {Object[]} args
+     * @returns {Object[]} pos
+     */
+    highlight(pos, args) {
+        return pos;
+    }
+
+    /**
+     * Highlight Syntax highlighter in reverse
+     *
+     * @param {Object[]} pos
+     * @param {number} pos[].start
+     * @param {number} pos[].end
+     * @param {Object[]} args
+     * @returns {Object[]} pos
+     */
+    highlightReverse(pos, args) {
+        return pos;
+    }
+
+}
+
+export default SyntaxHighlighter;

+ 53 - 0
src/core/operations/ToCamelCase.mjs

@@ -0,0 +1,53 @@
+/**
+ * @author tlwr [toby@toby.codes]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ */
+
+import { camelCase } from "lodash";
+import Operation from "../Operation";
+import { replaceVariableNames } from "../lib/Code";
+
+/**
+ * To Camel case operation
+ */
+class ToCamelCase extends Operation {
+
+    /**
+     * ToCamelCase constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "To Camel case";
+        this.module = "Code";
+        this.description = "Converts the input string to camel case.\n<br><br>\nCamel case is all lower case except letters after word boundaries which are uppercase.\n<br><br>\ne.g. thisIsCamelCase\n<br><br>\n'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Attempt to be context aware",
+                "type": "boolean",
+                "value": false
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const smart = args[0];
+
+        if (smart) {
+            return replaceVariableNames(input, camelCase);
+        } else {
+            return camelCase(input);
+        }
+    }
+
+}
+
+export default ToCamelCase;

+ 53 - 0
src/core/operations/ToKebabCase.mjs

@@ -0,0 +1,53 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import { kebabCase } from "lodash";
+import Operation from "../Operation";
+import { replaceVariableNames } from "../lib/Code";
+
+/**
+ * To Kebab case operation
+ */
+class ToKebabCase extends Operation {
+
+    /**
+     * ToKebabCase constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "To Kebab case";
+        this.module = "Code";
+        this.description = "Converts the input string to kebab case.\n<br><br>\nKebab case is all lower case with dashes as word boundaries.\n<br><br>\ne.g. this-is-kebab-case\n<br><br>\n'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Attempt to be context aware",
+                "type": "boolean",
+                "value": false
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const smart = args[0];
+
+        if (smart) {
+            return replaceVariableNames(input, kebabCase);
+        } else {
+            return kebabCase(input);
+        }
+    }
+
+}
+
+export default ToKebabCase;

+ 52 - 0
src/core/operations/ToSnakeCase.mjs

@@ -0,0 +1,52 @@
+/**
+ * @author tlwr [toby@toby.codes]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ */
+
+import { snakeCase } from "lodash";
+import Operation from "../Operation";
+import { replaceVariableNames } from "../lib/Code";
+
+/**
+ * To Snake case operation
+ */
+class ToSnakeCase extends Operation {
+
+    /**
+     * ToSnakeCase constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "To Snake case";
+        this.module = "Code";
+        this.description = "Converts the input string to snake case.\n<br><br>\nSnake case is all lower case with underscores as word boundaries.\n<br><br>\ne.g. this_is_snake_case\n<br><br>\n'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Attempt to be context aware",
+                "type": "boolean",
+                "value": false
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const smart = args[0];
+
+        if (smart) {
+            return replaceVariableNames(input, snakeCase);
+        } else {
+            return snakeCase(input);
+        }
+    }
+}
+
+export default ToSnakeCase;