📄 moduleparser.py
字号:
# Author: David Goodger# Contact: goodger@users.sourceforge.net# Revision: $Revision: 4242 $# Date: $Date: 2006-01-06 00:28:53 +0100 (Fri, 06 Jan 2006) $# Copyright: This module has been placed in the public domain."""Parser for Python modules. Requires Python 2.2 or higher.The `parse_module()` function takes a module's text and file name,runs it through the module parser (using compiler.py and tokenize.py)and produces a parse tree of the source code, using the nodes as foundin pynodes.py. For example, given this module (x.py):: # comment '''Docstring''' '''Additional docstring''' __docformat__ = 'reStructuredText' a = 1 '''Attribute docstring''' class C(Super): '''C's docstring''' class_attribute = 1 '''class_attribute's docstring''' def __init__(self, text=None): '''__init__'s docstring''' self.instance_attribute = (text * 7 + ' whaddyaknow') '''instance_attribute's docstring''' def f(x, # parameter x y=a*5, # parameter y *args): # parameter args '''f's docstring''' return [x + item for item in args] f.function_attribute = 1 '''f.function_attribute's docstring'''The module parser will produce this module documentation tree:: <module_section filename="test data"> <docstring> Docstring <docstring lineno="5"> Additional docstring <attribute lineno="7"> <object_name> __docformat__ <expression_value lineno="7"> 'reStructuredText' <attribute lineno="9"> <object_name> a <expression_value lineno="9"> 1 <docstring lineno="10"> Attribute docstring <class_section lineno="12"> <object_name> C <class_base> Super <docstring lineno="12"> C's docstring <attribute lineno="16"> <object_name> class_attribute <expression_value lineno="16"> 1 <docstring lineno="17"> class_attribute's docstring <method_section lineno="19"> <object_name> __init__ <docstring lineno="19"> __init__'s docstring <parameter_list lineno="19"> <parameter lineno="19"> <object_name> self <parameter lineno="19"> <object_name> text <parameter_default lineno="19"> None <attribute lineno="22"> <object_name> self.instance_attribute <expression_value lineno="22"> (text * 7 + ' whaddyaknow') <docstring lineno="24"> instance_attribute's docstring <function_section lineno="27"> <object_name> f <docstring lineno="27"> f's docstring <parameter_list lineno="27"> <parameter lineno="27"> <object_name> x <comment> # parameter x <parameter lineno="27"> <object_name> y <parameter_default lineno="27"> a * 5 <comment> # parameter y <parameter excess_positional="1" lineno="27"> <object_name> args <comment> # parameter args <attribute lineno="33"> <object_name> f.function_attribute <expression_value lineno="33"> 1 <docstring lineno="34"> f.function_attribute's docstring(Comments are not implemented yet.)compiler.parse() provides most of what's needed for this doctree, and"tokenize" can be used to get the rest. We can determine the linenumber from the compiler.parse() AST, and the TokenParser.rhs(lineno)method provides the rest.The Docutils Python reader component will transform this module doctree into aPython-specific Docutils doctree, and then a "stylist transform" willfurther transform it into a generic doctree. Namespaces will have to becompiled for each of the scopes, but I'm not certain at what stage ofprocessing.It's very important to keep all docstring processing out of this, so that it'sa completely generic and not tool-specific.::> Why perform all of those transformations? Why not go from the AST to a> generic doctree? Or, even from the AST to the final output?I want the docutils.readers.python.moduleparser.parse_module() function toproduce a standard documentation-oriented tree that can be used by any tool.We can develop it together without having to compromise on the rest of ourdesign (i.e., HappyDoc doesn't have to be made to work like Docutils, andvice-versa). It would be a higher-level version of what compiler.py provides.The Python reader component transforms this generic AST into a Python-specificdoctree (it knows about modules, classes, functions, etc.), but this isspecific to Docutils and cannot be used by HappyDoc or others. The stylisttransform does the final layout, converting Python-specific structures("class" sections, etc.) into a generic doctree using primitives (tables,sections, lists, etc.). This generic doctree does *not* know about Pythonstructures any more. The advantage is that this doctree can be handed off toany of the output writers to create any output format we like.The latter two transforms are separate because I want to be able to havemultiple independent layout styles (multiple runtime-selectable "stylisttransforms"). Each of the existing tools (HappyDoc, pydoc, epydoc, Crystal,etc.) has its own fixed format. I personally don't like the tables-basedformat produced by these tools, and I'd like to be able to customize theformat easily. That's the goal of stylist transforms, which are independentfrom the Reader component itself. One stylist transform could produceHappyDoc-like output, another could produce output similar to module docs inthe Python library reference manual, and so on.It's for exactly this reason::>> It's very important to keep all docstring processing out of this, so that>> it's a completely generic and not tool-specific.... but it goes past docstring processing. It's also important to keep styledecisions and tool-specific data transforms out of this module parser.Issues======* At what point should namespaces be computed? Should they be part of the basic AST produced by the ASTVisitor walk, or generated by another tree traversal?* At what point should a distinction be made between local variables & instance attributes in __init__ methods?* Docstrings are getting their lineno from their parents. Should the TokenParser find the real line no's?* Comments: include them? How and when? Only full-line comments, or parameter comments too? (See function "f" above for an example.)* Module could use more docstrings & refactoring in places."""__docformat__ = 'reStructuredText'import sysimport compilerimport compiler.astimport tokenizeimport tokenfrom compiler.consts import OP_ASSIGNfrom compiler.visitor import ASTVisitorfrom types import StringType, UnicodeType, TupleTypefrom docutils.readers.python import pynodesfrom docutils.nodes import Textdef parse_module(module_text, filename): """Return a module documentation tree from `module_text`.""" ast = compiler.parse(module_text) token_parser = TokenParser(module_text) visitor = ModuleVisitor(filename, token_parser) compiler.walk(ast, visitor, walker=visitor) return visitor.moduleclass BaseVisitor(ASTVisitor): def __init__(self, token_parser): ASTVisitor.__init__(self) self.token_parser = token_parser self.context = [] self.documentable = None def default(self, node, *args): self.documentable = None #print 'in default (%s)' % node.__class__.__name__ #ASTVisitor.default(self, node, *args) def default_visit(self, node, *args): #print 'in default_visit (%s)' % node.__class__.__name__ ASTVisitor.default(self, node, *args)class DocstringVisitor(BaseVisitor): def visitDiscard(self, node): if self.documentable: self.visit(node.expr) def visitConst(self, node): if self.documentable: if type(node.value) in (StringType, UnicodeType): self.documentable.append(make_docstring(node.value, node.lineno)) else: self.documentable = None def visitStmt(self, node): self.default_visit(node)class AssignmentVisitor(DocstringVisitor): def visitAssign(self, node): visitor = AttributeVisitor(self.token_parser) compiler.walk(node, visitor, walker=visitor) if visitor.attributes: self.context[-1].extend(visitor.attributes) if len(visitor.attributes) == 1: self.documentable = visitor.attributes[0] else: self.documentable = Noneclass ModuleVisitor(AssignmentVisitor): def __init__(self, filename, token_parser): AssignmentVisitor.__init__(self, token_parser) self.filename = filename self.module = None def visitModule(self, node): self.module = module = pynodes.module_section() module['filename'] = self.filename append_docstring(module, node.doc, node.lineno) self.context.append(module) self.documentable = module self.visit(node.node) self.context.pop() def visitImport(self, node): self.context[-1] += make_import_group(names=node.names, lineno=node.lineno) self.documentable = None def visitFrom(self, node): self.context[-1].append( make_import_group(names=node.names, from_name=node.modname, lineno=node.lineno)) self.documentable = None def visitFunction(self, node): visitor = FunctionVisitor(self.token_parser, function_class=pynodes.function_section) compiler.walk(node, visitor, walker=visitor) self.context[-1].append(visitor.function) def visitClass(self, node): visitor = ClassVisitor(self.token_parser) compiler.walk(node, visitor, walker=visitor) self.context[-1].append(visitor.klass)class AttributeVisitor(BaseVisitor): def __init__(self, token_parser): BaseVisitor.__init__(self, token_parser) self.attributes = pynodes.class_attribute_section() def visitAssign(self, node): # Don't visit the expression itself, just the attribute nodes: for child in node.nodes: self.dispatch(child) expression_text = self.token_parser.rhs(node.lineno) expression = pynodes.expression_value() expression.append(Text(expression_text)) for attribute in self.attributes: attribute.append(expression) def visitAssName(self, node): self.attributes.append(make_attribute(node.name, lineno=node.lineno)) def visitAssTuple(self, node): attributes = self.attributes self.attributes = [] self.default_visit(node) n = pynodes.attribute_tuple() n.extend(self.attributes) n['lineno'] = self.attributes[0]['lineno'] attributes.append(n) self.attributes = attributes #self.attributes.append(att_tuple) def visitAssAttr(self, node): self.default_visit(node, node.attrname) def visitGetattr(self, node, suffix): self.default_visit(node, node.attrname + '.' + suffix) def visitName(self, node, suffix): self.attributes.append(make_attribute(node.name + '.' + suffix, lineno=node.lineno))class FunctionVisitor(DocstringVisitor): in_function = 0 def __init__(self, token_parser, function_class): DocstringVisitor.__init__(self, token_parser) self.function_class = function_class def visitFunction(self, node): if self.in_function: self.documentable = None # Don't bother with nested function definitions. return self.in_function = 1 self.function = function = make_function_like_section( name=node.name, lineno=node.lineno, doc=node.doc, function_class=self.function_class) self.context.append(function) self.documentable = function
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -