# -*- coding: utf-8 -*-
#
# Copyright © 2012 Pierre Raybaut
# Licensed under the terms of the MIT License
# (see winpython/__init__.py for details)

"""
WinPython utilities

Created on Tue Aug 14 14:08:40 2012
"""

from __future__ import print_function

import os
import os.path as osp
from pathlib import Path
import subprocess
import re
import tarfile
import zipfile
import tempfile
import shutil
import atexit
import sys
import stat
import locale
import io

# Local imports
from winpython.py3compat import winreg

def get_python_executable(path = None):
    """return the python executable"""
    my_path = sys.executable if path == None else path  # default = current one
    my_path = my_path if osp.isdir(my_path) else osp.dirname(my_path)
    exec_py = os.path.join(my_path, 'python.exe')
    exec_pypy = os.path.join(my_path, 'pypy3.exe')  # PyPy !
    # PyPy >=7.3.6 3.8 aligns to python.exe and Lib\site-packages
    #python_executable = exec_pypy if osp.isfile(exec_pypy) else exec_py
    python_executable = exec_py if osp.isfile(exec_py) else exec_pypy
    return python_executable

def get_site_packages_path(path = None):
    """return the python site-packages"""
    my_path = sys.executable if path == None else path  # default = current one
    my_path = my_path if osp.isdir(my_path) else osp.dirname(my_path)
    site_py = os.path.join(my_path, 'Lib', 'site-packages')
    site_pypy = os.path.join(my_path, 'site-packages')  # PyPy !!
    site_packages_path = site_pypy if osp.isfile(site_pypy) else site_py
    return site_packages_path

def onerror(function, path, excinfo):
    """Error handler for `shutil.rmtree`.

    If the error is due to an access error (read-only file), it
    attempts to add write permission and then retries.
    If the error is for another reason, it re-raises the error.

    Usage: `shutil.rmtree(path, onerror=onerror)"""
    if not os.access(path, os.W_OK):
        # Is the error an access error?
        os.chmod(path, stat.S_IWUSR)
        function(path)
    else:
        raise


# Exact copy of 'spyderlib.utils.programs.is_program_installed' function
def is_program_installed(basename):
    """Return program absolute path if installed in PATH
    Otherwise, return None"""
    for path in os.environ["PATH"].split(os.pathsep):
        # abspath = osp.join(path, basename)
        abspath = str(Path(path) / basename)
        if osp.isfile(abspath):
            return abspath


# =============================================================================
# Environment variables
# =============================================================================
def get_env(name, current=True):
    """Return HKCU/HKLM environment variable name and value

    For example, get_user_env('PATH') may returns:
    ('Path', u'C:\\Program Files\\Intel\\WiFi\\bin\\')"""
    root = (
        winreg.HKEY_CURRENT_USER
        if current
        else winreg.HKEY_LOCAL_MACHINE
    )
    key = winreg.OpenKey(root, "Environment")
    for index in range(0, winreg.QueryInfoKey(key)[1]):
        try:
            value = winreg.EnumValue(key, index)
            if value[0].lower() == name.lower():
                # Return both value[0] and value[1] because value[0] could be
                # different from name (lowercase/uppercase)
                return value[0], value[1]
        except:
            break


def set_env(name, value, current=True):
    """Set HKCU/HKLM environment variables"""
    root = (
        winreg.HKEY_CURRENT_USER
        if current
        else winreg.HKEY_LOCAL_MACHINE
    )
    key = winreg.OpenKey(root, "Environment")
    try:
        _x, key_type = winreg.QueryValueEx(key, name)
    except WindowsError:
        key_type = winreg.REG_EXPAND_SZ
    key = winreg.OpenKey(
        root, "Environment", 0, winreg.KEY_SET_VALUE
    )
    winreg.SetValueEx(key, name, 0, key_type, value)
    from win32gui import SendMessageTimeout
    from win32con import (
        HWND_BROADCAST,
        WM_SETTINGCHANGE,
        SMTO_ABORTIFHUNG,
    )

    SendMessageTimeout(
        HWND_BROADCAST,
        WM_SETTINGCHANGE,
        0,
        "Environment",
        SMTO_ABORTIFHUNG,
        5000,
    )


