webcode-tk 1.1.3__tar.gz → 1.1.5__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.1
2
2
  Name: webcode-tk
3
- Version: 1.1.3
3
+ Version: 1.1.5
4
4
  Summary:
5
5
  Home-page: https://github.com/HundredVisionsGuy/webcode-tk
6
6
  License: MIT
7
7
  Keywords: html,validation,css
8
8
  Author: hundredvisionsguy
9
9
  Author-email: cwinikka@gmail.com
10
- Requires-Python: >=3.8,<4.0
10
+ Requires-Python: >=3.9.0,<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.8
15
14
  Classifier: Programming Language :: Python :: 3.9
16
15
  Classifier: Programming Language :: Python :: 3.10
17
16
  Classifier: Programming Language :: Python :: 3.11
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "webcode-tk"
3
- version = "1.1.3"
3
+ version = "1.1.5"
4
4
  description = ""
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.8"
19
+ python = ">=3.9.0,<4.0"
20
20
  bs4 = "^0.0.1"
21
21
  file-clerk = "^1.0.9"
22
22
  lxml = "^4.9.1"
@@ -9,6 +9,7 @@ Modules exported by this package:
9
9
  properties and hex and rgb values.
10
10
  - `color_tools`: processes CSS color related properties and values.
11
11
  - `css_tools`: creates Stylesheet objects that store CSS information.
12
+ - `font_tools`: A collection of functions used to process font-related styles.
12
13
  - `html_tools`: gets html files from a project folder, gets the HTML code
13
14
  from files, gets number of a particular element in a file
14
15
  or folder, gets elements as tags, and much more.
@@ -18,4 +19,4 @@ Modules exported by this package:
18
19
  - `validator_tools`: sends HTML or CSS code to the W3C Validator to check
19
20
  for errors.
20
21
  """
21
- __version__ = "1.1.3"
22
+ __version__ = "1.1.5"
@@ -13,6 +13,7 @@ import re
13
13
  from collections import abc
14
14
  from typing import Union
15
15
 
16
+ from bs4 import NavigableString
16
17
  from file_clerk import clerk
17
18
 
18
19
  from webcode_tk import color_tools as color
@@ -69,6 +70,7 @@ class Element(object):
69
70
  parent=None,
70
71
  parent_size=root_font_size,
71
72
  ancestors=None,
73
+ tag_contents=None,
72
74
  ) -> None:
73
75
  self.name = name
74
76
  self.attributes = attributes
@@ -104,6 +106,10 @@ class Element(object):
104
106
  value, unit, parent_size, name
105
107
  )
106
108
  self.is_bold = True
109
+ self.direct_text = ""
110
+ if tag_contents:
111
+ self.__set_direct_text(tag_contents)
112
+ self.has_direct_text = bool(self.direct_text)
107
113
  self.visited_color = {
108
114
  "value": "",
109
115
  "sheet": "",
@@ -164,6 +170,16 @@ class Element(object):
164
170
  if parent:
165
171
  self.ancestors.append(self.parent)
166
172
 
173
+ def __set_direct_text(self, contents: list):
174
+ """get only text not in a tag"""
175
+ text = ""
176
+ for i in contents:
177
+ if isinstance(i, NavigableString):
178
+ new_text = str(i)
179
+ if new_text != "\n":
180
+ text += new_text.strip()
181
+ self.direct_text = text
182
+
167
183
  def __set_link_color(self):
168
184
  if self.name == "a":
169
185
  self.color["value"] = default_link_color
@@ -239,12 +255,6 @@ class Element(object):
239
255
  self.visited_background["value"] = new_bg
240
256
  self.visited_background["specificity"] = new_specificity
241
257
 
242
- # Do we have a link selector?
243
- is_link_selector = css.is_link_selector(selector)
244
- if is_link_selector:
245
- print("We have a link selector in ammend_color_styles")
246
- print("I suppose we should check specificity")
247
-
248
258
  def ammend_bg_color(
249
259
  self,
250
260
  bg_color: str,
@@ -335,11 +345,6 @@ class Element(object):
335
345
  hexc: the hex code for the color value.
336
346
  hexbg: the hex code for the background color.
337
347
  """
