Skip to content

Commit 824a589

Browse files
authored
Merge pull request #1 from tannewt/pylint
Polish things up
2 parents 4da4e5a + a0b634f commit 824a589

File tree

6 files changed

+103
-80
lines changed

6 files changed

+103
-80
lines changed

README.rst

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Introduction
2121
:target: https://github.com/psf/black
2222
:alt: Code Style: Black
2323

24-
Slimmed down implementation of prompt_toolkit for CircuitPython
24+
Slimmed down implementation of `prompt_toolkit <https://github.com/prompt-toolkit/python-prompt-toolkit>`_ for CircuitPython
2525

2626

2727
Dependencies
@@ -36,39 +36,13 @@ This is easily achieved by downloading
3636
or individual libraries can be installed using
3737
`circup <https://github.com/adafruit/circup>`_.
3838

39-
.. todo:: Describe the Adafruit product this library works with. For PCBs, you can also add the
40-
image from the assets folder in the PCB's GitHub repo.
41-
42-
`Purchase one from the Adafruit shop <http://www.adafruit.com/products/>`_
4339
Installing from PyPI
4440
=====================
45-
.. note:: This library is not available on PyPI yet. Install documentation is included
46-
as a standard element. Stay tuned for PyPI availability!
47-
48-
.. todo:: Remove the above note if PyPI version is/will be available at time of release.
49-
50-
On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from
51-
PyPI <https://pypi.org/project/adafruit-circuitpython-prompt-toolkit/>`_.
52-
To install for current user:
53-
54-
.. code-block:: shell
55-
56-
pip3 install adafruit-circuitpython-prompt-toolkit
57-
58-
To install system-wide (this may be required in some cases):
59-
60-
.. code-block:: shell
61-
62-
sudo pip3 install adafruit-circuitpython-prompt-toolkit
6341

64-
To install in a virtual environment in your current project:
65-
66-
.. code-block:: shell
67-
68-
mkdir project-name && cd project-name
69-
python3 -m venv .venv
70-
source .env/bin/activate
71-
pip3 install adafruit-circuitpython-prompt-toolkit
42+
This library is available in PyPI for CircuitPython tools that need it. If you
43+
actually want to use it on CPython (not CircuitPython), then we recommend the
44+
full `prompt_toolkit <https://github.com/prompt-toolkit/python-prompt-toolkit>`_
45+
library that is also on PyPI.
7246

7347
Installing to a Connected CircuitPython Device with Circup
7448
==========================================================
@@ -96,8 +70,25 @@ Or the following command to update an existing version:
9670
Usage Example
9771
=============
9872

99-
.. todo:: Add a quick, simple example. It and other examples should live in the
100-
examples folder and be included in docs/examples.rst.
73+
.. code-block:: python
74+
75+
# This example works over the second CDC and supports history.
76+
77+
import usb_cdc
78+
79+
# Rename import to make the rest of the code compatible with CPython's prompt_toolkit library.
80+
import adafruit_prompt_toolkit as prompt_toolkit
81+
82+
# If the second CDC is available, then use it instead.
83+
serial = usb_cdc.console
84+
if usb_cdc.data:
85+
serial = usb_cdc.data
86+
87+
session = prompt_toolkit.PromptSession(input=serial, output=serial)
88+
89+
while True:
90+
response = prompt_toolkit.prompt("$ ")
91+
print("->", response, file=serial)
10192
10293
Documentation
10394
=============

adafruit_prompt_toolkit/__init__.py

