├── docs ├── .nojekyll ├── objects.inv ├── _images │ ├── catA.png │ ├── yaml.png │ ├── buses.png │ ├── sober.png │ ├── html-ugly.png │ ├── dts_ord_error.png │ ├── devicetree-shell.png │ └── catBl-node.svg ├── _static │ ├── file.png │ ├── plus.png │ ├── favicon.png │ ├── minus.png │ ├── css │ │ ├── fonts │ │ │ ├── lato-bold.woff │ │ │ ├── lato-bold.woff2 │ │ │ ├── lato-normal.woff │ │ │ ├── lato-normal.woff2 │ │ │ ├── Roboto-Slab-Bold.woff │ │ │ ├── lato-bold-italic.woff │ │ │ ├── Roboto-Slab-Bold.woff2 │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── lato-bold-italic.woff2 │ │ │ ├── lato-normal-italic.woff │ │ │ ├── Roboto-Slab-Regular.woff │ │ │ ├── Roboto-Slab-Regular.woff2 │ │ │ ├── fontawesome-webfont.woff │ │ │ ├── fontawesome-webfont.woff2 │ │ │ └── lato-normal-italic.woff2 │ │ └── badge_only.css │ ├── check-solid.svg │ ├── documentation_options.js │ ├── copy-button.svg │ ├── js │ │ ├── badge_only.js │ │ ├── html5shiv.min.js │ │ ├── html5shiv-printshiv.min.js │ │ └── theme.js │ ├── tabs.css │ ├── copybutton.css │ ├── copybutton_funcs.js │ ├── togglebutton.css │ ├── _sphinx_javascript_frameworks_compat.js │ ├── tabs.js │ ├── pygments.css │ ├── doctools.js │ ├── language_data.js │ └── sphinx_highlight.js ├── _sources │ ├── index.rst.txt │ └── faq-and-tips.rst.txt ├── genindex.html ├── bib.html └── search.html ├── src ├── dtsh │ ├── py.typed │ ├── __init__.py │ ├── rich │ │ └── __init__.py │ └── builtins │ │ ├── __init__.py │ │ ├── pwd.py │ │ ├── cd.py │ │ ├── alias.py │ │ ├── chosen.py │ │ └── tree.py └── devicetree │ ├── __init__.py │ ├── README │ ├── _private.py │ └── grutils.py ├── .gitignore ├── doc ├── ug │ └── DTSh.pdf ├── img │ └── buses.png └── site │ ├── src │ ├── img │ │ ├── buses.png │ │ ├── catA.png │ │ ├── ls-l.png │ │ ├── sober.png │ │ ├── html-ugly.png │ │ ├── dts_ord_error.png │ │ ├── devicetree-shell.png │ │ └── catBl-node.svg │ ├── static │ │ └── favicon.png │ ├── conf.py │ ├── index.rst │ └── faq-and-tips.rst │ ├── requirements.txt │ └── Makefile ├── tests ├── res │ ├── fs │ │ ├── A.txt │ │ ├── a.txt │ │ ├── ab.txt │ │ ├── A │ │ │ └── dummy.txt │ │ ├── a │ │ │ └── x │ │ │ │ └── dummy.txt │ │ └── foo │ │ │ └── dummy.txt │ ├── ini │ │ ├── override.ini │ │ └── test.ini │ ├── theme │ │ ├── override.ini │ │ └── test.ini │ ├── CMakeCache.txt │ ├── yaml │ │ ├── i2c-device.yaml │ │ ├── included_at_depth.yaml │ │ ├── sensor-device.yaml │ │ ├── inc1.yaml │ │ ├── inc2.yaml │ │ ├── inc3.yaml │ │ └── power.yaml │ └── README ├── __init__.py ├── typed.py ├── test_dtsh_builtin_pwd.py ├── test_dtsh_builtin_cd.py ├── test_dtsh_builtin_cat.py ├── test_dtsh_hwm.py ├── test_dtsh_io.py ├── test_dtsh_builtin_alias.py ├── test_dtsh_builtin_chosen.py ├── test_dtsh_builtin_ls.py ├── test_dtsh_builtin_tree.py ├── test_dtsh_uthelpers.py ├── test_dtsh_builtin_find.py ├── test_dtsh_rich_svg.py ├── test_dtsh_theme.py ├── test_dtsh_rich_shellutils.py ├── test_dtsh_config.py └── test_dtsh_rich_html.py ├── requirements.txt ├── requirements-lsp.txt ├── requirements-dev.txt ├── setup.py ├── pyproject.toml ├── etc ├── preferences │ ├── sober.ini │ ├── redir2html.ini │ └── redir2html-dark.ini ├── sh │ ├── genboards.sh │ └── dtsh └── themes │ └── sober.ini ├── README.rst ├── setup.cfg └── README.md /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/dtsh/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | build 3 | dist 4 | 5 | __pycache__ 6 | dtsh.egg-info 7 | -------------------------------------------------------------------------------- /doc/ug/DTSh.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/doc/ug/DTSh.pdf -------------------------------------------------------------------------------- /docs/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/objects.inv -------------------------------------------------------------------------------- /tests/res/fs/A.txt: -------------------------------------------------------------------------------- 1 | # Dummy file for DtShAutocomp.complete_fspath() unit tests. 2 | -------------------------------------------------------------------------------- /tests/res/fs/a.txt: -------------------------------------------------------------------------------- 1 | # Dummy file for DtShAutocomp.complete_fspath() unit tests. 2 | -------------------------------------------------------------------------------- /tests/res/fs/ab.txt: -------------------------------------------------------------------------------- 1 | # Dummy file for DtShAutocomp.complete_fspath() unit tests. 2 | -------------------------------------------------------------------------------- /doc/img/buses.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/doc/img/buses.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyYAML>=6.0 2 | rich 3 | gnureadline ; sys_platform == 'darwin' 4 | -------------------------------------------------------------------------------- /tests/res/fs/A/dummy.txt: -------------------------------------------------------------------------------- 1 | # Dummy file for DtShAutocomp.complete_fspath() unit tests. 2 | -------------------------------------------------------------------------------- /docs/_images/catA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_images/catA.png -------------------------------------------------------------------------------- /docs/_images/yaml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_images/yaml.png -------------------------------------------------------------------------------- /docs/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_static/file.png -------------------------------------------------------------------------------- /docs/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_static/plus.png -------------------------------------------------------------------------------- /tests/res/fs/a/x/dummy.txt: -------------------------------------------------------------------------------- 1 | # Dummy file for DtShAutocomp.complete_fspath() unit tests. 2 | -------------------------------------------------------------------------------- /tests/res/fs/foo/dummy.txt: -------------------------------------------------------------------------------- 1 | # Dummy file for DtShAutocomp.complete_fspath() unit tests. 2 | -------------------------------------------------------------------------------- /docs/_images/buses.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_images/buses.png -------------------------------------------------------------------------------- /docs/_images/sober.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_images/sober.png -------------------------------------------------------------------------------- /docs/_static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_static/favicon.png -------------------------------------------------------------------------------- /docs/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_static/minus.png -------------------------------------------------------------------------------- /doc/site/src/img/buses.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/doc/site/src/img/buses.png -------------------------------------------------------------------------------- /doc/site/src/img/catA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/doc/site/src/img/catA.png -------------------------------------------------------------------------------- /doc/site/src/img/ls-l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/doc/site/src/img/ls-l.png -------------------------------------------------------------------------------- /doc/site/src/img/sober.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/doc/site/src/img/sober.png -------------------------------------------------------------------------------- /docs/_images/html-ugly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_images/html-ugly.png -------------------------------------------------------------------------------- /doc/site/src/img/html-ugly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/doc/site/src/img/html-ugly.png -------------------------------------------------------------------------------- /doc/site/src/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/doc/site/src/static/favicon.png -------------------------------------------------------------------------------- /docs/_images/dts_ord_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_images/dts_ord_error.png -------------------------------------------------------------------------------- /doc/site/src/img/dts_ord_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/doc/site/src/img/dts_ord_error.png -------------------------------------------------------------------------------- /docs/_images/devicetree-shell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_images/devicetree-shell.png -------------------------------------------------------------------------------- /doc/site/src/img/devicetree-shell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/doc/site/src/img/devicetree-shell.png -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_static/css/fonts/lato-bold.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_static/css/fonts/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-normal.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_static/css/fonts/lato-normal.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_static/css/fonts/lato-normal.woff2 -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /docs/_static/css/fonts/Roboto-Slab-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_static/css/fonts/Roboto-Slab-Bold.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_static/css/fonts/lato-bold-italic.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/Roboto-Slab-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_static/css/fonts/Roboto-Slab-Bold.woff2 -------------------------------------------------------------------------------- /docs/_static/css/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_static/css/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/_static/css/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_static/css/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_static/css/fonts/lato-bold-italic.woff2 -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-normal-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_static/css/fonts/lato-normal-italic.woff -------------------------------------------------------------------------------- /tests/res/ini/override.ini: -------------------------------------------------------------------------------- 1 | [dtsh] 2 | # Override an existing option. 3 | test.string = overridden 4 | # Add an option. 5 | test.new = new 6 | -------------------------------------------------------------------------------- /docs/_static/css/fonts/Roboto-Slab-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_static/css/fonts/Roboto-Slab-Regular.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/Roboto-Slab-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_static/css/fonts/Roboto-Slab-Regular.woff2 -------------------------------------------------------------------------------- /docs/_static/css/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_static/css/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/_static/css/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_static/css/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/_static/css/fonts/lato-normal-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dottspina/dtsh/HEAD/docs/_static/css/fonts/lato-normal-italic.woff2 -------------------------------------------------------------------------------- /tests/res/theme/override.ini: -------------------------------------------------------------------------------- 1 | [styles] 2 | # Override an existing style. 3 | test.default = green on black 4 | # Add new style. 5 | test.new = cyan 6 | -------------------------------------------------------------------------------- /requirements-lsp.txt: -------------------------------------------------------------------------------- 1 | python-lsp-server[all] 2 | pylsp-mypy 3 | 4 | # Pin black versions to avoid unwanted formatting changes. 5 | python-lsp-black==1.3.0 6 | -------------------------------------------------------------------------------- /src/devicetree/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Nordic Semiconductor ASA 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | __all__ = ['edtlib', 'dtlib'] 5 | -------------------------------------------------------------------------------- /tests/typed.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # Marker file for PEP 561. 6 | -------------------------------------------------------------------------------- /tests/res/theme/test.ini: -------------------------------------------------------------------------------- 1 | [styles] 2 | test.default = default on default 3 | 4 | test.red = red 5 | test.bold = bold 6 | 7 | test.interpolation = %(test.red)s italic 8 | -------------------------------------------------------------------------------- /src/dtsh/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Core devicetree shell API.""" 6 | -------------------------------------------------------------------------------- /src/dtsh/rich/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Rich Text User Interface.""" 6 | -------------------------------------------------------------------------------- /doc/site/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx_rtd_theme 3 | sphinx-tabs 4 | sphinxcontrib-svg2pdfconverter 5 | sphinx-notfound-page 6 | sphinx-copybutton 7 | sphinx-togglebutton 8 | pygments 9 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pycodestyle 2 | flake8 3 | pylint 4 | mypy 5 | types-PyYAML 6 | pytest 7 | 8 | # Pin black versions to avoid unwanted formatting changes. 9 | black==23.3.0 10 | -------------------------------------------------------------------------------- /src/dtsh/builtins/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Built-in devicetree shell commands.""" 6 | -------------------------------------------------------------------------------- /tests/res/CMakeCache.txt: -------------------------------------------------------------------------------- 1 | ######################### 2 | # CMakeCache test entries 3 | ######################### 4 | 5 | DTSH_TEST_STRING_LIST:STRING=foo;bar 6 | DTSH_TEST_STRING:STRING=foobar 7 | DTSH_TEST_BOOL_TRUE:BOOL=ON 8 | DTSH_TEST_BOOL_FALSE:BOOL=0 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Required to bootstrap setipt.cfg in some situations.""" 6 | 7 | from setuptools import setup # type: ignore 8 | 9 | if __name__ == "__main__": 10 | setup() 11 | -------------------------------------------------------------------------------- /tests/res/yaml/i2c-device.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017, Linaro Limited 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Common fields for I2C devices 5 | 6 | include: [base.yaml, power.yaml] 7 | 8 | on-bus: i2c 9 | 10 | properties: 11 | reg: 12 | required: true 13 | description: device address on i2c bus 14 | -------------------------------------------------------------------------------- /docs/_static/check-solid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | const DOCUMENTATION_OPTIONS = { 2 | VERSION: '0.2.4', 3 | LANGUAGE: 'en', 4 | COLLAPSE_INDEX: false, 5 | BUILDER: 'html', 6 | FILE_SUFFIX: '.html', 7 | LINK_SUFFIX: '.html', 8 | HAS_SOURCE: true, 9 | SOURCELINK_SUFFIX: '.txt', 10 | NAVIGATION_WITH_KEYS: false, 11 | SHOW_SEARCH_SUMMARY: true, 12 | ENABLE_SEARCH_SHORTCUTS: true, 13 | }; -------------------------------------------------------------------------------- /docs/_static/copy-button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/res/yaml/included_at_depth.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | include: inc1.yaml 4 | 5 | properties: 6 | inc1_depth0_p2: 7 | required: true 8 | 9 | child-binding: 10 | include: 11 | - inc2.yaml 12 | 13 | properties: 14 | inc2_depth0_p2: 15 | required: true 16 | 17 | child-binding: 18 | include: 19 | - name: inc3.yaml 20 | 21 | properties: 22 | inc3_depth0_p2: 23 | required: true 24 | -------------------------------------------------------------------------------- /src/devicetree/README: -------------------------------------------------------------------------------- 1 | This directory contains a snapshot of Zephyr's python-devicetree library. 2 | 3 | See https://github.com/zephyrproject-rtos/zephyr/scripts/dts 4 | 5 | 6 | - edtlib: 7 | Copyright (c) 2019 Nordic Semiconductor ASA 8 | Copyright (c) 2019 Linaro Limited 9 | License BSD-3-Clause 10 | 11 | - dtlib: 12 | Copyright (c) 2019 Nordic Semiconductor 13 | License BSD-3-Clause 14 | 15 | - grutils: 16 | Copyright 2009-2013, 2019 Peter A. Bigot 17 | License Apache-2.0 18 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | 6 | [tool.black] 7 | # References: 8 | # - https://black.readthedocs.io/en/stable/usage_and_configuration/ 9 | line-length = 80 10 | target-version = ['py38'] 11 | include = '\.pyi?$' 12 | # 'extend-exclude' excludes files or directories in addition to the defaults 13 | extend-exclude = ''' 14 | # A regex preceded with ^/ will apply only to files and directories 15 | # in the root of the project. 16 | ( 17 | ^/foo.py # exclude a file named foo.py in the root of the project 18 | | .*_pb2.py # exclude autogenerated Protocol Buffer files anywhere in the project 19 | ) 20 | ''' 21 | -------------------------------------------------------------------------------- /tests/res/yaml/sensor-device.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Intel Corporation 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | description: Sensor Device 6 | 7 | include: base.yaml 8 | 9 | properties: 10 | friendly-name: 11 | type: string 12 | description: | 13 | Human readable string describing the sensor. It can be used to 14 | distinguish multiple instances of the same model (e.g., lid accelerometer 15 | vs. base accelerometer in a laptop) to a host operating system. 16 | 17 | This property is defined in the Generic Sensor Property Usages of the HID 18 | Usage Tables specification 19 | (https://usb.org/sites/default/files/hut1_3_0.pdf, section 22.5). 20 | -------------------------------------------------------------------------------- /doc/site/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = src 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /tests/res/yaml/inc1.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | properties: 4 | inc1_depth0_p1: 5 | type: int 6 | description: Defined here. 7 | inc1_depth0_p2: 8 | type: int 9 | description: Defined here. 10 | inc1_depth0_p3: 11 | # No description. 12 | type: int 13 | 14 | child-binding: 15 | properties: 16 | inc1_depth1_p1: 17 | type: int 18 | description: Defined here. 19 | inc1_depth1_p2: 20 | type: int 21 | description: Defined here. 22 | 23 | child-binding: 24 | properties: 25 | inc1_depth2_p1: 26 | type: int 27 | description: Defined here. 28 | inc1_depth2_p2: 29 | type: int 30 | description: Defined here. 31 | -------------------------------------------------------------------------------- /tests/res/yaml/inc2.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | properties: 4 | inc2_depth0_p1: 5 | type: int 6 | description: Defined here. 7 | inc2_depth0_p2: 8 | type: int 9 | description: Defined here. 10 | inc2_depth0_p3: 11 | # No description. 12 | type: int 13 | 14 | child-binding: 15 | properties: 16 | inc2_depth1_p1: 17 | type: int 18 | description: Defined here. 19 | inc2_depth1_p2: 20 | type: int 21 | description: Defined here. 22 | 23 | child-binding: 24 | properties: 25 | inc2_depth2_p1: 26 | type: int 27 | description: Defined here. 28 | inc2_depth2_p2: 29 | type: int 30 | description: Defined here. 31 | -------------------------------------------------------------------------------- /tests/res/yaml/inc3.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | properties: 4 | inc3_depth0_p1: 5 | type: int 6 | description: Defined here. 7 | inc3_depth0_p2: 8 | type: int 9 | description: Defined here. 10 | inc3_depth0_p3: 11 | # No description. 12 | type: int 13 | 14 | child-binding: 15 | properties: 16 | inc3_depth1_p1: 17 | type: int 18 | description: Defined here. 19 | inc3_depth1_p2: 20 | type: int 21 | description: Defined here. 22 | 23 | child-binding: 24 | properties: 25 | inc3_depth2_p1: 26 | type: int 27 | description: Defined here. 28 | inc3_depth2_p2: 29 | type: int 30 | description: Defined here. 31 | -------------------------------------------------------------------------------- /tests/test_dtsh_builtin_pwd.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Unit tests for the dtsh.builtins.pwd module.""" 6 | 7 | # Relax pylint a bit for unit tests. 8 | # pylint: disable=missing-function-docstring 9 | 10 | 11 | from dtsh.io import DTShOutput 12 | from dtsh.shell import DTSh 13 | from dtsh.builtins.pwd import DTShBuiltinPwd 14 | 15 | from .dtsh_uthelpers import DTShTests 16 | 17 | 18 | def test_dtsh_builtin_pwd() -> None: 19 | cmd = DTShBuiltinPwd() 20 | DTShTests.check_cmd_meta(cmd, "pwd") 21 | 22 | 23 | def test_dtsh_builtin_pwd_execute() -> None: 24 | out = DTShOutput() 25 | cmd = DTShBuiltinPwd() 26 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 27 | 28 | DTShTests.check_cmd_execute(cmd, sh, out) 29 | -------------------------------------------------------------------------------- /src/dtsh/builtins/pwd.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Devicetree shell built-in "pwd". 6 | 7 | Print current working branch. 8 | 9 | Unit tests and examples: tests/test_dtsh_builtin_pwd.py 10 | """ 11 | 12 | 13 | from typing import Sequence 14 | 15 | from dtsh.io import DTShOutput 16 | from dtsh.shell import DTSh, DTShCommand 17 | 18 | 19 | class DTShBuiltinPwd(DTShCommand): 20 | """Devicetree shell built-in "pwd".""" 21 | 22 | def __init__(self) -> None: 23 | """Command definition.""" 24 | super().__init__( 25 | "pwd", "print path of current working branch", [], None 26 | ) 27 | 28 | def execute(self, argv: Sequence[str], sh: DTSh, out: DTShOutput) -> None: 29 | """Overrides DTShCommand.execute().""" 30 | super().execute(argv, sh, out) 31 | out.write(sh.pwd) 32 | -------------------------------------------------------------------------------- /docs/_static/js/badge_only.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}({4:function(e,t,r){}}); -------------------------------------------------------------------------------- /tests/test_dtsh_builtin_cd.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Unit tests for the dtsh.builtins.cd module.""" 6 | 7 | # Relax pylint a bit for unit tests. 8 | # pylint: disable=missing-function-docstring 9 | 10 | 11 | from dtsh.io import DTShOutput 12 | from dtsh.shell import DTSh 13 | from dtsh.builtins.cd import DTShBuiltinCd 14 | 15 | from .dtsh_uthelpers import DTShTests 16 | 17 | _stdout = DTShOutput() 18 | 19 | 20 | def test_dtsh_builtin_cd() -> None: 21 | cmd = DTShBuiltinCd() 22 | DTShTests.check_cmd_meta(cmd, "cd") 23 | 24 | 25 | def test_dtsh_builtin_cd_param() -> None: 26 | cmd = DTShBuiltinCd() 27 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 28 | DTShTests.check_cmd_param_dtpath(cmd, sh, _stdout) 29 | 30 | 31 | def test_dtsh_builtin_cd_execute() -> None: 32 | cmd = DTShBuiltinCd() 33 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 34 | 35 | DTShTests.check_cmd_execute(cmd, sh, _stdout) 36 | -------------------------------------------------------------------------------- /tests/test_dtsh_builtin_cat.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Unit tests for the dtsh.builtins.cat module.""" 6 | 7 | # Relax pylint a bit for unit tests. 8 | # pylint: disable=missing-function-docstring 9 | 10 | 11 | from dtsh.io import DTShOutput 12 | from dtsh.shell import DTSh 13 | from dtsh.builtins.cat import DTShBuiltinCat 14 | 15 | from .dtsh_uthelpers import DTShTests 16 | 17 | _stdout = DTShOutput() 18 | 19 | 20 | def test_dtsh_builtin_cat() -> None: 21 | cmd = DTShBuiltinCat() 22 | DTShTests.check_cmd_meta(cmd, "cat") 23 | 24 | 25 | def test_dtsh_builtin_cat_flags() -> None: 26 | cmd = DTShBuiltinCat() 27 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 28 | DTShTests.check_cmd_flags(cmd, sh, _stdout) 29 | 30 | 31 | def test_dtsh_builtin_cat_execute() -> None: 32 | out = DTShOutput() 33 | cmd = DTShBuiltinCat() 34 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 35 | 36 | DTShTests.check_cmd_execute(cmd, sh, out) 37 | -------------------------------------------------------------------------------- /tests/res/ini/test.ini: -------------------------------------------------------------------------------- 1 | [dtsh] 2 | 3 | # Missing right-value in assignment. 4 | test.novalue = 5 | 6 | # Booleans, API DtshConfig.getbool(): 7 | # - True: '1', 'yes', 'true', and 'on' 8 | # - False: '0', 'no', 'false', and 'off' 9 | test.true = yes 10 | test.false = no 11 | test.bool.inval = not a bool 12 | 13 | # Integers, API DtshConfig.getint(): 14 | # - base- 2, -8, -10 and -16 are supported 15 | # - base-2, -8, and -16 literals can be optionally prefixed 16 | # with 0b/0B, 0o/0O, or 0x/0X 17 | test.int = 255 18 | test.hex = 0xff 19 | test.int.inval = not an int 20 | 21 | # Strings, API DtshConfig.getstring(): 22 | # - double-quote with " when containing spaces 23 | # - \u escape sequence, which is followed by four hex digits giving 24 | # the code point (e.g. \u2768) 25 | # - the \U escape sequence is similar, but expects eight hex digits 26 | test.string = a string 27 | test.string.quoted = "quoted string " 28 | test.string.unicode = ❯ 29 | test.string.literal = \u276F 30 | test.string.mixed = "\u276F ❯" 31 | 32 | # Interpolation. 33 | test.hello = hello 34 | test.interpolation = ${test.hello} world 35 | 36 | # Double quotes themselves. 37 | test.string.quotes = "a"b" 38 | -------------------------------------------------------------------------------- /src/dtsh/builtins/cd.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Devicetree shell built-in "cd". 6 | 7 | Change the current working branch. 8 | 9 | Unit tests and examples: tests/test_dtsh_builtin_cd.py 10 | """ 11 | 12 | 13 | from typing import Sequence 14 | 15 | from dtsh.io import DTShOutput 16 | from dtsh.shell import DTSh, DTShCommand, DTPathNotFoundError, DTShCommandError 17 | from dtsh.shellutils import DTShParamDTPath 18 | 19 | 20 | class DTShBuiltinCd(DTShCommand): 21 | """Devicetree shell built-in "cd".""" 22 | 23 | def __init__(self) -> None: 24 | super().__init__( 25 | "cd", 26 | "change the current working branch", 27 | [], 28 | DTShParamDTPath(), 29 | ) 30 | 31 | def execute(self, argv: Sequence[str], sh: DTSh, out: DTShOutput) -> None: 32 | """Overrides DTShCommand.execute().""" 33 | super().execute(argv, sh, out) 34 | 35 | param_path = self.with_param(DTShParamDTPath).path 36 | try: 37 | sh.cd(param_path) 38 | except DTPathNotFoundError as e: 39 | raise DTShCommandError(self, e.msg) from e 40 | -------------------------------------------------------------------------------- /tests/res/yaml/power.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020, Nordic Semiconductor ASA 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Properties for nodes with controllable power supplies. 5 | 6 | properties: 7 | supply-gpios: 8 | type: phandle-array 9 | description: | 10 | GPIO specifier that controls power to the device. 11 | 12 | This property should be provided when the device has a dedicated 13 | switch that controls power to the device. The supply state is 14 | entirely the responsibility of the device driver. 15 | 16 | Contrast with vin-supply. 17 | 18 | vin-supply: 19 | type: phandle 20 | description: | 21 | Reference to the regulator that controls power to the device. 22 | The referenced devicetree node must have a regulator compatible. 23 | 24 | This property should be provided when device power is supplied 25 | by a shared regulator. The supply state is dependent on the 26 | request status of all devices fed by the regulator. 27 | 28 | Contrast with supply-gpios. If both properties are provided 29 | then the regulator must be requested before the supply GPIOS is 30 | set to an active state, and the supply GPIOS must be set to an 31 | inactive state before releasing the regulator. 32 | -------------------------------------------------------------------------------- /doc/site/src/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | 9 | project = "DTSh" 10 | copyright = "2024, Chris Duf" 11 | author = "Chris Duf" 12 | version = "0.2.5" 13 | release = "0.2.5" 14 | 15 | # -- General configuration --------------------------------------------------- 16 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 17 | 18 | extensions = [ 19 | "sphinx_rtd_theme", 20 | "sphinx_tabs.tabs", 21 | "sphinx_copybutton", 22 | "sphinx_togglebutton", 23 | ] 24 | 25 | # We don't use templates. 26 | # templates_path = ["_templates"] 27 | 28 | exclude_patterns = [] 29 | 30 | 31 | # -- Options for HTML output ------------------------------------------------- 32 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 33 | # html_theme = "alabaster" 34 | 35 | # Static files. 36 | html_static_path = ["static"] 37 | 38 | html_theme = "sphinx_rtd_theme" 39 | html_favicon = "static/favicon.png" 40 | -------------------------------------------------------------------------------- /src/devicetree/_private.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Nordic Semiconductor ASA 2 | # Copyright (c) 2019 Linaro Limited 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | """ 6 | Shared internal code. Do not use outside of the package. 7 | """ 8 | 9 | from typing import Any, Callable 10 | 11 | def _slice_helper(node: Any, # avoids a circular import with dtlib 12 | prop_name: str, size: int, size_hint: str, 13 | err_class: Callable[..., Exception]): 14 | # Splits node.props[prop_name].value into 'size'-sized chunks, 15 | # returning a list of chunks. Raises err_class(...) if the length 16 | # of the property is not evenly divisible by 'size'. The argument 17 | # to err_class is a string which describes the error. 18 | # 19 | # 'size_hint' is a string shown on errors that gives a hint on how 20 | # 'size' was calculated. 21 | 22 | raw = node.props[prop_name].value 23 | if len(raw) % size: 24 | raise err_class( 25 | f"'{prop_name}' property in {node!r} has length {len(raw)}, " 26 | f"which is not evenly divisible by {size} (= {size_hint}). " 27 | "Note that #*-cells properties come either from the parent node or " 28 | "from the controller (in the case of 'interrupts').") 29 | 30 | return [raw[i:i + size] for i in range(0, len(raw), size)] 31 | -------------------------------------------------------------------------------- /tests/test_dtsh_hwm.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Unit tests for the dtsh.hwm module.""" 6 | 7 | # Relax pylint a bit for unit tests. 8 | # pylint: disable=missing-function-docstring 9 | 10 | 11 | from dtsh.hwm import DTShBoard 12 | 13 | 14 | def test_board_parse_v2_name() -> None: 15 | # Full target 16 | target = "board_name@1.0/soc/cpus/variant" 17 | (name, revision, soc, cpus, variant) = DTShBoard.v2_parse_board(target) 18 | assert "board_name" == name 19 | assert "1.0" == revision 20 | assert "soc" == soc 21 | assert "cpus" == cpus 22 | assert "variant" == variant 23 | 24 | # Full target, SoC omitted (no meta-data otherwise available). 25 | target = "board_name@1.0//cpus/variant" 26 | (name, revision, soc, cpus, variant) = DTShBoard.v2_parse_board(target) 27 | assert "board_name" == name 28 | assert "1.0" == revision 29 | assert "" == soc 30 | assert "cpus" == cpus 31 | assert "variant" == variant 32 | 33 | # Only board name (no meta-data otherwise available). 34 | target = "board_name" 35 | (name, revision, soc, cpus, variant) = DTShBoard.v2_parse_board(target) 36 | assert "board_name" == name 37 | assert revision is None 38 | assert soc is None 39 | assert cpus is None 40 | assert variant is None 41 | -------------------------------------------------------------------------------- /tests/test_dtsh_io.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Unit tests for the dtsh.io module.""" 6 | 7 | # Relax pylint a bit for unit tests. 8 | # pylint: disable=missing-function-docstring 9 | 10 | 11 | import os 12 | 13 | import pytest 14 | 15 | from dtsh.config import DTShConfig 16 | from dtsh.io import DTShRedirect 17 | 18 | from .dtsh_uthelpers import DTShTests 19 | 20 | 21 | _dtshconf: DTShConfig = DTShConfig.getinstance() 22 | 23 | 24 | def test_dtsh_redirect() -> None: 25 | redir2 = DTShRedirect("> path") 26 | assert not redir2.append 27 | assert redir2.path == os.path.abspath("path") 28 | 29 | redir2 = DTShRedirect(">> path") 30 | # Won't append to non existing file. 31 | assert not redir2.append 32 | assert redir2.path == os.path.abspath("path") 33 | 34 | with pytest.raises(DTShRedirect.Error): 35 | # Redirection to empty path. 36 | redir2 = DTShRedirect("command string>") 37 | 38 | with pytest.raises(DTShRedirect.Error): 39 | # No spaces allowed. 40 | redir2 = DTShRedirect("command string > to/file with spaces") 41 | 42 | path = DTShTests.get_resource_path("README") 43 | with pytest.raises(DTShRedirect.Error): 44 | # File exists, won't override. 45 | redir2 = DTShRedirect(f"command string > {path}") 46 | -------------------------------------------------------------------------------- /etc/preferences/sober.ini: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # Preferences file for: 6 | # - command redirection to HTML 7 | # - light themes 8 | 9 | [dtsh] 10 | 11 | # Traditional shell prompt. 12 | # Type: String 13 | prompt.wchar = (dtsh)$$ 14 | 15 | # Default ANSI prompt. 16 | # Type: String 17 | prompt.default = "${prompt.wchar} " 18 | 19 | # Alternative prompt, e.g. after a command has failed. 20 | # Type: String 21 | prompt.alt = "\001\x1b[38;5;124m\002${prompt.wchar}\001\x1b[0m\002 " 22 | 23 | # Pygments theme for YAML syntax highlighting. 24 | # 25 | # E.g.: 26 | # 27 | # - dark: "monokai", "dracula", "material" 28 | # - light: "bw", "sas", "arduino" 29 | # 30 | # See also: 31 | # - https://pygments.org/styles/ 32 | # - https://rich.readthedocs.io/en/latest/syntax.html 33 | # 34 | # Type: String 35 | pref.yaml.theme = friendly_grayscale 36 | 37 | # YAML views: rendering for actionable texts (aka links). 38 | # 39 | # Type: String 40 | # - "none": do not create hyperlinks 41 | # - "link" (default): link text like browsers do 42 | # - "alt": append alternative actionable view 43 | pref.yaml.actionable_type = alt 44 | 45 | # Pygments theme for DTS syntax highlighting. 46 | # 47 | # E.g.: 48 | # 49 | # - dark: "monokai", "dracula", "material" 50 | # - light: "bw", "sas", "arduino" 51 | # 52 | # See also: 53 | # - https://pygments.org/styles/ 54 | # 55 | # Type: String 56 | pref.dts.theme = friendly_grayscale 57 | -------------------------------------------------------------------------------- /tests/test_dtsh_builtin_alias.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Unit tests for the dtsh.builtins.alias module.""" 6 | 7 | # Relax pylint a bit for unit tests. 8 | # pylint: disable=missing-function-docstring 9 | 10 | 11 | from dtsh.io import DTShOutput 12 | from dtsh.shell import DTSh 13 | from dtsh.builtins.alias import DTShBuiltinAlias 14 | 15 | from .dtsh_uthelpers import DTShTests 16 | 17 | _stdout = DTShOutput() 18 | 19 | 20 | def test_dtsh_builtin_alias() -> None: 21 | cmd = DTShBuiltinAlias() 22 | DTShTests.check_cmd_meta(cmd, "alias") 23 | 24 | 25 | def test_dtsh_builtin_alias_flags() -> None: 26 | cmd = DTShBuiltinAlias() 27 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 28 | DTShTests.check_cmd_flags(cmd, sh, _stdout) 29 | 30 | 31 | def test_dtsh_builtin_alias_arg_longfmt() -> None: 32 | cmd = DTShBuiltinAlias() 33 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 34 | DTShTests.check_cmd_arg_longfmt(cmd, sh, _stdout) 35 | 36 | 37 | def test_dtsh_builtin_alias_param() -> None: 38 | cmd = DTShBuiltinAlias() 39 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 40 | DTShTests.check_cmd_param_alias(cmd, sh, _stdout) 41 | 42 | 43 | def test_dtsh_builtin_alias_execute() -> None: 44 | dtmodel = DTShTests.get_sample_dtmodel() 45 | cmd = DTShBuiltinAlias() 46 | sh = DTSh(dtmodel, [cmd]) 47 | 48 | DTShTests.check_cmd_execute(cmd, sh, _stdout) 49 | -------------------------------------------------------------------------------- /tests/test_dtsh_builtin_chosen.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Unit tests for the dtsh.builtins.chosen module.""" 6 | 7 | # Relax pylint a bit for unit tests. 8 | # pylint: disable=missing-function-docstring 9 | 10 | 11 | from dtsh.io import DTShOutput 12 | from dtsh.shell import DTSh 13 | from dtsh.builtins.chosen import DTShBuiltinChosen 14 | 15 | from .dtsh_uthelpers import DTShTests 16 | 17 | _stdout = DTShOutput() 18 | 19 | 20 | def test_dtsh_builtin_chosen() -> None: 21 | cmd = DTShBuiltinChosen() 22 | DTShTests.check_cmd_meta(cmd, "chosen") 23 | 24 | 25 | def test_dtsh_builtin_chosen_flags() -> None: 26 | cmd = DTShBuiltinChosen() 27 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 28 | DTShTests.check_cmd_flags(cmd, sh, _stdout) 29 | 30 | 31 | def test_dtsh_builtin_chosen_arg_longfmt() -> None: 32 | cmd = DTShBuiltinChosen() 33 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 34 | DTShTests.check_cmd_arg_longfmt(cmd, sh, _stdout) 35 | 36 | 37 | def test_dtsh_builtin_chosen_param() -> None: 38 | cmd = DTShBuiltinChosen() 39 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 40 | DTShTests.check_cmd_param_chosen(cmd, sh, _stdout) 41 | 42 | 43 | def test_dtsh_builtin_chosen_execute() -> None: 44 | dtmodel = DTShTests.get_sample_dtmodel() 45 | cmd = DTShBuiltinChosen() 46 | sh = DTSh(dtmodel, [cmd]) 47 | 48 | DTShTests.check_cmd_execute(cmd, sh, _stdout) 49 | -------------------------------------------------------------------------------- /tests/res/README: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | DTSh: Unit tests resource files 6 | 7 | + Build_gnuarm/ 8 | Sample DTS and corresponding CMakeCache.txt, 9 | built with "gnuarmemb" toolchain variant. 10 | 11 | + Build_gnuarm/ 12 | Sample DTS and corresponding CMakeCache.txt, 13 | built with "zephyr" toolchain variant. 14 | 15 | + fs/ 16 | Pseudo file-system root for file path auto-completion unit tests. 17 | 18 | + ini/ 19 | Configuration test files. 20 | 21 | + theme/ 22 | Theme test files. 23 | 24 | + yaml/ 25 | YAML test files. 26 | 27 | + CMakeCache.txt: CMake cache file parser test file. 28 | 29 | + zephyr.dts: A DTS file for which we won't be able 30 | to find a corresponding CMakeCache.txt. 31 | 32 | 33 | Devicetree sample: 34 | 35 | Sample DTS and CMakeCache.txt files are generated 36 | when building zephyr/samples/sensor/bme680: 37 | - for the nrf52840dk_nrf52840 board 38 | - edited to connect a second BME680 to the SPI bus 39 | and add an 'interrupt-names' property to test with 40 | 41 | nrf52840dk_nrf52840.overlay: 42 | 43 | &i2c0 { 44 | bme680_i2c: bme680@76 { 45 | compatible = "bosch,bme680"; 46 | reg = <0x76>; 47 | }; 48 | }; 49 | 50 | &spi1 { 51 | bme680_spi: bme680@0 { 52 | compatible = "bosch,bme680"; 53 | reg = <0>; 54 | spi-max-frequency = <1000000>; /* conservatively set to 1MHz */ 55 | }; 56 | }; 57 | 58 | 59 | prj.conf: 60 | 61 | CONFIG_SPI=y 62 | 63 | zephyr.dts: 64 | 65 | i2c0: arduino_i2c: i2c@40003000 { 66 | interrupt-names = "IRQ_i2c0"; 67 | }; 68 | -------------------------------------------------------------------------------- /doc/site/src/index.rst: -------------------------------------------------------------------------------- 1 | DTSh Project Documentation 2 | ########################## 3 | 4 | **DTSh** is a Devicetree Source (`DTS `_) files viewer 5 | with a shell-like command line interface: 6 | 7 | - *navigate* and *visualize* the devicetree 8 | - *search* for devices, bindings, buses or interrupts with flexible criteria 9 | - redirect command output to files (text, HTML, SVG) to *document* hardware configurations 10 | or illustrate notes 11 | - *rich* Textual User Interface, command line auto-completion, command history, user themes 12 | 13 | You can use it with: 14 | 15 | - all DTS files generated by **Zephyr** at build-time (aka `build/zephyr/zephyr.dts`) 16 | - arbitrary DTS files with bindings compatible with Zephyr's 17 | `Devicetree bindings syntax `_ 18 | 19 | .. figure:: img/devicetree-shell.png 20 | :align: center 21 | :alt: A Devicetree Shell 22 | 23 | A Devicetree Shell 24 | 25 | 26 | Status 27 | ****** 28 | 29 | Welcome to the DTSh Project’s documentation for the ``dtsh-next`` branch. 30 | This branch mirrors and packages the code base that serves as a proposal to upstream DTSh 31 | as a new `Zephyr extension to West `_ (would be ``west dtsh``): 32 | `RFC - DTSh, shell-like interface with Devicetree `_ 33 | 34 | Source code and documentation for the Proof of Concept and prototype (DTSh 0.1.x) are still 35 | available at the ``main`` `branch `_ of this repository. 36 | 37 | .. toctree:: 38 | :maxdepth: 2 39 | :caption: Contents: 40 | 41 | getting-started 42 | handbook 43 | faq-and-tips 44 | 45 | .. include:: bib.rst 46 | 47 | .. meta:: 48 | :keywords: zephyr, devicetree, dts, viewer, user interface, embedded development, IoT 49 | -------------------------------------------------------------------------------- /docs/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | DTSh Project Documentation 2 | ########################## 3 | 4 | **DTSh** is a Devicetree Source (`DTS `_) files viewer 5 | with a shell-like command line interface: 6 | 7 | - *navigate* and *visualize* the devicetree 8 | - *search* for devices, bindings, buses or interrupts with flexible criteria 9 | - redirect command output to files (text, HTML, SVG) to *document* hardware configurations 10 | or illustrate notes 11 | - *rich* Textual User Interface, command line auto-completion, command history, user themes 12 | 13 | You can use it with: 14 | 15 | - all DTS files generated by **Zephyr** at build-time (aka `build/zephyr/zephyr.dts`) 16 | - arbitrary DTS files with bindings compatible with Zephyr's 17 | `Devicetree bindings syntax `_ 18 | 19 | .. figure:: img/devicetree-shell.png 20 | :align: center 21 | :alt: A Devicetree Shell 22 | 23 | A Devicetree Shell 24 | 25 | 26 | Status 27 | ****** 28 | 29 | Welcome to the DTSh Project’s documentation for the ``dtsh-next`` branch. 30 | This branch mirrors and packages the code base that serves as a proposal to upstream DTSh 31 | as a new `Zephyr extension to West `_ (would be ``west dtsh``): 32 | `RFC - DTSh, shell-like interface with Devicetree `_ 33 | 34 | Source code and documentation for the Proof of Concept and prototype (DTSh 0.1.x) are still 35 | available at the ``main`` `branch `_ of this repository. 36 | 37 | .. toctree:: 38 | :maxdepth: 2 39 | :caption: Contents: 40 | 41 | getting-started 42 | handbook 43 | faq-and-tips 44 | 45 | .. include:: bib.rst 46 | 47 | .. meta:: 48 | :keywords: zephyr, devicetree, dts, viewer, user interface, embedded development, IoT 49 | -------------------------------------------------------------------------------- /tests/test_dtsh_builtin_ls.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Unit tests for the dtsh.builtins.ls module.""" 6 | 7 | # Relax pylint a bit for unit tests. 8 | # pylint: disable=missing-function-docstring 9 | 10 | 11 | from dtsh.io import DTShOutput 12 | from dtsh.shell import DTSh 13 | from dtsh.builtins.ls import DTShBuiltinLs 14 | 15 | from .dtsh_uthelpers import DTShTests 16 | 17 | _stdout = DTShOutput() 18 | 19 | 20 | def test_dtsh_builtin_ls() -> None: 21 | cmd = DTShBuiltinLs() 22 | DTShTests.check_cmd_meta(cmd, "ls") 23 | 24 | 25 | def test_dtsh_builtin_ls_flags() -> None: 26 | cmd = DTShBuiltinLs() 27 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 28 | DTShTests.check_cmd_flags(cmd, sh, _stdout) 29 | 30 | 31 | def test_dtsh_builtin_ls_arg_orderby() -> None: 32 | cmd = DTShBuiltinLs() 33 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 34 | DTShTests.check_cmd_arg_order_by(cmd, sh, _stdout) 35 | 36 | 37 | def test_dtsh_builtin_ls_arg_longfmt() -> None: 38 | cmd = DTShBuiltinLs() 39 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 40 | DTShTests.check_cmd_arg_longfmt(cmd, sh, _stdout) 41 | 42 | 43 | def test_dtsh_builtin_ls_arg_fixed_depth() -> None: 44 | cmd = DTShBuiltinLs() 45 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 46 | DTShTests.check_cmd_arg_fixed_depth(cmd, sh, _stdout) 47 | 48 | 49 | def test_dtsh_builtin_ls_param() -> None: 50 | cmd = DTShBuiltinLs() 51 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 52 | DTShTests.check_cmd_param_dtpaths(cmd, sh, _stdout) 53 | 54 | 55 | def test_dtsh_builtin_ls_execute() -> None: 56 | out = DTShOutput() 57 | cmd = DTShBuiltinLs() 58 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 59 | 60 | DTShTests.check_cmd_execute(cmd, sh, out) 61 | -------------------------------------------------------------------------------- /tests/test_dtsh_builtin_tree.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Unit tests for the dtsh.builtins.tree module.""" 6 | 7 | # Relax pylint a bit for unit tests. 8 | # pylint: disable=missing-function-docstring 9 | 10 | from dtsh.io import DTShOutput 11 | from dtsh.shell import DTSh 12 | from dtsh.builtins.tree import DTShBuiltinTree 13 | 14 | from .dtsh_uthelpers import DTShTests 15 | 16 | _stdout = DTShOutput() 17 | 18 | 19 | def test_dtsh_builtin_tree() -> None: 20 | cmd = DTShBuiltinTree() 21 | DTShTests.check_cmd_meta(cmd, "tree") 22 | 23 | 24 | def test_dtsh_builtin_tree_flags() -> None: 25 | cmd = DTShBuiltinTree() 26 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 27 | DTShTests.check_cmd_flags(cmd, sh, _stdout) 28 | 29 | 30 | def test_dtsh_builtin_tree_arg_orderby() -> None: 31 | cmd = DTShBuiltinTree() 32 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 33 | DTShTests.check_cmd_arg_order_by(cmd, sh, _stdout) 34 | 35 | 36 | def test_dtsh_builtin_tree_arg_longfmt() -> None: 37 | cmd = DTShBuiltinTree() 38 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 39 | DTShTests.check_cmd_arg_longfmt(cmd, sh, _stdout) 40 | 41 | 42 | def test_dtsh_builtin_tree_arg_fixed_depth() -> None: 43 | cmd = DTShBuiltinTree() 44 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 45 | DTShTests.check_cmd_arg_fixed_depth(cmd, sh, _stdout) 46 | 47 | 48 | def test_dtsh_builtin_tree_param() -> None: 49 | cmd = DTShBuiltinTree() 50 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 51 | DTShTests.check_cmd_param_dtpaths(cmd, sh, _stdout) 52 | 53 | 54 | def test_dtsh_builtin_tree_execute() -> None: 55 | dtmodel = DTShTests.get_sample_dtmodel() 56 | cmd = DTShBuiltinTree() 57 | sh = DTSh(dtmodel, [cmd]) 58 | 59 | DTShTests.check_cmd_execute(cmd, sh, _stdout) 60 | -------------------------------------------------------------------------------- /tests/test_dtsh_uthelpers.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Unit tests for the dtsh_uthelpers module.""" 6 | 7 | # Relax pylint a bit for unit tests. 8 | # pylint: disable=missing-function-docstring 9 | 10 | 11 | import os 12 | 13 | from .dtsh_uthelpers import DTShTests 14 | 15 | 16 | def test_dtsh_uthelpers_mockenv() -> None: 17 | # An existing variable we'll change. 18 | prev_pwd = os.environ["PWD"] 19 | assert prev_pwd 20 | tmp_pwd = "TMP1" 21 | # An existing variable we'll unset. 22 | prev_user = os.environ["USER"] 23 | assert prev_user 24 | # A new OS environment variable, that should not exist. 25 | tmp_kandiarok = "TMP2" 26 | assert not os.environ.get("KONDIARONK") 27 | 28 | with DTShTests.mock_env( 29 | {"PWD": tmp_pwd, "KONDIARONK": tmp_kandiarok, "USER": None} 30 | ): 31 | assert tmp_pwd == os.environ["PWD"] 32 | assert not os.environ.get("USER") 33 | assert tmp_kandiarok == os.environ["KONDIARONK"] 34 | 35 | assert prev_pwd == os.environ["PWD"] 36 | assert prev_user == os.environ["USER"] 37 | assert not os.environ.get("KONDIARONK") 38 | 39 | 40 | def test_dtsh_uthelpers_zephyr_base() -> None: 41 | assert os.path.isfile(os.path.join(DTShTests.ZEPHYR_BASE, "zephyr-env.sh")) 42 | 43 | 44 | def test_dtsh_uthelpers_get_resource_path() -> None: 45 | assert os.path.join( 46 | DTShTests.RES_BASE, "README" 47 | ) == DTShTests.get_resource_path("README") 48 | 49 | 50 | def test_dtsh_uthelpers_from_res() -> None: 51 | with DTShTests.from_res(): 52 | assert os.path.isfile("README") 53 | 54 | 55 | def test_get_sample_edt() -> None: 56 | model = DTShTests.get_sample_edt() 57 | assert model 58 | assert model is DTShTests.get_sample_edt() 59 | assert model is not DTShTests.get_sample_edt(force_reload=True) 60 | -------------------------------------------------------------------------------- /docs/_static/tabs.css: -------------------------------------------------------------------------------- 1 | .sphinx-tabs { 2 | margin-bottom: 1rem; 3 | } 4 | 5 | [role="tablist"] { 6 | border-bottom: 1px solid #a0b3bf; 7 | } 8 | 9 | .sphinx-tabs-tab { 10 | position: relative; 11 | font-family: Lato,'Helvetica Neue',Arial,Helvetica,sans-serif; 12 | color: #1D5C87; 13 | line-height: 24px; 14 | margin: 0; 15 | font-size: 16px; 16 | font-weight: 400; 17 | background-color: rgba(255, 255, 255, 0); 18 | border-radius: 5px 5px 0 0; 19 | border: 0; 20 | padding: 1rem 1.5rem; 21 | margin-bottom: 0; 22 | } 23 | 24 | .sphinx-tabs-tab[aria-selected="true"] { 25 | font-weight: 700; 26 | border: 1px solid #a0b3bf; 27 | border-bottom: 1px solid white; 28 | margin: -1px; 29 | background-color: white; 30 | } 31 | 32 | .sphinx-tabs-tab:focus { 33 | z-index: 1; 34 | outline-offset: 1px; 35 | } 36 | 37 | .sphinx-tabs-panel { 38 | position: relative; 39 | padding: 1rem; 40 | border: 1px solid #a0b3bf; 41 | margin: 0px -1px -1px -1px; 42 | border-radius: 0 0 5px 5px; 43 | border-top: 0; 44 | background: white; 45 | } 46 | 47 | .sphinx-tabs-panel.code-tab { 48 | padding: 0.4rem; 49 | } 50 | 51 | .sphinx-tab img { 52 | margin-bottom: 24 px; 53 | } 54 | 55 | /* Dark theme preference styling */ 56 | 57 | @media (prefers-color-scheme: dark) { 58 | body[data-theme="auto"] .sphinx-tabs-panel { 59 | color: white; 60 | background-color: rgb(50, 50, 50); 61 | } 62 | 63 | body[data-theme="auto"] .sphinx-tabs-tab { 64 | color: white; 65 | background-color: rgba(255, 255, 255, 0.05); 66 | } 67 | 68 | body[data-theme="auto"] .sphinx-tabs-tab[aria-selected="true"] { 69 | border-bottom: 1px solid rgb(50, 50, 50); 70 | background-color: rgb(50, 50, 50); 71 | } 72 | } 73 | 74 | /* Explicit dark theme styling */ 75 | 76 | body[data-theme="dark"] .sphinx-tabs-panel { 77 | color: white; 78 | background-color: rgb(50, 50, 50); 79 | } 80 | 81 | body[data-theme="dark"] .sphinx-tabs-tab { 82 | color: white; 83 | background-color: rgba(255, 255, 255, 0.05); 84 | } 85 | 86 | body[data-theme="dark"] .sphinx-tabs-tab[aria-selected="true"] { 87 | border-bottom: 2px solid rgb(50, 50, 50); 88 | background-color: rgb(50, 50, 50); 89 | } 90 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ==== 2 | DTSh 3 | ==== 4 | 5 | **DTSh** is a Devicetree Source (DTS) files viewer with a shell-like command line interface: 6 | 7 | - *navigate* and *visualize* the devicetree 8 | - *search* for devices, bindings, buses or interrupts with flexible criteria 9 | - redirect command output to files (text, HTML, SVG) to *document* hardware configurations 10 | or illustrate notes 11 | - *rich* Textual User Interface, command line auto-completion, command history, user themes 12 | 13 | :: 14 | 15 | $ dtsh build/zephyr/zephyr.dts 16 | dtsh (0.2.5): A Devicetree Shell 17 | How to exit: q, or quit, or exit, or press Ctrl-D 18 | 19 | / 20 | > cd &flash_controller 21 | 22 | /soc/flash-controller@4001e000 23 | > tree -l 24 | Description 25 | ───────────────────────────────────────────────────────────────── 26 | flash-controller@4001e000 Nordic NVMC (Non-Volatile Memory Controller) 27 | └── flash@0 Flash node 28 | └── partitions This binding is used to describe fixed partitions of a flash (or… 29 | ├── partition@0 Each child node of the fixed-partitions node represents… 30 | ├── partition@c000 Each child node of the fixed-partitions node represents… 31 | ├── partition@82000 Each child node of the fixed-partitions node represents… 32 | └── partition@f8000 Each child node of the fixed-partitions node represents… 33 | 34 | You can use it with: 35 | 36 | - all DTS files generated by **Zephyr** at build-time (aka ``build/zephyr/zephyr.dts``) 37 | - arbitrary DTS files with bindings compatible with Zephyr's `Devicetre bindings syntax `_ 38 | 39 | Status 40 | ****** 41 | 42 | DTSh 0.2.x mirror and package the code base that serves as a proposal to upstream DTSh 43 | as a new Zephyr extension to West: `RFC - DTSh, shell-like interface with Devicetree `_ 44 | 45 | This is the stable and maintained branch: if you have DTSh 0.1.x installed, please upgrade with 46 | e.g. ``pip install -U dtsh``. 47 | 48 | Please refer to the `DTSh project documentation `_. 49 | -------------------------------------------------------------------------------- /docs/_static/copybutton.css: -------------------------------------------------------------------------------- 1 | /* Copy buttons */ 2 | button.copybtn { 3 | position: absolute; 4 | display: flex; 5 | top: .3em; 6 | right: .3em; 7 | width: 1.7em; 8 | height: 1.7em; 9 | opacity: 0; 10 | transition: opacity 0.3s, border .3s, background-color .3s; 11 | user-select: none; 12 | padding: 0; 13 | border: none; 14 | outline: none; 15 | border-radius: 0.4em; 16 | /* The colors that GitHub uses */ 17 | border: #1b1f2426 1px solid; 18 | background-color: #f6f8fa; 19 | color: #57606a; 20 | } 21 | 22 | button.copybtn.success { 23 | border-color: #22863a; 24 | color: #22863a; 25 | } 26 | 27 | button.copybtn svg { 28 | stroke: currentColor; 29 | width: 1.5em; 30 | height: 1.5em; 31 | padding: 0.1em; 32 | } 33 | 34 | div.highlight { 35 | position: relative; 36 | } 37 | 38 | /* Show the copybutton */ 39 | .highlight:hover button.copybtn, button.copybtn.success { 40 | opacity: 1; 41 | } 42 | 43 | .highlight button.copybtn:hover { 44 | background-color: rgb(235, 235, 235); 45 | } 46 | 47 | .highlight button.copybtn:active { 48 | background-color: rgb(187, 187, 187); 49 | } 50 | 51 | /** 52 | * A minimal CSS-only tooltip copied from: 53 | * https://codepen.io/mildrenben/pen/rVBrpK 54 | * 55 | * To use, write HTML like the following: 56 | * 57 | *

