From: Thomas Walker Lynch Date: Tue, 13 Oct 2026 04:04:40 +0000 (+0000) Subject: skeleton update X-Git-Url: https://git.reasoningtechnology.com/USER_NAME.png?a=commitdiff_plain;h=4fa9b24df8f6ec39815d70cda618e6c6beab691d;p=RT-Style skeleton update --- diff --git a/administrator/document/how-to_add-project-dependency.html b/administrator/document/how-to_add-project-dependency.html new file mode 100644 index 0000000..327a28c --- /dev/null +++ b/administrator/document/how-to_add-project-dependency.html @@ -0,0 +1,119 @@ + + + + + How to add a project dependency + + + + + + + + + + + +

Project dependencies

+ +

+ A project dependency is software the project uses but did not author: a language toolchain, a library, a tool, or another project. A dependency is installed outside the project, as a sibling directory of it, and is linked into the project under shared/project-dependency. +

+ +

+ Getting an arbitrary piece of software working is different every time, so the administrator is free to do whatever a dependency needs inside shared/project-dependency. The skeleton relies on only the few conventions given in the next section. +

+ +

What the skeleton relies on

+ + + +

+ Files for one dependency share a base name, so that Python.init, Python.org, and the link they describe sit together in a listing. +

+ +

Steps

+ +

Install the dependency beside the project

+

+ Download, clone, or extract the dependency into the directory that holds the project, so that it sits as a sibling of the project directory. Build it there if it needs building. +

+ +

Link it

+

+ Make the link through the project link. The word project in the link target is literal. +

+ + > cd $REPO_HOME/shared/project-dependency + > ln -s project/<dependency directory> <name> + + +

Write the init file

+

+ Write <name>.init in the same directory. Because it runs on every setup for every role, keep it to setting the environment: +

+ +

+ Python.init, which comes with the skeleton, is an example: +

+ + if [[ -d "$REPO_HOME/shared/project-dependency/Python-3.12.3/bin" ]]; then + PATH="$REPO_HOME/shared/project-dependency/Python-3.12.3/bin:$PATH" + else + if [[ "$ROLE" == "administrator" ]]; then + echo "Notice: Local Python-3.12.3 bin not found. Using system Python." + fi + fi + + +

Write the installation notes

+

+ Record what was installed, where it came from, and the commands used, in <name>.org, <name>.html, or comments in the init file. A team member who has cloned the project follows these notes to install the dependency, and then recreates the link if it is not tracked. +

+ +

Decide what is tracked

+

+ The skeleton's .gitignore tracks itself, the project link, and files ending in .init, .install, .org, and .html. Everything else in the directory is ignored. To track a dependency's link, so that it arrives with every clone, add a line naming it: +

+ + !<name> + +

+ A tracked link points at nothing until the dependency is installed beside the project, which is why init files test for the dependency before using it. +

+ +

Commit

+

+ Add the init file, the notes, the .gitignore change, and any tracked link, then commit. +

