Skip to content

animation_tools.py

A group of functions that work with css_tools and cascade_tools to provide reports on CSS animations.

get_animation_properties_report(project_folder, num_goal, specific_properties=None)

returns a list of pass/fail messages based on number and type of unique animation keyframe properties.

You can specify just the number of unique properties or you can specify both the number as well as check for specific targetted properties. If you specify both, both must be met for a pass.

NOTE: In the case of the transform properties, you can just specify transform, or you can include the type of transform in the form of transform- + the transform value (eg. transform-rotate(), transform-translate(), transfrom-skew(), etc.)

Since animation_values might have multiple entries for the same file, we need to track a per file record to see if it meets or not.

Parameters:

Name Type Description Default
animation_values

a list of filenames with keyframe and property data.

required
num_goal int

the minimum number of percentage keyframes we would want to see.

required
specific_properties

a list or tuple of properties required to be present.

None

Returns:

Name Type Description
results list

a list of messages (one for each file in the project) with a pass or fail with number present of each type.

Source code in webcode_tk/animation_tools.py
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
def get_animation_properties_report(
    project_folder: str, num_goal: int, specific_properties=None
) -> list:
    """returns a list of pass/fail messages based on number and type of
    unique animation keyframe properties.

    You can specify just the number of unique properties or you can specify
    both the number as well as check for specific targetted properties. If you
    specify both, both must be met for a pass.

    NOTE: In the case of the transform properties, you can just specify
    transform, or you can include the type of transform in the form of
    transform- + the transform value (eg. transform-rotate(),
    transform-translate(), transfrom-skew(), etc.)

    Since animation_values might have multiple entries for the same file,
    we need to track a per file record to see if it meets or not.

    Args:
        animation_values: a list of filenames with keyframe and property
            data.
        num_goal: the minimum number of percentage keyframes we would want
            to see.
        specific_properties: a list or tuple of properties required to be
            present.

    Returns:
        results: a list of messages (one for each file in the project) with
            a pass or fail with number present of each type.
    """
    report = []
    animation_report = get_animation_report(project_folder)
    for item in animation_report:
        filename = utils.get_first_dict_key(item)
        properties_targetted = item[filename].get("properties")
        num_properties = len(properties_targetted)
        num_remaining = num_goal - num_properties
        if num_remaining > 0:
            msg = f"fail: {filename} did not target enough properties; should"
            msg += f"target {num_remaining} properties more."
            report.append(msg)
        else:
            if specific_properties:
                # make sure it's a list
                msg = get_targetted_properties_msg(
                    specific_properties, properties_targetted, filename
                )
                report.append(msg)
            else:
                # Now lets check for num of unique properties
                msg = get_num_properties_msg(
                    num_goal, properties_targetted, filename
                )
                report.append(msg)

        # now is the time to restart our list of properties
        properties_targetted = set()
    return report

get_animation_report(project_dir)

gets a report on the implementation of animation in a project.

The animation report should contain a single entry for each HTML file. Each HTML file will have a list of animations: the name of the keyframe, a list of each keyframe type (percentage, from, or two), and a list of properties included and if it is transform, it will treat each type of transform as a unique property: eg. skew(), rotate(), translate(), etc.

Parameters:

Name Type Description Default
project_dir str

the path to the project folder.

required

Returns:

Name Type Description
report list

a list of dictionary objects

Source code in webcode_tk/animation_tools.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def get_animation_report(project_dir: str) -> list:
    """gets a report on the implementation of animation in a project.

    The animation report should contain a single entry for each HTML
    file. Each HTML file will have a list of animations: the name of the
    keyframe, a list of each keyframe type (percentage, from, or two),
    and a list of properties included and if it is transform, it will
    treat each type of transform as a unique property: eg. skew(), rotate(),
    translate(), etc.

    Args:
        project_dir: the path to the project folder.

    Returns:
        report: a list of dictionary objects"""
    report = []
    # Animation Tests (test for # of keyframes and types of transitions)
    files_by_styles = css_tools.get_styles_by_html_files(project_dir)

    # loop through each file's stylesheet objects
    keyframe_animations = []

    for file in files_by_styles:
        for sheet in file.get("stylesheets"):
            filename = clerk.get_file_name(file.get("file"))
            nested_at_rules = sheet.nested_at_rules
            for at_rule in nested_at_rules:
                if "@keyframes" in at_rule.at_rule:
                    keyframe_animations.append(
                        (filename, at_rule.at_rule, at_rule.rulesets)
                    )

    report = []
    animation_dict = {}
    for animation in keyframe_animations:
        filename, keyframe, rulesets = animation
        if filename not in animation_dict:
            animation_dict = {
                filename: {
                    "keyframes": [],
                    "pct_keyframes": [],
                    "from_keyframes": [],
                    "to_keyframes": [],
                    "properties": set(),
                }
            }
            report.append(animation_dict)
        animation_dict[filename]["keyframes"].append(keyframe)
        current_dict = animation_dict.get(filename)
        for rule in rulesets:
            declarations = rule.declaration_block.declarations
            for declaration in declarations:
                if declaration.property == "transform":
                    value_type = declaration.value
                    value_split = value_type.split("(")
                    transform_value = "transform-" + value_split[0] + "()"
                    current_dict["properties"].add(transform_value)
                else:
                    current_dict["properties"].add(declaration.property)
            if "%" in rule.selector:
                current_dict["pct_keyframes"].append(rule.selector)
            elif "from" in rule.selector:
                current_dict["from_keyframes"].append(rule.selector)
            elif "to" in rule.selector:
                current_dict["to_keyframes"].append(rule.selector)
    return report

