123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540 |
- import {camelCase, kebabCase, snakeCase} from "lodash";
- import vkbeautify from "vkbeautify";
- import {DOMParser} from "xmldom";
- import xpath from "xpath";
- import jpath from "jsonpath";
- import nwmatcher from "nwmatcher";
- import hljs from "highlight.js";
- /**
- * Code operations.
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2016
- * @license Apache-2.0
- *
- * @namespace
- */
- const Code = {
- /**
- * @constant
- * @default
- */
- LANGUAGES: ["auto detect"].concat(hljs.listLanguages()),
- /**
- * Syntax highlighter operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {html}
- */
- runSyntaxHighlight: function(input, args) {
- const language = args[0];
- if (language === "auto detect") {
- return hljs.highlightAuto(input).value;
- }
- return hljs.highlight(language, input, true).value;
- },
- /**
- * @constant
- * @default
- */
- BEAUTIFY_INDENT: "\\t",
- /**
- * XML Beautify operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runXmlBeautify: function(input, args) {
- const indentStr = args[0];
- return vkbeautify.xml(input, indentStr);
- },
- /**
- * JSON Beautify operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runJsonBeautify: function(input, args) {
- const indentStr = args[0];
- if (!input) return "";
- return vkbeautify.json(input, indentStr);
- },
- /**
- * CSS Beautify operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runCssBeautify: function(input, args) {
- const indentStr = args[0];
- return vkbeautify.css(input, indentStr);
- },
- /**
- * SQL Beautify operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runSqlBeautify: function(input, args) {
- const indentStr = args[0];
- return vkbeautify.sql(input, indentStr);
- },
- /**
- * @constant
- * @default
- */
- PRESERVE_COMMENTS: false,
- /**
- * XML Minify operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runXmlMinify: function(input, args) {
- const preserveComments = args[0];
- return vkbeautify.xmlmin(input, preserveComments);
- },
- /**
- * JSON Minify operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runJsonMinify: function(input, args) {
- if (!input) return "";
- return vkbeautify.jsonmin(input);
- },
- /**
- * CSS Minify operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runCssMinify: function(input, args) {
- const preserveComments = args[0];
- return vkbeautify.cssmin(input, preserveComments);
- },
- /**
- * SQL Minify operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runSqlMinify: function(input, args) {
- return vkbeautify.sqlmin(input);
- },
- /**
- * Generic Code Beautify operation.
- *
- * Yeeeaaah...
- *
- * I'm not proud of this code, but seriously, try writing a generic lexer and parser that
- * correctly generates an AST for multiple different languages. I have tried, and I can tell
- * you it's pretty much impossible.
- *
- * This basically works. That'll have to be good enough. It's not meant to produce working code,
- * just slightly more readable code.
- *
- * Things that don't work:
- * - For loop formatting
- * - Do-While loop formatting
- * - Switch/Case indentation
- * - Bit shift operators
- *
- * @author n1474335 [n1474335@gmail.com]
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runGenericBeautify: function(input, args) {
- let code = input,
- t = 0,
- preservedTokens = [],
- 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);
- }
- },
- /**
- * @constant
- * @default
- */
- XPATH_INITIAL: "",
- /**
- * @constant
- * @default
- */
- XPATH_DELIMITER: "\\n",
- /**
- * XPath expression operation.
- *
- * @author Mikescher (https://github.com/Mikescher | https://mikescher.com)
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runXpath: function(input, args) {
- let query = args[0],
- delimiter = args[1];
- let doc;
- try {
- doc = new DOMParser().parseFromString(input, "application/xml");
- } catch (err) {
- return "Invalid input XML.";
- }
- let nodes;
- try {
- nodes = xpath.select(query, doc);
- } catch (err) {
- return "Invalid XPath. Details:\n" + err.message;
- }
- const nodeToString = function(node) {
- return node.toString();
- };
- return nodes.map(nodeToString).join(delimiter);
- },
- /**
- * @constant
- * @default
- */
- JPATH_INITIAL: "",
- /**
- * @constant
- * @default
- */
- JPATH_DELIMITER: "\\n",
- /**
- * JPath expression operation.
- *
- * @author Matt C (matt@artemisbot.uk)
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runJpath: function(input, args) {
- let query = args[0],
- delimiter = args[1],
- results,
- obj;
- try {
- obj = JSON.parse(input);
- } catch (err) {
- return "Invalid input JSON: " + err.message;
- }
- try {
- results = jpath.query(obj, query);
- } catch (err) {
- return "Invalid JPath expression: " + err.message;
- }
- return results.map(result => JSON.stringify(result)).join(delimiter);
- },
- /**
- * @constant
- * @default
- */
- CSS_SELECTOR_INITIAL: "",
- /**
- * @constant
- * @default
- */
- CSS_QUERY_DELIMITER: "\\n",
- /**
- * CSS selector operation.
- *
- * @author Mikescher (https://github.com/Mikescher | https://mikescher.com)
- * @author n1474335 [n1474335@gmail.com]
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runCSSQuery: function(input, args) {
- let query = args[0],
- delimiter = args[1],
- parser = new DOMParser(),
- dom,
- result;
- if (!query.length || !input.length) {
- return "";
- }
- try {
- dom = parser.parseFromString(input);
- } catch (err) {
- return "Invalid input HTML.";
- }
- try {
- const matcher = nwmatcher({document: dom});
- result = matcher.select(query, dom);
- } catch (err) {
- return "Invalid CSS Selector. Details:\n" + err.message;
- }
- const nodeToString = function(node) {
- return node.toString();
- /* xmldom does not return the outerHTML value.
- switch (node.nodeType) {
- case node.ELEMENT_NODE: return node.outerHTML;
- case node.ATTRIBUTE_NODE: return node.value;
- case node.TEXT_NODE: return node.wholeText;
- case node.COMMENT_NODE: return node.data;
- case node.DOCUMENT_NODE: return node.outerHTML;
- default: throw new Error("Unknown Node Type: " + node.nodeType);
- }*/
- };
- return result
- .map(nodeToString)
- .join(delimiter);
- },
- /**
- * 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}
- */
- _replaceVariableNames(input, replacer) {
- const tokenRegex = /\\"|"(?:\\"|[^"])*"|(\b[a-z0-9\-_]+\b)/ig;
- return input.replace(tokenRegex, (...args) => {
- let match = args[0],
- quotes = args[1];
- if (!quotes) {
- return match;
- } else {
- return replacer(match);
- }
- });
- },
- /**
- * To Snake Case operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runToSnakeCase(input, args) {
- const smart = args[0];
- if (smart) {
- return Code._replaceVariableNames(input, snakeCase);
- } else {
- return snakeCase(input);
- }
- },
- /**
- * To Camel Case operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runToCamelCase(input, args) {
- const smart = args[0];
- if (smart) {
- return Code._replaceVariableNames(input, camelCase);
- } else {
- return camelCase(input);
- }
- },
- /**
- * To Kebab Case operation.
- *
- * @param {string} input
- * @param {Object[]} args
- * @returns {string}
- */
- runToKebabCase(input, args) {
- const smart = args[0];
- if (smart) {
- return Code._replaceVariableNames(input, kebabCase);
- } else {
- return kebabCase(input);
- }
- },
- };
- export default Code;
|