def sanitize_dataframe(df: pd.DataFrame) -> pd.DataFrame:  # noqa: C901
    """Sanitize a DataFrame to prepare it for serialization.

    * Make a copy
    * Convert RangeIndex columns to strings
    * Raise ValueError if column names are not strings
    * Raise ValueError if it has a hierarchical index.
    * Convert categoricals to strings.
    * Convert np.bool_ dtypes to Python bool objects
    * Convert np.int dtypes to Python int objects
    * Convert floats to objects and replace NaNs/infs with None.
    * Convert DateTime dtypes into appropriate string representations
    * Convert Nullable integers to objects and replace NaN with None
    * Convert Nullable boolean to objects and replace NaN with None
    * convert dedicated string column to objects and replace NaN with None
    * Raise a ValueError for TimeDelta dtypes
    """
    df = df.copy()

    if isinstance(df.columns, pd.RangeIndex):
        df.columns = df.columns.astype(str)

    for col_name in df.columns:
        if not isinstance(col_name, str):
            raise ValueError(
                "Dataframe contains invalid column name: {0!r}. "
                "Column names must be strings".format(col_name)
            )

    if isinstance(df.index, pd.MultiIndex):
        raise ValueError("Hierarchical indices not supported")
    if isinstance(df.columns, pd.MultiIndex):
        raise ValueError("Hierarchical indices not supported")

    def to_list_if_array(val):
        if isinstance(val, np.ndarray):
            return val.tolist()
        else:
            return val

    for dtype_item in df.dtypes.items():
        # We know that the column names are strings from the isinstance check
        # further above but mypy thinks it is of type Hashable and therefore does not
        # let us assign it to the col_name variable which is already of type str.
        col_name = cast(str, dtype_item[0])
        dtype = dtype_item[1]
        dtype_name = str(dtype)
        if dtype_name == "category":
            # Work around bug in to_json for categorical types in older versions
            # of pandas as they do not properly convert NaN values to null in to_json.
            # We can probably remove this part once we require pandas >= 1.0
            col = df[col_name].astype(object)
            df[col_name] = col.where(col.notnull(), None)
        elif dtype_name == "string":
            # dedicated string datatype (since 1.0)
            # https://pandas.pydata.org/pandas-docs/version/1.0.0/whatsnew/v1.0.0.html#dedicated-string-data-type
            col = df[col_name].astype(object)
            df[col_name] = col.where(col.notnull(), None)
        elif dtype_name == "bool":
            # convert numpy bools to objects; np.bool is not JSON serializable
            df[col_name] = df[col_name].astype(object)
        elif dtype_name == "boolean":
            # dedicated boolean datatype (since 1.0)
            # https://pandas.io/docs/user_guide/boolean.html
            col = df[col_name].astype(object)
            df[col_name] = col.where(col.notnull(), None)
        elif dtype_name.startswith("datetime") or dtype_name.startswith("timestamp"):
            # Convert datetimes to strings. This needs to be a full ISO string
            # with time, which is why we cannot use ``col.astype(str)``.
            # This is because Javascript parses date-only times in UTC, but
            # parses full ISO-8601 dates as local time, and dates in Vega and
            # Vega-Lite are displayed in local time by default.
            # (see https://github.com/altair-viz/altair/issues/1027)
            df[col_name] = (
                df[col_name].apply(lambda x: x.isoformat()).replace("NaT", "")
            )
        elif dtype_name.startswith("timedelta"):
            raise ValueError(
                'Field "{col_name}" has type "{dtype}" which is '
                "not supported by Altair. Please convert to "
                "either a timestamp or a numerical value."
                "".format(col_name=col_name, dtype=dtype)
            )
        elif dtype_name.startswith("geometry"):
            # geopandas >=0.6.1 uses the dtype geometry. Continue here
            # otherwise it will give an error on np.issubdtype(dtype, np.integer)
            continue
        elif (
            dtype_name
            in {
                "Int8",
                "Int16",
                "Int32",
                "Int64",
                "UInt8",
                "UInt16",
                "UInt32",
                "UInt64",
                "Float32",
                "Float64",
            }
        ):  # nullable integer datatypes (since 24.0) and nullable float datatypes (since 1.2.0)
            # https://pandas.pydata.org/pandas-docs/version/0.25/whatsnew/v0.24.0.html#optional-integer-na-support
            col = df[col_name].astype(object)
            df[col_name] = col.where(col.notnull(), None)
        elif numpy_is_subtype(dtype, np.integer):
            # convert integers to objects; np.int is not JSON serializable
            df[col_name] = df[col_name].astype(object)
        elif numpy_is_subtype(dtype, np.floating):
            # For floats, convert to Python float: np.float is not JSON serializable
            # Also convert NaN/inf values to null, as they are not JSON serializable
            col = df[col_name]
            bad_values = col.isnull() | np.isinf(col)
            df[col_name] = col.astype(object).where(~bad_values, None)
        elif dtype == object:
            # Convert numpy arrays saved as objects to lists
            # Arrays are not JSON serializable
            col = df[col_name].astype(object).apply(to_list_if_array)
            df[col_name] = col.where(col.notnull(), None)
    return df

sanitize_dataframe(df=           date  precipitation  temp_max  temp_min  wind  weather0    2012-01-01            0.0      12.8       5.0   4.7  drizzle1    2012-01-02           10.9      10.6       2.8   4.5     rain2    2012-01-03            0.8      11.7       7.2   2.3     rain3    2012-01-04           20.3      12.2       5.6   4.7     rain4    2012-01-05            1.3       8.9       2.8   6.1     rain...         ...            ...       ...       ...   ...      ...1456 2015-12-27            8.6       4.4       1.7   2.9      fog1457 2015-12-28            1.5       5.0       1.7   1.3      fog1458 2015-12-29            0.0       7.2       0.6   2.6      fog1459 2015-12-30            0.0       5.6      -1.0   3.4      sun1460 2015-12-31            0.0       5.6      -2.1   3.5      sun[1461 rows x 6 columns])

<function sanitize_dataframe.<locals>.to_list_if_array at 0x7fc2dcd75700>

0       drizzle1          rain2          rain3          rain4          rain         ...   1456        fog1457        fog1458        fog1459        sun1460        sunName: weather, Length: 1461, dtype: object

0       False1       False2       False3       False4       False        ...  1456    False1457    False1458    False1459    False1460    FalseName: precipitation, Length: 1461, dtype: bool

0       False1       False2       False3       False4       False        ...  1456    False1457    False1458    False1459    False1460    FalseName: temp_max, Length: 1461, dtype: bool

0       False1       False2       False3       False4       False        ...  1456    False1457    False1458    False1459    False1460    FalseName: temp_min, Length: 1461, dtype: bool

0       False1       False2       False3       False4       False        ...  1456    False1457    False1458    False1459    False1460    FalseName: wind, Length: 1461, dtype: bool


def lineAfterContext(line, prefix):
    if line.startswith(prefix):
        line = line[len(prefix):]

    toks = line.split(' in ', 1)
    if len(toks) == 2:
        rest = toks[1].split(' ')
        line = ' '.join(rest[1:])

    return line

lineAfterContext(line='ic| eins: zwei', prefix='ic| ')


def argumentToString(obj):
    s = DEFAULT_ARG_TO_STRING_FUNCTION(obj)
    s = s.replace('\\n', '\n')  # Preserve string newlines in output.
    return s

argumentToString(obj=1)


def lineIsContextAndTime(line):
    line = stripPrefix(line)  # ic| f.py:33 in foo() at 08:08:51.389
    context, time = line.split(' at ')

    return (
        lineIsContext(context) and
        len(time.split(':')) == 3 and
        len(time.split('.')) == 2)

lineIsContextAndTime(line='ic| test_icecream.py:229 in testAsArgument() at 01:15:24.779')

'test_icecream.py:229 in testAsArgument() at 01:15:24.779'

Context: 'test_icecream.py:229 in testAsArgument()'


def lineIsContext(line):
    line = stripPrefix(line)  # ic| f.py:33 in foo()
    sourceLocation, function = line.split(' in ')  # f.py:33 in foo()
    filename, lineNumber = sourceLocation.split(':')  # f.py:33
    name, ext = splitext(filename)

    return (
        int(lineNumber) > 0 and
        ext in ['.py', '.pyc', '.pyo'] and
        name == splitext(MY_FILENAME)[0] and
        (function == '<module>' or function.endswith('()')))

lineIsContext(line='test_icecream.py:229 in testAsArgument()')


def format_pair(prefix, arg, value):
    if arg is _arg_source_missing:
        arg_lines = []
        value_prefix = prefix
    else:
        arg_lines = indented_lines(prefix, arg)
        value_prefix = arg_lines[-1] + ': '

    looksLikeAString = value[0] + value[-1] in ["''", '""']
    if looksLikeAString:  # Align the start of multiline strings.
        value = prefixLinesAfterFirst(' ', value)

    value_lines = indented_lines(value_prefix, value)
    lines = arg_lines[:-1] + value_lines
    return '\n'.join(lines)

format_pair(prefix='    ', arg='multilineStr', value="'line1\nline2'")


def appendTo(s):
            lst.append(s)

appendTo(s='ic| a: 1', lst=[])


def call_on_comment(*args, **kwargs):
    global events
    repo_full_name = kwargs["repo_full_name"]
    pr_id = kwargs["pr_number"]
    key = f"{repo_full_name}-{pr_id}"

    # Check if a previous process exists for the same key, cancel it
    thread = events.get(key, None)
    if thread:
        terminate_thread(thread)

    thread = threading.Thread(target=run_comment, args=args, kwargs=kwargs)
    events[key] = thread
    thread.start()

call_on_comment(args=(), kwargs={'repo_full_name': 'exampleRepo', 'pr_number': 1})


def hmac_digest(secret, message, encoding="utf-8"):
    """Return hex digest of a message HMAC using secret"""
    if isinstance(secret, str):
        secret = secret.encode(encoding)
    return hmac.new(secret, message.encode(encoding), hashlib.sha256).hexdigest()

hmac_digest(secret='secret_hmac_for_userids', message='mat:secret', encoding='utf-8')

'mat:secret'

'mat:secret'


def parse_csv(csv):
    lines = csv.strip().split('\n')
    lines = [line for line in lines if line.strip()]
    csv = '\n'.join(lines)
    if autoeq_pattern.match(csv):  # Matches AutoEq produced CSV files
        columns = lines[0].split(',')
        return {column: [float(line.split(',')[i]) for line in lines[1:]] for i, column in enumerate(columns)}

    if rew_pattern.match(csv) or crinacle_pattern.match(csv):
        # These two have all sort of junk in them but the first column is frequency and the second SPL, so all good
        csv = '\n'.join([re.sub(r'(?:, ?| |\t)', '\t', line) for line in lines if numeric_start.match(line) and '?' not in line])
        lines = csv.split('\n')

    column_separator, decimal_separator = find_csv_separators(csv)
    columns = find_csv_columns(csv, column_separator)

    # Find indexes of frequency and raw columns
    if columns is None:
        # No header, assume first column is frequency and the second is SPL
        ixs = {'frequency': 0, 'raw': 1}
    else:
        ixs = {'frequency': None, 'raw': None}
        for i, column in enumerate(columns):
            if re.match(r'^freq', column, flags=re.IGNORECASE):
                ixs['frequency'] = i
            if re.match(r'^(?:spl|gain|ampl|raw)', column, flags=re.IGNORECASE):
                ixs['raw'] = i
        if ixs['frequency'] is None:
            if len(columns) == 2:  # Can't find proper columns but there's only two, assuming freq + raw
                ixs = {'frequency': 0, 'raw': 1}
            else:
                raise CsvParseError('Failed to find frequency column')
        if ixs['raw'] is None:
            raise CsvParseError('Failed to find SPL column')

    # Read and parse data lines
    data_line_pattern = re.compile(rf'^-?\d+(?:{column_separator}\d+)?')
    data = {'frequency': [], 'raw': []}
    for line in lines:
        if not data_line_pattern.match(line):
            continue
        cells = line.split(column_separator)
        if decimal_separator == ',':
            cells = [float(cell.replace(',', '.')) for cell in cells]
        else:
            cells = [float(cell) for cell in cells]
        for column, ix in ixs.items():
            data[column].append(cells[ix])
    return data