338
- bg_gradient = color.is_gradient(hexbg)
339
- c_gradient = color.is_gradient(hexc)
340
- if bg_gradient or c_gradient:
341
- # worst contrast ratio takes the hit
342
- print("let's process this")
343
348
  link_contrast = color.get_color_contrast_report(hexc, hexbg)
344
349
  link_ratio = color.contrast_ratio(hexc, hexbg)
345
350
 
@@ -364,9 +369,6 @@ class Element(object):
364
369
  hexc: the hex code for the color value.
365
370
  hexbg: the hex code for the background color.
366
371
  """
367
- has_gradient = color.is_gradient(hexc) or color.is_gradient(hexbg)
368
- if has_gradient:
369
- print()
370
372
  link_contrast = color.get_color_contrast_report(hexc, hexbg)
371
373
  link_ratio = color.contrast_ratio(hexc, hexbg)
372
374
 
@@ -565,8 +567,13 @@ class Element(object):
565
567
  targets = True
566
568
  break
567
569
  if selector_type == "id_selector":
570
+ # NOTE: testing has not uncovered this state
571
+ # we're keeping it here just in case
568
572
  if "id" in self.attributes.keys():
569
- print("do like we do for classes")
573
+ for val in self.attributes.get("id"):
574
+ if val == selector[1:]:
575
+ targets = True
576
+ break
570
577
  return targets
571
578
 
572
579
  def been_applied(self, property: str) -> bool:
@@ -673,6 +680,7 @@ class Element(object):
673
680
  self.background_color["value"] = val
674
681
  self.background_color["sheet"] = filename
675
682
  self.background_color["applied_by"] = "context"
683
+ self.background_color["selector"] = selector
676
684
 
677
685
 
678
686
  class CSSAppliedTree:
@@ -758,7 +766,7 @@ class CSSAppliedTree:
758
766
  parent_bg = root.background_color.get("value")
759
767
  body = Element(
760
768
  "body",
761
- parent=("html", root_id, parent_bg),
769
+ parent=["html", root_id, parent_bg],
762
770
  parent_size=root_font_size,
763
771
  )
764
772
  body.parent_size = body.font_size
@@ -792,40 +800,58 @@ class CSSAppliedTree:
792
800
  continue
793
801
  tag_children = tag.contents
794
802
  tag_attributes = tag.attrs
795
- parent = (element.name, id(element))
803
+ parent = [element.name, id(element)]
796
804
  new_element = Element(
797
805
  tag_name,
798
806
  attributes=tag_attributes,
799
807
  parent=parent,
800
808
  parent_size=element.font_size,
801
809
  ancestors=element.ancestors,
810
+ tag_contents=tag.contents,
802
811
  )
803
812
  self.__adjust_font_size(new_element)
804
813
  element.children.append(new_element)
805
- for tag in tag_children:
806
- if tag and not isinstance(tag, str):
807
- their_kids = tag.contents
808
- kids_attributes = tag.attrs
814
+ for item in tag_children:
815
+ if item and not isinstance(item, str):
816
+ their_kids = item.contents
817
+ kids_attributes = item.attrs
809
818
  if kids_attributes:
810
819
  kid_element = Element(
811
- tag.name,
820
+ item.name,
812
821
  attributes=kids_attributes,
813
- parent=(new_element.name, id(new_element)),
822
+ parent=[new_element.name, id(new_element)],
814
823
  parent_size=new_element.font_size,
815
824
  ancestors=new_element.ancestors,
825
+ tag_contents=item.contents,
816
826
  )
817
827
  else:
818
828
  kid_element = Element(
819
- tag.name,
820
- parent=(new_element.name, id(new_element)),
829
+ item.name,
830
+ parent=[new_element.name, id(new_element)],
821
831
  parent_size=new_element.font_size,
822
832
  ancestors=new_element.ancestors,
833
+ tag_contents=item.contents,
823
834
  )
824
835
  self.__adjust_font_size(kid_element)
825
836
  new_element.children.append(kid_element)
826
837
  self.__get_children(kid_element, their_kids)
827
838
  return
828
839
 
840
+ def extract_selectors(self, element):
841
+ """Get background color and color selectors"""
842
+ bg_data = ""
843
+ col_data = ""
844
+ if isinstance(element, Element):
845
+ if element.background_color.get("selector"):
846
+ bg_color = element.background_color.get("value")
847
+ bg_selector = element.background_color.get("selector")
848
+ bg_data = f"bg: {bg_color} selector: {bg_selector}"
849
+ if element.color.get("selector"):
850
+ color = element.color.get("value")
851
+ col_selector = element.color.get("selector")
852
+ col_data = f"color: {color} selector: {col_selector}"
853
+ return bg_data, col_data
854
+
829
855
  def __adjust_largeness(self) -> None:
830
856
  """sets the is_large property based on whether it's bold or not
