diff --git a/README.md b/README.md index 9fd0ff3..6d45892 100644 --- a/README.md +++ b/README.md @@ -24,23 +24,25 @@ plugins: | Function | Description | |--|--| -| [lower][lower] | Converts a string to its lowercase representation | -| [upper][upper] | Converts a string to its uppercase representation | -| [ternary][ternary] | Performs equality check and returns a defined result | +| [capitalize][capitalize] | Converts a string to its Titlecase representation | | [join][join] | Joins a collection of values with a given delimiter | +| [lower][lower] | Converts a string to its lowercase representation | +| [replace][replace] | Replaces occurrences using plain strings or regex patterns | | [split][split] | Splits a string value on a given delimiter | | [switch][switch] | Performs switch-statement lookups | -| [capitalize][capitalize] | Converts a string to its Titlecase representation | +| [ternary][ternary] | Performs equality check and returns a defined result | +| [upper][upper] | Converts a string to its uppercase representation | [link-download]: https://img.shields.io/npm/dt/serverless-plugin-utils.svg [link-version]: https://img.shields.io/npm/v/serverless-plugin-utils.svg [link-license]: https://img.shields.io/npm/l/serverless-plugin-utils.svg -[lower]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/lower.md -[upper]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/upper.md +[capitalize]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/capitalize.md [join]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/join.md +[lower]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/lower.md +[replace]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/replace.md [split]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/split.md -[ternary]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/ternary.md [switch]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/switch.md -[capitalize]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/capitalize.md \ No newline at end of file +[ternary]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/ternary.md +[upper]: https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/documentation/upper.md \ No newline at end of file diff --git a/documentation/replace.md b/documentation/replace.md new file mode 100644 index 0000000..9c2bae9 --- /dev/null +++ b/documentation/replace.md @@ -0,0 +1,52 @@ +[Home](https://github.com/icarus-sullivan/serverless-plugin-utils/blob/master/README.md) + +# replace +Replaces occurrences of a search pattern with a replacement string in a given string. Supports both plain strings and regular expression patterns. + +### Usage +``` +varName: ${replace(string, searchPattern, replacementString)} +``` + +The `searchPattern` can be either: +- A plain string (replaces first occurrence only - native JavaScript behavior) +- A regex pattern using Node.js syntax like `/pattern/flags` (for global replacement, use `/pattern/g`) + +### Examples + +#### Plain String Replacement: +``` +service: MyApp + +provider: + stage: ${opt:stage, 'dev'} + +custom: + serviceName: ${replace(${self:service}, 'App', 'Service')} + # Note: For multiple slashes, use regex: ${replace(${opt:branch, 'main'}, '/\//g', '-')} + cleanBranch: ${replace(${opt:branch, 'main'}, '/', '-')} +``` + +#### Regular Expression Patterns: +``` +custom: + # Case-insensitive replacement + normalizedName: ${replace('Hello WORLD and world', '/world/gi', 'serverless')} + + # Replace only first occurrence + firstOnly: ${replace('test-test-test', '/test/', 'demo')} + + # Global replacement with regex + allMatches: ${replace('foo123bar456', '/[0-9]+/g', 'XXX')} +``` + +### Outputs: + +| Example | Input | Pattern | Replacement | Output | +|--|--|--|--|--| +| Plain string | MyApp | 'App' | 'Service' | MyService | +| Plain string (first only) | foo-bar-foo | 'foo' | 'baz' | baz-bar-foo | +| Regex (case-insensitive) | Hello WORLD and world | '/world/gi' | 'serverless' | Hello serverless and serverless | +| Regex (first only) | test-test-test | '/test/' | 'demo' | demo-test-test | +| Regex (global) | test-test-test | '/test/g' | 'demo' | demo-demo-demo | +| Regex (global numbers) | foo123bar456 | '/[0-9]+/g' | 'XXX' | fooXXXbarXXX | \ No newline at end of file diff --git a/spec/index.test.js b/spec/index.test.js index 88941eb..2d548f3 100644 --- a/spec/index.test.js +++ b/spec/index.test.js @@ -1,95 +1,137 @@ -const join = require('../src/utils/join'); -const split = require('../src/utils/split'); -const ternary = require('../src/utils/ternary'); -const lower = require('../src/utils/lower'); -const upper = require('../src/utils/upper'); -const capitalize = require('../src/utils/capitalize'); -const switchFn = require('../src/utils/switch'); +const join = require("../src/utils/join"); +const split = require("../src/utils/split"); +const ternary = require("../src/utils/ternary"); +const lower = require("../src/utils/lower"); +const upper = require("../src/utils/upper"); +const capitalize = require("../src/utils/capitalize"); +const switchFn = require("../src/utils/switch"); +const replace = require("../src/utils/replace"); -test('join', () => { +test("join", () => { const result = join({ - params: ['one', 'two', 'three', '-'], + params: ["one", "two", "three", "-"], }); expect(result).toMatchObject({ - value: 'one-two-three', + value: "one-two-three", }); }); -test('split', () => { +test("split", () => { const result = split({ - params: ['foo-bar-example', '-'], + params: ["foo-bar-example", "-"], }); expect(result).toMatchObject({ - value: ['foo', 'bar', 'example'], + value: ["foo", "bar", "example"], }); const result2 = split({ - params: ['foo-bar-example', '-', 0], + params: ["foo-bar-example", "-", 0], }); expect(result2).toMatchObject({ - value: 'foo', + value: "foo", }); }); -test('ternary', () => { +test("ternary", () => { const result = ternary({ - params: ['prod', 'prod', true, false], + params: ["prod", "prod", true, false], }); expect(result).toMatchObject({ value: true, }); const result2 = ternary({ - params: ['prod', 'beta', true, false], + params: ["prod", "beta", true, false], }); expect(result2).toMatchObject({ value: false, }); }); -test('lower', () => { +test("lower", () => { const result = lower({ - params: ['DTesjf3'], + params: ["DTesjf3"], }); expect(result).toMatchObject({ - value: 'dtesjf3', + value: "dtesjf3", }); }); -test('upper', () => { +test("upper", () => { const result = upper({ - params: ['l38gt1'], + params: ["l38gt1"], }); expect(result).toMatchObject({ - value: 'L38GT1', + value: "L38GT1", }); }); -test('capitalize', () => { +test("capitalize", () => { const result = capitalize({ - params: ['l38gt1'], + params: ["l38gt1"], }); expect(result).toMatchObject({ - value: 'L38gt1', + value: "L38gt1", }); }); -test('switch', () => { +test("switch", () => { const cases = { - foo: 'awesome', - '*': 'nope', + foo: "awesome", + "*": "nope", }; const result = switchFn({ - params: ['foo', cases], + params: ["foo", cases], }); expect(result).toMatchObject({ - value: 'awesome', + value: "awesome", }); const result2 = switchFn({ - params: ['bar', cases], + params: ["bar", cases], }); expect(result2).toMatchObject({ - value: 'nope', + value: "nope", + }); +}); + +test("replace", () => { + // Test with plain string (replaces first occurrence only - native JS behavior) + const result = replace({ + params: ["Hello world!", "world", "serverless"], + }); + expect(result).toMatchObject({ + value: "Hello serverless!", + }); + + const result2 = replace({ + params: ["foo-bar-foo", "foo", "baz"], + }); + expect(result2).toMatchObject({ + value: "baz-bar-foo", // Only first 'foo' is replaced + }); + + // Test with regex pattern - global flag + const result3 = replace({ + params: ["Hello World and WORLD!", "/world/gi", "serverless"], + }); + expect(result3).toMatchObject({ + value: "Hello serverless and serverless!", + }); + + // Test with regex pattern - case sensitive, first occurrence only + const result4 = replace({ + params: ["foo-bar-foo-BAR", "/foo/g", "fiz"], + }); + expect(result4).toMatchObject({ + value: "fiz-bar-fiz-BAR", + }); + + // Test with regex pattern - case insensitive + const result5 = replace({ + params: ["foo-bar-foo-BAR", "/bar/gi", "biz"], + }); + expect(result5).toMatchObject({ + value: "foo-biz-foo-biz", }); }); diff --git a/src/utils/index.js b/src/utils/index.js index 3a2870b..9250312 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -20,4 +20,7 @@ module.exports = { capitalize: { resolve: require('./capitalize'), }, + replace: { + resolve: require('./replace'), + }, }; diff --git a/src/utils/replace.js b/src/utils/replace.js new file mode 100644 index 0000000..aab080c --- /dev/null +++ b/src/utils/replace.js @@ -0,0 +1,24 @@ +module.exports = ({ params }) => { + if (params.length < 3) { + throw new Error('Missing params for function replace. Expected: string, searchPattern, replacementString'); + } + + const [string, searchPattern, replacementString] = params; + + // Check if searchPattern is a regex (starts with / and ends with /flags) + const regexMatch = searchPattern.match(/^\/(.+?)\/([gimsy]*)$/); + if (regexMatch) { + // It's a regex pattern, create RegExp object + const pattern = regexMatch[1]; + const flags = regexMatch[2]; + const searchRegex = new RegExp(pattern, flags); + return { + value: `${string}`.replace(searchRegex, replacementString), + }; + } else { + // Treat as plain string - replaces first occurrence only (native JS behavior) + return { + value: `${string}`.replace(searchPattern, replacementString), + }; + } +}; \ No newline at end of file