parse_csv(csv='frequency,raw\n20,0\n1000,3\n20000,0\n')


def _sort(a, aux, lo, hi):
        if lo >= hi:
            return

        if hi - lo + 1 < MergeSort.CUTOFF:
            InsertionSort.sort(a, lo, hi)
            return

        mid = lo + (hi - lo) // 2
        MergeSort._sort(a, aux, lo, mid)
        MergeSort._sort(a, aux, mid + 1, hi)
        MergeSort._merge(a, aux, lo, mid, hi)

_sort(a=[4, 2, 1, 23, 4, 5, 6, 7, 8, 9, 20, 11, 13, 34, 66], aux=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], lo=0, hi=7)


def partition(a, lo, hi):
        i = lo
        j = hi

        while True:
            while not util.less(a[lo], a[i]):
                i += 1
                if i >= hi:
                    break

            while util.less(a[lo], a[j]):
                j -= 1
                if j <= lo:
                    break

            if i >= j:
                break

            util.exchange(a, i, j)

        util.exchange(a, lo, j)
        return j

partition(a=[4, 2, 1, 23, 4, 5, 6, 7, 8, 9, 20, 11, 13, 34, 66], lo=0, hi=14)


def get_google_drive_folder_location():
    """
    Try to locate the Google Drive folder.

    Returns:
        (str) Full path to the current Google Drive folder
    """
    gdrive_db_path = "Library/Application Support/Google/Drive/sync_config.db"
    yosemite_gdrive_db_path = (
        "Library/Application Support/Google/Drive/" "user_default/sync_config.db"
    )
    yosemite_gdrive_db = os.path.join(os.environ["HOME"], yosemite_gdrive_db_path)
    if os.path.isfile(yosemite_gdrive_db):
        gdrive_db_path = yosemite_gdrive_db

    googledrive_home = None

    gdrive_db = os.path.join(os.environ["HOME"], gdrive_db_path)
    if os.path.isfile(gdrive_db):
        con = sqlite3.connect(gdrive_db)
        if con:
            cur = con.cursor()
            query = (
                "SELECT data_value "
                "FROM data "
                "WHERE entry_key = 'local_sync_root_path';"
            )
            cur.execute(query)
            data = cur.fetchone()
            googledrive_home = str(data[0])
            con.close()

    if not googledrive_home:
        error(
            constants.ERROR_UNABLE_TO_FIND_STORAGE.format(
                provider="Google Drive install"
            )
        )

    return googledrive_home

get_google_drive_folder_location()

Question: "SELECT data_value FROM data WHERE entry_key = 'local_sync_root_path';"


def can_file_be_synced_on_current_platform(path):
    """
    Check if the given path can be synced locally.

    Check if it makes sense to sync the file at the given path on the current
    platform.
    For now we don't sync any file in the ~/Library folder on GNU/Linux.
    There might be other exceptions in the future.

    Args:
        (str): Path to the file or folder to check. If relative, prepend it
               with the home folder.
               'abc' becomes '~/abc'
               '/def' stays '/def'

    Returns:
        (bool): True if given file can be synced
    """
    can_be_synced = True

    # If the given path is relative, prepend home
    fullpath = os.path.join(os.environ["HOME"], path)

    # Compute the ~/Library path on macOS
    # End it with a slash because we are looking for this specific folder and
    # not any file/folder named LibrarySomething
    library_path = os.path.join(os.environ["HOME"], "Library/")

    if platform.system() == constants.PLATFORM_LINUX:
        if fullpath.startswith(library_path):
            can_be_synced = False

    return can_be_synced

can_file_be_synced_on_current_platform(path='some/file')


def _compare_parts(self, a, b, decimal):
        if decimal:
            if a == "": a = "0"
            if b == "": b = "0"
            return int(a) - int(b)
        else:
            i = 0
            while i < (min(len(a), len(b)) + 1):
                res = self._order(self._get_empty_str_on_index_error(a, i)) \
                        - self._order(self._get_empty_str_on_index_error(b, i))
                if res != 0:
                    return res
                i += 1
            else:
                return 0

_compare_parts(self=2.2.0~rc5, a='', b='', decimal=False, self.epoch=0, self.revision='0', self.revision_allowed_chars=('.', '+', '~'), self.upstream_version='2.2.0~rc5', self.upstream_version_allowed_chars=('.', '+', '~', '-', ':'), self.version='2.2.0~rc5')


def getorcreatesubcontext(self, path):
        for name in path:
            that = self.resolvables.get(name)
            if that is None:
                self.resolvables[name] = that = Context(self)
            self = that
        return self

getorcreatesubcontext(self=Context(SuperContext(), False), path=('woo',), self.islist=False, self.parent=SuperContext(), self.resolvables=OrderedDict())

OrderedDict([('woo', Context(Context(SuperContext(), False), False))])


def execute(self, entry):
        directives = []
        for i, word in enumerate(entry.words()):
            def append(resolvable):
                directives.append((resolvable.directivevalue, i))
                raise self.Enough
            try:
                self.getresolvables(word.cat(), append)
            except (AttributeError, CatNotSupportedException, self.Enough):
                pass
        if 1 != len(directives):
            raise UnsupportedEntryException("Expected 1 directive but %s found: %s" % (directives, entry))
        d, i = directives[0]
        d(entry.subentry(0, i), entry.phrase(i + 1), self)

execute(self=Context(SuperContext(), False), entry=Entry([Text('ns'), Text('woo'), Blank(' '), Text('='), Blank(' '), Text('yay')]), self.islist=False, self.parent=SuperContext(), self.resolvables=OrderedDict([('here', Text('/tmp'))]))

OrderedDict([('here', Text('/tmp')), ('ns', Context(Context(SuperContext(), False), False))])

[(<aridimpl.directives.Equals object at 0x7fe13ba8cd60>, 2)]

<function AbstractContext.execute.<locals>.append at 0x7fe139b030d0>

<function AbstractContext.execute.<locals>.append at 0x7fe139c4a790>

<function AbstractContext.execute.<locals>.append at 0x7fe139b035e0>


def static_bool_env(varname: str, default: bool) -> bool:
  """Read an environment variable and interpret it as a boolean.

  This is deprecated. Please use bool_flag() unless your flag
  will be used in a static method and does not require runtime updates.

  True values are (case insensitive): 'y', 'yes', 't', 'true', 'on', and '1';
  false values are 'n', 'no', 'f', 'false', 'off', and '0'.
  Args:
    varname: the name of the variable
    default: the default boolean value
  Returns:
    boolean return value derived from defaults and environment.
  Raises: ValueError if the environment variable is anything else.
  """
  val = os.getenv(varname, str(default))
  val = val.lower()
  if val in ('y', 'yes', 't', 'true', 'on', '1'):
    return True
  elif val in ('n', 'no', 'f', 'false', 'off', '0'):
    return False
  else:
    raise ValueError(
      'invalid truth value {!r} for environment {!r}'.format(val, varname)
    )

static_bool_env(varname='FLAX_LAZY_RNG', default=True)


def splitdrive(path):
    """
    Split the path into a pair (drive, tail) where drive is either a
    mount point or the empty string. On systems which do not use drive
    specifications, drive will always be the empty string.

    In all cases, drive + tail will be the same as path.

    Equivalent to "os.path.splitdrive".

    Args:
        path (path-like object): Path or URL.

    Returns:
        tuple of str: drive, tail.
    """
    relative = get_instance(path).relpath(path)
    drive = path.rsplit(relative, 1)[0]
    if drive and not drive[-2:] == '//':
        # Keep "/" tail side
        relative = '/' + relative
        drive = drive.rstrip('/')
    return drive, relative

splitdrive(path='dummy://dir1/dir2/dir3')


def _copy(src, dst, src_is_storage, dst_is_storage):
    """
    Copies file from source to destination

    Args:
        src (str or file-like object): Source file.
        dst (str or file-like object): Destination file.
        src_is_storage (bool): Source is storage.
        dst_is_storage (bool): Destination is storage.
    """
    # If both storage: Tries to perform same storage direct copy
    if src_is_storage and dst_is_storage:
        system = get_instance(src)
        if system is get_instance(dst):

            # Checks if same file
            if system.relpath(src) == system.relpath(dst):
                raise same_file_error(
                    "'%s' and '%s' are the same file" % (src, dst))

            # Tries to copy
            try:
                return system.copy(src, dst)
            except (UnsupportedOperation, ObjectException):
                pass

    # At least one storage object: copies streams
    with cos_open(src, 'rb') as fsrc:
        with cos_open(dst, 'wb') as fdst:

            # Get stream buffer size
            for stream in (fdst, fsrc):
                try:
                    buffer_size = getattr(stream, '_buffer_size')
                    break
                except AttributeError:
                    continue
            else:
                buffer_size = 16384

            # Read and write
            copyfileobj(fsrc, fdst, buffer_size)

_copy(src='dummy_read://file.txt', dst='/tmp/pytest-of-jinjun/pytest-198/test_cos_open0/file_dst.txt', src_is_storage=True, dst_is_storage=False)


def _getmtime_from_header(header):
        """
        Return the time from header

        Args:
            header (dict): Object header.

        Returns:
            float: The number of seconds since the epoch
        """
        # By default, assumes that information are in a standard HTTP header
        for key in ('Last-Modified', 'last-modified'):
            try:
                return mktime(parsedate(header.pop(key)))
            except KeyError:
                continue
        else:
            raise UnsupportedOperation('getmtime')

_getmtime_from_header(header={'Accept-Ranges': 'bytes', 'Content-Length': '100', 'Last-Modified': 'Wed, 03 Apr 2024 20:18:10 GMT'})

{'Accept-Ranges': 'bytes', 'Content-Length': '100'}


def _getsize_from_header(header):
        """
        Return the size from header

        Args:
            header (dict): Object header.

        Returns:
            int: Size in bytes.
        """
        # By default, assumes that information are in a standard HTTP header
        for key in ('Content-Length', 'content-length'):
            try:
                return int(header.pop(key))
            except KeyError:
                continue
        else:
            raise UnsupportedOperation('getsize')

_getsize_from_header(header={'Accept-Ranges': 'bytes', 'Content-Length': '100'})


def put_object(key=None, data=None, **_):
            """Check arguments and returns fake value"""
            assert key.startswith(key_value)
            if key[-1] == '/':
                assert data == b''
            else:
                assert len(data) == len(ossobject._write_buffer)
            put_object_called.append(1)

put_object(key='key/', data=b'', _={}, key_value='key', ossobject=None, put_object_called=[])


def readHarFileInfo(filename: str) -> HarFileInfoObj:
        """
        :param filename: Filename.
        :return: An `dict`, with the key-value pairs:

        """
        hfiObj = HarFileInfoObj(file=filename)

        with open(filename, "rb") as f:
            f.seek(0)
            while True:
                # Read all header names
                pos, name, end_pos = HarFileIO._readHeaderPosName(f)
                if not name:
                    break
                hfiObj.addHAInfo(name, pos, end_pos)
                hfi=hfiObj.ha_infos[name]
                (hfi.version, hfi.data_type, hfi.storage_type,  hfi.long_name, hfi.file_dims) = HarFileIO._getHeaderInfo(f, name)
        return hfiObj

readHarFileInfo(filename='test_remove_header_array.har')

{filename='/local/rcs/jinjun/code/pytrace-collector/logs/pypibugs/tried/GEMPACKsoftware+HARPY/GEMPACKsoftware+HARPY/test_remove_header_array.har', _mtime=1708906163.399167, _ha_infos=OrderedDict([('XXCD', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334730>), ('XXCR', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163345e0>)])}

{filename='/local/rcs/jinjun/code/pytrace-collector/logs/pypibugs/tried/GEMPACKsoftware+HARPY/GEMPACKsoftware+HARPY/test_remove_header_array.har', _mtime=1708906163.399167, _ha_infos=OrderedDict([('XXCD', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334730>), ('XXCR', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163345e0>), ('XXCP', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163347c0>)])}

