<commit_before>"""
The merger of argparse and cmd goes here.  This holds the main base class
used by all commands.
"""

import collections
import configparser
import functools
import inspect
import itertools
import shlex
from . import supplement
from .. import completer, layout, eventing, session, paging


def parse_docstring(entity):
    """ Return sanitized docstring from an entity.  The first line of the
    docstring is the title, and remaining lines are the details, aka git
    style. """
    doc = inspect.getdoc(entity)
    if not doc:
        return None, None
    doc = [x.strip() for x in doc.splitlines()]
    if not doc[0]:
        doc.pop(0)
    title = (doc and doc.pop(0)) or None
    if doc and not doc[0]:
        doc.pop(0)
    desc = '\n'.join(doc) or None
    return title, desc


class Command(eventing.Eventer):
    """ The primary encapsulation for a shellish command.  Each command or
    subcommand should be an instance of this class.  The docstring for sub-
    classes is used in --help output for this command. """

    name = None
    title = None
    desc = None
    use_pager = False
    ArgumentParser = supplement.ShellishParser
    ArgumentFormatter = supplement.VTMLHelpFormatter
    Session = session.Session
    completion_excludes = {'--help'}
    arg_label_fmt = '__command[%d]__'

    def setup_args(self, parser):
        """ Subclasses should provide any setup for their parsers here. """
        pass

    def prerun(self, args):
        """ Hook to do something prior to invocation. """
        pass

    def postrun(self, args, result=None, exc=None):
        """ Hook to do something following invocation. """
        pass

    def run(self, args):
        """ Primary entry point for command exec. """
        self.argparser.print_usage()
        raise SystemExit(1)

    def __init__(self, parent=None, title=None, desc=None, name=None, run=None,
                 prerun=None, postrun=None, **context):
        self.add_events(['prerun', 'postrun', 'setup_args', 'precomplete',
                         'postcomplete'])
        if name:
            self.name = name
        if type(self) is not Command:
            alt_title, alt_desc = parse_docstring(self)
        else:
            alt_title, alt_desc = None, None
        if not self.title or title:
            self.title = title or alt_title
        if not self.desc or desc:
            self.desc = desc or alt_desc
        if run is not None:
            self.run = run
        if prerun is not None:
            self.prerun = prerun
        if postrun is not None:
            self.postrun = postrun
        self.subcommands = collections.OrderedDict()
        self.default_subcommand = None
        self.session = None
        self.context_keys = set()
        self.inject_context(context)
        self.parent = parent
        self.subparsers = None
        self.argparser = self.create_argparser()
        self.setup_args(self.argparser)
        self.fire_event('setup_args', self.argparser)

    def get_or_create_session(self):
        if self.session is None:
            self.attach_session()
        return self.session

    def parse_args(self, argv=None):
        """ Return an argparse.Namespace of the argv string or sys.argv if
        argv is None. """
        arg_input = shlex.split(argv) if argv is not None else None
        self.get_or_create_session()
        return self.argparser.parse_args(arg_input)

    def __call__(self, args=None, argv=None):
        """ If a subparser is present and configured  we forward invocation to
        the correct subcommand method. If all else fails we call run(). """
        session = self.get_or_create_session()
        if args is None:
            args = self.parse_args(argv)
        commands = self.get_commands_from(args)
        if self.subparsers:
            try:
                command = commands[self.depth]
            except IndexError:
                pass
            else:
                return command(args)
            if self.default_subcommand:
                parser = self.default_subcommand.argparser
                parser.parse_args([], namespace=args)
                return self(args)  # retry
        return session.execute(self, args)

    def get_pager_spec(self):
        """ Find the best pager settings for this command.  If the user has
        specified overrides in the INI config file we prefer those. """
        self_config = self.get_config()
        pagercmd = self_config.get('pager')
        istty = self_config.getboolean('pager_istty')
        core_config = self.get_config('core')
        if pagercmd is None:
            pagercmd = core_config.get('pager')
        if istty is None:
            istty = core_config.get('pager_istty')
        return {
            "pagercmd": pagercmd,
            "istty": istty
        }

    def run_wrap(self, args):
        """ Wrap some standard protocol around a command's run method.  This
        wrapper should generally never capture exceptions.  It can look at
        them and do things but prerun and postrun should always be symmetric.
        Any exception suppression should happen in the `session.execute`. """
        self.fire_event('prerun', args)
        self.prerun(args)
        try:
            if self.session.allow_pager and self.use_pager:
                with paging.pager_redirect(**self.get_pager_spec()):
                    result = self.run(args)
            else:
                result = self.run(args)
        except (SystemExit, Exception) as e:
            self.postrun(args, exc=e)
            self.fire_event('postrun', args, exc=e)
            raise e
        else:
            self.postrun(args, result=result)
            self.fire_event('postrun', args, result=result)
            return result

    def config_section(self):
        """ The string used in a .<ROOT>_config file section.  Usually this
        is just the full prog name for a command minus the root command. """
        names = []
        cmd = self
        while cmd.parent:
            names.append(cmd.name)
            cmd = cmd.parent
        if not names:
            return self.name
        else:
            return ' '.join(reversed(names))

    def default_config(self):
        """ Can be overridden to provide a 1 level deep dictionary of config
        values.  Theses values are optionally overridden by the end-user via
        the session's load_config routine, that essentially looks for an INI
        file where the `[section]` is the `.prog` value for this command. """
        return {}

    def get_config(self, section=None):
        """ Return the merged end-user configuration for this command or a
        specific section if set in `section`. """
        section = self.config_section() if section is None else section
        try:
            return self.session.config[section]
        except KeyError:
            return configparser.SectionProxy(self.session.config, section)

    @property
    def parent(self):<commit_msg>Better empty config section semantics<commit_after>"""
The merger of argparse and cmd goes here.  This holds the main base class
used by all commands.
"""

