Skip to content

Polish things up #1

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

Merged
merged 3 commits into from
May 22, 2023
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
57 changes: 24 additions & 33 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://github.com/prompt-toolkit/python-prompt-toolkit>`_ for CircuitPython


Dependencies
Expand All @@ -36,39 +36,13 @@ This is easily achieved by downloading
or individual libraries can be installed using
`circup <https://github.com/adafruit/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 <http://www.adafruit.com/products/>`_
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 <https://pypi.org/project/adafruit-circuitpython-prompt-toolkit/>`_.
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 <https://github.com/prompt-toolkit/python-prompt-toolkit>`_
library that is also on PyPI.

Installing to a Connected CircuitPython Device with Circup
==========================================================
Expand Down Expand Up @@ -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
=============
Expand Down
83 changes: 44 additions & 39 deletions adafruit_prompt_toolkit/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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"["):
Expand All @@ -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")
Expand All @@ -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
Expand All @@ -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")
Expand All @@ -125,22 +117,35 @@ 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

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
11 changes: 11 additions & 0 deletions adafruit_prompt_toolkit/history.py
Original file line number Diff line number Diff line change
@@ -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
23 changes: 22 additions & 1 deletion docs/examples.rst
Original file line number Diff line number Diff line change
@@ -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:
6 changes: 0 additions & 6 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion examples/prompt_toolkit_second_cdc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down