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 🙏

© 2025 – Pkg Stats / Ryan Hefner

chastity

v1.0.4

Published

If it can't be expressed through regexp, it doesn't belong in markdown

Readme

Chastity

For the love of Simplicity and Regular Expressions

A markdown parser where every feature is a regular expression. No hidden complexity, no abstract syntax trees - just beautiful patterns you can see, understand, and customize.

Philosophy: If it can't be expressed through regexp, it doesn't belong in markdown.

Installation

npm install chastity

Quick Start

import Chastity from 'chastity';

const md = new Chastity();
const html = md.parse(`# Hello World\n\nThis is **bold** and *italic*.`);
console.log(html);

Why Chastity?

  • Transparent: Every feature is a named regex pattern you can inspect
  • Customizable: Add, remove, or replace any feature
  • Educational: Learn regex through practical markdown parsing
  • Simple: No complex AST, no tokenizers - just pattern matching
  • Self-documenting: Each feature includes description, usage, and pattern

Built-in Features

const md = new Chastity();
md.help(); // See all features with their regex patterns

Includes: code blocks, headers, links, images, bold, italic, lists, blockquotes, and more.

Learning Regular Expressions Through Markdown

Let's learn regex by creating custom markdown features! Each example builds on the last, teaching you regex concepts through real-world use.

Example 1: WikiText Links (Beginner)

Remember old wikis where WordsJoinedTogether automatically became links? Let's build that!

md.register({
  name: 'wiki-links',
  description: 'Automatic links for WikiText (CapitalizedWordsJoinedTogether)',
  usage: 'HelloWorld becomes a link to /wiki/HelloWorld',
  pattern: '(?<![\\w/])(?<word>[A-Z][a-z]+(?:[A-Z][a-z]+)+)(?![\\w/])',
  flags: 'g',
  replacement: ({ word }) => `<a href="/wiki/${word}">${word}</a>`
});

// Now this works:
md.parse('Check out MyAwesomePage and WikiTextLinks!')
// Output: Check out <a href="/wiki/MyAwesomePage">MyAwesomePage</a> and <a href="/wiki/WikiTextLinks">WikiTextLinks</a>!

Regex Breakdown:

  • (?<![\\w/]) - Negative lookbehind: Don't match if preceded by word char or slash
  • (?<word>...) - Named capture group: Save matched text as "word"
  • [A-Z] - Match one uppercase letter (first letter)
  • [a-z]+ - Match one or more lowercase letters
  • (?:[A-Z][a-z]+)+ - Match one or more repetitions of Capital+lowercase (the "Joined" part)
  • (?![\\w/]) - Negative lookahead: Don't match if followed by word char or slash

Why the lookbehind/lookahead? So we don't match inside URLs like http://MyPage or filenames like MyPage.js

Example 2: Transcludes with Safety (Intermediate)

Let's add a feature to include other markdown files, but safely - only child paths, only .md files:

md.register({
  name: 'transclude',
  description: 'Include other markdown files safely (child paths only, .md only)',
  usage: '{{include: my-file.md}}',
  pattern: '\\{\\{include:\\s*(?<path>[a-zA-Z0-9_-]+(?:/[a-zA-Z0-9_-]+)*\\.md)\\}\\}',
  flags: 'g',
  replacement: ({ path }) => {
    // Safety checks are built into the regex!
    try {
      const content = fs.readFileSync(path, 'utf-8');
      return md.parse(content); // Recursively parse the included file
    } catch (e) {
      return `<span class="error">Could not include: ${path}</span>`;
    }
  }
});

// Safe usage:
md.parse('{{include: posts/hello.md}}')  // ✅ Works
md.parse('{{include: ../secret.md}}')    // ❌ Won't match (no .. allowed)
md.parse('{{include: /etc/passwd}}')     // ❌ Won't match (no leading slash)
md.parse('{{include: file.txt}}')        // ❌ Won't match (must end in .md)

