1# -*- coding: utf-8 -*- # 2 3# Copyright 2015 Google LLC. All Rights Reserved. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17r"""A module for console attributes, special characters and functions. 18 19The target architectures {linux, macos, windows} support inline encoding for 20all attributes except color. Windows requires win32 calls to manipulate the 21console color state. 22 23Usage: 24 25 # Get the console attribute state. 26 out = log.out 27 con = console_attr.GetConsoleAttr(out=out) 28 29 # Get the ISO 8879:1986//ENTITIES Box and Line Drawing characters. 30 box = con.GetBoxLineCharacters() 31 # Print an X inside a box. 32 out.write(box.dr) 33 out.write(box.h) 34 out.write(box.dl) 35 out.write('\n') 36 out.write(box.v) 37 out.write('X') 38 out.write(box.v) 39 out.write('\n') 40 out.write(box.ur) 41 out.write(box.h) 42 out.write(box.ul) 43 out.write('\n') 44 45 # Print the bullet characters. 46 for c in con.GetBullets(): 47 out.write(c) 48 out.write('\n') 49 50 # Print FAIL in red. 51 out.write('Epic ') 52 con.Colorize('FAIL', 'red') 53 out.write(', my first.') 54 55 # Print italic and bold text. 56 bold = con.GetFontCode(bold=True) 57 italic = con.GetFontCode(italic=True) 58 normal = con.GetFontCode() 59 out.write('This is {bold}bold{normal}, this is {italic}italic{normal},' 60 ' and this is normal.\n'.format(bold=bold, italic=italic, 61 normal=normal)) 62 63 # Read one character from stdin with echo disabled. 64 c = con.GetRawKey() 65 if c is None: 66 print 'EOF\n' 67 68 # Return the display width of a string that may contain FontCode() chars. 69 display_width = con.DisplayWidth(string) 70 71 # Reset the memoized state. 72 con = console_attr.ResetConsoleAttr() 73 74 # Print the console width and height in characters. 75 width, height = con.GetTermSize() 76 print 'width={width}, height={height}'.format(width=width, height=height) 77 78 # Colorize table data cells. 79 fail = console_attr.Colorizer('FAIL', 'red') 80 pass = console_attr.Colorizer('PASS', 'green') 81 cells = ['label', fail, 'more text', pass, 'end'] 82 for cell in cells; 83 if isinstance(cell, console_attr.Colorizer): 84 cell.Render() 85 else: 86 out.write(cell) 87""" 88 89 90from __future__ import absolute_import 91from __future__ import division 92from __future__ import unicode_literals 93 94import locale 95import os 96import sys 97import unicodedata 98 99from googlecloudsdk.core import properties 100from googlecloudsdk.core.console import console_attr_os 101from googlecloudsdk.core.console.style import text 102from googlecloudsdk.core.util import encoding as encoding_util 103 104import six 105 106 107# TODO(b/123522546): Unify this logic with console.style.mappings 108class BoxLineCharacters(object): 109 """Box/line drawing characters. 110 111 The element names are from ISO 8879:1986//ENTITIES Box and Line Drawing//EN: 112 http://www.w3.org/2003/entities/iso8879doc/isobox.html 113 """ 114 115 116class BoxLineCharactersUnicode(BoxLineCharacters): 117 """unicode Box/line drawing characters (cp437 compatible unicode).""" 118 dl = '┐' 119 dr = '┌' 120 h = '─' 121 hd = '┬' 122 hu = '┴' 123 ul = '┘' 124 ur = '└' 125 v = '│' 126 vh = '┼' 127 vl = '┤' 128 vr = '├' 129 d_dl = '╗' 130 d_dr = '╔' 131 d_h = '═' 132 d_hd = '╦' 133 d_hu = '╩' 134 d_ul = '╝' 135 d_ur = '╚' 136 d_v = '║' 137 d_vh = '╬' 138 d_vl = '╣' 139 d_vr = '╠' 140 141 142class BoxLineCharactersAscii(BoxLineCharacters): 143 """ASCII Box/line drawing characters.""" 144 dl = '+' 145 dr = '+' 146 h = '-' 147 hd = '+' 148 hu = '+' 149 ul = '+' 150 ur = '+' 151 v = '|' 152 vh = '+' 153 vl = '+' 154 vr = '+' 155 d_dl = '#' 156 d_dr = '#' 157 d_h = '=' 158 d_hd = '#' 159 d_hu = '#' 160 d_ul = '#' 161 d_ur = '#' 162 d_v = '#' 163 d_vh = '#' 164 d_vl = '#' 165 d_vr = '#' 166 167 168class BoxLineCharactersScreenReader(BoxLineCharactersAscii): 169 dl = ' ' 170 dr = ' ' 171 hd = ' ' 172 hu = ' ' 173 ul = ' ' 174 ur = ' ' 175 vh = ' ' 176 vl = ' ' 177 vr = ' ' 178 179 180class ProgressTrackerSymbols(object): 181 """Characters used by progress trackers.""" 182 183 184class ProgressTrackerSymbolsUnicode(ProgressTrackerSymbols): 185 """Characters used by progress trackers.""" 186 187 @property 188 def spin_marks(self): 189 return ['⠏', '⠛', '⠹', '⠼', '⠶', '⠧'] 190 191 success = text.TypedText(['✓'], text_type=text.TextTypes.PT_SUCCESS) 192 failed = text.TypedText(['X'], text_type=text.TextTypes.PT_FAILURE) 193 interrupted = '-' 194 not_started = '.' 195 prefix_length = 2 196 197 198class ProgressTrackerSymbolsAscii(ProgressTrackerSymbols): 199 """Characters used by progress trackers.""" 200 201 @property 202 def spin_marks(self): 203 return ['|', '/', '-', '\\',] 204 205 success = 'OK' 206 failed = 'X' 207 interrupted = '-' 208 not_started = '.' 209 prefix_length = 3 210 211 212class ConsoleAttr(object): 213 """Console attribute and special drawing characters and functions accessor. 214 215 Use GetConsoleAttr() to get a global ConsoleAttr object shared by all callers. 216 Use ConsoleAttr() for abstracting multiple consoles. 217 218 If _out is not associated with a console, or if the console properties cannot 219 be determined, the default behavior is ASCII art with no attributes. 220 221 Attributes: 222 _ANSI_COLOR: The ANSI color control sequence dict. 223 _ANSI_COLOR_RESET: The ANSI color reset control sequence string. 224 _csi: The ANSI Control Sequence indicator string, '' if not supported. 225 _encoding: The character encoding. 226 ascii: ASCII art. This is the default. 227 utf8: UTF-8 unicode. 228 win: Windows code page 437. 229 _font_bold: The ANSI bold font embellishment code string. 230 _font_italic: The ANSI italic font embellishment code string. 231 _get_raw_key: A function that reads one keypress from stdin with no echo. 232 _out: The console output file stream. 233 _term: TERM environment variable value. 234 _term_size: The terminal (x, y) dimensions in characters. 235 """ 236 237 _CONSOLE_ATTR_STATE = None 238 239 _ANSI_COLOR = { 240 'red': '31;1m', 241 'yellow': '33;1m', 242 'green': '32m', 243 'blue': '34;1m' 244 } 245 _ANSI_COLOR_RESET = '39;0m' 246 247 _BULLETS_UNICODE = ('▪', '◆', '▸', '▫', '◇', '▹') 248 _BULLETS_WINDOWS = ('■', '≡', '∞', 'Φ', '·') # cp437 compatible unicode 249 _BULLETS_ASCII = ('o', '*', '+', '-') 250 251 def __init__(self, encoding=None, term=None, suppress_output=False): 252 """Constructor. 253 254 Args: 255 encoding: Encoding override. 256 ascii -- ASCII art. This is the default. 257 utf8 -- UTF-8 unicode. 258 win -- Windows code page 437. 259 term: Terminal override. Replaces the value of ENV['TERM']. 260 suppress_output: True to create a ConsoleAttr that doesn't want to output 261 anything. 262 """ 263 # Normalize the encoding name. 264 if not encoding: 265 encoding = self._GetConsoleEncoding() 266 elif encoding == 'win': 267 encoding = 'cp437' 268 self._encoding = encoding or 'ascii' 269 if suppress_output: 270 self._term = '' 271 elif term: 272 self._term = term 273 else: 274 self._term = encoding_util.GetEncodedValue(os.environ, 'TERM', '').lower() 275 276 # ANSI "standard" attributes. 277 if self.SupportsAnsi(): 278 # Select Graphic Rendition paramaters from 279 # http://en.wikipedia.org/wiki/ANSI_escape_code#graphics 280 # Italic '3' would be nice here but its not widely supported. 281 self._csi = '\x1b[' 282 self._font_bold = '1' 283 self._font_italic = '4' 284 else: 285 self._csi = None 286 self._font_bold = '' 287 self._font_italic = '' 288 289 # Encoded character attributes. 290 is_screen_reader = properties.VALUES.accessibility.screen_reader.GetBool() 291 if self._encoding == 'utf-8' and not is_screen_reader: 292 self._box_line_characters = BoxLineCharactersUnicode() 293 self._bullets = self._BULLETS_UNICODE 294 self._progress_tracker_symbols = ProgressTrackerSymbolsUnicode() 295 elif self._encoding == 'cp437' and not is_screen_reader: 296 self._box_line_characters = BoxLineCharactersUnicode() 297 self._bullets = self._BULLETS_WINDOWS 298 # Windows does not suport the unicode characters used for the spinner. 299 self._progress_tracker_symbols = ProgressTrackerSymbolsAscii() 300 else: 301 self._box_line_characters = BoxLineCharactersAscii() 302 if is_screen_reader: 303 self._box_line_characters = BoxLineCharactersScreenReader() 304 self._bullets = self._BULLETS_ASCII 305 self._progress_tracker_symbols = ProgressTrackerSymbolsAscii() 306 307 # OS specific attributes. 308 self._get_raw_key = [console_attr_os.GetRawKeyFunction()] 309 self._term_size = ( 310 (0, 0) if suppress_output else console_attr_os.GetTermSize()) 311 312 self._display_width_cache = {} 313 314 def _GetConsoleEncoding(self): 315 """Gets the encoding as declared by the stdout stream. 316 317 Returns: 318 str, The encoding name or None if it could not be determined. 319 """ 320 console_encoding = getattr(sys.stdout, 'encoding', None) 321 if not console_encoding: 322 return None 323 console_encoding = console_encoding.lower() 324 if 'utf-8' in console_encoding: 325 # use ascii for windows code page 1252 326 locale_encoding = locale.getpreferredencoding() 327 if locale_encoding and 'cp1252' in locale_encoding: 328 return None 329 return 'utf-8' 330 elif 'cp437' in console_encoding: 331 return 'cp437' 332 elif 'cp1252' in console_encoding: 333 return None 334 return None 335 336 def Colorize(self, string, color, justify=None): 337 """Generates a colorized string, optionally justified. 338 339 Args: 340 string: The string to write. 341 color: The color name -- must be in _ANSI_COLOR. 342 justify: The justification function, no justification if None. For 343 example, justify=lambda s: s.center(10) 344 345 Returns: 346 str, The colorized string that can be printed to the console. 347 """ 348 if justify: 349 string = justify(string) 350 if self._csi and color in self._ANSI_COLOR: 351 return '{csi}{color_code}{string}{csi}{reset_code}'.format( 352 csi=self._csi, 353 color_code=self._ANSI_COLOR[color], 354 reset_code=self._ANSI_COLOR_RESET, 355 string=string) 356 return string 357 358 def ConvertOutputToUnicode(self, buf): 359 """Converts a console output string buf to unicode. 360 361 Mainly used for testing. Allows test comparisons in unicode while ensuring 362 that unicode => encoding => unicode works. 363 364 Args: 365 buf: The console output string to convert. 366 367 Returns: 368 The console output string buf converted to unicode. 369 """ 370 if isinstance(buf, six.text_type): 371 buf = buf.encode(self._encoding) 372 return six.text_type(buf, self._encoding, 'replace') 373 374 def GetBoxLineCharacters(self): 375 """Returns the box/line drawing characters object. 376 377 The element names are from ISO 8879:1986//ENTITIES Box and Line Drawing//EN: 378 http://www.w3.org/2003/entities/iso8879doc/isobox.html 379 380 Returns: 381 A BoxLineCharacters object for the console output device. 382 """ 383 return self._box_line_characters 384 385 def GetBullets(self): 386 """Returns the bullet characters list. 387 388 Use the list elements in order for best appearance in nested bullet lists, 389 wrapping back to the first element for deep nesting. The list size depends 390 on the console implementation. 391 392 Returns: 393 A tuple of bullet characters. 394 """ 395 return self._bullets 396 397 def GetProgressTrackerSymbols(self): 398 """Returns the progress tracker characters object. 399 400 Returns: 401 A ProgressTrackerSymbols object for the console output device. 402 """ 403 return self._progress_tracker_symbols 404 405 def GetControlSequenceIndicator(self): 406 """Returns the control sequence indicator string. 407 408 Returns: 409 The conrol sequence indicator string or None if control sequences are not 410 supported. 411 """ 412 return self._csi 413 414 def GetControlSequenceLen(self, buf): 415 """Returns the control sequence length at the beginning of buf. 416 417 Used in display width computations. Control sequences have display width 0. 418 419 Args: 420 buf: The string to check for a control sequence. 421 422 Returns: 423 The conrol sequence length at the beginning of buf or 0 if buf does not 424 start with a control sequence. 425 """ 426 if not self._csi or not buf.startswith(self._csi): 427 return 0 428 n = 0 429 for c in buf: 430 n += 1 431 if c.isalpha(): 432 break 433 return n 434 435 def GetEncoding(self): 436 """Returns the current encoding.""" 437 return self._encoding 438 439 def GetFontCode(self, bold=False, italic=False): 440 """Returns a font code string for 0 or more embellishments. 441 442 GetFontCode() with no args returns the default font code string. 443 444 Args: 445 bold: True for bold embellishment. 446 italic: True for italic embellishment. 447 448 Returns: 449 The font code string for the requested embellishments. Write this string 450 to the console output to control the font settings. 451 """ 452 if not self._csi: 453 return '' 454 codes = [] 455 if bold: 456 codes.append(self._font_bold) 457 if italic: 458 codes.append(self._font_italic) 459 return '{csi}{codes}m'.format(csi=self._csi, codes=';'.join(codes)) 460 461 def Emphasize(self, s, bold=True, italic=False): 462 """Returns a string emphasized.""" 463 if self._csi: 464 s = s.replace( 465 self._csi + self._ANSI_COLOR_RESET, 466 self._csi + self._ANSI_COLOR_RESET + self.GetFontCode(bold, italic)) 467 return ('{start}' + s + '{end}').format( 468 start=self.GetFontCode(bold, italic), 469 end=self.GetFontCode()) 470 471 def GetRawKey(self): 472 """Reads one key press from stdin with no echo. 473 474 Returns: 475 The key name, None for EOF, <KEY-*> for function keys, otherwise a 476 character. 477 """ 478 return self._get_raw_key[0]() 479 480 def GetTermIdentifier(self): 481 """Returns the TERM envrionment variable for the console. 482 483 Returns: 484 str: A str that describes the console's text capabilities 485 """ 486 return self._term 487 488 def GetTermSize(self): 489 """Returns the terminal (x, y) dimensions in characters. 490 491 Returns: 492 (x, y): A tuple of the terminal x and y dimensions. 493 """ 494 return self._term_size 495 496 def DisplayWidth(self, buf): 497 """Returns the display width of buf, handling unicode and ANSI controls. 498 499 Args: 500 buf: The string to count from. 501 502 Returns: 503 The display width of buf, handling unicode and ANSI controls. 504 """ 505 if not isinstance(buf, six.string_types): 506 # Handle non-string objects like Colorizer(). 507 return len(buf) 508 509 cached = self._display_width_cache.get(buf, None) 510 if cached is not None: 511 return cached 512 513 width = 0 514 max_width = 0 515 i = 0 516 while i < len(buf): 517 if self._csi and buf[i:].startswith(self._csi): 518 i += self.GetControlSequenceLen(buf[i:]) 519 elif buf[i] == '\n': 520 # A newline incidates the start of a new line. 521 # Newline characters have 0 width. 522 max_width = max(width, max_width) 523 width = 0 524 i += 1 525 else: 526 width += GetCharacterDisplayWidth(buf[i]) 527 i += 1 528 max_width = max(width, max_width) 529 530 self._display_width_cache[buf] = max_width 531 return max_width 532 533 def SplitIntoNormalAndControl(self, buf): 534 """Returns a list of (normal_string, control_sequence) tuples from buf. 535 536 Args: 537 buf: The input string containing one or more control sequences 538 interspersed with normal strings. 539 540 Returns: 541 A list of (normal_string, control_sequence) tuples. 542 """ 543 if not self._csi or not buf: 544 return [(buf, '')] 545 seq = [] 546 i = 0 547 while i < len(buf): 548 c = buf.find(self._csi, i) 549 if c < 0: 550 seq.append((buf[i:], '')) 551 break 552 normal = buf[i:c] 553 i = c + self.GetControlSequenceLen(buf[c:]) 554 seq.append((normal, buf[c:i])) 555 return seq 556 557 def SplitLine(self, line, width): 558 """Splits line into width length chunks. 559 560 Args: 561 line: The line to split. 562 width: The width of each chunk except the last which could be smaller than 563 width. 564 565 Returns: 566 A list of chunks, all but the last with display width == width. 567 """ 568 lines = [] 569 chunk = '' 570 w = 0 571 keep = False 572 for normal, control in self.SplitIntoNormalAndControl(line): 573 keep = True 574 while True: 575 n = width - w 576 w += len(normal) 577 if w <= width: 578 break 579 lines.append(chunk + normal[:n]) 580 chunk = '' 581 keep = False 582 w = 0 583 normal = normal[n:] 584 chunk += normal + control 585 if chunk or keep: 586 lines.append(chunk) 587 return lines 588 589 def SupportsAnsi(self): 590 """Indicates whether the terminal appears to support ANSI escape sequences. 591 592 Returns: 593 bool: True if ANSI seems to be supported; False otherwise. 594 """ 595 if console_attr_os.ForceEnableAnsi(): 596 return True 597 return (self._encoding != 'ascii' and 598 ('screen' in self._term or 'xterm' in self._term)) 599 600 601class Colorizer(object): 602 """Resource string colorizer. 603 604 Attributes: 605 _con: ConsoleAttr object. 606 _color: Color name. 607 _string: The string to colorize. 608 _justify: The justification function, no justification if None. For example, 609 justify=lambda s: s.center(10) 610 """ 611 612 def __init__(self, string, color, justify=None): 613 """Constructor. 614 615 Args: 616 string: The string to colorize. 617 color: Color name used to index ConsoleAttr._ANSI_COLOR. 618 justify: The justification function, no justification if None. For 619 example, justify=lambda s: s.center(10) 620 """ 621 self._con = GetConsoleAttr() 622 self._color = color 623 self._string = string 624 self._justify = justify 625 626 def __eq__(self, other): 627 return self._string == six.text_type(other) 628 629 def __ne__(self, other): 630 return not self == other 631 632 def __gt__(self, other): 633 return self._string > six.text_type(other) 634 635 def __lt__(self, other): 636 return self._string < six.text_type(other) 637 638 def __ge__(self, other): 639 return not self < other 640 641 def __le__(self, other): 642 return not self > other 643 644 def __len__(self): 645 return self._con.DisplayWidth(self._string) 646 647 def __str__(self): 648 return self._string 649 650 def Render(self, stream, justify=None): 651 """Renders the string as self._color on the console. 652 653 Args: 654 stream: The stream to render the string to. The stream given here *must* 655 have the same encoding as sys.stdout for this to work properly. 656 justify: The justification function, self._justify if None. 657 """ 658 stream.write( 659 self._con.Colorize(self._string, self._color, justify or self._justify)) 660 661 662def GetConsoleAttr(encoding=None, term=None, reset=False): 663 """Gets the console attribute state. 664 665 If this is the first call or reset is True or encoding is not None and does 666 not match the current encoding or out is not None and does not match the 667 current out then the state is (re)initialized. Otherwise the current state 668 is returned. 669 670 This call associates the out file stream with the console. All console related 671 output should go to the same stream. 672 673 Args: 674 encoding: Encoding override. 675 ascii -- ASCII. This is the default. 676 utf8 -- UTF-8 unicode. 677 win -- Windows code page 437. 678 term: Terminal override. Replaces the value of ENV['TERM']. 679 reset: Force re-initialization if True. 680 681 Returns: 682 The global ConsoleAttr state object. 683 """ 684 attr = ConsoleAttr._CONSOLE_ATTR_STATE # pylint: disable=protected-access 685 if not reset: 686 if not attr: 687 reset = True 688 elif encoding and encoding != attr.GetEncoding(): 689 reset = True 690 if reset: 691 attr = ConsoleAttr(encoding=encoding, term=term) 692 ConsoleAttr._CONSOLE_ATTR_STATE = attr # pylint: disable=protected-access 693 return attr 694 695 696def ResetConsoleAttr(encoding=None): 697 """Resets the console attribute state to the console default. 698 699 Args: 700 encoding: Reset to this encoding instead of the default. 701 ascii -- ASCII. This is the default. 702 utf8 -- UTF-8 unicode. 703 win -- Windows code page 437. 704 705 Returns: 706 The global ConsoleAttr state object. 707 """ 708 return GetConsoleAttr(encoding=encoding, reset=True) 709 710 711def GetCharacterDisplayWidth(char): 712 """Returns the monospaced terminal display width of char. 713 714 Assumptions: 715 - monospaced display 716 - ambiguous or unknown chars default to width 1 717 - ASCII control char width is 1 => don't use this for control chars 718 719 Args: 720 char: The character to determine the display width of. 721 722 Returns: 723 The monospaced terminal display width of char: either 0, 1, or 2. 724 """ 725 if not isinstance(char, six.text_type): 726 # Non-unicode chars have width 1. Don't use this function on control chars. 727 return 1 728 729 # Normalize to avoid special cases. 730 char = unicodedata.normalize('NFC', char) 731 732 if unicodedata.combining(char) != 0: 733 # Modifies the previous character and does not move the cursor. 734 return 0 735 elif unicodedata.category(char) == 'Cf': 736 # Unprintable formatting char. 737 return 0 738 elif unicodedata.east_asian_width(char) in 'FW': 739 # Fullwidth or Wide chars take 2 character positions. 740 return 2 741 else: 742 # Don't use this function on control chars. 743 return 1 744 745 746def SafeText(data, encoding=None, escape=True): 747 br"""Converts the data to a text string compatible with the given encoding. 748 749 This works the same way as Decode() below except it guarantees that any 750 characters in the resulting text string can be re-encoded using the given 751 encoding (or GetConsoleAttr().GetEncoding() if None is given). This means 752 that the string will be safe to print to sys.stdout (for example) without 753 getting codec exceptions if the user's terminal doesn't support the encoding 754 used by the source of the text. 755 756 Args: 757 data: Any bytes, string, or object that has str() or unicode() methods. 758 encoding: The encoding name to ensure compatibility with. Defaults to 759 GetConsoleAttr().GetEncoding(). 760 escape: Replace unencodable characters with a \uXXXX or \xXX equivalent if 761 True. Otherwise replace unencodable characters with an appropriate unknown 762 character, '?' for ASCII, and the unicode unknown replacement character 763 \uFFFE for unicode. 764 765 Returns: 766 A text string representation of the data, but modified to remove any 767 characters that would result in an encoding exception with the target 768 encoding. In the worst case, with escape=False, it will contain only ? 769 characters. 770 """ 771 if data is None: 772 return 'None' 773 encoding = encoding or GetConsoleAttr().GetEncoding() 774 string = encoding_util.Decode(data, encoding=encoding) 775 776 try: 777 # No change needed if the string encodes to the output encoding. 778 string.encode(encoding) 779 return string 780 except UnicodeError: 781 # The string does not encode to the output encoding. Encode it with error 782 # handling then convert it back into a text string (which will be 783 # guaranteed to only contain characters that can be encoded later. 784 return (string 785 .encode(encoding, 'backslashreplace' if escape else 'replace') 786 .decode(encoding)) 787 788 789def EncodeToBytes(data): 790 r"""Encode data to bytes. 791 792 The primary use case is for base64/mime style 7-bit ascii encoding where the 793 encoder input must be bytes. "safe" means that the conversion always returns 794 bytes and will not raise codec exceptions. 795 796 If data is text then an 8-bit ascii encoding is attempted, then the console 797 encoding, and finally utf-8. 798 799 Args: 800 data: Any bytes, string, or object that has str() or unicode() methods. 801 802 Returns: 803 A bytes string representation of the data. 804 """ 805 if data is None: 806 return b'' 807 if isinstance(data, bytes): 808 # Already bytes - our work is done. 809 return data 810 811 # Coerce to text that will be converted to bytes. 812 s = six.text_type(data) 813 814 try: 815 # Assume the text can be directly converted to bytes (8-bit ascii). 816 return s.encode('iso-8859-1') 817 except UnicodeEncodeError: 818 pass 819 820 try: 821 # Try the output encoding. 822 return s.encode(GetConsoleAttr().GetEncoding()) 823 except UnicodeEncodeError: 824 pass 825 826 # Punt to utf-8. 827 return s.encode('utf-8') 828 829 830def Decode(data, encoding=None): 831 """Converts the given string, bytes, or object to a text string. 832 833 Args: 834 data: Any bytes, string, or object that has str() or unicode() methods. 835 encoding: A suggesting encoding used to decode. If this encoding doesn't 836 work, other defaults are tried. Defaults to 837 GetConsoleAttr().GetEncoding(). 838 839 Returns: 840 A text string representation of the data. 841 """ 842 encoding = encoding or GetConsoleAttr().GetEncoding() 843 return encoding_util.Decode(data, encoding=encoding) 844