Ejemplo n.º 1
0
def append_property_docs(app, what, name, obj, options, lines):
    """
    Render an overview with properties and methods of :class:`.PropertyManager` subclasses.

    This function implements a callback for ``autodoc-process-docstring`` that
    generates and appends an overview of member details to the docstrings of
    :class:`.PropertyManager` subclasses.

    The parameters expected by this function are those defined for Sphinx event
    callback functions (i.e. I'm not going to document them here :-).
    """
    if is_suitable_type(obj):
        paragraphs = []
        details = TypeInspector(type=obj)
        hints = (details.required_hint, details.initializer_hint)
        if any(hints):
            paragraphs.append(' '.join(h for h in hints if h))
        paragraphs.append(
            format("Here's an overview of the :class:`%s` class:",
                   obj.__name__))
        # Whitespace in labels is replaced with non breaking spaces to disable wrapping of the label text.
        data = [(format("%s:", label.replace(' ', u'\u00A0')), text)
                for label, text in details.overview if text]
        paragraphs.append(format_rst_table(data))
        if lines:
            lines.append('')
        lines.extend('\n\n'.join(paragraphs).splitlines())
Ejemplo n.º 2
0
    def compose_usage_notes(self):
        """
        Get a description of the property's semantics to include in its documentation.

        :returns: A list of strings describing the semantics of the
                  :class:`custom_property` in reStructuredText_ format with
                  Sphinx_ directives.

        .. _reStructuredText: https://en.wikipedia.org/wiki/ReStructuredText
        .. _Sphinx: http://sphinx-doc.org/
        """
        template = DYNAMIC_PROPERTY_NOTE if self.dynamic else CUSTOM_PROPERTY_NOTE
        cls = custom_property if self.dynamic else self.__class__
        dotted_path = "%s.%s" % (cls.__module__, cls.__name__)
        notes = [format(template, name=self.__name__, type=dotted_path)]
        if self.environment_variable:
            notes.append(format(ENVIRONMENT_PROPERTY_NOTE, variable=self.environment_variable))
        if self.required:
            notes.append(format(REQUIRED_PROPERTY_NOTE, name=self.__name__))
        if self.key:
            notes.append(KEY_PROPERTY_NOTE)
        if self.writable:
            notes.append(WRITABLE_PROPERTY_NOTE)
        if self.cached:
            notes.append(CACHED_PROPERTY_NOTE)
        if self.resettable:
            if self.cached:
                notes.append(RESETTABLE_CACHED_PROPERTY_NOTE)
            else:
                notes.append(RESETTABLE_WRITABLE_PROPERTY_NOTE)
        return notes
Ejemplo n.º 3
0
    def compose_usage_notes(self):
        """
        Get a description of the property's semantics to include in its documentation.

        :returns: A list of strings describing the semantics of the
                  :class:`custom_property` in reStructuredText_ format with
                  Sphinx_ directives.

        .. _reStructuredText: https://en.wikipedia.org/wiki/ReStructuredText
        .. _Sphinx: http://sphinx-doc.org/
        """
        template = DYNAMIC_PROPERTY_NOTE if self.dynamic else CUSTOM_PROPERTY_NOTE
        cls = custom_property if self.dynamic else self.__class__
        dotted_path = "%s.%s" % (cls.__module__, cls.__name__)
        notes = [format(template, name=self.__name__, type=dotted_path)]
        if self.environment_variable:
            notes.append(
                format(ENVIRONMENT_PROPERTY_NOTE,
                       variable=self.environment_variable))
        if self.required:
            notes.append(format(REQUIRED_PROPERTY_NOTE, name=self.__name__))
        if self.key:
            notes.append(KEY_PROPERTY_NOTE)
        if self.writable:
            notes.append(WRITABLE_PROPERTY_NOTE)
        if self.cached:
            notes.append(CACHED_PROPERTY_NOTE)
        if self.resettable:
            if self.cached:
                notes.append(RESETTABLE_CACHED_PROPERTY_NOTE)
            else:
                notes.append(RESETTABLE_WRITABLE_PROPERTY_NOTE)
        return notes
