M .gitignore => .gitignore +1 -0
@@ 1,1 1,2 @@
/markup
+__pycache__
A gloss/__main__.py => gloss/__main__.py +103 -0
@@ 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]} <SRCDIR> <OUTDIR>
+
+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)
A gloss/markup/__init__.py => gloss/markup/__init__.py +46 -0
@@ 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
A gloss/markup/markup => gloss/markup/markup +0 -0
R markup.c => gloss/markup/markup.c +0 -1
@@ 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;
R gloss.py => gloss/modes/glossary.py +12 -47
@@ 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]} <SRCDIR> <OUTDIR>
@@ 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__':
</html>
'''
- 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])
A run => run +3 -0
@@ 0,0 1,3 @@
+#!/bin/sh
+
+PYTHONPATH="$(dirname "$0")" python -m gloss $@
A testsrc/.gloss => testsrc/.gloss +1 -0
@@ 0,0 1,1 @@
+mode: gloss
A testsrc/a.html => testsrc/a.html +24 -0
@@ 0,0 1,24 @@
+<!doctype html>
+<html>
+<head>
+ <meta encoding='utf-8'>
+ <title>The Letter A</title>
+</head>
+<body>
+<main>
+<h1>The Letter A</h1>
+<p>this is a paragraph block. these lines will be folded into one string and ultimately rendered roughly the same in the browser.</p>
+<div>
+ <blockquote cite="https://aleteoryx.me">this is a quote block</blockquote>
+ <p>— <cite><a href="https://aleteoryx.me">aleteoryx</a></cite>, 2025</p>
+</div>
+<p>\<http://example.com|example page></p>
+</main>
+<hr>
+<h3>See also:</h3>
+<ul>
+ <li><a href="b.html">The Letter B</a></li>
+</ul>
+<footer>File last modified Fri, 2025-15-51 00:51:39 </footer>
+</body>
+</html>
A testsrc/b.html => testsrc/b.html +19 -0
@@ 0,0 1,19 @@
+<!doctype html>
+<html>
+<head>
+ <meta encoding='utf-8'>
+ <title>The Letter B</title>
+</head>
+<body>
+<main>
+<h1>The Letter B</h1>
+<p>preceeded by <a href="a.html">the letter a</a>.</p>
+</main>
+<hr>
+<h3>See also:</h3>
+<ul>
+ <li><a href="a.html">The Letter A</a></li>
+</ul>
+<footer>File last modified Thu, 2025-14-40 18:40:52 </footer>
+</body>
+</html>