aboutsummaryrefslogtreecommitdiff
path: root/doc/manual/docroot.py
blob: e95f8abbf87e679515009776071d1e6e3814112d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#!/usr/bin/env python3

from pathlib import Path
import json
import os, os.path
import sys

name = 'process-docroot.py'

def log(*args, **kwargs):
    kwargs['file'] = sys.stderr
    return print(f'{name}:', *args, **kwargs)

def replace_docroot(relative_md_path: Path, content: str, book_root: Path):
    assert not relative_md_path.is_absolute(), f'{relative_md_path=} from mdbook should be relative'

    md_path_abs = book_root / relative_md_path
    docroot_abs = md_path_abs.parent
    assert docroot_abs.is_dir(), f'supposed docroot {docroot_abs} is not a directory (cwd={os.getcwd()})'

    # The paths mdbook gives us are relative to the directory with book.toml.
    # @docroot@ wants to be replaced with the path relative to `src/`.
    docroot_rel = os.path.relpath(book_root / 'src', start=docroot_abs)

    return content.replace('@docroot@', docroot_rel)

def recursive_replace(data, book_root):
    match data:
        case {'sections': sections}:
            return data | dict(
                sections = [recursive_replace(section, book_root) for section in sections],
            )
        case {'Chapter': chapter}:
            # Path to the .md file for this chapter, relative to book_root.
            path_to_chapter = Path('src') / chapter['path']
            chapter_content = chapter['content']

            return data | dict(
                Chapter = chapter | dict(
                    content = replace_docroot(path_to_chapter, chapter_content, book_root),
                    sub_items = [recursive_replace(sub_item, book_root) for sub_item in chapter['sub_items']],
                ),
            )

        case rest:
            assert False, f'should have been called on a dict, not {type(rest)=}\n\t{rest=}'

def main():

    if len(sys.argv) > 1 and sys.argv[1] == 'supports':
        log('confirming to mdbook that we support their stuff')
        return 0

    # mdbook communicates with us over stdin and stdout.
    # It splorks us a JSON array, the first element describing the context,
    # the second element describing the book itself,
    # and then expects us to send it the modified book JSON over stdout.

    context, book = json.load(sys.stdin)

    # book_root is *not* @docroot@. @docroot@ gets replaced with a relative path to `./src/`.
    # book_root is the directory where book.toml, aka `src`'s parent.
    book_root = Path(context['root'])
    assert book_root.exists(), f'{book_root=} does not exist'
    assert book_root.joinpath('book.toml').is_file(), f'{book_root / "book.toml"} is not a file'

    log('replacing all occurrences of @docroot@ with a relative path')

    # Find @docroot@ in all parts of our recursive book structure.
    replaced_content = recursive_replace(book, book_root)

    replaced_content_str = json.dumps(replaced_content)

    # Give mdbook our changes.
    print(replaced_content_str)

    log('done!')

try:
    sys.exit(main())
except AssertionError as e:
    print(f'{name}: INTERNAL ERROR in mdbook preprocessor', file=sys.stderr)
    print(f'this is a bug in {name}')
    raise