#!/usr/bin/env python3
# encoding: utf-8
"""
Pyolian: ctypes bindings for the eolian library.

This file (along with eolian_lib.py) implement full pythonic eolian bindings.

Those bindings are designed to work at runtime without any prepare/compilation
steps required. The only requirement is that we can find the eolian.so/dll
somwhere built in the source tree, look at eolian_lib.py to see the searchs
performed.

You can directly use the python API provided here if you need direct access
to eolian, or we suggest to look at the template-based generator.py if you just
need to generate some sort of text files out of the eolian database.

To use this library from outside this directory, you need to hack sys.path in
a way that this folder will be available on PYTHON_PATH, fe:

  pyolian_path = os.path.join(EFL_ROOT_PATH, 'src', 'scripts')
  sys.path.insert(0, pyolian_path)
  from pyolian import eolian
  from pyolian.generator import Template

"""
import os
from enum import IntEnum
import atexit
from ctypes import cast, byref, c_char_p, c_void_p, c_int
import ctypes

try:
    from .eolian_lib import lib
except ImportError:
    from eolian_lib import lib

_already_halted = False


# This is the same as the EOLIAN_FILE_FORMAT_VERSION macro
file_format_version = lib.eolian_file_format_version_get()


#  Eolian Enums  ##############################################################
class Eolian_Object_Type(IntEnum):
    UNKNOWN = 0
    CLASS = 1
    TYPEDECL = 2
    STRUCT_FIELD = 3
    ENUM_FIELD = 4
    TYPE = 5
    CONSTANT = 6
    EXPRESSION = 7
    FUNCTION = 8
    FUNCTION_PARAMETER = 9
    EVENT = 10
    PART = 11
    IMPLEMENT = 12
    CONSTRUCTOR = 13
    DOCUMENTATION = 14
    ERROR = 15


class Eolian_Function_Type(IntEnum):
    UNRESOLVED = 0
    PROPERTY = 1
    PROP_SET = 2
    PROP_GET = 3
    METHOD = 4
    FUNCTION_POINTER = 5


class Eolian_Parameter_Direction(IntEnum):
    UNKNOWN = 0
    IN = 1
    OUT = 2
    INOUT = 3


class Eolian_Class_Type(IntEnum):
    UNKNOWN_TYPE = 0
    REGULAR = 1
    ABSTRACT = 2
    MIXIN = 3
    INTERFACE = 4


class Eolian_Object_Scope(IntEnum):
    UNKNOWN = 0
    PUBLIC = 1
    PRIVATE = 2
    PROTECTED = 3


class Eolian_Typedecl_Type(IntEnum):
    UNKNOWN = 0
    STRUCT = 1
    STRUCT_OPAQUE = 2
    ENUM = 3
    ALIAS = 4
    FUNCTION_POINTER = 5


class Eolian_Type_Type(IntEnum):
    UNKNOWN_TYPE = 0
    VOID = 1
    REGULAR = 2
    CLASS = 3
    UNDEFINED = 4


class Eolian_Type_Builtin_Type(IntEnum):
    INVALID = 0
    BYTE = 1
    UBYTE = 2
    CHAR = 3
    SHORT = 4
    USHORT = 5
    INT = 6
    UINT = 7
    LONG = 8
    ULONG = 9
    LLONG = 10
    ULLONG = 11

    INT8 = 12
    UINT8 = 13
    INT16 = 14
    UINT16 = 15
    INT32 = 16
    UINT32 = 17
    INT64 = 18
    UINT64 = 19
    INT128 = 20
    UINT128 = 21

    SIZE = 22
    SSIZE = 23
    INTPTR = 24
    UINTPTR = 25
    PTRDIFF = 26

    TIME = 27

    FLOAT = 28
    DOUBLE = 29

    BOOL = 30

    SLICE = 31
    RW_SLICE = 32

    VOID = 33

    ACCESSOR = 34
    ARRAY = 35
    FUTURE = 36
    ITERATOR = 37
    LIST = 38

    ANY_VALUE = 39
    ANY_VALUE_REF = 40
    BINBUF = 41
    EVENT = 42

    MSTRING = 43
    STRING = 44
    STRINGSHARE = 45
    STRBUF = 46

    HASH = 47
    VOID_PTR = 48


class Eolian_Expression_Type(IntEnum):
    UNKNOWN = 0
    INT = 1
    UINT = 2
    LONG = 3
    ULONG = 4
    LLONG = 5
    ULLONG = 6
    FLOAT = 7
    DOUBLE = 8
    STRING = 9
    CHAR = 10
    NULL = 11
    BOOL = 12
    NAME = 13
    UNARY = 14
    BINARY = 15


class Eolian_Expression_Mask(IntEnum):
    SINT = 1 << 0
    UINT = 1 << 1
    INT = SINT | UINT
    FLOAT = 1 << 2
    BOOL = 1 << 3
    STRING = 1 << 4
    CHAR = 1 << 5
    NULL = 1 << 6
    SIGNED = SINT | FLOAT
    NUMBER = INT | FLOAT
    ALL = NUMBER | BOOL | STRING | CHAR | NULL


class Eolian_Binary_Operator(IntEnum):
    INVALID = 0
    ADD = 1  # + int, float
    SUB = 2  # - int, float
    MUL = 3  # * int, float
    DIV = 4  # / int, float
    MOD = 5  # % int
    EQ = 6  # == all types
    NQ = 7  # != all types
    GT = 8  # >  int, float
    LT = 9  # <  int, float
    GE = 10  # >= int, float
    LE = 11  # <= int, float
    AND = 12  # && all types
    OR = 13  # || all types
    BAND = 14  # &  int
    BOR = 15  # |  int
    BXOR = 16  # ^  int
    LSH = 17  # << int
    RSH = 18  # >> int


class Eolian_Unary_Operator(IntEnum):
    INVALID = 0
    UNM = 1  # - sint
    UNP = 2  # + sint
    NOT = 3   # ! int, float, bool
    BNOT = 4  # ~ int


class Eolian_Doc_Token_Type(IntEnum):
    UNKNOWN = 0
    TEXT = 1
    REF = 2
    MARK_NOTE = 3
    MARK_WARNING = 4
    MARK_REMARK = 5
    MARK_TODO = 6
    MARKUP_MONOSPACE = 7


#  Internal Classes  ##########################################################

