CLI shorthand syntax for Go

CLI Shorthand Syntax

CLI shorthand syntax is a contextual shorthand syntax for passing structured data into commands that require e.g. JSON/YAML. While you can always pass full JSON or other documents through stdin, you can also specify or modify them by hand as arguments to the command using this shorthand syntax. For example:

$ my-cli do-something foo.bar[0].baz: 1, .hello: world

Would result in the following body contents being sent on the wire (assuming a JSON media type is specified in the OpenAPI spec):

{
  "foo": {
    "bar": [
      {
        "baz": 1,
        "hello": "world"
      }
    ]
  }
}

The shorthand syntax supports the following features, described in more detail with examples below:

  • Automatic type coercion & forced strings
  • Nested object creation
  • Object property grouping
  • Nested array creation
  • Appending to arrays
  • Both object and array backreferences
  • Loading property values from files
    • Supports structured, forced string, and base64 data

Alternatives & Inspiration

The CLI shorthand syntax is not the only one you can use to generate data for CLI commands. Here are some alternatives:

For example, the shorthand example given above could be rewritten as:

$ jo -p foo=$(jo -p bar=$(jo -a $(jo baz=1 hello=world))) | my-cli do-something

The shorthand syntax implementation described herein uses those and the following for inspiration:

It seems reasonable to ask, why create a new syntax?

  1. Built-in. No extra executables required. Your CLI ships ready-to-go.
  2. No need to use sub-shells to build complex structured data.
  3. Syntax is closer to YAML & JSON and mimics how you do queries using tools like jq and jmespath.

Features in Depth

You can use the included j executable to try out the shorthand format examples below. Examples are shown in JSON, but the shorthand parses into structured data that can be marshalled as other formats, like YAML or TOML if you prefer.

go get -u github.com/danielgtaylor/shorthand/cmd/j

Also feel free to use this tool to generate structured data for input to other commands.

Keys & Values

At its most basic, a structure is built out of key & value pairs. They are separated by commas:

$ j hello: world, question: how are you?
{
  "hello": "world",
  "question": "how are you?"
}

Types and Type Coercion

Well-known values like null, true, and false get converted to their respective types automatically. Numbers also get converted. Similar to YAML, anything that doesn't fit one of those is treated as a string. If needed, you can disable this automatic coercion by forcing a value to be treated as a string with the ~ operator. Note: the ~ modifier must come directly after the colon.

# With coercion
$ j empty: null, bool: true, num: 1.5, string: hello
{
  "bool": true,
  "empty": null,
  "num": 1.5,
  "string": "hello"
}

# As strings
$ j empty:~ null, bool:~ true, num:~ 1.5, string:~ hello
{
  "bool": "true",
  "empty": "null",
  "num": "1.5",
  "string": "hello"
}

# Passing the empty string
$ j blank:~
{
  "blank": ""
}

# Passing a tilde using whitespace
$ j foo: ~/Documents
{
  "foo": "~/Documents"
}

# Passing a tilde using forced strings
$ j foo:~~/Documents
{
  "foo": "~/Documents"
}

Objects

Nested objects use a . separator when specifying the key.

$ j foo.bar.baz: 1
{
  "foo": {
    "bar": {
      "baz": 1
    }
  }
}

Properties of nested objects can be grouped by placing them inside { and }.

$ j foo.bar{id: 1, count.clicks: 5}
{
  "foo": {
    "bar": {
      "count": {
        "clicks": 5
      },
      "id": 1
    }
  }
}

Arrays

Simple arrays use a , between values. Nested arrays use square brackets [ and ] to specify the zero-based index to insert an item. Use a blank index to append to the array.

# Array shorthand
$ j a: 1, 2, 3
{
  "a": [
    1,
    2,
    3
  ]
}

# Nested arrays
$ j a[0][2][0]: 1
{
  "a": [
    [
      null,
      null,
      [
        1
      ]
    ]
  ]
}

# Appending arrays
$ j a[]: 1, a[]: 2, a[]: 3
{
  "a": [
    1,
    2,
    3
  ]
}

Backreferences

Since the shorthand syntax is context-aware, it is possible to use the current context to reference back to the most recently used object or array when creating new properties or items.

# Backref with object properties
$ j foo.bar: 1, .baz: 2
{
  "foo": {
    "bar": 1,
    "baz": 2
  }
}

# Backref with array appending
$ j foo.bar[]: 1, []: 2, []: 3
{
  "foo": {
    "bar": [
      1,
      2,
      3
    ]
  }
}

