|
import shlex |
|
from functools import total_ordering |
|
from itertools import groupby |
|
from operator import attrgetter |
|
from subprocess import check_output |
|
|
|
import funcy |
|
from packaging.version import Version, parse, InvalidVersion |
|
|
|
CMD_LIST_AVAILABLE = "pyenv install --list --bare" |
|
CMD_GLOBAL = "pyenv global" |
|
CMD_LIST_INSTALLED = "pyenv versions --bare" |
|
|
|
|
|
def get_output(cmd): |
|
return check_output(shlex.split(cmd), encoding="utf-8") |
|
|
|
|
|
def get_outputs(cmd): |
|
return get_output(cmd).splitlines() |
|
|
|
|
|
def get_versions(cmd): |
|
return [PyVersion(version) for version in get_outputs(cmd) if ":" not in version] |
|
|
|
|
|
@total_ordering |
|
class PyVersion: |
|
def __init__(self, original_version): |
|
# keep original because Version is not round-trip safe: .dev becomes .dev0 |
|
self.original_version = original_version.strip() |
|
|
|
try: |
|
self.version = parse(self.original_version) |
|
except InvalidVersion: |
|
self.version = '-' |
|
self.is_cpython = False |
|
else: |
|
# non cpythons like jython, pypy etc, will have versions |
|
# if type `LegacyVersion`. |
|
self.is_cpython = isinstance(self.version, Version) |
|
|
|
if not self.is_cpython: |
|
try: |
|
self.version = parse(self.original_version.partition("-")[2]) |
|
except InvalidVersion: |
|
self.version = Version('0.0.0') |
|
|
|
self.implementation = ( |
|
"" if self.is_cpython else self.original_version.split("-")[0] |
|
) |
|
|
|
# i.e. 3.7 3.6 3.5 3.4 3.9-dev 3.8-dev when reversed |
|
self.sort_val = ( |
|
self.is_cpython, |
|
(self.version.dev is None), |
|
(self.version.pre is None), |
|
self.version, |
|
) |
|
|
|
def __lt__(self, other): |
|
return self.sort_val < other.sort_val |
|
|
|
def __eq__(self, other): |
|
return self.version == other.version |
|
|
|
def __str__(self): |
|
return str(self.original_version) |
|
|
|
|
|
def get_cpython_versions(): |
|
for version in get_versions(CMD_LIST_AVAILABLE): |
|
if version.is_cpython: |
|
yield version |
|
|
|
|
|
def get_major_minor(version): |
|
if version.implementation: |
|
return version.implementation, version.version.release |
|
return version.version.release[:2] |
|
|
|
|
|
def get_latests(versions): |
|
versions = sorted(get_cpython_versions(), key=attrgetter("version"), reverse=True) |
|
for major_minor, patches in groupby(versions, get_major_minor): |
|
if major_minor == (2, 7) or major_minor >= (3, 6): |
|
yield max(patches) |
|
|
|
|
|
def print_global_set(versions): |
|
versions = list(versions) |
|
versions_str = " ".join([v.original_version for v in versions]) |
|
print(f"\tpyenv global {versions_str}") |
|
|
|
|
|
def main(): |
|
versions_available = get_cpython_versions() |
|
|
|
# latest version for each minor release |
|
latests = sorted(get_latests(versions_available), reverse=True) |
|
|
|
installed = get_versions(CMD_LIST_INSTALLED) |
|
|
|
global_order = get_versions(CMD_GLOBAL) |
|
|
|
gots = [] |
|
needs = [] |
|
not_in_globals = [] |
|
for max_patch in latests: |
|
if max_patch in installed: |
|
if max_patch not in global_order: |
|
not_in_globals.append(max_patch) |
|
gots.append(max_patch) |
|
else: |
|
needs.append(max_patch) |
|
|
|
print("Current:") |
|
print_global_set(global_order) |
|
print() |
|
|
|
if not_in_globals: |
|
print("Use latest installed versions:") |
|
|
|
def get_latest_globals(current, new): |
|
grouped = funcy.group_by(get_major_minor, [*current, *new]).values() |
|
for patches in grouped: |
|
yield max(patches) |
|
|
|
latest_globals = get_latest_globals(global_order, not_in_globals) |
|
print_global_set(latest_globals) |
|
print() |
|
|
|
if needs: |
|
print("After new installs:") |
|
non_cpython = [v for v in global_order if not v.is_cpython] |
|
print_global_set([*latests, *non_cpython]) |
|
print() |
|
print("Need:") |
|
print( |
|
"; \\\n".join(f"\tpyenv install --skip-existing {need}" for need in needs) |
|
) |
|
print() |
|
|
|
if gots: |
|
print("Got latest:") |
|
for got in gots: |
|
print(f"\t{got}") |
|
print() |
|
|
|
if not needs: |
|
print("All latest versions found.") |
|
|
|
|
|
if __name__ == "__main__": |
|
main() |