1 """
2 Generation of system docs by introspection and combination with static
3 docs.
4 """
5
6
7
8
9
10
11
12 from __future__ import print_function
13
14 import locale
15 import inspect
16 import re
17 import sys
18 import textwrap
19 import traceback
20
21 import pkg_resources
22
23 from gavo import base
24 from gavo import rscdef
25 from gavo import rscdesc
26 from gavo import utils
27 from gavo.base import structure
28 from gavo.utils import Arg, exposedFunction, makeCLIParser
32 return re.sub("(?m)^", indent, stuff)
33
36 """replaces all byte strings within stringList with unicode objects.
37
38 Everything not already a unicode object is, for now, assumed to be
39 iso-8859-1, since that is what code is encoded in when it's not ASCII.
40 """
41 for ind, s in enumerate(stringList):
42 if not isinstance(s, unicode):
43 stringList[ind] = s.decode("iso-8859-1", "replace")
44
47 """is a collection of convenience methods for generation of RST.
48 """
49 level1Underliner = "'"
50 level2Underliner = "."
51
54
56 """adds an empty line unless the last line already is empty.
57 """
58 if self.content and self.content[-1]!="\n":
59 self.content.append("\n")
60
61 - def addHead(self, head, underliner):
66
69
72
74 """deletes something that looks like a headline if it's the last
75 thing in our content list.
76 """
77 try:
78
79
80 if self.content[-1]=="\n" and len(set(self.content[-2]))==2:
81 self.content[-3:] = []
82 except IndexError:
83 pass
84
86 if content is None:
87 return
88 initialIndent = bullet+" "
89 self.content.append(textwrap.fill(content, initial_indent=initialIndent,
90 subsequent_indent=" "*len(initialIndent))+"\n")
91
97
99 """adds a definition list-style item .
100
101 defBody is re-indented with two spaces, defHead is assumed to only
102 contain a single line.
103 """
104 self.content.append(defHead+"\n")
105 self.content.append(utils.fixIndentation(defBody, " ",
106 governingLine=2)+"\n")
107
109 """adds stuff to the document, making sure it's not glued to any
110 previous material and removing whitespace as necessary for docstrings.
111 """
112 self.makeSpace()
113 self.content.append(utils.fixIndentation(stuff, "", governingLine=2)+"\n")
114
117
120 """is a sentinel left in the proto documentation, to be replaced by
121 docs on the element parents found while processing.
122 """
124 self.elementName = elementName
125
128 """a dict keeping track of what elements have been processed and
129 which children they had.
130
131 From this information, it can later fill out the ParentPlaceholders
132 left in the proto reference doc.
133
134 This also keeps track of what macros can be used where.
135 """
139
141 return "May occur in %s.\n"%", ".join("`Element %s`_"%name
142 for name in parents)
143
145 parentDict = {}
146 for parent, children in self.iteritems():
147 for child in children:
148 parentDict.setdefault(child, []).append(parent)
149
150 for index, item in enumerate(protoDoc):
151 if isinstance(item, ParentPlaceholder):
152 if item.elementName in parentDict:
153 protoDoc[index] = self._makeDoc(parentDict[item.elementName])
154 else:
155 protoDoc[index] = ""
156
159 return getattr(klass, "docName_", klass.name_)
160
163 """A class encapsulating generation of documentation from structs.
164 """
165
167 self.docParts = []
168 self.docStructure = docStructure
169 self.visitedClasses = set()
170
172 for att in sorted((a for a in klass.attrSeq
173 if condition(a)),
174 key=lambda att: att.name_):
175 yield att
176
178 return self._iterMatchingAtts(klass, lambda a: isinstance(a, base))
179
181 return self._iterMatchingAtts(klass, lambda a: not isinstance(a, base))
182
183 _noDefaultValues = set([base.Undefined, base.Computed])
189
205
207 name = getDocName(klass)
208 if name in self.docStructure:
209 return
210 self.visitedClasses.add(klass)
211 content = RSTFragment()
212 content.addHead1("Element %s"%name)
213 if klass.__doc__:
214 content.addNormalizedPara(klass.__doc__)
215 else:
216 content.addNormalizedPara("NOT DOCUMENTED")
217
218 content.addRaw(ParentPlaceholder(name))
219
220 content.addHead2("Atomic Children")
221 for att in self._iterAttsOfBase(klass, base.AtomicAttribute):
222 content.addULItem(att.makeUserDoc())
223 content.delEmptySection()
224
225 children = []
226 content.addHead2("Structure Children")
227 for att in self._iterAttsOfBase(klass, base.StructAttribute):
228 try:
229 content.addULItem(att.makeUserDoc())
230 if isinstance(getattr(att, "childFactory", None), structure.StructType):
231 children.append(getDocName(att.childFactory))
232 if att.childFactory not in self.visitedClasses:
233 self.addDocsFrom(att.childFactory, docStructure)
234 except:
235 sys.stderr.write("While gendoccing %s in %s:\n"%(
236 att.name_, name))
237 traceback.print_exc()
238 self.docStructure.setdefault(name, []).extend(children)
239 content.delEmptySection()
240
241 content.addHead2("Other Children")
242 for att in self._iterAttsNotOfBase(klass,
243 (base.AtomicAttribute, base.StructAttribute)):
244 content.addULItem(att.makeUserDoc())
245 content.delEmptySection()
246 content.makeSpace()
247
248 self._addMacroDocs(klass, content, docStructure)
249
250 self.docParts.append((klass.name_, content.content))
251
253 try:
254 self._realAddDocsFrom(klass, docStructure)
255 except:
256 sys.stderr.write("Cannot add docs from element %s\n"%klass.name_)
257 traceback.print_exc()
258
260 self.docParts.sort(key=lambda t: t[0].upper())
261 resDoc = []
262 for title, doc in self.docParts:
263 resDoc.extend(doc)
264 return resDoc
265
268 """documentation for a macro, including the objects that know about
269 it.
270 """
271 - def __init__(self, name, macFunc, foundIn):
272 self.name = name
273 self.macFunc = macFunc
274 self.inObjects = [foundIn]
275
277 """declares that macFunc is also available on the python object obj.
278
279 If also checks implements the "see <whatever>" mechanism described
280 in KnownMacros.
281 """
282 self.inObjects.append(obj)
283 if (self.macFunc.func_doc or "").startswith("see "):
284 self.macFunc = macFunc
285
287 """adds documentation of macFunc to the RSTFragment content.
288 """
289
290
291 args, varargs, varkw, defaults = inspect.getargspec(self.macFunc)
292 args = inspect.formatargspec(args[1:], varargs, varkw, defaults
293 ).replace("(", "{").replace(")", "}").replace("{}", ""
294 ).replace(", ", "}{")
295 content.addRaw("::\n\n \\%s%s\n\n"%(self.name, args))
296 content.addRaw(utils.fixIndentation(
297 self.macFunc.func_doc or "undocumented", "", 1).replace("\\", "\\\\"))
298 content.addNormalizedPara("Available in "+", ".join(
299 sorted(
300 "`Element %s`_"%c.name_ for c in self.inObjects)))
301
304 """An accumulator for all macros known to the various DaCHS objects.
305
306 Note that macros with identical names are supposed to do essentially
307 the same things. In particular, they must all have the same signature
308 or the documentation will be wrong.
309
310 When macros share names, all but one implementation should have
311 a docstring just saying "see <whatever>"; that way, the docstring
312 actually chosen is deterministic.
313 """
316
317 - def addMacro(self, name, macFunc, foundIn):
318 """registers macFunc as expanding name in the element foundIn.
319
320 macFunc is the method, foundIn is the python class it's defined on.
321 """
322 if name in self.macros:
323 self.macros[name].addObject(macFunc, foundIn)
324 else:
325 self.macros[name] = MacroDoc(name, macFunc, foundIn)
326
336
340
346
353
358
364
370
386
391
409
425
428 """returns documentation for all functions marked with @document in the
429 namespace module.
430 """
431 res = []
432 for name in dir(module):
433 if name.startswith("_"):
434
435 continue
436
437 ob = getattr(module, name)
438 if hasattr(ob, "buildDocsForThis"):
439 if ob.func_doc is None:
440 continue
441 res.append(
442 "*%s%s*"%(name, inspect.formatargspec(*inspect.getargspec(ob))))
443 res.append(utils.fixIndentation(ob.func_doc, " ", 1))
444 res.append("")
445 return "\n".join(res)
446
451
456
473
476 def buildDocs(docStructure):
477 return _getProcdefDocs([(id, base.resolveId(None, id))
478 for id in sorted(idList)])
479 return buildDocs
480
495
498 parts = []
499 for rdName in pkg_resources.resource_listdir(
500 "gavo", "resources/inputs/__system__"):
501 if not rdName.endswith(".rd"):
502 continue
503
504 try:
505 for tableDef in base.caches.getRD("//"+rdName).tables:
506 if tableDef.onDisk:
507 parts.append((tableDef.getQName(), _makeTableDoc(tableDef)))
508 except base.Error:
509 base.ui.notifyError("Bad system RD: %s"%rdName)
510
511 return "\n".join(content for _, content in sorted(parts))
512
533
543
544
545 NO_API_SYMBOLS = frozenset(["__builtins__", "AdhocQuerier",
546 "__doc__", "__file__", "__name__", "__package__", "addCartesian",
547 "combinePMs", "getBinaryName", "getDefaultValueParsers", "getHTTPPar",
548 "makeProc", "ParseOptions"])
583
584
585 _replaceWithResultPat = re.compile(".. replaceWithResult (.*)")
588 """returns a restructured text containing the reference documentation
589 built from the template in refdoc.rstx.
590
591 **WARNING**: refdoc.rstx can execute arbitrary code right now. We
592 probably want to change this to having macros here.
593 """
594 res, docStructure = [], DocumentStructure()
595 f = pkg_resources.resource_stream("gavo",
596 "resources/templates/refdoc.rstx")
597
598 parseState = "content"
599 code = []
600
601 for lnCount, ln in enumerate(f):
602 if parseState=="content":
603 mat = _replaceWithResultPat.match(ln)
604 if mat:
605 code.append(mat.group(1))
606 parseState = "code"
607 else:
608 res.append(ln.decode("utf-8"))
609
610 elif parseState=="code":
611 if ln.strip():
612 code.append(ln)
613 else:
614 try:
615 res.extend(eval(" ".join(code)))
616 except Exception:
617 sys.stderr.write("Invalid code near line %s: '%s'\n"%(
618 lnCount, " ".join(code)))
619 raise
620 code = []
621 parseState = "content"
622 res.append("\n")
623
624 else:
625
626 assert False
627
628 f.close()
629 docStructure.fillOut(res)
630 _decodeStrings(res)
631 return "..\n WARNING: GENERATED DOCUMENT.\n Edit this in refdoc.rstx or the DaCHS source code.\n\n"+"".join(res)
632
633
634 @exposedFunction([], help="Writes ReStructuredText for the reference"
635 " documentation to stdout")
640
641
642 @exposedFunction([Arg(help="Input file name", dest="src")],
643 help="Turns ReStructured text (with DaCHS extensions) to LaTeX source")
645 from docutils import core
646 locale.setlocale(locale.LC_ALL, '')
647 sys.argv[1:] = (
648 "--documentoptions=11pt,a4paper --stylesheet stylesheet.tex"
649 " --use-latex-citations").split()
650 sys.argv.append(args.src)
651
652 core.publish_cmdline(writer_name='latex', description="(DaCHS rst2latex)")
653
654
655 @exposedFunction([Arg(help="Input file name", dest="src")],
656 help="Turns ReStructured text (with DaCHS extensions) to HTML")
658 from docutils import core
659 locale.setlocale(locale.LC_ALL, '')
660
661 sys.argv[1:] = ("--template rst2html-template.txt --stylesheet ref.css"
662 " --link-stylesheet").split()
663 sys.argv.append(args.src)
664 core.publish_cmdline(writer_name='html', description="(DaCHS rst2html)")
665
668 args = makeCLIParser(globals()).parse_args()
669 args.subAction(args)
670
671 if __name__=="__main__":
672 docStructure = DocumentStructure()
673 print("".join(getAPIDocs(docStructure)))
674