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.
- {webcode_tk-1.2.9 → webcode_tk-1.2.12}/PKG-INFO +3 -3
- {webcode_tk-1.2.9 → webcode_tk-1.2.12}/pyproject.toml +3 -2
- {webcode_tk-1.2.9 → webcode_tk-1.2.12}/webcode_tk/__init__.py +1 -1
- {webcode_tk-1.2.9 → webcode_tk-1.2.12}/webcode_tk/cascade_tools.py +10 -0
- {webcode_tk-1.2.9 → webcode_tk-1.2.12}/webcode_tk/color_tools.py +5 -1
- {webcode_tk-1.2.9 → webcode_tk-1.2.12}/webcode_tk/contrast_tools.py +0 -34
- {webcode_tk-1.2.9 → webcode_tk-1.2.12}/webcode_tk/css_tools.py +86 -15
- {webcode_tk-1.2.9 → webcode_tk-1.2.12}/webcode_tk/html_tools.py +23 -0
- {webcode_tk-1.2.9 → webcode_tk-1.2.12}/LICENSE.rst +0 -0
- {webcode_tk-1.2.9 → webcode_tk-1.2.12}/README.rst +0 -0
- {webcode_tk-1.2.9 → webcode_tk-1.2.12}/webcode_tk/animation_tools.py +0 -0
- {webcode_tk-1.2.9 → webcode_tk-1.2.12}/webcode_tk/color_keywords.py +0 -0
- {webcode_tk-1.2.9 → webcode_tk-1.2.12}/webcode_tk/font_tools.py +0 -0
- {webcode_tk-1.2.9 → webcode_tk-1.2.12}/webcode_tk/sample_report.json +0 -0
- {webcode_tk-1.2.9 → webcode_tk-1.2.12}/webcode_tk/utils.py +0 -0
- {webcode_tk-1.2.9 → webcode_tk-1.2.12}/webcode_tk/ux_tools.py +0 -0
- {webcode_tk-1.2.9 → webcode_tk-1.2.12}/webcode_tk/validator_tools.py +0 -0
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: webcode-tk
|
|
3
|
-
Version: 1.2.
|
|
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.
|
|
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.
|
|
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.
|
|
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 = "*"
|
|
@@ -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
|
|
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
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
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
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
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
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|