blob: e83c008be1a546f3e06f4a9e5c59b62dcb1eec4a [file] [log] [blame]
Tim-Philipp Müller57a224f2020-07-31 07:26:11 +00001#!/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
26import argparse
27import sys
28import re
29
30parser = argparse.ArgumentParser()
31parser.add_argument('template')
32parser.add_argument('input')
33parser.add_argument('output')
34
35args = parser.parse_known_args()
36
37template_fn = args[0].template
38output_fn = args[0].output
39input_fn = args[0].input
40
41# -------------
42# Read template
43# -------------
44
45with open(template_fn, 'r', encoding='utf8') as f:
46 template_text = f.read()
47
48template_lines = template_text.strip().split('\n')
49
50# -------------------------------------
51# Read replacement sets from .fncs file
52# -------------------------------------
53
54replacement_sets = []
55
56# TODO: also allow '-' for stdin
57with open(input_fn, 'r', encoding='utf8') as f:
58 fncs_text = f.read()
59
60# split into replacement sets
61fncs_chunks = fncs_text.strip().split('@@')
62
63for 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
94if output_fn == '-':
95 fout = sys.stdout
96else:
97 fout = open(output_fn, "w", encoding='utf8')
98
99# ----------------
100# Process template
101# ----------------
102
103def 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
159for rep in replacement_sets:
160 do_replace(template_lines, rep)