From 82cbcee377df7e4305c6b450be1a104d94bb8dbd Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Fri, 11 Feb 2022 13:43:39 +0100 Subject: [PATCH] Ditaa-based diagrams --- .envrc | 3 ++ .gitignore | 2 + book.prb | 1 + book.prs | 34 +++++++++++++ book.toml | 3 ++ mdbook-ditaa | 109 +++++++++++++++++++++++++++++++++++++++++ src/figures/.gitignore | 1 + src/operation/index.md | 25 ++++++++++ 8 files changed, 178 insertions(+) create mode 100644 .envrc create mode 100644 book.prb create mode 100644 book.prs create mode 100755 mdbook-ditaa create mode 100644 src/figures/.gitignore diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..30a7964 --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +[ -d .venv ] || python -m venv .venv +. .venv/bin/activate +pip install preserves diff --git a/.gitignore b/.gitignore index 3006b27..fc70fff 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ book/ +.venv/ +.mdbook-ditaa.sqlite diff --git a/book.prb b/book.prb new file mode 100644 index 0000000..e198c54 --- /dev/null +++ b/book.prb @@ -0,0 +1 @@ +´³schema·³version‘³ definitions·³Book´³dict·±sections´³named³sections´³seqof´³refµ„³BookItem„„„±__non_exhaustive´³lit³null„„„³Chapter´³dict·±name´³named³name´³atom³String„„±path´³named³path´³refµ„³ MaybePath„„±number´³named³number´³refµ„³MaybeSectionNumber„„±content´³named³content´³atom³String„„± sub_items´³named³ sub_items´³seqof´³refµ„³BookItem„„„± source_path´³named³ source_path´³refµ„³ MaybePath„„± parent_names´³named³ parent_names´³seqof´³atom³String„„„„„³Context´³dict·±root´³named³root´³atom³String„„±config´³named³config³any„±renderer´³named³renderer´³atom³String„„±mdbook_version´³named³mdbook_version´³atom³String„„„„³BookItem´³orµµ±chapter´³dict·±Chapter´³named³value´³refµ„³Chapter„„„„„µ± separator´³lit± Separator„„µ± partTitle´³dict·± PartTitle´³named³value´³atom³String„„„„„„„³ MaybePath´³orµµ±absent´³lit³null„„µ±present´³atom³String„„„„³PreprocessorInput´³tupleµ´³named³context´³refµ„³Context„„´³named³book´³refµ„³Book„„„„³MaybeSectionNumber´³orµµ±absent´³lit³null„„µ±present´³seqof´³atom³ SignedInteger„„„„„„³ embeddedType€„„ \ No newline at end of file diff --git a/book.prs b/book.prs new file mode 100644 index 0000000..dffd242 --- /dev/null +++ b/book.prs @@ -0,0 +1,34 @@ +version 1 . + +PreprocessorInput = [@context Context, @book Book] . + +Context = { + "root": @root string, + "config": @config any, + "renderer": @renderer string, + "mdbook_version": @mdbook_version string, +} . + +Book = { + "sections": @sections [BookItem ...], + "__non_exhaustive": =null, +} . + +BookItem = +/ @chapter { "Chapter": @value Chapter } +/ @separator "Separator" +/ @partTitle { "PartTitle": @value string } +. + +Chapter = { + "name": @name string, + "content": @content string, + "number": @number MaybeSectionNumber, + "sub_items": @sub_items [BookItem ...], + "path": @path MaybePath, + "source_path": @source_path MaybePath, + "parent_names": @parent_names [string ...], +} . + +MaybeSectionNumber = @absent =null / @present [int ...] . +MaybePath = @absent =null / @present string . diff --git a/book.toml b/book.toml index 6a0639c..7937552 100644 --- a/book.toml +++ b/book.toml @@ -4,3 +4,6 @@ language = "en" multilingual = false src = "src" title = "The Synit Manual" + +[preprocessor.ditaa] +command = "./mdbook-ditaa figures/ditaa" diff --git a/mdbook-ditaa b/mdbook-ditaa new file mode 100755 index 0000000..205b4c6 --- /dev/null +++ b/mdbook-ditaa @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# -*- python -*- + +import sys +import subprocess +import re +import os +import sqlite3 + +from preserves import preserve, parse, stringify +from preserves.schema import load_schema_file, extend + +cache = sqlite3.connect('.mdbook-ditaa.sqlite') +try: + cache.cursor().execute('CREATE TABLE diagrams(source text, svg text)') +except: + pass + +try: + schema = load_schema_file('./book.prb').book +except FileNotFoundError: + subprocess.check_output(['sh', '-c', 'preserves-schemac --no-bundle .:book.prs > book.prb']) + schema = load_schema_file('./book.prb').book + +@extend(schema.Book) +def expand(self, context): + for i in self.sections: + i.expand(context) + +@extend(schema.BookItem.chapter) +def expand(self, context): + self.value.expand(context) + +@extend(schema.BookItem._ALL) +def expand(self, context): + pass + +@extend(schema.Chapter) +def expand(self, context): + if self.source_path.VARIANT.name == 'present': + directory = os.path.dirname(self.source_path.value) + self.content = expand_codeblock(DITAA_TAG, expand_ditaa, self.content, context, directory) + for i in self.sub_items: + i.expand(context) + +def expand_codeblock(language_re, expander, s, *args): + return re.sub('^```' + language_re + '\n(.*?)\n```\n', lambda m: expander(m, *args), s, 0, re.M | re.S) + +figure_count = 0 +def next_filename(): + global figure_count + figure_count = figure_count + 1 + return f'figure_{figure_count}' + +DITAA_TAG = r'ditaa(?: ((?:\w|-)+))?' +def expand_ditaa(m, context, directory): + sourcedir = context['config']['book']['src'] + baseurl = context['config']['output']['html']['site-url'] + filename = os.path.join(directory, (m.group(1) or next_filename()) + '.svg') + sys.stderr.write(f'ditaa {filename} ...') + + ditaa_source = m.group(2) + + cached = list(cache.cursor().execute('SELECT svg FROM diagrams WHERE source = ?', (ditaa_source,))) + if cached: + svg = cached[0][0] + else: + svg = subprocess.check_output(['ditaa', '--svg', '-'], input=ditaa_source.encode('utf-8')) + svg = svg.decode('utf-8') + cache.cursor().execute('INSERT INTO diagrams VALUES (?, ?)', (ditaa_source, svg)) + cache.cursor().execute('COMMIT') + + svgfilename = os.path.join(sourcedir, output_prefix, filename) + os.makedirs(os.path.dirname(svgfilename), exist_ok=True) + + need_write = True + if os.path.exists(svgfilename): + with open(svgfilename, 'rt') as f: + existing = f.read() + if existing == svg: + need_write = False + if need_write: + sys.stderr.write('updated\n') + with open(svgfilename, 'wt') as f: + f.write(svg) + else: + sys.stderr.write('unchanged\n') + + return f'

