зеркало из https://github.com/microsoft/clang-1.git
Add initial implementation of scan-view
- Web based interface to static analyzer. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@56375 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Родитель
e43038ec3d
Коммит
e33d3682b6
|
@ -0,0 +1,159 @@
|
|||
"""Methods for reporting bugs."""
|
||||
|
||||
import subprocess, sys, os
|
||||
|
||||
__all__ = ['BugReport', 'getReporters']
|
||||
|
||||
# Collect information about a bug.
|
||||
|
||||
class BugReport:
|
||||
def __init__(self, title, description, files):
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.files = files
|
||||
|
||||
# Reporter interfaces.
|
||||
|
||||
import os
|
||||
|
||||
import email, mimetypes, smtplib
|
||||
from email import encoders
|
||||
from email.message import Message
|
||||
from email.mime.base import MIMEBase
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
class EmailReporter:
|
||||
def getName(self):
|
||||
return 'Email'
|
||||
|
||||
def getParameterNames(self):
|
||||
return ['To', 'From', 'SMTP Server', 'SMTP Port']
|
||||
|
||||
# Lifted from python email module examples.
|
||||
def attachFile(self, outer, path):
|
||||
# Guess the content type based on the file's extension. Encoding
|
||||
# will be ignored, although we should check for simple things like
|
||||
# gzip'd or compressed files.
|
||||
ctype, encoding = mimetypes.guess_type(path)
|
||||
if ctype is None or encoding is not None:
|
||||
# No guess could be made, or the file is encoded (compressed), so
|
||||
# use a generic bag-of-bits type.
|
||||
ctype = 'application/octet-stream'
|
||||
maintype, subtype = ctype.split('/', 1)
|
||||
if maintype == 'text':
|
||||
fp = open(path)
|
||||
# Note: we should handle calculating the charset
|
||||
msg = MIMEText(fp.read(), _subtype=subtype)
|
||||
fp.close()
|
||||
else:
|
||||
fp = open(path, 'rb')
|
||||
msg = MIMEBase(maintype, subtype)
|
||||
msg.set_payload(fp.read())
|
||||
fp.close()
|
||||
# Encode the payload using Base64
|
||||
encoders.encode_base64(msg)
|
||||
# Set the filename parameter
|
||||
msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(path))
|
||||
outer.attach(msg)
|
||||
|
||||
def fileReport(self, report, parameters):
|
||||
mainMsg = """\
|
||||
BUG REPORT
|
||||
---
|
||||
Title: %s
|
||||
Description: %s
|
||||
"""%(report.title, report.description)
|
||||
|
||||
if not parameters.get('To') or not parameters.get('From'):
|
||||
raise ValueError,'Invalid email parameters.'
|
||||
|
||||
msg = MIMEMultipart()
|
||||
msg['Subject'] = 'BUG REPORT: %s'%(report.title)
|
||||
# FIXME: Get config parameters
|
||||
msg['To'] = parameters.get('To')
|
||||
msg['From'] = parameters.get('From')
|
||||
msg.preamble = mainMsg
|
||||
|
||||
msg.attach(MIMEText(mainMsg, _subtype='text/plain'))
|
||||
for file in report.files:
|
||||
self.attachFile(msg, file)
|
||||
|
||||
s = smtplib.SMTP(host=parameters.get('SMTP Server'),
|
||||
port=parameters.get('SMTP Port'))
|
||||
s.sendmail(msg['From'], msg['To'], msg.as_string())
|
||||
s.close()
|
||||
|
||||
class BugzillaReporter:
|
||||
def getName(self):
|
||||
return 'Bugzilla'
|
||||
|
||||
def getParameterNames(self):
|
||||
return ['URL', 'Product']
|
||||
|
||||
def fileReport(self, report, parameters):
|
||||
raise NotImplementedError
|
||||
|
||||
class RadarReporter:
|
||||
@staticmethod
|
||||
def isAvailable():
|
||||
# FIXME: Find this .scpt better
|
||||
path = os.path.join(os.path.dirname(__file__),'Resources/GetRadarVersion.scpt')
|
||||
try:
|
||||
p = subprocess.Popen(['osascript',path],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
except:
|
||||
return False
|
||||
data,err = p.communicate()
|
||||
res = p.wait()
|
||||
# FIXME: Check version? Check for no errors?
|
||||
return res == 0
|
||||
|
||||
def getName(self):
|
||||
return 'Radar'
|
||||
|
||||
def getParameterNames(self):
|
||||
return ['Component', 'Component Version']
|
||||
|
||||
def fileReport(self, report, parameters):
|
||||
component = parameters.get('Component', '')
|
||||
componentVersion = parameters.get('Component Version', '')
|
||||
personID = ""
|
||||
diagnosis = ""
|
||||
config = ""
|
||||
|
||||
if not component.strip():
|
||||
component = 'Bugs found by clang Analyzer'
|
||||
if not componentVersion.strip():
|
||||
componentVersion = 'X'
|
||||
|
||||
script = os.path.join(os.path.dirname(__file__),'Resources/FileRadar.scpt')
|
||||
args = ['osascript', script, component, componentVersion, personID, report.title,
|
||||
report.description, diagnosis, config] + map(os.path.abspath, report.files)
|
||||
# print >>sys.stderr, args
|
||||
try:
|
||||
p = subprocess.Popen(args,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
except:
|
||||
print >>sys.stderr, '%s: SERVER: radar failed'%(sys.argv[0],)
|
||||
sys.print_exc()
|
||||
raise
|
||||
data, err = p.communicate()
|
||||
# print >>sys.stderr, '%s: SERVER: radar report: "%s" "%s"'%(sys.argv[0],data, err)
|
||||
res = p.wait()
|
||||
# print >>sys.stderr, '%s: SERVER: radar report res: %d'%(sys.argv[0],res,)
|
||||
|
||||
if res:
|
||||
raise RuntimeError,'Radar submission failed.'
|
||||
|
||||
return data.replace('\n','\n<br>')
|
||||
|
||||
###
|
||||
|
||||
def getReporters():
|
||||
reporters = []
|
||||
if RadarReporter.isAvailable():
|
||||
reporters.append(RadarReporter())
|
||||
reporters.extend([EmailReporter(), BugzillaReporter()])
|
||||
return reporters
|
||||
|
Двоичный файл не отображается.
|
@ -0,0 +1,388 @@
|
|||
import BaseHTTPServer
|
||||
import SimpleHTTPServer
|
||||
import os
|
||||
import sys
|
||||
import urllib, urlparse
|
||||
import posixpath
|
||||
import StringIO
|
||||
import re
|
||||
import shutil
|
||||
import threading
|
||||
import time
|
||||
import socket
|
||||
|
||||
from Reporter import BugReport
|
||||
|
||||
# Keys replaced by server.
|
||||
|
||||
kReportBugRE = re.compile('<!-- REPORTBUG id="report-(.*)\\.html" -->')
|
||||
kReportBugRepl = '<a class="ReportBugLink" href="report/\\1">Report Bug</a>'
|
||||
kBugKeyValueRE = re.compile('<!-- BUG([^ ]*) (.*) -->')
|
||||
|
||||
###
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
__all__ = ["create_server"]
|
||||
|
||||
class ReporterThread(threading.Thread):
|
||||
def __init__(self, report, reporter, parameters, server):
|
||||
threading.Thread.__init__(self)
|
||||
self.report = report
|
||||
self.server = server
|
||||
self.reporter = reporter
|
||||
self.parameters = parameters
|
||||
self.status = None
|
||||
|
||||
def run(self):
|
||||
result = None
|
||||
try:
|
||||
if self.server.options.debug:
|
||||
print >>sys.stderr, "%s: SERVER: submitting bug."%(sys.argv[0],)
|
||||
result = self.reporter.fileReport(self.report, self.parameters)
|
||||
time.sleep(3)
|
||||
if self.server.options.debug:
|
||||
print >>sys.stderr, "%s: SERVER: submission complete."%(sys.argv[0],)
|
||||
except Exception,e:
|
||||
s = StringIO.StringIO()
|
||||
import traceback
|
||||
print >>s,'Submission Failed<br><pre>'
|
||||
traceback.print_exc(e,file=s)
|
||||
print >>s,'</pre>'
|
||||
self.status = s.getvalue()
|
||||
return
|
||||
|
||||
s = StringIO.StringIO()
|
||||
print >>s, 'Submission Complete!'
|
||||
print >>s, '<hr>'
|
||||
if result is not None:
|
||||
print >>s, result
|
||||
self.status = s.getvalue()
|
||||
|
||||
class ScanViewServer(BaseHTTPServer.HTTPServer):
|
||||
def __init__(self, address, handler, root, reporters, options):
|
||||
BaseHTTPServer.HTTPServer.__init__(self, address, handler)
|
||||
self.root = root
|
||||
self.reporters = reporters
|
||||
self.options = options
|
||||
self.halted = False
|
||||
|
||||
def halt(self):
|
||||
self.halted = True
|
||||
if self.options.debug:
|
||||
print >>sys.stderr, "%s: SERVER: halting." % (sys.argv[0],)
|
||||
|
||||
def serve_forever(self):
|
||||
while not self.halted:
|
||||
if self.options.debug > 1:
|
||||
print >>sys.stderr, "%s: SERVER: waiting..." % (sys.argv[0],)
|
||||
try:
|
||||
self.handle_request()
|
||||
except OSError,e:
|
||||
print 'OSError',e.errno
|
||||
|
||||
def handle_error(self, request, client_address):
|
||||
# Ignore socket errors
|
||||
info = sys.exc_info()
|
||||
if info and isinstance(info[1], socket.error):
|
||||
if self.options.debug > 1:
|
||||
print >>sys.stderr, "%s: SERVER: ignored socket error." % (sys.argv[0],)
|
||||
return
|
||||
BaseHTTPServer.HTTPServer.handle_error(request, client_address)
|
||||
|
||||
# Borrowed from Quixote, with simplifications.
|
||||
def parse_query(qs, fields=None):
|
||||
if fields is None:
|
||||
fields = {}
|
||||
for chunk in filter(None, qs.split('&')):
|
||||
if '=' not in chunk:
|
||||
name = chunk
|
||||
value = ''
|
||||
else:
|
||||
name, value = chunk.split('=', 1)
|
||||
name = urllib.unquote(name.replace('+', ' '))
|
||||
value = urllib.unquote(value.replace('+', ' '))
|
||||
fields[name] = value
|
||||
return fields
|
||||
|
||||
class ScanViewRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
||||
server_version = "ScanViewServer/" + __version__
|
||||
|
||||
def do_HEAD(self):
|
||||
try:
|
||||
SimpleHTTPServer.SimpleHTTPRequestHandler.do_HEAD(self)
|
||||
except Exception,e:
|
||||
self.handle_exception(e)
|
||||
|
||||
def do_GET(self):
|
||||
try:
|
||||
SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
|
||||
except Exception,e:
|
||||
self.handle_exception(e)
|
||||
|
||||
def do_POST(self):
|
||||
"""Serve a POST request."""
|
||||
try:
|
||||
length = self.headers.getheader('content-length') or "0"
|
||||
try:
|
||||
length = int(length)
|
||||
except:
|
||||
length = 0
|
||||
content = self.rfile.read(length)
|
||||
fields = parse_query(content)
|
||||
f = self.send_head(fields)
|
||||
if f:
|
||||
self.copyfile(f, self.wfile)
|
||||
f.close()
|
||||
except Exception,e:
|
||||
self.handle_exception(e)
|
||||
|
||||
def log_message(self, format, *args):
|
||||
if self.server.options.debug:
|
||||
sys.stderr.write("%s: SERVER: %s - - [%s] %s\n" %
|
||||
(sys.argv[0],
|
||||
self.address_string(),
|
||||
self.log_date_time_string(),
|
||||
format%args))
|
||||
|
||||
def load_report(self, report):
|
||||
path = os.path.join(self.server.root, 'report-%s.html'%report)
|
||||
data = open(path).read()
|
||||
keys = {}
|
||||
for item in kBugKeyValueRE.finditer(data):
|
||||
k,v = item.groups()
|
||||
keys[k] = v
|
||||
return keys
|
||||
|
||||
def handle_exception(self, exc):
|
||||
import traceback
|
||||
s = StringIO.StringIO()
|
||||
print >>s, "INTERNAL ERROR\n"
|
||||
traceback.print_exc(exc, s)
|
||||
f = self.send_string(s.getvalue(), 'text/plain')
|
||||
if f:
|
||||
self.copyfile(f, self.wfile)
|
||||
f.close()
|
||||
|
||||
def send_internal_error(self, message):
|
||||
return self.send_string('ERROR: %s'%(message,), 'text/plain')
|
||||
|
||||
def send_report_submit(self):
|
||||
s = StringIO.StringIO()
|
||||
report = self.fields.get('report')
|
||||
reporter = self.fields.get('reporter')
|
||||
title = self.fields.get('title')
|
||||
description = self.fields.get('description')
|
||||
|
||||
# Get the reporter and parameters.
|
||||
reporter = self.server.reporters[int(reporter)]
|
||||
parameters = {}
|
||||
for o in reporter.getParameterNames():
|
||||
name = '%s_%s'%(reporter.getName(),o)
|
||||
parameters[o] = self.fields.get(name)
|
||||
|
||||
# Create the report.
|
||||
path = os.path.join(self.server.root, 'report-%s.html'%report)
|
||||
files = [path]
|
||||
br = BugReport(title, description, files)
|
||||
|
||||
# Send back an initial response and wait for the report to
|
||||
# finish.
|
||||
initial_response = """<html>
|
||||
<head><title>Filing Report</title></head>
|
||||
<body>
|
||||
<h1>Filing Report</h1>
|
||||
<b>Report</b>: %(report)s<br>
|
||||
<b>Title</b>: %(title)s<br>
|
||||
<b>Description</b>: %(description)s<br>
|
||||
<hr>
|
||||
Submission in progress."""%locals()
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", 'text/html')
|
||||
self.end_headers()
|
||||
self.wfile.write(initial_response)
|
||||
self.wfile.flush()
|
||||
|
||||
# Kick off a reporting thread.
|
||||
t = ReporterThread(br, reporter, parameters, self.server)
|
||||
t.start()
|
||||
|
||||
# Wait for thread to die...
|
||||
while t.isAlive():
|
||||
self.wfile.write('.')
|
||||
self.wfile.flush()
|
||||
time.sleep(.25)
|
||||
submitStatus = t.status
|
||||
|
||||
end_response = """<br>
|
||||
%(submitStatus)s
|
||||
<hr>
|
||||
<a href="/">Home</a>
|
||||
</body>
|
||||
</html>
|
||||
"""%locals()
|
||||
return self.send_string(end_response, headers=False)
|
||||
|
||||
def send_report(self, report):
|
||||
try:
|
||||
keys = self.load_report(report)
|
||||
except IOError:
|
||||
return self.send_internal_error('Invalid report.')
|
||||
|
||||
initialTitle = keys.get('DESC','')
|
||||
initialDescription = 'Bug generated by the clang static analyzer.'
|
||||
|
||||
keysAndValues = '\n'.join(['<b>%s</b>: %s<br>'%(k,v) for k,v in keys.items()])
|
||||
reporterSelections = []
|
||||
reporterOptions = []
|
||||
|
||||
for i,r in enumerate(self.server.reporters):
|
||||
reporterSelections.append('<option value="%d">%s</option>'%(i,r.getName()))
|
||||
options = '\n'.join(['%s: <input type="text" name="%s_%s"><br>'%(o,r.getName(),o) for o in r.getParameterNames()])
|
||||
if i==0:
|
||||
display = 'inline'
|
||||
else:
|
||||
display = 'none'
|
||||
reporterOptions.append('<div id="%sReporterOptions" style="display:%s">\n<h3>%s Options</h3>%s\n</div>'%(r.getName(),display,r.getName(),options))
|
||||
reporterSelections = '\n'.join(reporterSelections)
|
||||
reporterOptionsDivs = '\n'.join(reporterOptions)
|
||||
reportersArray = '[%s]'%(','.join([`r.getName()` for r in self.server.reporters]))
|
||||
|
||||
result = """<html>
|
||||
<head>
|
||||
<title>File Report</title>
|
||||
</head>
|
||||
<script language="javascript" type="text/javascript">
|
||||
var reporters = %(reportersArray)s;
|
||||
function updateReporterOptions() {
|
||||
index = document.getElementById('reporter').selectedIndex;
|
||||
for (var i=0; i < reporters.length; ++i) {
|
||||
o = document.getElementById(reporters[i] + "ReporterOptions");
|
||||
if (i == index) {
|
||||
o.style.display = "inline";
|
||||
} else {
|
||||
o.style.display = "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<body>
|
||||
<h1>File Report</h1>
|
||||
%(keysAndValues)s
|
||||
<hr>
|
||||
<form name="form" action="/report_submit" method="post">
|
||||
Title:
|
||||
<input type="text" name="title" size="50" value="%(initialTitle)s"><br>
|
||||
Description:<br>
|
||||
<textarea rows="10" cols="80" name="description">
|
||||
%(initialDescription)s
|
||||
</textarea><br>
|
||||
<hr>
|
||||
<input type="hidden" name="report" value="%(report)s">
|
||||
Method: <select id="reporter" name="reporter" onChange="updateReporterOptions()">
|
||||
%(reporterSelections)s
|
||||
</select><br>
|
||||
<hr>
|
||||
%(reporterOptionsDivs)s
|
||||
<hr>
|
||||
<input type="submit" name="Submit" value="Submit">
|
||||
</form>
|
||||
</body>
|
||||
</html>"""%locals()
|
||||
return self.send_string(result)
|
||||
|
||||
def send_head(self, fields=None):
|
||||
if fields is None:
|
||||
fields = {}
|
||||
self.fields = fields
|
||||
|
||||
o = urlparse.urlparse(self.path)
|
||||
self.fields = parse_query(o.query, fields)
|
||||
path = posixpath.normpath(urllib.unquote(o.path))
|
||||
|
||||
# Split the components and strip the root prefix.
|
||||
components = path.split('/')[1:]
|
||||
|
||||
# Special case some top-level entries.
|
||||
if components:
|
||||
name = components[0]
|
||||
if name=='quit':
|
||||
self.server.halt()
|
||||
return self.send_string('Goodbye.', 'text/plain')
|
||||
elif name=='report':
|
||||
if len(components)==2:
|
||||
return self.send_report(components[1])
|
||||
else:
|
||||
return self.send_404()
|
||||
elif name=='report_submit':
|
||||
if len(components)==1:
|
||||
return self.send_report_submit()
|
||||
else:
|
||||
return self.send_404()
|
||||
|
||||
# Match directory entries.
|
||||
if components[-1] == '':
|
||||
components[-1] = 'index.html'
|
||||
|
||||
path = posixpath.join(self.server.root, '/'.join(components))
|
||||
if self.server.options.debug > 1:
|
||||
print >>sys.stderr, '%s: SERVER: sending path "%s"'%(sys.argv[0],
|
||||
path)
|
||||
return self.send_path(path)
|
||||
|
||||
def send_404(self):
|
||||
self.send_error(404, "File not found")
|
||||
return None
|
||||
|
||||
def send_path(self, path):
|
||||
ctype = self.guess_type(path)
|
||||
if ctype.startswith('text/'):
|
||||
# Patch file instead
|
||||
return self.send_patched_file(path, ctype)
|
||||
else:
|
||||
mode = 'rb'
|
||||
try:
|
||||
f = open(path, mode)
|
||||
except IOError:
|
||||
return self.send_404()
|
||||
return self.send_file(f, ctype)
|
||||
|
||||
def send_file(self, f, ctype):
|
||||
# Patch files to add links, but skip binary files.
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", ctype)
|
||||
fs = os.fstat(f.fileno())
|
||||
self.send_header("Content-Length", str(fs[6]))
|
||||
self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
|
||||
self.end_headers()
|
||||
return f
|
||||
|
||||
def send_string(self, s, ctype='text/html', headers=True, mtime=None):
|
||||
if headers:
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", ctype)
|
||||
self.send_header("Content-Length", str(len(s)))
|
||||
if mtime:
|
||||
self.send_header("Last-Modified", self.date_time_string(mtime))
|
||||
self.end_headers()
|
||||
return StringIO.StringIO(s)
|
||||
|
||||
def send_patched_file(self, path, ctype):
|
||||
f = open(path,'r')
|
||||
fs = os.fstat(f.fileno())
|
||||
data = f.read()
|
||||
data = kReportBugRE.sub(kReportBugRepl, data)
|
||||
return self.send_string(data, ctype, mtime=fs.st_mtime)
|
||||
|
||||
|
||||
def create_server(options, root):
|
||||
import Reporter
|
||||
|
||||
reporters = Reporter.getReporters()
|
||||
|
||||
return ScanViewServer((options.host, options.port),
|
||||
ScanViewRequestHandler,
|
||||
root,
|
||||
reporters,
|
||||
options)
|
|
@ -0,0 +1,96 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""The clang static analyzer results viewer.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import thread
|
||||
import time
|
||||
import urllib
|
||||
import webbrowser
|
||||
|
||||
# How long to wait for server to start.
|
||||
kSleepTimeout = .05
|
||||
kMaxSleeps = 100
|
||||
|
||||
# Default server parameters
|
||||
|
||||
kDefaultHost = 'localhost'
|
||||
kDefaultPort = 8181
|
||||
|
||||
###
|
||||
|
||||
def url_is_up(url):
|
||||
try:
|
||||
o = urllib.urlopen(url)
|
||||
except IOError:
|
||||
return False
|
||||
o.close()
|
||||
return True
|
||||
|
||||
def start_browser(options):
|
||||
import urllib, webbrowser
|
||||
|
||||
url = 'http://%s:%d'%(options.host, options.port)
|
||||
|
||||
# Wait for server to start...
|
||||
if options.debug:
|
||||
sys.stderr.write('%s: Waiting for server.' % sys.argv[0])
|
||||
sys.stderr.flush()
|
||||
for i in range(kMaxSleeps):
|
||||
if url_is_up(url):
|
||||
break
|
||||
if options.debug:
|
||||
sys.stderr.write('.')
|
||||
sys.stderr.flush()
|
||||
time.sleep(kSleepTimeout)
|
||||
else:
|
||||
print >>sys.stderr,'WARNING: Unable to detect that server started.'
|
||||
|
||||
if options.debug:
|
||||
print >>sys.stderr,'%s: Starting webbrowser...' % sys.argv[0]
|
||||
webbrowser.open(url)
|
||||
|
||||
def run(options, root):
|
||||
import ScanView
|
||||
try:
|
||||
if options.debug:
|
||||
print >>sys.stderr,'%s: SERVER: starting %s:%d'%(sys.argv[0],
|
||||
options.host,
|
||||
options.port)
|
||||
httpd = ScanView.create_server(options, root)
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
def main():
|
||||
from optparse import OptionParser
|
||||
parser = OptionParser('usage: %prog [options] <results directory>')
|
||||
parser.set_description(__doc__)
|
||||
parser.add_option(
|
||||
'--host', dest="host", default=kDefaultHost, type="string",
|
||||
help="Host interface to listen on. (default=%s)" % kDefaultHost)
|
||||
parser.add_option(
|
||||
'--port', dest="port", default=kDefaultPort, type="int",
|
||||
help="Port to listen on. (default=%s)" % kDefaultPort)
|
||||
parser.add_option("--debug", dest="debug", default=0,
|
||||
action="count",
|
||||
help="Print additional debugging information.")
|
||||
parser.add_option("--no-browser", dest="startBrowser", default=True,
|
||||
action="store_false",
|
||||
help="Don't open a webbrowser on startup.")
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if len(args) != 1:
|
||||
parser.error('invalid number of arguments.')
|
||||
root, = args
|
||||
|
||||
# Kick off thread to wait for server and start web browser, if
|
||||
# requested.
|
||||
if options.startBrowser:
|
||||
t = thread.start_new_thread(start_browser, (options,))
|
||||
|
||||
run(options, root)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Загрузка…
Ссылка в новой задаче