https://github.com/progrium/clon-spec
Command-Line Object Notation: Ergonomic JSON-compatible input syntax for CLI tools.
https://github.com/progrium/clon-spec
Last synced: 12 months ago
JSON representation
Command-Line Object Notation: Ergonomic JSON-compatible input syntax for CLI tools.
- Host: GitHub
- URL: https://github.com/progrium/clon-spec
- Owner: progrium
- License: mit
- Created: 2022-10-21T18:30:11.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2022-10-23T16:23:08.000Z (over 3 years ago)
- Last Synced: 2025-01-14T12:15:45.802Z (over 1 year ago)
- Homepage:
- Size: 5.86 KB
- Stars: 23
- Watchers: 4
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Command-Line Object Notation
Ergonomic JSON-compatible input syntax for CLI tools.
CLON is an argument syntax spec for defining JSON-like objects in a way that makes sense for the command-line that isn't just taking raw JSON or pointing to a file. This was shamelessly stolen from [HTTPie](https://httpie.io/docs/cli/json) with some modification.
CLON is not meant to be a tool itself, but an argument syntax that you can use when building CLI tools that need to take arbitrary JSON-like data. An example tool would be a CLI client for an RPC system with a usage format like `rpctool call `. CLON makes for a reasonably human-friendly way to input the argument data. Note that internally the tool may not use this syntax to build JSON, but a compatible format like CBOR, or even just to build an in-memory structure.
### Implementations
Below are some language specific libraries for easily accepting this syntax in your command-line tools:
* [progrium/clon-go](https://github.com/progrium/clon-go)
* your library here
## Simple Example
In these examples, `clon` is an *imaginary tool* that takes CLON arguments and outputs the resulting structure as JSON.
```shell
$ clon name=John email=john@example.org
```
```json
{
"name": "John",
"email": "john@example.org"
}
```
If the first argument is in key-value form (with `=` or `:=` operator), the arguments will be used to construct an object. If the first argument is simply a value, the arguments will be used to construct an array.
```shell
$ clon John john@example.org "Text string"
```
```json
[
"John",
"john@example.org",
"Text string"
]
```
## Non-string JSON fields
Non-string JSON fields use the `:=` separator, which allows you to embed arbitrary JSON data into the resulting JSON object. Additionally, text and raw JSON files can also be embedded into fields using `=@` and `:=@`:
```shell
$ clon \
name=John \ # String (default)
age:=29 \ # Raw JSON — Number
married:=false \ # Raw JSON — Boolean
hobbies:='["http", "pies"]' \ # Raw JSON — Array
favorite:='{"tool": "HTTPie"}' \ # Raw JSON — Object
bookmarks:=@files/data.json \ # Embed JSON file
description=@files/text.txt # Embed text file
```
```json
{
"age": 29,
"hobbies": [
"http",
"pies"
],
"description": "John is a nice guy who likes pies.",
"married": false,
"name": "John",
"favorite": {
"tool": "HTTPie"
},
"bookmarks": {
"HTTPie": "https://httpie.org",
}
}
```
When building a top level array, values can be prefixed with `:` to indicate a non-string or raw JSON value. This is a shorthand for the top level array syntax described later.
```shell
$ clon John :29 :false :'{"tool": "HTTPie"}'
```
```json
[
"John",
29,
false,
{
"tool": "HTTPie"
}
]
```
## Nested JSON
You still use the existing data field operators (`=`/`:=`) but instead of specifying a top-level field name (like `key=value`), you specify a path declaration telling where and how to put the given value inside an object:
```shell
$ clon \
platform[name]=HTTPie \
platform[about][mission]='Make APIs simple and intuitive' \
platform[about][homepage]=httpie.io \
platform[about][stars]:=54000 \
platform[apps][]=Terminal \
platform[apps][]=Desktop \
platform[apps][]=Web \
platform[apps][]=Mobile
```
```json
{
"platform": {
"name": "HTTPie",
"about": {
"mission": "Make APIs simple and intuitive",
"homepage": "httpie.io",
"stars": 54000
},
"apps": [
"Terminal",
"Desktop",
"Web",
"Mobile"
]
}
}
```
### Basic Usage
Let’s start with a simple example, and build a simple search query:
```shell
$ clon \
category=tools \
search[type]=id \
search[id]:=1
```
```json
{
"category": "tools",
"search": {
"id": 1,
"type": "id"
}
}
```
In the example above, the `search[type]` is an instruction for creating an object called `search`, and setting the `type` field of it to the given value (`"id"`).
Also note that, just as the regular syntax, you can use the `:=` operator to directly pass raw JSON values (e.g, numbers in the case above).
Building arrays is also possible, through `[]` suffix (an append operation). This creates an array in the given path (if there is not one already), and append the given value to that array.
```shell
$ clon \
category=tools \
search[type]=keyword \
search[keywords][]=APIs \
search[keywords][]=CLI
```
```json
{
"category": "tools",
"search": {
"keywords": [
"APIs",
"CLI"
],
"type": "keyword"
}
}
```
If you want to explicitly specify the position of elements inside an array, you can simply pass the desired index as the path:
```shell
$ clon \
category=tools \
search[type]=keyword \
search[keywords][1]=APIs \
search[keywords][0]=CLI
```
```json
{
"category": "tools",
"search": {
"keywords": [
"CLIs",
"API"
],
"type": "keyword"
}
}
```
If there are any missing indexes, they will be nullified in order to create a concrete object:
```shell
$ clon \
category=tools \
search[type]=platforms \
search[platforms][]=Terminal \
search[platforms][1]=Desktop \
search[platforms][3]=Mobile
```
```json
{
"category": "tools",
"search": {
"platforms": [
"Terminal",
"Desktop",
null,
"Mobile"
],
"type": "platforms"
}
}
```
It is also possible to embed raw JSON to a nested structure, for example:
```shell
$ clon \
category=tools \
search[type]=platforms \
search[platforms]:='["Terminal", "Desktop"]' \
search[platforms][]=Web \
search[platforms][]=Mobile
```
```json
{
"category": "tools",
"search": {
"platforms": [
"Terminal",
"Desktop",
"Web",
"Mobile"
],
"type": "platforms"
}
}
```
And just to demonstrate all of these features together, let’s create a very deeply nested JSON object:
```shell
$ clon \
shallow=value \ # Shallow key-value pair
object[key]=value \ # Nested key-value pair
array[]:=1 \ # Array — first item
array[1]:=2 \ # Array — second item
array[2]:=3 \ # Array — append (third item)
very[nested][json][3][httpie][power][]=Amaze # Nested object
```
### Advanced Usage
#### Top level arrays
To build an array instead of a regular object, you can simply do that by omitting the starting key:
```shell
$ clon \
[]:=1 \
[]:=2 \
[]:=3
```
```json
[
1,
2,
3
]
```
As mentioned before, there is a shorthand to make this scenario much easier. The first argument must not be a key-value, but key-values can be used in later arguments producing a single key object. You can also use the raw value prefix `:` to include JSON arrays or objects.
```shell
$ clon :1 :2 :3 valid:=true :'[4, 5, 6]'
```
```json
[
1,
2,
3,
{
"valid": true
},
[
4,
5,
6
]
]
```
For more complex top level elements, you can use the standard notation to apply nesting to the items by referencing their index:
```shell
$ clon \
[0][type]=platform [0][name]=terminal \
[1][type]=platform [1][name]=desktop
```
```json
[
{
"type": "platform",
"name": "terminal"
},
{
"type": "platform",
"name": "desktop"
}
]
```
#### Escaping behavior
Nested JSON syntax uses the same escaping rules as the terminal. There are 3 special characters, and 1 special token that you can escape.
To include a bracket as is, escape it with a backslash (`\`):
```shell
$ clon \
'foo\[bar\]:=1' \
'baz[\[]:=2' \
'baz[\]]:=3'
```
```json
{
"baz": {
"[": 2,
"]": 3
},
"foo[bar]": 1
}
```
If use the literal backslash character (`\`), escape it with another backslash:
```shell
$ clon \
'backslash[\\]:=1'
```
```json
{
"backslash": {
"\\": 1
}
}
```
A regular integer in a path (e.g `[10]`) means an array index; but if you want it to be treated as a string, you can escape the whole number by using a backslash (`\`) prefix.
```shell
$ clon \
'object[\1]=stringified' \
'object[\100]=same' \
'array[1]=indexified'
```
```json
{
"array": [
null,
"indexified"
],
"object": {
"1": "stringified",
"100": "same"
}
}
```
#### Guiding syntax errors
If you make a typo or forget to close a bracket, the errors SHOULD guide you to fix it. For example:
```
$ clon \
'foo[bar]=OK' \
'foo[baz][quux=FAIL'
Syntax Error: Expecting ']'
foo[baz][quux
^
```
You can follow to given instruction (adding a `]`) and repair your expression.
#### Type safety
Each container path (e.g., `x[y][z]` in `x[y][z][1]`) has a certain type, which gets defined with the first usage and can’t be changed after that. If you try to do a key-based access to an array or an index-based access to an object, you should get an error out:
```
$ clon \
'array[]:=1' \
'array[]:=2' \
'array[key]:=3'
Type Error: Can't perform 'key' based access on 'array' which has a type of 'array' but this operation requires a type of 'object'.
array[key]
^^^^^
```
Type Safety does not apply to value overrides, for example:
```shell
$ clon \
user[name]:=411 # Defined as an integer
user[name]=string # Overridden with a string
```
```json
{
"user": {
"name": "string"
}
}
```
## Raw JSON
For very complex JSON structures, it may be more convenient to pass it as raw input, for example:
```shell
$ echo -n '{"hello": "world"}' | clon
```
```shell
$ clon < files/data.json
```