831
857
  and the computed font size.
@@ -936,6 +962,7 @@ class CSSAppliedTree:
936
962
  filename: str,
937
963
  applied_by="default",
938
964
  selector_src="",
965
+ parent=None,
939
966
  ) -> None:
940
967
  """recursively adjust color where necessary to all elements that
941
968
  apply.
@@ -952,6 +979,22 @@ class CSSAppliedTree:
952
979
  element: the element in question.
953
980
  ruleset: the ruleset we want to apply.
954
981
  filename: the file where the styles came from."""
982
+ # If there is a parent, it's a good time to adjust the direct ancestor
983
+ if parent:
984
+ bg_sel, col_sel = self.extract_selectors(parent)
985
+ direct_ancestor = element.ancestors[-1]
986
+ if len(direct_ancestor) == 2:
987
+ direct_ancestor.append("")
988
+ if len(direct_ancestor) == 3:
989
+ direct_ancestor.append(bg_sel)
990
+ direct_ancestor.append(col_sel)
991
+ elif len(direct_ancestor) == 4:
992
+ direct_ancestor[-1] = bg_sel
993
+ direct_ancestor.append(col_sel)
994
+ elif len(direct_ancestor) == 5:
995
+ direct_ancestor[-2] = bg_sel
996
+ direct_ancestor[-1] = col_sel
997
+
955
998
  # Adjust colors if necessary - if there was a change,
956
999
  # adjust colors for children
957
1000
  selector = list(ruleset.keys())[0]
@@ -1000,32 +1043,6 @@ class CSSAppliedTree:
1000
1043
  self.__adjust_child_colors(
1001
1044
  child, ruleset, filename, element_name
1002
1045
  )
1003
- elif is_background_prop:
1004
- # background - if not set directly - apply by context
1005
- value = declaration.get("background-color")
1006
- if not value:
1007
- value = declaration.get("background")
1008
- if property == "background-image":
1009
- value = declaration.get("background-image")
1010
-
1011
- # adjust if value is not the same as current value
1012
- applied_by = element.background_color.get("applied_by")
1013
- current_value = element.background_color.get("value")
1014
- time_to_adjust = (
1015
- applied_by == "default"
1016
- or applied_by == "context"
1017
- and value != current_value
1018
- )
1019
- # But only if the value was applied to an ancestor
1020
- # For example, a th is not a parent of a td
1021
- applied_to_ancestor = False
1022
- for ancestor in element.ancestors:
1023
- ancestor_tag = ancestor[0]
1024
- if ancestor_tag == selector_src:
1025
- applied_to_ancestor = True
1026
- time_to_adjust = time_to_adjust and applied_to_ancestor
1027
- if time_to_adjust:
1028
- print("Wait")
1029
1046
  can_inherit = self.can_inherit_styles(element, selector, ruleset)
1030
1047
  if can_inherit and applies:
1031
1048
  declaration = ruleset.get(selector)
@@ -1034,7 +1051,7 @@ class CSSAppliedTree:
1034
1051
  selector, declaration.copy(), new_specificity, filename
1035
1052
  )
1036
1053
  for child in element.children:
1037
- self.__adjust_colors(child, ruleset, filename)
1054
+ self.__adjust_colors(child, ruleset, filename, parent=element)
1038
1055
 
