mirror of
https://github.com/kiwix/kiwix-apple-custom.git
synced 2025-09-22 11:51:04 -04:00
Merge pull request #21 from kiwix/feature/cd-per-brand-p3
Update tag/version number validation
This commit is contained in:
commit
44809e4631
@ -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.",
|
"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_name": "DWDS",
|
||||||
"app_store_id": "id6473090365",
|
"app_store_id": "id6473090365",
|
||||||
"build_version": 3,
|
"build_number": 3,
|
||||||
"enforced_lang": "de",
|
"enforced_lang": "de",
|
||||||
"settings_default_external_link_to": "alwaysLoad",
|
"settings_default_external_link_to": "alwaysLoad",
|
||||||
"settings_show_external_link_option": false,
|
"settings_show_external_link_option": false,
|
||||||
|
13
src/brand.py
13
src/brand.py
@ -1,22 +1,21 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
|
||||||
|
|
||||||
INFO_JSON = 'info.json'
|
INFO_JSON = 'info.json'
|
||||||
|
|
||||||
|
|
||||||
class Brand:
|
class Brand:
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
if Path(name).is_dir() == False:
|
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
|
self.info_file = Path(name)/INFO_JSON
|
||||||
if self.info_file.exists() == False:
|
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
|
self.name = name
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def all_info_files():
|
def all_info_files():
|
||||||
return list(Path().rglob(INFO_JSON))
|
return list(Path().rglob(INFO_JSON))
|
||||||
|
|
||||||
def _exit_with_error(self, msg):
|
|
||||||
print(f"Error: {msg}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
@ -7,8 +7,8 @@ INFO_JSON = 'info.json'
|
|||||||
|
|
||||||
class CustomApps:
|
class CustomApps:
|
||||||
|
|
||||||
def __init__(self, brands=["all"], build_version=None):
|
def __init__(self, brands=["all"], build_number=None):
|
||||||
self.build_version = build_version
|
self.build_number = build_number
|
||||||
if brands == ["all"]:
|
if brands == ["all"]:
|
||||||
self.info_files = Brand.all_info_files()
|
self.info_files = Brand.all_info_files()
|
||||||
else:
|
else:
|
||||||
@ -28,7 +28,7 @@ class CustomApps:
|
|||||||
dict = {"include": ["project.yml"]}
|
dict = {"include": ["project.yml"]}
|
||||||
targets = {}
|
targets = {}
|
||||||
for info in self.info_files:
|
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()
|
targets = targets | parser.as_project_yml()
|
||||||
|
|
||||||
dict["targets"] = targets
|
dict["targets"] = targets
|
||||||
@ -43,7 +43,7 @@ class CustomApps:
|
|||||||
it should be a copy from the Kiwix target
|
it should be a copy from the Kiwix target
|
||||||
"""
|
"""
|
||||||
for info in self.info_files:
|
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)
|
parser.create_plist(based_on_plist_file=custom_plist)
|
||||||
|
|
||||||
def download_zim_files(self):
|
def download_zim_files(self):
|
||||||
@ -60,7 +60,7 @@ class CustomApps:
|
|||||||
array: commands that can be feeded into subprocess.call()
|
array: commands that can be feeded into subprocess.call()
|
||||||
"""
|
"""
|
||||||
for info in self.info_files:
|
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()
|
url = parser.zimurl()
|
||||||
file_path = parser.zim_file_path()
|
file_path = parser.zim_file_path()
|
||||||
auth = parser.download_auth()
|
auth = parser.download_auth()
|
||||||
|
@ -19,17 +19,17 @@ def main():
|
|||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"build_version",
|
"build_number",
|
||||||
nargs='?',
|
nargs='?',
|
||||||
default=None,
|
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
|
type=int
|
||||||
)
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
brand = args.brand_name
|
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
|
# create the plist files
|
||||||
custom_apps.create_plists(custom_plist=Path("Custom.plist"))
|
custom_apps.create_plists(custom_plist=Path("Custom.plist"))
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from version import Version
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import shutil
|
import shutil
|
||||||
import plistlib
|
import plistlib
|
||||||
|
|
||||||
@ -23,20 +23,22 @@ JSON_TO_PLIST_MAPPING = {
|
|||||||
|
|
||||||
class InfoParser:
|
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
|
"""Parse a specific info.json file for a brand
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
json_path (Path): of the branded info.json file
|
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.brand_name = self._brandname_from(json_path)
|
||||||
self.build_version = build_version
|
|
||||||
content = json_path.read_text()
|
content = json_path.read_text()
|
||||||
self.data = json.loads(content)
|
self.data = json.loads(content)
|
||||||
assert (JSON_KEY_ZIM_URL in self.data)
|
assert (JSON_KEY_ZIM_URL in self.data)
|
||||||
self.zim_file_name = self._filename_from(
|
self.zim_file_name = self._filename_from(
|
||||||
self.data[JSON_KEY_ZIM_URL])
|
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):
|
def create_plist(self, based_on_plist_file):
|
||||||
with based_on_plist_file.open(mode="rb") as file:
|
with based_on_plist_file.open(mode="rb") as file:
|
||||||
@ -54,7 +56,7 @@ class InfoParser:
|
|||||||
dict = {
|
dict = {
|
||||||
"templates": ["ApplicationTemplate"],
|
"templates": ["ApplicationTemplate"],
|
||||||
"settings": {"base": {
|
"settings": {"base": {
|
||||||
"MARKETING_VERSION": self._app_version(),
|
"MARKETING_VERSION": self.version.semantic,
|
||||||
"PRODUCT_BUNDLE_IDENTIFIER": f"org.kiwix.custom.{self.brand_name}",
|
"PRODUCT_BUNDLE_IDENTIFIER": f"org.kiwix.custom.{self.brand_name}",
|
||||||
"INFOPLIST_FILE": f"custom/{self._info_plist_path()}",
|
"INFOPLIST_FILE": f"custom/{self._info_plist_path()}",
|
||||||
"INFOPLIST_KEY_CFBundleDisplayName": self._app_name(),
|
"INFOPLIST_KEY_CFBundleDisplayName": self._app_name(),
|
||||||
@ -104,10 +106,6 @@ class InfoParser:
|
|||||||
value = self.data[json_key]
|
value = self.data[json_key]
|
||||||
yield {plistKey: value}
|
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):
|
def _app_name(self):
|
||||||
return self.data[JSON_KEY_APP_NAME]
|
return self.data[JSON_KEY_APP_NAME]
|
||||||
|
|
||||||
@ -130,18 +128,6 @@ class InfoParser:
|
|||||||
def _filename_from(self, url):
|
def _filename_from(self, url):
|
||||||
return Path(urlparse(url).path).stem
|
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):
|
def _excluded_languages(self):
|
||||||
enforced = self._enforced_language()
|
enforced = self._enforced_language()
|
||||||
if enforced == None:
|
if enforced == None:
|
||||||
|
@ -4,20 +4,42 @@ import argparse
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from brand import Brand
|
from brand import Brand
|
||||||
|
from version import Version
|
||||||
|
from info_parser import InfoParser
|
||||||
|
|
||||||
|
|
||||||
def _is_valid(tag):
|
def _is_valid(tag):
|
||||||
# Regex verify the tag format
|
# Regex verify the tag format: folder_YYYY.MM.buildNr(_extra)
|
||||||
pattern = re.compile(
|
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)
|
match = pattern.match(tag)
|
||||||
|
|
||||||
if match:
|
if match:
|
||||||
groups = match.groupdict()
|
groups = match.groupdict()
|
||||||
brand_name = groups.get('brand_folder')
|
brand_name = groups.get('brand_name')
|
||||||
build_nr = int(groups.get('build_nr'))
|
year = int(groups.get('year'))
|
||||||
brand = Brand(brand_name)
|
month = int(groups.get('month'))
|
||||||
print(f"{brand.name} {build_nr}")
|
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:
|
else:
|
||||||
_exit_with_error(f"Invalid tag: {tag}")
|
_exit_with_error(f"Invalid tag: {tag}")
|
||||||
return False
|
return False
|
||||||
|
31
src/version.py
Normal file
31
src/version.py
Normal 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
|
@ -8,7 +8,7 @@ import os
|
|||||||
class InfoParserTest(unittest.TestCase):
|
class InfoParserTest(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.parser = InfoParser(Path()/"tests"/"test.json")
|
self.parser = InfoParser(Path("tests")/"test.json")
|
||||||
|
|
||||||
def test_json_to_project_yml(self):
|
def test_json_to_project_yml(self):
|
||||||
project = self.parser.as_project_yml()
|
project = self.parser.as_project_yml()
|
||||||
@ -17,7 +17,7 @@ class InfoParserTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_info_plist_path(self):
|
def test_info_plist_path(self):
|
||||||
custom_info = self.parser._info_plist_path()
|
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):
|
def test_file_name_from_url(self):
|
||||||
url = "https://www.dwds.de/kiwix/f/dwds_de_dictionary_nopic_2023-11-20.zim"
|
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)
|
brand_name = self.parser._brandname_from(filepath)
|
||||||
self.assertEqual(brand_name, "dwds")
|
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):
|
def test_app_name(self):
|
||||||
app_name = self.parser._app_name()
|
app_name = self.parser._app_name()
|
||||||
self.assertEqual(app_name, "DWDS")
|
self.assertEqual(app_name, "DWDS")
|
||||||
@ -62,15 +45,18 @@ class InfoParserTest(unittest.TestCase):
|
|||||||
excluded = self.parser._excluded_languages()
|
excluded = self.parser._excluded_languages()
|
||||||
self.assertIn("**/*.lproj", excluded)
|
self.assertIn("**/*.lproj", excluded)
|
||||||
|
|
||||||
def test_app_version(self):
|
def test_app_version_with_default_json_build_number(self):
|
||||||
self.assertEqual(self.parser._app_version(), "1023.12.3")
|
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):
|
def test_app_version_using_a_specific_build_number(self):
|
||||||
parser = InfoParser(Path()/"tests"/"test.json", build_version=15)
|
parser = InfoParser(Path("tests")/"test.json", build_number=15)
|
||||||
self.assertEqual(parser._app_version(), "1023.12.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)
|
parser = InfoParser(Path("tests")/"test.json", build_number=33)
|
||||||
self.assertEqual(parser._app_version(), "1023.12.33")
|
self.assertEqual(parser.version.semantic, "2023.12.33")
|
||||||
|
self.assertEqual(parser.version.semantic_downgraded, "1023.12.33")
|
||||||
|
|
||||||
def test_as_plist(self):
|
def test_as_plist(self):
|
||||||
self.parser.create_plist(
|
self.parser.create_plist(
|
||||||
|
@ -9,5 +9,5 @@
|
|||||||
"settings_show_external_link_option": false,
|
"settings_show_external_link_option": false,
|
||||||
"zim_auth": "DWDS_HTTP_BASIC_ACCESS_AUTHENTICATION",
|
"zim_auth": "DWDS_HTTP_BASIC_ACCESS_AUTHENTICATION",
|
||||||
"zim_url": "https://www.dwds.de/kiwix/f/dwds_de_dictionary_nopic_2023-12-15.zim",
|
"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
26
tests/version_test.py
Normal 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")
|
Loading…
x
Reference in New Issue
Block a user