class Extension(Configurable):
    """
    All extensions should inherit from this base class

    Attributes:
        extension_name: str, the unique name of this extension, should
            be overriden and namespaced appropriately.
        project: project.Project, the Project instance which documentation
            hotdoc is working on.
        formatter: formatter.Formatter, may be subclassed.
        argument_prefix (str): Short name of this extension, used as a prefix
            to add to automatically generated command-line arguments.
    """
    # pylint: disable=unused-argument
    extension_name = "base-extension"
    argument_prefix = ''
    paths_arguments = {}
    path_arguments = {}
    written_out_sitemaps = set()

    def __init__(self, app, project):
        """Constructor for `Extension`.

        This should never get called directly.

        Args:
            project: The `project.Project` instance which documentation
                is being generated.
        """
        self.project = project
        self.app = app
        self.sources = set()
        self.smart_sources = []
        self.index = None
        self.source_roots = OrderedSet()
        self._created_symbols = DefaultOrderedDict(OrderedSet)
        self.__package_root = None
        self.__toplevel_comments = OrderedSet()

        self.formatter = self._make_formatter()

    # pylint: disable=no-self-use
    def warn(self, code, message):
        """
        Shortcut function for `loggable.warn`

        Args:
            code: see `utils.loggable.warn`
            message: see `utils.loggable.warn`
        """
        warn(code, message)

    # pylint: disable=no-self-use
    def error(self, code, message):
        """
        Shortcut function for `utils.loggable.error`

        Args:
            code: see `utils.loggable.error`
            message: see `utils.loggable.error`
        """
        error(code, message)

    def debug(self, message, domain=None):
        """
        Shortcut function for `utils.loggable.debug`

        Args:
            message: see `utils.loggable.debug`
            domain: see `utils.loggable.debug`
        """
        if domain is None:
            domain = self.extension_name
        debug(message, domain)

    def info(self, message, domain=None):
        """
        Shortcut function for `utils.loggable.info`

        Args:
            message: see `utils.loggable.info`
            domain: see `utils.loggable.info`
        """
        if domain is None:
            domain = self.extension_name
        info(message, domain)

    def reset(self):
        """
        This function is only useful for testing purposes, at least
        for now.
        """
        self._created_symbols = DefaultOrderedDict(OrderedSet)
        self.__package_root = None

    def get_pagename(self, name):
        self.__find_package_root()
        # Find the longest prefix
        longest = None
        for path in OrderedSet([self.__package_root]) | self.source_roots:
            commonprefix = os.path.commonprefix([path, name])
            if commonprefix == path and (longest is None or len(path) > len(longest)):
                longest = path

        if longest is not None:
            return os.path.relpath(name, longest)

        return name

    def get_symbol_page(self, symbol_name, symbol_pages, smart_pages, section_links):
        if symbol_name in symbol_pages:
            return symbol_pages[symbol_name]

        symbol = self.app.database.get_symbol(symbol_name)
        assert symbol is not None

        if symbol.parent_name and symbol.parent_name != symbol_name:
            page = self.get_symbol_page(
                symbol.parent_name, symbol_pages, smart_pages, section_links)
        else:
            smart_key = self._get_smart_key(symbol)
            if smart_key is None:
                return None

            if smart_key in smart_pages:
                page = smart_pages[smart_key]
            else:
                pagename = self.get_pagename(smart_key)
                page = Page(smart_key, True, self.project.sanitized_name, self.extension_name,
                            output_path=os.path.dirname(pagename))
                if page.link.ref in section_links:
                    self.warn('output-page-conflict',
                              'Creating a page for symbol %s would overwrite the page '
                              'declared in a toplevel comment (%s)' % (symbol_name, page.link.ref))
                    page = None
                else:
                    smart_pages[smart_key] = page

        if page is not None:
            symbol_pages[symbol_name] = page

        return page

    def _get_toplevel_comments(self):
        return self.__toplevel_comments

    # pylint: disable=too-many-locals
    # pylint: disable=too-many-branches
    # pylint: disable=too-many-statements
    def make_pages(self):
        # All symbol names that no longer need to be assigned to a page
        dispatched_symbol_names = set()

        # Map symbol names with pages
        # This is used for assigning symbols with a parent to the page
        # where their parent will be rendered, unless the symbol has been
        # explicitly assigned or ignored
        symbol_pages = {}

        smart_pages = OrderedDict()

        # Map pages with the sources they explicitly import
        imported_sources = {}

        # This is simply used as a conflict detection mechanism, see
        # hotdoc.core.tests.test_doc_tree.TestTree.test_section_and_path_conflict
        section_links = set()

        # These are used as a duplicate detection mechanism, map
        # relocated or ignored symbols to the source files where they were initially
        # listed
        relocated_symbols = {}
        private_symbols = {}

        # First we make one page per toplevel comment (eg. SECTION comment)
        # This is the highest priority mechanism for sorting symbols
        for comment in self._get_toplevel_comments():
            # Programming error from extension author
            assert comment.name
            symbol_names = comment.meta.pop('symbols', [])
            private_symbol_names = comment.meta.pop('private-symbols', [])
            sources = comment.meta.pop('sources', None)

            page = Page(comment.name, True, self.project.sanitized_name, self.extension_name,
                        comment=comment)

            for symbol_name in symbol_names:
                if symbol_name in relocated_symbols:
                    self.warn('symbol-listed-twice',
                              'Symbol %s listed in %s was already listed in %s' %
                              (symbol_name, comment.filename, relocated_symbols[symbol_name]))
                    continue
                elif symbol_name in private_symbols:
                    self.warn('symbol-listed-twice',
                              'Symbol %s listed in %s was marked as private in %s' %
                              (symbol_name, comment.filename, private_symbols[symbol_name]))
                    continue
                else:
                    page.symbol_names.add(symbol_name)
                    symbol_pages[symbol_name] = page
                    relocated_symbols[symbol_name] = comment.filename
                    dispatched_symbol_names.add(symbol_name)

            for symbol_name in private_symbol_names:
                if symbol_name in relocated_symbols:
                    self.warn('symbol-listed-twice',
                              'Symbol %s marked private in %s was already listed in %s' %
                              (symbol_name, comment.filename, relocated_symbols[symbol_name]))
                    continue
                elif symbol_name in private_symbols:
                    self.warn('symbol-listed-twice',
                              'Symbol %s marked as private in %s was '
                              'already marked as private in %s' %
                              (symbol_name, comment.filename, private_symbols[symbol_name]))
                    continue
                private_symbols[symbol_name] = comment.filename
                symbol_pages[symbol_name] = None
                dispatched_symbol_names.add(symbol_name)

            section_links.add(page.link.ref)
            smart_key = self._get_comment_smart_key(comment)
            if smart_key in smart_pages:
                smart_pages[comment.name] = page
            else:
                smart_pages[smart_key] = page

            if sources is not None:
                abs_sources = []
                for source in sources:
                    if os.path.isabs(source):
                        abs_sources.append(source)
                    else:
                        abs_sources.append(os.path.abspath(os.path.join(
                            os.path.dirname(comment.filename), source)))
                imported_sources[page] = abs_sources

        # Used as a duplicate detection mechanism
        relocated_sources = {}

        # We now browse all the pages with explicitly imported sources
        # Importing sources has a lower level of priority than importing
        # symbols, which is why we do that in a separate loop
        for page, sources in imported_sources.items():
            for source in sources:
                if source not in self._get_all_sources():
                    self.warn('invalid-relocated-source',
                              'Source %s does not exist but is relocated in %s' %
                              (source, page.name))
                    continue

                if source in relocated_sources:
                    self.warn('invalid-relocated-source',
                              'Source %s relocated in %s was already relocated in %s' %
                              (source, page.name, relocated_sources[source]))
                    continue

                if source in self._created_symbols:
                    symbol_names = OrderedSet(
                        self._created_symbols[source]) - dispatched_symbol_names
                    page.symbol_names |= symbol_names
                    dispatched_symbol_names |= symbol_names

                relocated_sources[source] = page.name

        # We now browse all the symbols we have created
        for _, symbol_names in self._created_symbols.items():
            for symbol_name in symbol_names:
                if symbol_name in dispatched_symbol_names:
                    continue

                page = self.get_symbol_page(
                    symbol_name, symbol_pages, smart_pages, section_links)

                # Can be None if creating a page to hold the symbol conflicts with
                # a page explicitly declared in a toplevel comment or a parent has been
                # marked as private
                if page is None:
                    continue

                page.symbol_names.add(symbol_name)
                dispatched_symbol_names.add(symbol_name)

        # Finally we make our index page
        if self.index:
            index_page = self.project.tree.parse_page(
                self.index, next(iter(self.project.include_paths)), self.extension_name)
        else:
            index_page = Page('%s-index' % self.argument_prefix, True, self.project.sanitized_name,
                              self.extension_name)

        if not index_page.title:
            index_page.title = self._get_smart_index_title()

        smart_pages['%s-index' % self.argument_prefix] = index_page

        return smart_pages

    def setup(self):
        """
        Extension subclasses should implement this to scan whatever
        source files they have to scan, and connect to the various
        signals they have to connect to.

        Note that this will be called *after* the `tree.Tree`
        of this instance's `Extension.project` has been fully
        constructed, but before its `tree.Tree.resolve_symbols`
        method has been called.
        """
        pass

    @staticmethod
    def get_dependencies():
        """
        Override this to return the list of extensions this extension
        depends on if needed.

        Returns:
            list: A list of `ExtDependency` instances.
        """
        return []

    def parse_toplevel_config(self, config):
        """Parses and make use of the toplevel configuration."""
        self.formatter.parse_toplevel_config(config)

    def parse_config(self, config):
        """
        Override this, making sure to chain up first, if your extension adds
        its own custom command line arguments, or you want to do any further
        processing on the automatically added arguments.

        The default implementation will set attributes on the extension:
        - 'sources': a set of absolute paths to source files for this extension
        - 'index': absolute path to the index for this extension

        Additionally, it will set an attribute for each argument added with
        `Extension.add_path_argument` or `Extension.add_paths_argument`, with
        the extension's `Extension.argument_prefix` stripped, and dashes
        changed to underscores.

        Args:
            config: a `config.Config` instance
        """
        prefix = self.argument_prefix
        self.sources = config.get_sources(prefix)
        self.smart_sources = [
            self._get_smart_filename(s) for s in self.sources]
        self.index = config.get_index(prefix)
        self.source_roots = OrderedSet(
            config.get_paths('%s_source_roots' % prefix))

        for arg, dest in list(self.paths_arguments.items()):
            val = config.get_paths(arg)
            setattr(self, dest, val)

        for arg, dest in list(self.path_arguments.items()):
            val = config.get_path(arg)
            setattr(self, dest, val)

        self.formatter.parse_config(config)

    def add_attrs(self, symbol, **kwargs):
        """
        Helper for setting symbol extension attributes
        """
        for key, val in kwargs.items():
            symbol.add_extension_attribute(self.extension_name, key, val)

    def get_attr(self, symbol, attrname):
        """
        Helper for getting symbol extension attributes
        """
        return symbol.extension_attributes.get(self.extension_name, {}).get(
            attrname, None)

    @staticmethod
    def add_arguments(parser):
        """
        Subclasses may implement this method to add their own arguments to
        the hotdoc binary.

        In this function, you should add an argument group to the passed-in
        parser, corresponding to your extension.
        You can then add arguments to that argument group.

        Example::

            @staticmethod
            def add_arguments(parser):
                group = parser.add_argument_group('Chilidoc',
                    'Delicious Hotdoc extension')
                Chilidoc.add_sources_argument(group)
                group.add_argument('--chili-spicy', action='store_true',
                    help='Whether to add extra pepper')

        Args:
            parser (argparse.ArgumentParser): Main hotdoc argument parser
        """
        pass

    @classmethod
    def get_assets_licensing(cls):
        return {}

    @classmethod
    def add_index_argument(cls, group):
        """
        Subclasses may call this to add an index argument.

        Args:
            group: arparse.ArgumentGroup, the extension argument group
            prefix: str, arguments have to be namespaced
        """
        prefix = cls.argument_prefix

        group.add_argument(
            '--%s-index' % prefix, action="store",
            dest="%s_index" % prefix,
            help=("Name of the %s root markdown file, can be None" % (
                cls.extension_name)))

    @classmethod
    def add_sources_argument(cls, group, allow_filters=True, prefix=None, add_root_paths=False):
        """
        Subclasses may call this to add sources and source_filters arguments.

        Args:
            group: arparse.ArgumentGroup, the extension argument group
            allow_filters: bool,  Whether the extension wishes to expose a
                source_filters argument.
            prefix: str, arguments have to be namespaced.
        """
        prefix = prefix or cls.argument_prefix

        group.add_argument("--%s-sources" % prefix,
                           action="store", nargs="+",
                           dest="%s_sources" % prefix.replace('-', '_'),
                           help="%s source files to parse" % prefix)

        if allow_filters:
            group.add_argument("--%s-source-filters" % prefix,
                               action="store", nargs="+",
                               dest="%s_source_filters" % prefix.replace(
                                   '-', '_'),
                               help="%s source files to ignore" % prefix)

        if add_root_paths:
            group.add_argument("--%s-source-roots" % prefix,
                               action="store", nargs="+",
                               dest="%s_source_roots" % prefix.replace(
                                   '-', '_'),
                               help="%s source root directories allowing files "
                                    "to be referenced relatively to those" % prefix)

    @classmethod
    def add_path_argument(cls, group, argname, dest=None, help_=None):
        """
        Subclasses may call this to expose a path argument.

        Args:
            group: arparse.ArgumentGroup, the extension argument group
            argname: str, the name of the argument, will be namespaced.
            dest: str, similar to the `dest` argument of
                `argparse.ArgumentParser.add_argument`, will be namespaced.
            help_: str, similar to the `help` argument of
                `argparse.ArgumentParser.add_argument`.
        """
        prefixed = '%s-%s' % (cls.argument_prefix, argname)
        if dest is None:
            dest = prefixed.replace('-', '_')
            final_dest = dest[len(cls.argument_prefix) + 1:]
        else:
            final_dest = dest
            dest = '%s_%s' % (cls.argument_prefix, dest)

        group.add_argument('--%s' % prefixed, action='store',
                           dest=dest, help=help_)
        cls.path_arguments[dest] = final_dest

    @classmethod
    def add_paths_argument(cls, group, argname, dest=None, help_=None):
        """
        Subclasses may call this to expose a paths argument.

        Args:
            group: arparse.ArgumentGroup, the extension argument group
            argname: str, the name of the argument, will be namespaced.
            dest: str, similar to the `dest` argument of
                `argparse.ArgumentParser.add_argument`, will be namespaced.
            help_: str, similar to the `help` argument of
                `argparse.ArgumentParser.add_argument`.
        """
        prefixed = '%s-%s' % (cls.argument_prefix, argname)
        if dest is None:
            dest = prefixed.replace('-', '_')
            final_dest = dest[len(cls.argument_prefix) + 1:]
        else:
            final_dest = dest
            dest = '%s_%s' % (cls.argument_prefix, dest)

        group.add_argument('--%s' % prefixed, action='store', nargs='+',
                           dest=dest, help=help_)
        cls.paths_arguments[dest] = final_dest

    def add_comment(self, comment):
        if comment.toplevel:
            self.__toplevel_comments.add(comment)
        else:
            self.app.database.add_comment(comment)

    def create_symbol(self, *args, **kwargs):
        """
        Extensions that discover and create instances of `symbols.Symbol`
        should do this through this method, as it will keep an index
        of these which can be used when generating a "naive index".

        See `database.Database.create_symbol` for more
        information.

        Args:
            args: see `database.Database.create_symbol`
            kwargs: see `database.Database.create_symbol`

        Returns:
            symbols.Symbol: the created symbol, or `None`.
        """
        if not kwargs.get('project_name'):
            kwargs['project_name'] = self.project.project_name

        sym = self.app.database.create_symbol(*args, **kwargs)
        if sym:
            # pylint: disable=unidiomatic-typecheck
            if type(sym) != Symbol:
                self._created_symbols[sym.filename].add(sym.unique_name)

        return sym

    def rename_symbol(self, unique_name, target):
        sym = self.app.database.rename_symbol(unique_name, target)
        # pylint: disable=unidiomatic-typecheck
        if sym and type(sym) != Symbol:
            self._created_symbols[sym.filename].remove(target)
            self._created_symbols[sym.filename].add(sym.unique_name)

    def _make_formatter(self):
        return Formatter(self)

    def get_possible_path(self, name):
        self.__find_package_root()

        for path in OrderedSet([self.__package_root]) | self.source_roots:
            possible_path = os.path.join(path, name)
            if possible_path in self._get_all_sources():
                return self._get_smart_filename(possible_path)
        return None

    def __find_package_root(self):
        if self.__package_root:
            return

        commonprefix = os.path.commonprefix(
            list(self._get_all_sources()) + list(self.source_roots))
        self.__package_root = os.path.dirname(commonprefix)

    def _get_smart_index_title(self):
        return 'Reference Manual'

    def _get_all_sources(self):
        return self.sources

    def _get_smart_key(self, symbol):
        return symbol.filename

    def _get_smart_filename(self, filename):
        return filename

    def _get_comment_smart_key(self, comment):
        return comment.filename

    def format_page(self, page, link_resolver, output):
        """
        Called by `project.Project.format_page`, to leave full control
        to extensions over the formatting of the pages they are
        responsible of.

        Args:
            page: tree.Page, the page to format.
            link_resolver: links.LinkResolver, object responsible
                for resolving links potentially mentioned in `page`
            output: str, path to the output directory.
        """
        debug('Formatting page %s' % page.link.ref, 'formatting')

        if output:
            actual_output = os.path.join(output,
                                         'html')
            if not os.path.exists(actual_output):
                os.makedirs(actual_output)
        else:
            actual_output = None

        page.format(self.formatter, link_resolver, actual_output)

    def write_out_sitemap(self, opath):
        """
        Banana banana
        """
        if opath not in self.written_out_sitemaps:
            Extension.formatted_sitemap = self.formatter.format_navigation(
                self.app.project)
            if Extension.formatted_sitemap:
                escaped_sitemap = Extension.formatted_sitemap.replace(
                    '\\', '\\\\').replace('"', '\\"').replace('\n', '')
                js_wrapper = 'sitemap_downloaded_cb("%s");' % escaped_sitemap
                with open(opath, 'w') as _:
                    _.write(js_wrapper)

        self.written_out_sitemaps.add(opath)

    # pylint: disable=too-many-locals
    def write_out_page(self, output, page):
        """
        Banana banana
        """
        subpages = OrderedDict({})
        all_pages = self.project.tree.get_pages()
        subpage_names = self.get_subpages_sorted(all_pages, page)
        for pagename in subpage_names:
            proj = self.project.subprojects.get(pagename)

            if not proj:
                cpage = all_pages[pagename]
                sub_formatter = self.project.extensions[
                    cpage.extension_name].formatter
            else:
                cpage = proj.tree.root
                sub_formatter = proj.extensions[cpage.extension_name].formatter

            subpage_link, _ = cpage.link.get_link(self.app.link_resolver)
            prefix = sub_formatter.get_output_folder(cpage)
            if prefix:
                subpage_link = '%s/%s' % (prefix, subpage_link)
            subpages[subpage_link] = cpage

        html_subpages = self.formatter.format_subpages(page, subpages)

        js_dir = os.path.join(output, 'html', 'assets', 'js')
        if not os.path.exists(js_dir):
            os.makedirs(js_dir)
        sm_path = os.path.join(js_dir, 'sitemap.js')
        self.write_out_sitemap(sm_path)

        self.formatter.write_out(page, html_subpages, output)

    def get_subpages_sorted(self, pages, page):
        """Get @page subpages sorted appropriately."""

        sorted_pages = []
        to_sort = []
        for subpage in page.subpages:
            # Do not resort subprojects even if they are
            # 'generated'.
            if pages[subpage].pre_sorted:
                sorted_pages.append(subpage)
            else:
                to_sort.append(subpage)

        return sorted_pages + sorted(
            to_sort, key=lambda p: pages[p].get_title().lower()) class ExtDependency:
    """
    Represents a dependency on another extension.

    If not satisfied, the extension will not be instantiated.

    See the `Extension.get_dependencies` static method documentation
    for more information.

    Attributes:
        dependency_name: str, the name of the extension depended on.
        is_upstream: bool, if set to true hotdoc will arrange for
            the extension depended on to have its `Extension.setup`
            implementation called first. Circular dependencies will
            generate an error.
    """

    def __init__(self, dependency_name, is_upstream=False, optional=False):
        """
        Constructor for `Extension`.

        Args:
            dependency_name: str, see `ExtDependency.dependency_name`
            is_upstream: bool, see `ExtDependency.is_upstream`
        """
        self.dependency_name = dependency_name
        self.is_upstream = is_upstream
        self.optional = optional class Link:
    """
    Banana banana
    """
    resolving_title_signal = Signal()

    def __init__(self, ref, title, id_, mandatory=False):
        self.ref = None
        self._title = None
        # Whether the link should be always resolved when true and a warning
        # otherwise
        self.__mandatory = mandatory
        # Ensure we warn only once for a unresolved mandatory link
        self.__warned = False

        if title:
            self._title = str(title)
        if ref:
            self.ref = str(ref)

        self.id_ = id_

    @property
    def title(self):
        """
        Banana banana
        """
        resolved_title = Link.resolving_title_signal(self)
        resolved_title = [elem for elem in resolved_title if elem is not
                          None]
        if resolved_title:
            return str(resolved_title[0])
        return self._title

    @title.setter
    def title(self, value):
        self._title = str(value)

    def get_title(self):
        """
        Convenience wrapper for the `title` property.

        Exists mainly to facilitate the task of the cmark C extension.
        """
        return self.title

    def get_link(self, link_resolver):
        """
        Banana banana
        """
        res = link_resolver.resolving_link_signal(self)

        if not res:
            ref = self.ref
        else:
            ref = res[0] or self.ref

        if self.__mandatory and not self.__warned:
            if not ref:
                warn('mandatory-link-not-found',
                     'Mandatory link %s could not be resolved' % self)
            self.__warned = True

        if not res:
            return ref, None

        if res[1]:
            return ref, dict_to_html_attrs(res[1])
        return ref, None

    def __repr__(self):
        return "Link %s -> %s (%s)" % (self.id_, self.ref, self._title) class LinkResolver:
    """
    Banana banana
    """

    def __init__(self, database):
        self.__links = {}
        self.__doc_db = database
        self.get_link_signal = Signal()
        self.resolving_link_signal = Signal(optimized=True)

    # pylint: disable=too-many-return-statements
    def get_named_link(self, name):
        """
        Banana banana
        """
        return self.__get_named_link(name)

    def __get_named_link(self, name, recursed=False):
        url_components = urllib.parse.urlparse(name)
        if bool(url_components.netloc):
            return Link(name, None, name)

        if name in self.__links:
            return self.__links[name]

        sym = self.__doc_db.get_symbol(name)
        if sym and sym.link:
            self.__links[name] = sym.link
            return sym.link

        lazy_loaded = self.get_link_signal(self, name)
        lazy_loaded = [elem for elem in lazy_loaded if elem is not None]
        if lazy_loaded:
            link = lazy_loaded[0]
            self.__links[name] = link
            link.id_ = name
            return link

        if url_components.scheme in ['man', 'mailto']:
            return Link(name, None, name)

        # Formatters should warn later about broken anchor links
        if bool(url_components.fragment) and not bool(url_components.path):
            return Link(name, None, name)

        if name.endswith('s') and not recursed:
            return self.__get_named_link(name[:-1], True)

        return None

    def add_link(self, link):
        """
        Banana banana
        """
        if link.id_ not in self.__links:
            self.__links[link.id_] = link

    def upsert_link(self, link, overwrite_ref=False):
        """
        Banana banana
        """
        elink = self.__links.get(link.id_)

        if elink:
            if elink.ref is None or overwrite_ref and link.ref:
                elink.ref = link.ref
            # pylint: disable=protected-access
            if link._title is not None:
                # pylint: disable=protected-access
                elink.title = link._title
            return elink

        if not overwrite_ref:
            sym = self.__doc_db.get_symbol(link.id_)
            if sym and sym.link:
                self.__links[link.id_] = sym.link
                return sym.link

        self.add_link(link)
        return link class Page:
    "Banana banana"
    meta_schema = {Optional('title'): And(str, len),
                   Optional('short-description'): And(str),
                   Optional('description'): And(str),
                   Optional('render-subpages'): bool,
                   Optional('auto-sort'): bool,
                   Optional('full-width'): bool,
                   Optional('see-also'): And(str, len),
                   Optional('extra'): Schema({str: object}),
                   Optional('thumbnail'): And(str, len),
                   Optional('include'): And(str, len),
                   Optional('redirect'): str,
                   }

    # pylint: disable=too-many-arguments
    # pylint: disable=too-many-locals
    def __init__(self, name, generated, project_name, extension_name,
                 source_file=None,
                 ast=None,
                 output_path='',
                 raw_contents=None,
                 comment=None,
                 meta=None,
                 pre_sorted=False,
                 symbol_names=None):
        assert name

        if not generated:
            assert source_file is not None

        self.name = name
        basename = os.path.basename(name)
        name = os.path.splitext(basename)[0]
        ref = os.path.join(output_path,
                           re.sub(r'\W+', '-', os.path.splitext(basename)[0]))
        pagename = '%s.html' % ref

        self.generated = generated
        self.project_name = project_name
        self.extension_name = extension_name
        self.source_file = source_file
        self.ast = ast
        self.raw_contents = raw_contents
        self.comment = comment
        self.pre_sorted = pre_sorted
        self.symbol_names = OrderedSet(symbol_names or [])

        self.output_attrs = None
        self.subpages = OrderedSet()
        self.symbols = []
        self.private_symbols = []
        self.typed_symbols = OrderedDict()
        self.by_parent_symbols = OrderedDict()
        self.formatted_contents = None
        self.detailed_description = None
        self.build_path = None
        self.cached_paths = OrderedSet()

        if comment:
            meta = comment.meta
        elif meta:
            meta = meta
        else:
            meta = {}

        self.meta = {}
        for key, value in meta.items():
            try:
                self.meta.update(Schema(Page.meta_schema).validate({
                    key.replace('_', '-').lower(): value}))
            except SchemaError as err:
                warn('invalid-page-metadata',
                     '%s: Invalid metadata: \n%s, discarding metadata' %
                     (self.name, str(err)))

        if not self.meta.get('extra'):
            self.meta['extra'] = defaultdict()

        self.title = self.meta.get(
            'title', cmark.title_from_ast(self.ast) if ast else '')
        self.thumbnail = self.meta.get('thumbnail')
        self.short_description = self.meta.get('short-description', None)
        self.render_subpages = self.meta.get('render-subpages', True)

        self.link = Link(pagename, self.title or name, ref)

    def __repr__(self):
        return "<Page %s>" % self.name

    @staticmethod
    def __get_empty_typed_symbols():
        typed_symbols_list = namedtuple(
            'TypedSymbolsList', ['name', 'symbols'])
        empty_typed_symbols = {}

        for subclass in all_subclasses(Symbol):
            empty_typed_symbols[subclass] = typed_symbols_list(
                subclass.get_plural_name(), [])

        return empty_typed_symbols

    def resolve_symbols(self, tree, database, link_resolver):
        """
        When this method is called, the page's symbol names are queried
        from `database`, and added to lists of actual symbols, sorted
        by symbol class.
        """
        self.typed_symbols = self.__get_empty_typed_symbols()
        all_syms = OrderedSet()
        for sym_name in self.symbol_names:
            sym = database.get_symbol(sym_name)
            self.__query_extra_symbols(
                sym, all_syms, tree, link_resolver, database)

        if tree.project.is_toplevel:
            page_path = self.link.ref
        else:
            page_path = self.project_name + '/' + self.link.ref

        if self.meta.get("auto-sort", True):
            all_syms = sorted(all_syms, key=lambda x: x.unique_name)
        for sym in all_syms:
            sym.update_children_comments()
            self.__resolve_symbol(sym, link_resolver, page_path)
            self.symbol_names.add(sym.unique_name)

        # Always put symbols with no parent at the end
        no_parent_syms = self.by_parent_symbols.pop(None, None)
        if no_parent_syms:
            self.by_parent_symbols[None] = no_parent_syms

        for sym_type in [ClassSymbol, AliasSymbol, InterfaceSymbol,
                         StructSymbol]:
            syms = self.typed_symbols[sym_type].symbols

            if not syms:
                continue

            if self.title is None:
                self.title = syms[0].display_name
            if self.comment is None:
                self.comment = Comment(name=self.name)
                self.comment.short_description = syms[
                    0].comment.short_description
                self.comment.title = syms[0].comment.title
            break

    # pylint: disable=no-self-use
    def __fetch_comment(self, sym, database):
        sym.comment = database.get_comment(
            sym.unique_name) or Comment(sym.unique_name)

        for sym in sym.get_children_symbols():
            if isinstance(sym, Symbol):
                self.__fetch_comment(sym, database)

    def __format_page_comment(self, formatter, link_resolver):
        if not self.comment:
            return

        if self.comment.short_description:
            self.short_description = formatter.format_comment(
                self.comment.short_description, link_resolver).strip()
            if self.short_description.startswith('<p>'):
                self.short_description = self.short_description[3:-4]
        if self.comment.title:
            self.title = formatter.format_comment(
                self.comment.title, link_resolver).strip()
            if self.title.startswith('<p>'):
                self.title = self.title[3:-4]

        if self.title:
            self.formatted_contents += '<h1 id="%s-page">%s</h1>' % (
                id_from_text(self.title), self.title)

        self.formatted_contents += formatter.format_comment(
            self.comment, link_resolver)

    def __redirect_if_needed(self, formatter, link_resolver):
        redirect = self.meta.get("redirect")
        if not redirect:
            return False
        link = link_resolver.get_named_link(redirect)
        if link:
            if formatter.extension.project.is_toplevel:
                page_path = self.link.ref
            else:
                page_path = self.project_name + '/' + self.link.ref
            self_dir = os.path.dirname(page_path)
            if self_dir == self.link.ref:
                self_dir = ""
            self.meta["redirect"] = os.path.relpath(link.ref, self_dir)
        else:
            warn('markdown-bad-link', "Bad redirect link '%s' in page: %s"
                 % (redirect, self.name))
        return True

    def __format_content(self, formatter, link_resolver):
        if self.ast:
            out, diags = cmark.ast_to_html(self.ast, link_resolver)
            for diag in diags:
                warn(
                    diag.code,
                    message=diag.message,
                    filename=self.source_file or self.name)
            self.formatted_contents += out

        if not self.formatted_contents:
            self.__format_page_comment(formatter, link_resolver)

    def format(self, formatter, link_resolver, output):
        """
        Banana banana
        """

        if not self.title and self.name:
            title = os.path.splitext(self.name)[0]
            self.title = os.path.basename(title).replace('-', ' ')

        self.formatted_contents = u''

        self.build_path = os.path.join(formatter.get_output_folder(self),
                                       self.link.ref)
        if not self.__redirect_if_needed(formatter, link_resolver):
            self.__format_content(formatter, link_resolver)

        self.output_attrs = defaultdict(lambda: defaultdict(dict))
        formatter.prepare_page_attributes(self)
        self.__format_symbols(formatter, link_resolver)
        self.detailed_description =\
            formatter.format_page(self)[0]

        if output:
            formatter.cache_page(self)

    # pylint: disable=no-self-use
    def get_title(self):
        """
        Banana banana
        """
        return self.title or 'unnamed'

    def __discover_title(self, meta):
        if meta is not None and 'title' in meta:
            self.title = meta['title']
        elif self.ast:
            self.title = cmark.title_from_ast(self.ast)

    def __format_symbols(self, formatter, link_resolver):
        for symbol in self.symbols:
            if symbol is None:
                continue
            debug('Formatting symbol %s in page %s' % (
                symbol.unique_name, self.name), 'formatting')
            symbol.detailed_description = formatter.format_symbol(
                symbol, link_resolver)

    def __query_extra_symbols(self, sym, all_syms, tree, link_resolver,
                              database):
        if sym:
            self.__fetch_comment(sym, database)
            all_syms.add(sym)

    def __resolve_symbol(self, symbol, link_resolver, page_path):
        symbol.resolve_links(link_resolver)

        symbol.link.ref = "%s#%s" % (page_path, symbol.unique_name)

        for link in symbol.get_extra_links():
            link.ref = "%s#%s" % (page_path, link.id_)

        tsl = self.typed_symbols.get(type(symbol))
        if tsl:
            tsl.symbols.append(symbol)

            by_parent_symbols = self.by_parent_symbols.get(symbol.parent_name)
            if not by_parent_symbols:
                by_parent_symbols = self.__get_empty_typed_symbols()
                parent_name = symbol.parent_name
                if parent_name is None:
                    parent_name = 'Others symbols'
                self.by_parent_symbols[symbol.parent_name] = by_parent_symbols
            by_parent_symbols.get(type(symbol)).symbols.append(symbol)

        self.symbols.append(symbol)

        debug('Resolved symbol %s to page %s' %
              (symbol.unique_name, self.link.ref), 'resolution') class InvalidOutputException(HotdocException):
    """Banana banana"""
    pass @staticmethod
