# 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 files

- `FeatureFile`: 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

1. Create a new class extending `Visitor`.

2. Register operations for specific types or attributes with the register annotations.

3. 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

  Bases: `object`

  defaultStop = False
  classmethod register(clazzes)
  classmethod register_attr(clazzes, attrs)
  classmethod register_attrs(clazzes_attrs)
  visitObject(obj, *args, **kwargs)

    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`; or

    - It returns `None` (or doesn’t return anything) and `visitor.defaultStop` is `True` (non-default).

  visitAttr(obj, attr, value, *args, **kwargs)

    Called to visit an attribute of an object.

  visitList(obj, *args, **kwargs)

    Called to visit any value that is a list.

  visitDict(obj, *args, **kwargs)

    Called to visit any value that is a dictionary.

  visitLeaf(obj, *args, **kwargs)

    Called to visit any value that is not an object, list, or dictionary.

  visit(obj, *args, **kwargs)

    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`; or

    - It returns `None` (or doesn’t return anything) and `visitor.defaultStop` is `True` (non-default)

    Otherwise, the visitor will proceed to dispatch to one of `self.visitObject()`, `self.visitList()`, `self.visitDict()`, or `self.visitLeaf()` (any of which can be overriden in a subclass).