The pysds Command¶
Introduction¶
Installing pysds-tools also installs a command, pysds. It reads a
structure described in a JSON or YAML file, builds it with the classes
of the library, and displays it with the Visualizing Structures. It needs no
Python code: a structure can be written by hand, generated by another
program, or kept next to a document that shows it.
pysds show roads.json # text on standard output
pysds show roads.json --format mermaid # Mermaid source
pysds show roads.json --image roads.svg # image (image extra)
python -m sds runs the same command, which helps when the scripts
directory of an environment is not on the PATH:
python -m sds show roads.json
The Document Format¶
Data Model¶
A file describes one structure as an object with up to three kinds of keys:
type(required) names the class, exactly as in Python:"Stack","AVLTree","WeightedDirectedGraph"…the content, whose key depends on the family of the class;
options(optional) holds the arguments passed to the constructor.
graph TD
D[document] --> T["type: class name"]
D --> O["options: constructor arguments"]
D --> C{content}
C -- "linear, binary trees, B-trees" --> I["items: list"]
C -- "Tree" --> R["root: nested data / children"]
C -- "graphs" --> N["nodes: list of {id, data}"]
C -- "graphs" --> E["edges: list of [source, target, weight]"]
style D fill:#3498db,color:#fff
The content follows the way each family is built in Python. A linear structure or a search tree is filled item by item, so it takes a list. A general tree has a shape that insertion order cannot express, so it is written as nested nodes. A graph is two sets, nodes and edges, with edges referring to nodes by id.
Any other key is an error: a misspelled item is reported, not
silently ignored.
Linear Structures¶
items is added in order, with the operation of the class: pushed on a
stack, enqueued in a queue, appended to a list.
{"type": "PriorityQueue", "items": [5, 1, 3], "options": {"reverse": true}}
$ pysds show tasks.json
5 -> 3 -> 1
Binary Trees and B-Trees¶
items is inserted key by key; a self-balancing tree balances itself on
the way, exactly as with insert() in Python.
type: BTree
options: {t: 2}
items: [10, 20, 5, 6, 12, 30, 7, 17]
$ pysds show keys.yaml
[10 | 20]
├── [5 | 6 | 7]
├── [12 | 17]
└── [30]
General Trees¶
root is a node object: data (required) and children
(optional), a list of node objects of the same form. A missing or null
root gives an empty tree. YAML is especially readable here:
type: Tree
root:
data: project
children:
- data: src
children:
- data: cli.py
- data: README.md
$ pysds show files.yaml
project
├── src
│ └── cli.py
└── README.md
Graphs¶
nodes lists objects with an id (a string or an integer) and an
optional data, which defaults to the id. edges lists pairs
[source, target] of node ids, or triples [source, target, weight]
for a weighted graph. The edge class follows from the graph class:
DirectedEdge for a DirectedGraph, WeightedEdge for a
WeightedGraph, and so on.
{
"type": "WeightedGraph",
"nodes": [
{"id": "par", "data": "Paris"},
{"id": "lyo", "data": "Lyon"},
{"id": "lil", "data": "Lille"}
],
"edges": [["par", "lyo", 465], ["par", "lil", 225]]
}
$ pysds show roads.json
Paris -- Lyon (465.0), Lille (225.0)
Lyon -- Paris (465.0)
Lille -- Paris (225.0)
A pair in a weighted graph takes the default weight of the edge class, 1.0. A triple in an unweighted graph is an error.
Options¶
options are passed by name to the constructor. Only JSON values can be
written in a file, so options expecting a function, such as the key of
a PriorityQueue, are not available from the command.
Classes |
Options |
|---|---|
|
|
|
|
Graphs |
|
|
|
Other linear structures and trees |
none |
pysds show --help lists every supported class.
JSON or YAML?¶
The suffix decides: .yaml and .yml files are read as YAML, any
other file as JSON. YAML needs the yaml extra:
pip install "pysds-tools[yaml]"
YAML is lighter to write by hand; JSON needs nothing more than the base install and is what other programs usually produce.
Warning
Quote ambiguous scalars in YAML. PyYAML follows YAML 1.1, where
yes, no, on and off are booleans. A country code used as
a node id is a classic trap:
nodes:
- id: FR
- id: NO # read as false, rejected as a node id
- id: "NO" # a string, as intended
Using the Command¶
Synopsis¶
pysds show FILE [--format {text,mermaid,dot}] [--output PATH]
pysds show FILE --image PATH
pysds --version
Argument |
Effect |
|---|---|
|
The document to read; |
|
|
|
Writes the text to |
|
Draws the structure to |
|
Prints the installed version |
Standard Input¶
- reads the document from standard input, as JSON. It lets another
program feed the command without a temporary file:
$ echo '{"type": "Stack", "items": ["a", "b", "c"]}' | pysds show -
top: c -> b -> a
Errors and Exit Status¶
When the document cannot be used, the command prints one line on standard error and no traceback. The message gives the place of the problem inside the document:
$ pysds show broken.json
pysds: error: edges[1]: unknown node 'c'
Status |
Meaning |
|---|---|
0 |
The structure was displayed or saved |
1 |
The input could not be read or parsed, the document describes no valid structure, the output could not be written, or a needed extra is missing |
2 |
The command line itself is wrong (unknown option, missing file
argument, |
Status 1 messages, by cause:
Cause |
Example message |
|---|---|
File missing |
|
Invalid JSON or YAML |
|
Unknown class |
|
Malformed content |
|
Rejected by the structure |
|
Format not available |
|
Missing extra |
|
In Scripts¶
The exit status makes the command easy to chain. This loop draws every structure of a directory and stops at the first invalid file:
mkdir -p images
for file in structures/*.json; do
name=$(basename "$file" .json)
pysds show "$file" --image "images/$name.svg" || exit 1
done
And this one regenerates the Mermaid sources of a documentation:
for file in structures/*.yaml; do
pysds show "$file" --format mermaid --output "${file%.yaml}.mmd"
done
Best Practices¶
✅ Keep structure files next to the documents that show them, and regenerate the drawings with the command in the build.
✅ Give graph nodes short id values and readable data: the id
links edges, the data is what readers see.
✅ Use JSON when a program writes the file, YAML when a person does.
❌ Do not write a weight on an unweighted graph’s edges: choose a
Weighted... class instead.
❌ Do not leave YAML scalars such as NO or on unquoted when they
are meant as text.
References¶
T. Bray (ed.), The JavaScript Object Notation (JSON) Data Interchange Format, RFC 8259, 2017 — https://www.rfc-editor.org/rfc/rfc8259
YAML Ain’t Markup Language, version 1.2.2 — https://yaml.org/spec/1.2.2/
Python documentation, argparse — Parser for command-line options — https://docs.python.org/3/library/argparse.html