# Easily build complex structures
$ j name: foo, tags[]{id: 1, count.clicks: 5, .sales: 1}, []{id: 2, count.clicks: 8, .sales: 2}
{
  "name": "foo",
  "tags": [
    {
      "count": {
        "clicks": 5,
        "sales": 1
      },
      "id": 1
    },
    {
      "count": {
        "clicks": 8,
        "sales": 2
      },
      "id": 2
    }
  ]
}

Loading from Files

Sometimes a field makes more sense to load from a file than to be specified on the commandline. The @ preprocessor and ~ & % modifiers let you load structured data, strings, and base64-encoded data into values.

# Load a file's value as a parameter
$ j foo: @hello.txt
{
  "foo": "hello, world"
}

# Load structured data
$ j foo: @hello.json
{
  "foo": {
    "hello": "world"
  }
}

# Force loading a string
$ j foo: @~hello.json
{
  "foo": "{\n  \"hello\": \"world\"\n}"
}

# Load as base 64 data
$ j foo: @%hello.json
{
  "foo": "ewogICJoZWxsbyI6ICJ3b3JsZCIKfQ=="
}

Remember, it's possible to disable this behavior with the string modifier ~:

$ j twitter:~ @user
{
  "twitter": "@user"
}

Implementation

The shorthand syntax is implemented as a PEG grammar which creates an AST-like object that is used to build an in-memory structure that can then be serialized out into formats like JSON, YAML, TOML, etc.

The shorthand.peg file implements the parser while the shorthand.go file implements the builder. Here's how you can test local changes to the grammar:

# One-time setup: install PEG compiler
$ go get -u github.com/mna/pigeon

# Make your shorthand.peg edits. Then:
$ go generate

# Next, rebuild the j executable.
$ go install ./cmd/j

# Now, try it out!
$ j <your-new-thing>
Comments
  • fix: improved error handling, build action, cleaned deps

    fix: improved error handling, build action, cleaned deps

    This PR significantly improves error display for multiline inputs, adds some extra error handling around common cases like forgetting to close '}' while in an array or forgetting to close ", adds a new automated Github Actions build which submits coverage, and runs go mod tidy.

  • fix(GetPath): string truncation nil result

    fix(GetPath): string truncation nil result

    This fixes a bug where an attempt to use string truncation via e.g. field[:10] would result in nil if the input string is shorter than 10 characters. The desired behavior is that it should include the entire string so you can provide a limit of how many characters to include.

  • fix: parsing bug with projection query slicing, add test

    fix: parsing bug with projection query slicing, add test

    This fixes a bug that could easily be reproduced via Restish:

    restish api.rest.sh/example -f 'body.work.{name, highlights: highlights[0:2]}'
    

    This resulted in a key like highlights[0 instead of parsing the slice properly. This PR fixes that and adds a new test to capture this use case.

  • fix: improved query test coverage

    fix: improved query test coverage

    Adds a bunch of new tests to cover previously uncovered code. Fixes one discovered bug relating to quoted object properties when doing field selection.

  • feat: version 2 rewrite

    feat: version 2 rewrite

    This is a major rewrite and backward-incompatible change for shorthand v2. See the README for details.

    Breaking changes:

    The difficult choice to remove some features was made, primarily because after a couple years of use the features were considered confusing by users or saw little to no adoption/usage.

    • Removal of backreferences feature (confusing)
    • Removal of simple scalar arrays shorthand (confusing)
    • Removal of string coercion via ~ (confusing, use quotes instead)
    • Rename shorthand.Get -> shorthand.Marshal (consistency)

    New features:

    • Over 20x faster than shorthand v1 :rocket:
    • Non-map root objects (scalars, arrays, etc)
    • JSON is valid shorthand
    • Binary format support, e.g. CBOR
    • Full-fledged object patching support (inserting, unsetting, swapping, etc)
    • Simple query syntax built-in
    • Great test coverage
    • Fuzz testing
    # Comparing new (V2) vs. old (V1)
    BenchmarkShorthandV2-12     309817    3825 ns/op    1888 B/op    54 allocs/op
    BenchmarkShorthandV1-12      14670   83901 ns/op   36436 B/op   745 allocs/op
    
  • feat: port stdin loading/override logic from Restish

    feat: port stdin loading/override logic from Restish

    This copies over some logic from Restish to simplify reading data from os.Stdin as well as merging it with data passed as command line arguments. Use the new shorthand.GetInput(args) function for this purpose.

argv - Go library to split command line string as arguments array using the bash syntax.

Argv Argv is a library for Go to split command line string into arguments array. Documentation Documentation can be found at Godoc Example func TestAr

Nov 19, 2022
Generate High Level Cloud Architecture diagrams using YAML syntax.
Generate High Level Cloud Architecture diagrams using YAML syntax.

A commandline tool that generate High Level microservice & serverless Architecture diagrams using a declarative syntax defined in a YAML file.

Dec 24, 2022
Improved go doc with terminal syntax highlighting
Improved go doc with terminal syntax highlighting

GopherDoc Improved go doc with terminal syntax highlighting. This is a modification of the original go doc command that adds terminal syntax highlight

Nov 22, 2022
An alternative syntax to generate YAML (or JSON) from commandline

yo An alternative syntax to generate YAML (or JSON) from commandline. The ultimate commanline YAML (or JSON) generator! ... I'm kidding of course! but

Jul 30, 2022
GDScript Syntax Highlighting in GNU Nano

nano-gdscript GDScript Syntax Highlighting in GNU Nano. Updated regularly every minor updates. Contributions are welcomed Installation This is 100% fr

Dec 20, 2022
sleep command accepting Go duration syntax.

gsleep sleep command accepting Go duration syntax. Installation go install github.com/forestgagnon/gsleep@latest Usage # Sleep 5 minutes gsleep 5m #

Jan 14, 2022
Elegant CLI wrapper for kubeseal CLI

Overview This is a wrapper CLI ofkubeseal CLI, specifically the raw mode. If you just need to encrypt your secret on RAW mode, this CLI will be the ea

Jan 8, 2022
CLI to run a docker image with R. CLI built using cobra library in go.
CLI  to run a docker image with R. CLI built using cobra library in go.

BlueBeak Installation Guide Task 1: Building the CLI The directory structure looks like Fastest process: 1)cd into bbtools 2)cd into bbtools/bin 3)I h