Ejemplo n.º 4
0
 def error_message(self):
     """A user friendly explanation of how the remote command failed (a string or :data:`None`)."""
     if self.error_type is RemoteConnectFailed:
         return format("SSH connection to %s failed! (SSH command: %s)",
                       self.ssh_alias, quote(self.command_line))
     elif self.error_type is RemoteCommandNotFound:
         return format("External command on %s isn't available! (SSH command: %s)",
                       self.ssh_alias, quote(self.command_line))
     elif self.error_type is RemoteCommandFailed:
         return format("External command on %s failed with exit code %s! (SSH command: %s)",
                       self.ssh_alias, self.returncode, quote(self.command_line))
Ejemplo n.º 5
0
 def error_message(self):
     """A user friendly explanation of how the remote command failed (a string or :data:`None`)."""
     if self.error_type is RemoteConnectFailed:
         return format("SSH connection to %s failed! (SSH command: %s)",
                       self.ssh_alias, quote(self.command_line))
     elif self.error_type is RemoteCommandNotFound:
         return format(
             "External command on %s isn't available! (SSH command: %s)",
             self.ssh_alias, quote(self.command_line))
     elif self.error_type is RemoteCommandFailed:
         return format(
             "External command on %s failed with exit code %s! (SSH command: %s)",
             self.ssh_alias, self.returncode, quote(self.command_line))
Ejemplo n.º 6
0
    def check_usage_notes(self):
        """"Check whether the correct notes are embedded in the documentation."""
        class DocumentationTest(object):
            @self.property_type
            def documented_property(self):
                """Documentation written by the author."""
                return random.random()

        documentation = DocumentationTest.documented_property.__doc__
        # Test that the sentence added for custom properties is always present.
        cls = custom_property if self.property_type.dynamic else self.property_type
        custom_property_note = format(
            DYNAMIC_PROPERTY_NOTE
            if self.property_type.dynamic else CUSTOM_PROPERTY_NOTE,
            name='documented_property',
            type="%s.%s" % (cls.__module__, cls.__name__),
        )
        if DocumentationTest.documented_property.usage_notes:
            assert custom_property_note in documentation
        else:
            assert custom_property_note not in documentation
            # If CUSTOM_PROPERTY_NOTE is not present we assume that none of the
            # other usage notes will be present either.
            return
        # Test that the sentence added for writable properties is present when applicable.
        assert self.property_type.writable == (WRITABLE_PROPERTY_NOTE
                                               in documentation)
        # Test that the sentence added for cached properties is present when applicable.
        assert self.property_type.cached == (CACHED_PROPERTY_NOTE
                                             in documentation)
        # Test that the sentence added for resettable properties is present when applicable.
        if self.is_resettable:
            assert self.is_cached == (RESETTABLE_CACHED_PROPERTY_NOTE
                                      in documentation)
            assert self.is_writable == (RESETTABLE_WRITABLE_PROPERTY_NOTE
                                        in documentation)
        else:
            assert RESETTABLE_CACHED_PROPERTY_NOTE not in documentation
            assert RESETTABLE_WRITABLE_PROPERTY_NOTE not in documentation
        # Test that the sentence added for required properties is present when applicable.
        required_property_note = format(REQUIRED_PROPERTY_NOTE,
                                        name='documented_property')
        assert self.property_type.required == (required_property_note
                                               in documentation)
        # Test that the sentence added for environment properties is present when applicable.
        environment_note = format(
            ENVIRONMENT_PROPERTY_NOTE,
            variable=self.property_type.environment_variable)
        assert bool(
            self.property_type.environment_variable) == (environment_note
                                                         in documentation)
Ejemplo n.º 7
0
 def error_message(self):
     """An error message that explains which commands *failed unexpectedly* (a string)."""
     summary = format("%i out of %s failed unexpectedly:",
                      self.pool.num_failed,
                      pluralize(self.pool.num_commands, "command"))
     details = "\n".join(" - %s" % cmd.error_message for cmd in self.commands)
     return summary + "\n\n" + details
