# -*- coding: utf-8 -*-
# SPDX-License-Identifier: BSD-2-Clause
"""Handle bytes and strings in a polyglot fashion.
copied from ../ntpclient/ntpq.py which got it from
https://gitlab.com/esr/practical-python-porting/blob/master/polystr-inclusion.py
see http://www.catb.org/esr/faqs/practical-python-porting/ for more information.
"""
import sys
master_encoding = 'latin-1'
# General notes on Python 2/3 compatibility:
#
# This code uses the following strategy to allow it to run on both Python 2
# and Python 3:
#
# - Use latin-1 encoding to transform binary data to/from Unicode when
# necessary for operations where Python 3 expects Unicode; the
# polystr and polybytes functions are used to do this so that
# when running on Python 2, the byte string data is used unchanged.
#
# - Construct custom stdout and stderr streams when running
# on Python 3 that force UTF-8 encoding, and wrap them around the
# underlying binary buffers (in Python 2, the streams are binary
# and are used unchanged); this ensures that the same transformation
# is done on data from/to the standard streams, as is done on binary
# data from/to files and subprocesses; the make_std_wrapper function
# does this.
#
# anyone that changes this needs to test with all combinations of
# python2, python3, LC_ALL=ascii, LC_ALL=latin-1, LC_ALL=en_US.utf8, and
# piping output to a file. While looking at the UTF-8 in the output.
forced_utf8 = False
if str is bytes: # Python 2
polystr = str
polyunicode = unicode
polybytes = bytes
polyord = ord
polychr = str
polyinput = raw_input
def string_escape(s):
"""String_escape/unicode_escape."""
return s.decode('string_escape')
else: # Python 3
import io
polyinput = input
def polystr(o):
"""Polymorphic string factory function."""
if isinstance(o, str):
return o
if not isinstance(o, bytes):
return str(o)
return str(o, encoding=master_encoding)
polyunicode = polystr
def polybytes(s):
"""Polymorphic string encoding function."""
if isinstance(s, bytes):
return s
if not isinstance(s, str):
return bytes(s)
return bytes(s, encoding=master_encoding)
def polyord(c):
"Polymorphic ord() function"
if isinstance(c, str):
return ord(c)
else:
return c
def polychr(c):
"Polymorphic chr() function"
if isinstance(c, int):
return chr(c)
else:
return c
def string_escape(s):
"""Polymorphic string_escape/unicode_escape."""
# This hack is necessary because Unicode strings in Python 3 don't
# have a decode method, so there's no simple way to ask it for the
# equivalent of decode('string_escape') in Python 2. This function
# assumes that it will be called with a Python 3 'str' instance
return s.encode(master_encoding).decode('unicode_escape')
def make_std_wrapper(stream):
"""Standard input/output wrapper factory function."""
# This ensures that the encoding of standard output and standard
# error on Python 3 matches the master encoding we use to turn
# bytes to Unicode in polystr above
# line_buffering=True ensures that interactive command sessions
# work as expected
return io.TextIOWrapper(stream.buffer, encoding="utf-8",
newline="\n", line_buffering=True)
# This is the one situation where we *can* force unicode.
if "utf-8" != sys.stdout.encoding.lower():
forced_utf8 = True
sys.stdout = make_std_wrapper(sys.stdout)
if "utf-8" != sys.stderr.encoding.lower():
forced_utf8 = True
sys.stderr = make_std_wrapper(sys.stderr)
|