def id_from_text(text, add_hash=False):
    """
    Banana banana
    """
    id_ = text.strip().lower()

    # No unicode in urls
    id_ = id_.encode('ascii', errors='ignore').decode()

    id_ = re.sub(r"[^\w\s]", '', id_)
    id_ = re.sub(r"\s+", '-', id_)
    if add_hash:
        return '#%s' % id_
    return id_ def find_file(filename, include_paths):
    """Banana banana
    """
    if os.path.isabs(filename):
        if os.path.exists(filename):
            return (filename, None)
        return (None, None)

    for include_path in include_paths:
        fpath = os.path.join(include_path, filename)
        if os.path.exists(fpath) and os.path.isfile(fpath):
            return (fpath, include_path)

    return (None, None) def resolve(uri, include_paths):
    """
    Banana banana
    """
    include_filename, line_ranges, symbol = __parse_include(uri)
    if include_filename is None:
        return None
    (fpath, include_path) = find_file(include_filename, include_paths)
    if fpath is None:
        return None
    return __get_content(fpath.strip(),
                         line_ranges, symbol) class Symbol:
    """
    The base class for all symbols, there should be no reason for
    instantiating it directly.
    """
    __tablename__ = 'symbols'
    standalone = True

    def __init__(self):
        self.extension_contents = {}
        self.extension_attributes = {}
        self.skip = False

        self.extra = {}
        self.comment = None
        self.unique_name = None
        self.display_name = None
        self.filename = None
        self.lineno = -1
        self.extent_start = -1
        self.extent_end = -1
        self.link = None
        self.project_name = None
        self.parent_name = None
        self.aliases = []

    def __repr__(self):
        return "%s(unique_name=%s, filename=%s, project=%s)" % (
            type(self).__name__, self.unique_name, self.filename,
            self.project_name)

    @classmethod
    def get_plural_name(cls):
        """Default implementation of the vmethod
        to retrieve Plurial form of the symbol name."""
        return cls.__tablename__.replace("_", " ").title()

    # FIXME: this is a bit awkward to use.
    def add_extension_attribute(self, ext_name, key, value):
        """
        Banana banana
        """
        attributes = self.extension_attributes.pop(ext_name, {})
        attributes[key] = value
        self.extension_attributes[ext_name] = attributes

    def get_extension_attribute(self, ext_name, key):
        """
        Banana banana
        """
        attributes = self.extension_attributes.get(ext_name)
        if not attributes:
            return None
        return attributes.get(key)

    # pylint: disable=no-self-use
    def get_children_symbols(self):
        """
        Banana banana
        """
        return []

    # pylint: disable=unidiomatic-typecheck
    # pylint: disable=no-member
    def update_children_comments(self):
        """
        Banana banana
        """
        if self.comment is None:
            return

        for sym in self.get_children_symbols():
            if isinstance(sym, ParameterSymbol):
                sym.comment = self.comment.params.get(sym.argname)
            elif isinstance(sym, FieldSymbol):
                param_comment = self.comment.params.get(sym.member_name)
                if param_comment and (not sym.comment or not sym.comment.description):
                    sym.comment = param_comment
            elif isinstance(sym, EnumMemberSymbol):
                if not sym.comment or not sym.comment.description:
                    sym.comment = self.comment.params.get(sym.unique_name)
            elif isinstance(sym, ReturnItemSymbol):
                tag = self.comment.tags.get('returns')
                sym.comment = comment_from_tag(tag)
            elif type(sym) == Symbol:
                sym.comment = self.comment.params.get(sym.display_name)

            if isinstance(sym, Symbol):
                sym.update_children_comments()

    def make_name(self):
        return self.display_name or self.unique_name

    def get_extra_links(self):
        """
        Banana banana
        """
        return []

    def get_type_name(self):
        """
        Banana banana
        """
        return ''

    def resolve_links(self, link_resolver):
        """
        Banana banana
        """
        if self.link is None:
            self.link = Link(self.unique_name, self.make_name(),
                             self.unique_name)

        self.link = link_resolver.upsert_link(self.link, overwrite_ref=True)

        for sym in self.get_children_symbols():
            if sym:
                sym.resolve_links(link_resolver) class StructSymbol(Symbol):
    """
    Banana banana
    """
    __tablename__ = 'structures'

    def __init__(self, **kwargs):
        self.members = {}
        self.anonymous = False
        self.members = []
        self.raw_text = None
        Symbol.__init__(self, **kwargs)

    def get_children_symbols(self):
        return self.members + super().get_children_symbols()

    def get_extra_links(self):
        return [m.link for m in self.members]

    def get_type_name(self):
        return "Structure" class ClassSymbol(StructSymbol):
    """
    Banana banana
    """
    __tablename__ = 'classes'

    def __init__(self, **kwargs):
        self.hierarchy = []
        self.children = {}
        StructSymbol.__init__(self, **kwargs)

    def get_type_name(self):
        return "Class"

    def get_children_symbols(self):
        return self.hierarchy + list(
            self.children.values()) + super().get_children_symbols() class InterfaceSymbol(ClassSymbol):
    """
    Banana banana
    """
    __tablename__ = 'interfaces'

    def __init__(self, **kwargs):
        self.prerequisites = []
        ClassSymbol.__init__(self, **kwargs)

    def get_type_name(self):
        return "Interface"

    def get_children_symbols(self):
        return self.prerequisites + super().get_children_symbols() class AliasSymbol(Symbol):
    """
    Banana banana
    """
    __tablename__ = 'aliases'

    def __init__(self, **kwargs):
        self.aliased_type = None
        Symbol.__init__(self, **kwargs)

    def get_type_name(self):
        return "Alias"

    def get_children_symbols(self):
        return [self.aliased_type] + super().get_children_symbols() 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 HotdocSourceException(HotdocException):
    """Banana banana"""

    def __init__(self, message=None, filename=None, lineno=-1, column=-1):
        self.filename = filename
        self.lineno = lineno
        self.column = column
        if filename:
            message = _format_source_exception(filename, message,
                                               lineno, column)
        super(HotdocSourceException, self).__init__(message) class InvalidPageMetadata(HotdocException):
    """Invalid page metadata""" 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) def all_subclasses(cls):
    """
    Banana banana
    """
    return cls.__subclasses__() + [g for s in cls.__subclasses__()
                                   for g in all_subclasses(s)] class Signal(object):
    """
    The Signalling class
    """

    def __init__(self, optimized=False):
        self._functions = OrderedSet()
        self._after_functions = OrderedSet()
        self._optimized = optimized

    def __call__(self, *args, **kargs):
        res_list = []
        # Call handler functions
        for func in self._functions:
            res = func(*args, **kargs)
            if res and self._optimized:
                return res
            res_list.append(res)

        for func in self._after_functions:
            res = func(*args, **kargs)
            if res and self._optimized:
                return res
            res_list.append(res)

        if self._optimized:
            return None

        return res_list

    def connect(self, slot, *extra_args):
        """
        @slot: The method to be called on signal emission

        Connects to @slot
        """
        slot = Slot(slot, *extra_args)
        self._functions.add(slot)

    def connect_after(self, slot, *extra_args):
        """
        @slot: The method to be called at last stage of signal emission

        Connects to the signal after the signals has been handled by other
        connect callbacks.
        """
        slot = Slot(slot, *extra_args)
        self._after_functions.add(slot)

    def disconnect(self, slot, *extra_args):
        """
        Disconnect @slot from the signal
        """
        slot = Slot(slot, *extra_args)
        if slot in self._functions:
            self._functions.remove(slot)
        elif slot in self._after_functions:
            self._after_functions.remove(slot)

    def clear(self):
        """
        Cleanup the signal
        """
        self._functions.clear()
        self._after_functions.clear() @staticmethod
