add kill-phrase builder
This commit is contained in:
parent
f63f1a61ea
commit
279342bb4f
99
builders.py
99
builders.py
@ -1,4 +1,15 @@
|
||||
from contextlib import contextmanager
|
||||
import functools
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
from typing import Set
|
||||
|
||||
import git
|
||||
import tw
|
||||
from util import BuildError
|
||||
|
||||
|
||||
def _lazy_evaluable(func):
|
||||
"""
|
||||
@ -27,9 +38,93 @@ def _lazy_evaluable(func):
|
||||
# if the user wants to write her own builder.
|
||||
tzk_builder = _lazy_evaluable
|
||||
|
||||
|
||||
def stop(message: str) -> None:
|
||||
"Stop the build due to an error condition."
|
||||
raise BuildError(message)
|
||||
|
||||
|
||||
# Global state available to all builders.
|
||||
build_state = {}
|
||||
|
||||
|
||||
@tzk_builder
|
||||
def printer(username: str):
|
||||
def printer(username: str) -> None:
|
||||
"Display the user's name"
|
||||
if username == 'Maud':
|
||||
raise Exception("No Mauds allowed!")
|
||||
print(f"Hallelujah, {username} built a wiki!")
|
||||
printer.name = "Display the user's name"
|
||||
|
||||
|
||||
@tzk_builder
|
||||
def require_branch(branchname: str) -> None:
|
||||
"Require a specific Git branch to be checked out"
|
||||
if git.read("branch", "--show-current") != branchname:
|
||||
stop(f"You may only run this build from the {branchname} branch.")
|
||||
|
||||
|
||||
@tzk_builder
|
||||
def require_clean_working_tree() -> None:
|
||||
"Require the working tree of the Git repository to be clean"
|
||||
pleasecommit = "Please commit or stash them before publishing."
|
||||
if git.rc("diff-index", "--quiet", "--cached", "HEAD", "--") != 0:
|
||||
stop(f"There are staged changes. {pleasecommit}")
|
||||
if git.rc("diff-files", "--quiet") != 0:
|
||||
stop(f"There are uncommitted changes. {pleasecommit}")
|
||||
|
||||
|
||||
@tzk_builder
|
||||
def new_output_folder():
|
||||
"Create a new temporary folder to hold output being built"
|
||||
assert 'public_wiki_folder' not in build_state
|
||||
build_state['public_wiki_folder'] = tempfile.mkdtemp()
|
||||
|
||||
new_output_folder.cleaner = lambda: shutil.rmtree(build_state['public_wiki_folder'])
|
||||
|
||||
|
||||
@tzk_builder
|
||||
def export_public_tiddlers(export_filter: str) -> None:
|
||||
"Export public tiddlers to public wiki folder"
|
||||
assert 'public_wiki_folder' in build_state
|
||||
tw.exec((
|
||||
("savewikifolder", build_state['public_wiki_folder'], export_filter),
|
||||
))
|
||||
|
||||
|
||||
def _find_kill_phrases(phrases: Set[str]):
|
||||
regexes = [re.compile(phrase) for phrase in phrases]
|
||||
failures = []
|
||||
tid_files = (Path(build_state['public_wiki_folder']) / "tiddlers").glob("**/*.tid")
|
||||
|
||||
for tid_file in tid_files:
|
||||
with tid_file.open() as f:
|
||||
for line in f:
|
||||
for regex in regexes:
|
||||
if re.search(regex, line):
|
||||
failures.append((regex, str(tid_file), line))
|
||||
|
||||
return failures
|
||||
|
||||
@tzk_builder
|
||||
def check_for_kill_phrases(kill_phrase_file: str = None):
|
||||
"Fail build if any of a series of regexes appears in a tiddler's source"
|
||||
if kill_phrase_file is None:
|
||||
kill_phrase_file = "tiddlers/_system/config/zettelkasten/Build/KillPhrases.tid"
|
||||
|
||||
kill_phrases = set()
|
||||
with open(kill_phrase_file) as f:
|
||||
reading = False
|
||||
for line in f:
|
||||
if not line.strip():
|
||||
reading = True
|
||||
if line.strip() and reading:
|
||||
kill_phrases.add(line)
|
||||
|
||||
failures = _find_kill_phrases(kill_phrases)
|
||||
if failures:
|
||||
result = ["Kill phrases were found in your public wiki:"]
|
||||
for failed_regex, failed_file, failed_line in failures:
|
||||
trimmed_file = failed_file.replace(build_state['public_wiki_folder'], '')
|
||||
result.append(f"'{failed_regex.pattern}' matched file {trimmed_file}:\n"
|
||||
f" {failed_line.strip()}")
|
||||
stop('\n'.join(result))
|
||||
|
@ -1,4 +1,5 @@
|
||||
import datetime
|
||||
import functools
|
||||
import importlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
@ -54,4 +55,5 @@ class ConfigurationManager:
|
||||
f.write(f'{attr} = "{value}"\n')
|
||||
return True
|
||||
|
||||
|
||||
cm = ConfigurationManager()
|
||||
|
3
git.py
3
git.py
@ -4,5 +4,8 @@ from typing import Sequence
|
||||
def exec(*args: str):
|
||||
return subprocess.check_call(["git", *args])
|
||||
|
||||
def rc(*args: str):
|
||||
return subprocess.call(["git", *args])
|
||||
|
||||
def read(*args: str):
|
||||
return subprocess.check_output(["git", *args], text=True).strip()
|
||||
|
8
tw.py
8
tw.py
@ -7,7 +7,7 @@ import subprocess
|
||||
from textwrap import dedent
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from config import cm
|
||||
import config
|
||||
import git
|
||||
|
||||
|
||||
@ -96,12 +96,12 @@ def _save_wikifolder_to_config(wiki_name: str) -> bool:
|
||||
Return True if the option ended set to wiki_name, False otherwise.
|
||||
"""
|
||||
print("tzk: Writing new wiki folder to config file...")
|
||||
if not cm.write_attr("wiki_folder", wiki_name):
|
||||
if cm.wiki_folder == wiki_name:
|
||||
if not config.cm.write_attr("wiki_folder", wiki_name):
|
||||
if config.cm.wiki_folder == wiki_name:
|
||||
print("tzk: (Looks like it was already there.)")
|
||||
else:
|
||||
print(f"tzk: WARNING: The wiki_folder option in your config appears "
|
||||
f"to be set to '{cm.wiki_folder}', rather than the wiki folder "
|
||||
f"to be set to '{config.cm.wiki_folder}', rather than the wiki folder "
|
||||
f"you're initializing, {wiki_name}. Please check your config file "
|
||||
"and update this option if necessary.")
|
||||
return False
|
||||
|
21
tzk.py
21
tzk.py
@ -9,7 +9,7 @@ from typing import Optional
|
||||
from config import cm
|
||||
import git
|
||||
import tw
|
||||
from util import fail
|
||||
from util import BuildError, fail
|
||||
|
||||
|
||||
class CliCommand(ABC):
|
||||
@ -179,18 +179,29 @@ class BuildCommand(CliCommand):
|
||||
print(f"tzk: Found {len(steps)} build steps.")
|
||||
|
||||
for idx, step in enumerate(steps, 1):
|
||||
if hasattr(step, 'name'):
|
||||
print(f"\ntzk: Step {idx}/{len(steps)}: {step.name}")
|
||||
if hasattr(step, '__doc__'):
|
||||
print(f"\ntzk: Step {idx}/{len(steps)}: {step.__doc__}")
|
||||
else:
|
||||
print(f"\ntzk: Step {idx}/{len(steps)}")
|
||||
try:
|
||||
step()
|
||||
except Exception as e:
|
||||
print(f"\ntzk: Build of product '{args.product}' failed on step {failed}. "
|
||||
except BuildError as e:
|
||||
print(f"tzk: ERROR: {str(e)}")
|
||||
print(f"\ntzk: Build of product '{args.product}' failed on step {idx}, "
|
||||
f"backed by builder '{step.__name__}'.")
|
||||
sys.exit(1)
|
||||
except Exception:
|
||||
print(f"\ntzk: Build of product '{args.product}' failed on step {idx}: "
|
||||
f"unhandled exception. "
|
||||
f"The original error follows:")
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
for idx, step in enumerate(steps, 1):
|
||||
if hasattr(step, 'cleaner'):
|
||||
print(f"\ntzk: Running cleanup routine for step {idx}...")
|
||||
step.cleaner()
|
||||
|
||||
print(f"\ntzk: Build of product '{args.product}' completed successfully.")
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user