get_keyframe_data(report)

return a list of keyframe types and numbers from an animation report

The goal is to track all data related to animation keyframes per file. The data is a dictionary of filenames. The filenames will be the key, and each filename's values will be a dictionary of keyframe names, number of percentage keyframes, and the number of from {} and to {} keyframes.

Parameters:

Name Type Description Default
report list

an animation report, which is a list of project files with a dictionary of details

required

Returns:

Name Type Description
data dict

a dictionary of filenames as primary keys with a dictionary of keyframe data as the key's value.

Source code in webcode_tk/animation_tools.py
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
def get_keyframe_data(report: list) -> dict:
    """return a list of keyframe types and numbers from an animation report

    The goal is to track all data related to animation keyframes per file.
    The data is a dictionary of filenames. The filenames will be the key, and
    each filename's values will be a dictionary of keyframe names, number of
    percentage keyframes, and the number of from {} and to {} keyframes.

    Args:
        report: an animation report, which is a list of project files with a
            dictionary of details

    Returns:
        data: a dictionary of filenames as primary keys with a dictionary of
            keyframe data as the key's value.
    """
    data = {}
    for animation_data in report:
        filename = utils.get_first_dict_key(animation_data)
        if filename not in data:
            data[filename] = {}
            names = animation_data[filename].get("keyframes")
            data[filename]["keyframe_names"] = names
            data[filename]["froms_tos"] = 0
            data[filename]["pct_keyframes"] = 0
        else:
            names = animation_data[filename].get("keyframes")
            if names:
                data[filename]["keyframe_names"] += names
        pct_keyframes = animation_data[filename].get("pct_keyframes")
        froms = animation_data[filename].get("from_keyframes")
        if froms:
            data[filename]["froms_tos"] += len(froms)
        tos = animation_data[filename].get("to_keyframes")
        if tos:
            data[filename]["froms_tos"] += len(tos)
        if pct_keyframes:
            data[filename]["pct_keyframes"] += len(pct_keyframes)
    return data

get_keyframe_report(project_folder, num_goal, pct_goal=None, from_to_goals=None)

returns a list of pass/fail messages (1 for each goal in each file).

A report that allows you to set the minimum number of keyframes for each file. By setting num_goal, you are stating how many keyframes in all you expect to see in a project.

You can also set a minimum number of percentage keyframes, and the minimum number of from {} and to {} keyframes.

NOTE: for every goal, there will be a report on that goal (one for each file). If your project has two files, and you set all three goals ( num_goal, pct_goal, and from_to_goals), the report will create a list of 6 messages.

Parameters:

Name Type Description Default
project_folder str

the folder that houses the project.

required
num_goal int

the minimum number of keyframes per file (overall)

required
pct_goal

the minimum number of percentage keyframes we would want to see.

None
from_to_goals

the minimum number of from and to keyframes.

None

Returns:

Name Type Description
results list

a list of messages (one for each file in the project) with a pass or fail with number present of each type.

