From e174b09d5f10668d23320e741dd194f8ea8aa771 Mon Sep 17 00:00:00 2001 From: Avery Pennarun Date: Sat, 29 Jan 2011 23:20:05 -0800 Subject: [PATCH] Replace pandoc-based md-to-man conversion with a custom python script. Hopefully this will appease the people who can't install pandoc on MacOS. --- Documentation/.gitignore | 2 +- Documentation/all.do | 2 +- Documentation/clean.do | 2 +- Documentation/md-to-man.do | 5 +- Documentation/md2man.py | 278 ++++++++++++++++++++++++++++++++++++ Documentation/t/testitem.md | 87 +++++++++++ 6 files changed, 371 insertions(+), 5 deletions(-) create mode 100755 Documentation/md2man.py create mode 100644 Documentation/t/testitem.md diff --git a/Documentation/.gitignore b/Documentation/.gitignore index ba94fa2..16e7185 100644 --- a/Documentation/.gitignore +++ b/Documentation/.gitignore @@ -1,2 +1,2 @@ -/*.1 +*.1 /md-to-man diff --git a/Documentation/all.do b/Documentation/all.do index b76c0c4..f433edc 100644 --- a/Documentation/all.do +++ b/Documentation/all.do @@ -1,4 +1,4 @@ -/bin/ls *.md | +/bin/ls *.md t/*.md | sed 's/\.md/.1/' | xargs redo-ifchange diff --git a/Documentation/clean.do b/Documentation/clean.do index ebd0b40..78c7bf7 100644 --- a/Documentation/clean.do +++ b/Documentation/clean.do @@ -1,2 +1,2 @@ -rm -f *~ .*~ *.1 md-to-man *.tmp +rm -f *~ .*~ *.1 t/*.1 md-to-man *.tmp t/*.tmp diff --git a/Documentation/md-to-man.do b/Documentation/md-to-man.do index d678313..0ced5d3 100644 --- a/Documentation/md-to-man.do +++ b/Documentation/md-to-man.do @@ -1,5 +1,6 @@ -if pandoc /dev/null; then - echo 'pandoc -s -r markdown -w man -o $3 $1.md.tmp' +redo-ifchange md2man.py +if ./md2man.py /dev/null 2>/dev/null; then + echo './md2man.py $1.md.tmp' else (IFS=:; for DIR in $PATH; do redo-ifcreate "$DIR/pandoc"; done) echo "Warning: pandoc not installed; can't generate manpages." >&2 diff --git a/Documentation/md2man.py b/Documentation/md2man.py new file mode 100755 index 0000000..7aac0fc --- /dev/null +++ b/Documentation/md2man.py @@ -0,0 +1,278 @@ +#!/usr/bin/env python +import sys, os, markdown, re +from BeautifulSoup import BeautifulSoup + +def _split_lines(s): + return re.findall(r'([^\n]*\n?)', s) + + +class Writer: + def __init__(self): + self.started = False + self.indent = 0 + self.last_wrote = '\n' + + def _write(self, s): + if s: + self.last_wrote = s + sys.stdout.write(s) + + def writeln(self, s): + if s: + self.linebreak() + self._write('%s\n' % s) + + def write(self, s): + if s: + self.para() + for line in _split_lines(s): + if line.startswith('.'): + self._write('\\&' + line) + else: + self._write(line) + + def linebreak(self): + if not self.last_wrote.endswith('\n'): + self._write('\n') + + def para(self, bullet=None): + if not self.started: + if not bullet: + bullet = ' ' + if not self.indent: + self.writeln(_macro('.PP')) + else: + assert(self.indent >= 2) + prefix = ' '*(self.indent-2) + bullet + ' ' + self.writeln('.IP "%s" %d' % (prefix, self.indent)) + self.started = True + + def end_para(self): + self.linebreak() + self.started = False + + def start_bullet(self): + self.indent += 3 + self.para(bullet='\\[bu]') + + def end_bullet(self): + self.indent -= 3 + self.end_para() + +w = Writer() + + +def _macro(name, *args): + if not name.startswith('.'): + raise ValueError('macro names must start with "."') + fixargs = [] + for i in args: + i = str(i) + i = i.replace('\\', '') + i = i.replace('"', "'") + if (' ' in i) or not i: + i = '"%s"' % i + fixargs.append(i) + return ' '.join([name] + list(fixargs)) + + +def macro(name, *args): + w.writeln(_macro(name, *args)) + + +def _force_string(owner, tag): + if tag.string: + return tag.string + else: + out = '' + for i in tag: + if not (i.string or i.name in ['a', 'br']): + raise ValueError('"%s" tags must contain only strings: ' + 'got %r: %r' % (owner.name, tag.name, tag)) + out += _force_string(owner, i) + return out + + +def _clean(s): + s = s.replace('\\', '\\\\') + return s + + +def _bitlist(tag): + if not getattr(tag, 'contents', None): + for i in _split_lines(str(tag)): + yield None,_clean(i) + else: + for e in tag: + name = getattr(e, 'name', None) + if name in ['a', 'br']: + name = None # just treat as simple text + s = _force_string(tag, e) + if name: + yield name,_clean(s) + else: + for i in _split_lines(s): + yield None,_clean(i) + + +def _bitlist_simple(tag): + for typ,text in _bitlist(tag): + if typ and not typ in ['em', 'strong', 'code']: + raise ValueError('unexpected tag %r inside %r' % (typ, tag.name)) + yield text + + +def _text(bitlist): + out = '' + for typ,text in bitlist: + if not typ: + out += text + elif typ == 'em': + out += '\\fI%s\\fR' % text + elif typ in ['strong', 'code']: + out += '\\fB%s\\fR' % text + else: + raise ValueError('unexpected tag %r inside %r' % (typ, tag.name)) + out = out.strip() + out = re.sub(re.compile(r'^\s+', re.M), '', out) + return out + + +def text(tag): + w.write(_text(_bitlist(tag))) + + +# This is needed because .BI (and .BR, .RB, etc) are weird little state +# machines that alternate between two fonts. So if someone says something +# like foochickenwickendicken we have to convert that to +# .BI foo chickenwicken dicken +def _boldline(l): + out = [''] + last_bold = False + for typ,text in l: + nonzero = not not typ + if nonzero != last_bold: + last_bold = not last_bold + out.append('') + out[-1] += re.sub(r'\s+', ' ', text) + macro('.BI', *out) + + +def do_definition(tag): + w.end_para() + macro('.TP') + w.started = True + split = 0 + pre = [] + post = [] + for typ,text in _bitlist(tag): + if split: + post.append((typ,text)) + elif text.lstrip().startswith(': '): + split = 1 + post.append((typ,text.lstrip()[2:].lstrip())) + else: + pre.append((typ,text)) + _boldline(pre) + w.write(_text(post)) + + +def do_list(tag): + for i in tag: + name = getattr(i, 'name', '').lower() + if not name and not str(i).strip(): + pass + elif name != 'li': + raise ValueError('only
  • is allowed inside