1import os 2import threading 3import time 4import traceback 5 6from werkzeug.serving import run_simple 7from werkzeug.serving import WSGIRequestHandler 8 9from lektor.admin import WebAdmin 10from lektor.builder import Builder 11from lektor.builder import process_extra_flags 12from lektor.db import Database 13from lektor.reporter import CliReporter 14from lektor.utils import portable_popen 15from lektor.watcher import Watcher 16 17 18_os_alt_seps = list( 19 sep for sep in [os.path.sep, os.path.altsep] if sep not in (None, "/") 20) 21 22 23class SilentWSGIRequestHandler(WSGIRequestHandler): 24 def log(self, type, message, *args): 25 pass 26 27 28class BackgroundBuilder(threading.Thread): 29 def __init__(self, env, output_path, prune=True, verbosity=0, extra_flags=None): 30 threading.Thread.__init__(self) 31 watcher = Watcher(env, output_path) 32 watcher.observer.start() 33 self.env = env 34 self.watcher = watcher 35 self.output_path = output_path 36 self.prune = prune 37 self.verbosity = verbosity 38 self.last_build = time.time() 39 self.extra_flags = extra_flags 40 41 def build(self, update_source_info_first=False): 42 try: 43 db = Database(self.env) 44 builder = Builder( 45 db.new_pad(), self.output_path, extra_flags=self.extra_flags 46 ) 47 if update_source_info_first: 48 builder.update_all_source_infos() 49 builder.build_all() 50 if self.prune: 51 builder.prune() 52 except Exception: 53 traceback.print_exc() 54 else: 55 self.last_build = time.time() 56 57 def run(self): 58 with CliReporter(self.env, verbosity=self.verbosity): 59 self.build(update_source_info_first=True) 60 for ts, _, _ in self.watcher: 61 if self.last_build is None or ts > self.last_build: 62 self.build() 63 64 65class DevTools(object): 66 """This provides extra helpers for launching tools such as webpack.""" 67 68 def __init__(self, env): 69 self.watcher = None 70 self.env = env 71 72 def start(self): 73 if self.watcher is not None: 74 return 75 from lektor import admin 76 77 admin = os.path.dirname(admin.__file__) 78 portable_popen(["npm", "install", "."], cwd=admin).wait() 79 80 self.watcher = portable_popen( 81 [os.path.join(admin, "node_modules/.bin/webpack"), "--watch"], 82 cwd=os.path.join(admin, "static"), 83 ) 84 85 def stop(self): 86 if self.watcher is None: 87 return 88 self.watcher.kill() 89 self.watcher.wait() 90 self.watcher = None 91 92 93def browse_to_address(addr): 94 import webbrowser 95 96 def browse(): 97 time.sleep(1) 98 webbrowser.open("http://%s:%s" % addr) 99 100 t = threading.Thread(target=browse) 101 t.setDaemon(True) 102 t.start() 103 104 105def run_server( 106 bindaddr, 107 env, 108 output_path, 109 prune=True, 110 verbosity=0, 111 lektor_dev=False, 112 ui_lang="en", 113 browse=False, 114 extra_flags=None, 115): 116 """This runs a server but also spawns a background process. It's 117 not safe to call this more than once per python process! 118 """ 119 wz_as_main = os.environ.get("WERKZEUG_RUN_MAIN") == "true" 120 in_main_process = not lektor_dev or wz_as_main 121 extra_flags = process_extra_flags(extra_flags) 122 123 if in_main_process: 124 background_builder = BackgroundBuilder( 125 env, 126 output_path=output_path, 127 prune=prune, 128 verbosity=verbosity, 129 extra_flags=extra_flags, 130 ) 131 background_builder.setDaemon(True) 132 background_builder.start() 133 env.plugin_controller.emit( 134 "server-spawn", bindaddr=bindaddr, extra_flags=extra_flags 135 ) 136 137 app = WebAdmin( 138 env, 139 output_path=output_path, 140 verbosity=verbosity, 141 debug=lektor_dev, 142 ui_lang=ui_lang, 143 extra_flags=extra_flags, 144 ) 145 146 dt = None 147 if lektor_dev and not wz_as_main: 148 dt = DevTools(env) 149 dt.start() 150 151 if browse: 152 browse_to_address(bindaddr) 153 154 try: 155 return run_simple( 156 bindaddr[0], 157 bindaddr[1], 158 app, 159 use_debugger=True, 160 threaded=True, 161 use_reloader=lektor_dev, 162 request_handler=not lektor_dev 163 and SilentWSGIRequestHandler 164 or WSGIRequestHandler, 165 ) 166 finally: 167 if dt is not None: 168 dt.stop() 169 if in_main_process: 170 env.plugin_controller.emit("server-stop") 171