`ayrton` has always been able to use any Python module, package or extension as
long as it is in a directory in `sys.path`, but trying to solve a bigger bug, I
realized that there was no way to use `ayrton` modules or packages. Having only
laterally heard about the new `importlib` module and the new mechanism, I sat down
and read more about it.

The best source (or at least the easiest to find) is possibly
[what Python's reference says about the import system](https://docs.python.org/3/reference/import.html),
but I have to be honest: it was not an easy read. Next week I'll sit down and
see if I can improve it a little. So, for those out there who, like me, might be
having some troubles understanding the mechanism, here's how I understand the system
works (ignoring deprecated APIs and corner cases or even relative imports; I haven't
used or tried those yet):

```python
def import_single(full_path, parent=None, module=None):
    # try this cache first
    if full_path in sys.modules:
        return sys.modules[full_path]

    # if not, try all the finders
    for finder in sys.meta_path:
        if parent is not None:
            spec = finder.find_spec(full_path, parent.__path__, target)
        else:
            spec = finder.find_spec(full_path, None, target)

        # if the finder 'finds' ('knows how to handle') the full_path
        # it will return a loader
        if spec is not None:
            loader = spec.loader

            if module is None and hasattr(loader, 'create_module'):
                module = loader.create_module(spec)

            if module is None:
                module = ModuleType(spec.name)  # let's assume this creates an empty module object
                module.__spec__ = spec

            # add it to the cache before loading so it can referenced from it
            sys.modules[spec.name] = module
            try:
                # if the module was passed as parameter,
                # this repopulates the module's namespace
                # by executing the module's (possibly new) code
                loader.exec_module(module)
            except:
                # clean up
                del sys.modules[spec.name]
                raise

            return module

    raise ImportError


def import (full_path, target=None):
    parent= None

    # this code iterates over ['foo', 'foo.bar', 'foo.bar.baz']
    elems = full_path.split('.')
    for partial_path in [ '.'.join (elems[:i]) for i in range (len (elems)+1) ][1:]
        parent = import_single(partial_path, parent, target)

    # the module is loaded in parent
    return parent
```

A more complete version of the `if spec is not None` branch can be found in
[the Loading section](file:///usr/share/doc/python3.5/html/reference/import.html#loading)
of the reference. Notice that the algorithm uses all the finders in `sys.meta_path`.
So which are the default finders?

```python
In [^9]: sys.meta_path
Out[^9]:
[_frozen_importlib.BuiltinImporter,
 _frozen_importlib.FrozenImporter,
 _frozen_importlib_external.PathFinder]
```

Of those finders, the latter one is the one that traverses `sys.path`, and also has
a hook mechanism. I didn't use those, so for the moment I didn't untangle how they
work.

Finally, this is how I implemented importing `ayrton` modules and packages:

```python
from importlib.abc import MetaPathFinder, Loader
from importlib.machinery import ModuleSpec
import sys
import os
import os.path

from ayrton.file_test import _a, _d
from ayrton import Ayrton
import ayrton.utils


class AyrtonLoader (Loader):

    @classmethod
    def exec_module (klass, module):
        # «the loader should execute the module’s code
        # in the module’s global name space (module.__dict__).»
        load_path= module.__spec__.origin
        loader= Ayrton (g=module.__dict__)
        loader.run_file (load_path)

        # set the __path__
        # TODO: read PEP 420
        init_file_name= '__init__.ay'
        if load_path.endswith (init_file_name):
            # also remove the '/'
            module.__path__= [ load_path[:-len (init_file_name)-1] ]

loader= AyrtonLoader ()


class AyrtonFinder (MetaPathFinder):

    @classmethod
    def find_spec (klass, full_name, paths=None, target=None):
        # TODO: read PEP 420 :)
        last_mile= full_name.split ('.')[-1]

        if paths is not None:
            python_path= paths  # search only in the paths provided by the machinery
        else:
            python_path= sys.path

        for path in python_path:
            full_path= os.path.join (path, last_mile)
            init_full_path= os.path.join (full_path, '__init__.ay')
            module_full_path= full_path+'.ay'

            if _d (full_path) and _a (init_full_path):
                return ModuleSpec (full_name, loader, origin=init_full_path)

            else:
                if _a (module_full_path):
                    return ModuleSpec (full_name, loader, origin=module_full_path)

        return None

finder= AyrtonFinder ()


# I must insert it at the beginning so it goes before FileFinder
sys.meta_path.insert (0, finder)
```

Notice all the references to [PEP 420](https://www.python.org/dev/peps/pep-0420/).
I'm pretty sure I must be breaking something, but for the moment this works.

