il2ks 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- il2ks/__init__.py +3 -0
- il2ks/__main__.py +7 -0
- il2ks/cli.py +537 -0
- il2ks/config.py +729 -0
- il2ks/core/__init__.py +1 -0
- il2ks/core/achievement_rules.py +83 -0
- il2ks/core/achievements.py +473 -0
- il2ks/core/catalog/__init__.py +1 -0
- il2ks/core/catalog/data/ammo.csv +59 -0
- il2ks/core/catalog/data/object_aliases.csv +2 -0
- il2ks/core/catalog/data/object_names_de.csv +436 -0
- il2ks/core/catalog/data/object_names_es.csv +439 -0
- il2ks/core/catalog/data/object_names_fr.csv +437 -0
- il2ks/core/catalog/data/object_names_pt-br.csv +439 -0
- il2ks/core/catalog/data/object_names_ru.csv +470 -0
- il2ks/core/catalog/data/objects.csv +490 -0
- il2ks/core/catalog/data/ordnance.csv +33 -0
- il2ks/core/catalog/data/payload_aliases.csv +13 -0
- il2ks/core/catalog/data/payloads.csv +416 -0
- il2ks/core/catalog/data/weapon_mod_names_de.csv +24 -0
- il2ks/core/catalog/data/weapon_mod_names_es.csv +24 -0
- il2ks/core/catalog/data/weapon_mod_names_fr.csv +24 -0
- il2ks/core/catalog/data/weapon_mod_names_pt-br.csv +24 -0
- il2ks/core/catalog/data/weapon_mod_names_ru.csv +24 -0
- il2ks/core/catalog/data/weapon_mods.csv +34 -0
- il2ks/core/catalog/loader.py +737 -0
- il2ks/core/killboard.py +13 -0
- il2ks/core/logparse/__init__.py +1 -0
- il2ks/core/logparse/events.py +364 -0
- il2ks/core/logparse/files.py +148 -0
- il2ks/core/logparse/parser.py +950 -0
- il2ks/core/logparse/tail.py +69 -0
- il2ks/core/ratings/__init__.py +1 -0
- il2ks/core/ratings/elo.py +191 -0
- il2ks/core/ratings/score.py +171 -0
- il2ks/core/replay/__init__.py +1 -0
- il2ks/core/replay/ammo.py +604 -0
- il2ks/core/replay/areas.py +66 -0
- il2ks/core/replay/attack.py +166 -0
- il2ks/core/replay/breakdown.py +251 -0
- il2ks/core/replay/config.py +71 -0
- il2ks/core/replay/credit.py +87 -0
- il2ks/core/replay/damage.py +99 -0
- il2ks/core/replay/fate.py +414 -0
- il2ks/core/replay/hits.py +108 -0
- il2ks/core/replay/judge.py +264 -0
- il2ks/core/replay/kills.py +236 -0
- il2ks/core/replay/live.py +142 -0
- il2ks/core/replay/model.py +262 -0
- il2ks/core/replay/pve.py +63 -0
- il2ks/core/replay/rams.py +93 -0
- il2ks/core/replay/resolve.py +223 -0
- il2ks/core/replay/result.py +311 -0
- il2ks/core/replay/state.py +523 -0
- il2ks/core/replay/toggles.py +19 -0
- il2ks/core/stat_marks.py +304 -0
- il2ks/core/streaks.py +177 -0
- il2ks/core/tours.py +143 -0
- il2ks/data/il2ks.example.toml +329 -0
- il2ks/db/__init__.py +1 -0
- il2ks/db/apps.py +8 -0
- il2ks/db/copy.py +30 -0
- il2ks/db/migrations/0001_initial.py +3023 -0
- il2ks/db/migrations/__init__.py +0 -0
- il2ks/db/models.py +2039 -0
- il2ks/db/reprocess_requests.py +43 -0
- il2ks/db/site.py +59 -0
- il2ks/db/validators.py +29 -0
- il2ks/devtools/__init__.py +1 -0
- il2ks/devtools/anonymize.py +73 -0
- il2ks/devtools/assets.py +565 -0
- il2ks/devtools/bailout_eval.py +217 -0
- il2ks/devtools/bench.py +221 -0
- il2ks/devtools/check.py +336 -0
- il2ks/devtools/dbdump.py +40 -0
- il2ks/devtools/migrations.py +154 -0
- il2ks/devtools/templates.py +60 -0
- il2ks/devtools/translations.py +354 -0
- il2ks/exitcodes.py +7 -0
- il2ks/ingest/__init__.py +1 -0
- il2ks/ingest/achievements.py +479 -0
- il2ks/ingest/activity.py +54 -0
- il2ks/ingest/aggregates.py +647 -0
- il2ks/ingest/aircraft_mods.py +166 -0
- il2ks/ingest/aircraft_stats.py +788 -0
- il2ks/ingest/archive.py +183 -0
- il2ks/ingest/batch.py +168 -0
- il2ks/ingest/builds.py +78 -0
- il2ks/ingest/counters.py +135 -0
- il2ks/ingest/dbutil.py +92 -0
- il2ks/ingest/discover.py +187 -0
- il2ks/ingest/flight_score.py +39 -0
- il2ks/ingest/identity.py +97 -0
- il2ks/ingest/live.py +468 -0
- il2ks/ingest/lock.py +185 -0
- il2ks/ingest/pairs.py +191 -0
- il2ks/ingest/persist.py +911 -0
- il2ks/ingest/rating_games.py +43 -0
- il2ks/ingest/ratings.py +186 -0
- il2ks/ingest/reprocess.py +303 -0
- il2ks/ingest/reprocess_requests.py +104 -0
- il2ks/ingest/rollup.py +125 -0
- il2ks/ingest/rule_store.py +167 -0
- il2ks/ingest/runner.py +417 -0
- il2ks/ingest/score_apply.py +44 -0
- il2ks/ingest/scoring.py +80 -0
- il2ks/ingest/stat_marks.py +181 -0
- il2ks/ingest/streaks.py +343 -0
- il2ks/ingest/timeutil.py +86 -0
- il2ks/ingest/tours.py +425 -0
- il2ks/ingest/type_board.py +158 -0
- il2ks/ingest/watch.py +153 -0
- il2ks/ingest/worker.py +45 -0
- il2ks/locale/de/LC_MESSAGES/django.mo +0 -0
- il2ks/locale/de/LC_MESSAGES/django.po +6781 -0
- il2ks/locale/es/LC_MESSAGES/django.mo +0 -0
- il2ks/locale/es/LC_MESSAGES/django.po +6767 -0
- il2ks/locale/fr/LC_MESSAGES/django.mo +0 -0
- il2ks/locale/fr/LC_MESSAGES/django.po +6798 -0
- il2ks/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
- il2ks/locale/pt_BR/LC_MESSAGES/django.po +6747 -0
- il2ks/locale/ru/LC_MESSAGES/django.mo +0 -0
- il2ks/locale/ru/LC_MESSAGES/django.po +6754 -0
- il2ks/logsetup.py +186 -0
- il2ks/ops/__init__.py +1 -0
- il2ks/ops/admin.py +108 -0
- il2ks/ops/backup.py +478 -0
- il2ks/ops/checks.py +552 -0
- il2ks/ops/commands.py +293 -0
- il2ks/ops/dbfile.py +58 -0
- il2ks/ops/detect.py +181 -0
- il2ks/ops/doctor.py +54 -0
- il2ks/ops/migrate.py +250 -0
- il2ks/ops/prompt.py +63 -0
- il2ks/ops/report.py +86 -0
- il2ks/ops/serving_checks.py +267 -0
- il2ks/ops/setup.py +521 -0
- il2ks/ops/template.py +133 -0
- il2ks/queries/__init__.py +1 -0
- il2ks/queries/achievements.py +85 -0
- il2ks/queries/activity.py +24 -0
- il2ks/queries/aircraft.py +525 -0
- il2ks/queries/ammo.py +261 -0
- il2ks/queries/boards.py +180 -0
- il2ks/queries/builds.py +54 -0
- il2ks/queries/leaderboards.py +393 -0
- il2ks/queries/live.py +114 -0
- il2ks/queries/missions.py +207 -0
- il2ks/queries/paging.py +15 -0
- il2ks/queries/players.py +243 -0
- il2ks/queries/sorties.py +217 -0
- il2ks/queries/sorting.py +76 -0
- il2ks/queries/stat_marks.py +29 -0
- il2ks/queries/tours.py +184 -0
- il2ks/rule_settings.py +219 -0
- il2ks/serving/__init__.py +2 -0
- il2ks/serving/_vendor/LICENSE-rjsmin-rcssmin.txt +201 -0
- il2ks/serving/_vendor/__init__.py +1 -0
- il2ks/serving/_vendor/rcssmin.py +436 -0
- il2ks/serving/_vendor/rjsmin.py +594 -0
- il2ks/serving/bootid.py +25 -0
- il2ks/serving/caddy.py +219 -0
- il2ks/serving/commands.py +653 -0
- il2ks/serving/custom.py +395 -0
- il2ks/serving/djsettings.py +130 -0
- il2ks/serving/procutil.py +257 -0
- il2ks/serving/secret.py +50 -0
- il2ks/serving/service.py +160 -0
- il2ks/serving/setup_token.py +195 -0
- il2ks/serving/storage.py +70 -0
- il2ks/serving/supervisor.py +269 -0
- il2ks/serving/templateversions.py +303 -0
- il2ks/serving/webserver.py +71 -0
- il2ks/settings.py +173 -0
- il2ks/urls.py +11 -0
- il2ks/web/__init__.py +1 -0
- il2ks/web/achievement_config.py +112 -0
- il2ks/web/admin.py +802 -0
- il2ks/web/admin_achievements.py +196 -0
- il2ks/web/admin_config.py +8 -0
- il2ks/web/admin_quips.py +170 -0
- il2ks/web/admin_rules.py +178 -0
- il2ks/web/admin_score.py +28 -0
- il2ks/web/admin_site.py +298 -0
- il2ks/web/apps.py +6 -0
- il2ks/web/branding_images.py +140 -0
- il2ks/web/caching.py +124 -0
- il2ks/web/chart_data.py +84 -0
- il2ks/web/charts.py +197 -0
- il2ks/web/column_hints.py +209 -0
- il2ks/web/columns.py +295 -0
- il2ks/web/context_processors.py +119 -0
- il2ks/web/display.py +324 -0
- il2ks/web/feature_image.py +307 -0
- il2ks/web/flavor.py +458 -0
- il2ks/web/fonts.py +146 -0
- il2ks/web/ground.py +58 -0
- il2ks/web/icons.py +140 -0
- il2ks/web/ingest_status.py +100 -0
- il2ks/web/logo.py +128 -0
- il2ks/web/medals.py +380 -0
- il2ks/web/media.py +59 -0
- il2ks/web/object_names.py +33 -0
- il2ks/web/pve.py +84 -0
- il2ks/web/quips.py +140 -0
- il2ks/web/setup_forms.py +154 -0
- il2ks/web/site_forms.py +473 -0
- il2ks/web/sortie_view.py +870 -0
- il2ks/web/static/il2ks/il2ks.js +209 -0
- il2ks/web/static/il2ks/img/README.md +130 -0
- il2ks/web/static/il2ks/img/aircraft/generic-jet.svg +1 -0
- il2ks/web/static/il2ks/img/aircraft/generic-prop.svg +1 -0
- il2ks/web/static/il2ks/img/aircraft/unknown.svg +1 -0
- il2ks/web/static/il2ks/img/brand/favicon.svg +1 -0
- il2ks/web/static/il2ks/img/brand/logo-mark.svg +1 -0
- il2ks/web/static/il2ks/img/coalition/blufor.svg +11 -0
- il2ks/web/static/il2ks/img/coalition/insignia/kpaf.svg +1 -0
- il2ks/web/static/il2ks/img/coalition/insignia/plaaf.svg +1 -0
- il2ks/web/static/il2ks/img/coalition/insignia/rokaf.svg +1 -0
- il2ks/web/static/il2ks/img/coalition/insignia/un.svg +1 -0
- il2ks/web/static/il2ks/img/coalition/insignia/usaf.svg +1 -0
- il2ks/web/static/il2ks/img/coalition/insignia/vvs.svg +1 -0
- il2ks/web/static/il2ks/img/coalition/redfor.svg +11 -0
- il2ks/web/static/il2ks/img/event/assist.svg +14 -0
- il2ks/web/static/il2ks/img/event/bailout.svg +14 -0
- il2ks/web/static/il2ks/img/event/bomb-release.svg +13 -0
- il2ks/web/static/il2ks/img/event/damaged.svg +12 -0
- il2ks/web/static/il2ks/img/event/destroyed.svg +11 -0
- il2ks/web/static/il2ks/img/event/disconnect.svg +18 -0
- il2ks/web/static/il2ks/img/event/friendly-fire.svg +13 -0
- il2ks/web/static/il2ks/img/event/kill-air.svg +16 -0
- il2ks/web/static/il2ks/img/event/kill-ground.svg +13 -0
- il2ks/web/static/il2ks/img/event/landing.svg +12 -0
- il2ks/web/static/il2ks/img/event/ram.svg +18 -0
- il2ks/web/static/il2ks/img/event/rocket-salvo.svg +13 -0
- il2ks/web/static/il2ks/img/event/sortie-end.svg +13 -0
- il2ks/web/static/il2ks/img/event/spawn.svg +13 -0
- il2ks/web/static/il2ks/img/event/takeoff.svg +12 -0
- il2ks/web/static/il2ks/img/flag/br.svg +45 -0
- il2ks/web/static/il2ks/img/flag/de.svg +5 -0
- il2ks/web/static/il2ks/img/flag/es.svg +4 -0
- il2ks/web/static/il2ks/img/flag/fr.svg +5 -0
- il2ks/web/static/il2ks/img/flag/ru.svg +5 -0
- il2ks/web/static/il2ks/img/flag/us.svg +9 -0
- il2ks/web/static/il2ks/img/ground/aaa.svg +1 -0
- il2ks/web/static/il2ks/img/ground/artillery.svg +1 -0
- il2ks/web/static/il2ks/img/ground/building.svg +18 -0
- il2ks/web/static/il2ks/img/ground/other-static.svg +19 -0
- il2ks/web/static/il2ks/img/ground/parked-aircraft.svg +11 -0
- il2ks/web/static/il2ks/img/ground/ship.svg +14 -0
- il2ks/web/static/il2ks/img/ground/tank.svg +13 -0
- il2ks/web/static/il2ks/img/ground/train.svg +17 -0
- il2ks/web/static/il2ks/img/ground/vehicle.svg +13 -0
- il2ks/web/static/il2ks/img/illustration/404.svg +1 -0
- il2ks/web/static/il2ks/img/illustration/500.svg +1 -0
- il2ks/web/static/il2ks/img/illustration/empty.svg +1 -0
- il2ks/web/static/il2ks/img/medal/ace-in-a-day.svg +19 -0
- il2ks/web/static/il2ks/img/medal/career-kills.svg +16 -0
- il2ks/web/static/il2ks/img/medal/damaged-landing.svg +15 -0
- il2ks/web/static/il2ks/img/medal/elo-peak.svg +12 -0
- il2ks/web/static/il2ks/img/medal/first-blood.svg +11 -0
- il2ks/web/static/il2ks/img/medal/flight-hours.svg +12 -0
- il2ks/web/static/il2ks/img/medal/frequent-flyer.svg +12 -0
- il2ks/web/static/il2ks/img/medal/ground-score.svg +14 -0
- il2ks/web/static/il2ks/img/medal/ground-sortie.svg +11 -0
- il2ks/web/static/il2ks/img/medal/landing-streak.svg +12 -0
- il2ks/web/static/il2ks/img/medal/life-kills.svg +14 -0
- il2ks/web/static/il2ks/img/medal/multi-kill.svg +12 -0
- il2ks/web/static/il2ks/img/medal/ram.svg +18 -0
- il2ks/web/static/il2ks/img/medal/regular.svg +21 -0
- il2ks/web/static/il2ks/img/medal/shame-crashed.svg +13 -0
- il2ks/web/static/il2ks/img/medal/shame-friendly.svg +14 -0
- il2ks/web/static/il2ks/img/medal/shame-strafed.svg +11 -0
- il2ks/web/static/il2ks/img/medal/shame-taxi.svg +15 -0
- il2ks/web/static/il2ks/img/medal/sortie-kills.svg +15 -0
- il2ks/web/static/il2ks/img/medal/strike-hunter.svg +12 -0
- il2ks/web/static/il2ks/img/medal/survivor.svg +12 -0
- il2ks/web/static/il2ks/img/medal/tank-buster.svg +13 -0
- il2ks/web/static/il2ks/img/medal/tours-in-a-row.svg +12 -0
- il2ks/web/static/il2ks/img/medal/type-veteran.svg +13 -0
- il2ks/web/static/il2ks/img/medal/types-flown.svg +13 -0
- il2ks/web/static/il2ks/img/medal/types-with-kills.svg +16 -0
- il2ks/web/static/il2ks/img/nav/book.svg +15 -0
- il2ks/web/static/il2ks/img/nav/discord.svg +14 -0
- il2ks/web/static/il2ks/img/nav/forum.svg +12 -0
- il2ks/web/static/il2ks/img/nav/github.svg +11 -0
- il2ks/web/static/il2ks/img/nav/link.svg +13 -0
- il2ks/web/static/il2ks/img/nav/mail.svg +12 -0
- il2ks/web/static/il2ks/img/nav/patreon.svg +11 -0
- il2ks/web/static/il2ks/img/nav/steam.svg +12 -0
- il2ks/web/static/il2ks/img/nav/telegram.svg +11 -0
- il2ks/web/static/il2ks/img/nav/twitch.svg +13 -0
- il2ks/web/static/il2ks/img/nav/youtube.svg +12 -0
- il2ks/web/static/il2ks/img/outcome/bailed-out.svg +14 -0
- il2ks/web/static/il2ks/img/outcome/captured.svg +18 -0
- il2ks/web/static/il2ks/img/outcome/crashed.svg +11 -0
- il2ks/web/static/il2ks/img/outcome/dead.svg +11 -0
- il2ks/web/static/il2ks/img/outcome/disconnected.svg +18 -0
- il2ks/web/static/il2ks/img/outcome/ditched.svg +13 -0
- il2ks/web/static/il2ks/img/outcome/exited-on-ground.svg +13 -0
- il2ks/web/static/il2ks/img/outcome/in-flight.svg +12 -0
- il2ks/web/static/il2ks/img/outcome/landed.svg +12 -0
- il2ks/web/static/il2ks/img/outcome/mission-ended.svg +13 -0
- il2ks/web/static/il2ks/img/outcome/not-taken-off.svg +12 -0
- il2ks/web/static/il2ks/img/outcome/shot-down.svg +12 -0
- il2ks/web/static/il2ks/img/outcome/unknown.svg +13 -0
- il2ks/web/static/il2ks/img/outcome/wounded.svg +15 -0
- il2ks/web/static/il2ks/img/pattern/camo.svg +18 -0
- il2ks/web/static/il2ks/img/role/air-superiority.svg +14 -0
- il2ks/web/static/il2ks/img/role/attack.svg +13 -0
- il2ks/web/static/il2ks/img/stat/accuracy.svg +13 -0
- il2ks/web/static/il2ks/img/stat/air-kills.svg +16 -0
- il2ks/web/static/il2ks/img/stat/assists.svg +14 -0
- il2ks/web/static/il2ks/img/stat/bailouts.svg +14 -0
- il2ks/web/static/il2ks/img/stat/captures.svg +18 -0
- il2ks/web/static/il2ks/img/stat/deaths.svg +15 -0
- il2ks/web/static/il2ks/img/stat/elo-jet.svg +12 -0
- il2ks/web/static/il2ks/img/stat/elo-prop.svg +15 -0
- il2ks/web/static/il2ks/img/stat/flight-time.svg +12 -0
- il2ks/web/static/il2ks/img/stat/friendly-fire.svg +15 -0
- il2ks/web/static/il2ks/img/stat/ground-hour.svg +13 -0
- il2ks/web/static/il2ks/img/stat/ground-kills.svg +13 -0
- il2ks/web/static/il2ks/img/stat/interception.svg +14 -0
- il2ks/web/static/il2ks/img/stat/planes-lost.svg +11 -0
- il2ks/web/static/il2ks/img/stat/play-time.svg +14 -0
- il2ks/web/static/il2ks/img/stat/sorties.svg +12 -0
- il2ks/web/static/il2ks/img/stat/strafed.svg +15 -0
- il2ks/web/static/il2ks/img/stat/taxi-accidents.svg +16 -0
- il2ks/web/static/il2ks/leaderboards.css +26 -0
- il2ks/web/static/il2ks/localtime.js +68 -0
- il2ks/web/static/il2ks/missions.css +65 -0
- il2ks/web/static/il2ks/players.css +129 -0
- il2ks/web/static/il2ks/setup.css +24 -0
- il2ks/web/static/il2ks/setup.js +15 -0
- il2ks/web/static/il2ks/site.css +769 -0
- il2ks/web/static/il2ks/sortie.js +15 -0
- il2ks/web/static/il2ks/sorties.css +83 -0
- il2ks/web/static/il2ks/theme-init.js +15 -0
- il2ks/web/static/il2ks/vendor/BarlowCondensed-600.woff2 +0 -0
- il2ks/web/static/il2ks/vendor/BarlowCondensed-700.woff2 +0 -0
- il2ks/web/static/il2ks/vendor/LICENSE-barlow-condensed-OFL.txt +93 -0
- il2ks/web/static/il2ks/vendor/LICENSE-htmx.txt +13 -0
- il2ks/web/static/il2ks/vendor/LICENSE-pico.md +21 -0
- il2ks/web/static/il2ks/vendor/README.md +18 -0
- il2ks/web/static/il2ks/vendor/htmx.min.js +1 -0
- il2ks/web/static/il2ks/vendor/pico.min.css +4 -0
- il2ks/web/static/il2ks_admin/ingest_status.css +15 -0
- il2ks/web/static/il2ks_admin/override_warning.css +12 -0
- il2ks/web/static/il2ks_admin/theme_editor.css +18 -0
- il2ks/web/static/il2ks_admin/theme_editor.js +36 -0
- il2ks/web/svg_symbol.py +128 -0
- il2ks/web/template_versions.json +569 -0
- il2ks/web/templates/404.html +17 -0
- il2ks/web/templates/500.html +27 -0
- il2ks/web/templates/admin/_rule_fields.html +23 -0
- il2ks/web/templates/admin/base_site.html +22 -0
- il2ks/web/templates/admin/il2ks_achievements.html +74 -0
- il2ks/web/templates/admin/il2ks_db/tour/change_list.html +15 -0
- il2ks/web/templates/admin/il2ks_index.html +55 -0
- il2ks/web/templates/admin/il2ks_ingest_status.html +174 -0
- il2ks/web/templates/admin/il2ks_leaderboards.html +21 -0
- il2ks/web/templates/admin/il2ks_quips.html +74 -0
- il2ks/web/templates/admin/il2ks_reprocess_confirm.html +23 -0
- il2ks/web/templates/admin/il2ks_rules.html +21 -0
- il2ks/web/templates/admin/il2ks_score.html +40 -0
- il2ks/web/templates/admin/il2ks_tours.html +41 -0
- il2ks/web/templates/il2ks/achievements/_award.html +5 -0
- il2ks/web/templates/il2ks/achievements/_medal.html +12 -0
- il2ks/web/templates/il2ks/achievements/_ribbon.html +12 -0
- il2ks/web/templates/il2ks/achievements/holders.html +35 -0
- il2ks/web/templates/il2ks/achievements/home_feed.html +24 -0
- il2ks/web/templates/il2ks/achievements/overview.html +38 -0
- il2ks/web/templates/il2ks/aircraft/detail.html +265 -0
- il2ks/web/templates/il2ks/aircraft/list.html +65 -0
- il2ks/web/templates/il2ks/aircraft/parts/ammo_mix_row.html +10 -0
- il2ks/web/templates/il2ks/aircraft/top_elo.html +36 -0
- il2ks/web/templates/il2ks/aircraft/top_ground.html +36 -0
- il2ks/web/templates/il2ks/base.html +100 -0
- il2ks/web/templates/il2ks/components/accordion.html +8 -0
- il2ks/web/templates/il2ks/components/badge.html +6 -0
- il2ks/web/templates/il2ks/components/bar_chart.html +47 -0
- il2ks/web/templates/il2ks/components/breadcrumbs.html +9 -0
- il2ks/web/templates/il2ks/components/col_hint.html +6 -0
- il2ks/web/templates/il2ks/components/col_th.html +10 -0
- il2ks/web/templates/il2ks/components/columns_picker.html +17 -0
- il2ks/web/templates/il2ks/components/dropdown.html +10 -0
- il2ks/web/templates/il2ks/components/empty_row.html +6 -0
- il2ks/web/templates/il2ks/components/filter_bar.html +20 -0
- il2ks/web/templates/il2ks/components/filter_select.html +10 -0
- il2ks/web/templates/il2ks/components/filter_text.html +8 -0
- il2ks/web/templates/il2ks/components/kv_list.html +7 -0
- il2ks/web/templates/il2ks/components/language_menu.html +11 -0
- il2ks/web/templates/il2ks/components/mod_filter.html +12 -0
- il2ks/web/templates/il2ks/components/notice.html +9 -0
- il2ks/web/templates/il2ks/components/online_now.html +13 -0
- il2ks/web/templates/il2ks/components/online_now_body.html +47 -0
- il2ks/web/templates/il2ks/components/pagination.html +16 -0
- il2ks/web/templates/il2ks/components/pilot_fate.html +6 -0
- il2ks/web/templates/il2ks/components/results_region.html +9 -0
- il2ks/web/templates/il2ks/components/role_toggle.html +10 -0
- il2ks/web/templates/il2ks/components/sort_th.html +10 -0
- il2ks/web/templates/il2ks/components/stat_mark.html +9 -0
- il2ks/web/templates/il2ks/components/stat_mark_note.html +5 -0
- il2ks/web/templates/il2ks/components/stat_tile.html +12 -0
- il2ks/web/templates/il2ks/components/tour_absent.html +11 -0
- il2ks/web/templates/il2ks/components/tour_filter.html +13 -0
- il2ks/web/templates/il2ks/components/tour_next.html +7 -0
- il2ks/web/templates/il2ks/components/tour_select.html +23 -0
- il2ks/web/templates/il2ks/home.html +109 -0
- il2ks/web/templates/il2ks/home_activity.html +10 -0
- il2ks/web/templates/il2ks/home_feature.html +17 -0
- il2ks/web/templates/il2ks/leaderboards/home_block.html +38 -0
- il2ks/web/templates/il2ks/leaderboards/ironman_running.html +26 -0
- il2ks/web/templates/il2ks/leaderboards/list.html +65 -0
- il2ks/web/templates/il2ks/leaderboards/table.html +105 -0
- il2ks/web/templates/il2ks/missions/_player.html +7 -0
- il2ks/web/templates/il2ks/missions/_rows.html +20 -0
- il2ks/web/templates/il2ks/missions/detail.html +136 -0
- il2ks/web/templates/il2ks/missions/list.html +44 -0
- il2ks/web/templates/il2ks/players/_killboard_rows.html +10 -0
- il2ks/web/templates/il2ks/players/_type_killboard.html +40 -0
- il2ks/web/templates/il2ks/players/achievements.html +40 -0
- il2ks/web/templates/il2ks/players/detail.html +59 -0
- il2ks/web/templates/il2ks/players/detail_achievements.html +20 -0
- il2ks/web/templates/il2ks/players/detail_air_section.html +31 -0
- il2ks/web/templates/il2ks/players/detail_air_split.html +8 -0
- il2ks/web/templates/il2ks/players/detail_air_tiles.html +14 -0
- il2ks/web/templates/il2ks/players/detail_aircraft.html +44 -0
- il2ks/web/templates/il2ks/players/detail_aircraft_build.html +16 -0
- il2ks/web/templates/il2ks/players/detail_charts.html +12 -0
- il2ks/web/templates/il2ks/players/detail_ground.html +29 -0
- il2ks/web/templates/il2ks/players/detail_ground_scores.html +13 -0
- il2ks/web/templates/il2ks/players/detail_ground_section.html +17 -0
- il2ks/web/templates/il2ks/players/detail_ground_tiles.html +17 -0
- il2ks/web/templates/il2ks/players/detail_header.html +26 -0
- il2ks/web/templates/il2ks/players/detail_killboard.html +34 -0
- il2ks/web/templates/il2ks/players/detail_nav.html +13 -0
- il2ks/web/templates/il2ks/players/detail_overall.html +15 -0
- il2ks/web/templates/il2ks/players/detail_pve.html +50 -0
- il2ks/web/templates/il2ks/players/detail_ratios.html +16 -0
- il2ks/web/templates/il2ks/players/detail_recent.html +40 -0
- il2ks/web/templates/il2ks/players/detail_scores.html +11 -0
- il2ks/web/templates/il2ks/players/detail_shame.html +17 -0
- il2ks/web/templates/il2ks/players/detail_star_tile.html +13 -0
- il2ks/web/templates/il2ks/players/detail_streaks.html +26 -0
- il2ks/web/templates/il2ks/players/detail_tiles.html +35 -0
- il2ks/web/templates/il2ks/players/detail_totals.html +16 -0
- il2ks/web/templates/il2ks/players/detail_type_killboard.html +13 -0
- il2ks/web/templates/il2ks/players/killboard.html +49 -0
- il2ks/web/templates/il2ks/players/search.html +25 -0
- il2ks/web/templates/il2ks/players/search_table.html +36 -0
- il2ks/web/templates/il2ks/players/streak_runs.html +57 -0
- il2ks/web/templates/il2ks/players/streaks.html +51 -0
- il2ks/web/templates/il2ks/setup.html +136 -0
- il2ks/web/templates/il2ks/setup_done.html +48 -0
- il2ks/web/templates/il2ks/setup_gate.html +32 -0
- il2ks/web/templates/il2ks/sorties/all.html +50 -0
- il2ks/web/templates/il2ks/sorties/detail.html +32 -0
- il2ks/web/templates/il2ks/sorties/list.html +54 -0
- il2ks/web/templates/il2ks/sorties/parts/aircraft_cell.html +5 -0
- il2ks/web/templates/il2ks/sorties/parts/all_row.html +16 -0
- il2ks/web/templates/il2ks/sorties/parts/ammo.html +72 -0
- il2ks/web/templates/il2ks/sorties/parts/damage.html +20 -0
- il2ks/web/templates/il2ks/sorties/parts/damage_table.html +28 -0
- il2ks/web/templates/il2ks/sorties/parts/header.html +44 -0
- il2ks/web/templates/il2ks/sorties/parts/kill_table.html +17 -0
- il2ks/web/templates/il2ks/sorties/parts/killed_by.html +31 -0
- il2ks/web/templates/il2ks/sorties/parts/kills.html +51 -0
- il2ks/web/templates/il2ks/sorties/parts/list_row.html +18 -0
- il2ks/web/templates/il2ks/sorties/parts/medals.html +16 -0
- il2ks/web/templates/il2ks/sorties/parts/meta.html +15 -0
- il2ks/web/templates/il2ks/sorties/parts/notices.html +20 -0
- il2ks/web/templates/il2ks/sorties/parts/summary.html +39 -0
- il2ks/web/templates/il2ks/sorties/parts/timeline.html +11 -0
- il2ks/web/templates/il2ks/sorties/parts/timeline_table.html +30 -0
- il2ks/web/templates/il2ks/sorties/parts/who.html +9 -0
- il2ks/web/templates/il2ks/styleguide.html +251 -0
- il2ks/web/templates/il2ks_admin/theme_editor.html +22 -0
- il2ks/web/templatetags/il2ks.py +884 -0
- il2ks/web/templatetags/il2ks_achievements.py +85 -0
- il2ks/web/templatetags/il2ks_boards.py +71 -0
- il2ks/web/templatetags/il2ks_live.py +39 -0
- il2ks/web/templatetags/il2ks_sorties.py +25 -0
- il2ks/web/theme.py +384 -0
- il2ks/web/throttle.py +76 -0
- il2ks/web/urls.py +48 -0
- il2ks/web/views/__init__.py +1 -0
- il2ks/web/views/achievements.py +161 -0
- il2ks/web/views/aircraft.py +357 -0
- il2ks/web/views/boards.py +123 -0
- il2ks/web/views/language.py +37 -0
- il2ks/web/views/leaderboards.py +168 -0
- il2ks/web/views/live.py +34 -0
- il2ks/web/views/missions.py +159 -0
- il2ks/web/views/players.py +162 -0
- il2ks/web/views/setup.py +252 -0
- il2ks/web/views/sorties.py +240 -0
- il2ks/web/views/sprite.py +25 -0
- il2ks/web/views/styleguide.py +152 -0
- il2ks/wsgi.py +15 -0
- il2ks-0.1.0.dist-info/METADATA +119 -0
- il2ks-0.1.0.dist-info/RECORD +506 -0
- il2ks-0.1.0.dist-info/WHEEL +4 -0
- il2ks-0.1.0.dist-info/entry_points.txt +2 -0
- il2ks-0.1.0.dist-info/licenses/LICENSE +21 -0
- il2ks-0.1.0.dist-info/licenses/NOTICE +93 -0
il2ks/__init__.py
ADDED
il2ks/__main__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""`python -m il2ks ...`: same as the `il2ks` command. `il2ks run` starts its child processes this way, because it
|
|
2
|
+
needs no PATH lookup and always means the Python environment il2ks is installed in."""
|
|
3
|
+
|
|
4
|
+
from il2ks.cli import main
|
|
5
|
+
|
|
6
|
+
if __name__ == "__main__": # the guard matters: web-server workers re-import the main module when they start
|
|
7
|
+
raise SystemExit(main())
|
il2ks/cli.py
ADDED
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
"""The `il2ks` command (FR-OPS-1).
|
|
2
|
+
|
|
3
|
+
Exit codes (`il2ks.exitcodes`): 0 = done, 1 = done but some missions failed, 2 = usage or configuration error or a
|
|
4
|
+
refused action, 3 = another writer holds the lock (FR-ING-20). `il2ks doctor` reports its verdict the same way:
|
|
5
|
+
0 = all OK, 1 = warnings only, 2 = at least one error.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import contextlib
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import sys
|
|
16
|
+
from collections.abc import Callable, Sequence
|
|
17
|
+
from datetime import date
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from il2ks import __version__
|
|
21
|
+
from il2ks.config import Config, ConfigError, load_config
|
|
22
|
+
from il2ks.exitcodes import EXIT_FAILED, EXIT_LOCKED, EXIT_OK, EXIT_USAGE
|
|
23
|
+
from il2ks.ops.migrate import migrate_if_needed
|
|
24
|
+
from il2ks.ops.setup import HTTPS_MODES
|
|
25
|
+
from il2ks.serving import commands as serving_commands
|
|
26
|
+
from il2ks.serving import procutil
|
|
27
|
+
|
|
28
|
+
PLANNED: dict[str, tuple[str, str]] = {}
|
|
29
|
+
"""Subcommands that exist only as stubs: name -> (requirement that plans it, one-line description)."""
|
|
30
|
+
|
|
31
|
+
OPS_COMMANDS = frozenset({"setup", "createadmin", "doctor", "backup", "restore"}) # handlers: il2ks.ops.commands
|
|
32
|
+
|
|
33
|
+
log = logging.getLogger("il2ks.cli")
|
|
34
|
+
_DATE_RE = re.compile(r"\d{4}-\d{2}-\d{2}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _django_setup() -> None:
|
|
38
|
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "il2ks.settings")
|
|
39
|
+
import django
|
|
40
|
+
|
|
41
|
+
django.setup()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _setup_pending() -> bool:
|
|
45
|
+
"""No admin account yet (so `web` creates the setup token); a database problem is not the setup page's business."""
|
|
46
|
+
from django.db import DatabaseError
|
|
47
|
+
|
|
48
|
+
from il2ks.ops import admin
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
return not admin.admin_exists()
|
|
52
|
+
except DatabaseError:
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _iso_date(text: str) -> date:
|
|
57
|
+
"""argparse type for `YYYY-MM-DD` (strictly: not `20260401` or `2026-W14-3`)."""
|
|
58
|
+
try:
|
|
59
|
+
if _DATE_RE.fullmatch(text) is None:
|
|
60
|
+
raise ValueError(text)
|
|
61
|
+
return date.fromisoformat(text)
|
|
62
|
+
except ValueError:
|
|
63
|
+
raise argparse.ArgumentTypeError(f"{text!r} is not a date, expected YYYY-MM-DD") from None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
type SubParsers = argparse._SubParsersAction[argparse.ArgumentParser] # pyright: ignore[reportPrivateUsage]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _add_ops_parsers(sub: SubParsers) -> None:
|
|
70
|
+
"""setup, createadmin, doctor, backup, restore (FR-OPS-1, FR-OPS-6); handlers in `il2ks.ops.commands`."""
|
|
71
|
+
env_note = "Every answer can also come from an environment variable; flags win."
|
|
72
|
+
setup = sub.add_parser(
|
|
73
|
+
"setup",
|
|
74
|
+
help="first-time setup: asks a few questions, writes il2ks.toml, creates the database and the admin",
|
|
75
|
+
description=(
|
|
76
|
+
"First-time setup. Asks for the data folder, DServer's text log folder, the server's time zone and the "
|
|
77
|
+
"website address, writes il2ks.toml (the example file with your answers), creates the data folder and "
|
|
78
|
+
"database and the admin account. Refuses to overwrite an existing il2ks.toml unless --force (the old "
|
|
79
|
+
"file is then saved next to it). Without a terminal, or with --non-interactive, nothing is asked. "
|
|
80
|
+
+ env_note
|
|
81
|
+
),
|
|
82
|
+
)
|
|
83
|
+
setup.add_argument("--data-dir", help="data folder [IL2KS_DATA_DIR]; default ./.il2ks-data")
|
|
84
|
+
setup.add_argument("--logs-dir", help="DServer's text log folder [IL2KS_LOGS_DIR]")
|
|
85
|
+
setup.add_argument(
|
|
86
|
+
"--timezone", help="IANA name of the server's time zone, e.g. Europe/Berlin [IL2KS_SERVER_TIMEZONE]"
|
|
87
|
+
)
|
|
88
|
+
setup.add_argument("--domain", help="the website's domain name [IL2KS_HTTPS_DOMAIN]")
|
|
89
|
+
setup.add_argument(
|
|
90
|
+
"--https", choices=HTTPS_MODES, help="caddy = bundled HTTPS proxy, external = your own [IL2KS_HTTPS_MODE]"
|
|
91
|
+
)
|
|
92
|
+
setup.add_argument("--email", help="e-mail for certificate notices (caddy mode) [IL2KS_HTTPS_EMAIL]")
|
|
93
|
+
setup.add_argument("--admin-username", help="admin account name [IL2KS_ADMIN_USERNAME]; default admin")
|
|
94
|
+
setup.add_argument(
|
|
95
|
+
"--admin-password-file", type=Path, help="file holding the admin password [IL2KS_ADMIN_PASSWORD]"
|
|
96
|
+
)
|
|
97
|
+
setup.add_argument("--no-admin", action="store_true", help="do not create an admin account")
|
|
98
|
+
setup.add_argument("--non-interactive", action="store_true", help="never ask; use flags, environment and defaults")
|
|
99
|
+
setup.add_argument("--force", action="store_true", help="replace an existing il2ks.toml (a copy is kept)")
|
|
100
|
+
|
|
101
|
+
admin_parser = sub.add_parser(
|
|
102
|
+
"createadmin",
|
|
103
|
+
help="create an admin account, or reset the password of an existing one",
|
|
104
|
+
description=(
|
|
105
|
+
"Create an admin (superuser) account, or reset an existing account's password and make it an admin. "
|
|
106
|
+
"In a terminal it asks for the password twice. For scripts give --username and the password in the "
|
|
107
|
+
"IL2KS_ADMIN_PASSWORD environment variable or --password-file."
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
admin_parser.add_argument("--username", help="account name (asked for in a terminal, default admin)")
|
|
111
|
+
admin_parser.add_argument("--password-file", type=Path, help="file holding the password (first line break dropped)")
|
|
112
|
+
admin_parser.add_argument("--email", help="e-mail address of the account (optional)")
|
|
113
|
+
admin_parser.add_argument(
|
|
114
|
+
"--if-none",
|
|
115
|
+
action="store_true",
|
|
116
|
+
help="do nothing when an admin account already exists (for first-start scripts such as the Docker image)",
|
|
117
|
+
)
|
|
118
|
+
admin_parser.add_argument(
|
|
119
|
+
"--wait", type=float, default=30.0, metavar="SECONDS", help="how long to wait for another writer"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
sub.add_parser(
|
|
123
|
+
"doctor",
|
|
124
|
+
help="check the setup and say what to fix",
|
|
125
|
+
description=(
|
|
126
|
+
"Checks the configuration, data folder, database, log folder, time zone, server ID, disk space, ingestion "
|
|
127
|
+
"history and backups. Exit code: 0 = all OK, 1 = warnings only, 2 = at least one error."
|
|
128
|
+
),
|
|
129
|
+
).add_argument("--json", action="store_true", help="machine-readable output")
|
|
130
|
+
|
|
131
|
+
sub.add_parser(
|
|
132
|
+
"backup",
|
|
133
|
+
help="write a backup zip of the database, config, custom/ and media/ to <data dir>/backups",
|
|
134
|
+
description=(
|
|
135
|
+
"Backs up what the mission logs cannot rebuild: a consistent copy of the SQLite database, il2ks.toml, "
|
|
136
|
+
"custom/, media/ and the server ID. Not the archived mission logs. Keeps the newest [backup] keep zips. "
|
|
137
|
+
"Backups are also made before database updates and, by 'il2ks watch', once a day."
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
restore = sub.add_parser(
|
|
141
|
+
"restore",
|
|
142
|
+
help="restore a backup zip (database, config, custom/, media/)",
|
|
143
|
+
description=(
|
|
144
|
+
"Restores a backup made by 'il2ks backup'. Backs up the current state first, refuses while another "
|
|
145
|
+
"il2ks writer (watch, ingest) or the stack (il2ks run) is running unless --force, and checks the result. "
|
|
146
|
+
"SQLite only."
|
|
147
|
+
),
|
|
148
|
+
)
|
|
149
|
+
restore.add_argument("zip", type=Path, help="the backup zip")
|
|
150
|
+
restore.add_argument("--yes", action="store_true", help="do not ask for confirmation")
|
|
151
|
+
restore.add_argument(
|
|
152
|
+
"--force", action="store_true", help="restore even though il2ks seems to be running (stop it first if you can)"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _add_translation_parsers(dev: SubParsers) -> None:
|
|
157
|
+
"""`il2ks dev translations ...` (TD-24, docs/translating.md): pure-Python gettext workflow."""
|
|
158
|
+
tr = dev.add_parser("translations", help="extract, merge, compile and check the translations (docs/translating.md)")
|
|
159
|
+
sub = tr.add_subparsers(dest="translations_command", required=True)
|
|
160
|
+
sub.add_parser("update", help="re-extract the strings, merge them into every language's .po, then compile")
|
|
161
|
+
sub.add_parser("compile", help="compile every .po into its .mo (after editing a .po by hand)")
|
|
162
|
+
sub.add_parser("check", help="change nothing; exit 1 when update/compile would change a file (CI, pre-commit)")
|
|
163
|
+
sub.add_parser("status", help="per language: strings, translated, still llm-draft (unreviewed), untranslated")
|
|
164
|
+
missing = sub.add_parser("missing", help="print a language's untranslated strings as JSON (input for a draft)")
|
|
165
|
+
missing.add_argument("language", help="ru, de, es, fr or pt-br")
|
|
166
|
+
imp = sub.add_parser("import", help="fill untranslated strings from a JSON file {msgid: msgstr}, marked llm-draft")
|
|
167
|
+
imp.add_argument("language", help="ru, de, es, fr or pt-br")
|
|
168
|
+
imp.add_argument("file", type=Path, help="JSON object: msgid -> msgstr (a list of forms for plural messages)")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _translations_command(ns: argparse.Namespace) -> int:
|
|
172
|
+
import json
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
from il2ks.devtools import translations
|
|
176
|
+
except ModuleNotFoundError as e: # Babel is a development dependency (not in a wheel installed to run a site)
|
|
177
|
+
if not (e.name or "").startswith("babel"):
|
|
178
|
+
raise # another import broke: show it, do not blame Babel
|
|
179
|
+
print(
|
|
180
|
+
"il2ks dev translations needs the development tools (Babel): run it in a checkout after `uv sync`",
|
|
181
|
+
file=sys.stderr,
|
|
182
|
+
)
|
|
183
|
+
return EXIT_USAGE
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
match ns.translations_command:
|
|
187
|
+
case "update":
|
|
188
|
+
for directory, count in translations.update().items():
|
|
189
|
+
print(f"{directory}: {count} strings")
|
|
190
|
+
translations.compile_all()
|
|
191
|
+
case "check":
|
|
192
|
+
problems = translations.check()
|
|
193
|
+
for problem in problems:
|
|
194
|
+
print(f" - {problem}")
|
|
195
|
+
print("translations are up to date" if not problems else "run `uv run il2ks dev translations update`")
|
|
196
|
+
return EXIT_FAILED if problems else EXIT_OK
|
|
197
|
+
case "compile":
|
|
198
|
+
for path in translations.compile_all():
|
|
199
|
+
print(f"wrote {path}")
|
|
200
|
+
case "status":
|
|
201
|
+
print(f"{'language':<9}{'strings':>8}{'translated':>11}{'llm-draft':>10}{'reviewed':>9}{'missing':>8}")
|
|
202
|
+
for row in translations.status():
|
|
203
|
+
print(
|
|
204
|
+
f"{row.language:<9}{row.total:>8}{row.translated:>11}{row.drafts:>10}"
|
|
205
|
+
f"{row.reviewed:>9}{row.untranslated:>8}"
|
|
206
|
+
)
|
|
207
|
+
case "missing":
|
|
208
|
+
text = json.dumps(translations.missing(ns.language), ensure_ascii=False, indent=2)
|
|
209
|
+
sys.stdout.buffer.write(text.encode("utf-8") + b"\n") # UTF-8 whatever the console code page is
|
|
210
|
+
case _: # "import"
|
|
211
|
+
filled = translations.import_drafts(ns.language, translations.load_drafts(ns.file))
|
|
212
|
+
print(
|
|
213
|
+
f"{filled} strings filled, marked {translations.DRAFT_MARK}; run `il2ks dev translations compile`"
|
|
214
|
+
)
|
|
215
|
+
except translations.TranslationError as exc:
|
|
216
|
+
print(f"il2ks dev translations: {exc}", file=sys.stderr)
|
|
217
|
+
return EXIT_USAGE
|
|
218
|
+
return EXIT_OK
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
222
|
+
parser = argparse.ArgumentParser(prog="il2ks", description="IL-2 Korea stats")
|
|
223
|
+
parser.add_argument("--config", type=Path, help="il2ks.toml to use (default: IL2KS_CONFIG, ./il2ks.toml, data dir)")
|
|
224
|
+
parser.add_argument("--version", action="version", version=f"il2ks {__version__}")
|
|
225
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
226
|
+
for name, (requirement, description) in PLANNED.items():
|
|
227
|
+
text = f"not implemented yet (planned: {requirement}): {description}"
|
|
228
|
+
sub.add_parser(name, help=text, description=text)
|
|
229
|
+
|
|
230
|
+
_add_ops_parsers(sub)
|
|
231
|
+
|
|
232
|
+
wait_help = "if another writer holds the lock, wait up to this many seconds instead of exiting"
|
|
233
|
+
ingest = sub.add_parser("ingest", help="process every complete mission once, then exit")
|
|
234
|
+
ingest.add_argument(
|
|
235
|
+
"--from",
|
|
236
|
+
dest="source",
|
|
237
|
+
type=Path,
|
|
238
|
+
help="import from this folder or file instead of the log folder (concatenated .txt/.txt.zip archives or parts)",
|
|
239
|
+
)
|
|
240
|
+
ingest.add_argument("--wait", type=float, metavar="SECONDS", help=wait_help)
|
|
241
|
+
|
|
242
|
+
sub.add_parser("watch", help="run ingest every N seconds until Ctrl+C")
|
|
243
|
+
|
|
244
|
+
reprocess = sub.add_parser(
|
|
245
|
+
"reprocess",
|
|
246
|
+
help="re-run missions from their archives, then rebuild aggregates (choose: --all, --mission, --since/--until)",
|
|
247
|
+
description=(
|
|
248
|
+
"Re-runs missions from their archives with the current rules, then rebuilds the aggregates. It can take a "
|
|
249
|
+
"long time on a big archive, so you must choose what to reprocess: --all for every mission, or narrow it "
|
|
250
|
+
"with --mission and/or --since/--until. Without any of these, nothing is done."
|
|
251
|
+
),
|
|
252
|
+
)
|
|
253
|
+
reprocess.set_defaults(reprocess_parser=reprocess)
|
|
254
|
+
reprocess.add_argument(
|
|
255
|
+
"--all", action="store_true", help="reprocess every mission (explicit, can take a long time)"
|
|
256
|
+
)
|
|
257
|
+
reprocess.add_argument("--mission", action="append", metavar="UID", help="only this mission (repeatable)")
|
|
258
|
+
span_note = "The date is the one in the mission UID, i.e. the server's local time (not UTC). Inclusive."
|
|
259
|
+
reprocess.add_argument(
|
|
260
|
+
"--since", type=_iso_date, metavar="YYYY-MM-DD", help=f"only missions on or after this date. {span_note}"
|
|
261
|
+
)
|
|
262
|
+
reprocess.add_argument(
|
|
263
|
+
"--until", type=_iso_date, metavar="YYYY-MM-DD", help=f"only missions on or before this date. {span_note}"
|
|
264
|
+
)
|
|
265
|
+
reprocess.add_argument("--workers", type=int, help="parse/replay worker processes (default: CPUs - 1)")
|
|
266
|
+
reprocess.add_argument("--wait", type=float, metavar="SECONDS", help=wait_help)
|
|
267
|
+
|
|
268
|
+
rebuild = sub.add_parser("rebuild-aggregates", help="recompute level-2 tables from level 1")
|
|
269
|
+
rebuild.add_argument(
|
|
270
|
+
"--retour",
|
|
271
|
+
action="store_true",
|
|
272
|
+
help="first move every mission to its tour under the [tours] config (after changing mode, start or timezone)",
|
|
273
|
+
)
|
|
274
|
+
rebuild.add_argument("--wait", type=float, metavar="SECONDS", help=wait_help)
|
|
275
|
+
|
|
276
|
+
sub.add_parser("manage", help="run a Django management command")
|
|
277
|
+
db = sub.add_parser("db", help="database tools").add_subparsers(dest="db_command", required=True)
|
|
278
|
+
db_copy = db.add_parser("copy", help="copy all tables between database aliases (TD-19)")
|
|
279
|
+
db_copy.add_argument("--from", dest="source", default="default")
|
|
280
|
+
db_copy.add_argument("--to", dest="target", required=True)
|
|
281
|
+
dev = sub.add_parser("dev", help="developer tools").add_subparsers(dest="dev_command", required=True)
|
|
282
|
+
anon = dev.add_parser("anonymize", help="anonymize a mission log for test fixtures")
|
|
283
|
+
anon.add_argument("source", type=Path, help="mission .txt or .txt.zip")
|
|
284
|
+
anon.add_argument("target", type=Path, help="output .txt.zip")
|
|
285
|
+
bump = dev.add_parser(
|
|
286
|
+
"bump-templates",
|
|
287
|
+
help="after editing built-in templates/CSS/JS: add version lines, raise versions, rewrite the registry",
|
|
288
|
+
)
|
|
289
|
+
bump.add_argument("--check", action="store_true", help="change nothing; exit 1 if something would change")
|
|
290
|
+
changes = dev.add_parser(
|
|
291
|
+
"template-changes", help="template versions that changed since a release tag (release notes)"
|
|
292
|
+
)
|
|
293
|
+
changes.add_argument("old_tag", help="a git tag or commit, like v0.2.0")
|
|
294
|
+
bench = dev.add_parser("bench-ingest", help="time an import of a log folder, split by phase (throw-away data dir)")
|
|
295
|
+
bench.add_argument("source", type=Path, help="folder with mission logs (.txt / .txt.zip)")
|
|
296
|
+
bench.add_argument("--limit", type=int, metavar="N", help="only the first N missions")
|
|
297
|
+
bench.add_argument("--profile", type=Path, metavar="FILE", help="write a cProfile dump of the whole run")
|
|
298
|
+
bench.add_argument("--data-dir", type=Path, metavar="DIR", help="keep the resulting data dir here (for dump-db)")
|
|
299
|
+
bench.add_argument("--cpu", action="store_true", help="time process CPU instead of wall clock (noisy machines)")
|
|
300
|
+
bench.add_argument("--per-mission", action="store_true", help="level 2 after every mission, never batched")
|
|
301
|
+
dump = dev.add_parser(
|
|
302
|
+
"dump-db", help="write every table of a data dir's database as sorted JSON lines, to compare runs"
|
|
303
|
+
)
|
|
304
|
+
dump.add_argument("data_dir", type=Path, help="a data dir with il2ks.sqlite3")
|
|
305
|
+
dump.add_argument("target", type=Path, help="output file (.jsonl)")
|
|
306
|
+
assets = dev.add_parser(
|
|
307
|
+
"assets", help="inventory of every image the site uses or ships (design_doc/15, the designer brief)"
|
|
308
|
+
)
|
|
309
|
+
assets.add_argument("--check", action="store_true", help="exit 1 when the brief, the code and static/ disagree")
|
|
310
|
+
assets.add_argument("--write", action="store_true", help="rewrite the generated inventory in design_doc/15")
|
|
311
|
+
_add_translation_parsers(dev)
|
|
312
|
+
chk = dev.add_parser(
|
|
313
|
+
"check",
|
|
314
|
+
help="everything to run before committing (lint, types, guards, tests); see CLAUDE.md. Default tier: quick",
|
|
315
|
+
)
|
|
316
|
+
tier = chk.add_mutually_exclusive_group()
|
|
317
|
+
tier.add_argument("--fast", action="store_true", help="static checks + guards only (~15 s; the Stop hook)")
|
|
318
|
+
tier.add_argument("--quick", action="store_true", help="fast + unit tests (the default; before every commit)")
|
|
319
|
+
tier.add_argument("--full", action="store_true", help="quick + integration tests (before merging / finishing)")
|
|
320
|
+
chk.add_argument(
|
|
321
|
+
"--fix", action="store_true", help="first run the auto-fixers: ruff --fix/format, bump-templates, translations"
|
|
322
|
+
)
|
|
323
|
+
chk.add_argument("--no-tests", action="store_true", help="skip every test step (CI lint job)")
|
|
324
|
+
chk.add_argument("--postgres", action="store_true", help="also run the whole suite on Postgres (compose.dev.yaml)")
|
|
325
|
+
chk.add_argument("--e2e", action="store_true", help="also run the Playwright browser tests")
|
|
326
|
+
chk.add_argument("--only", action="append", default=[], metavar="STEP", help="just this step (repeatable)")
|
|
327
|
+
chk.add_argument("--list", action="store_true", dest="list_steps", help="list the steps of the chosen tier, exit")
|
|
328
|
+
chk.add_argument("--if-changed", action="store_true", help="skip when nothing changed since the last passing run")
|
|
329
|
+
chk.add_argument("--verbose", action="store_true", help="show the whole output of failed steps")
|
|
330
|
+
dev.add_parser(
|
|
331
|
+
"check-migrations",
|
|
332
|
+
help="released migrations unchanged (vs origin/main or main), exactly one leaf, no duplicate numbers",
|
|
333
|
+
)
|
|
334
|
+
serving_commands.add_parsers(sub)
|
|
335
|
+
return parser
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
339
|
+
"""Run the command. Ctrl+C / SIGTERM at any point (also while starting up) is a clean stop, not a traceback."""
|
|
340
|
+
try:
|
|
341
|
+
return _main(argv)
|
|
342
|
+
except KeyboardInterrupt:
|
|
343
|
+
return EXIT_OK
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _main(argv: Sequence[str] | None) -> int:
|
|
347
|
+
args = list(sys.argv[1:] if argv is None else argv)
|
|
348
|
+
if args and args[0] == "manage":
|
|
349
|
+
# Pass everything after "manage" to Django (migrate, createsuperuser, ...).
|
|
350
|
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "il2ks.settings")
|
|
351
|
+
# A first `il2ks manage migrate` on a fresh machine: SQLite can't create the folder it lives in. Any problem
|
|
352
|
+
# here is left for Django to report in its own words.
|
|
353
|
+
with contextlib.suppress(ConfigError, OSError):
|
|
354
|
+
load_config(create_server_uid=False).data_dir.mkdir(parents=True, exist_ok=True)
|
|
355
|
+
from django.core.management import execute_from_command_line
|
|
356
|
+
|
|
357
|
+
execute_from_command_line(["il2ks manage", *args[1:]])
|
|
358
|
+
return EXIT_OK
|
|
359
|
+
|
|
360
|
+
ns = _build_parser().parse_args(args)
|
|
361
|
+
command: str = ns.command
|
|
362
|
+
if command in serving_commands.COMMANDS:
|
|
363
|
+
hooks = serving_commands.Hooks(
|
|
364
|
+
django_setup=_django_setup,
|
|
365
|
+
migrate=migrate_if_needed,
|
|
366
|
+
setup_pending=_setup_pending,
|
|
367
|
+
contain_children=procutil.kill_children_when_we_die,
|
|
368
|
+
)
|
|
369
|
+
return serving_commands.dispatch(ns, hooks)
|
|
370
|
+
if command == "watch":
|
|
371
|
+
procutil.terminate_as_keyboard_interrupt() # `il2ks run` stops its children with SIGTERM / Ctrl+Break
|
|
372
|
+
if command == "dev" and ns.dev_command == "anonymize":
|
|
373
|
+
from il2ks.devtools.anonymize import anonymize_file
|
|
374
|
+
|
|
375
|
+
anonymize_file(ns.source, ns.target)
|
|
376
|
+
return EXIT_OK
|
|
377
|
+
if command == "dev" and ns.dev_command == "bailout-eval":
|
|
378
|
+
from il2ks.devtools.bailout_eval import run as run_bailout_eval
|
|
379
|
+
|
|
380
|
+
return run_bailout_eval(ns.directory, list_disagreements=ns.list)
|
|
381
|
+
if command == "dev" and ns.dev_command == "bench-ingest":
|
|
382
|
+
from il2ks.devtools.bench import bench_ingest
|
|
383
|
+
|
|
384
|
+
return bench_ingest(
|
|
385
|
+
ns.source, limit=ns.limit, profile=ns.profile, data_dir=ns.data_dir, cpu=ns.cpu, per_mission=ns.per_mission
|
|
386
|
+
)
|
|
387
|
+
if command == "dev" and ns.dev_command == "dump-db":
|
|
388
|
+
from il2ks.devtools.dbdump import dump_db
|
|
389
|
+
|
|
390
|
+
return dump_db(ns.data_dir, ns.target)
|
|
391
|
+
if command == "dev" and ns.dev_command == "bump-templates":
|
|
392
|
+
from il2ks.devtools.templates import bump_templates
|
|
393
|
+
|
|
394
|
+
return bump_templates(check=ns.check)
|
|
395
|
+
if command == "dev" and ns.dev_command == "assets":
|
|
396
|
+
_django_setup()
|
|
397
|
+
from il2ks.devtools.assets import run as run_assets
|
|
398
|
+
|
|
399
|
+
return run_assets(check=ns.check, write=ns.write)
|
|
400
|
+
if command == "dev" and ns.dev_command == "template-changes":
|
|
401
|
+
from il2ks.devtools.templates import template_changes
|
|
402
|
+
|
|
403
|
+
return template_changes(ns.old_tag)
|
|
404
|
+
if command == "dev" and ns.dev_command == "check":
|
|
405
|
+
from il2ks.devtools.check import Tier, check
|
|
406
|
+
|
|
407
|
+
chosen: Tier = "full" if ns.full else "fast" if ns.fast else "quick"
|
|
408
|
+
return check(
|
|
409
|
+
chosen,
|
|
410
|
+
fix=ns.fix,
|
|
411
|
+
no_tests=ns.no_tests,
|
|
412
|
+
postgres=ns.postgres,
|
|
413
|
+
e2e=ns.e2e,
|
|
414
|
+
only=ns.only,
|
|
415
|
+
if_changed=ns.if_changed,
|
|
416
|
+
verbose=ns.verbose,
|
|
417
|
+
list_only=ns.list_steps,
|
|
418
|
+
)
|
|
419
|
+
if command == "dev" and ns.dev_command == "check-migrations":
|
|
420
|
+
from il2ks.devtools import migrations as dev_migrations
|
|
421
|
+
from il2ks.devtools.check import repo_root
|
|
422
|
+
|
|
423
|
+
return dev_migrations.check(repo_root())
|
|
424
|
+
if command == "dev" and ns.dev_command == "translations":
|
|
425
|
+
return _translations_command(ns)
|
|
426
|
+
if command == "db" and ns.db_command == "copy":
|
|
427
|
+
_django_setup()
|
|
428
|
+
from il2ks.db.copy import copy_all
|
|
429
|
+
|
|
430
|
+
counts = copy_all(ns.source, ns.target)
|
|
431
|
+
for table, n in counts.items():
|
|
432
|
+
print(f"{table}: {n}")
|
|
433
|
+
return EXIT_OK
|
|
434
|
+
if command in {"ingest", "watch", "reprocess", "rebuild-aggregates"}:
|
|
435
|
+
return _writer_command(command, ns)
|
|
436
|
+
if command in OPS_COMMANDS:
|
|
437
|
+
from il2ks.ops import commands
|
|
438
|
+
|
|
439
|
+
handlers: dict[str, Callable[[argparse.Namespace], int]] = {
|
|
440
|
+
"setup": commands.cmd_setup,
|
|
441
|
+
"createadmin": commands.cmd_createadmin,
|
|
442
|
+
"doctor": commands.cmd_doctor,
|
|
443
|
+
"backup": commands.cmd_backup,
|
|
444
|
+
"restore": commands.cmd_restore,
|
|
445
|
+
}
|
|
446
|
+
return handlers[command](ns)
|
|
447
|
+
print(f"il2ks {command}: not implemented yet (planned: {PLANNED[command][0]})", file=sys.stderr)
|
|
448
|
+
return EXIT_USAGE
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _writer_command(command: str, ns: argparse.Namespace) -> int:
|
|
452
|
+
"""Commands that write the DB: config, logging, writer lock, migrations, then the job."""
|
|
453
|
+
if ns.command == "reprocess" and not (ns.all or ns.mission or ns.since is not None or ns.until is not None):
|
|
454
|
+
ns.reprocess_parser.print_help(sys.stderr)
|
|
455
|
+
print(
|
|
456
|
+
"\nil2ks: choose what to reprocess: --all, --mission UID or --since/--until (nothing was reprocessed)",
|
|
457
|
+
file=sys.stderr,
|
|
458
|
+
)
|
|
459
|
+
return EXIT_USAGE
|
|
460
|
+
if ns.command == "reprocess" and ns.all and (ns.mission or ns.since is not None or ns.until is not None):
|
|
461
|
+
print("il2ks: --all cannot be combined with --mission, --since or --until", file=sys.stderr)
|
|
462
|
+
return EXIT_USAGE
|
|
463
|
+
if ns.command == "reprocess" and ns.since is not None and ns.until is not None and ns.since > ns.until:
|
|
464
|
+
print(f"il2ks: --since {ns.since} is after --until {ns.until}", file=sys.stderr)
|
|
465
|
+
return EXIT_USAGE
|
|
466
|
+
try:
|
|
467
|
+
cfg = load_config(ns.config)
|
|
468
|
+
except ConfigError as exc:
|
|
469
|
+
print(f"il2ks: configuration error: {exc}", file=sys.stderr)
|
|
470
|
+
return EXIT_USAGE
|
|
471
|
+
source: Path | None = getattr(ns, "source", None)
|
|
472
|
+
if command in {"ingest", "watch"} and source is None and cfg.logs.dir is None:
|
|
473
|
+
print("il2ks: logs.dir is not configured ([logs] dir in il2ks.toml, or IL2KS_LOGS_DIR)", file=sys.stderr)
|
|
474
|
+
return EXIT_USAGE
|
|
475
|
+
if source is not None and not source.exists():
|
|
476
|
+
print(f"il2ks: --from: {source} does not exist", file=sys.stderr)
|
|
477
|
+
return EXIT_USAGE
|
|
478
|
+
if command in {"ingest", "watch"} and source is None and cfg.logs.dir is not None and not cfg.logs.dir.is_dir():
|
|
479
|
+
print(f"il2ks: log folder {cfg.logs.dir} does not exist", file=sys.stderr)
|
|
480
|
+
return EXIT_USAGE
|
|
481
|
+
|
|
482
|
+
# settings.py reads the data dir from the environment; make it match the config before Django starts.
|
|
483
|
+
os.environ["IL2KS_DATA_DIR"] = str(cfg.data_dir)
|
|
484
|
+
cfg.data_dir.mkdir(parents=True, exist_ok=True)
|
|
485
|
+
from il2ks import logsetup
|
|
486
|
+
|
|
487
|
+
logsetup.configure_logging(
|
|
488
|
+
command, cfg.log_dir, cfg.log_level, server_uid=cfg.server_uid, keep_days=cfg.log_keep_days
|
|
489
|
+
)
|
|
490
|
+
_django_setup()
|
|
491
|
+
|
|
492
|
+
from il2ks.ingest.lock import LockBusyError
|
|
493
|
+
|
|
494
|
+
wait: float | None = getattr(ns, "wait", None)
|
|
495
|
+
try:
|
|
496
|
+
migrate_if_needed(cfg, command, wait) # FR-OPS-3, after a backup when the database has data (FR-OPS-6)
|
|
497
|
+
return _run_job(command, ns, cfg, source, wait)
|
|
498
|
+
except LockBusyError as exc:
|
|
499
|
+
print(f"il2ks {command}: {exc}", file=sys.stderr)
|
|
500
|
+
return EXIT_LOCKED
|
|
501
|
+
except KeyboardInterrupt:
|
|
502
|
+
log.info("stopped")
|
|
503
|
+
return EXIT_OK
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def _run_job(command: str, ns: argparse.Namespace, cfg: Config, source: Path | None, wait: float | None) -> int:
|
|
507
|
+
from il2ks.ingest import reprocess as reprocess_mod
|
|
508
|
+
from il2ks.ingest import runner
|
|
509
|
+
from il2ks.ingest import watch as watch_mod
|
|
510
|
+
|
|
511
|
+
if command == "ingest":
|
|
512
|
+
opts = runner.IngestOptions(source=source, lock_wait=wait)
|
|
513
|
+
summary = runner.ingest_once(cfg, runner.default_pipeline(cfg), opts)
|
|
514
|
+
print(summary.describe())
|
|
515
|
+
return EXIT_FAILED if summary.failed else EXIT_OK
|
|
516
|
+
if command == "watch":
|
|
517
|
+
watch_mod.watch(cfg, runner.default_pipeline(cfg))
|
|
518
|
+
return EXIT_OK
|
|
519
|
+
if command == "reprocess":
|
|
520
|
+
result = reprocess_mod.reprocess(
|
|
521
|
+
cfg,
|
|
522
|
+
runner.default_pipeline(cfg, defer_ratings=True),
|
|
523
|
+
ns.mission,
|
|
524
|
+
since=ns.since,
|
|
525
|
+
until=ns.until,
|
|
526
|
+
workers=ns.workers,
|
|
527
|
+
lock_wait=wait,
|
|
528
|
+
)
|
|
529
|
+
print(result.describe())
|
|
530
|
+
return EXIT_FAILED if (result.failed or result.missing) else EXIT_OK
|
|
531
|
+
reprocess_mod.rebuild_all(cfg, reassign_tours=ns.retour, lock_wait=wait)
|
|
532
|
+
print("level-2 aggregates rebuilt" + (" (missions reassigned to tours)" if ns.retour else ""))
|
|
533
|
+
return EXIT_OK
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
if __name__ == "__main__":
|
|
537
|
+
raise SystemExit(main())
|