2.25.0 • Published 1 year ago

ropo v2.25.0

Weekly downloads
3
License
Artistic-2.0
Repository
github
Last release
1 year ago

String replacement utilities with support for both synchronous and asynchronous replacements. Supports replacing regular expressions, HTML Elements, and comment elements. Compatible with async/await.

ropo is replace in Yoruba

Usage

Complete API Documentation.

Tests.

import { strictEqual } from 'assert'
import {
    extractAttribute,
    replaceSync,
    replaceAsync,
    replaceElementSync,
    replaceElementAsync,
} from 'ropo'

async function main() {
    // uppercase `bc` of `abcd`
    console.log(
        replaceSync('abcd', /bc/, function ({ content }) {
            return content.toUpperCase()
        }),
    )
    // => aBCd

    // uppercase `bc` of `abcd` asynchronously
    console.log(
        await replaceAsync('abcd', /bc/, function ({ content }) {
            return new Promise(function (resolve) {
                process.nextTick(function () {
                    resolve(content.toUpperCase())
                })
            })
        }),
    )
    // => aBCd

    // use a RegExp named capture group to allow the inversion of content between BEGIN and END
    // https://github.com/tc39/proposal-regexp-named-groups
    console.log(
        replaceSync(
            'hello BEGIN good morning END world',
            new RegExp('BEGIN (?<inside>.+?) END'),
            function (section, captures) {
                strictEqual(section.outer, 'BEGIN good morning END')
                strictEqual(section.inner, null)
                strictEqual(section.content, section.outer)
                return captures.inside.split('').reverse().join('')
            },
        ),
    )
    // => hello gninrom doog world

    // for convenience, and some extra magic (magic explained in next example) we can call `inside` `inner` to have it used as a section
    console.log(
        replaceSync(
            'hello BEGIN good morning END world',
            new RegExp('BEGIN (?<inner>.+?) END'),
            function (section, captures) {
                strictEqual(section.outer, 'BEGIN good morning END')
                strictEqual(section.inner, captures.inner)
                strictEqual(section.content, captures.inner)
                return section.content.split('').reverse().join('')
            },
        ),
    )
    // => hello gninrom doog world

    // invert anything between INVERT:<N> with recursive rendering
    console.log(
        replaceSync(
            'hello INVERT:1 good INVERT:2 guten INVERT:3 gday /INVERT:3 morgen /INVERT:2 morning /INVERT:1 world',
            new RegExp('(?<element>INVERT:\\d+) (?<inner>.+?) /\\k<element>'),
            function ({ content }) {
                return content.split('').reverse().join('')
            },
        ),
    )
    // => hello gninrom guten yadg morgen doog world
    // notice how the text is replaced correctly, gday has 3 inversions applied, so it is inverted
    // whereas guten morgen has 2 inversions applied, so is reset
    // the ability to perform this recursive replacement is possible because if the RegExp named capture group `inner` exists,
    // then ropo will perform recursion on it inner, and return the result as the `content` section
    // e.g. by doing this, the initial recursion will occur on: good INVERT:2 guten INVERT:3 gday /INVERT:3 morgen /INVERT:2 morning
    // then on: guten INVERT:3 gday /INVERT:3 morgen
    // and finally on: gday
    // without doing this, recursion would have to happen on the outer, which would cause the replacement to recur infinitely against itself
    // e.g. the initial recursion would occur on: INVERT:1 good INVERT:2 guten INVERT:3 gday /INVERT:3 morgen /INVERT:2 morning /INVERT:1
    // and the second on: INVERT:1 good INVERT:2 guten INVERT:3 gday /INVERT:3 morgen /INVERT:2 morning /INVERT:1
    // and the third on: INVERT:1 good INVERT:2 guten INVERT:3 gday /INVERT:3 morgen /INVERT:2 morning /INVERT:1
    // and so on, so no progress is made
    // as such, ropo will only allow recursion when it detects the `inner` named capture group

    // invert anything between INVERT:<N>, without using the `inner` named capture group
    console.log(
        replaceSync(
            'hello INVERT:1 good INVERT:2 guten INVERT:3 gday /INVERT:3 morgen /INVERT:2 morning /INVERT:1 world',
            new RegExp(
                '(?<element>INVERT:\\d+) (?<whatever>.+?) /\\k<element>',
            ),
            function (sections, { whatever }) {
                return whatever.split('').reverse().join('')
            },
        ),
    )
    // => hello gninrom 2:TREVNI/ negrom 3:TREVNI/ yadg 3:TREVNI netug 2:TREVNI doog world
    // as we can see, recursion was correctly disabled
    // if it wasn't, we would end up with an error like:
    // (node:7252) UnhandledPromiseRejectionWarning: RangeError: Maximum call stack size exceeded
    // and if we used the `outer` section instead of the `whatever` capture group, we would have:
    // => hello 1:TREVNI/ gninrom 2:TREVNI/ negrom 3:TREVNI/ yadg 3:TREVNI netug 2:TREVNI doog 1:TREVNI world

    // ropo also has built in helpers for element replacements,
    // such that we only need to specify the tag name/regexp,
    // and ropo handles the replacement sections and capture groups for us

    // uppercase the contents of <x-uppercase>
    console.log(
        replaceElementSync(
            '<strong>I am <x-uppercase>awesome</x-uppercase></strong>',
            /x-uppercase/,
            function ({ content }) {
                return content.toUpperCase()
            },
        ),
    )
    // => <strong>I am AWESOME</strong>

    // power the numbers of <power> together
    console.log(
        replaceElementSync(
            '<x-pow>2 <x-power>3 4</x-power> 5</x-pow>',
            /x-pow(?:er)?/,
            function ({ content }) {
                const result = content
                    .split(/[\n\s]+/)
                    .reduce((a, b) => Math.pow(a, b))
                return result
            },
        ),
    )
    // => 8.263199609878108e+121
    // note that this is the correct result of: 2 ^ (3 ^ 4) ^ 5
    // which means, the nested element is replaced first, then the parent element, as expected

    // now as replace-element is just regex based, we must ensure that nested elements have unique tags
    // this can be done as above with `x-pow` and `x-power`, but can also be done via a `:<N>` suffix to the tag
    console.log(
        replaceElementSync(
            '<x-pow>2 <x-pow:2>3 4</x-pow:2> 5</x-pow>',
            /x-pow(?::\d+)?/,
            function ({ content }) {
                const result = content
                    .split(/[\n\s]+/)
                    .reduce((a, b) => Math.pow(a, b))
                return result
            },
        ),
    )
    // => 8.263199609878108e+121

    // we can even fetch attributes
    console.log(
        replaceElementSync(
            '<x-pow power=10>2</x-pow>',
            /x-pow/,
            function ({ content }, { attributes }) {
                const power = extractAttribute(attributes, 'power')
                const result = Math.pow(content, power)
                return result
            },
        ),
    )
    // => 1024

    // and even do asynchronous replacements
    console.log(
        await replaceElementAsync(
            '<x-readfile>example-fixture.txt</x-readfile>',
            /x-readfile/,
            function ({ content }) {
                return require('fs').promises.readFile(content, 'utf8')
            },
        ),
    )
    // => hello world from example-fixture.txt

    // and with support for self-closing elements
    console.log(
        replaceElementSync(
            '<x-pow x=2 y=3 /> <x-pow>4 6</x-pow>',
            /x-pow/,
            function ({ content }, { attributes }) {
                const x =
                    extractAttribute(attributes, 'x') || content.split(' ')[0]
                const y =
                    extractAttribute(attributes, 'y') || content.split(' ')[1]
                const result = Math.pow(x, y)
                return result
            },
        ),
    )
    // => 8 4096
}
main()

