doc/plugin_tutorial.fr.txt
author Pierre-Yves David <pierre-yves.david@logilab.fr>
Fri, 13 Jun 2008 15:37:48 +0200
changeset 5 ee38ef554863
parent 0 7710b138d4eb
permissions -rw-r--r--
remove apycot.ini

===============================
Ecrire un plugin pour OOBrother
===============================

:Author: Sylvain Thénault
:Organization: Logilab
:Version: $Revision: 1.6 $
:Date: $Date: 2004-10-15 10:19:03 $

.. contents::


Intro
-----
L'objectif de ce document est de suivre pas à pas la création d'un
plugin pour OOBrother. Nous allons donc suivre la création d'un plugin
devant intégrer 2 outils debian :

* debc
* lintian


Premier pas
-----------
OOBrother fournit une classe abstraite fournissant une implémentation
par défaut de quelques aspects du plugin. C'est la classe
AbstractPlugin du module oobrother.plugins. Commençons donc par le
plus simple module possible: ::

    from oobrother.plugins import AbstractPlugIn

    class DebianPlugin(AbstractPlugIn):
	"""a plugin for text file, propo"""
	name = 'debian'
	mimetypes = ('application/x-debian-package',
                     'application/x-debian-changes',
                     'application/x-debian-dsc')

	def __init__(self):
	    AbstractPlugIn.__init__(self, config_file='debian.ini')

	def get_actions(self, thefile):
	    """return actions provided by this plugin for the give file"""
	    return []


    def register(registry):
	"""register plugins from this module to the plugins registry"""
	registry.register(DebianPlugin())

Quelques points à noter :

* l'attribut *name* est utilisé pour référencer le plugin, on le voit
  notamment apparaître dans l'arbre de configuration des plugins.

* l'attribut *mimetypes* donne une liste de type MIME que ce plugin
  sait gérer (c'est à dire qu'il est susceptible de fournir des
  actions pour les fichiers de ce type). On s'intéresse ici uniquement
  aux paquets debian (.deb) et aux fichiers .changes et .dsc.

* ici on surcharge le constructeur pour donner un nom de fichier de
  configuration qui sera utilisé pour charger / sauver la configuration
  du plugin (mais on reparlera de ça plus tard...)

* la méthode *get_actions* est un des points d'entrée principal du
  plugin:

  * elle prend un objet oobrother.oobrowser.FileWrapper en argument

  * elle retourne une liste d'actions possibles sur cet objet, qui sera
    insérée dans le menu déroulant

  * elle est appelée uniquement sur des fichiers dont on supporte le
    type MIME

  * ici elle retourne une liste vide, donc notre plugin ne va pas être
    très utile...

* la fonction *register* est nécessaire pour que notre module soit
  automatiquement chargeable par OOBrother. Elle est appelée avec le
  registre de l'application en argument afin de fournir au module
  l'occasion d'enregistrer les plugins qu'il contient. Nous enregistrons
  donc ici une instance de notre plugin auprès du registre.

Sauvez ça dans un fichier **debian.py** dans le sous-répertoire
correspondant au package *oobrother.plugins*: ce plugin sera
automatiquement chargé et enregistré au démarrage l'application à 
condition d'ajouter "oobrother.plugins.debian" à la liste des plugins à
charger (Affichage -> Préférences -> Main). Vous pouvez mettre ce
module n'importe où tant que vous mettez le nom de module qui va bien
dans vos préférences. 


Fournir des actions
-------------------

Bon ben c'est pas tout ça mais faudrait lui faire faire quelque chose
à ce truc là. On voudrait :

* pouvoir lancer **debc** sur les fichiers .changes en utilisant la
  console que nous fournit OOBrother pour afficher le résultat. 

* pouvoir lancer **lintian** sur les fichiers .deb ou .dsc et parser
  la sortie pour affichier les résultats dans une table configurable.

Commençons donc par fournir l'implémentation de *get_actions* qui va
bien, avec une implémentation de ces actions utilisant la console pour
les deux : ::

    def get_actions(self, thefile):
        """return actions provided by this plugin for the give file"""
        actions = []
        if thefile.basename.endswith('.changes'):
            actions.append( ('show content', self.cb_debc) )
        else:
            actions.append( ('lintian', self.cb_lintian) )
        return [('debian', actions)]

    def cb_lintian(self, menuitem, thefile):
        """execute lintian"""
        self.execute_in_console('lintian %s' % thefile.abspath)
        
    def cb_debc(self, menuitem, thefile):
        """execute debc"""
        self.execute_in_console('debc %s' % thefile.abspath)

On voit ici qu'une "action" est un tuple à 2 éléments : le nom de
l'action suivi soit d'une fonction de rappel, soit d'une liste
d'action (auquel cas ces actions vont s'insérer dans le sous-menu
correspondant au nom donné). La fonction de rappel prend 2 arguments :
le widget correspondant à l'élément de menu sur lequel l'utilisateur a
cliqué et l'instance de FileWrapper sur lequel éxécuter l'action.

