littlefs-python 0.17.0__tar.gz → 0.18.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.
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/.github/workflows/deploy.yml +3 -3
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/PKG-INFO +1 -1
- littlefs_python-0.18.0/requirements.txt +3 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/src/littlefs/__init__.py +41 -17
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/src/littlefs/__main__.py +115 -66
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/src/littlefs/context.py +12 -4
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/src/littlefs/lfs.c +3855 -4171
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/src/littlefs/lfs.pyi +12 -10
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/src/littlefs/lfs.pyx +40 -25
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/src/littlefs/repl.py +5 -1
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/src/littlefs_python.egg-info/PKG-INFO +1 -1
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/src/littlefs_python.egg-info/SOURCES.txt +5 -0
- littlefs_python-0.18.0/test/cli/test_create_and_extract.py +193 -0
- littlefs_python-0.18.0/test/cli/test_create_and_repl.py +99 -0
- littlefs_python-0.18.0/test/cli/test_walk_all.py +60 -0
- littlefs_python-0.18.0/test/test_unicode_filenames.py +101 -0
- littlefs_python-0.18.0/test/test_windisk_context.py +60 -0
- littlefs_python-0.17.0/requirements.txt +0 -2
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/.gitattributes +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/.github/dependabot.yml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/.github/workflows/run-tests.yml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/.gitignore +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/.gitmodules +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/.pre-commit-config.yaml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/.readthedocs.yml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/LICENSE +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/MANIFEST.in +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/README.rst +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/ci/build-wheels.sh +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/ci/download_release_files.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/docs/Makefile +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/docs/api/index.rst +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/docs/cli.rst +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/docs/conf.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/docs/doc8.ini +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/docs/examples/index.rst +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/docs/index.rst +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/docs/make.bat +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/docs/requirements.txt +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/docs/usage.rst +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/examples/mkfsimg.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/examples/walk.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/.git +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/.gitattributes +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/.github/workflows/post-release.yml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/.github/workflows/release.yml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/.github/workflows/status.yml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/.github/workflows/test.yml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/.gitignore +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/DESIGN.md +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/LICENSE.md +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/Makefile +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/README.md +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/SPEC.md +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/bd/lfs_emubd.c +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/bd/lfs_emubd.h +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/bd/lfs_filebd.c +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/bd/lfs_filebd.h +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/bd/lfs_rambd.c +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/bd/lfs_rambd.h +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/benches/bench_dir.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/benches/bench_file.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/benches/bench_superblock.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/lfs.c +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/lfs.h +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/lfs_util.c +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/lfs_util.h +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/runners/bench_runner.c +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/runners/bench_runner.h +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/runners/test_runner.c +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/runners/test_runner.h +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/bench.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/changeprefix.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/code.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/cov.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/data.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/perf.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/perfbd.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/plot.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/plotmpl.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/prettyasserts.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/readblock.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/readmdir.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/readtree.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/stack.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/structs.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/summary.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/tailpipe.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/teepipe.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/test.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/tracebd.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/scripts/watch.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/tests/test_alloc.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/tests/test_attrs.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/tests/test_badblocks.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/tests/test_bd.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/tests/test_compat.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/tests/test_dirs.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/tests/test_entries.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/tests/test_evil.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/tests/test_exhaustion.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/tests/test_files.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/tests/test_interspersed.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/tests/test_move.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/tests/test_orphans.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/tests/test_paths.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/tests/test_powerloss.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/tests/test_relocations.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/tests/test_seek.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/tests/test_shrink.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/tests/test_superblocks.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/littlefs/tests/test_truncate.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/mypy.ini +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/pyproject.toml +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/setup.cfg +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/setup.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/src/littlefs/errors.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/src/littlefs/lfs.pxd +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/src/littlefs/py.typed +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/src/littlefs_python.egg-info/dependency_links.txt +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/src/littlefs_python.egg-info/entry_points.txt +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/src/littlefs_python.egg-info/not-zip-safe +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/src/littlefs_python.egg-info/requires.txt +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/src/littlefs_python.egg-info/top_level.txt +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/test/lfs/conftest.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/test/lfs/test_dir_functions.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/test/lfs/test_file_functions.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/test/lfs/test_fs_functions.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/test/test_attr.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/test/test_block_count.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/test/test_context.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/test/test_directories.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/test/test_files.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/test/test_grow.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/test/test_multiversion.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/test/test_name_max.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/test/test_remove_rename.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/test/test_version.py +0 -0
- {littlefs_python-0.17.0 → littlefs_python-0.18.0}/test/test_walk.py +0 -0
|
@@ -35,7 +35,7 @@ jobs:
|
|
|
35
35
|
platforms: all
|
|
36
36
|
|
|
37
37
|
- name: Build wheels
|
|
38
|
-
uses: pypa/cibuildwheel@v3.
|
|
38
|
+
uses: pypa/cibuildwheel@v3.4.1
|
|
39
39
|
|
|
40
40
|
- uses: actions/upload-artifact@v6
|
|
41
41
|
with:
|
|
@@ -57,7 +57,7 @@ jobs:
|
|
|
57
57
|
fetch-depth: 0
|
|
58
58
|
|
|
59
59
|
- name: Build wheels
|
|
60
|
-
uses: pypa/cibuildwheel@v3.
|
|
60
|
+
uses: pypa/cibuildwheel@v3.4.1
|
|
61
61
|
env:
|
|
62
62
|
CIBW_BUILD: ${{ matrix.cibw_build }}
|
|
63
63
|
CIBW_SKIP: "pp*"
|
|
@@ -119,7 +119,7 @@ jobs:
|
|
|
119
119
|
pattern: wheels-*
|
|
120
120
|
merge-multiple: true
|
|
121
121
|
|
|
122
|
-
- uses: pypa/gh-action-pypi-publish@v1.
|
|
122
|
+
- uses: pypa/gh-action-pypi-publish@v1.14.0
|
|
123
123
|
with:
|
|
124
124
|
user: __token__
|
|
125
125
|
password: ${{ secrets.pypi_api_token }}
|
|
@@ -58,7 +58,29 @@ if TYPE_CHECKING:
|
|
|
58
58
|
class LittleFS:
|
|
59
59
|
"""Littlefs file system"""
|
|
60
60
|
|
|
61
|
-
def __init__(
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
context: Optional["UserContext"] = None,
|
|
64
|
+
mount=True,
|
|
65
|
+
filename_encoding: Optional[str] = None,
|
|
66
|
+
**kwargs,
|
|
67
|
+
) -> None:
|
|
68
|
+
"""
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
filename_encoding : Optional[str]
|
|
72
|
+
Encoding used to encode/decode filenames passed to and returned by
|
|
73
|
+
the filesystem. littlefs stores names as opaque byte strings, so this
|
|
74
|
+
is a free choice. Defaults to :data:`littlefs.lfs.FILENAME_ENCODING`
|
|
75
|
+
(``"utf-8"``). Set this when reading an image whose names were written
|
|
76
|
+
with a different encoding (e.g. ``"latin-1"`` or ``"shift-jis"``).
|
|
77
|
+
|
|
78
|
+
Note that littlefs's ``name_max`` limit is measured in *encoded
|
|
79
|
+
bytes*, not characters. With a multi-byte encoding such as UTF-8, a
|
|
80
|
+
single non-ASCII character consumes 2-4 bytes, so a name can exceed
|
|
81
|
+
``name_max`` (default 255) well before it looks long.
|
|
82
|
+
"""
|
|
83
|
+
self.filename_encoding = filename_encoding or lfs.FILENAME_ENCODING
|
|
62
84
|
self.cfg = lfs.LFSConfig(context=context, **kwargs)
|
|
63
85
|
self.fs = lfs.LFSFilesystem()
|
|
64
86
|
|
|
@@ -204,7 +226,7 @@ class LittleFS:
|
|
|
204
226
|
buffering = -1
|
|
205
227
|
|
|
206
228
|
try:
|
|
207
|
-
fh = lfs.file_open(self.fs, fname, mode)
|
|
229
|
+
fh = lfs.file_open(self.fs, fname, mode, self.filename_encoding)
|
|
208
230
|
except LittleFSError as e:
|
|
209
231
|
# Try to map to standard Python exceptions
|
|
210
232
|
if e.code == LittleFSError.Error.LFS_ERR_NOENT:
|
|
@@ -251,15 +273,15 @@ class LittleFS:
|
|
|
251
273
|
|
|
252
274
|
def getattr(self, path: str, typ: Union[str, bytes, int]) -> bytes:
|
|
253
275
|
typ = _typ_to_uint8(typ)
|
|
254
|
-
return lfs.getattr(self.fs, path, typ)
|
|
276
|
+
return lfs.getattr(self.fs, path, typ, self.filename_encoding)
|
|
255
277
|
|
|
256
278
|
def setattr(self, path: str, typ: Union[str, bytes, int], data: bytes) -> None:
|
|
257
279
|
typ = _typ_to_uint8(typ)
|
|
258
|
-
lfs.setattr(self.fs, path, typ, data)
|
|
280
|
+
lfs.setattr(self.fs, path, typ, data, self.filename_encoding)
|
|
259
281
|
|
|
260
282
|
def removeattr(self, path: str, typ: Union[str, bytes, int]) -> None:
|
|
261
283
|
typ = _typ_to_uint8(typ)
|
|
262
|
-
lfs.removeattr(self.fs, path, typ)
|
|
284
|
+
lfs.removeattr(self.fs, path, typ, self.filename_encoding)
|
|
263
285
|
|
|
264
286
|
def listdir(self, path=".") -> List[str]:
|
|
265
287
|
"""List directory content
|
|
@@ -274,7 +296,7 @@ class LittleFS:
|
|
|
274
296
|
def mkdir(self, path: str) -> int:
|
|
275
297
|
"""Create a new directory"""
|
|
276
298
|
try:
|
|
277
|
-
return lfs.mkdir(self.fs, path)
|
|
299
|
+
return lfs.mkdir(self.fs, path, self.filename_encoding)
|
|
278
300
|
except errors.LittleFSError as e:
|
|
279
301
|
if e.code == LittleFSError.Error.LFS_ERR_EXIST:
|
|
280
302
|
msg = "[LittleFSError {:d}] Cannot create a file when that file already exists: '{:s}'.".format(
|
|
@@ -310,7 +332,7 @@ class LittleFS:
|
|
|
310
332
|
If ``true`` and ``path`` is a directory, recursively remove all children files/folders.
|
|
311
333
|
"""
|
|
312
334
|
try:
|
|
313
|
-
lfs.remove(self.fs, path)
|
|
335
|
+
lfs.remove(self.fs, path, self.filename_encoding)
|
|
314
336
|
return
|
|
315
337
|
except errors.LittleFSError as e:
|
|
316
338
|
if e.code == LittleFSError.Error.LFS_ERR_NOENT:
|
|
@@ -326,7 +348,7 @@ class LittleFS:
|
|
|
326
348
|
# Recursively delete the ``path`` directory
|
|
327
349
|
for elem in self.scandir(path):
|
|
328
350
|
self.remove(path + "/" + elem.name, recursive=True)
|
|
329
|
-
lfs.remove(self.fs, path)
|
|
351
|
+
lfs.remove(self.fs, path, self.filename_encoding)
|
|
330
352
|
|
|
331
353
|
def removedirs(self, name):
|
|
332
354
|
"""Remove directories recursively
|
|
@@ -351,7 +373,7 @@ class LittleFS:
|
|
|
351
373
|
|
|
352
374
|
def rename(self, src: str, dst: str) -> int:
|
|
353
375
|
"""Rename a file or directory"""
|
|
354
|
-
return lfs.rename(self.fs, src, dst)
|
|
376
|
+
return lfs.rename(self.fs, src, dst, self.filename_encoding)
|
|
355
377
|
|
|
356
378
|
def rmdir(self, path: str) -> int:
|
|
357
379
|
"""Remove a directory
|
|
@@ -362,17 +384,19 @@ class LittleFS:
|
|
|
362
384
|
|
|
363
385
|
def scandir(self, path=".") -> Iterator["LFSStat"]:
|
|
364
386
|
"""List directory content"""
|
|
365
|
-
dh = lfs.dir_open(self.fs, path)
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
387
|
+
dh = lfs.dir_open(self.fs, path, self.filename_encoding)
|
|
388
|
+
try:
|
|
389
|
+
info = lfs.dir_read(self.fs, dh, self.filename_encoding)
|
|
390
|
+
while info:
|
|
391
|
+
if info.name not in [".", ".."]:
|
|
392
|
+
yield info
|
|
393
|
+
info = lfs.dir_read(self.fs, dh, self.filename_encoding)
|
|
394
|
+
finally:
|
|
395
|
+
lfs.dir_close(self.fs, dh)
|
|
372
396
|
|
|
373
397
|
def stat(self, path: str) -> "LFSStat":
|
|
374
398
|
"""Get the status of a file or directory"""
|
|
375
|
-
return lfs.stat(self.fs, path)
|
|
399
|
+
return lfs.stat(self.fs, path, self.filename_encoding)
|
|
376
400
|
|
|
377
401
|
def unlink(self, path: str) -> int:
|
|
378
402
|
"""Remove a file or directory
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
from contextlib import suppress
|
|
3
|
+
import os
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
import sys
|
|
5
6
|
import textwrap
|
|
@@ -7,7 +8,7 @@ import textwrap
|
|
|
7
8
|
from littlefs import LittleFS, __version__
|
|
8
9
|
from littlefs.errors import LittleFSError
|
|
9
10
|
from littlefs.repl import LittleFSRepl
|
|
10
|
-
from littlefs.context import UserContextFile
|
|
11
|
+
from littlefs.context import UserContextFile, UserContext
|
|
11
12
|
|
|
12
13
|
# Dictionary mapping suffixes to their size in bytes
|
|
13
14
|
_suffix_map = {
|
|
@@ -17,13 +18,21 @@ _suffix_map = {
|
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
def _fs_from_args(args: argparse.Namespace, mount=True) -> LittleFS:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
def _fs_from_args(args: argparse.Namespace, block_count=None, mount=True, context: UserContext = None) -> LittleFS:
|
|
22
|
+
"""Build LittleFS from CLI args. Options name_max, attr_max, file_max are stored in the
|
|
23
|
+
superblock and must match when mounting an existing image. inline_max is format-relevant
|
|
24
|
+
(limiting it may improve flash usage)."""
|
|
25
|
+
block_count = block_count if block_count is not None else getattr(args, "block_count", 0)
|
|
26
|
+
kwargs = {
|
|
27
|
+
"block_size": args.block_size,
|
|
28
|
+
"block_count": block_count,
|
|
29
|
+
"name_max": args.name_max,
|
|
30
|
+
"inline_max": args.inline_max,
|
|
31
|
+
"attr_max": args.attr_max,
|
|
32
|
+
"file_max": args.file_max,
|
|
33
|
+
"filename_encoding": getattr(args, "filename_encoding", None),
|
|
34
|
+
}
|
|
35
|
+
return LittleFS(context=context, mount=mount, **kwargs)
|
|
27
36
|
|
|
28
37
|
|
|
29
38
|
def size_parser(size_str):
|
|
@@ -51,6 +60,16 @@ def size_parser(size_str):
|
|
|
51
60
|
return int(size_str, base)
|
|
52
61
|
|
|
53
62
|
|
|
63
|
+
def _walk_all(root):
|
|
64
|
+
"""Recursively yield all paths under root, following symlinks."""
|
|
65
|
+
for dirpath, dirnames, filenames in os.walk(root, followlinks=True):
|
|
66
|
+
dirpath = Path(dirpath)
|
|
67
|
+
for dirname in dirnames:
|
|
68
|
+
yield dirpath / dirname
|
|
69
|
+
for filename in filenames:
|
|
70
|
+
yield dirpath / filename
|
|
71
|
+
|
|
72
|
+
|
|
54
73
|
def create(parser: argparse.ArgumentParser, args: argparse.Namespace) -> int:
|
|
55
74
|
"""Create LittleFS image from file/directory contents."""
|
|
56
75
|
# fs_size OR block_count may be populated; make them consistent.
|
|
@@ -68,11 +87,17 @@ def create(parser: argparse.ArgumentParser, args: argparse.Namespace) -> int:
|
|
|
68
87
|
print(f" Image Size: {args.fs_size:9d} / 0x{args.fs_size:X}")
|
|
69
88
|
print(f" Block Count: {args.block_count:9d}")
|
|
70
89
|
print(f" Name Max: {args.name_max:9d}")
|
|
90
|
+
if args.inline_max:
|
|
91
|
+
print(f" Inline Max: {args.inline_max:9d} / 0x{args.inline_max:X}")
|
|
92
|
+
if args.attr_max:
|
|
93
|
+
print(f" Attr Max: {args.attr_max:9d}")
|
|
94
|
+
if args.file_max:
|
|
95
|
+
print(f" File Max: {args.file_max:9d}")
|
|
71
96
|
print(f" Image: {args.destination}")
|
|
72
97
|
|
|
73
98
|
source = Path(args.source).absolute()
|
|
74
99
|
if source.is_dir():
|
|
75
|
-
sources = source
|
|
100
|
+
sources = list(_walk_all(source))
|
|
76
101
|
root = source
|
|
77
102
|
else:
|
|
78
103
|
sources = [source]
|
|
@@ -94,21 +119,14 @@ def create(parser: argparse.ArgumentParser, args: argparse.Namespace) -> int:
|
|
|
94
119
|
if args.compact:
|
|
95
120
|
if args.verbose:
|
|
96
121
|
print(f"Compacting... {fs.used_block_count} / {args.block_count}")
|
|
97
|
-
compact_fs =
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
for _dir in dirs:
|
|
106
|
-
compact_fs.makedirs(root + _dir, exist_ok=True)
|
|
107
|
-
for file in files:
|
|
108
|
-
path = root + file
|
|
109
|
-
print(path)
|
|
110
|
-
with fs.open(path, "rb") as src, compact_fs.open(path, "wb") as dest:
|
|
111
|
-
dest.write(src.read())
|
|
122
|
+
compact_fs = _fs_from_args(args, block_count=fs.used_block_count)
|
|
123
|
+
for path in sources:
|
|
124
|
+
rel_path = path.relative_to(root)
|
|
125
|
+
if path.is_dir():
|
|
126
|
+
compact_fs.mkdir(rel_path.as_posix())
|
|
127
|
+
else:
|
|
128
|
+
with compact_fs.open(rel_path.as_posix(), "wb") as dest:
|
|
129
|
+
dest.write(path.read_bytes())
|
|
112
130
|
compact_fs.fs_grow(args.block_count)
|
|
113
131
|
data = compact_fs.context.buffer
|
|
114
132
|
if not args.no_pad:
|
|
@@ -121,21 +139,43 @@ def create(parser: argparse.ArgumentParser, args: argparse.Namespace) -> int:
|
|
|
121
139
|
return 0
|
|
122
140
|
|
|
123
141
|
|
|
124
|
-
def
|
|
125
|
-
|
|
126
|
-
fs = _fs_from_args(args, mount=False)
|
|
127
|
-
fs.context.buffer = bytearray(args.source.read_bytes())
|
|
142
|
+
def _mount_from_context(parser: argparse.ArgumentParser, args: argparse.Namespace, context: UserContext) -> LittleFS:
|
|
143
|
+
# Block count is 0 because we don't know the size of the real image yet, the source file may be compacted (with the create --compact option).
|
|
144
|
+
fs = _fs_from_args(args, block_count=0, mount=False, context=context)
|
|
128
145
|
fs.mount()
|
|
129
146
|
|
|
130
147
|
if args.verbose:
|
|
131
|
-
|
|
148
|
+
input_image_size = context.in_size
|
|
149
|
+
actual_image_size = fs.block_count * args.block_size
|
|
132
150
|
print("LittleFS Configuration:")
|
|
133
151
|
print(f" Block Size: {args.block_size:9d} / 0x{args.block_size:X}")
|
|
134
|
-
|
|
152
|
+
if input_image_size != actual_image_size:
|
|
153
|
+
print(f" Image Size: {actual_image_size:9d} / 0x{actual_image_size:X}")
|
|
154
|
+
print(f" Input Image Size (compacted): {input_image_size:9d} / 0x{input_image_size:X}")
|
|
155
|
+
else:
|
|
156
|
+
print(f" Image Size: {input_image_size:9d} / 0x{input_image_size:X}")
|
|
135
157
|
print(f" Block Count: {fs.block_count:9d}")
|
|
136
158
|
print(f" Name Max: {args.name_max:9d}")
|
|
159
|
+
if args.inline_max:
|
|
160
|
+
print(f" Inline Max: {args.inline_max:9d} / 0x{args.inline_max:X}")
|
|
161
|
+
if args.attr_max:
|
|
162
|
+
print(f" Attr Max: {args.attr_max:9d}")
|
|
163
|
+
if args.file_max:
|
|
164
|
+
print(f" File Max: {args.file_max:9d}")
|
|
137
165
|
print(f" Image: {args.source}")
|
|
138
166
|
|
|
167
|
+
return fs
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _list(parser: argparse.ArgumentParser, args: argparse.Namespace) -> int:
|
|
171
|
+
"""List LittleFS image contents."""
|
|
172
|
+
source: Path = args.source
|
|
173
|
+
if not source.is_file():
|
|
174
|
+
parser.error(f"Source image '{source}' does not exist.")
|
|
175
|
+
context = UserContext(buffer=bytearray(source.read_bytes()))
|
|
176
|
+
|
|
177
|
+
fs = _mount_from_context(parser, args, context)
|
|
178
|
+
|
|
139
179
|
for root, dirs, files in fs.walk("/"):
|
|
140
180
|
if not root.endswith("/"):
|
|
141
181
|
root += "/"
|
|
@@ -148,18 +188,12 @@ def _list(parser: argparse.ArgumentParser, args: argparse.Namespace) -> int:
|
|
|
148
188
|
|
|
149
189
|
def extract(parser: argparse.ArgumentParser, args: argparse.Namespace) -> int:
|
|
150
190
|
"""Extract LittleFS image contents to a directory."""
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
191
|
+
source: Path = args.source
|
|
192
|
+
if not source.is_file():
|
|
193
|
+
parser.error(f"Source image '{source}' does not exist.")
|
|
194
|
+
context = UserContext(buffer=bytearray(source.read_bytes()))
|
|
154
195
|
|
|
155
|
-
|
|
156
|
-
fs_size = len(fs.context.buffer)
|
|
157
|
-
print("LittleFS Configuration:")
|
|
158
|
-
print(f" Block Size: {args.block_size:9d} / 0x{args.block_size:X}")
|
|
159
|
-
print(f" Image Size: {fs_size:9d} / 0x{fs_size:X}")
|
|
160
|
-
print(f" Block Count: {fs.block_count:9d}")
|
|
161
|
-
print(f" Name Max: {args.name_max:9d}")
|
|
162
|
-
print(f" Image: {args.source}")
|
|
196
|
+
fs = _mount_from_context(parser, args, context)
|
|
163
197
|
|
|
164
198
|
root_dest = args.destination.absolute()
|
|
165
199
|
if not root_dest.exists():
|
|
@@ -192,36 +226,19 @@ def extract(parser: argparse.ArgumentParser, args: argparse.Namespace) -> int:
|
|
|
192
226
|
|
|
193
227
|
def repl(parser: argparse.ArgumentParser, args: argparse.Namespace) -> int:
|
|
194
228
|
"""Inspect an existing LittleFS image through an interactive shell."""
|
|
195
|
-
|
|
196
229
|
source: Path = args.source
|
|
197
230
|
if not source.is_file():
|
|
198
231
|
parser.error(f"Source image '{source}' does not exist.")
|
|
232
|
+
context = UserContextFile(str(source)) # In repl we want context to be the file itself, so commands will change it
|
|
199
233
|
|
|
200
|
-
image_size = source.stat().st_size
|
|
201
|
-
if not image_size or image_size % args.block_size:
|
|
202
|
-
parser.error(
|
|
203
|
-
f"Image size ({image_size} bytes) is not a multiple of the supplied block size ({args.block_size})."
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
block_count = image_size // args.block_size
|
|
207
|
-
if block_count == 0:
|
|
208
|
-
parser.error("Image is smaller than a single block; cannot mount.")
|
|
209
|
-
|
|
210
|
-
context = UserContextFile(str(source))
|
|
211
|
-
fs = LittleFS(
|
|
212
|
-
context=context,
|
|
213
|
-
block_size=args.block_size,
|
|
214
|
-
block_count=block_count,
|
|
215
|
-
name_max=args.name_max,
|
|
216
|
-
mount=False,
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
shell = LittleFSRepl(fs)
|
|
220
234
|
try:
|
|
221
235
|
try:
|
|
222
|
-
|
|
236
|
+
fs = _mount_from_context(parser, args, context)
|
|
223
237
|
except LittleFSError as exc:
|
|
224
238
|
parser.error(f"Failed to mount '{source}': {exc}")
|
|
239
|
+
|
|
240
|
+
shell = LittleFSRepl(fs)
|
|
241
|
+
|
|
225
242
|
shell.cmdloop()
|
|
226
243
|
finally:
|
|
227
244
|
if shell._mounted:
|
|
@@ -252,12 +269,41 @@ def get_parser():
|
|
|
252
269
|
|
|
253
270
|
common_parser = argparse.ArgumentParser(add_help=False)
|
|
254
271
|
common_parser.add_argument("-v", "--verbose", action="count", default=0)
|
|
272
|
+
# Stored in superblock; must match when mounting an existing image:
|
|
255
273
|
common_parser.add_argument(
|
|
256
274
|
"--name-max",
|
|
257
275
|
type=size_parser,
|
|
258
276
|
default=255,
|
|
259
277
|
help="LittleFS max file path length. Defaults to LittleFS's default (255).",
|
|
260
278
|
)
|
|
279
|
+
common_parser.add_argument(
|
|
280
|
+
"--attr-max",
|
|
281
|
+
type=int,
|
|
282
|
+
default=0,
|
|
283
|
+
help="Max custom attribute size per file. Defaults to LittleFS's default (0 = use library default).",
|
|
284
|
+
)
|
|
285
|
+
common_parser.add_argument(
|
|
286
|
+
"--file-max",
|
|
287
|
+
type=int,
|
|
288
|
+
default=0,
|
|
289
|
+
help="Max number of open files. Defaults to LittleFS's default (0 = use library default).",
|
|
290
|
+
)
|
|
291
|
+
# Format option: limiting inline_max may improve flash usage.
|
|
292
|
+
common_parser.add_argument(
|
|
293
|
+
"--inline-max",
|
|
294
|
+
type=size_parser,
|
|
295
|
+
default=0,
|
|
296
|
+
help="Max inline file size; 0 = use library default. Limiting can improve flash usage.",
|
|
297
|
+
)
|
|
298
|
+
# Host-side encode/decode choice; never stored in the image. The same encoding
|
|
299
|
+
# must be used to extract an image as was used to create it, otherwise filenames
|
|
300
|
+
# will fail to decode or come out as mojibake.
|
|
301
|
+
common_parser.add_argument(
|
|
302
|
+
"--filename-encoding",
|
|
303
|
+
default=None,
|
|
304
|
+
help="Encoding for filenames stored in the image. Defaults to utf-8. "
|
|
305
|
+
"Use e.g. latin-1 or shift-jis for images whose names use a different encoding.",
|
|
306
|
+
)
|
|
261
307
|
|
|
262
308
|
subparsers = parser.add_subparsers(required=True, title="Available Commands", dest="command")
|
|
263
309
|
|
|
@@ -358,10 +404,13 @@ def get_parser():
|
|
|
358
404
|
return parser
|
|
359
405
|
|
|
360
406
|
|
|
361
|
-
|
|
407
|
+
# Getting argv optionally from the caller to enable call from python (generally for testing, but could be used for other purposes)
|
|
408
|
+
def main(argv=None):
|
|
409
|
+
if argv is None:
|
|
410
|
+
argv = sys.argv
|
|
362
411
|
parser = get_parser()
|
|
363
|
-
parser.parse_known_args(
|
|
364
|
-
args = parser.parse_args(
|
|
412
|
+
parser.parse_known_args(argv[1:]) # Allows for ``littlefs-python --version``
|
|
413
|
+
args = parser.parse_args(argv[1:])
|
|
365
414
|
return args.func(parser, args)
|
|
366
415
|
|
|
367
416
|
|
|
@@ -10,8 +10,14 @@ if typing.TYPE_CHECKING:
|
|
|
10
10
|
class UserContext:
|
|
11
11
|
"""Basic User Context Implementation"""
|
|
12
12
|
|
|
13
|
-
def __init__(self, buffsize: int) -> None:
|
|
14
|
-
|
|
13
|
+
def __init__(self, buffsize: int = None, buffer: bytearray = None) -> None:
|
|
14
|
+
if buffer is not None:
|
|
15
|
+
self.buffer = buffer
|
|
16
|
+
elif buffsize is not None:
|
|
17
|
+
self.buffer = bytearray([0xFF] * buffsize)
|
|
18
|
+
else:
|
|
19
|
+
raise ValueError("Either buffsize or buffer must be provided")
|
|
20
|
+
self.in_size = len(self.buffer)
|
|
15
21
|
|
|
16
22
|
def read(self, cfg: "LFSConfig", block: int, off: int, size: int) -> bytearray:
|
|
17
23
|
"""read data
|
|
@@ -91,6 +97,7 @@ class UserContextFile(UserContext):
|
|
|
91
97
|
|
|
92
98
|
self._path = file_path
|
|
93
99
|
self._fh = open(file_path, mode)
|
|
100
|
+
self.in_size = os.path.getsize(file_path)
|
|
94
101
|
|
|
95
102
|
def read(self, cfg: "LFSConfig", block: int, off: int, size: int) -> bytearray:
|
|
96
103
|
logging.getLogger(__name__).debug("LFS Read : Block: %d, Offset: %d, Size=%d" % (block, off, size))
|
|
@@ -150,10 +157,11 @@ class UserContextWinDisk(UserContext):
|
|
|
150
157
|
"Unable to import 'win32file'. This module is required for Windows-specific functionality. Please ensure you are running on a Windows platform or install 'pywin32' using: 'pip install pywin32'."
|
|
151
158
|
)
|
|
152
159
|
self.device = win32file.CreateFile(
|
|
153
|
-
disk_path, win32file.GENERIC_READ, win32file.FILE_SHARE_READ, None, win32file.OPEN_EXISTING, 0, None
|
|
160
|
+
disk_path, win32file.GENERIC_READ | win32file.GENERIC_WRITE, win32file.FILE_SHARE_READ, None, win32file.OPEN_EXISTING, 0, None
|
|
154
161
|
)
|
|
155
162
|
if self.device == win32file.INVALID_HANDLE_VALUE:
|
|
156
163
|
raise IOError("Could not open disk %s" % disk_path)
|
|
164
|
+
self.in_size = win32file.GetFileSize(self.device)
|
|
157
165
|
|
|
158
166
|
def read(self, cfg: "LFSConfig", block: int, off: int, size: int) -> bytearray:
|
|
159
167
|
"""read data
|
|
@@ -214,7 +222,7 @@ class UserContextWinDisk(UserContext):
|
|
|
214
222
|
start = block * cfg.block_size
|
|
215
223
|
|
|
216
224
|
win32file.SetFilePointer(self.device, start, win32file.FILE_BEGIN)
|
|
217
|
-
win32file.WriteFile(self.device,
|
|
225
|
+
win32file.WriteFile(self.device, b'\xff' * cfg.block_size)
|
|
218
226
|
return 0
|
|
219
227
|
|
|
220
228
|
def sync(self, cfg: "LFSConfig") -> int:
|