La magia de los bloques en Ruby y lo que molan Hpricot y Ser Vago (tm)
Un truquito pequeño y rápido, que igual le vale a alguien para algo, y su explicación, que probablemente le valga a más gente.
Un compañero me preguntó si había alguna forma automática de hacer la misma edición a unos cuantos archivos HTML (se trataba de borrar un div que tenían todos en la cabecera). Que si era una cosita que no llevara trabajo guay, que si no lo hacía a mano. Esas palabras me hicieron pensar que se trataría de unos 15 o 20, por lo que claramente merecía la pena. Cuando después de prepararlo lo pusimos en marcha resultó que eran más de 500. [Nota mental: ponerle nombre al número de cosas iguales y repetitivas que estás dispuesto a hacer antes de automatizar, y usarlo como índice de vagancia].
Como es algo que en un mundo lleno de maquetadores es medio normal, tratamos de hacer algo genérico y reutilizable, y aquí está.
Ingredientes:
- Hpricot: una librería muy chula que sirve para parsear y modificar HTML (o XML ya que estamos). La interfaz es preciosa ya que además de otro tipo de formas de buscar en el HTML, soporta selectores CSS, así que las tareas simples son triviales, y las complejas, simples

- Los bloques, una de las features para mí más chulas de Ruby como lenguaje, que pasaremos a explicar ahora, y que dan a este sistema lo que tiene de genérico y reutilizable
Estoy seguro de que hay un montón de lenguajes que soportan bloques o cosas parecidas. Lo que mola de los bloques en Ruby es que la interfaz es muy limpia, y puedes usarlos casi sin darte cuenta.
Un bloque es un tipo de parámetro (que ha de ser el último, e ir marcado con &) que se puede pasar a un método, y que consiste en un fragmento de código, que el método podrá ejecutar cuando desee (con yield). De esta forma se puede flexibilizar un montón los métodos, dejando parte de la implementación a cargo del que llama (no del que define).
Como toda definición, es un poco críptica y seguro que se entiende mejor con un ejemplo:
def rodear_de_asteriscos(&block) puts "*********" yield puts "*********" end rodear_de_asteriscos do puts "Esta parte la decido yo" end
Os hacéis idea de lo que hace, ¿no?
Este ejemplo es muy sencillo, simplemente ejecuta el código, una vez e incondicionalmente, pero podríamos hacer lo que quisiéramos, como eso, poner condiciones, ejecutarlo varias veces, o pasarle parámetros (ahora vemos la sintaxis para hacer esto).
En nuestro caso lo que queríamos era iterar sobre una lista de archivos, e irlos abriendo uno por uno, realizar ciertas modificaciones en cada uno de ellos, y guardarlos. Eso de realizar ciertas modificaciones es carne de bloque, porque lo normal es que quien sepa qué modificaciones se han de realizar sea quien llama al método (nuestro amigo maquetador que hace sus pinitos programando), y no quien lo define (yo).
Así que ésta es la implementación:
require 'rubygems'
require 'hpricot'
def update_files(glob_expression, &block)
Dir.glob(glob_expression).each do |file|
puts file
doc = open(file) { |f| Hpricot(f) }
yield(doc)
open(file, File::TRUNC|File::RDWR) do |f|
f.write(doc.to_html)
end
end
end
Como dijimos, en primer lugar buscamos los archivos que concuerden con la expresión que nos pasen como primer parámetro (cosas como *.html), e iteramos por esa lista. En cada iteración, abrimos el archivo y creamos un objecto Hpricot con el contenido, y después, ejecutamos el bloque pasándole como parámetro el objeto Hpricot. De esa forma, en la llamada al método, nuestro usuario podrá escribir código usando ese objeto. Finalmente, volveremos a abrir el archivo, esta vez para escribir en él el contenido del objeto Hpricot una vez ejecutado el código del bloque (que supuestamente lo modificará).
¿Muy complicado otra vez? Así sería un ejemplo del uso de este método:
update_files('public/*/themes/*/example/example.html') do |doc|
doc.search("div#theshaker_headercommunity_logo").remove
end
De esta forma nuestro script diábólico que busque y abra archivos y los modifique está separado claramente en dos partes: de una, la lógica de la búsqueda, apertura, etc. (que será siempre igual y que por tanto podemos extraer a una librería), y de otra, la lógica de la modificación, que sí que será diferente cada vez.
Como ejercicio para el lector, hacer otra versión que no use Hpricot sino expresiones regulares, para que sirva para cualquier tipo de archivo (vamos, lo que es sed -i).




Luis Villa dijo
Qué manía de despreciar algo tan sano como es la búsqueda de la eficiencia... y así es como llaman a la "vagancia" los amargados que no consiguen salir de la rueda de hamster en la que están metidos y se rinden.
Repite conmigo el siguiente adagio: "La Eficiencia es la hermana inteligente de la Pereza"
28 Noviembre 2007 | 04:36