class Iterator(object):
    """ Generic eina iterator wrapper """
    def __init__(self, conv_func, iterator):
        self.next = self.__next__  # py2 compat
        self._conv = conv_func
        self._iter = c_void_p(iterator)
        self._tmp = c_void_p(0)

    def __iter__(self):
        return self

    def __next__(self):
        if not self._iter or not self._iter.value:
            raise StopIteration
        if not lib.eina_iterator_next(self._iter, byref(self._tmp)):
            lib.eina_iterator_free(self._iter)
            raise StopIteration
        return self._conv(self._tmp)

    def free(self):
        lib.eina_iterator_free(self._iter)


class cached_property(object):
    """ Decorator to be used on properties that can be cached """
    def __init__(self, fget, doc=None):
        self.fget = fget
        self.__doc__ = doc or fget.__doc__
        self.__name__ = fget.__name__
        self.__module__ = fget.__module__

    def __get__(self, obj, cls):
        obj.__dict__[self.__name__] = result = self.fget(obj)
        return result


class EolianBaseObject(object):
    __instances_cache = {}

    def __new__(cls, c_obj_pointer=None, *args, **kargs):
        # cannot cache without a pointer
        if c_obj_pointer is None:
            return super().__new__(cls)

        # cache based on the c pointer value (assume the eolian db is stable)
        if isinstance(c_obj_pointer, c_void_p):
            key = c_obj_pointer.value
        elif isinstance(c_obj_pointer, int):
            key = c_obj_pointer
        else:
            raise TypeError('Wrong EolianBaseObject constructor pointer!!')

        # get instance from cache or create a new one
        if key in cls.__instances_cache:
            inst = cls.__instances_cache[key]
        else:
            inst = cls.__instances_cache[key] = super().__new__(cls)

        return inst

    def __init__(self, c_obj_pointer):
        if isinstance(c_obj_pointer, c_void_p):
            self._obj = c_void_p(c_obj_pointer.value)
        elif isinstance(c_obj_pointer, int):
            self._obj = c_void_p(c_obj_pointer)
        else:
            raise TypeError('Invalid constructor of type: %s for class: %s' % (
                            type(c_obj_pointer), self.__class__.__name__))

    def __eq__(self, other):
        if isinstance(other, EolianBaseObject):
            return self._obj.value == other._obj.value
        elif isinstance(other, str):
            if hasattr(self, 'name'):
                return self.name == other
            elif hasattr(self, 'short_name'):
                return self.short_name == other
        return False

    def __gt__(self, other):
        if isinstance(other, EolianBaseObject):
            if hasattr(self, 'name'):
                return self.name > other.name
            elif hasattr(self, 'short_name'):
                return self.short_name > other.short_name

    def __hash__(self):
        return self._obj.value

    @property
    def _as_parameter_(self):
        """ Used by ctypes to convert instances when calling C functions """
        return self._obj


#  Main Eolian Unit  ##########################################################

class Eolian_Unit(EolianBaseObject):
    def __repr__(self):
        return "<eolian.Eolian_Unit '{0.file}'>".format(self)

    @property
    def children(self):
        return Iterator(Eolian_Unit, lib.eolian_unit_children_get(self))

    @cached_property
    def file(self):
        return _str_to_py(lib.eolian_unit_file_get(self))

    @cached_property
    def file_path(self):
        return _str_to_py(lib.eolian_unit_file_path_get(self))

    @cached_property
    def state(self):
        c_state = lib.eolian_unit_state_get(self)
        return Eolian_State(c_state) if c_state else None

    @cached_property
    def version(self):
        return lib.eolian_unit_version_get(self)

    @property
    def objects(self):
        return Iterator(Object, lib.eolian_unit_objects_get(self))

    def object_by_name_get(self, name):
        c_obj = lib.eolian_unit_object_by_name_get(self, _str_to_bytes(name))
        return Object(c_obj) if c_obj else None

    @property
    def classes(self):
        return Iterator(Class, lib.eolian_unit_classes_get(self))

    def class_by_name_get(self, class_name):
        c_cls = lib.eolian_unit_class_by_name_get(self, _str_to_bytes(class_name))
        return Class(c_cls) if c_cls else None

    @property
    def constants(self):
        return Iterator(Constant, lib.eolian_unit_constants_get(self))

    def constant_by_name_get(self, name):
        c_var = lib.eolian_unit_constant_by_name_get(self, _str_to_bytes(name))
        return Constant(c_var) if c_var else None

    @property
    def enums(self):
        return Iterator(Typedecl, lib.eolian_unit_enums_get(self))

    def enum_by_name_get(self, name):
        c_tdecl = lib.eolian_unit_enum_by_name_get(self, _str_to_bytes(name))
        return Typedecl(c_tdecl) if c_tdecl else None

    @property
    def structs(self):
        return Iterator(Typedecl, lib.eolian_unit_structs_get(self))

    def struct_by_name_get(self, name):
        c_tdecl = lib.eolian_unit_struct_by_name_get(self, _str_to_bytes(name))
        return Typedecl(c_tdecl) if c_tdecl else None

    @property
    def aliases(self):
        return Iterator(Typedecl, lib.eolian_unit_aliases_get(self))

    def alias_by_name_get(self, name):
        c_tdecl = lib.eolian_unit_alias_by_name_get(self, _str_to_bytes(name))
        return Typedecl(c_tdecl) if c_tdecl else None

    @property
    def all_namespaces(self):
        # TODO find a better way to find namespaces (maybe inside eolian?)
        nspaces = set()
        for obj in self.classes:
            nspaces.add(Namespace(self, obj.namespace))
        for obj in self.aliases:
            nspaces.add(Namespace(self, obj.namespace))
        for obj in self.structs:
            nspaces.add(Namespace(self, obj.namespace))
        for obj in self.enums:
            nspaces.add(Namespace(self, obj.namespace))
        return sorted(nspaces)

    def namespace_get_by_name(self, name):
        return Namespace(self, name)


