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

indentmon

v0.0.3

Published

Use to detect changes in indentation over several lines using Pythonic rules

Downloads

3

Readme

Node API used to detect meaningful indent levels a la Python, specifically by handling mixed tabs and spaces.

Node API usage

const {
	indentmonitor,
	IndentError,
} = require('indentmon');

const indentlevel = indentmonitor();

try {
	for (const line of somearray) {
		const [level, trimmed] = indentlevel(line);		
	}		
} catch(e) {
	if (e instanceof IndentError) {
		console.error('Your indentation ruined everyting');
		console.error(e);
	}
}

Motivation

When writing my own tooling I keep finding new reasons to write DSLs. Having a way to parse out meaningful indents aware of mixed tabs and spaces lets me quickly establish scope.

Indent rules

When indentmon scans each line, it is following these rules (paraphrased from Antti Haapala):

  • If both number of tabs and number of spaces matches the previous line (no matter the order), then the indent level does not change.
  • If the number of one of (tabs, spaces) is greater than that on the previous line and number of the other is at least equal to those on the previous line, this is an indented block.
  • If the tuple (tabs, spaces) matches an indent from a previous block, dedent to that block.
  • Otherwise, raise IndentError.

Testing

While testing may be automated using the below command, you may also use the Node.js REPL to interact with indentmon directly.

$ npm run test MINLEN NTRIALS

The test suite generates MINLEN*NTRIALS indented code samples and uses brute force to ensure they are all parsed correctly. It also intentionally indents code incorrectly to make sure indentmon raises exceptions.

* `MINLEN`: The minimum lines of code to create in each production.
* `NTRIALS`: The number of productions belonging to each family
   to generate for testing. 

Test reports have the format FM#: PROD, where FM is a two letter code for one of the below production families, # is the trial, or instance of the production tested, and PROD is the indent DSL used to generate code according to a given indent style and pattern.

Production families

Constant (CT)

Indentation never changes.

foo
bar
baz
snafu

Increasing (IN)

Indentation increases each line.

foo
	bar
		baz
			snafu

Nondecreasing (ND)

Indentation may or may not increase per line, but will never decrease.

foo
	bar
	baz
		snafu
		fubar
			barbaz

Nonmonotonic (NM)

Indentation grows, then shrinks.

foo
	bar
	baz
		snafu
		fubar
			barbaz
		goofus
		gallant
	archangel of shamalama

Anchored (AN)

Indentation always returns to column 0 at the end.

foo
	bar
		baz
			snafu
			fubar
				barbaz
		goofus
	gallant
archangel of shamalama

Dropoff (DO)

Indentation grows sharply, and falls to a low indent level.

foo
	bar
		baz
			snafu
				fubar
					barbaz
						foo
							bar
								baz
									snafu
										fubar
											barbaz
	gallant
archangel of shamalama

Indent DSL

DSL productions use the following charset: ><-0123456789

  • -: Print line number, then move to next line.
  • >: Indent one level, then '-' command.
  • <: Dedent one level, then '-' command.
  • 0-9: Go to indicated indent level, then '-' command.

The following rules apply:

  • Indent levels are zero-based.
  • You cannot use < before the first >.
  • There cannot be more <s than >s
  • Digits may only exceed the current level by at most 1.

Examples:

'+' indicates an indent. 'n' indicates a newline.

      ---- prints 1n2n3n4n
      ->>- prints 1n+2n++3n++4n
       ><> prints +1n2n+3n
->>>0><>>1 prints 1n+2n++3n+++4n5n+6n7n+8n++9n+10n
     ->>59 is invalid. Only 6 can follow 5.
     ->>95 is invalid. Only 3 can follow 2 (implied by >>).
       >>3 is valid. (Same as >>>)
      >>>1 is valid.

Using in Node.js REPL

$ node
> .load ./test.js
> render('>>--->><<--')
  1
    2
    3
    4
    5
      6
        7
      8
    9
    10
    11

You can also generate production from families like so, such that L is an integer for the minimum number of lines you want to print. Removing the call to render will show the indent DSL used.

> render(createConstantProduction(L))
> render(createIncreasingProduction(L))
> render(createNonDecreasingProduction(L))
> render(createAnchoredProduction(L))
> render(createNonMonotonicProduction(L))
> render(createDropOffProduction(L))

TODO

Consider using a generator like so:

const {
	indentmonitor,
	IndentError,
} = require('indentmon');

try {
	for (const [level, trimmed] of indentmonitor(someiterable)) {
		// Should user keep access to original line? 
		// If so, is it worth storing two strings for each line?
	}
} catch (e) {
	...
}

Also, distinguish between TabError and IndentError.