Dec 20, 2021
A wrapper of aliyun-cli subcommand alidns, run aliyun-cli in Declarative mode.

aliyun-dns A wrapper of aliyun-cli subcommand alidns, run aliyun-cli in Declarative mode. Installation Install aliyun-cli. Usage $ aliyun-dns -h A wra

Dec 21, 2021
Symfony-cli - The Symfony CLI tool For Golang

Symfony CLI Install To install Symfony CLI, please download the appropriate vers

Dec 28, 2022
Go-file-downloader-ftctl - A file downloader cli built using golang. Makes use of cobra for building the cli and go concurrent feature to download files.

ftctl This is a file downloader cli written in Golang which uses the concurrent feature of go to download files. The cli is built using cobra. How to

Jan 2, 2022
Cli-algorithm - A cli program with A&DS in go!

cli-algorithm Objectives The objective of this cli is to implement 4 basic algorithms to sort arrays been Merge Sort Insertion Sort Bubble Sort Quick

Jan 2, 2022
Nebulant-cli - Nebulant's CLI
Nebulant-cli - Nebulant's CLI

Nebulant CLI Website: https://nebulant.io Documentation: https://nebulant.io/docs.html The Nebulant CLI tool is a single binary that can be used as a

Jan 11, 2022
News-parser-cli - Simple CLI which allows you to receive news depending on the parameters passed to it
News-parser-cli - Simple CLI which allows you to receive news depending on the parameters passed to it

news-parser-cli Simple CLI which allows you to receive news depending on the par

Jan 4, 2022
Go-api-cli - Small CLI to fetch data from an API sync and async

Async API Cli CLI to fetch data on "todos" from a given API in a number of ways.

Jan 13, 2022
Syno-cli - Synology unofficial API CLI and library

Synology CLI Unofficial wrapper over Synology API in Go. Focus on administrative

Jan 6, 2023
Nebula Diagnosis CLI Tool is an information diagnosis cli tool for the nebula service and the node to which the service belongs.

Nebula Diagnosis CLI Tool is an information diagnosis cli tool for the nebula service and the node to which the service belongs.

Jan 12, 2022
bcrypt-cli is the CLI tool for hashing passwords with bcrypt.

bcrypt-cli bcrypt-cli is the CLI tool for hashing passwords with bcrypt. Install go install github.com/ryicoh/bcrypt-cli Usage It can be used like bas

Jan 9, 2023