1039
1056
  def __adjust_child_colors(
1040
1057
  self,
@@ -1060,6 +1077,8 @@ class CSSAppliedTree:
1060
1077
  filename: name of the file where the styles came from
1061
1078
  applied_from: the name of the element that was targetted
1062
1079
  """
1080
+ # First adjust direct ancestor
1081
+
1063
1082
  # Adjust colors if necessary - if there was a change,
1064
1083
  # adjust colors for children
1065
1084
  selector = list(ruleset.keys())[0]
@@ -1607,9 +1626,10 @@ def update_contrast_results(
1607
1626
  def get_color_contrast_details(tree: CSSAppliedTree, rating="AAA") -> list:
1608
1627
  """returns a list of all failures or a pass based on rating
1609
1628
 
1610
- We'll create a recursive inner nested function to traverse the DOM. We will
1611
- target only the styles that are applied directly so as to not overwhelm the
1612
- user and allow fixing the error more easily.
1629
+ We'll create a recursive inner nested function to traverse the DOM. We
1630
+ will target all elements, and then report on number of errors triggered
1631
+ by each selector to help avoid overwhelming the user with too many
1632
+ errors.
1613
1633
 
1614
1634
  Args:
1615
1635
  tree: the cascade tree for a single file.
@@ -1624,68 +1644,183 @@ def get_color_contrast_details(tree: CSSAppliedTree, rating="AAA") -> list:
1624
1644
  elements = tree.children
1625
1645
 
1626
1646
  def check_element(
1627
- el: Element, rating: str, filename: str
1647
+ el: Element, rating: str, filename: str, parent_sel=None
1628
1648
  ) -> Union[list, None]:
1629
1649
  children = el.children
1630
- msg = ""
1631
- applied_directly = (
1632
- el.background_color.get("applied_by") == "directly"
1633
- or el.color.get("applied_by") == "directly"
1634
- )
1635
- if applied_directly or el.name == "body":
1636
- is_gradient = el.background_color.get("is_gradient")
1637
- if is_gradient:
1638
- # get worst case
1639
- worst_case = el.background_color.get("worst_contrast")
1640
- worst_results = worst_case.get("results")
1641
- fails = "fail" in worst_results[:5]
1642
- if fails:
1643
- selector = el.background_color.get("selector")
1644
- if el.is_large:
1645
- size = "Large"
1646
- else:
1647
- size = "Normal"
1648
- msg = f"{el.name} tag (selector: {selector}) fails color "
1649
- msg += f"contrast at {size} {rating}."
1650
- results.append(msg)
1651
- return
1652
- contrast_data = el.contrast_data
1653
- if el.is_large:
1654
- if rating == "AAA":
1655
- passes = contrast_data.get("large_aaa")
1656
- else:
1657
- passes = contrast_data.get("large_aa")
1658
- else:
1659
- if rating == "AAA":
1660
- passes = contrast_data.get("normal_aaa")
1661
- else:
1662
- passes = contrast_data.get("normal_aa")
1663
1650
 
1664
- # only add results if failure
1665
- if not passes:
1651
+ is_gradient = el.background_color.get("is_gradient")
1652
+
1653
+ # if the gradient was not directly applied, we won't get is_gradient
1654
+ # since it might be applied contextually, we should double check
1655
+ if not is_gradient:
1656
+ bg_value = el.background_color.get("value")
1657
+ is_gradient = "gradient" in bg_value
1658
+
1659
+ # we should recalculate contrast if it didn't register
1660
+ if is_gradient:
1661
+ fg_color = el.color.get("value")
1662
+ bg_color = el.background_color.get("value")
1663
+ contrast_details = color.get_color_contrast_with_gradients(
1664
+ fg_color, bg_color, el.ancestors
1665
+ )
1666
+ worst_case = {
1667
+ "has_alpha": False,
1668
+ "computed_value": "",
1669
+ "contrast_ratio": 0.0,
1670
+ "results": "",
1671
+ }
1672
+ contrast, fg_color = contrast_details[0][:2]
1673
+ composite_bg, bg_has_alpha = contrast_details[0][3:]
1674
+ worst_case["has_alpha"] = bg_has_alpha
1675
+ worst_case["computed_value"] = composite_bg
1676
+ worst_case["contrast_ratio"] = contrast
1677
+ hex1 = color.get_hex(fg_color)
1678
+ hex2 = color.get_hex(composite_bg)
1679
+ passes_contrast = color.get_contrast_results_from_ratio(
1680
+ contrast, el.font_size, el.is_bold
1681
+ )
1682
+ worst_case["results"] = passes_contrast
1683
+ el.background_color["worst_contrast"] = worst_case
1684
+ if is_gradient:
1685
+ # get worst case
1686
+ worst_case = el.background_color.get("worst_contrast")
1687
+ worst_results = worst_case.get("results")
1688
+ fails = "fail" in worst_results[:5]
1689
+ if "conditional pass" in worst_results:
1690
+ if rating not in worst_results:
1691
+ fails = True
1692
+ if fails:
1693
+ selector = el.background_color.get("selector")
1666
1694
  if el.is_large:
1667
1695
  size = "Large"
1668
1696
  else:
1669
1697
  size = "Normal"
1670
- selector = el.background_color.get("selector")
1671
- if not selector:
1672
- selector = el.color.get("selector")
1673
- msg = f"selector: {selector} fails color contrast {size}"
1674
- msg += f"{rating}"
1675
- results.append(msg)
1698
+ if el.has_direct_text:
1699
+ if not selector and parent_sel:
1700
+ selector = parent_sel
1701
+ results.append((filename, selector, size, rating))
1702
+
1703
+ # NOTE: it's possible we won't be able to get a selector
1704
+ # in the future. If so, add an else block and deal with it
1705
+
1706
+ else:
1707
+ # We have a failure, but without direct text
1708
+ # We need to verify whether the failure is passed
1709
+ # on to the children or not
1710
+ parent_bg = el.background_color
1711
+ parent_bg_selector = parent_bg.get("selector")
1712
+ parent_col = el.color
1713
+ parent_col_selector = parent_col.get("selector")
1714
+ for child in children:
1715
+ child_bg_val = child.background_color.get("value")
1716
+ child_col_val = child.color.get("value")
1717
+
1718
+ # Check to see if the values in the child are the
1719
+ # same as with the parent, in which case, if parent
1720
+ # fails, then child should too
1721
+ if child_bg_val == parent_bg.get(
1722
+ "value"
1723
+ ) and child_col_val == parent_col.get("value"):
1724
+ selector = parent_bg_selector
1725
+ if not selector:
1726
+ selector = parent_col_selector
1727
+
1728
+ # double check to make sure child is same size or
1729
+ # smaller than parent (size is parent's size)
1730
+ if size == "Normal" and child.is_large:
1731
+ hex1 = color.get_hex(child_bg_val)
1732
+
1733
+ # In case the bg color is a gradient, we
1734
+ # need to pull from parent's bg_data
1735
+ if not hex1:
1736
+ css_color = parent_bg.get("computed_value")
1737
+ if css_color:
1738
+ hex1 = color.get_hex(css_color)
1739
+ else:
1740
+ parent_bg_val = worst_case.get("value")
1741
+ hex1 = color.get_hex(parent_bg_val)
1742
+ hex2 = color.get_hex(child_col_val)
1743
+ child_contrast = (
1744
+ color.get_color_contrast_report(hex1, hex2)
1745
+ )
1746
+ child_sizing = f"Large {rating}"
1747
+ passes = "Fail" != child_contrast.get(
1748
+ child_sizing
1749
+ )
1750
+ if not passes:
1751
+ results.append(
1752
+ (filename, selector, size, rating)
1753
+ )
1754
+ else:
1755
+ results.append(
1756
+ (filename, selector, size, rating)
1757
+ )
1758
+
1759
+ # TODO: add tests to look for a parent with normal
1760
+ # size and a child that's large - make an else block
1761
+ return
1762
+ contrast_data = el.contrast_data
1763
+ if el.is_large:
1764
+ if rating == "AAA":
1765
+ passes = contrast_data.get("large_aaa")
1766
+ else:
1767
+ passes = contrast_data.get("large_aa")
1768
+ else:
1769
+ if rating == "AAA":
1770
+ passes = contrast_data.get("normal_aaa")
1771
+ else:
1772
+ passes = contrast_data.get("normal_aa")
1773
+
1774
+ # only add results if failure
1775
+ if not passes:
1776
+ if el.is_large:
1777
+ size = "Large"
1778
+ else:
1779
+ size = "Normal"
1780
+ selector = el.background_color.get("selector")
1781
+ if not selector:
1782
+ selector = el.color.get("selector")
1783
+
1784
+ # if there still isn't a selector, get the parent selector
1785
+ if not selector:
1786
+ selector = parent_sel
1787
+
1788
+ # and only if the element has direct text (experimental)
1789
+ if el.has_direct_text:
1790
+ results.append((filename, selector, size, rating))
1676
1791
 
1677
1792
  if not children:
1678
1793
  return
1679
1794
  else:
1680
1795
  for kid in children:
1681
- check_element(kid, rating, filename)
1796
+ if el.background_color.get("selector"):
1797
+ parent_selector = el.background_color.get("selector")
1798
+ else:
1799
+ parent_selector = el.color.get("selector")
1800
+ if not parent_selector:
1801
+ parent_selector = kid.ancestors[-1][3]
1802
+ if not parent_selector:
1803
+ parent_selector = kid.ancestors[-1][-1]
1804
+ if parent_selector:
1805
+ parent_selector = parent_selector.split("selector:")[
1806
+ -1
1807
+ ]
1808
+ check_element(kid, rating, filename, parent_selector)
1682
1809
 
1683
1810
  for element in elements:
1684
1811
  check_element(element, rating, filename)
1685
1812
  if results:
1686
1813
  adjusted = []
1687
1814
  for result in results:
1688
- adjusted.append(f"fail: ({filename}) " + result)
1815
+ count = results.count(result)
1816
+ file, selector, size, rating = result
1817
+ msg = f"fail: in {file}, selector: {selector} triggered "
1818
+ if count > 1:
1819
+ msg += f"{count} contrast errors for {size} {rating}."
1820
+ else:
1821
+ msg += f"contrast error for {size} {rating}."
1822
+ if msg not in adjusted:
1823
+ adjusted.append(msg)
1689
1824
  results = adjusted
1690
1825
  else:
1691
1826
  # It's a success! Let's let them know
@@ -1716,8 +1851,6 @@ def get_color_contrast_report(dir_path: str, rating="AAA") -> list:
1716
1851
  for item in report:
1717
1852
  if item not in results:
1718
1853
  results.append(item)
1719
- # Do one more pass on hyperlinks and double check contrast
1720
- print(results)
1721
1854
  return results
1722
1855
 
1723
1856
 
@@ -808,10 +808,12 @@ def get_color_contrast_with_gradients(
808
808
  bg_color,
809
809
  ]
810
810
  composite_color = ""
811
+
811
812
  # check all permutations of color combinations for contrast results
812
813
  for foreground in foreground_colors:
813
814
  for background in bg_colors:
814
815
  composite_color = ""
816
+
815
817
  # check for background alpha transparency
816
818
  bg_has_alpha = has_alpha_channel(background)
817
819
  if bg_has_alpha:
@@ -819,11 +821,17 @@ def get_color_contrast_with_gradients(
819
821
  for i in range(len(ancestors) - 1, -1, -1):
820
822
  ancestor = ancestors[i]
821
823
  if len(ancestor) > 2:
822
- base_color = ancestor[2]
823
- composite_color = blend_alpha(base_color, background)
824
- container_bg = ancestor[2]
824
+ if ancestor[2]:
825
+ base_color = ancestor[2]
826
+ composite_color = blend_alpha(
827
+ base_color, background
828
+ )
829
+ container_bg = ancestor[2]
830
+ elif "background:" in ancestor[-1]:
831
+ input("Get something in here")
825
832
  if not composite_color:
826
833
  composite_color = blend_alpha(container_bg, background)
834
+
827
835
  # convert each to hex
828
836
  hex1 = to_hex(foreground)
829
837
  if composite_color:
@@ -7,6 +7,7 @@ from typing import Union
7
7
 
8
8
  from file_clerk import clerk
9
9
 
10
+ from webcode_tk import cascade_tools
10
11
  from webcode_tk import color_keywords as keyword
11
12
  from webcode_tk import color_tools
12
13
  from webcode_tk import html_tools
@@ -2469,6 +2470,8 @@ def get_heading_color_report(project_dir: str) -> list:
2469
2470
  report: a list of files and a pass or fail message for each."""
2470
2471
  report = []
2471
2472
  header_re = regex_patterns.get("header_selector")
2473
+
2474
+ # make sure we have a trailing slash separator
2472
2475
  if project_dir[-1] != "/":
2473
2476
  project_dir += "/"
2474
2477
  all_file_data = get_all_project_stylesheets(project_dir)
@@ -2477,13 +2480,12 @@ def get_heading_color_report(project_dir: str) -> list:
2477
2480
  filepath = project_dir + filename
2478
2481
  all_color_rules = get_all_color_rules(filepath)
2479
2482
  header_selectors = []
2480
- for key, val in all_color_rules.items():
2481
- if key == "file":
2482
- continue
2483
- is_header_selector = re.findall(header_re, key)
2483
+
2484
+ # Look through all selectors and their values
2485
+ # if selector is a header selector, then check color
2486
+ for sel, val in all_color_rules.items():
2487
+ is_header_selector = re.findall(header_re, sel)
2484
2488
  if is_header_selector:
2485
- # we have a header selector
2486
- # we only need to check color
2487
2489
  color_data = val
2488
2490
  color_value = color_data.get("color")
2489
2491
  bg_value = color_data.get("background-color")
@@ -2537,7 +2539,90 @@ def get_project_color_contrast_report(project_dir: str, level="AAA") -> list:
2537
2539
  return report
2538
2540
 
2539
2541
 
2542
+ def get_element_rulesets(project_dir: str, element: str) -> list:
2543
+ rulesets = []
2544
+ styles_by_files = get_styles_by_html_files(project_dir)
2545
+ for file in styles_by_files:
2546
+ elements = None
2547
+ filepath = file.get("file")
2548
+ filename = clerk.get_file_name(filepath)
2549
+ sheets = file.get("stylesheets")
2550
+ elements = html_tools.get_elements(element, filepath)
2551
+ for sheet in sheets:
2552
+ for ruleset in sheet.rulesets:
2553
+ sel = ruleset.selector
2554
+ selector_applies = cascade_tools.does_selector_apply(
2555
+ elements[0], sel
2556
+ )
2557
+ if selector_applies:
2558
+ rulesets.append((filename, ruleset))
2559
+
2560
+ return rulesets
2561
+
2562
+
2563
+ def get_properties_applied_report(project_dir: str, goals: dict) -> list:
2564
+ """returns a report on any elements that fail to have a property applied
2565
+
2566
+ Args:
2567
+ project_dir: the path to the project folder
2568
+ goals: a dictionary of elements and the properties expected to be
2569
+ present
2570
+
2571
+ Returns:
2572
+ report: a list of pass or fail messages that indicates the file, the
2573
+ element, and the missing property.
2574
+ """
2575
+ report = []
2576
+ elements = list(goals.keys())
2577
+ for element in elements:
2578
+ properties_found = []
2579
+ properties = goals.get(element)
2580
+ element_rulesets = get_element_rulesets(project_dir, element)
2581
+
2582
+ # check all rulesets for a given file to see which rulesets have
2583
+ # one or more properties we are checking for
2584
+ for file, rulesets in element_rulesets:
2585
+ found_properties_remaining = list(properties)
2586
+ for expected_property in properties:
2587
+ declarations = rulesets.declaration_block.declarations
2588
+
2589
+ # check all declarations to see if any properties
2590
+ # are a match and pull it from the list of remaining
2591
+ # properties.
2592
+ for declaration in declarations:
2593
+ element_property = declaration.property
2594
+ if expected_property == element_property:
2595
+ properties_found.append(element_property)
2596
+ found_properties_remaining.remove(element_property)
2597
+ break
2598
+
2599
+ # If there are any properties left, it's a fail
2600
+ if found_properties_remaining:
2601
+ count = len(found_properties_remaining)
2602
+ msg = f"fail: in {file}, the {element} tag does not apply "
2603
+ if count == 1:
2604
+ msg += f"1 property: {found_properties_remaining[0]}."
2605
+ else:
2606
+ msg += f"{count} properties: "
2607
+ msg += f"{found_properties_remaining}."
2608
+
2609
+ # all properties were accounted for (none remaining)
2610
+ else:
2611
+ msg = f"pass: in {file}, the {element} tag applies all "
2612
+ msg += "required properties."
2613
+ report.append(msg)
2614
+ return report
2615
+
2616
+
2540
2617
  if __name__ == "__main__":
2618
+ project_folder = "tests/test_files/cascade_complexities"
2619
+ goals = {
2620
+ "figure": ("margin", "padding", "border", "float"),
2621
+ }
2622
+ report = get_properties_applied_report(project_folder, goals)
2623
+ rulesets = get_element_rulesets(
2624
+ "tests/test_files/cascade_complexities", "figure"
2625
+ )
2541
2626
  insane_gradient = """
2542
2627
  -moz-radial-gradient(0% 200%, ellipse cover,
2543
2628
  rgba(143, 193, 242, 0.22) 10%,rgba(240, 205, 247,0) 40%),
@@ -22,9 +22,10 @@ dependent upon the font-family itself and require information from the
22
22
  font-file or some other hidden calculation from the browser.
23
23
  """
24
24
  import re
25
+ from typing import Any
25
26
  from typing import Union
26
27
 
27
- from webcode_tk.css_tools import Stylesheet
28
+ from webcode_tk import css_tools
28
29
 
29
30
  absolute_keyword_regex = (
30
31
  r"\b(?:xx-small|x-small|small(?!-caps)|medium|large|x-large|"
@@ -76,7 +77,7 @@ FONT_NON_SIZE_NON_FAMILY_KEYWORDS = (
76
77
  )
77
78
 
78
79
 
79
- def get_absolute_keyword_values(css_code: Union[str, Stylesheet]) -> list:
80
+ def get_absolute_keyword_values(css_code: any) -> list:
80
81
  """returns a list of all keyword values in CSS
81
82
 
82
83
  To be safe, we should remove all selectors using the curly brackets
@@ -85,7 +86,7 @@ def get_absolute_keyword_values(css_code: Union[str, Stylesheet]) -> list:
85
86
  Args:
86
87
  css_code: the CSS styles in either string or Stylesheet format
87
88
  """
88
- if isinstance(css_code, Stylesheet):
89
+ if isinstance(css_code, css_tools.Stylesheet):
89
90
  styles = css_code.text
90
91
  else:
91
92
  styles = css_code
@@ -108,7 +109,7 @@ def get_absolute_keyword_values(css_code: Union[str, Stylesheet]) -> list:
108
109
  return values
109
110
 
110
111
 
111
- def get_numeric_fontsize_values(css_code: Union[str, Stylesheet]) -> list:
112
+ def get_numeric_fontsize_values(css_code: Any) -> list:
112
113
  """returns a list of all numeric font size values.
113
114
 
114
115
  This should work for any standard size value (em, px, %,
@@ -117,10 +118,10 @@ def get_numeric_fontsize_values(css_code: Union[str, Stylesheet]) -> list:
117
118
  Args:
118
119
  css_code: the CSS styles in either string or Stylesheet format.
119
120
  """
120
- if isinstance(css_code, Stylesheet):
121
- code = css_code.text
122
- else:
121
+ if isinstance(css_code, str):
123
122
  code = css_code
123
+ else:
124
+ code = css_code.text
124
125
  matches = re.findall(numeric_value_regex, code)
125
126
  full_matches = []
126
127
  for match in matches:
File without changes
File without changes