{filename='/local/rcs/jinjun/code/pytrace-collector/logs/pypibugs/tried/GEMPACKsoftware+HARPY/GEMPACKsoftware+HARPY/test_remove_header_array.har', _mtime=1708906163.399167, _ha_infos=OrderedDict([('XXCD', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334730>), ('XXCR', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163345e0>), ('XXCP', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163347c0>), ('XXHS', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334910>)])}

{filename='/local/rcs/jinjun/code/pytrace-collector/logs/pypibugs/tried/GEMPACKsoftware+HARPY/GEMPACKsoftware+HARPY/test_remove_header_array.har', _mtime=1708906163.399167, _ha_infos=OrderedDict([('XXCD', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334730>), ('XXCR', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163345e0>), ('XXCP', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163347c0>), ('XXHS', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334910>), ('CHST', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163340a0>)])}

{filename='/local/rcs/jinjun/code/pytrace-collector/logs/pypibugs/tried/GEMPACKsoftware+HARPY/GEMPACKsoftware+HARPY/test_remove_header_array.har', _mtime=1708906163.399167, _ha_infos=OrderedDict([('XXCD', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334730>), ('XXCR', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163345e0>), ('XXCP', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163347c0>), ('XXHS', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334910>), ('CHST', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163340a0>), ('INTA', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334280>)])}

{filename='/local/rcs/jinjun/code/pytrace-collector/logs/pypibugs/tried/GEMPACKsoftware+HARPY/GEMPACKsoftware+HARPY/test_remove_header_array.har', _mtime=1708906163.399167, _ha_infos=OrderedDict([('XXCD', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334730>), ('XXCR', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163345e0>), ('XXCP', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163347c0>), ('XXHS', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334910>), ('CHST', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163340a0>), ('INTA', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334280>), ('SIMP', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163341c0>)])}

{filename='/local/rcs/jinjun/code/pytrace-collector/logs/pypibugs/tried/GEMPACKsoftware+HARPY/GEMPACKsoftware+HARPY/test_remove_header_array.har', _mtime=1708906163.399167, _ha_infos=OrderedDict([('XXCD', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334730>), ('XXCR', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163345e0>), ('XXCP', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163347c0>), ('XXHS', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334910>), ('CHST', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163340a0>), ('INTA', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334280>), ('SIMP', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163341c0>), ('SIM2', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163503a0>)])}

{filename='/local/rcs/jinjun/code/pytrace-collector/logs/pypibugs/tried/GEMPACKsoftware+HARPY/GEMPACKsoftware+HARPY/test_remove_header_array.har', _mtime=1708906163.399167, _ha_infos=OrderedDict([('XXCD', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334730>), ('XXCR', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163345e0>), ('XXCP', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163347c0>), ('XXHS', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334910>), ('CHST', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163340a0>), ('INTA', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334280>), ('SIMP', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163341c0>), ('SIM2', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163503a0>), ('NH01', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016350430>)])}

{filename='/local/rcs/jinjun/code/pytrace-collector/logs/pypibugs/tried/GEMPACKsoftware+HARPY/GEMPACKsoftware+HARPY/test_remove_header_array.har', _mtime=1708906163.399167, _ha_infos=OrderedDict([('XXCD', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334730>), ('XXCR', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163345e0>), ('XXCP', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163347c0>), ('XXHS', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334910>), ('CHST', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163340a0>), ('INTA', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016334280>), ('SIMP', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163341c0>), ('SIM2', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163503a0>), ('NH01', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd016350430>), ('ARR7', <harpy.har_file_io.HarFileInfoObj._HAInfo object at 0x7fd0163342b0>)])}

{name='XXCD', pos_name=0, pos_data=12, parent_har_file=<harpy.har_file_io.HarFileInfoObj object at 0x7fd016334ee0>, version=0, data_type=None, storage_type=None, long_name=None, file_dims=None, sets=None, coeff_name=None}

{name='XXCD', pos_name=0, pos_data=12, parent_har_file=<harpy.har_file_io.HarFileInfoObj object at 0x7fd016334ee0>, version=1, data_type='1C', storage_type='FULL', long_name='Creation Date and Time                                                ', file_dims=(1, 70), sets=None, coeff_name=None}

{name='XXCR', pos_name=206, pos_data=218, parent_har_file=<harpy.har_file_io.HarFileInfoObj object at 0x7fd016334ee0>, version=0, data_type=None, storage_type=None, long_name=None, file_dims=None, sets=None, coeff_name=None}

{name='XXCR', pos_name=206, pos_data=218, parent_har_file=<harpy.har_file_io.HarFileInfoObj object at 0x7fd016334ee0>, version=1, data_type='1C', storage_type='FULL', long_name='Creating Program                                                      ', file_dims=(2, 70), sets=None, coeff_name=None}

{name='XXCP', pos_name=482, pos_data=494, parent_har_file=<harpy.har_file_io.HarFileInfoObj object at 0x7fd016334ee0>, version=0, data_type=None, storage_type=None, long_name=None, file_dims=None, sets=None, coeff_name=None}

{name='XXCP', pos_name=482, pos_data=494, parent_har_file=<harpy.har_file_io.HarFileInfoObj object at 0x7fd016334ee0>, version=1, data_type='1C', storage_type='FULL', long_name='compiler used to make EXE which created this file                     ', file_dims=(1, 6), sets=None, coeff_name=None}

{name='XXHS', pos_name=624, pos_data=636, parent_har_file=<harpy.har_file_io.HarFileInfoObj object at 0x7fd016334ee0>, version=0, data_type=None, storage_type=None, long_name=None, file_dims=None, sets=None, coeff_name=None}

{name='XXHS', pos_name=624, pos_data=636, parent_har_file=<harpy.har_file_io.HarFileInfoObj object at 0x7fd016334ee0>, version=1, data_type='1C', storage_type='FULL', long_name='File History                                                          ', file_dims=(53, 60), sets=None, coeff_name=None}

{name='CHST', pos_name=3940, pos_data=3952, parent_har_file=<harpy.har_file_io.HarFileInfoObj object at 0x7fd016334ee0>, version=0, data_type=None, storage_type=None, long_name=None, file_dims=None, sets=None, coeff_name=None}

{name='CHST', pos_name=3940, pos_data=3952, parent_har_file=<harpy.har_file_io.HarFileInfoObj object at 0x7fd016334ee0>, version=1, data_type='1C', storage_type='FULL', long_name='Simple set of strings                                                 ', file_dims=(5, 12), sets=None, coeff_name=None}

{name='INTA', pos_name=4136, pos_data=4148, parent_har_file=<harpy.har_file_io.HarFileInfoObj object at 0x7fd016334ee0>, version=0, data_type=None, storage_type=None, long_name=None, file_dims=None, sets=None, coeff_name=None}

{name='INTA', pos_name=4136, pos_data=4148, parent_har_file=<harpy.har_file_io.HarFileInfoObj object at 0x7fd016334ee0>, version=1, data_type='2I', storage_type='FULL', long_name='2D integer array                                                      ', file_dims=(4, 4), sets=None, coeff_name=None}

{name='SIMP', pos_name=4352, pos_data=4364, parent_har_file=<harpy.har_file_io.HarFileInfoObj object at 0x7fd016334ee0>, version=0, data_type=None, storage_type=None, long_name=None, file_dims=None, sets=None, coeff_name=None}

{name='SIMP', pos_name=4352, pos_data=4364, parent_har_file=<harpy.har_file_io.HarFileInfoObj object at 0x7fd016334ee0>, version=1, data_type='1C', storage_type='FULL', long_name='Set SimpleSet Simple 2 item set                                       ', file_dims=(2, 1), sets=None, coeff_name=None}

{name='SIM2', pos_name=4490, pos_data=4502, parent_har_file=<harpy.har_file_io.HarFileInfoObj object at 0x7fd016334ee0>, version=0, data_type=None, storage_type=None, long_name=None, file_dims=None, sets=None, coeff_name=None}

{name='SIM2', pos_name=4490, pos_data=4502, parent_har_file=<harpy.har_file_io.HarFileInfoObj object at 0x7fd016334ee0>, version=1, data_type='1C', storage_type='FULL', long_name='Set SimpleSet2 Simple set 2                                           ', file_dims=(2, 2), sets=None, coeff_name=None}

{name='NH01', pos_name=4630, pos_data=4642, parent_har_file=<harpy.har_file_io.HarFileInfoObj object at 0x7fd016334ee0>, version=0, data_type=None, storage_type=None, long_name=None, file_dims=None, sets=None, coeff_name=None}

{name='NH01', pos_name=4630, pos_data=4642, parent_har_file=<harpy.har_file_io.HarFileInfoObj object at 0x7fd016334ee0>, version=1, data_type='RE', storage_type='FULL', long_name='Simple 2 dimensional array                                            ', file_dims=(2, 2, 1, 1, 1, 1, 1), sets=None, coeff_name=None}

{name='ARR7', pos_name=5088, pos_data=5100, parent_har_file=<harpy.har_file_io.HarFileInfoObj object at 0x7fd016334ee0>, version=0, data_type=None, storage_type=None, long_name=None, file_dims=None, sets=None, coeff_name=None}

{name='ARR7', pos_name=5088, pos_data=5100, parent_har_file=<harpy.har_file_io.HarFileInfoObj object at 0x7fd016334ee0>, version=1, data_type='RE', storage_type='FULL', long_name='7-Dimensional Array                                                   ', file_dims=(2, 2, 2, 2, 2, 2, 2), sets=None, coeff_name=None}


def _unpack_args(args, nargs_spec):
    """Given an iterable of arguments and an iterable of nargs specifications,
    it returns a tuple with all the unpacked arguments at the first index
    and all remaining arguments as the second.

    The nargs specification is the number of arguments that should be consumed
    or `-1` to indicate that this position should eat up all the remainders.

    Missing items are filled with `None`.
    """
    args = deque(args)
    nargs_spec = deque(nargs_spec)
    rv = []
    spos = None

    def _fetch(c):
        try:
            if spos is None:
                return c.popleft()
            else:
                return c.pop()
        except IndexError:
            return None

    while nargs_spec:
        nargs = _fetch(nargs_spec)
        if nargs == 1:
            rv.append(_fetch(args))
        elif nargs > 1:
            x = [_fetch(args) for _ in range(nargs)]
            # If we're reversed, we're pulling in the arguments in reverse,
            # so we need to turn them around.
            if spos is not None:
                x.reverse()
            rv.append(tuple(x))
        elif nargs < 0:
            if spos is not None:
                raise TypeError('Cannot have two nargs < 0')
            spos = len(rv)
            rv.append(None)

    # spos is the position of the wildcard (star).  If it's not `None`,
    # we fill it with the remainder.
    if spos is not None:
        rv[spos] = tuple(args)
        args = []
        rv[spos + 1:] = reversed(rv[spos + 1:])

    return tuple(rv), list(args)

_unpack_args(args=['foo.txt', 'bar.txt', 'dir'], nargs_spec=[-1, 1])

<function _unpack_args.<locals>._fetch at 0x7fe6e9431b80>


def join_options(options):
    """Given a list of option strings this joins them in the most appropriate
    way and returns them in the form ``(formatted_string,
    any_prefix_is_slash)`` where the second item in the tuple is a flag that
    indicates if any of the option prefixes was a slash.
    """
    rv = []
    any_prefix_is_slash = False
    for opt in options:
        prefix = split_opt(opt)[0]
        if prefix == '/':
            any_prefix_is_slash = True
        rv.append((len(prefix), opt))

    rv.sort(key=lambda x: x[0])

    rv = ', '.join(x[1] for x in rv)
    return rv, any_prefix_is_slash

join_options(options=['--help'])


def format_filename(filename, shorten=False):
    """Formats a filename for user display.  The main purpose of this
    function is to ensure that the filename can be displayed at all.  This
    will decode the filename to unicode if necessary in a way that it will
    not fail.  Optionally, it can shorten the filename to not include the
    full path to the filename.

    :param filename: formats a filename for UI display.  This will also convert
                     the filename into unicode without failing.
    :param shorten: this optionally shortens the filename to strip of the
                    path that leads up to it.
    """
    if shorten:
        filename = os.path.basename(filename)
    return filename_to_ui(filename)

