# `@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:

```sh
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:

```json
[
  "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:

```sh
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:

```json
["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:

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

You can write this:

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

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

```sh
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:

```sh
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.

```sh
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:

```json
[
  {
    "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:

```js
(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:

```sh
@ --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:

```js
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:

```js
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:

```js
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.

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

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

```js
(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:

```js
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

```sh
npm install -g @suchipi/at-js
```

## License

MIT
