Les chaussons Ruby I

NDT: Cet article est une traduction imparfaite du texte de _why the lucky stiff, que l’on peu trouver ici. Tout le crédit en revient a son prolifique auteur.

Porter des chaussons “Ruby” pour travailler

Ceci est la première partie d’une série destinée à nous faciliter la vie grâce à Ruby dans les plus sévères conditions. J’espère qu’à la fin de cette série, vous tiendrez autant à Ruby qu’à votre jambe. Et votre travail ne peut pas vous empêcher d’emmener votre jambe avec vous, au bureau.

La prochaine fois que quelqu’un désapprouvera votre utilisation de Ruby criez juste “VOUS NE POUVEZ PAS M’ENLEVER MA JAMBE!!”. Je vais sans doute créer une hotline pour ces abus de bigoterie.

Maintenant, sérieusement. Sérieusement. Vous ne pouvez pas utiliser Ruby au travail, n’est-ce pas ? Vous détourneriez le projet ! Vous n’édictez pas les règles, n’est-ce pas ? Vous ne voulez pas être un arriviste, n’est-ce pas ?

Non, nous ne sommes pas assez fou pour prendre d’assaut le château. Nous allons juste enfiler tranquillement nos chaussons “Ruby”sur nos pieds. Regardez une seconde.

