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"""
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():
@ -13,18 +13,35 @@ def process_arguments():
parser.add_argument('--github-rss-get',
type=bool,
default=False,
default=get_gh_rss_get(),
required=False,
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()
set_gh_rss_get(args.github_rss_get)
set_only_in_tree(args.only_in_tree)
set_verbose(args.verbose)
def main():
"""Main Function"""
load_config()
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():
check_package(pkg, meta)


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

@ -6,8 +6,10 @@ import collections
import re
import xml.etree.ElementTree as ET
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 .settings import get_verbose
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"]
)
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():
"""Scan current directory for ebuild packages"""
@ -70,6 +102,8 @@ def get_package_versions(meta):
eversion = EVersion.UNKNOWN
is_semantic = False
is_date = False
is_counter = False
is_vcs = False
if "versions" in meta:
for version in meta["versions"]:
@ -78,11 +112,19 @@ def get_package_versions(meta):
is_semantic = True
if isinstance(version_obj, DateVersion):
is_date = True
if isinstance(version_obj, CounterVersion):
is_counter = True
if isinstance(version_obj, VCSVersion):
is_vcs = True
versions.append(version_obj)
if is_date:
eversion = EVersion.DATE
elif is_semantic:
eversion = EVersion.SEMANTIC
elif is_counter:
eversion = EVersion.COUNTER
elif is_vcs:
eversion = EVersion.VCS
return versions, eversion
@ -99,10 +141,12 @@ def check_package(pkg, meta): # pylint: disable=R0911
match (version_type):
case EVersion.DATE:
if remote.TYPE == ERemote.UNKNOWN:
print(pkg + ": Unsupported backend: " + pkg_type)
print(f"{pkg}: Unsupported backend: {pkg_type}")
return
if not remote.support_latest_commit():
print(f"{pkg}: Error: {pkg_type} "
"date version check not implemented!")
return
version = remote.get_latest_commit(meta["remote-id"])
@ -115,32 +159,55 @@ def check_package(pkg, meta): # pylint: disable=R0911
case EVersion.SEMANTIC:
if remote.TYPE == ERemote.UNKNOWN:
print(pkg + ": Unsupported backend: " + pkg_type)
print(f"{pkg}: Unsupported backend: {pkg_type}")
return
if not remote.support_latest_release():
print(f"{pkg}: Error: {pkg_type} "
"semantic version check not implemented!")
return
tag = remote.get_latest_release(meta["remote-id"])
if tag is None or tag == "":
print(pkg + ": HTTPError! " + meta["remote-id"])
print(f"{pkg}: HTTPError! {meta['remote-id']}")
return
latest_version = parse_version(tag)
case EVersion.VCS:
print(pkg + ": VCS package")
print(f"{pkg}: VCS package")
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:
print(pkg + ": Manual Compare")
versions_str = ', '.join(str(ver.version) for ver in versions)
print(f"{pkg}: Manual Compare ({versions_str})")
return
is_new = True
for pkg_version in versions:
is_new = is_new and (not pkg_version.compare(latest_version))
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")

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

@ -2,9 +2,25 @@
import urllib.request
import re
import xml
from datetime import datetime
from enum import Enum
from xml.dom import minidom
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 .version import rex_semantic
from .settings import get_gh_rss_get
@ -115,29 +131,25 @@ class Remote:
@classmethod
def support_latest_release(cls):
"""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
def support_latest_commit(cls):
"""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
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"""
print(f"Error: {cls.NAME} semantic version check not implemented!")
print(f"{pkg_repo}: Error: {cls.NAME} "
"semantic version check not implemented!")
return ""
@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"""
print(f"Error: {cls.NAME} date version check not implemented!")
print(f"{pkg_repo}: Error: {cls.NAME} "
"date version check not implemented!")
return ""
@ -153,8 +165,8 @@ class Bitbucket(Remote):
"""Download releases"""
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)
@ -338,7 +350,7 @@ class Github(Remote):
"""
if get_gh_rss_get():
url = "https://github.com/" + pkg_repo + "/commits.atom"
url = f"https://github.com/{pkg_repo}/commits.atom"
try:
with urllib.request.urlopen(url) as req:
feed = req.read()
@ -367,7 +379,7 @@ class Github(Remote):
"""
if get_gh_rss_get():
url = "https://github.com/" + pkg_repo + "/tags.atom"
url = f"https://github.com/{pkg_repo}/tags.atom"
try:
with urllib.request.urlopen(url) as req:
feed = req.read()
@ -414,7 +426,7 @@ class Gitlab(Remote):
@classmethod
def get_latest_release(cls, pkg_repo):
ident = pkg_repo.replace("/", "%2F")
url = cls.URL + "/projects/" + ident + "/repository/tags"
url = f"{cls.URL}/projects/{ident}/repository/tags"
version = ""
try:
with requests.get(url, timeout=5) as resp:
@ -428,7 +440,7 @@ class Gitlab(Remote):
@classmethod
def get_latest_commit(cls, pkg_repo):
ident = pkg_repo.replace("/", "%2F")
url = cls.URL + "/projects/" + ident + "/repository/commits"
url = f"{cls.URL}/projects/{ident}/repository/commits"
version = ""
try:
with requests.get(url, timeout=5) as resp:
@ -558,7 +570,6 @@ class Pecl(Remote):
# TODO: get_latest_commit
# TODO: get_latest_release
class Pypi(Remote):
"""Remote for pypi.org hosted Python projects
@ -567,6 +578,48 @@ class Pypi(Remote):
TYPE = ERemote.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_release
@ -636,8 +689,33 @@ class Sourcehut(Remote):
TYPE = ERemote.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/"
# body = """
# query repositoryByDiskPath {


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

@ -1,6 +1,58 @@
"""Module Settings"""
import yaml
import xdg
from xdg import BaseDirectory
VERBOSE = 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():


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

@ -3,8 +3,9 @@
import re
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_numeric = re.compile("([0-9]+)$")
rex_revision = re.compile("-r([0-9]+)$")
@ -14,6 +15,7 @@ class EVersion(Enum):
UNKNOWN = 0
SEMANTIC = 1
DATE = 2
COUNTER = 3
VCS = 9999
@ -42,7 +44,7 @@ class Version:
@property
def revision(self):
"""Get Gentoo ebuild revision"""
"""Get ebuild revision"""
return self.__revision
@ -127,6 +129,18 @@ class DateVersion(Version):
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):
"""Parse version and return appropriate version class"""
m = rex_semantic.search(version)
@ -137,4 +151,11 @@ def parse_version(version):
if m is not None:
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)

Loading…
Cancel
Save