Browse Source

Add command line features

main 0.2.1
parent
commit
4bbd8facc0
7 changed files with 306 additions and 35 deletions
  1. +31
    -0
      bin/pkgupdates
  2. +5
    -0
      pkgupdates.yaml
  3. +21
    -4
      src/pkgupdates/__main__.py
  4. +75
    -8
      src/pkgupdates/package.py
  5. +99
    -21
      src/pkgupdates/remote.py
  6. +52
    -0
      src/pkgupdates/settings.py
  7. +23
    -2
      src/pkgupdates/version.py

+ 31
- 0
bin/pkgupdates View File

@ -0,0 +1,31 @@
#!/usr/bin/python3.13
# -*- coding: utf-8 -*-
import re
import sys
import os
from os.path import dirname, normpath, realpath
LAUNCH_PATH = dirname(realpath(__file__))
# Prevent loading Python modules from home folder
# They can interfere with Lutris and prevent it
# from working.
sys.path = [path for path in sys.path if not path.startswith("/home")
and not path.startswith("/var/home")]
if os.path.isdir(os.path.join(LAUNCH_PATH, "../src/pkgupdates")):
sys.dont_write_bytecode = True
SOURCE_PATH = normpath(os.path.join(LAUNCH_PATH, "../src"))
sys.path.insert(0, SOURCE_PATH)
else:
sys.path.insert(0, os.path.normpath(os.path.join(LAUNCH_PATH,
"../lib/pkgupdates")))
try:
from pkgupdates import main
except ImportError as ex:
sys.stderr.write(f"Error importing pkgupdates module: {ex}\n")
sys.exit(1)
sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
sys.exit(main())

+ 5
- 0
pkgupdates.yaml View File

@ -0,0 +1,5 @@
only-in-tree: true
remote:
github:
rss-get: true

+ 21
- 4
src/pkgupdates/__main__.py View File

@ -1,8 +1,8 @@
"""Main Module""" """Main Module"""
import argparse import argparse
from .package import get_packages, check_package
from .settings import set_gh_rss_get
from .package import get_packages, check_package, get_packages_from_tree
from .settings import set_gh_rss_get, set_verbose, set_only_in_tree, get_only_in_tree, get_verbose, get_gh_rss_get, load_config
def process_arguments(): def process_arguments():
@ -13,18 +13,35 @@ def process_arguments():
parser.add_argument('--github-rss-get', parser.add_argument('--github-rss-get',
type=bool, type=bool,
default=False,
default=get_gh_rss_get(),
required=False, required=False,
help='Get updates for Github over RSS instead of API') help='Get updates for Github over RSS instead of API')
parser.add_argument('--only-in-tree',
type=bool,
default=get_only_in_tree(),
required=False,
help='Only search for updates ebuilds in git tree')
parser.add_argument('--verbose',
type=bool,
default=get_verbose(),
required=False,
help='Verbose output')
args = parser.parse_args() args = parser.parse_args()
set_gh_rss_get(args.github_rss_get) set_gh_rss_get(args.github_rss_get)
set_only_in_tree(args.only_in_tree)
set_verbose(args.verbose)
def main(): def main():
"""Main Function""" """Main Function"""
load_config()
process_arguments() process_arguments()
pkgs = get_packages()
if get_only_in_tree():
pkgs = get_packages_from_tree()
else:
pkgs = get_packages()
for pkg, meta in pkgs.items(): for pkg, meta in pkgs.items():
check_package(pkg, meta) check_package(pkg, meta)


+ 75
- 8
src/pkgupdates/package.py View File

