diff --git a/src/littlefs/__main__.py b/src/littlefs/__main__.py index 7119994..60778fd 100644 --- a/src/littlefs/__main__.py +++ b/src/littlefs/__main__.py @@ -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): @@ -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() @@ -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 @@ -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") diff --git a/test/cli/test_create_and_extract.py b/test/cli/test_create_and_extract.py index 9be48ed..19c380a 100644 --- a/test/cli/test_create_and_extract.py +++ b/test/cli/test_create_and_extract.py @@ -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"