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

PriorityQueue

reverse (true serves the largest item first)

BTree

t, the minimum degree (default 3)

Graphs

allow_multi_edges (default false)

AdjacencyMatrixGraph

max_nodes (default 100), and allow_multi_edges

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

FILE

The document to read; - reads JSON from standard input

--format

text (default), mermaid, or dot (graphs only)

--output PATH

Writes the text to PATH instead of standard output

--image PATH

Draws the structure to PATH instead, format from the extension (image extra); cannot be combined with --format or --output

--version

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, --image combined with --format)

Status 1 messages, by cause:

Cause

Example message

File missing

missing.json: No such file or directory

Invalid JSON or YAML

data.json: invalid JSON: Expecting ',' delimiter: line 2 column 1 (char 3)

Unknown class

unsupported type 'MinHeap': use one of AdjacencyListGraph, ...

Malformed content

nodes[1]: a node id is a string or an integer

Rejected by the structure

nodes[1]: Node a already exists in graph

Format not available

DOT output is for graphs only, not Tree

Missing extra

yaml is required for this feature: pip install "pysds-tools[yaml]"

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