aboutsummaryrefslogtreecommitdiff
path: root/man/make_man_page.py
blob: a2c8980766815ea2919b166871b93451f9a18198 (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. if sys.version_info >= (3,0):
  27. textbytes = text.encode('utf-8')
  28. textlen = len(textbytes)
  29. else:
  30. textbytes = text
  31. textlen = len(text)
  32. return render_man(parse_document(textbytes, textlen))
  33. comment_start_re = re.compile('^\/\*\* ?')
  34. comment_delim_re = re.compile('^[/ ]\** ?')
  35. comment_end_re = re.compile('^ \**\/')
  36. function_re = re.compile('^ *(?:CMARK_EXPORT\s+)?(?P<type>(?:const\s+)?\w+(?:\s*[*])?)\s*(?P<name>\w+)\s*\((?P<args>[^)]*)\)')
  37. blank_re = re.compile('^\s*$')
  38. macro_re = re.compile('CMARK_EXPORT *')
  39. typedef_start_re = re.compile('typedef.*{$')
  40. typedef_end_re = re.compile('}')
  41. single_quote_re = re.compile("(?<!\w)'([^']+)'(?!\w)")
  42. double_quote_re = re.compile("(?<!\w)''([^']+)''(?!\w)")
  43. def handle_quotes(s):
  44. return re.sub(double_quote_re, '**\g<1>**', re.sub(single_quote_re, '*\g<1>*', s))
  45. typedef = False
  46. mdlines = []
  47. chunk = []
  48. sig = []
  49. if len(sys.argv) > 1:
  50. sourcefile = sys.argv[1]
  51. else:
  52. print("Usage: make_man_page.py sourcefile")
  53. exit(1)
  54. with open(sourcefile, 'r') as cmarkh:
  55. state = 'default'
  56. for line in cmarkh:
  57. # state transition
  58. oldstate = state
  59. if comment_start_re.match(line):
  60. state = 'man'
  61. elif comment_end_re.match(line) and state == 'man':
  62. continue
  63. elif comment_delim_re.match(line) and state == 'man':
  64. state = 'man'
  65. elif not typedef and blank_re.match(line):
  66. state = 'default'
  67. elif typedef and typedef_end_re.match(line):
  68. typedef = False
  69. elif state == 'man':
  70. state = 'signature'
  71. typedef = typedef_start_re.match(line)
  72. # handle line
  73. if state == 'man':
  74. chunk.append(handle_quotes(re.sub(comment_delim_re, '', line)))
  75. elif state == 'signature':
  76. ln = re.sub(macro_re, '', line)
  77. if typedef or not re.match(blank_re, ln):
  78. sig.append(ln)
  79. elif oldstate == 'signature' and state != 'signature':
  80. if len(mdlines) > 0 and mdlines[-1] != '\n':
  81. mdlines.append('\n')
  82. rawsig = ''.join(sig)
  83. m = function_re.match(rawsig)
  84. mdlines.append('.PP\n')
  85. if m:
  86. mdlines.append('\\fI' + m.group('type') + '\\f[]' + ' ')
  87. mdlines.append('\\fB' + m.group('name') + '\\f[]' + '(')
  88. first = True
  89. for argument in re.split(',', m.group('args')):
  90. if not first:
  91. mdlines.append(', ')
  92. first = False
  93. mdlines.append('\\fI' + argument.strip() + '\\f[]')
  94. mdlines.append(')\n')
  95. else:
  96. mdlines.append('.nf\n\\fC\n.RS 0n\n')
  97. mdlines += sig
  98. mdlines.append('.RE\n\\f[]\n.fi\n')
  99. if len(mdlines) > 0 and mdlines[-1] != '\n':
  100. mdlines.append('\n')
  101. mdlines += md2man(''.join(chunk))
  102. mdlines.append('\n')
  103. chunk = []
  104. sig = []
  105. elif oldstate == 'man' and state != 'signature':
  106. if len(mdlines) > 0 and mdlines[-1] != '\n':
  107. mdlines.append('\n')
  108. mdlines += md2man(''.join(chunk)) # add man chunk
  109. chunk = []
  110. mdlines.append('\n')
  111. sys.stdout.write('.TH ' + os.path.basename(sourcefile).replace('.h','') + ' 3 "' + date.today().strftime('%B %d, %Y') + '" "LOCAL" "Library Functions Manual"\n')
  112. sys.stdout.write(''.join(mdlines))