htmlcmp 1.0.13__tar.gz → 1.0.16__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: htmlcmp
3
- Version: 1.0.13
3
+ Version: 1.0.16
4
4
  Summary: Compare HTML files by rendered output
5
5
  Author: Andreas Stefl
6
6
  Maintainer-email: Andreas Stefl <stefl.andreas@gmail.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "htmlcmp"
3
- version = "1.0.13"
3
+ version = "1.0.16"
4
4
  description = "Compare HTML files by rendered output"
5
5
  classifiers = []
6
6
  authors = [
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
+ import io
4
5
  import sys
5
6
  import argparse
7
+ import logging
6
8
  import threading
7
- import io
8
9
  from pathlib import Path
9
10
  from concurrent.futures import ThreadPoolExecutor
10
11
 
@@ -33,26 +34,41 @@ class Observer:
33
34
  self._path = path
34
35
 
35
36
  def dispatch(self, event):
36
- if event.event_type in ["opened"]:
37
+ event_type = event.event_type
38
+ src_path = Path(event.src_path)
39
+
40
+ logging.verbose(f"Watchdog event: {event_type} {src_path}")
41
+
42
+ if event_type not in ["moved", "deleted", "created", "modified"]:
37
43
  return
38
44
 
39
- if event.src_path.is_file():
40
- Config.comparator.submit(event.src_path.relative_to(self._path))
45
+ if src_path.is_file():
46
+ logging.debug(
47
+ f"Submit watchdog file change: {event_type} {src_path}"
48
+ )
49
+ 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}")
41
54
 
42
55
  self._observer = watchdog.observers.Observer()
43
56
  self._observer.schedule(Handler(Config.path_a), Config.path_a, recursive=True)
44
57
  self._observer.schedule(Handler(Config.path_b), Config.path_b, recursive=True)
45
58
 
46
59
  def start(self):
60
+ logging.info("Starting watchdog observer")
47
61
  self._observer.start()
48
62
 
49
63
  def init_compare(a: Path, b: Path):
64
+ logging.verbose(f"Initial compare: {a} vs {b}")
65
+
50
66
  if not isinstance(a, Path) or not isinstance(b, Path):
51
67
  raise TypeError("Paths must be of type Path")
52
68
  if not a.is_dir() or not b.is_dir():
53
69
  raise ValueError("Both paths must be directories")
54
70
 
55
- common_path = a / Config.path_a
71
+ common_path = a.relative_to(Config.path_a)
56
72
 
57
73
  left = sorted(p.name for p in a.iterdir())
58
74
  right = sorted(p.name for p in b.iterdir())
@@ -61,16 +77,23 @@ class Observer:
61
77
 
62
78
  for name in common:
63
79
  if (a / name).is_file() and comparable_file(a / name):
80
+ logging.debug(
81
+ f"Submit initial file comparison: {common_path / name}"
82
+ )
64
83
  Config.comparator.submit(common_path / name)
65
84
  elif (a / name).is_dir():
66
85
  init_compare(a / name, b / name)
67
86
 
87
+ logging.info("Kick off initial comparison of all files")
68
88
  init_compare(Config.path_a, Config.path_b)
89
+ logging.info("Initial comparison submitted")
69
90
 
70
91
  def stop(self):
92
+ logging.info("Stopping watchdog observer")
71
93
  self._observer.stop()
72
94
 
73
95
  def join(self):
96
+ logging.info("Joining watchdog observer")
74
97
  self._observer.join()
75
98
 
76
99
 
@@ -82,6 +105,8 @@ class Comparator:
82
105
  browser = get_browser(driver=Config.driver)
83
106
  Config.thread_local.browser = browser
84
107
 
108
+ logging.info(f"Creating comparator with {max_workers} workers")
109
+
85
110
  self._executor = ThreadPoolExecutor(
86
111
  max_workers=max_workers, initializer=initializer
87
112
  )
@@ -89,6 +114,8 @@ class Comparator:
89
114
  self._future = {}
90
115
 
91
116
  def submit(self, path: Path):
117
+ logging.debug(f"Submitting comparison for path: {path}")
118
+
92
119
  if not isinstance(path, Path):
93
120
  raise TypeError("Path must be of type Path")
94
121
 
@@ -104,10 +131,10 @@ class Comparator:
104
131
  self._future[path] = self._executor.submit(self.compare, path)
105
132
 
106
133
  def compare(self, path: Path):