class Eolian_State(Eolian_Unit):
    def __init__(self, c_state=None):
        if c_state is None:
            c_state = lib.eolian_state_new()  # Eolian_State *
        EolianBaseObject.__init__(self, c_state)

    def __del__(self):
        if not _already_halted:  # do not free after eolian_shutdown
            lib.eolian_state_free(self)

    def __repr__(self):
        return "<eolian.Eolian_State, %d units loaded>" % len(list(self.units))

    def file_parse(self, filename):
        c_unit = lib.eolian_state_file_parse(self, _str_to_bytes(filename))
        return Eolian_Unit(c_unit) if c_unit else None

    def file_path_parse(self, filepath):
        c_unit = lib.eolian_state_file_path_parse(self, _str_to_bytes(filepath))
        return Eolian_Unit(c_unit) if c_unit else None

    @property
    def eo_file_paths(self):
        return Iterator(_str_to_py, lib.eolian_state_eo_file_paths_get(self))

    @property
    def eot_file_paths(self):
        return Iterator(_str_to_py, lib.eolian_state_eot_file_paths_get(self))

    @property
    def eo_files(self):
        return Iterator(_str_to_py, lib.eolian_state_eo_files_get(self))

    @property
    def eot_files(self):
        return Iterator(_str_to_py, lib.eolian_state_eot_files_get(self))

    def directory_add(self, dir_path):
        return bool(lib.eolian_state_directory_add(self, _str_to_bytes(dir_path)))

    def system_directory_add(self):
        return bool(lib.eolian_state_system_directory_add(self))

    def all_eo_files_parse(self):
        return bool(lib.eolian_state_all_eo_files_parse(self))

    def all_eot_files_parse(self):
        return bool(lib.eolian_state_all_eot_files_parse(self))

    def unit_by_file_get(self, file_name):
        c_unit = lib.eolian_state_unit_by_file_get(self, _str_to_bytes(file_name))
        return Eolian_Unit(c_unit) if c_unit else None

    @property
    def units(self):
        return Iterator(Eolian_Unit, lib.eolian_state_units_get(self))

    def objects_by_file_get(self, file_name):
        return Iterator(Object,
                        lib.eolian_state_objects_by_file_get(self, _str_to_bytes(file_name)))

    def class_by_file_get(self, file_name):
        c_cls = lib.eolian_state_class_by_file_get(self, _str_to_bytes(file_name))
        return Class(c_cls) if c_cls else None

    def constants_by_file_get(self, file_name):
        return Iterator(Constant,
                        lib.eolian_state_constants_by_file_get(self, _str_to_bytes(file_name)))

    def aliases_by_file_get(self, file_name):
        return Iterator(Typedecl,
                        lib.eolian_state_aliases_by_file_get(self, _str_to_bytes(file_name)))

    def structs_by_file_get(self, file_name):
        return Iterator(Typedecl,
                        lib.eolian_state_structs_by_file_get(self, _str_to_bytes(file_name)))

    def enums_by_file_get(self, file_name):
        return Iterator(Typedecl,
                        lib.eolian_state_enums_by_file_get(self, _str_to_bytes(file_name)))

    def state_check(self):
        return bool(lib.eolian_state_check(self))


#  Helper functions  ###################################################

def parse_folders(folders):
    """Loads a new database scanning the given folders.

    For example:

    SCAN_FOLDER = os.path.join(eolian.in_tree_src_dir(), 'src', 'lib')
    eolian_db = eolian.parse_folders(SCAN_FOLDER)
    """

    db = Eolian_State()
    if not isinstance(db, Eolian_State):
        raise (RuntimeError('Eolian, failed to create Eolian state'))

    if isinstance(folders, str):
        folders = [folders]

    for folder in folders:
        # eolian source tree scan
        if not db.directory_add(folder):
            raise (RuntimeError('Eolian, failed to scan source dirsectory'))

    # Parse all known eo files
    if not db.all_eot_files_parse():
        raise (RuntimeError('Eolian, failed to parse all EOT files'))

    if not db.all_eo_files_parse():
        raise (RuntimeError('Eolian, failed to parse all EO files'))

    return db

def in_tree_src_dir():
    """Returns the root folder of this script's source tree"""
    script_path = os.path.dirname(os.path.realpath(__file__))
    return os.path.abspath(os.path.join(script_path, '..', '..', '..'))


#  Namespace Utility Class  ###################################################

class Namespace(object):
    def __init__(self, unit, namespace_name):
        self._name = namespace_name
        self._unit = unit

    def __repr__(self):
        return "<eolian.Namespace '{0._name}'>".format(self)

    def __eq__(self, other):
        if isinstance(other, Namespace):
            return self.name == other.name
        if isinstance(other, str):
            return self.name == other
        raise TypeError('Namespace can only compare with Namespace or str')

    def __lt__(self, other):
        return self.name < other.name

    def __gt__(self, other):
        return self.name > other.name

    def __hash__(self):
        return hash(self._name)

    @property
    def unit(self):
        return self._unit

    @property
    def name(self):
        return self._name

    @property
    def namespaces(self):
        return self._name.split('.')

    @property
    def sub_namespaces(self):
        base = self._name + '.'
        deep = self._name.count('.') + 1
        return [ns for ns in self._unit.all_namespaces
                if ns.name.startswith(base) and ns.name.count('.') == deep]

    @property
    def classes(self):
        return sorted([c for c in self._unit.classes
                       if c.namespace == self._name])

    @property
    def regulars(self):
        return sorted([c for c in self._unit.classes
                       if c.type == Eolian_Class_Type.REGULAR
                       and c.namespace == self._name])

    @property
    def abstracts(self):
        return sorted([c for c in self._unit.classes
                       if c.type == Eolian_Class_Type.ABSTRACT
                       and c.namespace == self._name])

    @property
    def mixins(self):
        return sorted([c for c in self._unit.classes
                       if c.type == Eolian_Class_Type.MIXIN
                       and c.namespace == self._name])

    @property
    def interfaces(self):
        return sorted([c for c in self._unit.classes
                       if c.type == Eolian_Class_Type.INTERFACE
                       and c.namespace == self._name])

    @property
    def aliases(self):
        return sorted([td for td in self._unit.aliases
                       if td.namespace == self._name])

    @property
    def structs(self):
        return sorted([td for td in self._unit.structs
                       if td.namespace == self._name])

    @property
    def enums(self):
        return sorted([td for td in self._unit.enums
                       if td.namespace == self._name])


#  Eolian Classes  ############################################################