@ -6,8 +6,10 @@ import collections
import re import re
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import datetime import datetime
from .version import parse_version, EVersion, SemanticVersion, DateVersion
import git
from .version import parse_version, EVersion, SemanticVersion, DateVersion, CounterVersion, VCSVersion
from .remote import ERemote, Remote from .remote import ERemote, Remote
from .settings import get_verbose
rex_pkg_version = re.compile("-([0-9.]+)([_a-z0-9]+)*(-r[0-9]+)?[.]ebuild") rex_pkg_version = re.compile("-([0-9.]+)([_a-z0-9]+)*(-r[0-9]+)?[.]ebuild")
@ -16,6 +18,36 @@ PkgVersion = collections.namedtuple("PkgVersion",
["name", "category", "version"] ["name", "category", "version"]
) )
def get_packages_from_tree():
"""Get files from git tree"""
res = {}
repo = git.Repo(os.getcwd())
if repo.bare:
return
for entry in repo.commit().tree.traverse():
file = entry.path
root = os.path.dirname(file)
if not file.endswith('.ebuild'):
continue
p1 = os.path.split(root)
p2 = os.path.split(p1[0])
pkg = os.path.join(p2[1], p1[1])
match = rex_pkg_version.search(file)
if match is None:
print(file)
continue
if pkg in res:
res[pkg]["versions"].append(match.groups()[0])
else:
metadata = get_metadata(root)
if metadata is None:
metadata = {}
res[pkg] = metadata
res[pkg]["versions"] = [match.groups()[0]]
return res
def get_packages(): def get_packages():
"""Scan current directory for ebuild packages""" """Scan current directory for ebuild packages"""
@ -70,6 +102,8 @@ def get_package_versions(meta):
eversion = EVersion.UNKNOWN eversion = EVersion.UNKNOWN
is_semantic = False is_semantic = False
is_date = False is_date = False
is_counter = False
is_vcs = False
if "versions" in meta: if "versions" in meta:
for version in meta["versions"]: for version in meta["versions"]:
@ -78,11 +112,19 @@ def get_package_versions(meta):
is_semantic = True is_semantic = True
if isinstance(version_obj, DateVersion): if isinstance(version_obj, DateVersion):
is_date = True is_date = True
if isinstance(version_obj, CounterVersion):
is_counter = True
if isinstance(version_obj, VCSVersion):
is_vcs = True
versions.append(version_obj) versions.append(version_obj)
if is_date: if is_date:
eversion = EVersion.DATE eversion = EVersion.DATE
elif is_semantic: elif is_semantic:
eversion = EVersion.SEMANTIC eversion = EVersion.SEMANTIC
elif is_counter:
eversion = EVersion.COUNTER
elif is_vcs:
eversion = EVersion.VCS
return versions, eversion return versions, eversion
@ -99,10 +141,12 @@ def check_package(pkg, meta): # pylint: disable=R0911
match (version_type): match (version_type):
case EVersion.DATE: case EVersion.DATE:
if remote.TYPE == ERemote.UNKNOWN: if remote.TYPE == ERemote.UNKNOWN:
print(pkg + ": Unsupported backend: " + pkg_type)
print(f"{pkg}: Unsupported backend: {pkg_type}")
return return
if not remote.support_latest_commit(): if not remote.support_latest_commit():
print(f"{pkg}: Error: {pkg_type} "
"date version check not implemented!")
return return
version = remote.get_latest_commit(meta["remote-id"]) version = remote.get_latest_commit(meta["remote-id"])
@ -115,32 +159,55 @@ def check_package(pkg, meta): # pylint: disable=R0911
case EVersion.SEMANTIC: case EVersion.SEMANTIC:
if remote.TYPE == ERemote.UNKNOWN: if remote.TYPE == ERemote.UNKNOWN:
print(pkg + ": Unsupported backend: " + pkg_type)
print(f"{pkg}: Unsupported backend: {pkg_type}")
return return
if not remote.support_latest_release(): if not remote.support_latest_release():
print(f"{pkg}: Error: {pkg_type} "
"semantic version check not implemented!")
return return
tag = remote.get_latest_release(meta["remote-id"]) tag = remote.get_latest_release(meta["remote-id"])
if tag is None or tag == "": if tag is None or tag == "":
print(pkg + ": HTTPError! " + meta["remote-id"])
print(f"{pkg}: HTTPError! {meta['remote-id']}")
return return
latest_version = parse_version(tag) latest_version = parse_version(tag)
case EVersion.VCS: case EVersion.VCS:
print(pkg + ": VCS package")
print(f"{pkg}: VCS package")
return return
# TODO:
case EVersion.COUNTER:
if remote.TYPE == ERemote.UNKNOWN:
print(f"{pkg}: Unsupported backend: {pkg_type}")
return
if not remote.support_latest_release():
print(f"{pkg}: Error: {pkg_type} "
"counter version check not implemented!")
return
tag = remote.get_latest_release(meta["remote-id"])
if tag is None or tag == "":
print(f"{pkg}: HTTPError! {meta['remote-id']}")
return
latest_version = parse_version(tag)
case EVersion.UNKNOWN: case EVersion.UNKNOWN:
print(pkg + ": Manual Compare")
versions_str = ', '.join(str(ver.version) for ver in versions)
print(f"{pkg}: Manual Compare ({versions_str})")
return return
is_new = True is_new = True
for pkg_version in versions: for pkg_version in versions:
is_new = is_new and (not pkg_version.compare(latest_version)) is_new = is_new and (not pkg_version.compare(latest_version))
if is_new: if is_new:
print(pkg + ": New Version: " + latest_version.version)
else:
versions_str = ', '.join(str(ver.version) for ver in versions)
print(f"{pkg}: New Version: {latest_version.version} ({versions_str})")
elif get_verbose():
print(pkg + ": OK") print(pkg + ": OK")

