npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ropo

v2.25.0

Published

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

Downloads

1,036

Readme

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/[email protected]'
</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: