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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@suchipi/at-js

v1.6.1

Published

unix stdin-pipe-based tools for transforming data with JavaScript

Downloads

119

Readme

@suchipi/at-js

@ - JavaScript stdio transformation tool

Quick Example

cat package.json | @ .dependencies | @ Object.keys | @ '.join("\n")'

Overview

@ (read as 'at') is a flexible command-line program used to read, iterate over, transform, and create text.

@ achieves this by leveraging:

  • Unix stdio streams
  • JSON (de)serialization
  • and JavaScript evaluation

It's suitable for use-cases such as:

  • Executing a CLI command once for each file in the current directory
  • Transforming lines of text from one format into another (change colons into equals signs, remove prefixes/suffixes from each line, etc)
  • Parsing an arbitrary text structure into JSON
  • Formatting and printing the data in a JSON file as human-readable text, with colors

The goal of @ is to make text transformation and file iteration easier to do on the command-line. It supercharges the sed/awk/grep/xargs workflow by exposing the full power of JavaScript in a way that doesn't require writing .js files or learning Node.js APIs.

Basic Usage

Here's how it works:

  • When you pipe into @, it gathers all of the data it receives on stdin as a string.
  • If that string contains JSON, it parses it into a JSON object.
  • Then, it passes your input string/object into a JavaScript function, that you provide on the command-line.
  • Finally, the result of your function is written to stdout.

Here's an example of what it looks like:

ls | @ 'data => data.split("\n")'

This command line does the following:

  • lists all the files in the current working directory (ls)
  • pipes (|) the output of that command into @
  • instructs @ to transform that input string by using the JavaScript function data => data.split("\n").
  • prints the result of that function to stdout.

The function receives that input data as a string, and because it uses the string's "split" method, it returns an Array.

Whenever the function you give to @ returns something other than a string, @ checks if that object can be represented using JSON without losing any important information.

If so, then @ will use JSON.stringify on it prior to printing it to stdout.

As such, the above command line will output something like this:

[
  "bin",
  "lib",
  "LICENSE",
  "node_modules",
  "package.json",
  "package-lock.json",
  "README.md",
  ""
]

Because @ will automatically parse any JSON it receives as input, this output can be piped into @ again to transform it further:

ls | @ 'data => data.split("\n")' | @ 'data => data.slice(0, 3)'

The above command line passes the resulting array back into @ again, where it gets sliced into a subarray containing the first 3 items. As such, the above command line would output:

["bin", "lib", "LICENSE"]

Features, or: How To Make Code In Strings Not Terrible

You might be thinking:

"I dunno, that looks kinda janky"

And if so, I wouldn't blame you. I'll be the first to admit that writing JavaScript expressions embedded inside single-quoted shell strings isn't exactly a great experience.

However, I felt the idea of combining the shell and JavaScript was still really enticing. So, I designed @'s API with the goal of improving that experience as much as possible.

In order to facilitate that, I added several features to @:

1. If your input function string starts with '.', it'll automatically be prefixed with $it => $it.

Therefore, instead of writing this:

ls | @ 'data => data.split("\n")' | @ 'data => data.slice(0, 3)'

You can write this:

ls | @ '.split("\n")' | @ '.slice(0, 3)'

This can also be used to access nested properties on an object:

cat package.json | @ '.version'

2. If your input function string starts with .[, that .[ will be replaced with $it => $it[.

This is similar to feature #1; it gives you the same conveniences, but for number keys and computed property access:

ls | @ '.split("\n")' | @ '.[0].toUpperCase()'

3. If your input function string doesn't start with a property access, but contains $it, it will be prefixed with $it => .

This provides the benefits of features #1 and #2 to any expression, though it's a bit harder to understand at a glance.

ls | @ console.log($it)

4. You can use --target or -t to apply the function to only the value found at a specific property path, and you can use * as a wildcard.

This reduces the amount of boilerplate code needed to access and modify structures. For instance, given this data structure:

[
  {
    "label": "Pizza",
    "tags": ["italian", "cheese", "umami"]
  },
  {
    "label": "Ice Cream",
    "tags": ["dessert", "cold", "sweet"]
  },
  {
    "label": "Red Bean Buns",
    "tags": ["snack", "warm", "sweet"]
  }
]

If you wanted to capitalize the first letter of each tag, you might need to write a function like this:

(input) =>
  input.map((food) => ({
    ...food,
    tags: food.tags.map((tag) => tag[0].toUpperCase() + tag.slice(1)),
  }));

Which is a really long function to write on the command-line, with lots of opportunity for mismatched parentheses and brackets.

Instead, you can use --target to point the function at multiple deep object property paths in the data structure:

@ --target '*.tags.*' 'tag => tag[0].toUpperCase() + tag.slice(1)'

5. @ makes several helpful globals available to your function.

Node.js has a lot of powerful APIs; for instance, the child_process API is super useful for spawning subprocesses and subshells. However, it's not really suitable for use in small function strings on the command line. As such, @ offers an alternative: the exec function.

exec is a wrapper around Node.js's child_process.spawnSync function. It's available as a global inside of functions you pass to @.

If you call it with a string:

exec("echo hi");

It'll run that command in a subshell, then return its stdout as a string.

Additionally, if the command exits with a nonzero status code, exec will throw an Error with the stdout/stderr/code of the command.

You can also call it with multiple strings:

exec("echo", "hi");

And it will join all those strings together with a space between each, then run the joined string as a command.

The string returned from exec also has three properties on it:

exec("echo", "hi").stdout; // What was written to stdout; string.
exec("echo", "hi").stderr; // What was written to stderr; string.
exec("echo", "hi").code; // The exit status code; number.

@ also creates a global alias for JSON.stringify: quote.

quote("hello"); // returns '"hello"'

quote can be useful when working with filenames that might have a space in them:

(file) => exec("cp", quote(file), quote(file + ".bak"));

@ also makes all of the functions from the kleur package available as globals:

  • reset
  • bold
  • dim
  • italic
  • underline
  • inverse
  • hidden
  • strikethrough
  • black
  • red
  • green
  • yellow
  • blue
  • magenta
  • cyan
  • white
  • gray
  • grey
  • bgBlack
  • bgRed
  • bgGreen
  • bgYellow
  • bgBlue
  • bgMagenta
  • bgCyan
  • bgWhite

These functions can be used to style text and change its color:

bold("IMPORTANT");

red("FAILURE");

bold(green("SUCCESS!"));

6. @ will automatically attempt to call require on your behalf if you access an undefined global.

Libraries in your local node_modules folder can be super helpful, but trying to write a 'require' call inside of a shell string can be a little inconvenient; you have to make sure to use a different quote from the one you're wrapping your entire function with, or else you'll have to escape stuff, and.... it just gets dicey.

So, @ will do its best to automatically require node modules for you. Here's some examples of what it can do:

  • accessing fs as a global auto-requires the "fs" module
  • accessing child_process as a global auto-requires the "child_process" module
  • accessing changeCase as a global auto-requires the "change-case" module (if present in node_modules)
  • accessing __babel_types as a global auto-requires the "@babel/types" module (if present in node_modules)

The general rule is that you should write your module name using camelCase instead of kebab-case. In the case of scoped packages, you should replace the @ sign with two underscores, and the slash with one underscore (this is the same convention that the @types stuff on npm uses).

Installation

npm install -g @suchipi/at-js

License

MIT