circup 1.1.4__tar.gz → 1.2.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-1.1.4 → circup-1.2.0}/.pre-commit-config.yaml +4 -3
- {circup-1.1.4 → circup-1.2.0}/.pylintrc +5 -42
- {circup-1.1.4/circup.egg-info → circup-1.2.0}/PKG-INFO +1 -1
- {circup-1.1.4 → circup-1.2.0}/circup/__init__.py +99 -61
- {circup-1.1.4 → circup-1.2.0/circup.egg-info}/PKG-INFO +1 -1
- {circup-1.1.4 → circup-1.2.0}/circup.egg-info/SOURCES.txt +1 -0
- circup-1.2.0/tests/bad_python.py +6 -0
- {circup-1.1.4 → circup-1.2.0}/tests/test_circup.py +31 -16
- {circup-1.1.4 → circup-1.2.0}/.github/ISSUE_TEMPLATE.md +0 -0
- {circup-1.1.4 → circup-1.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {circup-1.1.4 → circup-1.2.0}/.github/workflows/build.yml +0 -0
- {circup-1.1.4 → circup-1.2.0}/.github/workflows/release.yml +0 -0
- {circup-1.1.4 → circup-1.2.0}/.gitignore +0 -0
- {circup-1.1.4 → circup-1.2.0}/CODE_OF_CONDUCT.rst +0 -0
- {circup-1.1.4 → circup-1.2.0}/CODE_OF_CONDUCT.rst.license +0 -0
- {circup-1.1.4 → circup-1.2.0}/CONTRIBUTING.rst +0 -0
- {circup-1.1.4 → circup-1.2.0}/CONTRIBUTING.rst.license +0 -0
- {circup-1.1.4 → circup-1.2.0}/LICENSE +0 -0
- {circup-1.1.4 → circup-1.2.0}/LICENSES/CC-BY-4.0.txt +0 -0
- {circup-1.1.4 → circup-1.2.0}/LICENSES/MIT.txt +0 -0
- {circup-1.1.4 → circup-1.2.0}/LICENSES/Unlicense.txt +0 -0
- {circup-1.1.4 → circup-1.2.0}/README.rst +0 -0
- {circup-1.1.4 → circup-1.2.0}/README.rst.license +0 -0
- {circup-1.1.4 → circup-1.2.0}/circup/config/bundle_config.json +0 -0
- {circup-1.1.4 → circup-1.2.0}/circup/config/bundle_config.json.license +0 -0
- {circup-1.1.4 → circup-1.2.0}/circup.egg-info/dependency_links.txt +0 -0
- {circup-1.1.4 → circup-1.2.0}/circup.egg-info/entry_points.txt +0 -0
- {circup-1.1.4 → circup-1.2.0}/circup.egg-info/requires.txt +6 -6
- {circup-1.1.4 → circup-1.2.0}/circup.egg-info/top_level.txt +0 -0
- {circup-1.1.4 → circup-1.2.0}/docs/_static/favicon.ico +0 -0
- {circup-1.1.4 → circup-1.2.0}/docs/_static/favicon.ico.license +0 -0
- {circup-1.1.4 → circup-1.2.0}/docs/conf.py +0 -0
- {circup-1.1.4 → circup-1.2.0}/docs/index.rst +0 -0
- {circup-1.1.4 → circup-1.2.0}/docs/index.rst.license +0 -0
- {circup-1.1.4 → circup-1.2.0}/docs/logo.png +0 -0
- {circup-1.1.4 → circup-1.2.0}/docs/logo.png.license +0 -0
- {circup-1.1.4 → circup-1.2.0}/readthedocs.yml +0 -0
- {circup-1.1.4 → circup-1.2.0}/requirements.txt +0 -0
- {circup-1.1.4 → circup-1.2.0}/requirements.txt.license +0 -0
- {circup-1.1.4 → circup-1.2.0}/setup.cfg +0 -0
- {circup-1.1.4 → circup-1.2.0}/setup.py +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/__init__.py +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/bad_module/__init__.py +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/bad_module/my_module.py +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/bundle.json +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/bundle.json.license +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/device.json +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/device.json.license +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/dir_module/__init__.py +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/dir_module/my_module.py +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/import_styles.py +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/local_module.py +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/local_module_cp7.mpy +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/local_module_cp7.mpy.license +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/mount_exists.txt +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/mount_exists.txt.license +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/mount_missing.txt +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/mount_missing.txt.license +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/remote_module.py +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/test_bundle_config.json +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/test_bundle_config.json.license +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/test_bundle_config_local.json +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/test_bundle_config_local.json.license +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/test_module.mpy +0 -0
- {circup-1.1.4 → circup-1.2.0}/tests/test_module.mpy.license +0 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
repos:
|
|
6
6
|
- repo: https://github.com/pycqa/pylint
|
|
7
|
-
rev:
|
|
7
|
+
rev: v2.15.5
|
|
8
8
|
hooks:
|
|
9
9
|
- id: pylint
|
|
10
10
|
name: lint (examples)
|
|
@@ -15,11 +15,12 @@ repos:
|
|
|
15
15
|
- id: pylint
|
|
16
16
|
name: lint (code)
|
|
17
17
|
types: [python]
|
|
18
|
-
exclude: "^(docs/|examples/|setup.py$)"
|
|
18
|
+
exclude: "^(docs/|examples/|setup.py$|tests/bad_python.py$)"
|
|
19
19
|
- repo: https://github.com/python/black
|
|
20
20
|
rev: 22.3.0
|
|
21
21
|
hooks:
|
|
22
|
-
-
|
|
22
|
+
- id: black
|
|
23
|
+
exclude: "^tests/bad_python.py$"
|
|
23
24
|
- repo: https://github.com/fsfe/reuse-tool
|
|
24
25
|
rev: v0.14.0
|
|
25
26
|
hooks:
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
# run arbitrary code
|
|
9
9
|
extension-pkg-whitelist=
|
|
10
10
|
|
|
11
|
-
# Add files or directories to the
|
|
11
|
+
# Add files or directories to the ignore-list. They should be base names, not
|
|
12
12
|
# paths.
|
|
13
13
|
ignore=CVS
|
|
14
14
|
|
|
15
|
-
# Add files or directories matching the regex patterns to the
|
|
15
|
+
# Add files or directories matching the regex patterns to the ignore-list. The
|
|
16
16
|
# regex matches against base names, not paths.
|
|
17
17
|
ignore-patterns=
|
|
18
18
|
|
|
@@ -54,8 +54,8 @@ confidence=
|
|
|
54
54
|
# --enable=similarities". If you want to run only the classes checker, but have
|
|
55
55
|
# no Warning level messages displayed, use"--disable=all --enable=classes
|
|
56
56
|
# --disable=W"
|
|
57
|
-
|
|
58
|
-
disable=
|
|
57
|
+
|
|
58
|
+
disable=too-many-lines, consider-using-f-string, use-dict-literal, global-statement, invalid-name, fixme, import-error
|
|
59
59
|
|
|
60
60
|
# Enable the message, report, category or checker with the given id(s). You can
|
|
61
61
|
# either give multiple identifier separated by comma (,) or put this option
|
|
@@ -225,12 +225,6 @@ max-line-length=100
|
|
|
225
225
|
# Maximum number of lines in a module
|
|
226
226
|
max-module-lines=1000
|
|
227
227
|
|
|
228
|
-
# List of optional constructs for which whitespace checking is disabled. `dict-
|
|
229
|
-
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
|
|
230
|
-
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
|
|
231
|
-
# `empty-line` allows space-only lines.
|
|
232
|
-
no-space-check=trailing-comma,dict-separator
|
|
233
|
-
|
|
234
228
|
# Allow the body of a class to be on the same line as the declaration if body
|
|
235
229
|
# contains single statement.
|
|
236
230
|
single-line-class-stmt=no
|
|
@@ -249,7 +243,7 @@ ignore-comments=yes
|
|
|
249
243
|
ignore-docstrings=yes
|
|
250
244
|
|
|
251
245
|
# Ignore imports when computing similarities.
|
|
252
|
-
ignore-imports=
|
|
246
|
+
ignore-imports=yes
|
|
253
247
|
|
|
254
248
|
# Minimum lines number of a similarity.
|
|
255
249
|
min-similarity-lines=4
|
|
@@ -257,38 +251,22 @@ min-similarity-lines=4
|
|
|
257
251
|
|
|
258
252
|
[BASIC]
|
|
259
253
|
|
|
260
|
-
# Naming hint for argument names
|
|
261
|
-
argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
|
|
262
|
-
|
|
263
254
|
# Regular expression matching correct argument names
|
|
264
255
|
argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
|
|
265
256
|
|
|
266
|
-
# Naming hint for attribute names
|
|
267
|
-
attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
|
|
268
|
-
|
|
269
257
|
# Regular expression matching correct attribute names
|
|
270
258
|
attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
|
|
271
259
|
|
|
272
260
|
# Bad variable names which should always be refused, separated by a comma
|
|
273
261
|
bad-names=foo,bar,baz,toto,tutu,tata
|
|
274
262
|
|
|
275
|
-
# Naming hint for class attribute names
|
|
276
|
-
class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
|
|
277
|
-
|
|
278
263
|
# Regular expression matching correct class attribute names
|
|
279
264
|
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
|
|
280
265
|
|
|
281
|
-
# Naming hint for class names
|
|
282
|
-
# class-name-hint=[A-Z_][a-zA-Z0-9]+$
|
|
283
|
-
class-name-hint=[A-Z_][a-zA-Z0-9_]+$
|
|
284
|
-
|
|
285
266
|
# Regular expression matching correct class names
|
|
286
267
|
# class-rgx=[A-Z_][a-zA-Z0-9]+$
|
|
287
268
|
class-rgx=[A-Z_][a-zA-Z0-9_]+$
|
|
288
269
|
|
|
289
|
-
# Naming hint for constant names
|
|
290
|
-
const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
|
|
291
|
-
|
|
292
270
|
# Regular expression matching correct constant names
|
|
293
271
|
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
|
|
294
272
|
|
|
@@ -296,9 +274,6 @@ const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
|
|
|
296
274
|
# ones are exempt.
|
|
297
275
|
docstring-min-length=-1
|
|
298
276
|
|
|
299
|
-
# Naming hint for function names
|
|
300
|
-
function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
|
|
301
|
-
|
|
302
277
|
# Regular expression matching correct function names
|
|
303
278
|
function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
|
|
304
279
|
|
|
@@ -309,21 +284,12 @@ good-names=r,g,b,w,i,j,k,n,x,y,z,ex,ok,Run,_
|
|
|
309
284
|
# Include a hint for the correct naming format with invalid-name
|
|
310
285
|
include-naming-hint=no
|
|
311
286
|
|
|
312
|
-
# Naming hint for inline iteration names
|
|
313
|
-
inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
|
|
314
|
-
|
|
315
287
|
# Regular expression matching correct inline iteration names
|
|
316
288
|
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
|
|
317
289
|
|
|
318
|
-
# Naming hint for method names
|
|
319
|
-
method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
|
|
320
|
-
|
|
321
290
|
# Regular expression matching correct method names
|
|
322
291
|
method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
|
|
323
292
|
|
|
324
|
-
# Naming hint for module names
|
|
325
|
-
module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
|
326
|
-
|
|
327
293
|
# Regular expression matching correct module names
|
|
328
294
|
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
|
329
295
|
|
|
@@ -339,9 +305,6 @@ no-docstring-rgx=^_
|
|
|
339
305
|
# to this list to register other decorators that produce valid properties.
|
|
340
306
|
property-classes=abc.abstractproperty
|
|
341
307
|
|
|
342
|
-
# Naming hint for variable names
|
|
343
|
-
variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
|
|
344
|
-
|
|
345
308
|
# Regular expression matching correct variable names
|
|
346
309
|
variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
|
|
347
310
|
|
|
@@ -63,6 +63,8 @@ PLATFORMS = {"py": "py", "7mpy": "7.x-mpy", "8mpy": "7.x-mpy"}
|
|
|
63
63
|
BOARDLESS_COMMANDS = ["show", "bundle-add", "bundle-remove", "bundle-show"]
|
|
64
64
|
#: Version identifier for a bad MPY file format
|
|
65
65
|
BAD_FILE_FORMAT = "Invalid"
|
|
66
|
+
#: Timeout for requests calls like get()
|
|
67
|
+
REQUESTS_TIMEOUT = 30
|
|
66
68
|
|
|
67
69
|
# Ensure DATA_DIR / LOG_DIR related directories and files exist.
|
|
68
70
|
if not os.path.exists(DATA_DIR): # pragma: no cover
|
|
@@ -143,7 +145,7 @@ class Bundle:
|
|
|
143
145
|
"requirements.txt",
|
|
144
146
|
)
|
|
145
147
|
if os.path.isfile(requirements_txt):
|
|
146
|
-
with open(requirements_txt, "r") as read_this:
|
|
148
|
+
with open(requirements_txt, "r", encoding="utf-8") as read_this:
|
|
147
149
|
return read_this.read()
|
|
148
150
|
return None
|
|
149
151
|
|
|
@@ -190,7 +192,7 @@ class Bundle:
|
|
|
190
192
|
return False
|
|
191
193
|
for platform in PLATFORMS.values():
|
|
192
194
|
url = self.url_format.format(platform=platform, tag=tag)
|
|
193
|
-
r = requests.get(url, stream=True)
|
|
195
|
+
r = requests.get(url, stream=True, timeout=REQUESTS_TIMEOUT)
|
|
194
196
|
# pylint: disable=no-member
|
|
195
197
|
if r.status_code != requests.codes.ok:
|
|
196
198
|
if VERBOSE:
|
|
@@ -438,7 +440,7 @@ def clean_library_name(assumed_library_name):
|
|
|
438
440
|
.replace("_circuitpython_", "_")
|
|
439
441
|
.replace("-", "_")
|
|
440
442
|
)
|
|
441
|
-
if assumed_library_name in not_standard_names
|
|
443
|
+
if assumed_library_name in not_standard_names:
|
|
442
444
|
return not_standard_names[assumed_library_name]
|
|
443
445
|
return assumed_library_name
|
|
444
446
|
|
|
@@ -516,7 +518,7 @@ def extract_metadata(path):
|
|
|
516
518
|
logger.info("%s", path)
|
|
517
519
|
if path.endswith(".py"):
|
|
518
520
|
result["mpy"] = False
|
|
519
|
-
with open(path, encoding="utf-8") as source_file:
|
|
521
|
+
with open(path, "r", encoding="utf-8") as source_file:
|
|
520
522
|
content = source_file.read()
|
|
521
523
|
#: The regex used to extract ``__version__`` and ``__repo__`` assignments.
|
|
522
524
|
dunder_key_val = r"""(__\w+__)(?:\s*:\s*\w+)?\s*=\s*(?:['"]|\(\s)(.+)['"]"""
|
|
@@ -692,7 +694,7 @@ def get_bundle(bundle, tag):
|
|
|
692
694
|
for platform, github_string in PLATFORMS.items():
|
|
693
695
|
url = bundle.url_format.format(platform=github_string, tag=tag)
|
|
694
696
|
logger.info("Downloading bundle: %s", url)
|
|
695
|
-
r = requests.get(url, stream=True)
|
|
697
|
+
r = requests.get(url, stream=True, timeout=REQUESTS_TIMEOUT)
|
|
696
698
|
# pylint: disable=no-member
|
|
697
699
|
if r.status_code != requests.codes.ok:
|
|
698
700
|
logger.warning("Unable to connect to %s", url)
|
|
@@ -702,9 +704,9 @@ def get_bundle(bundle, tag):
|
|
|
702
704
|
temp_zip = bundle.zip.format(platform=platform)
|
|
703
705
|
with click.progressbar(r.iter_content(1024), length=total_size) as pbar, open(
|
|
704
706
|
temp_zip, "wb"
|
|
705
|
-
) as
|
|
707
|
+
) as zip_fp:
|
|
706
708
|
for chunk in pbar:
|
|
707
|
-
|
|
709
|
+
zip_fp.write(chunk)
|
|
708
710
|
pbar.update(len(chunk))
|
|
709
711
|
logger.info("Saved to %s", temp_zip)
|
|
710
712
|
temp_dir = bundle.dir.format(platform=platform)
|
|
@@ -750,10 +752,10 @@ def get_bundles_dict():
|
|
|
750
752
|
"""
|
|
751
753
|
bundle_dict = get_bundles_local_dict()
|
|
752
754
|
try:
|
|
753
|
-
with open(BUNDLE_CONFIG_OVERWRITE) as bundle_config_json:
|
|
755
|
+
with open(BUNDLE_CONFIG_OVERWRITE, "rb") as bundle_config_json:
|
|
754
756
|
bundle_config = json.load(bundle_config_json)
|
|
755
757
|
except (FileNotFoundError, json.decoder.JSONDecodeError):
|
|
756
|
-
with open(BUNDLE_CONFIG_FILE) as bundle_config_json:
|
|
758
|
+
with open(BUNDLE_CONFIG_FILE, "rb") as bundle_config_json:
|
|
757
759
|
bundle_config = json.load(bundle_config_json)
|
|
758
760
|
for name, bundle in bundle_config.items():
|
|
759
761
|
if bundle not in bundle_dict.values():
|
|
@@ -768,7 +770,7 @@ def get_bundles_local_dict():
|
|
|
768
770
|
:return: Raw dictionary from the config file(s).
|
|
769
771
|
"""
|
|
770
772
|
try:
|
|
771
|
-
with open(BUNDLE_CONFIG_LOCAL) as bundle_config_json:
|
|
773
|
+
with open(BUNDLE_CONFIG_LOCAL, "rb") as bundle_config_json:
|
|
772
774
|
bundle_config = json.load(bundle_config_json)
|
|
773
775
|
if not isinstance(bundle_config, dict) or not bundle_config:
|
|
774
776
|
logger.error("Local bundle list invalid. Skipped.")
|
|
@@ -807,7 +809,9 @@ def get_circuitpython_version(device_path):
|
|
|
807
809
|
:return: A tuple with the version string for CircuitPython and the board ID string.
|
|
808
810
|
"""
|
|
809
811
|
try:
|
|
810
|
-
with open(
|
|
812
|
+
with open(
|
|
813
|
+
os.path.join(device_path, "boot_out.txt"), "r", encoding="utf-8"
|
|
814
|
+
) as boot:
|
|
811
815
|
version_line = boot.readline()
|
|
812
816
|
circuit_python = version_line.split(";")[0].split(" ")[-3]
|
|
813
817
|
board_line = boot.readline()
|
|
@@ -885,7 +889,7 @@ def get_dependencies(*requested_libraries, mod_names, to_install=()):
|
|
|
885
889
|
# If nothing is requested, we're done
|
|
886
890
|
return _to_install
|
|
887
891
|
|
|
888
|
-
for library in _requested_libraries:
|
|
892
|
+
for library in list(_requested_libraries):
|
|
889
893
|
if library not in _to_install:
|
|
890
894
|
_to_install = _to_install + (library,)
|
|
891
895
|
# get the requirements.txt from bundle
|
|
@@ -923,7 +927,7 @@ def get_latest_release_from_url(url):
|
|
|
923
927
|
"""
|
|
924
928
|
|
|
925
929
|
logger.info("Requesting redirect information: %s", url)
|
|
926
|
-
response = requests.head(url)
|
|
930
|
+
response = requests.head(url, timeout=REQUESTS_TIMEOUT)
|
|
927
931
|
responseurl = response.url
|
|
928
932
|
if response.is_redirect:
|
|
929
933
|
responseurl = response.headers["Location"]
|
|
@@ -945,7 +949,7 @@ def get_modules(path):
|
|
|
945
949
|
return result
|
|
946
950
|
single_file_py_mods = glob.glob(os.path.join(path, "*.py"))
|
|
947
951
|
single_file_mpy_mods = glob.glob(os.path.join(path, "*.mpy"))
|
|
948
|
-
|
|
952
|
+
package_dir_mods = [
|
|
949
953
|
d
|
|
950
954
|
for d in glob.glob(os.path.join(path, "*", ""))
|
|
951
955
|
if not os.path.basename(os.path.normpath(d)).startswith(".")
|
|
@@ -955,18 +959,18 @@ def get_modules(path):
|
|
|
955
959
|
metadata = extract_metadata(sfm)
|
|
956
960
|
metadata["path"] = sfm
|
|
957
961
|
result[os.path.basename(sfm).replace(".py", "").replace(".mpy", "")] = metadata
|
|
958
|
-
for
|
|
959
|
-
name = os.path.basename(os.path.dirname(
|
|
960
|
-
py_files = glob.glob(os.path.join(
|
|
961
|
-
mpy_files = glob.glob(os.path.join(
|
|
962
|
+
for package_path in package_dir_mods:
|
|
963
|
+
name = os.path.basename(os.path.dirname(package_path))
|
|
964
|
+
py_files = glob.glob(os.path.join(package_path, "**/*.py"), recursive=True)
|
|
965
|
+
mpy_files = glob.glob(os.path.join(package_path, "**/*.mpy"), recursive=True)
|
|
962
966
|
all_files = py_files + mpy_files
|
|
963
967
|
# default value
|
|
964
|
-
result[name] = {"path":
|
|
968
|
+
result[name] = {"path": package_path, "mpy": bool(mpy_files)}
|
|
965
969
|
# explore all the submodules to detect bad ones
|
|
966
970
|
for source in [f for f in all_files if not os.path.basename(f).startswith(".")]:
|
|
967
971
|
metadata = extract_metadata(source)
|
|
968
972
|
if "__version__" in metadata:
|
|
969
|
-
metadata["path"] =
|
|
973
|
+
metadata["path"] = package_path
|
|
970
974
|
result[name] = metadata
|
|
971
975
|
# break now if any of the submodules has a bad format
|
|
972
976
|
if metadata["__version__"] == BAD_FILE_FORMAT:
|
|
@@ -976,7 +980,7 @@ def get_modules(path):
|
|
|
976
980
|
|
|
977
981
|
# pylint: disable=too-many-locals,too-many-branches
|
|
978
982
|
def install_module(
|
|
979
|
-
device_path, device_modules, name,
|
|
983
|
+
device_path, device_modules, name, pyext, mod_names
|
|
980
984
|
): # pragma: no cover
|
|
981
985
|
"""
|
|
982
986
|
Finds a connected device and installs a given module name if it
|
|
@@ -987,7 +991,7 @@ def install_module(
|
|
|
987
991
|
:param str device_path: The path to the connected board.
|
|
988
992
|
:param list(dict) device_modules: List of module metadata from the device.
|
|
989
993
|
:param str name: Name of module to install
|
|
990
|
-
:param bool
|
|
994
|
+
:param bool pyext: Boolean to specify if the module should be installed from
|
|
991
995
|
source or from a pre-compiled module
|
|
992
996
|
:param mod_names: Dictionary of metadata from modules that can be generated
|
|
993
997
|
with get_bundle_versions()
|
|
@@ -1004,7 +1008,7 @@ def install_module(
|
|
|
1004
1008
|
if name in device_modules:
|
|
1005
1009
|
click.echo("'{}' is already installed.".format(name))
|
|
1006
1010
|
return
|
|
1007
|
-
if
|
|
1011
|
+
if pyext:
|
|
1008
1012
|
# Use Python source for module.
|
|
1009
1013
|
source_path = metadata["path"] # Path to Python source version.
|
|
1010
1014
|
if os.path.isdir(source_path):
|
|
@@ -1052,7 +1056,15 @@ def libraries_from_imports(code_py, mod_names):
|
|
|
1052
1056
|
:param str code_py: Full path of the code.py file
|
|
1053
1057
|
:return: sequence of library names
|
|
1054
1058
|
"""
|
|
1055
|
-
|
|
1059
|
+
# pylint: disable=broad-except
|
|
1060
|
+
try:
|
|
1061
|
+
found_imports = findimports.find_imports(code_py)
|
|
1062
|
+
except Exception as ex: # broad exception because anything could go wrong
|
|
1063
|
+
logger.exception(ex)
|
|
1064
|
+
click.secho('Unable to read the auto file: "{}"'.format(str(ex)), fg="red")
|
|
1065
|
+
sys.exit(2)
|
|
1066
|
+
# pylint: enable=broad-except
|
|
1067
|
+
imports = [info.name.split(".", 1)[0] for info in found_imports]
|
|
1056
1068
|
return [r for r in imports if r in mod_names]
|
|
1057
1069
|
|
|
1058
1070
|
|
|
@@ -1232,7 +1244,9 @@ def freeze(ctx, requirement): # pragma: no cover
|
|
|
1232
1244
|
cwd = os.path.abspath(os.getcwd())
|
|
1233
1245
|
for i, module in enumerate(output):
|
|
1234
1246
|
output[i] += "\n"
|
|
1235
|
-
with open(
|
|
1247
|
+
with open(
|
|
1248
|
+
cwd + "/" + "requirements.txt", "w", newline="\n", encoding="utf-8"
|
|
1249
|
+
) as file:
|
|
1236
1250
|
file.truncate(0)
|
|
1237
1251
|
file.writelines(output)
|
|
1238
1252
|
else:
|
|
@@ -1284,23 +1298,32 @@ def list_cli(ctx): # pragma: no cover
|
|
|
1284
1298
|
@click.argument(
|
|
1285
1299
|
"modules", required=False, nargs=-1, shell_complete=completion_for_install
|
|
1286
1300
|
)
|
|
1287
|
-
@click.option(
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1301
|
+
@click.option(
|
|
1302
|
+
"pyext",
|
|
1303
|
+
"--py",
|
|
1304
|
+
is_flag=True,
|
|
1305
|
+
help="Install the .py version of the module(s) instead of the mpy version.",
|
|
1306
|
+
)
|
|
1307
|
+
@click.option(
|
|
1308
|
+
"-r",
|
|
1309
|
+
"--requirement",
|
|
1310
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
1311
|
+
help="specify a text file to install all modules listed in the text file."
|
|
1312
|
+
" Typically requirements.txt.",
|
|
1313
|
+
)
|
|
1314
|
+
@click.option("--auto", "-a", help="Install the modules imported in code.py.")
|
|
1315
|
+
@click.option(
|
|
1316
|
+
"--auto-file",
|
|
1317
|
+
default=None,
|
|
1318
|
+
help="Specify the name of a file on the board to read for auto install."
|
|
1319
|
+
" Also accepts an absolute path or a local ./ path.",
|
|
1320
|
+
)
|
|
1291
1321
|
@click.pass_context
|
|
1292
|
-
def install(ctx, modules,
|
|
1322
|
+
def install(ctx, modules, pyext, requirement, auto, auto_file): # pragma: no cover
|
|
1293
1323
|
"""
|
|
1294
1324
|
Install a named module(s) onto the device. Multiple modules
|
|
1295
1325
|
can be installed at once by providing more than one module name, each
|
|
1296
1326
|
separated by a space.
|
|
1297
|
-
|
|
1298
|
-
Option --py installs .py version of module(s).
|
|
1299
|
-
|
|
1300
|
-
Option -r allows specifying a text file to install all modules listed in
|
|
1301
|
-
the text file.
|
|
1302
|
-
|
|
1303
|
-
Option -a installs based on the modules imported by code.py
|
|
1304
1327
|
"""
|
|
1305
1328
|
# TODO: Ensure there's enough space on the device
|
|
1306
1329
|
available_modules = get_bundle_versions(get_bundles_list())
|
|
@@ -1308,11 +1331,19 @@ def install(ctx, modules, py, requirement, auto, auto_file): # pragma: no cover
|
|
|
1308
1331
|
for module, metadata in available_modules.items():
|
|
1309
1332
|
mod_names[module.replace(".py", "").lower()] = metadata
|
|
1310
1333
|
if requirement:
|
|
1311
|
-
with open(requirement, "r") as
|
|
1312
|
-
requirements_txt =
|
|
1334
|
+
with open(requirement, "r", encoding="utf-8") as rfile:
|
|
1335
|
+
requirements_txt = rfile.read()
|
|
1313
1336
|
requested_installs = libraries_from_requirements(requirements_txt)
|
|
1314
|
-
elif auto:
|
|
1315
|
-
auto_file
|
|
1337
|
+
elif auto or auto_file:
|
|
1338
|
+
if auto_file is None:
|
|
1339
|
+
auto_file = "code.py"
|
|
1340
|
+
# pass a local file with "./" or "../"
|
|
1341
|
+
is_relative = auto_file.split(os.sep)[0] in [os.path.curdir, os.path.pardir]
|
|
1342
|
+
if not os.path.isabs(auto_file) and not is_relative:
|
|
1343
|
+
auto_file = os.path.join(ctx.obj["DEVICE_PATH"], auto_file or "code.py")
|
|
1344
|
+
if not os.path.isfile(auto_file):
|
|
1345
|
+
click.secho(f"Auto file not found: {auto_file}", fg="red")
|
|
1346
|
+
sys.exit(1)
|
|
1316
1347
|
requested_installs = libraries_from_imports(auto_file, mod_names)
|
|
1317
1348
|
else:
|
|
1318
1349
|
requested_installs = modules
|
|
@@ -1325,7 +1356,7 @@ def install(ctx, modules, py, requirement, auto, auto_file): # pragma: no cover
|
|
|
1325
1356
|
click.echo(f"Ready to install: {to_install}\n")
|
|
1326
1357
|
for library in to_install:
|
|
1327
1358
|
install_module(
|
|
1328
|
-
ctx.obj["DEVICE_PATH"], device_modules, library,
|
|
1359
|
+
ctx.obj["DEVICE_PATH"], device_modules, library, pyext, mod_names
|
|
1329
1360
|
)
|
|
1330
1361
|
|
|
1331
1362
|
|
|
@@ -1397,10 +1428,13 @@ def uninstall(ctx, module): # pragma: no cover
|
|
|
1397
1428
|
)
|
|
1398
1429
|
)
|
|
1399
1430
|
@click.option(
|
|
1400
|
-
"
|
|
1431
|
+
"update_all",
|
|
1432
|
+
"--all",
|
|
1433
|
+
is_flag=True,
|
|
1434
|
+
help="Update all modules without Major Version warnings.",
|
|
1401
1435
|
)
|
|
1402
1436
|
@click.pass_context
|
|
1403
|
-
def update(ctx,
|
|
1437
|
+
def update(ctx, update_all): # pragma: no cover
|
|
1404
1438
|
"""
|
|
1405
1439
|
Checks for out-of-date modules on the connected CIRCUITPYTHON device, and
|
|
1406
1440
|
prompts the user to confirm updating such modules.
|
|
@@ -1414,10 +1448,10 @@ def update(ctx, all): # pragma: no cover
|
|
|
1414
1448
|
]
|
|
1415
1449
|
if modules:
|
|
1416
1450
|
click.echo("Found {} module[s] needing update.".format(len(modules)))
|
|
1417
|
-
if not
|
|
1451
|
+
if not update_all:
|
|
1418
1452
|
click.echo("Please indicate which modules you wish to update:\n")
|
|
1419
1453
|
for module in modules:
|
|
1420
|
-
update_flag =
|
|
1454
|
+
update_flag = update_all
|
|
1421
1455
|
if VERBOSE:
|
|
1422
1456
|
click.echo(
|
|
1423
1457
|
"Device version: {}, Bundle version: {}".format(
|
|
@@ -1484,12 +1518,12 @@ def bundle_show(modules):
|
|
|
1484
1518
|
Show the list of bundles, default and local, with URL, current version
|
|
1485
1519
|
and latest version retrieved from the web.
|
|
1486
1520
|
"""
|
|
1487
|
-
|
|
1521
|
+
local_bundles = get_bundles_local_dict().values()
|
|
1488
1522
|
bundles = get_bundles_list()
|
|
1489
1523
|
available_modules = get_bundle_versions(bundles)
|
|
1490
1524
|
|
|
1491
1525
|
for bundle in bundles:
|
|
1492
|
-
if bundle.key in
|
|
1526
|
+
if bundle.key in local_bundles:
|
|
1493
1527
|
click.secho(bundle.key, fg="yellow")
|
|
1494
1528
|
else:
|
|
1495
1529
|
click.secho(bundle.key, fg="green")
|
|
@@ -1513,40 +1547,44 @@ def bundle_add(bundle):
|
|
|
1513
1547
|
"""
|
|
1514
1548
|
bundles_dict = get_bundles_local_dict()
|
|
1515
1549
|
modified = False
|
|
1516
|
-
for
|
|
1550
|
+
for bundle_repo in bundle:
|
|
1517
1551
|
# cleanup in case seombody pastes the URL to the repo/releases
|
|
1518
|
-
|
|
1519
|
-
|
|
1552
|
+
bundle_repo = re.sub(
|
|
1553
|
+
r"https?://github.com/([^/]+/[^/]+)(/.*)?", r"\1", bundle_repo
|
|
1554
|
+
)
|
|
1555
|
+
if bundle_repo in bundles_dict.values():
|
|
1520
1556
|
click.secho("Bundle already in list.", fg="yellow")
|
|
1521
|
-
click.secho(" " +
|
|
1557
|
+
click.secho(" " + bundle_repo, fg="yellow")
|
|
1522
1558
|
continue
|
|
1523
1559
|
try:
|
|
1524
|
-
|
|
1560
|
+
bundle_added = Bundle(bundle_repo)
|
|
1525
1561
|
except ValueError:
|
|
1526
1562
|
click.secho(
|
|
1527
1563
|
"Bundle string invalid, expecting github URL or `user/repository` string.",
|
|
1528
1564
|
fg="red",
|
|
1529
1565
|
)
|
|
1530
|
-
click.secho(" " +
|
|
1566
|
+
click.secho(" " + bundle_repo, fg="red")
|
|
1531
1567
|
continue
|
|
1532
|
-
result = requests.get(
|
|
1568
|
+
result = requests.get(
|
|
1569
|
+
"https://github.com/" + bundle_repo, timeout=REQUESTS_TIMEOUT
|
|
1570
|
+
)
|
|
1533
1571
|
# pylint: disable=no-member
|
|
1534
1572
|
if result.status_code == requests.codes.NOT_FOUND:
|
|
1535
1573
|
click.secho("Bundle invalid, the repository doesn't exist (404).", fg="red")
|
|
1536
|
-
click.secho(" " +
|
|
1574
|
+
click.secho(" " + bundle_repo, fg="red")
|
|
1537
1575
|
continue
|
|
1538
1576
|
# pylint: enable=no-member
|
|
1539
|
-
if not
|
|
1577
|
+
if not bundle_added.validate():
|
|
1540
1578
|
click.secho(
|
|
1541
1579
|
"Bundle invalid, is the repository a valid circup bundle ?", fg="red"
|
|
1542
1580
|
)
|
|
1543
|
-
click.secho(" " +
|
|
1581
|
+
click.secho(" " + bundle_repo, fg="red")
|
|
1544
1582
|
continue
|
|
1545
1583
|
# note: use bun as the dictionary key for uniqueness
|
|
1546
|
-
bundles_dict[
|
|
1584
|
+
bundles_dict[bundle_repo] = bundle_repo
|
|
1547
1585
|
modified = True
|
|
1548
|
-
click.echo("Added " +
|
|
1549
|
-
click.echo(" " +
|
|
1586
|
+
click.echo("Added " + bundle_repo)
|
|
1587
|
+
click.echo(" " + bundle_added.url)
|
|
1550
1588
|
if modified:
|
|
1551
1589
|
# save the bundles list
|
|
1552
1590
|
save_local_bundles(bundles_dict)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# SPDX-FileCopyrightText: 2019 Nicholas Tollervey, written for Adafruit Industries
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: MIT
|
|
4
|
+
# pylint: disable=too-many-lines,use-implicit-booleaness-not-comparison
|
|
5
|
+
# pylint: disable=invalid-name,unnecessary-dunder-call
|
|
4
6
|
"""
|
|
5
7
|
Unit tests for the circup module.
|
|
6
8
|
|
|
@@ -39,12 +41,12 @@ import circup
|
|
|
39
41
|
|
|
40
42
|
|
|
41
43
|
TEST_BUNDLE_CONFIG_JSON = "tests/test_bundle_config.json"
|
|
42
|
-
with open(TEST_BUNDLE_CONFIG_JSON) as tbc:
|
|
44
|
+
with open(TEST_BUNDLE_CONFIG_JSON, "rb") as tbc:
|
|
43
45
|
TEST_BUNDLE_DATA = json.load(tbc)
|
|
44
46
|
TEST_BUNDLE_NAME = TEST_BUNDLE_DATA["test_bundle"]
|
|
45
47
|
|
|
46
48
|
TEST_BUNDLE_CONFIG_LOCAL_JSON = "tests/test_bundle_config_local.json"
|
|
47
|
-
with open(TEST_BUNDLE_CONFIG_LOCAL_JSON) as tbc:
|
|
49
|
+
with open(TEST_BUNDLE_CONFIG_LOCAL_JSON, "rb") as tbc:
|
|
48
50
|
TEST_BUNDLE_LOCAL_DATA = json.load(tbc)
|
|
49
51
|
|
|
50
52
|
|
|
@@ -546,7 +548,7 @@ def test_get_latest_release_from_url():
|
|
|
546
548
|
with mock.patch("circup.requests.head", return_value=response) as mock_get:
|
|
547
549
|
result = circup.get_latest_release_from_url(expected_url)
|
|
548
550
|
assert result == "20190903"
|
|
549
|
-
mock_get.assert_called_once_with(expected_url)
|
|
551
|
+
mock_get.assert_called_once_with(expected_url, timeout=mock.ANY)
|
|
550
552
|
|
|
551
553
|
|
|
552
554
|
def test_extract_metadata_python():
|
|
@@ -563,7 +565,7 @@ def test_extract_metadata_python():
|
|
|
563
565
|
path = "foo.py"
|
|
564
566
|
with mock.patch("builtins.open", mock.mock_open(read_data=code)) as mock_open:
|
|
565
567
|
result = circup.extract_metadata(path)
|
|
566
|
-
mock_open.assert_called_once_with(path, encoding="utf-8")
|
|
568
|
+
mock_open.assert_called_once_with(path, "r", encoding="utf-8")
|
|
567
569
|
assert len(result) == 3
|
|
568
570
|
assert result["__version__"] == "1.1.4"
|
|
569
571
|
assert result["__repo__"] == "https://github.com/adafruit/SomeLibrary.git"
|
|
@@ -598,9 +600,9 @@ def test_find_modules():
|
|
|
598
600
|
Ensure that the expected list of Module instances is returned given the
|
|
599
601
|
metadata dictionary fixtures for device and bundle modules.
|
|
600
602
|
"""
|
|
601
|
-
with open("tests/device.json") as f:
|
|
603
|
+
with open("tests/device.json", "rb") as f:
|
|
602
604
|
device_modules = json.load(f)
|
|
603
|
-
with open("tests/bundle.json") as f:
|
|
605
|
+
with open("tests/bundle.json", "rb") as f:
|
|
604
606
|
bundle_modules = json.load(f)
|
|
605
607
|
with mock.patch(
|
|
606
608
|
"circup.get_device_versions", return_value=device_modules
|
|
@@ -698,7 +700,9 @@ def test_get_circuitpython_version():
|
|
|
698
700
|
)
|
|
699
701
|
with mock.patch("builtins.open", mock.mock_open(read_data=data_no_id)) as mock_open:
|
|
700
702
|
assert circup.get_circuitpython_version(device_path) == ("4.1.0", "")
|
|
701
|
-
mock_open.assert_called_once_with(
|
|
703
|
+
mock_open.assert_called_once_with(
|
|
704
|
+
os.path.join(device_path, "boot_out.txt"), "r", encoding="utf-8"
|
|
705
|
+
)
|
|
702
706
|
data_with_id = data_no_id + "\r\n" "Board ID:this_is_a_board"
|
|
703
707
|
with mock.patch(
|
|
704
708
|
"builtins.open", mock.mock_open(read_data=data_with_id)
|
|
@@ -707,7 +711,9 @@ def test_get_circuitpython_version():
|
|
|
707
711
|
"4.1.0",
|
|
708
712
|
"this_is_a_board",
|
|
709
713
|
)
|
|
710
|
-
mock_open.assert_called_once_with(
|
|
714
|
+
mock_open.assert_called_once_with(
|
|
715
|
+
os.path.join(device_path, "boot_out.txt"), "r", encoding="utf-8"
|
|
716
|
+
)
|
|
711
717
|
|
|
712
718
|
|
|
713
719
|
def test_get_device_versions():
|
|
@@ -961,7 +967,7 @@ def test_get_bundle_network_error():
|
|
|
961
967
|
"https://github.com/" + TEST_BUNDLE_NAME + "/releases/download"
|
|
962
968
|
"/{tag}/adafruit-circuitpython-bundle-py-{tag}.zip".format(tag=tag)
|
|
963
969
|
)
|
|
964
|
-
mock_requests.get.assert_called_once_with(url, stream=True)
|
|
970
|
+
mock_requests.get.assert_called_once_with(url, stream=True, timeout=mock.ANY)
|
|
965
971
|
assert mock_logger.warning.call_count == 1
|
|
966
972
|
mock_requests.get().raise_for_status.assert_called_once_with()
|
|
967
973
|
|
|
@@ -971,11 +977,11 @@ def test_show_command():
|
|
|
971
977
|
test_show_command
|
|
972
978
|
"""
|
|
973
979
|
runner = CliRunner()
|
|
974
|
-
|
|
975
|
-
with mock.patch("circup.get_bundle_versions", return_value=
|
|
980
|
+
test_bundle_modules = ["one.py", "two.py", "three.py"]
|
|
981
|
+
with mock.patch("circup.get_bundle_versions", return_value=test_bundle_modules):
|
|
976
982
|
result = runner.invoke(circup.show)
|
|
977
983
|
assert result.exit_code == 0
|
|
978
|
-
assert all(
|
|
984
|
+
assert all(m.replace(".py", "") in result.output for m in test_bundle_modules)
|
|
979
985
|
|
|
980
986
|
|
|
981
987
|
def test_show_match_command():
|
|
@@ -983,8 +989,8 @@ def test_show_match_command():
|
|
|
983
989
|
test_show_match_command
|
|
984
990
|
"""
|
|
985
991
|
runner = CliRunner()
|
|
986
|
-
|
|
987
|
-
with mock.patch("circup.get_bundle_versions", return_value=
|
|
992
|
+
test_bundle_modules = ["one.py", "two.py", "three.py"]
|
|
993
|
+
with mock.patch("circup.get_bundle_versions", return_value=test_bundle_modules):
|
|
988
994
|
result = runner.invoke(circup.show, ["t"])
|
|
989
995
|
assert result.exit_code == 0
|
|
990
996
|
assert "one" not in result.output
|
|
@@ -995,8 +1001,8 @@ def test_show_match_py_command():
|
|
|
995
1001
|
Check that py does not match the .py extention in the module names
|
|
996
1002
|
"""
|
|
997
1003
|
runner = CliRunner()
|
|
998
|
-
|
|
999
|
-
with mock.patch("circup.get_bundle_versions", return_value=
|
|
1004
|
+
test_bundle_modules = ["one.py", "two.py", "three.py"]
|
|
1005
|
+
with mock.patch("circup.get_bundle_versions", return_value=test_bundle_modules):
|
|
1000
1006
|
result = runner.invoke(circup.show, ["py"])
|
|
1001
1007
|
assert result.exit_code == 0
|
|
1002
1008
|
assert "0 shown" in result.output
|
|
@@ -1024,3 +1030,12 @@ def test_libraries_from_imports():
|
|
|
1024
1030
|
"adafruit_esp32spi",
|
|
1025
1031
|
"adafruit_hid",
|
|
1026
1032
|
]
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
def test_libraries_from_imports_bad():
|
|
1036
|
+
"""Ensure that we catch an import error"""
|
|
1037
|
+
TEST_BUNDLE_MODULES = {"one.py": {}, "two.py": {}, "three.py": {}}
|
|
1038
|
+
runner = CliRunner()
|
|
1039
|
+
with mock.patch("circup.get_bundle_versions", return_value=TEST_BUNDLE_MODULES):
|
|
1040
|
+
result = runner.invoke(circup.install, ["--auto-file", "./tests/bad_python.py"])
|
|
1041
|
+
assert result.exit_code == 2
|
|
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
|
|
@@ -9,15 +9,15 @@ update_checker
|
|
|
9
9
|
importlib_metadata
|
|
10
10
|
|
|
11
11
|
[all]
|
|
12
|
-
|
|
13
|
-
pytest-random-order>=1.0.0
|
|
12
|
+
sphinx
|
|
14
13
|
pytest-cov
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
coverage
|
|
15
|
+
pytest-random-order>=1.0.0
|
|
16
|
+
black
|
|
17
17
|
pytest
|
|
18
18
|
pylint
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
twine
|
|
20
|
+
wheel
|
|
21
21
|
pytest-faulthandler
|
|
22
22
|
|
|
23
23
|
[dev]
|
|
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
|