#==============================================================================
# https://stackoverflow.com/questions/580924/how-to-access-a-files-properties-on-windows
def getFileProperties(fname):
#==============================================================================
    """
    Read all properties of the given file return them as a dictionary.
    """
    import win32api
    propNames = ('Comments', 'InternalName', 'ProductName',
        'CompanyName', 'LegalCopyright', 'ProductVersion',
        'FileDescription', 'LegalTrademarks', 'PrivateBuild',
        'FileVersion', 'OriginalFilename', 'SpecialBuild')

    props = {'FixedFileInfo': None, 'StringFileInfo': None, 'FileVersion': None}

    try:
        # backslash as parm returns dictionary of numeric info corresponding to VS_FIXEDFILEINFO struc
        fixedInfo = win32api.GetFileVersionInfo(fname, '\\')
        props['FixedFileInfo'] = fixedInfo
        props['FileVersion'] = "%d.%d.%d.%d" % (fixedInfo['FileVersionMS'] / 65536,
                fixedInfo['FileVersionMS'] % 65536, fixedInfo['FileVersionLS'] / 65536,
                fixedInfo['FileVersionLS'] % 65536)

        # \VarFileInfo\Translation returns list of available (language, codepage)
        # pairs that can be used to retreive string info. We are using only the first pair.
        lang, codepage = win32api.GetFileVersionInfo(fname, '\\VarFileInfo\\Translation')[0]

        # any other must be of the form \StringfileInfo\%04X%04X\parm_name, middle
        # two are language/codepage pair returned from above

        strInfo = {}
        for propName in propNames:
            strInfoPath = u'\\StringFileInfo\\%04X%04X\\%s' % (lang, codepage, propName)
            ## print str_info
            strInfo[propName] = win32api.GetFileVersionInfo(fname, strInfoPath)

        props['StringFileInfo'] = strInfo
    except:
        pass

    return props
# =============================================================================
# Shortcuts, start menu
# =============================================================================


def get_special_folder_path(path_name):
    """Return special folder path"""
    from win32com.shell import shell, shellcon

    for maybe in """
       CSIDL_COMMON_STARTMENU CSIDL_STARTMENU CSIDL_COMMON_APPDATA
       CSIDL_LOCAL_APPDATA CSIDL_APPDATA CSIDL_COMMON_DESKTOPDIRECTORY
       CSIDL_DESKTOPDIRECTORY CSIDL_COMMON_STARTUP CSIDL_STARTUP
       CSIDL_COMMON_PROGRAMS CSIDL_PROGRAMS CSIDL_PROGRAM_FILES_COMMON
       CSIDL_PROGRAM_FILES CSIDL_FONTS""".split():
        if maybe == path_name:
            csidl = getattr(shellcon, maybe)
            return shell.SHGetSpecialFolderPath(
                0, csidl, False
            )
    raise ValueError(
        "%s is an unknown path ID" % (path_name,)
    )


def get_winpython_start_menu_folder(current=True):
    """Return WinPython Start menu shortcuts folder"""
    if current:
        # non-admin install - always goes in this user's start menu.
        folder = get_special_folder_path("CSIDL_PROGRAMS")
    else:
        try:
            folder = get_special_folder_path(
                "CSIDL_COMMON_PROGRAMS"
            )
        except OSError:
            # No CSIDL_COMMON_PROGRAMS on this platform
            folder = get_special_folder_path(
                "CSIDL_PROGRAMS"
            )
    # return osp.join(folder, 'WinPython')
    return str(Path(folder) / 'WinPython')



def create_winpython_start_menu_folder(current=True):
    """Create WinPython Start menu folder -- remove it if it already exists"""
    path = get_winpython_start_menu_folder(current=current)
    if osp.isdir(path):
        try:
            shutil.rmtree(path, onerror=onerror)
        except WindowsError:
            print(
                "Directory %s could not be removed" % path,
                file=sys.stderr,
            )
    else:
        os.mkdir(path)
    return path


