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 🙏

© 2026 – Pkg Stats / Ryan Hefner

copperlace

v0.3.1

Published

Procedural text renderer for generative grammar configuration

Readme

= Copperlace

Copperlace turns generative grammars into varied text.

Write the pieces once, then ask Copperlace to render a named rule. It can pick random options, remember a picked value for the rest of one result, fill in default values, and clean up text with processors such as article, possessive, pluralize, and sentence.

Use it for story snippets, game item descriptions, product blurbs, release notes, quest logs, or any place where hand-written text needs controlled variation.

== What It Does

Here is a tiny Copperlace configuration:

[source]

name = ["Mia", "Lina"] animal = ["owl", "raven"] mood = ["quiet", "restless"]

story = "{hero} traveled with {pet | article}. {hero} stayed {mood}." origin = "{% hero:name %}{% pet:animal %}{story}"

Rendering origin might produce:

[source,text]

Mia traveled with an owl. Mia stayed quiet.

Another render might produce:

[source,text]

Lina traveled with a raven. Lina stayed restless.

The important detail is consistency inside one result: {% hero:name %} chooses a name once, and later {hero} references reuse that same name.

When you need different entries from the same choice rule, add ! to draw without replacement for the current render:

[source]

name = ["Mia", "Lina"] duel = "{name!} faces {name!}"

Template expressions also support processor pipelines:

[source]

item = ["ancient key", "owl feather", "brass compass"] slug = "{item | slug}" description = "You found {item | article}."

This can render text like:

[source,text]

You found an ancient key.

The article processor chooses a or an, and slug turns text into a URL-friendly value such as ancient-key.

== License

Copyright 2026 Bruno Mahé.

Copperlace is licensed under the Apache License, Version 2.0. See link:LICENSE[LICENSE] for details.

== Quick Start

Run the bundled example from the Rust project:

[source,sh]

cd rust-core cargo run --bin copperlace -- render -c ../examples/character_scene.conf

Example output:

[source,text]

Darcy's scholar found a brass compass in a moonlit bridge while running. The quiet discovery changed everything.

Your exact result may differ because the example contains random choices.

Render three results from the same loaded configuration:

[source,sh]

cargo run --bin copperlace -- render -c ../examples/character_scene.conf -n 3

Example output:

[source,text]

Mia's ranger found an hourglass in a forgotten observatory while searching. The quiet discovery changed everything. Azra's ranger found a brass compass in a moonlit bridge while searching. The restless discovery changed everything. Lina's scholar found an owl feather in a moonlit bridge while watching. The restless discovery changed everything.

Provide a starting value with --set. Here the hero is fixed to Mia, while the other values can still vary:

[source,sh]

cargo run --bin copperlace -- render -c ../examples/character_scene.conf --set hero=Mia

Example output:

[source,text]

Mia's pilot found a brass compass in a forgotten observatory while running. The quiet discovery changed everything.

Check a configuration without rendering it:

[source,sh]

cargo run --bin copperlace -- check -c ../examples/character_scene.conf

Output:

[source,text]

OK

Use - as the config path to read configuration from standard input:

[source,sh]

cat ../examples/character_scene.conf | cargo run --bin copperlace -- render -c - cat ../examples/character_scene.conf | cargo run --bin copperlace -- check -c -

More runnable examples live in link:examples/README.adoc[examples/], covering story text, game item cards, gendered pronouns, JSON item data, structured item data, product copy, release notes, and quest logs.

Object-valued rules render as structured JSON. The CLI formats structured JSON with tabs by default:

[source,sh]

cargo run --bin copperlace -- render -c ../examples/structured_item.conf

Example output:

[source,json]

{ "description": "A moonstone compass once saved a lost caravan. It still releases 3 sparks when its bearer is searching in the dark.", "id": "rare-moonstone-compass-2nd", "level": "2", "name": "Rare Moonstone Compass", "properties": { "charges": "3", "effect": "spark", "material": "moonstone" }, "rarity": "rare", "tags": [ "rare", "compass", "spark" ], "type": "compass" }

Use --compact-json when one-line JSON is more convenient.

For development checks, run:

[source,sh]

make check

== Load Once, Render Many Times

For repeated renders, load the configuration once and call render multiple times. This avoids recompiling the rule set for each render.

