feat/flask: split app into modules
feat/flask: !fix strange code to previous
This commit is contained in:
209
app.py
209
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"(<tr\b[^>]*>.*?</tr>)", 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("/<lang>")
|
||||
def index(lang):
|
||||
return render_localized_template(lang, "index.html")
|
||||
return helpers.render_localized_template(lang, "index.html")
|
||||
|
||||
|
||||
@app.route("/<lang>/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" <url>",
|
||||
f" <loc>{loc}</loc>",
|
||||
f" <lastmod>{today}</lastmod>",
|
||||
f" <changefreq>monthly</changefreq>",
|
||||
f" <priority>0.8</priority>",
|
||||
f" </url>",
|
||||
f" <url>",
|
||||
f" <loc>{loc}</loc>",
|
||||
f" <lastmod>{today}</lastmod>",
|
||||
f" <changefreq>monthly</changefreq>",
|
||||
f" <priority>0.8</priority>",
|
||||
f" </url>",
|
||||
]
|
||||
)
|
||||
xml_lines.append("</urlset>")
|
||||
|
||||
1
modules/__init__.py
Normal file
1
modules/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
89
modules/autobuild.py
Normal file
89
modules/autobuild.py
Normal file
@@ -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"(<tr\b[^>]*>.*?</tr>)", 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()
|
||||
23
modules/helpers.py
Normal file
23
modules/helpers.py
Normal file
@@ -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,
|
||||
)
|
||||
51
modules/locales.py
Normal file
51
modules/locales.py
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user