removed obsolete issues (many of them fixed with AE)
[docutils.git] / sandbox / tibs / pysource2 / package.py
blob96524693fbc300711aa35e4746a901d71be8a9e3
1 """package.py - support for calculating package documentation.
3 :Author: Tibs
4 :Contact: tibs@tibsnjoan.co.uk
5 :Revision: $Revision$
6 :Date: $Date$
7 :Copyright: This module has been placed in the public domain.
8 """
10 __docformat__ = 'reStructuredText'
12 import os
13 from docutils.readers.python.moduleparser import Node, parse_module
15 DEBUG = 0
17 class NotAPackageException(Exception):
18 pass
21 # ----------------------------------------------------------------------
22 class Package(Node):
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).
32 """
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...)
43 """
44 # Hackery - the following two lines copied from Node itself.
45 self.children = []
46 self.lineno = None
47 self.filename = filename
49 def attlist(self):
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?
59 """
61 def __init__(self, filename):
62 """Initialise a NotPython instance.
64 @@@ Same caveats as Package.
65 """
66 # Hackery - the following two lines copied from Node itself.
67 self.children = []
68 self.lineno = None
69 self.filename = filename
71 def attlist(self):
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
80 a single Python file.
81 """
82 path = os.path.normpath(path)
83 if os.path.isdir(path):
84 return parse_package(path)
85 else:
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...
93 """
95 if DEBUG: print "Parsing package",package_path
97 package_path = os.path.normpath(package_path)
98 dir,file = os.path.split(package_path)
99 if dir == "":
100 dir = "."
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.
127 files.sort()
129 for filename in files:
130 fullpath = os.path.join(sub_path,filename)
131 if os.path.isdir(fullpath):
132 try:
133 node.append(parse_subpackage(sub_path,filename))
134 except NotAPackageException:
135 pass
136 else:
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 \
144 filename[-1] != "~":
145 node.append(parse_file(fullpath,filename))
146 return node
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)
168 try:
169 module_body = module.read()
170 try:
171 module_node = parse_module(module_body,filename)
172 except SyntaxError:
173 # OK - it wasn't Python - so what *should* we do with it?
174 module_node = NotPython(filename)
175 if DEBUG: print " (not Python)"
176 return module_node
177 finally:
178 module.close()
182 # ----------------------------------------------------------------------
183 if __name__ == "__main__":
184 result = parse_package("trivial_package")
185 print result