webcode-tk 1.1.10__tar.gz → 1.2.0__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.3
2
2
  Name: webcode-tk
3
- Version: 1.1.10
3
+ Version: 1.2.0
4
4
  Summary:
5
5
  License: MIT
6
6
  Keywords: html,validation,css
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "webcode-tk"
3
- version = "1.1.10"
3
+ version = "1.2.0"
4
4
  description = ""
5
5
  authors = ["hundredvisionsguy <cwinikka@gmail.com>"]
6
6
  keywords = ["html", "validation", "css", ]
@@ -2,6 +2,9 @@
2
2
  """Deals with html and css documents to check their code.
3
3
 
4
4
  Modules exported by this package:
5
+ - `animation_tools`: a library used to report back on animations applied. As
6
+ of now, we are focusing entirely on keyframe animations (not)
7
+ transitions.
5
8
  - `cascade_tools`: library to calculate how the cascade affects font-size
6
9
  and background color and color through inheritance and will be
7
10
  used for calculating color contrast.
@@ -16,7 +19,9 @@ Modules exported by this package:
16
19
  - `ux_tools`: gets readability stats for paragraphs of text (could
17
20
  be from just `p` tags or a list of other tags e.g. `li`,
18
21
  `div`, etc.).
22
+ - `utils.py`: a library of helper functions to reduce the workload of the
23
+ other libraries.
19
24
  - `validator_tools`: sends HTML or CSS code to the W3C Validator to check
20
25
  for errors.
21
26
  """
