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

hurml

v2.0.2

Published

Human Readable Markup Language Parser

Readme

HURML Parser

This module parses files that are formatted using the 'hurml' format, and provides simple tools for working with the resulting data structures. The 'hurml' format is a simple plain-text file format, which is somewhat resilient to human (user) error and is especially useful for configuration files that are meant to be changed by non-developer personnel.

File Format

'Hurml' is a line-based file format. Whitespace at the start and end of lines is ignored, and indentation is for humans only.

Comments start with a # character as the first non-whitespace symbol and span to the end of the line. A # preceded by whitespace later in a line also starts a comment, unless it appears inside a quoted string.

Scalars And Tokens

Property lines use name = value. Values may be strings, numbers, booleans, null, list literals, tokens, or multiline blocks.

title = "Example"
price = 123.45
mask = 0xff
enabled = true
flag = FOO_ENABLE

Result:

{
  "title": "Example",
  "price": 123.45,
  "mask": 255,
  "enabled": true,
  "flag": { "token": "FOO_ENABLE" }
}

Empty values and the literal null parse as undefined. Since JSON has no undefined, these properties disappear when serialized with JSON.stringify.

empty =
nothing = null
set = true

Result after JSON serialization:

{
  "set": true
}

Bare values separated by commas or semicolons become token arrays.

flags = FOO_ENABLE, FOO_VERBOSE

Result:

{
  "flags": [
    { "token": "FOO_ENABLE" },
    { "token": "FOO_VERBOSE" }
  ]
}

Strings may use single, double, straight, or curly quotes. Inside straight-quoted strings, the escapes \n, \t, \r, \", \', \\, and \uXXXX are recognized.

name = "A quoted value"
line = "one\ntwo"

Result:

{
  "name": "A quoted value",
  "line": "one\ntwo"
}

Triple-quoted strings may span multiple lines. Their content is preserved verbatim until a line containing only """.

description = """
A short paragraph spanning multiple lines.
It can contain "quotes", = signs, and # symbols freely.
"""

Result:

{
  "description": "A short paragraph spanning multiple lines.\nIt can contain \"quotes\", = signs, and # symbols freely."
}

Objects

Property lines at the same level form an object.

name = "Service"
port = 8080
enabled = true

Result:

{
  "name": "Service",
  "port": 8080,
  "enabled": true
}

Objects can also be written as blocks.

server = (
  host = "localhost"
  port = 8080
)

Result:

{
  "server": {
    "host": "localhost",
    "port": 8080
  }
}

The same object can be written using a section header.

[server]
host = "localhost"
port = 8080

Result:

{
  "server": {
    "host": "localhost",
    "port": 8080
  }
}

Dotted keys and dotted headers create nested objects as needed.

server.http.port = 8080

[server.http]
host = "localhost"

Result:

{
  "server": {
    "http": {
      "port": 8080,
      "host": "localhost"
    }
  }
}

Arrays

List literals are useful for short arrays.

tags = [one, "two", 3, true]

Result:

{
  "tags": [
    { "token": "one" },
    "two",
    3,
    true
  ]
}

Longer arrays can be written with dash items.

tags = (
  - one
  - two
  - three
)

Result:

{
  "tags": [
    { "token": "one" },
    { "token": "two" },
    { "token": "three" }
  ]
}

The same array can be written using a section header.

[tags]
- one
- two
- three

Result:

{
  "tags": [
    { "token": "one" },
    { "token": "two" },
    { "token": "three" }
  ]
}

Inside an object, dash arrays must be assigned to an explicit property.

[[catalog]]
items = (
  - id = 123
  - id = 456
)

Result:

{
  "catalog": [
    {
      "items": [
        { "id": 123 },
        { "id": 456 }
      ]
    }
  ]
}

Nested list literals are supported.

matrix = [[1, 2], [3, 4]]

Result:

{
  "matrix": [
    [1, 2],
    [3, 4]
  ]
}

Arrays Of Objects

Arrays of objects can be written with the dash-property shorthand. Each dash followed by a property assignment starts a new object; following property lines are assigned to that object until the next dash or section header.

[items]
- name = "first"
  value = 1
- name = "second"
  value = 2
  config.deep = true

Result:

{
  "items": [
    {
      "name": "first",
      "value": 1
    },
    {
      "name": "second",
      "value": 2,
      "config": {
        "deep": true
      }
    }
  ]
}

The same shape can be written using double-bracket array sections. Each repeated header appends a new object to the named array.

[[items]]
name = "first"
value = 1

[[items]]
name = "second"
value = 2
config.deep = true

Result:

{
  "items": [
    {
      "name": "first",
      "value": 1
    },
    {
      "name": "second",
      "value": 2,
      "config": {
        "deep": true
      }
    }
  ]
}

Double-bracket header names may be dotted.

[[server.routes]]
path = "/"
method = GET

[[server.routes]]
path = "/health"
method = GET

Result:

{
  "server": {
    "routes": [
      {
        "path": "/",
        "method": { "token": "GET" }
      },
      {
        "path": "/health",
        "method": { "token": "GET" }
      }
    ]
  }
}

Nested Structures

Blocks, sections, lists, and dotted keys can be composed.

[topLevel]
list = (
  - (
    name = "First item"
    [config]
    value = 1
  )
  - (
    name = "Second item"
    [config]
    value = 2
  )
)

Result:

{
  "topLevel": {
    "list": [
      {
        "name": "First item",
        "config": {
          "value": 1
        }
      },
      {
        "name": "Second item",
        "config": {
          "value": 2
        }
      }
    ]
  }
}

Single values that are not property assignments or list items are used as the value of a name property, with the exception of tokens, which are assigned to properties that have the same name as the token string.

Working with data

The hurml.parse(...) function parses a 'hurml' formatted text, and returns the corresponding data structure. This consists of an object, array, or value (string, number, or boolean), with nested properties or array elements similar to a plain JavaScript data structure. Only tokens have a separate HurmlToken type, which can be converted to a string using the .toString() method.

With this result, the following functions can be used to check and get data:

hurml.find(data, ...path) finds one or more values using (nested, case-insensitive) property names. Any nested arrays are flattened, and do not need to be specified in the property path.

hurml.test(data, ...path) tests whether the given (nested, case-insensitive) property exists and its value is truthy.

hurml.match(stringOrRegExp, data, ...path) tests whether the given (nested, case-insensitive) property's value matches given string, number, boolean, or regular expression.