All built-in Moler commands follow the same construction pattern, which makes creating new ones straightforward. This guide walks through the pattern using the actual source code ofDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/nokia/moler/llms.txt
Use this file to discover all available pages before exploring further.
ping.py and ps.py as reference, then shows a complete minimal example.
The command class hierarchy
GenericUnixCommand. For non-Unix textual commands, subclass CommandTextualGeneric directly.
Key methods to implement
Every command must implement two methods:| Method | Purpose |
|---|---|
build_command_string() | Returns the shell command string to send to the device |
on_new_line(line, is_full_line) | Called for each line of output; put your parsing logic here |
| Method | When called |
|---|---|
on_done() | Just before the command finishes (success or failure) |
on_success() | Just before the command finishes successfully |
on_failure() | Just before the command finishes with a failure |
How build_command_string() works
CommandTextualGeneric has a command_string property backed by build_command_string(). The property is called lazily — the first time the command string is needed. Here is the pattern from ping.py:
ps.py:
How on_new_line() works
on_new_line(line, is_full_line) is called by the framework for each line of output received after the command echo. The line argument has newline characters stripped. is_full_line is True if the line ended with a newline character (i.e., it is a complete line), False if it is a partial line (still being received).
The base class on_new_line() in CommandTextualGeneric checks whether the line matches the expected shell prompt. If it does, the command is marked as done and self.current_ret is stored as the result. You must call super().on_new_line(line, is_full_line) at the end of your override so the prompt detection still happens.
The ParsingDone exception is the standard early-exit mechanism — raise it inside a parse helper when a line has been fully handled to skip remaining parse methods:
_parse_* helper checks a regex against the line. If the regex matches, it populates self.current_ret with extracted values and raises ParsingDone.
The current_ret pattern
self.current_ret is a dict (or list, for commands like ps) that accumulates parsed data as output lines arrive. When the command detects the prompt and marks itself as done, the framework calls self.set_result(self.current_ret) which stores the value and makes it available via command() or command.await_done().
A complete minimal example
Here is a custom command foruptime that extracts the system load averages:
self._regex_helper is provided by CommandTextualGeneric. It wraps re search operations and stores the last match object, so you can call self._regex_helper.group('name') without re-running the regex.Using the command with a device
- Via device (get_cmd)
- Direct instantiation
If the command file is in a package that the device scans (e.g.,
moler/cmd/unix/), you can retrieve it by name:Adding failure detection
GenericUnixCommand already sets self.re_fail to a pattern matching common Unix error messages (not found, No such file or directory, etc.). If your command has additional failure indicators, add them:
re_fail, the command raises CommandFailure and marks itself as done with an exception.
Handling commands that produce no result
Some commands (liketouch or rm) produce no parseable output. Set self.ret_required = False so the command does not wait for a non-empty current_ret before completing:
Real source reference
The patterns above come directly from these source files:moler/cmd/unix/ping.py— regex parsing with multiple parse helpers andParsingDonemoler/cmd/unix/ps.py— list result (self.current_ret = []), dynamic header parsingmoler/cmd/unix/ls.py— structured dict result with helper methods (get_files(),get_dirs())moler/cmd/commandtextualgeneric.py— base class withon_new_line,build_command_string, lifecycle callbacks