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