Source code in webcode_tk/animation_tools.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
def get_keyframe_report(
    project_folder: str, num_goal: int, pct_goal=None, from_to_goals=None
) -> list:
    """returns a list of pass/fail messages (1 for each goal in each file).

    A report that allows you to set the minimum number of keyframes for each
    file. By setting num_goal, you are stating how many keyframes in all you
    expect to see in a project.

    You can also set a minimum number of percentage keyframes, and the minimum
    number of from {} and to {} keyframes.

    NOTE: for every goal, there will be a report on that goal (one for each
    file). If your project has two files, and you set all three goals (
    num_goal, pct_goal, and from_to_goals), the report will create a list of
    6 messages.

    Args:
        project_folder: the folder that houses the project.
        num_goal: the minimum number of keyframes per file (overall)
        pct_goal: the minimum number of percentage keyframes we would want
            to see.
        from_to_goals: the minimum number of from and to keyframes.

    Returns:
        results: a list of messages (one for each file in the project) with
            a pass or fail with number present of each type.
    """
    report = []
    animation_report = get_animation_report(project_folder)
    keyframe_results = get_keyframe_data(animation_report)
    for file, results in keyframe_results.items():
        pct_keyframes = results["pct_keyframes"]
        if isinstance(pct_keyframes, Iterable):
            pct_keyframes = len(pct_keyframes)
        num_froms_tos = results["froms_tos"]
        if isinstance(num_froms_tos, Iterable):
            num_froms_tos = len(num_froms_tos)
        overall_num = pct_keyframes + num_froms_tos
        if overall_num >= num_goal:
            msg = f"pass: {file} has {overall_num} keyframes (enough "
            msg += "overall to meet)."
        else:
            remaining = num_goal - overall_num
            msg = f"fail: {file} has only {overall_num} keyframes (needs "
            msg += f"{remaining} more to pass)."
        report.append(msg)
        if pct_goal:
            if pct_keyframes >= pct_goal:
                msg = f"pass: {file} has {pct_keyframes} percentage "
                msg += "keyframes."
            else:
                msg = f"fail: {file} does not have enough percentage "
                msg += "keyframes to pass."
            report.append(msg)
        if from_to_goals:
            if num_froms_tos >= from_to_goals:
                msg = f"pass: {file} has {num_froms_tos} from and to "
                msg += "keyframes."
            else:
                msg = f"fail: {file} does not have enough from and to "
                msg += "keyframes to pass."
            report.append(msg)
    return report

get_num_properties_msg(num_goal, properties_targetted, current_file)

Returns a pass/fail message based on whether a file targets the min number of properties found.

Parameters:

Name Type Description Default
num_goal int

the number of unique properties that should be targetted

required
Source code in webcode_tk/animation_tools.py
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
def get_num_properties_msg(
    num_goal: int,
    properties_targetted: Union[list, tuple, set],
    current_file: str,
) -> str:
    """Returns a pass/fail message based on whether a file targets the min
    number of properties found.

    Args:
        num_goal: the number of unique properties that should be targetted"""
    num_unique_props = len(properties_targetted)
    if num_unique_props >= num_goal:
        msg = f"pass: {current_file}'s animations targetted the minimum "
        msg += "required number of properties."
    else:
        off_by = num_goal - num_unique_props
        msg = f"fail: {current_file}'s animations did not target the "
        msg += f"{num_goal} required number of properties (missing "
        msg += f"{off_by})"
    return msg

get_targetted_properties_msg(properties, properties_targetted, current_file)

returns whether the file addresses all targetted keyframe properties or not.

Receives the keyframe properties found in a file's styles and returns a message that states whether the file has targetted all required properties in the form of "pass" or "fail".

Parameters:

Name Type Description Default
properties Union[list, tuple]

a list or tuple of the keyframe properties targetted in a file's stylesheets or not.

required
properties_targetted Union[list, tuple]

a list or tuple of the properties that the file should contain.

required
current_file str

the name of the HTML document we are checking.

required

Returns:

Name Type Description
msg str

a string that begins with 'pass:' or 'fail:', and details on what was missing if a fail.

Source code in webcode_tk/animation_tools.py
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
def get_targetted_properties_msg(
    properties: Union[list, tuple],
    properties_targetted: Union[list, tuple],
    current_file: str,
) -> str:
    """returns whether the file addresses all targetted keyframe properties or
    not.

    Receives the keyframe properties found in a file's styles and returns a
    message that states whether the file has targetted all required properties
    in the form of "pass" or "fail".

    Args:
        properties: a list or tuple of the keyframe properties targetted in a
            file's stylesheets or not.
        properties_targetted: a list or tuple of the properties that the file
            should contain.
        current_file: the name of the HTML document we are checking.

    Returns:
        msg: a string that begins with 'pass:' or 'fail:', and details on what
            was missing if a fail."""
    properties = list(properties)
    for property in properties_targetted:
        if property in properties:
            properties.remove(property)
    if properties:
        # we failed to include all required properties
        msg = f"fail: {current_file}'s animations did not target all "
        msg += f"required properties (missing {properties})"
    else:
        # Success on the required properties
        msg = f"pass: {current_file}'s animations targetted all "
        msg += "required properties"
    return msg

Notes

I added these tools to check CSS animations. In my class, my more advanced students are learning to make keyframe animations, and I want to test to make sure they have a certain number of keyframes, and be able to check whether they are using from {} and to {} keyframes as well as percentage keyframes.

I would also like to encourage them to animate more than use standard properties, but to target transforms.