1
2
3
4
5
6
7
8
9 """
10 Command-line interface for epydoc. Abbreviated Usage::
11
12 epydoc [options] NAMES...
13
14 NAMES... The Python modules to document.
15 --html Generate HTML output (default).
16 --latex Generate LaTeX output.
17 --pdf Generate pdf output, via LaTeX.
18 -o DIR, --output DIR The output directory.
19 --inheritance STYLE The format for showing inherited objects.
20 -V, --version Print the version of epydoc.
21 -h, --help Display a usage message.
22
23 Run \"epydoc --help\" for a complete option list. See the epydoc(1)
24 man page for more information.
25
26 Config Files
27 ============
28 Configuration files can be specified with the C{--config} option.
29 These files are read using U{ConfigParser
30 <http://docs.python.org/lib/module-ConfigParser.html>}. Configuration
31 files may set options or add names of modules to document. Option
32 names are (usually) identical to the long names of command line
33 options. To specify names to document, use any of the following
34 option names::
35
36 module modules value values object objects
37
38 A simple example of a config file is::
39
40 [epydoc]
41 modules: sys, os, os.path, re
42 name: Example
43 graph: classtree
44 introspect: no
45
46 Verbosity Levels
47 ================
48 The C{-v} and C{-q} options increase and decrease verbosity,
49 respectively. The default verbosity level is zero. The verbosity
50 levels are currently defined as follows::
51
52 Progress Markup warnings Warnings Errors
53 -3 none no no no
54 -2 none no no yes
55 -1 none no yes yes
56 0 (default) bar no yes yes
57 1 bar yes yes yes
58 2 list yes yes yes
59 """
60 __docformat__ = 'epytext en'
61
62 import sys, os, time, re, pstats
63 from glob import glob
64 from optparse import OptionParser, OptionGroup
65 import epydoc
66 from epydoc import log
67 from epydoc.util import wordwrap, run_subprocess, RunSubprocessError
68 from epydoc.apidoc import UNKNOWN
69 import ConfigParser
70
71 INHERITANCE_STYLES = ('grouped', 'listed', 'included')
72 GRAPH_TYPES = ('classtree', 'callgraph', 'umlclasstree')
73 ACTIONS = ('html', 'text', 'latex', 'dvi', 'ps', 'pdf', 'check')
74 DEFAULT_DOCFORMAT = 'epytext'
75
76
77
78
79
81
82 usage = '%prog ACTION [options] NAMES...'
83 version = "Epydoc, version %s" % epydoc.__version__
84 optparser = OptionParser(usage=usage, version=version)
85 action_group = OptionGroup(optparser, 'Actions')
86 options_group = OptionGroup(optparser, 'Options')
87
88
89 action_group.add_option(
90 "--html", action="store_const", dest="action", const="html",
91 help="Write HTML output.")
92 action_group.add_option(
93 "--text", action="store_const", dest="action", const="text",
94 help="Write plaintext output. (not implemented yet)")
95 action_group.add_option(
96 "--latex", action="store_const", dest="action", const="latex",
97 help="Write LaTeX output.")
98 action_group.add_option(
99 "--dvi", action="store_const", dest="action", const="dvi",
100 help="Write DVI output.")
101 action_group.add_option(
102 "--ps", action="store_const", dest="action", const="ps",
103 help="Write Postscript output.")
104 action_group.add_option(
105 "--pdf", action="store_const", dest="action", const="pdf",
106 help="Write PDF output.")
107 action_group.add_option(
108 "--check", action="store_const", dest="action", const="check",
109 help="Check completeness of docs.")
110
111
112 options_group.add_option(
113 "--output", "-o", dest="target", metavar="PATH",
114 help="The output directory. If PATH does not exist, then "
115 "it will be created.")
116 options_group.add_option(
117 "--inheritance", dest="inheritance", metavar="STYLE",
118 help="The format for showing inheritance objects. STYLE "
119 "should be one of: %s." % ', '.join(INHERITANCE_STYLES))
120 options_group.add_option(
121 "--docformat", dest="docformat", metavar="NAME",
122 help="The default markup language for docstrings. Defaults "
123 "to \"%s\"." % DEFAULT_DOCFORMAT)
124 options_group.add_option(
125 "--css", dest="css", metavar="STYLESHEET",
126 help="The CSS stylesheet. STYLESHEET can be either a "
127 "builtin stylesheet or the name of a CSS file.")
128 options_group.add_option(
129 "--name", dest="prj_name", metavar="NAME",
130 help="The documented project's name (for the navigation bar).")
131 options_group.add_option(
132 "--url", dest="prj_url", metavar="URL",
133 help="The documented project's URL (for the navigation bar).")
134 options_group.add_option(
135 "--navlink", dest="prj_link", metavar="HTML",
136 help="HTML code for a navigation link to place in the "
137 "navigation bar.")
138 options_group.add_option(
139 "--top", dest="top_page", metavar="PAGE",
140 help="The \"top\" page for the HTML documentation. PAGE can "
141 "be a URL, the name of a module or class, or one of the "
142 "special names \"trees.html\", \"indices.html\", or \"help.html\"")
143 options_group.add_option(
144 "--help-file", dest="help_file", metavar="FILE",
145 help="An alternate help file. FILE should contain the body "
146 "of an HTML file -- navigation bars will be added to it.")
147 options_group.add_option(
148 "--show-frames", action="store_true", dest="show_frames",
149 help="Include frames in the HTML output. (default)")
150 options_group.add_option(
151 "--no-frames", action="store_false", dest="show_frames",
152 help="Do not include frames in the HTML output.")
153 options_group.add_option(
154 "--show-private", action="store_true", dest="show_private",
155 help="Include private variables in the output. (default)")
156 options_group.add_option(
157 "--no-private", action="store_false", dest="show_private",
158 help="Do not include private variables in the output.")
159 options_group.add_option(
160 "--show-imports", action="store_true", dest="show_imports",
161 help="List each module's imports.")
162 options_group.add_option(
163 "--no-imports", action="store_false", dest="show_imports",
164 help="Do not list each module's imports. (default)")
165 options_group.add_option(
166 "--quiet", "-q", action="count", dest="quiet",
167 help="Decrease the verbosity.")
168 options_group.add_option(
169 "--verbose", "-v", action="count", dest="verbose",
170 help="Increase the verbosity.")
171 options_group.add_option(
172 "--debug", action="store_true", dest="debug",
173 help="Show full tracebacks for internal errors.")
174 options_group.add_option(
175 "--parse-only", action="store_false", dest="introspect",
176 help="Get all information from parsing (don't introspect)")
177 options_group.add_option(
178 "--introspect-only", action="store_false", dest="parse",
179 help="Get all information from introspecting (don't parse)")
180 if epydoc.DEBUG:
181
182 options_group.add_option(
183 "--profile-epydoc", action="store_true", dest="profile",
184 help="Run the profiler. Output will be written to profile.out")
185 options_group.add_option(
186 "--dotpath", dest="dotpath", metavar='PATH',
187 help="The path to the Graphviz 'dot' executable.")
188 options_group.add_option(
189 '--config', action='append', dest="configfiles", metavar='FILE',
190 help=("A configuration file, specifying additional OPTIONS "
191 "and/or NAMES. This option may be repeated."))
192 options_group.add_option(
193 '--graph', action='append', dest='graphs', metavar='GRAPHTYPE',
194 help=("Include graphs of type GRAPHTYPE in the generated output. "
195 "Graphs are generated using the Graphviz dot executable. "
196 "If this executable is not on the path, then use --dotpath "
197 "to specify its location. This option may be repeated to "
198 "include multiple graph types in the output. GRAPHTYPE "
199 "should be one of: all, %s." % ', '.join(GRAPH_TYPES)))
200 options_group.add_option(
201 '--separate-classes', action='store_true',
202 dest='list_classes_separately',
203 help=("When generating LaTeX or PDF output, list each class in "
204 "its own section, instead of listing them under their "
205 "containing module."))
206 options_group.add_option(
207 '--show-sourcecode', action='store_true', dest='include_source_code',
208 help=("Include source code with syntax highlighting in the "
209 "HTML output."))
210 options_group.add_option(
211 '--no-sourcecode', action='store_false', dest='include_source_code',
212 help=("Do not include source code with syntax highlighting in the "
213 "HTML output."))
214 options_group.add_option(
215 '--pstat', action='append', dest='pstat_files', metavar='FILE',
216 help="A pstat output file, to be used in generating call graphs.")
217
218
219 optparser.add_option_group(action_group)
220 optparser.add_option_group(options_group)
221
222
223 optparser.set_defaults(action="html", show_frames=True,
224 docformat=DEFAULT_DOCFORMAT,
225 show_private=True, show_imports=False,
226 inheritance="listed",
227 verbose=0, quiet=0,
228 parse=True, introspect=True,
229 debug=epydoc.DEBUG, profile=False,
230 graphs=[], list_classes_separately=False,
231 include_source_code=True, pstat_files=[])
232
233
234 options, names = optparser.parse_args()
235
236
237 if options.configfiles:
238 try:
239 parse_configfiles(options.configfiles, options, names)
240 except (KeyboardInterrupt,SystemExit): raise
241 except Exception, e:
242 optparser.error('Error reading config file:\n %s' % e)
243
244
245 if len(names) == 0:
246 optparser.error("No names specified.")
247
248
249 for i, name in enumerate(names[:]):
250 if '?' in name or '*' in name:
251 names[i:i+1] = glob(name)
252
253 if options.inheritance not in INHERITANCE_STYLES:
254 optparser.error("Bad inheritance style. Valid options are " +
255 ",".join(INHERITANCE_STYLES))
256 if not options.parse and not options.introspect:
257 optparser.error("Invalid option combination: --parse-only "
258 "and --introspect-only.")
259 if options.action == 'text' and len(names) > 1:
260 optparser.error("--text option takes only one name.")
261
262
263
264 options.graphs = [graph_type.lower() for graph_type in options.graphs]
265 for graph_type in options.graphs:
266 if graph_type == 'callgraph' and not options.pstat_files:
267 optparser.error('"callgraph" graph type may only be used if '
268 'one or more pstat files are specified.')
269
270
271 if graph_type == 'all':
272 if options.pstat_files:
273 options.graphs = GRAPH_TYPES
274 else:
275 options.graphs = [g for g in GRAPH_TYPES if g != 'callgraph']
276 break
277 elif graph_type not in GRAPH_TYPES:
278 optparser.error("Invalid graph type %s." % graph_type)
279
280
281 options.verbosity = options.verbose - options.quiet
282
283
284 if options.target is None:
285 options.target = options.action
286
287
288 return options, names
289
291 configparser = ConfigParser.ConfigParser()
292
293
294 for configfile in configfiles:
295 fp = open(configfile, 'r')
296 configparser.readfp(fp, configfile)
297 fp.close()
298 for optname in configparser.options('epydoc'):
299 val = configparser.get('epydoc', optname).strip()
300 optname = optname.lower().strip()
301 if optname in ('modules', 'objects', 'values',
302 'module', 'object', 'value'):
303 names.extend(val.replace(',', ' ').split())
304 elif optname == 'output':
305 if optname not in ACTIONS:
306 raise ValueError('"%s" expected one of: %s' %
307 (optname, ', '.join(ACTIONS)))
308 options.action = action
309 elif optname == 'target':
310 options.target = val
311 elif optname == 'inheritance':
312 if val.lower() not in INHERITANCE_STYLES:
313 raise ValueError('"%s" expected one of: %s.' %
314 (optname, ', '.join(INHERITANCE_STYLES)))
315 options.inerhitance = val.lower()
316 elif optname == 'docformat':
317 options.docformat = val
318 elif optname == 'css':
319 options.css = val
320 elif optname == 'name':
321 options.prj_name = val
322 elif optname == 'url':
323 options.prj_url = val
324 elif optname == 'link':
325 options.prj_link = val
326 elif optname == 'top':
327 options.top_page = val
328 elif optname == 'help':
329 options.help_file = val
330 elif optname =='frames':
331 options.frames = _str_to_bool(val, optname)
332 elif optname =='private':
333 options.private = _str_to_bool(val, optname)
334 elif optname =='imports':
335 options.imports = _str_to_bool(val, optname)
336 elif optname == 'verbosity':
337 try:
338 options.verbosity = int(val)
339 except ValueError:
340 raise ValueError('"%s" expected an int' % optname)
341 elif optname == 'parse':
342 options.parse = _str_to_bool(val, optname)
343 elif optname == 'introspect':
344 options.introspect = _str_to_bool(val, optname)
345 elif optname == 'dotpath':
346 options.dotpath = val
347 elif optname == 'graph':
348 graphtypes = val.replace(',', '').split()
349 for graphtype in graphtypes:
350 if graphtype not in GRAPH_TYPES:
351 raise ValueError('"%s" expected one of: %s.' %
352 (optname, ', '.join(GRAPH_TYPES)))
353 options.graphs.extend(graphtypes)
354 elif optname in ('separate-classes', 'separate_classes'):
355 options.list_classes_separately = _str_to_bool(val, optname)
356 elif optname == 'sourcecode':
357 options.include_source_code = _str_to_bool(val, optname)
358 elif optname == 'pstat':
359 options.pstat_files.extend(val.replace(',', ' ').split())
360 else:
361 raise ValueError('Unknown option %s' % optname)
362
364 if val.lower() in ('0', 'no', 'false', 'n', 'f', 'hide'):
365 return False
366 elif val.lower() in ('1', 'yes', 'true', 'y', 't', 'show'):
367 return True
368 else:
369 raise ValueError('"%s" option expected a boolean' % optname)
370
371
372
373
374
375 -def main(options, names):
376 if options.action == 'text':
377 if options.parse and options.introspect:
378 options.parse = False
379
380
381 if options.action == 'text':
382 logger = None
383 elif options.verbosity > 1:
384 logger = ConsoleLogger(options.verbosity)
385 log.register_logger(logger)
386 else:
387
388
389 stages = [40,
390 7,
391 1,
392 3,
393 30,
394 1,
395 2]
396 if options.action == 'html': stages += [100]
397 elif options.action == 'text': stages += [30]
398 elif options.action == 'latex': stages += [60]
399 elif options.action == 'dvi': stages += [60,30]
400 elif options.action == 'ps': stages += [60,40]
401 elif options.action == 'pdf': stages += [60,50]
402 elif options.action == 'check': stages += [10]
403 else: raise ValueError, '%r not supported' % options.action
404 if options.parse and not options.introspect:
405 del stages[1]
406 if options.introspect and not options.parse:
407 del stages[1:3]
408 logger = UnifiedProgressConsoleLogger(options.verbosity, stages)
409 log.register_logger(logger)
410
411
412 if options.action != 'text':
413 if os.path.exists(options.target):
414 if not os.path.isdir(options.target):
415 return log.error("%s is not a directory" % options.target)
416
417
418 from epydoc import docstringparser
419 docstringparser.DEFAULT_DOCFORMAT = options.docformat
420
421
422 if options.dotpath:
423 from epydoc import dotgraph
424 dotgraph.DOT_PATH = options.dotpath
425
426
427 from epydoc.docbuilder import build_doc_index
428 docindex = build_doc_index(names, options.introspect, options.parse,
429 add_submodules=(options.action!='text'))
430
431 if docindex is None:
432 return
433
434
435 if options.pstat_files:
436 try:
437 profile_stats = pstats.Stats(options.pstat_files[0])
438 for filename in options.pstat_files[1:]:
439 profile_stats.add(filename)
440 except KeyboardInterrupt: raise
441 except Exception, e:
442 log.error("Error reading pstat file: %s" % e)
443 profile_stats = None
444 if profile_stats is not None:
445 docindex.read_profiling_info(profile_stats)
446
447
448 if options.action == 'html':
449 write_html(docindex, options)
450 elif options.action in ('latex', 'dvi', 'ps', 'pdf'):
451 write_latex(docindex, options, options.action)
452 elif options.action == 'text':
453 write_text(docindex, options)
454 elif options.action == 'check':
455 check_docs(docindex, options)
456 else:
457 print >>sys.stderr, '\nUnsupported action %s!' % options.action
458
459
460 if logger is not None and logger.supressed_docstring_warning:
461 if logger.supressed_docstring_warning == 1:
462 prefix = '1 markup error was found'
463 else:
464 prefix = ('%d markup errors were found' %
465 logger.supressed_docstring_warning)
466 log.warning("%s while processing docstrings. Use the verbose "
467 "switch (-v) to display markup errors." % prefix)
468
469
470 if options.verbosity >= 2 and logger is not None:
471 logger.print_times()
472
482
483 _RERUN_LATEX_RE = re.compile(r'(?im)^LaTeX\s+Warning:\s+Label\(s\)\s+may'
484 r'\s+have\s+changed.\s+Rerun')
485
487 from epydoc.docwriter.latex import LatexWriter
488 latex_writer = LatexWriter(docindex, **options.__dict__)
489 log.start_progress('Writing LaTeX docs')
490 latex_writer.write(options.target)
491 log.end_progress()
492
493
494 if format == 'latex': return
495
496 if format == 'dvi': steps = 4
497 elif format == 'ps': steps = 5
498 elif format == 'pdf': steps = 6
499
500 log.start_progress('Processing LaTeX docs')
501 oldpath = os.path.abspath(os.curdir)
502 running = None
503 try:
504 try:
505 os.chdir(options.target)
506
507
508 for ext in 'tex aux log out idx ilg toc ind'.split():
509 if os.path.exists('apidoc.%s' % ext):
510 os.remove('apidoc.%s' % ext)
511
512
513 running = 'latex'
514 log.progress(0./steps, 'LaTeX: First pass')
515 run_subprocess('latex api.tex')
516
517
518 running = 'makeindex'
519 log.progress(1./steps, 'LaTeX: Build index')
520 run_subprocess('makeindex api.idx')
521
522
523 running = 'latex'
524 log.progress(2./steps, 'LaTeX: Second pass')
525 out, err = run_subprocess('latex api.tex')
526
527
528
529 running = 'latex'
530 if _RERUN_LATEX_RE.match(out):
531 log.progress(3./steps, 'LaTeX: Third pass')
532 out, err = run_subprocess('latex api.tex')
533
534
535 running = 'latex'
536 if _RERUN_LATEX_RE.match(out):
537 log.progress(3./steps, 'LaTeX: Fourth pass')
538 run_subprocess('latex api.tex')
539
540
541 if format in ('ps', 'pdf'):
542 running = 'dvips'
543 log.progress(4./steps, 'dvips')
544 run_subprocess('dvips api.dvi -o api.ps -G0 -Ppdf')
545
546
547 if format in ('pdf'):
548 running = 'ps2pdf'
549 log.progress(5./steps, 'ps2pdf')
550 run_subprocess(
551 'ps2pdf -sPAPERSIZE=letter -dMaxSubsetPct=100 '
552 '-dSubsetFonts=true -dCompatibilityLevel=1.2 '
553 '-dEmbedAllFonts=true api.ps api.pdf')
554 except RunSubprocessError, e:
555 if running == 'latex':
556 e.out = re.sub(r'(?sm)\A.*?!( LaTeX Error:)?', r'', e.out)
557 e.out = re.sub(r'(?sm)\s*Type X to quit.*', '', e.out)
558 e.out = re.sub(r'(?sm)^! Emergency stop.*', '', e.out)
559 log.error("%s failed: %s" % (running, (e.out+e.err).lstrip()))
560 except OSError, e:
561 log.error("%s failed: %s" % (running, e))
562 finally:
563 os.chdir(oldpath)
564 log.end_progress()
565
566 -def write_text(docindex, options):
567 log.start_progress('Writing output')
568 from epydoc.docwriter.plaintext import PlaintextWriter
569 plaintext_writer = PlaintextWriter()
570 s = ''
571 for apidoc in docindex.root:
572 s += plaintext_writer.write(apidoc)
573 log.end_progress()
574 if isinstance(s, unicode):
575 s = s.encode('ascii', 'backslashreplace')
576 print s
577
581
583
584 options, names = parse_arguments()
585
586 try:
587 if options.profile:
588 _profile()
589 else:
590 main(options, names)
591 except KeyboardInterrupt:
592 print '\n\n'
593 print >>sys.stderr, 'Keyboard interrupt.'
594 except:
595 if options.debug: raise
596 print '\n\n'
597 exc_info = sys.exc_info()
598 if isinstance(exc_info[0], basestring): e = exc_info[0]
599 else: e = exc_info[1]
600 print >>sys.stderr, ('\nUNEXPECTED ERROR:\n'
601 '%s\n' % (str(e) or e.__class__.__name__))
602 print >>sys.stderr, 'Use --debug to see trace information.'
603
605 import profile, pstats
606 try:
607 prof = profile.Profile()
608 prof = prof.runctx('main(*parse_arguments())', globals(), {})
609 except SystemExit:
610 pass
611 prof.dump_stats('profile.out')
612 return
613
614
615
616 try:
617 pstats_pyfile = os.path.splitext(pstats.__file__)[0]+'.py'
618 sys.argv = ['pstats.py', 'profile.out']
619 print
620 execfile(pstats_pyfile, {'__name__':'__main__'})
621 except:
622 print 'Could not run the pstats browser'
623
624 print 'Profiling output is in "profile.out"'
625
626
627
628
629
631 """
632 A class that can be used to portably generate formatted output to
633 a terminal. See
634 U{http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/475116}
635 for documentation. (This is a somewhat stripped-down version.)
636 """
637 BOL = ''
638 UP = ''
639 DOWN = ''
640 LEFT = ''
641 RIGHT = ''
642 CLEAR_EOL = ''
643 CLEAR_LINE = ''
644 BOLD = ''
645 NORMAL = ''
646 COLS = 75
647 BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = ''
648
649 _STRING_CAPABILITIES = """
650 BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1
651 CLEAR_EOL=el BOLD=bold UNDERLINE=smul NORMAL=sgr0""".split()
652 _COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split()
653 _ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split()
654
655 - def __init__(self, term_stream=sys.stdout):
656
657 if not term_stream.isatty(): return
658
659
660 try: import curses
661 except:
662
663
664 self.BOL = '\r'
665 self.CLEAR_LINE = '\r' + ' '*self.COLS + '\r'
666
667
668
669 try: curses.setupterm()
670 except: return
671
672
673 self.COLS = curses.tigetnum('cols')
674
675
676 for capability in self._STRING_CAPABILITIES:
677 (attrib, cap_name) = capability.split('=')
678 setattr(self, attrib, self._tigetstr(cap_name) or '')
679 if self.BOL and self.CLEAR_EOL:
680 self.CLEAR_LINE = self.BOL+self.CLEAR_EOL
681
682
683 set_fg = self._tigetstr('setf')
684 if set_fg:
685 for i,color in zip(range(len(self._COLORS)), self._COLORS):
686 setattr(self, color, curses.tparm(set_fg, i) or '')
687 set_fg_ansi = self._tigetstr('setaf')
688 if set_fg_ansi:
689 for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
690 setattr(self, color, curses.tparm(set_fg_ansi, i) or '')
691
693
694
695
696 import curses
697 cap = curses.tigetstr(cap_name) or ''
698 return re.sub(r'\$<\d+>[/*]?', '', cap)
699
702 self._verbosity = verbosity
703 self._progress = None
704 self._message_blocks = []
705
706 self._progress_start_time = None
707
708 self._task_times = []
709 self._progress_header = None
710
711 self.supressed_docstring_warning = 0
712 """This variable will be incremented once every time a
713 docstring warning is reported tothe logger, but the verbosity
714 level is too low for it to be displayed."""
715
716 self.term = TerminalController()
717
718
719 if verbosity >= 2: self._progress_mode = 'list'
720 elif verbosity >= 0:
721 if self.term.COLS < 15:
722 self._progress_mode = 'simple-bar'
723 if self.term.BOL and self.term.CLEAR_EOL and self.term.UP:
724 self._progress_mode = 'multiline-bar'
725 elif self.term.BOL and self.term.CLEAR_LINE:
726 self._progress_mode = 'bar'
727 else:
728 self._progress_mode = 'simple-bar'
729 else: self._progress_mode = 'hide'
730
732 self._message_blocks.append( (header, []) )
733
735 header, messages = self._message_blocks.pop()
736 if messages:
737 width = self.term.COLS - 5 - 2*len(self._message_blocks)
738 prefix = self.term.CYAN+self.term.BOLD+'| '+self.term.NORMAL
739 divider = (self.term.CYAN+self.term.BOLD+'+'+'-'*(width-1)+
740 self.term.NORMAL)
741
742 header = wordwrap(header, right=width-2, splitchars='\\/').rstrip()
743 header = '\n'.join([prefix+self.term.CYAN+l+self.term.NORMAL
744 for l in header.split('\n')])
745
746 body = ''
747 for message in messages:
748 if message.endswith('\n'): body += message
749 else: body += message+'\n'
750
751 body = '\n'.join([prefix+' '+l for l in body.split('\n')])
752
753 message = divider + '\n' + header + '\n' + body + '\n'
754 self._report(message)
755
771
772 - def log(self, level, message):
773 if self._verbosity >= -2 and level >= log.ERROR:
774 message = self._format(' Error: ', message, self.term.RED)
775 elif self._verbosity >= -1 and level >= log.WARNING:
776 message = self._format('Warning: ', message, self.term.YELLOW)
777 elif self._verbosity >= 1 and level >= log.DOCSTRING_WARNING:
778 message = self._format('Warning: ', message, self.term.YELLOW)
779 elif self._verbosity >= 3 and level >= log.INFO:
780 message = self._format(' Info: ', message, self.term.NORMAL)
781 elif epydoc.DEBUG and level == log.DEBUG:
782 message = self._format(' Debug: ', message, self.term.CYAN)
783 else:
784 if level >= log.DOCSTRING_WARNING:
785 self.supressed_docstring_warning += 1
786 return
787
788 self._report(message)
789
791 if not message.endswith('\n'): message += '\n'
792
793 if self._message_blocks:
794 self._message_blocks[-1][-1].append(message)
795 else:
796
797
798 if self._progress_mode == 'simple-bar':
799 if self._progress is not None:
800 print
801 self._progress = None
802 if self._progress_mode == 'bar':
803 sys.stdout.write(self.term.CLEAR_LINE)
804 if self._progress_mode == 'multiline-bar':
805 sys.stdout.write((self.term.CLEAR_EOL + '\n')*2 +
806 self.term.CLEAR_EOL + self.term.UP*2)
807
808
809 sys.stdout.write(message)
810 sys.stdout.flush()
811
812 - def progress(self, percent, message=''):
813 percent = min(1.0, percent)
814 message = '%s' % message
815
816 if self._progress_mode == 'list':
817 if message:
818 print '[%3d%%] %s' % (100*percent, message)
819 sys.stdout.flush()
820
821 elif self._progress_mode == 'bar':
822 dots = int((self.term.COLS/2-8)*percent)
823 background = '-'*(self.term.COLS/2-8)
824 if len(message) > self.term.COLS/2:
825 message = message[:self.term.COLS/2-3]+'...'
826 sys.stdout.write(self.term.CLEAR_LINE + '%3d%% '%(100*percent) +
827 self.term.GREEN + '[' + self.term.BOLD +
828 '='*dots + background[dots:] + self.term.NORMAL +
829 self.term.GREEN + '] ' + self.term.NORMAL +
830 message + self.term.BOL)
831 sys.stdout.flush()
832 self._progress = percent
833 elif self._progress_mode == 'multiline-bar':
834 dots = int((self.term.COLS-10)*percent)
835 background = '-'*(self.term.COLS-10)
836
837 if len(message) > self.term.COLS-10:
838 message = message[:self.term.COLS-10-3]+'...'
839 else:
840 message = message.center(self.term.COLS-10)
841
842 time_elapsed = time.time()-self._progress_start_time
843 if percent > 0:
844 time_remain = (time_elapsed / percent) * (1-percent)
845 else:
846 time_remain = 0
847
848 sys.stdout.write(
849
850 self.term.CLEAR_EOL + ' ' +
851 '%-8s' % self._timestr(time_elapsed) +
852 self.term.BOLD + 'Progress:'.center(self.term.COLS-26) +
853 self.term.NORMAL + '%8s' % self._timestr(time_remain) + '\n' +
854
855 self.term.CLEAR_EOL + ('%3d%% ' % (100*percent)) +
856 self.term.GREEN + '[' + self.term.BOLD + '='*dots +
857 background[dots:] + self.term.NORMAL + self.term.GREEN +
858 ']' + self.term.NORMAL + '\n' +
859
860 self.term.CLEAR_EOL + ' ' + message + self.term.BOL +
861 self.term.UP + self.term.UP)
862
863 sys.stdout.flush()
864 self._progress = percent
865 elif self._progress_mode == 'simple-bar':
866 if self._progress is None:
867 sys.stdout.write(' [')
868 self._progress = 0.0
869 dots = int((self.term.COLS-2)*percent)
870 progress_dots = int((self.term.COLS-2)*self._progress)
871 if dots > progress_dots:
872 sys.stdout.write('.'*(dots-progress_dots))
873 sys.stdout.flush()
874 self._progress = percent
875
877 dt = int(dt)
878 if dt >= 3600:
879 return '%d:%02d:%02d' % (dt/3600, dt%3600/60, dt%60)
880 else:
881 return '%02d:%02d' % (dt/60, dt%60)
882
884 if self._progress is not None:
885 raise ValueError
886 self._progress = None
887 self._progress_start_time = time.time()
888 self._progress_header = header
889 if self._progress_mode != 'hide' and header:
890 print self.term.BOLD + header + self.term.NORMAL
891
893 self.progress(1.)
894 if self._progress_mode == 'bar':
895 sys.stdout.write(self.term.CLEAR_LINE)
896 if self._progress_mode == 'multiline-bar':
897 sys.stdout.write((self.term.CLEAR_EOL + '\n')*2 +
898 self.term.CLEAR_EOL + self.term.UP*2)
899 if self._progress_mode == 'simple-bar':
900 print ']'
901 self._progress = None
902 self._task_times.append( (time.time()-self._progress_start_time,
903 self._progress_header) )
904
906 print
907 print 'Timing summary:'
908 total = sum([time for (time, task) in self._task_times])
909 max_t = max([time for (time, task) in self._task_times])
910 for (time, task) in self._task_times:
911 task = task[:31]
912 print ' %s%s %7.1fs' % (task, '.'*(35-len(task)), time),
913 if self.term.COLS > 55:
914 print '|'+'=' * int((self.term.COLS-53) * time / max_t)
915 else:
916 print
917 print
918
921 self.stage = 0
922 self.stages = stages
923 self.task = None
924 ConsoleLogger.__init__(self, verbosity)
925
926 - def progress(self, percent, message=''):
927
928 i = self.stage-1
929 p = ((sum(self.stages[:i]) + percent*self.stages[i]) /
930 float(sum(self.stages)))
931
932 if message == UNKNOWN: message = None
933 if message: message = '%s: %s' % (self.task, message)
934 ConsoleLogger.progress(self, p, message)
935
941
945
947 pass
948
949
950
951
952
953 if __name__ == '__main__':
954 try:
955 cli()
956 except:
957 print '\n\n'
958 raise
959