Skip to content

Commit 1dfb509

Browse files
Josverldpgeorge
authored andcommitted
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 <[email protected]>
1 parent ecbbc51 commit 1dfb509

File tree

2 files changed

+77
-6
lines changed

2 files changed

+77
-6
lines changed

tools/mpremote/mpremote/commands.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,49 @@ def do_filesystem_recursive_rm(state, path, args):
334334
print(f"removed: '{path}'")
335335

336336

337+
def human_size(size, decimals=1):
338+
for unit in ['B', 'K', 'M', 'G', 'T']:
339+
if size < 1024.0 or unit == 'T':
340+
break
341+
size /= 1024.0
342+
return f"{size:.{decimals}f}{unit}" if unit != 'B' else f"{int(size)}"
343+
344+
345+
def do_filesystem_tree(state, path, args):
346+
"""Print a tree of the device's filesystem starting at path."""
347+
connectors = ("├── ", "└── ")
348+
349+
def _tree_recursive(path, prefix=""):
350+
entries = state.transport.fs_listdir(path)
351+
entries.sort(key=lambda e: e.name)
352+
for i, entry in enumerate(entries):
353+
connector = connectors[1] if i == len(entries) - 1 else connectors[0]
354+
is_dir = entry.st_mode & 0x4000 # Directory
355+
size_str = ""
356+
# most MicroPython filesystems don't support st_size on directories, reduce clutter
357+
if entry.st_size > 0 or not is_dir:
358+
if args.size:
359+
size_str = f"[{entry.st_size:>9}] "
360+
elif args.human:
361+
size_str = f"[{human_size(entry.st_size):>6}] "
362+
print(f"{prefix}{connector}{size_str}{entry.name}")
363+
if is_dir:
364+
_tree_recursive(
365+
_remote_path_join(path, entry.name),
366+
prefix + (" " if i == len(entries) - 1 else "│ "),
367+
)
368+
369+
if not path or path == ".":
370+
path = state.transport.exec("import os;print(os.getcwd())").strip().decode("utf-8")
371+
if not (path == "." or state.transport.fs_isdir(path)):
372+
raise CommandError(f"tree: '{path}' is not a directory")
373+
if args.verbose:
374+
print(f":{path} on {state.transport.device_name}")
375+
else:
376+
print(f":{path}")
377+
_tree_recursive(path)
378+
379+
337380
def do_filesystem(state, args):
338381
state.ensure_raw_repl()
339382
state.did_action()
@@ -361,8 +404,8 @@ def do_filesystem(state, args):
361404
# leading ':' if the user included them.
362405
paths = [path[1:] if path.startswith(":") else path for path in paths]
363406

364-
# ls implicitly lists the cwd.
365-
if command == "ls" and not paths:
407+
# ls and tree implicitly lists the cwd.
408+
if command in ("ls", "tree") and not paths:
366409
paths = [""]
367410

368411
try:
@@ -404,6 +447,8 @@ def do_filesystem(state, args):
404447
)
405448
else:
406449
do_filesystem_cp(state, path, cp_dest, len(paths) > 1, not args.force)
450+
elif command == "tree":
451+
do_filesystem_tree(state, path, args)
407452
except OSError as er:
408453
raise CommandError("{}: {}: {}.".format(command, er.strerror, os.strerror(er.errno)))
409454
except TransportError as er:

tools/mpremote/mpremote/main.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,11 @@ def argparse_rtc():
181181

182182

183183
def argparse_filesystem():
184-
cmd_parser = argparse.ArgumentParser(description="execute filesystem commands on the device")
184+
cmd_parser = argparse.ArgumentParser(
185+
description="execute filesystem commands on the device",
186+
add_help=False,
187+
)
188+
cmd_parser.add_argument("--help", action="help", help="show this help message and exit")
185189
_bool_flag(cmd_parser, "recursive", "r", False, "recursive (for cp and rm commands)")
186190
_bool_flag(
187191
cmd_parser,
@@ -197,10 +201,26 @@ def argparse_filesystem():
197201
None,
198202
"enable verbose output (defaults to True for all commands except cat)",
199203
)
204+
size_group = cmd_parser.add_mutually_exclusive_group()
205+
size_group.add_argument(
206+
"--size",
207+
"-s",
208+
default=False,
209+
action="store_true",
210+
help="show file size in bytes(tree command only)",
211+
)
212+
size_group.add_argument(
213+
"--human",
214+
"-h",
215+
default=False,
216+
action="store_true",
217+
help="show file size in a more human readable way (tree command only)",
218+
)
219+
200220
cmd_parser.add_argument(
201221
"command",
202222
nargs=1,
203-
help="filesystem command (e.g. cat, cp, sha256sum, ls, rm, rmdir, touch)",
223+
help="filesystem command (e.g. cat, cp, sha256sum, ls, rm, rmdir, touch, tree)",
204224
)
205225
cmd_parser.add_argument("path", nargs="+", help="local and remote paths")
206226
return cmd_parser
@@ -355,6 +375,7 @@ def argparse_none(description):
355375
"rmdir": "fs rmdir",
356376
"sha256sum": "fs sha256sum",
357377
"touch": "fs touch",
378+
"tree": "fs tree",
358379
# Disk used/free.
359380
"df": [
360381
"exec",
@@ -552,8 +573,13 @@ def main():
552573
command_args = remaining_args
553574
extra_args = []
554575

555-
# Special case: "fs ls" allowed have no path specified.
556-
if cmd == "fs" and len(command_args) == 1 and command_args[0] == "ls":
576+
# Special case: "fs ls" and "fs tree" can have only options and no path specified.
577+
if (
578+
cmd == "fs"
579+
and len(command_args) >= 1
580+
and command_args[0] in ("ls", "tree")
581+
and sum(1 for a in command_args if not a.startswith('-')) == 1
582+
):
557583
command_args.append("")
558584

559585
# Use the command-specific argument parser.

0 commit comments

Comments
 (0)