blob: dbd388a7a7fa2f512e66709dda0e13ee5dee6faf [file] [log] [blame]
#!/usr/bin/env python
#
# objdoc: epydoc command-line interface
# Edward Loper
#
# Created [03/15/02 10:31 PM]
# $Id: gui.py 646 2004-03-19 19:01:37Z edloper $
#
"""
Graphical interface to epydoc. This interface might be useful for
systems where it's inconvenient to use the command-line interface
(such as Windows). It supports many (but not all) of the features
that are supported by the command-line interface. It also supports
loading and saving of X{project files}, which store a set of related
modules, and the options that should be used to generate the
documentation for those modules.
Usage::
epydocgui [OPTIONS] [FILE.prj | MODULES...]
FILE.prj An epydoc GUI project file.
MODULES... A list of Python modules to document.
-V, --version Print the version of epydoc.
-h, -?, --help, --usage Display this usage message
--debug Do not suppress error messages
@todo: Use ini-style project files, rather than pickles (using the
same format as the CLI).
"""
__docformat__ = 'epytext en'
import sys, os.path, re, glob
from Tkinter import *
from tkFileDialog import askopenfilename, asksaveasfilename
from thread import start_new_thread, exit_thread
from pickle import dump, load
# askdirectory is only defined in python 2.2+; fall back on
# asksaveasfilename if it's not available.
try: from tkFileDialog import askdirectory
except: askdirectory = None
# Include support for Zope, if it's available.
try: import ZODB
except: pass
##/////////////////////////////////////////////////////////////////////////
## CONSTANTS
##/////////////////////////////////////////////////////////////////////////
DEBUG = 0
# Colors for tkinter display
BG_COLOR='#e0e0e0'
ACTIVEBG_COLOR='#e0e0e0'
TEXT_COLOR='black'
ENTRYSELECT_COLOR = ACTIVEBG_COLOR
SELECT_COLOR = '#208070'
MESSAGE_COLOR = '#000060'
ERROR_COLOR = '#600000'
GUIERROR_COLOR = '#600000'
WARNING_COLOR = '#604000'
HEADER_COLOR = '#000000'
# Convenience dictionaries for specifying widget colors
COLOR_CONFIG = {'background':BG_COLOR, 'highlightcolor': BG_COLOR,
'foreground':TEXT_COLOR, 'highlightbackground': BG_COLOR}
ENTRY_CONFIG = {'background':BG_COLOR, 'highlightcolor': BG_COLOR,
'foreground':TEXT_COLOR, 'highlightbackground': BG_COLOR,
'selectbackground': ENTRYSELECT_COLOR,
'selectforeground': TEXT_COLOR}
SB_CONFIG = {'troughcolor':BG_COLOR, 'activebackground':BG_COLOR,
'background':BG_COLOR, 'highlightbackground':BG_COLOR}
LISTBOX_CONFIG = {'highlightcolor': BG_COLOR, 'highlightbackground': BG_COLOR,
'foreground':TEXT_COLOR, 'selectforeground': TEXT_COLOR,
'selectbackground': ACTIVEBG_COLOR, 'background':BG_COLOR}
BUTTON_CONFIG = {'background':BG_COLOR, 'highlightthickness':0, 'padx':4,
'highlightbackground': BG_COLOR, 'foreground':TEXT_COLOR,
'highlightcolor': BG_COLOR, 'activeforeground': TEXT_COLOR,
'activebackground': ACTIVEBG_COLOR, 'pady':0}
CBUTTON_CONFIG = {'background':BG_COLOR, 'highlightthickness':0, 'padx':4,
'highlightbackground': BG_COLOR, 'foreground':TEXT_COLOR,
'highlightcolor': BG_COLOR, 'activeforeground': TEXT_COLOR,
'activebackground': ACTIVEBG_COLOR, 'pady':0,
'selectcolor': SELECT_COLOR}
SHOWMSG_CONFIG = CBUTTON_CONFIG.copy()
SHOWMSG_CONFIG['foreground'] = MESSAGE_COLOR
SHOWWRN_CONFIG = CBUTTON_CONFIG.copy()
SHOWWRN_CONFIG['foreground'] = WARNING_COLOR
SHOWERR_CONFIG = CBUTTON_CONFIG.copy()
SHOWERR_CONFIG['foreground'] = ERROR_COLOR
# Colors for the progress bar
PROGRESS_HEIGHT = 16
PROGRESS_WIDTH = 200
PROGRESS_BG='#305060'
PROGRESS_COLOR1 = '#30c070'
PROGRESS_COLOR2 = '#60ffa0'
PROGRESS_COLOR3 = '#106030'
# On tkinter canvases, where's the zero coordinate?
if sys.platform.lower().startswith('win'):
DX = 3; DY = 3
DH = 0; DW = 7
else:
DX = 1; DY = 1
DH = 1; DW = 3
# How much of the progress is in each subtask?
IMPORT_PROGRESS = 0.1
BUILD_PROGRESS = 0.2
WRITE_PROGRESS = 1.0 - BUILD_PROGRESS - IMPORT_PROGRESS
##/////////////////////////////////////////////////////////////////////////
## IMAGE CONSTANTS
##/////////////////////////////////////////////////////////////////////////
UP_GIF = '''\
R0lGODlhCwAMALMAANnZ2QDMmQCZZgBmZgAAAAAzM////////wAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAACH5BAEAAAAALAAAAAALAAwAAAQjEMhJKxCW4gzCIJxXZIEwFGDlDadqsii1sq1U0nA64+ON
5xEAOw==
'''
DOWN_GIF = '''\
R0lGODlhCwAMALMAANnZ2QDMmQCZZgBmZgAAAAAzM////////wAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAACH5BAEAAAAALAAAAAALAAwAAAQmEIQxgLVUCsppsVPngVtXEFfIfWk5nBe4xuSL0tKLy/cu
7JffJQIAOw==
'''
LEFT_GIF='''\
R0lGODlhDAALAKIAANnZ2QDMmQCZZgBmZgAAAAAzM////////yH5BAEAAAAALAAAAAAMAAsAAAM4
CLocgaCrESiDoBshOAoAgBEyMzgAEIGCowsiOLoLgEBVOLoIqlSFo4OgC1RYM4Ogq1RYg6DLVJgA
Ow==
'''
RIGHT_GIF='''\
R0lGODlhDAALAKIAANnZ2QDMmQBmZgCZZgAzMwAAAP///////yH5BAEAAAAALAAAAAAMAAsAAAM5
GIGgyzIYgaCrIigTgaALIigyEQiqKLoTgaAoujuDgKJLVAgqIoJEBQAIIkKEhaArRFgIukqFoMsJ
ADs=
'''
##/////////////////////////////////////////////////////////////////////////
## MessageIO
##/////////////////////////////////////////////////////////////////////////
from epydoc import log
from epydoc.util import wordwrap
class GUILogger(log.Logger):
_STAGES = [40, 7, 1, 3, 1, 30, 1, 2, 100]
def __init__(self, progress, cancel):
self._progress = progress
self._cancel = cancel
self.clear()
def clear(self):
self._messages = []
self._n = 0
self._stage = 0
self._message_blocks = []
def log(self, level, message):
message = wordwrap(str(message)).rstrip() + '\n'
if self._message_blocks:
self._message_blocks[-1][-1].append( (level, message) )
else:
self._messages.append( (level, message) )
def start_block(self, header):
self._message_blocks.append( (header, []) )
def end_block(self):
header, messages = self._message_blocks.pop()
if messages:
self._messages.append( ('uline', ' '*75+'\n') )
self.log('header', header)
self._messages += messages
self._messages.append( ('uline', ' '*75+'\n') )
def start_progress(self, header=None):
self.log(log.INFO, header)
self._stage += 1
def end_progress(self):
pass
def progress(self, percent, message=''):
if self._cancel[0]: exit_thread()
i = self._stage - 1
p = ((sum(self._STAGES[:i]) + percent*self._STAGES[i]) /
float(sum(self._STAGES)))
self._progress[0] = p
def read(self):
if self._n >= len(self._messages):
return None, None
else:
self._n += 1
return self._messages[self._n-1]
##/////////////////////////////////////////////////////////////////////////
## THREADED DOCUMENTER
##/////////////////////////////////////////////////////////////////////////
def document(options, cancel, done):
"""
Create the documentation for C{modules}, using the options
specified by C{options}. C{document} is designed to be started in
its own thread by L{EpydocGUI._go}.
@param options: The options to use for generating documentation.
This includes keyword options that can be given to
L{docwriter.html.HTMLWriter}, as well as the option C{target}, which
controls where the output is written to.
@type options: C{dictionary}
"""
from epydoc.docwriter.html import HTMLWriter
from epydoc.docbuilder import build_doc_index
import epydoc.docstringparser
# Set the default docformat.
docformat = options.get('docformat', 'epytext')
epydoc.docstringparser.DEFAULT_DOCFORMAT = docformat
try:
parse = options['introspect_or_parse'] in ('parse', 'both')
introspect = options['introspect_or_parse'] in ('introspect', 'both')
docindex = build_doc_index(options['modules'], parse, introspect)
html_writer = HTMLWriter(docindex, **options)
log.start_progress('Writing HTML docs to %r' % options['target'])
html_writer.write(options['target'])
log.end_progress()
# We're done.
log.warning('Finished!')
done[0] = 'done'
except SystemExit:
# Cancel.
log.error('Cancelled!')
done[0] ='cancel'
raise
except Exception, e:
# We failed.
log.error('Internal error: %s' % e)
done[0] ='cancel'
raise
except:
# We failed.
log.error('Internal error!')
done[0] ='cancel'
raise
##/////////////////////////////////////////////////////////////////////////
## GUI
##/////////////////////////////////////////////////////////////////////////
class EpydocGUI:
"""
A graphical user interace to epydoc.
"""
def __init__(self):
self._afterid = 0
self._progress = [None]
self._cancel = [0]
self._filename = None
self._init_dir = None
# Store a copy of sys.modules, so that we can restore it
# later. This is useful for making sure that we reload
# everything when we re-build its documentation. This will
# *not* reload the modules that are present when the EpydocGUI
# is created, but that should only contain some builtins, some
# epydoc modules, Tkinter, pickle, and thread..
self._old_modules = sys.modules.keys()
# Create the main window.
self._root = Tk()
self._root['background']=BG_COLOR
self._root.bind('<Control-q>', self.destroy)
self._root.bind('<Alt-q>', self.destroy)
self._root.bind('<Alt-x>', self.destroy)
self._root.bind('<Control-x>', self.destroy)
#self._root.bind('<Control-d>', self.destroy)
self._root.title('Epydoc')
self._rootframe = Frame(self._root, background=BG_COLOR,
border=2, relief='raised')
self._rootframe.pack(expand=1, fill='both', padx=2, pady=2)
# Set up the basic frames. Do not pack the options frame or
# the messages frame; the GUI has buttons to expand them.
leftframe = Frame(self._rootframe, background=BG_COLOR)
leftframe.pack(expand=1, fill='both', side='left')
optsframe = Frame(self._rootframe, background=BG_COLOR)
mainframe = Frame(leftframe, background=BG_COLOR)
mainframe.pack(expand=1, fill='both', side='top')
ctrlframe = Frame(mainframe, background=BG_COLOR)
ctrlframe.pack(side="bottom", fill='x', expand=0)
msgsframe = Frame(leftframe, background=BG_COLOR)
self._optsframe = optsframe
self._msgsframe = msgsframe
# Initialize all the frames, etc.
self._init_menubar()
self._init_progress_bar(mainframe)
self._init_module_list(mainframe)
self._init_options(optsframe, ctrlframe)
self._init_messages(msgsframe, ctrlframe)
self._init_bindings()
# Set up logging
self._logger = GUILogger(self._progress, self._cancel)
log.register_logger(self._logger)
# Open the messages pane by default.
self._messages_toggle()
## For testing options:
#self._options_toggle()
def _init_menubar(self):
menubar = Menu(self._root, borderwidth=2,
background=BG_COLOR,
activebackground=BG_COLOR)
filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(label='New Project', underline=0,
command=self._new,
accelerator='Ctrl-n')
filemenu.add_command(label='Open Project', underline=0,
command=self._open,
accelerator='Ctrl-o')
filemenu.add_command(label='Save Project', underline=0,
command=self._save,
accelerator='Ctrl-s')
filemenu.add_command(label='Save As..', underline=5,
command=self._saveas,
accelerator='Ctrl-a')
filemenu.add_separator()
filemenu.add_command(label='Exit', underline=1,
command=self.destroy,
accelerator='Ctrl-x')
menubar.add_cascade(label='File', underline=0, menu=filemenu)
gomenu = Menu(menubar, tearoff=0)
gomenu.add_command(label='Run Epydoc', command=self._open,
underline=0, accelerator='Alt-g')
menubar.add_cascade(label='Run', menu=gomenu, underline=0)
self._root.config(menu=menubar)
def _init_module_list(self, mainframe):
mframe1 = Frame(mainframe, relief='groove', border=2,
background=BG_COLOR)
mframe1.pack(side="top", fill='both', expand=1, padx=4, pady=3)
l = Label(mframe1, text="Modules to document:",
justify='left', **COLOR_CONFIG)
l.pack(side='top', fill='none', anchor='nw', expand=0)
mframe2 = Frame(mframe1, background=BG_COLOR)
mframe2.pack(side="top", fill='both', expand=1)
mframe3 = Frame(mframe1, background=BG_COLOR)
mframe3.pack(side="bottom", fill='x', expand=0)
self._module_list = Listbox(mframe2, width=80, height=10,
selectmode='multiple',
**LISTBOX_CONFIG)
self._module_list.pack(side="left", fill='both', expand=1)
sb = Scrollbar(mframe2, orient='vertical',**SB_CONFIG)
sb['command']=self._module_list.yview
sb.pack(side='right', fill='y')
self._module_list.config(yscrollcommand=sb.set)
Label(mframe3, text="Add:", **COLOR_CONFIG).pack(side='left')
self._module_entry = Entry(mframe3, **ENTRY_CONFIG)
self._module_entry.pack(side='left', fill='x', expand=1)
self._module_entry.bind('<Return>', self._entry_module)
self._module_delete = Button(mframe3, text="Remove",
command=self._delete_module,
**BUTTON_CONFIG)
self._module_delete.pack(side='right', expand=0, padx=2)
self._module_browse = Button(mframe3, text="Browse",
command=self._browse_module,
**BUTTON_CONFIG)
self._module_browse.pack(side='right', expand=0, padx=2)
def _init_progress_bar(self, mainframe):
pframe1 = Frame(mainframe, background=BG_COLOR)
pframe1.pack(side="bottom", fill='x', expand=0)
self._go_button = Button(pframe1, width=4, text='Start',
underline=0, command=self._go,
**BUTTON_CONFIG)
self._go_button.pack(side='left', padx=4)
pframe2 = Frame(pframe1, relief='groove', border=2,
background=BG_COLOR)
pframe2.pack(side="top", fill='x', expand=1, padx=4, pady=3)
Label(pframe2, text='Progress:', **COLOR_CONFIG).pack(side='left')
H = self._H = PROGRESS_HEIGHT
W = self._W = PROGRESS_WIDTH
c = self._canvas = Canvas(pframe2, height=H+DH, width=W+DW,
background=PROGRESS_BG, border=0,
selectborderwidth=0, relief='sunken',
insertwidth=0, insertborderwidth=0,
highlightbackground=BG_COLOR)
self._canvas.pack(side='left', fill='x', expand=1, padx=4)
self._r2 = c.create_rectangle(0,0,0,0, outline=PROGRESS_COLOR2)
self._r3 = c.create_rectangle(0,0,0,0, outline=PROGRESS_COLOR3)
self._r1 = c.create_rectangle(0,0,0,0, fill=PROGRESS_COLOR1,
outline='')
self._canvas.bind('<Configure>', self._configure)
def _init_messages(self, msgsframe, ctrlframe):
self._downImage = PhotoImage(master=self._root, data=DOWN_GIF)
self._upImage = PhotoImage(master=self._root, data=UP_GIF)
# Set up the messages control frame
b1 = Button(ctrlframe, text="Messages", justify='center',
command=self._messages_toggle, underline=0,
highlightthickness=0, activebackground=BG_COLOR,
border=0, relief='flat', padx=2, pady=0, **COLOR_CONFIG)
b2 = Button(ctrlframe, image=self._downImage, relief='flat',
border=0, command=self._messages_toggle,
activebackground=BG_COLOR, **COLOR_CONFIG)
self._message_button = b2
self._messages_visible = 0
b2.pack(side="left")
b1.pack(side="left")
f = Frame(msgsframe, background=BG_COLOR)
f.pack(side='top', expand=1, fill='both')
messages = Text(f, width=80, height=10, **ENTRY_CONFIG)
messages['state'] = 'disabled'
messages.pack(fill='both', expand=1, side='left')
self._messages = messages
# Add a scrollbar
sb = Scrollbar(f, orient='vertical', **SB_CONFIG)
sb.pack(fill='y', side='right')
sb['command'] = messages.yview
messages['yscrollcommand'] = sb.set
# Set up some colorization tags
messages.tag_config('error', foreground=ERROR_COLOR)
messages.tag_config('warning', foreground=WARNING_COLOR)
messages.tag_config('guierror', foreground=GUIERROR_COLOR)
messages.tag_config('message', foreground=MESSAGE_COLOR)
messages.tag_config('header', foreground=HEADER_COLOR)
messages.tag_config('uline', underline=1)
# Keep track of tag state..
self._in_header = 0
self._last_tag = 'error'
# Add some buttons
buttons = Frame(msgsframe, background=BG_COLOR)
buttons.pack(side='bottom', fill='x')
self._show_errors = IntVar(self._root)
self._show_errors.set(1)
self._show_warnings = IntVar(self._root)
self._show_warnings.set(1)
self._show_messages = IntVar(self._root)
self._show_messages.set(0)
Checkbutton(buttons, text='Show Messages', var=self._show_messages,
command=self._update_msg_tags,
**SHOWMSG_CONFIG).pack(side='left')
Checkbutton(buttons, text='Show Warnings', var=self._show_warnings,
command=self._update_msg_tags,
**SHOWWRN_CONFIG).pack(side='left')
Checkbutton(buttons, text='Show Errors', var=self._show_errors,
command=self._update_msg_tags,
**SHOWERR_CONFIG).pack(side='left')
self._update_msg_tags()
def _update_msg_tags(self, *e):
elide_errors = not self._show_errors.get()
elide_warnings = not self._show_warnings.get()
elide_messages = not self._show_messages.get()
elide_headers = elide_errors and elide_warnings
self._messages.tag_config('error', elide=elide_errors)
self._messages.tag_config('guierror', elide=elide_errors)
self._messages.tag_config('warning', elide=elide_warnings)
self._messages.tag_config('message', elide=elide_messages)
self._messages.tag_config('header', elide=elide_headers)
def _init_options(self, optsframe, ctrlframe):
self._leftImage=PhotoImage(master=self._root, data=LEFT_GIF)
self._rightImage=PhotoImage(master=self._root, data=RIGHT_GIF)
# Set up the options control frame
b1 = Button(ctrlframe, text="Options", justify='center',
border=0, relief='flat',
command=self._options_toggle, padx=2,
underline=0, pady=0, highlightthickness=0,
activebackground=BG_COLOR, **COLOR_CONFIG)
b2 = Button(ctrlframe, image=self._rightImage, relief='flat',
border=0, command=self._options_toggle,
activebackground=BG_COLOR, **COLOR_CONFIG)
self._option_button = b2
self._options_visible = 0
b2.pack(side="right")
b1.pack(side="right")
oframe2 = Frame(optsframe, relief='groove', border=2,
background=BG_COLOR)
oframe2.pack(side="right", fill='both',
expand=0, padx=4, pady=3, ipadx=4)
Label(oframe2, text="Project Options", font='helvetica -16',
**COLOR_CONFIG).pack(anchor='w')
oframe3 = Frame(oframe2, background=BG_COLOR)
oframe3.pack(fill='x')
oframe4 = Frame(oframe2, background=BG_COLOR)
oframe4.pack(fill='x')
oframe7 = Frame(oframe2, background=BG_COLOR)
oframe7.pack(fill='x')
div = Frame(oframe2, background=BG_COLOR, border=1, relief='sunk')
div.pack(ipady=1, fill='x', padx=4, pady=2)
Label(oframe2, text="Help File", font='helvetica -16',
**COLOR_CONFIG).pack(anchor='w')
oframe5 = Frame(oframe2, background=BG_COLOR)
oframe5.pack(fill='x')
div = Frame(oframe2, background=BG_COLOR, border=1, relief='sunk')
div.pack(ipady=1, fill='x', padx=4, pady=2)
Label(oframe2, text="CSS Stylesheet", font='helvetica -16',
**COLOR_CONFIG).pack(anchor='w')
oframe6 = Frame(oframe2, background=BG_COLOR)
oframe6.pack(fill='x')
#==================== oframe3 ====================
# -n NAME, --name NAME
row = 0
l = Label(oframe3, text="Project Name:", **COLOR_CONFIG)
l.grid(row=row, column=0, sticky='e')
self._name_entry = Entry(oframe3, **ENTRY_CONFIG)
self._name_entry.grid(row=row, column=1, sticky='ew', columnspan=3)
# -u URL, --url URL
row += 1
l = Label(oframe3, text="Project URL:", **COLOR_CONFIG)
l.grid(row=row, column=0, sticky='e')
self._url_entry = Entry(oframe3, **ENTRY_CONFIG)
self._url_entry.grid(row=row, column=1, sticky='ew', columnspan=3)
# -o DIR, --output DIR
row += 1
l = Label(oframe3, text="Output Directory:", **COLOR_CONFIG)
l.grid(row=row, column=0, sticky='e')
self._out_entry = Entry(oframe3, **ENTRY_CONFIG)
self._out_entry.grid(row=row, column=1, sticky='ew', columnspan=2)
self._out_browse = Button(oframe3, text="Browse",
command=self._browse_out,
**BUTTON_CONFIG)
self._out_browse.grid(row=row, column=3, sticky='ew', padx=2)
#==================== oframe4 ====================
# --no-frames
row = 0
self._frames_var = IntVar(self._root)
self._frames_var.set(1)
l = Label(oframe4, text="Generate a frame-based table of contents",
**COLOR_CONFIG)
l.grid(row=row, column=1, sticky='w')
cb = Checkbutton(oframe4, var=self._frames_var, **CBUTTON_CONFIG)
cb.grid(row=row, column=0, sticky='e')
# --no-private
row += 1
self._private_var = IntVar(self._root)
self._private_var.set(1)
l = Label(oframe4, text="Generate documentation for private objects",
**COLOR_CONFIG)
l.grid(row=row, column=1, sticky='w')
cb = Checkbutton(oframe4, var=self._private_var, **CBUTTON_CONFIG)
cb.grid(row=row, column=0, sticky='e')
# --show-imports
row += 1
self._imports_var = IntVar(self._root)
self._imports_var.set(0)
l = Label(oframe4, text="List imported classes and functions",
**COLOR_CONFIG)
l.grid(row=row, column=1, sticky='w')
cb = Checkbutton(oframe4, var=self._imports_var, **CBUTTON_CONFIG)
cb.grid(row=row, column=0, sticky='e')
#==================== oframe7 ====================
# --docformat
row += 1
l = Label(oframe7, text="Default Docformat:", **COLOR_CONFIG)
l.grid(row=row, column=0, sticky='e')
df_var = self._docformat_var = StringVar(self._root)
self._docformat_var.set('epytext')
b = Radiobutton(oframe7, var=df_var, text='Epytext',
value='epytext', **CBUTTON_CONFIG)
b.grid(row=row, column=1, sticky='w')
b = Radiobutton(oframe7, var=df_var, text='ReStructuredText',
value='restructuredtext', **CBUTTON_CONFIG)
b.grid(row=row, column=2, columnspan=2, sticky='w')
row += 1
b = Radiobutton(oframe7, var=df_var, text='Plaintext',
value='plaintext', **CBUTTON_CONFIG)
b.grid(row=row, column=1, sticky='w')
b = Radiobutton(oframe7, var=df_var, text='Javadoc',
value='javadoc', **CBUTTON_CONFIG)
b.grid(row=row, column=2, columnspan=2, sticky='w')
row += 1
# Separater
Frame(oframe7, background=BG_COLOR).grid(row=row, column=1, pady=3)
row += 1
# --inheritance
l = Label(oframe7, text="Inheritance Style:", **COLOR_CONFIG)
l.grid(row=row, column=0, sticky='e')
inh_var = self._inheritance_var = StringVar(self._root)
self._inheritance_var.set('grouped')
b = Radiobutton(oframe7, var=inh_var, text='Grouped',
value='grouped', **CBUTTON_CONFIG)
b.grid(row=row, column=1, sticky='w')
b = Radiobutton(oframe7, var=inh_var, text='Listed',
value='listed', **CBUTTON_CONFIG)
b.grid(row=row, column=2, sticky='w')
b = Radiobutton(oframe7, var=inh_var, text='Included',
value='included', **CBUTTON_CONFIG)
b.grid(row=row, column=3, sticky='w')
row += 1
# Separater
Frame(oframe7, background=BG_COLOR).grid(row=row, column=1, pady=3)
row += 1
# --parse-only, --introspect-only
l = Label(oframe7, text="Get docs from:", **COLOR_CONFIG)
l.grid(row=row, column=0, sticky='e')
iop_var = self._introspect_or_parse_var = StringVar(self._root)
self._introspect_or_parse_var.set('both')
b = Radiobutton(oframe7, var=iop_var, text='Parsing',
value='parse', **CBUTTON_CONFIG)
b.grid(row=row, column=1, sticky='w')
b = Radiobutton(oframe7, var=iop_var, text='Introspecting',
value='introspect', **CBUTTON_CONFIG)
b.grid(row=row, column=2, sticky='w')
b = Radiobutton(oframe7, var=iop_var, text='Both',
value='both', **CBUTTON_CONFIG)
b.grid(row=row, column=3, sticky='w')
row += 1
#==================== oframe5 ====================
# --help-file FILE
row = 0
self._help_var = StringVar(self._root)
self._help_var.set('default')
b = Radiobutton(oframe5, var=self._help_var,
text='Default',
value='default', **CBUTTON_CONFIG)
b.grid(row=row, column=1, sticky='w')
row += 1
b = Radiobutton(oframe5, var=self._help_var,
text='Select File',
value='-other-', **CBUTTON_CONFIG)
b.grid(row=row, column=1, sticky='w')
self._help_entry = Entry(oframe5, **ENTRY_CONFIG)
self._help_entry.grid(row=row, column=2, sticky='ew')
self._help_browse = Button(oframe5, text='Browse',
command=self._browse_help,
**BUTTON_CONFIG)
self._help_browse.grid(row=row, column=3, sticky='ew', padx=2)
from epydoc.docwriter.html_css import STYLESHEETS
items = STYLESHEETS.items()
def _css_sort(css1, css2):
if css1[0] == 'default': return -1
elif css2[0] == 'default': return 1
else: return cmp(css1[0], css2[0])
items.sort(_css_sort)
#==================== oframe6 ====================
# -c CSS, --css CSS
# --private-css CSS
row = 0
#l = Label(oframe6, text="Public", **COLOR_CONFIG)
#l.grid(row=row, column=0, sticky='e')
#l = Label(oframe6, text="Private", **COLOR_CONFIG)
#l.grid(row=row, column=1, sticky='w')
row += 1
css_var = self._css_var = StringVar(self._root)
css_var.set('default')
#private_css_var = self._private_css_var = StringVar(self._root)
#private_css_var.set('default')
for (name, (sheet, descr)) in items:
b = Radiobutton(oframe6, var=css_var, value=name, **CBUTTON_CONFIG)
b.grid(row=row, column=0, sticky='e')
#b = Radiobutton(oframe6, var=private_css_var, value=name,
# text=name, **CBUTTON_CONFIG)
#b.grid(row=row, column=1, sticky='w')
l = Label(oframe6, text=descr, **COLOR_CONFIG)
l.grid(row=row, column=1, sticky='w')
row += 1
b = Radiobutton(oframe6, var=css_var, value='-other-',
**CBUTTON_CONFIG)
b.grid(row=row, column=0, sticky='e')
#b = Radiobutton(oframe6, text='Select File', var=private_css_var,
# value='-other-', **CBUTTON_CONFIG)
#b.grid(row=row, column=1, sticky='w')
#l = Label(oframe6, text='Select File', **COLOR_CONFIG)
#l.grid(row=row, column=1, sticky='w')
self._css_entry = Entry(oframe6, **ENTRY_CONFIG)
self._css_entry.grid(row=row, column=1, sticky='ew')
self._css_browse = Button(oframe6, text="Browse",
command=self._browse_css,
**BUTTON_CONFIG)
self._css_browse.grid(row=row, column=2, sticky='ew', padx=2)
def _init_bindings(self):
self._root.bind('<Delete>', self._delete_module)
self._root.bind('<Alt-o>', self._options_toggle)
self._root.bind('<Alt-m>', self._messages_toggle)
self._root.bind('<F5>', self._go)
self._root.bind('<Alt-s>', self._go)
self._root.bind('<Control-n>', self._new)
self._root.bind('<Control-o>', self._open)
self._root.bind('<Control-s>', self._save)
self._root.bind('<Control-a>', self._saveas)
def _options_toggle(self, *e):
if self._options_visible:
self._optsframe.forget()
self._option_button['image'] = self._rightImage
self._options_visible = 0
else:
self._optsframe.pack(fill='both', side='right')
self._option_button['image'] = self._leftImage
self._options_visible = 1
def _messages_toggle(self, *e):
if self._messages_visible:
self._msgsframe.forget()
self._message_button['image'] = self._rightImage
self._messages_visible = 0
else:
self._msgsframe.pack(fill='both', side='bottom', expand=1)
self._message_button['image'] = self._leftImage
self._messages_visible = 1
def _configure(self, event):
self._W = event.width-DW
def _delete_module(self, *e):
selection = self._module_list.curselection()
if len(selection) != 1: return
self._module_list.delete(selection[0])
def _entry_module(self, *e):
modules = [self._module_entry.get()]
if glob.has_magic(modules[0]):
modules = glob.glob(modules[0])
for name in modules:
self.add_module(name, check=1)
self._module_entry.delete(0, 'end')
def _browse_module(self, *e):
title = 'Select a module for documentation'
ftypes = [('Python module', '.py'),
('Python extension', '.so'),
('All files', '*')]
filename = askopenfilename(filetypes=ftypes, title=title,
defaultextension='.py',
initialdir=self._init_dir)
if not filename: return
self._init_dir = os.path.dirname(filename)
self.add_module(filename, check=1)
def _browse_css(self, *e):
title = 'Select a CSS stylesheet'
ftypes = [('CSS Stylesheet', '.css'), ('All files', '*')]
filename = askopenfilename(filetypes=ftypes, title=title,
defaultextension='.css')
if not filename: return
self._css_entry.delete(0, 'end')
self._css_entry.insert(0, filename)
def _browse_help(self, *e):
title = 'Select a help file'
self._help_var.set('-other-')
ftypes = [('HTML file', '.html'), ('All files', '*')]
filename = askopenfilename(filetypes=ftypes, title=title,
defaultextension='.html')
if not filename: return
self._help_entry.delete(0, 'end')
self._help_entry.insert(0, filename)
def _browse_out(self, *e):
ftypes = [('All files', '*')]
title = 'Choose the output directory'
if askdirectory is not None:
filename = askdirectory(mustexist=0, title=title)
if not filename: return
else:
# Hack for Python 2.1 or earlier:
filename = asksaveasfilename(filetypes=ftypes, title=title,
initialfile='--this directory--')
if not filename: return
(f1, f2) = os.path.split(filename)
if f2 == '--this directory--': filename = f1
self._out_entry.delete(0, 'end')
self._out_entry.insert(0, filename)
def destroy(self, *e):
if self._root is None: return
# Unload any modules that we've imported
for m in sys.modules.keys():
if m not in self._old_modules: del sys.modules[m]
self._root.destroy()
self._root = None
def add_module(self, name, check=0):
from epydoc.util import is_package_dir, is_pyname, is_module_file
from epydoc.docintrospecter import get_value_from_name
from epydoc.docintrospecter import get_value_from_filename
if (os.path.isfile(name) or is_package_dir(name) or is_pyname(name)):
# Check that it's a good module, if requested.
if check:
try:
if is_module_file(name) or is_package_dir(name):
get_value_from_filename(name)
elif os.path.isfile(name):
get_value_from_scriptname(name)
else:
get_value_from_name(name)
except ImportError, e:
log.error(e)
self._update_messages()
self._root.bell()
return
# Add the module to the list of modules.
self._module_list.insert('end', name)
self._module_list.yview('end')
else:
log.error("Couldn't find %r" % name)
self._update_messages()
self._root.bell()
def mainloop(self, *args, **kwargs):
self._root.mainloop(*args, **kwargs)
def _getopts(self):
options = {}
options['modules'] = self._module_list.get(0, 'end')
options['prj_name'] = self._name_entry.get() or ''
options['prj_url'] = self._url_entry.get() or None
options['docformat'] = self._docformat_var.get()
options['inheritance'] = self._inheritance_var.get()
options['introspect_or_parse'] = self._introspect_or_parse_var.get()
options['target'] = self._out_entry.get() or 'html'
options['frames'] = self._frames_var.get()
options['private'] = self._private_var.get()
options['show_imports'] = self._imports_var.get()
if self._help_var.get() == '-other-':
options['help'] = self._help_entry.get() or None
else:
options['help'] = None
if self._css_var.get() == '-other-':
options['css'] = self._css_entry.get() or 'default'
else:
options['css'] = self._css_var.get() or 'default'
#if self._private_css_var.get() == '-other-':
# options['private_css'] = self._css_entry.get() or 'default'
#else:
# options['private_css'] = self._private_css_var.get() or 'default'
return options
def _go(self, *e):
if len(self._module_list.get(0,'end')) == 0:
self._root.bell()
return
if self._progress[0] != None:
self._cancel[0] = 1
return
# Construct the argument list for document().
opts = self._getopts()
self._progress[0] = 0.0
self._cancel[0] = 0
args = (opts, self._cancel, self._progress)
# Clear the messages window.
self._messages['state'] = 'normal'
self._messages.delete('0.0', 'end')
self._messages['state'] = 'disabled'
self._logger.clear()
# Restore the module list. This will force re-loading of
# anything that we're documenting.
for m in sys.modules.keys():
if m not in self._old_modules:
del sys.modules[m]
# [xx] Reset caches??
# Start documenting
start_new_thread(document, args)
# Start the progress bar.
self._go_button['text'] = 'Stop'
self._afterid += 1
dt = 300 # How often to update, in milliseconds
self._update(dt, self._afterid)
def _update_messages(self):
while 1:
level, data = self._logger.read()
if data is None: break
self._messages['state'] = 'normal'
if level == 'header':
self._messages.insert('end', data, 'header')
elif level == 'uline':
self._messages.insert('end', data, 'uline header')
elif level >= log.ERROR:
data= data.rstrip()+'\n\n'
self._messages.insert('end', data, 'guierror')
elif level >= log.DOCSTRING_WARNING:
data= data.rstrip()+'\n\n'
self._messages.insert('end', data, 'warning')
elif log >= log.INFO:
data= data.rstrip()+'\n\n'
self._messages.insert('end', data, 'message')
# if data == '\n':
# if self._last_tag != 'header2':
# self._messages.insert('end', '\n', self._last_tag)
# elif data == '='*75:
# if self._messages.get('end-3c', 'end') == '\n\n\n':
# self._messages.delete('end-1c')
# self._in_header = 1
# self._messages.insert('end', ' '*75, 'uline header')
# self._last_tag = 'header'
# elif data == '-'*75:
# self._in_header = 0
# self._last_tag = 'header2'
# elif self._in_header:
# self._messages.insert('end', data, 'header')
# self._last_tag = 'header'
# elif re.match(r'\s*(L\d+:|-)?\s*Warning: ', data):
# self._messages.insert('end', data, 'warning')
# self._last_tag = 'warning'
# else:
# self._messages.insert('end', data, 'error')
# self._last_tag = 'error'
self._messages['state'] = 'disabled'
self._messages.yview('end')
def _update(self, dt, id):
if self._root is None: return
if self._progress[0] is None: return
if id != self._afterid: return
# Update the messages box
self._update_messages()
# Update the progress bar.
if self._progress[0] == 'done': p = self._W + DX
elif self._progress[0] == 'cancel': p = -5
else: p = DX + self._W * self._progress[0]
self._canvas.coords(self._r1, DX+1, DY+1, p, self._H+1)
self._canvas.coords(self._r2, DX, DY, p-1, self._H)
self._canvas.coords(self._r3, DX+1, DY+1, p, self._H+1)
# Are we done?
if self._progress[0] in ('done', 'cancel'):
if self._progress[0] == 'cancel': self._root.bell()
self._go_button['text'] = 'Start'
self._progress[0] = None
return
self._root.after(dt, self._update, dt, id)
def _new(self, *e):
self._module_list.delete(0, 'end')
self._name_entry.delete(0, 'end')
self._url_entry.delete(0, 'end')
self._docformat_var.set('epytext')
self._inheritance_var.set('grouped')
self._introspect_or_parse_var.set('both')
self._out_entry.delete(0, 'end')
self._module_entry.delete(0, 'end')
self._css_entry.delete(0, 'end')
self._help_entry.delete(0, 'end')
self._frames_var.set(1)
self._private_var.set(1)
self._imports_var.set(0)
self._css_var.set('default')
#self._private_css_var.set('default')
self._help_var.set('default')
self._filename = None
self._init_dir = None
def _open(self, *e):
title = 'Open project'
ftypes = [('Project file', '.prj'),
('All files', '*')]
filename = askopenfilename(filetypes=ftypes, title=title,
defaultextension='.css')
if not filename: return
self.open(filename)
def open(self, prjfile):
from epydoc.docwriter.html_css import STYLESHEETS
self._filename = prjfile
try:
opts = load(open(prjfile, 'r'))
modnames = list(opts.get('modules', []))
modnames.sort()
self._module_list.delete(0, 'end')
for name in modnames:
self.add_module(name)
self._module_entry.delete(0, 'end')
self._name_entry.delete(0, 'end')
if opts.get('prj_name'):
self._name_entry.insert(0, opts['prj_name'])
self._url_entry.delete(0, 'end')
if opts.get('prj_url'):
self._url_entry.insert(0, opts['prj_url'])
self._docformat_var.set(opts.get('docformat', 'epytext'))
self._inheritance_var.set(opts.get('inheritance', 'grouped'))
self._introspect_or_parse_var.set(
opts.get('introspect_or_parse', 'both'))
self._help_entry.delete(0, 'end')
if opts.get('help') is None:
self._help_var.set('default')
else:
self._help_var.set('-other-')
self._help_entry.insert(0, opts.get('help'))
self._out_entry.delete(0, 'end')
self._out_entry.insert(0, opts.get('target', 'html'))
self._frames_var.set(opts.get('frames', 1))
self._private_var.set(opts.get('private', 1))
self._imports_var.set(opts.get('show_imports', 0))
self._css_entry.delete(0, 'end')
if opts.get('css', 'default') in STYLESHEETS.keys():
self._css_var.set(opts.get('css', 'default'))
else:
self._css_var.set('-other-')
self._css_entry.insert(0, opts.get('css', 'default'))
#if opts.get('private_css', 'default') in STYLESHEETS.keys():
# self._private_css_var.set(opts.get('private_css', 'default'))
#else:
# self._private_css_var.set('-other-')
# self._css_entry.insert(0, opts.get('private_css', 'default'))
except Exception, e:
log.error('Error opening %s: %s' % (prjfile, e))
self._root.bell()
def _save(self, *e):
if self._filename is None: return self._saveas()
try:
opts = self._getopts()
dump(opts, open(self._filename, 'w'))
except Exception, e:
if self._filename is None:
log.error('Error saving: %s' % e)
else:
log.error('Error saving %s: %s' % (self._filename, e))
self._root.bell()
def _saveas(self, *e):
title = 'Save project as'
ftypes = [('Project file', '.prj'), ('All files', '*')]
filename = asksaveasfilename(filetypes=ftypes, title=title,
defaultextension='.prj')
if not filename: return
self._filename = filename
self._save()
def _version():
"""
Display the version information, and exit.
@rtype: C{None}
"""
import epydoc
print "Epydoc version %s" % epydoc.__version__
sys.exit(0)
# At some point I could add:
# --show-messages, --hide-messages
# --show-options, --hide-options
def _usage():
print
print 'Usage: epydocgui [OPTIONS] [FILE.prj | MODULES...]'
print
print ' FILE.prj An epydoc GUI project file.'
print ' MODULES... A list of Python modules to document.'
print ' -V, --version Print the version of epydoc.'
print ' -h, -?, --help, --usage Display this usage message'
print ' --debug Do not suppress error messages'
print
sys.exit(0)
def _error(s):
s = '%s; run "%s -h" for usage' % (s, os.path.basename(sys.argv[0]))
if len(s) > 80:
i = s.rfind(' ', 0, 80)
if i>0: s = s[:i]+'\n'+s[i+1:]
print >>sys.stderr, s
sys.exit(1)
def gui():
global DEBUG
sys.stderr = sys.__stderr__
projects = []
modules = []
for arg in sys.argv[1:]:
if arg[0] == '-':
if arg != '-V': arg = arg.lower()
if arg in ('-h', '--help', '-?', '--usage'): _usage()
elif arg in ('-V', '--version'): _version()
elif arg in ('--debug',): DEBUG = 1
else:
_error('Unknown parameter %r' % arg)
elif arg[-4:] == '.prj': projects.append(arg)
else: modules.append(arg)
if len(projects) > 1:
_error('Too many projects')
if len(projects) == 1:
if len(modules) > 0:
_error('You must specify either a project or a list of modules')
if not os.path.exists(projects[0]):
_error('Cannot open project file %s' % projects[0])
gui = EpydocGUI()
gui.open(projects[0])
gui.mainloop()
else:
gui = EpydocGUI()
for module in modules: gui.add_module(module, check=1)
gui.mainloop()
if __name__ == '__main__': gui()