M README.md => README.md +1 -31
@@ 6,34 6,4 @@ it is designed to maximize nonlinear browsing.
***
-the central gloss.py script translates each .gls file in its input directory to a .html in its output.
-to run it, you will need a python runtime and a C compiler. make sure to download the entire repo.
-
-.gls files are composed of a names section, a set of blocks, and an optional "see also" section.
-
-the names section consists of the first non-empty non-comment lines in a file.
-each line becomes a name for the file.
-a file's names are automatically turned into links,
-the first time it is referenced in another document.
-matching is performed at word boundaries: a file with name "foo" will not be linked from "foobar".
-the names section is terminated with a blank line.
-
-blocks can be either paragraph blocks or quote blocks, and they are terminated by blank lines.
-a block is a quote block if its first line begins with a '>'.
-to begin a paragraph block with '>', escape it with a backslash.
-
-if any line in a block begins with ~, it will become the block's metadata.
-only quotes have metadata, for authorship info.
-the format is thus: `<source> @@ <time> // <url>`.
-if `@@` is omitted, the signature will have no time associated.
-if `//` is omitted, the signature will have no URL associated.
-
-within a block, one can write a link of the form `<https://example.com>`, or `<https://example.com|example page>`.
-one can also escape `<` and `>` with backslashes. regions of text can be italicized by putting them `/in slashes/`.
-
-if a block begins with `***`, the "see also" section is entered.
-each subsequent non-empty line should be the slug (filename, minus .gls) of an article.
-it will be linked in a "see also" list at the bottom.
-
-if a template.html is present in the source directory, it will be the template for all rendered pages.
-run gloss.py with no arguments for more information.
+I'll document this another time. maybe. it's for personal use mostly
A gloss/__init__.py => gloss/__init__.py +84 -0
@@ 0,0 1,84 @@
+#!/bin/env python3
+
+import yaml
+from pathlib import Path
+from typing import List, Tuple, Callable
+from dataclasses import dataclass
+
+from .modes import glossary
+from .util import die, copy
+
+### MODES ###
+
+def copymode(sfmt, srcdir, outdir):
+ i = 0
+ for srcfile in srcdir.iterdir():
+ if not srcfile.is_file() or srcfile.name == '.gloss':
+ continue
+ i += 1
+ outfile = Path(outdir, srcfile.name)
+ copy(srcfile, outfile)
+ print(sfmt.format('###', f'copied {i} files'))
+
+modes = {
+ 'copy': copymode,
+ 'gloss': glossary.gloss
+}
+
+
+### FS WALK ###
+
+@dataclass
+class Step:
+ srcdir: Path
+ outdir: Path
+ reldir: Path
+ process: Callable[[str, Path, Path], None]
+
+ def test(self):
+ if not self.outdir.is_dir() and self.outdir.exists():
+ die(f'can\'t output "{self.outdir}": non-directory already exists', 3)
+
+
+ def exec(self, sfmt):
+ if not self.outdir.exists():
+ self.outdir.mkdir()
+ self.process(sfmt, self.srcdir, self.outdir)
+
+def get_steps(srcdir, outdir) -> List[Step]:
+ global modes
+
+ dirs = [srcdir]
+ ret = []
+ for adir in dirs:
+ reldir = adir.relative_to(srcdir)
+
+ 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 = reldir
+ if mode is None:
+ mode = 'copy'
+
+ if mode == 'ignore':
+ continue
+
+ if mode not in modes:
+ die(f'fatal: unknown mode in "{glossfile}": "{mode}"', 2)
+
+ empty = True
+ for what in adir.iterdir():
+ if what.is_dir():
+ dirs.append(what)
+ elif what.is_file():
+ empty = False
+
+ if mode != 'copy' or not empty:
+ ret.append(Step(adir, Path(outdir, out), reldir, modes[mode]))
+
+ return ret
M gloss/__main__.py => gloss/__main__.py +26 -73
@@ 1,14 1,15 @@
#!/bin/env python3
+from time import time
+start = time()
+
from sys import argv
-import yaml
from pathlib import Path
-from typing import List, Tuple, Callable
-from dataclasses import dataclass
+from math import log10
-from . import markup
+from . import get_steps
+from .util import die
-from .modes import glossary
usage = f'''
usage: {argv[0]} <SRCDIR> <OUTDIR>
@@ 26,78 27,30 @@ said file's 'mode' key is missing, the mode defaults to 'copy'.
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]
+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()
-def get_dirs(srcdir, outdir) -> List[SrcDir]:
- global modes
- dirs = [srcdir]
- ret = []
- for adir in dirs:
- if not adir.is_dir():
- continue
+print('collecting steps...')
+steps = get_steps(srcdir, outdir)
- 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
+numlen = int(log10(len(steps))+1)
+nfmt = f'[{{0:{numlen}}}/{len(steps)}]' # calculates the correct width for e.g. [ 5/52]
+sfmt = f'<{{0:^{numlen*2+1}}}> {{1}}' # and for the status, e.g. < END >
+print(sfmt.format('...', 'testing!'))
+for n, step in enumerate(steps):
+ print(f'{nfmt} testing {{1}}'.format(n+1, Path('/', step.reldir)))
+ step.test()
-if __name__ == '__main__':
- argv = argv[1:]
- if len(argv) != 2 or argv[0][0] == '-' or argv[1][0] == '-':
- die(usage)
+print(sfmt.format('...', 'building!'))
+for n, step in enumerate(steps):
+ print(f'{nfmt} building {{1}}'.format(n+1, Path('/', step.reldir)))
+ step.exec(sfmt)
- 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)
+stop = time()
+print(sfmt.format('END', f'build complete in {stop-start:.2}s! :3'))
M gloss/markup/__init__.py => gloss/markup/__init__.py +3 -0
@@ 14,6 14,9 @@ class Markup:
self.proc = Popen([str(self.bfile), 'convert'], stdin=PIPE, stdout=PIPE, text=True)
+ def __del__(self):
+ self.proc.kill()
+
def process(self, text):
self.proc.stdin.write(text+"\n")
self.proc.stdin.flush()
M gloss/modes/glossary.py => gloss/modes/glossary.py +8 -16
@@ 7,9 7,10 @@ from os import stat, system
from glob import glob
from typing import List, Optional, Union, Dict, Set, Tuple
from dataclasses import dataclass
-from sys import argv, stderr, exit
+from sys import argv, stderr
-from .. import markup
+from gloss.markup import get as getfmt
+from gloss.util import die
usage = f'''
usage: {argv[0]} <SRCDIR> <OUTDIR>
@@ 26,10 27,6 @@ interpolated as in str.format, and the following keys are provided:
{{modtime}} - The datetime of the article's last edit.
'''[1:]
-def die(why, code=1):
- print(why, file=stderr)
- exit(code)
-
### TYPES ###
@@ 151,7 148,7 @@ class Indexes:
pattern = re.compile(f"((?<=\\W)|^){re.escape(name)}((?=\\W)|$)", re.IGNORECASE)
self.names_sorted.append((name, pattern))
- sorted(self.names_sorted, key=lambda x: len(x[0]))
+ self.names_sorted = sorted(self.names_sorted, key=lambda x: len(x[0]), reverse=True)
def gen_inner_html(fmt, file, idx):
for block in file.blocks: # format text, listify it
@@ 229,7 226,7 @@ def gen_inner_html(fmt, file, idx):
### ENTRYPOINT ###
-def gloss(srcdir, outdir):
+def gloss(sfmt, srcdir, outdir):
try:
with open('{srcdir}/template.html', 'rt') as fp:
template = fp.read()
@@ 252,7 249,7 @@ def gloss(srcdir, outdir):
</html>
'''
- fmt = markup.get()
+ fmt = getfmt()
files = []
for fn in glob('*.gls', root_dir=srcdir):
@@ 273,10 270,5 @@ def gloss(srcdir, outdir):
'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])
+
+ print(sfmt.format('###', f'generated {len(files)} entries'))
A gloss/util.py => gloss/util.py +10 -0
@@ 0,0 1,10 @@
+from sys import exit
+
+def die(why, code=1):
+ print(why, file=stderr)
+ exit(code)
+
+def copy(a, b):
+ with open(a, 'rb') as af, open(b, 'wb') as bf:
+ while buf := af.read(2**20):
+ bf.write(buf)