cmark
My personal build of CMark ✏️
make_man_page.py (4736B)
1 #!/usr/bin/env python 2 3 # Creates a man page from a C file. 4 5 # first argument if present is path to cmark dynamic library 6 7 # Comments beginning with `/**` are treated as Groff man, except that 8 # 'this' is converted to \fIthis\f[], and ''this'' to \fBthis\f[]. 9 10 # Non-blank lines immediately following a man page comment are treated 11 # as function signatures or examples and parsed into .Ft, .Fo, .Fa, .Fc. The 12 # immediately preceding man documentation chunk is printed after the example 13 # as a comment on it. 14 15 # That's about it! 16 17 import sys, re, os, platform 18 from datetime import date 19 from ctypes import CDLL, c_char_p, c_long, c_void_p 20 21 sysname = platform.system() 22 23 if sysname == 'Darwin': 24 cmark = CDLL("build/src/libcmark.dylib") 25 else: 26 cmark = CDLL("build/src/libcmark.so") 27 28 parse_document = cmark.cmark_parse_document 29 parse_document.restype = c_void_p 30 parse_document.argtypes = [c_char_p, c_long] 31 32 render_man = cmark.cmark_render_man 33 render_man.restype = c_char_p 34 render_man.argtypes = [c_void_p, c_long, c_long] 35 36 def md2man(text): 37 if sys.version_info >= (3,0): 38 textbytes = text.encode('utf-8') 39 textlen = len(textbytes) 40 return render_man(parse_document(textbytes, textlen), 0, 65).decode('utf-8') 41 else: 42 textbytes = text 43 textlen = len(text) 44 return render_man(parse_document(textbytes, textlen), 0, 72) 45 46 comment_start_re = re.compile('^\/\*\* ?') 47 comment_delim_re = re.compile('^[/ ]\** ?') 48 comment_end_re = re.compile('^ \**\/') 49 function_re = re.compile('^ *(?:CMARK_EXPORT\s+)?(?P<type>(?:const\s+)?\w+(?:\s*[*])?)\s*(?P<name>\w+)\s*\((?P<args>[^)]*)\)') 50 blank_re = re.compile('^\s*$') 51 macro_re = re.compile('CMARK_EXPORT *') 52 typedef_start_re = re.compile('typedef.*{$') 53 typedef_end_re = re.compile('}') 54 single_quote_re = re.compile("(?<!\w)'([^']+)'(?!\w)") 55 double_quote_re = re.compile("(?<!\w)''([^']+)''(?!\w)") 56 57 def handle_quotes(s): 58 return re.sub(double_quote_re, '**\g<1>**', re.sub(single_quote_re, '*\g<1>*', s)) 59 60 typedef = False 61 mdlines = [] 62 chunk = [] 63 sig = [] 64 65 if len(sys.argv) > 1: 66 sourcefile = sys.argv[1] 67 else: 68 print("Usage: make_man_page.py sourcefile") 69 exit(1) 70 71 with open(sourcefile, 'r') as cmarkh: 72 state = 'default' 73 for line in cmarkh: 74 # state transition 75 oldstate = state 76 if comment_start_re.match(line): 77 state = 'man' 78 elif comment_end_re.match(line) and state == 'man': 79 continue 80 elif comment_delim_re.match(line) and state == 'man': 81 state = 'man' 82 elif not typedef and blank_re.match(line): 83 state = 'default' 84 elif typedef and typedef_end_re.match(line): 85 typedef = False 86 elif typedef_start_re.match(line): 87 typedef = True 88 state = 'signature' 89 elif state == 'man': 90 state = 'signature' 91 92 # handle line 93 if state == 'man': 94 chunk.append(handle_quotes(re.sub(comment_delim_re, '', line))) 95 elif state == 'signature': 96 ln = re.sub(macro_re, '', line) 97 if typedef or not re.match(blank_re, ln): 98 sig.append(ln) 99 elif oldstate == 'signature' and state != 'signature': 100 if len(mdlines) > 0 and mdlines[-1] != '\n': 101 mdlines.append('\n') 102 rawsig = ''.join(sig) 103 m = function_re.match(rawsig) 104 mdlines.append('.PP\n') 105 if m: 106 mdlines.append('\\fI' + m.group('type') + '\\f[]' + ' ') 107 mdlines.append('\\fB' + m.group('name') + '\\f[]' + '(') 108 first = True 109 for argument in re.split(',', m.group('args')): 110 if not first: 111 mdlines.append(', ') 112 first = False 113 mdlines.append('\\fI' + argument.strip() + '\\f[]') 114 mdlines.append(')\n') 115 else: 116 mdlines.append('.nf\n\\fC\n.RS 0n\n') 117 mdlines += sig 118 mdlines.append('.RE\n\\f[]\n.fi\n') 119 if len(mdlines) > 0 and mdlines[-1] != '\n': 120 mdlines.append('\n') 121 mdlines += md2man(''.join(chunk)) 122 mdlines.append('\n') 123 chunk = [] 124 sig = [] 125 elif oldstate == 'man' and state != 'signature': 126 if len(mdlines) > 0 and mdlines[-1] != '\n': 127 mdlines.append('\n') 128 mdlines += md2man(''.join(chunk)) # add man chunk 129 chunk = [] 130 mdlines.append('\n') 131 132 sys.stdout.write('.TH ' + os.path.basename(sourcefile).replace('.h','') + ' 3 "' + date.today().strftime('%B %d, %Y') + '" "LOCAL" "Library Functions Manual"\n') 133 sys.stdout.write(''.join(mdlines))