makerst.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. import codecs
  4. import sys
  5. import xml.etree.ElementTree as ET
  6. input_list = []
  7. for arg in sys.argv[1:]:
  8. input_list.append(arg)
  9. if len(input_list) < 1:
  10. print 'usage: makerst.py <classes.xml>'
  11. sys.exit(0)
  12. def validate_tag(elem, tag):
  13. if elem.tag != tag:
  14. print "Tag mismatch, expected '" + tag + "', got " + elem.tag
  15. sys.exit(255)
  16. class_names = []
  17. classes = {}
  18. def ul_string(str,ul):
  19. str+="\n"
  20. for i in range(len(str)-1):
  21. str+=ul
  22. str+="\n"
  23. return str
  24. def make_class_list(class_list, columns):
  25. f = codecs.open('class_list.rst', 'wb', 'utf-8')
  26. prev = 0
  27. col_max = len(class_list) / columns + 1
  28. print ('col max is ', col_max)
  29. col_count = 0
  30. row_count = 0
  31. last_initial = ''
  32. fit_columns = []
  33. for n in range(0, columns):
  34. fit_columns += [[]]
  35. indexers = []
  36. last_initial = ''
  37. idx = 0
  38. for n in class_list:
  39. col = idx / col_max
  40. if col >= columns:
  41. col = columns - 1
  42. fit_columns[col] += [n]
  43. idx += 1
  44. if n[:1] != last_initial:
  45. indexers += [n]
  46. last_initial = n[:1]
  47. row_max = 0
  48. f.write("\n")
  49. for n in range(0, columns):
  50. if len(fit_columns[n]) > row_max:
  51. row_max = len(fit_columns[n])
  52. f.write("| ")
  53. for n in range(0, columns):
  54. f.write(" | |")
  55. f.write("\n")
  56. f.write("+")
  57. for n in range(0, columns):
  58. f.write("--+-------+")
  59. f.write("\n")
  60. for r in range(0, row_max):
  61. s = '+ '
  62. for c in range(0, columns):
  63. if r >= len(fit_columns[c]):
  64. continue
  65. classname = fit_columns[c][r]
  66. initial = classname[0]
  67. if classname in indexers:
  68. s += '**' + initial + '** | '
  69. else:
  70. s += ' | '
  71. s += '[' + classname + '](class_'+ classname.lower()+') | '
  72. s += '\n'
  73. f.write(s)
  74. for n in range(0, columns):
  75. f.write("--+-------+")
  76. f.write("\n")
  77. def rstize_text(text,cclass):
  78. # Linebreak + tabs in the XML should become two line breaks unless in a "codeblock"
  79. pos = 0
  80. while True:
  81. pos = text.find('\n', pos)
  82. if pos == -1:
  83. break
  84. pre_text = text[:pos]
  85. while text[pos+1] == '\t':
  86. pos += 1
  87. post_text = text[pos+1:]
  88. # Handle codeblocks
  89. if post_text.startswith("[codeblock]"):
  90. end_pos = post_text.find("[/codeblock]")
  91. if end_pos == -1:
  92. sys.exit("ERROR! [codeblock] without a closing tag!")
  93. code_text = post_text[len("[codeblock]"):end_pos]
  94. post_text = post_text[end_pos:]
  95. # Remove extraneous tabs
  96. code_pos = 0
  97. while True:
  98. code_pos = code_text.find('\n', code_pos)
  99. if code_pos == -1:
  100. break
  101. to_skip = 0
  102. while code_pos+to_skip+1 < len(code_text) and code_text[code_pos+to_skip+1] == '\t':
  103. to_skip += 1
  104. if len(code_text[code_pos+to_skip+1:])==0:
  105. code_text = code_text[:code_pos] + "\n"
  106. code_pos += 1
  107. else:
  108. code_text = code_text[:code_pos] + "\n " + code_text[code_pos+to_skip+1:]
  109. code_pos += 5 - to_skip
  110. text = pre_text + "\n[codeblock]" + code_text + post_text
  111. pos += len("\n[codeblock]" + code_text)
  112. # Handle normal text
  113. else:
  114. text = pre_text + "\n\n" + post_text
  115. pos += 2
  116. # Escape * character to avoid interpreting it as emphasis
  117. pos = 0
  118. while True:
  119. pos = text.find('*', pos)
  120. if pos == -1:
  121. break
  122. text = text[:pos] + "\*" + text[pos + 1:]
  123. pos += 2
  124. # Escape _ character at the end of a word to avoid interpreting it as an inline hyperlink
  125. pos = 0
  126. while True:
  127. pos = text.find('_', pos)
  128. if pos == -1:
  129. break
  130. if not text[pos + 1].isalnum(): # don't escape within a snake_case word
  131. text = text[:pos] + "\_" + text[pos + 1:]
  132. pos += 2
  133. else:
  134. pos += 1
  135. # Handle [tags]
  136. pos = 0
  137. while True:
  138. pos = text.find('[', pos)
  139. if pos == -1:
  140. break
  141. endq_pos = text.find(']', pos + 1)
  142. if endq_pos == -1:
  143. break
  144. pre_text = text[:pos]
  145. post_text = text[endq_pos + 1:]
  146. tag_text = text[pos + 1:endq_pos]
  147. if tag_text in class_names:
  148. tag_text = make_type(tag_text)
  149. else: # command
  150. cmd = tag_text
  151. space_pos = tag_text.find(' ')
  152. if cmd.find('html') == 0:
  153. cmd = tag_text[:space_pos]
  154. param = tag_text[space_pos + 1:]
  155. tag_text = param
  156. elif cmd.find('method') == 0:
  157. cmd = tag_text[:space_pos]
  158. param = tag_text[space_pos + 1:]
  159. if param.find('.') != -1:
  160. (class_param, method_param) = param.split('.')
  161. tag_text = ':ref:`'+class_param+'.'+method_param+'<class_' + class_param + '_' + method_param + '>`'
  162. else:
  163. tag_text = ':ref:`' + param + '<class_' + cclass +"_"+ param + '>`'
  164. elif cmd.find('image=') == 0:
  165. tag_text = "" #'![](' + cmd[6:] + ')'
  166. elif cmd.find('url=') == 0:
  167. tag_text = ':ref:`' + cmd[4:] + '<'+cmd[4:]+">`"
  168. elif cmd == '/url':
  169. tag_text = ')'
  170. elif cmd == 'center':
  171. tag_text = ''
  172. elif cmd == '/center':
  173. tag_text = ''
  174. elif cmd == 'codeblock':
  175. tag_text = '\n::\n'
  176. elif cmd == '/codeblock':
  177. tag_text = ''
  178. # Strip newline if the tag was alone on one
  179. if pre_text[-1] == '\n':
  180. pre_text = pre_text[:-1]
  181. elif cmd == 'br':
  182. # Make a new paragraph instead of a linebreak, rst is not so linebreak friendly
  183. tag_text = '\n\n'
  184. # Strip potential leading spaces
  185. while post_text[0] == ' ':
  186. post_text = post_text[1:]
  187. elif cmd == 'i' or cmd == '/i':
  188. tag_text = '*'
  189. elif cmd == 'b' or cmd == '/b':
  190. tag_text = '**'
  191. elif cmd == 'u' or cmd == '/u':
  192. tag_text = ''
  193. elif cmd == 'code' or cmd == '/code':
  194. tag_text = '``'
  195. else:
  196. tag_text = ':ref:`' + tag_text + '<class_'+tag_text.lower()+'>`'
  197. text = pre_text + tag_text + post_text
  198. pos = len(pre_text) + len(tag_text)
  199. # tnode = ET.SubElement(parent,"div")
  200. # tnode.text=text
  201. return text
  202. def make_type(t):
  203. global class_names
  204. if t in class_names:
  205. return ':ref:`'+t+'<class_' + t.lower()+'>`'
  206. return t
  207. def make_method(
  208. f,
  209. name,
  210. m,
  211. declare,
  212. cname,
  213. event=False,
  214. pp=None
  215. ):
  216. if (declare or pp==None):
  217. t = '- '
  218. else:
  219. t = ""
  220. ret_type = 'void'
  221. args = list(m)
  222. mdata = {}
  223. mdata['argidx'] = []
  224. for a in args:
  225. if a.tag == 'return':
  226. idx = -1
  227. elif a.tag == 'argument':
  228. idx = int(a.attrib['index'])
  229. else:
  230. continue
  231. mdata['argidx'].append(idx)
  232. mdata[idx] = a
  233. if not event:
  234. if -1 in mdata['argidx']:
  235. t += make_type(mdata[-1].attrib['type'])
  236. else:
  237. t += 'void'
  238. t += ' '
  239. if declare or pp==None:
  240. # span.attrib["class"]="funcdecl"
  241. # a=ET.SubElement(span,"a")
  242. # a.attrib["name"]=name+"_"+m.attrib["name"]
  243. # a.text=name+"::"+m.attrib["name"]
  244. s = ' **'+m.attrib['name']+'** '
  245. else:
  246. s = ':ref:`'+ m.attrib['name']+'<class_' + cname+"_"+m.attrib['name'] + '>` '
  247. s += ' **(**'
  248. argfound = False
  249. for a in mdata['argidx']:
  250. arg = mdata[a]
  251. if a < 0:
  252. continue
  253. if a > 0:
  254. s += ', '
  255. else:
  256. s += ' '
  257. s += make_type(arg.attrib['type'])
  258. if 'name' in arg.attrib:
  259. s += ' ' + arg.attrib['name']
  260. else:
  261. s += ' arg' + str(a)
  262. if 'default' in arg.attrib:
  263. s += '=' + arg.attrib['default']
  264. argfound = True
  265. if argfound:
  266. s += ' '
  267. s += ' **)**'
  268. if 'qualifiers' in m.attrib:
  269. s += ' ' + m.attrib['qualifiers']
  270. # f.write(s)
  271. if (not declare):
  272. if (pp!=None):
  273. pp.append( (t,s) )
  274. else:
  275. f.write("- "+t+" "+s+"\n")
  276. else:
  277. f.write(t+s+"\n")
  278. def make_heading(title, underline):
  279. return title + '\n' + underline*len(title) + "\n\n"
  280. def make_rst_class(node):
  281. name = node.attrib['name']
  282. f = codecs.open("class_"+name.lower() + '.rst', 'wb', 'utf-8')
  283. # Warn contributors not to edit this file directly
  284. f.write(".. Generated automatically by doc/tools/makerst.py in Godot's source tree.\n")
  285. f.write(".. DO NOT EDIT THIS FILE, but the doc/base/classes.xml source instead.\n\n")
  286. f.write(".. _class_"+name+":\n\n")
  287. f.write(make_heading(name, '='))
  288. if 'inherits' in node.attrib:
  289. inh = node.attrib['inherits'].strip()
  290. # whle inh in classes[cn]
  291. f.write('**Inherits:** ')
  292. first=True
  293. while(inh in classes):
  294. if (not first):
  295. f.write(" **<** ")
  296. else:
  297. first=False
  298. f.write(make_type(inh))
  299. inode = classes[inh]
  300. if ('inherits' in inode.attrib):
  301. inh=inode.attrib['inherits'].strip()
  302. else:
  303. inh=None
  304. f.write("\n\n")
  305. inherited=[]
  306. for cn in classes:
  307. c=classes[cn]
  308. if 'inherits' in c.attrib:
  309. if (c.attrib['inherits'].strip()==name):
  310. inherited.append(c.attrib['name'])
  311. if (len(inherited)):
  312. f.write('**Inherited By:** ')
  313. for i in range(len(inherited)):
  314. if (i>0):
  315. f.write(", ")
  316. f.write(make_type(inherited[i]))
  317. f.write("\n\n")
  318. if 'category' in node.attrib:
  319. f.write('**Category:** ' + node.attrib['category'].strip() + "\n\n")
  320. f.write(make_heading('Brief Description', '-'))
  321. briefd = node.find('brief_description')
  322. if briefd != None:
  323. f.write(rstize_text(briefd.text.strip(),name) + "\n\n")
  324. methods = node.find('methods')
  325. if methods != None and len(list(methods)) > 0:
  326. f.write(make_heading('Member Functions', '-'))
  327. ml=[]
  328. for m in list(methods):
  329. make_method(f, node.attrib['name'], m, False,name,False,ml)
  330. longest_t = 0
  331. longest_s = 0
  332. for s in ml:
  333. sl = len(s[0])
  334. if (sl>longest_s):
  335. longest_s=sl
  336. tl = len(s[1])
  337. if (tl>longest_t):
  338. longest_t=tl
  339. sep="+"
  340. for i in range(longest_s+2):
  341. sep+="-"
  342. sep+="+"
  343. for i in range(longest_t+2):
  344. sep+="-"
  345. sep+="+\n"
  346. f.write(sep)
  347. for s in ml:
  348. rt = s[0]
  349. while( len(rt) < longest_s ):
  350. rt+=" "
  351. st = s[1]
  352. while( len(st) < longest_t ):
  353. st+=" "
  354. f.write("| "+rt+" | "+st+" |\n")
  355. f.write(sep)
  356. f.write('\n')
  357. events = node.find('signals')
  358. if events != None and len(list(events)) > 0:
  359. f.write(make_heading('Signals', '-'))
  360. for m in list(events):
  361. make_method(f, node.attrib['name'], m, True,name, True)
  362. f.write('\n')
  363. members = node.find('members')
  364. if members != None and len(list(members)) > 0:
  365. f.write(make_heading('Member Variables', '-'))
  366. for c in list(members):
  367. s = '- '
  368. s += make_type(c.attrib['type']) + ' '
  369. s += '**' + c.attrib['name'] + '**'
  370. if c.text.strip() != '':
  371. s += ' - ' + c.text.strip()
  372. f.write(s + '\n')
  373. f.write('\n')
  374. constants = node.find('constants')
  375. if constants != None and len(list(constants)) > 0:
  376. f.write(make_heading('Numeric Constants', '-'))
  377. for c in list(constants):
  378. s = '- '
  379. s += '**' + c.attrib['name'] + '**'
  380. if 'value' in c.attrib:
  381. s += ' = **' + c.attrib['value'] + '**'
  382. if c.text.strip() != '':
  383. s += ' --- ' + rstize_text(c.text.strip(),name)
  384. f.write(s + '\n')
  385. f.write('\n')
  386. descr = node.find('description')
  387. if descr != None and descr.text.strip() != '':
  388. f.write(make_heading('Description', '-'))
  389. f.write(rstize_text(descr.text.strip(),name) + "\n\n")
  390. methods = node.find('methods')
  391. if methods != None and len(list(methods)) > 0:
  392. f.write(make_heading('Member Function Description', '-'))
  393. for m in list(methods):
  394. f.write(".. _class_"+name+"_"+m.attrib['name']+":\n\n")
  395. # f.write(ul_string(m.attrib['name'],"^"))
  396. #f.write('\n<a name="'+m.attrib['name']+'">' + m.attrib['name'] + '</a>\n------\n')
  397. make_method(f, node.attrib['name'], m, True,name)
  398. f.write('\n')
  399. d = m.find('description')
  400. if d == None or d.text.strip() == '':
  401. continue
  402. f.write(rstize_text(d.text.strip(),name))
  403. f.write("\n\n")
  404. f.write('\n')
  405. for file in input_list:
  406. tree = ET.parse(file)
  407. doc = tree.getroot()
  408. if 'version' not in doc.attrib:
  409. print "Version missing from 'doc'"
  410. sys.exit(255)
  411. version = doc.attrib['version']
  412. for c in list(doc):
  413. if c.attrib['name'] in class_names:
  414. continue
  415. class_names.append(c.attrib['name'])
  416. classes[c.attrib['name']] = c
  417. class_names.sort()
  418. #Don't make class list for Sphinx, :toctree: handles it
  419. #make_class_list(class_names, 2)
  420. for cn in class_names:
  421. c = classes[cn]
  422. make_rst_class(c)