+ +
+ + diff --git a/administrator/tool/RT-Style_header_v5_to_v6 b/administrator/tool/RT-Style_header_v5_to_v6 new file mode 100755 index 0000000..3ca4169 --- /dev/null +++ b/administrator/tool/RT-Style_header_v5_to_v6 @@ -0,0 +1,306 @@ +#!/usr/bin/env python3 +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- + +""" +RT-Style_header_v5_to_v6 + +Converts RT-Style version 5 document headers to version 6. + +A version 5 document loads the engine through a locator file beside it: + + + +A version 6 document carries an inline header block instead. The block takes +the library from the record made by opening a Manuscript directory's +setup.html, and falls back to the Reasoning Technology server. + +For each version 5 .html file found, the tool replaces the locator line in + with the version 6 block, keeping its indentation. It then deletes +RT-Manuscript_locator.js from that file's directory, unless another version 5 +document there, not converted in this run, still uses it. Any other mention of +RT-Manuscript_locator in the file (an escaped example, prose) is logged for a +person to edit. + +Usage: RT-Style_header_v5_to_v6 [file ...] + RT-Style_header_v5_to_v6 preview [file ...] + + help | -h | --help print this message (also printed when given no arguments) + project walk the directory tree rooted at $REPO_HOME + tree walk the directory tree rooted at the current directory + local only the current directory, no subdirectories + file ... only the named .html files + preview print the log of what would do, change nothing + +Every directory is walked, whatever its name. Symbolic links to directories +are not followed, so a link back up the tree cannot cause a loop or reach +outside it. +""" + +import sys ,os ,re +from pathlib import Path + +LOCATOR_FILE = "RT-Manuscript_locator.js" +V6_MARK = 'localStorage.getItem("RT.dirpr_library")' + +V6_BLOCK = [ + '' +] + +LOCATOR_LINE = re.compile( + r'^(?P[ \t]*)\s*\s*$' + ,re.IGNORECASE +) +HEAD_END = re.compile(r'' ,re.IGNORECASE) + + +# ---------------------------------------------------------------- log + +class Log: + def __init__(self ,preview: bool ,base: Path): + self.preview = preview + self.base = base + self.count = {"converted": 0 ,"deleted": 0 ,"already": 0 ,"hand": 0 + ,"untouched": 0 ,"stray": 0 ,"error": 0} + + def rel(self ,p: Path) -> str: + try: return str(p.resolve().relative_to(self.base)) + except ValueError: return str(p) + + def line(self ,tag: str ,text: str): + print(f" {tag:<15} {text}") + + def act(self ,done: str ,would: str ,p: Path ,key: str): + self.line(would if self.preview else done ,self.rel(p)) + self.count[key] += 1 + + def summary(self): + c = self.count + v = "would be " if self.preview else "" + print() + print("summary:") + print(f" {c['converted']:>4} .html files {v}converted to version 6") + print(f" {c['deleted']:>4} {LOCATOR_FILE} files {v}deleted") + print(f" {c['already']:>4} .html files already version 6") + print(f" {c['hand']:>4} .html files with mentions to edit by hand") + print(f" {c['untouched']:>4} other .html files left alone") + if c['stray']: + print(f" {c['stray']:>4} {LOCATOR_FILE} files kept (see 'kept' lines)") + if c['error']: + print(f" {c['error']:>4} errors") + if self.preview: + print("preview only: nothing was changed") + + +# ---------------------------------------------------------------- finding files + +def walk(root: Path ,recursive: bool): + """Yields (directory ,[.html files] ,has_locator) for each directory.""" + if not recursive: + entries = sorted(root.iterdir()) + yield root ,[e for e in entries if e.is_file() and e.suffix == ".html"] ,(root / LOCATOR_FILE).is_file() + return + for d ,dirs ,files in os.walk(root): + dirs.sort() + files = sorted(files) + yield Path(d) ,[Path(d) / f for f in files if f.endswith(".html")] ,LOCATOR_FILE in files + + +# ---------------------------------------------------------------- conversion + +def convert(text: str): + """Returns (new_text ,replaced_count ,leftover). leftover lists + (line_number ,line) for locator mentions that were not replaced.""" + lines = text.split("\n") + out = [] + replaced = 0 + leftover = [] + in_head = True + for n ,line in enumerate(lines ,1): + m = LOCATOR_LINE.match(line) if in_head else None + if m: + eol = "\r" if line.endswith("\r") else "" + out.extend(m.group("indent") + b + eol for b in V6_BLOCK) + replaced += 1 + else: + out.append(line) + if LOCATOR_FILE.removesuffix(".js") in line: + leftover.append((n ,line.strip())) + if in_head and HEAD_END.search(line): + in_head = False + return "\n".join(out) ,replaced ,leftover + +def do_file(f: Path ,log: Log) -> bool: + """Converts one .html file. Returns True when it was version 5.""" + try: + text = f.read_text(encoding="utf-8") + except Exception as e: + log.line("error" ,f"{log.rel(f)}: cannot read: {e}") + log.count["error"] += 1 + return False + + if V6_MARK in text and LOCATOR_FILE.removesuffix(".js") not in text: + log.line("already v6" ,log.rel(f)) + log.count["already"] += 1 + return False + + new_text ,replaced ,leftover = convert(text) + + if replaced: + if not log.preview: + try: + mode = f.stat().st_mode + f.write_text(new_text ,encoding="utf-8") + os.chmod(f ,mode) + except Exception as e: + log.line("error" ,f"{log.rel(f)}: cannot write: {e}") + log.count["error"] += 1 + return False + log.act("converted" ,"would convert" ,f ,"converted") + if replaced > 1: + log.line("note" ,f"{log.rel(f)}: {replaced} locator lines in , all replaced") + elif not leftover: + log.count["untouched"] += 1 + + if leftover: + log.count["hand"] += 1 + for n ,line in leftover: + log.line("edit by hand" ,f"{log.rel(f)}:{n}: {line[:100]}") + + return replaced > 0 + +def uses_locator(f: Path) -> bool: + """True when f still loads the engine through a locator line in .""" + try: + text = f.read_text(encoding="utf-8") + except Exception: + return False + for line in text.split("\n"): + if LOCATOR_LINE.match(line): return True + if HEAD_END.search(line): return False + return False + +def do_locator(d: Path ,converted: set ,log: Log): + """Deletes d's locator unless a version 5 document not converted in this + run still uses it.""" + loc = d / LOCATOR_FILE + users = sorted(f.name for f in d.iterdir() + if f.is_file() and f.suffix == ".html" + and f.resolve() not in converted and uses_locator(f)) + if users: + shown = ", ".join(users[:3]) + (" ..." if len(users) > 3 else "") + log.line("kept" ,f"{log.rel(loc)} (still used by {shown})") + log.count["stray"] += 1 + return + if not log.preview: + try: + loc.unlink() + except Exception as e: + log.line("error" ,f"{log.rel(loc)}: cannot delete: {e}") + log.count["error"] += 1 + return + log.act("deleted" ,"would delete" ,loc ,"deleted") + +def run_dirs(root: Path ,recursive: bool ,log: Log): + for d ,html ,has_locator in walk(root ,recursive): + converted = set() + for f in html: + if do_file(f ,log): converted.add(f.resolve()) + if has_locator: + if converted: + do_locator(d ,converted ,log) + else: + log.line("kept" ,f"{log.rel(d / LOCATOR_FILE)} (no version 5 document beside it)") + log.count["stray"] += 1 + +def run_files(files: list[Path] ,log: Log): + converted = set() + dirs = [] + for f in files: + if do_file(f ,log): + converted.add(f.resolve()) + d = f.parent.resolve() + if d not in dirs: dirs.append(d) + for d in dirs: + if (d / LOCATOR_FILE).is_file(): + do_locator(d ,converted ,log) + + +# ---------------------------------------------------------------- command line + +def print_help() -> int: + print(__doc__.strip()) + return 0 + +def CLI(argv=None) -> int: + if argv is None: + argv = sys.argv[1:] + if not argv or argv[0] in ("help" ,"-h" ,"--help"): + return print_help() + + preview = False + if argv[0] == "preview": + preview = True + argv = argv[1:] + if not argv: + print("Error: 'preview' needs a command after it.\n" ,file=sys.stderr) + print_help() + return 1 + + command ,rest = argv[0] ,argv[1:] + + if command == "file": + if not rest: + print("Error: 'file' needs at least one file name." ,file=sys.stderr) + return 1 + files = [Path(a) for a in rest] + for f in files: + if not f.is_file(): + print(f"Error: {f} is not a file." ,file=sys.stderr) + return 1 + base = Path.cwd().resolve() + log = Log(preview ,base) + print(f"RT-Style_header_v5_to_v6 {'preview ' if preview else ''}file in {base}") + run_files(files ,log) + log.summary() + return 1 if log.count["error"] else 0 + + if rest: + print(f"Error: '{command}' takes no arguments." ,file=sys.stderr) + return 1 + + if command == "project": + repo_home = os.environ.get("REPO_HOME") + if not repo_home: + print("Error: 'project' needs REPO_HOME to be set." ,file=sys.stderr) + return 1 + root ,recursive = Path(repo_home) ,True + elif command == "tree": + root ,recursive = Path.cwd() ,True + elif command == "local": + root ,recursive = Path.cwd() ,False + else: + print(f"Error: unknown command '{command}'.\n" ,file=sys.stderr) + print_help() + return 1 + + if not root.is_dir(): + print(f"Error: {root} is not a directory." ,file=sys.stderr) + return 1 + + base = root.resolve() + log = Log(preview ,base) + print(f"RT-Style_header_v5_to_v6 {'preview ' if preview else ''}{command} in {base}") + run_dirs(base ,recursive ,log) + log.summary() + return 1 if log.count["error"] else 0 + +if __name__ == "__main__": + sys.exit(CLI()) diff --git a/administrator/tool/new-project b/administrator/tool/new-project index 5394bed..c949ddb 100755 --- a/administrator/tool/new-project +++ b/administrator/tool/new-project @@ -9,12 +9,15 @@ the Harmony skeleton architecture. The script executes the following sequential lifecycle: 1. Validates the execution environment (requires an active role session). - 2. Safely extracts a clean copy of all Git-tracked files using an archive stream. - 3. Renames the boilerplate 0pus_Harmony anchor file to reflect the new project. - 4. Injects an initial version tracking entry into the shared configuration space. - 5. Initializes a fresh Git repository bound to the core-developer_branch. - 6. Stages and commits the pristine skeleton infrastructure as a baseline. - 7. Maps any upstream remote network targets passed via CLI parameters. + 2. Checks the git status of the skeleton. The new project is made from the + last commit, so uncommitted changes and untracked files would be left + out. When there are any, lists them and asks whether to continue. + 3. Safely extracts a clean copy of all Git-tracked files using an archive stream. + 4. Renames the boilerplate 0pus_Harmony anchor file to reflect the new project. + 5. Injects an initial version tracking entry into the shared configuration space. + 6. Initializes a fresh Git repository bound to the core-developer_branch. + 7. Stages and commits the pristine skeleton infrastructure as a baseline. + 8. Maps any upstream remote network targets passed via CLI parameters. """ import sys ,os ,subprocess @@ -28,14 +31,31 @@ def get_Z_date() -> str: except Exception: return "1970-01-01" -def work(project_name: str ,remotes: list[str]) -> int: - """Orchestrates the assembly of the new skeleton instance.""" - repo_home = os.environ.get("REPO_HOME") - if not repo_home: - print("Error: REPO_HOME is not set. A person must run this from within a sourced role environment." ,file=sys.stderr) - return 1 +def uncommitted(repo_path: Path) -> list[str] | None: + """Returns the lines of 'git status --porcelain' for the skeleton: one per + file with uncommitted changes or untracked. None when git cannot report.""" + try: + res = subprocess.run( + ["git" ,"status" ,"--porcelain" ,"--untracked-files=normal"] + ,cwd=str(repo_path) ,capture_output=True ,text=True ,check=True + ) + except Exception: + return None + return [line for line in res.stdout.splitlines() if line.strip()] + +def confirm(question: str) -> bool: + """Asks a yes or no question at the terminal. Without a terminal to ask + at, the answer is no.""" + if not sys.stdin.isatty(): + return False + try: + answer = input(question + " [y/N] ") + except EOFError: + return False + return answer.strip().lower() in ("y" ,"yes") - repo_path = Path(repo_home).resolve() +def work(repo_path: Path ,project_name: str ,remotes: list[str]) -> int: + """Orchestrates the assembly of the new skeleton instance.""" # Resolve destination relative to the parent space of the current repository dest_path = Path(project_name) @@ -129,7 +149,29 @@ def CLI(argv=None) -> int: print("Error: project name is required." ,file=sys.stderr) return 1 - return work(project_name ,remotes) + repo_home = os.environ.get("REPO_HOME") + if not repo_home: + print("Error: REPO_HOME is not set. A person must run this from within a sourced role environment." ,file=sys.stderr) + return 1 + repo_path = Path(repo_home).resolve() + + status = uncommitted(repo_path) + if status is None: + print(f"Error: could not read the git status of {repo_path}." ,file=sys.stderr) + return 1 + if status: + shown = status[:20] + print(f"Warning: {repo_path} has {len(status)} uncommitted or untracked file(s):") + for line in shown: + print(" " + line) + if len(status) > len(shown): + print(f" ... and {len(status) - len(shown)} more (see 'git status')") + print("The new project is made from the last commit, so these will not be in it.") + if not confirm("Continue anyway?"): + print("Stopped. Nothing was made. Commit or stash the changes, then run new-project again.") + return 1 + + return work(repo_path ,project_name ,remotes) if __name__ == "__main__": sys.exit(CLI()) diff --git a/developer/tool/build_component/copy b/developer/tool/build_component/copy index 2939ef3..0a1998c 100755 --- a/developer/tool/build_component/copy +++ b/developer/tool/build_component/copy @@ -10,8 +10,8 @@ sys.path.insert(0, os.path.join(os.path.abspath("tool"), "build_component")) import sync_engine HELP = """usage: copy {all|clean|help} - all Stages files from authored to scratchpad. - clean Removes the specified namespace from scratchpad. + all Stages files from authored to scratchpad/made/. + clean Removes scratchpad/made/. help Shows this message. """ @@ -24,7 +24,7 @@ def cmd_all(): sys.exit(1) src_root = os.path.join("authored", namespace_dir) - dst_root = os.path.join("scratchpad", namespace) + dst_root = os.path.join("scratchpad", "made", namespace) sync_engine.execute_sync(src_root, dst_root, prefix="copy", apply_modes=False) @@ -34,7 +34,7 @@ def cmd_clean(): print("copy: error: NAMESPACE environment variable must be set.", file=sys.stderr) sys.exit(1) - dst_root = os.path.join("scratchpad", namespace) + dst_root = os.path.join("scratchpad", "made", namespace) if os.path.exists(dst_root): shutil.rmtree(dst_root) print(f"copy: Cleaned {dst_root}") diff --git a/document/introduction_Harmony.html b/document/introduction_Harmony.html index 4271b0b..432c63b 100644 --- a/document/introduction_Harmony.html +++ b/document/introduction_Harmony.html @@ -22,9 +22,9 @@ - @@ -32,333 +32,217 @@