class Object(EolianBaseObject):
    def __new__(cls, c_obj_pointer):
        if cls is Object:
            c_type = lib.eolian_object_type_get(c_obj_pointer)
            cls = _eolian_type_class_mapping[c_type]
        return super().__new__(cls)

    def __repr__(self):
        return "<eolian.Object '{0.name}', {0.type!s}>".format(self)

    @cached_property
    def unit(self):
        c_unit = lib.eolian_object_unit_get(self)
        return Eolian_Unit(c_unit) if c_unit else None

    @cached_property
    def name(self):
        return _str_to_py(lib.eolian_object_name_get(self))

    @cached_property
    def c_name(self):
        return _str_to_py(lib.eolian_object_c_name_get(self))

    @cached_property
    def short_name(self):
        return _str_to_py(lib.eolian_object_short_name_get(self))

    @property
    def namespaces(self):
        return Iterator(_str_to_py, lib.eolian_object_namespaces_get(self))

    @cached_property
    def namespace(self):
        return '.'.join(self.namespaces)

    @cached_property
    def file(self):
        return _str_to_py(lib.eolian_object_file_get(self))

    @cached_property
    def line(self):
        return int(lib.eolian_object_line_get(self))

    @cached_property
    def column(self):
        return int(lib.eolian_object_column_get(self))

    @cached_property
    def is_beta(self):
        return bool(lib.eolian_object_is_beta(self))


class Class(Object):
    def __repr__(self):
        return "<eolian.Class '{0.name}', {0.type!s}>".format(self)

    @cached_property
    def c_macro(self):
        s = lib.eolian_class_c_macro_get(self)
        ret = _str_to_py(s)
        lib.eina_stringshare_del(c_void_p(s))
        return ret

    @cached_property
    def c_get_function_name(self):
        s = lib.eolian_class_c_get_function_name_get(self)
        ret = _str_to_py(s)
        lib.eina_stringshare_del(c_void_p(s))
        return ret

    @cached_property
    def type(self):
        return Eolian_Class_Type(lib.eolian_class_type_get(self))
    
    @cached_property
    def data_type(self):
        return _str_to_py(lib.eolian_class_data_type_get(self))

    @cached_property
    def c_data_type(self):
        s = lib.eolian_class_c_data_type_get(self)
        ret = _str_to_py(s)
        lib.eina_stringshare_del(c_void_p(s))
        return ret

    @cached_property
    def c_prefix(self):
        return _str_to_py(lib.eolian_class_c_prefix_get(self))

    @cached_property
    def event_c_prefix(self):
        return _str_to_py(lib.eolian_class_event_c_prefix_get(self))

    @cached_property
    def documentation(self):
        c_doc = lib.eolian_class_documentation_get(self)
        return Documentation(c_doc) if c_doc else None

    @property
    def constructors(self):
        return Iterator(Constructor, lib.eolian_class_constructors_get(self))

    @property
    def events(self):
        return Iterator(Event, lib.eolian_class_events_get(self))

    def event_by_name_get(self, event_name):
        c_event = lib.eolian_class_event_by_name_get(self,
                                                     _str_to_bytes(event_name))
        return Event(c_event) if c_event else None

    @cached_property
    def parent(self):
        c_class = lib.eolian_class_parent_get(self)
        return Class(c_class) if c_class else None

    @property
    def extensions(self):
        return Iterator(Class, lib.eolian_class_extensions_get(self))

    @cached_property
    def extensions_hierarchy(self):
        visited = set()
        queue = [ext for ext in self.extensions]

        while queue:
            current = queue.pop()

            if current in visited:
                continue

            visited.add(current)

            queue.extend(current.extensions)

        return visited

    @cached_property
    def inherits_full(self):
        li = []

        def do_class_recursive(cls):
            if cls.parent:
                li.append(cls.parent)
                do_class_recursive(cls.parent)
            for other in cls.extensions:
                if other not in li:
                    li.append(other)
                do_class_recursive(other)

        do_class_recursive(self)
        return li

    @cached_property
    def hierarchy(self):
        li = []
        base = self.parent
        while base:
            li.append(base)
            base = base.parent
        return li

    @cached_property
    def ctor_enable(self):
        return bool(lib.eolian_class_ctor_enable_get(self))

    @cached_property
    def dtor_enable(self):
        return bool(lib.eolian_class_dtor_enable_get(self))

    def function_by_name_get(self, function_name,
                             ftype=Eolian_Function_Type.UNRESOLVED):
        f = lib.eolian_class_function_by_name_get(self,
                                                  _str_to_bytes(function_name),
                                                  ftype)
        return Function(f) if f else None

    def functions_get(self, ftype):
        return Iterator(Function, lib.eolian_class_functions_get(self, ftype))

    @property
    def methods(self):
        return self.functions_get(Eolian_Function_Type.METHOD)

    @property
    def properties(self):
        return self.functions_get(Eolian_Function_Type.PROPERTY)

    @property
    def implements(self):
        return Iterator(Implement, lib.eolian_class_implements_get(self))

    @property
    def parts(self):
        return Iterator(Part, lib.eolian_class_parts_get(self))

    @property
    def requires(self):
        return Iterator(Class, lib.eolian_class_requires_get(self))


class Part(Object):
    def __repr__(self):
        return "<eolian.Part '{0.name}'>".format(self)

    @cached_property
    def class_(self):
        return Class(lib.eolian_part_class_get(self))

    @cached_property
    def documentation(self):
        c_doc = lib.eolian_part_documentation_get(self)
        return Documentation(c_doc) if c_doc else None


class Constructor(Object):
    def __repr__(self):
        return "<eolian.Constructor '{0.name}', optional={0.is_optional}>".format(self)

    @cached_property
    def function(self):
        return Function(lib.eolian_constructor_function_get(self))

    @cached_property
    def is_optional(self):
        return bool(lib.eolian_constructor_is_optional(self))

    @cached_property
    def class_(self):
        return Class(lib.eolian_constructor_class_get(self))


class Event(Object):
    def __repr__(self):
        return "<eolian.Event '{0.name}', c_macro='{0.c_macro}'>".format(self)

    @cached_property
    def c_macro(self):
        s = lib.eolian_event_c_macro_get(self)
        ret = _str_to_py(s)
        lib.eina_stringshare_del(c_void_p(s))
        return ret

    @cached_property
    def type(self):
        c_type = lib.eolian_event_type_get(self)
        return Type(c_type) if c_type else None

    @cached_property
    def class_(self):
        c_cls = lib.eolian_event_class_get(self)
        return Class(c_cls) if c_cls else None

    @cached_property
    def documentation(self):
        c_doc = lib.eolian_event_documentation_get(self)
        return Documentation(c_doc) if c_doc else None

    @cached_property
    def scope(self):
        return Eolian_Object_Scope(lib.eolian_event_scope_get(self))

    @cached_property
    def is_hot(self):
        return bool(lib.eolian_event_is_hot(self))

    @cached_property
    def is_restart(self):
        return bool(lib.eolian_event_is_restart(self))


