|
1 #!/usr/bin/env python3 |
|
2 # -*- coding: utf-8 -*- |
|
3 |
|
4 # Copyright (c) 2003 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
5 # |
|
6 |
|
7 """ |
|
8 eric Documentation Generator. |
|
9 |
|
10 This is the main Python script of the documentation generator. It is |
|
11 this script that gets called via the source documentation interface. |
|
12 This script can be used via the commandline as well. |
|
13 """ |
|
14 |
|
15 import fnmatch |
|
16 import glob |
|
17 import os |
|
18 import shutil |
|
19 import sys |
|
20 |
|
21 sys.path.insert(1, os.path.dirname(__file__)) |
|
22 |
|
23 import Utilities.ModuleParser |
|
24 |
|
25 from DocumentationTools.ModuleDocumentor import ModuleDocument |
|
26 from DocumentationTools.IndexGenerator import IndexGenerator |
|
27 from DocumentationTools.QtHelpGenerator import QtHelpGenerator |
|
28 from DocumentationTools.Config import eric7docDefaultColors |
|
29 from DocumentationTools import TemplatesListsStyleCSS |
|
30 |
|
31 from UI.Info import Version |
|
32 import Utilities |
|
33 |
|
34 # list of supported filename extensions |
|
35 supportedExtensions = [".py", ".pyw", ".ptl", ".rb"] |
|
36 |
|
37 |
|
38 def usage(): |
|
39 """ |
|
40 Function to print some usage information. |
|
41 |
|
42 It prints a reference of all commandline parameters that may |
|
43 be used and ends the application. |
|
44 """ |
|
45 print("eric7_doc") |
|
46 print() |
|
47 print("Copyright (c) 2003 - 2022 Detlev Offenbach" |
|
48 " <detlev@die-offenbachs.de>.") |
|
49 print() |
|
50 print("Usage:") |
|
51 print() |
|
52 print(" eric7_doc [options] files...") |
|
53 print() |
|
54 print("where files can be either python modules, package") |
|
55 print("directories or ordinary directories.") |
|
56 print() |
|
57 print("Options:") |
|
58 print() |
|
59 print(" -c filename or --style-sheet=filename") |
|
60 print(" Specify a CSS style sheet file to be used.") |
|
61 print(" -e or --noempty") |
|
62 print(" Don't include empty modules.") |
|
63 print(" --eol=eol-type") |
|
64 print(" Use the given eol type to terminate lines.") |
|
65 print(" Valid values are 'cr', 'lf' and 'crlf'.") |
|
66 print(" --exclude-file=pattern") |
|
67 print(" Specify a filename pattern of files to be excluded.") |
|
68 print(" This option may be repeated multiple times.") |
|
69 print(" -h or --help") |
|
70 print(" Show this help and exit.") |
|
71 print(" -i or --noindex") |
|
72 print(" Don't generate index files.") |
|
73 print(" -o directory or --outdir=directory") |
|
74 print(" Generate files in the named directory.") |
|
75 print(" -R, -r or --recursive") |
|
76 print(" Perform a recursive search for Python files.") |
|
77 print(" -t ext or --extension=ext") |
|
78 print(" Add the given extension to the list of file extensions.") |
|
79 print(" This option may be given multiple times.") |
|
80 print(" -V or --version") |
|
81 print(" Show version information and exit.") |
|
82 print(" -x directory or --exclude=directory") |
|
83 print(" Specify a directory basename to be excluded.") |
|
84 print(" This option may be repeated multiple times.") |
|
85 print() |
|
86 print(" --body-color=color") |
|
87 print(" Specify the text color.") |
|
88 print(" --body-background-color=color") |
|
89 print(" Specify the text background color.") |
|
90 print(" --l1header-color=color") |
|
91 print(" Specify the text color of level 1 headers.") |
|
92 print(" --l1header-background-color=color") |
|
93 print(" Specify the text background color of level 1 headers.") |
|
94 print(" --l2header-color=color") |
|
95 print(" Specify the text color of level 2 headers.") |
|
96 print(" --l2header-background-color=color") |
|
97 print(" Specify the text background color of level 2 headers.") |
|
98 print(" --cfheader-color=color") |
|
99 print(" Specify the text color of class and function headers.") |
|
100 print(" --cfheader-background-color=color") |
|
101 print(" Specify the text background color of class and" |
|
102 " function headers.") |
|
103 print(" --link-color=color") |
|
104 print(" Specify the text color of hyperlinks.") |
|
105 print() |
|
106 print(" --create-qhp") |
|
107 print(" Enable generation of QtHelp files.") |
|
108 print(" --qhp-outdir=directory") |
|
109 print(" Generate QtHelp files in the named directory.") |
|
110 print(" --qhp-namespace=namespace") |
|
111 print(" Use the given namespace (mandatory).") |
|
112 print(" --qhp-virtualfolder=folder") |
|
113 print(" Use the given virtual folder (mandatory).") |
|
114 print(" The virtual folder must not contain '/'.") |
|
115 print(" --qhp-filtername=name") |
|
116 print(" Use the given name for the custom filter.") |
|
117 print(" --qhp-filterattribs=attributes") |
|
118 print(" Add the given attributes to the filter list.") |
|
119 print(" Attributes must be separated by ':'.") |
|
120 print(" --qhp-title=title") |
|
121 print(" Use this as the title for the generated help (mandatory).") |
|
122 print(" --create-qhc") |
|
123 print(" Enable generation of QtHelp Collection files.") |
|
124 sys.exit(1) |
|
125 |
|
126 |
|
127 def version(): |
|
128 """ |
|
129 Function to show the version information. |
|
130 """ |
|
131 print( |
|
132 """eric7_doc {0}\n""" |
|
133 """\n""" |
|
134 """eric API documentation generator.\n""" |
|
135 """\n""" |
|
136 """Copyright (c) 2003-2022 Detlev Offenbach""" |
|
137 """ <detlev@die-offenbachs.de>\n""" |
|
138 """This is free software; see the LICENSE.GPL3 for copying""" |
|
139 """ conditions.\n""" |
|
140 """There is NO warranty; not even for MERCHANTABILITY or FITNESS""" |
|
141 """ FOR A\n""" |
|
142 """PARTICULAR PURPOSE.""".format(Version)) |
|
143 sys.exit(1) |
|
144 |
|
145 |
|
146 def main(): |
|
147 """ |
|
148 Main entry point into the application. |
|
149 """ |
|
150 import getopt |
|
151 |
|
152 try: |
|
153 opts, args = getopt.getopt( |
|
154 sys.argv[1:], "c:ehio:Rrt:Vx:", |
|
155 ["exclude=", "extension=", "help", "noindex", "noempty", "outdir=", |
|
156 "recursive", "style-sheet=", "version", |
|
157 "exclude-file=", "eol=", |
|
158 "body-color=", "body-background-color=", |
|
159 "l1header-color=", "l1header-background-color=", |
|
160 "l2header-color=", "l2header-background-color=", |
|
161 "cfheader-color=", "cfheader-background-color=", |
|
162 "link-color=", |
|
163 "create-qhp", "qhp-outdir=", "qhp-namespace=", |
|
164 "qhp-virtualfolder=", "qhp-filtername=", "qhp-filterattribs=", |
|
165 "qhp-title=", "create-qhc", |
|
166 ]) |
|
167 except getopt.error: |
|
168 usage() |
|
169 |
|
170 excludeDirs = [".svn", ".hg", ".git", ".ropeproject", ".eric7project", |
|
171 "dist", "build", "doc", "docs"] |
|
172 excludePatterns = [] |
|
173 outputDir = "doc" |
|
174 recursive = False |
|
175 doIndex = True |
|
176 noempty = False |
|
177 newline = None |
|
178 |
|
179 stylesheetFile = "" |
|
180 colors = eric7docDefaultColors.copy() |
|
181 |
|
182 qtHelpCreation = False |
|
183 qtHelpOutputDir = "help" |
|
184 qtHelpNamespace = "" |
|
185 qtHelpFolder = "source" |
|
186 qtHelpFilterName = "unknown" |
|
187 qtHelpFilterAttribs = "" |
|
188 qtHelpTitle = "" |
|
189 qtHelpCreateCollection = False |
|
190 |
|
191 for k, v in opts: |
|
192 if k in ["-o", "--outdir"]: |
|
193 outputDir = v |
|
194 elif k in ["-R", "-r", "--recursive"]: |
|
195 recursive = True |
|
196 elif k in ["-x", "--exclude"]: |
|
197 excludeDirs.append(v) |
|
198 elif k == "--exclude-file": |
|
199 excludePatterns.append(v) |
|
200 elif k in ["-i", "--noindex"]: |
|
201 doIndex = False |
|
202 elif k in ["-e", "--noempty"]: |
|
203 noempty = True |
|
204 elif k in ["-h", "--help"]: |
|
205 usage() |
|
206 elif k in ["-V", "--version"]: |
|
207 version() |
|
208 elif k in ["-c", "--style-sheet"]: |
|
209 stylesheetFile = v |
|
210 elif k in ["-t", "--extension"]: |
|
211 if v.strip() and not v.startswith("."): |
|
212 v = ".{0}".format(v) |
|
213 supportedExtensions.append(v) |
|
214 elif k == "--eol": |
|
215 if v.lower() == "cr": |
|
216 newline = '\r' |
|
217 elif v.lower() == "lf": |
|
218 newline = '\n' |
|
219 elif v.lower() == "crlf": |
|
220 newline = '\r\n' |
|
221 |
|
222 elif k == "--body-color": |
|
223 colors['BodyColor'] = v |
|
224 elif k == "--body-background-color": |
|
225 colors['BodyBgColor'] = v |
|
226 elif k == "--l1header-color": |
|
227 colors['Level1HeaderColor'] = v |
|
228 elif k == "--l1header-background-color": |
|
229 colors['Level1HeaderBgColor'] = v |
|
230 elif k == "--l2header-color": |
|
231 colors['Level2HeaderColor'] = v |
|
232 elif k == "--l2header-background-color": |
|
233 colors['Level2HeaderBgColor'] = v |
|
234 elif k == "--cfheader-color": |
|
235 colors['CFColor'] = v |
|
236 elif k == "--cfheader-background-color": |
|
237 colors['CFBgColor'] = v |
|
238 elif k == "--link-color": |
|
239 colors['LinkColor'] = v |
|
240 |
|
241 elif k == "--create-qhp": |
|
242 qtHelpCreation = True |
|
243 elif k == "--qhp-outdir": |
|
244 qtHelpOutputDir = v |
|
245 elif k == "--qhp-namespace": |
|
246 qtHelpNamespace = v |
|
247 elif k == "--qhp-virtualfolder": |
|
248 qtHelpFolder = v |
|
249 elif k == "--qhp-filtername": |
|
250 qtHelpFilterName = v |
|
251 elif k == "--qhp-filterattribs": |
|
252 qtHelpFilterAttribs = v |
|
253 elif k == "--qhp-title": |
|
254 qtHelpTitle = v |
|
255 elif k == "--create-qhc": |
|
256 qtHelpCreateCollection = True |
|
257 |
|
258 if not args: |
|
259 usage() |
|
260 |
|
261 if ( |
|
262 qtHelpCreation and |
|
263 (qtHelpNamespace == "" or |
|
264 qtHelpFolder == "" or |
|
265 '/' in qtHelpFolder or |
|
266 qtHelpTitle == "") |
|
267 ): |
|
268 usage() |
|
269 |
|
270 basename = "" |
|
271 |
|
272 if outputDir: |
|
273 if not os.path.isdir(outputDir): |
|
274 try: |
|
275 os.makedirs(outputDir) |
|
276 except OSError: |
|
277 sys.stderr.write( |
|
278 "Could not create output directory {0}.".format(outputDir)) |
|
279 sys.exit(2) |
|
280 else: |
|
281 outputDir = os.getcwd() |
|
282 outputDir = os.path.abspath(outputDir) |
|
283 |
|
284 if stylesheetFile: |
|
285 try: |
|
286 shutil.copy(stylesheetFile, os.path.join(outputDir, "styles.css")) |
|
287 except OSError: |
|
288 sys.stderr.write( |
|
289 "The CSS stylesheet '{0}' does not exist\n".format( |
|
290 stylesheetFile)) |
|
291 sys.exit(2) |
|
292 else: |
|
293 try: |
|
294 with open(os.path.join(outputDir, "styles.css"), "w") as sf: |
|
295 sf.write(TemplatesListsStyleCSS.cssTemplate.format(**colors)) |
|
296 except OSError: |
|
297 sys.stderr.write( |
|
298 "The CSS stylesheet '{0}' could not be created\n".format( |
|
299 stylesheetFile)) |
|
300 sys.exit(2) |
|
301 |
|
302 indexGenerator = IndexGenerator(outputDir) |
|
303 |
|
304 if qtHelpCreation: |
|
305 if qtHelpOutputDir: |
|
306 if not os.path.isdir(qtHelpOutputDir): |
|
307 try: |
|
308 os.makedirs(qtHelpOutputDir) |
|
309 except OSError: |
|
310 sys.stderr.write( |
|
311 "Could not create QtHelp output directory {0}.".format( |
|
312 qtHelpOutputDir)) |
|
313 sys.exit(2) |
|
314 else: |
|
315 qtHelpOutputDir = os.getcwd() |
|
316 qtHelpOutputDir = os.path.abspath(qtHelpOutputDir) |
|
317 |
|
318 qtHelpGenerator = QtHelpGenerator(outputDir, |
|
319 qtHelpOutputDir, qtHelpNamespace, |
|
320 qtHelpFolder, qtHelpFilterName, |
|
321 qtHelpFilterAttribs, qtHelpTitle, |
|
322 qtHelpCreateCollection) |
|
323 |
|
324 for arg in args: |
|
325 if os.path.isdir(arg): |
|
326 if os.path.exists(os.path.join( |
|
327 arg, Utilities.joinext("__init__", ".py"))): |
|
328 basename = os.path.dirname(arg) |
|
329 if arg == '.': |
|
330 sys.stderr.write("The directory '.' is a package.\n") |
|
331 sys.stderr.write( |
|
332 "Please repeat the call giving its real name.\n") |
|
333 sys.stderr.write("Ignoring the directory.\n") |
|
334 continue |
|
335 else: |
|
336 basename = arg |
|
337 if basename: |
|
338 basename = "{0}{1}".format(basename, os.sep) |
|
339 |
|
340 if recursive and not os.path.islink(arg): |
|
341 names = [arg] + Utilities.getDirs(arg, excludeDirs) |
|
342 else: |
|
343 names = [arg] |
|
344 else: |
|
345 basename = "" |
|
346 names = [arg] |
|
347 |
|
348 for filename in names: |
|
349 inpackage = False |
|
350 if os.path.isdir(filename): |
|
351 files = [] |
|
352 for ext in supportedExtensions: |
|
353 files.extend(glob.glob(os.path.join( |
|
354 filename, Utilities.joinext("*", ext)))) |
|
355 initFile = os.path.join( |
|
356 filename, Utilities.joinext("__init__", ext)) |
|
357 if initFile in files: |
|
358 inpackage = True |
|
359 files.remove(initFile) |
|
360 files.insert(0, initFile) |
|
361 else: |
|
362 if Utilities.isWindowsPlatform() and glob.has_magic(filename): |
|
363 files = glob.glob(filename) |
|
364 else: |
|
365 files = [filename] |
|
366 |
|
367 for file in files: |
|
368 skipIt = False |
|
369 for pattern in excludePatterns: |
|
370 if fnmatch.fnmatch(os.path.basename(file), pattern): |
|
371 skipIt = True |
|
372 break |
|
373 if skipIt: |
|
374 continue |
|
375 |
|
376 try: |
|
377 module = Utilities.ModuleParser.readModule( |
|
378 file, basename=basename, |
|
379 inpackage=inpackage, extensions=supportedExtensions) |
|
380 moduleDocument = ModuleDocument(module) |
|
381 doc = moduleDocument.genDocument() |
|
382 except OSError as v: |
|
383 sys.stderr.write("{0} error: {1}\n".format(file, v[1])) |
|
384 continue |
|
385 except ImportError as v: |
|
386 sys.stderr.write("{0} error: {1}\n".format(file, v)) |
|
387 continue |
|
388 |
|
389 f = Utilities.joinext(os.path.join( |
|
390 outputDir, moduleDocument.name()), ".html") |
|
391 |
|
392 # remember for index file generation |
|
393 indexGenerator.remember(file, moduleDocument, basename) |
|
394 |
|
395 # remember for QtHelp generation |
|
396 if qtHelpCreation: |
|
397 qtHelpGenerator.remember(file, moduleDocument, basename) |
|
398 |
|
399 if ( |
|
400 (noempty or file.endswith('__init__.py')) and |
|
401 moduleDocument.isEmpty() |
|
402 ): |
|
403 continue |
|
404 |
|
405 # generate output |
|
406 try: |
|
407 with open(f, "w", encoding="utf-8", |
|
408 newline=newline) as out: |
|
409 out.write(doc) |
|
410 except OSError as v: |
|
411 sys.stderr.write("{0} error: {1}\n".format(file, v[1])) |
|
412 else: |
|
413 sys.stdout.write("{0} ok\n".format(f)) |
|
414 |
|
415 sys.stdout.flush() |
|
416 sys.stderr.flush() |
|
417 |
|
418 # write index files |
|
419 if doIndex: |
|
420 indexGenerator.writeIndices(basename, newline=newline) |
|
421 |
|
422 # generate the QtHelp files |
|
423 if qtHelpCreation: |
|
424 qtHelpGenerator.generateFiles(newline=newline) |
|
425 |
|
426 sys.exit(0) |
|
427 |
|
428 if __name__ == '__main__': |
|
429 main() |
|
430 |
|
431 # |
|
432 # eflag: noqa = M801 |