htmlcmp 1.1.1__tar.gz → 1.2.1__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.1.1
3
+ Version: 1.2.1
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.1.1"
3
+ version = "1.2.1"
4
4
  description = "Compare HTML files by rendered output"
5
5
  classifiers = []
6
6
  authors = [
@@ -241,9 +241,7 @@ def main():
241
241
  parser = argparse.ArgumentParser()
242
242
  parser.add_argument("a", type=Path, help="Path to the first directory")
243
243
  parser.add_argument("b", type=Path, help="Path to the second directory")
244
- parser.add_argument(
245
- "--driver", choices=["chrome", "firefox", "phantomjs"], default="firefox"
246
- )
244
+ parser.add_argument("--driver", choices=["chrome", "firefox"], default="firefox")
247
245
  parser.add_argument(
248
246
  "--diff-output", type=Path, help="Output directory for diff images"
249
247
  )
@@ -18,7 +18,6 @@ import watchdog.events
18
18
  from htmlcmp.compare_output import comparable_file, compare_files
19
19
  from htmlcmp.html_render_diff import get_browser, html_render_diff
20
20
 
21
-
22
21
  logger = logging.getLogger(__name__)
23
22
 
24
23
 
@@ -253,6 +252,28 @@ class Comparator:
253
252
  app = Flask("compare")
254
253
 
255
254
 
255
+ @app.route("/script.js")
256
+ def script_js():
257
+ logger.debug("Serving script.js")
258
+
259
+ return """
260
+ function updateRef(path) {
261
+ fetch(`/update_ref/${path}`)
262
+ .then(response => {
263
+ if (response.ok) {
264
+ alert(`Reference updated for ${path}`);
265
+ location.reload();
266
+ } else {
267
+ alert(`Failed to update reference for ${path}: ${response.statusText}`);
268
+ }
269
+ })
270
+ .catch(error => {
271
+ alert(`Error updating reference for ${path}: ${error}`);
272
+ });
273
+ }
274
+ """
275
+
276
+
256
277
  @app.route("/")
257
278
  def root():
258
279
  logger.debug("Generating root directory listing")
@@ -444,6 +465,7 @@ tr {
444
465
  padding-left: calc(1.0rem * var(--depth));
445
466
  }
446
467
  </style>
468
+ <script src="/script.js"></script>
447
469
  </head>
448
470
  <body>
449
471
  """
@@ -493,6 +515,11 @@ function toggleChildren(parentId, show) {
493
515
  document.querySelectorAll(`tr[data-parent-id="${parentId}"]`)
494
516
  .forEach(child => {
495
517
  child.hidden = !show;
518
+ if (!show) {
519
+ toggleChildren(child.dataset.entryId, false);
520
+ const toggle = child.querySelector(".toggle");
521
+ if (toggle) toggle.textContent = "▶";
522
+ }
496
523
  });
497
524
  }
498
525
 
@@ -507,21 +534,6 @@ function toggleAll(show) {
507
534
  }
508
535
  });
509
536
  }
510
-
511
- function updateRef(path) {
512
- fetch(`/update_ref/${path}`)
513
- .then(response => {
514
- if (response.ok) {
515
- alert(`Reference updated for ${path}`);
516
- location.reload();
517
- } else {
518
- alert(`Failed to update reference for ${path}: ${response.statusText}`);
519
- }
520
- })
521
- .catch(error => {
522
- alert(`Error updating reference for ${path}: ${error}`);
523
- });
524
- }
525
537
  </script>
526
538
  </body>
527
539
  </html>
@@ -553,6 +565,7 @@ def compare(path: str):
553
565
  <style>
554
566
  html,body {{height:100%;margin:0;}}
555
567
  </style>
568
+ <script src="/script.js"></script>
556
569
  </head>
557
570
  <body style="display:flex;flex-flow:row;">
558
571
  <div style="display:flex;flex:1;flex-flow:column;margin:5px;">
@@ -561,6 +574,7 @@ html,body {{height:100%;margin:0;}}
561
574
  </div>
562
575
  <div style="display:flex;flex:0 0 50px;flex-flow:column;">
563
576
  <a href="/image_diff/{path}" target="_blank">diff</a>
577
+ <button onclick="updateRef('{path}')">▶</button>
564
578
  <img src="/image_diff/{path}" width="50" height="0" style="flex:1;">
565
579
  </div>
566
580
  <div style="display:flex;flex:1;flex-flow:column;margin:5px;">
@@ -616,6 +630,27 @@ def file(variant: str, path: str):
616
630
  return send_from_directory(variant_root, path)
617
631
 
618
632
 
633
+ @app.route("/update_ref/<path:path>")
634
+ def update_ref(path: str):
635
+ logger.debug(f"Updating reference for path: {path}")
636
+
637
+ if not isinstance(path, str):
638
+ raise TypeError("Path must be a string")
639
+
640
+ src = Config.path_b / path
641
+ dst = Config.path_a / path
642
+
643
+ if not src.exists():
644
+ return f"Source file does not exist: {src}", 404
645
+
646
+ if src.is_file():
647
+ shutil.copy2(src, dst)
648
+ else:
649
+ shutil.copytree(src, dst, dirs_exist_ok=True)
650
+
651
+ return "Reference updated", 200
652
+
653
+
619
654
  def verbosity_to_level(verbosity: int) -> int:
620
655
  if verbosity >= 3:
621
656
  return logging.DEBUG
@@ -659,32 +694,11 @@ def setup_logging(
659
694
  root_logger.addHandler(file_handler)
660
695
 
661
696
 
662
- @app.route("/update_ref/<path:path>")
663
- def update_ref(path: str):
664
- logger.debug(f"Updating reference for path: {path}")
665
-
666
- if not isinstance(path, str):
667
- raise TypeError("Path must be a string")
668
-
669
- src = Config.path_b / path
670
- dst = Config.path_a / path
671
-
672
- if not src.exists():
673
- return f"Source file does not exist: {src}", 404
674
-
675
- if src.is_file():
676
- shutil.copy2(src, dst)
677
- else:
678
- shutil.copytree(src, dst, dirs_exist_ok=True)
679
-
680
- return "Reference updated", 200
681
-
682
-
683
697
  def main():
684
698
  parser = argparse.ArgumentParser()
685
699
  parser.add_argument("ref", type=Path, help="Path to the reference directory")
686
700
  parser.add_argument("mon", type=Path, help="Path to the monitored directory")
687
- parser.add_argument("--driver", choices=["chrome", "firefox", "phantomjs"])
701
+ parser.add_argument("--driver", choices=["chrome", "firefox"])
688
702
  parser.add_argument("--max-workers", type=int, default=1)
689
703
  parser.add_argument("--compare", action="store_true")
690
704
  parser.add_argument("--port", type=int, default=5000)
@@ -70,11 +70,7 @@ def get_browser(
70
70
  f"Expected int for max_width and max_height, got {type(max_width)} and {type(max_height)}"
71
71
  )
72
72
 
73
- if driver == "phantomjs":
74
- if shutil.which("phantomjs") is None:
75
- raise EnvironmentError("PhantomJS is not installed or not found in PATH")
76
- browser = webdriver.PhantomJS()
77
- elif driver == "firefox":
73
+ if driver == "firefox":
78
74
  options = webdriver.FirefoxOptions()
79
75
  options.add_argument("--headless")
80
76
  browser = webdriver.Firefox(options=options)
@@ -109,9 +105,7 @@ def main():
109
105
  parser = argparse.ArgumentParser()
110
106
  parser.add_argument("a", type=Path, help="Path to the first HTML file")
111
107
  parser.add_argument("b", type=Path, help="Path to the second HTML file")
112
- parser.add_argument(
113
- "--driver", choices=["chrome", "firefox", "phantomjs"], default="firefox"
114
- )
108
+ parser.add_argument("--driver", choices=["chrome", "firefox"], default="firefox")
115
109
  parser.add_argument("--max-width", default=1000)
116
110
  parser.add_argument("--max-height", default=10000)
117
111
  args = parser.parse_args()
@@ -10,7 +10,7 @@ from pathlib import Path
10
10
  from htmlcmp.common import bcolors
11
11
 
12
12
 
13
- def tidy_json(path: Path) -> int:
13
+ def tidy_json(path: Path, verbose: bool = False) -> int:
14
14
  if not isinstance(path, Path):
15
15
  raise TypeError("path must be a Path object")
16
16
  if not path.is_file():
@@ -21,10 +21,11 @@ def tidy_json(path: Path) -> int:
21
21
  json.load(f)
22
22
  return 0
23
23
  except ValueError:
24
+ print(f"{bcolors.FAIL}Error: {path} is not a valid JSON file{bcolors.ENDC}")
24
25
  return 1
25
26
 
26
27
 
27
- def tidy_html(path: Path, html_tidy_config: Path = None) -> int:
28
+ def tidy_html(path: Path, html_tidy_config: Path = None, verbose: bool = False) -> int:
28
29
  if not isinstance(path, Path):
29
30
  raise TypeError("path must be a Path object")
30
31
  if not path.is_file():
@@ -41,6 +42,15 @@ def tidy_html(path: Path, html_tidy_config: Path = None) -> int:
41
42
  result = subprocess.run(
42
43
  cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
43
44
  )
45
+ if result.stdout:
46
+ if verbose and result.returncode == 0:
47
+ print(result.stdout)
48
+ elif verbose and result.returncode == 1:
49
+ print(f"{bcolors.WARNING}Warning: {path} has warnings{bcolors.ENDC}")
50
+ print(f"{bcolors.WARNING}{result.stdout}{bcolors.ENDC}")
51
+ elif verbose or result.returncode > 1:
52
+ print(f"{bcolors.FAIL}Error: {path} has errors{bcolors.ENDC}")
53
+ print(f"{bcolors.FAIL}{result.stdout}{bcolors.ENDC}")
44
54
  if result.returncode == 1:
45
55
  return 1
46
56
  if result.returncode > 1:
@@ -48,16 +58,16 @@ def tidy_html(path: Path, html_tidy_config: Path = None) -> int:
48
58
  return 0
49
59
 
50
60
 
51
- def tidy_file(path: Path, html_tidy_config: Path = None) -> int:
61
+ def tidy_file(path: Path, html_tidy_config: Path = None, verbose: bool = False) -> int:
52
62
  if not isinstance(path, Path):
53
63
  raise TypeError("path must be a Path object")
54
64
  if not path.is_file():
55
65
  raise FileNotFoundError(f"{path} is not a file")
56
66
 
57
67
  if path.suffix == ".json":
58
- return tidy_json(path)
68
+ return tidy_json(path, verbose=verbose)
59
69
  elif path.suffix == ".html":
60
- return tidy_html(path, html_tidy_config=html_tidy_config)
70
+ return tidy_html(path, html_tidy_config=html_tidy_config, verbose=verbose)
61
71
 
62
72
 
63
73
  def tidyable_file(path: Path) -> bool:
@@ -74,7 +84,11 @@ def tidyable_file(path: Path) -> bool:
74
84
 
75
85
 
76
86
  def tidy_dir(
77
- path: Path, level: int = 0, prefix: str = "", html_tidy_config: Path = None
87
+ path: Path,
88
+ level: int = 0,
89
+ prefix: str = "",
90
+ html_tidy_config: Path = None,
91
+ verbose: bool = False,
78
92
  ) -> dict[str, list[Path]]:
79
93
  if not isinstance(path, Path):
80
94
  raise TypeError("path must be a Path object")
@@ -104,7 +118,7 @@ def tidy_dir(
104
118
 
105
119
  for filename in [path.name for path in files]:
106
120
  filepath = path / filename
107
- tidy = tidy_file(filepath, html_tidy_config=html_tidy_config)
121
+ tidy = tidy_file(filepath, html_tidy_config=html_tidy_config, verbose=verbose)
108
122
  if tidy == 0:
109
123
  print(f"{prefix_file}{bcolors.OKGREEN}{filename} ✓{bcolors.ENDC}")
110
124
  elif tidy == 1:
@@ -121,6 +135,7 @@ def tidy_dir(
121
135
  level=level + 1,
122
136
  prefix=prefix + "│ ",
123
137
  html_tidy_config=html_tidy_config,
138
+ verbose=verbose,
124
139
  )
125
140
  result["warning"].extend(subresult["warning"])
126
141
  result["error"].extend(subresult["error"])
@@ -134,9 +149,16 @@ def main():
134
149
  parser.add_argument(
135
150
  "--html-tidy-config", type=Path, help="Path to tidy config file"
136
151
  )
152
+ parser.add_argument(
153
+ "--verbose",
154
+ action="store_true",
155
+ help="Print verbose output (warnings and errors)",
156
+ )
137
157
  args = parser.parse_args()
138
158
 
139
- result = tidy_dir(args.path, html_tidy_config=args.html_tidy_config)
159
+ result = tidy_dir(
160
+ args.path, html_tidy_config=args.html_tidy_config, verbose=args.verbose
161
+ )
140
162
  if result["error"]:
141
163
  return 1
142
164
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: htmlcmp
3
- Version: 1.1.1
3
+ Version: 1.2.1
4
4
  Summary: Compare HTML files by rendered output
5
5
  Author: Andreas Stefl
6
6
  Maintainer-email: Andreas Stefl <stefl.andreas@gmail.com>
@@ -31,12 +31,6 @@ def test_get_browser():
31
31
  assert browser.name == "firefox"
32
32
  browser.quit()
33
33
 
34
- # Test with PhantomJS
35
- if shutil.which("phantomjs") is not None:
36
- browser = get_browser("phantomjs")
37
- assert browser.name == "phantomjs"
38
- browser.quit()
39
-
40
34
 
41
35
  def test_html_render_diff():
42
36
  test1 = Path(__file__).parent / "test1.html"
File without changes
File without changes
File without changes
File without changes