webcode-tk 1.1.5__tar.gz → 1.1.7__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
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: webcode-tk
3
- Version: 1.1.5
3
+ Version: 1.1.7
4
4
  Summary:
5
5
  Home-page: https://github.com/HundredVisionsGuy/webcode-tk
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "webcode-tk"
3
- version = "1.1.5"
3
+ version = "1.1.7"
4
4
  description = ""
5
5
  authors = ["hundredvisionsguy <cwinikka@gmail.com>"]
6
6
  keywords = ["html", "validation", "css", ]
@@ -19,4 +19,4 @@ Modules exported by this package:
19
19
  - `validator_tools`: sends HTML or CSS code to the W3C Validator to check
20
20
  for errors.
21
21
  """
22
- __version__ = "1.1.5"
22
+ __version__ = "1.1.7"
@@ -315,6 +315,16 @@ class Element(object):
315
315
  type: the type of color contrast.
316
316
  """
317
317
  col = self.color.get("value")
318
+ if not col:
319
+ for i in range(len(self.ancestors) - 1, -1, -1):
320
+ anc = self.ancestors[i]
321
+ if len(anc) > 2:
322
+ color_details = anc[4]
323
+ col = color_details.split(":")[1]
324
+ col = col.strip()
325
+ col = col.split()[0]
326
+ if color.is_color_value(col):
327
+ break
318
328
  bg = self.background_color.get("value")
319
329
  if color.is_gradient(bg):