Purpose

- Harmony provides a language-agnostic project directory structure and maintenance tools for long-lived, multi-person team software development. The structure exists to enforce: + Harmony is a project skeleton: a language-agnostic directory structure and a set of maintenance tools, kept in a git repository, from which new projects are made. It is designed for long-lived software developed by a team, and serves equally well for one person, who takes on each role in turn. The structure exists to give:

  1. Clarity about where things live.
    • Role-based work areas.
    • -
    • Separation of skeleton, team member authored, machine-made, and third-party installed software.
    • +
    • Separation of skeleton, team member authored, machine-made, and depended-upon software.
  2. A safe, predictable build and promotion workflow.
+

Making a project

- Harmony is a project skeleton, a set of tools and directories that have been checked into a git repository. To create a Harmony-based project, the project administrator performs these steps: + A project administrator makes a new project from a clone of Harmony, by taking on the administrator role and running new-project with the name of the new project: +

+ + > cd Harmony + > ./setup administrator + > new-project <name> + +

+ new-project makes the new project as a sibling directory of Harmony, then:

    -
  1. Clone the Harmony project to a local directory.
  2. -
  3. Remove the .git tree.
  4. -
  5. Rename the Harmony directory to the name of the new project.
  6. -
  7. Rename the 0pus_Harmony file to reflect the name of the new project.
  8. -
  9. Add a line to the shared/tool/version file for the new project.
  10. -
  11. git init -b core_developer_branch
  12. +
  13. Copies the files of Harmony's last commit, leaving out Harmony's git history, untracked files, and uncommitted changes.
  14. +
  15. Renames 0pus_Harmony to 0pus_<name>.
  16. +
  17. Adds a line for the new project to shared/tool/version.
  18. +
  19. Initializes a git repository on the core-developer_branch branch, and commits the copied skeleton.
  20. +
  21. Adds any remote repositories given as -remote <url>.
-

- The order is important so as to protect the Harmony skeleton project from accidental check-ins from users. + Because the new project starts with its own git history, work done in it cannot be checked in to the Harmony skeleton by accident.

- Leave the Harmony skeleton version in the version file, so that project administrators can understand what has changed in the skeleton over time. + Leave the Harmony line in the version file. It records which skeleton the project was made from, so that an administrator can later see what has changed in the skeleton since, and bring the project up to date. The administrator tool skeleton_diff compares a project with a Harmony checkout for this purpose.

+ +

Taking on a role

- The core_developer_branch is the branch that the core development team works on. Project releases are moved to release branches. + A person works in a project by taking on a role. The roles are administrator, developer, tester, and consumer. From the top directory of the project, run setup with the role:

- -

Environment setup

+ + > ./setup developer +

- Python programmers who use virtual environments will be familiar with an analogous process. The term 'virtual environment' does not invoke any hardware virtualization features; rather, it is a local environment setup. To avoid confusion, a Harmony user refers instead to the 'project setup'. The command to establish the environment is called 'setup' instead of 'activate'. Also note that in Harmony, unlike in Python, there are multiple setups available, each tailored to the specific role a person takes on. + setup starts a new bash shell configured for the role, and leaves the person in the role's directory. The prompt shows the project and the role. exit leaves the role and returns to the shell setup was run from. It is common to have several terminal windows or Emacs shells open, each in a different role.

