Merge pull request #21 from kiwix/feature/cd-per-brand-p3

Update tag/version number validation
This commit is contained in:
BPH 2024-01-13 19:57:54 +01:00 committed by GitHub
commit 44809e4631
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 123 additions and 73 deletions

View File

@ -3,7 +3,7 @@
"about_text": "Für Schreibende, Lernende, Lehrende und Sprachinteressierte: Das Digitale Wörterbuch der deutschen Sprache (DWDS) ist das große Bedeutungswörterbuch des Deutschen der Gegenwart. Es bietet umfassende und wissenschaftlich verlässliche lexikalische Informationen, kostenlos und werbefrei.",
"app_name": "DWDS",
"app_store_id": "id6473090365",
"build_version": 3,
"build_number": 3,
"enforced_lang": "de",
"settings_default_external_link_to": "alwaysLoad",
"settings_show_external_link_option": false,

View File

@ -1,22 +1,21 @@
from pathlib import Path
import sys
INFO_JSON = 'info.json'
class Brand:
def __init__(self, name):
if Path(name).is_dir() == False:
self._exit_with_error(f"The directory of the brand: '{name}' does not exist")
raise FileExistsError(
f"The directory for brand: '{name}' does not exist")
self.info_file = Path(name)/INFO_JSON
if self.info_file.exists() == False:
self._exit_with_error(f"There is no {INFO_JSON} file for brand {name}")
raise FileExistsError(
f"There is no {INFO_JSON} file for brand '{name}'")
self.name = name
@staticmethod
def all_info_files():
return list(Path().rglob(INFO_JSON))
def _exit_with_error(self, msg):
print(f"Error: {msg}")
sys.exit(1)

View File

@ -7,8 +7,8 @@ INFO_JSON = 'info.json'
class CustomApps:
def __init__(self, brands=["all"], build_version=None):
self.build_version = build_version
def __init__(self, brands=["all"], build_number=None):
self.build_number = build_number
if brands == ["all"]:
self.info_files = Brand.all_info_files()
else:
@ -28,7 +28,7 @@ class CustomApps:
dict = {"include": ["project.yml"]}
targets = {}
for info in self.info_files:
parser = InfoParser(info, build_version=self.build_version)
parser = InfoParser(info, build_number=self.build_number)
targets = targets | parser.as_project_yml()
dict["targets"] = targets
@ -43,7 +43,7 @@ class CustomApps:
it should be a copy from the Kiwix target
"""
for info in self.info_files:
parser = InfoParser(info, build_version=self.build_version)
parser = InfoParser(info, build_number=self.build_number)
parser.create_plist(based_on_plist_file=custom_plist)
def download_zim_files(self):
@ -60,7 +60,7 @@ class CustomApps:
array: commands that can be feeded into subprocess.call()
"""
for info in self.info_files:
parser = InfoParser(info, build_version=self.build_version)
parser = InfoParser(info, build_number=self.build_number)
url = parser.zimurl()
file_path = parser.zim_file_path()
auth = parser.download_auth()

View File

@ -19,17 +19,17 @@ def main():
)
parser.add_argument(
"build_version",
"build_number",
nargs='?',
default=None,
help="The optional build version to use, if not provided will fall back to the build_version defined in the info.json value",
help="The optional build version to use, if not provided will fall back to the build_number defined in the info.json value",
type=int
)
args = parser.parse_args()
brand = args.brand_name
build_version = args.build_version
build_number = args.build_number
custom_apps = CustomApps(brands=[brand], build_version=build_version)
custom_apps = CustomApps(brands=[brand], build_number=build_number)
# create the plist files
custom_apps.create_plists(custom_plist=Path("Custom.plist"))

View File

