Replace geninfo with mirrorz submodule

Signed-off-by: Harry Chen <i@harrychen.xyz>
This commit is contained in:
Harry Chen 2024-05-12 10:42:29 +08:00
parent 6b3307cea8
commit 228c36ad17
6 changed files with 17 additions and 1759 deletions

3
.gitmodules vendored
View File

@ -2,3 +2,6 @@
path = help/_posts/mirrorz-help-ng-transpiled
url = https://github.com/tuna/mirrorz-help-ng.git
branch = transpiled
[submodule "geninfo/z-geninfo"]
path = geninfo/z-geninfo
url = https://github.com/mirrorz-org/genisolist.git

12
geninfo/distro.ini Normal file
View File

@ -0,0 +1,12 @@
[%distro%]
d10 = Ubuntu
d20 = Ubuntu 衍生版
d30 = Debian
d40 = Arch Linux
d45 = Manjaro
d50 = Fedora
d60 = Deepin
d70 = Kali Linux
d80 = openSUSE
d90 = CentOS
# Distro not listed are set to 0xFFFF as default (to be at the end of the list)

File diff suppressed because it is too large Load Diff

View File

@ -1,378 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# usage:
# on server: python3 genisolist.py
# development: python3 genisolist.py -R mirrors.tuna.tsinghua.edu.cn -T Debian
import os
import re
import glob
import json
import logging
import collections
import sys
import fnmatch
from urllib.parse import urljoin
from configparser import ConfigParser
from argparse import ArgumentParser, ArgumentError
class Version:
def __init__(self, vstring=None):
if vstring:
self.parse(vstring)
def __repr__(self):
return f"{self.__class__.__name__} ('{str(self)}')"
def __eq__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c == 0
def __lt__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c < 0
def __le__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c <= 0
def __gt__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c > 0
def __ge__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c >= 0
class LooseVersion(Version):
component_re = re.compile(r"(\d+ | [a-z]+ | \.)", re.VERBOSE)
def parse(self, vstring):
self.vstring = vstring
components = [x for x in self.component_re.split(vstring) if x and x != "."]
for i, obj in enumerate(components):
try:
components[i] = int(obj)
except ValueError:
pass
# special handling for "latest"
if components[0] == "latest":
components[0] = float("inf")
self.version = components
def __str__(self):
return self.vstring
def __repr__(self):
return "LooseVersion ('%s')" % str(self)
def _cmp(self, other):
if isinstance(other, str):
other = LooseVersion(other)
elif not isinstance(other, LooseVersion):
return NotImplemented
try:
if self.version == other.version:
return 0
if self.version < other.version:
return -1
if self.version > other.version:
return 1
except TypeError:
# if comparison fails, convert everything into string and try again
self_version = [str(x) for x in self.version]
other_version = [str(x) for x in other.version]
if self_version == other_version:
return 0
if self_version < other_version:
return -1
if self_version > other_version:
return 1
parser = ArgumentParser(
prog="iso info list generator",
description="Generate iso info list",
)
parser.add_argument("-d", "--dir", default=None, help="Override root directory.")
parser.add_argument(
"-R",
"--remote",
default=None,
help="[Remote Mode] Using rsync to get file list instead of reading from INI. Need the base of target site, for example, `mirror.tuna.tsinghua.edu.cn`.",
)
parser.add_argument(
"-T",
"--test",
default=None,
nargs="*",
help="Test specified `distro`s (multiple arguments input is supported) in INI. If Remote Mode is on, `distro`s must be specified in case of heavy rsync job.",
)
args = parser.parse_args()
logger = logging.getLogger(__name__)
CONFIG_FILE = os.path.join(os.path.dirname(__file__), "genisolist.ini")
def getPlatformPriority(platform):
platform = platform.lower()
if platform in ["amd64", "x86_64", "64bit"]:
return 100
elif platform in ["arm64", "aarch64", "arm64v8"]:
return 95
elif platform in ["riscv64"]:
return 95
elif platform in ["loongson2f", "loongson3"]:
return 95
elif platform in ["i386", "i486", "i586", "i686", "x86", "32bit"]:
return 90
elif platform in ["arm32", "armhf", "armv7"]:
return 85
else:
return 0
def renderTemplate(template, result):
group_count = len(result.groups()) + 1
for i in range(group_count):
template = template.replace("$%d" % i, result.group(i) or "")
return template
def getSortKeys(template, result):
keys = []
for i in template.split(" "):
if not i:
continue
if i[0] != "$":
keys.append(i)
else:
keys.append(result.group(int(i[1:])) or "")
return keys
def parseSection(items, rsync=False):
items = dict(items)
if "location" in items:
locations = [items["location"]]
else:
locations = []
i = 0
while ("location_%d" % i) in items:
locations.append(items["location_%d" % i])
i += 1
pattern = items.get("pattern", "")
prog = re.compile(pattern)
images = {}
for location in locations:
if rsync:
# find last non-wildcard path
non_regex = []
for dir in location.split("/"):
if re.search(r"[\?\*\[\]]", dir):
break
non_regex.append(dir)
prefix = "/".join(non_regex + [""])
rsync_url = args.remote.rstrip("/") + "/" + prefix
print("[RSync] %s" % rsync_url)
image_paths = rsyncQuery(rsync_url)
# recover path
image_paths = [prefix + path for path in image_paths]
# use fnmatch to mimic glob behavior
image_paths = list(
filter(lambda path: fnmatch.fnmatch(path, location), image_paths)
)
else:
logger.debug("[GLOB] %s", location)
image_paths = glob.glob(location)
for imagepath in image_paths:
logger.debug("[FILE] %s", imagepath)
result = prog.search(imagepath)
if not (result):
logger.debug("[MATCH] None")
continue
else:
logger.debug("[MATCH] %r", result.groups())
imageinfo = {"filepath": imagepath, "distro": items["distro"]}
for prop in ("version", "type", "platform", "category"):
imageinfo[prop] = renderTemplate(items.get(prop, ""), result)
if "version" not in imageinfo:
imageinfo["version"] = "0.0"
sort_by = items.get("sort_by", "")
if not (sort_by):
imageinfo["sort_key"] = [
imageinfo["version"],
imageinfo["platform"],
imageinfo["type"],
]
else:
imageinfo["sort_key"] = getSortKeys(sort_by, result)
logger.debug("[JSON] %r", imageinfo)
key = renderTemplate(items.get("key_by", ""), result)
if key not in images:
images[key] = []
images[key].append(imageinfo)
for image_group in images.values():
if "nosort" not in items:
image_group.sort(
key=lambda k: (
LooseVersion(k["version"]),
getPlatformPriority(k["platform"]),
k["type"],
),
reverse=True,
)
i = 0
versions = set()
listvers = int(items.get("listvers", 0xFF))
for image in image_group:
versions.add(image["version"])
if len(versions) <= listvers:
yield image
else:
break
def getDetail(image_info, urlbase):
url = urljoin(urlbase, image_info["filepath"])
desc = (
"%s (%s%s)"
% (
image_info["version"],
image_info["platform"],
", %s" % image_info["type"] if image_info["type"] else "",
)
if image_info["platform"] != ""
else image_info["version"]
)
category = image_info.get("category", "os") or "os"
return (desc, url, category)
def getJsonOutput(url_dict, prio={}):
raw = []
for distro in url_dict:
raw.append(
{
"distro": distro,
"category": list({c for _, _, c in url_dict[distro]})[0],
"urls": [
{"name": name, "url": url} for name, url, _ in url_dict[distro]
],
}
)
raw.sort(key=lambda d: prio.get(d["distro"], 0xFFFF))
return json.dumps(raw, indent=2)
def getImageList():
ini = ConfigParser()
if not (ini.read(CONFIG_FILE)):
raise Exception("%s not found!" % CONFIG_FILE)
prior = {}
for name, value in ini.items("%main%"):
if re.match(r"d\d+$", name):
prior[value] = int(name[1:])
url_dict = {}
root = ini.get("%main%", "root")
urlbase = ini.get("%main%", "urlbase")
if not args.remote:
# Local
if args.dir:
# Allow to override root in command-line
root = args.dir
oldcwd = os.getcwd()
os.chdir(root)
img_dict = collections.defaultdict(list)
for section in ini.sections():
if section == "%main%":
continue
for image in parseSection(ini.items(section)):
img_dict[image["distro"]].append(image)
for distro, images in img_dict.items():
images.sort(key=lambda x: x["sort_key"], reverse=True)
logger.debug("[IMAGES] %r %r", distro, images)
url_dict[distro] = [getDetail(image, urlbase) for image in images]
os.chdir(oldcwd)
else:
# Remote
for spec_distro in args.test:
img_dict = collections.defaultdict(list)
for section in ini.sections():
if section == "%main%":
continue
if ini.get(section, "distro") != spec_distro:
continue
else:
for image in parseSection(ini.items(section), rsync=True):
img_dict[image["distro"]].append(image)
for distro, images in img_dict.items():
images.sort(key=lambda x: x["sort_key"], reverse=True)
logger.debug("[IMAGES] %r %r", distro, images)
url_dict[distro] = [getDetail(image, urlbase) for image in images]
return getJsonOutput(url_dict, prior)
def rsyncQuery(url: str):
rsync_proc = os.popen(
"rsync -r --list-only --no-motd rsync://%s | awk '{ $1=$2=$3=$4=\"\"; print substr($0,5); }'"
% url
)
return [path.rstrip() for path in rsync_proc.readlines()]
if __name__ == "__main__":
import sys
logging.basicConfig(stream=sys.stderr, level=logging.WARNING)
if args.remote:
if not args.test:
raise ArgumentError(
None,
"If Remote Mode is on, images must be specified in case of heavy rsync job.",
)
print(getImageList())

1
geninfo/z-geninfo Submodule

@ -0,0 +1 @@
Subproject commit d7ad6f6b33213fb0ddcc1ef8861e6e103cd7eac4

@ -1 +1 @@
Subproject commit 7cf283007f02886745259946a794377770ff725c
Subproject commit 351af7a77af5c1ed2a3d349de2878f933d2817cf