format_filename(filename='/x/foo.txt', shorten=True)


def test_echo_via_pager(monkeypatch, capfd, cat):
    monkeypatch.setitem(os.environ, 'PAGER', cat)
    monkeypatch.setattr(click._termui_impl, 'isatty', lambda x: True)
    click.echo_via_pager('haha')
    out, err = capfd.readouterr()
    assert out == 'haha\n'

test_echo_via_pager(monkeypatch={_setattr=[], _setitem=[], _cwd=None, _savesyspath=None}, capfd={request=<SubRequest 'capfd' for <Function test_echo_via_pager[cat]>>, _capture=<MultiCapture out=<FDCapture 1 oldfd=6 _state='started' tmpfile=<_io.TextIOWrapper name="<_io.FileIO name=7 mode='rb+' closefd=True>" mode='r+' encoding='utf-8'>> err=<FDCapture 2 oldfd=8 _state='started' tmpfile=<_io.TextIOWrapper name="<_io.FileIO name=9 mode='rb+' closefd=True>" mode='r+' encoding='utf-8'>> in_=None _state='started' _in_suspended=False>, _captured_out='', _captured_err=''}, cat='cat')

{_setattr=[], _setitem=[(environ({'SHELL': '/bin/bash', 'LSCOLORS': 'Gxfxcxdxbxegedabagacad', 'USER_ZDOTDIR': '/home/jinjun', 'COLORTERM': 'truecolor', 'LESS': '-R', 'TERM_PROGRAM_VERSION': '3.2a', 'GVM_VERSION': '1.0.22', 'CONDA_EXE': '/local/rcs/jinjun/miniforge3/bin/conda', '_CE_M': '', 'TMUX': '/tmp/tmux-19200/default,59951,3', 'PKG_CONFIG_PATH': '/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/lib/pkgconfig:/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/lib/pkgconfig:', '_P9K_TTY': '/dev/pts/20', 'GVM_PATH_BACKUP': '/home/jinjun/.gvm/bin:/local/rcs/jinjun/miniforge3/envs/mal/bin:/local/rcs/jinjun/miniforge3/condabin:/home/jinjun/.gdrive-downloader:/local/arise/jinjun/miniforge3/bin:/home/jinjun/.gvm/pkgsets/go1.19.1/global/bin:/home/jinjun/.gvm/gos/go1.19.1/bin:/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/bin:/home/jinjun/.gvm/bin:/home/jinjun/.vscode-server/cli/servers/Stable-31c37ee8f63491495ac49e43b8544550fbae4533/server/bin/remote-cli:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/jinjun/.local/bin:/home/jinjun/.local/bin:/home/jinjun/.local/bin', 'P9K_TTY': 'old', 'LC_FIG_SET_PARENT': '4c022497-5122-4b80-b325-c89bab32302a', 'PWD': '/local/rcs/jinjun/code/pytrace-collector/logs/pypibugs/tried/MrTango+click/MrTango+click', 'LOGNAME': 'jinjun', 'XDG_SESSION_TYPE': 'tty', 'CONDA_PREFIX': '/local/rcs/jinjun/miniforge3/envs/MrTango+click', 'VSCODE_GIT_ASKPASS_NODE': '/home/jinjun/.vscode-server/cli/servers/Stable-31c37ee8f63491495ac49e43b8544550fbae4533/server/node', 'MOTD_SHOWN': 'pam', 'VSCODE_INJECTION': '1', 'GVM_OVERLAY_PREFIX': '/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay', 'HOME': '/home/jinjun', 'LANG': 'en_US.UTF-8', 'DYLD_LIBRARY_PATH': '/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/lib:/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/lib', 'LS_COLORS': 'rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:', 'gvm_pkgset_name': 'global', 'SSL_CERT_DIR': '/usr/lib/ssl/certs', 'CONDA_PROMPT_MODIFIER': '(MrTango+click) ', 'GIT_ASKPASS': '/home/jinjun/.vscode-server/cli/servers/Stable-31c37ee8f63491495ac49e43b8544550fbae4533/server/extensions/git/dist/askpass.sh', 'GVM_ROOT': '/home/jinjun/.gvm', 'SSH_CONNECTION': '127.0.0.1 39996 127.0.0.1 22', 'GOROOT': '/home/jinjun/.gvm/gos/go1.19.1', 'NVM_DIR': '/local/rcs/jinjun/.nvm', 'VSCODE_GIT_ASKPASS_EXTRA_ARGS': '', 'XDG_SESSION_CLASS': 'user', 'PYTHONPATH': ':/local/rcs/jinjun/code/pytrace-collector:/local/rcs/jinjun/code/pytrace-collector/logs/pypibugs/tried/MrTango+click/MrTango+click', 'TERM': 'screen', 'ZSH': '/home/jinjun/.oh-my-zsh', '_CE_CONDA': '', 'VSCODE_NONCE': 'd0bc7031-48a3-4719-8bb5-ef236ddd0016', 'ZDOTDIR': '/home/jinjun', 'USER': 'jinjun', 'TMUX_PANE': '%3', 'VSCODE_GIT_IPC_HANDLE': '/run/user/19200/vscode-git-13d67c6199.sock', 'CONDA_SHLVL': '3', 'SHLVL': '3', 'PAGER': 'cat', '_P9K_SSH_TTY': '/dev/pts/20', 'XDG_SESSION_ID': '43', 'CONDA_PYTHON_EXE': '/local/rcs/jinjun/miniforge3/bin/python', 'LD_LIBRARY_PATH': '/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/lib:/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/lib', 'XDG_RUNTIME_DIR': '/run/user/19200', 'SSL_CERT_FILE': '/usr/lib/ssl/certs/ca-certificates.crt', 'SSH_CLIENT': '127.0.0.1 46946 22', 'CONDA_DEFAULT_ENV': 'MrTango+click', 'P9K_SSH': '1', 'LC_ALL': 'en_US.UTF-8', 'VSCODE_GIT_ASKPASS_MAIN': '/home/jinjun/.vscode-server/cli/servers/Stable-31c37ee8f63491495ac49e43b8544550fbae4533/server/extensions/git/dist/askpass-main.js', 'XDG_DATA_DIRS': '/usr/local/share:/usr/share:/var/lib/snapd/desktop', 'BROWSER': '/home/jinjun/.vscode-server/cli/servers/Stable-31c37ee8f63491495ac49e43b8544550fbae4533/server/bin/helpers/browser.sh', 'PATH': '/home/jinjun/.gdrive-downloader:/local/arise/jinjun/miniforge3/bin:/home/jinjun/.gvm/pkgsets/go1.19.1/global/bin:/home/jinjun/.gvm/gos/go1.19.1/bin:/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/bin:/home/jinjun/.gvm/bin:/local/rcs/jinjun/miniforge3/envs/MrTango+click/bin:/local/rcs/jinjun/miniforge3/condabin:/home/jinjun/.gdrive-downloader:/local/arise/jinjun/miniforge3/bin:/home/jinjun/.vscode-server/cli/servers/Stable-31c37ee8f63491495ac49e43b8544550fbae4533/server/bin/remote-cli:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/jinjun/.local/bin:/home/jinjun/.local/bin', 'DBUS_SESSION_BUS_ADDRESS': 'unix:path=/run/user/19200/bus', 'gvm_go_name': 'go1.19.1', 'CONDA_PREFIX_1': '/local/rcs/jinjun/miniforge3', 'CONDA_PREFIX_2': '/local/rcs/jinjun/miniforge3/envs/mal', 'OLDPWD': '/local/rcs/jinjun/code/pytrace-collector', 'GOPATH': '/home/jinjun/.gvm/pkgsets/go1.19.1/global', 'TERM_PROGRAM': 'tmux', 'VSCODE_IPC_HOOK_CLI': '/run/user/19200/vscode-ipc-518d6355-acaf-4714-a359-be3fe9f21e09.sock', '_': '/local/rcs/jinjun/miniforge3/envs/MrTango+click/bin/python', 'PYTEST_CURRENT_TEST': 'tests/test_utils.py::test_echo_via_pager[cat] (call)'}), 'PAGER', 'less')], _cwd=None, _savesyspath=None}

{_setattr=[(<module 'click._termui_impl' from '/local/rcs/jinjun/code/pytrace-collector/logs/pypibugs/tried/MrTango+click/MrTango+click/click/_termui_impl.py'>, 'isatty', <function isatty at 0x7fe6e9757e50>)], _setitem=[(environ({'SHELL': '/bin/bash', 'LSCOLORS': 'Gxfxcxdxbxegedabagacad', 'USER_ZDOTDIR': '/home/jinjun', 'COLORTERM': 'truecolor', 'LESS': '-R', 'TERM_PROGRAM_VERSION': '3.2a', 'GVM_VERSION': '1.0.22', 'CONDA_EXE': '/local/rcs/jinjun/miniforge3/bin/conda', '_CE_M': '', 'TMUX': '/tmp/tmux-19200/default,59951,3', 'PKG_CONFIG_PATH': '/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/lib/pkgconfig:/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/lib/pkgconfig:', '_P9K_TTY': '/dev/pts/20', 'GVM_PATH_BACKUP': '/home/jinjun/.gvm/bin:/local/rcs/jinjun/miniforge3/envs/mal/bin:/local/rcs/jinjun/miniforge3/condabin:/home/jinjun/.gdrive-downloader:/local/arise/jinjun/miniforge3/bin:/home/jinjun/.gvm/pkgsets/go1.19.1/global/bin:/home/jinjun/.gvm/gos/go1.19.1/bin:/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/bin:/home/jinjun/.gvm/bin:/home/jinjun/.vscode-server/cli/servers/Stable-31c37ee8f63491495ac49e43b8544550fbae4533/server/bin/remote-cli:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/jinjun/.local/bin:/home/jinjun/.local/bin:/home/jinjun/.local/bin', 'P9K_TTY': 'old', 'LC_FIG_SET_PARENT': '4c022497-5122-4b80-b325-c89bab32302a', 'PWD': '/local/rcs/jinjun/code/pytrace-collector/logs/pypibugs/tried/MrTango+click/MrTango+click', 'LOGNAME': 'jinjun', 'XDG_SESSION_TYPE': 'tty', 'CONDA_PREFIX': '/local/rcs/jinjun/miniforge3/envs/MrTango+click', 'VSCODE_GIT_ASKPASS_NODE': '/home/jinjun/.vscode-server/cli/servers/Stable-31c37ee8f63491495ac49e43b8544550fbae4533/server/node', 'MOTD_SHOWN': 'pam', 'VSCODE_INJECTION': '1', 'GVM_OVERLAY_PREFIX': '/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay', 'HOME': '/home/jinjun', 'LANG': 'en_US.UTF-8', 'DYLD_LIBRARY_PATH': '/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/lib:/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/lib', 'LS_COLORS': 'rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:', 'gvm_pkgset_name': 'global', 'SSL_CERT_DIR': '/usr/lib/ssl/certs', 'CONDA_PROMPT_MODIFIER': '(MrTango+click) ', 'GIT_ASKPASS': '/home/jinjun/.vscode-server/cli/servers/Stable-31c37ee8f63491495ac49e43b8544550fbae4533/server/extensions/git/dist/askpass.sh', 'GVM_ROOT': '/home/jinjun/.gvm', 'SSH_CONNECTION': '127.0.0.1 39996 127.0.0.1 22', 'GOROOT': '/home/jinjun/.gvm/gos/go1.19.1', 'NVM_DIR': '/local/rcs/jinjun/.nvm', 'VSCODE_GIT_ASKPASS_EXTRA_ARGS': '', 'XDG_SESSION_CLASS': 'user', 'PYTHONPATH': ':/local/rcs/jinjun/code/pytrace-collector:/local/rcs/jinjun/code/pytrace-collector/logs/pypibugs/tried/MrTango+click/MrTango+click', 'TERM': 'screen', 'ZSH': '/home/jinjun/.oh-my-zsh', '_CE_CONDA': '', 'VSCODE_NONCE': 'd0bc7031-48a3-4719-8bb5-ef236ddd0016', 'ZDOTDIR': '/home/jinjun', 'USER': 'jinjun', 'TMUX_PANE': '%3', 'VSCODE_GIT_IPC_HANDLE': '/run/user/19200/vscode-git-13d67c6199.sock', 'CONDA_SHLVL': '3', 'SHLVL': '3', 'PAGER': 'cat', '_P9K_SSH_TTY': '/dev/pts/20', 'XDG_SESSION_ID': '43', 'CONDA_PYTHON_EXE': '/local/rcs/jinjun/miniforge3/bin/python', 'LD_LIBRARY_PATH': '/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/lib:/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/lib', 'XDG_RUNTIME_DIR': '/run/user/19200', 'SSL_CERT_FILE': '/usr/lib/ssl/certs/ca-certificates.crt', 'SSH_CLIENT': '127.0.0.1 46946 22', 'CONDA_DEFAULT_ENV': 'MrTango+click', 'P9K_SSH': '1', 'LC_ALL': 'en_US.UTF-8', 'VSCODE_GIT_ASKPASS_MAIN': '/home/jinjun/.vscode-server/cli/servers/Stable-31c37ee8f63491495ac49e43b8544550fbae4533/server/extensions/git/dist/askpass-main.js', 'XDG_DATA_DIRS': '/usr/local/share:/usr/share:/var/lib/snapd/desktop', 'BROWSER': '/home/jinjun/.vscode-server/cli/servers/Stable-31c37ee8f63491495ac49e43b8544550fbae4533/server/bin/helpers/browser.sh', 'PATH': '/home/jinjun/.gdrive-downloader:/local/arise/jinjun/miniforge3/bin:/home/jinjun/.gvm/pkgsets/go1.19.1/global/bin:/home/jinjun/.gvm/gos/go1.19.1/bin:/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/bin:/home/jinjun/.gvm/bin:/local/rcs/jinjun/miniforge3/envs/MrTango+click/bin:/local/rcs/jinjun/miniforge3/condabin:/home/jinjun/.gdrive-downloader:/local/arise/jinjun/miniforge3/bin:/home/jinjun/.vscode-server/cli/servers/Stable-31c37ee8f63491495ac49e43b8544550fbae4533/server/bin/remote-cli:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/jinjun/.local/bin:/home/jinjun/.local/bin', 'DBUS_SESSION_BUS_ADDRESS': 'unix:path=/run/user/19200/bus', 'gvm_go_name': 'go1.19.1', 'CONDA_PREFIX_1': '/local/rcs/jinjun/miniforge3', 'CONDA_PREFIX_2': '/local/rcs/jinjun/miniforge3/envs/mal', 'OLDPWD': '/local/rcs/jinjun/code/pytrace-collector', 'GOPATH': '/home/jinjun/.gvm/pkgsets/go1.19.1/global', 'TERM_PROGRAM': 'tmux', 'VSCODE_IPC_HOOK_CLI': '/run/user/19200/vscode-ipc-518d6355-acaf-4714-a359-be3fe9f21e09.sock', '_': '/local/rcs/jinjun/miniforge3/envs/MrTango+click/bin/python', 'PYTEST_CURRENT_TEST': 'tests/test_utils.py::test_echo_via_pager[cat] (call)'}), 'PAGER', 'less')], _cwd=None, _savesyspath=None}


