diff --git a/scm-ui/ui-webapp/src/repos/components/changesets/textSplitAndReplace.test.ts b/scm-ui/ui-webapp/src/repos/components/changesets/textSplitAndReplace.test.ts new file mode 100644 index 0000000000..641ff2cb9b --- /dev/null +++ b/scm-ui/ui-webapp/src/repos/components/changesets/textSplitAndReplace.test.ts @@ -0,0 +1,101 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import textSplitAndReplace from "./textSplitAndReplace"; + +type Wrapped = { + text: string; +}; + +const testWrapper = (s: string) => { + return { text: s }; +}; + +describe("text split and replace", () => { + it("should wrap text if nothing should be replaced", () => { + const result = textSplitAndReplace("Don't Panic.", [], testWrapper); + expect(result).toHaveLength(1); + expect(result[0]).toStrictEqual({ text: "Don't Panic." }); + }); + + it("should replace single string", () => { + const result = textSplitAndReplace( + "Don't Panic.", + [{ textToReplace: "'", replacement: { text: "`" } }], + testWrapper + ); + expect(result).toHaveLength(3); + expect(result[0]).toStrictEqual({ text: "Don" }); + expect(result[1]).toStrictEqual({ text: "`" }); + expect(result[2]).toStrictEqual({ text: "t Panic." }); + }); + + it("should replace strings only once if replace all is not set", () => { + const result = textSplitAndReplace( + "'So this is it,' said Arthur, 'We are going to die.'", + [{ textToReplace: "'", replacement: { text: "“" } }], + testWrapper + ); + expect(result).toHaveLength(2); + expect(result[0]).toStrictEqual({ text: "“" }); + expect(result[1]).toStrictEqual({ text: "So this is it,' said Arthur, 'We are going to die.'" }); + }); + + it("should replace all strings if replace all is set to true", () => { + const result = textSplitAndReplace( + "'So this is it,' said Arthur, 'We are going to die.'", + [{ textToReplace: "'", replacement: { text: "“" }, replaceAll: true }], + testWrapper + ); + expect(result).toHaveLength(7); + expect(result[0]).toStrictEqual({ text: "“" }); + expect(result[1]).toStrictEqual({ text: "So this is it," }); + expect(result[2]).toStrictEqual({ text: "“" }); + expect(result[3]).toStrictEqual({ text: " said Arthur, " }); + expect(result[4]).toStrictEqual({ text: "“" }); + expect(result[5]).toStrictEqual({ text: "We are going to die." }); + expect(result[6]).toStrictEqual({ text: "“" }); + }); + + it("should replace strings with multiple replacements", () => { + const result = textSplitAndReplace( + "'So this is it,' said Arthur, 'We are going to die.'", + [ + { textToReplace: "'", replacement: { text: "“" }, replaceAll: true }, + { textToReplace: "Arthur", replacement: { text: "Dent" }, replaceAll: true } + ], + testWrapper + ); + expect(result).toHaveLength(9); + expect(result[0]).toStrictEqual({ text: "“" }); + expect(result[1]).toStrictEqual({ text: "So this is it," }); + expect(result[2]).toStrictEqual({ text: "“" }); + expect(result[3]).toStrictEqual({ text: " said " }); + expect(result[4]).toStrictEqual({ text: "Dent" }); + expect(result[5]).toStrictEqual({ text: ", " }); + expect(result[6]).toStrictEqual({ text: "“" }); + expect(result[7]).toStrictEqual({ text: "We are going to die." }); + expect(result[8]).toStrictEqual({ text: "“" }); + }); +}); diff --git a/scm-ui/ui-webapp/src/repos/components/changesets/textSplitAndReplace.ts b/scm-ui/ui-webapp/src/repos/components/changesets/textSplitAndReplace.ts new file mode 100644 index 0000000000..1f4bcf3fad --- /dev/null +++ b/scm-ui/ui-webapp/src/repos/components/changesets/textSplitAndReplace.ts @@ -0,0 +1,75 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +type Replacement = { + textToReplace: string; + replacement: T; + replaceAll?: boolean; +}; + +type PartToReplace = { + start: number; + length: number; + replacement: T; +}; + +export default function textSplitAndReplace( + text: string, + replacements: Replacement[], + textWrapper: (s: string) => T +): T[] { + const partsToReplace: PartToReplace[] = []; + + replacements.forEach(replacement => { + let lastIndex = -1; + do { + const start = text.indexOf(replacement.textToReplace, lastIndex); + if (start >= 0) { + const length = replacement.textToReplace.length; + partsToReplace.push({ start, length, replacement: replacement.replacement }); + lastIndex = start + length; + } else { + lastIndex = -1; + } + } while (replacement.replaceAll && lastIndex >= 0); + }); + + partsToReplace.sort((a, b) => a.start - b.start); + + const result: T[] = []; + + let lastIndex = 0; + for (const { start, length, replacement } of partsToReplace) { + if (start > lastIndex) { + result.push(textWrapper(text.substr(lastIndex, start - lastIndex))); + } + result.push(replacement); + lastIndex = start + length; + } + if (lastIndex < text.length) { + result.push(textWrapper(text.substr(lastIndex))); + } + + return result; +}