aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools/misc/visitor.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/fontTools/misc/visitor.py')
-rw-r--r--Lib/fontTools/misc/visitor.py143
1 files changed, 143 insertions, 0 deletions
diff --git a/Lib/fontTools/misc/visitor.py b/Lib/fontTools/misc/visitor.py
new file mode 100644
index 00000000..3d28135f
--- /dev/null
+++ b/Lib/fontTools/misc/visitor.py
@@ -0,0 +1,143 @@
+"""Generic visitor pattern implementation for Python objects."""
+
+import enum
+
+
+class Visitor(object):
+
+ defaultStop = False
+
+ @classmethod
+ def _register(celf, clazzes_attrs):
+ assert celf != Visitor, "Subclass Visitor instead."
+ if "_visitors" not in celf.__dict__:
+ celf._visitors = {}
+
+ def wrapper(method):
+ assert method.__name__ == "visit"
+ for clazzes, attrs in clazzes_attrs:
+ if type(clazzes) != tuple:
+ clazzes = (clazzes,)
+ if type(attrs) == str:
+ attrs = (attrs,)
+ for clazz in clazzes:
+ _visitors = celf._visitors.setdefault(clazz, {})
+ for attr in attrs:
+ assert attr not in _visitors, (
+ "Oops, class '%s' has visitor function for '%s' defined already."
+ % (clazz.__name__, attr)
+ )
+ _visitors[attr] = method
+ return None
+
+ return wrapper
+
+ @classmethod
+ def register(celf, clazzes):
+ if type(clazzes) != tuple:
+ clazzes = (clazzes,)
+ return celf._register([(clazzes, (None,))])
+
+ @classmethod
+ def register_attr(celf, clazzes, attrs):
+ clazzes_attrs = []
+ if type(clazzes) != tuple:
+ clazzes = (clazzes,)
+ if type(attrs) == str:
+ attrs = (attrs,)
+ for clazz in clazzes:
+ clazzes_attrs.append((clazz, attrs))
+ return celf._register(clazzes_attrs)
+
+ @classmethod
+ def register_attrs(celf, clazzes_attrs):
+ return celf._register(clazzes_attrs)
+
+ @classmethod
+ def _visitorsFor(celf, thing, _default={}):
+ typ = type(thing)
+
+ for celf in celf.mro():
+
+ _visitors = getattr(celf, "_visitors", None)
+ if _visitors is None:
+ break
+
+ m = celf._visitors.get(typ, None)
+ if m is not None:
+ return m
+
+ return _default
+
+ def visitObject(self, 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.
+
+ If there is no user-registered visit function, of if there is and it
+ returns True, or it returns None (or doesn't return anything) and
+ visitor.defaultStop is False (default), then the visitor will proceed
+ to call self.visitAttr()"""
+
+ keys = sorted(vars(obj).keys())
+ _visitors = self._visitorsFor(obj)
+ defaultVisitor = _visitors.get("*", None)
+ for key in keys:
+ if key[0] == "_":
+ continue
+ value = getattr(obj, key)
+ visitorFunc = _visitors.get(key, defaultVisitor)
+ if visitorFunc is not None:
+ ret = visitorFunc(self, obj, key, value, *args, **kwargs)
+ if ret == False or (ret is None and self.defaultStop):
+ continue
+ self.visitAttr(obj, key, value, *args, **kwargs)
+
+ def visitAttr(self, obj, attr, value, *args, **kwargs):
+ """Called to visit an attribute of an object."""
+ self.visit(value, *args, **kwargs)
+
+ def visitList(self, obj, *args, **kwargs):
+ """Called to visit any value that is a list."""
+ for value in obj:
+ self.visit(value, *args, **kwargs)
+
+ def visitDict(self, obj, *args, **kwargs):
+ """Called to visit any value that is a dictionary."""
+ for value in obj.values():
+ self.visit(value, *args, **kwargs)
+
+ def visitLeaf(self, obj, *args, **kwargs):
+ """Called to visit any value that is not an object, list,
+ or dictionary."""
+ pass
+
+ def visit(self, 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.
+
+ If there is no user-registered visit function, of if there is and it
+ returns True, or it returns None (or doesn't return anything) and
+ visitor.defaultStop is False (default), then 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)."""
+
+ visitorFunc = self._visitorsFor(obj).get(None, None)
+ if visitorFunc is not None:
+ ret = visitorFunc(self, obj, *args, **kwargs)
+ if ret == False or (ret is None and self.defaultStop):
+ return
+ if hasattr(obj, "__dict__") and not isinstance(obj, enum.Enum):
+ self.visitObject(obj, *args, **kwargs)
+ elif isinstance(obj, list):
+ self.visitList(obj, *args, **kwargs)
+ elif isinstance(obj, dict):
+ self.visitDict(obj, *args, **kwargs)
+ else:
+ self.visitLeaf(obj, *args, **kwargs)