- As of the time of this writing, the defined roles are: administrator, consumer, developer, and tester. A person takes on a role by sourcing the top-level setup script and giving the target role as an argument. For example, in a bash shell with > as the prompt, the command is: + Python programmers will recognize this as similar to a virtual environment. Harmony calls it the project setup, and the command is setup rather than activate. Unlike a Python virtual environment, there is one setup for each role.

- - - > . setup <role> - -

- Specifically for the developer role: + In the new shell, the setup:

- - - > . setup developer - - +
    +
  • Sets ROLE, ROLE_HOME and SETUP, and puts <role>/tool on the PATH.
  • +
  • Sources the project-wide shared/tool/setup, which sets REPO_HOME and PROJECT, puts shared/tool and shared/authored on the PATH, and sources each project dependency's .init file.
  • +
  • Sources shared/authored/setup, when the project has one, for project-specific additions to every role.
  • +
  • Changes into the role's directory and sources <role>/tool/setup, the setup customized for that role in this project.
  • +
  • When running inside Emacs, copies the environment to Emacs with env_to_emacs.
  • +

- For the administrator role: + Tools rely on this environment. Absolute paths are formed from REPO_HOME, and some tools, such as build and promote, refuse to run outside the role they belong to.

- - - > . setup administrator - +

Roles and their directories

- Instead of starting with a period, the source command can be spelled out explicitly, for example: + Each role has a directory at the top of the project. The shared directory holds what all roles use.

- - - > source setup tester - +

Administrator

- Behind the scenes, the setup script performs the following actions: + The administrator keeps the project in step with the Harmony skeleton, maintains the role environments, installs project dependencies, and makes releases. administrator/tool holds the tools for this, among them new-project, skeleton_diff, and archive, which writes an archive of the repository to the scratchpad.

+

Developer

+

+ The developer writes the project's source and builds it. +

    -
  • Sources the project-wide setup (shared/tool/setup) to establish the core environment variables (e.g., REPO_HOME and PROJECT).
  • -
  • Conditionally sources shared/authored/setup (if present) to apply administrator-injected, project-specific tool configurations.
  • -
  • Dynamically sources all .init files found in the shared/linked-project/ directory.
  • -
  • Configures the PATH to include shared tools, library environments, and the specific <role>/tool directory.
  • -
  • Changes the working directory into the specified role's workspace.
  • -
  • Sources the <role>/tool/setup script. While the earlier steps apply the standard Harmony skeleton setup, this final step applies the role setup that is customized for this specific project.
  • +
  • authored/ : Source written by the team. Tracked.
  • +
  • document/ : Documents for developers, such as coding and naming conventions.
  • +
  • experiment/ : Informal, short-lived trials.
  • +
  • scratchpad/ : Untracked. Intermediate build files and the staged products of a build.
  • +
  • tool/ : The developer's tools, including build and promote.
-

After git clone

+

Tester

- Because git does not track certain artifact directories (such as consumer/ outputs), a freshly cloned repository lacks external dependencies and consumable products. Team members must perform a few steps to populate these areas. + The tester holds the formal tests, including regression suites, and runs them against the work product in consumer/. A developer may keep informal tests in developer/experiment. A test that is to be kept is moved to tester/, which keeps writing code and validating it apart.

-

Third-party tools

-

- Harmony is language agnostic. When a project makes use of project-specific C, Python, NodeJS, Java, or other tools, the project administrator configures the project to expect these tools in the shared/linked-project directory via the `.init` plugin system. -

+

Consumer

- Because multiple team members will have to repeat the third-party install process after cloning a project, the administrator should carefully document the installation steps and place the resulting documents in the administrator/document directory. The standard installation method is to clone the external tool into the parent directory alongside the project, create a symlink to it under shared/linked-project/ using the project/ parent link, and supply an .init script to manage the local environment variables. + consumer/ receives the work product when a developer promotes it. It is untracked, apart from its .gitignore, and is treated as a deployment target: files arrive there only through promote, and nothing is built in it. The consumer role uses the work product as its end users would.

-

Consumer build

+

Shared

+
    +
  • shared/tool/ : Tools for all roles, and the project-wide setup. The version command prints the version lines.
  • +
  • shared/authored/ : Project-specific additions for all roles. It is on every role's PATH, and may hold a setup that every role sources.
  • +
  • shared/project-dependency/ : Software the project uses but did not author. See "Project dependencies" below.
  • +
+ +

Building and promoting

- In this section we use the term 'consumer' to mean any team member that wants to make use of the project work product. The tester will want to test it, and the consumer role will want to deploy it, etc. + Building and promotion are separate steps. In the developer role, build makes the work product from developer/authored and stages it on the developer's scratchpad. promote write then copies what changed to consumer/. The result is a promotion candidate, which stays as it is until the next promotion.

- Because compiled binaries and final layouts are not tracked in the repository, the current version of Harmony requires a work product consumer to run a local build after cloning the project. The results of the build will appear directly in the consumer/ directory. + Source in developer/authored is grouped into directories named <Namespace>.<Tool>. The suffix names the build component in developer/tool/build_component that builds that directory, such as make for C, or copy for files used as they are. Given no arguments, build prints its usage and the namespaces it found. promote given no arguments prints its commands, among them dry write, which shows what write would copy.

- To facilitate this, the developer must explicitly document the project's build and promote procedure, saving this guide as developer/document/build.html. The consumer must then read this document and execute the described steps to locally populate their consumer/ directory. + The tester runs tests against the promotion candidate. Tests read from consumer/ and never write to it, though a tester may copy files elsewhere to experiment with them. A developer often has a second window open in the tester role, and cycles through edit, build, promote and test.

+

After cloning a project

- Because the build and promotion tools are strictly isolated within the developer workspace, the consumer must temporarily put on the developer hat to perform the build. Typically the consumer build procedure will be a variation of the following: + Built products are not tracked, so a fresh clone has an empty consumer/. A team member who needs the work product builds it, taking on the developer role to do so:

- -
    -
  1. > bash
  2. -
  3. > cd <project>
  4. -
  5. > . setup developer
  6. -
  7. > build <namespace>
  8. -
  9. > promote write
  10. -
  11. > exit
  12. -
  13. > bash
  14. -
  15. > cd <project>
  16. -
  17. > . setup <role>
  18. -
- + + > ./setup developer + > build <namespace> + > promote write + > exit + > ./setup <role> +

- This sequence opens a bash shell, assumes the developer role to orchestrate the build, makes the work product, then promotes it to the consumer's workspace. The exit command drops the developer role. The last two lines put the person into the <role> workspace, typically for testing or deploying. + The developer documents any project-specific build steps in developer/document/build.html. A fresh clone also lacks the project's dependencies, which each team member installs as described beside each dependency in shared/project-dependency.

- -

Directory semantic properties

+

Project dependencies

- This section discusses our thinking in naming the files and directories found in the Harmony skeleton. + Harmony is language agnostic. When a project uses C, Python, NodeJS, Java, or other software it did not author, the administrator installs that software as a sibling directory of the project and links it in under shared/project-dependency, with an .init file that every role's setup sources to put the dependency into the environment. Installation notes for each dependency sit beside its .init file, so that each team member can repeat the installation after cloning.