def _pipepager(text, cmd, color):
    """Page through text by feeding it to another program.  Invoking a
    pager through this might support colors.
    """
    import subprocess
    env = dict(os.environ)

    # If we're piping to less we might support colors under the
    # condition that
    cmd_detail = cmd.rsplit('/', 1)[-1].split()
    if color is None and cmd_detail[0] == 'less':
        less_flags = os.environ.get('LESS', '') + ' '.join(cmd_detail[1:])
        if not less_flags:
            env['LESS'] = '-R'
            color = True
        elif 'r' in less_flags or 'R' in less_flags:
            color = True

    if not color:
        text = strip_ansi(text)

    c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
                         env=env)
    encoding = get_best_encoding(c.stdin)
    try:
        c.stdin.write(text.encode(encoding, 'replace'))
        c.stdin.close()
    except (IOError, KeyboardInterrupt):
        pass

    # Less doesn't respect ^C, but catches it for its own UI purposes (aborting
    # search or other commands inside less).
    #
    # That means when the user hits ^C, the parent process (click) terminates,
    # but less is still alive, paging the output and messing up the terminal.
    #
    # If the user wants to make the pager exit on ^C, they should set
    # `LESS='-K'`. It's not our decision to make.
    while True:
        try:
            c.wait()
        except KeyboardInterrupt:
            pass
        else:
            break

_pipepager(text='haha\n', cmd='cat', color=None)

'haha\n'

'haha\n'

<module 'subprocess' from '/local/rcs/jinjun/miniforge3/envs/MrTango+click/lib/python3.9/subprocess.py'>

{'SHELL': '/bin/bash', 'LSCOLORS': 'Gxfxcxdxbxegedabagacad', 'USER_ZDOTDIR': '/home/jinjun', 'COLORTERM': 'truecolor', 'LESS': '-R', 'TERM_PROGRAM_VERSION': '3.2a', 'GVM_VERSION': '1.0.22', 'CONDA_EXE': '/local/rcs/jinjun/miniforge3/bin/conda', '_CE_M': '', 'TMUX': '/tmp/tmux-19200/default,59951,3', 'PKG_CONFIG_PATH': '/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/lib/pkgconfig:/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/lib/pkgconfig:', '_P9K_TTY': '/dev/pts/20', 'GVM_PATH_BACKUP': '/home/jinjun/.gvm/bin:/local/rcs/jinjun/miniforge3/envs/mal/bin:/local/rcs/jinjun/miniforge3/condabin:/home/jinjun/.gdrive-downloader:/local/arise/jinjun/miniforge3/bin:/home/jinjun/.gvm/pkgsets/go1.19.1/global/bin:/home/jinjun/.gvm/gos/go1.19.1/bin:/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/bin:/home/jinjun/.gvm/bin:/home/jinjun/.vscode-server/cli/servers/Stable-31c37ee8f63491495ac49e43b8544550fbae4533/server/bin/remote-cli:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/jinjun/.local/bin:/home/jinjun/.local/bin:/home/jinjun/.local/bin', 'P9K_TTY': 'old', 'LC_FIG_SET_PARENT': '4c022497-5122-4b80-b325-c89bab32302a', 'PWD': '/local/rcs/jinjun/code/pytrace-collector/logs/pypibugs/tried/MrTango+click/MrTango+click', 'LOGNAME': 'jinjun', 'XDG_SESSION_TYPE': 'tty', 'CONDA_PREFIX': '/local/rcs/jinjun/miniforge3/envs/MrTango+click', 'VSCODE_GIT_ASKPASS_NODE': '/home/jinjun/.vscode-server/cli/servers/Stable-31c37ee8f63491495ac49e43b8544550fbae4533/server/node', 'MOTD_SHOWN': 'pam', 'VSCODE_INJECTION': '1', 'GVM_OVERLAY_PREFIX': '/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay', 'HOME': '/home/jinjun', 'LANG': 'en_US.UTF-8', 'DYLD_LIBRARY_PATH': '/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/lib:/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/lib', 'LS_COLORS': 'rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:', 'gvm_pkgset_name': 'global', 'SSL_CERT_DIR': '/usr/lib/ssl/certs', 'CONDA_PROMPT_MODIFIER': '(MrTango+click) ', 'GIT_ASKPASS': '/home/jinjun/.vscode-server/cli/servers/Stable-31c37ee8f63491495ac49e43b8544550fbae4533/server/extensions/git/dist/askpass.sh', 'GVM_ROOT': '/home/jinjun/.gvm', 'SSH_CONNECTION': '127.0.0.1 39996 127.0.0.1 22', 'GOROOT': '/home/jinjun/.gvm/gos/go1.19.1', 'NVM_DIR': '/local/rcs/jinjun/.nvm', 'VSCODE_GIT_ASKPASS_EXTRA_ARGS': '', 'XDG_SESSION_CLASS': 'user', 'PYTHONPATH': ':/local/rcs/jinjun/code/pytrace-collector:/local/rcs/jinjun/code/pytrace-collector/logs/pypibugs/tried/MrTango+click/MrTango+click', 'TERM': 'screen', 'ZSH': '/home/jinjun/.oh-my-zsh', '_CE_CONDA': '', 'VSCODE_NONCE': 'd0bc7031-48a3-4719-8bb5-ef236ddd0016', 'ZDOTDIR': '/home/jinjun', 'USER': 'jinjun', 'TMUX_PANE': '%3', 'VSCODE_GIT_IPC_HANDLE': '/run/user/19200/vscode-git-13d67c6199.sock', 'CONDA_SHLVL': '3', 'SHLVL': '3', 'PAGER': 'cat', '_P9K_SSH_TTY': '/dev/pts/20', 'XDG_SESSION_ID': '43', 'CONDA_PYTHON_EXE': '/local/rcs/jinjun/miniforge3/bin/python', 'LD_LIBRARY_PATH': '/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/lib:/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/lib', 'XDG_RUNTIME_DIR': '/run/user/19200', 'SSL_CERT_FILE': '/usr/lib/ssl/certs/ca-certificates.crt', 'SSH_CLIENT': '127.0.0.1 46946 22', 'CONDA_DEFAULT_ENV': 'MrTango+click', 'P9K_SSH': '1', 'LC_ALL': 'en_US.UTF-8', 'VSCODE_GIT_ASKPASS_MAIN': '/home/jinjun/.vscode-server/cli/servers/Stable-31c37ee8f63491495ac49e43b8544550fbae4533/server/extensions/git/dist/askpass-main.js', 'XDG_DATA_DIRS': '/usr/local/share:/usr/share:/var/lib/snapd/desktop', 'BROWSER': '/home/jinjun/.vscode-server/cli/servers/Stable-31c37ee8f63491495ac49e43b8544550fbae4533/server/bin/helpers/browser.sh', 'PATH': '/home/jinjun/.gdrive-downloader:/local/arise/jinjun/miniforge3/bin:/home/jinjun/.gvm/pkgsets/go1.19.1/global/bin:/home/jinjun/.gvm/gos/go1.19.1/bin:/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/bin:/home/jinjun/.gvm/bin:/local/rcs/jinjun/miniforge3/envs/MrTango+click/bin:/local/rcs/jinjun/miniforge3/condabin:/home/jinjun/.gdrive-downloader:/local/arise/jinjun/miniforge3/bin:/home/jinjun/.vscode-server/cli/servers/Stable-31c37ee8f63491495ac49e43b8544550fbae4533/server/bin/remote-cli:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/jinjun/.local/bin:/home/jinjun/.local/bin', 'DBUS_SESSION_BUS_ADDRESS': 'unix:path=/run/user/19200/bus', 'gvm_go_name': 'go1.19.1', 'CONDA_PREFIX_1': '/local/rcs/jinjun/miniforge3', 'CONDA_PREFIX_2': '/local/rcs/jinjun/miniforge3/envs/mal', 'OLDPWD': '/local/rcs/jinjun/code/pytrace-collector', 'GOPATH': '/home/jinjun/.gvm/pkgsets/go1.19.1/global', 'TERM_PROGRAM': 'tmux', 'VSCODE_IPC_HOOK_CLI': '/run/user/19200/vscode-ipc-518d6355-acaf-4714-a359-be3fe9f21e09.sock', '_': '/local/rcs/jinjun/miniforge3/envs/MrTango+click/bin/python', 'PYTEST_CURRENT_TEST': 'tests/test_utils.py::test_echo_via_pager[cat] (call)'}


def encode_request(*args):
    """Pack a series of arguments into a RESP array of bulk strings."""
    result = ["*" + str(len(args)) + CRLF]

    for arg in args:
        if arg is None:
            result.append('$-1' + CRLF)
        else:
            s = str(arg)
            result.append('$' + str(len(s)) + CRLF + s + CRLF)

    return "".join(result)

encode_request(args=('ping',))


