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

@freik/passable

v0.1.2

Published

## A CMake auto formatter

Readme

passable

A CMake auto formatter

CMake files can't be made any prettier, but they should at least be passable

This is a CMake file formatter, spiritually inspired by Christopher Chedeau's excellent Javascript formatter, Prettier. Having also had to deal with clang-format's embrace of every random configuration choice anyone might have come up with, I very much prefer Prettier's philosophy. To be fair, C++ is a much larger language than Javascript, but CMake certainly isn't. So, I'm using Prettier's philosophy to start from. The real problem, however, is that no CMakeLists.txt file will ever be pretty. There's the dumb reason for the name...

Installation

The project is available on NPM as @freik/passable. If you don't have a package.json configuration, you can just run the tool by installing Bun and then using bunx passable <args...>. If you do have a package.json, install @freik/passable (probably as as a dev-dependency : bun add --dev @freik/passable or npm install --dev @freik/passable).

Using passable to format CMake files

passable -i "**/CMakeLists.txt" "**/*.cmake" will get you started, in your source-controlled repository. If you're a node user, you should use passable-node -i "**/CMakeLists.txt" "**/*.cmake" instead.

Configuration

To change various settings, create a .passablerc.json file, and put it alongside (or 'above') the root of your CMake project. The JSON file is a single object (so, wrap it in {}'s) and can contain any of the following options:

  • "useTabs": true or false
    • kinda self-explanatory...
  • "tabWidth": positive integer
    • The number of spaces to use, or at least the number of spaces a tab will use, as measured against the printWidth.
  • "endOfLine": "\n" or "\r\n"
    • Which character to use for line endings (only applies for "in-place" file modification)
  • "printWidth": positive integer.
    • The maximum line length for code. This doesn't restrict comments. Any 'end of line' comment will stay with the code immediate before it, and all other comments are included as original, though they may be indented differently
  • "commands": A JS object of CMake commands and settings to affect the formatting of those commands.
    • Each command is name ("set" or "add_executable" for example), followed by an object with up to three different options:

    • "controlKeywords": An array of keywords.

      • These keywords will be used to indent the subsequent arguments to the command further. Think "PUBLIC" "STATIC" or "OBJECT" on a command invocation of add_library.

        For example, before:

        add_library(myLib PUBLIC main.cpp file.cpp other.cpp thingamajig.cpp so.cpp many.cpp files.cpp)

        after:

        add_library(
          myLib
          PUBLIC
            main.cpp
            file.cpp
            other.cpp
            thingamajig.cpp
        )
    • "options": An array of strings.

      • Arguments that should be capitalized for the command. For example, the add_executable command would include WIN32, MACOSX_BUNDLE, IMPORTED, ALIAS, and EXCLUDE_FROM_ALL.

        For example, before:

        add_executable(theApp MacOSX_Bundle)

        after:

        add_executable(theApp MACOSX_BUNDLE)
    • "indentAfter": positive integer

      • All arguments will be indented one level further after the argument specified. set has this set to '0' so the variable name is at 1 level of indentation, and the values assigned are indented 2 levels.

        For example, before:

        set(
          CPP_FILES
          file1.cpp
          file2.cpp
          file3.cpp
          file4.cpp
          file5.cpp
          file6.cpp
          file7.cpp
          file8.cpp
          file9.cpp)

        after:

        set(
          CPP_FILES
            file1.cpp
            file2.cpp
            file3.cpp
            file4.cpp
            file5.cpp
            file6.cpp
            file7.cpp
            file8.cpp
            file9.cpp
        )

Sample .passablerc.json file:

Actually, this is the default configuration, at least it is as I'm writing this. :smile:

{
  "useTabs": false,
  "tabWidth": 2,
  "endOfLine": "\n",
  "printWidth": 80,
  "commands": {
    "add_library": {
      "controlKeywords": [
        "STATIC",
        "SHARED",
        "MODULE",
        "OBJECT",
        "INTERFACE",
        "UNKNOWN",
        "ALIAS"
      ],
      "options": ["GLOBAL", "EXCLUDE_FROM_ALL", "IMPORTED"]
    },
    "add_executable": {
      "options": [
        "WIN32",
        "MACOSX_BUNDLE",
        "EXCLUDE_FROM_ALL",
        "IMPORTED",
        "ALIAS"
      ]
    },
    "target_sources": {
      "controlKeywords": [
        "INTERFACE",
        "PUBLIC",
        "PRIVATE",
        "FILE_SET",
        "TYPE",
        "BASE_DIRS",
        "FILES"
      ],
      "options": ["HEADERS", "CXX_MODULES"]
    },
    "target_precompile_headers": {
      "controlKeywords": ["INTERFACE", "PUBLIC", "PRIVATE", "REUSE_FROM"]
    },
    "target_compile_definitions": {
      "controlKeywords": ["INTERFACE", "PUBLIC", "PRIVATE"]
    },
    "target_include_directories": {
      "controlKeywords": ["INTERFACE", "PUBLIC", "PRIVATE"]
    },
    "set": { "indentAfter": 0 }
  }
}

Current Status

As of version 0.0.5, it is completely usable. My CMakeLists.txt files are slightly more passable now. :laughing: More importantly, their formatting is consistent which is the top level goal for any auto-formatter, IMO.

This thing will round-trip all 2000+ LLVM/Clang/LLDB/LLD CMake files as of this writing, which is (IMO) good enough to be useful because that's a very complex build system (I'm also a little familiar with it). What does "round-trip" mean, you may ask? passable produces the same token stream before and after formatting.

It does not attempt to do anything fancy with variables or other syntax like that. So all the ${SOURCE_FILE_LISTS} and $<other:monstrosities> are left as arguments.

If you find any problems, where the output isn't still syntactically identical, please open an issue. I'll try to fix it as quickly as my very busy retired life will allow!

Implementation details

Passable is written in Typescript using the bun Javascript runtime.

I hand-wrote a lexer and parser because the grammar as documented in pieces on CMake's website is not actually close to correct or complete. I started by trying to vibe-code the tokenizer and parser with AI. That resulted in a very mediocre tokenizer and parser with lots of random problems: exactly what I'd expect from something trained on all the code you can find on GitHub. :neutral_face: Making an actual flex/bison (or probably peg.js) based parser was really more than I thought I needed. I'm not sure that's still true, after having dealt with all the stupid corner cases of where comments can go, but the whole thing is pretty small, so needing to know details of fewer tools seems like a good trade-off. Maybe I'll migrate to peg.js in the future...

I'm retired, and this is one of the sillier rabbit-holes I've gone down. I do, however, appear to have gone all the way down this rabbit-hole.

TODO's

  • [ ] Build out a condition parser. if/while/elseif condition expressions wind up getting munged pretty badly, unfortunately.
  • [ ] Make a parse-tree checker, so that I can validate full parse tree round-tripping, instead of token-level-only validation.
  • [ ] Expand .passablerc.json configuration to a broader set of options, like Prettier (and every other JS-based config tool)
  • [ ] Merge command configurations from the config file, instead of strict replacement, so to override or add a single item, you don't have to duplicate everything else.
  • [ ] As always: Improve documentation
  • [ ] Maybe export the internals a bit? The printer/parser/tokenizer might be useulf to others?