Ejemplo n.º 8
0
    def run(self):
        """
        Keep spawning commands and collecting results until all commands have run.

        :returns: The value of :attr:`results`.
        :raises: Any exceptions raised by :func:`collect()`.

        This method calls :func:`spawn()` and :func:`collect()` in a loop until
        all commands registered using :func:`add()` have run and finished. If
        :func:`collect()` raises an exception any running commands are
        terminated before the exception is propagated to the caller.

        If you're writing code where you want to own the main loop then
        consider calling :func:`spawn()` and :func:`collect()` directly instead
        of using :func:`run()`.

        When :attr:`concurrency` is set to one, specific care is taken to make
        sure that the callbacks configured by :attr:`.start_event` and
        :attr:`.finish_event` are called in the expected (intuitive) order.
        """
        # Start spawning processes to execute the commands.
        timer = Timer()
        logger.debug("Preparing to run %s with a concurrency of %i ..",
                     pluralize(self.num_commands, "command"), self.concurrency)
        try:
            with Spinner(interactive=self.spinner, timer=timer) as spinner:
                num_started = 0
                num_collected = 0
                while not self.is_finished:
                    # When concurrency is set to one (I know, initially it
                    # sounds like a silly use case, bear with me) I want the
                    # start_event and finish_event callbacks of external
                    # commands to fire in the right order. The following
                    # conditional is intended to accomplish this goal.
                    if self.concurrency > (num_started - num_collected):
                        num_started += self.spawn()
                    num_collected += self.collect()
                    spinner.step(label=format(
                        "Waiting for %i/%i %s",
                        self.num_commands - self.num_finished,
                        self.num_commands,
                        "command" if self.num_commands == 1 else "commands",
                    ))
                    spinner.sleep()
        except Exception:
            if self.num_running > 0:
                logger.warning(
                    "Command pool raised exception, terminating running commands!"
                )
            # Terminate commands that are still running.
            self.terminate()
            # Re-raise the exception to the caller.
            raise
        # Collect the output and return code of any commands not yet collected.
        self.collect()
        logger.debug("Finished running %s in %s.",
                     pluralize(self.num_commands, "command"), timer)
        # Report the results to the caller.
        return self.results
Ejemplo n.º 9
0
 def error_message(self):
     """An error message that explains which commands *failed unexpectedly* (a string)."""
     summary = format("%i out of %s failed unexpectedly:",
                      self.pool.num_failed,
                      pluralize(self.pool.num_commands, "command"))
     details = "\n".join(" - %s" % cmd.error_message
                         for cmd in self.commands)
     return summary + "\n\n" + details
Ejemplo n.º 10
0
    def run(self):
        """
        Keep spawning commands and collecting results until all commands have run.

        :returns: The value of :attr:`results`.
        :raises: Any exceptions raised by :func:`collect()`.

        This method calls :func:`spawn()` and :func:`collect()` in a loop until
        all commands registered using :func:`add()` have run and finished. If
        :func:`collect()` raises an exception any running commands are
        terminated before the exception is propagated to the caller.

        If you're writing code where you want to own the main loop then
        consider calling :func:`spawn()` and :func:`collect()` directly instead
        of using :func:`run()`.

        When :attr:`concurrency` is set to one, specific care is taken to make
        sure that the callbacks configured by :attr:`.start_event` and
        :attr:`.finish_event` are called in the expected (intuitive) order.
        """
        # Start spawning processes to execute the commands.
        timer = Timer()
        logger.debug("Preparing to run %s with a concurrency of %i ..",
                     pluralize(self.num_commands, "command"),
                     self.concurrency)
        try:
            with Spinner(interactive=self.spinner, timer=timer) as spinner:
                num_started = 0
                num_collected = 0
                while not self.is_finished:
                    # When concurrency is set to one (I know, initially it
                    # sounds like a silly use case, bear with me) I want the
                    # start_event and finish_event callbacks of external
                    # commands to fire in the right order. The following
                    # conditional is intended to accomplish this goal.
                    if self.concurrency > (num_started - num_collected):
                        num_started += self.spawn()
                    num_collected += self.collect()
                    spinner.step(label=format(
                        "Waiting for %i/%i %s",
                        self.num_commands - self.num_finished, self.num_commands,
                        "command" if self.num_commands == 1 else "commands",
                    ))
                    spinner.sleep()
        except Exception:
            if self.num_running > 0:
                logger.warning("Command pool raised exception, terminating running commands!")
            # Terminate commands that are still running.
            self.terminate()
            # Re-raise the exception to the caller.
            raise
        # Collect the output and return code of any commands not yet collected.
        self.collect()
        logger.debug("Finished running %s in %s.",
                     pluralize(self.num_commands, "command"),
                     timer)
        # Report the results to the caller.
        return self.results