def bar_factory(chars=None, *, tip=None, background=None, borders=None, errors=None):
    """Create a factory of a bar with the given styling parameters.
    Supports unicode grapheme clusters and emoji chars (those that has length one but when on
    screen occupies two cells).

    Now supports transparent fills! Just send a tip, and leave `chars` as None.
    Also tips are now considered for the 100%, which means it smoothly enters and exits the
    frame to get to 100%!! The effect is super cool, use a multi-char tip to see.

    Args:
        chars (Optional[str]): the sequence of increasing glyphs to fill the bar
            can be None for a transparent fill, unless tip is also None.
        tip (Optional[str): the tip in front of the bar
            can be None, unless chars is also None.
        background (Optional[str]): the pattern to be used underneath the bar
        borders (Optional[Union[str, Tuple[str, str]]): the pattern or patterns to be used
            before and after the bar
        errors (Optional[Union[str, Tuple[str, str]]): the pattern or patterns to be used
            when an underflow or overflow occurs

    Returns:
        a styled bar factory

    """

    @bar_controller
    def inner_bar_factory(length, spinner_factory=None):
        if chars:
            if is_wide(chars[-1]):  # previous chars can be anything.
                def fill_style(complete, filling):  # wide chars fill.
                    odd = bool(complete % 2)
                    fill = (None,) if odd != bool(filling) else ()  # odd XOR filling.
                    fill += (chars[-1], None) * int(complete / 2)  # already marked wide chars.
                    if filling and odd:
                        fill += mark_graphemes((chars[filling - 1],))
                    return fill
            else:  # previous chars cannot be wide.
                def fill_style(complete, filling):  # narrow chars fill.
                    fill = (chars[-1],) * complete  # unneeded marks here.
                    if filling:
                        fill += (chars[filling - 1],)  # no widies here.
                    return fill
        else:
            def fill_style(complete, filling):  # invisible fill.
                return fix_cells(padding[:complete + bool(filling)])

        def running(fill):
            return None, (fix_cells(padding[len(fill) + len_tip:]),)  # this is a 1-tuple.

        def ended(fill):
            border = None if len(fill) + len(underflow) <= length else underflow
            texts = *(() if border else (underflow,)), blanks
            return border, texts

        @bordered(borders, '||')
        def draw_known(apply_state, percent):
            virtual_fill = round(virtual_length * max(0., min(1., percent)))
            fill = fill_style(*divmod(virtual_fill, num_graphemes))
            border, texts = apply_state(fill)
            border = overflow if percent > 1. else None if percent == 1. else border
            return fix_cells(combine_cells(fill, tip, *texts)[len_tip:length + len_tip]), border

        if spinner_factory:
            @bordered(borders, '||')
            def draw_unknown(_percent=None):
                return next(player), None

            player = spinner_player(spinner_factory(length))
        else:
            draw_unknown = None

        padding = (' ',) * len_tip + background * math.ceil((length + len_tip) / len(background))
        virtual_length, blanks = num_graphemes * (length + len_tip), (' ',) * length
        return draw_known, running, ended, draw_unknown

    assert chars or tip, 'tip is mandatory for transparent bars'
    assert not (chars and not is_wide(chars[-1]) and has_wide(chars)), \
        'cannot use grapheme with a narrow last char'

    chars = split_graphemes(chars or '')  # the only one not yet marked.
    tip, background = (to_cells(x) for x in (tip, background or ' '))
    underflow, overflow = extract_fill_graphemes(errors, (f'⚠{VS_15}', f'✗{VS_15}'))
    num_graphemes, len_tip = len(chars) or 1, len(tip)
    return inner_bar_factory

bar_factory(chars='▏▎▍▌▋▊▉█', tip=None, background=None, borders=None, errors=None)

<function bar_controller.<locals>.bar_assembler_factory at 0x7f35540940d0>


def static_sliding_window(sep, gap, contents, length, right, initial):
    """Implement a sliding window over some content interspersed with a separator.
    It is very efficient, storing data in only one string.

    Note that the implementation is "static" in the sense that the content is pre-
    calculated and maintained static, but actually when the window slides both the
    separator and content seem to be moved.
    Also keep in mind that `right` is for the content, not the window.
    """

    def sliding_window():
        pos = initial
        while True:
            if pos < 0:
                pos += original
            elif pos >= original:
                pos -= original
            yield content[pos:pos + length]
            pos += step

    adjusted_sep = fix_cells((sep * math.ceil(gap / len(sep)))[:gap]) if gap else ''
    content = tuple(chain.from_iterable(chain.from_iterable(zip(repeat(adjusted_sep), contents))))
    original, step = len(content), -1 if right else 1
    assert length <= original, f'window slides inside content, {length} must be <= {original}'
    content += content[:length]
    return sliding_window()

static_sliding_window(sep=(' ',), gap=3, contents=(('a', 'b', 'c'),), length=3, right=True, initial=0)

(('a', 'b', 'c'),)

(' ', ' ', ' ', 'a', 'b', 'c')

(('a', 'b', 'c'),)

<function static_sliding_window.<locals>.sliding_window at 0x7f354c6a3dc0>

(' ', ' ', ' ', 'a', 'b', 'c')

(' ', ' ', ' ', 'a', 'b', 'c', ' ', ' ', ' ')


def test_split_options(param, expected):
    if expected is SAME:
        expected = param
    assert split_options(param) == expected

test_split_options(param=None, expected=(None, None))


def calibrated_fps(calibrate):
    """Calibration of the dynamic frames per second engine.

    I've started with the equation y = log10(x + m) * k + n, where:
      y is the desired fps, m and n are horizontal and vertical translation,
      k is a calibration factor, computed from some user input c (see readme for details).

    Considering minfps and maxfps as given constants, I came to:
      fps = log10(x + 1) * k + minfps, which must be equal to maxfps for x = c,
    so the factor k = (maxfps - minfps) / log10(c + 1), and
      fps = log10(x + 1) * (maxfps - minfps) / log10(c + 1) + minfps

    Neat! ;)

    Args:
        calibrate (float): user provided

    Returns:
        a callable to calculate the fps

    """
    min_fps, max_fps = 2., 60.
    calibrate = max(1e-6, calibrate)
    adjust_log_curve = 100. / min(calibrate, 100.)  # adjust the curve for small numbers
    factor = (max_fps - min_fps) / math.log10((calibrate * adjust_log_curve) + 1.)

    def fps(rate):
        if rate <= 0:
            return 10.  # bootstrap speed
        if rate < calibrate:
            return math.log10((rate * adjust_log_curve) + 1.) * factor + min_fps
        return max_fps

    return fps

calibrated_fps(calibrate=-5.0)

<function calibrated_fps.<locals>.fps at 0x7f355669f1f0>


def elapsed_text(seconds, precise, prefix=''):
    seconds = round(seconds, 1 if precise else 0)
    if seconds < 60.:
        return '{}{:{}f}s'.format(prefix, seconds, .1 if precise else .0)

    minutes, seconds = divmod(seconds, 60.)
    if minutes < 60.:
        return '{}{:.0f}:{:0{}f}'.format(prefix, minutes, seconds, 4.1 if precise else 2.0)

    hours, minutes = divmod(minutes, 60.)
    return '{}{:.0f}:{:02.0f}:{:0{}f}'.format(prefix, hours, minutes, seconds,
                                              4.1 if precise else 2.0)

elapsed_text(seconds=1.23, precise=True, prefix='')


def getblockimpl(lines, first, last, pilcrow):
    max = len(lines) - 1
    first -= 1
    last -= 1
    i = first
    while i < max and not hastext(lines[i]):
        if i >= last and istoplevel(lines[i + 1]):
            return None, None, '# Nothing to send.' + pilcrow + eol
        i += 1
    while last < max and not istoplevel(lines[last + 1]):
        last += 1
    while first < last and not hastext(lines[first]):
        first += 1
    while first and not istoplevel(lines[first]):
        first -= 1
    lines[last] # Check for out of range.
    return first, last, eol.join(l for l in lines[first:last + 1] if hastext(l)) + pilcrow + eol

getblockimpl(lines=['', 'hello', 'function with', ' indented block', 'class with', '', '  block after blank', '  \tand its own indented block', '\t', '  and back again after a wrong blank', '', 'something else', '\t', ' \t', 'last'], first=10, last=11, pilcrow='')


def parse(text, *, strip=False, strict=True):
    parser = loguru._colorizer.AnsiParser()
    parser.feed(text)
    tokens = parser.done(strict=strict)

    if strip:
        return parser.strip(tokens)
    return parser.colorize(tokens, "")

parse(text='<red>Foo</red>\n', strip=False, strict=True)

'<red>Foo</red>\n'

'<red>Foo</red>\n'

'<red>Foo</red>\n'


def random_port():
        port = helper.random_port()
        while port in generated_ports:
            port = helper.random_port()
        generated_ports.add(port)
        return port

random_port(generated_ports=set())


def test_split_img_txt_da(inputs):
    txt_da = DocumentArray()
    img_da = DocumentArray()
    for doc in inputs[0]:
        split_img_txt_da(doc, img_da, txt_da)
    assert len(txt_da) == inputs[1][0]
    assert len(img_da) == inputs[1][1]

test_split_img_txt_da(inputs=(<DocumentArray (length=4) at 140538627068688>, (3, 1)))

<DocumentArray (length=3) at 140537876329136>

<DocumentArray (length=0) at 140537876329136>

<DocumentArray (length=1) at 140537876329136>

<DocumentArray (length=2) at 140537876329136>

<DocumentArray (length=0) at 140537876330432>

<DocumentArray (length=1) at 140537876330432>


def test_server_preprocess_ndarray_image(image_uri, size):
    d1 = Document(uri=image_uri)
    d1.load_uri_to_blob()
    d2 = Document(uri=image_uri)
    d2.load_uri_to_image_tensor()

    t1 = _transform_blob(size)(d1.blob).numpy()
    t2 = _transform_ndarray(size)(d2.tensor).numpy()
    assert t1.shape == t2.shape

test_server_preprocess_ndarray_image(image_uri='/local/rcs/jinjun/code/pytrace-collector/logs/self_collected/tried/jina-ai+clip-as-service/jina-ai+clip-as-service/tests/img/00000.jpg', size=224)

<Document ('id', 'mime_type', 'uri') at 3b1f02f040a677b8a7dfd83176b52d78>

<Document ('id', 'blob', 'mime_type', 'uri') at 3b1f02f040a677b8a7dfd83176b52d78>

<Document ('id', 'mime_type', 'uri') at eee52bb275edc026b2fb333d7656e3c6>

<Document ('id', 'tensor', 'mime_type', 'uri') at eee52bb275edc026b2fb333d7656e3c6>


def _from_timetuple(self, t):
        self.days_from_epoch = calendar.timegm(t) // Date.DAY

_from_timetuple(self=Date(0), t=time.struct_time(tm_year=2024, tm_mon=4, tm_mday=3, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=2, tm_yday=94, tm_isdst=-1))


def _insert(self, key, value):
        flat_key = self._serialize_key(key)
        i = self._index.get(flat_key, -1)
        if i >= 0:
            self._items[i] = (key, value)
        else:
            self._items.append((key, value))
            self._index[flat_key] = len(self._items) - 1

_insert(self=OrderedMapSerializedKey([]), key='みbob', value=199)


def stringified_dict_contains_value(key, value, str_dict):
    """Checks if dict in for of string like "{'test': 5}" contains
    key/value pair. This works faster, then creating actual dict
    from string since this operation is called for each task in case
    of kwargs search."""
    if not str_dict:
        return False
    value = str(value)
    try:
        # + 3 for key right quote, one for colon and one for space
        key_index = str_dict.index(key) + len(key) + 3
    except ValueError:
        return False
    try:
        comma_index = str_dict.index(',', key_index)
    except ValueError:
        # last value in dict
        comma_index = str_dict.index('}', key_index)
    return str(value) == str_dict[key_index:comma_index].strip('"\'')

stringified_dict_contains_value(key='test', value=5, str_dict="{'test': 5}")


def abs_path(path):
    path = os.path.expanduser(path)
    if not os.path.isabs(path):
        cwd = os.environ.get('PWD') or os.getcwd()
        path = os.path.join(cwd, path)
    return path

abs_path(path='~/file.txt')


def authenticate(pattern, email):
    if '|' in pattern:
        return email in pattern.split('|')
    if '*' in pattern:
        pattern = re.escape(pattern).replace(r'\.\*', r"[A-Za-z0-9!#$%&'*+/=?^_`{|}~.\-]*")
        return re.fullmatch(pattern, email)
    return pattern == email