Short

58 | */ 59 | .o-tooltip--left { 60 | position: relative; 61 | } 62 | 63 | .o-tooltip--left:after { 64 | opacity: 0; 65 | visibility: hidden; 66 | position: absolute; 67 | content: attr(data-tooltip); 68 | padding: .2em; 69 | font-size: .8em; 70 | left: -.2em; 71 | background: grey; 72 | color: white; 73 | white-space: nowrap; 74 | z-index: 2; 75 | border-radius: 2px; 76 | transform: translateX(-102%) translateY(0); 77 | transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); 78 | } 79 | 80 | .o-tooltip--left:hover:after { 81 | display: block; 82 | opacity: 1; 83 | visibility: visible; 84 | transform: translateX(-100%) translateY(0); 85 | transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); 86 | transition-delay: .5s; 87 | } 88 | 89 | /* By default the copy button shouldn't show up when printing a page */ 90 | @media print { 91 | button.copybtn { 92 | display: none; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/dtsh/builtins/alias.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Devicetree shell built-in "aliases". 6 | 7 | List aliased nodes. 8 | 9 | Unit tests and examples: tests/test_dtsh_builtin_alias.py 10 | """ 11 | 12 | 13 | from typing import Sequence, Mapping 14 | 15 | from dtsh.model import DTNode 16 | from dtsh.io import DTShOutput 17 | from dtsh.shell import DTSh 18 | from dtsh.shellutils import DTShFlagEnabledOnly, DTShParamAlias 19 | 20 | from dtsh.rich.shellutils import DTShCommandLongFmt 21 | from dtsh.rich.modelview import ViewNodeAkaList 22 | 23 | 24 | class DTShBuiltinAlias(DTShCommandLongFmt): 25 | """Devicetree shell built-in 'alias'.""" 26 | 27 | def __init__(self) -> None: 28 | super().__init__( 29 | "alias", 30 | "list aliased nodes", 31 | [ 32 | DTShFlagEnabledOnly(), 33 | ], 34 | DTShParamAlias(), 35 | ) 36 | 37 | def execute(self, argv: Sequence[str], sh: DTSh, out: DTShOutput) -> None: 38 | """Overrides DTShCommand.execute().""" 39 | super().execute(argv, sh, out) 40 | 41 | param_alias = self.with_param(DTShParamAlias).alias 42 | 43 | alias2node: Mapping[str, DTNode] 44 | if param_alias: 45 | # Aliased nodes that match the alias parameter. 46 | alias2node = { 47 | alias: node 48 | for alias, node in sh.dt.aliased_nodes.items() 49 | if alias.find(param_alias) != -1 50 | } 51 | else: 52 | # All aliased nodes. 53 | alias2node = sh.dt.aliased_nodes 54 | 55 | if self.with_flag(DTShFlagEnabledOnly): 56 | # Filter out aliased nodes which are disabled. 57 | alias2node = { 58 | alias: node 59 | for alias, node in alias2node.items() 60 | if node.enabled 61 | } 62 | 63 | # Silently output nothing if no matched aliased nodes. 64 | if alias2node: 65 | if self.has_longfmt: 66 | # Format output (unordered list view). 67 | # Default format string: "Path", "Binding". 68 | view = ViewNodeAkaList(alias2node, self.get_longfmt("pC")) 69 | out.write(view) 70 | else: 71 | # POSIX-like symlinks (link -> file). 72 | for alias, node in alias2node.items(): 73 | out.write(f"{alias} -> {node.path}") 74 | -------------------------------------------------------------------------------- /src/dtsh/builtins/chosen.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Devicetree shell built-in "chosen". 6 | 7 | List chosen nodes. 8 | 9 | Unit tests and examples: tests/test_dtsh_builtin_chosen.py 10 | """ 11 | 12 | 13 | from typing import Sequence, Mapping 14 | 15 | from dtsh.model import DTNode 16 | from dtsh.io import DTShOutput 17 | from dtsh.shell import DTSh 18 | from dtsh.shellutils import DTShFlagEnabledOnly, DTShParamChosen 19 | 20 | from dtsh.rich.shellutils import DTShCommandLongFmt 21 | from dtsh.rich.modelview import ViewNodeAkaList 22 | 23 | 24 | class DTShBuiltinChosen(DTShCommandLongFmt): 25 | """Devicetree shell built-in "chosen".""" 26 | 27 | def __init__(self) -> None: 28 | super().__init__( 29 | "chosen", 30 | "list chosen nodes", 31 | [ 32 | DTShFlagEnabledOnly(), 33 | ], 34 | DTShParamChosen(), 35 | ) 36 | 37 | def execute(self, argv: Sequence[str], sh: DTSh, out: DTShOutput) -> None: 38 | """Overrides DTShCommand.execute().""" 39 | super().execute(argv, sh, out) 40 | 41 | param_chosen = self.with_param(DTShParamChosen).chosen 42 | 43 | chosen2node: Mapping[str, DTNode] 44 | if param_chosen: 45 | # Chosen nodes that match the chosen parameter. 46 | chosen2node = { 47 | chosen: node 48 | for chosen, node in sh.dt.chosen_nodes.items() 49 | if chosen.find(param_chosen) != -1 50 | } 51 | else: 52 | # All chosen nodes. 53 | chosen2node = sh.dt.chosen_nodes 54 | 55 | if self.with_flag(DTShFlagEnabledOnly): 56 | # Filter out chosen nodes which are disabled. 57 | chosen2node = { 58 | chosen: node 59 | for chosen, node in chosen2node.items() 60 | if node.enabled 61 | } 62 | 63 | # Silently output nothing if no matched chosen nodes. 64 | if chosen2node: 65 | if self.has_longfmt: 66 | # Format output (unordered list view). 67 | # Default format string: "Path", "Binding". 68 | view = ViewNodeAkaList(chosen2node, self.get_longfmt("NC")) 69 | out.write(view) 70 | else: 71 | # POSIX-like symlinks (link -> file). 72 | for choice, node in chosen2node.items(): 73 | out.write(f"{choice} -> {node.path}") 74 | -------------------------------------------------------------------------------- /etc/preferences/redir2html.ini: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # Preferences file for: 6 | # - command redirection to HTML 7 | # - light themes 8 | 9 | [dtsh] 10 | 11 | # Alternative actionable text. 12 | # 13 | # This is the appended text element that will 14 | # actually be actionable. 15 | # Depending on availability, one may try: 16 | # - "^": ASCII fallback 17 | # - North-East Arrow (U+2197): the most widespread symbol 18 | # - Link symbol (U+1F517) 19 | # - External link symbol (not yet standardized, 20 | # e.g. U+F08E AwesomeFont) 21 | # 22 | # Type: String 23 | pref.actionable_wchar = [${wchar.arrow_ne}] 24 | 25 | # Command output redirection to HTML: theme. 26 | # 27 | # Configure text and background colors for HTML documents. 28 | # 29 | # Possible values: 30 | # - "svg": default theme for SVG documents (dark bakground, light text) 31 | # - "html": default theme for HTML documents (light bakground, dark text) 32 | # - "dark": darker 33 | # - "light": lighter 34 | # - "night": darkest 35 | # 36 | # Type: String 37 | pref.html.theme = html 38 | 39 | # Command output redirection to HTML: font family. 40 | # This the family name, e.g. "Source Code Pro". 41 | # 42 | # Note: 43 | # - multiple coma separated values allowed, 44 | # e.g. "Source Code Pro, Courier New" 45 | # - the generic "monospace" family is automatically appended last 46 | # - the "Courier New" default font family is installed nearly "everywhere", 47 | # but may appear a bit dull, and might not support the box drawing 48 | # characters range that make trees sharp 49 | # 50 | # Type: String 51 | pref.html.font_family = Source Code Pro 52 | 53 | # Pygments theme for YAML syntax highlighting. 54 | # 55 | # E.g.: 56 | # 57 | # - dark: "monokai", "dracula", "material" 58 | # - light: "bw", "sas", "arduino" 59 | # 60 | # See also: 61 | # - https://pygments.org/styles/ 62 | # - https://rich.readthedocs.io/en/latest/syntax.html 63 | # 64 | # Type: String 65 | pref.yaml.theme = bw 66 | 67 | # YAML views: rendering for actionable texts (aka links). 68 | # 69 | # Type: String 70 | # - "none": do not create hyperlinks 71 | # - "link" (default): link text like browsers do 72 | # - "alt": append alternative actionable view 73 | pref.yaml.actionable_type = alt 74 | 75 | # Pygments theme for DTS syntax highlighting. 76 | # 77 | # E.g.: 78 | # 79 | # - dark: "monokai", "dracula", "material" 80 | # - light: "bw", "sas", "arduino" 81 | # 82 | # See also: 83 | # - https://pygments.org/styles/ 84 | # 85 | # Type: String 86 | pref.dts.theme = bw 87 | -------------------------------------------------------------------------------- /etc/preferences/redir2html-dark.ini: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # Preferences file for: 6 | # - command redirection to HTML 7 | # - dark themes 8 | 9 | [dtsh] 10 | 11 | # Alternative actionable text. 12 | # 13 | # This is the appended text element that will 14 | # actually be actionable. 15 | # Depending on availability, one may try: 16 | # - "^": ASCII fallback 17 | # - North-East Arrow (U+2197): the most widespread symbol 18 | # - Link symbol (U+1F517) 19 | # - External link symbol (not yet standardized, 20 | # e.g. U+F08E AwesomeFont) 21 | # 22 | # Type: String 23 | pref.actionable_wchar = [${wchar.arrow_ne}] 24 | 25 | # Command output redirection to HTML: theme. 26 | # 27 | # Configure text and background colors for HTML documents. 28 | # 29 | # Possible values: 30 | # - "svg": default theme for SVG documents (dark bakground, light text) 31 | # - "html": default theme for HTML documents (light bakground, dark text) 32 | # - "dark": darker 33 | # - "light": lighter 34 | # - "night": darkest 35 | # 36 | # Type: String 37 | pref.html.theme = dark 38 | 39 | # Command output redirection to HTML: font family. 40 | # This the family name, e.g. "Source Code Pro". 41 | # 42 | # Note: 43 | # - multiple coma separated values allowed, 44 | # e.g. "Source Code Pro, Courier New" 45 | # - the generic "monospace" family is automatically appended last 46 | # - the "Courier New" default font family is installed nearly "everywhere", 47 | # but may appear a bit dull, and might not support the box drawing 48 | # characters range that make trees sharp 49 | # 50 | # Type: String 51 | pref.html.font_family = Source Code Pro 52 | 53 | # Pygments theme for YAML syntax highlighting. 54 | # 55 | # E.g.: 56 | # 57 | # - dark: "monokai", "dracula", "material" 58 | # - light: "bw", "sas", "arduino" 59 | # 60 | # See also: 61 | # - https://pygments.org/styles/ 62 | # - https://rich.readthedocs.io/en/latest/syntax.html 63 | # 64 | # Type: String 65 | pref.yaml.theme = github-dark 66 | 67 | # YAML views: rendering for actionable texts (aka links). 68 | # 69 | # Type: String 70 | # - "none": do not create hyperlinks 71 | # - "link" (default): link text like browsers do 72 | # - "alt": append alternative actionable view 73 | pref.yaml.actionable_type = alt 74 | 75 | # Pygments theme for DTS syntax highlighting. 76 | # 77 | # E.g.: 78 | # 79 | # - dark: "monokai", "dracula", "material" 80 | # - light: "bw", "sas", "arduino" 81 | # 82 | # See also: 83 | # - https://pygments.org/styles/ 84 | # 85 | # Type: String 86 | pref.dts.theme = github-dark 87 | -------------------------------------------------------------------------------- /tests/test_dtsh_builtin_find.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Unit tests for the dtsh.builtins.find module.""" 6 | 7 | # Relax pylint a bit for unit tests. 8 | # pylint: disable=missing-function-docstring 9 | 10 | 11 | from typing import List 12 | 13 | from dtsh.io import DTShOutput 14 | from dtsh.shell import DTSh 15 | from dtsh.shellutils import DTSH_ARG_NODE_CRITERIA, DTShArgCriterion 16 | from dtsh.builtins.find import DTShBuiltinFind 17 | 18 | from .dtsh_uthelpers import DTShTests 19 | 20 | _stdout = DTShOutput() 21 | 22 | 23 | def test_dtsh_builtin_find() -> None: 24 | cmd = DTShBuiltinFind() 25 | DTShTests.check_cmd_meta(cmd, "find") 26 | 27 | 28 | def test_dtsh_builtin_find_flags() -> None: 29 | cmd = DTShBuiltinFind() 30 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 31 | DTShTests.check_cmd_flags(cmd, sh, _stdout) 32 | 33 | 34 | def test_dtsh_builtin_find_arg_orderby() -> None: 35 | cmd = DTShBuiltinFind() 36 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 37 | DTShTests.check_cmd_arg_order_by(cmd, sh, _stdout) 38 | 39 | 40 | def test_dtsh_builtin_find_arg_longfmt() -> None: 41 | cmd = DTShBuiltinFind() 42 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 43 | DTShTests.check_cmd_arg_longfmt(cmd, sh, _stdout) 44 | 45 | 46 | def test_dtsh_builtin_find_arg_criteria() -> None: 47 | cmd = DTShBuiltinFind() 48 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 49 | 50 | args: List[DTShArgCriterion] = [ 51 | cmd.option( 52 | f"-{opt.sh}" if opt.shortname else f"--{opt.longname}" # type: ignore 53 | ) 54 | for opt in DTSH_ARG_NODE_CRITERIA 55 | ] 56 | 57 | for arg in args: 58 | assert not arg.isset 59 | assert not arg.get_criterion() 60 | 61 | argv: List[str] = [] 62 | for opt in args: 63 | argv.append( 64 | f"-{opt.shortname}" if opt.shortname else f"--{opt.longname}" 65 | ) 66 | # "*" is valid for both text-based and int-based criteria. 67 | argv.append("*") 68 | 69 | cmd.execute(argv, sh, _stdout) 70 | 71 | for arg in args: 72 | assert arg.isset 73 | assert arg.get_criterion() 74 | 75 | 76 | def test_dtsh_builtin_find_param() -> None: 77 | cmd = DTShBuiltinFind() 78 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 79 | DTShTests.check_cmd_param_dtpaths(cmd, sh, _stdout) 80 | 81 | 82 | def test_dtsh_builtin_find_execute() -> None: 83 | cmd = DTShBuiltinFind() 84 | sh = DTSh(DTShTests.get_sample_dtmodel(), [cmd]) 85 | 86 | DTShTests.check_cmd_execute(cmd, sh, _stdout) 87 | -------------------------------------------------------------------------------- /docs/_static/js/html5shiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /etc/sh/genboards.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | genboard_svg() { 5 | board="$1" 6 | board_dir="$2" 7 | 8 | svg_dir=doc/img 9 | mkdir -p "$svg_dir" 10 | 11 | board_svg="$svg_dir/board.svg" 12 | 13 | dtsh -c "ls --format nKd / > $board_svg" \ 14 | -c "tree --fixed-depth 3 --format naKd soc >> $board_svg" \ 15 | -c "tree --format nKrC &flash0 >> $board_svg" 16 | 17 | # Equivalent to (but avoiding to create a new DTSh session 18 | # for each command): 19 | # dtsh -c "ls --format nKd / > $board_svg" 20 | # dtsh -c "ls --format naKd soc >> $board_svg" 21 | # dtsh -c "tree --format nKrC &flash0 >> $board_svg" 22 | # 23 | # But clearly not equivalent to the commands bellow 24 | # where the OS shell would simly concatenate ANSI outputs text: 25 | # dtsh -c "ls --format nKd /" > $board_svg 26 | # dtsh -c "ls --format naKd soc" >> $board_svg 27 | # dtsh -c "tree --format nKrC &flash0" >> $board_svg 28 | } 29 | 30 | genboard_html() { 31 | board="$1" 32 | board_dir="$2" 33 | 34 | html_dir=doc 35 | mkdir -p "$html_dir" 36 | 37 | board_html="$html_dir/board.html" 38 | 39 | dtsh -c "ls --format nKd / > $board_html" \ 40 | -c "tree --fixed-depth 3 --format naKd soc >> $board_html" \ 41 | -c "tree --format nKrC &flash0 >> $board_html" 42 | } 43 | 44 | # Would be nice to be able to build a covenient list with something 45 | # like: 46 | # west boards -f '{name_v2} {dir}' >/tmp/boards.txt 47 | # May be based on name/qualifiers. 48 | echo "native_sim boards/native/native_sim" >/tmp/boards.txt 49 | { 50 | echo "nrf52840dk/nrf52840 boards/nordic/nrf52840dk" 51 | echo "arduino_nano_33_ble/nrf52840/sense boards/arduino/nano_33_ble" 52 | echo "stm32vl_disco/stm32f100xb boards/st/stm32vl_disco" 53 | echo "mt8195_adsp/mt8195_adsp boards/mediatek/mt8195_adsp" 54 | echo "intel_socfpga_agilex5_socdk/agilex5 boards/intel/socfpga/agilex5_socdk" 55 | echo "esp32s3_devkitm/esp32s3/procpu boards/espressif/esp32s3_devkitm" 56 | echo "qemu_cortex_m3/ti_lm3s6965 boards/qemu/cortex_m3" 57 | 58 | # Can't distinguish variants (e.g. finding the qualifiers in 59 | # some CMake cache variable). 60 | # Would overwrite the board above. 61 | # echo "native_sim/native/64 boards/native/native_sim" 62 | # echo "esp32s3_devkitm/esp32s3/appcpu boards/espressif/esp32s3_devkitm" 63 | 64 | } >>/tmp/boards.txt 65 | 66 | old_wd="$PWD" 67 | 68 | cat /tmp/boards.txt | tr -d '\r' | 69 | while read -r board_name board_dir; do 70 | echo "==== $board_name $board_dir ====" 71 | mkdir -p "$board_dir" 72 | cd "$board_dir" || return 73 | west build -p -b "$board_name" "$ZEPHYR_BASE/samples/hello_world" 74 | 75 | genboard_svg "$board" "$board_dir" 76 | genboard_html "$board" "$board_dir" 77 | 78 | cd "$old_wd" || return 79 | done 80 | -------------------------------------------------------------------------------- /docs/_static/copybutton_funcs.js: -------------------------------------------------------------------------------- 1 | function escapeRegExp(string) { 2 | return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string 3 | } 4 | 5 | /** 6 | * Removes excluded text from a Node. 7 | * 8 | * @param {Node} target Node to filter. 9 | * @param {string} exclude CSS selector of nodes to exclude. 10 | * @returns {DOMString} Text from `target` with text removed. 11 | */ 12 | export function filterText(target, exclude) { 13 | const clone = target.cloneNode(true); // clone as to not modify the live DOM 14 | if (exclude) { 15 | // remove excluded nodes 16 | clone.querySelectorAll(exclude).forEach(node => node.remove()); 17 | } 18 | return clone.innerText; 19 | } 20 | 21 | // Callback when a copy button is clicked. Will be passed the node that was clicked 22 | // should then grab the text and replace pieces of text that shouldn't be used in output 23 | export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { 24 | var regexp; 25 | var match; 26 | 27 | // Do we check for line continuation characters and "HERE-documents"? 28 | var useLineCont = !!lineContinuationChar 29 | var useHereDoc = !!hereDocDelim 30 | 31 | // create regexp to capture prompt and remaining line 32 | if (isRegexp) { 33 | regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') 34 | } else { 35 | regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') 36 | } 37 | 38 | const outputLines = []; 39 | var promptFound = false; 40 | var gotLineCont = false; 41 | var gotHereDoc = false; 42 | const lineGotPrompt = []; 43 | for (const line of textContent.split('\n')) { 44 | match = line.match(regexp) 45 | if (match || gotLineCont || gotHereDoc) { 46 | promptFound = regexp.test(line) 47 | lineGotPrompt.push(promptFound) 48 | if (removePrompts && promptFound) { 49 | outputLines.push(match[2]) 50 | } else { 51 | outputLines.push(line) 52 | } 53 | gotLineCont = line.endsWith(lineContinuationChar) & useLineCont 54 | if (line.includes(hereDocDelim) & useHereDoc) 55 | gotHereDoc = !gotHereDoc 56 | } else if (!onlyCopyPromptLines) { 57 | outputLines.push(line) 58 | } else if (copyEmptyLines && line.trim() === '') { 59 | outputLines.push(line) 60 | } 61 | } 62 | 63 | // If no lines with the prompt were found then just use original lines 64 | if (lineGotPrompt.some(v => v === true)) { 65 | textContent = outputLines.join('\n'); 66 | } 67 | 68 | // Remove a trailing newline to avoid auto-running when pasting 69 | if (textContent.endsWith("\n")) { 70 | textContent = textContent.slice(0, -1) 71 | } 72 | return textContent 73 | } 74 | -------------------------------------------------------------------------------- /docs/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} -------------------------------------------------------------------------------- /etc/sh/dtsh: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Chris Duf 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # Helper shell functions dtsh CI. 6 | 7 | thisfile=$(realpath -m "$0") 8 | thisdir=$(dirname "$thisfile") 9 | DTSH_HOME=$(realpath -m "$thisdir/../..") 10 | unset thisfile 11 | unset thisdir 12 | 13 | if [ -z "$ZEF" ]; then 14 | # shellcheck disable=SC1091 15 | . "$DTSH_HOME/etc/sh/zef" 16 | fi 17 | 18 | 19 | dtsh_clean() { 20 | if [ -n "$DTSH_HOME" ]; then 21 | rm -rf "$DTSH_HOME/.pytest_cache" 22 | rm -rf "$DTSH_HOME/build" 23 | rm -rf "$DTSH_HOME/dist" 24 | rm -rf "$DTSH_HOME/src/dtsh.egg-info" 25 | rm -rf "$DTSH_HOME/src/dtsh/__pycache__" 26 | rm -rf "$DTSH_HOME/tests/__pycache__" 27 | fi 28 | } 29 | 30 | 31 | dtsh_venv() { 32 | local arg_venv="$1" 33 | python3.11 -m venv "$arg_venv" 34 | . "$arg_venv/bin/activate" 35 | echo "Python venv: $arg_venv" 36 | pip install -U pip setuptools 37 | pip install -q "$DTSH_HOME" 38 | } 39 | 40 | 41 | dtsh_unittests() { 42 | local test_venv="$DTSH_HOME/tmp-tests/.venv" 43 | echo '**** Setup unit tests environment' 44 | dtsh_clean 45 | dtsh_venv "$test_venv" 46 | pip install -q ".[test]" || zef_abort 47 | echo 'done.' 48 | echo 49 | echo '**** Run unit tests' 50 | python3.11 -m pytest tests || zef_abort 51 | echo 'done.' 52 | echo 53 | echo '**** Dispose unit tests environment' 54 | deactivate 55 | rm -rf "$DTSH_HOME/tmp-tests" 56 | echo 'done.' 57 | } 58 | 59 | 60 | dtsh_build() { 61 | echo '**** Setup build environment' 62 | local build_venv="$DTSH_HOME/tmp-build/.venv" 63 | dtsh_clean 64 | dtsh_venv "$build_venv" 65 | pip install -q ".[dist]" || zef_abort 66 | echo 'done.' 67 | echo 68 | echo '**** Build' 69 | python3.11 -m build || zef_abort 70 | echo 'done.' 71 | echo 72 | echo '**** Dispose build environment' 73 | deactivate 74 | rm -rf "$DTSH_HOME/tmp-build" 75 | echo 'done.' 76 | } 77 | 78 | 79 | dtsh_dist_test() { 80 | echo "DTSH_HOME: $DTSH_HOME" 81 | zef_continue_yn 82 | 83 | dtsh_build 84 | echo 85 | echo '**** Setup dist environment' 86 | local dist_venv="$DTSH_HOME/tmp-dist/.venv" 87 | dtsh_venv "$dist_venv" 88 | pip install -q ".[dist]" || zef_abort 89 | echo 'done.' 90 | echo 91 | echo '**** Uploading to TestPyPI' 92 | # PyPI does not allow for a filename to be reused, 93 | # even once a project has been deleted and recreated. 94 | local whl_dist=$(find "$DTSH_HOME"/dist/ -name "*.whl") 95 | echo "Wheel dist: $whl_dist" 96 | echo "WARNING: will publish on TestPyPI!!" 97 | python3.11 -m twine upload --repository testpypi "$whl_dist" || zef_abort 98 | echo 'done.' 99 | echo 100 | echo '**** Dispose dist environment' 101 | deactivate 102 | rm -rf "$DTSH_HOME/tmp-dist" 103 | echo 'done.' 104 | } 105 | 106 | 107 | dtsh_dist_release() { 108 | echo "DTSH_HOME: $DTSH_HOME" 109 | zef_continue_yn 110 | 111 | dtsh_build 112 | echo 113 | echo '**** Setup dist environment' 114 | local dist_venv="$DTSH_HOME/tmp-dist/.venv" 115 | dtsh_venv "$dist_venv" 116 | pip install -q ".[dist]" || zef_abort 117 | echo 'done.' 118 | echo 119 | local whl_dist=$(find "$DTSH_HOME"/dist/ -name "*.whl") 120 | local src_dist=$(find "$DTSH_HOME"/dist/ -name "*.tar.gz") 121 | echo "Source dist: $src_dist" 122 | echo "Wheel dist: $whl_dist" 123 | echo "WARNING: will publish on PyPI!!" 124 | zef_continue_yn 125 | echo '**** Uploading to PyPI' 126 | python3.11 -m twine upload "$src_dist" "$whl_dist" || zef_abort 127 | echo 'done.' 128 | echo 129 | echo '**** Dispose dist environment' 130 | deactivate 131 | rm -rf "$DTSH_HOME/tmp-dist" 132 | echo 'done.' 133 | } 134 | -------------------------------------------------------------------------------- /src/dtsh/builtins/tree.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Devicetree shell built-in "tree". 6 | 7 | List nodes in tree-like format. 8 | 9 | Unit tests and examples: tests/test_dtsh_builtin_tree.py 10 | """ 11 | 12 | 13 | from typing import Sequence, Mapping, Dict 14 | 15 | from dtsh.model import DTNode 16 | from dtsh.io import DTShOutput 17 | from dtsh.shell import DTSh 18 | from dtsh.shellutils import ( 19 | DTShFlagReverse, 20 | DTShFlagEnabledOnly, 21 | DTShFlagPager, 22 | DTShArgOrderBy, 23 | DTShArgFixedDepth, 24 | DTShParamDTPaths, 25 | ) 26 | 27 | from dtsh.rich.modelview import ( 28 | SketchMV, 29 | ViewNodeTreePOSIX, 30 | ViewNodeTwoSided, 31 | ) 32 | from dtsh.rich.shellutils import DTShCommandLongFmt 33 | 34 | 35 | class DTShBuiltinTree(DTShCommandLongFmt): 36 | """Devicetree shell built-in "tree".""" 37 | 38 | def __init__(self) -> None: 39 | super().__init__( 40 | "tree", 41 | "list branch contents in tree-like format", 42 | [ 43 | DTShFlagReverse(), 44 | DTShFlagEnabledOnly(), 45 | DTShFlagPager(), 46 | DTShArgOrderBy(), 47 | DTShArgFixedDepth(), 48 | ], 49 | DTShParamDTPaths(), 50 | ) 51 | 52 | def execute(self, argv: Sequence[str], sh: DTSh, out: DTShOutput) -> None: 53 | """Overrides DTShCommand.execute().""" 54 | super().execute(argv, sh, out) 55 | 56 | # Expand path parameter: can't be empty. 57 | path_expansions: Sequence[DTSh.PathExpansion] = self.with_param( 58 | DTShParamDTPaths 59 | ).expand(self, sh) 60 | # Get the model, mapping the branches to list to their expected pathways. 61 | # pathway -> node. 62 | path2branch: Mapping[str, DTNode] = self._get_path2branch( 63 | path_expansions, sh 64 | ) 65 | 66 | if self.with_flag(DTShFlagPager): 67 | out.pager_enter() 68 | 69 | N = len(path2branch) 70 | for i, (path, branch) in enumerate(path2branch.items()): 71 | if self.has_longfmt: 72 | # Formatted output (2sided view). 73 | self._output_longfmt(branch, out) 74 | else: 75 | # POSIX-like simple tree. 76 | self._output_raw(path, branch, out) 77 | 78 | if i != N - 1: 79 | # Insert empty line between trees. 80 | out.write() 81 | 82 | if self.with_flag(DTShFlagPager): 83 | out.pager_exit() 84 | 85 | def _get_path2branch( 86 | self, 87 | path_expansions: Sequence[DTSh.PathExpansion], 88 | sh: DTSh, 89 | ) -> Mapping[str, DTNode]: 90 | path2branch: Dict[str, DTNode] = {} 91 | for expansion in path_expansions: 92 | for branch in self.sort(expansion.nodes): 93 | path = sh.pathway(branch, expansion.prefix) 94 | path2branch[path] = branch 95 | 96 | return path2branch 97 | 98 | def _output_longfmt(self, branch: DTNode, out: DTShOutput) -> None: 99 | # Formatted branch output (2-sided view). 100 | sketch = self.get_sketch(SketchMV.Layout.TWO_SIDED) 101 | cells = self.get_longfmt(sketch.default_fmt) 102 | view_2sided = ViewNodeTwoSided(branch, cells) 103 | view_2sided.do_layout( 104 | self.arg_sorter, 105 | self.flag_reverse, 106 | self.flag_enabled_only, 107 | self.with_arg(DTShArgFixedDepth).depth, 108 | ) 109 | out.write(view_2sided) 110 | 111 | def _output_raw(self, path: str, branch: DTNode, out: DTShOutput) -> None: 112 | # Branch as tree (POSIX output). 113 | tree = ViewNodeTreePOSIX(path, branch) 114 | tree.do_layout( 115 | self.arg_sorter, 116 | self.flag_reverse, 117 | self.flag_enabled_only, 118 | self.with_arg(DTShArgFixedDepth).depth, 119 | ) 120 | out.write(tree) 121 | -------------------------------------------------------------------------------- /docs/_static/js/html5shiv-printshiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3-pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = dtsh 3 | version = 0.2.5 4 | author = Christophe Dufaza 5 | author_email = chris@openmarl.org 6 | description = Shell-like interface with Zephyr Devicetree 7 | long_description = file: README.rst 8 | license = Apache License version 2.0 9 | url = https://github.com/dottspina/dtsh 10 | keywords = devicetree, zephyr, dts, embedded 11 | 12 | classifiers = 13 | Development Status :: 5 - Production/Stable 14 | Programming Language :: Python :: 3 15 | Intended Audience :: Developers 16 | Topic :: Software Development :: Embedded Systems 17 | License :: OSI Approved :: Apache Software License 18 | Programming Language :: Python :: 3 :: Only 19 | 20 | [options] 21 | python_requires = >=3.10 22 | install_requires = 23 | PyYAML>=6.0 24 | rich 25 | gnureadline ; sys_platform == 'darwin' 26 | 27 | packages = 28 | dtsh 29 | dtsh.builtins 30 | dtsh.rich 31 | devicetree 32 | 33 | package_dir = 34 | = src 35 | 36 | [options.package_data] 37 | dtsh = 38 | py.typed 39 | dtsh.ini 40 | dtsh.rich = 41 | theme.ini 42 | 43 | [options.entry_points] 44 | console_scripts = 45 | dtsh = dtsh.cli:run 46 | 47 | [options.extras_require] 48 | # Linters, type hinting, unit tests, etc. 49 | dev = 50 | pycodestyle 51 | flake8 52 | pylint 53 | mypy 54 | types-PyYAML 55 | pytest 56 | # IDE/LSP integration. 57 | lsp = 58 | python-lsp-server[all] 59 | pylsp-mypy 60 | python-lsp-black 61 | # Package distribution only 62 | dist = 63 | build 64 | twine 65 | 66 | [tool:pytest] 67 | pythonpath = src 68 | testpaths = tests 69 | 70 | 71 | [pycodestyle] 72 | # References: 73 | # - https://pycodestyle.pycqa.org/en/latest/intro.html#configuration 74 | max-line-length = 80 75 | 76 | 77 | [pydocstyle] 78 | # References: 79 | # - https://peps.python.org/pep-0257/ 80 | # - http://www.pydocstyle.org/en/stable/usage.html 81 | # - https://google.github.io/styleguide/pyguide.html#Comments 82 | # 83 | # Cannot pass both ignore and convention (unfortunately). 84 | #ignore = D105 85 | 86 | # Relax pydocstyle for test source code. 87 | convention = google 88 | match_dir = ^(?!tests|build|\.venv).* 89 | 90 | 91 | [pylint.] 92 | # References: 93 | # - https://pylint.readthedocs.io/en/latest/user_guide/usage/run.html 94 | disable = 95 | # invalid-name 96 | # Fix: except Exception as e 97 | C0103, 98 | # too-many-ancestor 99 | # Fix: _Loader(YAMLLoader) 100 | R0901, 101 | # too-many-instance-attributes 102 | R0902, 103 | # too-few-public-methods 104 | # Example: abstract base class DTNodeCriterion 105 | R0903, 106 | # too-many-public-methods 107 | R0904, 108 | # too-many-return-statements 109 | R0911, 110 | # too-many-branches 111 | R0912, 112 | # too-many-function-args 113 | R0913, 114 | # too-many-locals 115 | R0914, 116 | # line-too-long 117 | # Example: URL in docstrings 118 | C0301, 119 | # too-many-lines 120 | # Example: dtsh.model module 121 | C0302, 122 | # missing-function-docstring 123 | # C0116, 124 | # missing-class-docstring 125 | # C0115, 126 | # protected-access 127 | # W0212, 128 | # pointless-statement 129 | # W0104 130 | # To ignore files or directories (base names, not paths): 131 | # ignore= 132 | ignore = setup.py 133 | 134 | # Zephyr linter configuration. 135 | min-similarity-lines = 10 136 | 137 | 138 | [flake8] 139 | # References: 140 | # - https://flake8.pycqa.org/en/latest/user/configuration.html 141 | extend-ignore = 142 | # line-too-long: we rely on black for this 143 | E501 144 | # black formatting would fail with "whitespace before ':'" 145 | # See https://github.com/psf/black/issues/280 146 | E203 147 | 148 | 149 | [mypy] 150 | # References: 151 | # - https://mypy.readthedocs.io/en/stable/config_file.html 152 | mypy_path = src:tests 153 | exclude = tests/res 154 | python_version = 3.10 155 | packages = dtsh 156 | strict = true 157 | 158 | 159 | [pylsp-mypy] 160 | # References: 161 | # - https://github.com/python-lsp/pylsp-mypy 162 | enabled = true 163 | dmypy = false 164 | live_mode = true 165 | strict = true 166 | 167 | 168 | [pep8] 169 | aggressive = 3 170 | -------------------------------------------------------------------------------- /docs/_static/togglebutton.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Admonition-based toggles 3 | */ 4 | 5 | /* Visibility of the target */ 6 | .admonition.toggle .admonition-title ~ * { 7 | transition: opacity .3s, height .3s; 8 | } 9 | 10 | /* Toggle buttons inside admonitions so we see the title */ 11 | .admonition.toggle { 12 | position: relative; 13 | } 14 | 15 | /* Titles should cut off earlier to avoid overlapping w/ button */ 16 | .admonition.toggle .admonition-title { 17 | padding-right: 25%; 18 | cursor: pointer; 19 | } 20 | 21 | /* Hovering will cause a slight shift in color to make it feel interactive */ 22 | .admonition.toggle .admonition-title:hover { 23 | box-shadow: inset 0 0 0px 20px rgb(0 0 0 / 1%); 24 | } 25 | 26 | /* Hovering will cause a slight shift in color to make it feel interactive */ 27 | .admonition.toggle .admonition-title:active { 28 | box-shadow: inset 0 0 0px 20px rgb(0 0 0 / 3%); 29 | } 30 | 31 | /* Remove extra whitespace below the admonition title when hidden */ 32 | .admonition.toggle-hidden { 33 | padding-bottom: 0; 34 | } 35 | 36 | .admonition.toggle-hidden .admonition-title { 37 | margin-bottom: 0; 38 | } 39 | 40 | /* hides all the content of a page until de-toggled */ 41 | .admonition.toggle-hidden .admonition-title ~ * { 42 | height: 0; 43 | margin: 0; 44 | opacity: 0; 45 | visibility: hidden; 46 | } 47 | 48 | /* General button style and position*/ 49 | button.toggle-button { 50 | /** 51 | * Background and shape. By default there's no background 52 | * but users can style as they wish 53 | */ 54 | background: none; 55 | border: none; 56 | outline: none; 57 | 58 | /* Positioning just inside the admonition title */ 59 | position: absolute; 60 | right: 0.5em; 61 | padding: 0px; 62 | border: none; 63 | outline: none; 64 | } 65 | 66 | /* Display the toggle hint on wide screens */ 67 | @media (min-width: 768px) { 68 | button.toggle-button.toggle-button-hidden:before { 69 | content: attr(data-toggle-hint); /* This will be filled in by JS */ 70 | font-size: .8em; 71 | align-self: center; 72 | } 73 | } 74 | 75 | /* Icon behavior */ 76 | .tb-icon { 77 | transition: transform .2s ease-out; 78 | height: 1.5em; 79 | width: 1.5em; 80 | stroke: currentColor; /* So that we inherit the color of other text */ 81 | } 82 | 83 | /* The icon should point right when closed, down when open. */ 84 | /* Open */ 85 | .admonition.toggle button .tb-icon { 86 | transform: rotate(90deg); 87 | } 88 | 89 | /* Closed */ 90 | .admonition.toggle button.toggle-button-hidden .tb-icon { 91 | transform: rotate(0deg); 92 | } 93 | 94 | /* With details toggles, we don't rotate the icon so it points right */ 95 | details.toggle-details .tb-icon { 96 | height: 1.4em; 97 | width: 1.4em; 98 | margin-top: 0.1em; /* To center the button vertically */ 99 | } 100 | 101 | 102 | /** 103 | * Details-based toggles. 104 | * In this case, we wrap elements with `.toggle` in a details block. 105 | */ 106 | 107 | /* Details blocks */ 108 | details.toggle-details { 109 | margin: 1em 0; 110 | } 111 | 112 | 113 | details.toggle-details summary { 114 | display: flex; 115 | align-items: center; 116 | cursor: pointer; 117 | list-style: none; 118 | border-radius: .2em; 119 | border-left: 3px solid #1976d2; 120 | background-color: rgb(204 204 204 / 10%); 121 | padding: 0.2em 0.7em 0.3em 0.5em; /* Less padding on left because the SVG has left margin */ 122 | font-size: 0.9em; 123 | } 124 | 125 | details.toggle-details summary:hover { 126 | background-color: rgb(204 204 204 / 20%); 127 | } 128 | 129 | details.toggle-details summary:active { 130 | background: rgb(204 204 204 / 28%); 131 | } 132 | 133 | .toggle-details__summary-text { 134 | margin-left: 0.2em; 135 | } 136 | 137 | details.toggle-details[open] summary { 138 | margin-bottom: .5em; 139 | } 140 | 141 | details.toggle-details[open] summary .tb-icon { 142 | transform: rotate(90deg); 143 | } 144 | 145 | details.toggle-details[open] summary ~ * { 146 | animation: toggle-fade-in .3s ease-out; 147 | } 148 | 149 | @keyframes toggle-fade-in { 150 | from {opacity: 0%;} 151 | to {opacity: 100%;} 152 | } 153 | 154 | /* Print rules - we hide all toggle button elements at print */ 155 | @media print { 156 | /* Always hide the summary so the button doesn't show up */ 157 | details.toggle-details summary { 158 | display: none; 159 | } 160 | } -------------------------------------------------------------------------------- /doc/site/src/faq-and-tips.rst: -------------------------------------------------------------------------------- 1 | .. _dtsh-tips: 2 | 3 | FAQ & Tips 4 | ########## 5 | 6 | Collection of FAQ and tips. 7 | 8 | 9 | .. _dtsh-tips-dtsh: 10 | 11 | Running DTSh 12 | ************ 13 | 14 | 15 | .. _dtsh-tips-dtsh-lacks-binding: 16 | 17 | Failed to open devicetree, lacks bindings 18 | ========================================= 19 | 20 | DTSh will most often rely on the CMake cache content to retrieve the *bindings search path*:: 21 | 22 | build/ 23 | ├── CMakeCache.txt 24 | └── zephyr/ 25 | └── zephyr.dts 26 | 27 | When DTSh can't find this cache file, and ``ZEPHYR_BASE`` is not set, the devicetree model initialization will fail:: 28 | 29 | $ dtsh foobar.dts 30 | Failed to initialize devicetree: 31 | DTS error: interrupt controller for lacks binding 32 | 33 | .. tip:: 34 | 35 | Setting ``ZEPHYR_BASE`` will likely fix the initialization error:: 36 | 37 | $ export ZEPHYR_BASE=/path/to/zephyr 38 | $ dtsh /path/to/foobar.dts 39 | 40 | For more complex use cases, refer to :ref:`dtsh-other-uses` in the Handbook. 41 | 42 | 43 | .. _dtsh-tips-tui: 44 | 45 | User interface 46 | *************** 47 | 48 | 49 | .. _dtsh-tips-tui-prompt: 50 | 51 | I'd prefer the traditional ``$`` prompt 52 | ======================================= 53 | 54 | The default prompt is based on a Unicode symbol (``U+276D``): 55 | 56 | - it may not render properly 57 | - you may prefer a more traditional shell prompt like ``$`` 58 | 59 | .. tip:: 60 | 61 | Create a user preferences file (see :ref:`dtsh-preferences`), and change the prompt string to your convenience:: 62 | 63 | # Traditional user prompt: "$ ", 64 | # using $$ to escape the dollar sign: 65 | prompt.wchar = $$ 66 | 67 | # Or to prevent confusion with the OS shell prompt, 68 | # e.g. "(dtsh)$ ": 69 | prompt.wchar = (dtsh)$$ 70 | 71 | See also the other :ref:`preferences ` that configure the prompt. 72 | 73 | 74 | .. _dtsh-tips-tui-sober: 75 | 76 | I'd prefer something a little more sober 77 | ======================================== 78 | 79 | DTSh use :ref:`themes ` to consistently represent the different types of information: e.g. by default compatible strings are always green, and things that behave like *symbolic links* (e.g. aliases) are all italics 80 | 81 | However, the default colors and styles: 82 | 83 | - are heavily subjective, and may not be to your linking 84 | - may not play well with desktop or terminal theme 85 | - you can end up finding these *garish* colors tiring (we do) 86 | 87 | .. tip:: 88 | 89 | The `/etc/preferences `_ and `/etc/themes `_ directories contain *sober* preferences and theme files. 90 | 91 | .. code-block:: none 92 | 93 | $ dtsh --preferences etc/preferences/sober.ini --theme etc/themes/sober.ini 94 | 95 | .. figure:: img/sober.png 96 | :align: center 97 | :alt: Something a little more sober 98 | :width: 100% 99 | 100 | Something a little more sober 101 | 102 | 103 | .. _dtsh-tips-redi2: 104 | 105 | Command output redirection 106 | *************************** 107 | 108 | .. _dtsh-tips-html-backgrounds: 109 | 110 | Mismatched HTML backgrounds 111 | =========================== 112 | 113 | Default styles are intended for reading the commands output on the terminal, and my not play well when redirecting to HTML, e.g. producing disturbing mix of backgrounds: 114 | 115 | .. list-table:: Default HTML rendering 116 | :widths: auto 117 | :align: center 118 | 119 | * - ``pref.html.theme`` 120 | - ``html`` (light background) 121 | * - ``pref.yaml.theme`` 122 | - ``monokai`` (dark background) 123 | * - ``pref.dts.theme`` 124 | - ``monokai`` (dark background) 125 | 126 | 127 | .. figure:: /img/html-ugly.png 128 | :align: center 129 | :alt: Mismatched HTML backgrounds 130 | 131 | Mismatched HTML backgrounds 132 | 133 | .. tip:: 134 | 135 | Create a user preferences file (see :ref:`dtsh-preferences`), and try to adjust involved themes to get a better backgrounds match, e.g. for an HTML file with *light* CSS styles:: 136 | 137 | pref.html.theme = html 138 | pref.yaml.theme = bw 139 | pref.dts.theme = bw 140 | 141 | The `/etc/preferences `_ directory contains example preference files. 142 | 143 | 144 | .. include:: bib.rst 145 | 146 | .. meta:: 147 | :keywords: zephyr, devicetree, dts, viewer, user interface 148 | -------------------------------------------------------------------------------- /docs/_sources/faq-and-tips.rst.txt: -------------------------------------------------------------------------------- 1 | .. _dtsh-tips: 2 | 3 | FAQ & Tips 4 | ########## 5 | 6 | Collection of FAQ and tips. 7 | 8 | 9 | .. _dtsh-tips-dtsh: 10 | 11 | Running DTSh 12 | ************ 13 | 14 | 15 | .. _dtsh-tips-dtsh-lacks-binding: 16 | 17 | Failed to open devicetree, lacks bindings 18 | ========================================= 19 | 20 | DTSh will most often rely on the CMake cache content to retrieve the *bindings search path*:: 21 | 22 | build/ 23 | ├── CMakeCache.txt 24 | └── zephyr/ 25 | └── zephyr.dts 26 | 27 | When DTSh can't find this cache file, and ``ZEPHYR_BASE`` is not set, the devicetree model initialization will fail:: 28 | 29 | $ dtsh foobar.dts 30 | Failed to initialize devicetree: 31 | DTS error: interrupt controller for lacks binding 32 | 33 | .. tip:: 34 | 35 | Setting ``ZEPHYR_BASE`` will likely fix the initialization error:: 36 | 37 | $ export ZEPHYR_BASE=/path/to/zephyr 38 | $ dtsh /path/to/foobar.dts 39 | 40 | For more complex use cases, refer to :ref:`dtsh-other-uses` in the Handbook. 41 | 42 | 43 | .. _dtsh-tips-tui: 44 | 45 | User interface 46 | *************** 47 | 48 | 49 | .. _dtsh-tips-tui-prompt: 50 | 51 | I'd prefer the traditional ``$`` prompt 52 | ======================================= 53 | 54 | The default prompt is based on a Unicode symbol (``U+276D``): 55 | 56 | - it may not render properly 57 | - you may prefer a more traditional shell prompt like ``$`` 58 | 59 | .. tip:: 60 | 61 | Create a user preferences file (see :ref:`dtsh-preferences`), and change the prompt string to your convenience:: 62 | 63 | # Traditional user prompt: "$ ", 64 | # using $$ to escape the dollar sign: 65 | prompt.wchar = $$ 66 | 67 | # Or to prevent confusion with the OS shell prompt, 68 | # e.g. "(dtsh)$ ": 69 | prompt.wchar = (dtsh)$$ 70 | 71 | See also the other :ref:`preferences ` that configure the prompt. 72 | 73 | 74 | .. _dtsh-tips-tui-sober: 75 | 76 | I'd prefer something a little more sober 77 | ======================================== 78 | 79 | DTSh use :ref:`themes ` to consistently represent the different types of information: e.g. by default compatible strings are always green, and things that behave like *symbolic links* (e.g. aliases) are all italics 80 | 81 | However, the default colors and styles: 82 | 83 | - are heavily subjective, and may not be to your linking 84 | - may not play well with desktop or terminal theme 85 | - you can end up finding these *garish* colors tiring (we do) 86 | 87 | .. tip:: 88 | 89 | The `/etc/preferences `_ and `/etc/themes `_ directories contain *sober* preferences and theme files. 90 | 91 | .. code-block:: none 92 | 93 | $ dtsh --preferences etc/preferences/sober.ini --theme etc/themes/sober.ini 94 | 95 | .. figure:: img/sober.png 96 | :align: center 97 | :alt: Something a little more sober 98 | :width: 100% 99 | 100 | Something a little more sober 101 | 102 | 103 | .. _dtsh-tips-redi2: 104 | 105 | Command output redirection 106 | *************************** 107 | 108 | .. _dtsh-tips-html-backgrounds: 109 | 110 | Mismatched HTML backgrounds 111 | =========================== 112 | 113 | Default styles are intended for reading the commands output on the terminal, and my not play well when redirecting to HTML, e.g. producing disturbing mix of backgrounds: 114 | 115 | .. list-table:: Default HTML rendering 116 | :widths: auto 117 | :align: center 118 | 119 | * - ``pref.html.theme`` 120 | - ``html`` (light background) 121 | * - ``pref.yaml.theme`` 122 | - ``monokai`` (dark background) 123 | * - ``pref.dts.theme`` 124 | - ``monokai`` (dark background) 125 | 126 | 127 | .. figure:: /img/html-ugly.png 128 | :align: center 129 | :alt: Mismatched HTML backgrounds 130 | 131 | Mismatched HTML backgrounds 132 | 133 | .. tip:: 134 | 135 | Create a user preferences file (see :ref:`dtsh-preferences`), and try to adjust involved themes to get a better backgrounds match, e.g. for an HTML file with *light* CSS styles:: 136 | 137 | pref.html.theme = html 138 | pref.yaml.theme = bw 139 | pref.dts.theme = bw 140 | 141 | The `/etc/preferences `_ directory contains example preference files. 142 | 143 | 144 | .. include:: bib.rst 145 | 146 | .. meta:: 147 | :keywords: zephyr, devicetree, dts, viewer, user interface 148 | -------------------------------------------------------------------------------- /docs/_static/js/theme.js: -------------------------------------------------------------------------------- 1 | !function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t= 0 && 56 | !jQuery(node.parentNode).hasClass(className) && 57 | !jQuery(node.parentNode).hasClass("nohighlight")) { 58 | var span; 59 | var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); 60 | if (isInSVG) { 61 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 62 | } else { 63 | span = document.createElement("span"); 64 | span.className = className; 65 | } 66 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 67 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 68 | document.createTextNode(val.substr(pos + text.length)), 69 | node.nextSibling)); 70 | node.nodeValue = val.substr(0, pos); 71 | if (isInSVG) { 72 | var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); 73 | var bbox = node.parentElement.getBBox(); 74 | rect.x.baseVal.value = bbox.x; 75 | rect.y.baseVal.value = bbox.y; 76 | rect.width.baseVal.value = bbox.width; 77 | rect.height.baseVal.value = bbox.height; 78 | rect.setAttribute('class', className); 79 | addItems.push({ 80 | "parent": node.parentNode, 81 | "target": rect}); 82 | } 83 | } 84 | } 85 | else if (!jQuery(node).is("button, select, textarea")) { 86 | jQuery.each(node.childNodes, function() { 87 | highlight(this, addItems); 88 | }); 89 | } 90 | } 91 | var addItems = []; 92 | var result = this.each(function() { 93 | highlight(this, addItems); 94 | }); 95 | for (var i = 0; i < addItems.length; ++i) { 96 | jQuery(addItems[i].parent).before(addItems[i].target); 97 | } 98 | return result; 99 | }; 100 | 101 | /* 102 | * backward compatibility for jQuery.browser 103 | * This will be supported until firefox bug is fixed. 104 | */ 105 | if (!jQuery.browser) { 106 | jQuery.uaMatch = function(ua) { 107 | ua = ua.toLowerCase(); 108 | 109 | var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || 110 | /(webkit)[ \/]([\w.]+)/.exec(ua) || 111 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || 112 | /(msie) ([\w.]+)/.exec(ua) || 113 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || 114 | []; 115 | 116 | return { 117 | browser: match[ 1 ] || "", 118 | version: match[ 2 ] || "0" 119 | }; 120 | }; 121 | jQuery.browser = {}; 122 | jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; 123 | } 124 | -------------------------------------------------------------------------------- /docs/_static/tabs.js: -------------------------------------------------------------------------------- 1 | try { 2 | var session = window.sessionStorage || {}; 3 | } catch (e) { 4 | var session = {}; 5 | } 6 | 7 | window.addEventListener("DOMContentLoaded", () => { 8 | const allTabs = document.querySelectorAll('.sphinx-tabs-tab'); 9 | const tabLists = document.querySelectorAll('[role="tablist"]'); 10 | 11 | allTabs.forEach(tab => { 12 | tab.addEventListener("click", changeTabs); 13 | }); 14 | 15 | tabLists.forEach(tabList => { 16 | tabList.addEventListener("keydown", keyTabs); 17 | }); 18 | 19 | // Restore group tab selection from session 20 | const lastSelected = session.getItem('sphinx-tabs-last-selected'); 21 | if (lastSelected != null) selectNamedTabs(lastSelected); 22 | }); 23 | 24 | /** 25 | * Key focus left and right between sibling elements using arrows 26 | * @param {Node} e the element in focus when key was pressed 27 | */ 28 | function keyTabs(e) { 29 | const tab = e.target; 30 | let nextTab = null; 31 | if (e.keyCode === 39 || e.keyCode === 37) { 32 | tab.setAttribute("tabindex", -1); 33 | // Move right 34 | if (e.keyCode === 39) { 35 | nextTab = tab.nextElementSibling; 36 | if (nextTab === null) { 37 | nextTab = tab.parentNode.firstElementChild; 38 | } 39 | // Move left 40 | } else if (e.keyCode === 37) { 41 | nextTab = tab.previousElementSibling; 42 | if (nextTab === null) { 43 | nextTab = tab.parentNode.lastElementChild; 44 | } 45 | } 46 | } 47 | 48 | if (nextTab !== null) { 49 | nextTab.setAttribute("tabindex", 0); 50 | nextTab.focus(); 51 | } 52 | } 53 | 54 | /** 55 | * Select or deselect clicked tab. If a group tab 56 | * is selected, also select tab in other tabLists. 57 | * @param {Node} e the element that was clicked 58 | */ 59 | function changeTabs(e) { 60 | // Use this instead of the element that was clicked, in case it's a child 61 | const notSelected = this.getAttribute("aria-selected") === "false"; 62 | const positionBefore = this.parentNode.getBoundingClientRect().top; 63 | const notClosable = !this.parentNode.classList.contains("closeable"); 64 | 65 | deselectTabList(this); 66 | 67 | if (notSelected || notClosable) { 68 | selectTab(this); 69 | const name = this.getAttribute("name"); 70 | selectNamedTabs(name, this.id); 71 | 72 | if (this.classList.contains("group-tab")) { 73 | // Persist during session 74 | session.setItem('sphinx-tabs-last-selected', name); 75 | } 76 | } 77 | 78 | const positionAfter = this.parentNode.getBoundingClientRect().top; 79 | const positionDelta = positionAfter - positionBefore; 80 | // Scroll to offset content resizing 81 | window.scrollTo(0, window.scrollY + positionDelta); 82 | } 83 | 84 | /** 85 | * Select tab and show associated panel. 86 | * @param {Node} tab tab to select 87 | */ 88 | function selectTab(tab) { 89 | tab.setAttribute("aria-selected", true); 90 | 91 | // Show the associated panel 92 | document 93 | .getElementById(tab.getAttribute("aria-controls")) 94 | .removeAttribute("hidden"); 95 | } 96 | 97 | /** 98 | * Hide the panels associated with all tabs within the 99 | * tablist containing this tab. 100 | * @param {Node} tab a tab within the tablist to deselect 101 | */ 102 | function deselectTabList(tab) { 103 | const parent = tab.parentNode; 104 | const grandparent = parent.parentNode; 105 | 106 | Array.from(parent.children) 107 | .forEach(t => t.setAttribute("aria-selected", false)); 108 | 109 | Array.from(grandparent.children) 110 | .slice(1) // Skip tablist 111 | .forEach(panel => panel.setAttribute("hidden", true)); 112 | } 113 | 114 | /** 115 | * Select grouped tabs with the same name, but no the tab 116 | * with the given id. 117 | * @param {Node} name name of grouped tab to be selected 118 | * @param {Node} clickedId id of clicked tab 119 | */ 120 | function selectNamedTabs(name, clickedId=null) { 121 | const groupedTabs = document.querySelectorAll(`.sphinx-tabs-tab[name="${name}"]`); 122 | const tabLists = Array.from(groupedTabs).map(tab => tab.parentNode); 123 | 124 | tabLists 125 | .forEach(tabList => { 126 | // Don't want to change the tabList containing the clicked tab 127 | const clickedTab = tabList.querySelector(`[id="${clickedId}"]`); 128 | if (clickedTab === null ) { 129 | // Select first tab with matching name 130 | const tab = tabList.querySelector(`.sphinx-tabs-tab[name="${name}"]`); 131 | deselectTabList(tab); 132 | selectTab(tab); 133 | } 134 | }) 135 | } 136 | 137 | if (typeof exports === 'undefined') { 138 | exports = {}; 139 | } 140 | 141 | exports.keyTabs = keyTabs; 142 | exports.changeTabs = changeTabs; 143 | exports.selectTab = selectTab; 144 | exports.deselectTabList = deselectTabList; 145 | exports.selectNamedTabs = selectNamedTabs; 146 | -------------------------------------------------------------------------------- /docs/_static/pygments.css: -------------------------------------------------------------------------------- 1 | pre { line-height: 125%; } 2 | td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 3 | span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 4 | td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 5 | span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 6 | .highlight .hll { background-color: #ffffcc } 7 | .highlight { background: #f8f8f8; } 8 | .highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ 9 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 10 | .highlight .k { color: #008000; font-weight: bold } /* Keyword */ 11 | .highlight .o { color: #666666 } /* Operator */ 12 | .highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ 13 | .highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ 14 | .highlight .cp { color: #9C6500 } /* Comment.Preproc */ 15 | .highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ 16 | .highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ 17 | .highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ 18 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 19 | .highlight .ge { font-style: italic } /* Generic.Emph */ 20 | .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ 21 | .highlight .gr { color: #E40000 } /* Generic.Error */ 22 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 23 | .highlight .gi { color: #008400 } /* Generic.Inserted */ 24 | .highlight .go { color: #717171 } /* Generic.Output */ 25 | .highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 26 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 27 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 28 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 29 | .highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 30 | .highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 31 | .highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 32 | .highlight .kp { color: #008000 } /* Keyword.Pseudo */ 33 | .highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 34 | .highlight .kt { color: #B00040 } /* Keyword.Type */ 35 | .highlight .m { color: #666666 } /* Literal.Number */ 36 | .highlight .s { color: #BA2121 } /* Literal.String */ 37 | .highlight .na { color: #687822 } /* Name.Attribute */ 38 | .highlight .nb { color: #008000 } /* Name.Builtin */ 39 | .highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 40 | .highlight .no { color: #880000 } /* Name.Constant */ 41 | .highlight .nd { color: #AA22FF } /* Name.Decorator */ 42 | .highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ 43 | .highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ 44 | .highlight .nf { color: #0000FF } /* Name.Function */ 45 | .highlight .nl { color: #767600 } /* Name.Label */ 46 | .highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 47 | .highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ 48 | .highlight .nv { color: #19177C } /* Name.Variable */ 49 | .highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 50 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 51 | .highlight .mb { color: #666666 } /* Literal.Number.Bin */ 52 | .highlight .mf { color: #666666 } /* Literal.Number.Float */ 53 | .highlight .mh { color: #666666 } /* Literal.Number.Hex */ 54 | .highlight .mi { color: #666666 } /* Literal.Number.Integer */ 55 | .highlight .mo { color: #666666 } /* Literal.Number.Oct */ 56 | .highlight .sa { color: #BA2121 } /* Literal.String.Affix */ 57 | .highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ 58 | .highlight .sc { color: #BA2121 } /* Literal.String.Char */ 59 | .highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ 60 | .highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ 61 | .highlight .s2 { color: #BA2121 } /* Literal.String.Double */ 62 | .highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ 63 | .highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ 64 | .highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ 65 | .highlight .sx { color: #008000 } /* Literal.String.Other */ 66 | .highlight .sr { color: #A45A77 } /* Literal.String.Regex */ 67 | .highlight .s1 { color: #BA2121 } /* Literal.String.Single */ 68 | .highlight .ss { color: #19177C } /* Literal.String.Symbol */ 69 | .highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ 70 | .highlight .fm { color: #0000FF } /* Name.Function.Magic */ 71 | .highlight .vc { color: #19177C } /* Name.Variable.Class */ 72 | .highlight .vg { color: #19177C } /* Name.Variable.Global */ 73 | .highlight .vi { color: #19177C } /* Name.Variable.Instance */ 74 | .highlight .vm { color: #19177C } /* Name.Variable.Magic */ 75 | .highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /tests/test_dtsh_rich_svg.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Unit tests for the dtsh.rich.svg module.""" 6 | 7 | # Relax pylint a bit for unit tests. 8 | # pylint: disable=missing-function-docstring 9 | 10 | from dtsh.rich.svg import ( 11 | SVGFragmentViewBox, 12 | SVGFragmentStyle, 13 | SVGFragmentDefs, 14 | SVGFragmentChrome, 15 | SVGFragmentGCircles, 16 | SVGFragmentGTerminal, 17 | ) 18 | 19 | 20 | def test_svg_fragment_viewbox() -> None: 21 | fragment = SVGFragmentViewBox.ifind( 22 | [ 23 | "", 24 | '', 25 | "", 26 | ] 27 | ) 28 | assert 1 == fragment.i_end 29 | assert 1 == len(fragment.content) 30 | assert 1434 == fragment.width 31 | assert 2563.2 == fragment.height 32 | assert [ 33 | '', 34 | ] == fragment.content 35 | 36 | fragment.set_width_height(1024, 768) 37 | assert 1024 == fragment.width 38 | assert 768 == fragment.height 39 | assert [ 40 | '', 41 | ] == fragment.content 42 | 43 | 44 | def test_svg_fragment_style() -> None: 45 | fragment = SVGFragmentStyle.ifind( 46 | [ 47 | "", 48 | '", 51 | "", 52 | ] 53 | ) 54 | assert 3 == fragment.i_end 55 | assert 3 == len(fragment.content) 56 | assert [ 57 | '", 60 | ] == fragment.content 61 | 62 | 63 | def test_svg_fragment_defs() -> None: 64 | fragment = SVGFragmentDefs.ifind( 65 | [ 66 | "", 67 | "", 68 | '', 69 | '', 70 | "", 71 | "...", 72 | "", 73 | "", 74 | ] 75 | ) 76 | assert 6 == fragment.i_end 77 | assert 6 == len(fragment.content) 78 | assert [ 79 | "", 80 | '', 81 | # 307.4 = 299.4 + 8 (rich library issue 3576) 82 | '', 83 | "", 84 | "...", 85 | "", 86 | ] == fragment.content 87 | 88 | 89 | def test_svg_fragment_rect() -> None: 90 | fragment = SVGFragmentChrome.ifind( 91 | [ 92 | "", 93 | '', 94 | "", 95 | ] 96 | ) 97 | assert 1 == fragment.i_end 98 | assert 1 == len(fragment.content) 99 | assert 1432 == fragment.width 100 | assert 2561.2 == fragment.height 101 | assert [ 102 | '' 103 | ] == fragment.content 104 | 105 | fragment.set_width_height(1024, 768) 106 | assert 1024 == fragment.width 107 | assert 768 == fragment.height 108 | assert [ 109 | '' 110 | ] == fragment.content 111 | 112 | 113 | def test_svg_fragment_gcircles() -> None: 114 | fragment = SVGFragmentGCircles.ifind( 115 | [ 116 | "", 117 | '', 118 | "...", 119 | "", 120 | "", 121 | ] 122 | ) 123 | assert 3 == fragment.i_end 124 | assert 3 == len(fragment.content) 125 | assert [ 126 | '', 127 | "...", 128 | "", 129 | ] == fragment.content 130 | 131 | 132 | def test_svg_fragment_gterminal() -> None: 133 | fragment = SVGFragmentGTerminal.ifind( 134 | [ 135 | "", 136 | '', 137 | "", 138 | '', 139 | "...", 140 | "", 141 | "", 142 | "", 143 | ] 144 | ) 145 | assert 6 == fragment.i_end 146 | assert 6 == len(fragment.content) 147 | assert [ 148 | '', 149 | "", 150 | '', 151 | "...", 152 | "", 153 | "", 154 | ] == fragment.content 155 | -------------------------------------------------------------------------------- /tests/test_dtsh_theme.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Unit tests for the dtsh.rich.theme module.""" 6 | 7 | # Relax pylint a bit for unit tests. 8 | # pylint: disable=missing-function-docstring 9 | # pylint: disable=too-many-statements 10 | 11 | 12 | import os 13 | 14 | import pytest 15 | 16 | from rich.color import Color 17 | 18 | from dtsh.rich.theme import DTShTheme 19 | 20 | from .dtsh_uthelpers import DTShTests 21 | 22 | 23 | def test_dtshtheme_load_theme_file() -> None: 24 | # 1. Initialize configuration with bundled defaults. 25 | theme = DTShTheme(DTShTests.get_resource_path("theme", "test.ini")) 26 | 27 | assert not theme.styles["test.default"].bold 28 | # See Color.ANSI_COLOR_NAMES for valid color names. 29 | assert theme.styles["test.red"].color == Color.parse("red") 30 | 31 | assert theme.styles["test.interpolation"].color == Color.parse("red") 32 | 33 | # 2. Load user's specific configuration (overrides defaults). 34 | theme.load_theme_file(DTShTests.get_resource_path("theme", "override.ini")) 35 | assert theme.styles["test.default"].color == Color.parse("green") 36 | 37 | with pytest.raises(DTShTheme.Error): 38 | theme.load_theme_file("not/a/styles/file.ini") 39 | 40 | 41 | def test_dtshtheme_defaults() -> None: 42 | # All these constants MUST have suitable values in the bundled theme.ini. 43 | theme_ini = os.path.abspath( 44 | os.path.join( 45 | os.path.dirname(__file__), "..", "src", "dtsh", "rich", "theme.ini" 46 | ) 47 | ) 48 | assert os.path.isfile(theme_ini) 49 | theme_defaults = DTShTheme(theme_ini) 50 | 51 | assert theme_defaults.styles[DTShTheme.STYLE_DEFAULT] 52 | assert theme_defaults.styles[DTShTheme.STYLE_WARNING] 53 | assert theme_defaults.styles[DTShTheme.STYLE_ERROR] 54 | assert theme_defaults.styles[DTShTheme.STYLE_DISABLED] 55 | 56 | assert theme_defaults.styles[DTShTheme.STYLE_FS_FILE] 57 | assert theme_defaults.styles[DTShTheme.STYLE_FS_DIR] 58 | 59 | assert theme_defaults.styles[DTShTheme.STYLE_LINK_LOCAL] 60 | assert theme_defaults.styles[DTShTheme.STYLE_LINK_WWW] 61 | 62 | assert theme_defaults.styles[DTShTheme.STYLE_LIST_HEADER] 63 | assert theme_defaults.styles[DTShTheme.STYLE_TREE_HEADER] 64 | 65 | assert theme_defaults.styles[DTShTheme.STYLE_DT_PATH_BRANCH] 66 | assert theme_defaults.styles[DTShTheme.STYLE_DT_PATH_NODE] 67 | 68 | assert theme_defaults.styles[DTShTheme.STYLE_DT_DESCRIPTION] 69 | 70 | assert theme_defaults.styles[DTShTheme.STYLE_DT_NODE_NAME] 71 | assert theme_defaults.styles[DTShTheme.STYLE_DT_UNIT_NAME] 72 | assert theme_defaults.styles[DTShTheme.STYLE_DT_UNIT_ADDR] 73 | assert theme_defaults.styles[DTShTheme.STYLE_DT_DEVICE_LABEL] 74 | assert theme_defaults.styles[DTShTheme.STYLE_DT_NODE_LABEL] 75 | assert theme_defaults.styles[DTShTheme.STYLE_DT_COMPAT_STR] 76 | assert theme_defaults.styles[DTShTheme.STYLE_DT_BINDING_COMPAT] 77 | assert theme_defaults.styles[DTShTheme.STYLE_DT_BINDING_DESC] 78 | assert theme_defaults.styles[DTShTheme.STYLE_DT_CB_ORDER] 79 | assert theme_defaults.styles[DTShTheme.STYLE_DT_IS_CHILD_BINDING] 80 | assert theme_defaults.styles[DTShTheme.STYLE_DT_ALIAS] 81 | assert theme_defaults.styles[DTShTheme.STYLE_DT_CHOSEN] 82 | assert theme_defaults.styles[DTShTheme.STYLE_DT_BUS] 83 | assert theme_defaults.styles[DTShTheme.STYLE_DT_ON_BUS] 84 | assert theme_defaults.styles[DTShTheme.STYLE_DT_IRQ_NUMBER] 85 | assert theme_defaults.styles[DTShTheme.STYLE_DT_IRQ_PRIORITY] 86 | assert theme_defaults.styles[DTShTheme.STYLE_DT_REG_ADDR] 87 | assert theme_defaults.styles[DTShTheme.STYLE_DT_REG_SIZE] 88 | assert theme_defaults.styles[DTShTheme.STYLE_DT_ORDINAL] 89 | assert theme_defaults.styles[DTShTheme.STYLE_DT_DEP_ON] 90 | assert theme_defaults.styles[DTShTheme.STYLE_DT_DEP_FAILED] 91 | assert theme_defaults.styles[DTShTheme.STYLE_DT_REQ_BY] 92 | assert theme_defaults.styles[DTShTheme.STYLE_DT_VENDOR_NAME] 93 | assert theme_defaults.styles[DTShTheme.STYLE_DT_VENDOR_PREFIX] 94 | 95 | assert theme_defaults.styles[DTShTheme.STYLE_DT_PROPERTY] 96 | assert theme_defaults.styles[DTShTheme.STYLE_DTVALUE_TRUE] 97 | assert theme_defaults.styles[DTShTheme.STYLE_DTVALUE_FALSE] 98 | assert theme_defaults.styles[DTShTheme.STYLE_DTVALUE_INT] 99 | assert theme_defaults.styles[DTShTheme.STYLE_DTVALUE_INT_ARRAY] 100 | assert theme_defaults.styles[DTShTheme.STYLE_DTVALUE_STR] 101 | assert theme_defaults.styles[DTShTheme.STYLE_DTVALUE_UINT8] 102 | assert theme_defaults.styles[DTShTheme.STYLE_DTVALUE_PHANDLE] 103 | assert theme_defaults.styles[DTShTheme.STYLE_DTVALUE_PHANDLE_DATA] 104 | 105 | assert theme_defaults.styles[DTShTheme.STYLE_YAML_BINDING] 106 | assert theme_defaults.styles[DTShTheme.STYLE_YAML_INCLUDE] 107 | assert theme_defaults.styles[DTShTheme.STYLE_DTS_FILE] 108 | 109 | assert theme_defaults.styles[DTShTheme.STYLE_FORM_LABEL] 110 | assert theme_defaults.styles[DTShTheme.STYLE_FORM_DEFAULT] 111 | -------------------------------------------------------------------------------- /docs/genindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Index — DTSh 0.2.4 documentation 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 70 | 71 |
75 | 76 |
77 |
78 |
79 |
    80 |
  • 81 | 82 |
  • 83 |
  • 84 |
85 |
86 |
87 |
88 |
89 | 90 | 91 |

Index

92 | 93 |
94 | 95 |
96 | 97 | 98 |
99 |
100 |
101 | 102 |
103 | 104 |
105 |

© Copyright 2024, Chris Duf.

106 |
107 | 108 | Built with Sphinx using a 109 | theme 110 | provided by Read the Docs. 111 | 112 | 113 |
114 |
115 |
116 |
117 |
118 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /docs/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Base JavaScript utilities for all Sphinx HTML documentation. 6 | * 7 | * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | "use strict"; 12 | 13 | const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ 14 | "TEXTAREA", 15 | "INPUT", 16 | "SELECT", 17 | "BUTTON", 18 | ]); 19 | 20 | const _ready = (callback) => { 21 | if (document.readyState !== "loading") { 22 | callback(); 23 | } else { 24 | document.addEventListener("DOMContentLoaded", callback); 25 | } 26 | }; 27 | 28 | /** 29 | * Small JavaScript module for the documentation. 30 | */ 31 | const Documentation = { 32 | init: () => { 33 | Documentation.initDomainIndexTable(); 34 | Documentation.initOnKeyListeners(); 35 | }, 36 | 37 | /** 38 | * i18n support 39 | */ 40 | TRANSLATIONS: {}, 41 | PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), 42 | LOCALE: "unknown", 43 | 44 | // gettext and ngettext don't access this so that the functions 45 | // can safely bound to a different name (_ = Documentation.gettext) 46 | gettext: (string) => { 47 | const translated = Documentation.TRANSLATIONS[string]; 48 | switch (typeof translated) { 49 | case "undefined": 50 | return string; // no translation 51 | case "string": 52 | return translated; // translation exists 53 | default: 54 | return translated[0]; // (singular, plural) translation tuple exists 55 | } 56 | }, 57 | 58 | ngettext: (singular, plural, n) => { 59 | const translated = Documentation.TRANSLATIONS[singular]; 60 | if (typeof translated !== "undefined") 61 | return translated[Documentation.PLURAL_EXPR(n)]; 62 | return n === 1 ? singular : plural; 63 | }, 64 | 65 | addTranslations: (catalog) => { 66 | Object.assign(Documentation.TRANSLATIONS, catalog.messages); 67 | Documentation.PLURAL_EXPR = new Function( 68 | "n", 69 | `return (${catalog.plural_expr})` 70 | ); 71 | Documentation.LOCALE = catalog.locale; 72 | }, 73 | 74 | /** 75 | * helper function to focus on search bar 76 | */ 77 | focusSearchBar: () => { 78 | document.querySelectorAll("input[name=q]")[0]?.focus(); 79 | }, 80 | 81 | /** 82 | * Initialise the domain index toggle buttons 83 | */ 84 | initDomainIndexTable: () => { 85 | const toggler = (el) => { 86 | const idNumber = el.id.substr(7); 87 | const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); 88 | if (el.src.substr(-9) === "minus.png") { 89 | el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; 90 | toggledRows.forEach((el) => (el.style.display = "none")); 91 | } else { 92 | el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; 93 | toggledRows.forEach((el) => (el.style.display = "")); 94 | } 95 | }; 96 | 97 | const togglerElements = document.querySelectorAll("img.toggler"); 98 | togglerElements.forEach((el) => 99 | el.addEventListener("click", (event) => toggler(event.currentTarget)) 100 | ); 101 | togglerElements.forEach((el) => (el.style.display = "")); 102 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); 103 | }, 104 | 105 | initOnKeyListeners: () => { 106 | // only install a listener if it is really needed 107 | if ( 108 | !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && 109 | !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS 110 | ) 111 | return; 112 | 113 | document.addEventListener("keydown", (event) => { 114 | // bail for input elements 115 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 116 | // bail with special keys 117 | if (event.altKey || event.ctrlKey || event.metaKey) return; 118 | 119 | if (!event.shiftKey) { 120 | switch (event.key) { 121 | case "ArrowLeft": 122 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 123 | 124 | const prevLink = document.querySelector('link[rel="prev"]'); 125 | if (prevLink && prevLink.href) { 126 | window.location.href = prevLink.href; 127 | event.preventDefault(); 128 | } 129 | break; 130 | case "ArrowRight": 131 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 132 | 133 | const nextLink = document.querySelector('link[rel="next"]'); 134 | if (nextLink && nextLink.href) { 135 | window.location.href = nextLink.href; 136 | event.preventDefault(); 137 | } 138 | break; 139 | } 140 | } 141 | 142 | // some keyboard layouts may need Shift to get / 143 | switch (event.key) { 144 | case "/": 145 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; 146 | Documentation.focusSearchBar(); 147 | event.preventDefault(); 148 | } 149 | }); 150 | }, 151 | }; 152 | 153 | // quick alias for translations 154 | const _ = Documentation.gettext; 155 | 156 | _ready(Documentation.init); 157 | -------------------------------------------------------------------------------- /docs/bib.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <no title> — DTSh 0.2.4 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 70 | 71 |
75 | 76 |
77 |
78 |
79 | 86 |
87 |
88 |
89 |
90 | 91 | 92 | 93 |
94 |
95 |
96 | 97 |
98 | 99 |
100 |

