#!/usr/bin/env python3 import copy import re import sys import CommonMark_bkrs as CommonMark import yaml def format_keyword(line): words = line.split(' ') keyword = words[0] return '**{}** '.format(keyword) + line[len(keyword):] def format_scenario_step(bind, line, prev_keyword): debug('step: line={!r} prev_keyword={}'.format(line, prev_keyword)) words = line.split() if not words: return line.strip(), prev_keyword keyword = words[0] real_keyword = keyword if keyword.lower() == 'and': if prev_keyword is None: sys.exit('AND may not be used on first step in snippet') real_keyword = prev_keyword line = ' '.join(words[1:]) debug(' keyword={!r} rest={!r}'.format(keyword, line)) for b in bind: if real_keyword not in b: continue m = re.match(b[real_keyword.lower()], line, re.I) if m and m.end() == len(line): debug(' found binding: {!r}'.format(b)) n = len(m.groups()) if n > 0: end = 0 parts = [] for i in range(1, n+1): parts.append(line[end:m.start(i)]) thispart = line[m.start(i) : m.end(i)] parts.append('_{}_'.format(thispart)) end = m.end(i) line = ''.join(parts) + line[m.end(n):] line = '{} {}'.format(keyword, line) debug(' match: {!r}'.format(line)) break if not line.strip(): return line return format_keyword(line), real_keyword def format_fable_snippet(bind, lines): debug('snippet: lines={!r}'.format(lines)) prev_keyword = None output = [] for line in lines: ln, prev_keyword = format_scenario_step(bind, line, prev_keyword) output.append(ln) return output def is_fable_snippet(o): prefix = "```fable\n" return o.t == 'FencedCode' and o.info == 'fable' def is_heading(o): return o.t =='ATXHeader' def write_document(bind, f, o): pass def write_atxheader(bind, f, o): f.write('{} {}\n\n'.format('#' * o.level, ' '.join(o.strings))) def write_setextheader(bind, f, o): chars = { 1: '=', 2: '-', } c = chars[o.level] f.write('{}\n{}\n\n'.format(' '.join(o.strings), c * 72)) def write_paragraph(bind, f, o): for s in o.strings: f.write('{}\n'.format(s)) f.write('\n') def write_fable_snippet(bind, f, o): for line in format_fable_snippet(bind, o.strings[1:]): f.write('> {} \n'.format(line)) f.write('\n') def write_not_fable_snippet(bind, f, o): fence = o.fence_char * o.fence_length lang = o.strings[0] f.write('{}{}\n'.format(fence, lang)) for line in o.strings[1:]: f.write('{}\n'.format(line)) f.write('{}\n'.format(fence)) f.write('\n') def write_fencedcode(bind, f, o): if is_fable_snippet(o): write_fable_snippet(bind, f, o) else: write_not_fable_snippet(bind, f, o) def write_indentedcode(bind, f, o): for s in o.strings: f.write(' {}\n'.format(s)) f.write('\n') def write_horizontalrule(bind, f, o): f.write('---\n') def write_list(bind, f, o): pass def write_listitem(bind, f, o): bullet = o.list_data['bullet_char'] offset = o.list_data['marker_offset'] padding = o.list_data['padding'] prefix = '{}{} '.format(' ' * offset, bullet) cont = ' ' * padding for c in o.children: prepend = prefix for s in c.strings: f.write('{}{}\n'.format(prepend, s)) prepend = cont return True def write_referencedef(bind, f, o): for s in o.strings: f.write('{}\n'.format(s)) writers = { 'Document': write_document, 'ATXHeader': write_atxheader, 'SetextHeader': write_setextheader, 'Paragraph': write_paragraph, 'FencedCode': write_fencedcode, 'IndentedCode': write_indentedcode, 'HorizontalRule': write_horizontalrule, 'List': write_list, 'ListItem': write_listitem, 'ReferenceDef': write_referencedef, } def write(bind, f, o): if o.t not in writers: debug('{} not known'.format(repr(o.t))) return writer = writers[o.t] return writer(bind, f, o) def walk(o, func): done = func(o) if not done: for c in o.children: walk(c, func) def debug(msg): sys.stderr.write('DEBUG: {}\n'.format(msg)) sys.stderr.flush() debug('reading bindings') bindings = yaml.safe_load(open(sys.argv[1])) debug('reading inputs') text = ''.join(open(filename).read() for filename in sys.argv[2:]) start = '---\n' end = '\n...\n' if text.startswith(start): meta, text = text.split(end, 1) meta += end + '\n' else: meta = '' debug('parse') parser = CommonMark.DocParser() ast = parser.parse(text) debug('output') sys.stdout.write(meta) walk(ast, lambda o: write(bindings, sys.stdout, o)) debug('ok')