export: export run writes selected kb/ pages as a derived, read-only version into configured target folders (markdown and command handlers, manifest, after_publish), raw accept also refuses manifest-listed files, doctor checks the section (#214); 10.0.0-beta.13
Files changed: - CHANGES.md - VERSION - docs/how-to/README.md - docs/how-to/back-up-to-a-synced-folder.md - docs/how-to/collect-from-a-synced-folder.md - docs/how-to/export-guidelines.md - docs/how-to/export-to-a-synced-folder.md - docs/reference/commands/README.md - docs/reference/commands/exports.md - docs/reference/commands/git.md - docs/reference/commands/guideline-export.md - docs/reference/commands/instance-health.md - docs/reference/commands/raw-material-and-uploads.md - docs/reference/configuration.md - docs/reference/exit-codes.md - instructions/setup-instance.md - raw/CONTRACT.md - tools/CONTRACT.md - tools/README.md - tools/chemenu/backup.py - tools/chemenu/cli_contract.py - tools/chemenu/commands/doctor.py - tools/chemenu/commands/export_cmd.py - tools/chemenu/commands/git_publish.py - tools/chemenu/commands/raw/_common.py - tools/chemenu/commands/raw/accept.py - tools/chemenu/commands/raw/capture.py - tools/chemenu/config_shapes.py - tools/chemenu/guideline_export.py - tools/chemenu/page_export.py - tools/chemenu/reference_doc.py - tools/chemenu/repo_capture.py - tools/chemenu/tests/test_config_shapes.py - tools/chemenu/tests/test_export_run.py Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnAJ7Z3CpVD3PRbN73QtU2
This commit is contained in:
1 parent
ecefe0a033
commit
99945c4841
33 files changed
+2316
-96
No files matched your search
@@ -1,6 +1,8 @@
|
||||
"""`wikitool export guidelines` - the instance's guideline pages as one
|
||||
generated `GUIDELINES.md`, printed, or written into the captured repositories
|
||||
behind the Guideline Push Gate.
|
||||
behind the Guideline Push Gate - and `wikitool export run`, the derived reading
|
||||
copy of `kb/` in the folders the `export` section configures
|
||||
(`chemenu/page_export.py`; no gate, since a target is a folder the user named).
|
||||
|
||||
The core - selection, rendering, the header - is `chemenu/guideline_export.py`;
|
||||
the git write is `repo_capture.commit_file`/`push_commit`. This module decides
|
||||
@@ -42,12 +44,12 @@ from typing import Optional
|
||||
import typer
|
||||
from rich.markup import escape
|
||||
|
||||
from chemenu import cli_contract, config, guideline_export, repo_capture
|
||||
from chemenu import cli_contract, config, guideline_export, page_export, repo_capture
|
||||
from chemenu.commands._util import EXIT_NEEDS_CLEARANCE, console, fail, rel_path
|
||||
from chemenu.errors import BackendError, ValidationError
|
||||
from chemenu.search import filters
|
||||
|
||||
app = typer.Typer(help="Generate files from the wiki for other repositories.")
|
||||
app = typer.Typer(help="Generate files from the wiki for other repositories and folders.")
|
||||
|
||||
GATE = "guideline-push"
|
||||
|
||||
@@ -427,3 +429,127 @@ def export_guidelines_command(
|
||||
"written; run the command again for the rest."
|
||||
)
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
|
||||
@app.command("run")
|
||||
@cli_contract.record(cli_contract.CommandRecord(
|
||||
path="export run",
|
||||
summary="Write the derived, read-only reading copy of `kb/` into the folders the `export` "
|
||||
"section configures - the selected pages as Markdown, or converted by a program of this "
|
||||
"checkout.",
|
||||
synopsis=(
|
||||
cli_contract.Variant(
|
||||
usage="export run [<target> ...]",
|
||||
notes="Every configured target, or the named ones. Needs an `export` section in "
|
||||
f"`{config.LOCAL_SETTINGS_FILENAME}`; the user writes it by hand.",
|
||||
),
|
||||
),
|
||||
properties=cli_contract.Properties(
|
||||
effect=cli_contract.Effect.WRITE,
|
||||
idempotent=cli_contract.Idempotent.YES,
|
||||
atomic="Per file within a target (a temporary file in the target, then a move over its "
|
||||
"place), the manifest last; targets are independent, so a failed target does not undo "
|
||||
"another",
|
||||
budget=cli_contract.Budget.COUNTED,
|
||||
),
|
||||
notes=(
|
||||
"An export is derived: regenerated from `kb/` on every run, never edited, never read back "
|
||||
"into the wiki. Each target names its pages with an allowlist of `search --field` "
|
||||
"predicates (`include`: a list of selections, AND within one, OR across them), a handler "
|
||||
"(`markdown`, or `command` for the operator's converter) and the folder (`path`, outside "
|
||||
"the instance). Nothing is exported that no selection names.",
|
||||
"Every Markdown file starts with a frontmatter block whose `wikitool_export` line says "
|
||||
"`kind=pages instance=<origin URL without userinfo, or local> commit=<newest commit "
|
||||
"touching a selected page>`, followed by `title`, `type`, `summary`, `modified` where the "
|
||||
"page has them (`frontmatter: summary`, the default; `minimal` keeps only the marker, "
|
||||
"`full` the whole frontmatter), then the page text. A file keeps its path below `kb/`. A "
|
||||
"wikilink to an exported page becomes a relative Markdown link (`links: relative`, the "
|
||||
"default) or plain text (`links: text`); a link to a page that is not exported is always "
|
||||
"plain text. Footnote markers stay; code is never rewritten. LF endings, no timestamp: "
|
||||
"the same pages give the same bytes.",
|
||||
"Handler `command`: the Markdown is rendered into a staging directory, the program of "
|
||||
"`export.commands` runs with no shell, stdin closed and a timeout of 300 s, and receives "
|
||||
"the Markdown directory and an empty output directory as its last two arguments. On exit "
|
||||
"0 the regular files in the output directory (recursively) become the target's content; "
|
||||
"any other end leaves the target unchanged and prints the end of its stderr. The staging "
|
||||
"directory is always removed.",
|
||||
"The folder is only ever touched by what it holds in its manifest, `.wikitool-export.json`: "
|
||||
"the files an earlier run wrote, each with its sha256. A run removes only manifest files "
|
||||
"that are no longer exported (and folders it created, once empty), rewrites a manifest "
|
||||
"file that is missing, leaves every other file byte for byte as it was, and writes "
|
||||
"nothing - not even a modification time - for a file that did not change. A run that "
|
||||
"changes nothing writes nothing at all.",
|
||||
"Refuses a target, before writing anything into it: a non-empty folder with no manifest "
|
||||
"(it is somebody's, not an export), a manifest file that was changed in the folder since "
|
||||
"the last run (naming it), a foreign file where an exported one belongs, an empty "
|
||||
"selection (it would delete the earlier export), an unknown field in a predicate. A "
|
||||
"missing folder is created.",
|
||||
"Refuses everything, before any target: uncommitted changes in `kb/` or `types/` "
|
||||
"(untracked files included), no git checkout, a page under `kb/` with unreadable "
|
||||
"frontmatter, an unknown target name, a broken `export` section.",
|
||||
"Output per target: `new` (with the page titles), `changed`, `removed`, the count "
|
||||
"`unchanged`, or `failed` with the reason. A page that newly enters a selection is "
|
||||
"reported as new, not gated. Exit 0 when every target ran; exit 1 when any failed - the "
|
||||
"others were still served.",
|
||||
"A target with `after_publish: true` is also refreshed by every `publish` that "
|
||||
"committed, `--no-push` included; a failure there is a warning, the commit stands, and "
|
||||
"this command is the repeat.",
|
||||
"`raw accept` refuses a file whose sha256 a target's manifest lists, and one that carries "
|
||||
"the marker: an export never goes back into `raw/`.",
|
||||
),
|
||||
failures=(
|
||||
cli_contract.Failure(
|
||||
cause="No `export` section, an unknown target name, or a broken section (a predicate "
|
||||
"that does not parse, a path inside the instance or in the way of another target, an "
|
||||
"`intake` folder or the backup target)",
|
||||
reaction="Show the message to the user; the section is theirs to write by hand, never "
|
||||
"yours",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="`kb/` or `types/` has uncommitted changes, the instance is not a git checkout, "
|
||||
"or a page under `kb/` has unreadable frontmatter",
|
||||
reaction="Fix the named pages, or publish the changes first, then retry once",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="A target failed: a foreign folder, a file changed in the target, an empty "
|
||||
"selection, an unknown field, a converter that did not exit 0",
|
||||
reaction="Show the target's reason to the user; the other targets were still served. "
|
||||
"Never delete or edit files in a target folder to make a run pass",
|
||||
),
|
||||
),
|
||||
examples=(
|
||||
"tools/wikitool export run",
|
||||
"tools/wikitool export run work-notes",
|
||||
),
|
||||
never=(
|
||||
"Never edit a file in a target folder to change an export - the next run refuses or "
|
||||
"overwrites it; change the page, or the target's `include`, instead.",
|
||||
"Never put an export folder into `incoming/` or `raw/`.",
|
||||
),
|
||||
see_also=(
|
||||
"`wikitool search` - the same predicates, to preview a selection",
|
||||
"`wikitool backup run` - the same kind of folder, for this machine's own files",
|
||||
"`docs/how-to/export-to-a-synced-folder.md`",
|
||||
),
|
||||
))
|
||||
def export_run_command(
|
||||
names: Optional[list[str]] = typer.Argument(
|
||||
None, metavar="[TARGET]...", help="Targets to run; default: every configured target."
|
||||
),
|
||||
):
|
||||
"""Write the reading copy of kb/ into the configured export folders."""
|
||||
try:
|
||||
targets = page_export.choose(page_export.require_config(), names or [])
|
||||
results = page_export.run(targets)
|
||||
except ValidationError as exc:
|
||||
fail(escape(str(exc)))
|
||||
return
|
||||
failed = 0
|
||||
for result in results:
|
||||
for line in page_export.report_lines(result):
|
||||
typer.echo(line)
|
||||
failed += not result.ok
|
||||
if failed:
|
||||
console.print(f"[bold red]ERROR[/bold red] {failed} of {len(results)} target(s) failed - the "
|
||||
"others were served; fix the reason above and run again.")
|
||||
raise typer.Exit(code=1)
|
||||
Reference in new issue
Block a user