From 63e33c3226ad62e8845b50d07807ede4e6fb77ac Mon Sep 17 00:00:00 2001 From: Aleteoryx Date: Sat, 6 Sep 2025 18:04:49 -0400 Subject: [PATCH] build system basics --- .gitignore | 1 + gloss/__main__.py | 103 ++++++++++++++++++++++++++++ gloss/markup/__init__.py | 46 +++++++++++++ gloss/markup/markup | Bin 0 -> 17576 bytes markup.c => gloss/markup/markup.c | 1 - gloss.py => gloss/modes/glossary.py | 59 ++++------------ run | 3 + testsrc/.gloss | 1 + testsrc/a.html | 24 +++++++ testsrc/b.html | 19 +++++ 10 files changed, 209 insertions(+), 48 deletions(-) create mode 100755 gloss/__main__.py create mode 100644 gloss/markup/__init__.py create mode 100755 gloss/markup/markup rename markup.c => gloss/markup/markup.c (99%) rename gloss.py => gloss/modes/glossary.py (85%) create mode 100755 run create mode 100644 testsrc/.gloss create mode 100644 testsrc/a.html create mode 100644 testsrc/b.html diff --git a/.gitignore b/.gitignore index 534afa61597090d20ee208113498936dbeedf7e9..36884a5a651ecd294e3ba30a4af595422d819a3f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /markup +__pycache__ diff --git a/gloss/__main__.py b/gloss/__main__.py new file mode 100755 index 0000000000000000000000000000000000000000..6f3c741e9c673348024f260660806a0f6ae7995b --- /dev/null +++ b/gloss/__main__.py @@ -0,0 +1,103 @@ +#!/bin/env python3 + +from sys import argv +import yaml +from pathlib import Path +from typing import List, Tuple, Callable +from dataclasses import dataclass + +from . import markup + +from .modes import glossary + +usage = f''' +usage: {argv[0]} + +recurses into SRCDIR, parsing each .gloss file as YAML and running the +requested parser. if a directory does not contain a '.gloss' file, or +said file's 'mode' key is missing, the mode defaults to 'copy'. + +.gloss may include the following fields: + mode: sets the mode. may be one of 'copy', 'ignore', or + 'gloss'. if mode is 'ignore', the directory and all + subdirectories will be skipped. + out: sets the output directory, relative to OUTDIR. if + omitted, it defaults to the current path relative to + SRCDIR. +'''[1:] + +def die(why, code=1): + print(why, file=stderr) + exit(code) + + +### MODES ### + +def copymode(srcdir, outdir): + for srcfile in srcdir.iterdir(): + if not srcfile.is_file() or srcfile.name == '.gloss': + continue + outfile = outdir + srcfile.name + outfile.write_bytes(srcfile.read_bytes()) + +modes = { + 'copy': copymode, + 'gloss': glossary.gloss +} + + +### FS WALK ### + +@dataclass +class SrcDir: + srcdir: Path + outdir: Path + process: Callable[[Path, Path], None] + +def get_dirs(srcdir, outdir) -> List[SrcDir]: + global modes + + dirs = [srcdir] + ret = [] + for adir in dirs: + if not adir.is_dir(): + continue + + glossfile = Path(adir, '.gloss') + out = mode = None + if glossfile.is_file(): + parsed = yaml.safe_load(open(glossfile, 'rt')) + out = parsed.get('out', None) + mode = parsed.get('mode', None) + + if out is None: + out = adir.relative_to(srcdir) + if mode is None: + mode = 'copy' + + if mode == 'ignore': + continue + + if mode not in modes: + die(f'fatal: unknown mode in "{glossfile}": "{mode}"') + + ret.append(SrcDir(adir, Path(outdir, out), modes[mode])) + dirs.extend(adir.iterdir()) + + return ret + + +if __name__ == '__main__': + argv = argv[1:] + if len(argv) != 2 or argv[0][0] == '-' or argv[1][0] == '-': + die(usage) + + srcdir = Path(argv[0]).absolute() + outdir = Path(argv[1]).absolute() + + steps = get_dirs(srcdir, outdir) + print(steps) + for step in steps: + if not step.outdir.exists(): + step.outdir.mkdir() + step.process(step.srcdir, step.outdir) diff --git a/gloss/markup/__init__.py b/gloss/markup/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6bfcfd7c9ae3d4dc6caaa3ef3baf4c0db227fc6b --- /dev/null +++ b/gloss/markup/__init__.py @@ -0,0 +1,46 @@ +import html +from pathlib import Path +from subprocess import Popen, PIPE, run as runcmd + +class Markup: + def __init__(self, where, *, cfile='markup.c', bfile='markup'): + self.cfile = Path(where, cfile) + self.bfile = Path(where, bfile) + + if not self.bfile.exists() or self.cfile.stat().st_mtime > self.bfile.stat().st_mtime: + print("recompiling markup subsystem...") + runcmd(['cc', str(self.cfile), '-o', str(self.bfile), '-Wall'], check=True) + print("recompiled!") + + self.proc = Popen([str(self.bfile), 'convert'], stdin=PIPE, stdout=PIPE, text=True) + + def process(self, text): + self.proc.stdin.write(text+"\n") + self.proc.stdin.flush() + + segments = [] + while (line := self.proc.stdout.readline()) not in ('NEXT\n', ''): + ty = line[0:4] + + length = int(line[5:9]) + ltext = line[12:12+length] + + if ty == 'HTML': + segments.append((ltext,)) + elif ty == 'IESC': + segments.append((html.escape(ltext),)) + elif ty == 'TEXT': + segments.append(ltext) + else: + print(f'read in unknown type "{ty}" from markup subprocess', file=stderr) + + return segments + +markup = None + +def get(): + global markup + if markup is None: + markup = Markup(Path(__file__).parent) + + return markup diff --git a/gloss/markup/markup b/gloss/markup/markup new file mode 100755 index 0000000000000000000000000000000000000000..f6f6a525cd03e919d4637f6e31246d28d663a36c GIT binary patch literal 17576 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 literal 0 HcmV?d00001 diff --git a/markup.c b/gloss/markup/markup.c similarity index 99% rename from markup.c rename to gloss/markup/markup.c index ed27a33cd83aa9c6cfe6c35735fd71bb10adb334..b38d6eb583814cfc86eb7b3cb6020c7fc3007488 100644 --- a/markup.c +++ b/gloss/markup/markup.c @@ -262,7 +262,6 @@ segmentize(size_t length, const char *source) } } if(in_italic){ - PLINE(); tail->next = italic_head->next; if(tail->next != NULL) tail = italic_tail; diff --git a/gloss.py b/gloss/modes/glossary.py similarity index 85% rename from gloss.py rename to gloss/modes/glossary.py index 6990f2357a702f67e9b90af30683a91c15aa9da6..b8442478faf192daaaee87a40ad2e436b2d4235d 100755 --- a/gloss.py +++ b/gloss/modes/glossary.py @@ -8,8 +8,8 @@ from glob import glob from typing import List, Optional, Union, Dict, Set, Tuple from dataclasses import dataclass from sys import argv, stderr, exit -from pathlib import Path -from subprocess import Popen, PIPE, run as runcmd + +from .. import markup usage = f''' usage: {argv[0]} @@ -116,42 +116,7 @@ def first_pass(slug, fp): elif block_type is not None: blocks.append(Block(block_type, block_text, block_meta)) - return GlsFile(slug, title, [*names], blocks, see_also) - -class Markup: - def __init__(self, where, *, cfile='markup.c', bfile='markup'): - self.cfile = Path(where, cfile) - self.bfile = Path(where, bfile) - - if not self.bfile.exists() or self.cfile.stat().st_mtime > self.bfile.stat().st_mtime: - print("recompiling markup subsystem...") - runcmd(['cc', str(self.cfile), '-o', str(self.bfile), '-Wall'], check=True) - print("recompiled!") - - self.proc = Popen([str(self.bfile), 'convert'], stdin=PIPE, stdout=PIPE, text=True) - - def process(self, text): - self.proc.stdin.write(text+"\n") - self.proc.stdin.flush() - - segments = [] - while (line := self.proc.stdout.readline()) not in ('NEXT\n', ''): - ty = line[0:4] - - length = int(line[5:9]) - ltext = line[12:12+length] - - if ty == 'HTML': - segments.append((ltext,)) - elif ty == 'IESC': - segments.append((html.escape(ltext),)) - elif ty == 'TEXT': - segments.append(ltext) - else: - print(f'read in unknown type "{ty}" from markup subprocess', file=stderr) - - return segments - + return GlsFile(slug, title, [*names], blocks, see_also) quote_pat = re.compile("((?:(?!@@)(?!//).)*)(?:@@((?:(?!//).)+))?(?://(.+))?") @@ -264,14 +229,7 @@ def gen_inner_html(fmt, file, idx): ### ENTRYPOINT ### -if __name__ == '__main__': - argv = argv[1:] - if len(argv) != 2 or argv[0][0] == '-' or argv[1][0] == '-': - die(usage) - - srcdir = argv[0] - outdir = argv[1] - +def gloss(srcdir, outdir): try: with open('{srcdir}/template.html', 'rt') as fp: template = fp.read() @@ -294,7 +252,7 @@ if __name__ == '__main__': ''' - fmt = Markup(Path(__file__).parent) + fmt = markup.get() files = [] for fn in glob('*.gls', root_dir=srcdir): @@ -315,3 +273,10 @@ if __name__ == '__main__': 'modtime': datetime.fromtimestamp(stat(f'{srcdir}/{file.slug}.gls').st_mtime) } fp.write(template.format(**ctx)) + +if __name__ == '__main__': + argv = argv[1:] + if len(argv) != 2 or argv[0][0] == '-' or argv[1][0] == '-': + die(usage) + + gloss(argv[0], argv[1]) diff --git a/run b/run new file mode 100755 index 0000000000000000000000000000000000000000..b0fe215e629c263f19c66bdcbde358f22a03f365 --- /dev/null +++ b/run @@ -0,0 +1,3 @@ +#!/bin/sh + +PYTHONPATH="$(dirname "$0")" python -m gloss $@ diff --git a/testsrc/.gloss b/testsrc/.gloss new file mode 100644 index 0000000000000000000000000000000000000000..398dbd21a44f9182dcc077682b6694ab682be812 --- /dev/null +++ b/testsrc/.gloss @@ -0,0 +1 @@ +mode: gloss diff --git a/testsrc/a.html b/testsrc/a.html new file mode 100644 index 0000000000000000000000000000000000000000..95febb67d19f756b97b3a6580bb3f6f51c2254bd --- /dev/null +++ b/testsrc/a.html @@ -0,0 +1,24 @@ + + + + + The Letter A + + +
+

The Letter A

+

this is a paragraph block. these lines will be folded into one string and ultimately rendered roughly the same in the browser.

+
+
this is a quote block
+

aleteoryx, 2025

+
+

\<http://example.com|example page>

+
+
+

See also:

+ +
File last modified Fri, 2025-15-51 00:51:39
+ + diff --git a/testsrc/b.html b/testsrc/b.html new file mode 100644 index 0000000000000000000000000000000000000000..ae105ea6d7a50d9ded98f07f911ecf41c8e311d1 --- /dev/null +++ b/testsrc/b.html @@ -0,0 +1,19 @@ + + + + + The Letter B + + +
+

The Letter B

+

preceeded by the letter a.

+
+
+

See also:

+ + + +