.. _guide_command_line: ================= 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 :doc:`visualizer`. 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. .. code-block:: bash 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``: .. code-block:: bash 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. .. mermaid:: 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. .. code-block:: json {"type": "PriorityQueue", "items": [5, 1, 3], "options": {"reverse": true}} .. code-block:: text $ 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. .. code-block:: yaml type: BTree options: {t: 2} items: [10, 20, 5, 6, 12, 30, 7, 17] .. code-block:: text $ 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: .. code-block:: yaml type: Tree root: data: project children: - data: src children: - data: cli.py - data: README.md .. code-block:: text $ 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. .. code-block:: json { "type": "WeightedGraph", "nodes": [ {"id": "par", "data": "Paris"}, {"id": "lyo", "data": "Lyon"}, {"id": "lil", "data": "Lille"} ], "edges": [["par", "lyo", 465], ["par", "lil", 225]] } .. code-block:: text $ 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. .. list-table:: :header-rows: 1 :widths: 40 60 * - 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: .. code-block:: bash 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: .. code-block:: yaml nodes: - id: FR - id: NO # read as false, rejected as a node id - id: "NO" # a string, as intended Using the Command ================= Synopsis -------- .. code-block:: text pysds show FILE [--format {text,mermaid,dot}] [--output PATH] pysds show FILE --image PATH pysds --version .. list-table:: :header-rows: 1 :widths: 25 75 * - 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: .. code-block:: text $ 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: .. code-block:: text $ pysds show broken.json pysds: error: edges[1]: unknown node 'c' .. list-table:: :header-rows: 1 :widths: 15 85 * - 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: .. list-table:: :header-rows: 1 :widths: 40 60 * - 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: .. code-block:: bash 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: .. code-block:: bash 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