22
- __version__ = "1.1.10"
27
+ __version__ = "1.2.0"
@@ -0,0 +1,257 @@
1
+ """
2
+ A group of functions that work with css_tools and cascade_tools to provide
3
+ reports on CSS animations.
4
+ """
5
+ from file_clerk import clerk
6
+
7
+ from webcode_tk import css_tools
8
+ from webcode_tk import utils
9
+
10
+
11
+ def get_animation_report(project_dir: str) -> list:
12
+ """gets a report on the implementation of animation in a project.
13
+
14
+ The animation report should contain a single entry for each HTML
15
+ file. Each HTML file will have a list of animations: the name of the
16
+ keyframe, a list of each keyframe type (percentage, from, or two),
17
+ and a list of properties included and if it is transform, it will
18
+ treat each type of transform as a unique property: eg. skew(), rotate(),
19
+ translate(), etc.
20
+
21
+ Args:
22
+ project_dir: the path to the project folder.
23
+
24
+ Returns:
25
+ report: a list of dictionary objects"""
26
+ report = []
27
+ # Animation Tests (test for # of keyframes and types of transitions)
28
+ files_by_styles = css_tools.get_styles_by_html_files(project_dir)
29
+
30
+ # loop through each file's stylesheet objects
31
+ keyframe_animations = []
32
+
33
+ for file in files_by_styles:
34
+ for sheet in file.get("stylesheets"):
35
+ filename = clerk.get_file_name(file.get("file"))
36
+ nested_at_rules = sheet.nested_at_rules
37
+ for at_rule in nested_at_rules:
38
+ if "@keyframes" in at_rule.at_rule:
39
+ keyframe_animations.append(
40
+ (filename, at_rule.at_rule, at_rule.rulesets)
41
+ )
42
+
43
+ report = []
44
+ animation_dict = {}
45
+ for animation in keyframe_animations:
46
+ filename, keyframe, rulesets = animation
47
+ if filename not in animation_dict:
48
+ animation_dict = {
49
+ filename: {
50
+ "keyframes": [],
51
+ "pct_keyframes": [],
52
+ "from_keyframes": [],
53
+ "to_keyframes": [],
54
+ "properties": set(),
55
+ }
56
+ }
57
+ report.append(animation_dict)
58
+ animation_dict[filename]["keyframes"].append(keyframe)
59
+ current_dict = animation_dict.get(filename)
60
+ for rule in rulesets:
61
+ declarations = rule.declaration_block.declarations
62
+ for declaration in declarations:
63
+ if declaration.property == "transform":
64
+ value_type = declaration.value
65
+ value_split = value_type.split("(")
66
+ transform_value = "transform-" + value_split[0] + "()"
67
+ current_dict["properties"].add(transform_value)
68
+ else:
69
+ current_dict["properties"].add(declaration.property)
70
+ if "%" in rule.selector:
71
+ current_dict["pct_keyframes"].append(rule.selector)
72
+ elif "from" in rule.selector:
73
+ current_dict["from_keyframes"].append(rule.selector)
74
+ elif "to" in rule.selector:
75
+ current_dict["to_keyframes"].append(rule.selector)
76
+ return report
77
+
78
+
79
+ def get_keyframe_data(report: list) -> dict:
80
+ """return a list of keyframe types and numbers from an animation report
81
+
82
+ The goal is to track all data related to animation keyframes per file.
83
+ The data is a dictionary of filenames. The filenames will be the key, and
84
+ each filename's values will be a dictionary of keyframe names, number of
85
+ percentage keyframes, and the number of from {} and to {} keyframes.
86
+
87
+ Args:
88
+ report: an animation report, which is a list of project files with a
89
+ dictionary of details
90
+
91
+ Returns:
92
+ data: a dictionary of filenames as primary keys with a dictionary of
93
+ keyframe data as the key's value.
94
+ """
95
+ data = {}
96
+ for animation_data in report:
97
+ filename = utils.get_first_dict_key(animation_data)
98
+ if filename not in data:
99
+ data[filename] = {}
100
+ names = animation_data[filename].get("keyframes")
101
+ data[filename]["keyframe_names"] = names
102
+ data[filename]["froms_tos"] = 0
103
+ data[filename]["pct_keyframes"] = 0
104
+ else:
105
+ names = animation_data[filename].get("keyframes")
106
+ if names:
107
+ data[filename]["keyframe_names"] += names
108
+ pct_keyframes = animation_data[filename].get("pct_keyframes")
109
+ froms = animation_data[filename].get("from_keyframes")
110
+ if froms:
111
+ data[filename]["froms_tos"] += len(froms)
112
+ tos = animation_data[filename].get("to_keyframes")
113
+ if tos:
114
+ data[filename]["froms_tos"] += len(tos)
115
+ if pct_keyframes:
116
+ data[filename]["pct_keyframes"] += len(pct_keyframes)
117
+ return data
118
+
119
+
120
+ def get_keyframe_report(
121
+ keyframe_results: list, pct_goal: int, overall_goal: int
122
+ ) -> list:
123
+ """returns a list of pass/fail messages (1 for each file)
124
+
125
+ Args:
126
+ keyframe_results: a list of filenames with number of percentage
127
+ keyframes and from and to keyframes.
128
+ pct_goal: the minimum number of percentage keyframes we would want
129
+ to see.
130
+ overall_goal: the total number of keyframes in case there are not
131
+ the minimum number of percentage keyframes.
132
+
133
+ Returns:
134
+ results: a list of messages (one for each file in the project) with
135
+ a pass or fail with number present of each type.
136
+ """
137
+ results = []
138
+ for item in keyframe_results:
139
+ file, pct_keyframes, from_to_keyframes = item
140
+ msg = ""
141
+
142
+ # first check the percentage goal
143
+ if pct_keyframes > pct_goal:
144
+ msg = f"pass: {file} has {pct_keyframes} percentage keyframes."
145
+ else:
146
+ # if that fails, check overall
147
+ num_overall = pct_keyframes + from_to_keyframes
148
+ if num_overall >= overall_goal:
149
+ msg = f"pass: {file} has {num_overall} keyframes (enough "
150
+ msg += "overall to meet)."
151
+ else:
152
+ msg = f"fail: {file} does not have enough keyframes to pass."
153
+ results.append(msg)
154
+ return results
155
+
156
+
157
+ def get_animation_properties_report(
158
+ animation_values: list, num_goal: int, specific_properties=None
159
+ ):
160
+ """returns a list of pass/fail messages (1 for each file)
161
+
162
+ Since animation_values might have multiple entries for the same file,
163
+ we need to track a per file record to see if it meets or not.
164
+
165
+ Args:
166
+ animation_values: a list of filenames with keyframe and property
167
+ data.
168
+ num_goal: the minimum number of percentage keyframes we would want
169
+ to see.
170
+ specific_properties: a list or tuple of properties required to be
171
+ present.
172
+
173
+ Returns:
174
+ results: a list of messages (one for each file in the project) with
175
+ a pass or fail with number present of each type.
176
+ """
177
+ results = []
178
+ properties_targetted = set()
179
+ current_file = animation_values[0].get("file")
180
+ for item in animation_values:
181
+ filename = item.get("file")
182
+ if filename != current_file:
183
+ # we are on to a new file, it's time to create a message
184
+ if specific_properties:
185
+ # make sure it's a list
186
+ msg = get_targetted_properties_msg(
187
+ specific_properties, properties_targetted, current_file
188
+ )
189
+ results.append(msg)
190
+ else:
191
+ # Now lets check for num of unique properties
192
+ msg = get_num_properties_msg(
193
+ num_goal, properties_targetted, current_file
194
+ )
195
+ results.append(msg)
196
+
197
+ # now is the time to restart our list of properties
198
+ properties_targetted = set()
199
+ current_file = filename
200
+
201
+ # will need to change the key on the animations_values list
202
+ properties = item.get("values_targetted")
203
+ for property in properties:
204
+ # add only unique properties
205
+ properties_targetted.add(property)
206
+
207
+ # process the current file (now that we're done looping)
208
+ # check to see if there are any required properties
209
+ if specific_properties:
210
+ # make sure it's a list
211
+ msg = get_targetted_properties_msg(
212
+ specific_properties, properties_targetted, current_file
213
+ )
214
+ results.append(msg)
215
+ else:
216
+ # Now lets check for num of unique properties
217
+ msg = get_num_properties_msg(
218
+ num_goal, properties_targetted, current_file
219
+ )
220
+ results.append(msg)
221
+ return results
222
+
223
+
224
+ def get_num_properties_msg(num_goal, properties_targetted, current_file):
225
+ num_unique_props = len(properties_targetted)
226
+ if num_unique_props >= num_goal:
227
+ msg = f"pass: {current_file}'s animations targetted the minimum "
228
+ msg += "required number of properties."
229
+ else:
230
+ off_by = num_goal - num_unique_props
231
+ msg = f"fail: {current_file}'s animations did not target the "
232
+ msg += f"{num_goal} required number of properties (missing "
233
+ msg += f"{off_by})"
234
+ return msg
235
+
236
+
237
+ def get_targetted_properties_msg(
238
+ properties, properties_targetted, current_file
239
+ ):
240
+ properties = list(properties)
241
+ for property in properties_targetted:
242
+ if property in properties:
243
+ properties.remove(property)
244
+ if properties:
245
+ # we failed to include all required properties
246
+ msg = f"fail: {current_file}'s animations did not target all "
247
+ msg += f"required properties (missing {properties})"
248
+ else:
249
+ # Success on the required properties
250
+ msg = f"pass: {current_file}'s animations targetted all "
251
+ msg += "required properties"
252
+ return msg
253
+
254
+
255
+ if __name__ == "__main__":
256
+ project_folder = "tests/test_files/cascade_complexities"
257
+ report = get_animation_report(project_folder)
@@ -1399,10 +1399,13 @@ def descendant_selector_applies(element: Element, sel: str) -> None:
1399
1399
  for ancestor in ancestor_list:
