Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 44 additions & 7 deletions src/littlefs/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,19 @@


def _fs_from_args(args: argparse.Namespace, block_count=None, mount=True, context: UserContext = None) -> LittleFS:
"""Build LittleFS from CLI args. Options name_max, attr_max, file_max are stored in the
superblock and must match when mounting an existing image. inline_max is format-relevant
(limiting it may improve flash usage)."""
block_count = block_count if block_count is not None else getattr(args, "block_count", 0)
return LittleFS(
context=context,
block_size=args.block_size,
block_count=block_count,
name_max=args.name_max,
mount=mount,
)
kwargs = {
"block_size": args.block_size,
"block_count": block_count,
"name_max": args.name_max,
"inline_max": args.inline_max,
"attr_max": args.attr_max,
"file_max": args.file_max,
}
return LittleFS(context=context, mount=mount, **kwargs)


def size_parser(size_str):
Expand Down Expand Up @@ -81,6 +86,12 @@ def create(parser: argparse.ArgumentParser, args: argparse.Namespace) -> int:
print(f" Image Size: {args.fs_size:9d} / 0x{args.fs_size:X}")
print(f" Block Count: {args.block_count:9d}")
print(f" Name Max: {args.name_max:9d}")
if args.inline_max:
print(f" Inline Max: {args.inline_max:9d} / 0x{args.inline_max:X}")
if args.attr_max:
print(f" Attr Max: {args.attr_max:9d}")
if args.file_max:
print(f" File Max: {args.file_max:9d}")
print(f" Image: {args.destination}")

source = Path(args.source).absolute()
Expand Down Expand Up @@ -144,6 +155,12 @@ def _mount_from_context(parser: argparse.ArgumentParser, args: argparse.Namespac
print(f" Image Size: {input_image_size:9d} / 0x{input_image_size:X}")
print(f" Block Count: {fs.block_count:9d}")
print(f" Name Max: {args.name_max:9d}")
if args.inline_max:
print(f" Inline Max: {args.inline_max:9d} / 0x{args.inline_max:X}")
if args.attr_max:
print(f" Attr Max: {args.attr_max:9d}")
if args.file_max:
print(f" File Max: {args.file_max:9d}")
print(f" Image: {args.source}")

return fs
Expand Down Expand Up @@ -251,12 +268,32 @@ def get_parser():

common_parser = argparse.ArgumentParser(add_help=False)
common_parser.add_argument("-v", "--verbose", action="count", default=0)
# Stored in superblock; must match when mounting an existing image:
common_parser.add_argument(
"--name-max",
type=size_parser,
default=255,
help="LittleFS max file path length. Defaults to LittleFS's default (255).",
)
common_parser.add_argument(
"--attr-max",
type=int,
default=0,
help="Max custom attribute size per file. Defaults to LittleFS's default (0 = use library default).",
)
common_parser.add_argument(
"--file-max",
type=int,
default=0,
help="Max number of open files. Defaults to LittleFS's default (0 = use library default).",
)
# Format option: limiting inline_max may improve flash usage.
common_parser.add_argument(
"--inline-max",
type=size_parser,
default=0,
help="Max inline file size; 0 = use library default. Limiting can improve flash usage.",
)

subparsers = parser.add_subparsers(required=True, title="Available Commands", dest="command")

Expand Down
45 changes: 45 additions & 0 deletions test/cli/test_create_and_extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,48 @@ def test_create_compact_roundtrip(tmp_path, num_files, file_size):
extracted_file = extract_dir / f"file_{i}.txt"
assert extracted_file.exists(), f"file_{i}.txt missing from compact image"
assert extracted_file.read_text() == f"content_{i}_" + "x" * file_size


def _make_small_source(tmp_path):
"""Create a small source tree (one dir, two small files) for config option tests."""
source_dir = tmp_path / "source"
source_dir.mkdir()
(source_dir / "a.txt").write_text("hello")
(source_dir / "b.txt").write_text("world")
return source_dir


@pytest.mark.parametrize(
"option_name, create_extra, extract_extra",
[
("inline_max", ["--inline-max", "64"], ["--inline-max", "64"]),
("attr_max", ["--attr-max", "64"], ["--attr-max", "64"]),
("file_max", ["--file-max", "32"], ["--file-max", "32"]),
],
)
def test_create_extract_roundtrip_with_config_option(tmp_path, option_name, create_extra, extract_extra):
"""Create image with a config option, extract with same option, compare to source."""
source_dir = _make_small_source(tmp_path)
image_file = tmp_path / "image.bin"
extract_dir = tmp_path / "extracted"

create_argv = [
"littlefs", "create", str(source_dir), str(image_file),
"--block-size", "1024", "--fs-size", "64KB",
] + create_extra
assert main(create_argv) == 0
assert image_file.exists()

extract_argv = [
"littlefs", "extract", str(image_file), str(extract_dir),
"--block-size", "1024",
] + extract_extra
assert main(extract_argv) == 0
assert extract_dir.exists()

comparison = filecmp.dircmp(source_dir, extract_dir)
assert not comparison.diff_files
assert not comparison.left_only
assert not comparison.right_only
assert (extract_dir / "a.txt").read_text() == "hello"
assert (extract_dir / "b.txt").read_text() == "world"
Loading