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.
- {htmlcmp-1.0.13 → htmlcmp-1.0.16}/PKG-INFO +1 -1
- {htmlcmp-1.0.13 → htmlcmp-1.0.16}/pyproject.toml +1 -1
- {htmlcmp-1.0.13 → htmlcmp-1.0.16}/src/htmlcmp/compare_output_server.py +82 -8
- {htmlcmp-1.0.13 → htmlcmp-1.0.16}/src/htmlcmp.egg-info/PKG-INFO +1 -1
- {htmlcmp-1.0.13 → htmlcmp-1.0.16}/README.md +0 -0
- {htmlcmp-1.0.13 → htmlcmp-1.0.16}/setup.cfg +0 -0
- {htmlcmp-1.0.13 → htmlcmp-1.0.16}/src/htmlcmp/__init__.py +0 -0
- {htmlcmp-1.0.13 → htmlcmp-1.0.16}/src/htmlcmp/common.py +0 -0
- {htmlcmp-1.0.13 → htmlcmp-1.0.16}/src/htmlcmp/compare_output.py +0 -0
- {htmlcmp-1.0.13 → htmlcmp-1.0.16}/src/htmlcmp/html_render_diff.py +0 -0
- {htmlcmp-1.0.13 → htmlcmp-1.0.16}/src/htmlcmp/tidy_output.py +0 -0
- {htmlcmp-1.0.13 → htmlcmp-1.0.16}/src/htmlcmp.egg-info/SOURCES.txt +0 -0
- {htmlcmp-1.0.13 → htmlcmp-1.0.16}/src/htmlcmp.egg-info/dependency_links.txt +0 -0
- {htmlcmp-1.0.13 → htmlcmp-1.0.16}/src/htmlcmp.egg-info/entry_points.txt +0 -0
- {htmlcmp-1.0.13 → htmlcmp-1.0.16}/src/htmlcmp.egg-info/requires.txt +0 -0
- {htmlcmp-1.0.13 → htmlcmp-1.0.16}/src/htmlcmp.egg-info/top_level.txt +0 -0
- {htmlcmp-1.0.13 → htmlcmp-1.0.16}/tests/test_html_render_diff.py +0 -0
|
@@ -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
|
-
|
|
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
|
|
40
|
-
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|