def create_shortcut(
    path,
    description,
    filename,
    arguments="",
    workdir="",
    iconpath="",
    iconindex=0,
):
    """Create Windows shortcut (.lnk file)"""
    import pythoncom
    from win32com.shell import shell

    ilink = pythoncom.CoCreateInstance(
        shell.CLSID_ShellLink,
        None,
        pythoncom.CLSCTX_INPROC_SERVER,
        shell.IID_IShellLink,
    )
    ilink.SetPath(path)
    ilink.SetDescription(description)
    if arguments:
        ilink.SetArguments(arguments)
    if workdir:
        ilink.SetWorkingDirectory(workdir)
    if iconpath or iconindex:
        ilink.SetIconLocation(iconpath, iconindex)
    # now save it.
    ipf = ilink.QueryInterface(pythoncom.IID_IPersistFile)
    if not filename.endswith('.lnk'):
        filename += '.lnk'
    ipf.Save(filename, 0)


# =============================================================================
# Misc.
# =============================================================================


def print_box(text):
    """Print text in a box"""
    line0 = "+" + ("-" * (len(text) + 2)) + "+"
    line1 = "| " + text + " |"
    print(
        ("\n\n" + "\n".join([line0, line1, line0]) + "\n")
    )


def is_python_distribution(path):
    """Return True if path is a Python distribution"""
    # XXX: This test could be improved but it seems to be sufficient
    has_exec = osp.isfile(get_python_executable(path))
    has_site = osp.isdir(get_site_packages_path(path))    
    return has_exec and has_site


# =============================================================================
# Shell, Python queries
# =============================================================================


def decode_fs_string(string):
    """Convert string from file system charset to unicode"""
    charset = sys.getfilesystemencoding()
    if charset is None:
        charset = locale.getpreferredencoding()
    return string.decode(charset)


def exec_shell_cmd(args, path):
    """Execute shell command (*args* is a list of arguments) in *path*"""
    # print " ".join(args)
    process = subprocess.Popen(
        args,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        cwd=path,
        shell=True,
    )
    return decode_fs_string(process.stdout.read())