+ 99
- 21
src/pkgupdates/remote.py View File

@ -2,9 +2,25 @@
import urllib.request import urllib.request
import re import re
import xml
from datetime import datetime
from enum import Enum from enum import Enum
from xml.dom import minidom from xml.dom import minidom
import requests import requests
from sourcehut.client import (
SRHT_SERVICE,
APIVersion,
SrhtClient,
_FileUpload,
_get_upload_data,
)
from sourcehut.services.builds import BuildsSrhtClient
from sourcehut.services.git import GitSrhtClient
from sourcehut.services.lists import ListsSrhtClient
from sourcehut.services.meta import MetaSrhtClient
from sourcehut.services.pages import PagesSrhtClient
from sourcehut.services.paste import PasteSrhtClient
from sourcehut.services.todo import TodoSrhtClient
from github import GithubException, Github as GithubApi from github import GithubException, Github as GithubApi
from .version import rex_semantic from .version import rex_semantic
from .settings import get_gh_rss_get from .settings import get_gh_rss_get
@ -115,29 +131,25 @@ class Remote:
@classmethod @classmethod
def support_latest_release(cls): def support_latest_release(cls):
"""Check if remote has latest release implemented""" """Check if remote has latest release implemented"""
if cls.get_latest_release == Remote.get_latest_release:
print(f"Error: {cls.NAME} semantic version check not implemented!")
return False
return True
return cls.get_latest_release != Remote.get_latest_release
@classmethod @classmethod
def support_latest_commit(cls): def support_latest_commit(cls):
"""Check if remote has latest commit implemented""" """Check if remote has latest commit implemented"""
if cls.get_latest_commit == Remote.get_latest_commit:
print(f"Error: {cls.NAME} date version check not implemented!")
return False
return True
return cls.get_latest_commit != Remote.get_latest_commit
@classmethod @classmethod
def get_latest_release(cls, pkg_repo): # pylint: disable=unused-argument
def get_latest_release(cls, pkg_repo):
"""Get latest release - mostly for Semantic versioning""" """Get latest release - mostly for Semantic versioning"""
print(f"Error: {cls.NAME} semantic version check not implemented!")
print(f"{pkg_repo}: Error: {cls.NAME} "
"semantic version check not implemented!")
return "" return ""
@classmethod @classmethod
def get_latest_commit(cls, pkg_repo): # pylint: disable=unused-argument
def get_latest_commit(cls, pkg_repo):
"""Get latest commit - mostly for date versioning""" """Get latest commit - mostly for date versioning"""
print(f"Error: {cls.NAME} date version check not implemented!")
print(f"{pkg_repo}: Error: {cls.NAME} "
"date version check not implemented!")
return "" return ""
@ -153,8 +165,8 @@ class Bitbucket(Remote):
"""Download releases""" """Download releases"""
version = "" version = ""
url = "https://api.bitbucket.org/2.0/repositories/" + pkg_repo
url += "/refs/tags?sort=-target.date"
url = ((f"https://api.bitbucket.org/2.0/repositories/{pkg_repo}"
"/refs/tags?sort=-target.date"))
response = requests.get(url, timeout=5) response = requests.get(url, timeout=5)
@ -338,7 +350,7 @@ class Github(Remote):
""" """
if get_gh_rss_get(): if get_gh_rss_get():
url = "https://github.com/" + pkg_repo + "/commits.atom"
url = f"https://github.com/{pkg_repo}/commits.atom"
try: try:
with urllib.request.urlopen(url) as req: with urllib.request.urlopen(url) as req:
feed = req.read() feed = req.read()
@ -367,7 +379,7 @@ class Github(Remote):
""" """
if get_gh_rss_get(): if get_gh_rss_get():
url = "https://github.com/" + pkg_repo + "/tags.atom"
url = f"https://github.com/{pkg_repo}/tags.atom"
try: try:
with urllib.request.urlopen(url) as req: with urllib.request.urlopen(url) as req:
feed = req.read() feed = req.read()
@ -414,7 +426,7 @@ class Gitlab(Remote):
@classmethod @classmethod
def get_latest_release(cls, pkg_repo): def get_latest_release(cls, pkg_repo):
ident = pkg_repo.replace("/", "%2F") ident = pkg_repo.replace("/", "%2F")
url = cls.URL + "/projects/" + ident + "/repository/tags"
url = f"{cls.URL}/projects/{ident}/repository/tags"
version = "" version = ""
try: try:
with requests.get(url, timeout=5) as resp: with requests.get(url, timeout=5) as resp:
@ -428,7 +440,7 @@ class Gitlab(Remote):
@classmethod @classmethod
def get_latest_commit(cls, pkg_repo): def get_latest_commit(cls, pkg_repo):
ident = pkg_repo.replace("/", "%2F") ident = pkg_repo.replace("/", "%2F")
url = cls.URL + "/projects/" + ident + "/repository/commits"
url = f"{cls.URL}/projects/{ident}/repository/commits"
version = "" version = ""
try: try:
with requests.get(url, timeout=5) as resp: with requests.get(url, timeout=5) as resp:
@ -558,7 +570,6 @@ class Pecl(Remote):
# TODO: get_latest_commit # TODO: get_latest_commit
# TODO: get_latest_release
class Pypi(Remote): class Pypi(Remote):
"""Remote for pypi.org hosted Python projects """Remote for pypi.org hosted Python projects
@ -567,6 +578,48 @@ class Pypi(Remote):
TYPE = ERemote.PYPI TYPE = ERemote.PYPI
NAME = "pypi" NAME = "pypi"
@classmethod
def get_latest_release(cls, pkg_repo):
"""Download releases
Downloads the releases from the RSS feed.
"""
if get_gh_rss_get():
url = f"https://pypi.org/rss/project/{pkg_repo}/releases.xml"
try:
with urllib.request.urlopen(url) as req:
feed = req.read()
except urllib.request.HTTPError:
return ""
dom = minidom.parseString(feed)
nodelist = dom.getElementsByTagName("rss")
nodes = ["channel", "item", "title"]
for node in nodes:
if len(nodelist) < 1:
return ""
nodelist = nodelist[0].getElementsByTagName(node)
if len(nodelist) < 1:
return ""
version = nodelist[0].firstChild.nodeValue
else:
version = ""
url = f"https://pypi.org/simple/{pkg_repo}/"
response = requests.get(
url,
timeout=5,
headers={"Accept": "application/vnd.pypi.simple.v1+json"},
)
if response.status_code == 200:
data = response.json()
versions = data["versions"]
if len(versions) > 1:
version = versions[-1]
else:
print(f"Error: {response.status_code}")
return version
# TODO: get_latest_commit # TODO: get_latest_commit
# TODO: get_latest_release # TODO: get_latest_release
@ -636,8 +689,33 @@ class Sourcehut(Remote):
TYPE = ERemote.SOURCEHUT TYPE = ERemote.SOURCEHUT
NAME = "sourcehut" NAME = "sourcehut"
# @classmethod
# def get_latest_commit(cls, pkg_repo):
@classmethod
def get_latest_commit(cls, pkg_repo):
url = f"https://git.sr.ht/{pkg_repo}/log/rss.xml"
try:
with urllib.request.urlopen(url) as req:
feed = req.read()
except urllib.request.HTTPError:
return ""
dom = minidom.parseString(feed)
date = ""
try:
node = dom.getElementsByTagName("rss")
node = node[0].getElementsByTagName("channel")
node = node[0].getElementsByTagName("item")
node = node[0].getElementsByTagName("pubDate")
date = node[0].firstChild.nodeValue
except Exception as e:
print(e)
return ""
date = datetime.strptime(date, "%a, %d %b %Y %H:%M:%S %z")
version = date.isoformat()
return version
# user, reponame = cls.split_user_repo(pkg_repo)
# repo = GitSrhtClient.get_repository(GitSrhtClient, user, reponame)
# repo.cr_await()
# print(repo)
# url = "https://git.sr.ht/api/" # url = "https://git.sr.ht/api/"
# body = """ # body = """
# query repositoryByDiskPath { # query repositoryByDiskPath {


