11#!/usr/bin/env python3
22# -*- coding: utf-8 -*-
33
4+ import io
45import sys
56import argparse
7+ import logging
68import threading
7- import io
89from pathlib import Path
910from concurrent .futures import ThreadPoolExecutor
1011
@@ -36,19 +37,32 @@ def dispatch(self, event):
3637 event_type = event .event_type
3738 src_path = Path (event .src_path )
3839
39- if event_type in ["opened" ]:
40+ logging .verbose (f"Watchdog event: { event_type } { src_path } " )
41+
42+ if event_type not in ["moved" , "deleted" , "created" , "modified" ]:
4043 return
4144
4245 if src_path .is_file ():
46+ logging .debug (
47+ f"Submit watchdog file change: { event_type } { src_path } "
48+ )
4349 Config .comparator .submit (src_path .relative_to (self ._path ))
50+
51+ logging .info ("Create watchdog for paths:" )
52+ logging .info (f" A: { Config .path_a } " )
53+ logging .info (f" B: { Config .path_b } " )
54+
4455 self ._observer = watchdog .observers .Observer ()
4556 self ._observer .schedule (Handler (Config .path_a ), Config .path_a , recursive = True )
4657 self ._observer .schedule (Handler (Config .path_b ), Config .path_b , recursive = True )
4758
4859 def start (self ):
60+ logging .info ("Starting watchdog observer" )
4961 self ._observer .start ()
5062
5163 def init_compare (a : Path , b : Path ):
64+ logging .verbose (f"Initial compare: { a } vs { b } " )
65+
5266 if not isinstance (a , Path ) or not isinstance (b , Path ):
5367 raise TypeError ("Paths must be of type Path" )
5468 if not a .is_dir () or not b .is_dir ():
@@ -63,16 +77,23 @@ def init_compare(a: Path, b: Path):
6377
6478 for name in common :
6579 if (a / name ).is_file () and comparable_file (a / name ):
80+ logging .debug (
81+ f"Submit initial file comparison: { common_path / name } "
82+ )
6683 Config .comparator .submit (common_path / name )
6784 elif (a / name ).is_dir ():
6885 init_compare (a / name , b / name )
6986
87+ logging .info ("Kick off initial comparison of all files" )
7088 init_compare (Config .path_a , Config .path_b )
89+ logging .info ("Initial comparison submitted" )
7190
7291 def stop (self ):
92+ logging .info ("Stopping watchdog observer" )
7393 self ._observer .stop ()
7494
7595 def join (self ):
96+ logging .info ("Joining watchdog observer" )
7697 self ._observer .join ()
7798
7899
@@ -84,13 +105,17 @@ def initializer():
84105 browser = get_browser (driver = Config .driver )
85106 Config .thread_local .browser = browser
86107
108+ logging .info (f"Creating comparator with { max_workers } workers" )
109+
87110 self ._executor = ThreadPoolExecutor (
88111 max_workers = max_workers , initializer = initializer
89112 )
90113 self ._result = {}
91114 self ._future = {}
92115
93116 def submit (self , path : Path ):
117+ logging .debug (f"Submitting comparison for path: { path } " )
118+
94119 if not isinstance (path , Path ):
95120 raise TypeError ("Path must be of type Path" )
96121
@@ -106,6 +131,8 @@ def submit(self, path: Path):
106131 self ._future [path ] = self ._executor .submit (self .compare , path )
107132
108133 def compare (self , path : Path ):
134+ logging .debug (f"Comparing files for path: { path } " )
135+
109136 if not isinstance (path , Path ):
110137 raise TypeError ("Path must be of type Path" )
111138 if path not in self ._future :
@@ -121,6 +148,8 @@ def compare(self, path: Path):
121148 self ._future .pop (path )
122149
123150 def result (self , path : Path ):
151+ logging .debug (f"Getting comparison result for path: { path } " )
152+
124153 if not isinstance (path , Path ):
125154 raise TypeError ("Path must be of type Path" )
126155
@@ -129,6 +158,8 @@ def result(self, path: Path):
129158 return "unknown"
130159
131160 def result_symbol (self , path : Path ):
161+ logging .debug (f"Getting comparison result symbol for path: { path } " )
162+
132163 if not isinstance (path , Path ):
133164 raise TypeError ("Path must be of type Path" )
134165
@@ -142,6 +173,8 @@ def result_symbol(self, path: Path):
142173 return "⛔"
143174
144175 def result_css (self , path : Path ):
176+ logging .debug (f"Getting comparison result CSS for path: { path } " )
177+
145178 if not isinstance (path , Path ):
146179 raise TypeError ("Path must be of type Path" )
147180
@@ -160,6 +193,8 @@ def result_css(self, path: Path):
160193
161194@app .route ("/" )
162195def root ():
196+ logging .debug ("Generating root directory listing" )
197+
163198 def print_tree (a : Path , b : Path ):
164199 if not isinstance (a , Path ) or not isinstance (b , Path ):
165200 raise TypeError ("Paths must be of type Path" )
@@ -239,6 +274,8 @@ def print_tree(a: Path, b: Path):
239274
240275@app .route ("/compare/<path:path>" )
241276def compare (path : str ):
277+ logging .debug (f"Generating comparison page for path: { path } " )
278+
242279 if not isinstance (path , str ):
243280 raise TypeError ("Path must be a string" )
244281
@@ -279,6 +316,8 @@ def compare(path: str):
279316
280317@app .route ("/image_diff/<path:path>" )
281318def image_diff (path : str ):
319+ logging .debug (f"Generating image diff for path: { path } " )
320+
282321 if not isinstance (path , str ):
283322 raise TypeError ("Path must be a string" )
284323
@@ -295,6 +334,8 @@ def image_diff(path: str):
295334
296335@app .route ("/file/<variant>/<path:path>" )
297336def file (variant : str , path : str ):
337+ logging .debug (f"Serving file for variant: { variant } , path: { path } " )
338+
298339 if not isinstance (variant , str ) or not isinstance (path , str ):
299340 raise TypeError ("Variant and path must be strings" )
300341 if variant not in ["a" , "b" ]:
@@ -304,6 +345,30 @@ def file(variant: str, path: str):
304345 return send_from_directory (variant_root , path )
305346
306347
348+ def setup_logging (verbosity : int ):
349+ if verbosity >= 3 :
350+ level = logging .VERBOSE
351+ elif verbosity == 2 :
352+ level = logging .DEBUG
353+ elif verbosity == 1 :
354+ level = logging .INFO
355+ else :
356+ level = logging .WARNING
357+
358+ logger = logging .getLogger ()
359+ logger .setLevel (level )
360+ logger .handlers .clear ()
361+
362+ formatter = logging .Formatter (
363+ fmt = "%(asctime)s [%(levelname)s] %(name)s: %(message)s" ,
364+ datefmt = "%Y-%m-%d %H:%M:%S" ,
365+ )
366+
367+ console_handler = logging .StreamHandler (sys .stderr )
368+ console_handler .setFormatter (formatter )
369+ logger .addHandler (console_handler )
370+
371+
307372def main ():
308373 parser = argparse .ArgumentParser ()
309374 parser .add_argument ("a" , type = Path , help = "Path to the first directory" )
@@ -314,8 +379,17 @@ def main():
314379 parser .add_argument ("--max-workers" , type = int , default = 1 )
315380 parser .add_argument ("--compare" , action = "store_true" )
316381 parser .add_argument ("--port" , type = int , default = 5000 )
382+ parser .add_argument (
383+ "-v" ,
384+ "--verbose" ,
385+ action = "count" ,
386+ default = 0 ,
387+ help = "Increase verbosity (-v, -vv, -vvv)" ,
388+ )
317389 args = parser .parse_args ()
318390
391+ setup_logging (args .verbose )
392+
319393 Config .path_a = args .a
320394 Config .path_b = args .b
321395 Config .driver = args .driver
0 commit comments