--- /dev/null
+#!/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 <A_root> <B_root>
+"""
+
+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} <A_root> <B_root>")
+ 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())
--- /dev/null
+#!/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())
--- /dev/null
+#!/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 <A_root> <B_root>
+"""
+
+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} <A_root> <B_root>")
+ 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())
--- /dev/null
+#!/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())
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
,[] # 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
+ )
--- /dev/null
+#!/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 <A_root> <B_root>
+"""
+
+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} <A_root> <B_root>")
+ 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())
--- /dev/null
+#!/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 <A_root> <B_root>
+"""
+
+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} <A_root> <B_root>")
+ 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())
# 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
#
* Any path (directory or file) for which GitIgnore.check(<rel_path>)
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)
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"
else:
rel_path = os.path.join(rel_dir, name)
- # Filter files via GitIgnore as well.
if gi.check(rel_path) == "Ignore":
continue
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
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(
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())
-
-
+++ /dev/null
-#!/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 <function_name> <A_root> <B_root>
-
-Where <function_name> 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 <A_root>.
- 2. Build tree_dict B from <B_root>.
- 3. Enable the debug flag named <function_name>.
- 4. Call skeleton.<function_name>(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} <function_name> <A_root> <B_root>")
- print("Where <function_name> 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())