Ejemplo n.º 11
0
 def overview(self):
     """Render an overview with related members grouped together."""
     return (
         ("Superclass" if len(self.type.__bases__) == 1 else "Superclasses",
          concatenate(
              format(":class:`~%s.%s`", b.__module__, b.__name__)
              for b in self.type.__bases__)),
         ("Special methods", self.format_methods(self.special_methods)),
         ("Public methods", self.format_methods(self.public_methods)),
         ("Properties",
          self.format_properties(n for n, v in self.properties)),
     )
Ejemplo n.º 12
0
    def __getitem__(self, name):
        """
        Get the objects associated to the given tag.

        :param name: The name of the tag (a string).
        :returns: A :class:`set` of objects associated to the tag.
        :raises: :exc:`.EmptyTagError` when no associated objects are available.
        """
        objects = self.tags[name].objects
        if not objects:
            msg = "The tag '%s' doesn't match anything!"
            raise EmptyTagError(format(msg, name))
        return objects
Ejemplo n.º 13
0
def generate_sources_list(mirror_url,
                          codename,
                          suites=DEFAULT_SUITES,
                          components=VALID_COMPONENTS,
                          enable_sources=False):
    """
    Generate the contents of ``/etc/apt/sources.list`` for a Debian system.

    :param mirror_url: The base URL of the mirror (a string).
    :param codename: The codename of a Debian release (a string like 'wheezy'
                     or 'jessie') or a Debian release class (a string like
                     'stable', 'testing', etc).
    :param suites: An iterable of strings (defaults to
                   :data:`DEFAULT_SUITES`, refer to
                   :data:`VALID_SUITES` for details).
    :param components: An iterable of strings (refer to
                       :data:`VALID_COMPONENTS` for details).
    :param enable_sources: :data:`True` to include ``deb-src`` entries,
                           :data:`False` to omit them.
    :returns: The suggested contents of ``/etc/apt/sources.list`` (a string).
    """
    # Validate the suites.
    invalid_suites = [s for s in suites if s not in VALID_SUITES]
    if invalid_suites:
        msg = "Invalid Debian suite(s) given! (%s)"
        raise ValueError(msg % invalid_suites)
    # Validate the components.
    invalid_components = [c for c in components if c not in VALID_COMPONENTS]
    if invalid_components:
        msg = "Invalid Debian component(s) given! (%s)"
        raise ValueError(msg % invalid_components)
    # Generate the /etc/apt/sources.list file contents.
    lines = []
    directives = ('deb', 'deb-src') if enable_sources else ('deb', )
    for suite in suites:
        for directive in directives:
            lines.append(
                format(
                    '{directive} {mirror} {suite} {components}',
                    directive=directive,
                    mirror=(
                        OLD_RELEASES_URL if mirrors_are_equal(
                            mirror_url, OLD_RELEASES_URL) else
                        (SECURITY_URL if suite == 'security' else mirror_url)),
                    suite=(codename if suite == 'release' else
                           (('%s/updates' %
                             codename if suite == 'security' else codename +
                             '-' + suite))),
                    components=' '.join(components),
                ))
    return '\n'.join(lines)
