Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update all hierarchy charts in the docs (re #451) #469

Merged
merged 2 commits into from Jun 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 11 additions & 9 deletions README.rst
Expand Up @@ -2,17 +2,19 @@
gpiozero
========

.. image:: https://badge.fury.io/py/gpiozero.svg
:target: https://badge.fury.io/py/gpiozero
:alt: Latest Version
.. only:: builder_html

.. image:: https://travis-ci.org/RPi-Distro/python-gpiozero.svg?branch=master
:target: https://travis-ci.org/RPi-Distro/python-gpiozero
:alt: Build Tests
.. image:: https://badge.fury.io/py/gpiozero.svg
:target: https://badge.fury.io/py/gpiozero
:alt: Latest Version

.. image:: https://img.shields.io/codecov/c/github/RPi-Distro/python-gpiozero/master.svg?maxAge=2592000
:target: https://codecov.io/github/RPi-Distro/python-gpiozero
:alt: Code Coverage
.. image:: https://travis-ci.org/RPi-Distro/python-gpiozero.svg?branch=master
:target: https://travis-ci.org/RPi-Distro/python-gpiozero
:alt: Build Tests

.. image:: https://img.shields.io/codecov/c/github/RPi-Distro/python-gpiozero/master.svg?maxAge=2592000
:target: https://codecov.io/github/RPi-Distro/python-gpiozero
:alt: Code Coverage

A simple interface to GPIO devices with Raspberry Pi.

Expand Down
3 changes: 2 additions & 1 deletion docs/api_generic.rst
Expand Up @@ -23,7 +23,8 @@ classes (most of which are documented in their corresponding chapters):
devices like HATs

There are also several `mixin classes`_ for adding important functionality
at numerous points in the hierarchy, which is illustrated below:
at numerous points in the hierarchy, which is illustrated below (mixin classes
are represented in purple, while abstract classes are shaded lighter):

.. image:: images/device_hierarchy.*

Expand Down
3 changes: 2 additions & 1 deletion docs/api_input.rst
Expand Up @@ -52,7 +52,8 @@ Base Classes

The classes in the sections above are derived from a series of base classes,
some of which are effectively abstract. The classes form the (partial)
hierarchy displayed in the graph below:
hierarchy displayed in the graph below (abstract classes are shaded lighter
than concrete classes):

.. image:: images/input_device_hierarchy.*

Expand Down
3 changes: 2 additions & 1 deletion docs/api_other.rst
Expand Up @@ -35,7 +35,8 @@ Base Classes

The classes in the sections above are derived from a series of base classes,
some of which are effectively abstract. The classes form the (partial)
hierarchy displayed in the graph below:
hierarchy displayed in the graph below (abstract classes are shaded lighter
than concrete classes):

.. image:: images/other_device_hierarchy.*

Expand Down
3 changes: 2 additions & 1 deletion docs/api_output.rst
Expand Up @@ -62,7 +62,8 @@ Base Classes

The classes in the sections above are derived from a series of base classes,
some of which are effectively abstract. The classes form the (partial)
hierarchy displayed in the graph below:
hierarchy displayed in the graph below (abstract classes are shaded lighter
than concrete classes):

.. image:: images/output_device_hierarchy.*

Expand Down
3 changes: 2 additions & 1 deletion docs/api_spi.rst
Expand Up @@ -121,7 +121,8 @@ Base Classes

The classes in the sections above are derived from a series of base classes,
some of which are effectively abstract. The classes form the (partial)
hierarchy displayed in the graph below:
hierarchy displayed in the graph below (abstract classes are shaded lighter
than concrete classes):

.. image:: images/spi_device_hierarchy.*

Expand Down
173 changes: 173 additions & 0 deletions docs/images/class_graph
@@ -0,0 +1,173 @@
#!/usr/bin/python3

import re
import sys
import argparse
from pathlib import Path


ABSTRACT_CLASSES = {
'Device',
'GPIODevice',
'SmoothedInputDevice',
'AnalogInputDevice',
'MCP3xxx',
'MCP33xx',
'CompositeDevice',
'CompositeOutputDevice',
'LEDCollection',
'InternalDevice',
}

OMIT_CLASSES = {
'object',
'GPIOBase',
'GPIOMeta',
'frozendict',
'WeakMethod',
'_EnergenieMaster',
}


def main(args=None):
"""
A simple application for generating GPIO Zero's charts. Specify the root
class to generate with -i (multiple roots can be specified). Specify parts
of the hierarchy to exclude with -x. Output is in a format suitable for
feeding to graphviz's dot application.
"""
if args is None:
args = sys.argv[1:]
my_path = Path(__file__).parent
# XXX make this relative to repo root rather than this script
default_path = my_path / '..' / '..' / 'gpiozero'
default_path = default_path.resolve()
parser = argparse.ArgumentParser(description=main.__doc__)
parser.add_argument('-p', '--path', action='append', metavar='PATH',
default=[], help=
"search under PATH for Python source files; can be "
"specified multiple times, defaults to %s" % default_path)
parser.add_argument('-i', '--include', action='append', metavar='BASE',
default=[], help=
"only include classes which have BASE somewhere in "
"their ancestry; can be specified multiple times")
parser.add_argument('-x', '--exclude', action='append', metavar='BASE',
default=[], help=
"exclude any classes which have BASE somewhere in "
"their ancestry; can be specified multiple times")
parser.add_argument('output', nargs='?', type=argparse.FileType('w'),
default=sys.stdout, help=
"the file to write the output to; defaults to stdout")
args = parser.parse_args(args)
if not args.path:
args.path = [str(default_path)]

m = make_class_map(args.path, OMIT_CLASSES)
if args.include or args.exclude:
m = filter_map(m, include_roots=set(args.include), exclude_roots=set(args.exclude))
args.output.write(render_map(m, ABSTRACT_CLASSES))


def make_class_map(search_paths, omit):
"""
Find all Python source files under *search_paths*, extract (via a crude
regex) all class definitions and return a mapping of class-name to the list
of base classes.

All classes listed in *omit* will be excluded from the result, but not
their descendents (useful for excluding "object" etc.)
"""
def find_classes():
class_re = re.compile(r'^class (?P<name>\w+)(?:\((?P<bases>.*)\))?:', re.MULTILINE)
for path in search_paths:
for py_file in Path(path).rglob('*.py'):
with py_file.open() as f:
for match in class_re.finditer(f.read()):
if match.group('name') not in omit:
yield match.group('name'), [
base.strip()
for base in (match.group('bases') or '').split(',')
if base.strip() not in omit
]
return {
name: bases
for name, bases in find_classes()
}


def filter_map(class_map, include_roots, exclude_roots):
"""
Returns *class_map* (which is a mapping such as that returned by
:func:`make_class_map`), with only those classes which have at least one
of the *include_roots* in their ancestry, and none of the *exclude_roots*.
"""
def has_parent(cls, parent):
return cls == parent or any(
has_parent(base, parent) for base in class_map.get(cls, ()))

return {
name: bases
for name, bases in class_map.items()
if (not include_roots or any(has_parent(name, root) for root in include_roots))
and not any(has_parent(name, root) for root in exclude_roots)
}


def render_map(class_map, abstract):
"""
Renders *class_map* (which is a mapping such as that returned by
:func:`make_class_map`) to graphviz's dot language.

The *abstract* sequence determines which classes will be rendered lighter
to indicate their abstract nature. All classes with names ending "Mixin"
will be implicitly rendered in a different style.
"""
def all_names(class_map):
for name, bases in class_map.items():
yield name
for base in bases:
yield base

template = """\
digraph classes {{
graph [rankdir=RL];
node [shape=rect, style=filled, fontname=Sans, fontsize=10];
edge [];

/* Mixin classes */
node [color="#c69ee0", fontcolor="#000000"]

{mixin_nodes}

/* Abstract classes */
node [color="#9ec6e0", fontcolor="#000000"]

{abstract_nodes}

/* Concrete classes */
node [color="#2980b9", fontcolor="#ffffff"];

{edges}
}}
"""

return template.format(
mixin_nodes='\n '.join(
'{name};'.format(name=name)
for name in set(all_names(class_map))
if name.endswith('Mixin')
),
abstract_nodes='\n '.join(
'{name};'.format(name=name)
for name in abstract & set(all_names(class_map))
),
edges='\n '.join(
'{name}->{base};'.format(name=name, base=base)
for name, bases in class_map.items()
for base in bases
),
)


if __name__ == '__main__':
main()
3 changes: 2 additions & 1 deletion docs/images/composite_device_hierarchy.dot
@@ -1,7 +1,7 @@
/* vim: set et sw=4 sts=4: */

digraph classes {
graph [rankdir=BT];
graph [rankdir=RL];
node [shape=rect, style=filled, fontname=Sans, fontsize=10];
edge [];

Expand All @@ -24,6 +24,7 @@ digraph classes {
PiLiter->LEDBoard;
PiLiterBarGraph->LEDBarGraph;
TrafficLights->LEDBoard;
SnowPi->LEDBoard;
PiTraffic->TrafficLights;
PiStop->TrafficLights;
TrafficLightsBuzzer->CompositeOutputDevice;
Expand Down
Binary file modified docs/images/composite_device_hierarchy.pdf
Binary file not shown.
Binary file modified docs/images/composite_device_hierarchy.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.