예제 #1
0
 def test_plain_text_output_format(self):
     """Inspect the plain text output of coloredlogs."""
     logger = VerboseLogger(random_string(25))
     stream = StringIO()
     install(level=logging.NOTSET, logger=logger, stream=stream)
     # Test that filtering on severity works.
     logger.setLevel(logging.INFO)
     logger.debug("No one should see this message.")
     assert len(stream.getvalue().strip()) == 0
     # Test that the default output format looks okay in plain text.
     logger.setLevel(logging.NOTSET)
     for method, severity in ((logger.debug, 'DEBUG'), (logger.info,
                                                        'INFO'),
                              (logger.verbose, 'VERBOSE'), (logger.warning,
                                                            'WARN'),
                              (logger.error, 'ERROR'), (logger.critical,
                                                        'CRITICAL')):
         # Prepare the text.
         text = "This is a message with severity %r." % severity.lower()
         # Log the message with the given severity.
         method(text)
         # Get the line of output generated by the handler.
         output = stream.getvalue()
         lines = output.splitlines()
         last_line = lines[-1]
         assert text in last_line
         assert severity in last_line
         assert PLAIN_TEXT_PATTERN.match(last_line)
예제 #2
0
파일: usage.py 프로젝트: yangsongx/doc
def render_usage(text):
    """
    Reformat a command line program's usage message to reStructuredText_.

    :param text: The plain text usage message (a string).
    :returns: The usage message rendered to reStructuredText_ (a string).
    """
    meta_variables = find_meta_variables(text)
    introduction, options = parse_usage(text)
    output = [render_paragraph(p, meta_variables) for p in introduction]
    if options:
        output.append('\n'.join([
            '.. csv-table::',
            '   :header: Option, Description',
            '   :widths: 30, 70',
            '',
        ]))
        csv_buffer = StringIO()
        csv_writer = csv.writer(csv_buffer)
        while options:
            variants = options.pop(0)
            description = options.pop(0)
            csv_writer.writerow([
                render_paragraph(variants, meta_variables),
                '\n\n'.join(render_paragraph(p, meta_variables) for p in split_paragraphs(description)),
            ])
        csv_lines = csv_buffer.getvalue().splitlines()
        output.append('\n'.join('   %s' % l for l in csv_lines))
    logger.debug("Rendered output: %s", output)
    return '\n\n'.join(trim_empty_lines(o) for o in output)
예제 #3
0
def render_usage(text):
    """
    Reformat a command line program's usage message to reStructuredText_.

    :param text: The plain text usage message (a string).
    :returns: The usage message rendered to reStructuredText_ (a string).
    """
    meta_variables = find_meta_variables(text)
    introduction, options = parse_usage(text)
    output = [render_paragraph(p, meta_variables) for p in introduction]
    if options:
        output.append('\n'.join([
            '.. csv-table::',
            '   :header: Option, Description',
            '   :widths: 30, 70',
            '',
        ]))
        csv_buffer = StringIO()
        csv_writer = csv.writer(csv_buffer)
        while options:
            variants = options.pop(0)
            description = options.pop(0)
            csv_writer.writerow([
                render_paragraph(variants, meta_variables),
                ('\n\n'.join(
                    render_paragraph(p, meta_variables)
                    for p in split_paragraphs(description))).rstrip(),
            ])
        csv_lines = csv_buffer.getvalue().splitlines()
        output.append('\n'.join('   %s' % l for l in csv_lines))
    logger.debug("Rendered output: %s", output)
    return '\n\n'.join(trim_empty_lines(o) for o in output)
예제 #4
0
 def test_plain_text_output_format(self):
     """Inspect the plain text output of coloredlogs."""
     logger = VerboseLogger(random_string(25))
     stream = StringIO()
     install(level=logging.NOTSET, logger=logger, stream=stream)
     # Test that filtering on severity works.
     logger.setLevel(logging.INFO)
     logger.debug("No one should see this message.")
     assert len(stream.getvalue().strip()) == 0
     # Test that the default output format looks okay in plain text.
     logger.setLevel(logging.NOTSET)
     for method, severity in ((logger.debug, 'DEBUG'),
                              (logger.info, 'INFO'),
                              (logger.verbose, 'VERBOSE'),
                              (logger.warning, 'WARNING'),
                              (logger.error, 'ERROR'),
                              (logger.critical, 'CRITICAL')):
         # Prepare the text.
         text = "This is a message with severity %r." % severity.lower()
         # Log the message with the given severity.
         method(text)
         # Get the line of output generated by the handler.
         output = stream.getvalue()
         lines = output.splitlines()
         last_line = lines[-1]
         assert text in last_line
         assert severity in last_line
         assert PLAIN_TEXT_PATTERN.match(last_line)
