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