add kill-phrase builder
This commit is contained in:
		
							
								
								
									
										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.") | ||||
|  | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Soren I. Bjornstad
					Soren I. Bjornstad