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))