320
330
  results = color.get_color_contrast_with_gradients(
@@ -1273,6 +1283,7 @@ def does_selector_apply(element: Element, selector: str) -> bool:
1273
1283
  is_descendant_selector = bool(
1274
1284
  re.match(regex_patterns.get("descendant_selector"), sel)
1275
1285
  )
1286
+ is_at_rule = "@" in sel
1276
1287
 
1277
1288
  # If the tag is an anchor, but the selector is not - doesn't apply
1278
1289
  # link color can only be changed by a link selector
@@ -1348,6 +1359,10 @@ def does_selector_apply(element: Element, selector: str) -> bool:
1348
1359
  applies = descendant_selector_applies(element, sel)
1349
1360
  if applies:
1350
1361
  break
1362
+ elif is_at_rule:
1363
+ # We should return false as it is an @rule and shouldn't
1364
+ # apply
1365
+ return False
1351
1366
  else:
1352
1367
  raise ValueError(f"Selector not recognized: Got {selector}")
1353
1368
  return applies
@@ -19,7 +19,7 @@ regex_patterns: dict = {
19
19
  "advanced_link_selector": r"(a[:.#\[]\w+)",
20
20
  "attribute_selectors": r"[a-zA-Z]*\[(.*?)\]",
21
21
  "child_combinator": r"\w+\s*>\s*\w+",
22
- "class_selector": r"\.\w+",
22
+ "class_selector": r"\w*\.\w+",
23
23
  "descendant_selector": r"\w+\s\w+",
24
24
  "general_sibling_combinator": r"\w+\s*~\s*\w+",
25
25
  "grouped_selector": r"\w+\s*,\s*\w+",
@@ -175,44 +175,51 @@ class Stylesheet:
175
175
  self.text = code_without_comments
176
176
 
177
177
  def __extract_nested_at_rules(self):
178
- """Pulls out any nested at-rule and stores them in a list."""
178
+ """Pulls out any nested at-rule and stores them in a list.
179
+
180
+ Algorithm: get # of @ signs"""
179
181
  at_rules = []
180
182
  non_at_rules_css = []
181
183
 
182
- # split at the double }} (end of a nested at rule)
183
- css_split = self.text.split("}}")
184
- css_split = restore_braces(css_split)
184
+ css_code = self.text
185
185
 
186
- if len(css_split) == 1:
186
+ # no @ sign, no at_rules - we're done
187
+ num_at_rules = css_code.count("@")
188
+ if num_at_rules == 0:
187
189
  return
188
- for code in css_split:
189
- # continue if empty
190
- if not code.strip():
191
- continue
192
- for rule in nested_at_rules:
193
- # if there is a nested @rule
194
- # split code from @rule
195
- if rule in code:
196
- split_code = code.split(rule)
197
- if len(split_code) == 2:
198
- if split_code[0]:
199
- # an @rule was NOT at the beginning or else,
200
- # there would be an empty string
201
- # that means there is CSS to add (non-at-rules)
202
- non_at_rules_css.append(split_code[0])
203
-
204
- # create a nested at-rule object
205
- text = split_code[1]
206
- pos = text.find("{")
207
- at_rule = rule + text[:pos]
208
- ruleset_string = text[pos + 1 : -1]
209
- nested = NestedAtRule(at_rule, ruleset_string)
210
- if nested.has_repeat_selectors:
211
- self.has_repeat_selectors = True
212
- at_rules.append(nested)
213
- else:
214
- # it's only an @rule
215
- print("skipping non-nested @rule.")
190
+
191
+ # add a marker symbols !! to indicate @ sign after split
192
+ css_code = css_code.replace("@", "@!!")
193
+
194
+ # everything between @ sign and }} is a nested at rule
195
+ css_split_at_at = css_code.split("@")
196
+
197
+ # Loop through code, each string beginning with ! is an at-rule
198
+ for code in css_split_at_at:
199
+ if code[:2] == "!!":
200
+ code = code.replace("!!", "@")
201
+
202
+ # get a slice up to }} (end of @rule)
203
+ code_split = code.split("}}")
204
+
205
+ # first element is the @rule
206
+ at_rule = code_split[0] + "}}"
207
+
208
+ # create a nested at-rule object
209
+ pos = at_rule.find("{")
210
+ rule = at_rule[:pos]
211
+ ruleset_string = at_rule[pos + 1 : -1]
212
+ nested = NestedAtRule(rule, ruleset_string)
213
+ if nested.has_repeat_selectors:
214
+ self.has_repeat_selectors = True
215
+ at_rules.append(nested)
216
+
217
+ # second element is any other CSS code
218
+ non_at_rule = code_split[1]
219
+ if non_at_rule:
220
+ non_at_rules_css.append(non_at_rule)
221
+ else:
222
+ non_at_rules_css.append(code)
216
223
 
217
224
  self.text = "".join(non_at_rules_css)
218
225
  self.nested_at_rules = at_rules
@@ -273,10 +280,22 @@ class Stylesheet:
273
280
  ):
274
281
  selector = ruleset.selector
275
282
  for declaration in ruleset.declaration_block.declarations:
276
- if (
283
+ color_counts = (
277
284
  "color" in declaration.property
278
285
  or "background" in declaration.property
279
- ):
286
+ and declaration.property
287
+ not in [
288
+ "border-color",
289
+ "outline-color",
290
+ "text-declaration-color",
291
+ "text-emphasis-color",
292
+ "text-shadow",
293
+ "caret-color",
294
+ "column-rule-color",
295
+ "print-color-adjust",
296
+ ]
297
+ )
298
+ if color_counts:
280
299
  property = declaration.property
281
300
  value = declaration.value
282
301
 
@@ -2548,6 +2567,8 @@ def get_element_rulesets(project_dir: str, element: str) -> list:
2548
2567
  filename = clerk.get_file_name(filepath)
2549
2568
  sheets = file.get("stylesheets")
2550
2569
  elements = html_tools.get_elements(element, filepath)
2570
+ if not elements:
2571
+ return []
2551
2572
  for sheet in sheets:
2552
2573
  for ruleset in sheet.rulesets:
2553
2574
  sel = ruleset.selector
@@ -2563,6 +2584,25 @@ def get_element_rulesets(project_dir: str, element: str) -> list:
2563
2584
  def get_properties_applied_report(project_dir: str, goals: dict) -> list:
2564
2585
  """returns a report on any elements that fail to have a property applied
2565
2586
 
2587
+ goals should be a dictionary with a key for each element we are checking.
2588
+ Each key has as it's value a dictionary with 1 to 3 possible keys...
2589
+ 1. (required) properties (the properties that should be applied)
2590
+ 2. (optional) min_required: If min_required is specified, then to pass
2591
+ it only requires the minimum number of properties to be present.
2592
+
2593
+ Sample goals might look like the following:
2594
+ goals_simple = {
2595
+ "figure": {
2596
+ "properties": ("box-shadow", "border-radius", "animation"),
2597
+ }
2598
+ }
2599
+ goals_complex = {
2600
+ "figure": {
2601
+ "properties": ("box-shadow", "border-radius", "animation"),
2602
+ "min_required": 2,
2603
+ }
2604
+ }
2605
+
2566
2606
  Args:
2567
2607
  project_dir: the path to the project folder
2568
2608
  goals: a dictionary of elements and the properties expected to be
@@ -2576,48 +2616,172 @@ def get_properties_applied_report(project_dir: str, goals: dict) -> list:
2576
2616
  elements = list(goals.keys())
2577
2617
  for element in elements:
2578
2618
  properties_found = []
2579
- properties = goals.get(element)
2580
- element_rulesets = get_element_rulesets(project_dir, element)
2619
+ details = goals.get(element)
2620
+ min_required = 0
2621
+ # details might be a tuple or a dictionary
2622
+ if isinstance(details, tuple):
2623
+ properties = details
2624
+ min_required = len(properties)
2625
+ else:
2626
+ properties = details.get("properties")
2627
+ min_required = details.get("min_required")
2581
2628
 
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:
2629
+ html_files = get_styles_by_html_files(project_dir)
2630
+ for file in html_files:
2585
2631
  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
2632
+ file_path = file.get("file")
2633
+ targetted_elements_in_file = html_tools.get_elements(
2634
+ element, file_path
2635
+ )
2636
+ # check all selectors in all stylesheet objects
2637
+ for sheet in file.get("stylesheets"):
2638
+ for rule in sheet.rulesets:
2639
+ selector = rule.selector
2640
+ selector_type = get_selector_type(selector)
2641
+ if selector_type == "type_selector":
2642
+ if selector == element:
2643
+ # loop through all properties and take what we can
2644
+ take_targetted_properties(
2645
+ properties_found,
2646
+ properties,
2647
+ found_properties_remaining,
2648
+ rule,
2649
+ )
2650
+ if selector_type == "descendant_selector":
2651
+ sel_split = selector.split()
2652
+ target = sel_split[-1]
2653
+ if element == target:
2654
+ take_targetted_properties(
2655
+ properties_found,
2656
+ properties,
2657
+ found_properties_remaining,
2658
+ rule,
2659
+ )
2660
+ if selector_type == "grouped_selector":
2661
+ sel_split = selector.split(",")
2662
+ if element in sel_split:
2663
+ take_targetted_properties(
2664
+ properties_found,
2665
+ properties,
2666
+ found_properties_remaining,
2667
+ rule,
2668
+ )
2669
+ if selector_type == "pseudo_selector":
2670
+ sel_split = selector.split(":")
2671
+ target = sel_split[0]
2672
+ if target == element:
2673
+ take_targetted_properties(
2674
+ properties_found,
2675
+ properties,
2676
+ found_properties_remaining,
2677
+ rule,
2678
+ )
2679
+ if selector_type == "id_selector":
2680
+ # could be starting with # or have tag, then hash
2681
+ # tag, then ID
2682
+ the_tag, its_id = selector.split("#")
2683
+ for target in targetted_elements_in_file:
2684
+ # our target must have an id attribute or no match
2685
+ if target.attrs:
2686
+ target_id = target.attrs.get("id")
2687
+ if target_id:
2688
+ a_match = ""
2689
+ if the_tag:
2690
+ a_match = element + "#" + target_id
2691
+ else:
2692
+ a_match = "#" + target_id
2693
+ if selector == a_match:
2694
+ take_targetted_properties(
2695
+ properties_found,
2696
+ properties,
2697
+ found_properties_remaining,
2698
+ rule,
2699
+ )
2700
+ if selector_type == "class_selector":
2701
+ # is the first part the tag?
2702
+
2703
+ selector_split = selector.split(".")
2704
+ selector_tag = ""
2705
+ if selector[0] != ".":
2706
+ selector_tag = selector_split[0]
2707
+ its_classes = selector_split[1:]
2708
+ for target in targetted_elements_in_file:
2709
+ # our target must have attributes
2710
+ if target.attrs:
2711
+ target_classes = target.attrs.get("class")
2712
+ if target_classes:
2713
+ # We need to sort both and compare
2714
+ its_classes.sort()
2715
+ target_classes.sort()
2716
+ if its_classes == target_classes:
2717
+ # could be a match
2718
+ if selector_tag:
2719
+ # but not if the selector
2720
+ # doesn't match
2721
+ if selector_tag != element:
2722
+ continue
2723
+ # It's a match
2724
+ take_targetted_properties(
2725
+ properties_found,
2726
+ properties,
2727
+ found_properties_remaining,
2728
+ rule,
2729
+ )
2598
2730
 
2599
2731
  # If there are any properties left, it's a fail
2732
+ filename = clerk.get_file_name(file.get("file"))
2600
2733
  if found_properties_remaining:
2601
2734
  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]}."
2735
+ if min_required:
2736
+ applied = len(properties) - count
2737
+ if applied >= min_required:
2738
+ msg = f"pass: in {filename} the {element} tag applies"
2739
+ msg += " minimum required properties ("
2740
+ msg += f"{min_required})."
2741
+ else:
2742
+ msg = f"fail: in {filename}, the {element} tag only "
2743
+ msg += f" applied {applied} properties out of "
2744
+ msg += f" {min_required} required properties."
2605
2745
  else:
2606
- msg += f"{count} properties: "
2607
- msg += f"{found_properties_remaining}."
2746
+ msg = f"fail: in {filename}, the {element} tag does not "
2747
+ msg += "apply "
2748
+ if count == 1:
2749
+ msg += f"1 property: {found_properties_remaining[0]}."
2750
+ else:
2751
+ msg += f"{count} properties: "
2752
+ msg += f"{found_properties_remaining}."
2608
2753
 
2609
2754
  # all properties were accounted for (none remaining)
2610
2755
  else:
2611
- msg = f"pass: in {file}, the {element} tag applies all "
2756
+ msg = f"pass: in {filename}, the {element} tag applies all "
2612
2757
  msg += "required properties."
2613
2758
  report.append(msg)
2759
+ if not report:
2760
+ msg = f"fail: no file directly targetted the {element} tag's "
2761
+ msg += f"properties: {properties}."
2762
+ report.append(msg)
2614
2763
  return report
2615
2764
 
2616
2765
 
2766
+ def take_targetted_properties(
2767
+ properties_found, properties, found_properties_remaining, rule
2768
+ ):
2769
+ declarations = rule.declaration_block.declarations
2770
+ for dec in declarations:
2771
+ prop = dec.property
2772
+ if prop in properties:
2773
+ if found_properties_remaining:
2774
+ if prop in found_properties_remaining:
2775
+ properties_found.append(prop)
2776
+ found_properties_remaining.remove(prop)
2777
+
2778
+
2617
2779
  if __name__ == "__main__":
2618
2780
  project_folder = "tests/test_files/cascade_complexities"
2619
2781
  goals = {
2620
- "figure": ("margin", "padding", "border", "float"),
2782
+ "figure": {
2783
+ "properties": ("box-shadow", "border-radius", "animation"),
2784
+ }
2621
2785
  }
2622
2786
  report = get_properties_applied_report(project_folder, goals)
2623
2787
  rulesets = get_element_rulesets(
File without changes
File without changes