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>
This commit is contained in:
committed by
Damien George
parent
ecbbc512b2
commit
1dfb5092fc
@@ -334,6 +334,49 @@ def do_filesystem_recursive_rm(state, path, args):
|
|||||||
print(f"removed: '{path}'")
|
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):
|
def do_filesystem(state, args):
|
||||||
state.ensure_raw_repl()
|
state.ensure_raw_repl()
|
||||||
state.did_action()
|
state.did_action()
|
||||||
@@ -361,8 +404,8 @@ def do_filesystem(state, args):
|
|||||||
# leading ':' if the user included them.
|
# leading ':' if the user included them.
|
||||||
paths = [path[1:] if path.startswith(":") else path for path in paths]
|
paths = [path[1:] if path.startswith(":") else path for path in paths]
|
||||||
|
|
||||||
# ls implicitly lists the cwd.
|
# ls and tree implicitly lists the cwd.
|
||||||
if command == "ls" and not paths:
|
if command in ("ls", "tree") and not paths:
|
||||||
paths = [""]
|
paths = [""]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -404,6 +447,8 @@ def do_filesystem(state, args):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
do_filesystem_cp(state, path, cp_dest, len(paths) > 1, not args.force)
|
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:
|
except OSError as er:
|
||||||
raise CommandError("{}: {}: {}.".format(command, er.strerror, os.strerror(er.errno)))
|
raise CommandError("{}: {}: {}.".format(command, er.strerror, os.strerror(er.errno)))
|
||||||
except TransportError as er:
|
except TransportError as er:
|
||||||
|
|||||||
@@ -181,7 +181,11 @@ def argparse_rtc():
|
|||||||
|
|
||||||
|
|
||||||
def argparse_filesystem():
|
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, "recursive", "r", False, "recursive (for cp and rm commands)")
|
||||||
_bool_flag(
|
_bool_flag(
|
||||||
cmd_parser,
|
cmd_parser,
|
||||||
@@ -197,10 +201,26 @@ def argparse_filesystem():
|
|||||||
None,
|
None,
|
||||||
"enable verbose output (defaults to True for all commands except cat)",
|
"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(
|
cmd_parser.add_argument(
|
||||||
"command",
|
"command",
|
||||||
nargs=1,
|
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")
|
cmd_parser.add_argument("path", nargs="+", help="local and remote paths")
|
||||||
return cmd_parser
|
return cmd_parser
|
||||||
@@ -355,6 +375,7 @@ _BUILTIN_COMMAND_EXPANSIONS = {
|
|||||||
"rmdir": "fs rmdir",
|
"rmdir": "fs rmdir",
|
||||||
"sha256sum": "fs sha256sum",
|
"sha256sum": "fs sha256sum",
|
||||||
"touch": "fs touch",
|
"touch": "fs touch",
|
||||||
|
"tree": "fs tree",
|
||||||
# Disk used/free.
|
# Disk used/free.
|
||||||
"df": [
|
"df": [
|
||||||
"exec",
|
"exec",
|
||||||
@@ -552,8 +573,13 @@ def main():
|
|||||||
command_args = remaining_args
|
command_args = remaining_args
|
||||||
extra_args = []
|
extra_args = []
|
||||||
|
|
||||||
# Special case: "fs ls" allowed have no path specified.
|
# 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] == "ls":
|
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("")
|
command_args.append("")
|
||||||
|
|
||||||
# Use the command-specific argument parser.
|
# Use the command-specific argument parser.
|
||||||
|
|||||||
Reference in New Issue
Block a user