From fb5c8562f5e4fe76538b7ead206afe5349e2aea5 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Fri, 12 May 2023 12:20:48 -0700 Subject: [PATCH 1/3] pylint --- adafruit_prompt_toolkit/__init__.py | 83 ++++++++++++++------------- adafruit_prompt_toolkit/history.py | 11 ++++ examples/prompt_toolkit_second_cdc.py | 3 +- 3 files changed, 57 insertions(+), 40 deletions(-) diff --git a/adafruit_prompt_toolkit/__init__.py b/adafruit_prompt_toolkit/__init__.py index 6d08396..4d4c8ab 100644 --- a/adafruit_prompt_toolkit/__init__.py +++ b/adafruit_prompt_toolkit/__init__.py @@ -1,4 +1,3 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries # SPDX-FileCopyrightText: Copyright (c) 2023 Scott Shawcroft for Adafruit Industries # # SPDX-License-Identifier: MIT @@ -8,35 +7,27 @@ Slimmed down implementation of prompt_toolkit for CircuitPython - -* Author(s): Scott Shawcroft - -Implementation Notes --------------------- - """ -# imports +from .history import InMemoryHistory __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Prompt_Toolkit.git" -from .history import InMemoryHistory - -def _prompt(message="", *, input=None, output=None, history=None): +def _prompt(message="", *, input_=None, output=None, history=None): + # pylint: disable=too-many-nested-blocks,too-many-branches,too-many-statements output.write(message.encode("utf-8")) commands = [] control_command = [] offset = 0 selected_history_entry = None while not commands or commands[-1] != ord("\r"): - c = input.read(1) - print(c, c[0]) + char = input_.read(1) + print(char, char[0]) if control_command: - control_command.append(c[0]) - print("control", control_command) + control_command.append(char[0]) # Check for unsupported codes. If one is found, add the second character to the # plain command and ignore the escape. if len(control_command) == 2 and not control_command[-1] == ord(b"["): @@ -47,8 +38,8 @@ def _prompt(message="", *, input=None, output=None, history=None): ord("0") <= control_command[-1] <= ord("9") ): echo = False - cc = control_command[-1] - if cc == ord("A") or cc == ord("B"): + control_char = control_command[-1] + if control_char == ord("A") or control_char == ord("B"): if history is None: control_command = [] output.write(b"\a") @@ -58,7 +49,7 @@ def _prompt(message="", *, input=None, output=None, history=None): control_command = [] output.write(b"\a") continue - if cc == ord("A"): + if control_char == ord("A"): # up if selected_history_entry is None: selected_history_entry = 1 @@ -69,49 +60,50 @@ def _prompt(message="", *, input=None, output=None, history=None): selected_history_entry = len(strings) else: # down - print("down") if selected_history_entry is None: output.write(b"\a") else: selected_history_entry -= 1 if selected_history_entry < 1: selected_history_entry = None - # Move the cursor left as much as our current command - for _ in commands: - output.write(b"\b") - # Set and print the new command - commands = list(strings[-selected_history_entry].encode("utf-8")) - output.write(bytes(commands)) - # Clear the rest of the line - output.write(b"\x1b[K") - elif cc == ord("C"): + if selected_history_entry is not None: + # Move the cursor left as much as our current command + for _ in commands: + output.write(b"\b") + # Set and print the new command + commands = list( + strings[-selected_history_entry].encode("utf-8") + ) + output.write(bytes(commands)) + # Clear the rest of the line + output.write(b"\x1b[K") + elif control_char == ord("C"): echo = True offset = max(0, offset - 1) - elif cc == ord("D"): + elif control_char == ord("D"): echo = True offset += 1 if echo: b = bytes(control_command) - print("echo", b) output.write(b) control_command = [] continue - elif c == b"\x1b": - control_command.append(c[0]) + if char == b"\x1b": + control_command.append(char[0]) continue - elif offset == 0 or c == b"\r": - commands.append(c[0]) + if offset == 0 or char == b"\r": + commands.append(char[0]) else: - commands[-offset] = c[0] + commands[-offset] = char[0] offset -= 1 - if c[-1] == 127: + if char[-1] == 127: commands.pop() commands.pop() output.write(b"\b\x1b[K") else: - output.write(c) + output.write(char) print(commands, not commands) output.write(b"\n") @@ -125,11 +117,22 @@ def _prompt(message="", *, input=None, output=None, history=None): def prompt(message="", *, input=None, output=None): - return _prompt(message, input=input, output=output) + """Prompt the user for input over the ``input`` stream with the given + ``message`` output on ``output``. Handles control characters for value editing.""" + # "input" and "output" are only on PromptSession in upstream "prompt_toolkit" but we use it for + # prompts without history. + # pylint: disable=redefined-builtin + return _prompt(message, input_=input, output=output) class PromptSession: + """Session for multiple prompts. Stores common arguments to `prompt()` and + history of commands for user selection.""" + def __init__(self, message="", *, input=None, output=None, history=None): + # "input" and "output" are names used in upstream "prompt_toolkit" so we + # use them too. + # pylint: disable=redefined-builtin self.message = message self._input = input self._output = output @@ -137,10 +140,12 @@ def __init__(self, message="", *, input=None, output=None, history=None): self.history = history if history else InMemoryHistory() def prompt(self, message=None) -> str: + """Prompt the user for input over the session's ``input`` with the given + message or the default message.""" message = message if message else self.message decoded = _prompt( - message, input=self._input, output=self._output, history=self.history + message, input_=self._input, output=self._output, history=self.history ) return decoded diff --git a/adafruit_prompt_toolkit/history.py b/adafruit_prompt_toolkit/history.py index 847045c..7aef963 100644 --- a/adafruit_prompt_toolkit/history.py +++ b/adafruit_prompt_toolkit/history.py @@ -1,9 +1,20 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 Scott Shawcroft for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +"""Various ways of storing command history.""" + + class InMemoryHistory: + """Simple in-memory history of commands. It is infinite size.""" + def __init__(self): self._history = [] def append_string(self, string: str) -> None: + """Append a string to the history of commands.""" self._history.append(string) def get_strings(self) -> list[str]: + """List of all past strings. Oldest first.""" return self._history diff --git a/examples/prompt_toolkit_second_cdc.py b/examples/prompt_toolkit_second_cdc.py index df330ce..18bab93 100644 --- a/examples/prompt_toolkit_second_cdc.py +++ b/examples/prompt_toolkit_second_cdc.py @@ -5,9 +5,10 @@ # This example works over the second CDC and supports history. +import usb_cdc + # Rename import to make the rest of the code compatible with CPython's prompt_toolkit library. import adafruit_prompt_toolkit as prompt_toolkit -import usb_cdc # If the second CDC is available, then use it instead. serial = usb_cdc.console From eb23a17436f4231e7727572ef3a8b3de86993601 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Fri, 12 May 2023 12:28:01 -0700 Subject: [PATCH 2/3] Doc update --- README.rst | 57 ++++++++++++++++++++--------------------------- docs/examples.rst | 23 ++++++++++++++++++- docs/index.rst | 6 ----- 3 files changed, 46 insertions(+), 40 deletions(-) diff --git a/README.rst b/README.rst index 11b4e63..cab4901 100644 --- a/README.rst +++ b/README.rst @@ -21,7 +21,7 @@ Introduction :target: https://github.com/psf/black :alt: Code Style: Black -Slimmed down implementation of prompt_toolkit for CircuitPython +Slimmed down implementation of `prompt_toolkit `_ for CircuitPython Dependencies @@ -36,39 +36,13 @@ This is easily achieved by downloading or individual libraries can be installed using `circup `_. -.. todo:: Describe the Adafruit product this library works with. For PCBs, you can also add the -image from the assets folder in the PCB's GitHub repo. - -`Purchase one from the Adafruit shop `_ Installing from PyPI ===================== -.. note:: This library is not available on PyPI yet. Install documentation is included - as a standard element. Stay tuned for PyPI availability! - -.. todo:: Remove the above note if PyPI version is/will be available at time of release. - -On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from -PyPI `_. -To install for current user: - -.. code-block:: shell - - pip3 install adafruit-circuitpython-prompt-toolkit - -To install system-wide (this may be required in some cases): - -.. code-block:: shell - - sudo pip3 install adafruit-circuitpython-prompt-toolkit -To install in a virtual environment in your current project: - -.. code-block:: shell - - mkdir project-name && cd project-name - python3 -m venv .venv - source .env/bin/activate - pip3 install adafruit-circuitpython-prompt-toolkit +This library is available in PyPI for CircuitPython tools that need it. If you +actually want to use it on CPython (not CircuitPython), then we recommend the +full `prompt_toolkit `_ +library that is also on PyPI. Installing to a Connected CircuitPython Device with Circup ========================================================== @@ -96,8 +70,25 @@ Or the following command to update an existing version: Usage Example ============= -.. todo:: Add a quick, simple example. It and other examples should live in the -examples folder and be included in docs/examples.rst. +.. code-block:: python + + # This example works over the second CDC and supports history. + + import usb_cdc + + # Rename import to make the rest of the code compatible with CPython's prompt_toolkit library. + import adafruit_prompt_toolkit as prompt_toolkit + + # If the second CDC is available, then use it instead. + serial = usb_cdc.console + if usb_cdc.data: + serial = usb_cdc.data + + session = prompt_toolkit.PromptSession(input=serial, output=serial) + + while True: + response = prompt_toolkit.prompt("$ ") + print("->", response, file=serial) Documentation ============= diff --git a/docs/examples.rst b/docs/examples.rst index 7cda7f6..a3a7715 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -1,8 +1,29 @@ Simple test ------------ -Ensure your device works with this simple test. +Ensure the library works with this simple test. .. literalinclude:: ../examples/prompt_toolkit_simpletest.py :caption: examples/prompt_toolkit_simpletest.py :linenos: + +Second USB +---------- + +Use the library over a second USB CDC serial connection. `boot.py` must include +`usb_cdc.enable(console=True, data=True)`. `console` can be `False`. + +Example "boot.py": + +.. code-block:: python + + import usb_cdc + + # Enable console and data + usb_cdc.enable(console=True, data=True) + +Example "code.py": + +.. literalinclude:: ../examples/prompt_toolkit_second_cdc.py + :caption: examples/prompt_toolkit_second_cdc.py + :linenos: diff --git a/docs/index.rst b/docs/index.rst index a3b0dc8..f69f676 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,15 +24,9 @@ Table of Contents .. toctree:: :caption: Tutorials -.. todo:: Add any Learn guide links here. If there are none, then simply delete this todo and leave - the toctree above for use later. - .. toctree:: :caption: Related Products -.. todo:: Add any product links here. If there are none, then simply delete this todo and leave - the toctree above for use later. - .. toctree:: :caption: Other Links From a0b634f1cfdae41e3a425deb0d94b33861fcf927 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Fri, 12 May 2023 12:36:27 -0700 Subject: [PATCH 3/3] Double quote --- docs/examples.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/examples.rst b/docs/examples.rst index a3a7715..7ce18f3 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -10,10 +10,10 @@ Ensure the library works with this simple test. Second USB ---------- -Use the library over a second USB CDC serial connection. `boot.py` must include -`usb_cdc.enable(console=True, data=True)`. `console` can be `False`. +Use the library over a second USB CDC serial connection. ``boot.py`` must include +``usb_cdc.enable(console=True, data=True)``. ``console`` can be `False`. -Example "boot.py": +Example ``boot.py``: .. code-block:: python @@ -22,7 +22,7 @@ Example "boot.py": # Enable console and data usb_cdc.enable(console=True, data=True) -Example "code.py": +Example ``code.py``: .. literalinclude:: ../examples/prompt_toolkit_second_cdc.py :caption: examples/prompt_toolkit_second_cdc.py