© Copyright 2024, Chris Duf.

101 |
102 | 103 | Built with Sphinx using a 104 | theme 105 | provided by Read the Docs. 106 | 107 | 108 |
109 |
110 |
111 |
112 |
113 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /docs/_images/catBl-node.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 23 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | Compatible:This binding does not define a compatible string   68 | Bus:This binding neither provides nor depends on buses 69 | Child-Bindings:nxp,imx7d-pinctrl 70 | └── iMX pin controller pin group                   71 | └── iMX pin controller pin configuration node. 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /doc/site/src/img/catBl-node.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 23 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | Compatible:This binding does not define a compatible string   68 | Bus:This binding neither provides nor depends on buses 69 | Child-Bindings:nxp,imx7d-pinctrl 70 | └── iMX pin controller pin group                   71 | └── iMX pin controller pin configuration node. 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /docs/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Search — DTSh 0.2.4 documentation 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 | 73 | 74 |
78 | 79 |
80 |
81 |
82 |
    83 |
  • 84 | 85 |
  • 86 |
  • 87 |
88 |
89 |
90 |
91 |
92 | 93 | 100 | 101 | 102 |
103 | 104 |
105 | 106 |
107 |
108 |
109 | 110 |
111 | 112 |
113 |

© Copyright 2024, Chris Duf.

