Merge pull request #1 from gaijinat/gaijinat-newops-1

Gaijinat new ops 1
This commit is contained in:
Gaijin.at 2023-05-09 20:58:03 +02:00 committed by GitHub
commit 4c57346ff9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 626 additions and 0 deletions

View file

@ -3,6 +3,17 @@
"name": "Favourites",
"ops": []
},
{
"name": "Gaijin",
"ops": [
"ByteAnalyser",
"Output",
"Pad",
"Prepend / Append",
"Store / Restore Input",
"Trim"
]
},
{
"name": "Data format",
"ops": [

View file

@ -0,0 +1,208 @@
/**
* @author gaijinat [web@gaijin.at]
* @copyright Crown Copyright 2023
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
/**
* ByteAnalyser operation
*/
class ByteAnalyser extends Operation {
/**
* ByteAnalyser constructor
*/
constructor() {
super();
this.name = "ByteAnalyser";
this.module = "Default";
this.description = "Analyses the bytes in the input and displays statistics about them.<br><br>The histogram shows the distribution of the individual bytes.";
this.infoURL = "";
this.inputType = "ArrayBuffer";
this.outputType = "JSON";
this.presentType = "html";
this.args = [
{
"name": "Show 0%s",
"type": "boolean",
"value": false
},
{
"name": "Sort by count",
"type": "boolean",
"value": true
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {JSON}
*/
run(input, args) {
const data = new Uint8Array(input);
const byteTable = [],
byteCount = data.length;
let byteRepresented = 0,
byteRepresentedPercent = 0.0,
byteNotRepresented = 256,
byteNotRepresentedPercent = 0.0,
asciiCharsRepresented = "",
maxCount = 0,
i;
// Initialisation of byte information data
for (i = 0; i < 256; i++) {
byteTable[i] = {
dec: i,
char: this.printableChar(i),
hex: i.toString(16).toUpperCase().padStart(2, "0"),
bin: i.toString(2).padStart(8, "0"),
count: 0,
percent: 0.0,
proportion: 0.0,
};
}
// Count bytes in data
for (i = 0; i < byteCount; ++i) {
byteTable[data[i]].count++;
}
// Get the count of the most represented byte (100% in statistics)
for (i = 0; i < byteTable.length; ++i) {
if (byteTable[i].count > maxCount) maxCount = byteTable[i].count;
}
// Calculate percentage occurrence of bytes
const divPercent = (100 / byteCount);
const divProportion = (100 / maxCount);
for (i = 0; i < byteTable.length; ++i) {
byteTable[i].percent = (byteTable[i].count * divPercent).toFixed(4);
byteTable[i].proportion = (byteTable[i].count * divProportion).toFixed(2);
if (byteTable[i].count > 0) {
// Collect number of represented bytes
byteRepresented++;
// Collect represented printable ASCII characters
if ((i >= 32) && (i <= 127)) {
asciiCharsRepresented += byteTable[i].char;
}
}
}
byteRepresentedPercent = (byteRepresented * (100 / 256)).toFixed(2);
byteNotRepresented = 256 - byteRepresented;
byteNotRepresentedPercent = (byteNotRepresented * (100 / 256)).toFixed(2);
// console.log(byteTable);
return {
"byteTable": byteTable,
"byteCount": byteCount,
"byteRepresented": byteRepresented,
"byteRepresentedPercent": byteRepresentedPercent,
"byteNotRepresented": byteNotRepresented,
"byteNotRepresentedPercent": byteNotRepresentedPercent,
"asciiCharsRepresented": asciiCharsRepresented,
};
}
/**
* Displays the statistics for web apps.
*
* @param {json} statistics
* @returns {html}
*/
present(stat, args) {
const [showZeros, sortCount] = args;
let output = "",
i;
const clsc = ' class="text-center" style="background-color: var(--secondary-background-colour);"',
clsr = ' class="text-right"';
// Histogram
output += '<div style="border:1px solid var(--table-border-colour);">';
output += '<table style="border:0; table-layout:fixed; border-collapse:collapse; border-spacing:0; padding:0; width:100%;">';
for (i = 0; i < 256; i++) {
output += '<td title="';
output += `Character ${stat.byteTable[i].dec.toString()} (0x${stat.byteTable[i].hex}) &quot;${this.htmlEntities(stat.byteTable[i].char)}&quot;\nCount: ${stat.byteTable[i].count.toLocaleString("en")} (${stat.byteTable[i].percent.toLocaleString("en")}%)`;
output += '" style="vertical-align:bottom; padding:0; background-color: var(--';
if (i % 2) output += "primary"; else output += "secondary";
output += `-background-colour);"><div style="height:${(stat.byteTable[i].proportion * 2).toString()}px;background-color:var(--primary-font-colour);"></div></td>`;
}
output += "</tr></table></div>\n";
// Overview
output += '<table class="table table-hover table-sm table-bordered" style="table-layout:fixed; width:auto;">';
output += `<tr><td${clsr}>Number of bytes total:</td><td colspan="2"><b>${stat.byteCount.toLocaleString("en")}</b></td></tr>`;
output += `<tr><td${clsr}>Number of bytes represented:</td><td${clsr}><b>${stat.byteRepresented.toLocaleString("en")}</b></td><td${clsr}>(${stat.byteRepresentedPercent.toLocaleString("en")}%)</td></tr>`;
output += `<tr><td${clsr}>Number of bytes not represented:</td><td${clsr}><b>${stat.byteNotRepresented.toLocaleString("en")}</b></td><td${clsr}>(${stat.byteNotRepresentedPercent.toLocaleString("en")}%)</td></tr>`;
output += "</table>\n";
output += "<p><b>Represented printable ASCII characters:</b><br>" + this.htmlEntities(stat.asciiCharsRepresented) + "</p>\n";
// Details
if (sortCount) {
stat.byteTable.sort(this.compareByteTableItemsCount);
}
output += '<table class="table table-hover table-sm table-bordered" style="table-layout:fixed; width:auto;">';
output += `<tr><th${clsc}>Binary</th><th${clsc}>Hex</th><th${clsc}>Code</th><th${clsc}>Char</th><th${clsc}>Count</th><th${clsc}>Percent</th></tr>`;
for (i = 0; i < 256; i++) {
if (!showZeros && (stat.byteTable[i].count === 0)) continue;
output += `<tr><td${clsr}>${stat.byteTable[i].bin}</td><td${clsr}>${stat.byteTable[i].hex}</td>`;
output += `<td${clsr}>${stat.byteTable[i].dec.toString()}</td><td${clsc}><b>${this.htmlEntities(stat.byteTable[i].char)}</b></td>`;
output += `<td${clsr}>${stat.byteTable[i].count.toLocaleString("en")}</td><td${clsr}>${stat.byteTable[i].percent.toLocaleString("en")}</td></tr>`;
}
output += "</table>\n";
return output;
}
/**
* Gets the printable character for a byte.
*
* @param {UInt8} charCode
* @returns {html}
*/
printableChar(charCode) {
if (charCode < 32) return " ";
if (charCode === 127) return " ";
return String.fromCharCode(charCode);
}
/**
* Compare byteTable items by count.
*/
compareByteTableItemsCount(a, b) {
if (a.count > b.count) {
return -1;
} else if (a.count < b.count) {
return 1;
} else {
return 0;
}
}
/**
* Converts reserved characters to HTML entities.
*
* @param {string} string
* @returns {string}
*/
htmlEntities(string) {
return String(string).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
}
export default ByteAnalyser;

View file

@ -0,0 +1,53 @@
/**
* @author gaijinat [web@gaijin.at]
* @copyright Crown Copyright 2023
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
/**
* Output operation
*/
class Output extends Operation {
/**
* Output constructor
*/
constructor() {
super();
this.name = "Output";
this.module = "Default";
this.description = "Outputs the entered text.<br><br>This is useful to output text with stored registers.<br><br>Example:<br>Assuming <code>$R0</code> is <code>test</code>, the string:<br><code>Value=$R0</code><br>will output:<br><code>Value=test</code>";
this.infoURL = "";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Output",
type: "toggleString",
value: "",
toggleValues: ["Simple string", "Extended (\\n, \\t, \\x...)"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const typeOutput = args[0].option;
let strOutput = args[0].string;
if (typeOutput.startsWith("Extended")) strOutput = Utils.parseEscapedChars(strOutput);
return strOutput;
}
}
export default Output;

View file

@ -0,0 +1,113 @@
/**
* @author gaijinat [web@gaijin.at]
* @copyright Crown Copyright 2023
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
/**
* Pad operation
*/
class Pad extends Operation {
/**
* Pad constructor
*/
constructor() {
super();
this.name = "Pad";
this.module = "Default";
this.description = "Fills the input with one or more characters until the specified length is reached.";
this.infoURL = "";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Position",
type: "option",
value: ["Start", "End", "Both"]
},
{
name: "Length",
type: "number",
value: 0
},
{
name: "String",
type: "toggleString",
value: "",
toggleValues: ["Simple string", "Extended (\\n, \\t, \\x...)"]
},
{
name: "Apply to",
type: "option",
value: ["Input", "Lines"],
defaultIndex: 1
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [position, padLength, {option: type}, applyTo] = args;
let padCharacters = args[2].string;
let output = "";
let joinCharLenght = 0;
if (type.startsWith("Extended")) padCharacters = Utils.parseEscapedChars(padCharacters);
if (padCharacters === "") padCharacters = " ";
if (applyTo === "Lines") {
input = input.split("\n");
joinCharLenght = 1;
for (let i = 0; i < input.length; i++) {
output += this.padEx(input[i], position, padLength, padCharacters) + "\n";
}
} else {
output = this.padEx(input, position, padLength, padCharacters);
}
if (output === "") {
return input;
} else {
if (joinCharLenght > 0) output = output.slice(0, output.length - joinCharLenght);
return output;
}
}
/**
* @param {string} input
* @param {integer} position
* @param {integer} padLength
* @param {string} padCharacters
* @returns {string}
*/
padEx(input, position, padLength, padCharacters) {
let spaceLength, padStartLength;
let output = "";
if (position === "Start") {
output = input.padStart(padLength, padCharacters);
} else if (position === "End") {
output = input.padEnd(padLength, padCharacters);
} else if (position === "Both") {
spaceLength = padLength - input.length;
if (spaceLength > 0) {
padStartLength = Math.floor(spaceLength / 2) + input.length;
output = input.padStart(padStartLength, padCharacters).padEnd(padLength, padCharacters);
}
}
return output;
}
}
export default Pad;

View file

@ -0,0 +1,81 @@
/**
* @author gaijinat [web@gaijin.at]
* @copyright Crown Copyright 2023
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
/**
* Prepend / Append operation
*/
class PrependAppend extends Operation {
/**
* PrependAppend constructor
*/
constructor() {
super();
this.name = "Prepend / Append";
this.module = "Default";
this.description = "Adds the specified text to the beginning and/or end of each line, character or the entire input.<br><br>Includes support for simple strings and extended strings (which support \\n, \\r, \\t, \\b, \\f and escaped hex bytes using \\x notation, e.g. \\x00 for a null byte).";
this.infoURL = "";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Prepend",
type: "toggleString",
value: "",
toggleValues: ["Simple string", "Extended (\\n, \\t, \\x...)"]
},
{
name: "Append",
type: "toggleString",
value: "",
toggleValues: ["Simple string", "Extended (\\n, \\t, \\x...)"]
},
{
name: "Apply to",
type: "option",
value: ["Input", "Lines", "Characters"],
defaultIndex: 1
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [{option: typePrepend}, {option: typeAppend}, applyTo] = args;
let [{string: strPrepend}, {string: strAppend}] = args;
let output = "";
let joinChar = "";
if (typePrepend.startsWith("Extended")) strPrepend = Utils.parseEscapedChars(strPrepend);
if (typeAppend.startsWith("Extended")) strAppend = Utils.parseEscapedChars(strAppend);
if (applyTo === "Input") {
output = strPrepend + input + strAppend;
} else {
if (applyTo === "Lines") {
input = input.split("\n");
joinChar = "\n";
}
for (let i = 0; i < input.length; i++) {
output += strPrepend + input[i] + strAppend + joinChar;
}
}
if (joinChar.length > 0) output = output.slice(0, output.length - joinChar.length);
return output;
}
}
export default PrependAppend;

View file

@ -0,0 +1,99 @@
/**
* @author gaijinat [web@gaijin.at]
* @copyright Crown Copyright 2023
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import Dish from "../Dish.mjs";
/**
* StoreRestoreInput operation
*/
class StoreRestoreInput extends Operation {
static variables = {};
/**
* StoreRestoreInput constructor
*/
constructor() {
super();
this.name = "Store / Restore Input";
this.flowControl = true;
this.module = "Default";
this.description = "Stores the input value and restores it later as output.<br><br><code>Store</code> stores the input under the given name.<br><code>Restore</code> restores the input with the given name as output.<br><code>Clear</code> removes the stored input with the given name. Without a name, all stored inputs will be removed.<br><br>You should deactivate 'Auto Bake' for this operation.";
this.infoURL = "";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Mode",
type: "option",
value: ["Clear", "Store", "Restore"]
},
{
name: "Name",
type: "string",
value: ""
},
];
}
/**
* @param {Object} state - The current state of the recipe.
* @param {number} state.progress - The current position in the recipe.
* @param {Dish} state.dish - The Dish being operated on.
* @param {Operation[]} state.opList - The list of operations in the recipe.
* @returns {Object} The updated state of the recipe.
*/
async run(state) {
const ings = state.opList[state.progress].ingValues;
const [mode, varName] = ings;
const input = await state.dish.get(Dish.STRING);
let firstOpIndex = -1;
// Find the first index that matches this operation and clear the variables
for (let i = 0; i < state.opList.length; i++) {
if (state.opList[i].name === this.name) {
firstOpIndex = i;
break;
}
}
if (state.progress === firstOpIndex) {
StoreRestoreInput.variables = {};
}
if (mode === "Clear") {
if (varName === "") {
StoreRestoreInput.variables = {};
} else {
if (StoreRestoreInput.variables[varName] !== undefined) {
delete StoreRestoreInput.variables[varName];
}
}
} else if (varName && (mode === "Store")) {
StoreRestoreInput.variables[varName] = input;
} else if (varName && (mode === "Restore")) {
if (StoreRestoreInput.variables[varName] !== undefined) {
state.dish.set(StoreRestoreInput.variables[varName], Dish.STRING);
return state;
}
}
// console.log(StoreRestoreInput.variables);
return state;
}
}
export default StoreRestoreInput;

View file

@ -0,0 +1,61 @@
/**
* @author gaijinat [web@gaijin.at]
* @copyright Crown Copyright 2023
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
/**
* Trim operation
*/
class Trim extends Operation {
/**
* Trim constructor
*/
constructor() {
super();
this.name = "Trim";
this.module = "Default";
this.description = "Removes all whitespaces and line breaks from the beginning, end or both of the input data.";
this.infoURL = "";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Position",
type: "option",
value: ["Start", "End", "Both"],
defaultIndex: 2
},
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [position] = args;
let output = "";
switch (position) {
case "Start":
output = input.trimStart();
break;
case "End":
output = input.trimEnd();
break;
default:
output = input.trim();
}
return output;
}
}
export default Trim;