- A directory name is considered to be a property given to each file contained in the directory. A full path then forms a semantic sentence describing each file. + The directory comes with a project link to the directory holding the project and its siblings, through which dependencies are linked, and with Python as an example dependency. The steps are given in administrator/document/how-to_add-project-dependency.html.

+ +

Directory names

- Because a directory name represents a property, it is rarely plural. For example, when each and every file in a directory is a test, the directory is named test. + A directory name is a property of each file in the directory, so a full path reads as a description of the file. Because a name states a property of each file, it is rarely plural: when every file in a directory is a test, the directory is named test.

- We run into limitations when using a conventional file system as though it were a property based file system. One limitation is that we are forced to choose a single directory name for each file. When a set of files in a directory all share the same multiple properties, we can use a compound directory name with the properties separated by an underscore, but it is impractical to specify overlapping directory groupings, i.e. we can't arbitrarily define any number of properties for a file in this manner. + A conventional file system can only approximate a property-based one. Each file sits in one directory, so it receives one property per level of the path. Files sharing several properties can be given a compound name with the properties separated by an underscore, but overlapping groupings cannot be expressed.

- The following list presents each property type in order of preference when naming directories: + When choosing a directory name, prefer properties in this order:

    -
  • Role Association (administrator, developer, tester, consumer): Identifies the persona, whether human or AI, intended to interact with the files.
  • -
  • Provenance (authored, made): Indicates whether the file was created by an intellect or mechanically produced by a tool.
  • -
  • Capability (tool, document, experiment): Describes the primary function or structural nature of the file.
  • -
  • Lifecycle State (scratchpad, stage): Denotes the persistence, volatility, or promotion status of the file.
  • -
  • Tracking Status (tracked, untracked): Specifies the version control expectations for the artifacts.
  • +
  • Role (administrator, developer, tester, consumer): the role, human or AI, that works with the files.
  • +
  • Provenance (authored, made): whether a person wrote the file or a tool produced it.
  • +
  • Capability (tool, document, experiment): what the file is for.
  • +
  • Lifecycle (scratchpad, stage): how long the file is expected to last, or where it is in promotion.
  • +
  • Tracking (tracked, untracked): what version control does with it.

Authored, made, scratchpad, inherited

- Files found in a directory named authored were written by project team members. They did not come with the Harmony skeleton, nor with the installation of other software. Project build tools treat authored directories as strictly read-only. Typically these files constitute the intellectual property of a project. -

-

- All source code that gets built into a promotion or project release must be placed in the developers' authored directory. The story is not as clean for build tools and other files. New documents go into document directories, and new tools go into the tool directories, etc. As a specific example, the developer will almost certainly edit the developer/tool/build file. -

-

- When the Harmony version line in the shared/tool/version file is left in place, it is straightforward for a project administrator to determine which Harmony skeleton files have been edited in a project, and which new files have been added. -

-

- Files found in a directory named scratchpad are not tracked. Hence, a git clone will always return empty scratchpad directories. It is common for tools to place intermediate files on a scratchpad. It is also common for files to be staged on a scratchpad. Tools play nice and use subdirectories on the pad, so a person who is aware of those subdirectory names can use a scratchpad as a temporary directory. There is a scratchpad maintenance tool that comes with the Harmony, called unimaginatively, scratchpad. Pay attention as one of its commands is clear, and that deletes everything on the current directory's scratchpad. -

-

- Third party software is installed under shared/linked-project. Other files are said to be inherited, or to be customizations. + Files in a directory named authored were written by the project's team. They did not come with the skeleton, nor with the installation of other software, and they typically constitute the project's intellectual property. Build tools treat authored directories as read-only. All source that is built into a promotion or a release is placed in developer/authored.

- -

Top-level repository layout

- A team member will source the project setup file to take on a role. As of this writing, the supported roles are: administrator, developer, tester, and consumer. + Files that came with the skeleton are inherited. When the team edits one, such as developer/tool/build, it becomes a customization. New documents go in document directories and new tools in tool directories. Because the Harmony line stays in the version file, an administrator can tell which skeleton files a project has customized and which it has added.

- -
    -
  • administrator/ : Project-local tools and skeleton maintenance.
  • -
  • developer/ : Primary workspace for developers.
  • -
  • tester/ : Regression and validation workspace for testers.
  • -
  • consumer/ : Consumption workspace acting as the final artifact sink.
  • -
  • shared/ : Shared ecosystem tools and global environments.
  • -
- -

The administrator work area

- This directory holds the tools and documentation used to manage the project as a whole. It includes the HTML documentation for the project ontology and workflow, as well as project-local tools utilized by the administrator to maintain the Harmony skeleton. + Files in a directory named scratchpad are untracked, so a clone always has empty scratchpads. Tools place intermediate and staged files there, each in its own subdirectory, so a person who knows those names can also use a scratchpad as a temporary directory. The scratchpad tool maintains the scratchpad of the current directory. Note that its clear command deletes everything on that scratchpad.

-

The developer work area

+

Documents

- This directory is entered by first going to the top-level directory of the project, then sourcing . setup developer. + Each directory named document holds documents for the role, or the scope, of its parent:

    -
  • authored/ : Human-written source. Tracked by Git.
  • -
  • made/ : Tracked artifacts generated by tools (e.g., links to CLI entry points).
  • -
  • experiment/ : Try-it-here code. Short-lived spot testing.
  • -
  • scratchpad/ : Git-ignored directory. Holds all intermediate build outputs, including the staged namespace directories for promotions.
  • -
  • tool/ : Developer-specific tools (like the promote and build scripts).
  • +
  • document/ : The project as a whole, such as this introduction.
  • +
  • administrator/document/ : Maintaining the project skeleton, adding dependencies, and making releases.
  • +
  • developer/document/ : Coding standards, conventions, and the build.
  • +
  • tester/document/ : Test plans and testing tools.
  • +
  • consumer/<namespace>/document/ : Documents released with the work product, such as manuals and API references.
- -

The tester work area

- This directory is dedicated to formal testing, including regression suites. While a developer can run and keep informal spot tests in their experiment/ directory, any experiment promoted to a formal test is moved here. This enforces the boundary between writing code and validating it. + Documents are written in HTML with the RT semantic tags, and formatted by RT-Style, a separate project at https://github.com/Thomas-Walker-Lynch/RT-Style. A document loads RT-Style through a short script block at the top of its head. To write a new document, copy an existing one, keep that block as it is, and type over the contents.

- -

The shared tree

- This directory contains ecosystem tools and global environments available to all roles. This includes the shared tool directory, as well as third-party symlinks and .init scripts required by the project. To assist in project specific modifications to the Harmony skeleton, Harmony comes with an empty shared/authored directory that is listed earlier in the executable search path than shared/tool. + Documents are read in Chrome. With nothing set up, a document loads RT-Style from style.ReasoningTechnology.com. To read without a network connection, or against a local copy of RT-Style, open that copy's Manuscript/setup.html once in Chrome. Chrome then loads RT-Style from it for every local document.

- -

The consumer tree