Lines changed: 44 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
21
# SPDX-FileCopyrightText: Copyright (c) 2023 Scott Shawcroft for Adafruit Industries
32
#
43
# SPDX-License-Identifier: MIT
@@ -8,35 +7,27 @@
87
98
Slimmed down implementation of prompt_toolkit for CircuitPython
109
11-
12-
* Author(s): Scott Shawcroft
13-
14-
Implementation Notes
15-
--------------------
16-
1710
"""
1811

19-
# imports
12+
from .history import InMemoryHistory
2013

2114
__version__ = "0.0.0+auto.0"
2215
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Prompt_Toolkit.git"
2316

24-
from .history import InMemoryHistory
25-
2617

27-
def _prompt(message="", *, input=None, output=None, history=None):
18+
def _prompt(message="", *, input_=None, output=None, history=None):
19+
# pylint: disable=too-many-nested-blocks,too-many-branches,too-many-statements
2820
output.write(message.encode("utf-8"))
2921
commands = []
3022
control_command = []
3123
offset = 0
3224
selected_history_entry = None
3325
while not commands or commands[-1] != ord("\r"):
34-
c = input.read(1)
35-
print(c, c[0])
26+
char = input_.read(1)
27+
print(char, char[0])
3628

3729
if control_command:
38-
control_command.append(c[0])
39-
print("control", control_command)
30+
control_command.append(char[0])
4031
# Check for unsupported codes. If one is found, add the second character to the
4132
# plain command and ignore the escape.
4233
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):
4738
ord("0") <= control_command[-1] <= ord("9")
4839
):
4940
echo = False
50-
cc = control_command[-1]
51-
if cc == ord("A") or cc == ord("B"):
41+
control_char = control_command[-1]
42+
if control_char == ord("A") or control_char == ord("B"):
5243
if history is None:
5344
control_command = []
5445
output.write(b"\a")
@@ -58,7 +49,7 @@ def _prompt(message="", *, input=None, output=None, history=None):
5849
control_command = []
5950
output.write(b"\a")
6051
continue
61-
if cc == ord("A"):
52+
if control_char == ord("A"):
6253
# up
6354
if selected_history_entry is None:
6455
selected_history_entry = 1
@@ -69,49 +60,50 @@ def _prompt(message="", *, input=None, output=None, history=None):
6960
selected_history_entry = len(strings)
7061
else:
7162
# down
72-
print("down")
7363
if selected_history_entry is None:
7464
output.write(b"\a")
7565
else:
7666
selected_history_entry -= 1
7767
if selected_history_entry < 1:
7868
selected_history_entry = None
79-
# Move the cursor left as much as our current command
80-
for _ in commands:
81-
output.write(b"\b")
82-
# Set and print the new command
83-
commands = list(strings[-selected_history_entry].encode("utf-8"))
84-
output.write(bytes(commands))
85-
# Clear the rest of the line
86-
output.write(b"\x1b[K")
87-
elif cc == ord("C"):
69+
if selected_history_entry is not None:
70+
# Move the cursor left as much as our current command
71+
for _ in commands:
72+
output.write(b"\b")
73+
# Set and print the new command
74+
commands = list(
75+
strings[-selected_history_entry].encode("utf-8")
76+
)
77+
output.write(bytes(commands))
78+
# Clear the rest of the line
79+
output.write(b"\x1b[K")
80+
elif control_char == ord("C"):
8881
echo = True
8982
offset = max(0, offset - 1)
90-
elif cc == ord("D"):
83+
elif control_char == ord("D"):
9184
echo = True
9285
offset += 1
9386

9487
if echo:
9588
b = bytes(control_command)
96-
print("echo", b)
9789
output.write(b)
9890
control_command = []
9991
continue
100-
elif c == b"\x1b":
101-
control_command.append(c[0])
92+
if char == b"\x1b":
93+
control_command.append(char[0])
10294
continue
103-
elif offset == 0 or c == b"\r":
104-
commands.append(c[0])
95+
if offset == 0 or char == b"\r":
96+
commands.append(char[0])
10597
else:
106-
commands[-offset] = c[0]
98+
commands[-offset] = char[0]
10799
offset -= 1
108100

109-
if c[-1] == 127:
101+
if char[-1] == 127:
110102
commands.pop()
111103
commands.pop()
112104
output.write(b"\b\x1b[K")
113105
else:
114-
output.write(c)
106+
output.write(char)
115107

116108
print(commands, not commands)
117109
output.write(b"\n")
@@ -125,22 +117,35 @@ def _prompt(message="", *, input=None, output=None, history=None):
125117

126118

127119
def prompt(message="", *, input=None, output=None):
128-
return _prompt(message, input=input, output=output)
120+
"""Prompt the user for input over the ``input`` stream with the given
121+
``message`` output on ``output``. Handles control characters for value editing."""
122+
# "input" and "output" are only on PromptSession in upstream "prompt_toolkit" but we use it for
123+
# prompts without history.
124+
# pylint: disable=redefined-builtin
125+
return _prompt(message, input_=input, output=output)
129126

130127

131128
class PromptSession:
129+
"""Session for multiple prompts. Stores common arguments to `prompt()` and
130+
history of commands for user selection."""
131+
132132
def __init__(self, message="", *, input=None, output=None, history=None):
133+
# "input" and "output" are names used in upstream "prompt_toolkit" so we
134+
# use them too.
135+
# pylint: disable=redefined-builtin
133136
self.message = message
134137
self._input = input
135138
self._output = output
136139

137140
self.history = history if history else InMemoryHistory()
138141

139142
def prompt(self, message=None) -> str:
143+
"""Prompt the user for input over the session's ``input`` with the given
144+
message or the default message."""
140145
message = message if message else self.message
141146

142147
decoded = _prompt(
143-
message, input=self._input, output=self._output, history=self.history
148+
message, input_=self._input, output=self._output, history=self.history
144149
)
145150

146151
return decoded

adafruit_prompt_toolkit/history.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2023 Scott Shawcroft for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
"""Various ways of storing command history."""
6+
7+
18
class InMemoryHistory:
9+
"""Simple in-memory history of commands. It is infinite size."""
10+
211
def __init__(self):
312
self._history = []
413

514
def append_string(self, string: str) -> None:
15+
"""Append a string to the history of commands."""
616
self._history.append(string)
717

818
def get_strings(self) -> list[str]:
19+
"""List of all past strings. Oldest first."""
920
return self._history

docs/examples.rst

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,29 @@
11
Simple test
22
------------
33

4-
Ensure your device works with this simple test.
4+
Ensure the library works with this simple test.
55

66
.. literalinclude:: ../examples/prompt_toolkit_simpletest.py
77
:caption: examples/prompt_toolkit_simpletest.py
88
:linenos:
9+
10+
Second USB
11+
----------
12+
13+
Use the library over a second USB CDC serial connection. ``boot.py`` must include
14+
``usb_cdc.enable(console=True, data=True)``. ``console`` can be `False`.
15+
16+
Example ``boot.py``:
17+
18+
.. code-block:: python
19+
20+
import usb_cdc
21+
22+
# Enable console and data
23+
usb_cdc.enable(console=True, data=True)
24+
25+
Example ``code.py``:
26+
27+
.. literalinclude:: ../examples/prompt_toolkit_second_cdc.py
28+
:caption: examples/prompt_toolkit_second_cdc.py
29+
:linenos:

docs/index.rst

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,9 @@ Table of Contents
2424
.. toctree::
2525
:caption: Tutorials
2626

27-
.. todo:: Add any Learn guide links here. If there are none, then simply delete this todo and leave
28-
the toctree above for use later.
29-
3027
.. toctree::
3128
:caption: Related Products
3229

33-
.. todo:: Add any product links here. If there are none, then simply delete this todo and leave
34-
the toctree above for use later.
35-
3630
.. toctree::
3731
:caption: Other Links
3832

examples/prompt_toolkit_second_cdc.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55

66
# This example works over the second CDC and supports history.
77

8+
import usb_cdc
9+
810
# Rename import to make the rest of the code compatible with CPython's prompt_toolkit library.
911
import adafruit_prompt_toolkit as prompt_toolkit
10-
import usb_cdc
1112

1213
# If the second CDC is available, then use it instead.
1314
serial = usb_cdc.console

0 commit comments

Comments
 (0)