class Function(Object):
    def __repr__(self):
        return "<eolian.Function '{0.name}'>".format(self)

    def full_c_name_get(self, ftype):
        s = lib.eolian_function_full_c_name_get(self, ftype)
        ret = _str_to_py(s)
        lib.eina_stringshare_del(c_void_p(s))
        return ret

    @cached_property
    def full_c_method_name(self):
        return self.full_c_name_get(Eolian_Function_Type.METHOD)

    @cached_property
    def full_c_getter_name(self):
        return self.full_c_name_get(Eolian_Function_Type.PROP_GET)
    
    @cached_property
    def full_c_setter_name(self):
        return self.full_c_name_get(Eolian_Function_Type.PROP_SET)

    @cached_property
    def type(self):
        return Eolian_Function_Type(lib.eolian_function_type_get(self))

    def scope_get(self, ftype):
        return Eolian_Object_Scope(lib.eolian_function_scope_get(self, ftype))

    @cached_property
    def method_scope(self):
        return self.scope_get(Eolian_Function_Type.METHOD)

    @cached_property
    def getter_scope(self):
        return self.scope_get(Eolian_Function_Type.PROP_GET)

    @cached_property
    def setter_scope(self):
        return self.scope_get(Eolian_Function_Type.PROP_SET)

    @cached_property
    def is_static(self):
        return bool(lib.eolian_function_is_static(self))

    @cached_property
    def object_is_const(self):
        return bool(lib.eolian_function_object_is_const(self))

    @cached_property
    def class_(self):
        c_cls = lib.eolian_function_class_get(self)
        return Class(c_cls) if c_cls else None

    def is_constructor(self, klass):
        return bool(lib.eolian_function_is_constructor(self, klass._obj))

    #  @cached_property
    #  def is_function_pointer(self):
        #  return bool(lib.eolian_function_is_function_pointer(self))

    @property
    def parameters(self):
        return Iterator(Function_Parameter,
                        lib.eolian_function_parameters_get(self))

    def values_get(self, ftype):
        # TODO rename in property_values_get
        #  (or implement a proper Property class?)
        return Iterator(Function_Parameter,
                        lib.eolian_property_values_get(self, ftype))

    @property
    def getter_values(self):  # TODO rename ...
        return self.values_get(Eolian_Function_Type.PROP_GET)

    @property
    def setter_values(self):  # TODO rename ...
        return self.values_get(Eolian_Function_Type.PROP_SET)

    def keys_get(self, ftype):
        # TODO rename in property_keys_get
        #  (or implement a proper Property class?)
        return Iterator(Function_Parameter,
                        lib.eolian_property_keys_get(self, ftype))

    @property
    def getter_keys(self):  # TODO rename ...
        return self.keys_get(Eolian_Function_Type.PROP_GET)

    @property
    def setter_keys(self):  # TODO rename ...
        return self.keys_get(Eolian_Function_Type.PROP_SET)

    def return_type_get(self, ftype):
        c_type = lib.eolian_function_return_type_get(self, ftype)
        return Type(c_type) if c_type else None

    def return_c_type_get(self, ftype):
        s = lib.eolian_function_return_c_type_get(self, ftype)
        ret = _str_to_py(s)
        lib.eina_stringshare_del(c_void_p(s))
        return ret

    def return_default_value(self, ftype):
        c_expr = lib.eolian_function_return_default_value_get(self._obj, ftype)
        return Expression(c_expr) if c_expr else None

    def return_documentation(self, ftype):
        c_doc = lib.eolian_function_return_documentation_get(self, ftype)
        return Documentation(c_doc) if c_doc else None

    def return_allow_unused(self, ftype):
        return bool(lib.eolian_function_return_allow_unused(self, ftype))

    def return_is_by_ref(self, ftype):
        return bool(lib.eolian_function_return_is_by_ref(self, ftype))

    def return_is_move(self, ftype):
        return bool(lib.eolian_function_return_is_move(self, ftype))

    @cached_property
    def method_return_type(self):
        return self.return_type_get(Eolian_Function_Type.METHOD)

    @cached_property
    def getter_return_type(self):
        return self.return_type_get(Eolian_Function_Type.PROP_GET)

    @cached_property
    def setter_return_type(self):
        return self.return_type_get(Eolian_Function_Type.PROP_SET)
    
    @cached_property
    def prop_readable(self):
        # TODO: maybe there is a better way to do this...
        ftype = Eolian_Function_Type.PROP_GET
        scope = lib.eolian_function_scope_get(self, ftype)
        return True if scope != Eolian_Object_Scope.UNKNOWN else False

    @cached_property
    def prop_writable(self):
        # TODO: maybe there is a better way to do this...
        ftype = Eolian_Function_Type.PROP_SET
        scope = lib.eolian_function_scope_get(self, ftype)
        return True if scope != Eolian_Object_Scope.UNKNOWN else False

    @cached_property
    def implement(self):
        c_impl = lib.eolian_function_implement_get(self)
        return Implement(c_impl) if c_impl else None


class Function_Parameter(Object):
    def __repr__(self):
        return "<eolian.Function_Parameter '{0.name}', type={0.type}," \
               " optional={0.is_optional}>".format(self)

    @cached_property
    def direction(self):
        return Eolian_Parameter_Direction(lib.eolian_parameter_direction_get(self))

    @cached_property
    def documentation(self):
        c_doc = lib.eolian_parameter_documentation_get(self)
        return Documentation(c_doc) if c_doc else None

    @cached_property
    def is_optional(self):
        return bool(lib.eolian_parameter_is_optional(self))

    @cached_property
    def is_by_ref(self):
        return bool(lib.eolian_parameter_is_by_ref(self))

    @cached_property
    def is_move(self):
        return bool(lib.eolian_parameter_is_move(self))

    @cached_property
    def type(self):
        c_type = lib.eolian_parameter_type_get(self)
        return Type(c_type) if c_type else None

    @cached_property
    def default_value(self):
        c_expr = lib.eolian_parameter_default_value_get(self)
        return Expression(c_expr) if c_expr else None

    def c_type_get(self, as_return=False):
        s = lib.eolian_parameter_c_type_get(self, as_return)
        ret = _str_to_py(s)
        lib.eina_stringshare_del(c_void_p(s))
        return ret


