Merge branch 'claude/relaxed-faraday-h4zd09' of https://github.com/pablovolenski/vienalatina
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
commit
3cb2c86217
@ -12,6 +12,11 @@ Loop prevention, which was structural back when only Spanish could be a source:
|
|||||||
never itself treated as a source;
|
never itself treated as a source;
|
||||||
* the bot's own commits carry [skip-translate] and are skipped outright.
|
* the bot's own commits carry [skip-translate] and are skipped outright.
|
||||||
|
|
||||||
|
Deletion is handled too, and has to be: removing a post in the CMS deletes one
|
||||||
|
file, the original, and the siblings this script wrote would otherwise stay on
|
||||||
|
the site as posts in a language with no original. Any sibling whose source has
|
||||||
|
disappeared is removed, unless it is frozen.
|
||||||
|
|
||||||
`manual_translation: true` means "hands off", on both sides:
|
`manual_translation: true` means "hands off", on both sides:
|
||||||
* on an authored source — do not generate siblings for this post at all;
|
* on an authored source — do not generate siblings for this post at all;
|
||||||
* on a generated sibling — never overwrite it again.
|
* on a generated sibling — never overwrite it again.
|
||||||
@ -180,6 +185,41 @@ def missing_siblings() -> list[Path]:
|
|||||||
return incomplete
|
return incomplete
|
||||||
|
|
||||||
|
|
||||||
|
def orphaned_siblings() -> list[Path]:
|
||||||
|
"""Generated siblings whose source no longer exists.
|
||||||
|
|
||||||
|
Deleting a post in the CMS removes one file — the Spanish original. The
|
||||||
|
German and Portuguese siblings were written by this script, not by the
|
||||||
|
author, so nothing else deletes them and they stay on the site as posts in
|
||||||
|
a language that has no original. A rename leaves the same debris, since it
|
||||||
|
is a delete plus an add.
|
||||||
|
|
||||||
|
This scans the whole content tree rather than the push diff, so it also
|
||||||
|
clears siblings orphaned by earlier runs that predate this check.
|
||||||
|
"""
|
||||||
|
orphans = []
|
||||||
|
for path in sorted(CONTENT_DIR.rglob("*.md")):
|
||||||
|
parsed = split_lang(path)
|
||||||
|
if not parsed:
|
||||||
|
continue
|
||||||
|
basename, lang = parsed
|
||||||
|
fm = read_frontmatter(path)
|
||||||
|
source_lang = fm.get("translated_from")
|
||||||
|
if not source_lang:
|
||||||
|
continue # authored by a human; only its author deletes it
|
||||||
|
if path.with_name(f"{basename}.{source_lang}.md").exists():
|
||||||
|
continue
|
||||||
|
rel = path.relative_to(REPO_ROOT)
|
||||||
|
if is_frozen(fm):
|
||||||
|
# Someone edited this translation by hand. Deleting it would throw
|
||||||
|
# that work away on the strength of an inference, so say so instead.
|
||||||
|
print(f" {rel}: source is gone but manual_translation=true — left in place; "
|
||||||
|
f"delete it by hand if the post is meant to disappear")
|
||||||
|
continue
|
||||||
|
orphans.append(path)
|
||||||
|
return orphans
|
||||||
|
|
||||||
|
|
||||||
def translate_file(source: Path, basename: str, src_lang: str, provider) -> list[Path]:
|
def translate_file(source: Path, basename: str, src_lang: str, provider) -> list[Path]:
|
||||||
fm, body = split_frontmatter(source.read_text(encoding="utf-8"))
|
fm, body = split_frontmatter(source.read_text(encoding="utf-8"))
|
||||||
written: list[Path] = []
|
written: list[Path] = []
|
||||||
@ -218,7 +258,7 @@ def translate_file(source: Path, basename: str, src_lang: str, provider) -> list
|
|||||||
return written
|
return written
|
||||||
|
|
||||||
|
|
||||||
def commit_and_push(files: list[Path]) -> None:
|
def commit_and_push(files: list[Path], summary: str) -> None:
|
||||||
branch = os.environ.get("CI_COMMIT_BRANCH", "main")
|
branch = os.environ.get("CI_COMMIT_BRANCH", "main")
|
||||||
run("git", "config", "user.name", BOT_NAME)
|
run("git", "config", "user.name", BOT_NAME)
|
||||||
run("git", "config", "user.email", BOT_EMAIL)
|
run("git", "config", "user.email", BOT_EMAIL)
|
||||||
@ -227,11 +267,13 @@ def commit_and_push(files: list[Path]) -> None:
|
|||||||
if token: # clone credentials are read-only; pushing needs the bot token
|
if token: # clone credentials are read-only; pushing needs the bot token
|
||||||
run("git", "remote", "set-url", "origin",
|
run("git", "remote", "set-url", "origin",
|
||||||
f"https://{BOT_NAME}:{token}@git.vienalatina.com/{repo}.git")
|
f"https://{BOT_NAME}:{token}@git.vienalatina.com/{repo}.git")
|
||||||
run("git", "add", *[str(f) for f in files])
|
# --all so a path that no longer exists stages as a deletion rather than
|
||||||
|
# failing; reaped orphans arrive here alongside freshly written siblings.
|
||||||
|
run("git", "add", "--all", "--", *[str(f) for f in files])
|
||||||
if not run("git", "status", "--porcelain"):
|
if not run("git", "status", "--porcelain"):
|
||||||
print("Translations identical to committed siblings — nothing to push.")
|
print("Translations identical to committed siblings — nothing to push.")
|
||||||
return
|
return
|
||||||
run("git", "commit", "-m", f"translate: update generated siblings {SKIP_MARKER}")
|
run("git", "commit", "-m", f"translate: {summary} {SKIP_MARKER}")
|
||||||
run("git", "push", "origin", f"HEAD:{branch}")
|
run("git", "push", "origin", f"HEAD:{branch}")
|
||||||
print(f"Pushed sibling commit to {branch}.")
|
print(f"Pushed sibling commit to {branch}.")
|
||||||
|
|
||||||
@ -250,23 +292,39 @@ def main() -> None:
|
|||||||
|
|
||||||
paths = missing_siblings() if args.backfill else changed_markdown()
|
paths = missing_siblings() if args.backfill else changed_markdown()
|
||||||
sources = authored_sources(paths)
|
sources = authored_sources(paths)
|
||||||
if not sources:
|
|
||||||
print("No authored content changed — nothing to translate.")
|
|
||||||
return
|
|
||||||
|
|
||||||
provider = build_provider()
|
|
||||||
written: list[Path] = []
|
written: list[Path] = []
|
||||||
for source, basename, lang in sources:
|
if sources:
|
||||||
print(f"Translating {source.relative_to(REPO_ROOT)} (from {lang}):")
|
provider = build_provider()
|
||||||
written.extend(translate_file(source, basename, lang, provider))
|
for source, basename, lang in sources:
|
||||||
|
print(f"Translating {source.relative_to(REPO_ROOT)} (from {lang}):")
|
||||||
|
written.extend(translate_file(source, basename, lang, provider))
|
||||||
|
else:
|
||||||
|
print("No authored content changed — nothing to translate.")
|
||||||
|
|
||||||
if not written:
|
# Runs whether or not anything was translated: a push that only deletes
|
||||||
print("Every sibling is frozen — nothing written.")
|
# posts reaches this point with no sources at all, and that is exactly the
|
||||||
|
# case where siblings are left stranded.
|
||||||
|
removed = []
|
||||||
|
for orphan in orphaned_siblings():
|
||||||
|
orphan.unlink()
|
||||||
|
removed.append(orphan)
|
||||||
|
print(f" {orphan.relative_to(REPO_ROOT)}: source deleted — removed")
|
||||||
|
|
||||||
|
touched = written + removed
|
||||||
|
if not touched:
|
||||||
|
print("Nothing to commit.")
|
||||||
return
|
return
|
||||||
if args.no_push:
|
if args.no_push:
|
||||||
print(f"--no-push: wrote {len(written)} files, leaving them uncommitted.")
|
print(f"--no-push: wrote {len(written)} and removed {len(removed)} files, "
|
||||||
|
f"leaving them uncommitted.")
|
||||||
return
|
return
|
||||||
commit_and_push(written)
|
parts = []
|
||||||
|
if written:
|
||||||
|
parts.append(f"update {len(written)} generated siblings")
|
||||||
|
if removed:
|
||||||
|
parts.append(f"remove {len(removed)} orphaned by a deleted source")
|
||||||
|
commit_and_push(touched, " and ".join(parts))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user