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