+ 52
- 0
src/pkgupdates/settings.py View File

@ -1,6 +1,58 @@
"""Module Settings""" """Module Settings"""
import yaml
import xdg
from xdg import BaseDirectory
VERBOSE = False
GITHUB_RSS_GET = False GITHUB_RSS_GET = False
ONLY_IN_TREE = False
def load_config():
""""POOP"""
global GITHUB_RSS_GET
global VERBOSE
global ONLY_IN_TREE
filename = BaseDirectory.load_first_config('pkgupdates/config.yml')
if filename is None:
return
with open(filename, 'r') as file:
config_data = yaml.safe_load(file)
if 'verbose' in config_data:
VERBOSE = config_data['verbose']
if 'only-in-tree' in config_data:
ONLY_IN_TREE = config_data['only-in-tree']
if 'remote' in config_data:
if 'github' in config_data['remote']:
if 'rss-get' in config_data['remote']['github']:
GITHUB_RSS_GET = config_data['remote']['github']['rss-get']
def get_verbose():
"""Verbose output"""
return VERBOSE
def set_verbose(v):
"""Verbose output setter"""
global VERBOSE
VERBOSE = v
return VERBOSE
def get_only_in_tree():
"""Only in tree"""
return ONLY_IN_TREE
def set_only_in_tree(v):
"""ONLY IN TREE set"""
global ONLY_IN_TREE
ONLY_IN_TREE = v
return ONLY_IN_TREE
def get_gh_rss_get(): def get_gh_rss_get():