Ici nos fonctions de rappels sont les méthodes cb_lintian et cb_debc
qui utilisent toutes les deux la console de l'application pour éxécuter
une commande shell (la méthode *execute_in_console* est un raccourci
fourni par la classe de base du plugin).

Si vous avez enregistré votre plugin après avoir effectué ces
modifications et que vous relancez OOBrother, vous devriez voir ces
actions apparaître et vérifier leur fonctionnement.


Affichage de l'analyse fournie par lintian
------------------------------------------
Les messages affichés par lintian ont un format simple : ::

  type de message:package:message

On voudrait afficher tout ça dans une fenêtre spéciale contenant une
liste à 3 colonnes. Très sympatiquement, OOBrother fournit une classe
qui nous mache le travail, la magique ConfigurableListWindow
:). Allonzi  pour le code de cette fenêtre : ::

    import commands
    import gobject
    from oobrother.uiutils.windows import ConfigurableListWindow

    class LintianReportWindow(ConfigurableListWindow):
	"""display lintian report in a 3 columns list (configurable)"""
        name = 'lintian'

	def __init__(self):
	    ConfigurableListWindow.__init__(self, 'lintian.ini',
					    (gobject.TYPE_STRING, # type
					     gobject.TYPE_STRING, # path
					     gobject.TYPE_STRING, # message
					     ),
					    ('Type', 'Package', 'Message'),
					    )
	    self.init_columns(self.cfg['columns'], (0, 0, 0))

	def lintian(self, thefile):
	    """check the given wrapped package using checkpackage in a safe
	    environment
	    """
	    self.set_title('lintian: %s' % thefile.abspath)
	    self.clear()
	    self.show()
	    output = commands.getoutput("lintian %s" % thefile.abspath)
	    for line in output.splitlines():
		self.append_line( line.split(':', 2) )
    
Il nous suffit donc d'hériter de cette classe, de lui donner en
argument de son constructeur le nom de fichier où sauver cette
configuration et enfin une description du store et des titres de
colonnes utilisés pour configurer le tree view. L'attribut *name* va
être utilisé pour l'onglet de configuration de cette fenêtre.
L'appel de la méthode *init_columns* va initialiser la vue en fonction
des colonnes à afficher (le 2eme paramètre permet de configurer les
méthodes de tri pour les différentes colonnes).

Le lecteur attentif aura remarqué que je parle de configuration et se
sera demandé ce que ça vient faire là... Et oui ! C'est la que ça
devient magique, car vous avez ici une fenêtre dont les colonnes à
afficher sont configurables sans que vous n'ayez rien d'autre à faire
que de donner accès à cet objet configurable. En gros donc, de le
brancher avec notre plugin. Il faut pour cela modifier le callback
*cb_lintian* pour qu'il utilise notre fenêtre et faire que cette fenêtre
soit intégrée dans le système de configuration. C'est parti : ::

    def __init__(self):
        AbstractPlugIn.__init__(self, config_file='debian.ini')
        self.lintian_window = LintianReportWindow()
        
    def cb_lintian(self, menuitem, thefile):
        """execute lintian"""
        self.lintian_window.lintian(thefile)
        
    def subconfiguration(self):
        """return subconfiguration objects if any"""
        return [self.lintian_window]

Et voilà. Pour l'intégration de la configuration, il a suffit de
retourner la fenêtre dans la méthode *subconfiguration()* de notre
plugin, qui retourne une liste vide par défaut. Si on relance
l'application on doit pouvoir maintenant configurer notre fenêtre en
passant par Préférences -> plugins -> debian -> lintian.


Gestion de configuration
------------------------
On pourrait s'arrêter là... Mais je voudrais pouvoir dire à lintian
d'éxécuter toutes ses vérifications ou alors seulement quelques
unes. Facile : ::

    class LintianReportWindow(ConfigurableListWindow):
	"""display lintian report in a 3 columns list (configurable)"""
        name = 'lintian'
	options = (
	    ('all-checks', {'type': 'yn', 'default': True}),
	    ('checks', {'type': 'csv', 'default': ()}),
	    )

	def lintian(self, thefile):
	    """check the given wrapped package using checkpackage in a safe
	    environment
	    """
	    cmd = 'lintian'
	    if not self.cfg['all-checks']:
		cmd = 'lintian %s' % ','.join(self.cfg['checks'])
	    self.set_title('lintian: %s' % thefile.abspath)
	    self.clear()
	    self.show()
	    output = commands.getoutput("%s %s" % (cmd, thefile.abspath))
	    for line in output.splitlines():
		self.append_line( line.split(':', 2) )

Maintenant je peux configurer via les préférences le comportement de
lintian (la classe ConfigurableWindow utilise l'attribut *options*
pour construire sa configuration). Bien entendu la configuration est
sauvegardée et rechargée automatiquement au besoin.



Le plugin complet
-----------------
Voir le fichier sample_plugin.py_ dans ce répertoire.


Aller plus loin
---------------
Gestion des répertoires, interaction avec l'éditeur de texte, logger
des informations...


.. _sample_plugin.py: sample_plugin.py