33# Author: Samuel Amen Ague
44from __future__ import annotations
55
6- from API_SDK import PluginBase , PreCompileContext , wrap_context , plugin , PluginMeta
6+ from API_SDK import PluginBase , PluginMeta , PreCompileContext , plugin , wrap_context
77
88# BCASL package signature (required)
99BCASL_PLUGIN = True
1313BCASL_VERSION = "1.1.0"
1414BCASL_AUTHOR = "Samuel Amen Ague"
1515BCASL_CREATED = "2025-09-06"
16- BCASL_COMPATIBILITY = [' PyCompiler ARK++ v3.2+' , ' Python 3.10+' ]
16+ BCASL_COMPATIBILITY = [" PyCompiler ARK++ v3.2+" , " Python 3.10+" ]
1717BCASL_LICENSE = "GPL-3.0-only"
18- BCASL_TAGS = [' pre-compilation' , ' license' , ' SPDX' ]
18+ BCASL_TAGS = [" pre-compilation" , " license" , " SPDX" ]
1919
20- import API_SDK
21- from pathlib import Path
22- from typing import List , Optional , Tuple
2320import re
21+ from pathlib import Path
22+ from typing import Optional
23+
24+ import API_SDK
2425
2526# Détection et normalisation de la licence du workspace
2627try :
3233 _tomllib = None
3334
3435
35- def _normalize_spdx (lic : str ) -> Tuple [str , str ]:
36+ def _normalize_spdx (lic : str ) -> tuple [str , str ]:
3637 s = (lic or "" ).strip ()
3738 if not s :
3839 return "" , ""
@@ -115,9 +116,13 @@ def _detect_license_from_pyproject(root: Path) -> Optional[str]:
115116def _detect_license_from_files (root : Path ) -> Optional [str ]:
116117 try :
117118 candidates = [
118- "LICENSE" , "LICENSE.txt" , "LICENSE.md" ,
119- "LICENCE" , "LICENCE.txt" ,
120- "COPYING" , "COPYING.txt" ,
119+ "LICENSE" ,
120+ "LICENSE.txt" ,
121+ "LICENSE.md" ,
122+ "LICENCE" ,
123+ "LICENCE.txt" ,
124+ "COPYING" ,
125+ "COPYING.txt" ,
121126 ]
122127 for name in candidates :
123128 p = root / name
@@ -131,7 +136,7 @@ def _detect_license_from_files(root: Path) -> Optional[str]:
131136 return None
132137
133138
134- def _detect_workspace_license (root : Path ) -> Tuple [str , str ]:
139+ def _detect_workspace_license (root : Path ) -> tuple [str , str ]:
135140 lic = _detect_license_from_pyproject (root ) or _detect_license_from_files (root )
136141 if not lic :
137142 return "" , ""
@@ -141,12 +146,14 @@ def _detect_workspace_license(root: Path) -> Tuple[str, str]:
141146
142147# Détection SPDX déjà présente
143148
149+
144150def _has_spdx (text : str ) -> bool :
145151 return "SPDX-License-Identifier:" in text
146152
147153
148154# Injection d'une ligne SPDX minimale selon le type de fichier
149155
156+
150157def _inject_license_for_file (path : Path , text : str , spdx_id : str ) -> str :
151158 """Injecte une ligne SPDX minimale selon le type de fichier, sans en-tête additionnel.
152159 - .py: insère '# SPDX-License-Identifier: <ID>' après shebang/encodage
@@ -183,7 +190,11 @@ def _inject_license_for_file(path: Path, text: str, spdx_id: str) -> str:
183190 return text
184191
185192
186- @plugin (id = "license_injector" , version = "1.1.0" , description = "Injecte une ligne de licence (SPDX) en tête des fichiers ciblés" )
193+ @plugin (
194+ id = "license_injector" ,
195+ version = "1.1.0" ,
196+ description = "Injecte une ligne de licence (SPDX) en tête des fichiers ciblés" ,
197+ )
187198class LicenseInjector (PluginBase ):
188199 def on_pre_compile (self , ctx : PreCompileContext ) -> None :
189200 try :
@@ -193,31 +204,37 @@ def on_pre_compile(self, ctx: PreCompileContext) -> None:
193204 return
194205
195206 cfg = sctx .config_view
196- subcfg = cfg .for_plugin (getattr (self , 'id' , ' license_injector' ))
207+ subcfg = cfg .for_plugin (getattr (self , "id" , " license_injector" ))
197208
198209 # Déterminer la licence du workspace
199210 spdx_id , lic_name = _detect_workspace_license (sctx .workspace_root )
200211 if not spdx_id :
201- sctx .log_info ("license_injector: aucune licence détectée dans le workspace (pyproject/LICEN[SC]E/COPYING). Aucune injection." )
212+ sctx .log_info (
213+ "license_injector: aucune licence détectée dans le workspace (pyproject/LICEN[SC]E/COPYING). Aucune injection."
214+ )
202215 return
203216
204217 # Cibles et exclusions
205- patterns : List [str ] = subcfg .get ("file_patterns" , []) or cfg .file_patterns or ["**/*.py" , "**/*.md" ]
206- exclude : List [str ] = list (cfg .exclude_patterns ) + list (subcfg .get ("exclude_patterns" , []))
218+ patterns : list [str ] = subcfg .get ("file_patterns" , []) or cfg .file_patterns or ["**/*.py" , "**/*.md" ]
219+ exclude : list [str ] = list (cfg .exclude_patterns ) + list (subcfg .get ("exclude_patterns" , []))
207220 # Exclusions communes
208221 for pat in ("venv/**" , ".git/**" , "main.build/**" ):
209222 if pat not in exclude :
210223 exclude .append (pat )
211224
212225 # Demande confirmation
213- if not sctx .msg_question ("License Injector" , f"Injecter la licence (SPDX: { spdx_id } ) dans les fichiers correspondant aux motifs: { patterns } ?" , default_yes = False ):
226+ if not sctx .msg_question (
227+ "License Injector" ,
228+ f"Injecter la licence (SPDX: { spdx_id } ) dans les fichiers correspondant aux motifs: { patterns } ?" ,
229+ default_yes = False ,
230+ ):
214231 sctx .log_warn ("license_injector: opération annulée par l'utilisateur" )
215232 return
216233
217234 # Phase 1: analyse des cibles
218235 ph = API_SDK .progress ("Injection de licence" , "Analyse des fichiers cibles..." , maximum = 0 , cancelable = True )
219236 try :
220- to_modify : List [Path ] = []
237+ to_modify : list [Path ] = []
221238 found = 0
222239 skipped_dup = 0
223240 for p in sctx .iter_files (patterns , exclude = exclude , enforce_workspace = True ):
@@ -228,7 +245,9 @@ def on_pre_compile(self, ctx: PreCompileContext) -> None:
228245 try :
229246 text = p .read_text (encoding = "utf-8" , errors = "ignore" )
230247 except Exception as e :
231- sctx .log_warn (f"Lecture échouée pour { p .relative_to (sctx .workspace_root ) if p .exists () else p } : { e } " )
248+ sctx .log_warn (
249+ f"Lecture échouée pour { p .relative_to (sctx .workspace_root ) if p .exists () else p } : { e } "
250+ )
232251 continue
233252 if _has_spdx (text ):
234253 skipped_dup += 1
@@ -245,7 +264,9 @@ def on_pre_compile(self, ctx: PreCompileContext) -> None:
245264 if found == 0 :
246265 sctx .log_info ("license_injector: aucun fichier cible" )
247266 else :
248- sctx .log_info (f"license_injector: aucun fichier à modifier (déj�� avec SPDX={ skipped_dup } , total scannés={ found } )" )
267+ sctx .log_info (
268+ f"license_injector: aucun fichier à modifier (déj�� avec SPDX={ skipped_dup } , total scannés={ found } )"
269+ )
249270 return
250271
251272 # Phase 2: injection
@@ -270,7 +291,9 @@ def on_pre_compile(self, ctx: PreCompileContext) -> None:
270291 except Exception as e :
271292 sctx .log_warn (f"Écriture échouée pour { rel } : { e } " )
272293
273- sctx .log_info (f"license_injector: terminé. Modifiés={ changed } , déjà avec SPDX={ skipped_dup } , total scannés={ found } " )
294+ sctx .log_info (
295+ f"license_injector: terminé. Modifiés={ changed } , déjà avec SPDX={ skipped_dup } , total scannés={ found } "
296+ )
274297 finally :
275298 ph .close ()
276299
0 commit comments