@ -1,8 +1,8 @@
from urllib.parse import urlparse
import json
from pathlib import Path
from version import Version
import os
import re
import shutil
import plistlib
@ -23,20 +23,22 @@ JSON_TO_PLIST_MAPPING = {
class InfoParser:
def __init__(self, json_path, build_version=None):
def __init__(self, json_path, build_number=None):
"""Parse a specific info.json file for a brand
Args:
json_path (Path): of the branded info.json file
build_number (int, optional): If defined it will be used instead of the info.json[build_version]. Defaults to None.
build_number (int, optional): If defined it will be used instead of the info.json[build_number]. Defaults to None.
"""
self.brand_name = self._brandname_from(json_path)
self.build_version = build_version
content = json_path.read_text()
self.data = json.loads(content)
assert (JSON_KEY_ZIM_URL in self.data)
self.zim_file_name = self._filename_from(
self.data[JSON_KEY_ZIM_URL])
build_number = build_number or self.data["build_number"]
self.version = Version.from_file_name(file_name=self.zim_file_name,
build_number=build_number)
def create_plist(self, based_on_plist_file):
with based_on_plist_file.open(mode="rb") as file:
@ -54,7 +56,7 @@ class InfoParser:
dict = {
"templates": ["ApplicationTemplate"],
"settings": {"base": {
"MARKETING_VERSION": self._app_version(),
"MARKETING_VERSION": self.version.semantic,
"PRODUCT_BUNDLE_IDENTIFIER": f"org.kiwix.custom.{self.brand_name}",
"INFOPLIST_FILE": f"custom/{self._info_plist_path()}",
"INFOPLIST_KEY_CFBundleDisplayName": self._app_name(),
@ -104,10 +106,6 @@ class InfoParser:
value = self.data[json_key]
yield {plistKey: value}
def _app_version(self):
build_version = self.build_version or self.data["build_version"]
return f"{self._app_version_from(self.zim_file_name)}.{build_version}"
def _app_name(self):
return self.data[JSON_KEY_APP_NAME]
@ -130,18 +128,6 @@ class InfoParser:
def _filename_from(self, url):
return Path(urlparse(url).path).stem
def _app_version_from(self, file_name):
p = re.compile('(?P<year>\d{4})-(?P<month>\d{1,2})')
m = p.search(file_name)
year = int(m.group('year'))
month = int(m.group('month'))
assert (year > 2000)
assert (month > 0)
assert (month <= 12)
# downgrade the version by 1000 for testing the release
year -= 1000
return ".".join([str(year), str(month)])
def _excluded_languages(self):
enforced = self._enforced_language()
if enforced == None:

View File

@ -4,20 +4,42 @@ import argparse
import re
import sys
from brand import Brand
from version import Version
from info_parser import InfoParser
def _is_valid(tag):
# Regex verify the tag format
# Regex verify the tag format: folder_YYYY.MM.buildNr(_extra)
pattern = re.compile(
r'^(?P<brand_folder>\w+)_(?P<build_nr>\d+)(?:_(?P<extra_tag>\w+))?$')
r'^(?P<brand_name>\w+)_(?P<year>\d{4})\.(?P<month>\d{1,2})\.(?P<build_number>\d+)(?:_(?P<extra_tag>\w+))?$'
)
match = pattern.match(tag)
if match:
groups = match.groupdict()
brand_name = groups.get('brand_folder')
build_nr = int(groups.get('build_nr'))
brand = Brand(brand_name)
print(f"{brand.name} {build_nr}")
brand_name = groups.get('brand_name')
year = int(groups.get('year'))
month = int(groups.get('month'))
build_number = int(groups.get('build_number'))
try:
brand = Brand(brand_name)
except FileExistsError as e:
_exit_with_error(f"Invalid tag {tag}: {e}")
try:
version = Version(
year=year, month=month, build_number=build_number
)
except ValueError as e:
_exit_with_error(f"Invalid tag {tag}: {e}")
parser = InfoParser(json_path=brand.info_file, build_number=version.build_number)
if parser.version != version:
_exit_with_error(f"Ivalid date in tag: {tag}, does not match year.month of ZIM file in {brand.info_file}, it should be: {parser.version.semantic}")
print(f"{brand.name} {version.semantic_downgraded}")
else:
_exit_with_error(f"Invalid tag: {tag}")
return False

31
src/version.py Normal file
View File

@ -0,0 +1,31 @@
import re
from datetime import datetime
class Version:
def __init__(self, year: int, month: int, build_number: int):
if (1 <= month <= 12) == False:
raise ValueError(f"invalid month: {month}")
if (0 <= build_number) == False:
raise ValueError(f"invalid build number: {build_number}")
max_year = datetime.now().year + 5
if (2000 < year < max_year) == False:
raise ValueError(f"invalid year: {year}")
self.semantic = f"{year}.{month}.{build_number}"
self.semantic_downgraded = f"{year-1000}.{month}.{build_number}"
self.build_number = build_number
@classmethod
def from_file_name(self, file_name: str, build_number: int):
p = re.compile('(?P<year>\d{4})-(?P<month>\d{1,2})')
m = p.search(file_name)
return Version(year=int(m.group('year')),
month=int(m.group('month')),
build_number=build_number)
def __eq__(self, other) -> bool:
if isinstance(other, Version):
return self.semantic == other.semantic
return False

View File

@ -8,7 +8,7 @@ import os
class InfoParserTest(unittest.TestCase):
def setUp(self):
self.parser = InfoParser(Path()/"tests"/"test.json")
self.parser = InfoParser(Path("tests")/"test.json")
def test_json_to_project_yml(self):
project = self.parser.as_project_yml()
@ -17,7 +17,7 @@ class InfoParserTest(unittest.TestCase):
def test_info_plist_path(self):
custom_info = self.parser._info_plist_path()
self.assertEqual(custom_info, Path()/"tests"/"tests.plist")
self.assertEqual(custom_info, Path("tests")/"tests.plist")
def test_file_name_from_url(self):
url = "https://www.dwds.de/kiwix/f/dwds_de_dictionary_nopic_2023-11-20.zim"
@ -33,23 +33,6 @@ class InfoParserTest(unittest.TestCase):
brand_name = self.parser._brandname_from(filepath)
self.assertEqual(brand_name, "dwds")
def test_version_from_filename(self):
version = self.parser._app_version_from(
"dwds_de_dictionary_nopic_2023-11-20")
self.assertEqual(version, "1023.11")
version = self.parser._app_version_from(
"dwds_de_dictionary_nopic_2023-09-20")
self.assertEqual(version, "1023.9")
version = self.parser._app_version_from(
"dwds_de_dictionary_nopic_2023-01")
self.assertEqual(version, "1023.1")
version = self.parser._app_version_from(
"dwds_de_dictionary_nopic_2023-12")
self.assertEqual(version, "1023.12")
def test_app_name(self):
app_name = self.parser._app_name()
self.assertEqual(app_name, "DWDS")
@ -62,15 +45,18 @@ class InfoParserTest(unittest.TestCase):
excluded = self.parser._excluded_languages()
self.assertIn("**/*.lproj", excluded)
def test_app_version(self):
self.assertEqual(self.parser._app_version(), "1023.12.3")
def test_app_version_with_default_json_build_number(self):
self.assertEqual(self.parser.version.semantic, "2023.12.3")
self.assertEqual(self.parser.version.semantic_downgraded, "1023.12.3")
def test_app_version_using_a_tag(self):
parser = InfoParser(Path()/"tests"/"test.json", build_version=15)
self.assertEqual(parser._app_version(), "1023.12.15")
def test_app_version_using_a_specific_build_number(self):
parser = InfoParser(Path("tests")/"test.json", build_number=15)
self.assertEqual(parser.version.semantic, "2023.12.15")
self.assertEqual(parser.version.semantic_downgraded, "1023.12.15")
parser = InfoParser(Path()/"tests"/"test.json", build_version=33)
self.assertEqual(parser._app_version(), "1023.12.33")
parser = InfoParser(Path("tests")/"test.json", build_number=33)
self.assertEqual(parser.version.semantic, "2023.12.33")
self.assertEqual(parser.version.semantic_downgraded, "1023.12.33")
def test_as_plist(self):
self.parser.create_plist(

View File

@ -9,5 +9,5 @@
"settings_show_external_link_option": false,
"zim_auth": "DWDS_HTTP_BASIC_ACCESS_AUTHENTICATION",
"zim_url": "https://www.dwds.de/kiwix/f/dwds_de_dictionary_nopic_2023-12-15.zim",
"build_version": 3
"build_number": 3
}

26
tests/version_test.py Normal file
View File

@ -0,0 +1,26 @@
import unittest
from src.version import Version
class VersionTest(unittest.TestCase):
def test_version_from_filename(self):
version = Version.from_file_name(
"dwds_de_dictionary_nopic_2023-11-20", build_number=10)
self.assertEqual(version.semantic, "2023.11.10")
self.assertEqual(version.semantic_downgraded, "1023.11.10")
version = Version.from_file_name(
"dwds_de_dictionary_nopic_2023-09-20", build_number=0)
self.assertEqual(version.semantic, "2023.9.0")
self.assertEqual(version.semantic_downgraded, "1023.9.0")
version = Version.from_file_name(
"dwds_de_dictionary_nopic_2023-01", build_number=7)
self.assertEqual(version.semantic, "2023.1.7")
self.assertEqual(version.semantic_downgraded, "1023.1.7")
version = Version.from_file_name(
"dwds_de_dictionary_nopic_2023-12", build_number=129)
self.assertEqual(version.semantic, "2023.12.129")
self.assertEqual(version.semantic_downgraded, "1023.12.129")