circup 2.0.4__tar.gz → 2.1.1__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.4 → circup-2.1.1}/CONTRIBUTING.rst +3 -3
- {circup-2.0.4/circup.egg-info → circup-2.1.1}/PKG-INFO +8 -7
- {circup-2.0.4 → circup-2.1.1}/README.rst +6 -5
- {circup-2.0.4 → circup-2.1.1}/circup/__init__.py +1 -1
- {circup-2.0.4 → circup-2.1.1}/circup/backends.py +85 -17
- {circup-2.0.4 → circup-2.1.1}/circup/command_utils.py +41 -1
- {circup-2.0.4 → circup-2.1.1}/circup/commands.py +31 -9
- {circup-2.0.4 → circup-2.1.1}/circup/shared.py +6 -2
- circup-2.1.1/circup/wwshell/README.rst +105 -0
- circup-2.1.1/circup/wwshell/README.rst.license +3 -0
- circup-2.1.1/circup/wwshell/__init__.py +14 -0
- circup-2.1.1/circup/wwshell/commands.py +231 -0
- {circup-2.0.4 → circup-2.1.1/circup.egg-info}/PKG-INFO +8 -7
- {circup-2.0.4 → circup-2.1.1}/circup.egg-info/SOURCES.txt +4 -0
- {circup-2.0.4 → circup-2.1.1}/circup.egg-info/entry_points.txt +1 -0
- {circup-2.0.4 → circup-2.1.1}/docs/conf.py +0 -1
- {circup-2.0.4 → circup-2.1.1}/docs/index.rst +3 -1
- {circup-2.0.4 → circup-2.1.1}/pyproject.toml +1 -0
- {circup-2.0.4 → circup-2.1.1}/.github/ISSUE_TEMPLATE.md +0 -0
- {circup-2.0.4 → circup-2.1.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {circup-2.0.4 → circup-2.1.1}/.github/workflows/build.yml +0 -0
- {circup-2.0.4 → circup-2.1.1}/.github/workflows/release.yml +0 -0
- {circup-2.0.4 → circup-2.1.1}/.gitignore +0 -0
- {circup-2.0.4 → circup-2.1.1}/.isort.cfg +0 -0
- {circup-2.0.4 → circup-2.1.1}/.pre-commit-config.yaml +0 -0
- {circup-2.0.4 → circup-2.1.1}/.pylintrc +0 -0
- {circup-2.0.4 → circup-2.1.1}/CODE_OF_CONDUCT.rst +0 -0
- {circup-2.0.4 → circup-2.1.1}/CODE_OF_CONDUCT.rst.license +0 -0
- {circup-2.0.4 → circup-2.1.1}/CONTRIBUTING.rst.license +0 -0
- {circup-2.0.4 → circup-2.1.1}/LICENSE +0 -0
- {circup-2.0.4 → circup-2.1.1}/LICENSES/CC-BY-4.0.txt +0 -0
- {circup-2.0.4 → circup-2.1.1}/LICENSES/MIT.txt +0 -0
- {circup-2.0.4 → circup-2.1.1}/LICENSES/Unlicense.txt +0 -0
- {circup-2.0.4 → circup-2.1.1}/README.rst.license +0 -0
- {circup-2.0.4 → circup-2.1.1}/circup/bundle.py +0 -0
- {circup-2.0.4 → circup-2.1.1}/circup/config/bundle_config.json +0 -0
- {circup-2.0.4 → circup-2.1.1}/circup/config/bundle_config.json.license +0 -0
- {circup-2.0.4 → circup-2.1.1}/circup/logging.py +0 -0
- {circup-2.0.4 → circup-2.1.1}/circup/module.py +0 -0
- {circup-2.0.4 → circup-2.1.1}/circup.egg-info/dependency_links.txt +0 -0
- {circup-2.0.4 → circup-2.1.1}/circup.egg-info/requires.txt +0 -0
- {circup-2.0.4 → circup-2.1.1}/circup.egg-info/top_level.txt +0 -0
- {circup-2.0.4 → circup-2.1.1}/docs/_static/favicon.ico +0 -0
- {circup-2.0.4 → circup-2.1.1}/docs/_static/favicon.ico.license +0 -0
- {circup-2.0.4 → circup-2.1.1}/docs/index.rst.license +0 -0
- {circup-2.0.4 → circup-2.1.1}/docs/logo.png +0 -0
- {circup-2.0.4 → circup-2.1.1}/docs/logo.png.license +0 -0
- {circup-2.0.4 → circup-2.1.1}/optional_requirements.txt +0 -0
- {circup-2.0.4 → circup-2.1.1}/optional_requirements.txt.license +0 -0
- {circup-2.0.4 → circup-2.1.1}/readthedocs.yml +0 -0
- {circup-2.0.4 → circup-2.1.1}/requirements.txt +0 -0
- {circup-2.0.4 → circup-2.1.1}/requirements.txt.license +0 -0
- {circup-2.0.4 → circup-2.1.1}/setup.cfg +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/__init__.py +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/bad_module/__init__.py +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/bad_module/my_module.py +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/bad_python.py +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/bundle.json +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/bundle.json.license +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/device.json +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/device.json.license +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/dir_module/__init__.py +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/dir_module/my_module.py +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/import_styles.py +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/local_module.py +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/local_module_cp7.mpy +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/local_module_cp7.mpy.license +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/mock_device/boot_out.txt +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/mock_device/boot_out.txt.license +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/mock_device/lib/adafruit_waveform/.gitkeep +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/mount_exists.txt +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/mount_exists.txt.license +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/mount_missing.txt +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/mount_missing.txt.license +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/remote_module.py +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/test_bundle_config.json +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/test_bundle_config.json.license +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/test_bundle_config_local.json +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/test_bundle_config_local.json.license +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/test_circup.py +0 -0
- {circup-2.0.4 → circup-2.1.1}/tests/test_module.mpy +0 -0
- {circup-2.0.4 → circup-2.1.1}/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
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: circup
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.1
|
|
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.
|
|
@@ -281,7 +287,9 @@ class WebBackend(Backend):
|
|
|
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
|
|
@@ -306,6 +314,7 @@ class WebBackend(Backend):
|
|
|
306
314
|
self.library_path = self.device_location + "/" + self.LIB_DIR_PATH
|
|
307
315
|
self.timeout = timeout
|
|
308
316
|
self.version_override = version_override
|
|
317
|
+
self.FS_URL = urljoin(self.device_location, self.FS_PATH)
|
|
309
318
|
|
|
310
319
|
def __repr__(self):
|
|
311
320
|
return f"<WebBackend @{self.device_location}>"
|
|
@@ -546,10 +555,9 @@ class WebBackend(Backend):
|
|
|
546
555
|
metadata["path"] = sfm_url
|
|
547
556
|
result[sfm[:idx]] = metadata
|
|
548
557
|
|
|
549
|
-
def
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
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:
|
|
553
561
|
if r.status_code == 409:
|
|
554
562
|
_writeable_error()
|
|
555
563
|
r.raise_for_status()
|
|
@@ -560,11 +568,56 @@ class WebBackend(Backend):
|
|
|
560
568
|
self.device_location,
|
|
561
569
|
"/".join(("fs", location_to_paste, target_file, "")),
|
|
562
570
|
)
|
|
563
|
-
self.
|
|
571
|
+
self.create_directory(self.device_location, create_directory_url)
|
|
564
572
|
self.install_dir_http(target_file)
|
|
565
573
|
else:
|
|
566
574
|
self.install_file_http(target_file)
|
|
567
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
|
+
|
|
568
621
|
def install_module_mpy(self, bundle, metadata):
|
|
569
622
|
"""
|
|
570
623
|
:param bundle library bundle.
|
|
@@ -668,11 +721,7 @@ class WebBackend(Backend):
|
|
|
668
721
|
"""
|
|
669
722
|
retuns the full path on the device to a given file name.
|
|
670
723
|
"""
|
|
671
|
-
return
|
|
672
|
-
urljoin(self.device_location, "fs/", allow_fragments=False),
|
|
673
|
-
filename,
|
|
674
|
-
allow_fragments=False,
|
|
675
|
-
)
|
|
724
|
+
return "/".join((self.device_location, "fs", filename))
|
|
676
725
|
|
|
677
726
|
def is_device_present(self):
|
|
678
727
|
"""
|
|
@@ -743,6 +792,19 @@ class WebBackend(Backend):
|
|
|
743
792
|
return r.json()["free"] * r.json()["block_size"] # bytes
|
|
744
793
|
sys.exit(1)
|
|
745
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
|
+
|
|
746
808
|
|
|
747
809
|
class DiskBackend(Backend):
|
|
748
810
|
"""
|
|
@@ -821,9 +883,9 @@ class DiskBackend(Backend):
|
|
|
821
883
|
"""
|
|
822
884
|
return _get_modules_file(device_lib_path, self.logger)
|
|
823
885
|
|
|
824
|
-
def
|
|
825
|
-
if not os.path.exists(
|
|
826
|
-
os.makedirs(
|
|
886
|
+
def create_directory(self, device_path, directory):
|
|
887
|
+
if not os.path.exists(directory): # pragma: no cover
|
|
888
|
+
os.makedirs(directory)
|
|
827
889
|
|
|
828
890
|
def copy_file(self, target_file, location_to_paste):
|
|
829
891
|
target_filename = target_file.split(os.path.sep)[-1]
|
|
@@ -838,6 +900,9 @@ class DiskBackend(Backend):
|
|
|
838
900
|
os.path.join(self.device_location, location_to_paste, target_filename),
|
|
839
901
|
)
|
|
840
902
|
|
|
903
|
+
def upload_file(self, target_file, location_to_paste):
|
|
904
|
+
self.copy_file(target_file, location_to_paste)
|
|
905
|
+
|
|
841
906
|
def install_module_mpy(self, bundle, metadata):
|
|
842
907
|
"""
|
|
843
908
|
:param bundle library bundle.
|
|
@@ -885,7 +950,10 @@ class DiskBackend(Backend):
|
|
|
885
950
|
# Copy the directory.
|
|
886
951
|
shutil.copytree(source_path, target_path)
|
|
887
952
|
else:
|
|
888
|
-
|
|
953
|
+
if "target_name" in metadata:
|
|
954
|
+
target = metadata["target_name"]
|
|
955
|
+
else:
|
|
956
|
+
target = os.path.basename(source_path)
|
|
889
957
|
target_path = os.path.join(location, target)
|
|
890
958
|
# Copy file.
|
|
891
959
|
shutil.copyfile(source_path, target_path)
|
|
@@ -100,6 +100,7 @@ def completion_for_example(ctx, param, incomplete):
|
|
|
100
100
|
Returns the list of available modules for the command line tab-completion
|
|
101
101
|
with the ``circup example`` command.
|
|
102
102
|
"""
|
|
103
|
+
|
|
103
104
|
# pylint: disable=unused-argument, consider-iterating-dictionary
|
|
104
105
|
available_examples = get_bundle_examples(get_bundles_list(), avoid_download=True)
|
|
105
106
|
|
|
@@ -319,14 +320,22 @@ def get_bundle_examples(bundles_list, avoid_download=False):
|
|
|
319
320
|
:return: A dictionary of metadata about the examples available in the
|
|
320
321
|
library bundle.
|
|
321
322
|
"""
|
|
322
|
-
# pylint: disable=too-many-nested-blocks
|
|
323
|
+
# pylint: disable=too-many-nested-blocks,too-many-locals
|
|
323
324
|
all_the_examples = dict()
|
|
325
|
+
bundle_examples = dict()
|
|
324
326
|
|
|
325
327
|
try:
|
|
326
328
|
for bundle in bundles_list:
|
|
327
329
|
if not avoid_download or not os.path.isdir(bundle.lib_dir("py")):
|
|
328
330
|
ensure_latest_bundle(bundle)
|
|
329
331
|
path = bundle.examples_dir("py")
|
|
332
|
+
meta_saved = os.path.join(path, "../bundle_examples.json")
|
|
333
|
+
if os.path.exists(meta_saved):
|
|
334
|
+
with open(meta_saved, "r", encoding="utf-8") as f:
|
|
335
|
+
bundle_examples = json.load(f)
|
|
336
|
+
all_the_examples.update(bundle_examples)
|
|
337
|
+
bundle_examples.clear()
|
|
338
|
+
continue
|
|
330
339
|
path_examples = _get_modules_file(path, logger)
|
|
331
340
|
for lib_name, lib_metadata in path_examples.items():
|
|
332
341
|
for _dir_level in os.walk(lib_metadata["path"]):
|
|
@@ -337,8 +346,13 @@ def get_bundle_examples(bundles_list, avoid_download=False):
|
|
|
337
346
|
if _dirs[-1] == "":
|
|
338
347
|
_dirs.pop(-1)
|
|
339
348
|
slug = f"{os.path.sep}".join(_dirs + [_file.replace(".py", "")])
|
|
349
|
+
bundle_examples[slug] = os.path.join(_dir_level[0], _file)
|
|
340
350
|
all_the_examples[slug] = os.path.join(_dir_level[0], _file)
|
|
341
351
|
|
|
352
|
+
with open(meta_saved, "w", encoding="utf-8") as f:
|
|
353
|
+
json.dump(bundle_examples, f)
|
|
354
|
+
bundle_examples.clear()
|
|
355
|
+
|
|
342
356
|
except NotADirectoryError:
|
|
343
357
|
# Bundle does not have new style examples directory
|
|
344
358
|
# so we cannot include its examples.
|
|
@@ -625,3 +639,29 @@ def get_device_path(host, port, password, path):
|
|
|
625
639
|
else:
|
|
626
640
|
device_path = find_device()
|
|
627
641
|
return device_path
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def sorted_by_directory_then_alpha(list_of_files):
|
|
645
|
+
"""
|
|
646
|
+
Sort the list of files into alphabetical seperated
|
|
647
|
+
with directories grouped together before files.
|
|
648
|
+
"""
|
|
649
|
+
dirs = {}
|
|
650
|
+
files = {}
|
|
651
|
+
|
|
652
|
+
for cur_file in list_of_files:
|
|
653
|
+
if cur_file["directory"]:
|
|
654
|
+
dirs[cur_file["name"]] = cur_file
|
|
655
|
+
else:
|
|
656
|
+
files[cur_file["name"]] = cur_file
|
|
657
|
+
|
|
658
|
+
sorted_dir_names = sorted(dirs.keys())
|
|
659
|
+
sorted_file_names = sorted(files.keys())
|
|
660
|
+
|
|
661
|
+
sorted_full_list = []
|
|
662
|
+
for cur_name in sorted_dir_names:
|
|
663
|
+
sorted_full_list.append(dirs[cur_name])
|
|
664
|
+
for cur_name in sorted_file_names:
|
|
665
|
+
sorted_full_list.append(files[cur_name])
|
|
666
|
+
|
|
667
|
+
return sorted_full_list
|
|
@@ -84,7 +84,7 @@ from circup.command_utils import (
|
|
|
84
84
|
"with --board-id, it overrides the detected CPy version.",
|
|
85
85
|
)
|
|
86
86
|
@click.version_option(
|
|
87
|
-
prog_name="
|
|
87
|
+
prog_name="Circup",
|
|
88
88
|
message="%(prog)s, A CircuitPython module updater. Version %(version)s",
|
|
89
89
|
)
|
|
90
90
|
@click.pass_context
|
|
@@ -94,7 +94,7 @@ def main( # pylint: disable=too-many-locals
|
|
|
94
94
|
"""
|
|
95
95
|
A tool to manage and update libraries on a CircuitPython device.
|
|
96
96
|
"""
|
|
97
|
-
# 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
|
|
98
98
|
ctx.ensure_object(dict)
|
|
99
99
|
ctx.obj["TIMEOUT"] = timeout
|
|
100
100
|
|
|
@@ -178,8 +178,8 @@ def main( # pylint: disable=too-many-locals
|
|
|
178
178
|
else (cpy_version, board_id)
|
|
179
179
|
)
|
|
180
180
|
click.echo(
|
|
181
|
-
"Found device at {}, running CircuitPython {}.".format(
|
|
182
|
-
device_path, cpy_version
|
|
181
|
+
"Found device {} at {}, running CircuitPython {}.".format(
|
|
182
|
+
board_id, device_path, cpy_version
|
|
183
183
|
)
|
|
184
184
|
)
|
|
185
185
|
try:
|
|
@@ -406,31 +406,53 @@ def install(
|
|
|
406
406
|
|
|
407
407
|
@main.command()
|
|
408
408
|
@click.option("--overwrite", is_flag=True, help="Overwrite the file if it exists.")
|
|
409
|
+
@click.option("--list", "-ls", "op_list", is_flag=True, help="List available examples.")
|
|
410
|
+
@click.option("--rename", is_flag=True, help="Install the example as code.py.")
|
|
409
411
|
@click.argument(
|
|
410
|
-
"examples", required=
|
|
412
|
+
"examples", required=False, nargs=-1, shell_complete=completion_for_example
|
|
411
413
|
)
|
|
412
414
|
@click.pass_context
|
|
413
|
-
def example(ctx, examples, overwrite):
|
|
415
|
+
def example(ctx, examples, op_list, rename, overwrite):
|
|
414
416
|
"""
|
|
415
417
|
Copy named example(s) from a bundle onto the device. Multiple examples
|
|
416
418
|
can be installed at once by providing more than one example name, each
|
|
417
419
|
separated by a space.
|
|
418
420
|
"""
|
|
419
421
|
|
|
422
|
+
if op_list:
|
|
423
|
+
if examples:
|
|
424
|
+
click.echo("\n".join(completion_for_example(ctx, "", examples)))
|
|
425
|
+
else:
|
|
426
|
+
click.echo("Available example libraries:")
|
|
427
|
+
available_examples = get_bundle_examples(
|
|
428
|
+
get_bundles_list(), avoid_download=True
|
|
429
|
+
)
|
|
430
|
+
lib_names = {
|
|
431
|
+
str(key.split(os.path.sep)[0]): value
|
|
432
|
+
for key, value in available_examples.items()
|
|
433
|
+
}
|
|
434
|
+
click.echo("\n".join(sorted(lib_names.keys())))
|
|
435
|
+
return
|
|
436
|
+
|
|
420
437
|
for example_arg in examples:
|
|
421
438
|
available_examples = get_bundle_examples(
|
|
422
439
|
get_bundles_list(), avoid_download=True
|
|
423
440
|
)
|
|
424
441
|
if example_arg in available_examples:
|
|
425
442
|
filename = available_examples[example_arg].split(os.path.sep)[-1]
|
|
443
|
+
install_metadata = {"path": available_examples[example_arg]}
|
|
444
|
+
|
|
445
|
+
filename = available_examples[example_arg].split(os.path.sep)[-1]
|
|
446
|
+
if rename:
|
|
447
|
+
if os.path.isfile(available_examples[example_arg]):
|
|
448
|
+
filename = "code.py"
|
|
449
|
+
install_metadata["target_name"] = filename
|
|
426
450
|
|
|
427
451
|
if overwrite or not ctx.obj["backend"].file_exists(filename):
|
|
428
452
|
click.echo(
|
|
429
453
|
f"{'Copying' if not overwrite else 'Overwriting'}: {filename}"
|
|
430
454
|
)
|
|
431
|
-
ctx.obj["backend"].install_module_py(
|
|
432
|
-
{"path": available_examples[example_arg]}, location=""
|
|
433
|
-
)
|
|
455
|
+
ctx.obj["backend"].install_module_py(install_metadata, location="")
|
|
434
456
|
else:
|
|
435
457
|
click.secho(
|
|
436
458
|
f"File: {filename} already exists. Use --overwrite if you wish to replace it.",
|
|
@@ -79,14 +79,18 @@ def _get_modules_file(path, logger):
|
|
|
79
79
|
py_files = glob.glob(os.path.join(package_path, "**/*.py"), recursive=True)
|
|
80
80
|
mpy_files = glob.glob(os.path.join(package_path, "**/*.mpy"), recursive=True)
|
|
81
81
|
all_files = py_files + mpy_files
|
|
82
|
+
# put __init__ first if any, assumed to have the version number
|
|
83
|
+
all_files.sort()
|
|
82
84
|
# default value
|
|
83
85
|
result[name] = {"path": package_path, "mpy": bool(mpy_files)}
|
|
84
86
|
# explore all the submodules to detect bad ones
|
|
85
87
|
for source in [f for f in all_files if not os.path.basename(f).startswith(".")]:
|
|
86
88
|
metadata = extract_metadata(source, logger)
|
|
87
89
|
if "__version__" in metadata:
|
|
88
|
-
metadata
|
|
89
|
-
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
|
|
90
94
|
# break now if any of the submodules has a bad format
|
|
91
95
|
if metadata["__version__"] == BAD_FILE_FORMAT:
|
|
92
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
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: circup
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.1
|
|
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
|
|
File without changes
|
|
File without changes
|