#!/usr/bin/env python3 # # fontconfig/doc/edit-sgml.py # # Copyright © 2003 Keith Packard # Copyright © 2020 Tim-Philipp Müller # # Permission to use, copy, modify, distribute, and sell this software and its # documentation for any purpose is hereby granted without fee, provided that # the above copyright notice appear in all copies and that both that # copyright notice and this permission notice appear in supporting # documentation, and that the name of the author(s) not be used in # advertising or publicity pertaining to distribution of the software without # specific, written prior permission. The authors make no # representations about the suitability of this software for any purpose. It # is provided "as is" without express or implied warranty. # # THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO # EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, # DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. import argparse import sys import re parser = argparse.ArgumentParser() parser.add_argument('template') parser.add_argument('input') parser.add_argument('output') args = parser.parse_known_args() template_fn = args[0].template output_fn = args[0].output input_fn = args[0].input # ------------- # Read template # ------------- with open(template_fn, 'r', encoding='utf8') as f: template_text = f.read() template_lines = template_text.strip().split('\n') # ------------------------------------- # Read replacement sets from .fncs file # ------------------------------------- replacement_sets = [] # TODO: also allow '-' for stdin with open(input_fn, 'r', encoding='utf8') as f: fncs_text = f.read() # split into replacement sets fncs_chunks = fncs_text.strip().split('@@') for chunk in fncs_chunks: # get rid of any preamble such as license and FcFreeTypeQueryAll decl in fcfreetype.fncs start = chunk.find('@') if start: chunk = chunk[start:] # split at '@' and remove empty lines (keep it simple instead of doing fancy # things with regular expression matches, we control the input after all) lines = [line for line in chunk.split('@') if line.strip()] replacement_set = {} while lines: tag = lines.pop(0).strip() # FIXME: this hard codes the tag used in funcs.sgml - we're lazy if tag.startswith('PROTOTYPE'): text = '' else: text = lines.pop(0).strip() if text.endswith('%'): text = text[:-1] + ' ' replacement_set[tag] = text if replacement_set: replacement_sets += [replacement_set] # ---------------- # Open output file # ---------------- if output_fn == '-': fout = sys.stdout else: fout = open(output_fn, "w", encoding='utf8') # ---------------- # Process template # ---------------- def do_replace(template_lines, rep, tag_suffix=''): skip_tag = None skip_lines = False loop_lines = [] loop_tag = None for t_line in template_lines: # This makes processing easier and is the case for our templates if t_line.startswith('@') and not t_line.endswith('@'): sys.exit('Template lines starting with @ are expected to end with @, please fix me!') if loop_tag: loop_lines += [t_line] # Check if line starts with a directive if t_line.startswith('@?'): tag = t_line[2:-1] + tag_suffix if skip_tag: sys.exit('Recursive skipping not supported, please fix me!') skip_tag = tag skip_lines = tag not in rep elif t_line.startswith('@:'): if not skip_tag: sys.exit('Skip else but no active skip list?!') skip_lines = skip_tag in rep elif t_line.startswith('@;'): if not skip_tag: sys.exit('Skip end but no active skip list?!') skip_tag = None skip_lines = False elif t_line.startswith('@{'): if loop_tag or tag_suffix != '': sys.exit('Recursive looping not supported, please fix me!') loop_tag = t_line[2:-1] elif t_line.startswith('@}'): tag = t_line[2:-1] + tag_suffix if not loop_tag: sys.exit('Loop end but no active loop?!') if loop_tag != tag: sys.exit(f'Loop end but loop tag mismatch: {loop_tag} != {tag}!') loop_lines.pop() # remove loop end directive suffix = '+' while loop_tag + suffix in rep: do_replace(loop_lines, rep, suffix) suffix += '+' loop_tag = None loop_lines = [] else: if not skip_lines: # special-case inline optional substitution (hard-codes specific pattern in funcs.sgml because we're lazy) output_line = re.sub(r'@\?(RET)@@RET@@:@(void)@;@', lambda m: rep.get(m.group(1) + tag_suffix, m.group(2)), t_line) # replace any substitution tags with their respective substitution text output_line = re.sub(r'@(\w+)@', lambda m: rep.get(m.group(1) + tag_suffix, ''), output_line) print(output_line, file=fout) # process template for each replacement set for rep in replacement_sets: do_replace(template_lines, rep)