From: Thomas Walker Lynch Date: Wed, 19 Nov 2025 05:51:20 +0000 (+0000) Subject: adds skeleton function CLIs and better tree_dict pretty print X-Git-Url: https://git.reasoningtechnology.com/style/static/git-favicon.png?a=commitdiff_plain;h=e39c6a6e1eed1829c4831be68f99c35882a68c7a;p=Harmony.git adds skeleton function CLIs and better tree_dict pretty print --- diff --git a/tool/skeleton/A_minus_B b/tool/skeleton/A_minus_B new file mode 100755 index 0000000..f6f7bbb --- /dev/null +++ b/tool/skeleton/A_minus_B @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- + +""" +A_minus_B - CLI test driver for skeleton.tree_dict_A_minus_B(A, B) + +Usage: + A_minus_B +""" + +from __future__ import annotations + +import os +import sys +from typing import Sequence + +import meta +import skeleton + + +def CLI(argv: Sequence[str] | None = None) -> int: + if argv is None: + argv = sys.argv[1:] + + prog = os.path.basename(sys.argv[0]) if sys.argv else "A_minus_B" + + if len(argv) != 2 or argv[0] in ("-h", "--help"): + print(f"Usage: {prog} ") + return 1 + + A_root = argv[0] + B_root = argv[1] + + if not os.path.isdir(A_root): + print(f"{prog}: {A_root}: not a directory") + return 2 + + if not os.path.isdir(B_root): + print(f"{prog}: {B_root}: not a directory") + return 3 + + A = skeleton.tree_dict_make(A_root, None) + B = skeleton.tree_dict_make(B_root, None) + + meta.debug_set("tree_dict_A_minus_B") + + _result = skeleton.tree_dict_A_minus_B(A, B) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(CLI()) diff --git a/tool/skeleton/Harmony_where b/tool/skeleton/Harmony_where new file mode 100755 index 0000000..9d39f1e --- /dev/null +++ b/tool/skeleton/Harmony_where @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- + +""" +Harmony_where - CLI to locate the Harmony project root + +Usage: + Harmony_where + +Prints the status and path returned by Harmony.where(). +""" + +from __future__ import annotations + +import sys + +import Harmony + + +def CLI(argv=None) -> int: + # Ignore argv; no arguments expected + status, Harmony_root = Harmony.where() + + if status == "found": + print(f"Harmony project root found at: {Harmony_root}") + return 0 + + if status == "different": + print(f"Harmony not found, but nearest .git directory is: {Harmony_root}") + return 1 + + print("Harmony project root not found.") + return 2 + + +if __name__ == "__main__": + raise SystemExit(CLI()) diff --git a/tool/skeleton/in_between_and_below b/tool/skeleton/in_between_and_below new file mode 100755 index 0000000..2993767 --- /dev/null +++ b/tool/skeleton/in_between_and_below @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- + +""" +in_between_and_below - CLI test driver for skeleton.tree_dict_in_between_and_below(A, B) + +Usage: + in_between_and_below +""" + +from __future__ import annotations + +import os +import sys +from typing import Sequence + +import meta +import skeleton + + +def CLI(argv: Sequence[str] | None = None) -> int: + if argv is None: + argv = sys.argv[1:] + + prog = os.path.basename(sys.argv[0]) if sys.argv else "in_between_and_below" + + if len(argv) != 2 or argv[0] in ("-h", "--help"): + print(f"Usage: {prog} ") + return 1 + + A_root = argv[0] + B_root = argv[1] + + if not os.path.isdir(A_root): + print(f"{prog}: {A_root}: not a directory") + return 2 + + if not os.path.isdir(B_root): + print(f"{prog}: {B_root}: not a directory") + return 3 + + A = skeleton.tree_dict_make(A_root, None) + B = skeleton.tree_dict_make(B_root, None) + + meta.debug_set("tree_dict_in_between_and_below") + + _result = skeleton.tree_dict_in_between_and_below(A, B) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(CLI()) diff --git a/tool/skeleton/make_Harmony_tree_dict b/tool/skeleton/make_Harmony_tree_dict new file mode 100755 index 0000000..2ed3cea --- /dev/null +++ b/tool/skeleton/make_Harmony_tree_dict @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- + +""" +skeleton_test - build and print the Harmony tree_dict + +Usage: + skeleton_test + +Behavior: + 1. Locate the Harmony project root via Harmony.where(). + 2. Enable 'tree_dict_print' debug flag. + 3. Call skeleton.tree_dict_make(Harmony_root, None). + +The skeleton.tree_dict_make() function is expected to call +tree_dict_print() when the 'tree_dict_print' debug flag is set. +""" + +from __future__ import annotations + +import sys + +import Harmony +import meta +import skeleton + + +def CLI(argv=None) -> int: + # No arguments expected + status, Harmony_root = Harmony.where() + + if status == "not-found": + print("Harmony project not found; cannot build tree_dict.") + return 1 + + if status == "different": + print("Warning: Harmony not found, using nearest .git directory for tree_dict.") + + # Enable printing inside tree_dict_make + meta.debug_set("tree_dict_print") + + _tree = skeleton.tree_dict_make(Harmony_root, None) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(CLI()) diff --git a/tool/skeleton/meta.py b/tool/skeleton/meta.py index 2025597..dee6439 100644 --- a/tool/skeleton/meta.py +++ b/tool/skeleton/meta.py @@ -16,11 +16,13 @@ Current responsibilities: from __future__ import annotations +import datetime from load_command_module import load_command_module # Load the incommon printenv module once at import time _PRINTENV_MODULE = load_command_module("printenv") +_Z_MODULE = load_command_module("Z") # Meta module version @@ -78,3 +80,18 @@ def printenv() -> int: ,[] # names ,"printenv" ) + + +def z_format_mtime( + mtime: float +) -> str: + """ + Format a POSIX mtime (seconds since epoch, UTC) using the Z module. + + Uses Z.ISO8601_FORMAT and Z.make_timestamp(dt=...). + """ + dt = datetime.datetime.fromtimestamp(mtime, datetime.timezone.utc) + return _Z_MODULE.make_timestamp( + fmt=_Z_MODULE.ISO8601_FORMAT + ,dt=dt + ) diff --git a/tool/skeleton/newer b/tool/skeleton/newer new file mode 100755 index 0000000..30aa373 --- /dev/null +++ b/tool/skeleton/newer @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- + +""" +newer - CLI test driver for skeleton.tree_dict_newer(A, B) + +Usage: + newer +""" + +from __future__ import annotations + +import os +import sys +from typing import Sequence + +import meta +import skeleton + + +def CLI(argv: Sequence[str] | None = None) -> int: + if argv is None: + argv = sys.argv[1:] + + prog = os.path.basename(sys.argv[0]) if sys.argv else "newer" + + if len(argv) != 2 or argv[0] in ("-h", "--help"): + print(f"Usage: {prog} ") + return 1 + + A_root = argv[0] + B_root = argv[1] + + if not os.path.isdir(A_root): + print(f"{prog}: {A_root}: not a directory") + return 2 + + if not os.path.isdir(B_root): + print(f"{prog}: {B_root}: not a directory") + return 3 + + A = skeleton.tree_dict_make(A_root, None) + B = skeleton.tree_dict_make(B_root, None) + + meta.debug_set("tree_dict_newer") + + _result = skeleton.tree_dict_newer(A, B) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(CLI()) diff --git a/tool/skeleton/older b/tool/skeleton/older new file mode 100755 index 0000000..f8ff24d --- /dev/null +++ b/tool/skeleton/older @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- + +""" +older - CLI test driver for skeleton.tree_dict_older(A, B) + +Usage: + older +""" + +from __future__ import annotations + +import os +import sys +from typing import Sequence + +import meta +import skeleton + + +def CLI(argv: Sequence[str] | None = None) -> int: + if argv is None: + argv = sys.argv[1:] + + prog = os.path.basename(sys.argv[0]) if sys.argv else "older" + + if len(argv) != 2 or argv[0] in ("-h", "--help"): + print(f"Usage: {prog} ") + return 1 + + A_root = argv[0] + B_root = argv[1] + + if not os.path.isdir(A_root): + print(f"{prog}: {A_root}: not a directory") + return 2 + + if not os.path.isdir(B_root): + print(f"{prog}: {B_root}: not a directory") + return 3 + + A = skeleton.tree_dict_make(A_root, None) + B = skeleton.tree_dict_make(B_root, None) + + meta.debug_set("tree_dict_older") + + _result = skeleton.tree_dict_older(A, B) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(CLI()) diff --git a/tool/skeleton/skeleton.py b/tool/skeleton/skeleton.py old mode 100755 new mode 100644 index d777a88..4799b64 --- a/tool/skeleton/skeleton.py +++ b/tool/skeleton/skeleton.py @@ -28,7 +28,7 @@ import Harmony # Values are dicts with: # 1. 'mtime' : last modification time (float seconds) # 2. 'node_type' : 'file', 'directory', or 'other' -# 3. 'dir_info' : 'not-a-directory', 'leaf', 'project-root' +# 3. 'dir_info' : 'not-a-directory', 'leaf', 'root' # 4. 'checksum' : present only for file nodes when checksum_fn is # not None # @@ -46,6 +46,13 @@ def tree_dict_make( * Any path (directory or file) for which GitIgnore.check() returns 'Ignore' is completely omitted from the tree_dict. * The root directory ('') is always included. + * Directory dir_info: + - 'root' for the root + - 'branch' for directories that have child directories + (after GitIgnore filtering) + - 'leaf' for directories with no child directories + * Non-directory dir_info: + - 'NA' """ root = os.path.abspath(path) gi = GitIgnore(root) @@ -58,19 +65,29 @@ def tree_dict_make( rel_dir = "" # Skip ignored directories (except the root). - # We do this BEFORE recording the directory, so ignored dirs - # like '.git' or '__pycache__' never appear in tree_dict. if rel_dir != "" and gi.check(rel_dir) == "Ignore": - dirnames[:] = [] # do not descend + dirnames[:] = [] continue + # Filter child directories by GitIgnore so dir_info reflects + # only directories we will actually traverse. + kept_dirnames: list[str] = [] + for dn in list(dirnames): + child_rel = dn if rel_dir == "" else os.path.join(rel_dir, dn) + if gi.check(child_rel) == "Ignore": + dirnames.remove(dn) + else: + kept_dirnames.append(dn) + # Record the directory node itself dir_abs = dirpath dir_mtime = os.path.getmtime(dir_abs) dir_node_type = "directory" if rel_dir == "": - dir_info = "project-root" + dir_info = "root" + elif kept_dirnames: + dir_info = "branch" else: dir_info = "leaf" @@ -88,7 +105,6 @@ def tree_dict_make( else: rel_path = os.path.join(rel_dir, name) - # Filter files via GitIgnore as well. if gi.check(rel_path) == "Ignore": continue @@ -102,12 +118,13 @@ def tree_dict_make( mtime = os.path.getmtime(abs_path) if node_type == "directory": + # Defensive; os.walk normally handles directories separately. if rel_path == "": - dir_info_f = "project-root" + dir_info_f = "root" else: - dir_info_f = "leaf" + dir_info_f = "branch" else: - dir_info_f = "not-a-directory" + dir_info_f = "NA" info: Dict[str, Any] = { "mtime": mtime @@ -129,24 +146,77 @@ def tree_dict_print( tree_dict: Dict[str, Dict[str, Any]] ) -> None: """ - Pretty-print a tree_dict produced by tree_dict_make(). + Pretty-print a tree_dict produced by tree_dict_make() in fixed-width columns: + + [type] [dir] [mtime] [checksum?] [relative path] + + Only the values are printed in each column (no 'field=' prefixes). + mtime is formatted via the Z module for human readability. """ - print("Tree dictionary contents:") + entries: list[tuple[str, str, str, str, str]] = [] + has_checksum = False + for rel_path in sorted(tree_dict.keys()): info = tree_dict[rel_path] display_path = rel_path if rel_path != "" else "." - parts = [ - f"path={display_path}" - ,f"type={info.get('node_type')}" - ,f"mtime={info.get('mtime')}" - ,f"dir={info.get('dir_info')}" - ] + type_val = str(info.get("node_type", "")) + dir_val = str(info.get("dir_info", "")) + + raw_mtime = info.get("mtime") + if isinstance(raw_mtime, (int, float)): + mtime_val = meta.z_format_mtime(raw_mtime) + else: + mtime_val = str(raw_mtime) if "checksum" in info: - parts.append(f"checksum={info['checksum']}") + checksum_val = str(info["checksum"]) + has_checksum = True + else: + checksum_val = "" + + entries.append(( + type_val + ,dir_val + ,mtime_val + ,checksum_val + ,display_path + )) + + # Compute column widths (values only) + type_w = 0 + dir_w = 0 + mtime_w = 0 + checksum_w = 0 + + for type_val, dir_val, mtime_val, checksum_val, _ in entries: + if len(type_val) > type_w: + type_w = len(type_val) + if len(dir_val) > dir_w: + dir_w = len(dir_w) if False else len(dir_val) # keep RT style simple + if len(mtime_val) > mtime_w: + mtime_w = len(mtime_val) + if has_checksum and len(checksum_val) > checksum_w: + checksum_w = len(checksum_val) + + print("Tree dictionary contents:") + for type_val, dir_val, mtime_val, checksum_val, display_path in entries: + line = " " + + line += type_val.ljust(type_w) + line += " " + line += dir_val.ljust(dir_w) + line += " " + line += mtime_val.ljust(mtime_w) + + if has_checksum: + line += " " + line += checksum_val.ljust(checksum_w) + + line += " " + line += display_path - print(" " + ", ".join(parts)) + print(line) def tree_dict_A_minus_B( @@ -377,42 +447,3 @@ def tree_dict_older( return result -def test_tree_dict() -> int: - """ - Test helper for tree_dict_make: - - 1. Locate the Harmony project root. - 2. Set the debug flag 'tree_dict_print'. - 3. Call tree_dict_make() on the Harmony root with no checksum - function (checksum_fn=None). - - The debug flag causes tree_dict_print() to be called automatically - inside tree_dict_make(). - """ - status, Harmony_root = Harmony.where() - - if status == "not-found": - print("Harmony project not found; cannot test tree_dict_make.") - return 1 - - if status == "different": - print("Warning: Harmony not found, using nearest .git directory for tree_dict_make test.") - - meta.debug_set("tree_dict_print") - - _tree = tree_dict_make(Harmony_root, None) - - return 0 - - -def test() -> int: - flag = 1 - if not test_tree_dict(): - print("fail: test_tree_dict") - flag = 0 - return flag - -if __name__ == "__main__": - raise SystemExit(test()) - - diff --git a/tool/skeleton/skeleton_CLI.py b/tool/skeleton/skeleton_CLI.py deleted file mode 100644 index b66b369..0000000 --- a/tool/skeleton/skeleton_CLI.py +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env python3 -# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- - -""" -skeleton_CLI.py - generic CLI for skeleton A,B tree_dict functions - -Usage: - skeleton_CLI.py - -Where is one of: - - - tree_dict_A_minus_B - - tree_dict_in_between_and_below - - tree_dict_newer - - tree_dict_older - -Behavior: - 1. Build tree_dict A from . - 2. Build tree_dict B from . - 3. Enable the debug flag named . - 4. Call skeleton.(A, B). - -Any printing is expected to be done by the called function when the -corresponding debug flag is set. -""" - -from __future__ import annotations - -import os -import sys -from typing import Sequence - -import meta -import skeleton - - -FUNCTIONS = { - "tree_dict_A_minus_B": skeleton.tree_dict_A_minus_B - ,"tree_dict_in_between_and_below": skeleton.tree_dict_in_between_and_below - ,"tree_dict_newer": skeleton.tree_dict_newer - ,"tree_dict_older": skeleton.tree_dict_older -} - - -def _print_usage(prog: str) -> None: - print(f"Usage: {prog} ") - print("Where is one of:") - for name in sorted(FUNCTIONS.keys()): - print(f" - {name}") - - -def CLI(argv: Sequence[str] | None = None) -> int: - if argv is None: - argv = sys.argv[1:] - - prog = os.path.basename(sys.argv[0]) if sys.argv else "skeleton_CLI.py" - - if len(argv) != 3 or argv[0] in ("-h", "--help"): - _print_usage(prog) - return 1 - - func_name = argv[0] - A_root = argv[1] - B_root = argv[2] - - func = FUNCTIONS.get(func_name) - if func is None: - print(f"{prog}: unknown function_name: {func_name}") - _print_usage(prog) - return 2 - - if not os.path.isdir(A_root): - print(f"{prog}: {A_root}: not a directory") - return 3 - - if not os.path.isdir(B_root): - print(f"{prog}: {B_root}: not a directory") - return 4 - - # Build tree_dicts - A = skeleton.tree_dict_make(A_root, None) - B = skeleton.tree_dict_make(B_root, None) - - # Enable debug flag with the same name as the function - meta.debug_set(func_name) - - # Call the function; any printing is done via debug hooks - _result = func(A, B) - - return 0 - - -if __name__ == "__main__": - raise SystemExit(CLI())