Entropie anthropique

Aller au contenu | Aller au menu | Aller à la recherche

lundi 26 mai 2008

Imbriquer les templates ERB "à la :render chez rails"

Parfois on a envie de pouvoir profiter du templating ERB fourni dans Ruby pour se faire des bouts de codes, ou pour toutes autres raisons, toutefois, pour ceux que ça intéresse, je les laisse évaluer un template.run dans un autre template ERB pour voir le résultat. Pour s'en sortir, il va falloir utiliser les bindings Ruby. Il y a peut-être une autre façon de faire avec les obscurs "_erbout", mais il y a peu de documentation là dessus. Aussi, devant le défi et le travail sur les bindings, je me suis laissé tenté.

A noter que pour certains j'aurai l'impression de réinventer la roue, je ne suis pas allé vérifier dans la touffe de code de Rails s'ils utilisaient la même technique.

Tout d'abord, et car j'ai la flemme de faire un bel article avec toutes les étapes qui m'ont conduit à cette solution, il n'y aura qu'un gros bloc de code avec la classe et l'exemple (certains auront l'habitude). La classe est nommée Renderer et je n'ai défini que des méthodes de classe. On pourrait faire autrement avec des instances d'une classe, mais bon, ça ne me semble pas le point essentiel.

Le point essentiel est le binding dans Ruby. Un binding, c'est une référence au contexte d'éxecution d'un bout de code. Ainsi, passer un binding permet d'avoir accès à un contexte d'éxécution autre que le langage seul le permet. C'est ce qu'on passe à ERB pour qu'il sache à quoi correspond quel nom de variable, même si le fichier est évalué dans un objet différent.

Le but étant de passer dans un Hash le nom des variables de l'intérieur du template, et de les convertir en ce qui est pointé par la clé, on sent déjà venir le eval. Pour faire simple je veux pouvoir utiliser un sous-template dans mon template en faisant

<%= render(:template_1,{'x' => 42}) %>

Je vais donc itérer sur les élements du hash, la clé me servira de variable à déclarer, et la valeur sera justement la valeur pointée. Je dois en outre éviter de déclarer une variable qui écraserait, soit une méthode, soit une variable de mon algorithme de transformation lui même (ce serait la catastrophe). Pour rendre cette probabilité plus faible, j'ai donc utilisé des underscores, vu que je serai le seul à utiliser mon système de templates, je sais la convention qui est ne pas définir une variable "__trucmuche__" dans les paramètres.

L'élément binding me permet de pouvoir itérer à l'intérieur du hash, et de faire mes eval dans le bloc passé à each et en faisant survivre hors du bloc les valeurs créées par eval.

Après ces mises en garde, je dois ajouter que j'aurai pu faire des tests et générer une exception, mais pour laisser l'importance à l'algorithme, je ne l'ai pas fait. De même, j'ai effectué un appel à eval par paire clé/valeur, mais on aurait pu faire un seul appel en créant une chaîne plus longue (et limitant un peu plus les effets de bords des écrasements de variables)

Pour finir, l'évaluation d'un template ERB se fera avec le binding passé à eval. C'est à dire qu'en cas de valeur manquante, l'interpréteur ruby cherchera une méthode et lévera une erreur. Je définis donc method_missing afin d'éviter le carnage si d'aventure on veut pouvoir rendre optionnel une des variables du template.

Bon, j'espère ne pas vous avoir assommé. Voici le code, comme d'hab questionnez si vous le voulez. En tout cas je n'ai pas cherché à savoir ce que donnerait un template qui sortirai du code à son tour interprétable par ERB, pour générer un template de template de template de ... (je doute que ça marche "out of the box")

require 'erb'

class Renderer
    @@partials = {}

    def self.add_partial(name,str="")
        @@partials[name] = str
    end
  
    def self.render(__name__,__params__=nil)
        __b__ = binding
        __params__.each_key do |k|
            __str__ = %{#{k} = __params__['#{k}']}
            puts __str__ if $DEBUG
            eval  __str__ , __b__
        end
        ERB.new(@@partials[__name__]).result __b__
    end

    def self.method_missing(m,*args)
        puts "missing params or method: #{m}, nil-ed out" if $DEBUG
        nil
    end
end

#simple example
t1 = %q{
    str1 : <%= x %>
}

#calling another partial in a partial
t2 = %q{
str2:
<% values.each do |value| %>
    <%= render(:first,{'x' => value})%>
<% end %>
}

#now test missing functions
t3 = %q{
<%= str.class %>
<% unless params.nil? %>
    there are some params
<% end %>
}

Renderer.add_partial(:first , t1)
Renderer.add_partial(:second, t2)
Renderer.add_partial(:third , t3)

puts Renderer.render(:first , {'x'=>0})
puts Renderer.render(:second, {'values'=>(0 .. 10)})
puts Renderer.render(:third , {'str'=>Array.new})
puts Renderer.render(:third , {'str'=> "ok", 'params'=>Array.new})

samedi 24 mai 2008

Wmii moi itou

Bon, et bien, dans ma quête du tuning de distribution, j'ai essayé Wmii, les concepts de design correspondent mieux à ce dont j'ai besoin que les mamouths du window management, à savoir: des fenêtres qui prennent toute la place, quand elles en ont besoin seulement, pilotable au clavier et facilement scriptable.

Par exemple, pour voir combien de jus il reste dans la batterie, plutôt que de regarder l'uptime, j'utilise

echo -n $(cat /proc/acpi/battery/BAT1/state | grep remaining | sed 's/.*:\s*//') '|' $(date)

à l'intérieur de la fonction status() .

dimanche 18 mai 2008

Evaluation des arguments multiples et des blocs avec l'héritage en Ruby

Ça me servira à la fois de piqûre de rappel quand j'en aurais besoin, et ça peut aider à éclaircir les idées. On oubliera pas de ne pas oublier (oui oui, tout ça, j'insiste) que l'appel à super récupère lui-même le bloc. Ce peut être gênant si on a besoin de faire un yield dans la classe fille en même temps qu'on a besoin d'appeler super. Ceci rend plus difficile le changement de comportement d'une méthode qui detruit des informations quand on lui passe un bloc (par exemple l'ouverture/fermeture automatique des fichiers) : il faut faire son yield à l'intérieur d'un nouveau bloc, selon ce qui passe dans les paramètres du bloc ça peut être plus ou moins facile.

Au niveau des arguments multiples, on oubliera pas de ne pas oublier la différence entre *args et args. Il faut voir ça comme un pointeur sur un objet Array. Également, la classe d'un objet, même quand dans la méthode parente est celle de l'objet hérité, ça pourrait jouer des tours si on fait des tests avec == au lieu de is_a? (je n'ai pas mis dans le test, mais is_a? teste l'appartenance d'un objet à une classe et toutes les classes parentes.

Voici le code qui m'a servit pour tout mettre au clair :

class Test
    def initialize(*args,&block)
        puts args.size
        puts self.class
        yield self if block_given?
    end
end

class SubTest < Test
    def initialize(*args)
        super(*args)
        yield self if block_given?
    end
end

class SubSubTest < SubTest
    def initialize
        super(:un,:dos,:tres) {|l| puts "truc"}
        yield self if block_given?
    end
end

class SubSubSubTest < SubSubTest
    def initialize
        super() do |l|
                puts "sub-truc"
                yield self
        end
    end
end

Test.new(1,2) {|l| puts l.class}
SubTest.new {|l| puts l.class}
SubSubTest.new {|l| puts l.class}
SubSubSubTest.new {|l| puts l.class}

Et voici le résultat brut de décoffrage :

2
Test
Test
0
SubTest
SubTest
SubTest
3
SubSubTest
truc
truc
SubSubTest
3
SubSubSubTest
truc
truc
sub-truc
SubSubSubTest

On notera que le "puts self.class" dans la classe principale rajoute une occurence du texte, j'aurais pû mettre ici (ou équivalent plus bas dans la hierarchie) un "if self.class == Test" ou un "if self.is_a? Test" pour montrer la différence entre les deux.

Si vous avez des questions, questionnez, mais je ne promets rien pour les réponses.

vendredi 2 mai 2008

Kylie Minogue décorée par Albanel

Et oui, Lundi 5 Mai 2008, Kylie Minogue recevra l'insigne de "Chevalier dans l'ordre des Arts et des Lettres". Consécutivement à d'autres artistes émanants de l'industrie pure et dure (Bob Dylan, Meryl Streep et Uma Thurman). A quand Madonna ou Britney Spears pour leur passion des enfants? Bienvenue à la Cour.

http://news.bbc.co.uk/2/hi/entertainment/7380789.stm http://www.culture.gouv.fr/culture/actualites/index.htm