예제 #5
0
 def test_support_for_milliseconds_directive(self):
     """Make sure milliseconds using the ``%f`` directive are supported."""
     stream = StringIO()
     install(reconfigure=True,
             stream=stream,
             datefmt='%Y-%m-%dT%H:%M:%S.%f%z')
     logging.info("This should be timestamped according to #45.")
     assert re.match(
         r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}[+-]\d{4}\s',
         stream.getvalue())
예제 #6
0
 def test_support_for_milliseconds(self):
     """Make sure milliseconds are hidden by default but can be easily enabled."""
     # Check that the default log format doesn't include milliseconds.
     stream = StringIO()
     install(reconfigure=True, stream=stream)
     logging.info("This should not include milliseconds.")
     assert all(map(PLAIN_TEXT_PATTERN.match, stream.getvalue().splitlines()))
     # Check that milliseconds can be enabled via a shortcut.
     stream = StringIO()
     install(milliseconds=True, reconfigure=True, stream=stream)
     logging.info("This should include milliseconds.")
     assert all(map(PATTERN_INCLUDING_MILLISECONDS.match, stream.getvalue().splitlines()))
예제 #7
0
    def __init__(self, merged=False, input=''):
        """
        Initialize a :class:`CaptureOutput` object.

        :param merged: :data:`True` to merge the streams,
                       :data:`False` to capture them separately.
        :param input: The data that reads from :data:`sys.stdin`
                      should return (a string).
        """
        self.stdin = StringIO(input)
        self.stdout = StringIO()
        self.stderr = self.stdout if merged else StringIO()
        self.patched_attributes = [
            PatchedAttribute(sys, name, getattr(self, name))
            for name in ('stdin', 'stdout', 'stderr')
        ]
예제 #8
0
 def test_spinner(self):
     """Test :func:`humanfriendly.Spinner`."""
     stream = StringIO()
     spinner = humanfriendly.Spinner('test spinner', total=4, stream=stream, interactive=True)
     for progress in [1, 2, 3, 4]:
         spinner.step(progress=progress)
         time.sleep(0.2)
     spinner.clear()
     output = stream.getvalue()
     output = (output.replace(humanfriendly.show_cursor_code, '')
                     .replace(humanfriendly.hide_cursor_code, ''))
     lines = [line for line in output.split(humanfriendly.erase_line_code) if line]
     self.assertTrue(len(lines) > 0)
     self.assertTrue(all('test spinner' in l for l in lines))
     self.assertTrue(all('%' in l for l in lines))
     self.assertEqual(sorted(set(lines)), sorted(lines))
예제 #9
0
 def test_spinner(self):
     """Test :func:`humanfriendly.Spinner`."""
     stream = StringIO()
     spinner = humanfriendly.Spinner('test spinner', total=4, stream=stream, interactive=True)
     for progress in [1, 2, 3, 4]:
         spinner.step(progress=progress)
         time.sleep(0.2)
     spinner.clear()
     output = stream.getvalue()
     output = (output.replace(humanfriendly.show_cursor_code, '')
                     .replace(humanfriendly.hide_cursor_code, ''))
     lines = [line for line in output.split(humanfriendly.erase_line_code) if line]
     self.assertTrue(len(lines) > 0)
     self.assertTrue(all('test spinner' in l for l in lines))
     self.assertTrue(all('%' in l for l in lines))
     self.assertEqual(sorted(set(lines)), sorted(lines))
예제 #10
0
def main(*args, **kw):
    """Utility function to test the command line interface without invoking a subprocess."""
    returncode = 0
    input_buffer = StringIO(kw.get('input', ''))
    output_buffer = StringIO()
    saved_argv = sys.argv
    saved_stdin = sys.stdin
    saved_stdout = sys.stdout
    try:
        sys.argv = [sys.argv[0]] + list(args)
        sys.stdin = input_buffer
        sys.stdout = output_buffer
        cli.main()
    except SystemExit as e:
        returncode = e.code or 1
    finally:
        sys.argv = saved_argv
        sys.stdin = saved_stdin
        sys.stdout = saved_stdout
    return returncode, output_buffer.getvalue()