Regex Breakdown (Safety First!):

  • \\{\\{include:\\s* - Match {{include: with optional whitespace
  • (?<path>...) - Named capture group for the file path
  • [a-zA-Z0-9_-]+ - First path segment: only letters, numbers, underscore, dash (no dots!)
  • (?:/[a-zA-Z0-9_-]+)* - Optional sub-paths: zero or more /segment parts (same safe chars)
  • \\.md - Must end in .md (the \\ escapes the dot)
  • \\}\\} - Closing braces

Security through Regex:

  • ❌ No .. (can't go up directories)
  • ❌ No leading / (can't use absolute paths)
  • ❌ No . in filenames except .md (can't do file.txt or ../../secret.md)
  • ✅ Only a-z, A-Z, 0-9, _, - in paths
  • ✅ Only .md files

This pattern makes path traversal attacks impossible - the regex simply won't match malicious input!

Example 3: Custom Syntax (Advanced)

Want to add highlight syntax? ==highlighted text==

md.register({
  name: 'highlight',
  description: 'Highlight text with ==double equals==',
  usage: '==important text==',
  pattern: '==(?<text>[^=]+)==',
  flags: 'g',
  replacement: ({ text }) => `<mark>${text}</mark>`
});

Regex Breakdown:

  • == - Two literal equals signs
  • (?<text>...) - Named capture group
  • [^=]+ - Match one or more of anything except equals signs (the ^ inside [] means "not")
  • == - Closing equals signs

The [^=]+ is key - it means "grab everything until you hit another ="

Example 4: Advanced - Attributes on Images

Let's add CSS classes to images: ![alt](url){.center .shadow}

md.register({
  name: 'images-with-classes',
  description: 'Images with CSS classes',
  usage: '![alt](url){.class1 .class2}',
  pattern: '!\\[(?<alt>[^\\]]*)\\]\\((?<url>[^)]+)\\)\\{(?<classes>(?:\\.[a-z-]+\\s*)+)\\}',
  flags: 'g',
  replacement: ({ alt, url, classes }) => {
    const classList = classes.trim().split(/\s+/).map(c => c.slice(1)).join(' ');
    return `<img src="${url}" alt="${alt}" class="${classList}">`;
  }
});

// Usage:
md.parse('![Logo](logo.png){.center .shadow}')
// Output: <img src="logo.png" alt="Logo" class="center shadow">

Regex Breakdown:

  • !\\[(?<alt>[^\\]]*)\\]\\((?<url>[^)]+)\\) - Standard image syntax
  • \\{ - Opening brace
  • (?<classes>...) - Named group for classes
  • (?:\\.[a-z-]+\\s*)+ - One or more of: dot + letters/dashes + optional space
    • (?:...) - Non-capturing group (groups pattern but doesn't save it separately)
    • \\. - Literal dot
    • [a-z-]+ - Class name (letters and dashes)
    • \\s* - Optional whitespace
    • + - One or more repetitions
  • \\} - Closing brace

Basic Usage

import Chastity from 'chastity';
const md = new Chastity();

// Parse markdown
const html = md.parse(`# Hello\n---\n\nworld`);

// Add custom AVIF picture feature
md.register({
  name: 'avif-picture',
  description: 'AVIF images with fallback',
  usage: '![alt](image.jpg){avif}',
  pattern: '!\\[(?<alt>[^\\]]*)\\]\\((?<url>[^)]+)\\)\\{avif\\}',
  flags: 'g',
  replacement: ({ alt, url }) => {
    const avifUrl = url.replace(/\.(jpg|png)$/i, '.avif');
    return `<picture>
  <source srcset="${avifUrl}" type="image/avif">
  <img src="${url}" alt="${alt}">
</picture>`;
  }
});

// See all features
md.help();              // List all features
md.help('avif-picture'); // Detailed help for one feature

// Remove unwanted features
md.unregister('strikethrough');

Terminal Mode

import Chastity from 'chastity';
const md = new Chastity();

// For documentation/README (default)
console.log(md.help());  // Returns formatted markdown string

// For CLI tools
md.tty = true;
md.help();  // Prints beautiful colored terminal output

// Save documentation
fs.writeFileSync('FEATURES.md', md.help());

API

new Chastity()

Create a new parser instance with all default features registered.

md.register(feature)

Add or replace a feature. Feature object requires:

  • name (string): Unique identifier
  • description (string): What this feature does
  • usage (string): Example syntax
  • pattern (string): Regular expression pattern
  • flags (string): Regex flags ('g', 'gm', etc.)
  • replacement (function): Receives named capture groups, returns HTML

md.unregister(name)

Remove a feature by name.

md.parse(markdown)

Convert markdown string to HTML.

md.help(featureName?)

Get help documentation. Returns markdown by default, or prints colored output if md.tty = true.

md.list()

Get array of all registered feature names.

Philosophy

Regular expressions are beautiful when used for what they're good at: pattern matching in text. Chastity embraces this by:

  1. Making every feature a regex - no hidden complexity
  2. Using named capture groups - self-documenting patterns
  3. Teaching through examples - learn regex by doing
  4. Keeping it simple - if it needs a parser, it's too complex

Markdown was meant to be simple. Let's keep it that way.

Contributing

Have a great regex pattern for a markdown feature? Share it! The best regular expressions deserve to be celebrated and reused.

License

MIT


"For the love of Simplicity and Regular Expressions"