def info(message, domain):
    """Log simple info"""
    if domain in Logger._ignored_domains:
        return

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

    Logger._log(None, message, DEBUG, domain) @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 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 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"))
import io
import re
import os
import pathlib
import yaml
from urllib.parse import urlparse
from collections import namedtuple, defaultdict, OrderedDict
from yaml.constructor import ConstructorError
from schema import Schema, SchemaError, Optional, And
from hotdoc.utils.utils import id_from_text
from hotdoc.core.inclusions import find_file, resolve
from hotdoc.core.symbols import Symbol, StructSymbol, ClassSymbol,\
    InterfaceSymbol, AliasSymbol
from hotdoc.core.links import Link
from hotdoc.core.exceptions import HotdocSourceException, InvalidPageMetadata
from hotdoc.core.comment import Comment
from hotdoc.parsers import cmark
from hotdoc.utils.utils import OrderedSet, all_subclasses
from hotdoc.utils.signals import Signal
from hotdoc.utils.loggable import info, debug, warn, error, Logger

    # pylint: disable=no-self-use
    def __setup_folder(self, folder):
        if not os.path.exists(folder):
            os.makedirs(folder)

    def __get_link_cb(self, link_resolver, name):
        url_components = urlparse(name)

        page = self.__all_pages.get(url_components.path)
        if not page:
            return None

        ext = self.__extensions[page.extension_name]
        formatter = ext.formatter
        prefix = formatter.get_output_folder(page)
        ref = page.link.ref
        if url_components.fragment:
            ref += '#%s' % url_components.fragment
        return Link(os.path.join(prefix, ref), page.link.get_title(), None)

    def resolve(self, uri):
        """
        Banana banana
        """
        return resolve(uri, self.project.include_paths)

    def walk(self, parent=None):
        """Generator that yields pages in infix order

        Args:
            parent: hotdoc.core.tree.Page, optional, the page to start
                traversal from. If None, defaults to the root of the tree.

        Yields:
            hotdoc.core.tree.Page: the next page
        """
        if parent is None:
            yield self.root
            parent = self.root

        for cpage_name in parent.subpages:
            cpage = self.__all_pages[cpage_name]
            yield cpage
            for page in self.walk(parent=cpage):
                yield page

    def page_from_raw_text(self, source_file, include_path, contents, extension_name):
        """
        Banana banana
        """
        raw_contents = contents

        meta = {}
        if contents.startswith('---\n'):
            split = contents.split('\n...\n', 1)
            if len(split) == 2:
                contents = split[1]
                try:
                    blocks = yaml.load_all(split[0], Loader=yaml.FullLoader)
                    for block in blocks:
                        if block:
                            meta.update(block)
                except ConstructorError as exception:
                    warn('invalid-page-metadata',
                         '%s: Invalid metadata: \n%s' % (source_file,
                                                         str(exception)))

        output_path = os.path.dirname(os.path.relpath(source_file, include_path))

        ast = cmark.hotdoc_to_ast(contents, self, source_file)
        return Page(source_file, False, self.project.sanitized_name, extension_name,
                    source_file=source_file, ast=ast, meta=meta, raw_contents=raw_contents,
                    output_path=output_path)

    def get_pages(self):
        """
        Banana banana
        """
        return self.__all_pages

    def __update_dep_map(self, page, symbols):
        for sym in symbols:
            if not isinstance(sym, Symbol):
                continue

            self.__dep_map[sym.unique_name] = page
            self.__update_dep_map(page, sym.get_children_symbols())

    def resolve_symbols(self, database, link_resolver, page=None):
        """Will call resolve_symbols on all the stale subpages of the tree.
        Args:
          page: hotdoc.core.tree.Page, the page to resolve symbols in,
          will recurse on potential subpages.
        """

        page = page or self.root

        if page.ast is None and not page.generated:
            with io.open(page.source_file, 'r', encoding='utf-8') as _:
                page.ast = cmark.hotdoc_to_ast(_.read(), self, page.source_file)

        page.resolve_symbols(self, database, link_resolver)
        self.__update_dep_map(page, page.symbols)

        for pagename in page.subpages:
            cpage = self.__all_pages[pagename]
            self.resolve_symbols(database, link_resolver, page=cpage)

    def format_page(self, page, link_resolver, output, extensions):
        """
        Banana banana
        """
        info('formatting %s' % page.source_file, 'formatting')
        extension = extensions[page.extension_name]
        extension.format_page(page, link_resolver, output)

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

        info('Formatting documentation tree', 'formatting')