WillTools 2026-09-09 -- clean snapshot of the development repo for review
ci / test (push) Successful in 6m18s
ci / test (push) Successful in 6m18s
History-free snapshot of the private development repository at cbe45c5 (2026-09-09). Code, docs and public catalogue only; no plant or job knowledge layers, no client or job names (tools/scrub.py clean). The intro deck is left out of this snapshot because its slide images could not be checked for job drawings.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Where the PLANT knowledge layer lives on disk.
|
||||
|
||||
Split out of `designspec` for one reason: `designspec` imports the Revit API
|
||||
at module scope (evidence scraping needs a live document), and choosing the
|
||||
plant knowledge file is pure path arithmetic that a host CPython test can and
|
||||
should exercise directly. `designspec` re-exports every name here, so nothing
|
||||
that already says `designspec.plant_knowledge_path()` had to change.
|
||||
|
||||
THE LAYOUT, in one line:
|
||||
|
||||
<root>/knowledge.yaml the machine-wide plant file
|
||||
<root>/<plant>/knowledge.yaml one folder per plant
|
||||
|
||||
A job's own knowledge file names its plant in `job.plant`; `knowledge.load()`
|
||||
reads that name BEFORE it merges anything and passes it here. A job that
|
||||
names no plant reads the machine-wide file, which is how every install worked
|
||||
before per-plant folders existed and still does.
|
||||
|
||||
IronPython 2.7: no f-strings, no pathlib.
|
||||
"""
|
||||
# pylint: disable=invalid-name
|
||||
import os
|
||||
import re
|
||||
|
||||
from vibe.compat import string_types
|
||||
|
||||
# The plant-wide knowledge file: one per machine, above every project.
|
||||
PLANT_FOLDER = 'WillTools'
|
||||
PLANT_ENV = 'WILLTOOLS_KNOWLEDGE'
|
||||
|
||||
# What a plant id may be made of, because it is used as a PATH COMPONENT.
|
||||
# It arrives from a YAML file, so `..`, a slash or a drive letter in it would
|
||||
# be a directory traversal written by whoever edits a job file. Anything
|
||||
# outside this set is refused outright rather than scrubbed into something
|
||||
# else: a plant id that quietly became a different folder name is exactly the
|
||||
# silence this codebase keeps paying for.
|
||||
_PLANT_ID_RE = re.compile(r'^[A-Za-z0-9][A-Za-z0-9._-]*$')
|
||||
|
||||
|
||||
def valid_plant_id(plant):
|
||||
"""Is this string usable as a plant id, i.e. as one folder name?
|
||||
|
||||
Returns False for None, for the empty string, and for anything carrying a
|
||||
separator, a drive letter or a dot-dot. See _PLANT_ID_RE.
|
||||
"""
|
||||
if not plant or not isinstance(plant, string_types):
|
||||
return False
|
||||
return bool(_PLANT_ID_RE.match(plant.strip()))
|
||||
|
||||
|
||||
def plant_root():
|
||||
"""The folder the plant knowledge lives in -- one file, or one per plant.
|
||||
|
||||
THE ROOT IS A FOLDER AND THE MACHINE-WIDE FILE IS `knowledge.yaml` INSIDE
|
||||
IT. That relationship has always held (%APPDATA%/WillTools/knowledge.yaml)
|
||||
and is what lets WILLTOOLS_KNOWLEDGE keep meaning what it always meant
|
||||
while also naming a root:
|
||||
|
||||
set to a FOLDER that folder is the root. Nothing else to say.
|
||||
set to a FILE that file is the machine-wide plant file, so its
|
||||
DIRECTORY is the root, by the same relationship.
|
||||
Not a guess -- the definition, read backwards.
|
||||
unset %APPDATA%/WillTools, as ever.
|
||||
|
||||
Which is why a machine already pointing the variable at a single file
|
||||
picks up per-plant folders with no change to the variable: the folders
|
||||
are siblings of the file it already names.
|
||||
|
||||
A path that is set but exists as neither is treated as a FILE, because
|
||||
that is what the variable has meant for its whole life and the operator
|
||||
almost certainly meant a file that has moved -- the missing path is then
|
||||
reported by name instead of a different, existing path being read in
|
||||
silence.
|
||||
"""
|
||||
override = os.environ.get(PLANT_ENV)
|
||||
if not override:
|
||||
base = os.environ.get('APPDATA') or os.path.expanduser('~')
|
||||
return os.path.join(base, PLANT_FOLDER)
|
||||
if os.path.isdir(override):
|
||||
return override
|
||||
return os.path.dirname(override) or override
|
||||
|
||||
|
||||
def plant_ids():
|
||||
"""Every plant folder under the root that carries a knowledge.yaml,
|
||||
sorted -- the candidates a job can be resolved to. A root that does not
|
||||
exist yields []. Never raises."""
|
||||
try:
|
||||
root = plant_root()
|
||||
out = []
|
||||
for name in os.listdir(root):
|
||||
if not valid_plant_id(name):
|
||||
continue
|
||||
if os.path.isfile(os.path.join(root, name, 'knowledge.yaml')):
|
||||
out.append(name)
|
||||
return sorted(out)
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def plant_knowledge_path(plant=None):
|
||||
"""The plant knowledge file this run should read.
|
||||
|
||||
No plant named -- the ordinary case, and every install that predates the
|
||||
key -- returns the machine-wide file exactly as this function always has:
|
||||
WILLTOOLS_KNOWLEDGE if it names one, else <root>/knowledge.yaml.
|
||||
|
||||
A plant named (`job.plant` in the job layer) returns
|
||||
<root>/<plant>/knowledge.yaml. A SECOND PLANT IS A NEW FOLDER, never an
|
||||
edit to the first one's file. See plant_root() for what <root> is and why
|
||||
a machine already pointing the variable at a single file needs no change.
|
||||
|
||||
The per-plant FOLDER and the machine-wide FILE share a root and cannot
|
||||
collide: one is a directory, the other is called `knowledge.yaml`.
|
||||
|
||||
An INVALID plant id -- a separator, a `..`, an empty string -- resolves to
|
||||
NOTHING and returns None. load() reports it. Scrubbing a malformed name
|
||||
into some nearby folder is how a run ends up on another plant's rules
|
||||
while looking entirely normal.
|
||||
"""
|
||||
if plant is None:
|
||||
override = os.environ.get(PLANT_ENV)
|
||||
if override and not os.path.isdir(override):
|
||||
return override # the single-file arrangement, untouched
|
||||
return os.path.join(plant_root(), 'knowledge.yaml')
|
||||
if not valid_plant_id(plant):
|
||||
return None
|
||||
return os.path.join(plant_root(), plant.strip(), 'knowledge.yaml')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user