Ejemplo n.º 14
0
 def check_usage_notes(self):
     """"Check whether the correct notes are embedded in the documentation."""
     class DocumentationTest(object):
         @self.property_type
         def documented_property(self):
             """Documentation written by the author."""
             return random.random()
     documentation = DocumentationTest.documented_property.__doc__
     # Test that the sentence added for custom properties is always present.
     cls = custom_property if self.property_type.dynamic else self.property_type
     custom_property_note = format(
         DYNAMIC_PROPERTY_NOTE if self.property_type.dynamic else CUSTOM_PROPERTY_NOTE,
         name='documented_property', type="%s.%s" % (cls.__module__, cls.__name__),
     )
     if DocumentationTest.documented_property.usage_notes:
         assert custom_property_note in documentation
     else:
         assert custom_property_note not in documentation
         # If CUSTOM_PROPERTY_NOTE is not present we assume that none of the
         # other usage notes will be present either.
         return
     # Test that the sentence added for writable properties is present when applicable.
     assert self.property_type.writable == (WRITABLE_PROPERTY_NOTE in documentation)
     # Test that the sentence added for cached properties is present when applicable.
     assert self.property_type.cached == (CACHED_PROPERTY_NOTE in documentation)
     # Test that the sentence added for resettable properties is present when applicable.
     if self.is_resettable:
         assert self.is_cached == (RESETTABLE_CACHED_PROPERTY_NOTE in documentation)
         assert self.is_writable == (RESETTABLE_WRITABLE_PROPERTY_NOTE in documentation)
     else:
         assert RESETTABLE_CACHED_PROPERTY_NOTE not in documentation
         assert RESETTABLE_WRITABLE_PROPERTY_NOTE not in documentation
     # Test that the sentence added for required properties is present when applicable.
     required_property_note = format(REQUIRED_PROPERTY_NOTE, name='documented_property')
     assert self.property_type.required == (required_property_note in documentation)
     # Test that the sentence added for environment properties is present when applicable.
     environment_note = format(ENVIRONMENT_PROPERTY_NOTE, variable=self.property_type.environment_variable)
     assert bool(self.property_type.environment_variable) == (environment_note in documentation)
Ejemplo n.º 15
0
    def __init__(self, command, timeout):
        """
        Initialize a :class:`CommandTimedOut` object.

        :param command: The command that timed out (an
                        :class:`~executor.ExternalCommand` object).
        :param timeout: The timeout that was exceeded (a number).
        """
        super(CommandTimedOut, self).__init__(
            command=command,
            error_message=format(
                "External command exceeded timeout of %s: %s",
                format_timespan(timeout),
                quote(command.command_line),
            ),
        )
Ejemplo n.º 16
0
    def __init__(self, command, timeout):
        """
        Initialize a :class:`CommandTimedOut` object.

        :param command: The command that timed out (an
                        :class:`~executor.ExternalCommand` object).
        :param timeout: The timeout that was exceeded (a number).
        """
        super(CommandTimedOut, self).__init__(
            command=command,
            error_message=format(
                "External command exceeded timeout of %s: %s",
                format_timespan(timeout),
                quote(command.command_line),
            ),
        )
Ejemplo n.º 17
0
 def search_messages(self, keywords):
     """Search the chat messages in the local archive for the given keyword(s)."""
     query = (self.session.query(Message).join(Conversation).join(
         Account).outerjoin(
             (Contact, Contact.id == Message.sender_id)).outerjoin(
                 (EmailAddress, Contact.email_addresses)))
     for kw in keywords:
         search_term = format(u"%{kw}%", kw=kw)
         query = query.filter(
             Account.backend.like(search_term)
             | Account.name.like(search_term)
             | Conversation.name.like(search_term)
             | Contact.full_name.like(search_term)
             | EmailAddress.value.like(search_term)
             | Message.timestamp.like(search_term)
             | Message.text.like(search_term))
     return query.order_by(Message.timestamp)
