Coverage for bookbuilderpy/preprocessor.py: 67%
81 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-17 23:15 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-17 23:15 +0000
1"""The preprocessor commands to be applied once the text has been loaded."""
2from typing import Callable, Final
4import bookbuilderpy.constants as bc
5from bookbuilderpy.git import Repo
6from bookbuilderpy.path import Path
7from bookbuilderpy.preprocessor_code import get_programming_language, load_code
8from bookbuilderpy.preprocessor_commands import create_preprocessor
9from bookbuilderpy.strings import enforce_non_empty_str
12def preprocess(text: str,
13 input_dir: str,
14 get_meta: Callable,
15 get_repo: Callable,
16 repo: Repo | None,
17 output_dir: str) -> str:
18 """
19 Apply all the preprocessor commands to the given text.
21 :param text: the text of the book to be preprocessed.
22 :param input_dir: the input director
23 :param get_meta: a command for obtaining meta information.
24 :param get_repo: a command for obtaining a repository
25 :param repo: the root repository of the project
26 :param output_dir: the output directory
27 """
28 src = Path.directory(input_dir)
29 dst = Path.directory(output_dir)
30 src.enforce_neither_contains(dst)
32 # execute all meta-data commands
33 text = (create_preprocessor(name=bc.CMD_GET_META,
34 func=get_meta,
35 n=1,
36 strip_white_space=False))(text)
38 def __get_repo(repo_id, info_id) -> str:
39 gitrepo: Final[Repo] = get_repo(repo_id)
40 if info_id == bc.META_REPO_INFO_URL:
41 return gitrepo.get_base_url()
42 if info_id == bc.META_REPO_INFO_DATE:
43 return gitrepo.date_time
44 if info_id == bc.META_REPO_INFO_COMMIT:
45 return gitrepo.commit
46 if info_id == bc.META_REPO_INFO_NAME:
47 return gitrepo.get_name()
48 raise ValueError(
49 f"Invalid repo query '{info_id}' for repo '{repo_id}'.")
51 # execute all repo-info commands
52 text = (create_preprocessor(name=bc.CMD_GET_REPO,
53 func=__get_repo,
54 n=2,
55 strip_white_space=False))(text)
57 # make the definitions
58 def_map: dict[str, str] = {}
59 def_count: dict[str, int] = {}
61 def __make_def(deftype: str,
62 label: str,
63 body: str) -> str:
64 nonlocal def_map
65 nonlocal def_count
66 prefix = enforce_non_empty_str(get_meta(f"{deftype}Title").strip())
67 count: int = def_count.get(deftype, 0) + 1
68 def_count[deftype] = count
69 anchor = f"{prefix} {count}"
70 enforce_non_empty_str(label)
71 if label in def_map:
72 raise ValueError(
73 f"Redefined label '{label}' of type '{deftype}'.")
74 label_name = f"_def:{count}:{label}"
75 def_map[label] = f"[{anchor}](#{label_name})"
76 enforce_non_empty_str(body)
77 return f'<div id="{label_name}">**{anchor}.** {body}</div>'
79 text = (create_preprocessor(name=bc.CMD_DEFINITION,
80 func=__make_def,
81 n=3,
82 strip_white_space=True,
83 wrap_in_newlines=3))(text)
84 del def_count, __make_def
86 text = (create_preprocessor(name=bc.CMD_DEF_REF,
87 func=def_map.__getitem__,
88 n=1,
89 strip_white_space=False))(text)
90 del def_map
92 # create all figures
93 def __make_absolute_figure(label: str,
94 caption: str,
95 path: str,
96 args: str) -> str:
97 nonlocal src
98 nonlocal dst
99 new_path = Path.copy_resource(src, path, dst)
100 caption = enforce_non_empty_str(caption.strip())
101 use_path = enforce_non_empty_str(new_path.relative_to(dst).strip())
102 cmd = enforce_non_empty_str(" ".join([enforce_non_empty_str(
103 label.strip()), args.strip()]).strip())
104 return f"![{caption}]({use_path}){{#fig:{cmd}}}"
106 text = (create_preprocessor(name=bc.CMD_ABSOLUTE_FIGURE,
107 func=__make_absolute_figure,
108 n=4, strip_white_space=True,
109 wrap_in_newlines=2))(text)
111 # make a code section
112 def __make_code(label: str, caption: str, code: str, file: Path,
113 userepo: Repo | None) -> str:
114 code = enforce_non_empty_str(code.strip())
115 label = enforce_non_empty_str(label.strip())
116 code = enforce_non_empty_str(code.strip())
117 plang: str | None = get_programming_language(file)
118 plang = "" if plang is None else f" .{plang}"
120 caption = enforce_non_empty_str(caption.strip())
121 spacer = " "
122 end = caption[len(caption) - 1]
123 # Below, we check first whether the text ends in a Chinese punctuation
124 # mark and if it does not, if it ends in a Western one.
125 if end in ("\u3001\u3002\u3003\u3009\u300b\u300d\u3011\u3015\u3017"
126 "\u3019\u301b\u301e\ufe16\ufe57\uff01\uff1f\uff61\uff64"):
127 spacer = ""
128 elif end not in ".;!?)]}\"'\u00a1\u00b7\u00bf\u037e\u0387":
129 caption = f"{caption}."
130 if userepo:
131 userepo.path.enforce_contains(file)
132 url = userepo.make_url(file.relative_to(userepo.path))
133 caption = f"{caption}{spacer}([src]({url}))"
135 return (f"Listing: {caption}\n\n"
136 f"```{{#lst:{label}{plang} .numberLines}}\n{code}\n```")
138 # create all local code
139 def __make_absolute_code(label: str, caption: str, path: str, lines: str,
140 labels: str, args: str) -> str:
141 nonlocal src
142 file: Final[Path] = Path.file(path)
143 src.enforce_contains(file)
144 code = load_code(file, lines=lines, labels=labels, args=args)
145 return __make_code(label=label, caption=caption,
146 code=code, file=file, userepo=repo)
148 text = (create_preprocessor(name=bc.CMD_ABSOLUTE_CODE,
149 func=__make_absolute_code,
150 n=6, strip_white_space=True,
151 wrap_in_newlines=2))(text)
153 # create all git code
154 def __make_git_code(repoid: str, label: str, caption: str, path: str,
155 lines: str, labels: str, args: str) -> str:
156 userepo: Final[Repo] = get_repo(repoid)
157 file: Final[Path] = userepo.path.resolve_inside(path)
158 file.enforce_file()
159 userepo.path.enforce_contains(file)
160 code = load_code(file, lines=lines, labels=labels, args=args)
161 return __make_code(label=label, caption=caption,
162 code=code, file=file, userepo=userepo)
164 return (create_preprocessor(name=bc.CMD_GIT_CODE,
165 func=__make_git_code,
166 n=7, strip_white_space=True,
167 wrap_in_newlines=2))(text)