aboutsummaryrefslogtreecommitdiff
path: root/man/make_man_page.py
blob: 4dadd51744a111f865c05df7be3decb03a484446 (plain)
  1. #!/usr/bin/env python
  2. # Creates a man page from a C file.
  3. # Comments beginning with `/**` are treated as Groff man.
  4. # Non-blank lines immediately following a man page comment are treated
  5. # as function signatures or examples and parsed into .Ft, .Fo, .Fa, .Fc. The
  6. # immediately preceding man documentation chunk is printed after the example
  7. # as a comment on it.
  8. # That's about it!
  9. import sys, re, os
  10. from datetime import date
  11. comment_start_re = re.compile('^\/\*\* ?')
  12. comment_delim_re = re.compile('^[/ ]\** ?')
  13. comment_end_re = re.compile('^ \**\/')
  14. function_re = re.compile('^ *(?:CMARK_EXPORT\s+)?(?P<type>(?:const\s+)?\w+(?:\s*[*])?)\s*(?P<name>\w+)\s*\((?P<args>[^)]*)\)')
  15. blank_re = re.compile('^\s*$')
  16. macro_re = re.compile('CMARK_EXPORT *')
  17. typedef_start_re = re.compile('typedef.*{$')
  18. typedef_end_re = re.compile('}')
  19. typedef = False
  20. mdlines = []
  21. chunk = []
  22. sig = []
  23. if len(sys.argv) > 1:
  24. sourcefile = sys.argv[1]
  25. else:
  26. print("Usage: make_man_page.py sourcefile")
  27. exit(1)
  28. with open(sourcefile, 'r') as cmarkh:
  29. state = 'default'
  30. for line in cmarkh:
  31. # state transition
  32. oldstate = state
  33. if comment_start_re.match(line):
  34. state = 'man'
  35. elif comment_end_re.match(line) and state == 'man':
  36. continue
  37. elif comment_delim_re.match(line) and state == 'man':
  38. state = 'man'
  39. elif not typedef and blank_re.match(line):
  40. state = 'default'
  41. elif typedef and typedef_end_re.match(line):
  42. typedef = False
  43. elif state == 'man':
  44. state = 'signature'
  45. typedef = typedef_start_re.match(line)
  46. # handle line
  47. if state == 'man':
  48. chunk.append(re.sub(comment_delim_re, '', line))
  49. elif state == 'signature':
  50. ln = re.sub(macro_re, '', line)
  51. if typedef or not re.match(blank_re, ln):
  52. sig.append(ln)
  53. elif oldstate == 'signature' and state != 'signature':
  54. if len(mdlines) > 0 and mdlines[-1] != '\n':
  55. mdlines.append('\n')
  56. rawsig = ''.join(sig)
  57. m = function_re.match(rawsig)
  58. if m:
  59. mdlines.append('.Ft ' + m.group('type') + '\n')
  60. mdlines.append('.Fo ' + m.group('name') + '\n')
  61. for argument in re.split('/s*,/s*', m.group('args')):
  62. mdlines.append('.Fa ' + argument + '\n')
  63. mdlines.append('.Fc\n')
  64. else:
  65. mdlines.append('.Bd -literal\n')
  66. mdlines += sig
  67. mdlines.append('.Ed\n')
  68. if len(mdlines) > 0 and mdlines[-1] != '\n':
  69. mdlines.append('\n')
  70. mdlines += chunk
  71. chunk = []
  72. sig = []
  73. elif oldstate == 'man' and state != 'signature':
  74. if len(mdlines) > 0 and mdlines[-1] != '\n':
  75. mdlines.append('\n')
  76. mdlines += chunk # add man chunk
  77. chunk = []
  78. mdlines.append('\n')
  79. sys.stdout.write('.Dd ' + date.today().strftime('%B %d, %Y') + '\n')
  80. sys.stdout.write('.Dt ' + os.path.basename(sourcefile).replace('.h','') + ' 3\n')
  81. sys.stdout.write(''.join(mdlines))