+ 23
- 2
src/pkgupdates/version.py View File

@ -3,8 +3,9 @@
import re import re
from enum import Enum from enum import Enum
rex_date = re.compile("([0-9]{4})([0-9]{2})([0-9]{2})")
rex_date = re.compile("(20[0-9]{2})([0-9]{2})([0-9]{2})")
rex_semantic = re.compile("([0-9]+)[.]([0-9]+)(?:[.]([0-9]+))?") rex_semantic = re.compile("([0-9]+)[.]([0-9]+)(?:[.]([0-9]+))?")
rex_numeric = re.compile("([0-9]+)$")
rex_revision = re.compile("-r([0-9]+)$") rex_revision = re.compile("-r([0-9]+)$")
@ -14,6 +15,7 @@ class EVersion(Enum):
UNKNOWN = 0 UNKNOWN = 0
SEMANTIC = 1 SEMANTIC = 1
DATE = 2 DATE = 2
COUNTER = 3
VCS = 9999 VCS = 9999
@ -42,7 +44,7 @@ class Version:
@property @property
def revision(self): def revision(self):
"""Get Gentoo ebuild revision"""
"""Get ebuild revision"""
return self.__revision return self.__revision
@ -127,6 +129,18 @@ class DateVersion(Version):
return self.__day return self.__day
class CounterVersion(Version):
"""Versions that are just a single incrementing counter"""
TYPE = EVersion.COUNTER
class VCSVersion(Version):
"""Not a version but an indicator to install the newest commit"""
TYPE = EVersion.VCS
def parse_version(version): def parse_version(version):
"""Parse version and return appropriate version class""" """Parse version and return appropriate version class"""
m = rex_semantic.search(version) m = rex_semantic.search(version)
@ -137,4 +151,11 @@ def parse_version(version):
if m is not None: if m is not None:
return DateVersion(version, *m.groups()) return DateVersion(version, *m.groups())
m = rex_numeric.search(version)
if m is not None:
if int(m[1]) == 9999:
return VCSVersion(version)
return CounterVersion(version)
return Version(version) return Version(version)

Loading…
Cancel
Save