aboutsummaryrefslogtreecommitdiff
path: root/man/make_man_page.py
blob: 01a1c6f74c29b2976320f8123078aa641f06cb27 (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\fR, and ''this'' to \fBthis\fR.
  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
  11. from datetime import date
  12. comment_start_re = re.compile('^\/\*\* ?')
  13. comment_delim_re = re.compile('^[/ ]\** ?')
  14. comment_end_re = re.compile('^ \**\/')
  15. function_re = re.compile('^ *(?:CMARK_EXPORT\s+)?(?P<type>(?:const\s+)?\w+(?:\s*[*])?)\s*(?P<name>\w+)\s*\((?P<args>[^)]*)\)')
  16. blank_re = re.compile('^\s*$')
  17. macro_re = re.compile('CMARK_EXPORT *')
  18. typedef_start_re = re.compile('typedef.*{$')
  19. typedef_end_re = re.compile('}')
  20. single_quote_re = re.compile("(?<!\w)'([^']+)'(?!\w)")
  21. double_quote_re = re.compile("(?<!\w)''([^']+)''(?!\w)")
  22. def handle_quotes(s):
  23. return re.sub(double_quote_re, '\\\\fB\g<1>\\\\fR', re.sub(single_quote_re, '\\\\fI\g<1>\\\\fR', s))
  24. typedef = False
  25. mdlines = []
  26. chunk = []
  27. sig = []
  28. if len(sys.argv) > 1:
  29. sourcefile = sys.argv[1]
  30. else:
  31. print("Usage: make_man_page.py sourcefile")
  32. exit(1)
  33. with open(sourcefile, 'r') as cmarkh:
  34. state = 'default'
  35. for line in cmarkh:
  36. # state transition
  37. oldstate = state
  38. if comment_start_re.match(line):
  39. state = 'man'
  40. elif comment_end_re.match(line) and state == 'man':
  41. continue
  42. elif comment_delim_re.match(line) and state == 'man':
  43. state = 'man'
  44. elif not typedef and blank_re.match(line):
  45. state = 'default'
  46. elif typedef and typedef_end_re.match(line):
  47. typedef = False
  48. elif state == 'man':
  49. state = 'signature'
  50. typedef = typedef_start_re.match(line)
  51. # handle line
  52. if state == 'man':
  53. chunk.append(handle_quotes(re.sub(comment_delim_re, '', line)))
  54. elif state == 'signature':
  55. ln = re.sub(macro_re, '', line)
  56. if typedef or not re.match(blank_re, ln):
  57. sig.append(ln)
  58. elif oldstate == 'signature' and state != 'signature':
  59. if len(mdlines) > 0 and mdlines[-1] != '\n':
  60. mdlines.append('\n')
  61. rawsig = ''.join(sig)
  62. m = function_re.match(rawsig)
  63. if m:
  64. mdlines.append('\\fI' + m.group('type') + '\\fR' + ' ')
  65. mdlines.append('\\fB' + m.group('name') + '\\fR' + '(')
  66. first = True
  67. for argument in re.split(',', m.group('args')):
  68. if not first:
  69. mdlines.append(', ')
  70. first = False
  71. mdlines.append('\\fI' + argument.strip() + '\\fR')
  72. mdlines.append(')\n')
  73. else:
  74. mdlines.append('.nf\n\\f[C]\n.RS 0n\n')
  75. mdlines += sig
  76. mdlines.append('.RE\n\\f[]\n.fi\n')
  77. if len(mdlines) > 0 and mdlines[-1] != '\n':
  78. mdlines.append('\n')
  79. mdlines.append('.PP\n')
  80. mdlines += chunk
  81. chunk = []
  82. sig = []
  83. elif oldstate == 'man' and state != 'signature':
  84. if len(mdlines) > 0 and mdlines[-1] != '\n':
  85. mdlines.append('\n')
  86. mdlines += chunk # add man chunk
  87. chunk = []
  88. mdlines.append('\n')
  89. sys.stdout.write('.TH ' + os.path.basename(sourcefile).replace('.h','') + ' 3 "' + date.today().strftime('%B %d, %Y') + '" "LOCAL" "Library Functions Manual"\n')
  90. sys.stdout.write(''.join(mdlines))