1 """package.py - support for calculating package documentation.
4 :Contact: tibs@tibsnjoan.co.uk
7 :Copyright: This module has been placed in the public domain.
10 __docformat__
= 'reStructuredText'
13 from docutils
.readers
.python
.moduleparser
import Node
, parse_module
17 class NotAPackageException(Exception):
21 # ----------------------------------------------------------------------
23 """This class represents a Python package.
25 `filename` is the name of the package - i.e., the package name.
26 This may be extended/altered/expanded to include/disambiguate the
27 name of the package, the "full" name of the package (e.g., if it is
28 a sub-package) and the full path of the package, as needs indicate.
30 Note that a package must, by definition, include at least one module,
31 i.e., __init__.py (otherwise, it isn't a package).
34 def __init__(self
, filename
):
35 """Initialise a Package.
37 Note that this does *not* take a "node" argument, since a Package
38 is not actually quite like the Module and other sub-nodes.
40 @@@ (Actually, there's a case to say that Node should be able to take
41 a "node" value of None and cope, in which case our life would be
42 easier - I may work on that later on...)
44 # Hackery - the following two lines copied from Node itself.
47 self
.filename
= filename
50 return Node
.attlist(self
, filename
=self
.filename
)
54 # ----------------------------------------------------------------------
55 class NotPython(Node
):
56 """This class is used to represent a non-Python file.
58 @@@ If the file isn't Python, should we try for reStructuredText?
61 def __init__(self
, filename
):
62 """Initialise a NotPython instance.
64 @@@ Same caveats as Package.
66 # Hackery - the following two lines copied from Node itself.
69 self
.filename
= filename
72 return Node
.attlist(self
, filename
=self
.filename
)
75 # ----------------------------------------------------------------------
76 def parse_package_or_module(path
):
77 """Parse a package or module for documentation purposes.
79 `path` should either be a directory representing a Python package, or
82 path
= os
.path
.normpath(path
)
83 if os
.path
.isdir(path
):
84 return parse_package(path
)
86 return parse_file(path
,path
)
88 def parse_package(package_path
):
89 """Parse a package for documentation purposes.
91 `package_path` should be the system path of the package directory, which is
92 not necessarily the same as the Python path...
95 if DEBUG
: print "Parsing package",package_path
97 package_path
= os
.path
.normpath(package_path
)
98 dir,file = os
.path
.split(package_path
)
101 return parse_subpackage(dir,file)
103 def parse_subpackage(package_path
,subpackage
):
104 """Parse a subpackage for documentation purposes.
106 `package_path` should be the system path of the package directory,
107 and `subpackage` is the (file) name of the subpackage therein. It
108 is assumed that this is already known to be a directory.
111 sub_path
= os
.path
.join(package_path
,subpackage
)
113 if DEBUG
: print "Parsing sub-package",sub_path
115 files
= os
.listdir(sub_path
)
116 if "__init__.py" not in files
:
117 raise NotAPackageException
,\
118 "Directory '%s' is not a Python package"%sub
_path
120 node
= Package(subpackage
)
122 # Should we sort the files? Well, if we don't have them in a predictable
123 # order, it is harder to test the result(!), and also I believe that it
124 # is easier to use the output if there is some obvious ordering. Of course,
125 # the question then becomes whether packages and modules should be in the
126 # same sequence, or separated.
129 for filename
in files
:
130 fullpath
= os
.path
.join(sub_path
,filename
)
131 if os
.path
.isdir(fullpath
):
133 node
.append(parse_subpackage(sub_path
,filename
))
134 except NotAPackageException
:
137 # We do *not* want to try .pyc or .pyo files - we can guarantee
138 # that they won't parse (the Python compiler code gets unhappy
139 # about NULL bytes therein), and we definitely don't want an
140 # entry for such files in our documentation.
141 # Similarly, I work on Linux, and don't want to consider files
142 # that end with "~" (this last is a bit nasty...)
143 if os
.path
.splitext(filename
)[1] not in (".pyc",".pyo") and \
145 node
.append(parse_file(fullpath
,filename
))
148 def parse_file(fullpath
,filename
):
149 """Parse a single file (which we hope is a Python file).
151 * `fullpath` is the full path of the file
152 * `filename` is the name we want to use for it in the docutils tree
154 Returns a docutils parse tree for said file.
157 if DEBUG
: print "Parsing file",fullpath
159 # @@@ Should we worry about the extension of the file?
160 # Trying to use that to predict the contents can be a problem
161 # - we already know that we have to worry about ".pyw" as well
162 # as ".py", not to mention the possibility (e.g., on Unix) of
163 # having removed the extension in order to make an executable
164 # file "look" more like a Unix executable. On the whole, it's
165 # probably better to try to parse a file, and worry about it
166 # not parsing if/when that occurs.
167 module
= open(fullpath
)
169 module_body
= module
.read()
171 module_node
= parse_module(module_body
,filename
)
173 # OK - it wasn't Python - so what *should* we do with it?
174 module_node
= NotPython(filename
)
175 if DEBUG
: print " (not Python)"
182 # ----------------------------------------------------------------------
183 if __name__
== "__main__":
184 result
= parse_package("trivial_package")