Copperlace is available as a command-line tool and as libraries for Rust, Python, Java, and browser JavaScript/WebAssembly.

=== Rust

[source,rust]

use copperlace::Copperlace;

let copperlace = Copperlace::from_file("examples/character_scene.conf")?; let first = copperlace.render("origin")?; let second = copperlace.render("origin")?; let seeded = copperlace.render_with_context( "origin", [("hero".to_string(), "Mia".to_string())].into(), )?;

One-shot helpers are also available when repeated rendering is not needed.

=== Python

[source,python]

from copperlace import Copperlace

with Copperlace.from_file("examples/character_scene.conf") as copperlace: first = copperlace.render("origin") second = copperlace.render("origin") seeded = copperlace.render("origin", {"hero": "Mia"})

with Copperlace.from_string( 'name = ["Mia"]\norigin = "{name | shout}"', {"shout": lambda value: value.upper()}, ) as copperlace: shouted = copperlace.render("origin")

Python also exposes RuleSet and one-shot render helpers. Wrapper errors are raised as CopperlaceError.

=== Java

[source,java]

import dev.mahe.copperlace.Copperlace; import java.util.Map;

try (final Copperlace copperlace = Copperlace.fromFile("examples/character_scene.conf")) { final String first = copperlace.render("origin"); final String second = copperlace.render("origin"); final String seeded = copperlace.render("origin", Map.of("hero", "Mia")); }

try (final Copperlace copperlace = Copperlace.fromStringWithProcessors( "name = ["Mia"]\norigin = "{name | shout}"", Map.of("shout", value -> value.toUpperCase()))) { final String shouted = copperlace.render("origin"); }

Java also exposes RuleSet and one-shot render helpers. Copperlace and RuleSet implement AutoCloseable.

=== JS/TS

[source,ts]

import { Copperlace } from "./pkg/copperlace";

const response = await fetch("/config.conf"); const config = await response.text(); const copperlace = new Copperlace(config); const output = copperlace.render("origin"); const seeded = copperlace.renderWithContext("origin", { hero: "Mia" }); const custom = Copperlace.withProcessors(config, { shout: (value) => value.toUpperCase(), }).render("origin");

The JS/TS package is generated with wasm-pack. Bundler output can be imported directly; direct browser ES module output must call the generated init function before using Copperlace. Browser code should load config text, then pass the string to new Copperlace(config). File-path APIs are not exported to WebAssembly.

== Documentation

The detailed behavior and configuration contract lives in docs/:

  • link:docs/index.adoc[Documentation index]
  • link:docs/capabilities.adoc[Capabilities]
  • link:docs/configuration.adoc[Configuration]
  • link:docs/errors.adoc[Errors and API behavior]
  • link:docs/packaging.adoc[Packaging and language wrappers]

== Project Layout

  • rust-core/ contains the renderer library, C ABI, and CLI.
  • python/ contains the Python wheel wrapper and ctypes bindings.
  • java/ contains the Java FFM wrapper and Maven packaging.
  • js/ contains JS/TS WebAssembly package documentation.
  • docs/ contains the behavior and configuration specification.
  • examples/ contains runnable rule examples.

== Development Commands

Use the root Makefile for normal workflows:

[source,sh]

make help # list available targets make check # Rust fmt check plus Rust, Python, and Java tests make test # Rust, Python, and Java tests make package # Python wheel, JS/TS wasm package, and Java JARs make clean # remove generated Rust, Python, JS/TS, and Java build outputs

Common targeted commands:

[source,sh]

make rust-build make rust-test make rust-cli make python-test make python-wheel make js-package make js-web make java-test make java-package

== Packaging Notes

Python wheels and Java native JARs are platform-specific because they include the Rust dynamic library. Python source distributions include the Rust sources needed to build that native library locally. JS/TS packages are generated from the same Rust renderer as WebAssembly and require wasm-pack.

The simplest Java dependency is dev.mahe.copperlace:copperlace, which brings in the Java API plus all supported native platform artifacts. Users who only want one runtime platform can depend on a platform artifact such as dev.mahe.copperlace:copperlace-linux-x86_64, and advanced users can depend on dev.mahe.copperlace:copperlace-api directly. At runtime, the Java wrapper loads the native library from COPPERLACE_LIBRARY_PATH first, then from packaged native resources, then from local Rust build output for source-tree development.