authenticate(pattern='.*@example.com', email='one@example.com')


def _add_committer_to_committers(all_committers: List[str], initials: str, name: str, email: str):
    committer_formatted = f'{initials},{name},{email}\n'
    committer_position = _position_of_committer_with_initials(all_committers, initials)
    if committer_position is _COMMITTER_NOT_PRESENT:
        all_committers.append(committer_formatted)
    else:
        all_committers[committer_position] = committer_formatted

_add_committer_to_committers(all_committers=[], initials='initials3', name='name3', email='email3')


def add_committer(initials: str, name: str, email: str, *, file_path: str = _GLOBAL) -> None:
    all_committers = _read_all_committers_from_file(file_path)
    _add_committer_to_committers(all_committers, initials, name, email)
    _write_committers_to_file(all_committers, file_path)

add_committer(initials='initials3', name='name3', email='email3', file_path='/home/jinjun/.guet/committers')


def _load_global_committers(path: str) -> List[Committer]:
    lines = read_lines(path)
    committers = []
    for line in lines:
        initials, name, email = line.rstrip().split(',')
        committers.append(GlobalCommitter(initials=initials, name=name, email=email))
    return committers

_load_global_committers(path='/home/jinjun/.guet/committers')

[<guet.committers.global_committer.GlobalCommitter object at 0x7fc0ad6a46a0>]


def _load_local_committers(path: str, path_to_project_root: str) -> List[Committer]:
    lines = read_lines(path)
    committers = []
    for line in lines:
        initials, name, email = line.rstrip().split(',')
        committers.append(LocalCommitter(initials=initials, name=name, email=email, project_root=path_to_project_root))
    return committers

_load_local_committers(path='/path/to/project/root/.guet/committers', path_to_project_root='/path/to/project/root')

[<guet.committers.local_committer.LocalCommitter object at 0x7fc0ad2c3d00>]


def _add_to_current_set_lines(current_set, formatted_set_committers_information):
    git_path = git_path_from_cwd()
    line_with_git_path = next((line for line in current_set if line.endswith(git_path)), None)
    if line_with_git_path:
        index = current_set.index(line_with_git_path)
        current_set[index] = formatted_set_committers_information
    else:
        current_set.append(formatted_set_committers_information)

_add_to_current_set_lines(current_set=['initials3,initials4,1000000000000,/absolute/path/to/other/.git'], formatted_set_committers_information='initials1,initials2,1000000000000,/absolute/path/to/.git')


def _parse_file_content(create, path_to_hook):
        _content = Hook._get_file_content(path_to_hook, create)
        if _content != GUET_HOOK_FILE:
            _content = Hook._handle_mismatched_content(_content, create)
        return _content

_parse_file_content(create=True, path_to_hook='/path/to/.git/hooks/name')

['#! /usr/bin/env python3', 'from guet.hooks import manage', 'import sys', 'manage(sys.argv[0])']


def which(exe=None):
    """
    Python clone of /usr/bin/which
    """

    if not exe:
        log.error("No executable was passed to be searched by salt.utils.path.which()")
        return None

    ## define some utilities (we use closures here because our predecessor used them)
    def is_executable_common(path):
        """
        This returns truth if posixy semantics (which python simulates on
        windows) states that this is executable.
        """
        return os.path.isfile(path) and os.access(path, os.X_OK)

    def resolve(path):
        """
        This will take a path and recursively follow the link until we get to a
        real file.
        """
        while os.path.islink(path):
            res = readlink(path)

            # if the link points to a relative target, then convert it to an
            # absolute path relative to the original path
            if not os.path.isabs(res):
                directory, _ = os.path.split(path)
                res = join(directory, res)
            path = res
        return path

    # windows-only
    def has_executable_ext(path, ext_membership):
        """
        Extract the extension from the specified path, lowercase it so we
        can be insensitive, and then check it against the available exts.
        """
        p, ext = os.path.splitext(path)
        return ext.lower() in ext_membership

    ## prepare related variables from the environment
    res = salt.utils.stringutils.to_unicode(os.environ.get("PATH", ""))
    system_path = res.split(os.pathsep)

    # add some reasonable defaults in case someone's PATH is busted
    if not salt.utils.platform.is_windows():
        res = set(system_path)
        extended_path = [
            "/sbin",
            "/bin",
            "/usr/sbin",
            "/usr/bin",
            "/usr/local/sbin",
            "/usr/local/bin",
        ]
        system_path.extend([p for p in extended_path if p not in res])

    ## now to define the semantics of what's considered executable on a given platform
    if salt.utils.platform.is_windows():
        # executable semantics on windows requires us to search PATHEXT
        res = salt.utils.stringutils.to_str(os.environ.get("PATHEXT", ".EXE"))

        # generate two variables, one of them for O(n) searches (but ordered)
        # and another for O(1) searches. the previous guy was trying to use
        # memoization with a function that has no arguments, this provides
        # the exact same benefit
        pathext = res.split(os.pathsep)
        res = {ext.lower() for ext in pathext}

        # check if our caller already specified a valid extension as then we don't need to match it
        _, ext = os.path.splitext(exe)
        if ext.lower() in res:
            pathext = [""]

            is_executable = is_executable_common

        # The specified extension isn't valid, so we just assume it's part of the
        # filename and proceed to walk the pathext list
        else:
            is_executable = lambda path, membership=res: is_executable_common(
                path
            ) and has_executable_ext(path, membership)

    else:
        # in posix, there's no such thing as file extensions..only zuul
        pathext = [""]

        # executable semantics are pretty simple on reasonable platforms...
        is_executable = is_executable_common

    ## search for the executable

    # check to see if the full path was specified as then we don't need
    # to actually walk the system_path for any reason
    if is_executable(exe):
        return exe

    # now to search through our system_path
    for path in system_path:
        p = join(path, exe)

        # iterate through all extensions to see which one is executable
        for ext in pathext:
            pext = p + ext
            rp = resolve(pext)
            if is_executable(rp):
                return p + ext
            continue
        continue

    ## if something was executable, we should've found it already...
    log.trace(
        "'%s' could not be found in the following search path: '%s'", exe, system_path
    )
    return None

which(exe='true')

['/home/jinjun/.gdrive-downloader', '/local/arise/jinjun/miniforge3/bin', '/home/jinjun/.gvm/pkgsets/go1.19.1/global/bin', '/home/jinjun/.gvm/gos/go1.19.1/bin', '/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/bin', '/home/jinjun/.gvm/bin', '/local/rcs/jinjun/miniforge3/envs/saltstack+salt/bin', '/local/rcs/jinjun/miniforge3/condabin', '/home/jinjun/.gdrive-downloader', '/local/arise/jinjun/miniforge3/bin', '/home/jinjun/.vscode-server/cli/servers/Stable-31c37ee8f63491495ac49e43b8544550fbae4533/server/bin/remote-cli', '/usr/local/sbin', '/usr/local/bin', '/usr/sbin', '/usr/bin', '/sbin', '/bin', '/usr/games', '/usr/local/games', '/snap/bin', '/home/jinjun/.local/bin', '/home/jinjun/.local/bin']

{'/usr/games', '/sbin', '/home/jinjun/.gvm/gos/go1.19.1/bin', '/home/jinjun/.gvm/bin', '/home/jinjun/.gvm/pkgsets/go1.19.1/global/bin', '/home/jinjun/.local/bin', '/snap/bin', '/usr/bin', '/home/jinjun/.vscode-server/cli/servers/Stable-31c37ee8f63491495ac49e43b8544550fbae4533/server/bin/remote-cli', '/home/jinjun/.gvm/pkgsets/go1.19.1/global/overlay/bin', '/bin', '/local/rcs/jinjun/miniforge3/envs/saltstack+salt/bin', '/usr/local/games', '/usr/local/sbin', '/home/jinjun/.gdrive-downloader', '/local/rcs/jinjun/miniforge3/condabin', '/local/arise/jinjun/miniforge3/bin', '/usr/sbin', '/usr/local/bin'}

<function which.<locals>.is_executable_common at 0x7f27d3207c10>

<function which.<locals>.resolve at 0x7f27d3207820>

<function which.<locals>.has_executable_ext at 0x7f27d31c20d0>

['/sbin', '/bin', '/usr/sbin', '/usr/bin', '/usr/local/sbin', '/usr/local/bin']


def join(*parts, **kwargs):
    """
    This functions tries to solve some issues when joining multiple absolute
    paths on both *nix and windows platforms.

    See tests/unit/utils/path_join_test.py for some examples on what's being
    talked about here.

    The "use_posixpath" kwarg can be be used to force joining using poxixpath,
    which is useful for Salt fileserver paths on Windows masters.
    """
    parts = [salt.utils.stringutils.to_str(part) for part in parts]

    kwargs = salt.utils.args.clean_kwargs(**kwargs)
    use_posixpath = kwargs.pop("use_posixpath", False)
    if kwargs:
        salt.utils.args.invalid_kwargs(kwargs)

    pathlib = posixpath if use_posixpath else os.path

    # Normalize path converting any os.sep as needed
    parts = [pathlib.normpath(p) for p in parts]

    try:
        root = parts.pop(0)
    except IndexError:
        # No args passed to func
        return ""

    root = salt.utils.stringutils.to_unicode(root)
    if not parts:
        ret = root
    else:
        stripped = [p.lstrip(os.sep) for p in parts]
        ret = pathlib.join(root, *salt.utils.data.decode(stripped))
    return pathlib.normpath(ret)

join(parts=('/home/jinjun/.gdrive-downloader', 'true'), kwargs={})

<module 'posixpath' from '/local/rcs/jinjun/miniforge3/envs/saltstack+salt/lib/python3.9/posixpath.py'>


def parse(theme_file):
    """Parse the theme file."""
    data = util.read_file_json(theme_file)

    if "wallpaper" not in data:
        data["wallpaper"] = "None"

    if "alpha" not in data:
        data["alpha"] = util.Color.alpha_num

    # Terminal.sexy format.
    if "color" in data:
        data = terminal_sexy_to_wal(data)

    return data

parse(theme_file='tests/test_files/test_file.json')


def get(img, cache_dir=CACHE_DIR, iterative=False, recursive=False):
    """Validate image input."""
    if os.path.isfile(img):
        wal_img = img

    elif os.path.isdir(img):
        if iterative:
            wal_img = get_next_image(img, recursive)

        else:
            wal_img = get_random_image(img, recursive)

    else:
        logging.error("No valid image file found.")
        sys.exit(1)

    wal_img = os.path.abspath(wal_img)

    # Cache the image file path.
    util.save_file(wal_img, os.path.join(cache_dir, "wal"))

    logging.info("Using image \033[1;37m%s\033[0m.", os.path.basename(wal_img))
    return wal_img

get(img='tests/test_files/test.jpg', cache_dir='/home/jinjun/.cache/wal', iterative=False, recursive=False)


def get_random_image(img_dir, recursive):
    """Pick a random image file from a directory."""
    if recursive:
        images, current_wall = get_image_dir_recursive(img_dir)
    else:
        images, current_wall = get_image_dir(img_dir)

    if len(images) > 2 and current_wall in images:
        images.remove(current_wall)

    elif not images:
        logging.error("No images found in directory.")
        sys.exit(1)

    random.shuffle(images)
    return os.path.join(img_dir if not recursive else "", images[0])

get_random_image(img_dir='tests/test_files', recursive=False)


def get_image_dir(img_dir):
    """Get all images in a directory."""
    current_wall = wallpaper.get()
    current_wall = os.path.basename(current_wall)

    file_types = (".png", ".jpg", ".jpeg", ".jpe", ".gif")

    return [img.name for img in os.scandir(img_dir)
            if img.name.lower().endswith(file_types)], current_wall

get_image_dir(img_dir='tests/test_files')


def check_impl_detail(**guards):
    """This function returns True or False depending on the host platform.
       Examples:
          if check_impl_detail():               # only on CPython (default)
          if check_impl_detail(jython=True):    # only on Jython
          if check_impl_detail(cpython=False):  # everywhere except on CPython
    """
    guards, default = _parse_guards(guards)
    return guards.get(sys.implementation.name, default)