Ejemplo n.º 18
0
    def wait_until_connected(self):
        """
        Wait until connections are being accepted.

        :raises: :exc:`TimeoutError` when the SSH server isn't fast enough to
                 initialize.
        """
        timer = Timer()
        with Spinner(timer=timer) as spinner:
            while not self.is_connected:
                if timer.elapsed_time > self.wait_timeout:
                    raise TimeoutError(format(
                        "Failed to establish connection to %s within configured timeout of %s!",
                        self, format_timespan(self.wait_timeout),
                    ))
                spinner.step(label="Waiting for %s to accept connections" % self)
                spinner.sleep()
        logger.debug("Waited %s for %s to accept connections.", timer, self)
Ejemplo n.º 19
0
def get_secret_from_store(name, directory=None):
    """
    Use :mod:`qpass` to get a secret from ``~/.password-store``.

    :param name: The name of a password or a search pattern that matches a
                 single entry in the password store (a string).
    :param directory: The directory to use (a string, defaults to
                      ``~/.password-store``).
    :returns: The secret (a string).
    :raises: :exc:`exceptions.ValueError` when the given `name` doesn't match
             any entries or matches multiple entries in the password store.
    """
    kw = dict(directory=directory) if directory else {}
    store = PasswordStore(**kw)
    matches = store.smart_search(name)
    if len(matches) != 1:
        msg = "Expected exactly one match in password database! (input: %s)"
        raise ValueError(format(msg, name))
    return matches[0].password
Ejemplo n.º 20
0
    def parse(self, value):
        """
        Parse a string expression into a :class:`Tag` object.

        :param value: The tag expression to parse (a string).
        :returns: A :class:`Tag` object.
        :raises: :exc:`~exceptions.ValueError` for unsupported `value` types.

        During normal use you won't need the :func:`parse()` method, in fact
        it's not currently being used anywhere in :mod:`gentag`. This method
        was originally created with the idea of having :func:`define()` parse
        string expressions up front to validate their syntax, however this
        approach has since been abandoned. The :func:`parse()` method now
        remains because it may be useful to callers for unforeseen use cases.
        """
        if isinstance(value, string_types):
            # We override __builtins__ to avoid leaking any built-ins into eval().
            return eval(value, dict(__builtins__={}), self.tags)
        else:
            msg = "Unsupported value type! (%r)"
            raise ValueError(format(msg, value))
Ejemplo n.º 21
0
    def evaluate_raw(self, expression):
        """
        Get the objects matching the given expression.

        :param expression: The tag expression to evaluate (a string).
        :returns: A :class:`set` with matching objects.
        :raises: :exc:`.TagExpressionError` when the given expression
                 cannot be evaluated due to a syntax error.

        This method uses :func:`eval()` to evaluate the expression given by the
        caller, however it overrides ``__builtins__`` to avoid leaking any
        built-ins into the :func:`eval()` call.
        """
        try:
            logger.debug("Evaluating expression '%s' ..", expression)
            objects = eval(expression, dict(__builtins__={}), self.objects)
            logger.debug("The expression matched %s.", pluralize(len(objects), "object"))
            return objects
        except SyntaxError as e:
            msg = "Failed to evaluate tag expression due to syntax error! (%s)"
            raise TagExpressionError(format(msg, e))
Ejemplo n.º 22
0
    def wait_until_connected(self):
        """
        Wait until connections are being accepted.

        :raises: :exc:`TimeoutError` when the SSH server isn't fast enough to
                 initialize.
        """
        timer = Timer()
        with Spinner(timer=timer) as spinner:
            while not self.is_connected:
                if timer.elapsed_time > self.wait_timeout:
                    raise TimeoutError(
                        format(
                            "Failed to establish connection to %s within configured timeout of %s!",
                            self,
                            format_timespan(self.wait_timeout),
                        ))
                spinner.step(label="Waiting for %s to accept connections" %
                             self)
                spinner.sleep()
        logger.debug("Waited %s for %s to accept connections.", timer, self)
