diff options
| author | Jos Verlinde <jos_verlinde@hotmail.com> | 2025-04-26 23:41:32 +0200 |
|---|---|---|
| committer | Damien George <damien@micropython.org> | 2025-05-18 00:19:14 +1000 |
| commit | 1dfb5092fc66b70420af5a6b8d6239e70b8c6e13 (patch) | |
| tree | 5ac8bf2994b379a2aed81ebbcd2166a40f54dad5 /tools | |
| parent | ecbbc512b2083c472f7bec45e8fae521e7af22aa (diff) | |
tools/mpremote: Add new 'fs tree' command.
Add `mpremote fs tree` command to show a tree of the device's files. It:
- Shows a treeview from current path or specified path.
- Uses the graph chars ("├── ", "└── ") (not configurable).
- Has the options:
-v/--verbose adds the serial device name to the top of the tree
-s/--size add a size to the files
-h/--human add a human readable size to the files
Signed-off-by: Jos Verlinde <jos_verlinde@hotmail.com>
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/mpremote/mpremote/commands.py | 49 | ||||
| -rw-r--r-- | tools/mpremote/mpremote/main.py | 34 |
2 files changed, 77 insertions, 6 deletions
diff --git a/tools/mpremote/mpremote/commands.py b/tools/mpremote/mpremote/commands.py index 452384728..428600baf 100644 --- a/tools/mpremote/mpremote/commands.py +++ b/tools/mpremote/mpremote/commands.py @@ -334,6 +334,49 @@ def do_filesystem_recursive_rm(state, path, args): print(f"removed: '{path}'") +def human_size(size, decimals=1): + for unit in ['B', 'K', 'M', 'G', 'T']: + if size < 1024.0 or unit == 'T': + break + size /= 1024.0 + return f"{size:.{decimals}f}{unit}" if unit != 'B' else f"{int(size)}" + + +def do_filesystem_tree(state, path, args): + """Print a tree of the device's filesystem starting at path.""" + connectors = ("├── ", "└── ") + + def _tree_recursive(path, prefix=""): + entries = state.transport.fs_listdir(path) + entries.sort(key=lambda e: e.name) + for i, entry in enumerate(entries): + connector = connectors[1] if i == len(entries) - 1 else connectors[0] + is_dir = entry.st_mode & 0x4000 # Directory + size_str = "" + # most MicroPython filesystems don't support st_size on directories, reduce clutter + if entry.st_size > 0 or not is_dir: + if args.size: + size_str = f"[{entry.st_size:>9}] " + elif args.human: + size_str = f"[{human_size(entry.st_size):>6}] " + print(f"{prefix}{connector}{size_str}{entry.name}") + if is_dir: + _tree_recursive( + _remote_path_join(path, entry.name), + prefix + (" " if i == len(entries) - 1 else "│ "), + ) + + if not path or path == ".": + path = state.transport.exec("import os;print(os.getcwd())").strip().decode("utf-8") + if not (path == "." or state.transport.fs_isdir(path)): + raise CommandError(f"tree: '{path}' is not a directory") + if args.verbose: + print(f":{path} on {state.transport.device_name}") + else: + print(f":{path}") + _tree_recursive(path) + + def do_filesystem(state, args): state.ensure_raw_repl() state.did_action() @@ -361,8 +404,8 @@ def do_filesystem(state, args): # leading ':' if the user included them. paths = [path[1:] if path.startswith(":") else path for path in paths] - # ls implicitly lists the cwd. - if command == "ls" and not paths: + # ls and tree implicitly lists the cwd. + if command in ("ls", "tree") and not paths: paths = [""] try: @@ -404,6 +447,8 @@ def do_filesystem(state, args): ) else: do_filesystem_cp(state, path, cp_dest, len(paths) > 1, not args.force) + elif command == "tree": + do_filesystem_tree(state, path, args) except OSError as er: raise CommandError("{}: {}: {}.".format(command, er.strerror, os.strerror(er.errno))) except TransportError as er: diff --git a/tools/mpremote/mpremote/main.py b/tools/mpremote/mpremote/main.py index b30a1a213..f8c913d26 100644 --- a/tools/mpremote/mpremote/main.py +++ b/tools/mpremote/mpremote/main.py @@ -181,7 +181,11 @@ def argparse_rtc(): def argparse_filesystem(): - cmd_parser = argparse.ArgumentParser(description="execute filesystem commands on the device") + cmd_parser = argparse.ArgumentParser( + description="execute filesystem commands on the device", + add_help=False, + ) + cmd_parser.add_argument("--help", action="help", help="show this help message and exit") _bool_flag(cmd_parser, "recursive", "r", False, "recursive (for cp and rm commands)") _bool_flag( cmd_parser, @@ -197,10 +201,26 @@ def argparse_filesystem(): None, "enable verbose output (defaults to True for all commands except cat)", ) + size_group = cmd_parser.add_mutually_exclusive_group() + size_group.add_argument( + "--size", + "-s", + default=False, + action="store_true", + help="show file size in bytes(tree command only)", + ) + size_group.add_argument( + "--human", + "-h", + default=False, + action="store_true", + help="show file size in a more human readable way (tree command only)", + ) + cmd_parser.add_argument( "command", nargs=1, - help="filesystem command (e.g. cat, cp, sha256sum, ls, rm, rmdir, touch)", + help="filesystem command (e.g. cat, cp, sha256sum, ls, rm, rmdir, touch, tree)", ) cmd_parser.add_argument("path", nargs="+", help="local and remote paths") return cmd_parser @@ -355,6 +375,7 @@ _BUILTIN_COMMAND_EXPANSIONS = { "rmdir": "fs rmdir", "sha256sum": "fs sha256sum", "touch": "fs touch", + "tree": "fs tree", # Disk used/free. "df": [ "exec", @@ -552,8 +573,13 @@ def main(): command_args = remaining_args extra_args = [] - # Special case: "fs ls" allowed have no path specified. - if cmd == "fs" and len(command_args) == 1 and command_args[0] == "ls": + # Special case: "fs ls" and "fs tree" can have only options and no path specified. + if ( + cmd == "fs" + and len(command_args) >= 1 + and command_args[0] in ("ls", "tree") + and sum(1 for a in command_args if not a.startswith('-')) == 1 + ): command_args.append("") # Use the command-specific argument parser. |