{filename}

\n' + +def structure(items): + answer = [] + for i in items: + if i.VARIANT.name == 'chapter': + a = structure(i.value.sub_items) + answer.append({ i.value.name: a }) + return answer + +if __name__ == '__main__': + if sys.argv[-2:-1] == ['supports']: + sys.exit(0 if sys.argv[-1] == 'html' else 1) + output_prefix = sys.argv[1] if len(sys.argv) > 1 else 'figures' + + raw = parse(sys.stdin.read()) + # sys.stderr.write(stringify(raw, indent=2) + '\n') + i = schema.PreprocessorInput.decode(raw) + # sys.stderr.write(stringify(preserve(i.context), indent=2) + '\n') + i.book.expand(i.context) + print(stringify(preserve(i.book), with_commas=True)) diff --git a/src/figures/.gitignore b/src/figures/.gitignore new file mode 100644 index 0000000..189c4d9 --- /dev/null +++ b/src/figures/.gitignore @@ -0,0 +1 @@ +ditaa/ diff --git a/src/operation/index.md b/src/operation/index.md index d2fb04e..b137f13 100644 --- a/src/operation/index.md +++ b/src/operation/index.md @@ -6,6 +6,31 @@ All processes in the system are arranged into a [supervision tree](../glossary.md#supervision-tree), conceptually rooted at the [system bus](./system-bus.md) (NB. not at PID 1). +```ditaa system-supervision-tree +(Example) + +----------------------------------+ + |Root System Bus (syndicate–server)| + +----------------+-----------------+ + | + +--------+--------+---------+----------+---------------+ + | | | | | | ++--+--+ +---+---+ +--+--+ +----+----+ +---+---+ +-----+-----+ +|init | |console| |udevd| |Network | |Wifi | |Session bus| ++-----+ |getty | +-----+ |Interface| |Daemon | ... +-----+-----+ + +-------+ |Monitor | |(wlan0)| | + |Daemon | +-------+ | + +---------+ | + | + +----------+----------------------------+ + | | | + +---+---+ +--+--+ +----+---+ + |Browser| |Email| . . . |X Server| + +-------+ +-----+ +--------+ + +``` + +Here's an example of `ps` output from a Synit prototype running on a mobile phone: + PID TTY STAT TIME COMMAND 1 ? Ssl 0:00 /sbin/synit-pid1 1034 ? Sl 0:00 /usr/bin/syndicate-server --inferior --config /etc/syndicate/boot