Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: add -c / -i for non-interactive use #4

Merged
merged 4 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 48 additions & 2 deletions doc/site/src/getting-started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,11 @@ Once installed, the Devicetree Shell is available as the ``dtsh`` command:
.. code-block:: none

$ dtsh -h
usage: dtsh [-h] [-b DIR] [-u] [--preferences FILE] [--theme FILE] [DTS]
usage: dtsh [-h] [-b DIR] [-u] [--preferences FILE] [--theme FILE] [-c CMD] [-f FILE] [-i] [DTS]

shell-like interface with Devicetree

optional arguments:
options:
-h, --help show this help message and exit

open a DTS file:
Expand All @@ -167,6 +167,11 @@ Once installed, the Devicetree Shell is available as the ``dtsh`` command:
--preferences FILE load additional preferences file
--theme FILE load additional styles file

session control:
-c CMD execute CMD at startup (may be repeated)
-f FILE execute batch commands from FILE at startup
-i, --interactive enter interactive loop after batch commands

We'll first confirm that the installation went well with a simple but typical usage,
before tackling a few other scenarios.

Expand Down Expand Up @@ -272,6 +277,47 @@ Where:
- one of these directories shall contain a valid vendors file, e.g. ``dir1/vendor-prefixes.txt``


.. _dtsh-usage-batch:

Batch Mode
==========

For scripting and automation, DTSh can also be used non-interactively by passing:

- a series of commands to execute at startup: ``-c CMD1 -c CMD2``
- or a file containing such commands: ``-f FILE``

The ``-i --interactive`` option then permits to enter the interactive loop after the batch commands have been executed.

For example, to list the contents of the root of the devicetree and then change
to another directory, before entering the interactive loop, you can use the
following command:

.. code-block:: none

$ dtsh -c "ls -l" -c "cd &i2c0" -i
dtsh (0.2.1): A Devicetree Shell
How to exit: q, or quit, or exit, or press Ctrl-D

> Name Labels Binding
───────────────────────────────────────────────────────
chosen
aliases
soc
pin-controller pinctrl nordic,nrf-pinctrl
entropy_bt_hci rng_hci zephyr,bt-hci-entropy
sw-pwm sw_pwm nordic,nrf-sw-pwm
cpus
leds gpio-leds
pwmleds pwm-leds
buttons gpio-keys
connector arduino_header arduino-header-r3
analog-connector arduino_adc arduino,uno-adc

/soc/i2c@40003000


.. _dtsh-configuration:

Configuration
Expand Down
53 changes: 49 additions & 4 deletions src/dtsh/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
Run the devicetree shell without West.
"""

from typing import cast, Optional, List
from typing import cast, Optional, Union, List

import argparse
import os
Expand Down Expand Up @@ -65,7 +65,28 @@ def __init__(self) -> None:
metavar="FILE",
)

grp_session_ctrl = self._parser.add_argument_group("session control")
grp_session_ctrl.add_argument(
"-c",
help="execute CMD at startup (may be repeated)",
action="append",
metavar="CMD",
)
grp_session_ctrl.add_argument(
"-f",
help="execute batch commands from FILE at startup",
metavar="FILE",
)
grp_session_ctrl.add_argument(
"-i",
"--interactive",
help="enter interactive loop after batch commands",
action="store_true",
)

self._argv = self._parser.parse_args()
if self._argv.f and self._argv.c:
self._parser.error("-c and -f are mutually exclusive")

@property
def binding_dirs(self) -> Optional[List[str]]:
Expand Down Expand Up @@ -98,6 +119,23 @@ def theme(self) -> Optional[str]:
"""Additional styles file."""
return str(self._argv.theme[0]) if self._argv.theme else None

@property
def batch_source(self) -> Optional[Union[str, List[str]]]:
"""Batch command source, if defined."""
if self._argv.c:
return cast(List[str], self._argv.c)
if self._argv.f:
return cast(str, self._argv.f)
return None

@property
def interactive(self) -> bool:
"""Is the interactive loop requested?"""
if not (self._argv.c or self._argv.f):
# no batch input, must be interactive
return True
return cast(bool, self._argv.interactive)


def _load_preference_file(path: str) -> None:
try:
Expand All @@ -118,7 +156,9 @@ def _load_theme_file(path: str) -> None:


def run() -> None:
"""Open a devicetree shell session and run its interactive loop."""
"""Open a devicetree shell session and run batch commands
and/or its interactive loop.
"""
argv = DTShCliArgv()

if argv.user_files:
Expand All @@ -136,14 +176,19 @@ def run() -> None:

session = None
try:
session = DTShRichSession.create(argv.dts, argv.binding_dirs)
if argv.batch_source:
session = DTShRichSession.create_batch(
argv.dts, argv.binding_dirs, argv.batch_source, argv.interactive
)
else:
session = DTShRichSession.create(argv.dts, argv.binding_dirs)
except DTShError as e:
print(e.msg, file=sys.stderr)
print("Failed to initialize devicetree", file=sys.stderr)
sys.exit(-22)

if session:
session.run()
session.run(argv.interactive)


if __name__ == "__main__":
Expand Down
27 changes: 26 additions & 1 deletion src/dtsh/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
Unit tests and examples: tests/test_dtsh_io.py
"""

from typing import Any, IO, Tuple, Optional, Sequence
from typing import Any, IO, Tuple, Optional, List, Sequence

import os
import sys
Expand Down Expand Up @@ -321,3 +321,28 @@ def _is_cmdline(self, line: str) -> bool:
# A non-empty line should be a command line if not only spaces,
# and not a comment.
return not (line.isspace() or line.startswith("#"))


class DTShInputCmds(DTShInput):
"""Command-line argument source.

Returns commands as they are provided on the tool command line.
"""

_cmds: List[str]

def __init__(self, cmds: List[str]) -> None:
"""Initialize object.

Args:
cmds: List of commands to execute.
"""
self._cmds = cmds

def readline(self, multi_prompt: Optional[Sequence[Any]] = None) -> str:
"""Overrides DTShInput.readline()."""
if self._cmds:
line: str = self._cmds.pop(0)
return line

raise EOFError()
10 changes: 6 additions & 4 deletions src/dtsh/rich/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
DTShCommandError,
)
from dtsh.session import DTShSession
from dtsh.io import DTShInput, DTShOutput, DTShRedirect, DTShVT, DTShInputFile
from dtsh.io import DTShInput, DTShOutput, DTShRedirect, DTShVT, DTShInputFile, DTShInputCmds

from dtsh.rich.io import (
DTShRichVT,
Expand All @@ -49,7 +49,7 @@ def create_batch(
cls,
dts_path: str,
binding_dirs: Optional[List[str]],
batch: Union[str, Sequence[str]],
batch: Union[str, List[str]],
interactive: bool,
) -> DTShSession:
"""Create batch session.
Expand Down Expand Up @@ -79,9 +79,11 @@ def create_batch(
batch_is = DTShInputFile(batch)
except DTShInputFile.Error as e:
raise DTShError(str(e)) from e
elif isinstance(batch, List):
# Batch commands from cli arguments.
batch_is = DTShInputCmds(batch)
else:
# Batch commands.
raise NotImplementedError("create_batch(CMD)")
raise NotImplementedError(f"create_batch({repr(type(batch))})")

dt = cls._create_dtmodel(dts_path, binding_dirs)
vt = DTShBatchRichVT(batch_is, interactive)
Expand Down