visitor: Tools for traversing nested data structures
Overview
The visitor module provides an extensible visitor pattern, which can
be used for traversing, inspecting, and editing nested Python data structures.
The utility class Visitor
automatically handles recursion into most common data types, which allows for
user code to skip boilerplate and focus on operation logic. This recursion logic
is documented in full under the visit()
function.
Commonly targeted data structures in font projects and fonttools include:
TTFont: Compiled font filesFeatureFile: Feature code syntax trees
Commonly implemented operations include:
Summarizing
Subsetting
Scaling
Specializations
The ttLib.ttVisitor module provides the
TTVisitor class, which handles
common edge cases when using visitors with TTFont objects. For this reason, it should be
preferred in that scenario.
Guide
Create a new class extending
Visitor.Register operations for specific types or attributes with the register annotations.
Instantiate the class and call
visit()on the target object.
Example code
One can create a visitor class that checks the case of feature names:
>>> from fontTools.feaLib.ast import FeatureNameStatement
>>> from fontTools.misc.visitor import Visitor
>>>
>>> class SpellCheckVisitor(Visitor):
... found: list[FeatureNameStatement]
...
... def __init__(self):
... self.found = []
>>>
>>> @SpellCheckVisitor.register(FeatureNameStatement)
... def visit(visitor: SpellCheckVisitor, statement: FeatureNameStatement):
... # Find name statements that are not in title case.
... if statement.string != statement.string.title():
... visitor.found.append(statement)
This can then be applied on a feature file object:
>>> io = StringIO("""
... feature ss01 {
... featureNames {
... name "Monolinear Grotesque";
... };
... } ss01;
...
... feature ss02 {
... featureNames {
... name "Optical Apertures";
... };
... } ss02;
...
... feature ss03 {
... featureNames {
... name "slanted Humanism";
... };
... } ss03;
... """)
>>> fea = Parser(io).parse()
>>>
>>> visitor = SpellCheckVisitor()
>>> visitor.visit(fea)
>>>
>>> for statement in visitor.found:
... print(
... "Found feature name that was not in title case: "
... f"'{statement.string}' at location '{statement.location}'",
... )
Found feature name that was not in title case: 'slanted Humanism' at location '<features>:16:13'
Package contents
Generic visitor pattern implementation for Python objects.
- class fontTools.misc.visitor.Visitor[source]
Bases:
object- defaultStop = False
- visitObject(obj, *args, **kwargs)[source]
Called to visit an object. This function loops over all non-private attributes of the objects and calls any user-registered (via
@register_attr()or@register_attrs())visit()functions.The visitor will proceed to call
self.visitAttr(), unless there is a user-registered visit function and:It returns
False; orIt returns
None(or doesn’t return anything) andvisitor.defaultStopisTrue(non-default).
- visitLeaf(obj, *args, **kwargs)[source]
Called to visit any value that is not an object, list, or dictionary.
- visit(obj, *args, **kwargs)[source]
This is the main entry to the visitor. The visitor will visit object
obj.The visitor will first determine if there is a registered (via
@register()) visit function for the type of object. If there is, it will be called, and(visitor, obj, *args, **kwargs)will be passed to the user visit function.The visitor will not recurse if there is a user-registered visit function and:
It returns
False; orIt returns
None(or doesn’t return anything) andvisitor.defaultStopisTrue(non-default)
Otherwise, the visitor will proceed to dispatch to one of
self.visitObject(),self.visitList(),self.visitDict(), orself.visitLeaf()(any of which can be overriden in a subclass).