import collections
import configparser
import functools
import inspect
import itertools
import shlex
from . import supplement
from .. import completer, layout, eventing, session, paging


def parse_docstring(entity):
    """ Return sanitized docstring from an entity.  The first line of the
    docstring is the title, and remaining lines are the details, aka git
    style. """
    doc = inspect.getdoc(entity)
    if not doc:
        return None, None
    doc = [x.strip() for x in doc.splitlines()]
    if not doc[0]:
        doc.pop(0)
    title = (doc and doc.pop(0)) or None
    if doc and not doc[0]:
        doc.pop(0)
    desc = '\n'.join(doc) or None
    return title, desc


class Command(eventing.Eventer):
    """ The primary encapsulation for a shellish command.  Each command or
    subcommand should be an instance of this class.  The docstring for sub-
    classes is used in --help output for this command. """

    name = None
    title = None
    desc = None
    use_pager = False
    ArgumentParser = supplement.ShellishParser
    ArgumentFormatter = supplement.VTMLHelpFormatter
    Session = session.Session
    completion_excludes = {'--help'}
    arg_label_fmt = '__command[%d]__'

    def setup_args(self, parser):
        """ Subclasses should provide any setup for their parsers here. """
        pass

    def prerun(self, args):
        """ Hook to do something prior to invocation. """
        pass

    def postrun(self, args, result=None, exc=None):
        """ Hook to do something following invocation. """
        pass

    def run(self, args):
        """ Primary entry point for command exec. """
        self.argparser.print_usage()
        raise SystemExit(1)

    def __init__(self, parent=None, title=None, desc=None, name=None, run=None,
                 prerun=None, postrun=None, **context):
        self.add_events(['prerun', 'postrun', 'setup_args', 'precomplete',
                         'postcomplete'])
        if name:
            self.name = name
        if type(self) is not Command:
            alt_title, alt_desc = parse_docstring(self)
        else:
            alt_title, alt_desc = None, None
        if not self.title or title:
            self.title = title or alt_title
        if not self.desc or desc:
            self.desc = desc or alt_desc
        if run is not None:
            self.run = run
        if prerun is not None:
            self.prerun = prerun
        if postrun is not None:
            self.postrun = postrun
        self.subcommands = collections.OrderedDict()
        self.default_subcommand = None
        self.session = None
        self.context_keys = set()
        self.inject_context(context)
        self.parent = parent
        self.subparsers = None
        self.argparser = self.create_argparser()
        self.setup_args(self.argparser)
        self.fire_event('setup_args', self.argparser)

    def get_or_create_session(self):
        if self.session is None:
            self.attach_session()
        return self.session

    def parse_args(self, argv=None):
        """ Return an argparse.Namespace of the argv string or sys.argv if
        argv is None. """
        arg_input = shlex.split(argv) if argv is not None else None
        self.get_or_create_session()
        return self.argparser.parse_args(arg_input)

    def __call__(self, args=None, argv=None):
        """ If a subparser is present and configured  we forward invocation to
        the correct subcommand method. If all else fails we call run(). """
        session = self.get_or_create_session()
        if args is None:
            args = self.parse_args(argv)
        commands = self.get_commands_from(args)
        if self.subparsers:
            try:
                command = commands[self.depth]
            except IndexError:
                pass
            else:
                return command(args)
            if self.default_subcommand:
                parser = self.default_subcommand.argparser
                parser.parse_args([], namespace=args)
                return self(args)  # retry
        return session.execute(self, args)

    def get_pager_spec(self):
        """ Find the best pager settings for this command.  If the user has
        specified overrides in the INI config file we prefer those. """
        self_config = self.get_config()
        pagercmd = self_config.get('pager')
        istty = self_config.getboolean('pager_istty')
        core_config = self.get_config('core')
        if pagercmd is None:
            pagercmd = core_config.get('pager')
        if istty is None:
            istty = core_config.get('pager_istty')
        return {
            "pagercmd": pagercmd,
            "istty": istty
        }

    def run_wrap(self, args):
        """ Wrap some standard protocol around a command's run method.  This
        wrapper should generally never capture exceptions.  It can look at
        them and do things but prerun and postrun should always be symmetric.
        Any exception suppression should happen in the `session.execute`. """
        self.fire_event('prerun', args)
        self.prerun(args)
        try:
            if self.session.allow_pager and self.use_pager:
                with paging.pager_redirect(**self.get_pager_spec()):
                    result = self.run(args)
            else:
                result = self.run(args)
        except (SystemExit, Exception) as e:
            self.postrun(args, exc=e)
            self.fire_event('postrun', args, exc=e)
            raise e
        else:
            self.postrun(args, result=result)
            self.fire_event('postrun', args, result=result)
            return result

    def config_section(self):
        """ The string used in a .<ROOT>_config file section.  Usually this
        is just the full prog name for a command minus the root command. """
        names = []
        cmd = self
        while cmd.parent:
            names.append(cmd.name)
            cmd = cmd.parent
        if not names:
            return self.name
        else:
            return ' '.join(reversed(names))

    def default_config(self):
        """ Can be overridden to provide a 1 level deep dictionary of config
        values.  Theses values are optionally overridden by the end-user via
        the session's load_config routine, that essentially looks for an INI
        file where the `[section]` is the `.prog` value for this command. """
        return {}

    def get_config(self, section=None):
        """ Return the merged end-user configuration for this command or a
        specific section if set in `section`. """
        config = self.session.config
        section = self.config_section() if section is None else section
        try:
            return config[section]
        except KeyError:
            config.add_section(section)
            return config[section]

    @property
    def parent(self):
{'fraction_non_alphanumeric': 0.08461101646791595, 'fraction_numerical': 0.0009937535491198182, 'mean_word_length': 2.1129032258064515, 'pattern_counts': {'":': 4, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6059499', 'n_tokens_mistral': 3891, 'n_tokens_neox': 3777, 'n_words': 1285}