1400
1400
  id = ancestor[1]
1401
1401
  ancestor_element = __get_element_by_id(id)
1402
- selector = selector_list[current_pos]
1403
- cur_selector_applies = does_selector_apply(ancestor_element, selector)
1404
- if cur_selector_applies:
1405
- selector_list = selector_list[1:]
1402
+ if selector_list:
1403
+ selector = selector_list[current_pos]
1404
+ cur_selector_applies = does_selector_apply(
1405
+ ancestor_element, selector
1406
+ )
1407
+ if cur_selector_applies:
1408
+ selector_list = selector_list[1:]
1406
1409
  applies = not selector_list
1407
1410
  return applies
1408
1411
 
@@ -2866,81 +2866,6 @@ def border_checks_out(declaration: Declaration) -> bool:
2866
2866
  return checks_out
2867
2867
 
2868
2868
 
2869
- def get_animation_report(project_dir: str) -> list:
2870
- """gets a report on the implementation of animation in a project."""
2871
- report = []
2872
- # Animation Tests (test for # of keyframes and types of transitions)
2873
- files_by_styles = get_styles_by_html_files(project_dir)
2874
-
2875
- # loop through each file's stylesheet objects
2876
- keyframe_animations = []
2877
-
2878
- for file in files_by_styles:
2879
- for sheet in file.get("stylesheets"):
2880
- filename = clerk.get_file_name(file.get("file"))
2881
- nested_at_rules = sheet.nested_at_rules
2882
- for at_rule in nested_at_rules:
2883
- if "@keyframes" in at_rule.at_rule:
2884
- keyframe_animations.append(
2885
- (filename, at_rule.at_rule, at_rule.rulesets)
2886
- )
2887
-
2888
- report = []
2889
- animation_values_targetted = set()
2890
- print(animation_values_targetted)
2891
- for animation in keyframe_animations:
2892
- animation_dict = {
2893
- "file": animation[0],
2894
- "animation": animation[1],
2895
- "keyframes": [],
2896
- "values_targetted": [],
2897
- }
2898
- percentage_keyframes = []
2899
- from_keyframes = []
2900
- to_keyframes = []
2901
- for rule in animation[2]:
2902
- declarations = rule.declaration_block.declarations
2903
- for declaration in declarations:
2904
- animation_values_targetted.add(
2905
- (filename, declaration.property)
2906
- )
2907
- if "%" in rule.selector:
2908
- percentage_keyframes.append(rule.selector)
2909
- elif "from" in rule.selector:
2910
- from_keyframes.append(rule.selector)
2911
- elif "to" in rule.selector:
2912
- to_keyframes.append(rule.selector)
2913
- animation_dict["keyframes"].append(
2914
- ("percentage", percentage_keyframes)
2915
- )
2916
- animation_dict["keyframes"].append(("from", from_keyframes))
2917
- animation_dict["keyframes"].append(("to", to_keyframes))
2918
- for value_targetted in animation_values_targetted:
2919
- value = value_targetted[1]
2920
- animation_dict["values_targetted"].append(value)
2921
- report.append(animation_dict)
2922
- return report
2923
-
2924
-
2925
- def test_for_number_of_keyframes(keyframes_by_animation):
2926
- froms_and_tos = 0
2927
- num_keyframes = 0
2928
- passing = False
2929
- for animation in keyframes_by_animation:
2930
- keyframes = animation.get("keyframes")
2931
- for keyframe_data in keyframes:
2932
- keyframe_type = keyframe_data[0]
2933
- num_keyframes += len(keyframe_data[1])
2934
- if keyframe_type == "percentage":
2935
- if num_keyframes >= 4:
2936
- passing = True
2937
- elif keyframe_type in ("from", "to"):
2938
- froms_and_tos += num_keyframes
2939
- if froms_and_tos + num_keyframes >= 6:
2940
- passing = True
2941
- assert passing
2942
-
2943
-
2944
2869
  if __name__ == "__main__":
2945
2870
  project_folder = "tests/test_files/cascade_complexities"
2946
2871
  goals = {
@@ -0,0 +1,16 @@
1
+ """a script of utility functions that are useful for webcode-tk but don't
2
+ belong to any one particular webcode tool."""
3
+
4
+
5
+ def get_first_dict_key(some_dict: dict) -> str:
6
+ """returns the first key of a dictionary.
7
+
8
+ Args:
9
+ some_dict: a dictionary (any dictionary)
10
+
11
+ Returns:
12
+ first_key: the first key of a dictionary"""
13
+ first_key = some_dict.keys()
14
+ first_key = list(first_key)
15
+ first_key = first_key[0]
16
+ return first_key
File without changes
File without changes