def warn(code, message, **kwargs):
    """
    Call this to store a warning in the journal.

    Will raise if `Logger.fatal_warnings` is set to True and
    `Logger.raise_on_fatal_warnings` as well.
    """

    if code in Logger._ignored_codes:
        return

    assert code in Logger._warning_code_to_exception
    exc_type, domain = Logger._warning_code_to_exception[code]

    if domain in Logger._ignored_domains:
        return

    level = WARNING
    if Logger.fatal_warnings:
        level = ERROR

    exc = exc_type(message, **kwargs)

    Logger._log(code, exc.message, level, domain)

    if Logger.fatal_warnings:
        if Logger.raise_on_fatal_warnings:
            raise exc
        else:
            Logger.n_fatal_warnings += 1 class Logger(Configurable):

    """Subclasses can inherit from this class to report recoverable errors."""

    _error_code_to_exception = defaultdict()
    _domain_codes = defaultdict(set)
    _warning_code_to_exception = defaultdict()
    journal = []
    fatal_warnings = False
    raise_on_fatal_warnings = False
    _ignored_codes = set()
    _ignored_domains = set()
    _last_checkpoint = 0
    _verbosity = WARNING
    silent = False
    n_fatal_warnings = 0

    @staticmethod
    def register_error_code(code, exception_type, domain='core'):
        """Register a new error code"""
        Logger._error_code_to_exception[code] = (exception_type, domain)
        Logger._domain_codes[domain].add(code)

    @staticmethod
    def register_warning_code(code, exception_type, domain='core'):
        """Register a new warning code"""
        Logger._warning_code_to_exception[code] = (exception_type, domain)
        Logger._domain_codes[domain].add(code)

    @staticmethod
    def _log(code, message, level, domain):
        """Call this to add an entry in the journal"""
        entry = LogEntry(level, domain, code, message)
        Logger.journal.append(entry)

        if Logger.silent:
            return

        if level >= Logger._verbosity:
            _print_entry(entry)

    @staticmethod
    def error(code, message, **kwargs):
        """Call this to raise an exception and have it stored in the journal"""
        assert code in Logger._error_code_to_exception
        exc_type, domain = Logger._error_code_to_exception[code]
        exc = exc_type(message, **kwargs)
        Logger._log(code, exc.message, ERROR, domain)
        raise exc

    @staticmethod
    def warn(code, message, **kwargs):
        """
        Call this to store a warning in the journal.

        Will raise if `Logger.fatal_warnings` is set to True and
        `Logger.raise_on_fatal_warnings` as well.
        """

        if code in Logger._ignored_codes:
            return

        assert code in Logger._warning_code_to_exception
        exc_type, domain = Logger._warning_code_to_exception[code]

        if domain in Logger._ignored_domains:
            return

        level = WARNING
        if Logger.fatal_warnings:
            level = ERROR

        exc = exc_type(message, **kwargs)

        Logger._log(code, exc.message, level, domain)

        if Logger.fatal_warnings:
            if Logger.raise_on_fatal_warnings:
                raise exc
            else:
                Logger.n_fatal_warnings += 1

    @staticmethod
    def debug(message, domain):
        """Log debugging information"""
        if domain in Logger._ignored_domains:
            return

        Logger._log(None, message, DEBUG, domain)

    @staticmethod
    def info(message, domain):
        """Log simple info"""
        if domain in Logger._ignored_domains:
            return

        Logger._log(None, message, INFO, domain)

    @staticmethod
    def add_ignored_code(code):
        """Add a code to ignore. Errors cannot be ignored."""
        Logger._ignored_codes.add(code)

    @staticmethod
    def add_ignored_domain(code):
        """Add a domain to ignore. Errors cannot be ignored."""
        Logger._ignored_domains.add(code)

    @staticmethod
    def checkpoint():
        """Add a checkpoint"""
        Logger._last_checkpoint = len(Logger.journal)

    @staticmethod
    def since_checkpoint():
        """Get journal since last checkpoint"""
        return Logger.journal[Logger._last_checkpoint:]

    @staticmethod
    def get_issues():
        """Get actual issues in the journal."""
        issues = []
        for entry in Logger.journal:
            if entry.level >= WARNING:
                issues.append(entry)
        return issues

    @staticmethod
    def reset():
        """Resets Logger to its initial state"""
        Logger.journal = []
        Logger.fatal_warnings = False
        Logger.raise_on_fatal_warnings = False
        Logger._ignored_codes = set()
        Logger._ignored_domains = set()
        Logger._verbosity = 2
        Logger._last_checkpoint = 0
        Logger.n_fatal_warnings = 0

    @staticmethod
    def add_arguments(parser):
        """Banana banana
        """
        group = parser.add_argument_group(
            'Logger', 'logging options')
        group.add_argument("--verbose", '-v', action="count",
                           dest="verbose",
                           help="Turn on verbosity, -vv for debug")
        group.add_argument("--fatal-warnings", action="store_true",
                           dest="fatal_warnings", help="Make warnings fatal")

    @staticmethod
    def set_verbosity(verbosity):
        """Banana banana
        """
        Logger._verbosity = min(max(0, WARNING - verbosity), 2)
        debug("Verbosity set to %d" % (WARNING - Logger._verbosity), 'logging')

    @staticmethod
    def parse_config(config):
        Logger._verbosity = max(0, Logger._verbosity - (
            config.get('verbose') or 0))

        Logger.fatal_warnings = bool(config.get("fatal_warnings")) class OrderedSet(MutableSet):
    """
    Banana banana
    """

    def __init__(self, iterable=None):
        self.end = end = []
        end += [None, end, end]         # sentinel node for doubly linked list
        self.map = {}                   # key --> [key, prev, next]
        if iterable is not None:
            self |= iterable

    def __len__(self):
        return len(self.map)

    def __contains__(self, key):
        return key in self.map

    # pylint: disable=arguments-differ
    def add(self, key):
        """
        Banana banana
        """
        if key not in self.map:
            end = self.end
            curr = end[1]
            curr[2] = end[1] = self.map[key] = [key, curr, end]

    def __getstate__(self):
        if not self:
            # The state can't be an empty list.
            # We need to return a truthy value, or else
            # __setstate__ won't be run.
            #
            # This could have been done more gracefully by always putting
            # the state in a tuple, but this way is backwards- and forwards-
            # compatible with previous versions of OrderedSet.
            return (None,)
        return list(self)

    def __setstate__(self, state):
        if state == (None,):
            self.__init__([])
        else:
            self.__init__(state)

    # pylint: disable=arguments-differ
    def discard(self, key):
        """
        Banana banana
        """
        if key in self.map:
            key, prev, nxt = self.map.pop(key)
            prev[2] = nxt
            nxt[1] = prev

    def __iter__(self):
        end = self.end
        curr = end[2]
        while curr is not end:
            yield curr[0]
            curr = curr[2]

    def __reversed__(self):
        end = self.end
        curr = end[1]
        while curr is not end:
            yield curr[0]
            curr = curr[1]

    # pylint: disable=arguments-differ
    def pop(self, last=True):
        """
        Banana banana
        """
        if not self:
            raise KeyError('set is empty')
        key = self.end[1][0] if last else self.end[2][0]
        self.discard(key)
        return key

    def __repr__(self):
        if not self:
            return '%s()' % (self.__class__.__name__,)
        return '%s(%r)' % (self.__class__.__name__, list(self))

    def __eq__(self, other):
        if isinstance(other, OrderedSet):
            return len(self) == len(other) and list(self) == list(other)
        return set(self) == set(other) class GIFormatter(Formatter):
    sitemap_language = None
    engine = None

    def __init__(self, gi_extension):
        Formatter.__init__(self, gi_extension)
        self._order_by_parent = True
        self._symbol_formatters.update(
                {
                    GIClassSymbol: self._format_class_symbol,
                    GIStructSymbol: self._format_class_symbol,
                    GIInterfaceSymbol: self._format_interface_symbol,
                })
        self._ordering.insert(self._ordering.index(ClassSymbol) + 1, GIClassSymbol)
        self._ordering.insert(self._ordering.index(GIClassSymbol) + 1, GIStructSymbol)
        self._ordering.insert(self._ordering.index(InterfaceSymbol) + 1, GIInterfaceSymbol)
        self.__annotation_parser = GIAnnotationParser()

    def format_annotations (self, annotations):
        template = self.engine.get_template('gi_annotations.html')
        return template.render ({'annotations': annotations})

    def __add_attrs(self, symbol, **kwargs):
        if not symbol:
            return
        self.extension.add_attrs(symbol, **kwargs)
        for csym in symbol.get_children_symbols():
            self.__add_attrs(csym, **kwargs)

    def __wrap_in_language(self, symbol, langs_docs):
        template = self.get_template('symbol_language_wrapper.html')
        res = template.render(
                {'symbol': symbol,
                 'languages': langs_docs})
        return res

    def _format_symbol (self, symbol):
        if isinstance(symbol, (QualifiedSymbol, FieldSymbol, EnumMemberSymbol)):
            return Formatter._format_symbol(self, symbol)

        langs_docs = {}
        previous_lang = None
        for lang in self.extension.get_languages():
            self.extension.setup_language(lang, previous_lang)
            lang_name = lang.language_name
            if lang_name == 'c' or is_introspectable(symbol.unique_name, lang):
                self.__add_attrs(symbol, language=lang_name)
                langs_docs[lang_name] = Formatter._format_symbol(self, symbol)
            else:
                langs_docs[lang_name] = None
            previous_lang = lang

        self.extension.setup_language(None, previous_lang)
        return self.__wrap_in_language(symbol, langs_docs)

    def _format_flags (self, flags):
        template = self.engine.get_template('gi_flags.html')
        out = template.render ({'flags': flags})
        return out

    def _format_type_tokens(self, symbol, type_tokens):
        language = symbol.get_extension_attribute(self.extension.extension_name, 'language')
        if language != 'c':
            type_desc = self.extension.get_attr(symbol, 'type_desc')
            assert(type_desc)
            gi_name = type_desc.gi_name
            new_tokens = []
            link = None
            lang = self.extension.get_language(language)
            fund_link = lang.get_fundamental(gi_name)
            if fund_link:
                link = Link(fund_link.ref, fund_link._title, gi_name)
            elif gi_name in ALL_GI_TYPES:
                ctype_name = ALL_GI_TYPES[gi_name]
                link = self.extension.app.link_resolver.get_named_link(ctype_name)

            if type_desc.nesting_depth:
                new_tokens.append('[' * type_desc.nesting_depth + ' ')
            if link:
                new_tokens.append(link)
            else: # Should not happen but let's be conservative
                new_tokens.append(type_desc.gi_name)
            if type_desc.nesting_depth:
                new_tokens.append(']' * type_desc.nesting_depth)

            return Formatter._format_type_tokens (self, symbol, new_tokens)

        return Formatter._format_type_tokens (self, symbol, type_tokens)

    def __add_annotations (self, symbol):
        if self.extension.get_attr(symbol, 'language') == 'c':
            annotations = self.__annotation_parser.make_annotations(symbol)

            # FIXME: OK this is format time but still seems strange
            if annotations:
                extra_content = self.format_annotations (annotations)
                symbol.extension_contents['Annotations'] = extra_content
        else:
            symbol.extension_contents.pop('Annotations', None)

    def _format_return_item_symbol(self, symbol):
        self.__add_annotations(symbol)
        return Formatter._format_return_item_symbol (self, symbol)

    def _format_return_value_symbol (self, *args):
        retval = args[0]
        is_void = retval[0] is None or \
                retval[0].get_extension_attribute('gi-extension',
                        'gi_name') == 'none'

        if not is_void:
            language = retval[0].get_extension_attribute(self.extension.extension_name, 'language')
        else:
            language = 'c'

        if language == 'c':
            if is_void:
                retval = [None]
            else:
                retval = retval[:1]

            for item in retval:
                if item:
                    item.formatted_link = ''

        elif is_void:
            retval = retval[1:] or [None]

        args = list(args)
        args[0] = retval
        return Formatter._format_return_value_symbol (self, *args)

    def _format_parameter_symbol (self, parameter):
        self.__add_annotations(parameter)
        language = parameter.get_extension_attribute(self.extension.extension_name, 'language')
        if language != 'c':
            direction = parameter.get_extension_attribute ('gi-extension',
                    'direction')
            if direction == 'out':
                return None

            is_destroy = parameter.get_extension_attribute ('gi-extension', 'is_destroy')
            if is_destroy:
                return None

            is_closure = parameter.get_extension_attribute ('gi-extension', 'is_closure')

            if is_closure and language == 'python':
                parameter.extension_contents['type-link'] = self._format_link(
                        PYTHON_VARIADIC_LINK, None, 'variadic')
            else:
                parameter.extension_contents['type-link'] = self._format_linked_symbol (parameter)
        else:
            if self.extension.get_attr(parameter, 'action'):
                parameter.extension_contents['type-link'] = self._format_type_tokens (parameter, parameter.type_tokens)
            else:
                parameter.extension_contents.pop('type-link', None)

        res = Formatter._format_parameter_symbol (self, parameter)
        return res

    def _format_linked_symbol (self, symbol):
        if not symbol:
            return Formatter._format_linked_symbol (self, symbol)

        language = symbol.get_extension_attribute(self.extension.extension_name, 'language')
        if language == 'c':
            res = Formatter._format_linked_symbol (self, symbol)
            if symbol == None:
                res = 'void'
            return res

        if not isinstance (symbol, QualifiedSymbol):
            return Formatter._format_linked_symbol (self, symbol)

        type_desc = symbol.get_extension_attribute ('gi-extension', 'type_desc')
        if type_desc:
            return self._format_type_tokens (symbol, symbol.type_tokens)

        return Formatter._format_linked_symbol (self, symbol)

    def _format_prototype (self, function, is_pointer, title):
        language = function.get_extension_attribute(self.extension.extension_name, 'language')
        if language == 'c':
            return Formatter._format_prototype (self, function,
                    is_pointer, title)

        params = function.get_extension_attribute ('gi-extension', 'parameters')

        if params is None:
            return Formatter._format_prototype (self, function,
                    is_pointer, title)

        if type (function) == ActionSignalSymbol:
            template = self.engine.get_template(language + '_action_prototype.html')
            res = template.render ({
                'return_value': function.return_value,
                'name': title,
                'parameters': params})
        else:
            c_name = function.make_name()
            template = self.engine.get_template(language + '_prototype.html')

            if type (function) == SignalSymbol:
                comment = "%s callback for the '%s' signal" % (language, c_name)
            elif type (function) == VFunctionSymbol:
                comment = "%s implementation of the '%s' virtual method" % \
                        (language, c_name)
            else:
                comment = "%s wrapper for '%s'" % (language, c_name)

            res = template.render ({'return_value': function.return_value,
                'parent_name': function.parent_name, 'function_name': title, 'parameters':
                params, 'comment': comment, 'throws': function.throws,
                'out_params': [], 'is_method': isinstance(function, MethodSymbol)})

        return res

    def _format_members_list (self, members, member_designation, struct):
        language = struct.get_extension_attribute(self.extension.extension_name, 'language')
        if language != 'c':
            # Never render members that are in a union, introspected won't show them
            members = [m for m in members if not m.get_extension_attribute(
                self.extension.extension_name, 'in_union')]

        return super()._format_members_list (members, member_designation, struct)

    def _format_struct (self, struct):
        language = struct.get_extension_attribute(self.extension.extension_name, 'language')
        if language == 'c':
            return Formatter._format_struct (self, struct)

        members_list = self._format_members_list (struct.members, 'Attributes', struct)

        template = self.engine.get_template ("python_compound.html")
        out = template.render ({"symbol": struct,
                                "members_list": members_list})
        return out

    def _format_class_symbol (self, klass):
        saved_raw_text = klass.raw_text
        if klass.get_extension_attribute(self.extension.extension_name, 'language') != 'c':
            klass.raw_text = None
        out = Formatter._format_class_symbol(self, klass)

        if klass.get_extension_attribute(self.extension.extension_name, 'language') == 'c':
            # Render class structure if available.
            if klass.class_struct_symbol:
                out += '<h3>Class structure</h3>'
                out += self._format_symbol(klass.class_struct_symbol)

        klass.raw_text = saved_raw_text
        return out

    def _format_constant(self, constant):
        language = constant.get_extension_attribute(self.extension.extension_name, 'language')
        if language == 'c':
            return Formatter._format_constant (self, constant)

        template = self.engine.get_template('constant.html')
        out = template.render ({'symbol': constant,
                                'definition': None,
                                'constant': constant})
        return out

    def _format_comment(self, comment, link_resolver):
        ast = comment.extension_attrs['gi-extension']['ast']

        if not comment.description:
            out = u''
        elif ast:
            out = self._docstring_formatter.ast_to_html(
                ast, link_resolver)
        else:
            ast = self._docstring_formatter.comment_to_ast(
                comment, link_resolver, self.extension.project.tree)
            out = self._docstring_formatter.ast_to_html(
                ast, link_resolver)
            comment.extension_attrs['gi-extension']['ast'] = ast

        return out

    def _format_callable(self, callable_, callable_type, title,
                         is_pointer=False):
        language = callable_.get_extension_attribute(self.extension.extension_name, 'language')
        if language == 'python' and isinstance(callable_, ClassMethodSymbol):
            return None

        return super()._format_callable(callable_, callable_type, title, is_pointer)

    def _format_property_prototype(self, prop, title, type_link):
        language = prop.get_extension_attribute(self.extension.extension_name, 'language')
        if language == 'python':
            title = 'self.props.%s' % title
        return Formatter._format_property_prototype (self, prop, title, type_link)

    def _format_alias(self, alias):
        language = alias.get_extension_attribute(self.extension.extension_name, 'language')
        if language == 'c':
            return super()._format_alias(alias)

        return None

    def get_template(self, name):
        return GIFormatter.engine.get_template(name)

    def parse_toplevel_config(self, config):
        super().parse_toplevel_config(config)
        if GIFormatter.engine is None:
            module_path = os.path.dirname(__file__)
            searchpath = [os.path.join(module_path, "html_templates")] + Formatter.engine.loader.searchpath
            GIFormatter.engine = Engine(
                    loader=FileLoader(searchpath, encoding='UTF-8'),
                    extensions=[CoreExtension(), CodeExtension()])
            GIFormatter.engine.global_vars.update({'e': html.escape}) class GtkDocParser:
    """
    Banana banana
    """

    def __init__(self, project, section_file_matching=True):
        """
        Lifted from
        http://stackoverflow.com/questions/5323703/regex-how-to-match-sequence-of-key-value-pairs-at-end-of-string
        """
        self.kv_regex = re.compile(r'''
                                   [\S]+=
                                   (?:
                                   \s*
                                   (?!\S+=)\S+
                                   )+
                                   ''', re.VERBOSE)
        self.project = project

        tag_validation_regex = r'((?:^|\n)[ \t]*('
        tag_validation_regex += 'returns|Returns|since|Since|deprecated'\
            '|Deprecated|stability|Stability|Return value|topic|Topic'
        for validator in list(project.tag_validators.values()):
            tag_validation_regex += '|%s|%s' % (validator.name,
                                                validator.name.lower())
        tag_validation_regex += '):)'

        self.tag_validation_regex = re.compile(tag_validation_regex)
        self.__section_file_matching = section_file_matching

    def __parse_title(self, source_filename, raw_title):
        if raw_title.startswith('SECTION'):
            section_name = raw_title.split('SECTION:')[1].strip()
            return section_name, [], True

        split = raw_title.split(': ', 1)

        title = split[0].rstrip(':')
        if not len(split) > 1 and not title.endswith(':') and " " in title:
            raise HotdocSourceException(
                message='Unexpected title in gtk-doc comment')

        annotations = []

        if len(split) > 1:
            annotations = self.__parse_annotations(split[1])
        return title, annotations, False

    def __parse_key_value_annotation(self, name, string):
        kvs = self.kv_regex.findall(string)
        kvs = {kv.split('=', 1)[0]: kv.split('=', 1)[1] for kv in kvs}
        return Annotation(name=name, argument=kvs)

    def __parse_annotation(self, string):
        split = string.split()
        name = split[0].strip()
        if len(split) == 1:
            return Annotation(name=name)
        if '=' in split[1]:
            return self.__parse_key_value_annotation(name, split[1])
        return Annotation(name=name, argument=[split[1]])

    def __parse_annotations(self, string):
        parsed_annotations = []
        par_level = 0
        current_annotation = ""
        for _ in string:
            if _ == '(':
                par_level += 1
            elif _ == ')':
                par_level -= 1

            if par_level > 1:
                return []
            if par_level == 1 and _ not in '()':
                current_annotation += _
            elif par_level == 0:
                if _ not in ' \t\n\r():':
                    return []
                if current_annotation:
                    ann = self.__parse_annotation(current_annotation)
                    if ann:
                        parsed_annotations.append(ann)
                    current_annotation = ""
            elif par_level < 0:
                return []

        if par_level != 0:
            return []

        return parsed_annotations

    def __extract_annotations(self, desc):
        split = desc.split(': ', 1)

        if len(split) == 1:
            return desc, []

        annotations = self.__parse_annotations(split[0])
        if not annotations:
            return desc, []

        return split[1].strip(), annotations

    def __parse_parameter(self, name, desc):
        name = name.strip()[1:-1].strip()
        raw_comment = '%s:%s' % (name, desc)
        desc = desc.strip()
        desc, annotations = self.__extract_annotations(desc)
        annotations = {annotation.name: annotation for annotation in
                       annotations}
        return Comment(name=name, annotations=annotations,
                       meta={'description': desc}, raw_comment=raw_comment)

    def __parse_title_and_parameters(self, filename, title_and_params):
        tps = re.split(r'(\n[ \t]*@[\S]+[ \t]*:)', title_and_params)
        title, annotations, is_section = self.__parse_title(filename, tps[0])
        parameters = []
        for name, desc in _grouper(tps[1:], 2):
            n_chars = len(name.strip()) + len(desc)
            param = self.__parse_parameter(name, desc)
            param.line_offset = len(desc.split('\n'))
            parameters.append(param)
            param.initial_col_offset = n_chars - len(param.description)
        return title, parameters, annotations, is_section

    # pylint: disable=no-self-use
    def __parse_since_tag(self, name, desc):
        return Tag(name, desc, value=desc)

    def __parse_topic_tag(self, name, desc):
        return Tag(name, None, value=desc)

    # pylint: disable=no-self-use
    def __parse_deprecated_tag(self, name, desc):
        split = desc.split(':', 1)
        if len(split) == 2 and len(split[0]) > 1:
            value = split[0]
            if ' ' in value:
                value = None
        else:
            value = None

        return Tag(name, desc, value=value)

    # pylint: disable=no-self-use
    def __parse_stability_tag(self, name, desc):
        value = desc.strip().lower()
        if value not in ('private', 'stable', 'unstable'):
            # FIXME warn
            return None
        return Tag(name, desc, value=value)

    # pylint: disable=no-self-use
    def __parse_returns_tag(self, name, desc):
        desc, annotations = self.__extract_annotations(desc)
        annotations = {annotation.name: annotation for annotation in
                       annotations}
        return Tag(name, desc, annotations=annotations)

    # pylint: disable=too-many-return-statements
    def __parse_tag(self, name, desc):
        if name.lower() == "since":
            return self.__parse_since_tag(name, desc)
        if name.lower() == "returns":
            return self.__parse_returns_tag(name, desc)
        if name.lower() == "return value":
            return self.__parse_returns_tag("returns", desc)
        if name.lower() == "stability":
            return self.__parse_stability_tag("stability", desc)
        if name.lower() == "deprecated":
            return self.__parse_deprecated_tag("deprecated", desc)
        if name.lower() == "topic":
            return self.__parse_topic_tag("topic", desc)

        validator = self.project.tag_validators.get(name)
        if not validator:
            warn('gtk-doc', "FIXME no tag validator")
            return None
        if not validator.validate(desc):
            warn('gtk-doc', "invalid value for tag %s : %s" % (name, desc))
            return None
        return Tag(name=name, description=desc, value=desc)

    def __parse_description_and_tags(self, desc_and_tags):
        dts = self.tag_validation_regex.split(desc_and_tags)
        tags = []

        desc = dts[0]
        if len(dts) == 1:
            return desc, tags

        # pylint: disable=unused-variable
        for raw, name, tag_desc in _grouper(dts[1:], 3):
            tag = self.__parse_tag(name.strip(), tag_desc.strip())
            if tag:
                tags.append(tag)
            else:
                desc += '\n%s: %s' % (name, tag_desc)

        return desc, tags

    def __strip_comment(self, comment):
        n_lines = len(comment.split('\n'))
        comment = re.sub(r'^[\W]*\/[\*]+[\W]*', '', comment)
        title_offset = n_lines - len(comment.split('\n'))
        comment = re.sub(r'\*\/[\W]*$', '', comment)
        comment = re.sub(r'\n[ \t]*\*', '\n', comment)
        return comment.strip(), title_offset

    def __validate_c_comment(self, comment):
        return re.match(r'(/\*\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/)$',
                        comment) is not None

    def __parse_yaml_comment(self, comment, filename):
        res = {}
        try:
            blocks = yaml.load_all(comment.raw_comment, Loader=yaml.SafeLoader)
            for block in blocks:
                if block:
                    res.update(block)
        except (ConstructorError, yaml.parser.ParserError) as exception:
            warn('invalid-page-metadata',
                 '%s: Invalid metadata: \n%s' % (filename, str(exception)))
        return res

    def __extract_titles_params_and_description(self, comment):
        titleandparams_description = re.split(r'\n[\s]*\n', comment, maxsplit=1)
        title_and_params = titleandparams_description[0]

        title_and_params_lines = title_and_params.split('\n')
        if len(title_and_params_lines) > 1 and not title_and_params_lines[1].strip().startswith('@'):
            return title_and_params_lines[0].rstrip(':'), '\n'.join([l.strip() for l in title_and_params_lines[1:]])

        if len(titleandparams_description) > 1:
            description = titleandparams_description[1]
        else:
            description = None

        return title_and_params, description

    # pylint: disable=too-many-arguments
    # pylint: disable=too-many-locals
    # pylint: disable=unused-argument
    # pylint: disable=too-many-branches
    # pylint: disable=too-many-statements
    def parse_comment(self, comment, filename, lineno, endlineno,
                      include_paths=None, stripped=False):
        """
        Returns a Comment given a string
        """
        if not stripped and not self.__validate_c_comment(comment.strip()):
            return None

        title_offset = 0

        column_offset = 0

        raw_comment = comment
        if not stripped:
            try:
                while comment[column_offset * -1 - 1] != '\n':
                    column_offset += 1
            except IndexError:
                column_offset = 0
            comment, title_offset = self.__strip_comment(comment)

        title_and_params, description = self.__extract_titles_params_and_description(comment)
        try:
            block_name, parameters, annotations, is_section = \
                self.__parse_title_and_parameters(filename, title_and_params)
        except HotdocSourceException as _:
            warn('gtk-doc-bad-syntax',
                 message=_.message,
                 filename=filename,
                 lineno=lineno + title_offset)
            return None

        params_offset = 0
        for param in parameters:
            param.filename = filename
            param.lineno = lineno
            param_offset = param.line_offset
            param.line_offset = title_offset + params_offset + 1
            params_offset += param_offset
            param.col_offset = column_offset

        if not block_name:
            return None

        description_offset = 0
        meta = {}
        tags = []
        if description is not None:
            n_lines = len(comment.split('\n'))
            description_offset = (title_offset + n_lines -
                                  len(description.split('\n')))
            meta['description'], tags = self.__parse_description_and_tags(description)

        actual_parameters = OrderedDict({})
        for param in parameters:
            if is_section:
                cleaned_up_name = param.name.lower().replace('_', '-')
                if cleaned_up_name in ['symbols', 'private-symbols', 'auto-sort', 'sources']:
                    meta.update(self.__parse_yaml_comment(param, filename))
                    if cleaned_up_name == 'sources':
                        sources_paths = [os.path.abspath(os.path.join(os.path.dirname(filename), path)) for path in meta[cleaned_up_name]]
                        meta[cleaned_up_name] = sources_paths
                else:
                    meta[param.name] = param.description
            else:
                actual_parameters[param.name] = param

        annotations = {annotation.name: annotation for annotation in
                       annotations}
        tags = {tag.name.lower(): tag for tag in tags}

        block = Comment(name=block_name, filename=filename, lineno=lineno,
                        endlineno=endlineno,
                        annotations=annotations, params=actual_parameters,
                        tags=tags, raw_comment=raw_comment,
                        meta=meta, toplevel=is_section)
        block.line_offset = description_offset
        block.col_offset = column_offset

        return block GTKDOC_HREFS = {} def gather_links():
    global GATHERED_GTKDOC_LINKS

    if GATHERED_GTKDOC_LINKS:
        return

    GATHERED_GTKDOC_LINKS = True

    # XDG_DATA_DIRS is preference-ordered, we reverse so that preferred
    # links override less-preferred ones
    for datadir in reversed([XDG_DATA_HOME] + XDG_DATA_DIRS):
        for path in (os.path.join(datadir, 'devhelp', 'books'), os.path.join(datadir, 'gtk-doc', 'html')):
            if not os.path.exists(path):
                info("no gtk doc to gather links from in %s" % path)
                continue

            for node in os.listdir(path):
                dir_ = os.path.join(path, node)
                if os.path.isdir(dir_):
                    if not parse_devhelp_index(dir_):
                        try:
                            parse_sgml_index(dir_)
                        except IOError:
                            pass def search_online_links(resolver, name):
    href = GTKDOC_HREFS.get(name)
    if href:
        return Link(href, name, name)
    return None SMART_FILTERS = set() def get_klass_parents(gi_name):
    '''
    Returns a sorted list of qualified symbols representing
    the parents of the klass-like symbol named gi_name
    '''
    res = []
    parents = list(__HIERARCHY_GRAPH.predecessors(gi_name))
    if not parents:
        return []
    __get_parent_link_recurse(parents[0], res)
    return res def get_klass_children(gi_name):
    '''
    Returns a dict of qualified symbols representing
    the children of the klass-like symbol named gi_name
    '''
    res = {}
    children = __HIERARCHY_GRAPH.successors(gi_name)
    for gi_name in children:
        ctype_name = ALL_GI_TYPES[gi_name]
        qs = QualifiedSymbol(type_tokens=[Link(None, ctype_name, ctype_name)])
        qs.add_extension_attribute ('gi-extension', 'type_desc',
                SymbolTypeDesc([], gi_name, ctype_name, 0))
        res[ctype_name] = qs
    return res def cache_nodes(gir_root, all_girs, languages):
    '''
    Identify and store all the gir symbols the symbols we will document
    may link to, or be typed with
    '''
    ns_node = gir_root.find('./{%s}namespace' % NS_MAP['core'])
    id_prefixes = ns_node.attrib['{%s}identifier-prefixes' % NS_MAP['c']]
    sym_prefixes = ns_node.attrib['{%s}symbol-prefixes' % NS_MAP['c']].split(',')

    id_key = '{%s}identifier' % NS_MAP['c']
    for node in gir_root.xpath(
            './/*[@c:identifier]',
            namespaces=NS_MAP):
        for language in languages:
            language.make_translations (node.attrib[id_key], node)

    id_type = c_ns('type')
    glib_type = glib_ns('type-name')
    class_tag = core_ns('class')
    callback_tag = core_ns('callback')
    interface_tag = core_ns('interface')
    enum_tag = core_ns('enumeration')
    bitfield_tag = core_ns('bitfield')
    record_tag = core_ns('record')
    for node in gir_root.xpath('.//*[not(self::core:type) and not (self::core:array)][@c:type or @glib:type-name]',
            namespaces=NS_MAP):
        try:
            name = node.attrib[id_type]
        except KeyError:
            name = node.attrib[glib_type]
        for language in languages:
            language.make_translations (name, node)
        gi_name = '.'.join(get_gi_name_components(node))
        ALL_GI_TYPES[gi_name] = get_klass_name(node)
        if node.tag in (class_tag, interface_tag):
            __update_hierarchies (ns_node.attrib.get('name'), node, gi_name)
            for language in languages:
                language.make_translations('%s::%s' % (name, name), node)
            __generate_smart_filters(id_prefixes, sym_prefixes, node)
        elif node.tag in (enum_tag, bitfield_tag, record_tag):
            __generate_smart_filters(id_prefixes, sym_prefixes, node)
        elif node.tag in (callback_tag,):
            ALL_CALLBACK_TYPES.add(node.attrib[c_ns('type')])

    for field in gir_root.xpath('.//self::core:field', namespaces=NS_MAP):
        unique_name = get_field_c_name(field)
        for language in languages:
            language.make_translations(unique_name, field)

    for node in gir_root.xpath(
            './/core:property',
            namespaces=NS_MAP):
        name = '%s:%s' % (get_klass_name(node.getparent()),
                          node.attrib['name'])
        for language in languages:
            language.make_translations (name, node)

    for node in gir_root.xpath(
            './/glib:signal',
            namespaces=NS_MAP):
        name = '%s::%s' % (get_klass_name(node.getparent()),
                           node.attrib['name'])
        for language in languages:
            language.make_translations (name, node)

    for node in gir_root.xpath(
            './/core:virtual-method',
            namespaces=NS_MAP):
        name = get_symbol_names(node)[0]
        for language in languages:
            language.make_translations (name, node)

    for inc in gir_root.findall('./core:include',
            namespaces = NS_MAP):
        inc_name = inc.attrib["name"]
        inc_version = inc.attrib["version"]
        gir_file = __find_gir_file('%s-%s.gir' % (inc_name, inc_version), all_girs)
        if not gir_file:
            warn('missing-gir-include', "Couldn't find a gir for %s-%s.gir" %
                    (inc_name, inc_version))
            continue

        if gir_file in __PARSED_GIRS:
            continue

        __PARSED_GIRS.add(gir_file)
        inc_gir_root = etree.parse(gir_file).getroot()
        cache_nodes(inc_gir_root, all_girs, languages) def type_description_from_node(gi_node):
    '''
    Parse a typed node, returns a usable description
    '''
    ctype_name, gi_name, array_nesting = unnest_type (gi_node)

    cur_ns = get_namespace(gi_node)

    if ctype_name is not None:
        type_tokens = __type_tokens_from_cdecl (ctype_name)
    else:
        type_tokens = __type_tokens_from_gitype (cur_ns, gi_name)

    namespaced = '%s.%s' % (cur_ns, gi_name)
    if namespaced in ALL_GI_TYPES:
        gi_name = namespaced

    return SymbolTypeDesc(type_tokens, gi_name, ctype_name, array_nesting) def is_introspectable(name, language):
    '''
    Do not call this before caching the nodes
    '''
    if language.get_fundamental(name):
        return True

    if not language.get_translation(name):
        return False

    return True def is_callback_type(c_name):
    return c_name in ALL_CALLBACK_TYPES class GIClassSymbol(ClassSymbol):
    def __init__(self, **kwargs):
        self.class_struct_symbol = None
        self.interfaces = []
        self.properties = []
        self.methods = []
        self.signals = []
        self.vfuncs = []
        ClassSymbol.__init__(self, **kwargs)

    def get_children_symbols(self):
        res = self.interfaces + self.properties + self.methods + \
            self.signals + self.vfuncs + super().get_children_symbols()

        if self.class_struct_symbol:
            res += [self.class_struct_symbol] + self.class_struct_symbol.get_children_symbols()

        return res class GIInterfaceSymbol(InterfaceSymbol):
    def __init__(self, **kwargs):
        self.class_struct_symbol = None
        self.properties = []
        self.methods = []
        self.signals = []
        self.vfuncs = []
        InterfaceSymbol.__init__(self, **kwargs)

    def get_children_symbols(self):
        res = self.properties + self.methods + self.signals + self.vfuncs + super().get_children_symbols()
        if self.class_struct_symbol:
            res += [self.class_struct_symbol] + self.class_struct_symbol.get_children_symbols()

        return res class GIStructSymbol(ClassSymbol):
    """Boxed types are pretty much handled like classes with a possible
       constructors, methods, etc... we render them as such.
    """
    def __init__(self, **kwargs):
        self.class_struct_symbol = None
        self.methods = []
        ClassSymbol.__init__(self, **kwargs)

    def get_children_symbols(self):
        return [self.class_struct_symbol] + self.methods + super().get_children_symbols() TYPE_MAP = {
    FunctionSymbol: 'function',
    ClassSymbol: 'class',
    StructSymbol: 'struct',
    EnumSymbol: 'enum',
    EnumMemberSymbol: 'member',
    PropertySymbol: 'property',
    SignalSymbol: 'signal',
    ActionSignalSymbol: 'signal',
    ConstantSymbol: 'macro',
    FunctionMacroSymbol: 'macro',
    CallbackSymbol: 'function',
    InterfaceSymbol: 'interface',
    AliasSymbol: 'alias',
    VFunctionSymbol: 'vfunc',
    ExportedVariableSymbol: 'extern',
    FieldSymbol: 'field',
    MethodSymbol: 'function',
    ConstructorSymbol: 'function',
}
import os
from lxml import etree
from collections import defaultdict
from collections import OrderedDict
from hotdoc.core.symbols import *
from hotdoc.core.extension import Extension, ExtDependency
from hotdoc.core.links import Link, LinkResolver
from hotdoc.core.tree import Page
from hotdoc.core.comment import Comment
from hotdoc.core.exceptions import InvalidOutputException
from hotdoc.utils.loggable import warn, Logger
from hotdoc.utils.utils import OrderedSet
from hotdoc.extensions.gi.formatter import GIFormatter
from hotdoc.parsers.gtk_doc import GtkDocParser, GTKDOC_HREFS, gather_links, search_online_links
from hotdoc.extensions.c.utils import CCommentExtractor
from hotdoc.extensions.gi.flags import *
from hotdoc.extensions.gi.utils import *
from hotdoc.extensions.gi.node_cache import (
    SMART_FILTERS, get_klass_parents,
    get_klass_children, cache_nodes, type_description_from_node,
    is_introspectable, is_callback_type)