Ejemplo n.º 23
0
def generate_sources_list(mirror_url, codename,
                          suites=DEFAULT_SUITES,
                          components=VALID_COMPONENTS,
                          enable_sources=False):
    """
    Generate the contents of ``/etc/apt/sources.list`` for an Ubuntu system.

    :param mirror_url: The base URL of the mirror (a string).
    :param codename: The codename of the Ubuntu release (a string like 'trusty' or 'xenial').
    :param suites: An iterable of strings (defaults to :data:`DEFAULT_SUITES`,
                   refer to :data:`VALID_SUITES` for details).
    :param components: An iterable of strings (refer to
                       :data:`VALID_COMPONENTS` for details).
    :param enable_sources: :data:`True` to include ``deb-src`` entries,
                           :data:`False` to omit them.
    :returns: The suggested contents of ``/etc/apt/sources.list`` (a string).
    """
    # Validate the suites.
    invalid_suites = [s for s in suites if s not in VALID_SUITES]
    if invalid_suites:
        msg = "Invalid Ubuntu suite(s) given! (%s)"
        raise ValueError(msg % invalid_suites)
    # Validate the components.
    invalid_components = [c for c in components if c not in VALID_COMPONENTS]
    if invalid_components:
        msg = "Invalid Ubuntu component(s) given! (%s)"
        raise ValueError(msg % invalid_components)
    # Generate the /etc/apt/sources.list file contents.
    lines = []
    directives = ('deb', 'deb-src') if enable_sources else ('deb',)
    for suite in suites:
        for directive in directives:
            lines.append(format(
                '{directive} {mirror} {suite} {components}', directive=directive,
                mirror=(OLD_RELEASES_URL if mirrors_are_equal(mirror_url, OLD_RELEASES_URL)
                        else (SECURITY_URL if suite == 'security' else mirror_url)),
                suite=(codename if suite == 'release' else codename + '-' + suite),
                components=' '.join(components),
            ))
    return '\n'.join(lines)
Ejemplo n.º 24
0
    def documentation(self):
        r"""
        Configuration documentation in reStructuredText_ syntax (a string).

        The purpose of the :attr:`documentation` property is to provide
        documentation on the integration of :class:`ConfigLoader` into other
        projects without denormalizing the required knowledge via copy/paste.

        .. _reStructuredText: https://en.wikipedia.org/wiki/ReStructuredText
        """
        from humanfriendly.tables import format_rst_table
        formatted_table = format_rst_table([
            (directory,
             self.get_main_pattern(directory).replace('*', r'\*'),
             self.get_modular_pattern(directory).replace('*', r'\*'))
            for directory in self.base_directories
        ], [
            "Directory",
            "Main configuration file",
            "Modular configuration files",
        ])
        return format(DOCUMENTATION_TEMPLATE, table=formatted_table).strip()
Ejemplo n.º 25
0
    def confirm_installation(self, requirement, missing_dependencies, install_command):
        """
        Ask the operator's permission to install missing system packages.

        :param requirement: A :class:`.Requirement` object.
        :param missing_dependencies: A list of strings with missing dependencies.
        :param install_command: A list of strings with the command line needed
                                to install the missing dependencies.
        :raises: :exc:`.DependencyInstallationRefused` when the operator refuses.
        """
        try:
            return prompt_for_confirmation(
                format(
                    "Do you want me to install %s %s?",
                    "this" if len(missing_dependencies) == 1 else "these",
                    "dependency" if len(missing_dependencies) == 1 else "dependencies",
                ),
                default=True,
            )
        except KeyboardInterrupt:
            # Control-C is a negative response but doesn't
            # otherwise interrupt the program flow.
            return False