Which results in:

aBCd
aBCd
hello gninrom doog world
hello gninrom doog world
hello gninrom guten yadg morgen doog world
hello gninrom 2:TREVNI/ negrom 3:TREVNI/ yadg 3:TREVNI netug 2:TREVNI doog world
<strong>I am AWESOME</strong>
8.263199609878108e+121
8.263199609878108e+121
1024
hello world from example-fixture.txt
8 4096

npm

Skypack

<script type="module">
    import * as pkg from '//cdn.skypack.dev/ropo@^2.25.0'
</script>

unpkg

<script type="module">
    import * as pkg from '//unpkg.com/ropo@^2.25.0'
</script>

jspm

<script type="module">
    import * as pkg from '//dev.jspm.io/ropo@2.25.0'
</script>

Discover the release history by heading on over to the HISTORY.md file.

Discover how you can contribute by heading on over to the CONTRIBUTING.md file.

These amazing people are maintaining this project:

No sponsors yet! Will you be the first?

These amazing people have contributed code to this project:

Discover how you can contribute by heading on over to the CONTRIBUTING.md file.

Unless stated otherwise all works are:

and licensed under:

2.25.0

1 year ago

2.24.0

3 years ago

2.23.0

3 years ago

2.22.0

4 years ago

2.21.0

4 years ago

2.20.0

4 years ago

2.19.0

4 years ago

2.18.0

4 years ago

2.17.0

5 years ago

2.15.0

5 years ago

2.16.0

5 years ago

2.14.0

5 years ago

2.12.0

5 years ago

2.13.0

5 years ago

2.11.0

5 years ago

2.10.0

5 years ago

2.9.0

5 years ago

2.8.0

5 years ago

2.7.0

5 years ago

2.6.0

5 years ago

2.5.0

5 years ago

2.4.0

5 years ago

2.3.0

5 years ago

2.1.0

5 years ago

2.0.1

6 years ago

2.0.0

7 years ago

1.2.0

7 years ago

1.1.0

7 years ago

1.0.0

7 years ago