예제 #11
0
def main(*args, **kw):
    """Utility function to test the command line interface without invoking a subprocess."""
    returncode = 0
    input_buffer = StringIO(kw.get('input', ''))
    output_buffer = StringIO()
    saved_argv = sys.argv
    saved_stdin = sys.stdin
    saved_stdout = sys.stdout
    try:
        sys.argv = [sys.argv[0]] + list(args)
        sys.stdin = input_buffer
        sys.stdout = output_buffer
        cli.main()
    except SystemExit as e:
        returncode = e.code or 1
    finally:
        sys.argv = saved_argv
        sys.stdin = saved_stdin
        sys.stdout = saved_stdout
    return returncode, output_buffer.getvalue()
예제 #12
0
 def test_find_terminal_size(self):
     """Test :func:`humanfriendly.terminal.find_terminal_size()`."""
     lines, columns = find_terminal_size()
     # We really can't assert any minimum or maximum values here because it
     # simply doesn't make any sense; it's impossible for me to anticipate
     # on what environments this test suite will run in the future.
     assert lines > 0
     assert columns > 0
     # The find_terminal_size_using_ioctl() function is the default
     # implementation and it will likely work fine. This makes it hard to
     # test the fall back code paths though. However there's an easy way to
     # make find_terminal_size_using_ioctl() fail ...
     saved_stdin = sys.stdin
     saved_stdout = sys.stdout
     saved_stderr = sys.stderr
     try:
         # What do you mean this is brute force?! ;-)
         sys.stdin = StringIO()
         sys.stdout = StringIO()
         sys.stderr = StringIO()
         # Now find_terminal_size_using_ioctl() should fail even though
         # find_terminal_size_using_stty() might work fine.
         lines, columns = find_terminal_size()
         assert lines > 0
         assert columns > 0
         # There's also an ugly way to make `stty size' fail: The
         # subprocess.Popen class uses os.execvp() underneath, so if we
         # clear the $PATH it will break.
         saved_path = os.environ['PATH']
         try:
             os.environ['PATH'] = ''
             # Now find_terminal_size_using_stty() should fail.
             lines, columns = find_terminal_size()
             assert lines > 0
             assert columns > 0
         finally:
             os.environ['PATH'] = saved_path
     finally:
         sys.stdin = saved_stdin
         sys.stdout = saved_stdout
         sys.stderr = saved_stderr
예제 #13
0
def main(*arguments, **options):
    """Wrap the command line interface to make it easier to test."""
    capture = options.get('capture', False)
    saved_argv = sys.argv
    saved_stdout = sys.stdout
    try:
        sys.argv = arguments
        if capture:
            sys.stdout = StringIO()
        coloredlogs.cli.main()
        if capture:
            return sys.stdout.getvalue()
    finally:
        sys.argv = saved_argv
        sys.stdout = saved_stdout
예제 #14
0
 def test_plain_text_output_format(self):
     """Inspect the plain text output of coloredlogs."""
     logger = VerboseLogger(random_string(25))
     stream = StringIO()
     install(level=logging.NOTSET, logger=logger, stream=stream)
     # Test that filtering on severity works.
     logger.setLevel(logging.INFO)
     logger.debug("No one should see this message.")
     assert len(stream.getvalue().strip()) == 0
     # Test that the default output format looks okay in plain text.
     logger.setLevel(logging.NOTSET)
     for method, severity in ((logger.debug, 'DEBUG'),
                              (logger.info, 'INFO'),
                              (logger.verbose, 'VERBOSE'),
                              (logger.warning, 'WARNING'),
                              (logger.error, 'ERROR'),
                              (logger.critical, 'CRITICAL')):
         # XXX Workaround for a regression in Python 3.7 caused by the
         # Logger.isEnabledFor() method using stale cache entries. If we
         # don't clear the cache then logger.isEnabledFor(logging.DEBUG)
         # returns False and no DEBUG message is emitted.
         try:
             logger._cache.clear()
         except AttributeError:
             pass
         # Prepare the text.
         text = "This is a message with severity %r." % severity.lower()
         # Log the message with the given severity.
         method(text)
         # Get the line of output generated by the handler.
         output = stream.getvalue()
         lines = output.splitlines()
         last_line = lines[-1]
         assert text in last_line
         assert severity in last_line
         assert PLAIN_TEXT_PATTERN.match(last_line)