Ejemplo n.º 26
0
    def confirm_installation(self, requirement, missing_dependencies,
                             install_command):
        """
        Ask the operator's permission to install missing system packages.

        :param requirement: A :class:`.Requirement` object.
        :param missing_dependencies: A list of strings with missing dependencies.
        :param install_command: A list of strings with the command line needed
                                to install the missing dependencies.
        :raises: :exc:`.DependencyInstallationRefused` when the operator refuses.
        """
        try:
            return prompt_for_confirmation(format(
                "Do you want me to install %s %s?",
                "this" if len(missing_dependencies) == 1 else "these",
                "dependency"
                if len(missing_dependencies) == 1 else "dependencies",
            ),
                                           default=True)
        except KeyboardInterrupt:
            # Control-C is a negative response but doesn't
            # otherwise interrupt the program flow.
            return False
Ejemplo n.º 27
0
    def define(self, name, value):
        """
        Define the value of a tag.

        :param name: The name of the tag (a string).
        :param value: A string containing an expression or an iterable
                      of values associated to the given tag.
        :returns: The :class:`Tag` object.
        :raises: :exc:`~exceptions.ValueError` for unsupported `value` types.
        """
        if isinstance(value, string_types):
            logger.debug("Setting expression of tag '%s' to: %s", name, value)
            tag = self.tags[name]
            tag.expression = value
            return tag
        elif isinstance(value, collections.Iterable):
            logger.debug("Setting objects of tag '%s' to: %s", name, value)
            tag = self.tags[name]
            tag.objects = value
            return tag
        else:
            msg = "Unsupported value for tag '%s'! (%r)"
            raise ValueError(format(msg, name, value))
Ejemplo n.º 28
0
    def get_modular_pattern(self, directory):
        """
        Get the :func:`~glob.glob()` pattern to find modular configuration files.

        :param directory: The pathname of a base directory (a string).
        :returns: A filename pattern (a string).

        This method generates a pattern that matches a directory whose name is
        based on :attr:`program_name` with the suffix ``.d`` containing files
        matching the configured :attr:`filename_extension`. Here's an example:

        >>> from update_dotdee import ConfigLoader
        >>> loader = ConfigLoader(program_name='update-dotdee')
        >>> [loader.get_modular_pattern(d) for d in loader.base_directories]
        ['/etc/update-dotdee.d/*.ini',
         '~/.update-dotdee.d/*.ini',
         '~/.config/update-dotdee.d/*.ini']
        """
        return os.path.join(directory, format(
            '{prefix}{program_name}.d/*.{extension}',
            extension=self.filename_extension.lstrip('.'),
            program_name=self.program_name,
            prefix=self.get_prefix(directory),
        ))
Ejemplo n.º 29
0
 def format_methods(self, names):
     """Format a list of method names as reStructuredText."""
     return concatenate(format(":func:`%s()`", n) for n in sorted(names))
Ejemplo n.º 30
0
 def format_properties(self, names):
     """Format a list of property names as reStructuredText."""
     return concatenate(format(":attr:`%s`", n) for n in sorted(names))
Ejemplo n.º 31
0
 def check_response(self, response, message, *args, **kw):
     """Validate an IMAP server response."""
     logger.debug("IMAP response: rv=%r, data=%r", response[0], response[1])
     if response[0] != "OK":
         raise Exception(format(message, *args, **kw))
     return response[1]
Ejemplo n.º 32
0
 def __str__(self):
     """Render a human friendly representation."""
     return format("%s://%s:%i", self.scheme, self.hostname,
                   self.port_number)
Ejemplo n.º 33
0
 def report_issue(self, message, *args, **kw):
     """Handle a problem by raising an exception or logging a warning (depending on :attr:`strict`)."""
     if self.strict:
         raise ValueError(format(message, *args, **kw))
     else:
         logger.warning(format(message, *args, **kw))
Ejemplo n.º 34
0
 def __str__(self):
     """Render a human friendly representation."""
     return format("%s://%s:%i", self.scheme, self.hostname, self.port_number)
Ejemplo n.º 35
0
 def render_location(self, scheme=None, hostname=None, port_number=None):
     """Render a human friendly representation of an :class:`EphemeralTCPServer` object."""
     return format("{scheme}://{host}:{port}",
                   scheme=scheme or self.scheme,
                   host=hostname or self.hostname,
                   port=port_number or self.port_number)