Tim-Philipp Müller | 57a224f | 2020-07-31 07:26:11 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # fontconfig/doc/edit-sgml.py |
| 4 | # |
| 5 | # Copyright © 2003 Keith Packard |
| 6 | # Copyright © 2020 Tim-Philipp Müller |
| 7 | # |
| 8 | # Permission to use, copy, modify, distribute, and sell this software and its |
| 9 | # documentation for any purpose is hereby granted without fee, provided that |
| 10 | # the above copyright notice appear in all copies and that both that |
| 11 | # copyright notice and this permission notice appear in supporting |
| 12 | # documentation, and that the name of the author(s) not be used in |
| 13 | # advertising or publicity pertaining to distribution of the software without |
| 14 | # specific, written prior permission. The authors make no |
| 15 | # representations about the suitability of this software for any purpose. It |
| 16 | # is provided "as is" without express or implied warranty. |
| 17 | # |
| 18 | # THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, |
| 19 | # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO |
| 20 | # EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR |
| 21 | # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, |
| 22 | # DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER |
| 23 | # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR |
| 24 | # PERFORMANCE OF THIS SOFTWARE. |
| 25 | |
| 26 | import argparse |
| 27 | import sys |
| 28 | import re |
| 29 | |
| 30 | parser = argparse.ArgumentParser() |
| 31 | parser.add_argument('template') |
| 32 | parser.add_argument('input') |
| 33 | parser.add_argument('output') |
| 34 | |
| 35 | args = parser.parse_known_args() |
| 36 | |
| 37 | template_fn = args[0].template |
| 38 | output_fn = args[0].output |
| 39 | input_fn = args[0].input |
| 40 | |
| 41 | # ------------- |
| 42 | # Read template |
| 43 | # ------------- |
| 44 | |
| 45 | with open(template_fn, 'r', encoding='utf8') as f: |
| 46 | template_text = f.read() |
| 47 | |
| 48 | template_lines = template_text.strip().split('\n') |
| 49 | |
| 50 | # ------------------------------------- |
| 51 | # Read replacement sets from .fncs file |
| 52 | # ------------------------------------- |
| 53 | |
| 54 | replacement_sets = [] |
| 55 | |
| 56 | # TODO: also allow '-' for stdin |
| 57 | with open(input_fn, 'r', encoding='utf8') as f: |
| 58 | fncs_text = f.read() |
| 59 | |
| 60 | # split into replacement sets |
| 61 | fncs_chunks = fncs_text.strip().split('@@') |
| 62 | |
| 63 | for chunk in fncs_chunks: |
| 64 | # get rid of any preamble such as license and FcFreeTypeQueryAll decl in fcfreetype.fncs |
| 65 | start = chunk.find('@') |
| 66 | if start: |
| 67 | chunk = chunk[start:] |
| 68 | |
| 69 | # split at '@' and remove empty lines (keep it simple instead of doing fancy |
| 70 | # things with regular expression matches, we control the input after all) |
| 71 | lines = [line for line in chunk.split('@') if line.strip()] |
| 72 | |
| 73 | replacement_set = {} |
| 74 | |
| 75 | while lines: |
| 76 | tag = lines.pop(0).strip() |
| 77 | # FIXME: this hard codes the tag used in funcs.sgml - we're lazy |
| 78 | if tag.startswith('PROTOTYPE'): |
| 79 | text = '' |
| 80 | else: |
| 81 | text = lines.pop(0).strip() |
| 82 | if text.endswith('%'): |
| 83 | text = text[:-1] + ' ' |
| 84 | |
| 85 | replacement_set[tag] = text |
| 86 | |
| 87 | if replacement_set: |
| 88 | replacement_sets += [replacement_set] |
| 89 | |
| 90 | # ---------------- |
| 91 | # Open output file |
| 92 | # ---------------- |
| 93 | |
| 94 | if output_fn == '-': |
| 95 | fout = sys.stdout |
| 96 | else: |
| 97 | fout = open(output_fn, "w", encoding='utf8') |
| 98 | |
| 99 | # ---------------- |
| 100 | # Process template |
| 101 | # ---------------- |
| 102 | |
| 103 | def do_replace(template_lines, rep, tag_suffix=''): |
| 104 | skip_tag = None |
| 105 | skip_lines = False |
| 106 | loop_lines = [] |
| 107 | loop_tag = None |
| 108 | |
| 109 | for t_line in template_lines: |
| 110 | # This makes processing easier and is the case for our templates |
| 111 | if t_line.startswith('@') and not t_line.endswith('@'): |
| 112 | sys.exit('Template lines starting with @ are expected to end with @, please fix me!') |
| 113 | |
| 114 | if loop_tag: |
| 115 | loop_lines += [t_line] |
| 116 | |
| 117 | # Check if line starts with a directive |
| 118 | if t_line.startswith('@?'): |
| 119 | tag = t_line[2:-1] + tag_suffix |
| 120 | if skip_tag: |
| 121 | sys.exit('Recursive skipping not supported, please fix me!') |
| 122 | skip_tag = tag |
| 123 | skip_lines = tag not in rep |
| 124 | elif t_line.startswith('@:'): |
| 125 | if not skip_tag: |
| 126 | sys.exit('Skip else but no active skip list?!') |
| 127 | skip_lines = skip_tag in rep |
| 128 | elif t_line.startswith('@;'): |
| 129 | if not skip_tag: |
| 130 | sys.exit('Skip end but no active skip list?!') |
| 131 | skip_tag = None |
| 132 | skip_lines = False |
| 133 | elif t_line.startswith('@{'): |
| 134 | if loop_tag or tag_suffix != '': |
| 135 | sys.exit('Recursive looping not supported, please fix me!') |
| 136 | loop_tag = t_line[2:-1] |
| 137 | elif t_line.startswith('@}'): |
| 138 | tag = t_line[2:-1] + tag_suffix |
| 139 | if not loop_tag: |
| 140 | sys.exit('Loop end but no active loop?!') |
| 141 | if loop_tag != tag: |
| 142 | sys.exit(f'Loop end but loop tag mismatch: {loop_tag} != {tag}!') |
| 143 | loop_lines.pop() # remove loop end directive |
| 144 | suffix = '+' |
| 145 | while loop_tag + suffix in rep: |
| 146 | do_replace(loop_lines, rep, suffix) |
| 147 | suffix += '+' |
| 148 | loop_tag = None |
| 149 | loop_lines = [] |
| 150 | else: |
| 151 | if not skip_lines: |
| 152 | # special-case inline optional substitution (hard-codes specific pattern in funcs.sgml because we're lazy) |
| 153 | output_line = re.sub(r'@\?(RET)@@RET@@:@(void)@;@', lambda m: rep.get(m.group(1) + tag_suffix, m.group(2)), t_line) |
| 154 | # replace any substitution tags with their respective substitution text |
| 155 | output_line = re.sub(r'@(\w+)@', lambda m: rep.get(m.group(1) + tag_suffix, ''), output_line) |
| 156 | print(output_line, file=fout) |
| 157 | |
| 158 | # process template for each replacement set |
| 159 | for rep in replacement_sets: |
| 160 | do_replace(template_lines, rep) |