from hotdoc.extensions.gi.symbols import GIClassSymbol, GIInterfaceSymbol, GIStructSymbol
from hotdoc.extensions.devhelp.devhelp_extension import TYPE_MAP
            children = field.getchildren()
            if not children:
                continue

            if field.attrib.get('private', False):
                continue

            if children[0].tag == core_ns('callback'):
                continue

            type_desc = type_description_from_node(field)

            field_name = field.attrib['name']
            name = "%s.%s" % (struct_name, field_name)

            qtype = QualifiedSymbol(type_tokens=type_desc.type_tokens)
            self.add_attrs(qtype, type_desc=type_desc)

            member = self.create_symbol(
                FieldSymbol,
                member_name=field_name, qtype=qtype,
                filename=filename, display_name=name,
                unique_name=name, parent_name=parent_name)

            if member:
                self.add_attrs(member, type_desc=type_desc, in_union=in_union)
                members.append(member)

        return members

    def __find_structure_pagename(self, node, unique_name, is_class):
        filename = self.__get_symbol_filename(unique_name, node)
        if filename != self.__default_page:
            return filename

        if self.__symbol_is_relocated(unique_name, None):
            return self.__default_page

        if not is_class:
            sym = self.__class_gtype_structs.get(node.attrib['name'])
            if sym and sym.filename:
                return sym.filename

        filenames = []
        for cnode in node:
            cunique_name = get_symbol_names(cnode)[0]
            if not cunique_name:
                continue
            fname = self.__get_symbol_filename(cunique_name, cnode)
            if fname != self.__default_page:
                if cnode.tag == core_ns('constructor'):
                    filenames.insert(0, fname)
                else:
                    filenames.append(fname)

        unique_filenames = list(OrderedSet(filenames))
        if not filenames:
            # Did not find any symbols, trying to can get information
            # about the class structure linked to that object class.
            nextnode = node.getnext()
            name = node.attrib['name']
            if nextnode is not None and nextnode.tag == core_ns('record'):
                nextnode_classfor = nextnode.attrib.get(glib_ns(
                    'is-gtype-struct-for'))
                if nextnode_classfor == name:
                    nunique_name = get_symbol_names(nextnode)[0]
                    filename = self.__get_symbol_filename(nunique_name)

            if filename == self.__default_page:
                self.warn("no-location-indication",
                          "No way to determine where %s should land"
                          " putting it to %s."
                          " Document the symbol for smart indexing to work" % (
                              unique_name, os.path.basename(filename)))
        else:
            filename = unique_filenames[0]
            if len(unique_filenames) > 1:
                self.warn("no-location-indication",
                          " Going wild here to determine where %s needs to land"
                          " as we could detect the following possibilities: %s."
                          % (unique_name, unique_filenames))
            else:
                self.debug(" No class comment for %s determined that it should"
                           " land into %s with all other class related documentation."
                           % (unique_name, os.path.basename(filename)))

        return filename

    def __sort_parameters(self, symbol, retval, parameters):
        in_parameters = []
        out_parameters = []

        for i, param in enumerate(parameters):
            if isinstance(symbol, MethodSymbol) and i == 0:
                continue

            direction = self.get_attr(param, 'direction')

            if direction == 'in' or direction == 'inout':
                in_parameters.append(param)
            if direction == 'out' or direction == 'inout':
                out_parameters.append(param)

        self.add_attrs(symbol, parameters=in_parameters)

    def __create_parameter_symbol(self, gi_parameter):
        param_name = gi_parameter.attrib['name']

        type_desc = type_description_from_node(gi_parameter)
        direction = gi_parameter.attrib.get('direction')
        if direction is None:
            direction = 'in'

        res = ParameterSymbol(argname=param_name,
                              type_tokens=type_desc.type_tokens)
        self.add_attrs(res, type_desc=type_desc, direction=direction)

        return res, direction

    def __create_return_value_symbol(self, gi_retval, out_parameters):

        type_desc = type_description_from_node(gi_retval)