glfm 1.0.4__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.
- glfm/__init__.py +19 -0
- glfm/_version.py +18 -0
- glfm/build_unified.py +197 -0
- glfm/config.py +170 -0
- glfm/data/override_scripts.json +23 -0
- glfm/data/uralic_languages.json +8 -0
- glfm/download.py +127 -0
- glfm/loaders/__init__.py +18 -0
- glfm/loaders/load_cldr_likely_subtags.py +167 -0
- glfm/loaders/load_glottolog.py +79 -0
- glfm/loaders/load_iso_639.py +187 -0
- glfm/loaders/load_override_scripts.py +51 -0
- glfm/loaders/load_pos_stats.py +54 -0
- glfm/loaders/load_uralic_languages.py +70 -0
- glfm/loaders/load_wiktionary_languages.py +61 -0
- glfm/loaders/load_written_languages.py +66 -0
- glfm/logic/__init__.py +17 -0
- glfm/logic/build_bcp47.py +42 -0
- glfm/logic/build_extinct_json.py +97 -0
- glfm/logic/decide_default_region.py +46 -0
- glfm/logic/decide_default_script.py +89 -0
- glfm/logic/decide_fallback.py +37 -0
- glfm/logic/is_uralic.py +41 -0
- glfm/logic/resolve_cldr_key.py +35 -0
- glfm/logic/set_uralic_langs.py +34 -0
- glfm/models/__init__.py +11 -0
- glfm/models/iso_639_2.py +108 -0
- glfm/models/iso_639_3.py +140 -0
- glfm/models/language.py +72 -0
- glfm/paths.py +70 -0
- glfm/run_full_pipeline.py +215 -0
- glfm/tools/__init__.py +9 -0
- glfm/tools/all_language_tests.py +169 -0
- glfm/tools/build_consumer_dataset.py +169 -0
- glfm/tools/build_glottolog_json.py +302 -0
- glfm/tools/build_iso_data.py +213 -0
- glfm/tools/build_pos_stats.py +111 -0
- glfm/tools/compress_stdlib.py +125 -0
- glfm/tools/compute_nearest_languages.py +336 -0
- glfm/tools/convert_to_msgpack.py +112 -0
- glfm/tools/generate_iso_files.py +179 -0
- glfm/tools/generate_report.py +216 -0
- glfm/tools/parse_iso_files.py +249 -0
- glfm/tools/run_full_pipeline_test.py +129 -0
- glfm/tools/validate_bcp47.py +87 -0
- glfm/tools/validate_fallbacks.py +106 -0
- glfm/tools/validate_glottolog.py +109 -0
- glfm/tools/validate_iso_consistency.py +101 -0
- glfm/tools/validate_pos_stats.py +88 -0
- glfm/tools/visualize_fallbacks.py +143 -0
- glfm-1.0.4.dist-info/METADATA +344 -0
- glfm-1.0.4.dist-info/RECORD +55 -0
- glfm-1.0.4.dist-info/WHEEL +5 -0
- glfm-1.0.4.dist-info/licenses/LICENCE +21 -0
- glfm-1.0.4.dist-info/top_level.txt +1 -0
glfm/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# __init__.py
|
|
2
|
+
"""
|
|
3
|
+
GLFM – Global Language Family Mapper
|
|
4
|
+
|
|
5
|
+
Kielimetatietokanta ja -työkalut 7900+ kielelle (ISO 639-3).
|
|
6
|
+
|
|
7
|
+
Yksi totuuden lähde versiolle: _version.py
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from _version import __version__, __version_tuple__
|
|
12
|
+
except ImportError:
|
|
13
|
+
try:
|
|
14
|
+
from glfm._version import __version__, __version_tuple__
|
|
15
|
+
except ImportError:
|
|
16
|
+
__version__ = "1.0.4"
|
|
17
|
+
__version_tuple__ = (1, 0, 4)
|
|
18
|
+
|
|
19
|
+
__all__ = ["__version__", "__version_tuple__"]
|
glfm/_version.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# glfm/_version.py
|
|
2
|
+
"""
|
|
3
|
+
Yksi totuuden lähde versiolle.
|
|
4
|
+
|
|
5
|
+
Käyttö:
|
|
6
|
+
from glfm._version import __version__
|
|
7
|
+
from glfm._version import __version_tuple__
|
|
8
|
+
|
|
9
|
+
Versio noudattaa PEP 440 -standardia:
|
|
10
|
+
MAJOR.MINOR.PATCH[.devN | .aN | .bN | .rcN]
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
__version__ = "1.0.4"
|
|
14
|
+
__version_tuple__ = tuple(int(x) for x in __version__.split(".")[:3])
|
|
15
|
+
|
|
16
|
+
# Valinnainen: git-commit, build-päivä (voi päivittää CI:ssä)
|
|
17
|
+
__build_date__ = "2026-10-26"
|
|
18
|
+
__git_commit__ = "unknown"
|
glfm/build_unified.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
#
|
|
4
|
+
# GLFM Project
|
|
5
|
+
# Copyright (c) 2026 Tuomas Lähteenmäki
|
|
6
|
+
# Version 1.0.4
|
|
7
|
+
#
|
|
8
|
+
# https://codeberg.org/lahtis/GLFM
|
|
9
|
+
# Licensed under the MIT License.
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
import gzip
|
|
13
|
+
import json
|
|
14
|
+
import logging
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, Dict
|
|
17
|
+
|
|
18
|
+
from glfm.loaders.load_iso_639 import load_iso_639
|
|
19
|
+
from glfm.loaders.load_cldr_likely_subtags import load_cldr_likely_subtags
|
|
20
|
+
from glfm.loaders.load_wiktionary_languages import load_wiktionary_languages
|
|
21
|
+
from glfm.loaders.load_uralic_languages import load_uralic_languages
|
|
22
|
+
from glfm.loaders.load_written_languages import load_written_languages
|
|
23
|
+
from glfm.loaders.load_glottolog import load_glottolog
|
|
24
|
+
from glfm.loaders.load_pos_stats import load_pos_stats
|
|
25
|
+
from glfm.loaders.load_override_scripts import load_override_scripts
|
|
26
|
+
|
|
27
|
+
from glfm.logic.decide_default_script import decide_default_script
|
|
28
|
+
from glfm.logic.decide_default_region import decide_default_region
|
|
29
|
+
from glfm.logic.build_bcp47 import build_bcp47
|
|
30
|
+
from glfm.logic.decide_fallback import decide_fallback
|
|
31
|
+
from glfm.logic.is_uralic import is_uralic
|
|
32
|
+
from glfm.logic.set_uralic_langs import set_uralic_langs
|
|
33
|
+
|
|
34
|
+
from glfm.models.language import Language
|
|
35
|
+
from glfm.paths import get_output_dir
|
|
36
|
+
|
|
37
|
+
log = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
# ---------------------------------------------------------
|
|
40
|
+
# ETÄISYYS- JA LÄHDEMETATIEDOT (URIEL / lang2vec)
|
|
41
|
+
# ---------------------------------------------------------
|
|
42
|
+
DISTANCE_SOURCE = "lang2vec / URIEL"
|
|
43
|
+
DISTANCE_TYPE = "fam"
|
|
44
|
+
DISTANCE_BASES = ["WALS", "SSWL", "PHOIBLE", "Ethnologue", "Glottolog"]
|
|
45
|
+
|
|
46
|
+
# ---------------------------------------------------------
|
|
47
|
+
# OUTPUT-kansiot (glfm.paths:stä)
|
|
48
|
+
# ---------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
OUTPUT_ROOT = get_output_dir()
|
|
51
|
+
|
|
52
|
+
SUBFOLDERS = ["unified", "validation", "dialects", "final", "logs"]
|
|
53
|
+
|
|
54
|
+
OUTPUT = OUTPUT_ROOT / "unified" / "unified_languages.json"
|
|
55
|
+
OUTPUT_GZ = OUTPUT_ROOT / "unified" / "unified_languages.json.gz"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _ensure_dirs() -> None:
|
|
59
|
+
"""Luo output-kansiot. Kutsutaan main():sta, ei importin aikana."""
|
|
60
|
+
for folder in SUBFOLDERS:
|
|
61
|
+
(OUTPUT_ROOT / folder).mkdir(parents=True, exist_ok=True)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------
|
|
65
|
+
# Unified-rakenteen rakentaminen
|
|
66
|
+
# ---------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
def build_unified() -> Dict[str, Any]:
|
|
69
|
+
|
|
70
|
+
# --- Lataa datalähteet ---
|
|
71
|
+
iso = load_iso_639()
|
|
72
|
+
cldr = load_cldr_likely_subtags()
|
|
73
|
+
wikt = load_wiktionary_languages()
|
|
74
|
+
written = load_written_languages()
|
|
75
|
+
uralic_set = load_uralic_languages()
|
|
76
|
+
glottolog = load_glottolog()
|
|
77
|
+
pos_stats = load_pos_stats()
|
|
78
|
+
overrides = load_override_scripts()
|
|
79
|
+
|
|
80
|
+
if not uralic_set:
|
|
81
|
+
log.warning("uralic_languages.json puuttuu tai on tyhjä – "
|
|
82
|
+
"kaikki uralicNLP-flagit ovat False")
|
|
83
|
+
|
|
84
|
+
set_uralic_langs(uralic_set)
|
|
85
|
+
|
|
86
|
+
unified: Dict[str, Any] = {}
|
|
87
|
+
|
|
88
|
+
# --- Käy läpi kaikki ISO-kielet ---
|
|
89
|
+
for lang_id, iso_info in iso.items():
|
|
90
|
+
|
|
91
|
+
w = wikt.get(lang_id, {})
|
|
92
|
+
w_written = written.get(lang_id, {})
|
|
93
|
+
glotto = glottolog.get(lang_id, {})
|
|
94
|
+
pos = pos_stats.get(lang_id, {})
|
|
95
|
+
|
|
96
|
+
# Nimet
|
|
97
|
+
name = w.get("name") or iso_info.get("name") or lang_id
|
|
98
|
+
official_name = w.get("official_name") or name
|
|
99
|
+
|
|
100
|
+
# ISO-koodit
|
|
101
|
+
iso1 = iso_info.get("iso639_1", "")
|
|
102
|
+
iso2B = iso_info.get("iso639_2B", "")
|
|
103
|
+
iso2T = iso_info.get("iso639_2T", "")
|
|
104
|
+
iso3 = iso_info.get("iso639_3", "")
|
|
105
|
+
iso5 = iso_info.get("iso639_5", "")
|
|
106
|
+
language_type = iso_info.get("language_type", "")
|
|
107
|
+
|
|
108
|
+
# --- Skripti ja alue dynaamisesti lähteistä ---
|
|
109
|
+
script = decide_default_script(lang_id, w, cldr, w_written, iso_info, overrides)
|
|
110
|
+
region = decide_default_region(lang_id, w, cldr, iso_info)
|
|
111
|
+
|
|
112
|
+
# --- Kirjoitetun kielen tila määräytyy YKSINOMAAN todennetusta skriptistä ---
|
|
113
|
+
is_written_flag = bool(script and script.upper() != "UNKNOWN")
|
|
114
|
+
scripts = [script] if is_written_flag else []
|
|
115
|
+
|
|
116
|
+
# --- BCP-47 ---
|
|
117
|
+
bcp47 = build_bcp47(lang_id, script if is_written_flag else "", region, iso_info)
|
|
118
|
+
|
|
119
|
+
# --- Fallback ja Uralic-tuki ---
|
|
120
|
+
fallback = decide_fallback(lang_id, w)
|
|
121
|
+
uralic = is_uralic(lang_id)
|
|
122
|
+
|
|
123
|
+
# --- Glottolog-kentät ---
|
|
124
|
+
glottocode = (glotto.get("glottocode")
|
|
125
|
+
or w_written.get("glottocode", ""))
|
|
126
|
+
family = (glotto.get("family")
|
|
127
|
+
or w_written.get("family", ""))
|
|
128
|
+
|
|
129
|
+
# Luo Language-olio
|
|
130
|
+
lang_obj = Language(
|
|
131
|
+
id=lang_id,
|
|
132
|
+
name=name,
|
|
133
|
+
official_name=official_name,
|
|
134
|
+
iso639_1=iso1,
|
|
135
|
+
iso639_2B=iso2B,
|
|
136
|
+
iso639_2T=iso2T,
|
|
137
|
+
iso639_3=iso3,
|
|
138
|
+
iso639_5=iso5,
|
|
139
|
+
language_type=language_type,
|
|
140
|
+
default_script=script if is_written_flag else "",
|
|
141
|
+
default_region=region,
|
|
142
|
+
bcp47=bcp47,
|
|
143
|
+
fallback=fallback,
|
|
144
|
+
uralicNLP=uralic,
|
|
145
|
+
written=is_written_flag,
|
|
146
|
+
written_scripts=scripts,
|
|
147
|
+
glottocode=glottocode,
|
|
148
|
+
family=family,
|
|
149
|
+
glottolog=glotto,
|
|
150
|
+
pos_stats=pos,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
lang_dict = lang_obj.__dict__
|
|
154
|
+
|
|
155
|
+
# --- Etäisyysmetatiedot: AINA läsnä ---
|
|
156
|
+
lang_dict["distance_source"] = DISTANCE_SOURCE
|
|
157
|
+
lang_dict["distance_type"] = DISTANCE_TYPE
|
|
158
|
+
lang_dict["distance_bases"] = DISTANCE_BASES
|
|
159
|
+
lang_dict["nearest_languages"] = []
|
|
160
|
+
|
|
161
|
+
unified[lang_id] = lang_dict
|
|
162
|
+
|
|
163
|
+
return unified
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# ---------------------------------------------------------
|
|
167
|
+
# Tallennus
|
|
168
|
+
# ---------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
def _save(unified: Dict[str, Any]) -> None:
|
|
171
|
+
"""Tallentaa sekä pretty-JSON (debug) että gzip-JSON (tuotanto)."""
|
|
172
|
+
# Pretty (debug, gitignoressa)
|
|
173
|
+
with open(OUTPUT, "w", encoding="utf-8") as f:
|
|
174
|
+
json.dump(unified, f, ensure_ascii=False, indent=2)
|
|
175
|
+
log.info("Kirjoitettu: %s (%.1f MB)",
|
|
176
|
+
OUTPUT, OUTPUT.stat().st_size / 1e6)
|
|
177
|
+
|
|
178
|
+
# Gzip (tuotanto, pieni)
|
|
179
|
+
with gzip.open(OUTPUT_GZ, "wt", encoding="utf-8") as f:
|
|
180
|
+
json.dump(unified, f, ensure_ascii=False, separators=(",", ":"))
|
|
181
|
+
log.info("Kirjoitettu: %s (%.1f MB)",
|
|
182
|
+
OUTPUT_GZ, OUTPUT_GZ.stat().st_size / 1e6)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def main() -> None:
|
|
186
|
+
logging.basicConfig(
|
|
187
|
+
level=logging.INFO,
|
|
188
|
+
format="%(levelname)s %(name)s: %(message)s",
|
|
189
|
+
)
|
|
190
|
+
_ensure_dirs()
|
|
191
|
+
unified = build_unified()
|
|
192
|
+
_save(unified)
|
|
193
|
+
log.info("Valmis: %d kieltä", len(unified))
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
if __name__ == "__main__":
|
|
197
|
+
main()
|
glfm/config.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
#
|
|
4
|
+
# GLFM Project
|
|
5
|
+
# Copyright (c) 2026 Tuomas Lähteenmäki
|
|
6
|
+
# Version 1.0.4
|
|
7
|
+
#
|
|
8
|
+
# https://codeberg.org/lahtis/GLFM
|
|
9
|
+
# Licensed under the MIT License.
|
|
10
|
+
#
|
|
11
|
+
# Konfiguraation lataus: config.toml + ympäristömuuttujat.
|
|
12
|
+
#
|
|
13
|
+
|
|
14
|
+
import copy
|
|
15
|
+
import logging
|
|
16
|
+
import os
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from glfm.paths import get_data_dir, get_output_dir
|
|
20
|
+
|
|
21
|
+
log = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
# Oletuskonfiguraatio – ylikirjoitetaan config.toml:llä ja env-muuttujilla
|
|
24
|
+
DEFAULT_CONFIG = {
|
|
25
|
+
"sources": {
|
|
26
|
+
"iso_639_3": {
|
|
27
|
+
"url": (
|
|
28
|
+
"https://iso639-3.sil.org/sites/iso639-3/files/"
|
|
29
|
+
"downloads/iso-639-3.tab"
|
|
30
|
+
),
|
|
31
|
+
},
|
|
32
|
+
"iso_639_3_names": {
|
|
33
|
+
"url": (
|
|
34
|
+
"https://iso639-3.sil.org/sites/iso639-3/files/"
|
|
35
|
+
"downloads/iso-639-3_Name_Index.tab"
|
|
36
|
+
),
|
|
37
|
+
},
|
|
38
|
+
"iso_639_3_macrolanguages": {
|
|
39
|
+
"url": (
|
|
40
|
+
"https://iso639-3.sil.org/sites/iso639-3/files/"
|
|
41
|
+
"downloads/iso-639-3-macrolanguages.tab"
|
|
42
|
+
),
|
|
43
|
+
},
|
|
44
|
+
"iso_639_5": {
|
|
45
|
+
"url": "https://id.loc.gov/vocabulary/iso639-5.skos.rdf",
|
|
46
|
+
},
|
|
47
|
+
"cldr": {
|
|
48
|
+
"version": "48.0.0",
|
|
49
|
+
"url": (
|
|
50
|
+
"https://raw.githubusercontent.com/unicode-org/"
|
|
51
|
+
"cldr-json/48.0.0/cldr-json/cldr-core/supplemental/"
|
|
52
|
+
"likelySubtags.json"
|
|
53
|
+
),
|
|
54
|
+
},
|
|
55
|
+
"wikidata": {
|
|
56
|
+
"endpoint": "https://query.wikidata.org/sparql",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
"download": {
|
|
60
|
+
"url": (
|
|
61
|
+
"https://codeberg.org/lahtis/GLFM/releases/download/"
|
|
62
|
+
"v1.0.4/data.zip"
|
|
63
|
+
),
|
|
64
|
+
"sha256": "",
|
|
65
|
+
"timeout": 300,
|
|
66
|
+
},
|
|
67
|
+
"http": {
|
|
68
|
+
"user_agent": "glfm/1.0.4 (https://codeberg.org/lahtis/GLFM)",
|
|
69
|
+
"timeout": 60,
|
|
70
|
+
},
|
|
71
|
+
"paths": {
|
|
72
|
+
"cache_dir": "",
|
|
73
|
+
"data_dir": "",
|
|
74
|
+
"cache_dir_name": "glfm",
|
|
75
|
+
"data_subdir": "data",
|
|
76
|
+
},
|
|
77
|
+
"logging": {
|
|
78
|
+
"level": "INFO",
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# Ympäristömuuttujat → config-avaimet
|
|
83
|
+
ENV_MAP = {
|
|
84
|
+
"GLFM_ISO_639_3_URL": ("sources", "iso_639_3", "url"),
|
|
85
|
+
"GLFM_ISO_639_3_NAMES_URL": ("sources", "iso_639_3_names", "url"),
|
|
86
|
+
"GLFM_ISO_639_3_MACRO_URL": ("sources", "iso_639_3_macrolanguages", "url"),
|
|
87
|
+
"GLFM_ISO_639_5_URL": ("sources", "iso_639_5", "url"),
|
|
88
|
+
"GLFM_CLDR_VERSION": ("sources", "cldr", "version"),
|
|
89
|
+
"GLFM_CLDR_URL": ("sources", "cldr", "url"),
|
|
90
|
+
"GLFM_WIKIDATA_ENDPOINT": ("sources", "wikidata", "endpoint"),
|
|
91
|
+
"GLFM_DATA_URL": ("download", "url"),
|
|
92
|
+
"GLFM_DATA_SHA256": ("download", "sha256"),
|
|
93
|
+
"GLFM_DATA_TIMEOUT": ("download", "timeout"),
|
|
94
|
+
"GLFM_USER_AGENT": ("http", "user_agent"),
|
|
95
|
+
"GLFM_HTTP_TIMEOUT": ("http", "timeout"),
|
|
96
|
+
"GLFM_CACHE_DIR": ("paths", "cache_dir"),
|
|
97
|
+
"GLFM_DATA_DIR": ("paths", "data_dir"),
|
|
98
|
+
"GLFM_LOG_LEVEL": ("logging", "level"),
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def load_config() -> dict:
|
|
103
|
+
"""
|
|
104
|
+
Lataa konfiguraation seuraavassa järjestyksessä:
|
|
105
|
+
|
|
106
|
+
1. DEFAULT_CONFIG (kovakoodatut oletukset)
|
|
107
|
+
2. config.toml (repon juuressa tai ~/.config/glfm/config.toml)
|
|
108
|
+
3. Ympäristömuuttujat (GLFM_*)
|
|
109
|
+
|
|
110
|
+
Palauttaa syvän kopion – ei jaa tilaa DEFAULT_CONFIG:n kanssa.
|
|
111
|
+
"""
|
|
112
|
+
config = copy.deepcopy(DEFAULT_CONFIG)
|
|
113
|
+
|
|
114
|
+
# 1. config.toml
|
|
115
|
+
config_path = get_config_path()
|
|
116
|
+
if config_path.exists():
|
|
117
|
+
try:
|
|
118
|
+
import tomllib
|
|
119
|
+
with open(config_path, "rb") as f:
|
|
120
|
+
user_config = tomllib.load(f)
|
|
121
|
+
_merge(config, user_config)
|
|
122
|
+
log.info("Luettu config: %s", config_path)
|
|
123
|
+
except Exception as e:
|
|
124
|
+
log.warning("Config-virhe (%s): %s", config_path, e)
|
|
125
|
+
else:
|
|
126
|
+
log.debug("config.toml puuttuu (%s), käytetään oletuksia",
|
|
127
|
+
config_path)
|
|
128
|
+
|
|
129
|
+
# 2. Ympäristömuuttujat
|
|
130
|
+
for env_key, path in ENV_MAP.items():
|
|
131
|
+
if value := os.environ.get(env_key):
|
|
132
|
+
_set_nested(config, path, value)
|
|
133
|
+
log.debug("Env: %s → %s", env_key, ".".join(path))
|
|
134
|
+
|
|
135
|
+
return config
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def get_config_path() -> Path:
|
|
139
|
+
"""Palauttaa config.toml-polkua."""
|
|
140
|
+
if env := os.environ.get("GLFM_CONFIG"):
|
|
141
|
+
return Path(env).expanduser().resolve()
|
|
142
|
+
|
|
143
|
+
xdg = os.environ.get("XDG_CONFIG_HOME")
|
|
144
|
+
if xdg:
|
|
145
|
+
return Path(xdg) / "glfm" / "config.toml"
|
|
146
|
+
|
|
147
|
+
return Path.home() / ".config" / "glfm" / "config.toml"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _set_nested(config: dict, path: tuple, value) -> None:
|
|
151
|
+
"""Asettaa arvon syvään dictiin."""
|
|
152
|
+
current = config
|
|
153
|
+
for key in path[:-1]:
|
|
154
|
+
if key not in current or not isinstance(current[key], dict):
|
|
155
|
+
current[key] = {}
|
|
156
|
+
current = current[key]
|
|
157
|
+
current[path[-1]] = value
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _merge(base: dict, override: dict) -> None:
|
|
161
|
+
"""Syvä merge: override korvaa base:n arvot."""
|
|
162
|
+
for key, value in override.items():
|
|
163
|
+
if (
|
|
164
|
+
key in base
|
|
165
|
+
and isinstance(base[key], dict)
|
|
166
|
+
and isinstance(value, dict)
|
|
167
|
+
):
|
|
168
|
+
_merge(base[key], value)
|
|
169
|
+
else:
|
|
170
|
+
base[key] = value
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ang": ["Latn"],
|
|
3
|
+
"arc": ["Armi", "Syrc"],
|
|
4
|
+
"akk": ["Xsux"],
|
|
5
|
+
"cop": ["Copt", "Grek"],
|
|
6
|
+
"egy": ["Egyp", "Egyh", "Egyd"],
|
|
7
|
+
"fro": ["Latn"],
|
|
8
|
+
"got": ["Goth"],
|
|
9
|
+
"grc": ["Grek", "Linb"],
|
|
10
|
+
"hca": ["Latn"],
|
|
11
|
+
"hdn": ["Latn"],
|
|
12
|
+
"hit": ["Xsux"],
|
|
13
|
+
"mga": ["Ogam", "Latn"],
|
|
14
|
+
"mvi": ["Jpan"],
|
|
15
|
+
"mvb": ["Latn"],
|
|
16
|
+
"non": ["Runr", "Latn"],
|
|
17
|
+
"orv": ["Cyrs", "Cyrl"],
|
|
18
|
+
"peo": ["Xpeo"],
|
|
19
|
+
"phn": ["Phnx"],
|
|
20
|
+
"san": ["Deva", "Shar", "Brah"],
|
|
21
|
+
"sux": ["Xsux"],
|
|
22
|
+
"lat": ["Latn"]
|
|
23
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
[
|
|
2
|
+
"hun","kca","mns","ekk","fin","fkv","izh","krl","zkv","liv",
|
|
3
|
+
"olo","lud","fit","vep","vot","vro","mrj","mhr","myv","mdf",
|
|
4
|
+
"udm","koi","kpv","sia","smn","sjk","sjd","sms","sjt","sju",
|
|
5
|
+
"smj","sme","sje","sma","mtm","yrk","nio","enf","enh","rts",
|
|
6
|
+
"xas","sel"
|
|
7
|
+
]
|
|
8
|
+
|
glfm/download.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
#
|
|
4
|
+
# GLFM Project
|
|
5
|
+
# Copyright (c) 2026 Tuomas Lähteenmäki
|
|
6
|
+
# Version 1.0.4
|
|
7
|
+
#
|
|
8
|
+
# https://codeberg.org/lahtis/GLFM
|
|
9
|
+
# Licensed under the MIT License.
|
|
10
|
+
#
|
|
11
|
+
# Lataa ladattavan datan (~/.cache/glfm/data/).
|
|
12
|
+
# Käyttö:
|
|
13
|
+
# python3 -m glfm.download
|
|
14
|
+
# python3 -m glfm.download --force
|
|
15
|
+
# python3 -m glfm.download --target /tmp/glfm-data
|
|
16
|
+
#
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import hashlib
|
|
20
|
+
import io
|
|
21
|
+
import logging
|
|
22
|
+
import shutil
|
|
23
|
+
import sys
|
|
24
|
+
import urllib.request
|
|
25
|
+
import zipfile
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
log = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def download_data(force: bool = False) -> Path:
|
|
32
|
+
"""
|
|
33
|
+
Lataa datan cacheen (~/.cache/glfm/data/).
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
force: pakota uudelleenlataus, vaikka data olisi jo olemassa
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Polku data-hakemistoon
|
|
40
|
+
"""
|
|
41
|
+
from glfm.config import load_config
|
|
42
|
+
from glfm.paths import get_cache_dir
|
|
43
|
+
|
|
44
|
+
config = load_config()
|
|
45
|
+
cache = get_cache_dir() / "data"
|
|
46
|
+
|
|
47
|
+
if cache.exists() and not force:
|
|
48
|
+
log.info("Data jo olemassa: %s", cache)
|
|
49
|
+
return cache
|
|
50
|
+
|
|
51
|
+
url = config["download"]["url"]
|
|
52
|
+
sha256 = config["download"]["sha256"]
|
|
53
|
+
timeout = config["download"]["timeout"]
|
|
54
|
+
|
|
55
|
+
log.info("Ladataan: %s", url)
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
with urllib.request.urlopen(url, timeout=timeout) as resp:
|
|
59
|
+
data = resp.read()
|
|
60
|
+
except Exception as e:
|
|
61
|
+
log.error("Lataus epäonnistui: %s", e)
|
|
62
|
+
raise
|
|
63
|
+
|
|
64
|
+
# Tarkista hash
|
|
65
|
+
if sha256:
|
|
66
|
+
h = hashlib.sha256(data).hexdigest()
|
|
67
|
+
if h != sha256:
|
|
68
|
+
raise RuntimeError(f"SHA256 ei täsmää: {h} != {sha256}")
|
|
69
|
+
|
|
70
|
+
# Pura
|
|
71
|
+
cache.parent.mkdir(parents=True, exist_ok=True)
|
|
72
|
+
tmp = cache.parent / "data.tmp"
|
|
73
|
+
if tmp.exists():
|
|
74
|
+
shutil.rmtree(tmp)
|
|
75
|
+
tmp.mkdir()
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
with zipfile.ZipFile(io.BytesIO(data)) as z:
|
|
79
|
+
z.extractall(tmp)
|
|
80
|
+
except zipfile.BadZipFile as e:
|
|
81
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
82
|
+
raise RuntimeError(f"Ladattu tiedosto ei ole kelvollinen zip: {e}")
|
|
83
|
+
|
|
84
|
+
# Atominen uudelleennimeäminen
|
|
85
|
+
if cache.exists():
|
|
86
|
+
shutil.rmtree(cache)
|
|
87
|
+
tmp.rename(cache)
|
|
88
|
+
|
|
89
|
+
log.info("Valmis: %s", cache)
|
|
90
|
+
return cache
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _data_size(path: Path) -> int:
|
|
94
|
+
"""Palauttaa hakemiston koon tavuina."""
|
|
95
|
+
return sum(f.stat().st_size for f in path.rglob("*") if f.is_file())
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def main():
|
|
99
|
+
parser = argparse.ArgumentParser(
|
|
100
|
+
description="Lataa GLFM-data (~/.cache/glfm/data/).",
|
|
101
|
+
)
|
|
102
|
+
parser.add_argument(
|
|
103
|
+
"--force", action="store_true",
|
|
104
|
+
help="Pakota uudelleenlataus (ohita olemassa oleva data)",
|
|
105
|
+
)
|
|
106
|
+
args = parser.parse_args()
|
|
107
|
+
|
|
108
|
+
logging.basicConfig(
|
|
109
|
+
level=logging.INFO,
|
|
110
|
+
format="%(levelname)s %(message)s",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
path = download_data(force=args.force)
|
|
115
|
+
except RuntimeError as e:
|
|
116
|
+
log.error("%s", e)
|
|
117
|
+
return 1
|
|
118
|
+
|
|
119
|
+
size_kt = _data_size(path) / 1024
|
|
120
|
+
print()
|
|
121
|
+
print(f"Data: {path}")
|
|
122
|
+
print(f"Koko: {size_kt:.1f} kt")
|
|
123
|
+
return 0
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
if __name__ == "__main__":
|
|
127
|
+
raise SystemExit(main())
|
glfm/loaders/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# loaders/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
GLFM – datalähteiden latausmoduulit.
|
|
4
|
+
|
|
5
|
+
Jokainen loaderi lukee yhden datalähteen ja palauttaa jäsennellyn dictin.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"load_cldr_likely_subtags",
|
|
10
|
+
"load_glottolog",
|
|
11
|
+
"load_iso_639",
|
|
12
|
+
"load_override_scripts",
|
|
13
|
+
"load_pos_stats",
|
|
14
|
+
"load_uralic_languages",
|
|
15
|
+
"load_wiktionary_languages",
|
|
16
|
+
"load_written_languages",
|
|
17
|
+
]
|
|
18
|
+
|