diff --git a/app.py b/app.py
index 0d79cff..be59d0f 100644
--- a/app.py
+++ b/app.py
@@ -1,22 +1,10 @@
import re
-import threading
-import time
-
-from os import path, listdir
-from datetime import date
-from configparser import ConfigParser
+import datetime
from sass import compile as compile_sass
-from htmlmin import minify as minify_html
+from flask import Flask, redirect, request, url_for, g, Response
-from flask import Flask, redirect, render_template, request, url_for, g, Response
-
-
-# ---------- ENV VARS --------------------------------------------------------
-
-
-STATUS_URL = "https://builds.kolibrios.org/status.html"
-STATUS_FETCH_DELAY_SEC = 300 # 5 minutes
+from modules import autobuild, locales, helpers
# ---------- APP CONFIG ------------------------------------------------------
@@ -24,6 +12,8 @@ STATUS_FETCH_DELAY_SEC = 300 # 5 minutes
app = Flask(__name__)
+locales.ensure_loaded()
+
if app.debug:
# CSS Compilation and minification
css = compile_sass(filename="static/style.scss", output_style="compressed")
@@ -40,163 +30,27 @@ if app.debug:
f.write(js)
-# ---------- LATEST COMMIT DATE (MINIMAL ADD-ON) -----------------------------
-
-
-autobuild_date = "DD.MM.YYYY"
-autobuild_vers = "0.0.0.0+0000-0000000"
-
-
-def _refresh_build_date_once():
- global autobuild_date, autobuild_vers
- try:
- from urllib.request import Request, urlopen
- import re
-
- req = Request(
- STATUS_URL,
- headers={
- "User-Agent": "Mozilla/5.0",
- "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
- },
- )
- with urlopen(req, timeout=10) as r:
- html = r.read().decode(
- r.headers.get_content_charset() or "utf-8", "replace"
- )
-
- rows = re.findall(r"(
]*>.*?
)", html, flags=re.I | re.S)
- if not rows:
- return
-
- last_commit_ver = None
-
- for row in rows:
- cls = re.search(r'class\s*=\s*"([^"]*)"', row, flags=re.I)
- classes = cls.group(1).lower() if cls else ""
-
- text = re.sub(r"<[^>]+>", " ", row)
-
- if "commit" in classes:
- mver = re.search(
- r"\b(\d+\.\d+\.\d+\.\d+\+\d{3,8}-[0-9a-fA-F]{7,40})\b", row
- )
- if mver:
- last_commit_ver = mver.group(1)
-
- elif "success" in classes:
- mts = re.search(
- r"\b(\d{4})\.(\d{2})\.(\d{2})\s+\d{2}:\d{2}:\d{2}\b", text
- )
- if not mts:
- mds = re.search(r"\b(\d{2})\.(\d{2})\.(\d{4})\b", text)
- if mds:
- autobuild_date = f"{mds.group(1)}.{mds.group(2)}.{mds.group(3)}"
- else:
- return
- else:
- y, mo, d = mts.groups()
- autobuild_date = f"{d}.{mo}.{y}"
-
- if last_commit_ver:
- autobuild_vers = last_commit_ver
- return
-
- except Exception:
- pass
-
-
-def _updater_loop():
- while True:
- _refresh_build_date_once()
- time.sleep(STATUS_FETCH_DELAY_SEC)
-
-
-_started = False
-_refresh_build_date_once()
-_updater_lock = threading.Lock()
-
-
@app.before_request
def _ensure_updater_started():
- global _started
- if not _started:
- with _updater_lock:
- if not _started:
- _started = True
- threading.Thread(target=_updater_loop, daemon=True).start()
-
-
-@app.context_processor
-def _inject_autobuild_vers():
- return {'autobuild_vers': autobuild_vers}
-
-
-@app.context_processor
-def _inject_autobuild_date():
- return {'autobuild_date': autobuild_date}
-
-
-# ---------- LOCALES FUNCTIONS -----------------------------------------------
-
-
-def load_all_locales():
- translations = {}
- locales_dir = "locales"
-
- locales_code_default = ('en', 'ru', 'es')
- locales_code_extra = []
- locales_code = ()
-
- for filename in listdir(locales_dir):
- if filename.endswith(".ini"):
- cp = ConfigParser()
- lang = path.splitext(filename)[0]
- with open(path.join(locales_dir, filename), encoding="utf-8") as f:
- cp.read_file(f)
-
- if lang not in locales_code_default:
- locales_code_extra.append(lang)
-
- translations[lang] = {
- section: dict(cp[section]) for section in cp.sections()
- }
-
- locales_code = locales_code_default + tuple(sorted(locales_code_extra))
- locales_name = {l: translations[l]['title']['language'] for l in locales_code}
-
- return translations, locales_name, locales_code
-
-
-translations, locales_name, locales_code = load_all_locales()
-
-
-# ---------- HELPER FUNCTIONS ------------------------------------------------
-
-
-def get_best_lang():
- return request.accept_languages.best_match(locales_code) or "en"
-
-
-def render_localized_template(lang, template_name):
- if lang not in locales_code:
- return redirect(url_for("index", lang=get_best_lang()))
-
- return minify_html(
- render_template(
- template_name,
- year=date.today().year,
- ),
- remove_empty_space=True,
- )
+ autobuild.ensure_started()
@app.before_request
def before_request():
if args := request.view_args:
g.locale = args.get("lang", "en")
- g.translations = translations.get(g.locale, get_best_lang())
- g.locales_name = locales_name
+ g.translations = locales.translations.get(g.locale, helpers.get_best_lang())
+ g.locales_name = locales.locales_name
+
+
+@app.context_processor
+def _inject_autobuild_vers():
+ return {'autobuild_vers': autobuild.autobuild_vers}
+
+
+@app.context_processor
+def _inject_autobuild_date():
+ return {'autobuild_date': autobuild.autobuild_date}
@app.context_processor
@@ -216,25 +70,22 @@ def inject_translations():
return {'_': translate}
-# ---------- MAIN PAGES ------------------------------------------------------
+# ---------- ROUTES -------------------------------------------------------
@app.route("/")
def home():
- return redirect(url_for("index", lang=get_best_lang()))
+ return redirect(url_for("index", lang=helpers.get_best_lang()))
@app.route("/")
def index(lang):
- return render_localized_template(lang, "index.html")
+ return helpers.render_localized_template(lang, "index.html")
@app.route("//download")
def download(lang):
- return render_localized_template(lang, "download.html")
-
-
-# ---------- ROBOTS.TXT + SITEMAP.XML ----------------------------------------
+ return helpers.render_localized_template(lang, "download.html")
@app.route("/robots.txt")
@@ -251,10 +102,10 @@ def robots_txt():
@app.route("/sitemap.xml")
def sitemap_xml():
base_url = request.url_root.rstrip("/")
- today = date.today().isoformat()
+ today = datetime.date.today().isoformat()
urls = []
- for lang in locales_code:
+ for lang in locales.locales_code:
urls.append(f"{base_url}/{lang}")
urls.append(f"{base_url}/{lang}/download")
@@ -265,12 +116,12 @@ def sitemap_xml():
for loc in urls:
xml_lines.extend(
[
- f" ",
- f" {loc}",
- f" {today}",
- f" monthly",
- f" 0.8",
- f" ",
+ f" ",
+ f" {loc}",
+ f" {today}",
+ f" monthly",
+ f" 0.8",
+ f" ",
]
)
xml_lines.append("")
diff --git a/modules/__init__.py b/modules/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/modules/__init__.py
@@ -0,0 +1 @@
+
diff --git a/modules/autobuild.py b/modules/autobuild.py
new file mode 100644
index 0000000..56df4cf
--- /dev/null
+++ b/modules/autobuild.py
@@ -0,0 +1,89 @@
+import re
+import threading
+import time
+
+
+STATUS_URL = "https://builds.kolibrios.org/status.html"
+STATUS_SEC = 300 # refetch each 5 minutes
+
+autobuild_date = "DD.MM.YYYY"
+autobuild_vers = "0.0.0.0+0000-0000000"
+
+_started = False
+_updater_lock = threading.Lock()
+
+
+def _refresh_build_date_once():
+ global autobuild_date, autobuild_vers
+ try:
+ from urllib.request import Request, urlopen
+
+ req = Request(
+ STATUS_URL,
+ headers={
+ "User-Agent": "Mozilla/5.0",
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
+ },
+ )
+ with urlopen(req, timeout=10) as r:
+ html = r.read().decode(
+ r.headers.get_content_charset() or "utf-8", "replace"
+ )
+
+ rows = re.findall(r"(]*>.*?
)", html, flags=re.I | re.S)
+ if not rows:
+ return
+
+ last_commit_ver = None
+
+ for row in rows:
+ cls = re.search(r'class\s*=\s*"([^"]*)"', row, flags=re.I)
+ classes = cls.group(1).lower() if cls else ""
+
+ text = re.sub(r"<[^>]+>", " ", row)
+
+ if "commit" in classes:
+ mver = re.search(
+ r"\b(\d+\.\d+\.\d+\.\d+\+\d{3,8}-[0-9a-fA-F]{7,40})\b", row
+ )
+ if mver:
+ last_commit_ver = mver.group(1)
+
+ elif "success" in classes:
+ mts = re.search(
+ r"\b(\d{4})\.(\d{2})\.(\d{2})\s+\d{2}:\d{2}:\d{2}\b", text
+ )
+ if not mts:
+ mds = re.search(r"\b(\d{2})\.(\d{2})\.(\d{4})\b", text)
+ if mds:
+ autobuild_date = f"{mds.group(1)}.{mds.group(2)}.{mds.group(3)}"
+ else:
+ return
+ else:
+ y, mo, d = mts.groups()
+ autobuild_date = f"{d}.{mo}.{y}"
+
+ if last_commit_ver:
+ autobuild_vers = last_commit_ver
+ return
+
+ except Exception:
+ pass
+
+
+def _updater_loop():
+ while True:
+ _refresh_build_date_once()
+ time.sleep(STATUS_SEC)
+
+
+def ensure_started():
+ global _started
+ with _updater_lock:
+ if _started:
+ return
+ threading.Thread(target=_updater_loop, daemon=True).start()
+ _started = True
+
+
+_refresh_build_date_once()
diff --git a/modules/helpers.py b/modules/helpers.py
new file mode 100644
index 0000000..d6fa2b4
--- /dev/null
+++ b/modules/helpers.py
@@ -0,0 +1,23 @@
+from datetime import date
+
+from flask import redirect, render_template, request, url_for
+from htmlmin import minify as minify_html
+
+from modules import locales
+
+
+def get_best_lang():
+ return request.accept_languages.best_match(locales.locales_code) or "en"
+
+
+def render_localized_template(lang, template_name):
+ if lang not in locales.locales_code:
+ return redirect(url_for("index", lang=get_best_lang()))
+
+ return minify_html(
+ render_template(
+ template_name,
+ year=date.today().year,
+ ),
+ remove_empty_space=True,
+ )
diff --git a/modules/locales.py b/modules/locales.py
new file mode 100644
index 0000000..c9f698f
--- /dev/null
+++ b/modules/locales.py
@@ -0,0 +1,51 @@
+from os import path, listdir
+from configparser import ConfigParser
+import threading
+
+
+translations = {}
+locales_name = {}
+locales_code = ()
+
+_loaded = False
+_load_lock = threading.Lock()
+
+
+def load_all_locales():
+ new_translations = {}
+ locales_dir = "locales"
+
+ locales_code_default = ("en", "ru", "es")
+ locales_code_extra = []
+ new_locales_code = ()
+
+ for filename in listdir(locales_dir):
+ if filename.endswith(".ini"):
+ cp = ConfigParser()
+ lang = path.splitext(filename)[0]
+ with open(path.join(locales_dir, filename), encoding="utf-8") as f:
+ cp.read_file(f)
+
+ if lang not in locales_code_default:
+ locales_code_extra.append(lang)
+
+ new_translations[lang] = {
+ section: dict(cp[section]) for section in cp.sections()
+ }
+
+ new_locales_code = locales_code_default + tuple(sorted(locales_code_extra))
+ new_locales_name = {
+ locale_code: new_translations[locale_code]["title"]["language"]
+ for locale_code in new_locales_code
+ }
+
+ return new_translations, new_locales_name, new_locales_code
+
+
+def ensure_loaded():
+ global translations, locales_name, locales_code, _loaded
+ with _load_lock:
+ if _loaded:
+ return
+ translations, locales_name, locales_code = load_all_locales()
+ _loaded = True