|
28 | 28 | "PluginMeta", |
29 | 29 | "PreCompileContext", |
30 | 30 | "ExecutionReport", |
| 31 | + "bc_register", |
31 | 32 | ] |
32 | 33 |
|
33 | 34 | # Configuration logger par défaut (faible verbosité pour embarqué) |
@@ -580,5 +581,241 @@ def __init__(self, plugin: BcPluginBase, insert_idx: int) -> None: |
580 | 581 |
|
581 | 582 |
|
582 | 583 | def register_plugin(cls: Any) -> Any: |
| 584 | + """Marque une classe comme plugin BCASL (legacy). |
| 585 | +
|
| 586 | + Args: |
| 587 | + cls: Classe à marquer |
| 588 | +
|
| 589 | + Returns: |
| 590 | + La classe inchangée, avec l'attribut __bcasl_plugin__ = True |
| 591 | + """ |
583 | 592 | setattr(cls, "__bcasl_plugin__", True) |
584 | 593 | return cls |
| 594 | + |
| 595 | + |
| 596 | +def bc_register( |
| 597 | + cls: Optional[type] = None, |
| 598 | + *, |
| 599 | + manager: Any = None, |
| 600 | + auto_instantiate: bool = True, |
| 601 | + priority: Optional[int] = None, |
| 602 | +) -> Any: |
| 603 | + """Décorateur pour enregistrer un plugin BCASL. |
| 604 | +
|
| 605 | + Peut être utilisé de plusieurs façons: |
| 606 | +
|
| 607 | + 1. En tant que décorateur simple (le plugin sera automatiquement instancié |
| 608 | + et enregistré lorsque le module est importé par BCASL): |
| 609 | +
|
| 610 | + @bc_register |
| 611 | + class MyPlugin(BcPluginBase): |
| 612 | + meta = PluginMeta( |
| 613 | + id="my_plugin", |
| 614 | + name="My Plugin", |
| 615 | + version="1.0.0", |
| 616 | + ) |
| 617 | +
|
| 618 | + def on_pre_compile(self, ctx: PreCompileContext) -> None: |
| 619 | + ... |
| 620 | +
|
| 621 | + 2. Avec un manager explicite (pour enregistrement immédiat): |
| 622 | +
|
| 623 | + @bc_register(manager=my_manager) |
| 624 | + class MyPlugin(BcPluginBase): |
| 625 | + meta = PluginMeta( |
| 626 | + id="my_plugin", |
| 627 | + name="My Plugin", |
| 628 | + version="1.0.0", |
| 629 | + ) |
| 630 | + ... |
| 631 | +
|
| 632 | + 3. Avec des options de configuration: |
| 633 | +
|
| 634 | + @bc_register(priority=10) |
| 635 | + class MyPlugin(BcPluginBase): |
| 636 | + meta = PluginMeta( |
| 637 | + id="my_plugin", |
| 638 | + name="My Plugin", |
| 639 | + version="1.0.0", |
| 640 | + ) |
| 641 | + ... |
| 642 | +
|
| 643 | + Args: |
| 644 | + cls: Classe de plugin (rempli automatiquement par Python lors de l'utilisation |
| 645 | + du décorateur sans parenthèses). |
| 646 | + manager: Instance optionnelle de BCASL pour enregistrement immédiat. |
| 647 | + auto_instantiate: Si True (défaut), le plugin est instancié automatiquement. |
| 648 | + priority: Priorité optionnelle du plugin (écrase celle définie dans le meta). |
| 649 | +
|
| 650 | + Returns: |
| 651 | + La classe de plugin (avec instance créée si auto_instantiate=True) |
| 652 | +
|
| 653 | + Raises: |
| 654 | + TypeError: Si la classe décorée n'hérite pas de BcPluginBase |
| 655 | + ValueError: Si le plugin n'a pas de métadonnées valides |
| 656 | +
|
| 657 | + Example: |
| 658 | + # Utilisation simple |
| 659 | + from bcasl import BcPluginBase, PluginMeta, bc_register |
| 660 | + from bcasl.Base import PreCompileContext |
| 661 | +
|
| 662 | + @bc_register |
| 663 | + class MyLinter(BcPluginBase): |
| 664 | + meta = PluginMeta( |
| 665 | + id="my_linter", |
| 666 | + name="My Linter", |
| 667 | + version="1.0.0", |
| 668 | + tags=["lint"], |
| 669 | + ) |
| 670 | +
|
| 671 | + def on_pre_compile(self, ctx: PreCompileContext) -> None: |
| 672 | + # Votre logique de linting ici |
| 673 | + pass |
| 674 | +
|
| 675 | + # Utilisation avec options |
| 676 | + @bc_register(priority=5) |
| 677 | + class EarlyPlugin(BcPluginBase): |
| 678 | + meta = PluginMeta( |
| 679 | + id="early_plugin", |
| 680 | + name="Early Plugin", |
| 681 | + version="1.0.0", |
| 682 | + tags=["prepare"], |
| 683 | + ) |
| 684 | +
|
| 685 | + def on_pre_compile(self, ctx: PreCompileContext) -> None: |
| 686 | + pass |
| 687 | + """ |
| 688 | + def decorator_inner(cls_to_decorate: type) -> Any: |
| 689 | + # Vérifier que c'est bien une classe |
| 690 | + if not isinstance(cls_to_decorate, type): |
| 691 | + raise TypeError( |
| 692 | + f"bc_register doit être appliqué à une classe, pas à {type(cls_to_decorate)}" |
| 693 | + ) |
| 694 | + |
| 695 | + # Vérifier l'héritage BcPluginBase (support des deux versions) |
| 696 | + is_valid = False |
| 697 | + try: |
| 698 | + # Vérifier BcPluginBase de bcasl |
| 699 | + if issubclass(cls_to_decorate, BcPluginBase): |
| 700 | + is_valid = True |
| 701 | + except TypeError: |
| 702 | + pass |
| 703 | + |
| 704 | + # Vérifier si le module Plugins_SDK est disponible |
| 705 | + try: |
| 706 | + from Plugins_SDK.BcPluginContext import BcPluginBase as BcPluginBaseSDK |
| 707 | + if issubclass(cls_to_decorate, BcPluginBaseSDK): |
| 708 | + is_valid = True |
| 709 | + except (ImportError, TypeError): |
| 710 | + pass |
| 711 | + |
| 712 | + if not is_valid: |
| 713 | + raise TypeError( |
| 714 | + f"La classe décorée avec @bc_register doit hériter de BcPluginBase. " |
| 715 | + f"Classe reçue: {cls_to_decorate.__name__}" |
| 716 | + ) |
| 717 | + |
| 718 | + # Marquer la classe comme plugin BCASL |
| 719 | + setattr(cls_to_decorate, "__bcasl_plugin__", True) |
| 720 | + |
| 721 | + # Récupérer les métadonnées depuis l'attribut de classe |
| 722 | + meta = getattr(cls_to_decorate, "meta", None) |
| 723 | + if meta is None: |
| 724 | + # Essayer d'instancier pour récupérer le meta depuis l'instance |
| 725 | + # (utile pour les plugins old-style qui passent META à super().__init__) |
| 726 | + try: |
| 727 | + if cls_to_decorate.__init__ is not BcPluginBase.__init__: |
| 728 | + # Custom __init__ - try to instantiate |
| 729 | + temp_instance = cls_to_decorate() |
| 730 | + if hasattr(temp_instance, 'meta'): |
| 731 | + meta = temp_instance.meta |
| 732 | + except Exception: |
| 733 | + pass |
| 734 | + |
| 735 | + if meta is None: |
| 736 | + raise ValueError( |
| 737 | + f"La classe plugin '{cls_to_decorate.__name__}' doit avoir un attribut 'meta' " |
| 738 | + f"contenant un PluginMeta valide." |
| 739 | + ) |
| 740 | + |
| 741 | + # Valider les métadonnées |
| 742 | + if not hasattr(meta, "id") or not meta.id: |
| 743 | + raise ValueError( |
| 744 | + f"Le PluginMeta du plugin '{cls_to_decorate.__name__}' doit avoir un 'id' défini." |
| 745 | + ) |
| 746 | + |
| 747 | + # Fonction helper pour instancier le plugin |
| 748 | + def _create_plugin_instance() -> "BcPluginBase": |
| 749 | + """Crée une instance du plugin en gérant différents styles d'initialisation.""" |
| 750 | + # Stratégie 1: Si la classe a son propre __init__ (comme les plugins existants), |
| 751 | + # essayer d'instancier directement |
| 752 | + init_method = getattr(cls_to_decorate, "__init__", None) |
| 753 | + # Obtenir le __init__ original de BcPluginBase |
| 754 | + base_init = getattr(BcPluginBase, "__init__", None) |
| 755 | + |
| 756 | + # Si la classe n'a pas redéfini __init__ ou si son __init__ est le même que BcPluginBase |
| 757 | + if init_method is base_init or cls_to_decorate.__init__ is BcPluginBase.__init__: |
| 758 | + # Pas de __init__ personnalisé, utiliser meta de l'attribut de classe |
| 759 | + cls_meta = getattr(cls_to_decorate, "meta", None) |
| 760 | + if cls_meta is not None: |
| 761 | + return cls_to_decorate(meta=cls_meta) |
| 762 | + # fallback: passer meta directement |
| 763 | + return cls_to_decorate(meta) |
| 764 | + else: |
| 765 | + # __init__ personnalisé (probablement comme Cleaner), instancier directement |
| 766 | + try: |
| 767 | + return cls_to_decorate() |
| 768 | + except TypeError: |
| 769 | + # Si ça échoue, essayer avec meta |
| 770 | + cls_meta = getattr(cls_to_decorate, "meta", None) |
| 771 | + if cls_meta is not None: |
| 772 | + return cls_to_decorate(meta=cls_meta) |
| 773 | + return cls_to_decorate(meta) |
| 774 | + |
| 775 | + # Créer l'instance si demandé |
| 776 | + plugin_instance = None |
| 777 | + if auto_instantiate: |
| 778 | + try: |
| 779 | + plugin_instance = _create_plugin_instance() |
| 780 | + setattr(cls_to_decorate, "_bcasl_instance_", plugin_instance) |
| 781 | + except Exception as e: |
| 782 | + raise ValueError( |
| 783 | + f"Impossible d'instancier le plugin '{cls_to_decorate.__name__}': {e}" |
| 784 | + ) from e |
| 785 | + |
| 786 | + # Si un manager est fourni, enregistrer immédiatement |
| 787 | + if manager is not None: |
| 788 | + if plugin_instance is None: |
| 789 | + try: |
| 790 | + plugin_instance = _create_plugin_instance() |
| 791 | + except Exception as e: |
| 792 | + raise ValueError( |
| 793 | + f"Impossible d'instancier le plugin '{cls_to_decorate.__name__}': {e}" |
| 794 | + ) from e |
| 795 | + |
| 796 | + # Appliquer la priorité personnalisée si fournie |
| 797 | + if priority is not None and plugin_instance is not None: |
| 798 | + plugin_instance.priority = priority |
| 799 | + |
| 800 | + # Ajouter au manager |
| 801 | + try: |
| 802 | + manager.add_plugin(plugin_instance) |
| 803 | + _logger.debug("Plugin '%s' enregistré avec @bc_register", meta.id) |
| 804 | + except Exception as e: |
| 805 | + raise RuntimeError( |
| 806 | + f"Échec de l'enregistrement du plugin '{meta.id}': {e}" |
| 807 | + ) from e |
| 808 | + |
| 809 | + return cls_to_decorate |
| 810 | + |
| 811 | + # Gestion des deux syntaxes du décorateur: |
| 812 | + # - @bc_register sans parenthèses: cls est la classe, manager/priority sont None |
| 813 | + # - @bc_register() ou @bc_register(manager=x): cls est None, retourne decorator_inner |
| 814 | + |
| 815 | + if cls is not None and isinstance(cls, type): |
| 816 | + # @bc_register sans parenthèses - cls est la classe à décorée |
| 817 | + return decorator_inner(cls) |
| 818 | + |
| 819 | + # @bc_register() ou @bc_register(manager=x, priority=x) - retourner le décorateur |
| 820 | + # Les paramètres manager et priority ont été capturés dans la closure |
| 821 | + return decorator_inner |
0 commit comments