- The consumer/ tree is where developers put work product that is ready to be consumed. The entire directory is git-ignored and treated as a transient deployment target. Artifacts arrive in the consumer/ tree only when the promote script is invoked, which performs a flat-copy from the developer's scratchpad/made directory. + document/writing_tool-script.html gives the conventions for writing tools.

-

Document directories

+

What git tracks

- There is a directory for documents that talks about the project as a whole, one for each role, one for tools that are shared among the roles, and the released work product probably comes with a document directory of its own. + Everything is tracked except:

    -
  • document/ : Top-level onboarding, project-wide structure, such as this document.
  • -
  • consumer/<namespace>/document/ : Documentation for end-users of made code (e.g., man pages, application manuals, library API references).
  • -
  • administrator/document/ : Documentation for maintaining the project skeleton and global tools.
  • -
  • developer/document/ : Documentation for developers, including coding standards and internal API guides.
  • -
  • tester/document/ : Documentation for testers detailing test plans and tools.
  • -
  • shared/document/ : Documentation on installing and configuring shared tools.
  • +
  • consumer/, apart from its .gitignore.
  • +
  • Every scratchpad/, apart from its .gitignore.
  • +
  • In shared/project-dependency/, whatever its .gitignore does not list as tracked.
  • +
  • Archives, editor backups, and compiled files, as listed in the top-level .gitignore.
+

Workflow and releases

- Currently, our developers write documents directly in HTML using the RT semantic tags. See the RT·Style project and the documentation there. A common approach is to copy another document and the setup.js file, then to type over the top of that other document. -

- -

Untracked directories

-
    -
  1. consumer/ (Excluding the base .gitignore)
  2. -
  3. shared/linked-project/ (Excluding .init and .gitignore files)
  4. -
  5. **/scratchpad/
  6. -
- -

Workflow

-

See the document "Product Development Roles and Workflow" for more details.

- -

Developer promotion and project releases

-

- As a first step, a developer creates a promotion candidate inside of the consumer/ directory. This is typically done by running build to stage the artifacts into scratchpad/made, followed by running promote write. The developer will often modify the versions of one or both of those tools that come with the Harmony skeleton. The promotion candidate remains stable until the next promotion. -

-

- Then the tester runs tests on the promotion candidate. Tests must only read from the consumer/ directory, though local copies can be made and edited as experiments. + The project moves forward through a cycle: the developer promotes a candidate, the tester tests it and files issues, and the developer fixes them and promotes again. The core-developer_branch is the branch the core development team works on. When the product manager judges the work product reliable and complete enough, the administrator makes a project release on a branch named release_v<major>, and tags it.

- It is common for a developer to open a second window on his desktop, and then enter the project as a tester in that second window. The developer can then make a promotion candidate, run the tests, edit source code, and perhaps tests, and then quickly spin through the test-debug-fix-promote cycle repeatedly. + These documents give the details:

-

- When the product manager determines the work product to be sufficiently reliable and feature rich, the administrator will make a project release. He will do this by creating a branch called release_v<major> and tagging it. The major release numbers go up incrementally. -

- -

The Harmony directory tree

- - - 2026-06-23 13:41:45 Z [Harmony:administrator] Thomas_developer@StanleyPark - §/home/Thomas/subu_data/developer/project§ - > tree Harmony - Harmony - ├── 0pus_Harmony - ├── administrator - │ ├── authored - │ ├── document - │ │ └── setup.js - │ └── tool - │ ├── archive - │ └── setup - ├── consumer - │ └── .gitignore - ├── developer - │ ├── authored - │ │ └── hello.CLI.c - │ ├── document - │ │ ├── 02_RT_Code_Format.html - │ │ ├── 03_Naming_and_Directory_Conventions.html - │ │ ├── 04_Language_Addenda.html - │ │ └── setup.js - │ ├── experiment - │ ├── made - │ ├── scratchpad - │ └── tool - │ ├── do_all - │ ├── build - │ ├── makefile - │ ├── promote - │ └── setup - ├── document - │ ├── introduction_Harmony.html - │ ├── role-and-workflow_product-development.html - │ └── setup.js - ├── LICENSE - ├── README.md - ├── scratchpad - ├── setup - ├── shared - │ ├── authored - │ ├── document - │ │ ├── install_generic.org - │ │ ├── install_Python.org - │ │ └── setup.js - │ ├── made - │ ├── dictionary_style-directory.js - │ ├── linked-project - │ │ ├── project -> ../../../ - │ │ ├── Python-3.12.3 -> project/Python-3.12.3 - │ │ ├── Python.init - │ │ ├── RT·Style -> project/RT·Style/consumer - │ │ └── RT·Style.init - │ └── tool - │ ├── scratchpad - │ ├── setup - │ ├── style - │ └── version - └── tester - ├── authored - │ └── test_routine.sh - ├── RT_Format - │ ├── RT_Format - │ ├── RT·formatter.el - │ ├── data_test-0.c - │ └── data_test-1.py - └── tool - └── setup - +
    +
  • document/role-and-workflow_product-development.html : The roles, and how their work interacts.
  • +
  • document/role-and-workflow_product-maintenance.html : Issues, triage, and patching releases.
  • +
  • administrator/document/how-to_release.html : Making a release.
  • +
diff --git a/document/writing_tool-script.html b/document/writing_tool-script.html new file mode 100644 index 0000000..5942d3f --- /dev/null +++ b/document/writing_tool-script.html @@ -0,0 +1,287 @@ + + + + + Writing a tool script + + + + + + + + + + + +

Purpose

+ +

+ A tool is a script a person runs from a role setup to do work on files. This document gives the conventions for how an RT tool presents itself on the command line, reports what it does, and changes files safely. It complements RT-code-format.html, which covers identifier names, layout, and the separation of the CLI() function from the work functions. Those rules apply here and are not repeated. +

+ +

+ The conventions serve three needs of the person at the keyboard. They must be able to see what a tool will do before it does it, read afterward what it did, and run it again without harm. +

+ +

+ Examples are taken from RT-Style_header_v5_to_v6, the administrator tool that converts RT-Style version 5 document headers to version 6. +

+ +

The command line

+ +

No arguments prints the help

+ +

+ A tool given no arguments prints its help message and exits with status 0. A person who types the bare name of a tool is asking what it does. Nothing on the file system changes. +

+ +

The first argument is a command

+ +

+ The first argument names a command, written as a word, not a flag. The words help, -h and --help all print the help message. Further arguments, when a command accepts any, follow the command. A command that accepts no further arguments rejects them with an error. +

+ +

+ An unknown command prints an error naming it, then the help message, and exits with a nonzero status. +

+ +

The help message is the module docstring

+ +

+ In Python the help message is the module docstring, printed by print(__doc__.strip()). The documentation at the top of the file and the message the person sees are then the same text, and cannot drift apart. The docstring opens with the tool name, a paragraph on what the tool does, then the usage and one line per command: +

+ + + Usage: RT-Style_header_v5_to_v6 <command> [file ...] + RT-Style_header_v5_to_v6 preview <command> [file ...] + + help | -h | --help print this message (also printed when given no arguments) + project walk the directory tree rooted at $REPO_HOME + tree walk the directory tree rooted at the current directory + local only the current directory, no subdirectories + file <name> ... only the named .html files + preview <command> print the log of what <command> would do, change nothing + + +

