This project was created in appreciation for the design of NestedText, the readability of yamlpath queries, the utility of cattrs, and the joy of plumbum and ward -- none of which are this author's projects.
This project, NestedTextTo, provides six command line tools for convenient conversion between NestedText and other formats:
nt2json
,nt2toml
,nt2yaml
json2nt
,toml2nt
,yaml2nt
- What's NestedText?
- How does this translate to formats with more value types?
- Installation
- Usage Docs
- More Examples
- Development Docs
From the NestedText docs, with emphasis added:
NestedText is a file format for holding structured data to be entered, edited, or viewed by people. It organizes the data into a nested collection of dictionaries, lists, and strings without the need for quoting or escaping. A unique feature of this file format is that it only supports one scalar type: strings. While the decision to eschew integer, real, date, etc. types may seem counter intuitive, it leads to simpler data files and applications that are more robust.
When converting from NestedText to formats supporting more value types, all plain values will be strings by default. But you can provide options to cast any values as numbers, booleans, nulls, or dates/times, if the target format supports it, using the powerful and concise YAML Path query syntax.
$ cat example.nt
people:
-
name: Bill Sky
problems: 99
happy: False
-
name: Vorbis Florbis
problems: 6
happy: yes
$ nt2json example.nt --number /people/problems --boolean /people/happy
{
"people": [
{
"name": "Bill Sky",
"problems": 99,
"happy": false
},
{
"name": "Vorbis Florbis",
"problems": 6,
"happy": true
}
]
}
You may instead store these type mappings in a NestedText "schema" file.
$ cat example.types.nt
boolean:
- /people/happy
number:
- /people/problems
The following command will then also yield the above JSON:
$ nt2json example.nt --schema example.types.nt
Such a schema may be automatically generated from JSON/TOML/YAML:
$ json2nt --to-schema example.json
Options may be provided before or after the document, and content may be piped directly to the command instead of specifying a file.
For more YAML Path syntax information see the YAML Path wiki.
For example, you could match all items which are probably intended as booleans,
at any depth, with --boolean '/**[.=~/^(?i)(yes|no|true|false)$/]'
.
If you don't need TOML support, you can omit the [toml]
bits below.
Here are some ways to install it:
$ uv tool install 'nt2[toml]' # Install using uv (Python all-around manager)
$ pipx install 'nt2[toml]' # Install using pipx (Python app manager)
$ pipz install 'nt2[toml]' # Install using zpy (Python app and environment manager for Zsh)
$ pip install --user 'nt2[toml]' # Install in your user's environment
$ pip install 'nt2[toml]' # Install in current environment
I recommend using uv,
pipx,
or pipz
from zpy.
For Zsh completion, add this line to your .zshrc
, any time after compinit
:
if (( $+functions[compdef] )) && (( $+commands[nt2json] )) compdef _gnu_generic nt2yaml nt2toml nt2json json2nt toml2nt yaml2nt
nt2json
nt2json 0.2.7
Read NestedText and output its content as JSON.
By default, generated JSON values will only contain strings, arrays, and maps,
but you can cast nodes matching YAML Paths to boolean, null, or number.
Casting switches may be before or after file arguments.
Examples:
nt2json example.nt
nt2json <example.nt
cat example.nt | nt2json
nt2json --int People.age --boolean 'People."is a wizard"' example.nt
Usage:
nt2json [SWITCHES] input_files...
Meta-switches:
-h, --help Prints this help message and quits
-v, --version Prints the program's version and quits
Switches:
--boolean, -b YAMLPATH:str Cast each node matching the given YAML Path
query as boolean; may be given multiple
times
--null, -n YAMLPATH:str Cast each node matching the given YAML Path
query as null, if it is an empty string; may
be given multiple times
--number, --int, --float, -i, -f YAMLPATH:str
Cast each node matching the given YAML Path
query as a number; may be given multiple
times
--schema, -s NESTEDTEXTFILE:ExistingFile
Cast nodes matching YAML Path queries
specified in a NestedText document. It must
be a map with one or more of the keys:
'null', 'boolean', 'number'Each key's value
is a list of YAML Paths.; may be given
multiple times
nt2yaml
nt2yaml 0.2.7
Read NestedText and output its content as YAML.
By default, generated YAML values will only contain strings, arrays, and maps,
but you can cast nodes matching YAML Paths to boolean, null, number, or date.
Casting switches may be before or after file arguments.
Examples:
nt2yaml example.nt
nt2yaml <example.nt
cat example.nt | nt2yaml
nt2yaml --int People.age --boolean 'People."is a wizard"' example.nt
Usage:
nt2yaml [SWITCHES] input_files...
Meta-switches:
-h, --help Prints this help message and quits
-v, --version Prints the program's version and quits
Switches:
--boolean, -b YAMLPATH:str Cast each node matching the given YAML Path
query as boolean; may be given multiple
times
--date, -d YAMLPATH:str Cast each node matching the given YAML Path
query as a date, assuming it's ISO 8601; may
be given multiple times
--null, -n YAMLPATH:str Cast each node matching the given YAML Path
query as null, if it is an empty string; may
be given multiple times
--number, --int, --float, -i, -f YAMLPATH:str
Cast each node matching the given YAML Path
query as a number; may be given multiple
times
--schema, -s NESTEDTEXTFILE:ExistingFile
Cast nodes matching YAML Path queries
specified in a NestedText document. It must
be a map with one or more of the keys:
'null', 'boolean', 'number'Each key's value
is a list of YAML Paths.; may be given
multiple times
nt2toml
nt2toml 0.2.7
Read NestedText and output its content as TOML.
By default, generated TOML values will only contain strings, arrays, and maps,
but you can cast nodes matching YAML Paths to boolean, number, or date.
Casting switches may be before or after file arguments.
Examples:
nt2toml example.nt
nt2toml <example.nt
cat example.nt | nt2toml
nt2toml --int People.age --boolean 'People."is a wizard"' example.nt
Usage:
nt2toml [SWITCHES] input_files...
Meta-switches:
-h, --help Prints this help message and quits
-v, --version Prints the program's version and quits
Switches:
--boolean, -b YAMLPATH:str Cast each node matching the given YAML Path
query as boolean; may be given multiple
times
--date, -d YAMLPATH:str Cast each node matching the given YAML Path
query as a date, assuming it's ISO 8601; may
be given multiple times
--number, --int, --float, -i, -f YAMLPATH:str
Cast each node matching the given YAML Path
query as a number; may be given multiple
times
--schema, -s NESTEDTEXTFILE:ExistingFile
Cast nodes matching YAML Path queries
specified in a NestedText document. It must
be a map with one or more of the keys:
'null', 'boolean', 'number'Each key's value
is a list of YAML Paths.; may be given
multiple times
json2nt
json2nt 0.2.7
Read JSON and output its content as NestedText.
Examples:
json2nt example.json
json2nt <example.json
cat example.json | json2nt
Usage:
json2nt [SWITCHES] input_files...
Meta-switches:
-h, --help Prints this help message and quits
-v, --version Prints the program's version and quits
Switches:
--to-schema, -s Rather than convert the inputs, generate a schema
yaml2nt
yaml2nt 0.2.7
Read YAML and output its content as NestedText.
Examples:
yaml2nt example.yml
yaml2nt <example.yml
cat example.yml | yaml2nt
Usage:
yaml2nt [SWITCHES] input_files...
Meta-switches:
-h, --help Prints this help message and quits
-v, --version Prints the program's version and quits
Switches:
--to-schema, -s Rather than convert the inputs, generate a schema
toml2nt
toml2nt 0.2.7
Read TOML and output its content as NestedText.
Examples:
toml2nt example.yml
toml2nt <example.yml
cat example.yml | toml2nt
Usage:
toml2nt [SWITCHES] input_files...
Meta-switches:
-h, --help Prints this help message and quits
-v, --version Prints the program's version and quits
Switches:
--to-schema, -s Rather than convert the inputs, generate a schema
YAML officially supports non-string key types, like maps, lists, and numbers. Support for non-string keys varies from one YAML parser to the next, and is currently not handled by NestedTextTo.
If anyone is interested in using NestedTextTo with non-string key types, please open an issue and I'll see what I can do!
$ cat log.jsonl
Output
{"chat_id": 651321, "event": "receiving code", "user_first_name": "Andy", "user_id": 651321}
{"event": "guessed syntax", "ext": null, "probability": 0.05201493203639984, "probability_min": 0.12, "syntax": "Matlab"}
{"chat_id": 651321, "event": "colorizing code", "syntax": "py3", "user_first_name": "Andy", "user_id": 651321}
{"event": "Got deletion request", "reply_to_msg_user_id": 651321, "user_id": 651321}
{"chat_id": 651321, "event": "failed to delete message (it's probably gone already)", "exception": "Traceback (most recent call last):\n File \"/home/andy/Code/colorcodebot/app/colorcodebot.py\", line 278, in delete_after_delay\n bot.delete_message(message.chat.id, message.message_id)\n File \"/home/andy/.local/share/venvs/84f7fb558856f9ccc2c54e3d122862b6/venv/lib/python3.10/site-packages/telebot/__init__.py\", line 1081, in delete_message\n return apihelper.delete_message(self.token, chat_id, message_id, timeout)\n File \"/home/andy/.local/share/venvs/84f7fb558856f9ccc2c54e3d122862b6/venv/lib/python3.10/site-packages/telebot/apihelper.py\", line 1299, in delete_message\n return _make_request(token, method_url, params=payload, method='post')\n File \"/home/andy/.local/share/venvs/84f7fb558856f9ccc2c54e3d122862b6/venv/lib/python3.10/site-packages/telebot/apihelper.py\", line 152, in _make_request\n json_result = _check_result(method_name, result)\n File \"/home/andy/.local/share/venvs/84f7fb558856f9ccc2c54e3d122862b6/venv/lib/python3.10/site-packages/telebot/apihelper.py\", line 179, in _check_result\n raise ApiTelegramException(method_name, result, result_json)\ntelebot.apihelper.ApiTelegramException: A request to the Telegram API was unsuccessful. Error code: 400. Description: Bad Request: message to delete not found"}
$ json2nt log.jsonl
Output
-
chat_id: 651321
event: receiving code
user_first_name: Andy
user_id: 651321
-
event: guessed syntax
ext:
probability: 0.05201493203639984
probability_min: 0.12
syntax: Matlab
-
chat_id: 651321
event: colorizing code
syntax: py3
user_first_name: Andy
user_id: 651321
-
event: Got deletion request
reply_to_msg_user_id: 651321
user_id: 651321
-
chat_id: 651321
event: failed to delete message (it's probably gone already)
exception:
> Traceback (most recent call last):
> File "/home/andy/Code/colorcodebot/app/colorcodebot.py", line 278, in delete_after_delay
> bot.delete_message(message.chat.id, message.message_id)
> File "/home/andy/.local/share/venvs/84f7fb558856f9ccc2c54e3d122862b6/venv/lib/python3.10/site-packages/telebot/__init__.py", line 1081, in delete_message
> return apihelper.delete_message(self.token, chat_id, message_id, timeout)
> File "/home/andy/.local/share/venvs/84f7fb558856f9ccc2c54e3d122862b6/venv/lib/python3.10/site-packages/telebot/apihelper.py", line 1299, in delete_message
> return _make_request(token, method_url, params=payload, method='post')
> File "/home/andy/.local/share/venvs/84f7fb558856f9ccc2c54e3d122862b6/venv/lib/python3.10/site-packages/telebot/apihelper.py", line 152, in _make_request
> json_result = _check_result(method_name, result)
> File "/home/andy/.local/share/venvs/84f7fb558856f9ccc2c54e3d122862b6/venv/lib/python3.10/site-packages/telebot/apihelper.py", line 179, in _check_result
> raise ApiTelegramException(method_name, result, result_json)
> telebot.apihelper.ApiTelegramException: A request to the Telegram API was unsuccessful. Error code: 400. Description: Bad Request: message to delete not found
For local development, it's recommended to activate a venv, then
$ pip install -r local-requirements.txt
From there, you may want to look at common task definitions:
$ task -l
$ nox -l
And you may wish to browse the structure and in-code documentation as rendered HTML, at the GitHub Pages site.