class Implement(Object):
    def __repr__(self):
        return "<eolian.Implement '{0.name}'>".format(self)

    @cached_property
    def parent(self):
        c_impl = lib.eolian_aux_implement_parent_get(self)
        return Implement(c_impl) if c_impl else None

    @cached_property
    def class_(self):
        c_cls = lib.eolian_implement_class_get(self)
        return Class(c_cls) if c_cls else None

    @cached_property
    def implementing_class(self):
        c_cls = lib.eolian_implement_implementing_class_get(self)
        return Class(c_cls) if c_cls else None

    @cached_property
    def function(self):
        c_func = lib.eolian_implement_function_get(self, None)
        return Function(c_func) if c_func else None

    def documentation_get(self, ftype=Eolian_Function_Type.METHOD):
        # c_doc = lib.eolian_implement_documentation_get(self, ftype)
        c_doc = lib.eolian_aux_implement_documentation_get(self, ftype)
        return Documentation(c_doc) if c_doc else None

    @cached_property
    def documentation_fallback(self):
        c_doc = lib.eolian_aux_implement_documentation_fallback_get(self)
        return Documentation(c_doc) if c_doc else None

    def is_auto(self, ftype=Eolian_Function_Type.METHOD):
        return bool(lib.eolian_implement_is_auto(self, ftype))
    # TODO implement util properties for is_auto

    def is_empty(self, ftype=Eolian_Function_Type.METHOD):
        return bool(lib.eolian_implement_is_empty(self, ftype))
    # TODO implement util properties for is_empty

    def is_pure_virtual(self, ftype=Eolian_Function_Type.METHOD):
        return bool(lib.eolian_implement_is_pure_virtual(self, ftype))
    # TODO implement util properties for is_pure_virtual

    @cached_property
    def is_prop_set(self):
        return bool(lib.eolian_implement_is_prop_set(self))

    @cached_property
    def is_prop_get(self):
        return bool(lib.eolian_implement_is_prop_get(self))

    @property
    def is_property(self):
        return self.is_prop_get or self.is_prop_set

    @property
    def is_method(self):
        return not self.is_property


class Type(Object):
    def __repr__(self):
        # return "<eolian.Type '{0.name}', type: {0.type!s}, c_type: '{0.c_type}'>".format(self)
        return "<eolian.Type '{0.name}', type={0.type!s}>".format(self)

    @cached_property
    def type(self):
        return Eolian_Type_Type(lib.eolian_type_type_get(self))

    @cached_property
    def builtin_type(self):
        return Eolian_Type_Builtin_Type(lib.eolian_type_builtin_type_get(self))

    @cached_property
    def c_type(self):
        s = lib.eolian_type_c_type_get(self)
        ret = _str_to_py(s)
        lib.eina_stringshare_del(c_void_p(s))
        return ret

    @cached_property
    def typedecl(self):
        c_tdecl = lib.eolian_type_typedecl_get(self)
        return Typedecl(c_tdecl) if c_tdecl else None

    @cached_property
    def base_type(self):
        c_type = lib.eolian_type_base_type_get(self)
        return Type(c_type) if c_type else None 

    @cached_property
    def next_type(self):
        c_type = lib.eolian_type_next_type_get(self)
        return Type(c_type) if c_type else None 

    @cached_property
    def aliased_base(self):
        c_type = lib.eolian_type_aliased_base_get(self)
        return Type(c_type) if c_type else None

    @cached_property
    def class_(self):
        c_cls = lib.eolian_type_class_get(self)
        return Class(c_cls) if c_cls else None

    @cached_property
    def is_const(self):
        return bool(lib.eolian_type_is_const(self))

    @cached_property
    def is_ptr(self):
        return bool(lib.eolian_type_is_ptr(self))

    @cached_property
    def is_move(self):
        return bool(lib.eolian_type_is_move(self))


class Typedecl(Object):
    def __repr__(self):
        return "<eolian.Typedecl '{0.name}', type={0.type!s}>".format(self)

    @cached_property
    def type(self):
        return Eolian_Typedecl_Type(lib.eolian_typedecl_type_get(self))

    @cached_property
    def c_type(self):
        s = lib.eolian_typedecl_c_type_get(self)
        ret = _str_to_py(s)
        lib.eina_stringshare_del(c_void_p(s))
        return ret

    @cached_property
    def free_func(self):
        return _str_to_py(lib.eolian_typedecl_free_func_get(self))

    @cached_property
    def is_extern(self):
        return bool(lib.eolian_typedecl_is_extern(self))

    @property
    def enum_fields(self):
        return Iterator(Enum_Type_Field,
                        lib.eolian_typedecl_enum_fields_get(self))

    def enum_field_get(self, field):
        c_field = lib.eolian_typedecl_enum_field_get(self, _str_to_bytes(field))
        return Enum_Type_Field(c_field) if c_field else None

    @property
    def struct_fields(self):
        return Iterator(Struct_Type_Field,
                        lib.eolian_typedecl_struct_fields_get(self)) 

    def struct_field_get(self, field):
        c_field = lib.eolian_typedecl_struct_field_get(self, _str_to_bytes(field))
        return Struct_Type_Field(c_field) if c_field else None

    @cached_property
    def base_type(self):
        c_type = lib.eolian_typedecl_base_type_get(self)
        return Type(c_type) if c_type else None

    @cached_property
    def aliased_base(self):
        c_type = lib.eolian_typedecl_aliased_base_get(self)
        return Type(c_type) if c_type else None

    @cached_property
    def documentation(self):
        c_doc = lib.eolian_typedecl_documentation_get(self)
        return Documentation(c_doc) if c_doc else None

    @cached_property
    def enum_legacy_prefix(self):
        return _str_to_py(lib.eolian_typedecl_enum_legacy_prefix_get(self))

    @cached_property
    def function_pointer(self):
        c_func = lib.eolian_typedecl_function_pointer_get(self)
        return Function(c_func) if c_func else None


class Enum_Type_Field(Object):
    def __repr__(self):
        return "<eolian.Enum_Type_Field '{0.name}', c_constant='{0.c_constant}'>".format(self)

    @cached_property
    def c_constant(self):
        s = lib.eolian_typedecl_enum_field_c_constant_get(self)
        ret = _str_to_py(s)
        lib.eina_stringshare_del(c_void_p(s))
        return ret

    @cached_property
    def value(self):
        c_expr = lib.eolian_typedecl_enum_field_value_get(self, True)
        return Expression(c_expr) if c_expr else None

    @cached_property
    def documentation(self):
        c_doc = lib.eolian_typedecl_enum_field_documentation_get(self)
        return Documentation(c_doc) if c_doc else None