def exec_run_cmd(args, path=None):
    """run a single command (*args* is a list of arguments) in optional *path*"""
    # only applicable to Python-3.5+
    # python-3.7+ allows to replace "stdout and stderr ", per "capture_output=True"
    if path:
        process = subprocess.run(args,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE,
                                cwd=path)
    else:
        process = subprocess.run(args,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
        return  decode_fs_string(process.stdout)


def get_r_version(path):
    """Return version of the R installed in *path*"""
    return (
        exec_shell_cmd('dir ..\README.R*', path)
        .splitlines()[-3]
        .split("-")[-1]
    )


def get_julia_version(path):
    """Return version of the Julia installed in *path*"""
    return (
        exec_shell_cmd('julia.exe -v', path)
        .splitlines()[0]
        .split(" ")[-1]
    )


def get_nodejs_version(path):
    """Return version of the Nodejs installed in *path*"""
    return exec_shell_cmd('node -v', path).splitlines()[0]


def get_npmjs_version(path):
    """Return version of the Nodejs installed in *path*"""
    return exec_shell_cmd('npm -v', path).splitlines()[0]


def get_pandoc_version(path):
    """Return version of the Pandoc executable in *path*"""
    return (
        exec_shell_cmd('pandoc -v', path)
        .splitlines()[0]
        .split(" ")[-1]
    )


def python_query(cmd, path):
    """Execute Python command using the Python interpreter located in *path*"""
    the_exe = get_python_executable(path)
    # debug2021-09-12
    print('"%s" -c "%s"' % (the_exe, cmd), ' * ',  path)
    return exec_shell_cmd('"%s" -c "%s"' % (the_exe, cmd), path).splitlines()[0]

def python_execmodule(cmd, path):
    """Execute Python command using the Python interpreter located in *path*"""
    the_exe = get_python_executable(path)
    exec_shell_cmd('%s -m %s' % (the_exe, cmd), path)


def get_python_infos(path):
    """Return (version, architecture) for the Python distribution located in
    *path*. The version number is limited to MAJOR.MINOR, the architecture is
    an integer: 32 or 64"""
    is_64 = python_query(
        'import sys; print(sys.maxsize > 2**32)', path
    )
    arch = {'True': 64, 'False': 32}.get(is_64, None)
    ver = python_query(
        "import sys; print('%d.%d' % (sys.version_info.major, "
        "sys.version_info.minor))",
        path,
    )
    if re.match(r'([0-9]*)\.([0-9]*)', ver) is None:
        ver = None
  
    return ver, arch


def get_python_long_version(path):
    """Return long version (X.Y.Z) for the Python distribution located in
    *path*"""
    ver = python_query(
        "import sys; print('%d.%d.%d' % "
        "(sys.version_info.major, sys.version_info.minor,"
        "sys.version_info.micro))",
        path,
    )
    if (
        re.match(r'([0-9]*)\.([0-9]*)\.([0-9]*)', ver)
        is None
    ):
        ver = None
    return ver


# =============================================================================
# Patch chebang line (courtesy of Christoph Gohlke)
# =============================================================================
def patch_shebang_line(
    fname, pad=b' ', to_movable=True, targetdir=""
):
    """Remove absolute path to python.exe in shebang lines, or re-add it"""

    import re
    import sys
    import os

    target_dir = targetdir  # movable option
    if to_movable == False:
        target_dir = os.path.abspath(os.path.dirname(fname))
        target_dir = (
            os.path.abspath(os.path.join(target_dir, r'..'))
            + '\\'
        )
    executable = sys.executable
    if sys.version_info[0] == 2:
        shebang_line = re.compile(
            r"(#!.*pythonw?\.exe)"
        )  # Python2.7
    else:
        shebang_line = re.compile(
            b"(#!.*pythonw?\.exe)"
        )  # Python3+
        if 'pypy3' in sys.executable:
            shebang_line = re.compile(
            b"(#!.*pypy3w?\.exe)"
        )  # Pypy3+
            
        target_dir = target_dir.encode('utf-8')
    with open(fname, 'rb') as fh:
        initial_content = fh.read()
        fh.close
        fh = None
    content = shebang_line.split(
        initial_content, maxsplit=1
    )
    if len(content) != 3:
        return
    exe = os.path.basename(content[1][2:])
    content[1] = (
        b'#!' + target_dir + exe
    )  # + (pad * (len(content[1]) - len(exe) - 2))
    final_content = b''.join(content)
    if initial_content == final_content:
        return
    try:
        with open(fname, 'wb') as fo:
            fo.write(final_content)
            fo.close
            fo = None
            print("patched", fname)
    except Exception:
        print("failed to patch", fname)


# =============================================================================
# Patch shebang line in .py files
# =============================================================================
def patch_shebang_line_py(
    fname, to_movable=True, targetdir=""
):
    """Changes shebang line in '.py' file to relative or absolue path"""
    import fileinput
    import re
    import sys

    if sys.version_info[0] == 2:
        # Python 2.x doesn't create .py files for .exe files. So, Moving
        # WinPython doesn't break running executable files.
        return
    if to_movable:
        exec_path = '#!.\python.exe'
        if 'pypy3' in sys.executable:  # PyPy !
            exec_path = '#!.\pypy3.exe'
    else:
        exec_path = '#!' + sys.executable
    for line in fileinput.input(fname, inplace=True):
        if re.match('^#\!.*python\.exe$', line) is not None:
            print(exec_path)
        elif re.match('^#\!.*pypy3\.exe$', line) is not None:# PyPy !
            print(exec_path)          
        else:
            print(line, end='')


# =============================================================================
# Guess encoding (shall rather be utf-8 per default)
# =============================================================================
def guess_encoding(csv_file):
    """guess the encoding of the given file"""
    # UTF_8_BOM = "\xEF\xBB\xBF"
    # Python behavior on UTF-16 not great on write, so we drop it
    with io.open(csv_file, "rb") as f:
        data = f.read(5)
    if data.startswith(b"\xEF\xBB\xBF"):  # UTF-8 with a "BOM" (normally no BOM in utf-8)
        return ["utf-8-sig"]
    else:  # in Windows, guessing utf-8 doesn't work, so we have to try
        try:
            with io.open(csv_file, encoding="utf-8") as f:
                preview = f.read(222222)
                return ["utf-8"]
        except:
            return [locale.getdefaultlocale()[1], "utf-8"]
            
# =============================================================================
# Patch sourcefile (instead of forking packages)
# =============================================================================
def patch_sourcefile(
    fname, in_text, out_text, silent_mode=False
):
    """Replace a string in a source file"""
    import io

    if osp.isfile(fname) and not in_text == out_text:
        the_encoding = guess_encoding(fname)[0]
        with io.open(fname, 'r', encoding=the_encoding) as fh:
            content = fh.read()
        new_content = content.replace(in_text, out_text)
        if not new_content == content:
            if not silent_mode:
                print(
                    "patching ",
                    fname,
                    "from",
                    in_text,
                    "to",
                    out_text,
                )
            with io.open(fname, 'wt', encoding=the_encoding) as fh:
                fh.write(new_content)


# =============================================================================
# Patch sourcelines (instead of forking packages)
# =============================================================================
def patch_sourcelines(
    fname,
    in_line_start,
    out_line,
    endline='\n',
    silent_mode=False,
):
    """Replace the middle of lines between in_line_start and endline """
    import io
    import os.path as osp

    if osp.isfile(fname):
        the_encoding = guess_encoding(fname)[0]
        with io.open(fname, 'r', encoding=the_encoding) as fh:
            contents = fh.readlines()
            content = "".join(contents)
            for l in range(len(contents)):
                if contents[l].startswith(in_line_start):
                    begining, middle = (
                        in_line_start,
                        contents[l][len(in_line_start) :],
                    )
                    ending = ""
                    if middle.find(endline) > 0:
                        ending = endline + endline.join(
                            middle.split(endline)[1:]
                        )
                        middle = middle.split(endline)[0]
                    middle = out_line
                    new_line = begining + middle + ending
                    if not new_line == contents[l]:
                        if not silent_mode:
                            print(
                                "patching ",
                                fname,
                                " from\n",
                                contents[l],
                                "\nto\n",
                                new_line,
                            )
                    contents[l] = new_line
            new_content = "".join(contents)
        if not new_content == content:
            # if not silent_mode:
            #    print("patching ", fname, "from", content, "to", new_content)
            with io.open(fname, 'wt', encoding=the_encoding) as fh:
                try:
                    fh.write(new_content)
                except:
                    print(
                        "impossible to patch",
                        fname,
                        "from",
                        content,
                        "to",
                        new_content,
                    )


# =============================================================================
# Extract functions
# =============================================================================
def _create_temp_dir():
    """Create a temporary directory and remove it at exit"""
    tmpdir = tempfile.mkdtemp(prefix='wppm_')
    atexit.register(
        lambda path: shutil.rmtree(path, onerror=onerror),
        tmpdir,
    )
    return tmpdir


def extract_exe(fname, targetdir=None, verbose=False):
    """Extract .exe archive to a temporary directory (if targetdir
    is None). Return the temporary directory path"""
    if targetdir is None:
        targetdir = _create_temp_dir()
    extract = '7z.exe'
    assert is_program_installed(extract), (
        "Required program '%s' was not found" % extract
    )
    bname = osp.basename(fname)
    args = ['x', '-o%s' % targetdir, '-aos', bname]
    if verbose:
        retcode = subprocess.call(
            [extract] + args, cwd=osp.dirname(fname)
        )
    else:
        p = subprocess.Popen(
            [extract] + args,
            cwd=osp.dirname(fname),
            stdout=subprocess.PIPE,
        )
        p.communicate()
        p.stdout.close()
        retcode = p.returncode
    if retcode != 0:
        raise RuntimeError(
            "Failed to extract %s (return code: %d)"
            % (fname, retcode)
        )
    return targetdir


def extract_archive(fname, targetdir=None, verbose=False):
    """Extract .zip, .exe (considered to be a zip archive) or .tar.gz archive
    to a temporary directory (if targetdir is None).
    Return the temporary directory path"""
    if targetdir is None:
        targetdir = _create_temp_dir()
    else:
        try:
            os.mkdir(targetdir)
        except:
            pass
    if osp.splitext(fname)[1] in ('.zip', '.exe'):
        obj = zipfile.ZipFile(fname, mode="r")
    elif fname.endswith('.tar.gz'):
        obj = tarfile.open(fname, mode='r:gz')
    else:
        raise RuntimeError(
            "Unsupported archive filename %s" % fname
        )
    obj.extractall(path=targetdir)
    return targetdir


WININST_PATTERN = r'([a-zA-Z0-9\-\_]*|[a-zA-Z\-\_\.]*)-([0-9\.\-]*[a-z]*[0-9]?)(-Qt-([0-9\.]+))?.(win32|win\-amd64)(-py([0-9\.]+))?(-setup)?\.exe'

# SOURCE_PATTERN defines what an acceptable source package name is
# As of 2014-09-08 :
#    - the wheel package format is accepte in source directory
#    - the tricky regexp is tuned also to support the odd jolib naming :
#         . joblib-0.8.3_r1-py2.py3-none-any.whl,
#         . joblib-0.8.3-r1.tar.gz

SOURCE_PATTERN = r'([a-zA-Z0-9\-\_\.]*)-([0-9\.\_]*[a-z]*[\-]?[0-9]*)(\.zip|\.tar\.gz|\-(py[2-7]*|py[2-7]*\.py[2-7]*)\-none\-any\.whl)'

# WHEELBIN_PATTERN defines what an acceptable binary wheel package is
# "cp([0-9]*)" to replace per cp(34) for python3.4
# "win32|win\_amd64" to replace per "win\_amd64" for 64bit
WHEELBIN_PATTERN = r'([a-zA-Z0-9\-\_\.]*)-([0-9\.\_]*[a-z0-9\+]*[0-9]?)-cp([0-9]*)\-[0-9|c|o|n|e|p|m]*\-(win32|win\_amd64)\.whl'


def get_source_package_infos(fname):
    """Return a tuple (name, version) of the Python source package"""
    if fname[-4:] == '.whl':
        return osp.basename(fname).split("-")[:2]
    match = re.match(SOURCE_PATTERN, osp.basename(fname))
    if match is not None:
        return match.groups()[:2]


def build_wininst(
    root,
    python_exe=None,
    copy_to=None,
    architecture=None,
    verbose=False,
    installer='bdist_wininst',
):
    """Build wininst installer from Python package located in *root*
    and eventually copy it to *copy_to* folder.
    Return wininst installer full path."""
    if python_exe is None:
        python_exe = sys.executable
    assert osp.isfile(python_exe)
    cmd = [python_exe, 'setup.py', 'build']
    if architecture is not None:
        archstr = (
            'win32' if architecture == 32 else 'win-amd64'
        )
        cmd += ['--plat-name=%s' % archstr]
    cmd += [installer]
    # root = a tmp dir in windows\tmp,
    if verbose:
        subprocess.call(cmd, cwd=root)
    else:
        p = subprocess.Popen(
            cmd,
            cwd=root,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )
        p.communicate()
        p.stdout.close()
        p.stderr.close()
    # distdir = osp.join(root, 'dist')
    distdir = str(Path(root) / 'dist')
    if not osp.isdir(distdir):
        raise RuntimeError(
            "Build failed: see package README file for further"
            " details regarding installation requirements.\n\n"
            "For more concrete debugging infos, please try to build "
            "the package from the command line:\n"
            "1. Open a WinPython command prompt\n"
            "2. Change working directory to the appropriate folder\n"
            "3. Type `python setup.py build install`"
        )
    pattern = WININST_PATTERN.replace(
        r'(win32|win\-amd64)', archstr
    )
    for distname in os.listdir(distdir):
        match = re.match(pattern, distname)
        if match is not None:
            break
        # for wheels (winpython here)
        match = re.match(SOURCE_PATTERN, distname)
        if match is not None:
            break
        match = re.match(WHEELBIN_PATTERN, distname)
        if match is not None:
            break
    else:
        raise RuntimeError(
            "Build failed: not a pure Python package? %s"
            % distdir
        )
    # src_fname = osp.join(distdir, distname)
    src_fname = str(Path(distdir) / distname)
    if copy_to is None:
        return src_fname
    else:
        # dst_fname = osp.join(copy_to, distname)
        dst_fname = str(Path(copy_to) / distname)
        shutil.move(src_fname, dst_fname)
        if verbose:
            print(
                (
                    "Move: %s --> %s"
                    % (src_fname, (dst_fname))
                )
            )
            # remove tempo dir 'root' no more needed
            shutil.rmtree(root, onerror=onerror)
        return dst_fname


def direct_pip_install(
    fname,
    python_exe=None,
    architecture=None,
    verbose=False,
    install_options=None,
):
    """Direct install via pip !"""
    copy_to = osp.dirname(fname)

    if python_exe is None:
        python_exe = sys.executable
    assert osp.isfile(python_exe)
    myroot = os.path.dirname(python_exe)

    cmd = [python_exe, '-m', 'pip', 'install']
    if install_options:
        cmd += install_options  # typically ['--no-deps']
        print('pip install_options', install_options)
    cmd += [fname]

    if verbose:
        subprocess.call(cmd, cwd=myroot)
    else:
        p = subprocess.Popen(
            cmd,
            cwd=myroot,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )
        stdout, stderr = p.communicate()
        the_log = "%s" % stdout + "\n %s" % stderr

        if (
            ' not find ' in the_log
            or ' not found ' in the_log
        ):
            print("Failed to Install: \n %s \n" % fname)
            print("msg: %s" % the_log)
            raise RuntimeError
        p.stdout.close()
        p.stderr.close()
    src_fname = fname
    if copy_to is None:
        return src_fname
    else:
        if verbose:
            print("Installed %s" % src_fname)
        return src_fname


def do_script(
    this_script,
    python_exe=None,
    copy_to=None,
    architecture=None,
    verbose=False,
    install_options=None,
):
    """Execute a script (get-pip typically)"""
    if python_exe is None:
        python_exe = sys.executable
    myroot = os.path.dirname(python_exe)

    # cmd = [python_exe, myroot + r'\Scripts\pip-script.py', 'install']
    cmd = [python_exe]
    if install_options:
        cmd += install_options  # typically ['--no-deps']
        print('script install_options', install_options)
    if this_script:
        cmd += [this_script]
    # print('build_wheel', myroot, cmd)
    print("Executing ", cmd)

    if verbose:
        subprocess.call(cmd, cwd=myroot)
    else:
        p = subprocess.Popen(
            cmd,
            cwd=myroot,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )
        p.communicate()
        p.stdout.close()
        p.stderr.close()
    if verbose:
        print("Executed " % cmd)
    return 'ok'


if __name__ == '__main__':

    print_box("Test")
    dname = sys.prefix
    print((dname + ':', '\n', get_python_infos(dname)))
    # dname = r'E:\winpython\sandbox\python-2.7.3'
    # print dname+':', '\n', get_python_infos(dname)

    tmpdir = r'D:\Tests\winpython_tests'
    if not osp.isdir(tmpdir):
        os.mkdir(tmpdir)
    print(
        (
            extract_archive(
                #osp.join(
                #    r'D:\WinP\bd37',
                #    'packages.win-amd64',
                #    'python-3.7.3.amd64.zip',
                #),
                str(Path(r'D:\WinP\bd37') / 'packages.win-amd64' /
                    'python-3.7.3.amd64.zip'),        
                tmpdir,
            )    
        )
    )
