webcode-tk 1.2.9__tar.gz → 1.2.12__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,17 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webcode-tk
3
- Version: 1.2.9
3
+ Version: 1.2.12
4
4
  Summary: A set of tools for inspecting HTML and CSS code
5
5
  License: MIT
6
6
  License-File: LICENSE.rst
7
7
  Keywords: html,validation,css
8
8
  Author: hundredvisionsguy
9
9
  Author-email: cwinikka@gmail.com
10
- Requires-Python: >=3.9.0,<4.0
10
+ Requires-Python: >=3.10,<4.0
11
11
  Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Operating System :: OS Independent
13
13
  Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.9
15
14
  Classifier: Programming Language :: Python :: 3.10
16
15
  Classifier: Programming Language :: Python :: 3.11
17
16
  Classifier: Programming Language :: Python :: 3.12
@@ -22,6 +21,7 @@ Requires-Dist: file-clerk (>=1.0.9,<2.0.0)
22
21
  Requires-Dist: lxml (>=6.0.0,<7.0.0)
23
22
  Requires-Dist: mechanicalsoup (>=1.4.0,<2.0.0)
24
23
  Requires-Dist: nltk (>=3.8.1,<4.0.0)
24
+ Requires-Dist: pre-commit (>=4.5.0,<5.0.0)
25
25
  Requires-Dist: py-readability-metrics (>=1.4.5,<2.0.0)
26
26
  Requires-Dist: pytest-mock (>=3.15.1,<4.0.0)
27
27
  Requires-Dist: requests (>=2.28.1,<3.0.0)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "webcode-tk"
3
- version = "1.2.9"
3
+ version = "1.2.12"
4
4
  description = "A set of tools for inspecting HTML and CSS code"
5
5
  authors = ["hundredvisionsguy <cwinikka@gmail.com>"]
6
6
  keywords = ["html", "validation", "css", ]
@@ -16,7 +16,7 @@ classifiers = [
16
16
  ]
17
17
 
18
18
  [tool.poetry.dependencies]
19
- python = ">=3.9.0,<4.0"
19
+ python = ">=3.10,<4.0"
20
20
  bs4 = "^0.0.1"
21
21
  file-clerk = "^1.0.9"
22
22
  requests = "^2.28.1"
@@ -27,6 +27,7 @@ tinycss2 = "^1.4.0"
27
27
  lxml = "^6.0.0"
28
28
  mechanicalsoup = "^1.4.0"
29
29
  pytest-mock = "^3.15.1"
30
+ pre-commit = "^4.5.0"
30
31
 
31
32
  [tool.poetry.group.dev.dependencies]
32
33
  pytest = "*"
@@ -40,4 +40,4 @@
40
40
  - **`validator_tools`**: sends HTML or CSS code to the W3C Validator to check
41
41
  for errors.
42
42
  """
43
- __version__ = "1.2.9"
43
+ __version__ = "1.2.12"
@@ -1539,6 +1539,16 @@ def targets_element_directly(element: Element, selector: str) -> bool:
1539
1539
  elif selector_type == "id_selector":
1540
1540
  attributes = element.attributes
1541
1541
  if attributes:
1542
+ if not isinstance(attributes, dict):
1543
+ try:
1544
+ attributes = dict(attributes)
1545
+ except Exception:
1546
+ msg = "This is not a dictionary and cannot be converted"
1547
+ msg += " into one!"
1548
+ raise ValueError(msg)
1549
+ else:
1550
+ data_type = str(type(attributes))
1551
+ print(data_type)
1542
1552
  id_attribute = attributes.get("id")
1543
1553
  if id_attribute:
1544
1554
  targets = selector in attributes.get("id")
@@ -313,7 +313,7 @@ def has_alpha_channel(code: str) -> bool:
313
313
  """
314
314
  has_alpha = False
315
315
  if "#" in code:
316
- if len(code) == 9:
316
+ if len(code) == 9 or len(code) == 5:
317
317
  has_alpha = True
318
318
  if "hsla(" in code:
319
319
  has_alpha = True
@@ -321,6 +321,10 @@ def has_alpha_channel(code: str) -> bool:
321
321
  has_alpha = True
322
322
  if "rgba(" in code:
323
323
  has_alpha = True
324
+ if "rgb" in code and "/" in code:
325
+ has_alpha = True
326
+ if "transparent" in code:
327
+ has_alpha = True
324
328
  return has_alpha
325
329
 
326
330
 