예제 #15
0
class CaptureOutput(ContextManager):

    """Context manager that captures what's written to :data:`sys.stdout` and :data:`sys.stderr`."""

    def __init__(self, merged=False, input=''):
        """
        Initialize a :class:`CaptureOutput` object.

        :param merged: :data:`True` to merge the streams,
                       :data:`False` to capture them separately.
        :param input: The data that reads from :data:`sys.stdin`
                      should return (a string).
        """
        self.stdin = StringIO(input)
        self.stdout = StringIO()
        self.stderr = self.stdout if merged else StringIO()
        self.patched_attributes = [
            PatchedAttribute(sys, name, getattr(self, name))
            for name in ('stdin', 'stdout', 'stderr')
        ]

    stdin = None
    """The :class:`~humanfriendly.compat.StringIO` object used to feed the standard input stream."""

    stdout = None
    """The :class:`~humanfriendly.compat.StringIO` object used to capture the standard output stream."""

    stderr = None
    """The :class:`~humanfriendly.compat.StringIO` object used to capture the standard error stream."""

    def __enter__(self):
        """Start capturing what's written to :data:`sys.stdout` and :data:`sys.stderr`."""
        super(CaptureOutput, self).__enter__()
        for context in self.patched_attributes:
            context.__enter__()
        return self

    def __exit__(self, exc_type=None, exc_value=None, traceback=None):
        """Stop capturing what's written to :data:`sys.stdout` and :data:`sys.stderr`."""
        super(CaptureOutput, self).__exit__(exc_type, exc_value, traceback)
        for context in self.patched_attributes:
            context.__exit__(exc_type, exc_value, traceback)

    def getvalue(self):
        """Get the text written to :data:`sys.stdout`."""
        return self.stdout.getvalue()
예제 #16
0
 def test_support_for_milliseconds(self):
     """Make sure milliseconds are hidden by default but can be easily enabled."""
     # Check that the default log format doesn't include milliseconds.
     stream = StringIO()
     install(reconfigure=True, stream=stream)
     logging.info("This should not include milliseconds.")
     assert all(map(PLAIN_TEXT_PATTERN.match, stream.getvalue().splitlines()))
     # Check that milliseconds can be enabled via a shortcut.
     stream = StringIO()
     install(milliseconds=True, reconfigure=True, stream=stream)
     logging.info("This should include milliseconds.")
     assert all(map(PATTERN_INCLUDING_MILLISECONDS.match, stream.getvalue().splitlines()))
예제 #17
0
    def reset(self):
        """
        Reset the state of the HTML parser and ANSI converter.

        When `output` is a :class:`~python3:io.StringIO` object a new
        instance will be created (and the old one garbage collected).
        """
        # Reset the state of the superclass.
        HTMLParser.reset(self)
        # Reset our instance variables.
        self.link_text = None
        self.link_url = None
        self.preformatted_text_level = 0
        if self.output is None or isinstance(self.output, StringIO):
            # If the caller specified something like output=sys.stdout then it
            # doesn't make much sense to negate that choice here in reset().
            self.output = StringIO()
        self.stack = []
예제 #18
0
 def test_dynamic_stderr_lookup(self):
     """Make sure coloredlogs.install() uses StandardErrorHandler when possible."""
     coloredlogs.install()
     # Redirect sys.stderr to a temporary buffer.
     initial_stream = StringIO()
     initial_text = "Which stream will receive this text?"
     with PatchedAttribute(sys, 'stderr', initial_stream):
         logging.info(initial_text)
     assert initial_text in initial_stream.getvalue()
     # Redirect sys.stderr again, to a different destination.
     subsequent_stream = StringIO()
     subsequent_text = "And which stream will receive this other text?"
     with PatchedAttribute(sys, 'stderr', subsequent_stream):
         logging.info(subsequent_text)
     assert subsequent_text in subsequent_stream.getvalue()
예제 #19
0
    def __init__(self, merged=False, input='', enabled=True):
        """
        Initialize a :class:`CaptureOutput` object.

        :param merged: :data:`True` to merge the streams,
                       :data:`False` to capture them separately.
        :param input: The data that reads from :data:`sys.stdin`
                      should return (a string).
        :param enabled: :data:`True` to enable capturing (the default),
                        :data:`False` otherwise. This makes it easy to
                        unconditionally use :class:`CaptureOutput` in
                        a :keyword:`with` block while preserving the
                        choice to opt out of capturing output.
        """
        self.stdin = StringIO(input)
        self.stdout = CaptureBuffer()
        self.stderr = self.stdout if merged else CaptureBuffer()
        self.patched_attributes = []
        if enabled:
            self.patched_attributes.extend(
                PatchedAttribute(sys, name, getattr(self, name))
                for name in ('stdin', 'stdout', 'stderr'))
예제 #20
0
 def test_support_for_milliseconds_directive(self):
     """Make sure milliseconds using the ``%f`` directive are supported."""
     stream = StringIO()
     install(reconfigure=True, stream=stream, datefmt='%Y-%m-%dT%H:%M:%S.%f%z')
     logging.info("This should be timestamped according to #45.")
     assert re.match(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\+\d{4}\s', stream.getvalue())