def test_carriage_return_interpretation(self): """Sanity check the results of clean_terminal_output().""" # Simple output should pass through unharmed. assert clean_terminal_output('foo') == ['foo'] # Simple output should pass through unharmed. assert clean_terminal_output('foo\nbar') == ['foo', 'bar'] # Carriage returns and preceding substrings should be stripped. assert clean_terminal_output('foo\rbar\nbaz') == ['bar', 'baz'] # Trailing empty lines should be stripped. assert clean_terminal_output('foo\nbar\nbaz\n\n\n') == [ 'foo', 'bar', 'baz' ]
def capture(command, encoding='UTF-8'): """ Capture the output of an external command as if it runs in an interactive terminal. :param command: The command name and its arguments (a list of strings). :param encoding: The encoding to use to decode the output (a string). :returns: The output of the command. This function runs an external command under ``script`` (emulating an interactive terminal) to capture the output of the command as if it was running in an interactive terminal (including ANSI escape sequences). """ with open(os.devnull, 'wb') as dev_null: # We start by invoking the `script' program in a form that is supported # by the Linux implementation [1] but fails command line validation on # the MacOS (BSD) implementation [2]: The command is specified using # the -c option and the typescript file is /dev/null. # # [1] http://man7.org/linux/man-pages/man1/script.1.html # [2] https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/script.1.html command_line = ['script', '-qc', ' '.join(map(pipes.quote, command)), '/dev/null'] script = subprocess.Popen(command_line, stdout=subprocess.PIPE, stderr=dev_null) stdout, stderr = script.communicate() if script.returncode == 0: # If `script' succeeded we assume that it understood our command line # invocation which means it's the Linux implementation (in this case # we can use standard output instead of a temporary file). output = stdout.decode(encoding) else: # If `script' failed we assume that it didn't understand our command # line invocation which means it's the MacOS (BSD) implementation # (in this case we need a temporary file because the command line # interface requires it). fd, temporary_file = tempfile.mkstemp(prefix='colouredlogs-', suffix='-capture.txt') try: command_line = ['script', '-q', temporary_file] + list(command) subprocess.Popen(command_line, stdout=dev_null, stderr=dev_null).wait() with codecs.open(temporary_file, 'r', encoding) as handle: output = handle.read() finally: os.unlink(temporary_file) # On MacOS when standard input is /dev/null I've observed # the captured output starting with the characters '^D': # # $ script -q capture.txt echo example </dev/null # example # $ xxd capture.txt # 00000000: 5e44 0808 6578 616d 706c 650d 0a ^D..example.. # # I'm not sure why this is here, although I suppose it has to do # with ^D in caret notation signifying end-of-file [1]. What I do # know is that this is an implementation detail that callers of the # capture() function shouldn't be bothered with, so we strip it. # # [1] https://en.wikipedia.org/wiki/End-of-file if output.startswith(b'^D'): output = output[2:] # Clean up backspace and carriage return characters and the 'erase line' # ANSI escape sequence and return the output as a Unicode string. return u'\n'.join(clean_terminal_output(output))
def capture(command, encoding='UTF-8'): """ Capture the output of an external command as if it runs in an interactive terminal. :param command: The command name and its arguments (a list of strings). :param encoding: The encoding to use to decode the output (a string). :returns: The output of the command. This function runs an external command under ``script`` (emulating an interactive terminal) to capture the output of the command as if it was running in an interactive terminal (including ANSI escape sequences). """ with open(os.devnull, 'wb') as dev_null: # We start by invoking the `script' program in a form that is supported # by the Linux implementation [1] but fails command line validation on # the MacOS (BSD) implementation [2]: The command is specified using # the -c option and the typescript file is /dev/null. # # [1] http://man7.org/linux/man-pages/man1/script.1.html # [2] https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/script.1.html command_line = ['script', '-qc', ' '.join(map(pipes.quote, command)), '/dev/null'] script = subprocess.Popen(command_line, stdout=subprocess.PIPE, stderr=dev_null) stdout, stderr = script.communicate() if script.returncode == 0: # If `script' succeeded we assume that it understood our command line # invocation which means it's the Linux implementation (in this case # we can use standard output instead of a temporary file). output = stdout.decode(encoding) else: # If `script' failed we assume that it didn't understand our command # line invocation which means it's the MacOS (BSD) implementation # (in this case we need a temporary file because the command line # interface requires it). fd, temporary_file = tempfile.mkstemp(prefix='coloredlogs-', suffix='-capture.txt') try: command_line = ['script', '-q', temporary_file] + list(command) subprocess.Popen(command_line, stdout=dev_null, stderr=dev_null).wait() with codecs.open(temporary_file, 'r', encoding) as handle: output = handle.read() finally: os.unlink(temporary_file) # On MacOS when standard input is /dev/null I've observed # the captured output starting with the characters '^D': # # $ script -q capture.txt echo example </dev/null # example # $ xxd capture.txt # 00000000: 5e44 0808 6578 616d 706c 650d 0a ^D..example.. # # I'm not sure why this is here, although I suppose it has to do # with ^D in caret notation signifying end-of-file [1]. What I do # know is that this is an implementation detail that callers of the # capture() function shouldn't be bothered with, so we strip it. # # [1] https://en.wikipedia.org/wiki/End-of-file if output.startswith(b'^D'): output = output[2:] # Clean up backspace and carriage return characters and the 'erase line' # ANSI escape sequence and return the output as a Unicode string. return u'\n'.join(clean_terminal_output(output))
def capture(command, encoding='UTF-8'): """ Capture the output of an external command as if it runs in an interactive terminal. :param command: The command name and its arguments (a list of strings). :param encoding: The encoding to use to decode the output (a string). :returns: The output of the command. This function runs an external command under ``script`` (emulating an interactive terminal) to capture the output of the command as if it was running in an interactive terminal (including ANSI escape sequences). """ with open(os.devnull, 'wb') as dev_null: # We start by invoking the `script' program in a form that is supported # by the Linux implementation [1] but fails command line validation on # the Mac OS X (BSD) implementation [2]: The command is specified # using the -c option and the typescript file is /dev/null. # # [1] http://man7.org/linux/man-pages/man1/script.1.html # [2] https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/script.1.html command_line = [ 'script', '-qc', ' '.join(map(pipes.quote, command)), '/dev/null' ] script = subprocess.Popen(command_line, stdout=subprocess.PIPE, stderr=dev_null) stdout, stderr = script.communicate() if script.returncode == 0: # If `script' succeeded we assume that it understood our command line # invocation which means it's the Linux implementation (in this case # we can use standard output instead of a temporary file). output = stdout.decode(encoding) else: # If `script' failed we assume that it didn't understand our command # line invocation which means it's the Mac OS X (BSD) implementation # (in this case we need a temporary file because the command line # interface requires it). fd, temporary_file = tempfile.mkstemp(prefix='coloredlogs-', suffix='-capture.txt') try: command_line = ['script', '-q', temporary_file] + list(command) subprocess.Popen(command_line, stdout=dev_null, stderr=dev_null).wait() with codecs.open(temporary_file, 'r', encoding) as handle: output = handle.read() finally: os.unlink(temporary_file) # Clean up backspace and carriage return characters and the 'erase line' # ANSI escape sequence and return the output as a Unicode string. return u'\n'.join(clean_terminal_output(output))
def get_text(self, interpreted=True, partial=PARTIAL_DEFAULT): """get_text(interpreted=True, partial=False) Get the captured output as a single string. :param interpreted: If :data:`True` (the default) captured output is processed using :func:`clean_terminal_output()`. :param partial: Refer to :func:`get_handle()` for details. :returns: The captured output as a Unicode string. .. warning:: If partial is :data:`True` (not the default) the output can end in a partial line, possibly in the middle of a multi byte character (this may cause decoding errors). """ output = self.get_bytes(partial) output = output.decode(self.encoding) if interpreted: output = u'\n'.join(clean_terminal_output(output)) return output
def capture(command, encoding='UTF-8'): """ Capture the output of an external command as if it runs in an interactive terminal. :param command: The command name and its arguments (a list of strings). :param encoding: The encoding to use to decode the output (a string). :returns: The output of the command. This function runs an external command under ``script`` (emulating an interactive terminal) to capture the output of the command as if it was running in an interactive terminal (including ANSI escape sequences). """ with open(os.devnull, 'wb') as dev_null: # We start by invoking the `script' program in a form that is supported # by the Linux implementation [1] but fails command line validation on # the Mac OS X (BSD) implementation [2]: The command is specified # using the -c option and the typescript file is /dev/null. # # [1] http://man7.org/linux/man-pages/man1/script.1.html # [2] https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/script.1.html command_line = ['script', '-qc', ' '.join(map(pipes.quote, command)), '/dev/null'] script = subprocess.Popen(command_line, stdout=subprocess.PIPE, stderr=dev_null) stdout, stderr = script.communicate() if script.returncode == 0: # If `script' succeeded we assume that it understood our command line # invocation which means it's the Linux implementation (in this case # we can use standard output instead of a temporary file). output = stdout.decode(encoding) else: # If `script' failed we assume that it didn't understand our command # line invocation which means it's the Mac OS X (BSD) implementation # (in this case we need a temporary file because the command line # interface requires it). fd, temporary_file = tempfile.mkstemp(prefix='coloredlogs-', suffix='-capture.txt') try: command_line = ['script', '-q', temporary_file] + list(command) subprocess.Popen(command_line, stdout=dev_null, stderr=dev_null).wait() with codecs.open(temporary_file, 'r', encoding) as handle: output = handle.read() finally: os.unlink(temporary_file) # Clean up backspace and carriage return characters and the 'erase line' # ANSI escape sequence and return the output as a Unicode string. return u'\n'.join(clean_terminal_output(output))
def test_clean_output(self): """Test :func:`humanfriendly.terminal.clean_terminal_output()`.""" # Simple output should pass through unharmed (single line). assert clean_terminal_output('foo') == ['foo'] # Simple output should pass through unharmed (multiple lines). assert clean_terminal_output('foo\nbar') == ['foo', 'bar'] # Carriage returns and preceding substrings are removed. assert clean_terminal_output('foo\rbar\nbaz') == ['bar', 'baz'] # Carriage returns move the cursor to the start of the line without erasing text. assert clean_terminal_output('aaa\rab') == ['aba'] # Backspace moves the cursor one position back without erasing text. assert clean_terminal_output('aaa\b\bb') == ['aba'] # Trailing empty lines should be stripped. assert clean_terminal_output('foo\nbar\nbaz\n\n\n') == ['foo', 'bar', 'baz']