Scope commands

+ +

+ A tool that acts on files found in a directory tree offers the same four commands for choosing which files: +

+
    +
  • project walks the tree rooted at $REPO_HOME. When REPO_HOME is not set, the command is an error. Otherwise it behaves as tree run from $REPO_HOME, from whatever directory the person is in.
  • +
  • tree walks the tree rooted at the current directory.
  • +
  • local acts on the current directory only, without descending.
  • +
  • file acts on the files named after it.
  • +
+ +

+ Only project depends on a Harmony project. The others work anywhere, because the files a tool serves are not always in a project. RT-Style documents, for example, appear in projects and outside them. +

+ +

Preview

+ +

+ A tool that changes files offers preview, placed before any other command. preview tree prints the log that tree would print and changes nothing. The person reads it, decides, then runs the command without preview. +

+ +

+ Preview is not a separate code path. The work functions are given a preview flag and test it only at the point of each action, immediately before writing or deleting. Everything else, finding files, deciding what to do, and logging, runs the same in both modes. A preview log that came from different code could not be trusted to describe the real run. +

+ +

The log

+ +

+ A tool that acts on files prints a log of each thing it did. The log is for a person reading a terminal, and for that same person reading it again in a scrollback or a saved file later. This is the log of converting the Harmony skeleton documents, with the path on the first line shortened to fit the page: +

+ + + RT-Style_header_v5_to_v6 project in /home/Thomas/.../project/Harmony + converted administrator/document/how-to_release.html + converted developer/document/naming_file-and-directory.html + converted developer/document/single-file_C-module-and-namespace.html + converted document/introduction_Harmony.html + converted document/role-and-workflow_product-development.html + converted document/role-and-workflow_product-maintenance.html + + summary: + 6 .html files converted to version 6 + 0 RT-Manuscript_locator.js files deleted + 0 .html files already version 6 + 0 .html files with mentions to edit by hand + 0 other .html files left alone + + +
    +
  • The first line names the tool, the command, and the absolute directory the work is rooted in, so a saved log says where it came from.
  • +
  • Each action is one line. A fixed-width tag in the past tense says what happened, followed by the path relative to the root. Relative paths keep lines short and make logs from different machines comparable.
  • +
  • In a preview the tags read would convert, would delete, and the summary ends with preview only: nothing was changed.
  • +
  • A decision not to act is logged too, with the reason in parentheses, as in kept shared/document/RT-Manuscript_locator.js (no version 5 document beside it). A file the tool left alone on purpose must be distinguishable from one it missed.
  • +
  • Work the tool cannot do safely is logged with the tag edit by hand, the file, the line number and the line, as in edit by hand .../Document/user.html:83: &lt;script src=..., so a person can go straight to it.
  • +
  • A summary of counts ends the log. A count line that would always be zero for a run, such as errors, may be left out when it is zero.
  • +
  • Walk directories and files in sorted order, so two runs over the same tree produce the same log.
  • +
+ +

+ Errors are logged on their own lines with the tag error, the tool carries on with the remaining files, and the exit status is nonzero when any error occurred. Errors in the command line itself are printed to standard error before any work starts. +

+ +

Walking directories

+ +

+ A tool walks every directory in its scope, whatever the directory is named. It does not skip directories by name, since a name that means nothing to the tool's author may hold files that matter to its user. +

+ +

+ A tool does not follow symbolic links to directories. A Harmony project has shared/project-dependency/project pointing to ../../../, the directory holding all the projects. Following it would send the walk into every neighboring project and around the loop back into this one. In Python, os.walk does not follow such links unless given followlinks=True, so leave that argument out. +

+ +

Paths

+ +

+ A tool forms an absolute path only by starting from an environment variable that the role setup provides, such as $REPO_HOME. Paths the tool writes into files, and symbolic links it creates, are relative. +

+ +

+ An absolute path written into a file is correct for only one view of the file system. Under the subu arrangement, the master user reaches a sub-user's files through a mapped mount at a different absolute path from the one the sub-user sees. A path recorded by one of them is then unreadable, or points somewhere else, for the other. A relative path is resolved from wherever the file is reached, so it is correct in every view. +

+ +

Changing files safely

+ +

Checkpoint first

+ +

+ A tool edits files in place and does not make backup copies. The person checkpoints the project with git before running it, and reviews the result with git diff. Run preview first. +

+ +

Change only what is recognized exactly

+ +

+ Match the precise form the tool was written to change, in the precise place it occurs. RT-Style_header_v5_to_v6 replaces a locator script line only when it stands alone on a line inside <head>. A mention of the locator anywhere else, such as an escaped example in a manual, is not touched. It is logged for a person to edit. +

+ +

Keep everything not being changed

+ +

+ Carry over what the edit is not about: the indentation of a replaced line, line endings, file permissions, and every other line of the file byte for byte. After a run, git diff should show the intended change and nothing else. +

+ +

Running twice does no harm

+ +

+ A second run of the same command finds nothing left to do, changes nothing, and says so in its summary. A tool achieves this by recognizing work already done. The header converter counts a file holding the version 6 block as already version 6 and leaves it alone. A person can therefore rerun a tool to confirm a job is complete, or to finish one that stopped part way. +

+ +

Before deleting, check nothing still uses it

+ +

+ A tool deletes a file only when nothing remaining depends on it. In testing, the header converter run with file on two of six test documents in a directory deleted the shared locator file while the other four still needed it. The rule now is that a locator is deleted only when no unconverted version 5 document beside it still loads it. Otherwise it is kept, and the log names the documents still using it. +

+ +

Leave alone what the tool cannot identify as its own

+ +

+ A file with the right name in an unexpected place is not evidence that the tool should remove it. The converter keeps a locator file that has no version 5 document beside it, and logs it as kept, leaving the decision to a person. +

+ +

Testing a tool

+ +

+ Test a new tool on a copy of real material before it runs on the original. An archive of the project, extracted into a scratch directory, gives realistic files that can be discarded. +

+
    +
  1. Run with no arguments, an unknown command, and each command's error cases, such as project with REPO_HOME unset or file naming a file that does not exist.
  2. +
  3. Run preview and confirm that nothing changed, for example with diff -r against an untouched copy.
  4. +
  5. Run each scope command from more than one directory, including from outside any project.
  6. +
  7. Run each command twice. The second run must report nothing left to do.
  8. +
  9. Run on part of a job, such as file on some of the documents in a directory, and confirm the rest still work.
  10. +
  11. Read the diff of a changed file in full.
  12. +
+ +

A skeleton

+ +

+ A Python tool laid out to these conventions begins with #!/usr/bin/env python3, then the Emacs mode line the Harmony tools carry, which sets two-space indentation and no tabs. The rest of the skeleton follows: +

