def __init__(self, encoding=None, out=None): """Constructor. Args: encoding: Encoding override. ascii -- ASCII art. This is the default. utf8 -- UTF-8 unicode. win -- Windows code page 437. out: The console output file stream, log.out if None. """ self._out = out or log.out # Normalize the encoding name. if not encoding: encoding = 'ascii' if hasattr(self._out, 'encoding') and self._out.encoding: console_encoding = self._out.encoding.lower() if 'utf-8' in console_encoding: encoding = 'utf8' elif 'cp437' in console_encoding: encoding = 'win' self._encoding = encoding self._term = os.getenv('TERM', '').lower() # ANSI "standard" attributes. if self._encoding != 'ascii' and ('screen' in self._term or 'xterm' in self._term): # Select Graphic Rendition paramaters from # http://en.wikipedia.org/wiki/ANSI_escape_code#graphics # Italic '3' would be nice here but its not widely supported. self._csi = '\x1b[' self._font_bold = '1' self._font_italic = '4' else: self._csi = None self._font_bold = '' self._font_italic = '' # Encoded character attributes. if self._encoding == 'utf8': self._box_line_characters = BoxLineCharactersUtf8() self._bullets = self._BULLETS_UTF8 elif self._encoding == 'win': self._box_line_characters = BoxLineCharactersWindows() self._bullets = self._BULLETS_WINDOWS else: self._box_line_characters = BoxLineCharactersAscii() self._bullets = self._BULLETS_ASCII # OS specific attributes. self._get_raw_char = [console_attr_os.GetRawCharFunction()] self._term_size = console_attr_os.GetTermSize()
def __init__(self, encoding=None): """Constructor. Args: encoding: Encoding override. ascii -- ASCII art. This is the default. utf8 -- UTF-8 unicode. win -- Windows code page 437. """ # Normalize the encoding name. if not encoding: encoding = self._GetConsoleEncoding() elif encoding == 'win': encoding = 'cp437' self._encoding = encoding or 'ascii' self._term = os.getenv('TERM', '').lower() # ANSI "standard" attributes. if self._encoding != 'ascii' and ('screen' in self._term or 'xterm' in self._term): # Select Graphic Rendition paramaters from # http://en.wikipedia.org/wiki/ANSI_escape_code#graphics # Italic '3' would be nice here but its not widely supported. self._csi = '\x1b[' self._font_bold = '1' self._font_italic = '4' else: self._csi = None self._font_bold = '' self._font_italic = '' # Encoded character attributes. if self._encoding == 'utf8': self._box_line_characters = BoxLineCharactersUnicode() self._bullets = self._BULLETS_UNICODE self._progress_tracker_symbols = ProgressTrackerSymbolsUnicode() elif self._encoding == 'cp437': self._box_line_characters = BoxLineCharactersUnicode() self._bullets = self._BULLETS_WINDOWS # Windows does not suport the unicode characters used for the spinner. self._progress_tracker_symbols = ProgressTrackerSymbolsAscii() else: self._box_line_characters = BoxLineCharactersAscii() self._bullets = self._BULLETS_ASCII self._progress_tracker_symbols = ProgressTrackerSymbolsAscii() # OS specific attributes. self._get_raw_key = [console_attr_os.GetRawKeyFunction()] self._term_size = console_attr_os.GetTermSize() self._display_width_cache = {}
def GetDivider(text=''): """Return a console-width divider (ex: '======================' (etc.)). Supports messages (ex: '=== Messsage Here ==='). Args: text: str, a message to display centered in the divider. Returns: str, the formatted divider """ if text: text = ' ' + text + ' ' width, _ = console_attr_os.GetTermSize() return text.center(width, '=')
def _FormatNonAsciiMarkerString(args): r"""Format a string that will mark the first non-ASCII character it contains. Example: >>> args = ['command.py', '--foo=\xce\x94'] >>> _FormatNonAsciiMarkerString(args) == ( ... 'command.py --foo=\u0394\n' ... ' ^ invalid character' ... ) True Args: args: The arg list for the command executed Returns: unicode, a properly formatted string with two lines, the second of which indicates the non-ASCII character in the first. Raises: ValueError: if the given string is all ASCII characters """ # pos is the position of the first non-ASCII character in ' '.join(args) pos = 0 for arg in args: first_non_ascii_index = _NonAsciiIndex(arg) if first_non_ascii_index >= 0: pos += first_non_ascii_index break # this arg was all ASCII; add 1 for the ' ' between args pos += len(arg) + 1 else: raise ValueError( 'The command line is composed entirely of ASCII characters.') # Make a string that, when printed in parallel, will point to the non-ASCII # character marker_string = ' ' * pos + _MARKER # Make sure that this will still print out nicely on an odd-sized screen align = len(marker_string) args_string = ' '.join([console_attr.SafeText(arg) for arg in args]) width, _ = console_attr_os.GetTermSize() fill = '...' if width < len(_MARKER) + len(fill): # It's hopeless to try to wrap this and make it look nice. Preserve it in # full for logs and so on. return '\n'.join((args_string, marker_string)) # If len(args_string) < width < len(marker_string) (ex:) # # args_string = 'command BAD' # marker_string = ' ^ invalid character' # width = len('----------------') # # then the truncation can give a result like the following: # # args_string = 'command BAD' # marker_string = ' ^ invalid character' # # (This occurs when args_string is short enough to not be truncated, but # marker_string is long enough to be truncated.) # # ljust args_string to make it as long as marker_string before passing to # _TruncateToLineWidth, which will yield compatible truncations. rstrip at the # end to get rid of the new trailing spaces. formatted_args_string = _TruncateToLineWidth(args_string.ljust(align), align, width, fill=fill).rstrip() formatted_marker_string = _TruncateToLineWidth(marker_string, align, width) return '\n'.join((formatted_args_string, formatted_marker_string))
def Build(self, docker_client): """Calls "docker build" command. Args: docker_client: instance of docker.Client connected to a Docker daemon. Raises: ImageBuildError: if the image could not be built. """ log.info('Building docker image %s from %s/Dockerfile:', self.tag, self._dockerfile_dir) width, _ = console_attr_os.GetTermSize() log.status.Print( DOCKER_OUTPUT_BEGIN.center(width, DOCKER_OUTPUT_LINE_CHAR)) build_res = docker_client.build(path=self._dockerfile_dir, tag=self.tag, quiet=False, fileobj=None, nocache=self._nocache, rm=self._rm, pull=False) info = None error = None error_detail = None log_records = [] try: for line in build_res: line = line.strip() if not line: continue log_record = json.loads(line) log_records.append(log_record) if 'stream' in log_record: info = log_record['stream'].strip() log.status.Print(info) if 'error' in log_record: error = log_record['error'].strip() # will be logged to log.error in the thrown exception log.status.Print(error) if 'errorDetail' in log_record: error_detail = log_record['errorDetail']['message'].strip() log.status.Print(error_detail) except docker.errors.APIError as e: log.error(e.explanation) error = e.explanation error_detail = '' finally: log.status.Print(DOCKER_OUTPUT_LINE_CHAR * width + '\n') if not log_records: raise ImageBuildError( 'Error building docker image {0} [with no output]'.format( self.tag)) success_message = log_records[-1].get(_STREAM) if success_message: m = _SUCCESSFUL_BUILD_PATTERN.match(success_message) if m: # The build was successful. self._id = m.group(1) log.info('Image %s built, id = %s', self.tag, self.id) return raise ImageBuildError('Docker build aborted: ' + error)
def _PrintLastLine(self, msg=''): """Print a pretty ending line to identify end of build output logs.""" width, _ = console_attr_os.GetTermSize() # We print an extra blank visually separating the log from other output. self._PrintLogLine(msg.center(width, self.OUTPUT_LINE_CHAR) + '\n')
def _PrintFirstLine(self): """Print a pretty starting line to identify start of build output logs.""" width, _ = console_attr_os.GetTermSize() self._PrintLogLine( self.LOG_OUTPUT_BEGIN.center(width, self.OUTPUT_LINE_CHAR))
def __init__(self, encoding=None, term=None, suppress_output=False): """Constructor. Args: encoding: Encoding override. ascii -- ASCII art. This is the default. utf8 -- UTF-8 unicode. win -- Windows code page 437. term: Terminal override. Replaces the value of ENV['TERM']. suppress_output: True to create a ConsoleAttr that doesn't want to output anything. """ # Normalize the encoding name. if not encoding: encoding = self._GetConsoleEncoding() elif encoding == 'win': encoding = 'cp437' self._encoding = encoding or 'ascii' if suppress_output: self._term = '' elif term: self._term = term else: self._term = encoding_util.GetEncodedValue(os.environ, 'TERM', '').lower() # ANSI "standard" attributes. if self.SupportsAnsi(): # Select Graphic Rendition paramaters from # http://en.wikipedia.org/wiki/ANSI_escape_code#graphics # Italic '3' would be nice here but its not widely supported. self._csi = '\x1b[' self._font_bold = '1' self._font_italic = '4' else: self._csi = None self._font_bold = '' self._font_italic = '' # Encoded character attributes. is_screen_reader = properties.VALUES.accessibility.screen_reader.GetBool( ) if self._encoding == 'utf-8' and not is_screen_reader: self._box_line_characters = BoxLineCharactersUnicode() self._bullets = self._BULLETS_UNICODE self._progress_tracker_symbols = ProgressTrackerSymbolsUnicode() elif self._encoding == 'cp437' and not is_screen_reader: self._box_line_characters = BoxLineCharactersUnicode() self._bullets = self._BULLETS_WINDOWS # Windows does not suport the unicode characters used for the spinner. self._progress_tracker_symbols = ProgressTrackerSymbolsAscii() else: self._box_line_characters = BoxLineCharactersAscii() if is_screen_reader: self._box_line_characters = BoxLineCharactersScreenReader() self._bullets = self._BULLETS_ASCII self._progress_tracker_symbols = ProgressTrackerSymbolsAscii() # OS specific attributes. self._get_raw_key = [console_attr_os.GetRawKeyFunction()] self._term_size = ((0, 0) if suppress_output else console_attr_os.GetTermSize()) self._display_width_cache = {}
def testGetTermsizeTput(self): columns, lines = console_attr_os.GetTermSize() self.assertEqual((84, 28), (columns, lines))
def testGetTermsizeEnvironment(self): columns, lines = console_attr_os.GetTermSize() self.assertEqual((83, 27), (columns, lines))
def testGetTermsizeWindowsNoScreenBuffer(self): self.imports.SetImport('ctypes', self.MockCtypesModuleNoScreenBuffer) columns, lines = console_attr_os.GetTermSize() self.assertEqual((80, 24), (columns, lines))
def testGetTermsizePosix(self): columns, lines = console_attr_os.GetTermSize() self.assertEqual((81, 25), (columns, lines))
def _FormatNonAsciiMarkerString(args_string): u"""Format a string that will mark the first non-ASCII character it contains. Example: >>> args = 'command.py --foo=\xce\x94' >>> _FormatNonAsciiMarkerString(args) == ( ... 'command.py --foo=\u0394\n' ... ' ^ invalid character' ... ) True Args: args_string: str representing the command executed Returns: unicode, a properly formatted string with two lines, the second of which indicates the non-ASCII character in the first. Raises: ValueError: if the given string is all ASCII characters """ # idx is the first index of the first non-ASCII character in args_string idx = None for idx, char in enumerate(args_string): try: char.decode('ascii') # idx gets set by enumerate; unset it to indicate that the last character # was successfully decoded as ASCII idx = None except UnicodeError: # idx will remain set, indicating the first non-ASCII character break if idx is None: raise ValueError( 'Given string is composed entirely of ASCII characters.') # Make a string that, when printed in parallel, will point to the non-ASCII # character marker_string = ' ' * idx + _MARKER # Make sure that this will still print out nicely on an odd-sized screen align = len(marker_string) args_string = args_string.decode('utf-8', 'replace') width, _ = console_attr_os.GetTermSize() fill = '...' if width < len(_MARKER) + len(fill): # It's hopeless to try to wrap this and make it look nice. Preserve it in # full for logs and so on. return '\n'.join((args_string, marker_string)) # If len(args_string) < width < len(marker_string) (ex:) # # args_string = 'command BAD' # marker_string = ' ^ invalid character' # width = len('----------------') # # then the truncation can give a result like the following: # # args_string = 'command BAD' # marker_string = ' ^ invalid character' # # (This occurs when args_string is short enough to not be truncated, but # marker_string is long enough to be truncated.) # # ljust args_string to make it as long as marker_string before passing to # _TruncateToLineWidth, which will yield compatible truncations. rstrip at the # end to get rid of the new trailing spaces. formatted_args_string = _TruncateToLineWidth(args_string.ljust(align), align, width, fill=fill).rstrip() formatted_marker_string = _TruncateToLineWidth(marker_string, align, width) return '\n'.join((formatted_args_string, formatted_marker_string))
def _PrintFirstLine(self): width, _ = console_attr_os.GetTermSize() self._PrintLogLine( self.LOG_OUTPUT_BEGIN.center(width, self.OUTPUT_LINE_CHAR))
def testGetTermsizeFallBack(self): columns, lines = console_attr_os.GetTermSize() self.assertEqual((80, 24), (columns, lines))
def _PrintLastLine(self, msg=''): width, _ = console_attr_os.GetTermSize() # We print an extra blank visually separating the log from other output. self._PrintLogLine(msg.center(width, self.OUTPUT_LINE_CHAR) + '\n')
def Execute(self, args=None, call_arg_complete=True): """Execute the CLI tool with the given arguments. Args: args: [str], The arguments from the command line or None to use sys.argv call_arg_complete: Call the _ArgComplete function if True Returns: The result of executing the command determined by the command implementation. Raises: ValueError: for ill-typed arguments. """ if isinstance(args, basestring): raise ValueError( 'Execute expects an iterable of strings, not a string.') # The argparse module does not handle unicode args when run in Python 2 # because it uses str(x) even when type(x) is unicode. This sets itself up # for failure because it converts unicode strings back to byte strings which # will trigger ASCII codec exceptions. It works in Python 3 because str() is # equivalent to unicode() in Python 3. The next Pythonically magic and dirty # statement coaxes the Python 3 behavior out of argparse running in # Python 2. Doing it here ensures that the workaround is in place for # calliope argparse use cases. argparse.str = unicode if call_arg_complete: _ArgComplete(self.__top_element.ai) if not args: args = sys.argv[1:] # help ... is the same as ... --help or ... --document=style=help. We use # --document=style=help to signal the metrics.Help() 'help' label in # actions.RenderDocumentAction().Action(). It doesn't matter if we append # to a command that already has --help or --document=style=... because the # first --help/--document from the left takes effect. Note that # `help ... --help` produces help for the help command itself. if args and args[0] == 'help' and '--help' not in args: args = args[1:] + ['--document=style=help'] # Look for a --configuration flag and update property state based on # that before proceeding to the main argparse parse step. named_configs.FLAG_OVERRIDE_STACK.PushFromArgs(args) properties.VALUES.PushInvocationValues() # Set the command name in case an exception happens before the command name # is finished parsing. command_path_string = self.__name specified_arg_names = None argv = self._ConvertNonAsciiArgsToUnicode(args) old_user_output_enabled = None old_verbosity = None try: args = self.__parser.parse_args(argv) command_path_string = '.'.join(args.command_path) if not args.calliope_command.IsUnicodeSupported(): self._EnforceAsciiArgs(argv) specified_arg_names = args.GetSpecifiedArgNames() # -h|--help|--document are dispatched by parse_args and never get here. # Now that we have parsed the args, reload the settings so the flags will # take effect. These will use the values from the properties. old_user_output_enabled = log.SetUserOutputEnabled(None) old_verbosity = log.SetVerbosity(None) # Set the command_name property so it is persisted until the process ends. # Only do this for the top level command that can be detected by looking # at the stack. It will have one initial level, and another level added by # the PushInvocationValues earlier in this method. if len(properties.VALUES.GetInvocationStack()) == 2: properties.VALUES.metrics.command_name.Set(command_path_string) # Set the invocation value for all commands, this is lost when popped properties.VALUES.SetInvocationValue( properties.VALUES.metrics.command_name, command_path_string, None) if properties.VALUES.core.capture_session_file.Get() is not None: capturer = session_capturer.SessionCapturer() capturer.CaptureArgs(args) # Create a new unique random seed: the state is different each run and # hashes will be different with high probability random_seed = hash(random.getstate()) random.seed(random_seed) capturer.CaptureState( interactive_console=console_io.IsInteractive(), random_seed=random_seed, term_size=console_attr_os.GetTermSize()) capturer.CaptureProperties(properties.VALUES.AllValues()) session_capturer.SessionCapturer.capturer = capturer for hook in self.__pre_run_hooks: hook.Run(command_path_string) resources = args.calliope_command.Run(cli=self, args=args) for hook in self.__post_run_hooks: hook.Run(command_path_string) # Preserve generator or static list resources. if isinstance(resources, types.GeneratorType): def _Yield(): """Activates generator exceptions.""" try: for resource in resources: yield resource except Exception as exc: # pylint: disable=broad-except self._HandleAllErrors(exc, command_path_string, specified_arg_names) return _Yield() # Do this last. If there is an error, the error handler will log the # command execution along with the error. metrics.Commands(command_path_string, config.CLOUD_SDK_VERSION, specified_arg_names) return resources except Exception as exc: # pylint: disable=broad-except self._HandleAllErrors(exc, command_path_string, specified_arg_names) finally: if session_capturer.SessionCapturer.capturer is not None: with open(properties.VALUES.core.capture_session_file.Get(), 'w') as f: session_capturer.SessionCapturer.capturer.Print(f) properties.VALUES.PopInvocationValues() named_configs.FLAG_OVERRIDE_STACK.Pop() # Reset these values to their previous state now that we popped the flag # values. if old_user_output_enabled is not None: log.SetUserOutputEnabled(old_user_output_enabled) if old_verbosity is not None: log.SetVerbosity(old_verbosity)