Skip to content

Commit cd6deab

Browse files
author
PyCompiler ARK++ Team
committed
feat(header_injector): détection de licence robuste; pas d'injection si absente; i18n via API_SDK (sctx.tr)
1 parent e1aebdf commit cd6deab

1 file changed

Lines changed: 60 additions & 21 deletions

File tree

API/header_injector/__init__.py

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -114,23 +114,48 @@ def _detect_license_from_pyproject(root: Path) -> Optional[str]:
114114

115115

116116
def _detect_license_from_files(root: Path) -> Optional[str]:
117+
"""Detect license text from common files and folders in a robust, case-insensitive way.
118+
Looks for top-level files like LICENSE/LICENCE/COPYING/NOTICE (any case, with .txt/.md/.rst),
119+
accepts prefixed variants (e.g., LICENSE-APACHE), and scans LICENSES/LICENCES/LEGAL directories.
120+
Returns up to the first 8 KiB of detected license text.
121+
"""
117122
try:
118-
candidates = [
119-
"LICENSE",
120-
"LICENSE.txt",
121-
"LICENSE.md",
122-
"LICENCE",
123-
"LICENCE.txt",
124-
"COPYING",
125-
"COPYING.txt",
126-
]
127-
for name in candidates:
128-
p = root / name
129-
if p.is_file():
123+
names = ("license", "licence", "copying", "copyright", "unlicense", "notice")
124+
exts = ("", ".txt", ".md", ".rst")
125+
# Top-level files
126+
try:
127+
for p in sorted(root.iterdir()):
130128
try:
131-
return p.read_text(encoding="utf-8", errors="ignore")[:8192]
129+
if not p.is_file():
130+
continue
131+
low = p.name.lower()
132+
stem = p.stem.lower()
133+
suf = p.suffix.lower()
134+
if (stem in names and (suf in exts)) or any(
135+
low.startswith(prefix + "-") for prefix in ("license", "licence")
136+
):
137+
try:
138+
return p.read_text(encoding="utf-8", errors="ignore")[:8192]
139+
except Exception:
140+
continue
132141
except Exception:
133142
continue
143+
except Exception:
144+
pass
145+
# Common license folders
146+
for dname in ("licenses", "licences", "license", "licence", "legal", "LEGAL", "LICENSES", "LICENCES"):
147+
d = root / dname
148+
if not d.is_dir():
149+
continue
150+
# Prefer obvious files first
151+
patterns = ("*LICENSE*", "*LICENCE*", "*COPYING*", "*NOTICE*", "*")
152+
for pat in patterns:
153+
for p in sorted(d.glob(pat)):
154+
try:
155+
if p.is_file():
156+
return p.read_text(encoding="utf-8", errors="ignore")[:8192]
157+
except Exception:
158+
continue
134159
except Exception:
135160
return None
136161
return None
@@ -210,7 +235,10 @@ def on_pre_compile(self, ctx: PreCompileContext) -> None:
210235
spdx_id, lic_name = _detect_workspace_license(sctx.workspace_root)
211236
if not spdx_id:
212237
sctx.log_info(
213-
"license_injector: aucune licence détectée dans le workspace (pyproject/LICEN[SC]E/COPYING). Aucune injection."
238+
sctx.tr(
239+
"license_injector: aucune licence détectée (pyproject ou fichiers LICENSE/LICENCE/COPYING/NOTICE). Aucune injection.",
240+
"license_injector: no license detected (pyproject or LICENSE/LICENCE/COPYING/NOTICE files). No injection performed.",
241+
)
214242
)
215243
return
216244

@@ -224,22 +252,30 @@ def on_pre_compile(self, ctx: PreCompileContext) -> None:
224252

225253
# Demande confirmation
226254
if not sctx.msg_question(
227-
"License Injector",
228-
f"Injecter la licence (SPDX: {spdx_id}) dans les fichiers correspondant aux motifs: {patterns}?",
255+
sctx.tr("License Injector", "License Injector"),
256+
sctx.tr(
257+
f"Injecter la licence (SPDX: {spdx_id}) dans les fichiers correspondant aux motifs: {patterns} ?",
258+
f"Inject license (SPDX: {spdx_id}) into files matching: {patterns} ?",
259+
),
229260
default_yes=False,
230261
):
231-
sctx.log_warn("license_injector: opération annulée par l'utilisateur")
262+
sctx.log_warn(sctx.tr("license_injector: opération annulée par l'utilisateur", "license_injector: operation canceled by user"))
232263
return
233264

234265
# Phase 1: analyse des cibles
235-
ph = API_SDK.progress("Injection de licence", "Analyse des fichiers cibles...", maximum=0, cancelable=True)
266+
ph = API_SDK.progress(
267+
sctx.tr("Injection de licence", "License Injection"),
268+
sctx.tr("Analyse des fichiers cibles...", "Scanning target files..."),
269+
maximum=0,
270+
cancelable=True,
271+
)
236272
try:
237273
to_modify: list[Path] = []
238274
found = 0
239275
skipped_dup = 0
240276
for p in sctx.iter_files(patterns, exclude=exclude, enforce_workspace=True):
241277
if ph.canceled:
242-
sctx.log_warn("license_injector: opération annulée par l'utilisateur")
278+
sctx.log_warn(sctx.tr("license_injector: opération annulée par l'utilisateur", "license_injector: operation canceled by user"))
243279
return
244280
found += 1
245281
try:
@@ -274,7 +310,7 @@ def on_pre_compile(self, ctx: PreCompileContext) -> None:
274310
changed = 0
275311
for i, p in enumerate(to_modify, start=1):
276312
if ph.canceled:
277-
sctx.log_warn("license_injector: opération annulée par l'utilisateur")
313+
sctx.log_warn(sctx.tr("license_injector: opération annulée par l'utilisateur", "license_injector: operation canceled by user"))
278314
return
279315
rel = p.relative_to(sctx.workspace_root)
280316
ph.update(i, f"{i}/{len(to_modify)}: {rel}")
@@ -292,7 +328,10 @@ def on_pre_compile(self, ctx: PreCompileContext) -> None:
292328
sctx.log_warn(f"Écriture échouée pour {rel}: {e}")
293329

294330
sctx.log_info(
295-
f"license_injector: terminé. Modifiés={changed}, déjà avec SPDX={skipped_dup}, total scannés={found}"
331+
sctx.tr(
332+
f"license_injector: terminé. Modifiés={changed}, déjà avec SPDX={skipped_dup}, total scannés={found}",
333+
f"license_injector: done. Changed={changed}, already SPDX={skipped_dup}, total scanned={found}",
334+
)
296335
)
297336
finally:
298337
ph.close()

0 commit comments

Comments
 (0)