| #!/usr/bin/env python |
| |
| # Creates a man page from a C file. |
| |
| # first argument if present is path to cmark dynamic library |
| |
| # Comments beginning with `/**` are treated as Groff man, except that |
| # 'this' is converted to \fIthis\f[], and ''this'' to \fBthis\f[]. |
| |
| # Non-blank lines immediately following a man page comment are treated |
| # as function signatures or examples and parsed into .Ft, .Fo, .Fa, .Fc. The |
| # immediately preceding man documentation chunk is printed after the example |
| # as a comment on it. |
| |
| # That's about it! |
| |
| import sys, re, os, platform |
| from datetime import date |
| from ctypes import CDLL, c_char_p, c_long, c_void_p |
| |
| sysname = platform.system() |
| |
| if sysname == 'Darwin': |
| cmark = CDLL("build/src/libcmark.dylib") |
| else: |
| cmark = CDLL("build/src/libcmark.so") |
| |
| parse_document = cmark.cmark_parse_document |
| parse_document.restype = c_void_p |
| parse_document.argtypes = [c_char_p, c_long] |
| |
| render_man = cmark.cmark_render_man |
| render_man.restype = c_char_p |
| render_man.argtypes = [c_void_p, c_long, c_long] |
| |
| def md2man(text): |
| if sys.version_info >= (3,0): |
| textbytes = text.encode('utf-8') |
| textlen = len(textbytes) |
| return render_man(parse_document(textbytes, textlen), 0, 65).decode('utf-8') |
| else: |
| textbytes = text |
| textlen = len(text) |
| return render_man(parse_document(textbytes, textlen), 0, 72) |
| |
| comment_start_re = re.compile('^\/\*\* ?') |
| comment_delim_re = re.compile('^[/ ]\** ?') |
| comment_end_re = re.compile('^ \**\/') |
| function_re = re.compile('^ *(?:CMARK_EXPORT\s+)?(?P<type>(?:const\s+)?\w+(?:\s*[*])?)\s*(?P<name>\w+)\s*\((?P<args>[^)]*)\)') |
| blank_re = re.compile('^\s*$') |
| macro_re = re.compile('CMARK_EXPORT *') |
| typedef_start_re = re.compile('typedef.*{$') |
| typedef_end_re = re.compile('}') |
| single_quote_re = re.compile("(?<!\w)'([^']+)'(?!\w)") |
| double_quote_re = re.compile("(?<!\w)''([^']+)''(?!\w)") |
| |
| def handle_quotes(s): |
| return re.sub(double_quote_re, '**\g<1>**', re.sub(single_quote_re, '*\g<1>*', s)) |
| |
| typedef = False |
| mdlines = [] |
| chunk = [] |
| sig = [] |
| |
| if len(sys.argv) > 1: |
| sourcefile = sys.argv[1] |
| else: |
| print("Usage: make_man_page.py sourcefile") |
| exit(1) |
| |
| with open(sourcefile, 'r') as cmarkh: |
| state = 'default' |
| for line in cmarkh: |
| # state transition |
| oldstate = state |
| if comment_start_re.match(line): |
| state = 'man' |
| elif comment_end_re.match(line) and state == 'man': |
| continue |
| elif comment_delim_re.match(line) and state == 'man': |
| state = 'man' |
| elif not typedef and blank_re.match(line): |
| state = 'default' |
| elif typedef and typedef_end_re.match(line): |
| typedef = False |
| elif state == 'man': |
| state = 'signature' |
| typedef = typedef_start_re.match(line) |
| |
| # handle line |
| if state == 'man': |
| chunk.append(handle_quotes(re.sub(comment_delim_re, '', line))) |
| elif state == 'signature': |
| ln = re.sub(macro_re, '', line) |
| if typedef or not re.match(blank_re, ln): |
| sig.append(ln) |
| elif oldstate == 'signature' and state != 'signature': |
| if len(mdlines) > 0 and mdlines[-1] != '\n': |
| mdlines.append('\n') |
| rawsig = ''.join(sig) |
| m = function_re.match(rawsig) |
| mdlines.append('.PP\n') |
| if m: |
| mdlines.append('\\fI' + m.group('type') + '\\f[]' + ' ') |
| mdlines.append('\\fB' + m.group('name') + '\\f[]' + '(') |
| first = True |
| for argument in re.split(',', m.group('args')): |
| if not first: |
| mdlines.append(', ') |
| first = False |
| mdlines.append('\\fI' + argument.strip() + '\\f[]') |
| mdlines.append(')\n') |
| else: |
| mdlines.append('.nf\n\\fC\n.RS 0n\n') |
| mdlines += sig |
| mdlines.append('.RE\n\\f[]\n.fi\n') |
| if len(mdlines) > 0 and mdlines[-1] != '\n': |
| mdlines.append('\n') |
| mdlines += md2man(''.join(chunk)) |
| mdlines.append('\n') |
| chunk = [] |
| sig = [] |
| elif oldstate == 'man' and state != 'signature': |
| if len(mdlines) > 0 and mdlines[-1] != '\n': |
| mdlines.append('\n') |
| mdlines += md2man(''.join(chunk)) # add man chunk |
| chunk = [] |
| mdlines.append('\n') |
| |
| sys.stdout.write('.TH ' + os.path.basename(sourcefile).replace('.h','') + ' 3 "' + date.today().strftime('%B %d, %Y') + '" "LOCAL" "Library Functions Manual"\n') |
| sys.stdout.write(''.join(mdlines)) |