From 861313e49369e27552f09984c73b79dae87a72e3 Mon Sep 17 00:00:00 2001 From: Oliver Mitchell Date: Wed, 6 May 2026 02:53:23 +0930 Subject: [PATCH] feat: Add string insertion operators Append, Prepend, and Insert At - Added Append operator to insert text at the end of the input - Added Prepend operator to insert text at the start of the input - Added Insert At operator to insert text at a given index within the input - Added tests for all new operations. --- src/core/config/Categories.json | 5 +- src/core/operations/Append.mjs | 64 +++++++++++++++++++++ src/core/operations/InsertAt.mjs | 88 +++++++++++++++++++++++++++++ src/core/operations/Prepend.mjs | 64 +++++++++++++++++++++ tests/operations/tests/Append.mjs | 42 ++++++++++++++ tests/operations/tests/InsertAt.mjs | 66 ++++++++++++++++++++++ tests/operations/tests/Prepend.mjs | 42 ++++++++++++++ 7 files changed, 370 insertions(+), 1 deletion(-) create mode 100644 src/core/operations/Append.mjs create mode 100644 src/core/operations/InsertAt.mjs create mode 100644 src/core/operations/Prepend.mjs create mode 100644 tests/operations/tests/Append.mjs create mode 100644 tests/operations/tests/InsertAt.mjs create mode 100644 tests/operations/tests/Prepend.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index ab1dafb0..cea6ec3b 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -349,7 +349,10 @@ "Sleep", "File Tree", "Take nth bytes", - "Drop nth bytes" + "Drop nth bytes", + "Append", + "Prepend", + "Insert At" ] }, { diff --git a/src/core/operations/Append.mjs b/src/core/operations/Append.mjs new file mode 100644 index 00000000..640effc9 --- /dev/null +++ b/src/core/operations/Append.mjs @@ -0,0 +1,64 @@ +/** + * @author oliver-mitchell [oliver@polymerlabs.dev] + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { DELIM_OPTIONS } from "../lib/Delim.mjs"; + +/** + * Append operation. + */ +class Append extends Operation { + /** + * Append constructor. + */ + constructor() { + super(); + + this.name = "Append"; + this.module = "Default"; + this.description = "Add characters to the end of the input string, optionally with respect to a delimeter."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Text", + "type": "binaryString", + "value": "" + }, + { + "name": "Delimiter", + "type": "option", + "value": ["None", ...DELIM_OPTIONS] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [text, delim] = args; + + if (delim === "None") { + return input + text; + } + + const output = [], + delimChar = Utils.charRep(delim), + subs = input.split(Utils.charRep(delim)); + + for (let i = 0; i < subs.length; i++) { + output.push(subs[i] + text); + } + + return output.join(delimChar); + } +} + +export default Append; diff --git a/src/core/operations/InsertAt.mjs b/src/core/operations/InsertAt.mjs new file mode 100644 index 00000000..ff723a9d --- /dev/null +++ b/src/core/operations/InsertAt.mjs @@ -0,0 +1,88 @@ +/** + * @author oliver-mitchell [oliver@polymerlabs.dev] + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { DELIM_OPTIONS } from "../lib/Delim.mjs"; + +/** + * Insert At operation. + */ +class InsertAt extends Operation { + /** + * InsertAt constructor. + */ + constructor() { + super(); + + this.name = "Insert At"; + this.module = "Default"; + this.description = "Add characters to input string at a given index, optionally with respect to a delimeter."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Text", + "type": "binaryString", + "value": "" + }, + { + "name": "Index", + "type": "number", + "value": 0, + "min": 0 + }, + { + "name": "Delimiter", + "type": "option", + "value": ["None", ...DELIM_OPTIONS] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [text, index, delim] = args; + + if (delim === "None") { + return InsertAt.performInsert(input, text, index); + } + + const output = [], + delimChar = Utils.charRep(delim), + subs = input.split(Utils.charRep(delim)); + + for (let i = 0; i < subs.length; i++) { + output.push(InsertAt.performInsert(subs[i], text, index)); + } + + return output.join(delimChar); + } + + /** + * @param {string} input + * @param {string} text + * @param {number} index + * @returns {string} + */ + static performInsert(input, text, index) { + if (index >= input.length) { + return input + text; + } + + if (index <= 0) { + return text + input; + } + + return input.slice(0, index) + text + input.slice(index); + } +} + +export default InsertAt; diff --git a/src/core/operations/Prepend.mjs b/src/core/operations/Prepend.mjs new file mode 100644 index 00000000..25f8e376 --- /dev/null +++ b/src/core/operations/Prepend.mjs @@ -0,0 +1,64 @@ +/** + * @author oliver-mitchell [oliver@polymerlabs.dev] + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { DELIM_OPTIONS } from "../lib/Delim.mjs"; + +/** + * Prepend operation. + */ +class Prepend extends Operation { + /** + * Prepend constructor. + */ + constructor() { + super(); + + this.name = "Prepend"; + this.module = "Default"; + this.description = "Add characters to the start of the input string, optionally with respect to a delimeter."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Text", + "type": "binaryString", + "value": "" + }, + { + "name": "Delimiter", + "type": "option", + "value": ["None", ...DELIM_OPTIONS] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [text, delim] = args; + + if (delim === "None") { + return input + text; + } + + const output = [], + delimChar = Utils.charRep(delim), + subs = input.split(Utils.charRep(delim)); + + for (let i = 0; i < subs.length; i++) { + output.push(text + subs[i]); + } + + return output.join(delimChar); + } +} + +export default Prepend; diff --git a/tests/operations/tests/Append.mjs b/tests/operations/tests/Append.mjs new file mode 100644 index 00000000..6998d4b7 --- /dev/null +++ b/tests/operations/tests/Append.mjs @@ -0,0 +1,42 @@ +/** + * Media operation tests. + * @author oliver-mitchell [oliver@polymerlabs.dev] + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Append empty", + input: "test", + expectedOutput: "test", + recipeConfig: [ + { op: "Append", args: ["", "None"] } // [text, delimiter] + ] + }, + { + name: "Append empty input", + input: "", + expectedOutput: "test", + recipeConfig: [ + { op: "Append", args: ["test", "None"] } // [text, delimiter] + ] + }, + { + name: "Append with input and text", + input: "hello ", + expectedOutput: "hello world", + recipeConfig: [ + { op: "Append", args: ["world", "None"] } // [text, delimiter] + ] + }, + { + name: "Append with space delim", + input: "hello world", + expectedOutput: "hello: world:", + recipeConfig: [ + { op: "Append", args: [":", "Space"] } // [text, delimiter] + ] + } +]); diff --git a/tests/operations/tests/InsertAt.mjs b/tests/operations/tests/InsertAt.mjs new file mode 100644 index 00000000..58e2e187 --- /dev/null +++ b/tests/operations/tests/InsertAt.mjs @@ -0,0 +1,66 @@ +/** + * Media operation tests. + * @author oliver-mitchell [oliver@polymerlabs.dev] + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Insert At empty", + input: "test", + expectedOutput: "test", + recipeConfig: [ + { op: "Insert At", args: ["", 0, "None"] } // [text, index, delimiter] + ] + }, + { + name: "Insert At empty input", + input: "", + expectedOutput: "test", + recipeConfig: [ + { op: "Insert At", args: ["test", 0, "None"] } // [text, index, delimiter] + ] + }, + { + name: "Insert At with input and text", + input: "hello ", + expectedOutput: "hello world", + recipeConfig: [ + { op: "Insert At", args: ["world", 6, "None"] } // [text, index, delimiter] + ] + }, + { + name: "Insert At negative", + input: " world", + expectedOutput: "hello world", + recipeConfig: [ + { op: "Insert At", args: ["hello", -5, "None"] } // [text, index, delimiter] + ] + }, + { + name: "Insert At beyond string length", + input: "hello ", + expectedOutput: "hello world", + recipeConfig: [ + { op: "Insert At", args: ["world", 100, "None"] } // [text, index, delimiter] + ] + }, + { + name: "Insert At inside", + input: "heo", + expectedOutput: "hello", + recipeConfig: [ + { op: "Insert At", args: ["ll", 2, "None"] } // [text, index, delimiter] + ] + }, + { + name: "Insert At with space delim", + input: "hello world", + expectedOutput: "he:llo wo:rld", + recipeConfig: [ + { op: "Insert At", args: [":", 2, "Space"] } // [text, index, delimiter] + ] + } +]); diff --git a/tests/operations/tests/Prepend.mjs b/tests/operations/tests/Prepend.mjs new file mode 100644 index 00000000..67655d41 --- /dev/null +++ b/tests/operations/tests/Prepend.mjs @@ -0,0 +1,42 @@ +/** + * Media operation tests. + * @author oliver-mitchell [oliver@polymerlabs.dev] + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Prepend empty", + input: "test", + expectedOutput: "test", + recipeConfig: [ + { op: "Prepend", args: ["", "None"] } // [text, delimiter] + ] + }, + { + name: "Prepend empty input", + input: "", + expectedOutput: "test", + recipeConfig: [ + { op: "Prepend", args: ["test", "None"] } // [text, delimiter] + ] + }, + { + name: "Prepend with input and text", + input: " world", + expectedOutput: "hello world", + recipeConfig: [ + { op: "Prepend", args: ["hello", "None"] } // [text, delimiter] + ] + }, + { + name: "Prepend with space delim", + input: "hello world", + expectedOutput: ":hello :world", + recipeConfig: [ + { op: "Prepend", args: [":", "Space"] } // [text, delimiter] + ] + } +]);