class Struct_Type_Field(Object):
    def __repr__(self):
        return "<eolian.Struct_Type_Field '{0.name}', type={0.type!s}>".format(self)

    @cached_property
    def type(self):
        c_type = lib.eolian_typedecl_struct_field_type_get(self)
        return Type(c_type) if c_type else None

    @cached_property
    def c_type(self):
        s = lib.eolian_typedecl_struct_field_c_type_get(self)
        ret = _str_to_py(s)
        lib.eina_stringshare_del(c_void_p(s))
        return ret

    @cached_property
    def is_by_ref(self):
        return bool(lib.eolian_typedecl_struct_field_is_by_ref(self))

    @cached_property
    def is_move(self):
        return bool(lib.eolian_typedecl_struct_field_is_move(self))

    @cached_property
    def documentation(self):
        c_doc = lib.eolian_typedecl_struct_field_documentation_get(self)
        return Documentation(c_doc) if c_doc else None


class Expression(Object):
    def __repr__(self):
        return "<eolian.Expression type={0.type!s}, serialize='{0.serialize}'>".format(self)

    @cached_property
    def type(self):
        return Eolian_Expression_Type(lib.eolian_expression_type_get(self))

    # TODO: EAPI Eolian_Value eolian_expression_value_get(const Eolian_Expression *expr);

    @cached_property
    def serialize(self):
        s = lib.eolian_expression_serialize(self)
        ret = _str_to_py(s)
        lib.eina_stringshare_del(c_void_p(s))
        return ret
    
    @cached_property
    def binary_operator(self):
        c_op = lib.eolian_expression_binary_operator_get(self)
        return Eolian_Binary_Operator(c_op) if c_op is not None else None

    @cached_property
    def binary_lhs(self):
        c_expr = lib.eolian_expression_binary_lhs_get(self)
        return Expression(c_expr) if c_expr else None

    @cached_property
    def binary_rhs(self):
        c_expr = lib.eolian_expression_binary_rhs_get(self)
        return Expression(c_expr) if c_expr else None

    @cached_property
    def unary_operator(self):
        c_op = lib.eolian_expression_unary_operator_get(self)
        return Eolian_Unary_Operator(c_op) if c_op is not None else None

    @cached_property
    def unary_expression(self):
        c_expr = lib.eolian_expression_unary_expression_get(self)
        return Expression(c_expr) if c_expr is not None else None


class Constant(Object):
    def __repr__(self):
        return "<eolian.Constant '{0.name}', type={0.type!s}, file={0.file}>".format(self)

    @cached_property
    def value(self):
        c_expr = lib.eolian_constant_value_get(self)
        return Expression(c_expr) if c_expr else None

    @cached_property
    def type(self):
        c_type = lib.eolian_constant_type_get(self)
        return Type(c_type) if c_type else None

    @cached_property
    def is_extern(self):
        return bool(lib.eolian_constant_is_extern(self))

    @cached_property
    def documentation(self):
        c_doc = lib.eolian_constant_documentation_get(self)
        return Documentation(c_doc) if c_doc else None


class _Eolian_Doc_Token_Struct(ctypes.Structure):
    _fields_ = [("type", c_int),
                ("text", c_char_p),
                ("text_end", c_char_p)]


class Documentation(Object):
    def __repr__(self):
        t = self.summary if len(self.summary) < 40 else self.summary[:40] + '...'
        return "<eolian.Documentation summary='{}'>".format(t)

    # this is too much for py, just use string.split('\n\n') instead
    # def string_split(self, string):
    #    c_list = lib.eolian_documentation_string_split

    @cached_property
    def summary(self):
        return _str_to_py(lib.eolian_documentation_summary_get(self))

    @cached_property
    def description(self):
        return _str_to_py(lib.eolian_documentation_description_get(self))

    @cached_property
    def since(self):
        return _str_to_py(lib.eolian_documentation_since_get(self))

    @cached_property
    def summary_tokens(self):
        """ return a list of paragraphs, each one is a list of tokens """
        return self._tokenize(self.summary)

    @cached_property
    def description_tokens(self):
        """ return a list of paragraphs, each one is a list of tokens """
        return self._tokenize(self.description)

    def _tokenize(self, full_text):
        paragraphs = []
        if not full_text:
            return paragraphs

        tok = _Eolian_Doc_Token_Struct()
        for paragraph in full_text.split('\n\n'):
            tokens = []
            c_paragraph = _str_to_bytes(paragraph)  # keep c_paragraph alive !

            lib.eolian_doc_token_init(byref(tok))
            next_chunk = lib.eolian_documentation_tokenize(c_paragraph, byref(tok))
            while next_chunk:
                typ = lib.eolian_doc_token_type_get(byref(tok))
                txt = lib.eolian_doc_token_text_get(byref(tok))
                ref_obj = c_void_p(0)
                ref_attr = c_void_p(0)
                ref_type = lib.eolian_doc_token_ref_resolve(byref(tok), self.unit.state, byref(ref_obj), byref(ref_attr))
                tokens.append(Documentation_Token(typ, txt, ref_type, ref_obj, ref_attr))
                lib.free(c_void_p(txt))
                next_chunk = lib.eolian_documentation_tokenize(c_char_p(next_chunk), byref(tok))
            paragraphs.append(tokens)

        return paragraphs


class Documentation_Token(object):
    def __init__(self, c_token_type, c_text, c_ref_type, c_ref_obj, c_ref_attr):
        self._type = Eolian_Doc_Token_Type(c_token_type)
        self._text = _str_to_py(c_text)
        self._ref_type = Eolian_Object_Type(c_ref_type)
        self._ref_obj = Object(c_ref_obj) if c_ref_obj else None
        self._ref_attr = Object(c_ref_attr) if c_ref_attr else None

    def __repr__(self):
        t = self.text if len(self.text) < 40 else self.text[:40] + '...'
        return "<eolian.Doc_Token ({}), text='{}', len={}>".format(
                self.type.name, t, len(self.text))

    @property
    def type(self):
        return self._type

    @property
    def text(self):
        return self._text

    @property
    def ref_type(self):
        return self._ref_type

    @property
    def ref_obj(self):
        return self._ref_obj

    @property
    def ref_attr(self):
        return self._ref_attr