@@ -166,20 +166,6 @@ def generate_contrast_report(
166
166
  for file in project_files:
167
167
  errors = project_files.get(file)
168
168
 
169
- # Check for global selectors (html, *, or body) and calculate contrast
170
- global_color_result = css_tools.get_global_color_report(project_path)
171
-
172
- if isinstance(global_color_result, list):
173
- for result in global_color_result:
174
- if file in result:
175
- # Always add global color failures
176
- if "fail" in result:
177
- error_msg = result.split(file)[1]
178
- msg = f"fail: in {file}, {error_msg.strip()}"
179
- # Add to errors list if not already there
180
- if msg not in project_files[file]:
181
- project_files[file].append(msg)
182
-
183
169
  # Now append to report
184
170
  if errors:
185
171
  if "has no CSS" in errors[0]:
@@ -1557,25 +1543,6 @@ def analyze_elements_for_contrast(
1557
1543
  "contrast_analysis"
1558
1544
  )
1559
1545
 
1560
- # Check if this element inherited both text and background colors
1561
- text_color_source_type = extract_property_source(
1562
- element_data["color"]
1563
- ).get("source_type")
1564
- bg_color_source_type = extract_property_source(
1565
- element_data["background-color"]
1566
- ).get("source_type")
1567
-
1568
- # Determine if this is a global color failure
1569
- inherited_text = text_color_source_type in [
1570
- "inherited",
1571
- "browser_default",
1572
- ]
1573
- inherited_bg = bg_color_source_type in [
1574
- "visual_inheritance",
1575
- "browser_default",
1576
- ]
1577
- is_global_failure = inherited_text and inherited_bg
1578
-
1579
1546
  if is_large:
1580
1547
  aaa_results = contrast_report.get("Large AAA")
1581
1548
  aa_results = contrast_report.get("Large AA")
@@ -1607,7 +1574,6 @@ def analyze_elements_for_contrast(
1607
1574
  "wcag_aa_pass": aa_results,
1608
1575
  "wcag_aaa_pass": aaa_results,
1609
1576
  "contrast_analysis": contrast_analysis,
1610
- "global_color_failure": is_global_failure,
1611
1577
  }
1612
1578
 
1613
1579
  # add to results
@@ -1383,7 +1383,11 @@ def get_global_color_details(rulesets: Union[list, tuple]) -> list:
1383
1383
  global_selectors = ("html", "body", ":root", "*")
1384
1384
  global_rulesets = []
1385
1385
  for ruleset in rulesets:
1386
- if ruleset.selector in global_selectors:
1386
+ if (
1387
+ ruleset.selector in global_selectors
1388
+ or "html" in ruleset.selector[:4]
1389
+ or "body" in ruleset.selector[:4]
1390
+ ):
1387
1391
  selector = ruleset.selector
1388
1392
  background_color = ""
1389
1393
  color = ""
@@ -1392,10 +1396,6 @@ def get_global_color_details(rulesets: Union[list, tuple]) -> list:
1392
1396
  background_color = declaration.value
1393
1397
  elif declaration.property == "color":
1394
1398
  color = declaration.value
1395
- if is_gradient(color):
1396
- colors = process_gradient(color)
1397
- todo = input("We have colors: " + colors)
1398
- print(todo)
1399
1399
  elif declaration.property == "background":
1400
1400
  background_color = declaration.value
1401
1401
  if is_gradient(background_color):
@@ -2452,22 +2452,93 @@ def get_global_color_report(project_dir: str, level="aaa") -> list:
2452
2452
  for data in all_file_data:
2453
2453
  filename = data[0]
2454
2454
  passes = []
2455
+ global_data = {
2456
+ "selector": "",
2457
+ "specificity": "",
2458
+ "bg_color": "",
2459
+ "color": "",
2460
+ "contrast_ratio": 0.0,
2461
+ "passes_normal": False,
2462
+ "passes_large": False,
2463
+ }
2455
2464
  for sheet in data[1]:
2456
2465
  rules = sheet.rulesets
2457
2466
  global_color_data = get_global_color_details(rules)
2458
2467
  if global_color_data:
2459
- for item in global_color_data:
2460
- file, result = get_color_data(filename, item, level)
2461
- if "fail" in result:
2462
- passes.append(f"fail: {file} {result}")
2468
+ previous_selector = global_data.get("selector")
2469
+ if previous_selector:
2470
+ previous_data = global_data.copy()
2471
+
2472
+ data = global_color_data[0]
2473
+ current_selector = data.get("selector")
2474
+ global_data["selector"] = current_selector
2475
+
2476
+ current_specificity = get_specificity(current_selector)
2477
+ global_data["specificity"] = current_specificity
2478
+
2479
+ bg_color = data.get("background-color")
2480
+ global_data["bg_color"] = bg_color
2481
+
2482
+ color = data.get("color")
2483
+ global_data["color"] = color
2484
+
2485
+ ratio = data.get("contrast_ratio")
2486
+ global_data["contrast_ratio"] = ratio
2487
+
2488
+ if level == "aaa":
2489
+ passes_normal = data.get("passes_normal_aaa")
2490
+ passes_large = data.get("passes_large_aaa")
2491
+ else:
2492
+ passes_normal = data.get("passes_normal_aa")
2493
+ passes_large = data.get("passes_large_aa")
2494
+
2495
+ global_data["passes_normal"] = passes_normal
2496
+ global_data["passes_large"] = passes_large
2497
+
2498
+ # If there was a previous selector
2499
+ if previous_selector:
2500
+ previous_specificity = previous_data.get("specificity")
2501
+
2502
+ # If previous specificity is higher, we need everything
2503
+ # from previous
2504
+ if previous_specificity > current_specificity:
2505
+ # Keep all from previous data
2506
+ for key, val in previous_data.items():
2507
+ if key in ["bg_color", "color"] and val:
2508
+ global_data[key] = val
2463
2509
  else:
2464
- passes.append(f"pass: {file} {result}")
2465
- if passes:
2466
- details = ""
2467
- for detail in passes:
2468
- details += detail
2510
+ # we already overrode everything
2511
+ # Anything missing? We need to pull from previous data
2512
+ for key, val in global_data.items():
2513
+ if not val:
2514
+ new_val = previous_data.get(key)
2515
+ if new_val:
2516
+ global_data[key] = new_val
2517
+ # reassess color contrast for global_data
2518
+ bg_color = global_data.get("bg_color")
2519
+ color = global_data.get("color")
2520
+ if not bg_color or not color:
2521
+ details = f"fail: {filename} does not apply "
2522
+ if not bg_color and not color:
2523
+ details += "neither background nor text color."
2524
+ elif not bg_color:
2525
+ details += "background color."
2526
+ else:
2527
+ details += "text color."
2469
2528
  else:
2470
- details = f"fail: {filename} does NOT apply global colors"
2529
+ bg_hex = color_tools.get_hex(bg_color)
2530
+ color_hex = color_tools.get_hex(color)
2531
+ contrast_ratio = color_tools.contrast_ratio(bg_hex, color_hex)
2532
+ normal_level = f"Normal {level.upper()}"
2533
+ passes = color_tools.passes_color_contrast(
2534
+ normal_level, bg_hex, color_hex
2535
+ )
2536
+ if passes:
2537
+ details = f"pass: {filename} applies global colors and passes"
2538
+ details += f" Normal {level.upper()} at a ratio of "
2539
+ details += f"{contrast_ratio}:1."
2540
+ else:
2541
+ details = f"fail: {filename} does NOT apply global colors"
2471
2542
  report.append(details)
2472
2543
  if not report:
2473
2544
  report.append("fail: no html files to apply color styles to")
@@ -50,6 +50,29 @@ def get_all_html_files(dir_path: str) -> list:
50
50
  return html_files
51
51
 
52
52
 
53
+ def get_image_hotlinks(soup: BeautifulSoup) -> list:
54
+ """Returns a list of all img src values that are external (hotlinked) URLs.
55
+
56
+ Hotlinked images are those with src attributes pointing to external
57
+ domains rather than local/relative paths. I ask that all students avoid
58
+ hotlinking images, as it's bad form and generally frowned upon.
59
+
60
+ Args:
61
+ soup: html soupt object.
62
+
63
+ Returns:
64
+ hotlinks: a list of hotlinked image srcs"""
65
+ hotlinks = []
66
+ # Get all img urls
67
+ image_elements = soup.find_all("img")
68
+ image_srcs = [img["src"] for img in image_elements]
69
+ for src in image_srcs:
70
+ if "http" in src[:4]:
71
+ hotlinks.append(src)
72
+
73
+ return hotlinks
74
+
75
+
53
76
  def get_html(file_path: str) -> BeautifulSoup:
54
77
  """Returns an html document (from file_path) as a BeautifulSoup object
55
78
 
File without changes
File without changes