circup 2.0.3__tar.gz → 2.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {circup-2.0.3 → circup-2.1.0}/.pre-commit-config.yaml +1 -1
- {circup-2.0.3 → circup-2.1.0}/.pylintrc +1 -1
- {circup-2.0.3 → circup-2.1.0}/CONTRIBUTING.rst +3 -3
- {circup-2.0.3/circup.egg-info → circup-2.1.0}/PKG-INFO +7 -6
- {circup-2.0.3 → circup-2.1.0}/README.rst +6 -5
- {circup-2.0.3 → circup-2.1.0}/circup/__init__.py +1 -1
- {circup-2.0.3 → circup-2.1.0}/circup/backends.py +87 -18
- {circup-2.0.3 → circup-2.1.0}/circup/command_utils.py +28 -2
- {circup-2.0.3 → circup-2.1.0}/circup/commands.py +8 -4
- {circup-2.0.3 → circup-2.1.0}/circup/shared.py +9 -6
- circup-2.1.0/circup/wwshell/README.rst +105 -0
- circup-2.1.0/circup/wwshell/README.rst.license +3 -0
- circup-2.1.0/circup/wwshell/__init__.py +14 -0
- circup-2.1.0/circup/wwshell/commands.py +231 -0
- {circup-2.0.3 → circup-2.1.0/circup.egg-info}/PKG-INFO +7 -6
- {circup-2.0.3 → circup-2.1.0}/circup.egg-info/SOURCES.txt +4 -0
- {circup-2.0.3 → circup-2.1.0}/circup.egg-info/entry_points.txt +1 -0
- {circup-2.0.3 → circup-2.1.0}/docs/conf.py +0 -1
- {circup-2.0.3 → circup-2.1.0}/docs/index.rst +3 -1
- {circup-2.0.3 → circup-2.1.0}/pyproject.toml +1 -0
- {circup-2.0.3 → circup-2.1.0}/.github/ISSUE_TEMPLATE.md +0 -0
- {circup-2.0.3 → circup-2.1.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {circup-2.0.3 → circup-2.1.0}/.github/workflows/build.yml +0 -0
- {circup-2.0.3 → circup-2.1.0}/.github/workflows/release.yml +0 -0
- {circup-2.0.3 → circup-2.1.0}/.gitignore +0 -0
- {circup-2.0.3 → circup-2.1.0}/.isort.cfg +0 -0
- {circup-2.0.3 → circup-2.1.0}/CODE_OF_CONDUCT.rst +0 -0
- {circup-2.0.3 → circup-2.1.0}/CODE_OF_CONDUCT.rst.license +0 -0
- {circup-2.0.3 → circup-2.1.0}/CONTRIBUTING.rst.license +0 -0
- {circup-2.0.3 → circup-2.1.0}/LICENSE +0 -0
- {circup-2.0.3 → circup-2.1.0}/LICENSES/CC-BY-4.0.txt +0 -0
- {circup-2.0.3 → circup-2.1.0}/LICENSES/MIT.txt +0 -0
- {circup-2.0.3 → circup-2.1.0}/LICENSES/Unlicense.txt +0 -0
- {circup-2.0.3 → circup-2.1.0}/README.rst.license +0 -0
- {circup-2.0.3 → circup-2.1.0}/circup/bundle.py +0 -0
- {circup-2.0.3 → circup-2.1.0}/circup/config/bundle_config.json +0 -0
- {circup-2.0.3 → circup-2.1.0}/circup/config/bundle_config.json.license +0 -0
- {circup-2.0.3 → circup-2.1.0}/circup/logging.py +0 -0
- {circup-2.0.3 → circup-2.1.0}/circup/module.py +0 -0
- {circup-2.0.3 → circup-2.1.0}/circup.egg-info/dependency_links.txt +0 -0
- {circup-2.0.3 → circup-2.1.0}/circup.egg-info/requires.txt +0 -0
- {circup-2.0.3 → circup-2.1.0}/circup.egg-info/top_level.txt +0 -0
- {circup-2.0.3 → circup-2.1.0}/docs/_static/favicon.ico +0 -0
- {circup-2.0.3 → circup-2.1.0}/docs/_static/favicon.ico.license +0 -0
- {circup-2.0.3 → circup-2.1.0}/docs/index.rst.license +0 -0
- {circup-2.0.3 → circup-2.1.0}/docs/logo.png +0 -0
- {circup-2.0.3 → circup-2.1.0}/docs/logo.png.license +0 -0
- {circup-2.0.3 → circup-2.1.0}/optional_requirements.txt +0 -0
- {circup-2.0.3 → circup-2.1.0}/optional_requirements.txt.license +0 -0
- {circup-2.0.3 → circup-2.1.0}/readthedocs.yml +0 -0
- {circup-2.0.3 → circup-2.1.0}/requirements.txt +0 -0
- {circup-2.0.3 → circup-2.1.0}/requirements.txt.license +0 -0
- {circup-2.0.3 → circup-2.1.0}/setup.cfg +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/__init__.py +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/bad_module/__init__.py +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/bad_module/my_module.py +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/bad_python.py +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/bundle.json +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/bundle.json.license +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/device.json +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/device.json.license +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/dir_module/__init__.py +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/dir_module/my_module.py +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/import_styles.py +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/local_module.py +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/local_module_cp7.mpy +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/local_module_cp7.mpy.license +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/mock_device/boot_out.txt +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/mock_device/boot_out.txt.license +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/mock_device/lib/adafruit_waveform/.gitkeep +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/mount_exists.txt +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/mount_exists.txt.license +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/mount_missing.txt +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/mount_missing.txt.license +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/remote_module.py +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/test_bundle_config.json +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/test_bundle_config.json.license +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/test_bundle_config_local.json +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/test_bundle_config_local.json.license +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/test_circup.py +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/test_module.mpy +0 -0
- {circup-2.0.3 → circup-2.1.0}/tests/test_module.mpy.license +0 -0
|
@@ -25,7 +25,7 @@ Developer Setup
|
|
|
25
25
|
|
|
26
26
|
.. note::
|
|
27
27
|
|
|
28
|
-
Please try to use Python 3.9+ while developing
|
|
28
|
+
Please try to use Python 3.9+ while developing Circup. This is so we can
|
|
29
29
|
use the
|
|
30
30
|
`Black code formatter <https://black.readthedocs.io/en/stable/index.html>`_
|
|
31
31
|
and so that we're supporting versions which still receive security updates.
|
|
@@ -100,7 +100,7 @@ subsequently used to facilitate the various commands the tool makes available.
|
|
|
100
100
|
|
|
101
101
|
These commands are defined at the very end of the ``circup.py`` code.
|
|
102
102
|
|
|
103
|
-
Unit tests can be found in the ``tests`` directory.
|
|
103
|
+
Unit tests can be found in the ``tests`` directory. Circup uses
|
|
104
104
|
`pytest <http://www.pytest.org/en/latest/>`_ style testing conventions. Test
|
|
105
105
|
functions should include a comment to describe its *intention*. We currently
|
|
106
106
|
have 100% unit test coverage for all the core functionality (excluding
|
|
@@ -124,7 +124,7 @@ available options to help you work with the code base.
|
|
|
124
124
|
Before submitting a PR, please remember to ``pre-commit run --all-files``.
|
|
125
125
|
But if you forget the CI process in Github will run it for you. ;-)
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
Circup uses the `Click <https://click.palletsprojects.com>`_ module to
|
|
128
128
|
run command-line interaction. The
|
|
129
129
|
`AppDirs <https://pypi.org/project/appdirs/>`_ module is used to determine
|
|
130
130
|
where to store user-specific assets created by the tool in such a way that
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: circup
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: A tool to manage/update libraries on CircuitPython devices.
|
|
5
5
|
Author-email: Adafruit Industries <circuitpython@adafruit.com>
|
|
6
6
|
License: MIT License
|
|
@@ -59,7 +59,7 @@ Requires-Dist: pytest-faulthandler; extra == "optional"
|
|
|
59
59
|
Requires-Dist: pytest-random-order; extra == "optional"
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
Circup
|
|
63
63
|
======
|
|
64
64
|
|
|
65
65
|
.. image:: https://readthedocs.org/projects/circup/badge/?version=latest
|
|
@@ -88,7 +88,7 @@ A tool to manage and update libraries (modules) on a CircuitPython device.
|
|
|
88
88
|
Installation
|
|
89
89
|
------------
|
|
90
90
|
|
|
91
|
-
Circup requires Python 3.
|
|
91
|
+
Circup requires Python 3.9 or higher.
|
|
92
92
|
|
|
93
93
|
In a `virtualenv <https://virtualenv.pypa.io/en/latest/>`_,
|
|
94
94
|
``pip install circup`` should do the trick. This is the simplest way to make it
|
|
@@ -99,7 +99,7 @@ If you have no idea what a virtualenv is, try the following command,
|
|
|
99
99
|
|
|
100
100
|
.. note::
|
|
101
101
|
|
|
102
|
-
If you use the ``pip3`` command to install
|
|
102
|
+
If you use the ``pip3`` command to install Circup you must make sure that
|
|
103
103
|
your path contains the directory into which the script will be installed.
|
|
104
104
|
To discover this path,
|
|
105
105
|
|
|
@@ -136,7 +136,7 @@ Usage
|
|
|
136
136
|
-----
|
|
137
137
|
|
|
138
138
|
If you need more detailed help using Circup see the Learn Guide article
|
|
139
|
-
`"Use
|
|
139
|
+
`"Use Circup to easily keep your CircuitPython libraries up to date" <https://learn.adafruit.com/keep-your-circuitpython-libraries-on-devices-up-to-date-with-circup/>`_.
|
|
140
140
|
|
|
141
141
|
First, plug in a device running CircuiPython. This should appear as a mounted
|
|
142
142
|
storage device called ``CIRCUITPY``.
|
|
@@ -290,7 +290,7 @@ The ``--version`` flag will tell you the current version of the
|
|
|
290
290
|
``circup`` command itself::
|
|
291
291
|
|
|
292
292
|
$ circup --version
|
|
293
|
-
|
|
293
|
+
Circup, A CircuitPython module updater. Version 0.0.1
|
|
294
294
|
|
|
295
295
|
|
|
296
296
|
To use circup via the `Web Workflow <https://learn.adafruit.com/getting-started-with-web-workflow-using-the-code-editor>`_. on devices that support it. Use the ``--host`` and ``--password`` arguments before your circup command.::
|
|
@@ -323,6 +323,7 @@ For Bash, add this to ~/.bashrc::
|
|
|
323
323
|
|
|
324
324
|
For Zsh, add this to ~/.zshrc::
|
|
325
325
|
|
|
326
|
+
autoload -U compinit; compinit
|
|
326
327
|
eval "$(_CIRCUP_COMPLETE=zsh_source circup)"
|
|
327
328
|
|
|
328
329
|
For Fish, add this to ~/.config/fish/completions/foo-bar.fish::
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
|
|
2
|
+
Circup
|
|
3
3
|
======
|
|
4
4
|
|
|
5
5
|
.. image:: https://readthedocs.org/projects/circup/badge/?version=latest
|
|
@@ -28,7 +28,7 @@ A tool to manage and update libraries (modules) on a CircuitPython device.
|
|
|
28
28
|
Installation
|
|
29
29
|
------------
|
|
30
30
|
|
|
31
|
-
Circup requires Python 3.
|
|
31
|
+
Circup requires Python 3.9 or higher.
|
|
32
32
|
|
|
33
33
|
In a `virtualenv <https://virtualenv.pypa.io/en/latest/>`_,
|
|
34
34
|
``pip install circup`` should do the trick. This is the simplest way to make it
|
|
@@ -39,7 +39,7 @@ If you have no idea what a virtualenv is, try the following command,
|
|
|
39
39
|
|
|
40
40
|
.. note::
|
|
41
41
|
|
|
42
|
-
If you use the ``pip3`` command to install
|
|
42
|
+
If you use the ``pip3`` command to install Circup you must make sure that
|
|
43
43
|
your path contains the directory into which the script will be installed.
|
|
44
44
|
To discover this path,
|
|
45
45
|
|
|
@@ -76,7 +76,7 @@ Usage
|
|
|
76
76
|
-----
|
|
77
77
|
|
|
78
78
|
If you need more detailed help using Circup see the Learn Guide article
|
|
79
|
-
`"Use
|
|
79
|
+
`"Use Circup to easily keep your CircuitPython libraries up to date" <https://learn.adafruit.com/keep-your-circuitpython-libraries-on-devices-up-to-date-with-circup/>`_.
|
|
80
80
|
|
|
81
81
|
First, plug in a device running CircuiPython. This should appear as a mounted
|
|
82
82
|
storage device called ``CIRCUITPY``.
|
|
@@ -230,7 +230,7 @@ The ``--version`` flag will tell you the current version of the
|
|
|
230
230
|
``circup`` command itself::
|
|
231
231
|
|
|
232
232
|
$ circup --version
|
|
233
|
-
|
|
233
|
+
Circup, A CircuitPython module updater. Version 0.0.1
|
|
234
234
|
|
|
235
235
|
|
|
236
236
|
To use circup via the `Web Workflow <https://learn.adafruit.com/getting-started-with-web-workflow-using-the-code-editor>`_. on devices that support it. Use the ``--host`` and ``--password`` arguments before your circup command.::
|
|
@@ -263,6 +263,7 @@ For Bash, add this to ~/.bashrc::
|
|
|
263
263
|
|
|
264
264
|
For Zsh, add this to ~/.zshrc::
|
|
265
265
|
|
|
266
|
+
autoload -U compinit; compinit
|
|
266
267
|
eval "$(_CIRCUP_COMPLETE=zsh_source circup)"
|
|
267
268
|
|
|
268
269
|
For Fish, add this to ~/.config/fish/completions/foo-bar.fish::
|
|
@@ -73,7 +73,7 @@ class Backend:
|
|
|
73
73
|
"""
|
|
74
74
|
return self.get_modules(os.path.join(self.device_location, self.LIB_DIR_PATH))
|
|
75
75
|
|
|
76
|
-
def
|
|
76
|
+
def create_directory(self, device_path, directory):
|
|
77
77
|
"""
|
|
78
78
|
To be overridden by subclass
|
|
79
79
|
"""
|
|
@@ -97,6 +97,12 @@ class Backend:
|
|
|
97
97
|
"""
|
|
98
98
|
raise NotImplementedError
|
|
99
99
|
|
|
100
|
+
def upload_file(self, target_file, location_to_paste):
|
|
101
|
+
"""Paste a copy of the specified file at the location given
|
|
102
|
+
To be overridden by subclass
|
|
103
|
+
"""
|
|
104
|
+
raise NotImplementedError
|
|
105
|
+
|
|
100
106
|
# pylint: disable=too-many-locals,too-many-branches,too-many-arguments,too-many-nested-blocks,too-many-statements
|
|
101
107
|
def install_module(
|
|
102
108
|
self, device_path, device_modules, name, pyext, mod_names, upgrade=False
|
|
@@ -189,7 +195,7 @@ class Backend:
|
|
|
189
195
|
return
|
|
190
196
|
|
|
191
197
|
# Create the library directory first.
|
|
192
|
-
self.
|
|
198
|
+
self.create_directory(device_path, library_path)
|
|
193
199
|
if local_path is None:
|
|
194
200
|
if pyext:
|
|
195
201
|
# Use Python source for module.
|
|
@@ -277,11 +283,13 @@ class WebBackend(Backend):
|
|
|
277
283
|
"""
|
|
278
284
|
|
|
279
285
|
def __init__( # pylint: disable=too-many-arguments
|
|
280
|
-
self, host, password, logger, timeout=10, version_override=None
|
|
286
|
+
self, host, port, password, logger, timeout=10, version_override=None
|
|
281
287
|
):
|
|
282
288
|
super().__init__(logger)
|
|
283
289
|
if password is None:
|
|
284
|
-
raise ValueError(
|
|
290
|
+
raise ValueError(
|
|
291
|
+
"Must pass --password or set CIRCUP_WEBWORKFLOW_PASSWORD environment variable"
|
|
292
|
+
)
|
|
285
293
|
|
|
286
294
|
# pylint: disable=no-member
|
|
287
295
|
# verify hostname/address
|
|
@@ -297,14 +305,19 @@ class WebBackend(Backend):
|
|
|
297
305
|
self.FS_PATH = "fs/"
|
|
298
306
|
self.LIB_DIR_PATH = f"{self.FS_PATH}lib/"
|
|
299
307
|
self.host = host
|
|
308
|
+
self.port = port
|
|
300
309
|
self.password = password
|
|
301
|
-
self.device_location = f"http://:{self.password}@{self.host}"
|
|
310
|
+
self.device_location = f"http://:{self.password}@{self.host}:{self.port}"
|
|
302
311
|
|
|
303
312
|
self.session = requests.Session()
|
|
304
313
|
self.session.mount(self.device_location, HTTPAdapter(max_retries=5))
|
|
305
314
|
self.library_path = self.device_location + "/" + self.LIB_DIR_PATH
|
|
306
315
|
self.timeout = timeout
|
|
307
316
|
self.version_override = version_override
|
|
317
|
+
self.FS_URL = urljoin(self.device_location, self.FS_PATH)
|
|
318
|
+
|
|
319
|
+
def __repr__(self):
|
|
320
|
+
return f"<WebBackend @{self.device_location}>"
|
|
308
321
|
|
|
309
322
|
def install_file_http(self, source, location=None):
|
|
310
323
|
"""
|
|
@@ -542,10 +555,9 @@ class WebBackend(Backend):
|
|
|
542
555
|
metadata["path"] = sfm_url
|
|
543
556
|
result[sfm[:idx]] = metadata
|
|
544
557
|
|
|
545
|
-
def
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
with self.session.put(library_path, auth=auth, timeout=self.timeout) as r:
|
|
558
|
+
def create_directory(self, device_path, directory):
|
|
559
|
+
auth = HTTPBasicAuth("", self.password)
|
|
560
|
+
with self.session.put(directory, auth=auth, timeout=self.timeout) as r:
|
|
549
561
|
if r.status_code == 409:
|
|
550
562
|
_writeable_error()
|
|
551
563
|
r.raise_for_status()
|
|
@@ -556,11 +568,56 @@ class WebBackend(Backend):
|
|
|
556
568
|
self.device_location,
|
|
557
569
|
"/".join(("fs", location_to_paste, target_file, "")),
|
|
558
570
|
)
|
|
559
|
-
self.
|
|
571
|
+
self.create_directory(self.device_location, create_directory_url)
|
|
560
572
|
self.install_dir_http(target_file)
|
|
561
573
|
else:
|
|
562
574
|
self.install_file_http(target_file)
|
|
563
575
|
|
|
576
|
+
def upload_file(self, target_file, location_to_paste):
|
|
577
|
+
"""
|
|
578
|
+
copy a file from the host PC to the microcontroller
|
|
579
|
+
:param target_file: file on the host PC to copy
|
|
580
|
+
:param location_to_paste: Location on the microcontroller to paste it.
|
|
581
|
+
:return:
|
|
582
|
+
"""
|
|
583
|
+
if os.path.isdir(target_file):
|
|
584
|
+
create_directory_url = urljoin(
|
|
585
|
+
self.device_location,
|
|
586
|
+
"/".join(("fs", location_to_paste, target_file, "")),
|
|
587
|
+
)
|
|
588
|
+
self.create_directory(self.device_location, create_directory_url)
|
|
589
|
+
self.install_dir_http(target_file, location_to_paste)
|
|
590
|
+
else:
|
|
591
|
+
self.install_file_http(target_file, location_to_paste)
|
|
592
|
+
|
|
593
|
+
def download_file(self, target_file, location_to_paste):
|
|
594
|
+
"""
|
|
595
|
+
Download a file from the MCU device to the local host PC
|
|
596
|
+
:param target_file: The file on the MCU to download
|
|
597
|
+
:param location_to_paste: The location on the host PC to put the downloaded copy.
|
|
598
|
+
:return:
|
|
599
|
+
"""
|
|
600
|
+
auth = HTTPBasicAuth("", self.password)
|
|
601
|
+
with self.session.get(
|
|
602
|
+
self.FS_URL + target_file, timeout=self.timeout, auth=auth
|
|
603
|
+
) as r:
|
|
604
|
+
if r.status_code == 404:
|
|
605
|
+
click.secho(f"{target_file} was not found on the device", "red")
|
|
606
|
+
|
|
607
|
+
file_name = target_file.split("/")[-1]
|
|
608
|
+
if location_to_paste is None:
|
|
609
|
+
with open(file_name, "wb") as f:
|
|
610
|
+
f.write(r.content)
|
|
611
|
+
|
|
612
|
+
click.echo(f"Downloaded File: {file_name}")
|
|
613
|
+
else:
|
|
614
|
+
with open(os.path.join(location_to_paste, file_name), "wb") as f:
|
|
615
|
+
f.write(r.content)
|
|
616
|
+
|
|
617
|
+
click.echo(
|
|
618
|
+
f"Downloaded File: {os.path.join(location_to_paste, file_name)}"
|
|
619
|
+
)
|
|
620
|
+
|
|
564
621
|
def install_module_mpy(self, bundle, metadata):
|
|
565
622
|
"""
|
|
566
623
|
:param bundle library bundle.
|
|
@@ -664,11 +721,7 @@ class WebBackend(Backend):
|
|
|
664
721
|
"""
|
|
665
722
|
retuns the full path on the device to a given file name.
|
|
666
723
|
"""
|
|
667
|
-
return
|
|
668
|
-
urljoin(self.device_location, "fs/", allow_fragments=False),
|
|
669
|
-
filename,
|
|
670
|
-
allow_fragments=False,
|
|
671
|
-
)
|
|
724
|
+
return "/".join((self.device_location, "fs", filename))
|
|
672
725
|
|
|
673
726
|
def is_device_present(self):
|
|
674
727
|
"""
|
|
@@ -739,6 +792,19 @@ class WebBackend(Backend):
|
|
|
739
792
|
return r.json()["free"] * r.json()["block_size"] # bytes
|
|
740
793
|
sys.exit(1)
|
|
741
794
|
|
|
795
|
+
def list_dir(self, dirpath):
|
|
796
|
+
"""
|
|
797
|
+
Returns the list of files located in the given dirpath.
|
|
798
|
+
"""
|
|
799
|
+
auth = HTTPBasicAuth("", self.password)
|
|
800
|
+
with self.session.get(
|
|
801
|
+
urljoin(self.device_location, f"fs/{dirpath if dirpath else ''}"),
|
|
802
|
+
auth=auth,
|
|
803
|
+
headers={"Accept": "application/json"},
|
|
804
|
+
timeout=self.timeout,
|
|
805
|
+
) as r:
|
|
806
|
+
return r.json()["files"]
|
|
807
|
+
|
|
742
808
|
|
|
743
809
|
class DiskBackend(Backend):
|
|
744
810
|
"""
|
|
@@ -817,9 +883,9 @@ class DiskBackend(Backend):
|
|
|
817
883
|
"""
|
|
818
884
|
return _get_modules_file(device_lib_path, self.logger)
|
|
819
885
|
|
|
820
|
-
def
|
|
821
|
-
if not os.path.exists(
|
|
822
|
-
os.makedirs(
|
|
886
|
+
def create_directory(self, device_path, directory):
|
|
887
|
+
if not os.path.exists(directory): # pragma: no cover
|
|
888
|
+
os.makedirs(directory)
|
|
823
889
|
|
|
824
890
|
def copy_file(self, target_file, location_to_paste):
|
|
825
891
|
target_filename = target_file.split(os.path.sep)[-1]
|
|
@@ -834,6 +900,9 @@ class DiskBackend(Backend):
|
|
|
834
900
|
os.path.join(self.device_location, location_to_paste, target_filename),
|
|
835
901
|
)
|
|
836
902
|
|
|
903
|
+
def upload_file(self, target_file, location_to_paste):
|
|
904
|
+
self.copy_file(target_file, location_to_paste)
|
|
905
|
+
|
|
837
906
|
def install_module_mpy(self, bundle, metadata):
|
|
838
907
|
"""
|
|
839
908
|
:param bundle library bundle.
|
|
@@ -610,7 +610,7 @@ def libraries_from_code_py(code_py, mod_names):
|
|
|
610
610
|
return [r for r in imports if r in mod_names]
|
|
611
611
|
|
|
612
612
|
|
|
613
|
-
def get_device_path(host, password, path):
|
|
613
|
+
def get_device_path(host, port, password, path):
|
|
614
614
|
"""
|
|
615
615
|
:param host Hostname or IP address.
|
|
616
616
|
:param password REST API password.
|
|
@@ -621,7 +621,33 @@ def get_device_path(host, password, path):
|
|
|
621
621
|
device_path = path
|
|
622
622
|
elif host:
|
|
623
623
|
# pylint: enable=no-member
|
|
624
|
-
device_path = f"http://:{password}@"
|
|
624
|
+
device_path = f"http://:{password}@{host}:{port}"
|
|
625
625
|
else:
|
|
626
626
|
device_path = find_device()
|
|
627
627
|
return device_path
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def sorted_by_directory_then_alpha(list_of_files):
|
|
631
|
+
"""
|
|
632
|
+
Sort the list of files into alphabetical seperated
|
|
633
|
+
with directories grouped together before files.
|
|
634
|
+
"""
|
|
635
|
+
dirs = {}
|
|
636
|
+
files = {}
|
|
637
|
+
|
|
638
|
+
for cur_file in list_of_files:
|
|
639
|
+
if cur_file["directory"]:
|
|
640
|
+
dirs[cur_file["name"]] = cur_file
|
|
641
|
+
else:
|
|
642
|
+
files[cur_file["name"]] = cur_file
|
|
643
|
+
|
|
644
|
+
sorted_dir_names = sorted(dirs.keys())
|
|
645
|
+
sorted_file_names = sorted(files.keys())
|
|
646
|
+
|
|
647
|
+
sorted_full_list = []
|
|
648
|
+
for cur_name in sorted_dir_names:
|
|
649
|
+
sorted_full_list.append(dirs[cur_name])
|
|
650
|
+
for cur_name in sorted_file_names:
|
|
651
|
+
sorted_full_list.append(files[cur_name])
|
|
652
|
+
|
|
653
|
+
return sorted_full_list
|
|
@@ -57,6 +57,9 @@ from circup.command_utils import (
|
|
|
57
57
|
"--host",
|
|
58
58
|
help="Hostname or IP address of a device. Overrides automatic path detection.",
|
|
59
59
|
)
|
|
60
|
+
@click.option(
|
|
61
|
+
"--port", help="Port to contact. Overrides automatic path detection.", default=80
|
|
62
|
+
)
|
|
60
63
|
@click.option(
|
|
61
64
|
"--password",
|
|
62
65
|
help="Password to use for authentication when --host is used."
|
|
@@ -81,24 +84,24 @@ from circup.command_utils import (
|
|
|
81
84
|
"with --board-id, it overrides the detected CPy version.",
|
|
82
85
|
)
|
|
83
86
|
@click.version_option(
|
|
84
|
-
prog_name="
|
|
87
|
+
prog_name="Circup",
|
|
85
88
|
message="%(prog)s, A CircuitPython module updater. Version %(version)s",
|
|
86
89
|
)
|
|
87
90
|
@click.pass_context
|
|
88
91
|
def main( # pylint: disable=too-many-locals
|
|
89
|
-
ctx, verbose, path, host, password, timeout, board_id, cpy_version
|
|
92
|
+
ctx, verbose, path, host, port, password, timeout, board_id, cpy_version
|
|
90
93
|
): # pragma: no cover
|
|
91
94
|
"""
|
|
92
95
|
A tool to manage and update libraries on a CircuitPython device.
|
|
93
96
|
"""
|
|
94
|
-
# pylint: disable=too-many-arguments,too-many-branches,too-many-statements,too-many-locals
|
|
97
|
+
# pylint: disable=too-many-arguments,too-many-branches,too-many-statements,too-many-locals, R0801
|
|
95
98
|
ctx.ensure_object(dict)
|
|
96
99
|
ctx.obj["TIMEOUT"] = timeout
|
|
97
100
|
|
|
98
101
|
if password is None:
|
|
99
102
|
password = os.getenv("CIRCUP_WEBWORKFLOW_PASSWORD")
|
|
100
103
|
|
|
101
|
-
device_path = get_device_path(host, password, path)
|
|
104
|
+
device_path = get_device_path(host, port, password, path)
|
|
102
105
|
|
|
103
106
|
using_webworkflow = "host" in ctx.params.keys() and ctx.params["host"] is not None
|
|
104
107
|
|
|
@@ -114,6 +117,7 @@ def main( # pylint: disable=too-many-locals
|
|
|
114
117
|
try:
|
|
115
118
|
ctx.obj["backend"] = WebBackend(
|
|
116
119
|
host=host,
|
|
120
|
+
port=port,
|
|
117
121
|
password=password,
|
|
118
122
|
logger=logger,
|
|
119
123
|
timeout=timeout,
|
|
@@ -10,8 +10,8 @@ import glob
|
|
|
10
10
|
import os
|
|
11
11
|
import re
|
|
12
12
|
import json
|
|
13
|
+
import importlib.resources
|
|
13
14
|
import appdirs
|
|
14
|
-
import pkg_resources
|
|
15
15
|
import requests
|
|
16
16
|
|
|
17
17
|
#: Version identifier for a bad MPY file format
|
|
@@ -27,9 +27,8 @@ PLATFORMS = {"py": "py", "8mpy": "8.x-mpy", "9mpy": "9.x-mpy"}
|
|
|
27
27
|
REQUESTS_TIMEOUT = 30
|
|
28
28
|
|
|
29
29
|
#: The path to the JSON file containing the metadata about the bundles.
|
|
30
|
-
BUNDLE_CONFIG_FILE =
|
|
31
|
-
|
|
32
|
-
)
|
|
30
|
+
BUNDLE_CONFIG_FILE = importlib.resources.files("circup") / "config/bundle_config.json"
|
|
31
|
+
|
|
33
32
|
#: Overwrite the bundles list with this file (only done manually)
|
|
34
33
|
BUNDLE_CONFIG_OVERWRITE = os.path.join(DATA_DIR, "bundle_config.json")
|
|
35
34
|
#: The path to the JSON file containing the local list of bundles.
|
|
@@ -80,14 +79,18 @@ def _get_modules_file(path, logger):
|
|
|
80
79
|
py_files = glob.glob(os.path.join(package_path, "**/*.py"), recursive=True)
|
|
81
80
|
mpy_files = glob.glob(os.path.join(package_path, "**/*.mpy"), recursive=True)
|
|
82
81
|
all_files = py_files + mpy_files
|
|
82
|
+
# put __init__ first if any, assumed to have the version number
|
|
83
|
+
all_files.sort()
|
|
83
84
|
# default value
|
|
84
85
|
result[name] = {"path": package_path, "mpy": bool(mpy_files)}
|
|
85
86
|
# explore all the submodules to detect bad ones
|
|
86
87
|
for source in [f for f in all_files if not os.path.basename(f).startswith(".")]:
|
|
87
88
|
metadata = extract_metadata(source, logger)
|
|
88
89
|
if "__version__" in metadata:
|
|
89
|
-
metadata
|
|
90
|
-
result[name]
|
|
90
|
+
# don't replace metadata if already found
|
|
91
|
+
if "__version__" not in result[name]:
|
|
92
|
+
metadata["path"] = package_path
|
|
93
|
+
result[name] = metadata
|
|
91
94
|
# break now if any of the submodules has a bad format
|
|
92
95
|
if metadata["__version__"] == BAD_FILE_FORMAT:
|
|
93
96
|
break
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
|
|
2
|
+
wwshell
|
|
3
|
+
=======
|
|
4
|
+
|
|
5
|
+
.. image:: https://readthedocs.org/projects/circup/badge/?version=latest
|
|
6
|
+
:target: https://circuitpython.readthedocs.io/projects/circup/en/latest/
|
|
7
|
+
:alt: Documentation Status
|
|
8
|
+
|
|
9
|
+
.. image:: https://img.shields.io/discord/327254708534116352.svg
|
|
10
|
+
:target: https://adafru.it/discord
|
|
11
|
+
:alt: Discord
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
.. image:: https://github.com/adafruit/circup/workflows/Build%20CI/badge.svg
|
|
15
|
+
:target: https://github.com/adafruit/circup/actions
|
|
16
|
+
:alt: Build Status
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
|
20
|
+
:target: https://github.com/psf/black
|
|
21
|
+
:alt: Code Style: Black
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
A tool to manage files on a CircuitPython device via wireless workflows.
|
|
25
|
+
Currently supports Web Workflow.
|
|
26
|
+
|
|
27
|
+
.. contents::
|
|
28
|
+
|
|
29
|
+
Installation
|
|
30
|
+
------------
|
|
31
|
+
|
|
32
|
+
wwshell is bundled along with Circup. When you install Circup you'll get wwshell automatically.
|
|
33
|
+
|
|
34
|
+
Circup requires Python 3.5 or higher.
|
|
35
|
+
|
|
36
|
+
In a `virtualenv <https://virtualenv.pypa.io/en/latest/>`_,
|
|
37
|
+
``pip install circup`` should do the trick. This is the simplest way to make it
|
|
38
|
+
work.
|
|
39
|
+
|
|
40
|
+
If you have no idea what a virtualenv is, try the following command,
|
|
41
|
+
``pip3 install --user circup``.
|
|
42
|
+
|
|
43
|
+
.. note::
|
|
44
|
+
|
|
45
|
+
If you use the ``pip3`` command to install CircUp you must make sure that
|
|
46
|
+
your path contains the directory into which the script will be installed.
|
|
47
|
+
To discover this path,
|
|
48
|
+
|
|
49
|
+
* On Unix-like systems, type ``python3 -m site --user-base`` and append
|
|
50
|
+
``bin`` to the resulting path.
|
|
51
|
+
* On Windows, type the same command, but append ``Scripts`` to the
|
|
52
|
+
resulting path.
|
|
53
|
+
|
|
54
|
+
What does wwshell do?
|
|
55
|
+
---------------------
|
|
56
|
+
|
|
57
|
+
It lets you view, delete, upload, and download files from your Circuitpython device
|
|
58
|
+
via wireless workflows. Similar to ampy, but operates over wireless workflow rather
|
|
59
|
+
than USB serial.
|
|
60
|
+
|
|
61
|
+
Usage
|
|
62
|
+
-----
|
|
63
|
+
|
|
64
|
+
To use web workflow you need to enable it by putting WIFI credentials and a web workflow
|
|
65
|
+
password into your settings.toml file. `See here <https://learn.adafruit.com/getting-started-with-web-workflow-using-the-code-editor/device-setup>`_,
|
|
66
|
+
|
|
67
|
+
To get help, just type the command::
|
|
68
|
+
|
|
69
|
+
$ wwshell
|
|
70
|
+
Usage: wwshell [OPTIONS] COMMAND [ARGS]...
|
|
71
|
+
|
|
72
|
+
A tool to manage files CircuitPython device over web workflow.
|
|
73
|
+
|
|
74
|
+
Options:
|
|
75
|
+
--verbose Comprehensive logging is sent to stdout.
|
|
76
|
+
--path DIRECTORY Path to CircuitPython directory. Overrides automatic path
|
|
77
|
+
detection.
|
|
78
|
+
--host TEXT Hostname or IP address of a device. Overrides automatic
|
|
79
|
+
path detection.
|
|
80
|
+
--password TEXT Password to use for authentication when --host is used.
|
|
81
|
+
You can optionally set an environment variable
|
|
82
|
+
CIRCUP_WEBWORKFLOW_PASSWORD instead of passing this
|
|
83
|
+
argument. If both exist the CLI arg takes precedent.
|
|
84
|
+
--timeout INTEGER Specify the timeout in seconds for any network
|
|
85
|
+
operations.
|
|
86
|
+
--version Show the version and exit.
|
|
87
|
+
--help Show this message and exit.
|
|
88
|
+
|
|
89
|
+
Commands:
|
|
90
|
+
get Download a copy of a file or directory from the device to the...
|
|
91
|
+
ls Lists the contents of a directory.
|
|
92
|
+
put Upload a copy of a file or directory from the local computer to...
|
|
93
|
+
rm Delete a file on the device.
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
.. note::
|
|
97
|
+
|
|
98
|
+
If you find a bug, or you want to suggest an enhancement or new feature
|
|
99
|
+
feel free to create an issue or submit a pull request here:
|
|
100
|
+
|
|
101
|
+
https://github.com/adafruit/circup
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
Discussion of this tool happens on the Adafruit CircuitPython
|
|
105
|
+
`Discord channel <https://discord.gg/rqrKDjU>`_.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2024 Tim Cocks, written for Adafruit Industries
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
"""
|
|
5
|
+
wwshell is a CLI utility for managing files on CircuitPython devices via wireless workflows.
|
|
6
|
+
It currently supports Web Workflow.
|
|
7
|
+
"""
|
|
8
|
+
from .commands import main
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Allows execution via `python -m circup ...`
|
|
12
|
+
# pylint: disable=no-value-for-parameter
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
main()
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2019 Nicholas Tollervey, 2024 Tim Cocks, written for Adafruit Industries
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
"""
|
|
5
|
+
# ----------- CLI command definitions ----------- #
|
|
6
|
+
|
|
7
|
+
The following functions have IO side effects (for instance they emit to
|
|
8
|
+
stdout). Ergo, these are not checked with unit tests. Most of the
|
|
9
|
+
functionality they provide is provided by the functions from util_functions.py,
|
|
10
|
+
and the respective Backends which *are* tested. Most of the logic of the following
|
|
11
|
+
functions is to prepare things for presentation to / interaction with the user.
|
|
12
|
+
"""
|
|
13
|
+
import os
|
|
14
|
+
import time
|
|
15
|
+
import sys
|
|
16
|
+
import logging
|
|
17
|
+
import update_checker
|
|
18
|
+
import click
|
|
19
|
+
import requests
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
from circup.backends import WebBackend
|
|
23
|
+
from circup.logging import logger, log_formatter, LOGFILE
|
|
24
|
+
from circup.shared import BOARDLESS_COMMANDS
|
|
25
|
+
|
|
26
|
+
from circup.command_utils import (
|
|
27
|
+
get_device_path,
|
|
28
|
+
get_circup_version,
|
|
29
|
+
sorted_by_directory_then_alpha,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@click.group()
|
|
34
|
+
@click.option(
|
|
35
|
+
"--verbose", is_flag=True, help="Comprehensive logging is sent to stdout."
|
|
36
|
+
)
|
|
37
|
+
@click.option(
|
|
38
|
+
"--path",
|
|
39
|
+
type=click.Path(exists=True, file_okay=False),
|
|
40
|
+
help="Path to CircuitPython directory. Overrides automatic path detection.",
|
|
41
|
+
)
|
|
42
|
+
@click.option(
|
|
43
|
+
"--host",
|
|
44
|
+
help="Hostname or IP address of a device. Overrides automatic path detection.",
|
|
45
|
+
default="circuitpython.local",
|
|
46
|
+
)
|
|
47
|
+
@click.option(
|
|
48
|
+
"--port",
|
|
49
|
+
help="HTTP port that the web workflow is listening on.",
|
|
50
|
+
default=80,
|
|
51
|
+
)
|
|
52
|
+
@click.option(
|
|
53
|
+
"--password",
|
|
54
|
+
help="Password to use for authentication when --host is used."
|
|
55
|
+
" You can optionally set an environment variable CIRCUP_WEBWORKFLOW_PASSWORD"
|
|
56
|
+
" instead of passing this argument. If both exist the CLI arg takes precedent.",
|
|
57
|
+
)
|
|
58
|
+
@click.option(
|
|
59
|
+
"--timeout",
|
|
60
|
+
default=30,
|
|
61
|
+
help="Specify the timeout in seconds for any network operations.",
|
|
62
|
+
)
|
|
63
|
+
@click.version_option(
|
|
64
|
+
prog_name="CircFile",
|
|
65
|
+
message="%(prog)s, A CircuitPython web workflow file managemenr. Version %(version)s",
|
|
66
|
+
)
|
|
67
|
+
@click.pass_context
|
|
68
|
+
def main( # pylint: disable=too-many-locals
|
|
69
|
+
ctx,
|
|
70
|
+
verbose,
|
|
71
|
+
path,
|
|
72
|
+
host,
|
|
73
|
+
port,
|
|
74
|
+
password,
|
|
75
|
+
timeout,
|
|
76
|
+
): # pragma: no cover
|
|
77
|
+
"""
|
|
78
|
+
A tool to manage files CircuitPython device over web workflow.
|
|
79
|
+
"""
|
|
80
|
+
# pylint: disable=too-many-arguments,too-many-branches,too-many-statements,too-many-locals, R0801
|
|
81
|
+
ctx.ensure_object(dict)
|
|
82
|
+
ctx.obj["TIMEOUT"] = timeout
|
|
83
|
+
|
|
84
|
+
if password is None:
|
|
85
|
+
password = os.getenv("CIRCUP_WEBWORKFLOW_PASSWORD")
|
|
86
|
+
|
|
87
|
+
device_path = get_device_path(host, port, password, path)
|
|
88
|
+
|
|
89
|
+
using_webworkflow = "host" in ctx.params.keys() and ctx.params["host"] is not None
|
|
90
|
+
if using_webworkflow:
|
|
91
|
+
if host == "circuitpython.local":
|
|
92
|
+
click.echo("Checking versions.json on circuitpython.local to find hostname")
|
|
93
|
+
versions_resp = requests.get(
|
|
94
|
+
"http://circuitpython.local/cp/version.json", timeout=timeout
|
|
95
|
+
)
|
|
96
|
+
host = f'{versions_resp.json()["hostname"]}.local'
|
|
97
|
+
click.echo(f"Using hostname: {host}")
|
|
98
|
+
device_path = device_path.replace("circuitpython.local", host)
|
|
99
|
+
try:
|
|
100
|
+
ctx.obj["backend"] = WebBackend(
|
|
101
|
+
host=host, port=port, password=password, logger=logger, timeout=timeout
|
|
102
|
+
)
|
|
103
|
+
except ValueError as e:
|
|
104
|
+
click.secho(e, fg="red")
|
|
105
|
+
time.sleep(0.3)
|
|
106
|
+
sys.exit(1)
|
|
107
|
+
except RuntimeError as e:
|
|
108
|
+
click.secho(e, fg="red")
|
|
109
|
+
sys.exit(1)
|
|
110
|
+
|
|
111
|
+
if verbose:
|
|
112
|
+
# Configure additional logging to stdout.
|
|
113
|
+
ctx.obj["verbose"] = True
|
|
114
|
+
verbose_handler = logging.StreamHandler(sys.stdout)
|
|
115
|
+
verbose_handler.setLevel(logging.INFO)
|
|
116
|
+
verbose_handler.setFormatter(log_formatter)
|
|
117
|
+
logger.addHandler(verbose_handler)
|
|
118
|
+
click.echo("Logging to {}\n".format(LOGFILE))
|
|
119
|
+
else:
|
|
120
|
+
ctx.obj["verbose"] = False
|
|
121
|
+
|
|
122
|
+
logger.info("### Started Circfile ###")
|
|
123
|
+
|
|
124
|
+
# If a newer version of circfile is available, print a message.
|
|
125
|
+
logger.info("Checking for a newer version of circfile")
|
|
126
|
+
version = get_circup_version()
|
|
127
|
+
if version:
|
|
128
|
+
update_checker.update_check("circfile", version)
|
|
129
|
+
|
|
130
|
+
# stop early if the command is boardless
|
|
131
|
+
if ctx.invoked_subcommand in BOARDLESS_COMMANDS or "--help" in sys.argv:
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
ctx.obj["DEVICE_PATH"] = device_path
|
|
135
|
+
|
|
136
|
+
if device_path is None or not ctx.obj["backend"].is_device_present():
|
|
137
|
+
click.secho("Could not find a connected CircuitPython device.", fg="red")
|
|
138
|
+
sys.exit(1)
|
|
139
|
+
else:
|
|
140
|
+
click.echo("Found device at {}.".format(device_path))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@main.command("ls")
|
|
144
|
+
@click.argument("file", required=True, nargs=1, default="/")
|
|
145
|
+
@click.pass_context
|
|
146
|
+
def ls_cli(ctx, file): # pragma: no cover
|
|
147
|
+
"""
|
|
148
|
+
Lists the contents of a directory. Defaults to root directory
|
|
149
|
+
if not supplied.
|
|
150
|
+
"""
|
|
151
|
+
logger.info("ls")
|
|
152
|
+
if not file.endswith("/"):
|
|
153
|
+
file += "/"
|
|
154
|
+
click.echo(f"running: ls {file}")
|
|
155
|
+
|
|
156
|
+
files = ctx.obj["backend"].list_dir(file)
|
|
157
|
+
click.echo("Size\tName")
|
|
158
|
+
for cur_file in sorted_by_directory_then_alpha(files):
|
|
159
|
+
click.echo(
|
|
160
|
+
f"{cur_file['file_size']}\t{cur_file['name']}{'/' if cur_file['directory'] else ''}"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@main.command("put")
|
|
165
|
+
@click.argument("file", required=True, nargs=1)
|
|
166
|
+
@click.argument("location", required=False, nargs=1, default="")
|
|
167
|
+
@click.option("--overwrite", is_flag=True, help="Overwrite the file if it exists.")
|
|
168
|
+
@click.pass_context
|
|
169
|
+
def put_cli(ctx, file, location, overwrite):
|
|
170
|
+
"""
|
|
171
|
+
Upload a copy of a file or directory from the local computer
|
|
172
|
+
to the device
|
|
173
|
+
"""
|
|
174
|
+
click.echo(f"Attempting PUT: {file} at {location} overwrite? {overwrite}")
|
|
175
|
+
if not ctx.obj["backend"].file_exists(f"{location}{file}"):
|
|
176
|
+
ctx.obj["backend"].upload_file(file, location)
|
|
177
|
+
click.echo(f"Successfully PUT {location}{file}")
|
|
178
|
+
else:
|
|
179
|
+
if overwrite:
|
|
180
|
+
click.secho(
|
|
181
|
+
f"{location}{file} already exists. Overwriting it.", fg="yellow"
|
|
182
|
+
)
|
|
183
|
+
ctx.obj["backend"].upload_file(file, location)
|
|
184
|
+
click.echo(f"Successfully PUT {location}{file}")
|
|
185
|
+
else:
|
|
186
|
+
click.secho(
|
|
187
|
+
f"{location}{file} already exists. Pass --overwrite if you wish to replace it.",
|
|
188
|
+
fg="red",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# pylint: enable=too-many-arguments,too-many-locals
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@main.command("get")
|
|
196
|
+
@click.argument("file", required=True, nargs=1)
|
|
197
|
+
@click.argument("location", required=False, nargs=1)
|
|
198
|
+
@click.pass_context
|
|
199
|
+
def get_cli(ctx, file, location): # pragma: no cover
|
|
200
|
+
"""
|
|
201
|
+
Download a copy of a file or directory from the device to the local computer.
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
click.echo(f"running: get {file} {location}")
|
|
205
|
+
ctx.obj["backend"].download_file(file, location)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@main.command("rm")
|
|
209
|
+
@click.argument("file", nargs=1)
|
|
210
|
+
@click.pass_context
|
|
211
|
+
def rm_cli(ctx, file): # pragma: no cover
|
|
212
|
+
"""
|
|
213
|
+
Delete a file on the device.
|
|
214
|
+
"""
|
|
215
|
+
click.echo(f"running: rm {file}")
|
|
216
|
+
ctx.obj["backend"].uninstall(
|
|
217
|
+
ctx.obj["backend"].device_location, ctx.obj["backend"].get_file_path(file)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@main.command("mkdir")
|
|
222
|
+
@click.argument("directory", nargs=1)
|
|
223
|
+
@click.pass_context
|
|
224
|
+
def mkdir_cli(ctx, directory): # pragma: no cover
|
|
225
|
+
"""
|
|
226
|
+
Create
|
|
227
|
+
"""
|
|
228
|
+
click.echo(f"running: mkdir {directory}")
|
|
229
|
+
ctx.obj["backend"].create_directory(
|
|
230
|
+
ctx.obj["backend"].device_location, ctx.obj["backend"].get_file_path(directory)
|
|
231
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: circup
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: A tool to manage/update libraries on CircuitPython devices.
|
|
5
5
|
Author-email: Adafruit Industries <circuitpython@adafruit.com>
|
|
6
6
|
License: MIT License
|
|
@@ -59,7 +59,7 @@ Requires-Dist: pytest-faulthandler; extra == "optional"
|
|
|
59
59
|
Requires-Dist: pytest-random-order; extra == "optional"
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
Circup
|
|
63
63
|
======
|
|
64
64
|
|
|
65
65
|
.. image:: https://readthedocs.org/projects/circup/badge/?version=latest
|
|
@@ -88,7 +88,7 @@ A tool to manage and update libraries (modules) on a CircuitPython device.
|
|
|
88
88
|
Installation
|
|
89
89
|
------------
|
|
90
90
|
|
|
91
|
-
Circup requires Python 3.
|
|
91
|
+
Circup requires Python 3.9 or higher.
|
|
92
92
|
|
|
93
93
|
In a `virtualenv <https://virtualenv.pypa.io/en/latest/>`_,
|
|
94
94
|
``pip install circup`` should do the trick. This is the simplest way to make it
|
|
@@ -99,7 +99,7 @@ If you have no idea what a virtualenv is, try the following command,
|
|
|
99
99
|
|
|
100
100
|
.. note::
|
|
101
101
|
|
|
102
|
-
If you use the ``pip3`` command to install
|
|
102
|
+
If you use the ``pip3`` command to install Circup you must make sure that
|
|
103
103
|
your path contains the directory into which the script will be installed.
|
|
104
104
|
To discover this path,
|
|
105
105
|
|
|
@@ -136,7 +136,7 @@ Usage
|
|
|
136
136
|
-----
|
|
137
137
|
|
|
138
138
|
If you need more detailed help using Circup see the Learn Guide article
|
|
139
|
-
`"Use
|
|
139
|
+
`"Use Circup to easily keep your CircuitPython libraries up to date" <https://learn.adafruit.com/keep-your-circuitpython-libraries-on-devices-up-to-date-with-circup/>`_.
|
|
140
140
|
|
|
141
141
|
First, plug in a device running CircuiPython. This should appear as a mounted
|
|
142
142
|
storage device called ``CIRCUITPY``.
|
|
@@ -290,7 +290,7 @@ The ``--version`` flag will tell you the current version of the
|
|
|
290
290
|
``circup`` command itself::
|
|
291
291
|
|
|
292
292
|
$ circup --version
|
|
293
|
-
|
|
293
|
+
Circup, A CircuitPython module updater. Version 0.0.1
|
|
294
294
|
|
|
295
295
|
|
|
296
296
|
To use circup via the `Web Workflow <https://learn.adafruit.com/getting-started-with-web-workflow-using-the-code-editor>`_. on devices that support it. Use the ``--host`` and ``--password`` arguments before your circup command.::
|
|
@@ -323,6 +323,7 @@ For Bash, add this to ~/.bashrc::
|
|
|
323
323
|
|
|
324
324
|
For Zsh, add this to ~/.zshrc::
|
|
325
325
|
|
|
326
|
+
autoload -U compinit; compinit
|
|
326
327
|
eval "$(_CIRCUP_COMPLETE=zsh_source circup)"
|
|
327
328
|
|
|
328
329
|
For Fish, add this to ~/.config/fish/completions/foo-bar.fish::
|
|
@@ -38,6 +38,10 @@ circup.egg-info/requires.txt
|
|
|
38
38
|
circup.egg-info/top_level.txt
|
|
39
39
|
circup/config/bundle_config.json
|
|
40
40
|
circup/config/bundle_config.json.license
|
|
41
|
+
circup/wwshell/README.rst
|
|
42
|
+
circup/wwshell/README.rst.license
|
|
43
|
+
circup/wwshell/__init__.py
|
|
44
|
+
circup/wwshell/commands.py
|
|
41
45
|
docs/conf.py
|
|
42
46
|
docs/index.rst
|
|
43
47
|
docs/index.rst.license
|
|
@@ -109,7 +109,6 @@ if not on_rtd: # only import and set the theme if we're building docs locally
|
|
|
109
109
|
import sphinx_rtd_theme
|
|
110
110
|
|
|
111
111
|
html_theme = "sphinx_rtd_theme"
|
|
112
|
-
html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."]
|
|
113
112
|
except:
|
|
114
113
|
html_theme = "default"
|
|
115
114
|
html_theme_path = ["."]
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
..
|
|
1
|
+
.. Circup documentation master file, created by
|
|
2
2
|
sphinx-quickstart on Mon Sep 2 10:58:36 2019.
|
|
3
3
|
You can adapt this file completely to your liking, but it should at least
|
|
4
4
|
contain the root `toctree` directive.
|
|
5
5
|
|
|
6
6
|
.. include:: ../README.rst
|
|
7
7
|
|
|
8
|
+
.. include:: ../circup/wwshell/README.rst
|
|
9
|
+
|
|
8
10
|
.. include:: ../CONTRIBUTING.rst
|
|
9
11
|
|
|
10
12
|
API
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|