class Error(Object):
    def __repr__(self):
        return "<eolian.Error '{0.name}', message='{0.message}'>".format(self)

    @cached_property
    def message(self):
        return _str_to_py(lib.eolian_error_message_get(self))

    @cached_property
    def documentation(self):
        c_doc = lib.eolian_error_documentation_get(self)
        return Documentation(c_doc) if c_doc else None

    @cached_property
    def is_extern(self):
        return bool(lib.eolian_error_is_extern(self))


#  internal string encode/decode  #############################################

def _str_to_bytes(s):
    return s.encode('utf-8')


def _str_to_py(s):
    if s is None:
        return None    
    if isinstance(s, bytes):
        return s.decode('utf-8')
    if isinstance(s, c_char_p):
        return s.value.decode('utf-8')
    if isinstance(s, c_void_p):
        return cast(s, c_char_p).value.decode('utf-8')
    if isinstance(s, int):
        return cast(s, c_char_p).value.decode('utf-8')
    print('WARNING !!!!!!!!! Unknown type: %s' % type(s))


#  internal Object type -> Class mapping  #####################################

_eolian_type_class_mapping = {
    Eolian_Object_Type.UNKNOWN: Object,
    Eolian_Object_Type.CLASS: Class,
    Eolian_Object_Type.TYPEDECL: Typedecl,
    Eolian_Object_Type.STRUCT_FIELD: Struct_Type_Field,
    Eolian_Object_Type.ENUM_FIELD: Enum_Type_Field,
    Eolian_Object_Type.TYPE: Type,
    Eolian_Object_Type.CONSTANT: Constant,
    Eolian_Object_Type.EXPRESSION: Expression,
    Eolian_Object_Type.FUNCTION: Function,
    Eolian_Object_Type.FUNCTION_PARAMETER: Function_Parameter,
    Eolian_Object_Type.EVENT: Event,
    Eolian_Object_Type.PART: Part,
    Eolian_Object_Type.IMPLEMENT: Implement,
    Eolian_Object_Type.CONSTRUCTOR: Constructor,
    Eolian_Object_Type.DOCUMENTATION: Documentation,
    Eolian_Object_Type.ERROR: Error,
}


#  module init/shutdown  ######################################################

def _cleanup():
    global _already_halted
    lib.eolian_shutdown()
    _already_halted = True


lib.eolian_init()
atexit.register(_cleanup)


#  API coverage statistics  ###################################################

if __name__ == '__main__':
    import sys
    import os
    import re

    # find Eolian.h in source tree
    script_path = os.path.dirname(os.path.realpath(__file__))
    eolian_header = os.path.join(script_path, '..', '..', 'lib', 'eolian', 'Eolian.h')
    eolian_header = os.path.abspath(eolian_header)

    # prepare the two regexp
    flags = re.S | re.M
    FUNC_DEFINED_RE = re.compile(r'^\s*EAPI[\w\s*]*(eolian_\w*)\s*\([\w\s*,]*\);', flags)
    FUNC_USED_RE = re.compile(r'^[^\n#]*lib\.(eolian_[\w]*)\s*\(', flags)

    ENUM_DEFINED_RE = re.compile(r'typedef enum\s*{([^}]*)}\s*(Eolian_[\w]*);', flags)
    ENUM_USED_RE = re.compile(r'^class\s([\w]*)\(IntEnum\):', flags)

    # ignore some know cases (function or enum)
    IGNORED = {
        'eolian_implement_documentation_get',  # we are using eolian_aux_implement_documentation_get
        'eolian_documentation_string_split',  # not needed in python
    }

    # extract functions and enums from Eolian.h
    defined_funcs = []
    defined_enums = []
    with open(eolian_header, 'r') as fh:
        header = fh.read()
    for match in re.finditer(FUNC_DEFINED_RE, header):
        func_name = match.group(1)
        if func_name not in IGNORED:
            defined_funcs.append(func_name)
    for match in re.finditer(ENUM_DEFINED_RE, header):
        enum_name = match.group(2)
        if enum_name not in IGNORED:
            defined_enums.append(enum_name)
    defined_funcs = set(defined_funcs)
    defined_enums = set(defined_enums)

    # extract functions and enums from eolian.py (this file)
    used_funcs = []
    used_enums = []
    with open(__file__, 'r') as fh:
        source = fh.read()
    for match in re.finditer(FUNC_USED_RE, source):
        func_name = match.group(1)
        used_funcs.append(func_name)
    for match in re.finditer(ENUM_USED_RE, source):
        enum_name = match.group(1)
        used_enums.append(enum_name)
    used_funcs = set(used_funcs)
    used_enums = set(used_enums)

    # show general info
    num_def, num_usd = len(defined_funcs), len(used_funcs)
    num_enum_def, num_enum_usd = len(defined_enums), len(used_enums)
    print('Pyolian coverage results')
    print('========================')
    print('Found %d functions defined in Eolian.h (%s)' % (num_def, eolian_header))
    print('Found %d functions used in eolian.py (hopefully not multiline-commented)' % num_usd)
    print('Found %d enums defined in Eolian.h' % num_enum_def)
    print('Found %d enums defined in eolian.py' % num_enum_usd)
    print('Total API coverage %.1f%%' % (num_usd / num_def * 100))
    print()

    # list all missing functions
    missing = defined_funcs - used_funcs
    print('{} Missing functions in eolian.py'.format(len(missing)))
    print('=================================')
    for i, func_name in enumerate(sorted(missing), 1):
        print('{:02d}. {}'.format(i, func_name))
    print()

    # list all missing enums
    missing = defined_enums - used_enums
    if len(missing) > 0:
        print('{} Missing enums in eolian.py'.format(len(missing)))
        print('=================================')
        for i, enum_name in enumerate(sorted(missing), 1):
            print('{:02d}. {}'.format(i, enum_name))
        print()

    # List all functions found in C and Py (--all option)
    if '--all' in sys.argv:
        to_list = (
            (defined_funcs, '{} EAPI functions defined in Eolian.h'),
            (used_funcs, '{} functions called in eolian.py'),
            (defined_enums, '{} Enums defined in Eolian.h'),
            (used_enums, '{} Enums defined in eolian.py'),
        )
        for bag, label in to_list:
            print(label.format(len(bag)))
            print('==============================')
            for i, obj_name in enumerate(sorted(bag), 1):
                print('{:03d}. {}'.format(i, obj_name))
            print()
    else:
        print('Additional arguments')
        print('====================')
        print(' --all   To list all functions and enums found')
        print()
