# Licensed under a 3-clause BSD style license - see LICENSE.rstimporterrnoimporthttp.serverimportosimportrandomimportsocketserverfrom..importutilfrom..consoleimportlogfrom.importCommand
[docs]defrandom_ports(port,n):"""Generate a list of n random ports near the given port. The first 5 ports will be sequential, and the remaining n-5 will be randomly selected in the range [port-2*n, port+2*n]. """ifport!=0:yieldportelse:port=8080foriinrange(min(5,n)):yieldport+iforiinrange(n-5):yieldmax(1,port+random.randint(-2*n,2*n))
[docs]defcreate_httpd(handler_cls,port=0):# Create a server that allows address reuseclassMyTCPServer(socketserver.ThreadingTCPServer):allow_reuse_address=Truedaemon_threads=Trueforportinrandom_ports(port,5):try:httpd=MyTCPServer(("",port),handler_cls)base_url=f"http://127.0.0.1:{port}/"breakexceptOSErrorase:ife.errno==errno.EADDRINUSE:continueelse:raiseelse:raiseutil.UserError("Failed to find an unused port for serving web pages")returnhttpd,base_url
[docs]defsetup_arguments(cls,subparsers):parser=subparsers.add_parser("preview",help="Preview the results using a local web server",description="Preview the results using a local web server",)parser.add_argument("--port","-p",type=int,default=0,help="Port to run webserver on. [8080]")parser.add_argument("--browser","-b",action="store_true",help="Open in webbrowser")parser.add_argument('--html-dir','-o',default=None,help=("Optional output directory. Default is 'html_dir' from asv config"),)parser.set_defaults(func=cls.run_from_args)returnparser
[docs]defrun(cls,conf,port=0,browser=False):os.chdir(conf.html_dir)classHandler(http.server.SimpleHTTPRequestHandler):deftranslate_path(self,path):path=http.server.SimpleHTTPRequestHandler.translate_path(self,path)returnutil.long_path(path)httpd,base_url=create_httpd(Handler,port=port)log.info(f"Serving at {base_url}")ifbrowser:importwebbrowserwebbrowser.open(base_url)log.info("Press ^C to abort\n")try:httpd.serve_forever()exceptKeyboardInterrupt:return