Les chaussons qui traînent les pieds (Utiliser Dri.glob et File#grep)

Il n’y a pas si longtemps un ami m’a demandé comment chercher une chaîne de caractères de façon récursive dans ses scripts PHP. Ses répertoires contenaient beaucoup de fichiers binaires et de templates, qui auraient vraiment gêné un grep. Comme je ne trouvais pas de moyen simple d’utiliser grep pour ce problème, j’ai pensé qu’utiliser find et grep ensemble serait le meilleur moyen.

Je suis arrivé à ça :

 find . -name "*.php" -exec grep 'search_string' {} \; -print 

Je ne suis pas très fort à enchaîner ensemble des commandes shell. C’est amusant et stimulant sur le moment, mais souvent je cherche sur les man pages pendant un moment et parfois je rêve de mieux connaître sed. Je ne peux pas y arriver d’un seul trait à chaque fois. Et vous ?

Mais pourquoi voudrais-je connaître sed mieux que je ne connais Ruby ? Et bien, si je commençais à formuler mes enchaînements de commandes shell en Ruby, alors je serais capable d’être encore plus expressif si j’en avais besoin.

Voilà la recherche précédente retravaillée en Ruby:

 Dir['**/*.php'].each do |path|
   File.open( path ) do |f|
     f.grep( /search_string/ ) do |line|
       puts path, ':', line
     end
   end
 end

Je pense que Ruby a beaucoup de façons simples de s’occuper du système de fichiers. Pour moi, Dir::glob en est un énorme. Au dessus, j’ai utiliser un court-circuit pour Dir::glob appelé Dir::[]. L’expression utilisée entre les [] peut contenir quelques caractères spéciaux: ? pour matcher n’importe quel caractère, * pour matcher n’importe quel ensemble de caractères dans un nom de fichier, * pour matcher n’importe quel ensemble de caractère dans un path complet ( ce qui veut dire que * est récursif).

Dir::glob renvoie un tableau (objet Array) contenant les noms de fichiers qui matchent l’expression. Aussi je parcours la liste des fichiers, et pour chacun l’ouvre et regarde si une de ses lignes matchent l’expression (/search_string/).

Notez la ligne f.grep. La classe File a une méthode each, qui itère sur chaque ligne d’un fichier texte. La classe File a aussi le module Enumerable inclus (mix-in). Ce mix-in fournit la méthode grep, qui va tester chacune de ses lignes avec une expression régulière, et exécutera le block pour chaque ligne matchant.

Votre première réaction peut être: “Bien, c’est quand même plus verbeux que l’original. Et je ne peux que le reconnaître. “C’est beaucoup plus simple a étendre”, répondrai-je. Et cela marche sur toutes les plateformes.

La morale est: Utilisez vos chaussons Ruby pour élégamment jouer avec vos fichiers.

Les chaussons qui nettoient (Utiliser FileUtils et Find)

OK. Bon, j’ai utilisé Ruby pour gérer n’importe quelle commande shell qui nécessite plus d’un seul pipe. Et bien sur, les scripts shell sont interdits.

J’ai besoin de commiter un répertoire mal rangé dans CVS. Plein de répertoire Old et Poubelle. Et des images que je ne veux pas incorporer à CVS. J’ai commencé par effacer des fichiers, mais cela me prenait tant de temps que j’ai décidé d’écrire un script qui scannerait un répertoire, et copierait les “bons” fichiers dans un nouveau répertoire.

require 'find'
require 'fileutils'

include FileUtils::Verbose
from_dir = File.expand_path( ARGV[0] ) + "/" 
to_dir = File.expand_path( ARGV[1] ) + "/" 

makedirs( to_dir )
chdir( to_dir ) do
  Find.find( from_dir ) do |path|
    fname = path.sub( from_dir, '' )
    fdir, base = File.split( fname )
    if FileTest.directory? path
      Find.prune if base[0] == ?. or base =~ /^(?:CVS|images|Old|Trash|delme|bak)/i
      makedirs( fname )
    elsif FileTest.file? path
      unless base =~ /(?:\.jpg|\.jpeg|\.pdf|\.gif|\.psd|~)$/
        copy path, fname
      end
    end
  end
end

Quelques bons modules travaillent ensemble ici. Je commencerais par FileUtils car c’est le plus simple. Le module FileUtils fournit simplement la plupart des commandes orientées fichiers que l’on peut avoir à n’importe quel prompt. Dans le script précédent, vous pouvez voir copy et chdir (aka cd) utilisés.

Toutefois j’ai inclus la version FileUtils::Verbose du module, qui est parfaite pour le scripting shell. Cette version du module affiche sur le terminal le texte complet des opérations. Ainsi vous pouvez voir ce qu’il se passe quand vous lancez le script.

J’utilise aussi le module Find, qui traite chaque fichier sous un répertoire donné de façon récursive. Vous pouvez dire a find d’éviter de chercher dans un répertoire en utilisant la commande Find.prune. C’est exactement ce que je fais pour être sûr que la copie saute les répertoires Old, Trash .. Notez que je saute aussi les répertoires commençant par un point.

Ces modules sont livrés avec Ruby 1.8, donc c’est tout bénef. Si vous avez installé 1.9 (ou si Ri marche sur 1.8 dans votre version), vous pouvez alors obtenir les documentations de ces modules avec: ri Find et ri FileUtils.

Aussi je dis: Utilisez vos chaussons-Ruby pour nettoyer. C’est comme si vous aviez des brosses sous les pieds.

Les chaussons qui mergent

Finalement j’ai finit par en faire un peu plus que le script précédent. Voyez vous, chacun de ces répertoires représente une façon manuelle de faire du versioning. Aussi, je veux non-seulement nettoyer le répertoire, mais aussi le merger dans une version checkoutée d’un module CVS.

require 'find'
require 'fileutils'

include FileUtils::Verbose
checkout_dir = File.expand_path( ARGV[0] ) + "/" 
add_dir = File.expand_path( ARGV[1] ) + "/" 

checked = {}
chdir( checkout_dir ) do
  # 1. comparer les répertoires, copier les fichiers manquant ou updatés.
  #  sauté les fichiers binaires, sauter Old/Trash/delme/bak
  #  et renommer.htaccess en htaccess.sample.
  Find.find( add_dir ) do |path|
    fname = path.sub( add_dir, '' )
    fdir, base = File.split( fname )
    if FileTest.directory? path
      Find.prune if base[0] == ?. or base =~ /^(?:CVS|images|Old|Trash|delme|bak)/i
      unless File.exists?( fname ) or fname.empty?
        mkdir fname
        `cvs add #{ fname }`
      end
    elsif FileTest.file? path
      base_to = base.dup
      base_to = "htaccess.sample" if base_to == ".htaccess" 
      unless base =~ /(?:\.jpg|\.jpeg|\.pdf|\.gif|\.psd|~)$/
        path_to = fdir == "." ? base_to : File.join( fdir, base_to )
        unless File.exists? path_to
          copy path, path_to
          `cvs add #{ path_to }`
        else  
          copy path, path_to
        end
        checked[path_to] = true
      end
    end
  end

  # 2. demander l'effacement CVS des fichiers manquants.
  Find.find( checkout_dir ) do |path|
    fname = path.sub( checkout_dir, '' )
    fdir, base = File.split( fname )
    if FileTest.directory? path
      Find.prune if base[0] == ?. or
                    base == "CVS" 
    elsif FileTest.file? path
      unless checked[fname]
        rm fname
        `cvs rm #{ fname }`
      end
    end
  end
end

Vous voyez plusieurs utilisations semblables de Find et de FileUtils dans ce script. Mais j’ai aussi besoin d’envoyer des commandes à CVS. Aussi, j’ai juste utilisé les backticks pour sortir dans un shell, ce n’est pas interdit. J’ai FileUtils parce qu’il semble joli et qu’il imprime ce qu’il fait sur le terminal.

Un peu

OK. Bon on a eu un peu de Ruby aujourd’hui. C’est le petit coup d’optimisation des taches répétitives. Ce n’est pas Ruby partout. Mais il a sa place.

Ruby nous a évité un peu de travail manuel aujourd’hui. Dans quelques semaines, je vous montrerait comme vous pouvez utiliser Ruby pour gérer votre travail et vos taches.