|  | # Copyright 2014 Google Inc. | 
|  | # | 
|  | # Use of this source code is governed by a BSD-style license that can be | 
|  | # found in the LICENSE file. | 
|  |  | 
|  |  | 
|  | """Module to host the VerboseSubprocess, ChangeDir, and ReSearch classes. | 
|  | """ | 
|  |  | 
|  | import os | 
|  | import re | 
|  | import subprocess | 
|  |  | 
|  |  | 
|  | def print_subprocess_args(prefix, *args, **kwargs): | 
|  | """Print out args in a human-readable manner.""" | 
|  | def quote_and_escape(string): | 
|  | """Quote and escape a string if necessary.""" | 
|  | if ' ' in string or '\n' in string: | 
|  | string = '"%s"' % string.replace('"', '\\"') | 
|  | return string | 
|  | if 'cwd' in kwargs: | 
|  | print '%scd %s' % (prefix, kwargs['cwd']) | 
|  | print prefix + ' '.join(quote_and_escape(arg) for arg in args[0]) | 
|  | if 'cwd' in kwargs: | 
|  | print '%scd -' % prefix | 
|  |  | 
|  |  | 
|  | class VerboseSubprocess(object): | 
|  | """Call subprocess methods, but print out command before executing. | 
|  |  | 
|  | Attributes: | 
|  | verbose: (boolean) should we print out the command or not.  If | 
|  | not, this is the same as calling the subprocess method | 
|  | quiet: (boolean) suppress stdout on check_call and call. | 
|  | prefix: (string) When verbose, what to print before each command. | 
|  | """ | 
|  |  | 
|  | def __init__(self, verbose): | 
|  | self.verbose = verbose | 
|  | self.quiet = not verbose | 
|  | self.prefix = '~~$ ' | 
|  |  | 
|  | def check_call(self, *args, **kwargs): | 
|  | """Wrapper for subprocess.check_call(). | 
|  |  | 
|  | Args: | 
|  | *args: to be passed to subprocess.check_call() | 
|  | **kwargs: to be passed to subprocess.check_call() | 
|  | Returns: | 
|  | Whatever subprocess.check_call() returns. | 
|  | Raises: | 
|  | OSError or subprocess.CalledProcessError: raised by check_call. | 
|  | """ | 
|  | if self.verbose: | 
|  | print_subprocess_args(self.prefix, *args, **kwargs) | 
|  | if self.quiet: | 
|  | with open(os.devnull, 'w') as devnull: | 
|  | return subprocess.check_call(*args, stdout=devnull, **kwargs) | 
|  | else: | 
|  | return subprocess.check_call(*args, **kwargs) | 
|  |  | 
|  | def call(self, *args, **kwargs): | 
|  | """Wrapper for subprocess.check(). | 
|  |  | 
|  | Args: | 
|  | *args: to be passed to subprocess.check_call() | 
|  | **kwargs: to be passed to subprocess.check_call() | 
|  | Returns: | 
|  | Whatever subprocess.call() returns. | 
|  | Raises: | 
|  | OSError or subprocess.CalledProcessError: raised by call. | 
|  | """ | 
|  | if self.verbose: | 
|  | print_subprocess_args(self.prefix, *args, **kwargs) | 
|  | if self.quiet: | 
|  | with open(os.devnull, 'w') as devnull: | 
|  | return subprocess.call(*args, stdout=devnull, **kwargs) | 
|  | else: | 
|  | return subprocess.call(*args, **kwargs) | 
|  |  | 
|  | def check_output(self, *args, **kwargs): | 
|  | """Wrapper for subprocess.check_output(). | 
|  |  | 
|  | Args: | 
|  | *args: to be passed to subprocess.check_output() | 
|  | **kwargs: to be passed to subprocess.check_output() | 
|  | Returns: | 
|  | Whatever subprocess.check_output() returns. | 
|  | Raises: | 
|  | OSError or subprocess.CalledProcessError: raised by check_output. | 
|  | """ | 
|  | if self.verbose: | 
|  | print_subprocess_args(self.prefix, *args, **kwargs) | 
|  | return subprocess.check_output(*args, **kwargs) | 
|  |  | 
|  | def strip_output(self, *args, **kwargs): | 
|  | """Wrap subprocess.check_output and str.strip(). | 
|  |  | 
|  | Pass the given arguments into subprocess.check_output() and return | 
|  | the results, after stripping any excess whitespace. | 
|  |  | 
|  | Args: | 
|  | *args: to be passed to subprocess.check_output() | 
|  | **kwargs: to be passed to subprocess.check_output() | 
|  |  | 
|  | Returns: | 
|  | The output of the process as a string without leading or | 
|  | trailing whitespace. | 
|  | Raises: | 
|  | OSError or subprocess.CalledProcessError: raised by check_output. | 
|  | """ | 
|  | if self.verbose: | 
|  | print_subprocess_args(self.prefix, *args, **kwargs) | 
|  | return str(subprocess.check_output(*args, **kwargs)).strip() | 
|  |  | 
|  | def popen(self, *args, **kwargs): | 
|  | """Wrapper for subprocess.Popen(). | 
|  |  | 
|  | Args: | 
|  | *args: to be passed to subprocess.Popen() | 
|  | **kwargs: to be passed to subprocess.Popen() | 
|  | Returns: | 
|  | The output of subprocess.Popen() | 
|  | Raises: | 
|  | OSError or subprocess.CalledProcessError: raised by Popen. | 
|  | """ | 
|  | if self.verbose: | 
|  | print_subprocess_args(self.prefix, *args, **kwargs) | 
|  | return subprocess.Popen(*args, **kwargs) | 
|  |  | 
|  |  | 
|  | class ChangeDir(object): | 
|  | """Use with a with-statement to temporarily change directories.""" | 
|  | # pylint: disable=I0011,R0903 | 
|  |  | 
|  | def __init__(self, directory, verbose=False): | 
|  | self._directory = directory | 
|  | self._verbose = verbose | 
|  |  | 
|  | def __enter__(self): | 
|  | if self._directory != os.curdir: | 
|  | if self._verbose: | 
|  | print '~~$ cd %s' % self._directory | 
|  | cwd = os.getcwd() | 
|  | os.chdir(self._directory) | 
|  | self._directory = cwd | 
|  |  | 
|  | def __exit__(self, etype, value, traceback): | 
|  | if self._directory != os.curdir: | 
|  | if self._verbose: | 
|  | print '~~$ cd %s' % self._directory | 
|  | os.chdir(self._directory) | 
|  |  | 
|  |  | 
|  | class ReSearch(object): | 
|  | """A collection of static methods for regexing things.""" | 
|  |  | 
|  | @staticmethod | 
|  | def search_within_stream(input_stream, pattern, default=None): | 
|  | """Search for regular expression in a file-like object. | 
|  |  | 
|  | Opens a file for reading and searches line by line for a match to | 
|  | the regex and returns the parenthesized group named return for the | 
|  | first match.  Does not search across newlines. | 
|  |  | 
|  | For example: | 
|  | pattern = '^root(:[^:]*){4}:(?P<return>[^:]*)' | 
|  | with open('/etc/passwd', 'r') as stream: | 
|  | return search_within_file(stream, pattern) | 
|  | should return root's home directory (/root on my system). | 
|  |  | 
|  | Args: | 
|  | input_stream: file-like object to be read | 
|  | pattern: (string) to be passed to re.compile | 
|  | default: what to return if no match | 
|  |  | 
|  | Returns: | 
|  | A string or whatever default is | 
|  | """ | 
|  | pattern_object = re.compile(pattern) | 
|  | for line in input_stream: | 
|  | match = pattern_object.search(line) | 
|  | if match: | 
|  | return match.group('return') | 
|  | return default | 
|  |  | 
|  | @staticmethod | 
|  | def search_within_string(input_string, pattern, default=None): | 
|  | """Search for regular expression in a string. | 
|  |  | 
|  | Args: | 
|  | input_string: (string) to be searched | 
|  | pattern: (string) to be passed to re.compile | 
|  | default: what to return if no match | 
|  |  | 
|  | Returns: | 
|  | A string or whatever default is | 
|  | """ | 
|  | match = re.search(pattern, input_string) | 
|  | return match.group('return') if match else default | 
|  |  | 
|  | @staticmethod | 
|  | def search_within_output(verbose, pattern, default, *args, **kwargs): | 
|  | """Search for regular expression in a process output. | 
|  |  | 
|  | Does not search across newlines. | 
|  |  | 
|  | Args: | 
|  | verbose: (boolean) shoule we call print_subprocess_args? | 
|  | pattern: (string) to be passed to re.compile | 
|  | default: what to return if no match | 
|  | *args: to be passed to subprocess.Popen() | 
|  | **kwargs: to be passed to subprocess.Popen() | 
|  |  | 
|  | Returns: | 
|  | A string or whatever default is | 
|  | """ | 
|  | if verbose: | 
|  | print_subprocess_args('~~$ ', *args, **kwargs) | 
|  | proc = subprocess.Popen(*args, stdout=subprocess.PIPE, **kwargs) | 
|  | return ReSearch.search_within_stream(proc.stdout, pattern, default) | 
|  |  | 
|  |  |