1import time 2import traceback 3from contextlib import contextmanager 4 5import click 6from click import style 7from werkzeug.local import LocalProxy 8from werkzeug.local import LocalStack 9 10from lektor._compat import text_type 11 12 13_reporter_stack = LocalStack() 14_build_buffer_stack = LocalStack() 15 16 17def describe_build_func(func): 18 self = getattr(func, "__self__", None) 19 if self is not None and any( 20 x.__name__ == "BuildProgram" for x in self.__class__.__mro__ 21 ): 22 return self.__class__.__module__ + "." + self.__class__.__name__ 23 return func.__module__ + "." + func.__name__ 24 25 26class Reporter(object): 27 def __init__(self, env, verbosity=0): 28 self.env = env 29 self.verbosity = verbosity 30 31 self.builder_stack = [] 32 self.artifact_stack = [] 33 self.source_stack = [] 34 35 def push(self): 36 _reporter_stack.push(self) 37 38 def pop(self): 39 _reporter_stack.pop() 40 41 def __enter__(self): 42 self.push() 43 return self 44 45 def __exit__(self, exc_type, exc_value, tb): 46 self.pop() 47 48 @property 49 def builder(self): 50 if self.builder_stack: 51 return self.builder_stack[-1] 52 return None 53 54 @property 55 def current_artifact(self): 56 if self.artifact_stack: 57 return self.artifact_stack[-1] 58 return None 59 60 @property 61 def current_source(self): 62 if self.source_stack: 63 return self.source_stack[-1] 64 return None 65 66 @property 67 def show_build_info(self): 68 return self.verbosity >= 1 69 70 @property 71 def show_tracebacks(self): 72 return self.verbosity >= 1 73 74 @property 75 def show_current_artifacts(self): 76 return self.verbosity >= 2 77 78 @property 79 def show_artifact_internals(self): 80 return self.verbosity >= 3 81 82 @property 83 def show_source_internals(self): 84 return self.verbosity >= 3 85 86 @property 87 def show_debug_info(self): 88 return self.verbosity >= 4 89 90 @contextmanager 91 def build(self, activity, builder): 92 now = time.time() 93 self.builder_stack.append(builder) 94 self.start_build(activity) 95 try: 96 yield 97 finally: 98 self.builder_stack.pop() 99 self.finish_build(activity, now) 100 101 def start_build(self, activity): 102 pass 103 104 def finish_build(self, activity, start_time): 105 pass 106 107 @contextmanager 108 def build_artifact(self, artifact, build_func, is_current): 109 now = time.time() 110 self.artifact_stack.append(artifact) 111 self.start_artifact_build(is_current) 112 self.report_build_func(build_func) 113 try: 114 yield 115 finally: 116 self.finish_artifact_build(now) 117 self.artifact_stack.pop() 118 119 def start_artifact_build(self, is_current): 120 pass 121 122 def finish_artifact_build(self, start_time): 123 pass 124 125 def report_failure(self, artifact, exc_info): 126 pass 127 128 def report_build_all_failure(self, failures): 129 pass 130 131 def report_dependencies(self, dependencies): 132 for dep in dependencies: 133 self.report_debug_info("dependency", dep[1]) 134 135 def report_dirty_flag(self, value): 136 pass 137 138 def report_write_source_info(self, info): 139 pass 140 141 def report_prune_source_info(self, source): 142 pass 143 144 def report_sub_artifact(self, artifact): 145 pass 146 147 def report_build_func(self, build_func): 148 pass 149 150 def report_debug_info(self, key, value): 151 pass 152 153 def report_generic(self, message): 154 pass 155 156 def report_pruned_artifact(self, artifact_name): 157 pass 158 159 @contextmanager 160 def process_source(self, source): 161 now = time.time() 162 self.source_stack.append(source) 163 self.enter_source() 164 try: 165 yield 166 finally: 167 self.leave_source(now) 168 self.source_stack.pop() 169 170 def enter_source(self): 171 pass 172 173 def leave_source(self, start_time): 174 pass 175 176 177class NullReporter(Reporter): 178 pass 179 180 181class BufferReporter(Reporter): 182 def __init__(self, env, verbosity=0): 183 Reporter.__init__(self, env, verbosity) 184 self.buffer = [] 185 186 def clear(self): 187 self.buffer = [] 188 189 def get_recorded_dependencies(self): 190 rv = set() 191 for event, data in self.buffer: 192 if event == "debug-info" and data["key"] == "dependency": 193 rv.add(data["value"]) 194 return sorted(rv) 195 196 def get_major_events(self): 197 rv = [] 198 for event, data in self.buffer: 199 if event not in ("debug-info", "dirty-flag", "write-source-info"): 200 rv.append((event, data)) 201 return rv 202 203 def get_failures(self): 204 rv = [] 205 for event, data in self.buffer: 206 if event == "failure": 207 rv.append(data) 208 return rv 209 210 def _emit(self, _event, **extra): 211 self.buffer.append((_event, extra)) 212 213 def start_build(self, activity): 214 self._emit("start-build", activity=activity) 215 216 def finish_build(self, activity, start_time): 217 self._emit("finish-build", activity=activity) 218 219 def start_artifact_build(self, is_current): 220 self._emit( 221 "start-artifact-build", 222 artifact=self.current_artifact, 223 is_current=is_current, 224 ) 225 226 def finish_artifact_build(self, start_time): 227 self._emit("finish-artifact-build", artifact=self.current_artifact) 228 229 def report_build_all_failure(self, failures): 230 self._emit("build-all-failure", failures=failures) 231 232 def report_failure(self, artifact, exc_info): 233 self._emit("failure", artifact=artifact, exc_info=exc_info) 234 235 def report_dirty_flag(self, value): 236 self._emit("dirty-flag", artifact=self.current_artifact, value=value) 237 238 def report_write_source_info(self, info): 239 self._emit("write-source-info", info=info, artifact=self.current_artifact) 240 241 def report_prune_source_info(self, source): 242 self._emit("prune-source-info", source=source) 243 244 def report_build_func(self, build_func): 245 self._emit("build-func", func=describe_build_func(build_func)) 246 247 def report_sub_artifact(self, artifact): 248 self._emit("sub-artifact", artifact=artifact) 249 250 def report_debug_info(self, key, value): 251 self._emit("debug-info", key=key, value=value) 252 253 def report_generic(self, message): 254 self._emit("generic", message=message) 255 256 def enter_source(self): 257 self._emit("enter-source", source=self.current_source) 258 259 def leave_source(self, start_time): 260 self._emit("leave-source", source=self.current_source) 261 262 def report_pruned_artifact(self, artifact_name): 263 self._emit("pruned-artifact", artifact_name=artifact_name) 264 265 266class CliReporter(Reporter): 267 def __init__(self, env, verbosity=0): 268 Reporter.__init__(self, env, verbosity) 269 self.indentation = 0 270 271 def indent(self): 272 self.indentation += 1 273 274 def outdent(self): 275 self.indentation -= 1 276 277 def _write_line(self, text): 278 click.echo(" " * (self.indentation * 2) + text) 279 280 def _write_kv_info(self, key, value): 281 self._write_line("%s: %s" % (key, style(text_type(value), fg="yellow"))) 282 283 def start_build(self, activity): 284 self._write_line(style("Started %s" % activity, fg="cyan")) 285 if not self.show_build_info: 286 return 287 self._write_line(style(" Tree: %s" % self.env.root_path, fg="cyan")) 288 self._write_line( 289 style(" Output path: %s" % self.builder.destination_path, fg="cyan") 290 ) 291 292 def finish_build(self, activity, start_time): 293 self._write_line( 294 style( 295 "Finished %s in %.2f sec" % (activity, time.time() - start_time), 296 fg="cyan", 297 ) 298 ) 299 300 def start_artifact_build(self, is_current): 301 artifact = self.current_artifact 302 if is_current: 303 if not self.show_current_artifacts: 304 return 305 sign = click.style("X", fg="cyan") 306 else: 307 sign = click.style("U", fg="green") 308 self._write_line("%s %s" % (sign, artifact.artifact_name)) 309 310 self.indent() 311 312 def finish_artifact_build(self, start_time): 313 self.outdent() 314 315 def report_build_all_failure(self, failures): 316 self._write_line( 317 click.style( 318 "Error: Build failed with %s failure%s." 319 % (failures, failures != 1 and "s" or ""), 320 fg="red", 321 ) 322 ) 323 324 def report_failure(self, artifact, exc_info): 325 sign = click.style("E", fg="red") 326 err = " ".join( 327 "".join(traceback.format_exception_only(*exc_info[:2])).splitlines() 328 ).strip() 329 self._write_line("%s %s (%s)" % (sign, artifact.artifact_name, err)) 330 331 if not self.show_tracebacks: 332 return 333 334 tb = traceback.format_exception(*exc_info) 335 for line in "".join(tb).splitlines(): 336 if line.startswith("Traceback "): 337 line = click.style(line, fg="red") 338 elif line.startswith(" File "): 339 line = click.style(line, fg="yellow") 340 elif not line.startswith(" "): 341 line = click.style(line, fg="red") 342 self._write_line(" " + line) 343 344 def report_dirty_flag(self, value): 345 if self.show_artifact_internals and (value or self.show_debug_info): 346 self._write_kv_info("forcing sources dirty", value) 347 348 def report_write_source_info(self, info): 349 if self.show_artifact_internals and self.show_debug_info: 350 self._write_kv_info( 351 "writing source info", "%s [%s]" % (info.title_i18n["en"], info.type) 352 ) 353 354 def report_prune_source_info(self, source): 355 if self.show_artifact_internals and self.show_debug_info: 356 self._write_kv_info("pruning source info", source) 357 358 def report_build_func(self, build_func): 359 if self.show_artifact_internals: 360 self._write_kv_info("build program", describe_build_func(build_func)) 361 362 def report_sub_artifact(self, artifact): 363 if self.show_artifact_internals: 364 self._write_kv_info("sub artifact", artifact.artifact_name) 365 366 def report_debug_info(self, key, value): 367 if self.show_debug_info: 368 self._write_kv_info(key, value) 369 370 def report_generic(self, message): 371 self._write_line(style(text_type(message), fg="cyan")) 372 373 def enter_source(self): 374 if not self.show_source_internals: 375 return 376 self._write_line("Source %s" % style(repr(self.current_source), fg="magenta")) 377 self.indent() 378 379 def leave_source(self, start_time): 380 if self.show_source_internals: 381 self.outdent() 382 383 def report_pruned_artifact(self, artifact_name): 384 self._write_line("%s %s" % (style("D", fg="red"), artifact_name)) 385 386 387null_reporter = NullReporter(None) 388 389 390@LocalProxy 391def reporter(): 392 rv = _reporter_stack.top 393 if rv is None: 394 rv = null_reporter 395 return rv 396