114 |
115 | 116 | Built with Sphinx using a 117 | theme 118 | provided by Read the Docs. 119 | 120 | 121 |
122 |
123 |
124 |
125 |
126 | 131 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /docs/_static/language_data.js: -------------------------------------------------------------------------------- 1 | /* 2 | * language_data.js 3 | * ~~~~~~~~~~~~~~~~ 4 | * 5 | * This script contains the language-specific data used by searchtools.js, 6 | * namely the list of stopwords, stemmer, scorer and splitter. 7 | * 8 | * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. 9 | * :license: BSD, see LICENSE for details. 10 | * 11 | */ 12 | 13 | var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; 14 | 15 | 16 | /* Non-minified version is copied as a separate JS file, is available */ 17 | 18 | /** 19 | * Porter Stemmer 20 | */ 21 | var Stemmer = function() { 22 | 23 | var step2list = { 24 | ational: 'ate', 25 | tional: 'tion', 26 | enci: 'ence', 27 | anci: 'ance', 28 | izer: 'ize', 29 | bli: 'ble', 30 | alli: 'al', 31 | entli: 'ent', 32 | eli: 'e', 33 | ousli: 'ous', 34 | ization: 'ize', 35 | ation: 'ate', 36 | ator: 'ate', 37 | alism: 'al', 38 | iveness: 'ive', 39 | fulness: 'ful', 40 | ousness: 'ous', 41 | aliti: 'al', 42 | iviti: 'ive', 43 | biliti: 'ble', 44 | logi: 'log' 45 | }; 46 | 47 | var step3list = { 48 | icate: 'ic', 49 | ative: '', 50 | alize: 'al', 51 | iciti: 'ic', 52 | ical: 'ic', 53 | ful: '', 54 | ness: '' 55 | }; 56 | 57 | var c = "[^aeiou]"; // consonant 58 | var v = "[aeiouy]"; // vowel 59 | var C = c + "[^aeiouy]*"; // consonant sequence 60 | var V = v + "[aeiou]*"; // vowel sequence 61 | 62 | var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 63 | var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 64 | var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 65 | var s_v = "^(" + C + ")?" + v; // vowel in stem 66 | 67 | this.stemWord = function (w) { 68 | var stem; 69 | var suffix; 70 | var firstch; 71 | var origword = w; 72 | 73 | if (w.length < 3) 74 | return w; 75 | 76 | var re; 77 | var re2; 78 | var re3; 79 | var re4; 80 | 81 | firstch = w.substr(0,1); 82 | if (firstch == "y") 83 | w = firstch.toUpperCase() + w.substr(1); 84 | 85 | // Step 1a 86 | re = /^(.+?)(ss|i)es$/; 87 | re2 = /^(.+?)([^s])s$/; 88 | 89 | if (re.test(w)) 90 | w = w.replace(re,"$1$2"); 91 | else if (re2.test(w)) 92 | w = w.replace(re2,"$1$2"); 93 | 94 | // Step 1b 95 | re = /^(.+?)eed$/; 96 | re2 = /^(.+?)(ed|ing)$/; 97 | if (re.test(w)) { 98 | var fp = re.exec(w); 99 | re = new RegExp(mgr0); 100 | if (re.test(fp[1])) { 101 | re = /.$/; 102 | w = w.replace(re,""); 103 | } 104 | } 105 | else if (re2.test(w)) { 106 | var fp = re2.exec(w); 107 | stem = fp[1]; 108 | re2 = new RegExp(s_v); 109 | if (re2.test(stem)) { 110 | w = stem; 111 | re2 = /(at|bl|iz)$/; 112 | re3 = new RegExp("([^aeiouylsz])\\1$"); 113 | re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 114 | if (re2.test(w)) 115 | w = w + "e"; 116 | else if (re3.test(w)) { 117 | re = /.$/; 118 | w = w.replace(re,""); 119 | } 120 | else if (re4.test(w)) 121 | w = w + "e"; 122 | } 123 | } 124 | 125 | // Step 1c 126 | re = /^(.+?)y$/; 127 | if (re.test(w)) { 128 | var fp = re.exec(w); 129 | stem = fp[1]; 130 | re = new RegExp(s_v); 131 | if (re.test(stem)) 132 | w = stem + "i"; 133 | } 134 | 135 | // Step 2 136 | re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; 137 | if (re.test(w)) { 138 | var fp = re.exec(w); 139 | stem = fp[1]; 140 | suffix = fp[2]; 141 | re = new RegExp(mgr0); 142 | if (re.test(stem)) 143 | w = stem + step2list[suffix]; 144 | } 145 | 146 | // Step 3 147 | re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; 148 | if (re.test(w)) { 149 | var fp = re.exec(w); 150 | stem = fp[1]; 151 | suffix = fp[2]; 152 | re = new RegExp(mgr0); 153 | if (re.test(stem)) 154 | w = stem + step3list[suffix]; 155 | } 156 | 157 | // Step 4 158 | re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; 159 | re2 = /^(.+?)(s|t)(ion)$/; 160 | if (re.test(w)) { 161 | var fp = re.exec(w); 162 | stem = fp[1]; 163 | re = new RegExp(mgr1); 164 | if (re.test(stem)) 165 | w = stem; 166 | } 167 | else if (re2.test(w)) { 168 | var fp = re2.exec(w); 169 | stem = fp[1] + fp[2]; 170 | re2 = new RegExp(mgr1); 171 | if (re2.test(stem)) 172 | w = stem; 173 | } 174 | 175 | // Step 5 176 | re = /^(.+?)e$/; 177 | if (re.test(w)) { 178 | var fp = re.exec(w); 179 | stem = fp[1]; 180 | re = new RegExp(mgr1); 181 | re2 = new RegExp(meq1); 182 | re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 183 | if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) 184 | w = stem; 185 | } 186 | re = /ll$/; 187 | re2 = new RegExp(mgr1); 188 | if (re.test(w) && re2.test(w)) { 189 | re = /.$/; 190 | w = w.replace(re,""); 191 | } 192 | 193 | // and turn initial Y back to y 194 | if (firstch == "y") 195 | w = firstch.toLowerCase() + w.substr(1); 196 | return w; 197 | } 198 | } 199 | 200 | -------------------------------------------------------------------------------- /tests/test_dtsh_rich_shellutils.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Unit tests for the dtsh.rich.shellutils module.""" 6 | 7 | # Relax pylint a bit for unit tests. 8 | # pylint: disable=missing-function-docstring 9 | 10 | 11 | import pytest 12 | 13 | from dtsh.shell import DTSh, DTShError 14 | from dtsh.shellutils import ( 15 | DTShFlagReverse, 16 | DTShFlagEnabledOnly, 17 | DTShArgOrderBy, 18 | DTSH_NODE_ORDER_BY, 19 | ) 20 | from dtsh.rich.shellutils import ( 21 | DTShNodeFmt, 22 | DTShFlagLongList, 23 | DTShArgLongFmt, 24 | DTShCommandLongFmt, 25 | DTSH_NODE_FMT_SPEC, 26 | ) 27 | from dtsh.rich.modelview import SketchMV 28 | 29 | from .dtsh_uthelpers import DTShTests 30 | 31 | 32 | NODE_FMT_ALL = "".join(spec for spec in DTSH_NODE_FMT_SPEC) 33 | NODE_COL_ALL = [fmt.col for fmt in DTSH_NODE_FMT_SPEC.values()] 34 | 35 | 36 | def test_dtsh_node_fmt_spc() -> None: 37 | for key, spec in DTSH_NODE_FMT_SPEC.items(): 38 | assert key == spec.key 39 | assert spec.brief 40 | assert spec.col 41 | assert spec.col.header 42 | 43 | 44 | def test_dtsh_node_fmt() -> None: 45 | # Parse all valid format specifiers. 46 | assert NODE_COL_ALL == DTShNodeFmt.parse(NODE_FMT_ALL) 47 | 48 | # Client code expect DTShError on invalid format string. 49 | with pytest.raises(DTShError): 50 | DTShNodeFmt.parse("invalid format string") 51 | 52 | 53 | def test_dtshflag_longlist() -> None: 54 | DTShTests.check_flag(DTShFlagLongList()) 55 | 56 | 57 | def test_dtsharg_longfmt() -> None: 58 | arg = DTShArgLongFmt() 59 | assert not arg.fmt 60 | 61 | DTShTests.check_arg(arg, NODE_FMT_ALL) 62 | assert NODE_COL_ALL == arg.fmt 63 | 64 | 65 | def test_dtsharg_longfmt_autocomp() -> None: 66 | sh = DTSh(DTShTests.get_sample_dtmodel(), []) 67 | arg = DTShArgLongFmt() 68 | 69 | # Auto-complete with all available fields. 70 | autocomp_states = sorted(NODE_FMT_ALL, key=lambda x: x.lower()) 71 | assert autocomp_states == [state.rlstr for state in arg.autocomp("", sh)] 72 | 73 | # Auto-complete with all fields expect those already set. 74 | autocomp_states.remove("n") 75 | autocomp_states.remove("a") 76 | assert autocomp_states == [state.rlstr for state in arg.autocomp("na", sh)] 77 | 78 | # No match 79 | assert [] == arg.autocomp("invalid_fmt", sh) 80 | 81 | 82 | def test_dtsh_command_lonfmt() -> None: 83 | cmd = DTShCommandLongFmt("foo", "", [], None) 84 | # DTShFlagLongList 85 | assert cmd.option("-l") 86 | # DTShArgLongFmt 87 | assert cmd.option("--format") 88 | 89 | assert not cmd.with_flag(DTShFlagLongList) 90 | assert not cmd.with_arg(DTShArgLongFmt).isset 91 | assert not cmd.with_arg(DTShArgLongFmt).fmt 92 | 93 | cmd.parse_argv(["-l"]) 94 | assert cmd.with_flag(DTShFlagLongList) 95 | # Default fields are not set on parsing the command line. 96 | assert not cmd.with_arg(DTShArgLongFmt).fmt 97 | # get_longformat() is intended to be used once the parser has finished, 98 | # and will answer "something" (fallback). 99 | assert 0 < len(cmd.get_longfmt("")) 100 | 101 | # Caller my also provide a default value when calling get_fields(), 102 | # typically using long lists without explicit format. 103 | assert 3 == len(cmd.get_longfmt("nac")) 104 | 105 | # Set all fields in format string. 106 | cmd.parse_argv(["--format", NODE_FMT_ALL]) 107 | assert NODE_COL_ALL == cmd.with_arg(DTShArgLongFmt).fmt 108 | # A format is explicitly provided: default is ignored. 109 | assert NODE_COL_ALL == cmd.get_longfmt("") 110 | assert NODE_COL_ALL == cmd.get_longfmt("pd") 111 | 112 | with pytest.raises(DTShError): 113 | cmd.parse_argv(["--format", "invalid format string"]) 114 | 115 | 116 | def test_dtsh_command_lonfmt_sort() -> None: 117 | flag_reverse = DTShFlagReverse() 118 | arg_orderby = DTShArgOrderBy() 119 | cmd = DTShCommandLongFmt("mock", "", [flag_reverse, arg_orderby], None) 120 | 121 | assert not cmd.flag_reverse 122 | assert not cmd.arg_sorter 123 | 124 | cmd.parse_argv(["--order-by", "p"]) 125 | assert not cmd.flag_reverse 126 | assert cmd.arg_sorter is DTSH_NODE_ORDER_BY["p"].sorter 127 | 128 | dt = DTShTests.get_sample_dtmodel() 129 | nodes = dt["/leds"].children 130 | expect_nodes = list(sorted(nodes)) 131 | assert expect_nodes == cmd.sort(nodes) 132 | 133 | cmd.parse_argv(["-r", "--order-by", "p"]) 134 | assert cmd.flag_reverse 135 | assert cmd.arg_sorter is DTSH_NODE_ORDER_BY["p"].sorter 136 | expect_nodes = list(reversed(expect_nodes)) 137 | assert expect_nodes == cmd.sort(nodes) 138 | 139 | 140 | def test_dtsh_command_lonfmt_prune() -> None: 141 | dt = DTShTests.get_sample_dtmodel() 142 | flag_enabled_only = DTShFlagEnabledOnly() 143 | cmd = DTShCommandLongFmt("foo", "", [flag_enabled_only], None) 144 | 145 | # Enabled. 146 | dt_i2c0 = dt.labeled_nodes["i2c0"] 147 | # Disabled. 148 | dt_i2c1 = dt.labeled_nodes["i2c1"] 149 | nodes = [dt_i2c0, dt_i2c1] 150 | 151 | assert not cmd.flag_enabled_only 152 | assert nodes == cmd.prune(nodes) 153 | 154 | cmd.parse_argv(["--enabled-only"]) 155 | assert cmd.flag_enabled_only 156 | assert [dt_i2c0] == cmd.prune(nodes) 157 | 158 | 159 | def test_dtsh_node_fmt_columns() -> None: 160 | # Crash-test: we should be able to render all nodes with all columns 161 | # for all layouts. 162 | sh = DTSh(DTShTests.get_sample_dtmodel(), []) 163 | for node in sh.dt: 164 | for col in NODE_COL_ALL: 165 | for layout in SketchMV.Layout: 166 | col.mk_view(node, SketchMV(layout)) 167 | -------------------------------------------------------------------------------- /docs/_static/sphinx_highlight.js: -------------------------------------------------------------------------------- 1 | /* Highlighting utilities for Sphinx HTML documentation. */ 2 | "use strict"; 3 | 4 | const SPHINX_HIGHLIGHT_ENABLED = true 5 | 6 | /** 7 | * highlight a given string on a node by wrapping it in 8 | * span elements with the given class name. 9 | */ 10 | const _highlight = (node, addItems, text, className) => { 11 | if (node.nodeType === Node.TEXT_NODE) { 12 | const val = node.nodeValue; 13 | const parent = node.parentNode; 14 | const pos = val.toLowerCase().indexOf(text); 15 | if ( 16 | pos >= 0 && 17 | !parent.classList.contains(className) && 18 | !parent.classList.contains("nohighlight") 19 | ) { 20 | let span; 21 | 22 | const closestNode = parent.closest("body, svg, foreignObject"); 23 | const isInSVG = closestNode && closestNode.matches("svg"); 24 | if (isInSVG) { 25 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 26 | } else { 27 | span = document.createElement("span"); 28 | span.classList.add(className); 29 | } 30 | 31 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 32 | const rest = document.createTextNode(val.substr(pos + text.length)); 33 | parent.insertBefore( 34 | span, 35 | parent.insertBefore( 36 | rest, 37 | node.nextSibling 38 | ) 39 | ); 40 | node.nodeValue = val.substr(0, pos); 41 | /* There may be more occurrences of search term in this node. So call this 42 | * function recursively on the remaining fragment. 43 | */ 44 | _highlight(rest, addItems, text, className); 45 | 46 | if (isInSVG) { 47 | const rect = document.createElementNS( 48 | "http://www.w3.org/2000/svg", 49 | "rect" 50 | ); 51 | const bbox = parent.getBBox(); 52 | rect.x.baseVal.value = bbox.x; 53 | rect.y.baseVal.value = bbox.y; 54 | rect.width.baseVal.value = bbox.width; 55 | rect.height.baseVal.value = bbox.height; 56 | rect.setAttribute("class", className); 57 | addItems.push({ parent: parent, target: rect }); 58 | } 59 | } 60 | } else if (node.matches && !node.matches("button, select, textarea")) { 61 | node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); 62 | } 63 | }; 64 | const _highlightText = (thisNode, text, className) => { 65 | let addItems = []; 66 | _highlight(thisNode, addItems, text, className); 67 | addItems.forEach((obj) => 68 | obj.parent.insertAdjacentElement("beforebegin", obj.target) 69 | ); 70 | }; 71 | 72 | /** 73 | * Small JavaScript module for the documentation. 74 | */ 75 | const SphinxHighlight = { 76 | 77 | /** 78 | * highlight the search words provided in localstorage in the text 79 | */ 80 | highlightSearchWords: () => { 81 | if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight 82 | 83 | // get and clear terms from localstorage 84 | const url = new URL(window.location); 85 | const highlight = 86 | localStorage.getItem("sphinx_highlight_terms") 87 | || url.searchParams.get("highlight") 88 | || ""; 89 | localStorage.removeItem("sphinx_highlight_terms") 90 | url.searchParams.delete("highlight"); 91 | window.history.replaceState({}, "", url); 92 | 93 | // get individual terms from highlight string 94 | const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); 95 | if (terms.length === 0) return; // nothing to do 96 | 97 | // There should never be more than one element matching "div.body" 98 | const divBody = document.querySelectorAll("div.body"); 99 | const body = divBody.length ? divBody[0] : document.querySelector("body"); 100 | window.setTimeout(() => { 101 | terms.forEach((term) => _highlightText(body, term, "highlighted")); 102 | }, 10); 103 | 104 | const searchBox = document.getElementById("searchbox"); 105 | if (searchBox === null) return; 106 | searchBox.appendChild( 107 | document 108 | .createRange() 109 | .createContextualFragment( 110 | '" 114 | ) 115 | ); 116 | }, 117 | 118 | /** 119 | * helper function to hide the search marks again 120 | */ 121 | hideSearchWords: () => { 122 | document 123 | .querySelectorAll("#searchbox .highlight-link") 124 | .forEach((el) => el.remove()); 125 | document 126 | .querySelectorAll("span.highlighted") 127 | .forEach((el) => el.classList.remove("highlighted")); 128 | localStorage.removeItem("sphinx_highlight_terms") 129 | }, 130 | 131 | initEscapeListener: () => { 132 | // only install a listener if it is really needed 133 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; 134 | 135 | document.addEventListener("keydown", (event) => { 136 | // bail for input elements 137 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 138 | // bail with special keys 139 | if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; 140 | if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { 141 | SphinxHighlight.hideSearchWords(); 142 | event.preventDefault(); 143 | } 144 | }); 145 | }, 146 | }; 147 | 148 | _ready(() => { 149 | /* Do not call highlightSearchWords() when we are on the search page. 150 | * It will highlight words from the *previous* search query. 151 | */ 152 | if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); 153 | SphinxHighlight.initEscapeListener(); 154 | }); 155 | -------------------------------------------------------------------------------- /tests/test_dtsh_config.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Unit tests for the dtsh.config module.""" 6 | 7 | # Relax pylint a bit for unit tests. 8 | # pylint: disable=missing-function-docstring 9 | # pylint: disable=import-outside-toplevel 10 | 11 | 12 | import os 13 | 14 | import pytest 15 | 16 | from dtsh.config import DTShConfig, ActionableType 17 | 18 | from .dtsh_uthelpers import DTShTests 19 | 20 | 21 | def test_dtshconfig_load_ini_file() -> None: 22 | # 1. Initialize configuration with bundled defaults. 23 | cfg = DTShConfig(DTShTests.get_resource_path("ini", "test.ini")) 24 | assert cfg.getstr("test.string") == "a string" 25 | assert cfg.getstr("not.an.option") == "" 26 | # 2. Load user's specific configuration (overrides defaults). 27 | cfg.load_ini_file(DTShTests.get_resource_path("ini", "override.ini")) 28 | assert cfg.getstr("test.string") == "overridden" 29 | assert cfg.getstr("test.new") == "new" 30 | 31 | # Should not fault. 32 | cfg.load_ini_file("not/a/config/file.ini", fail_early=False) 33 | 34 | with pytest.raises(DTShConfig.Error): 35 | cfg.load_ini_file("not/a/config/file.ini") 36 | 37 | 38 | def test_dtshconfig_getbool() -> None: 39 | cfg = DTShConfig(DTShTests.get_resource_path("ini", "test.ini")) 40 | 41 | assert cfg.getbool("test.true", False) 42 | assert not cfg.getbool("test.false", True) 43 | # getbool() is fail-safe. 44 | assert not cfg.getbool("test.bool.inval") 45 | assert cfg.getbool("undefined", True) 46 | 47 | 48 | def test_dtshconfig_getint() -> None: 49 | cfg = DTShConfig(DTShTests.get_resource_path("ini", "test.ini")) 50 | 51 | assert cfg.getint("test.int") == 255 52 | assert cfg.getint("test.hex") == 0xFF 53 | # getint() is fail-safe. 54 | assert cfg.getint("test.int.inval") == 0 55 | assert cfg.getint("test.int.inval", -1) == -1 56 | 57 | 58 | def test_dtshconfig_getstr() -> None: 59 | cfg = DTShConfig(DTShTests.get_resource_path("ini", "test.ini")) 60 | 61 | assert cfg.getstr("test.string") == "a string" 62 | assert cfg.getstr("test.string.quoted") == "quoted string " 63 | assert cfg.getstr("test.string.quotes") == 'a"b' 64 | assert cfg.getstr("test.string.unicode") == "❯" 65 | assert cfg.getstr("test.string.literal") == "\u276F" 66 | assert cfg.getstr("test.string.mixed") == "\u276F ❯" 67 | # getstr() is fail-safe. 68 | assert cfg.getstr("undefined") == "" 69 | assert cfg.getstr("undefined", "any") == "any" 70 | # Empty values map to empty strings. 71 | assert cfg.getstr("test.novalue") == "" 72 | 73 | 74 | def test_dtshconfig_interpolation() -> None: 75 | cfg = DTShConfig(DTShTests.get_resource_path("ini", "test.ini")) 76 | assert cfg.getstr("test.interpolation") == "hello world" 77 | 78 | 79 | def test_dtshconfig_defaults() -> None: 80 | dtsh_ini = os.path.abspath( 81 | os.path.join(os.path.dirname(__file__), "..", "src", "dtsh", "dtsh.ini") 82 | ) 83 | assert os.path.isfile(dtsh_ini) 84 | cfg_defaults = DTShConfig(dtsh_ini) 85 | 86 | # Wide characters. 87 | assert "…" == cfg_defaults.wchar_ellipsis 88 | assert "↗" == cfg_defaults.wchar_arrow_ne 89 | assert "↖" == cfg_defaults.wchar_arrow_nw 90 | assert "→" == cfg_defaults.wchar_arrow_right 91 | assert "↳" == cfg_defaults.wchar_arrow_right_hook 92 | assert "—" == cfg_defaults.wchar_dash 93 | 94 | # Prompt. 95 | assert cfg_defaults.prompt_default 96 | assert cfg_defaults.prompt_alt 97 | assert cfg_defaults.prompt_sparse 98 | 99 | # General preferences. 100 | assert 255 == cfg_defaults.pref_redir2_maxwidth 101 | assert not cfg_defaults.pref_always_longfmt 102 | assert cfg_defaults.pref_sizes_si 103 | assert not cfg_defaults.pref_hex_upper 104 | assert cfg_defaults.pref_fs_hide_dotted 105 | assert cfg_defaults.pref_fs_no_spaces 106 | assert cfg_defaults.pref_fs_no_overwrite 107 | assert not cfg_defaults.pref_fs_no_overwrite_strict 108 | assert ActionableType.LINK == cfg_defaults.pref_actionable_type 109 | 110 | # List views. 111 | assert cfg_defaults.pref_list_headers 112 | assert not cfg_defaults.pref_list_placeholder 113 | assert cfg_defaults.pref_list_fmt 114 | assert ActionableType.LINK == ActionableType( 115 | cfg_defaults.pref_list_actionable_type 116 | ) 117 | 118 | # Tree views. 119 | assert cfg_defaults.pref_tree_headers 120 | assert cfg_defaults.pref_tree_placeholder 121 | assert cfg_defaults.pref_tree_fmt 122 | assert ActionableType.NONE == ActionableType( 123 | cfg_defaults.pref_tree_actionable_type 124 | ) 125 | assert ActionableType.ALT == cfg_defaults.pref_2Sided_actionable_type 126 | # 2-sided views child marker (disabled). 127 | assert not cfg_defaults.pref_tree_cb_anchor 128 | 129 | # Actionable type. 130 | assert ActionableType.LINK == cfg_defaults.pref_actionable_type 131 | assert cfg_defaults.pref_actionable_text 132 | 133 | # HTML. 134 | assert "svg" == cfg_defaults.pref_html_theme 135 | assert "'DejaVu Sans Mono'" == cfg_defaults.pref_html_font_family 136 | 137 | # SVG. 138 | assert "svg" == cfg_defaults.pref_svg_theme 139 | assert "'Fira Code','DejaVu Sans Mono'" == cfg_defaults.pref_svg_font_family 140 | assert 0.61 == cfg_defaults.pref_svg_font_ratio 141 | 142 | # YAML. 143 | assert "nord" == cfg_defaults.pref_yaml_theme 144 | assert ActionableType.LINK == cfg_defaults.pref_yaml_actionable_type 145 | 146 | # DTS. 147 | assert "monokai" == cfg_defaults.pref_dts_theme 148 | assert ActionableType.ALT == cfg_defaults.pref_dts_actionable_type 149 | 150 | # Forms. 151 | assert cfg_defaults.pref_form_show_all 152 | assert ActionableType.ALT == cfg_defaults.pref_form_actionable_type 153 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![PyPI](https://badge.fury.io/py/dtsh.svg)](https://badge.fury.io/py/dtsh) 2 | 3 | [**Getting Started Guide**](https://dottspina.github.io/dtsh/getting-started.html) 4 | 5 | [**Handbook**](https://dottspina.github.io/dtsh/handbook.html) 6 | 7 | 8 | # DTSh - A Devicetre Shell 9 | 10 | **DTSh** is a Devicetree Source ([DTS](https://devicetree-specification.readthedocs.io/en/latest/chapter6-source-language.html)) files viewer with a shell-like command line interface: 11 | 12 | - *navigate* and *visualize* the devicetree 13 | - *search* for devices, bindings, buses or interrupts with flexible criteria 14 | - redirect command output to files (text, HTML, SVG) to *document* hardware configurations 15 | or illustrate notes 16 | - *rich* Textual User Interface, command line auto-completion, command history, user themes 17 | 18 | You can use it with: 19 | 20 | - all DTS files generated by **Zephyr** at build-time (aka `build/zephyr/zephyr.dts`) 21 | - arbitrary DTS files with bindings compatible with Zephyr's [Devicetre bindings syntax](https://docs.zephyrproject.org/latest/build/dts/bindings-syntax.html) 22 | 23 | ![dtsh](doc/img/buses.png) 24 | 25 | 26 | ## Status 27 | 28 | This project started as a Proof of Concept of a simple tool that could *open and understand* the famous `zephyr.dts` file [generated](https://docs.zephyrproject.org/latest/build/cmake/index.html#configuration-phase) at built-time. 29 | Source code and documentation for this prototype (DTSh 0.1.x) are still available from the [main](https://github.com/dottspina/dtsh/tree/main) branch of this repository. 30 | 31 | This branch (`dtsh-next`) mirrors and packages the new code base which serves as a proposal to upstream DTSh as a [Zephyr extension to West](https://docs.zephyrproject.org/latest/develop/west/index.html) (would be `west dtsh`): [RFC - DTSh, shell-like interface with Devicetree](https://github.com/zephyrproject-rtos/zephyr/pull/59863) 32 | 33 | This is the branch that shall be installed, used and commented on: it is now the **default** branch of this repository. 34 | 35 | > [!NOTE] 36 | > Although this branch *reflects* the state of the PR's [content](https://github.com/dottspina/zephyr/tree/rfc-dtsh), it's not an actual mirror: 37 | > - while the implementation in the RFC can *find* the [python-devicetree](https://github.com/zephyrproject-rtos/zephyr/tree/main/scripts/dts/python-devicetree) library the same way other [Zepyhr's devicetree tool](https://github.com/zephyrproject-rtos/zephyr/tree/main/scripts/dts) do, DTSh has to re-distribute snapshots of this library when published on PyPI (see [Possible multiple different versions of python-devicetree](https://github.com/dottspina/dtsh/issues/2)) 38 | > - this code base does not include the West integration which we couldn't install from PyPI 39 | > - changes may happen here first, and be *backported* to the RFC, or vice versa 40 | 41 | 42 | ## DTSh in a Nutshell 43 | 44 | Here are some one-liner steps for the very impatient: 45 | 46 | - install DTSh in *your* Zephyr development environment 47 | - generate the DTS for the board you're interested in 48 | - open this devicetree in DTSh 49 | 50 | **Please** refer to the [project's documentation](https://dottspina.github.io/dtsh/), where you'll find: 51 | 52 | - a [Getting Started Guide](https://dottspina.github.io/dtsh/getting-started.html): install, configure and run DTSh 53 | - the [DTSh Handbook](https://dottspina.github.io/dtsh/handbook.html): the shell and its *rich* Textual User Interface, reference manual of built-in commands, numerous usage examples 54 | 55 | There's also a beginners friendly blog post at golioth: [DTSh – A Devicetree viewer for Zephyr](https://blog.golioth.io/dtsh-a-devicetree-viewer-for-zephyr/) 56 | 57 | 58 | ### Install DTSh 59 | 60 | This method installs the latest DTSh version in the Python virtual environment that belongs to *your* West workspace. 61 | 62 | From the same prompt where you usually enter `west` commands: 63 | 64 | ``` 65 | $ pip install -U dtsh 66 | $ dtsh -h 67 | usage: dtsh [-h] [-b DIR] [-u] [--preferences FILE] [--theme FILE] [DTS] 68 | 69 | shell-like interface with Devicetree 70 | 71 | optional arguments: 72 | -h, --help show this help message and exit 73 | 74 | open a DTS file: 75 | -b DIR, --bindings DIR 76 | directory to search for binding files 77 | DTS path to the DTS file 78 | 79 | user files: 80 | -u, --user-files initialize per-user configuration files and exit 81 | --preferences FILE load additional preferences file 82 | --theme FILE load additional styles file 83 | ``` 84 | 85 | 86 | ### Generate the DTS 87 | 88 | ``` 89 | $ cd zephyr/samples/sensor/bme680 90 | $ west build -b nrf52840dk/nrf52840 91 | ``` 92 | 93 | > [!TIP] 94 | > - Actually building the sample is not necessary, the configuration phase is sufficient: `cmake -B build -DBOARD=nrf52840dk/nrf52840` 95 | > - Replace `nrf52840dk/nrf52840` with the *name* of the [board](https://docs.zephyrproject.org/latest/boards/index.html) you're interested in. 96 | > - Replace `sensor/bme680` with any sample supported by your board. 97 | 98 | ### Open the devicetree 99 | 100 | ``` 101 | $ cd zephyr/samples/sensor/bme680 102 | $ dtsh 103 | dtsh (0.2.4): A Devicetree Shell 104 | How to exit: q, or quit, or exit, or press Ctrl-D 105 | 106 | / 107 | > ls -l 108 | Name Labels Binding 109 | ─────────────────────────────────────────────────────── 110 | chosen 111 | aliases 112 | soc 113 | pin-controller pinctrl nordic,nrf-pinctrl 114 | entropy_bt_hci rng_hci zephyr,bt-hci-entropy 115 | sw-pwm sw_pwm nordic,nrf-sw-pwm 116 | cpus 117 | leds gpio-leds 118 | pwmleds pwm-leds 119 | buttons gpio-keys 120 | connector arduino_header arduino-header-r3 121 | analog-connector arduino_adc arduino,uno-adc 122 | ``` 123 | -------------------------------------------------------------------------------- /tests/test_dtsh_rich_html.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Christophe Dufaza 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | """Unit tests for the dtsh.rich.svg module.""" 6 | 7 | # Relax pylint a bit for unit tests. 8 | # pylint: disable=missing-function-docstring 9 | 10 | from typing import List 11 | 12 | from dtsh.rich.html import HtmlStyle, HtmlPreformatted, HtmlDocument 13 | 14 | SAMPLE_HTML_CAPTURE = """\ 15 | 16 | 17 | 18 | 19 | 20 | 21 | 29 | 30 | 31 | 32 |
DESCRIPTION
 33 |     Nordic NVMC (Non-Volatile Memory Controller)
 34 | 
 35 | 
36 | 37 | 38 | 39 | 40 | """ 41 | 42 | SAMPLE_HTML_DOCUMENT = """\ 43 | 44 | 45 | 46 | 47 | 48 | 49 | 59 | 60 | 61 | 62 |
DESCRIPTION
 63 |     Nordic NVMC (Non-Volatile Memory Controller)
 64 | 
 65 | 
66 | 67 |
DESCRIPTION
 68 |     Nordic NVMC (Non-Volatile Memory Controller)
 69 | 
 70 | 
71 | 72 | 73 | 74 | 75 | """ 76 | 77 | 78 | def test_html_style() -> None: 79 | html_style: HtmlStyle = HtmlStyle.ifind(SAMPLE_HTML_CAPTURE.splitlines()) 80 | assert 13 == html_style.i_end 81 | assert 8 == len(html_style.content) 82 | assert ( 83 | """\ 84 | 92 | """.splitlines() 93 | == html_style.content 94 | ) 95 | 96 | assert 2 == len(html_style.rclasses) 97 | assert html_style.rclasses[0].startswith(".r1") 98 | assert html_style.rclasses[1].startswith(".r2") 99 | 100 | assert 4 == len(html_style.body) 101 | assert ( 102 | """\ 103 | body { 104 | color: #c5c8c6; 105 | background-color: #292929; 106 | } 107 | """.splitlines() 108 | == html_style.body 109 | ) 110 | 111 | html_style.shift_rclasses(5) 112 | assert 2 == len(html_style.rclasses) 113 | assert html_style.rclasses[0].startswith(".r6") 114 | assert html_style.rclasses[1].startswith(".r7") 115 | 116 | 117 | def test_html_preformatted() -> None: 118 | pre_code: HtmlPreformatted = HtmlPreformatted.ifind( 119 | SAMPLE_HTML_CAPTURE.splitlines() 120 | ) 121 | assert 20 == pre_code.i_end 122 | assert 4 == len(pre_code.content) 123 | assert "r1" in pre_code.content[0] 124 | assert "r2" in pre_code.content[1] 125 | 126 | pre_code.shift_rclasses(2, 5) 127 | assert "r6" in pre_code.content[0] 128 | assert "r7" in pre_code.content[1] 129 | 130 | pre_codes: List[HtmlPreformatted] = HtmlPreformatted.ifind_list( 131 | SAMPLE_HTML_DOCUMENT.splitlines() 132 | ) 133 | assert 2 == len(pre_codes) 134 | 135 | assert 4 == len(pre_codes[0].content) 136 | assert "r1" in pre_codes[0].content[0] 137 | assert "r2" in pre_codes[0].content[1] 138 | assert "r3" in pre_codes[1].content[0] 139 | assert "r4" in pre_codes[1].content[1] 140 | 141 | 142 | def test_html_doc() -> None: 143 | doc: HtmlDocument = HtmlDocument(SAMPLE_HTML_DOCUMENT) 144 | other_doc: HtmlDocument = HtmlDocument(SAMPLE_HTML_CAPTURE) 145 | 146 | doc.append(other_doc) 147 | assert 6 == len(doc.style.rclasses) 148 | assert doc.style.rclasses[0].startswith(".r1") 149 | assert doc.style.rclasses[5].startswith(".r6") 150 | 151 | assert 3 == len(doc.codes) 152 | assert "r1" in doc.codes[0].content[0] 153 | assert "r2" in doc.codes[0].content[1] 154 | assert "r3" in doc.codes[1].content[0] 155 | assert "r4" in doc.codes[1].content[1] 156 | assert "r5" in doc.codes[2].content[0] 157 | assert "r6" in doc.codes[2].content[1] 158 | 159 | print() 160 | print(doc.content) 161 | 162 | 163 | ISSUE_CONTENT = """\ 164 | 165 | 166 | 167 | 168 | 169 | 170 | 183 | 184 | 185 | 186 | 187 |
nrf52840dk nrf52840nrf52840Zephyr-RTOS v3.7.0 (36940db938a) HWMv2hello_world 3.7.0
188 | 
189 | 190 | 191 | 192 | 193 | """ 194 | -------------------------------------------------------------------------------- /etc/themes/sober.ini: -------------------------------------------------------------------------------- 1 | [styles] 2 | # Rich styles (aka dtsh theme). 3 | # 4 | # dtsh styles are prefixed by "dtsh" to avoid collisions 5 | # with styles inherited from rich.Theme. 6 | # 7 | # Colors: 8 | # - Standard color: color(N), 0 <= N <= 255 9 | # https://rich.readthedocs.io/en/latest/appendix/colors.html 10 | # - 24-bit color: CSS-like syntax, #rrggbb or rgb(r,g,b) 11 | # 12 | # Attributes: 13 | # - dim 14 | # - bold 15 | # - italic 16 | # - blink 17 | # - reverse 18 | # - strike 19 | # - underline 20 | # 21 | # Styles: [dim|bold|...|underline] on 22 | # 23 | # See also: 24 | # - https://rich.readthedocs.io/en/stable/style.html. 25 | 26 | # Terminal default foreground on terminal default background. 27 | # Please, do not change this, whenever possible prefer 28 | # to configure the terminal appearance (background, colors palette, etc). 29 | dtsh.default = default on default 30 | 31 | # Style for shell error and warning messages. 32 | dtsh.error = red1 33 | dtsh.warning = dark_orange 34 | dtsh.apologies = dim italic 35 | 36 | # Style modifier for disabled items. 37 | # By default, disabled items (e.g. nodes) are dim, 38 | # which may not be legible with some light desktop 39 | # themes: try "strike" instead. 40 | dtsh.disabled = dim 41 | 42 | 43 | # Styles for file system entries (files). 44 | dtsh.fs.file = default 45 | 46 | # Styles for file system entries (directories). 47 | dtsh.fs.dir = italic 48 | 49 | 50 | # Styles for links to local files. 51 | dtsh.link.local = default 52 | 53 | # Styles for external links. 54 | dtsh.link.www = default 55 | 56 | 57 | # Style for all list views headers. 58 | dtsh.list.header = default 59 | 60 | # Style for all tree views headers. 61 | dtsh.tree.header = default 62 | 63 | 64 | # Style for the branch part of a devicetree path. 65 | dtsh.dt.path_branch = default 66 | 67 | # Style for the node part (rightest) of a devicetree path. 68 | dtsh.dt.path_node = bold 69 | 70 | # Base style for descriptions from bindings. 71 | dtsh.dt.description = default 72 | 73 | # Style for DT node names. 74 | dtsh.dt.node_name = default 75 | 76 | # Style for unit names. 77 | dtsh.dt.unit_name = default 78 | 79 | # Style for unit addresses. 80 | dtsh.dt.unit_addr = default 81 | 82 | # Style for device labels (the 'label' property). 83 | dtsh.dt.device_label = default 84 | 85 | # Style for device labels (the labels attached in the DTS). 86 | dtsh.dt.node_label = bold italic 87 | 88 | # Base style for compatible strings. 89 | dtsh.dt.compat_str = default 90 | 91 | # Style for compatible strings when they represent device bindings. 92 | dtsh.dt.binding_compat = bold 93 | 94 | # Style for device bindings descriptions. 95 | dtsh.dt.binding_desc = default 96 | 97 | # Style for child-binding depth. 98 | dtsh.dt.cb_depth = default 99 | 100 | # Style for child-binding orders greater than 0 101 | # (the binding is actually a child-binding). 102 | dtsh.dt.is_child_binding = bold 103 | 104 | # Style for child-binding anchors. 105 | dtsh.dt.cb_anchor = white 106 | 107 | # Style for DT alias names. 108 | dtsh.dt.alias = italic 109 | 110 | # Style for DT chosen names. 111 | dtsh.dt.chosen = italic 112 | 113 | # Base style for bus protocols. 114 | dtsh.dt.bus = default 115 | 116 | # Style for buses when they represent a bus of appearance. 117 | dtsh.dt.on_bus = default 118 | 119 | # Style for IRQ numbers. 120 | dtsh.dt.irq_number = default 121 | 122 | # Style for IRQ priorities. 123 | dtsh.dt.irq_priority = default 124 | 125 | # Style for register addresses. 126 | dtsh.dt.reg_addr = default 127 | 128 | # Style for register sizes. 129 | dtsh.dt.reg_size = default 130 | 131 | # Style for device requirements. 132 | dtsh.dt.depend_on = default 133 | 134 | # Style for disabled requirements. 135 | dtsh.dt.depend_failed = dark_orange 136 | 137 | # Style for dependent devices. 138 | dtsh.dt.required_by = default 139 | 140 | # Style for device vendor prefixes. 141 | dtsh.dt.vendor_prefix = default 142 | 143 | # Style for device vendor names. 144 | dtsh.dt.vendor_name = default 145 | 146 | # Style for node dependency ordinals. 147 | dtsh.dt.dep_ordinal = default 148 | 149 | # Style for okay status string. 150 | dtsh.dt.status_enabled = default 151 | 152 | # Style for disabled status string. 153 | dtsh.dt.status_disabled = dim orange1 154 | 155 | # Style for property names. 156 | dtsh.dt.property = default 157 | 158 | # Style for "boolean" values. 159 | dtsh.dtvalue.bool = default 160 | dtsh.dtvalue.true = bold 161 | dtsh.dtvalue.false = strike 162 | 163 | # Style for "int" values. 164 | dtsh.dtvalue.int = default 165 | 166 | # Style for "int" values in arrays. 167 | dtsh.dtvalue.int_array = default 168 | 169 | # Style for "uint8" values. 170 | dtsh.dtvalue.uint8 = default 171 | 172 | # Style for "string" values. 173 | dtsh.dtvalue.string = default 174 | 175 | # Style for "phandle" values. 176 | dtsh.dtvalue.phandle = italic 177 | 178 | # Style for data values in "phandle-array" entries. 179 | dtsh.dtvalue.phandle_data = default 180 | 181 | # Style for data values in "compound" entries. 182 | dtsh.dtvalue.compound = default 183 | 184 | # Style for top-level YAML binding file names. 185 | dtsh.yaml.binding = %(dtsh.dt.binding_compat)s 186 | 187 | # Style for included YAML binding file names. 188 | dtsh.yaml.include = default 189 | 190 | 191 | # Style for form labels. 192 | dtsh.form.label = default 193 | 194 | # Default style for form fields. 195 | dtsh.form.default = default 196 | 197 | 198 | # Style for DTS file names. 199 | dtsh.dts = default 200 | 201 | 202 | # Styles for information in form contents: 203 | # 204 | # ZEPHYR_BASE 205 | dtsh.inf.zephyr_base = %(dtsh.link.local)s 206 | # Zephyr-RTOS kernel 207 | dtsh.inf.kernel = default 208 | # Devicetree (DTS) 209 | dtsh.inf.devicetree = default 210 | # Toolchain 211 | dtsh.inf.toolchain = default 212 | # Binding search path 213 | dtsh.inf.bindings = %(dtsh.dt.compat_str)s 214 | # Vendors file 215 | dtsh.inf.vendors = default 216 | # Application firmware 217 | dtsh.inf.application = default 218 | # Board name 219 | dtsh.inf.board = default 220 | # Board file (DTS) 221 | dtsh.inf.board_file = default 222 | # SoC name 223 | dtsh.inf.soc = default 224 | # SoC SVD 225 | dtsh.inf.soc_svd = default 226 | -------------------------------------------------------------------------------- /src/devicetree/grutils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2009-2013, 2019 Peter A. Bigot 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # This implementation is derived from the one in 6 | # [PyXB](https://github.com/pabigot/pyxb), stripped down and modified 7 | # specifically to manage edtlib Node instances. 8 | 9 | import collections 10 | 11 | class Graph: 12 | """ 13 | Represent a directed graph with edtlib Node objects as nodes. 14 | 15 | This is used to determine order dependencies among nodes in a 16 | devicetree. An edge from C{source} to C{target} indicates that 17 | some aspect of C{source} requires that some aspect of C{target} 18 | already be available. 19 | """ 20 | 21 | def __init__(self, root=None): 22 | self.__roots = None 23 | if root is not None: 24 | self.__roots = {root} 25 | self.__edge_map = collections.defaultdict(set) 26 | self.__reverse_map = collections.defaultdict(set) 27 | self.__nodes = set() 28 | 29 | def add_node(self, node): 30 | """ 31 | Add a node without any target to the graph. 32 | """ 33 | self.__nodes.add(node) 34 | 35 | def add_edge(self, source, target): 36 | """ 37 | Add a directed edge from the C{source} to the C{target}. 38 | 39 | The nodes are added to the graph if necessary. 40 | """ 41 | self.__edge_map[source].add(target) 42 | if source != target: 43 | self.__reverse_map[target].add(source) 44 | self.__nodes.add(source) 45 | self.__nodes.add(target) 46 | 47 | def roots(self): 48 | """ 49 | Return the set of nodes calculated to be roots (i.e., those 50 | that have no incoming edges). 51 | 52 | This caches the roots calculated in a previous invocation. 53 | 54 | @rtype: C{set} 55 | """ 56 | if not self.__roots: 57 | self.__roots = set() 58 | for n in self.__nodes: 59 | if n not in self.__reverse_map: 60 | self.__roots.add(n) 61 | return self.__roots 62 | 63 | def _tarjan(self): 64 | # Execute Tarjan's algorithm on the graph. 65 | # 66 | # Tarjan's algorithm 67 | # (http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm) 68 | # computes the strongly-connected components 69 | # (http://en.wikipedia.org/wiki/Strongly_connected_component) 70 | # of the graph: i.e., the sets of nodes that form a minimal 71 | # closed set under edge transition. In essence, the loops. 72 | # We use this to detect groups of components that have a 73 | # dependency cycle, and to impose a total order on components 74 | # based on dependencies. 75 | 76 | self.__stack = [] 77 | self.__scc_order = [] 78 | self.__index = 0 79 | self.__tarjan_index = {} 80 | self.__tarjan_low_link = {} 81 | for v in self.__nodes: 82 | self.__tarjan_index[v] = None 83 | roots = sorted(self.roots(), key=node_key) 84 | if self.__nodes and not roots: 85 | raise Exception('TARJAN: No roots found in graph with {} nodes'.format(len(self.__nodes))) 86 | 87 | for r in roots: 88 | self._tarjan_root(r) 89 | 90 | # Assign ordinals for edtlib 91 | ordinal = 0 92 | for scc in self.__scc_order: 93 | # Zephyr customization: devicetree Node graphs should have 94 | # no loops, so all SCCs should be singletons. That may 95 | # change in the future, but for now we only give an 96 | # ordinal to singletons. 97 | if len(scc) == 1: 98 | scc[0].dep_ordinal = ordinal 99 | ordinal += 1 100 | 101 | def _tarjan_root(self, v): 102 | # Do the work of Tarjan's algorithm for a given root node. 103 | 104 | if self.__tarjan_index.get(v) is not None: 105 | # "Root" was already reached. 106 | return 107 | self.__tarjan_index[v] = self.__tarjan_low_link[v] = self.__index 108 | self.__index += 1 109 | self.__stack.append(v) 110 | source = v 111 | for target in sorted(self.__edge_map[source], key=node_key): 112 | if self.__tarjan_index[target] is None: 113 | self._tarjan_root(target) 114 | self.__tarjan_low_link[v] = min(self.__tarjan_low_link[v], self.__tarjan_low_link[target]) 115 | elif target in self.__stack: 116 | self.__tarjan_low_link[v] = min(self.__tarjan_low_link[v], self.__tarjan_low_link[target]) 117 | 118 | if self.__tarjan_low_link[v] == self.__tarjan_index[v]: 119 | scc = [] 120 | while True: 121 | scc.append(self.__stack.pop()) 122 | if v == scc[-1]: 123 | break 124 | self.__scc_order.append(scc) 125 | 126 | def scc_order(self): 127 | """Return the strongly-connected components in order. 128 | 129 | The data structure is a list, in dependency order, of strongly 130 | connected components (which can be single nodes). Appearance 131 | of a node in a set earlier in the list indicates that it has 132 | no dependencies on any node that appears in a subsequent set. 133 | This order is preferred over a depth-first-search order for 134 | code generation, since it detects loops. 135 | """ 136 | if not self.__scc_order: 137 | self._tarjan() 138 | return self.__scc_order 139 | __scc_order = None 140 | 141 | def depends_on(self, node): 142 | """Get the nodes that 'node' directly depends on.""" 143 | return sorted(self.__edge_map[node], key=node_key) 144 | 145 | def required_by(self, node): 146 | """Get the nodes that directly depend on 'node'.""" 147 | return sorted(self.__reverse_map[node], key=node_key) 148 | 149 | def node_key(node): 150 | # This sort key ensures that sibling nodes with the same name will 151 | # use unit addresses as tiebreakers. That in turn ensures ordinals 152 | # for otherwise indistinguishable siblings are in increasing order 153 | # by unit address, which is convenient for displaying output. 154 | 155 | if node.parent: 156 | parent_path = node.parent.path 157 | else: 158 | parent_path = '/' 159 | 160 | if node.unit_addr is not None: 161 | name = node.name.rsplit('@', 1)[0] 162 | unit_addr = node.unit_addr 163 | else: 164 | name = node.name 165 | unit_addr = -1 166 | 167 | return (parent_path, name, unit_addr) 168 | --------------------------------------------------------------------------------