check_impl_detail(guards={})


def _find_and_replace_patterns(content, patterns_and_insertions):
  r"""content: str

  patterns_and_insertions: List[Dict]

  Example for patterns_and_insertions:

      [
          {
              "pattern" :
              r"(?:\\figcompfigures{\s*)(?P<first>.*?)\s*}\s*{\s*(?P<second>.*?)\s*}\s*{\s*(?P<third>.*?)\s*}",
              "insertion" :
              r"\parbox[c]{{{second}\linewidth}}{{\includegraphics[width={third}\linewidth]{{figures/{first}}}}}}",
              "description": "Replace figcompfigures"
          },
      ]
  """
  for pattern_and_insertion in patterns_and_insertions:
    pattern = pattern_and_insertion['pattern']
    insertion = pattern_and_insertion['insertion']
    description = pattern_and_insertion['description']
    logging.info('Processing pattern: %s.', description)
    p = regex.compile(pattern)
    m = p.search(content)
    while m is not None:
      local_insertion = insertion.format(**m.groupdict())
      if pattern_and_insertion.get('strip_whitespace', True):
        local_insertion = strip_whitespace(local_insertion)
      logging.info(f'Found {content[m.start():m.end()]:<70}')
      logging.info(f'Replacing with {local_insertion:<30}')
      content = content[: m.start()] + local_insertion + content[m.end() :]
      m = p.search(content)
    logging.info('Finished pattern: %s.', description)
  return content

_find_and_replace_patterns(content='& \\figcompfigures{\n\timage1.jpg\n}{\n\t\\ww\n}{\n\t1.0\n\t}\n& \\figcompfigures{image2.jpg}{\\ww}{1.0}', patterns_and_insertions=[{'pattern': '(?:\\\\figcompfigures{\\s*)(?P<first>.*?)\\s*}\\s*{\\s*(?P<second>.*?)\\s*}\\s*{\\s*(?P<third>.*?)\\s*}', 'insertion': '\\parbox[c]{{\n            {second}\\linewidth\n        }}{{\n            \\includegraphics[\n                width={third}\\linewidth\n            ]{{\n                figures/{first}\n            }}\n        }} ', 'description': 'Replace figcompfigures'}])

'& \\parbox[c]{\\ww\\linewidth}{\\includegraphics[width=1.0\\linewidth]{figures/image1.jpg}}\n& \\figcompfigures{image2.jpg}{\\ww}{1.0}'

'& \\parbox[c]{\\ww\\linewidth}{\\includegraphics[width=1.0\\linewidth]{figures/image1.jpg}}\n& \\parbox[c]{\\ww\\linewidth}{\\includegraphics[width=1.0\\linewidth]{figures/image2.jpg}}'

'Replace figcompfigures'

<regex.Match object; span=(2, 49), match='\\figcompfigures{\n\timage1.jpg\n}{\n\t\\ww\n}{\n\t1.0\n\t}'>

<regex.Match object; span=(89, 126), match='\\figcompfigures{image2.jpg}{\\ww}{1.0}'>

'\\parbox[c]{\n            \\ww\\linewidth\n        }{\n            \\includegraphics[\n                width=1.0\\linewidth\n            ]{\n                figures/image1.jpg\n            }\n        } '

'\\parbox[c]{\n            \\ww\\linewidth\n        }{\n            \\includegraphics[\n                width=1.0\\linewidth\n            ]{\n                figures/image2.jpg\n            }\n        } '


def strip_whitespace(text):
  """Strips all whitespace characters.

  https://stackoverflow.com/questions/8270092/remove-all-whitespace-in-a-string
  """
  pattern = regex.compile(r'\s+')
  text = regex.sub(pattern, '', text)
  return text

strip_whitespace(text='\\parbox[c]{\n            \\ww\\linewidth\n        }{\n            \\includegraphics[\n                width=1.0\\linewidth\n            ]{\n                figures/image1.jpg\n            }\n        } ')

'\\parbox[c]{\\ww\\linewidth}{\\includegraphics[width=1.0\\linewidth]{figures/image1.jpg}}'


def merge_args_into_config(args, config_params):
  final_args = copy.deepcopy(config_params)
  config_keys = config_params.keys()
  for key, value in args.items():
    if key in config_keys:
      if any([isinstance(value, t) for t in [str, bool, float, int]]):
        # Overwrites config value with args value.
        final_args[key] = value
      elif isinstance(value, list):
        # Appends args values to config values.
        final_args[key] = value + config_params[key]
      elif isinstance(value, dict):
        # Updates config params with args params.
        final_args[key].update(**value)
    else:
      final_args[key] = value
  return final_args

merge_args_into_config(args={'input_folder': 'foo/bar', 'resize_images': False, 'im_size': 500, 'compress_pdf': False, 'pdf_im_resolution': 500, 'images_allowlist': {'path1/': 1000}, 'commands_to_delete': ['\\todo1'], 'use_external_tikz': 'foo/bar/tikz'}, config_params={'input_folder': 'foo_/bar_', 'resize_images': True, 'im_size': 1000, 'compress_pdf': True, 'pdf_im_resolution': 1000, 'images_allowlist': {'path2/': 1000}, 'commands_to_delete': ['\\todo2'], 'use_external_tikz': 'foo_/bar_/tikz_'})

{'input_folder': 'foo/bar', 'resize_images': True, 'im_size': 1000, 'compress_pdf': True, 'pdf_im_resolution': 1000, 'images_allowlist': {'path2/': 1000}, 'commands_to_delete': ['\\todo2'], 'use_external_tikz': 'foo_/bar_/tikz_'}

{'input_folder': 'foo/bar', 'resize_images': False, 'im_size': 1000, 'compress_pdf': True, 'pdf_im_resolution': 1000, 'images_allowlist': {'path2/': 1000}, 'commands_to_delete': ['\\todo2'], 'use_external_tikz': 'foo_/bar_/tikz_'}

{'input_folder': 'foo/bar', 'resize_images': False, 'im_size': 500, 'compress_pdf': True, 'pdf_im_resolution': 1000, 'images_allowlist': {'path2/': 1000}, 'commands_to_delete': ['\\todo2'], 'use_external_tikz': 'foo_/bar_/tikz_'}

{'input_folder': 'foo/bar', 'resize_images': False, 'im_size': 500, 'compress_pdf': False, 'pdf_im_resolution': 1000, 'images_allowlist': {'path2/': 1000}, 'commands_to_delete': ['\\todo2'], 'use_external_tikz': 'foo_/bar_/tikz_'}

{'input_folder': 'foo/bar', 'resize_images': False, 'im_size': 500, 'compress_pdf': False, 'pdf_im_resolution': 500, 'images_allowlist': {'path2/': 1000}, 'commands_to_delete': ['\\todo2'], 'use_external_tikz': 'foo_/bar_/tikz_'}

{'input_folder': 'foo/bar', 'resize_images': False, 'im_size': 500, 'compress_pdf': False, 'pdf_im_resolution': 500, 'images_allowlist': {'path2/': 1000, 'path1/': 1000}, 'commands_to_delete': ['\\todo2'], 'use_external_tikz': 'foo_/bar_/tikz_'}

{'input_folder': 'foo/bar', 'resize_images': False, 'im_size': 500, 'compress_pdf': False, 'pdf_im_resolution': 500, 'images_allowlist': {'path2/': 1000, 'path1/': 1000}, 'commands_to_delete': ['\\todo1', '\\todo2'], 'use_external_tikz': 'foo_/bar_/tikz_'}

{'input_folder': 'foo/bar', 'resize_images': False, 'im_size': 500, 'compress_pdf': False, 'pdf_im_resolution': 500, 'images_allowlist': {'path2/': 1000, 'path1/': 1000}, 'commands_to_delete': ['\\todo1', '\\todo2'], 'use_external_tikz': 'foo/bar/tikz'}

dict_keys(['input_folder', 'resize_images', 'im_size', 'compress_pdf', 'pdf_im_resolution', 'images_allowlist', 'commands_to_delete', 'use_external_tikz'])


def _remove_comments_inline(text):
  """Removes the comments from the string 'text' and ignores % inside \\url{}."""
  if 'auto-ignore' in text:
    return text
  if text.lstrip(' ').lstrip('\t').startswith('%'):
    return ''

  url_pattern = r'\\url\{(?>[^{}]|(?R))*\}'

  def remove_comments(segment):
    """Remove comments from a segment of text."""
    if segment.lstrip().startswith('%'):
      return ''
    match = regex.search(r'(?<!\\)%', segment)
    if match:
      return segment[: match.end()] + '\n'
    else:
      return segment

  # split the text into segments based on \url{} tags
  segments = regex.split(f'({url_pattern})', text)

  for i in range(len(segments)):
    # only process segments that are not part of a \url{} tag
    if not regex.match(url_pattern, segments[i]):
      segments[i] = remove_comments(segments[i])

  final_text = ''.join(segments)
  return (
      final_text
      if final_text.endswith('\n') or final_text.endswith('\\n')
      else final_text + '\n'
  )

_remove_comments_inline(text='Foo %Comment\n')

'Foo %Comment\n'

'Foo %Comment\n'

'Foo %Comment\n'

<function _remove_comments_inline.<locals>.remove_comments at 0x7f185616f280>


def _replace_includesvg(content, svg_inkscape_files):
  def repl_svg(matchobj):
    svg_path = matchobj.group(2)
    svg_filename = os.path.basename(svg_path)
    # search in svg_inkscape split if pdf_tex file is available
    matching_pdf_tex_files = _keep_pattern(
        svg_inkscape_files, ['/' + svg_filename + '-tex.pdf_tex']
    )
    if len(matching_pdf_tex_files) == 1:
      options = '' if matchobj.group(1) is None else matchobj.group(1)
      return f'\\includeinkscape{options}{{{matching_pdf_tex_files[0]}}}'
    else:
      return matchobj.group(0)

  content = regex.sub(r'\\includesvg(\[.*?\])?{(.*?)}', repl_svg, content)

  return content

_replace_includesvg(content='Foo\\includesvg{test2}\nFoo', svg_inkscape_files=['ext_svg/test1-tex.pdf_tex', 'ext_svg/test2-tex.pdf_tex'])

<function _replace_includesvg.<locals>.repl_svg at 0x7f1855304550>

'Foo\\includeinkscape{ext_svg/test2-tex.pdf_tex}\nFoo'


def _search_reference(filename, contents, strict=False):
  """Returns a match object if filename is referenced in contents, and None otherwise.

  If not strict mode, path prefix and extension are optional.
  """
  if strict:
    # regex pattern for strict=True for path/to/img.ext:
    # \{[\s%]*path/to/img\.ext[\s%]*\}
    filename_regex = filename.replace('.', r'\.')
  else:
    filename_path = Path(filename)

    # make extension optional
    root, extension = filename_path.stem, filename_path.suffix
    basename_regex = '{}({})?'.format(
        regex.escape(root), regex.escape(extension)
    )

    # iterate through parent fragments to make path prefix optional
    path_prefix_regex = ''
    for fragment in reversed(filename_path.parents):
      if fragment.name == '.':
        continue
      fragment = regex.escape(fragment.name)
      path_prefix_regex = '({}{}{})?'.format(
          path_prefix_regex, fragment, os.sep
      )

    # Regex pattern for strict=True for path/to/img.ext:
    # \{[\s%]*(<path_prefix>)?<basename>(<ext>)?[\s%]*\}
    filename_regex = path_prefix_regex + basename_regex

  # Some files 'path/to/file' are referenced in tex as './path/to/file' thus
  # adds prefix for relative paths starting with './' or '.\' to regex search.
  filename_regex = r'(.' + os.sep + r')?' + filename_regex

  # Pads with braces and optional whitespace/comment characters.
  patn = r'\{{[\s%]*{}[\s%]*\}}'.format(filename_regex)
  # Picture references in LaTeX are allowed to be in different cases.
  return regex.search(patn, contents, regex.IGNORECASE)

_search_reference(filename='to/img.ext', contents='{img.ext}', strict=False)

'{img.ext}'

'{img.ext}'