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