134
+ logging.debug(f"Comparing files for path: {path}")
135
+
107
136
  if not isinstance(path, Path):
108
137
  raise TypeError("Path must be of type Path")
109
- if not path.is_file():
110
- raise ValueError("Path must be a file")
111
138
  if path not in self._future:
112
139
  raise RuntimeError("Path not submitted for comparison")
113
140
 
@@ -121,6 +148,8 @@ class Comparator:
121
148
  self._future.pop(path)
122
149
 
123
150
  def result(self, path: Path):
151
+ logging.debug(f"Getting comparison result for path: {path}")
152
+
124
153
  if not isinstance(path, Path):
125
154
  raise TypeError("Path must be of type Path")
126
155
 
@@ -129,6 +158,8 @@ class Comparator:
129
158
  return "unknown"
130
159
 
131
160
  def result_symbol(self, path: Path):
161
+ logging.debug(f"Getting comparison result symbol for path: {path}")
162
+
132
163
  if not isinstance(path, Path):
133
164
  raise TypeError("Path must be of type Path")
134
165
 
@@ -142,6 +173,8 @@ class Comparator:
142
173
  return "⛔"
143
174
 
144
175
  def result_css(self, path: Path):
176
+ logging.debug(f"Getting comparison result CSS for path: {path}")
177
+
145
178
  if not isinstance(path, Path):
146
179
  raise TypeError("Path must be of type Path")
147
180
 
@@ -160,13 +193,15 @@ app = Flask("compare")
160
193
 
161
194
  @app.route("/")
162
195
  def root():
196
+ logging.debug("Generating root directory listing")
197
+
163
198
  def print_tree(a: Path, b: Path):
164
199
  if not isinstance(a, Path) or not isinstance(b, Path):
165
200
  raise TypeError("Paths must be of type Path")
166
201
  if not a.is_dir() or not b.is_dir():
167
202
  raise ValueError("Both paths must be directories")
168
203
 
169
- common_path = a / Config.path_a
204
+ common_path = a.relative_to(Config.path_a)
170
205
 
171
206
  left = sorted(p.name for p in a.iterdir())
172
207
  right = sorted(p.name for p in b.iterdir())
@@ -239,6 +274,8 @@ def root():
239
274
 
240
275
  @app.route("/compare/<path:path>")
241
276
  def compare(path: str):
277
+ logging.debug(f"Generating comparison page for path: {path}")
278
+
242
279
  if not isinstance(path, str):
243
280
  raise TypeError("Path must be a string")
244
281
 
@@ -279,6 +316,8 @@ iframe_b.contentWindow.addEventListener('scroll', function(event) {{
279
316
 
280
317
  @app.route("/image_diff/<path:path>")
281
318
  def image_diff(path: str):
319
+ logging.debug(f"Generating image diff for path: {path}")
320
+
282
321
  if not isinstance(path, str):
283
322
  raise TypeError("Path must be a string")
284
323
 
@@ -295,6 +334,8 @@ def image_diff(path: str):
295
334
 
296
335
  @app.route("/file/<variant>/<path:path>")
297
336
  def file(variant: str, path: str):
337
+ logging.debug(f"Serving file for variant: {variant}, path: {path}")
338
+
298
339
  if not isinstance(variant, str) or not isinstance(path, str):
299
340
  raise TypeError("Variant and path must be strings")
300
341
  if variant not in ["a", "b"]:
@@ -304,6 +345,30 @@ def file(variant: str, path: str):
304
345
  return send_from_directory(variant_root, path)
305
346
 
306
347
 
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
+
307
372
  def main():
308
373
  parser = argparse.ArgumentParser()
309
374
  parser.add_argument("a", type=Path, help="Path to the first directory")
@@ -314,8 +379,17 @@ def main():
314
379
  parser.add_argument("--max-workers", type=int, default=1)
315
380
  parser.add_argument("--compare", action="store_true")
316
381
  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
+ )
317
389
  args = parser.parse_args()
318
390
 
391
+ setup_logging(args.verbose)
392
+
319
393
  Config.path_a = args.a
320
394
  Config.path_b = args.b
321
395
  Config.driver = args.driver
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: htmlcmp
3
- Version: 1.0.13
3
+ Version: 1.0.16
4
4
  Summary: Compare HTML files by rendered output
5
5
  Author: Andreas Stefl
6
6
  Maintainer-email: Andreas Stefl <stefl.andreas@gmail.com>
File without changes
File without changes
File without changes