+ + + """ + tool_name + + What the tool does, in a paragraph. + + Usage: tool_name <command> [file ...] + tool_name preview <command> [file ...] + + help | -h | --help print this message, also printed given no arguments + project walk the tree rooted at $REPO_HOME + tree walk the tree rooted at the current directory + local only the current directory + file <name> ... only the named files + preview <command> print the log of <command>, change nothing + """ + + import sys ,os + from pathlib import Path + + def work(dirpa_root ,recursive ,preview): + ... + + def print_help(): + print(__doc__.strip()) + return 0 + + def CLI(argv=None): + if argv is None: argv = sys.argv[1:] + if not argv or argv[0] in ("help" ,"-h" ,"--help"): return print_help() + + preview = argv[0] == "preview" + if preview: argv = argv[1:] + if not argv: + print("Error: 'preview' needs a command after it." ,file=sys.stderr) + return 1 + + command ,rest = argv[0] ,argv[1:] + ... + + if __name__ == "__main__": + sys.exit(CLI()) + + +

Exercises

+ +

Exercise 1: The bare tool

+

+ A tool named tidy deletes editor backup files ending in ~. When run with no arguments it deletes them from the current directory. List what is wrong with that behavior under these conventions, and give the command line it should have instead. +

+ +

Exercise 2: A log line

+

+ A tool prints Processing /home/Thomas/subu/developer/project/Two/document/a.html ... done for every file it looks at, whether or not it changed it. Rewrite the log for a run in which one of three files was changed, one was already correct, and one could not be read. +

+ +

Exercise 3: A safe delete

+

+ A tool replaces each include.css link in an HTML document with an inline style block, then deletes include.css from the directory. Describe the run in which this breaks a document, and the check that prevents it. +

+ +
+ + diff --git a/setup b/setup index f750f1d..8ab56c8 100755 --- a/setup +++ b/setup @@ -30,11 +30,6 @@ def main(): print(f"ROLE_HOME {role_home}") - # Python-domain logic: PATH injection - path_parts = env.get("PATH", "").split(os.pathsep) - if tool_dir not in path_parts: - env["PATH"] = f"{tool_dir}{os.pathsep}{env.get('PATH', '')}" - # Python-domain logic: Directory validation if not os.path.isdir(role): print(f"setup: failed to locate directory for role '{role}'") @@ -47,6 +42,8 @@ def main(): 'if [[ -f ~/.bashrc ]]; then source ~/.bashrc; fi', 'if [[ -f "shared/tool/setup" ]]; then source "shared/tool/setup"; fi', 'if [[ -f "shared/authored/setup" ]]; then source "shared/authored/setup"; fi', + '# the role tool directory is searched before everything the shared setup added', + f'PATH="{tool_dir}:$PATH"; export PATH', f'cd "{role}"', 'if [[ -f "tool/setup" ]]; then', ' source "tool/setup"', diff --git a/shared/project-dependency/.gitignore b/shared/project-dependency/.gitignore new file mode 100644 index 0000000..a033c31 --- /dev/null +++ b/shared/project-dependency/.gitignore @@ -0,0 +1,11 @@ +# shared/project-dependency/.gitignore +# Tracks the skeleton's parts. Add a ! line for anything else to track, +# such as a dependency's link. +* +!.gitignore +!project +!*.init +!*.install +!*.org +!*.html +!Python-3.12.3 diff --git a/shared/project-dependency/Python.init b/shared/project-dependency/Python.init new file mode 100644 index 0000000..eddbaad --- /dev/null +++ b/shared/project-dependency/Python.init @@ -0,0 +1,16 @@ +# -*- mode: sh; sh-shell: bash; -*- +# init files are bash script + +# Python.init +# -------------------------------------------------------------------------------- +# Ensure the local Python build is prioritized in the PATH. +# The user must establish the Python-3.12.3 symlink for this to activate. + +if [[ -d "$REPO_HOME/shared/project-dependency/Python-3.12.3/bin" ]]; then + PATH="$REPO_HOME/shared/project-dependency/Python-3.12.3/bin:$PATH" +else + # Optional: Issue a quiet warning for the administrator role + if [[ "$ROLE" == "administrator" ]]; then + echo "Notice: Local Python-3.12.3 bin not found. Using system Python." + fi +fi diff --git a/shared/project-dependency/Python.org b/shared/project-dependency/Python.org new file mode 100644 index 0000000..a5ba50c --- /dev/null +++ b/shared/project-dependency/Python.org @@ -0,0 +1,38 @@ +#+TITLE: Python Installation Guide +#+AUTHOR: Project Administrator + +This project requires a dedicated Python environment. The configuration plugin (`shared/project-dependency/Python.init`) was written targeting Python 3.12.3, though the architecture does not rely on any known version-dependent features. + +* 1. Download and Extract +Download the Python source code and extract it as a sibling to the main project directory. Assuming you are at the top level of your project workspace: + +#+begin_src bash +cd ../ +wget https://www.python.org/ftp/python/3.12.3/Python-3.12.3.tgz +tar -xzf Python-3.12.3.tgz +cd Python-3.12.3 +#+end_src + +* 2. Build the Environment +Compile the Python binaries in place. Setting the installation prefix to the current directory isolates the build, and enabling shared libraries ensures compatibility with C extensions. + +#+begin_src bash +./configure --prefix="$(pwd)" --enable-optimizations --enable-shared +make -j8 +make install +#+end_src + +* 3. Link the Project + +Python-3.12.3 is linked to a Harmony derived project by default. + +See administrator/document/how-to_add-project-dependency.html for details about linking a project, if you want to check the setup. + +If you are installing a different version, then at least the Python-3.12.3 version project linking is there as a good example. + +Project linking is not difficult, make a sym link to `project/`, and then add an init file, as described in administrator/document/how-to_add-project-dependency.html. The 'project' in the link is literal. + + + + + diff --git a/shared/tool/setup b/shared/tool/setup index d5e3d76..a7a66aa 100644 --- a/shared/tool/setup +++ b/shared/tool/setup @@ -57,25 +57,28 @@ umask 0077 # -------------------------------------------------------------------------------- # PATH & Plugin Sourcing -# precedence: last defined, first discovered - - PATH="$REPO_HOME/shared/authored:$PATH" - PATH="$REPO_HOME/shared/made:$PATH" +# Each addition goes to the front, so the last added is searched first. +# Resulting search order, most specific first: +# shared/authored, shared/made, shared/tool, project dependencies, the rest. +# The top-level setup then puts the role's tool directory in front of these. + + # Project dependencies: source each .init file. Each prepends its own entries. + for init_file in "$REPO_HOME/shared/project-dependency/"*.init; do + if [[ -f "$init_file" ]]; then + source "$init_file" + fi + done + PATH="$REPO_HOME/shared/tool:$PATH" + PATH="$REPO_HOME/shared/made:$PATH" + PATH="$REPO_HOME/shared/authored:$PATH" - # Remove duplicates + # Remove duplicates, keeping the first occurrence clean_path() { PATH=$(echo ":$PATH" | awk -v RS=: -v ORS=: '!seen[$0]++' | sed 's/^://; s/:$//') } clean_path - # Plugin Sourcing: Dynamically load all the linked project configurations - for init_file in "$REPO_HOME/shared/linked-project/"*.init; do - if [[ -f "$init_file" ]]; then - source "$init_file" - fi - done - export PATH # -------------------------------------------------------------------------------- diff --git a/tester/.gitkeep b/tester/.gitkeep new file mode 100644 index 0000000..e69de29