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.
- {webcode_tk-1.1.10 → webcode_tk-1.2.0}/PKG-INFO +1 -1
- {webcode_tk-1.1.10 → webcode_tk-1.2.0}/pyproject.toml +1 -1
- {webcode_tk-1.1.10 → webcode_tk-1.2.0}/webcode_tk/__init__.py +6 -1
- webcode_tk-1.2.0/webcode_tk/animation_tools.py +257 -0
- {webcode_tk-1.1.10 → webcode_tk-1.2.0}/webcode_tk/cascade_tools.py +7 -4
- {webcode_tk-1.1.10 → webcode_tk-1.2.0}/webcode_tk/css_tools.py +0 -75
- webcode_tk-1.2.0/webcode_tk/utils.py +16 -0
- {webcode_tk-1.1.10 → webcode_tk-1.2.0}/LICENSE.rst +0 -0
- {webcode_tk-1.1.10 → webcode_tk-1.2.0}/README.rst +0 -0
- {webcode_tk-1.1.10 → webcode_tk-1.2.0}/webcode_tk/color_keywords.py +0 -0
- {webcode_tk-1.1.10 → webcode_tk-1.2.0}/webcode_tk/color_tools.py +0 -0
- {webcode_tk-1.1.10 → webcode_tk-1.2.0}/webcode_tk/font_tools.py +0 -0
- {webcode_tk-1.1.10 → webcode_tk-1.2.0}/webcode_tk/html_tools.py +0 -0
- {webcode_tk-1.1.10 → webcode_tk-1.2.0}/webcode_tk/sample_report.json +0 -0
- {webcode_tk-1.1.10 → webcode_tk-1.2.0}/webcode_tk/ux_tools.py +0 -0
- {webcode_tk-1.1.10 → webcode_tk-1.2.0}/webcode_tk/validator_tools.py +0 -0
|
@@ -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.
|
|
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
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|