Mostrando entradas con la etiqueta comandos. Mostrar todas las entradas
Mostrando entradas con la etiqueta comandos. Mostrar todas las entradas

miércoles, 18 de septiembre de 2013

Las murallas infranqueables de vim



En estos días he tratado de mejorar mis habilidades con vim porque, honestamente, no paso de ser un principiante. Lo digo basado en lo que pude aprender en este video grabado en una conferencia europea de Ruby On Rails. Hay varios mensajes claros. En general es un hecho que el desempeño de un programador aumenta muchísimo a medida que le resulta intuitivo el uso de los diferentes comandos de vi, especialmente si se construye un .vimrc que permita reducir el número de teclas utilizadas para realizar las acciones más comunes. Es por eso que no es una ciencia exacta. Algunas personas hacen ciertas cosas más frecuentemente que otras. Pero esto no basta, hay que coneguirse un buen teclado. Empiezo a pensar que los teclados que traen las computadoras y laptops frecuentemente no son apropiados para los programadores. Interesado en estos temas hice mis búsquedas y descubrí las respuestas de varios misterios.

Las teclas hjkl

Son disímeles las reacciones de las personas cuando conocen que en vi los movimientos del cursor se hacen con las teclas h (izquierda) , j (abajo), k (arriba), l (derecha). Algunos no se explican semejante barbaridad. Otros (casi siempre simpatizantes de Unix y/o GNU/Linux) inicialmente piensan que fue una decisión inteligente para disminuir el desplazamiento de las manos. Puede ser que haya algo de esto, quién sabe. En realidad el caso es que Bill Joy creó el editor de textos vi utilizando la terminal ADM-3A. Si observan la figura se podrán dar cuenta de que en ese (antiguo) aparato el desplazamiento se hacía con esas teclas. Más allá de que fuera una decisión inteligente o no, el comportamiento de vi fue algo natural (para la época). Después llegó vim y el resto es historia ... ;)

Teclas hjkl de ADM-3A

A continuación una foto de cuerpo entero de la terminal dónde se creó vi

Terminal ADM-3A de Lear Siegler

A continuación aparece el teclado completo. Fíjense en la posición de la tecla ESC y se darán cuenta de las razones por las cuales se utiliza para cambiar de modo.

Teclado completo ADM-3A

La huella de este terminal no parece limitarse a estos hechos. Si se presta atención a la tecla Home en la esquina superior derecha quizás comprendamos porqué es que la carpeta home de los usuarios de Unix (GNU , ...) es referenciada con el símbolo ~.

No son pocos los detalles interesantes relacionados con vi. Es un editor de texto que no deja de sorprenderme. A todos los interesados en conocer todos los secretos de vim les respondo parafraseando un poema de Nogueras

(...) vi tiene sus arcanos
vi tiene sus secretos
vi tiene sus «comandos infranqueables» (...)

viernes, 14 de junio de 2013

Tutorial de LD_PRELOAD, segunda parte

man ld.so

Un artículo previo mostraba cómo remplazar una función de la librería estándar de C con una versión personalizada. Esta nota explica lo que es preciso hacer para invocar la función original desde la nueva función.

Comencemos por recordar el ejemplo presentado anteriormente. Todo consistía en un programa llamado prog.c que invocaba la función fopen.


#include <stdio.h>

int main(void) {
    printf("Calling the fopen() function...\n");

    FILE *fd = fopen("test.txt", "r");
    if (!fd) {
        printf("fopen() returned NULL\n");
        return 1;
    }

    printf("fopen() succeeded\n");

    return 0;
}

¿Cómo hacer entonces una función que pueda remplazar a fopen y al mismo tiempo sea capaz de invocar la función fopen de la libraría estándar de C? Veamos el código de myfopen.c.


#define _GNU_SOURCE

#include <stdio.h>
#include <dlfcn.h>

FILE *fopen(const char *path, const char *mode) {
    printf("In our own fopen, opening %s\n", path);

    FILE *(*original_fopen)(const char*, const char*);
    original_fopen = dlsym(RTLD_NEXT, "fopen");
    return (*original_fopen)(path, mode);
}

Como se puede apreciar la librería exporta la versión personaizada de la función fopen. Prestando atención se puede constatar cómo se obtiene la referencia a la función original pasando el símbolo RTLD_NEXT a dlsym. Es muy importante definir el símbolo _GNU_SOURCE para tener acceso a RTLD_NEXT en dlfcn.h. De esta forma se coninua la resolución del símbolo en cuestión en el orden de búsqueda a continuación de la librería actual. La compilamos de la siguiente manera:

$ gcc -Wall -fPIC -shared -o myfopen.so myfopen.c -ldl

Al ejecutar el comando de ejemplo en cuestión con ayuda de la variable de entorno LD_PRELOAD, esto es lo que se obtiene:

$ LD_PRELOAD=./myfopen.so ./prog
Calling the fopen() function...
In our own fopen, opening test.txt
fopen() succeeded

Este truco es realmente útil para cambiar el funcionamiento de partes del sistema o depurar detalladamente ciertos detalles. Le invito a suscribirse mediante RSS para que no se pierda los próximos artículos de esta serie.

miércoles, 24 de abril de 2013

Breve tutorial sobre LD_PRELOAD

man ld.so

¿Sabía Usted que es posible remplazar por completo las funciones de la librería estándar con copias personalizadas? o.O Este corto artículo trata un tema que, de ser bien utilizado , puede resultar muy poderoso . Podrá conocer lo que se logra con la variable de entorno LD_PRELOAD en GNU/Linux . ¿Qué cosa es ese bicho? No muerde :) . Le invito a seguir leyendo este artículo y suscribirse a este blog mediante RSS para estar informado acerca de temas avanzados de programación y uso de los comandos del shell de los sistemas Unix .

LD_PRELOAD en acción

A modo de ejemplo vamos a cambiar la función fopen y utilizar nuestra propia copia . Lo mismo se puede hacer con otras funciones e.g. printf , scanf , ... Comencemos con un programa simple (prog.c):

#include <stdio.h>

int main(void) {
    printf("Calling the fopen() function...\n");

    FILE *fd = fopen("test.txt","r");
    if (!fd) {
        printf("fopen() returned NULL\n");
        return 1;
    }

    printf("fopen() succeeded\n");

    return 0;
}

En pocas palabras solamente invocamos la función estándar fopen y chequeamos el valor de retorno . Vamos a compilar y ejecutar .

$ ls
prog.c  test.txt

$ gcc prog.c -o prog

$ ls
prog  prog.c  test.txt

$ ./prog
Calling the fopen() function...
fopen() succeeded

Por otra parte compilamos una versión personalizada de la función fopen y la distribuimos por separado :

#include <stdio.h>

FILE *fopen(const char *path, const char *mode) {
    printf("Always failing fopen\n");
    return NULL;
}

Llamamos a este fichero nullfopen.c y lo compilamos como una librería nullfopen.o .

$ gcc -Wall -fPIC -shared -o nullfopen.so nullfopen.c

Ahora ya estamos listos para modificar LD_PRELOAD :

$ LD_PRELOAD=./nullfopen.so ./prog
Calling the fopen() function...
Always failing fopen
fopen() returned NULL

Como se puede constatar se remplazó fopen con una versión que siempre falla . Este truco es muy útil para depurar aplicaciones o remplazar ciertas partes de libc u otra librería que utilice la aplicación . Un uso práctico notable es el comando tsocks , utilizado para dotar las aplicaciones con la capacidad de conectarse a través de un proxy .

Espero tener tiempo pronto y escribir otro artículo acerca de la mecánica interna que hace posible los resultados que se obtienen con la variable LD_PRELOAD . Si está interesado en conocer más acerca de los sistemas Unix le invito a leer otros artículos sobre comandos y suscribirse a este blog mediante RSS para estar al tanto de nuevos temas . Si tiene preguntas no dude en dejar un comentario . Todo es posible simelo pide ...

lunes, 22 de abril de 2013

Cambiando contexto de usuario con sudo y chroot

chroot

Supongamos que desea ejecutar un comando dentro de un chroot como otro usuario. ¿Cómo se hace? En este artículo exploraremos el camino para lograrlo . No es algo evidente . Por tanto iré explicando paso a paso pues lo más importante es familiarizarse con los comandos de los sistemas Unix y entender porqué se hace lo que se hace cuando se hace. Si desea conocer acerca de otros comandos le sugiero leer también otros artículos acerca de comandos Unix . Si este tutorial le resulta interesante le invito a suscribirse a este blog mediante RSS . ¿Listos? ¡ Comenzamos !

Comenzamos echando un vistazo a man chroot . La sinopsis dice que el modo de uso es

$ chroot /new-root-path`

Bien , ya podemos ejecutar un comando dentro de un chroot como root . ¿Cómo hacemos para que cambie de usuario? Para eso está el comando su .

$ chroot /new-root-path su - user

Por ahora todo bien . Ya logramos ejecutar su dentro del chroot como user , pero ... se nos presenta un prompt interactivo . Al leer la documentación de man su la sinopsis dice

$ su - user cmd

¡Perfecto! Combinandolo todo nos queda :

$ chroot /new-root-path su - user sh

Sin embargo todavía nos quedamos dentro de un shell interactivo . ¿Como nos deshacemos del prompt? Al leer man sh aparece la opción sh -c "command args args..." , que lanzará un shell no interactivo y ejecutará el comando con ciertos argumentos . Exaxtmente lo que se buscaba . Combinandolo todo nos queda :

$ chroot /new-root-path su - user sh -c "command args"

Podría parecer que ya está todo bien , pero hay un detalle . Si el usuario tiene un [entorno personalizado] entonces el mismo no se inicializará automáticamente . Hay que hacerlo manualmente .

$ chroot /new-root-path su - user sh -c ". ~/.profile; command args"

Esto hace lo que se pide :

  1. crea un chroot en /new-root-path
  2. ejecuta el comando con ciertos argumentos en ese contexto
  3. devuelve el control a la sesión activa del shell

Pero ... bueno ... como no es aconsejable estar trabajando como root de forma cotidiana , ejecutamos todo esto con sudo

$ sudo chroot /new-root-path su - user sh -c ". ~/.profile; command args"

De esta forma es posible ejecutar ejecutables de fuentes desconocidas y codificar en un entorno seguro .

Si desea conocer otros trucos relacionados con comandos de Unix la inviación esta hecha para que se suscriba a este blog mediante RSS

martes, 10 de enero de 2012

Identificando números primos con expresión regular en Perl

Lista de números primos

Como puede resultar raro que una expresión regular tenga algo que ver con los números primos quiero aclarar algo : este artículo no es una broma. Las  expresiones regulares provienen fundamentalmente de la rama de la teoría de compiladores y son el bloque fundamental de muchos lenguajes de programación. Esto se debe en primer lugar a que son utilizadas por el analizador sintáctico del lenguaje. Además hay librerías para trabajar con expresiones regulares en la gran mayoría de los lenguajes de programación (de alto nivel ;) . Estas expresiones están relacionadas con las  máquinas de estado y algunos  autómatas simples. Otra historia muy diferente son los  números primos, los archi-conocidos guardianes de gran parte de los secretos más celosamente guardados de la  teoría de números. A continuación trato un tema de esos que evidencian las extrañas conexiones entre la informática y las matemáticas y que me dejó sin palabras cuando supe del truco recientemente por primera vez. Antes ya he otras curiosidades similares . Recuerdo ahora por ejemplo el  artículo publicado anteriormente acerca de los  números de Fibonacci. La vida es bella ... y los números y la informática todavía son capaces de sorprendernos. En caso que Usted desee estar al tanto de curiosidades matemáticas semejantes, les invito a  suscribirse mediante RSS.

Los hechos

Para ir directo al grano, la expresión regular es la siguiente :

perl -lne '(1x$_) =~ /^1?$|^(11+?)\1+$/ || print "$_ is prime"'

¿Puede darse cuenta de cómo funciona? Más adelante ofrezco una explicación, pero trate de descifrarlo Usted mismo. A continuación les presento lo que ocurre cuando se ejecuta (es preciso teclear el número que se desea comprobar y pulsar la tecla Enter para que el comando facilite la respuesta ;)

$ perl -lne '(1x$_) =~ /^1?$|^(11+?)\1+$/ || print "$_ is prime"'
1
2
2 is prime
3
3 is prime
4
5
5 is prime
6
7
7 is prime
8
9
10
11
11 is prime
127
127 is prime
1009
1009 is prime
1234577
1234577 is prime

Con números grandes el comando puede tardar un poco en dar una respuesta. Sea paciente ;)

¿Cómo funciona?

En primer lugar el número es convertido a su representación unitaria mediante (1x$_) . Por ejemplo el número 7 es convertido en 1x7, que es 1111111 (o sea 1 repetido 7 veces ;). En términos generales la idea consiste en aplicar posteriormente la expresión regular que aparece en el miembro derecho a esta cadena. Si se encuentra una coincidencia el número es compuesto sino es primo.

Explicaré a continuación cómo funciona la expresión regular. La misma consta de dos partes ^1?$ y ^(11+?)\1+$. La primera reconoce el número 1 y la cadena vacía. Ambos casos se considera que no son números primos. La segunda determina si dos o más 1s repetidos llegan a conformar una parte del número. Si este es el caso entonces el número es compuesto sino es primo.

Dos ejemplos

Veamos cómo es que funciona todo con los números 5 y 4.

La representación del número 5 es 11111. La expresión (11+?) reconoce los dos primeros dígitos (i.e. 11). La referencia \1 se instancia al mismo valor (i.e. 11) y la expresión regular en sí se expande a ^11(11)+$. La misma no puede aceptar cinco números uno, por tanto falla. Sin embargo, como se utilizó +?, se inicia el mecanismo de backtracking y reconoce los tres primeros dígitos (i.e. 111) . La referencia \1 se instancia al mismo valor (i.e. 111) y la expresión regular en sí se expande a ^111(111)+$. Tampoco tiene éxito ese intento. El proceso se repite para 1111 y 11111, que tampoco aporta una coincidencia. Por tanto la expresión regular en el global no devuelve coincidencia y se concluye que el número es primo.

La representación del número 4 es 1111. La expresión (11+?) reconoce los dos primeros dígitos (i.e. 11). La referencia \1 se instancia al mismo valor (i.e. 11) y la expresión regular en sí se expande a ^11(11)+$. En este caso la representación original sí es aceptada por la expresión regular expandida. Por tanto la expresión regular en el global sí devuelve una coincidencia y se concluye que el número es compuesto.

Conclusiones

Debo dejar claro que no tomo el crédito por haber descubierto este truco. Al parecer fue inventado por  Abigail en 1998. Hay que aclarar que el ejemplo mencionado no es exactamente ni una expresión regular (atendiendo a su definición formal) ni tampoco es un método para verificar si un número es primo. Es simplemente algo raro que se puede hacer con Perl. Le invito a  suscribirse a este blog y así descubrir juntos los secretos de la matemática y la informática.

miércoles, 16 de noviembre de 2011

Insertando calendarios de jQuery con Symfony

Calendario de jQuery con plugin sfFormExtraPlugin para Symfony

Recientemente comencé a desarrollar aplicaciones en  PHP con el  framework Simfony. En este corto articulo les narro cómo instalar el  plugin sfFormExtraPlugin y luego facilitarle al usuario la selección de fechas mediante el uso de un calendario (en este caso de  jQuery Datepicker). De más está decir que me ha dado unos cuantos dolores de cabeza , así que espero que este corto tutorial también pueda servirle a todos aquellos que tienen problemas similares al mío. Por el camino, como efecto secundario, también espero que queden claras las razones por las que me gusta  Python, especialmente  las plantillas de Genshi con Django. Espero también que Ustedes con sus comentarios puedan iluminarme el camino que lleva a mejorar la solución que les presento por acá. Les sugiero  suscribirse a este blog mediante RSS si desea estar al tanto de nuevos trucos de programación. Sin más rodeos, let the hacking begin ! .

Pre-requisitos

Formulario estándar de Symfony

Sinceramente hay  muchos tutoriales detallando los primeros pasos en Symfony. Es por esto que no pretendo abordar todos estos pasos. Asumo entonces que ya existe un sitio funcional y muy especialmente una página que utiliza los  formularios de Symfony para ofrecerle al usuario la posibilidad de seleccionar fechas. En otras palabras , ya se tiene una aplicación donde existe una clase TestForm.class.php que utiliza los modelos que brindan acceso a la base de datos; además existen módulos, componentes, y todo lo que sea preciso (... hay muchos caminos para llegar a Roma ;) para obtener una página similar a la que se muestra en la figura.

Instalando el plugin sfFormExtraPlugin

Segun la documentación hay  muchas formas de instalar un plugin de Symfony. De más está decir que Murphy (... mi gran amigo de mil batallas ... Jo jo jo ...) se encargó de que ninguna funcionara. Me explico. Cualquier variante de instalación en línea que incluyera conectarse al  PEAR channel de Symfony siempre terminaba en un adorable mensaje The channel symfony does not support the REST protocol . Por tal razon pasé a la próxima fase : la instalación offline . A continuación les muestro brevemente cómo se hace (basado en  este artículo del sitio My Rant).

Primeramente, edite el fichero config/ProjectConfiguration.class.php y habilite el plugin sfFormExtraPlugin. Le debe quedar algo más o menos así (aunque puede ser diferente si Usted utiliza otros plugins en su aplicación ;) .




class ProjectConfiguration extends sfProjectConfiguration  
{  
  public function setup()  
  {  
    $this->enablePlugins(array('sfDoctrinePlugin', 'sfFormExtraPlugin'));  
  }  
}  

También acostumbro a incluir el plugin en el fichero apps/<appname>/config/settings.yml (aunque sinceramente no he podido constatar la necesidad de hacerlo). Allí sería preciso modificar la directiva enabled_modules y añadir sfFormExtraPlugin , por ejemplo, de la siguiente manera :

enabled_modules:      [default, sfGuardAuth, sfFormExtraPlugin]

Luego proceda a instalar el plugin en cuestión.

$ cd plugins  
$ wget "http://plugins.symfony-project.org/get/sfFormExtraPlugin/sfFormExtraPlugin-1.1.3.tgz"  
$ tar zxvf sfFormExtraPlugin-1.1.3.tgz  
$ mv sfFormExtraPlugin-1.1.3 sfFormExtraPlugin  
$ cd ..  
$ ./symfony plugin:publish-assets  
$ cd web/js  
$ wget "http://code.jquery.com/jquery-1.4.3.min.js"  
$ wget "http://jqueryui.com/download/jquery-ui-1.8.5.custom.zip"  
$ mkdir jquery-ui  
$ cd jquery-ui  
$ unzip ../jquery-ui-1.8.5.custom.zip  
$ mv jquery-ui/css/smoothness ../css  

En este punto ya deberían estar instalados los ficheros del plugin, pero faltan unos detalles. En primer lugar, hay que inyectar las referencias a los ficheros estáticos de jQuery . Por tanto , edite el fichero apps/<app_name>/config/view.yml e incluya los valores mostrados a continuación :

stylesheets:    [main.css, smoothness/jquery-ui-1.8.5.custom.css]  
  
javascripts:    [jquery-1.4.3.min.js, jquery-ui/js/jquery-ui-1.8.5.custom.min.js]  

Ya por último hace falta indicarle a Symfony cuales son los campos para los cuales se ofrecerá la posibilidad de edición mediante un calendario. Edite el fichero del formulario (e.g. lib/form/doctrine/TestForm.class.php ) y edite el método configure añadiendo instrucciones como las que se muestran a continuación.




class TestForm extends BaseTestForm
{
  public function configure()
  {
                $this->widgetSchema['fechainicial'] = new sfWidgetFormJqueryDate();
                $this->widgetSchema['fechafinal'] = new sfWidgetFormJqueryDate();
                $this->widgetSchema['fecharev'] = new sfWidgetFormJqueryDate();
  }
}

Todas las referencias indican que en este punto ya se visualizaría un botón que desplegaría el calendario de jQuery ... pero ...

Cuando Olemis llora Murphy tiembla …

Ejemplo de calendario insertado en formulario

Está de más decir que por alguna razón (... que todavía no he logrado descubrir, así que si la conocen espero sus comentarios con detalles ... ;) no me funciona. Cuando se va a visualizar la página, en mi caso, se muestra el siguiente mensaje Fatal error: Class 'sfWidgetFormJqueryDate' not found in /path/to/project/lib/form/doctrine/TestForm.class.php on line 17 . Sospecho que la causa es que el fichero plugins/.filemap no está correctamente generado (¿ algo que supongo que deba hacer el comando plugin:publish-assets ?). Llego a esa conclusión al comparar los contenidos de mi fichero (mostrado a continuación) con los de otros proyectos que hacen uso del mismo plugin (e.g.  Kakrail)

a:0:{}

Por tales razones me vi obligado a incluir la siguiente línea al principio del fichero lib/form/doctrine/TestForm.class.php. Preste atención al hecho de que la ruta especificada puede cambiar en dependencia del lugar donde se ubique el fichero .php del formulario, pero siempre tiene que resultar en una referencia al fichero plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormJQueryDate.class.php.




require dirname(__FILE__).'/../../../plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormJQueryDate.class.php';

Después de este pequeño detallito, todo funciona. Sin embargo, como ya mencioné anteriormente, me gustaría no tener que incluir esta línea y lograr que se concrete la magia del fichero .filemap ... pero por ahora ¡qué remedio! . De más está decir que espero sus comentarios al respecto.

Agradecimientos

No sería posible concluir este artículo sin agradecer a Yamila , quien se encargó del proceso de creación del sitio de Symfony , los modelos de Doctrine , etc, etc ... y facilitó los enlaces a los  artículos de instalación de los plugins de Symfony, exactamente dos minutos antes de enviarle un ramo de flores a los desarrolladores del plugin y del framework (... y a Murphy ...) .

Conclusiones

Como han podido apreciar es posible enriquecer las interfaces de usuario de sitios implementados con Symfony . En particular se brinda la posibilidad de desplegar un calendario para seleccionar cómodamente fechas. El proceso es ... enriquecedor . La ruta ideal puede fallar en varios puntos del camino , pero al final se logra el objetivo . Recuerde que todo es posible simelo pides ... . Pronto publicaré más artículos sobre temas afines. Si Usted está interesad@ le invito a  suscribirse a este blog mediante RSS . Cualquier sugerencia para mejorar el artículo, correcciones a partes del proceso explicado , preguntas ... pues no dude en compartirlas y dejar su comentario. Podrían ser útiles para que otros resuelvan algún problema y usen eficientemente su tiempo. Hasta pronto

sábado, 15 de octubre de 2011

Tutorial - Eliminando cookies, cache y otros datos privados de navegadores web en Windows

Los 5 grandes navegadores web

En este tutorial utilizaré a  Opera,  Google Chrome,  Mozilla Firefox,  Safari e  Internet Explorer para mostrar con cada uno de ellos cómo eliminar el cache de páginas visitadas, las cookies (e.g. preferencias y sesiones iniciadas en el servidor), y el historial de navegación. Las técnicas aquí mencionadas son específicas para el sistema operativo  Microsoft Windows, pero espero pronto hacer una versión que incluya los detalles para hacer lo mismo en  GNU/Linux y  Mac OS X. Por lo tanto no se asombren si encuentran scripts .bat a lo largo de todo el artículo. Al final también trato el tema de eliminar las cookies específicas de  Adobe Flash Player que son independientes del navegador web.

Google Chrome

 Google Chrome, al igual que  Chromium, guarda la historia, los cookies el cache y los bookmarks en varias bases de datos en el directorio de configuración de aplicaciones del usuario. La ruta en cuestión es C:\Users\<username>\AppData\Local\Google\Chrome\User Data . La manera más fácil de eliminar esos datos es borrar todo lo que esté ahí. El navegador crea todos los ficheros necesarios para empezar desde cero en caso de no encontrar nada allí.


@echo off

set ChromeDir=C:\Users\%USERNAME%\AppData\Local\Google\Chrome\User Data

del /q /s /f "%ChromeDir%"
rd /s /q "%ChromeDir%"

El script primeramente borra todos los ficheros en la carpeta antes mencionada y luego el propio directorio. La opción /q del comando del es para que no se imprima información en la pantalla, mientras que /s es para borrar ficheros y carpetas de forma recursiva y /f se utiliza para obligarlo a que borre ficheros de solo lectura. Las opciones /q y /s del comando rd son similares a las ya mencionadas, solo que en vez de ficheros se borran carpetas.

Mozilla Firefox

 Firefox guarda las cookies, el cache y el historial en dos lugares. Primeramente en el directorio de configuración de aplicaciones C:\Users\<username>\AppData\Local\Mozilla\Firefox\Profiles y, en segundo lugar, en el directorio C:\Users\<username>\AppData\Roaming\Mozilla\Firefox\Profiles. Para eliminar los datos privados se puede proceder a borrar el primer directorio y todas las bases de datos sqlite que se encuentren en el segundo.


@echo off

set DataDir=C:\Users\%USERNAME%\AppData\Local\Mozilla\Firefox\Profiles

del /q /s /f "%DataDir%"
rd /s /q "%DataDir%"

for /d %%x in (C:\Users\%USERNAME%\AppData\Roaming\Mozilla\Firefox\Profiles\*) do del /q /s /f %%x\*sqlite

La primera parte del script es muy similar a la que se explicó anteriormente. El comando for que aparece al final se utiliza para procesar uno a uno los subdirectorios de la carpeta C:\Users\<username>\AppData\Roaming\Mozilla\Firefox\Profiles e ir borrando las bases de datos sqlite que allí aparecen. No es aconsejable borrar completamente esta carpeta porque es allí donde  Firefox guarda las extensiones y sus respectivas configuraciones.

Opera

 Opera también guarda los datos privados en dos lugares diferentes de una forma parecida a Firefox : el directorio de datos de aplicaciones específico para el usuario C:\Users\<username>\AppData\Local\Opera\Opera y la carpeta C:\Users\<username>\AppData\Roaming\Opera\Opera. Si se eliminan los dos directorios se logra el objetivo sin muchos problemas colaterales.


@echo off

set DataDir=C:\Users\%USERNAME%\AppData\Local\Opera\Opera
set DataDir2=C:\Users\%USERNAME%\AppData\Roaming\Opera\Opera

del /q /s /f "%DataDir%"
rd /s /q "%DataDir%"

del /q /s /f "%DataDir2%"
rd /s /q "%DataDir2%"

Apple Safari

El proceso con  Safari es muy parecido al caso de Opera, pero en este caso utilizando los directorios C:\Users\<username>\AppData\Local\Apple Computer\Safari y C:\Users\<username>\AppData\Roaming\Apple Computer\Safari. El script quedaría de la siguiente forma.


@echo off

set DataDir=C:\Users\%USERNAME%\AppData\Local\Applec~1\Safari
set DataDir2=C:\Users\%USERNAME%\AppData\Roaming\Applec~1\Safari

del /q /s /f "%DataDir%\History"
rd /s /q "%DataDir%\History"

del /q /s /f "%DataDir%\Cache.db"
del /q /s /f "%DataDir%\WebpageIcons.db"

del /q /s /f "%DataDir2%"
rd /s /q "%DataDir2%"

Microsoft Internet Explorer

Estoy seguro de que no se sorprenderán cuando les diga que el caso del  Internet Explorer es ... es más complicado :o). La historia de páginas visitadas, los cookies y el cache se encuentra disperso por todos lados, incluyendo el Registro de Windows. A continuación les presento un script que borra toda la información que se encuentra en el sistema de archivos y finalmente en el registro.


@echo off

set DataDir=C:\Users\%USERNAME%\AppData\Local\Microsoft\Intern~1

del /q /s /f "%DataDir%"
rd /s /q "%DataDir%"

set History=C:\Users\%USERNAME%\AppData\Local\Microsoft\Windows\History

del /q /s /f "%History%"
rd /s /q "%History%"

set IETemp=C:\Users\%USERNAME%\AppData\Local\Microsoft\Windows\Tempor~1

del /q /s /f "%IETemp%"
rd /s /q "%IETemp%"

set Cookies=C:\Users\%USERNAME%\AppData\Roaming\Microsoft\Windows\Cookies

del /q /s /f "%Cookies%"
rd /s /q "%Cookies%"

C:\bin\regdelete.exe HKEY_CURRENT_USER "Software\Microsoft\Internet Explorer\TypedURLs"

El ejecutable invocado al final es regdelete.exe. Esta es una aplicación Win32 escrita en C++ que borra cualquier llave del registro, en este caso la historia de navegación. A continuación les muestro el código:


#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <string>
// compile as: mingw32-g++ regdelete.c -o regdelete.exe -mwindows

#define eq(s1,s2) (strcmp((s1),(s2))==0)

int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR cmdLine, int nCmdShow) 
{ 
    if (!cmdLine || !strlen(cmdLine)) {
        printf("Usage: regdel.exe <HKEY> <path to regkey> - be careful not to delete whole registry\n");
        return 1;
    }

    int argc;
    LPWSTR *argv = CommandLineToArgvW(GetCommandLineW(), &argc);

    if (argc < 3) {
        printf("Usage: regdel.exe <HKEY> <path to regkey> - be careful not to delete whole registry\n");
        return 1;
    }

    char **argv8 = (char **)malloc(sizeof(char *) * argc);
    for (int i = 0; i<argc; i++) {
        int len = wcslen(argv[i]);
        argv8[i] = (char *)malloc(sizeof(char)*(len+1));
        wcstombs(argv8[i], argv[i], len+1);
    }

    HKEY hkey;
    if (eq(argv8[1], "HKEY_CLASSES_ROOT")) {
        hkey == HKEY_CLASSES_ROOT;
    }
    else if (eq(argv8[1], "HKEY_CURRENT_CONFIG")) {
        hkey = HKEY_CURRENT_CONFIG;
    }
    else if (eq(argv8[1], "HKEY_CURRENT_USER")) {
        hkey = HKEY_CURRENT_USER;
    }
    else if (eq(argv8[1], "HKEY_LOCAL_MACHINE")) {
        hkey = HKEY_LOCAL_MACHINE;
    }
    else if (eq(argv8[1], "HKEY_USERS")) {
        hkey = HKEY_USERS;
    }
    else {
        printf("Unknown hkey\n");
        return 1;
    }

    HKEY key;
    int status = RegOpenKeyEx(hkey, argv8[2], 0, KEY_ALL_ACCESS, &key);
    if (status != ERROR_SUCCESS) {
        printf("failed opening %s\n", argv8[2]);
        return 1;
    }

    std::vector<std::string> vals;

    for (unsigned int i = 0; ; i++) {
        DWORD size = 1024;
        char val[size+1];
        DWORD type;
        status = RegEnumValue(key, i, val, &size, NULL, &type, NULL, NULL);
        if (status == ERROR_NO_MORE_ITEMS) break;
        if (status == ERROR_SUCCESS) {
            vals.push_back(std::string(val));
            continue;
        }
        printf("failed enumerating %s\n", argv8[2]);
        return 1;
    }

    typedef std::vector<std::string>::iterator vsi;
    for (vsi i = vals.begin(); i != vals.end(); i++) {
        status = RegDeleteValue(key, i->c_str());
        if (status != ERROR_SUCCESS) {
            printf("failed deleting %s\n", i->c_str());
            return 1;
        }
    }

    return 0;
}

Realmente bello el código :o) ... bueno ... El fichero .exe se obtiene después de compilar este fichero con  MinGW o  Microsoft Visual Studio.

Flash Cookies

Ha sido largo el camino hasta acá ... así que mejor me apuro y les hablo de los cookies del plugin de Flash. Estos datos se encuentran en la carpeta C:\Users\<username>\AppData\Roaming\Macromedia\Flash Player\*. La forma más fácil de lograr el objetivo es eliminando, de forma similar a como ya lo habíamos hecho antes, todo lo que se encuentra allí.


@echo off

set FlashCookies=C:\Users\%USERNAME%\AppData\Roaming\Macromedia\Flashp~1

del /q /s /f "%FlashCookies%"
rd /s /q "%FlashCookies%"

Conclusiones

¡Eso es todo! Con estos scripts se pueden eliminar todos los datos personales de navegación. Si quiere estar al tanto de nuevos trucos y temas interesantes, le invito a  suscribirse a este blog. Los scripts mostrados pueden ser refinados. Cualquier señalamiento o sugerencia será bienvenida; espero sus comentarios. Recuerde que todo es posible ... simelo pide ....

lunes, 28 de diciembre de 2009

Tutorial: ¿Cómo migrar desde Subversion (SVN) hacia Mercurial con Hgsvn?

Mercurial SCM

Después de sumarme al desarrollo del plugin de Trac para RPC (gracias a Odd Simon Simonsen ;o) y trabajar en unas mejoras, he decidido migrar mis proyectos anteriores desde Subversion hacia Mercurial. Aquí describiré una alternativa para realizarla. Por el momento no soy un experto, por lo que el análisis de las ventajas y puntos débiles de esta variante quedará pospuesto hasta que pruebe otras alternativas (si es que eso ocurre ...). Sin embargo, Usted puede formar parte de la búsqueda de la solución más adecuada y opinar u ofrecer sugerencias. Si desea saber acerca de cómo compartir código y colaborar utilizando repositorios de Mercurial, le sugiero que consulte un artículo previo sobre el uso de MQ con Bitbucket. Es así cómo se desarrolla el plugin que les mencioné anteriormente, utilizando su repositorio de parches.

Actualizado 23/04/2010 13:43:09 con hgpullsvn

Estado inicial : Subversion

Subversion ha sido por mucho tiempo la opción más difundida para desarrollar proyectos de software libre. De hecho este es el sistema de control de versiones que fue ofrecido en primer lugar por Sourceforge, Google Code y casi todos los otros grandes patrocinadores del open source. Sin embargo el surgimiento primero de GitHub, luego de Bitbucket y otros sitios similares basados en Bazaar, han hecho que los sistemas distribuidos de control de versiones ganen una fuerza tremenda. En gran parte esto se debe a la inmensa gama de modelos de colaboración que son posibles cuando no se tiene un repositorio centralizado. De hecho, desde que comencé a utilizar SVN la sugerencia que nos hacia mi profesor, Alí, era que le echáramos un vistazo a SVK.

En este momento se gestó el proyecto FLiOOPS con una idea original de Kyrie (a.k.a. Luis Alberto Zarrabeitia) para concebir una implementación de la programación por contratos en Python. Lo que una vez fue una tarea de curso, con el tiempo fue creciendo y se incorporaron nuevas ideas. Entre todas ellas se destacó el módulo dutest, que se utilizó para escribir las pruebas de unidad. El aumento en la complejidad, implicó la existencia de varias ramas de desarrollo y otros elementos a los que Subversion no les da una solución. Por ejemplo acá les muestro la estructura actual del repositorio original :

/
    java
        trunk
        branches
        tags
    py
        trunk
        branches
            add_opts
            mdl_inv
            pep302
            test_pycontract
            guarded_synch
            ochk_arch
            pysimrec
        tags
            dutest
                0_1_0
                0_1_1
                0_1_2
                0_2_1
                0_2_2

La buena noticia es que Sourceforge permite también el uso de (múltiples) repositorios de Mercurial y Git. Inicialmente esto hace posible tener un repositorio para cada lenguaje de programación, o para cada librería que se desarrolle en el marco del proyecto. Sin embargo hay que trasladar todas las versiones ya existentes hacia su nuevo destino.

Opciones para migrar

Después de buscar información e indagar un poco, encontré varias herramientas, pero parece que tres se distinguen del resto:

  • HgSubversion es una librería externa cuyo objetivo es lograr una integración completa con Subversion.
  • El comando convert permite migrar repositorios de distintos tipos (incluso otros repositorios hechos con Mercurial) hacia un repositorio de Mercurial. Como permite filtrar y renombrar los contenidos que serán incorporados, también resulta útil para filtrar repositorios Mercurial completos y obtener un subconjunto de los cambios que resultan de interés. Entre los tipos de herramientas soportadas se encuentran :
  • Hgsvn es un projrcto que surgió a partir de la comunidad de desarollo de Python para dar una respuesta a la migración de los repositorios de la librería estándar. Varias de sus características son:
    • Los datos de las versiones se solicitan por lotes.
    • El proceso es incremental, por lo que es posible interrumpirlo y resumirlo posteriormente.
    • Preserva los meta-datos (e.g. nombres y fechas de los cambios, registros de copia y de cambio de nombres de ficheros).
    • Crea marcas (a.k.a. tags) locales para cada versión en el repositorio original.
    • Preserva los nombres y las relaciones entre las ramas (a.k.a. branches).

En todos los casos parece que el punto débil resulta ser la posibilidad de hacer commits en el repositorio SVN. Sin embargo, como ya les expliqué, este no era mi objetivo en este caso. Finalmente me decidí a utilizar Hgsvn.

Clonando el repositorio con Hgsvn

Hgsvn solo clona una rama a la vez. Considerando la estructura del repositorio original, el primer paso es clonar el trunk. Para ello se ejecuta el comando hgimportsvn de la siguiente manera:

$ pwd
/home/olemis/repos/
$ mkdir flioops
$ cd flioops
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/trunk > ./trunk.log
$ cd trunk && hgpullsvn >> ../trunk.log

En el ejemplo se redirecciona la salida hacia el fichero ./trunk.log para su posterior análisis. Les recomiendo observarlo, puesto a que conocerán los cambios que se van incorporando y los datos que se van recuperando del repositorio original. La lista puede ser larga.

En este caso se crea un repositorio dentro de la carpeta trunk. En general el nombre se corresponde con el último tramo de la URL que se especifica como parámetro. Podemos comprobar que todo está en orden después de observar el log del nuevo repositorio.

$ pwd
/home/olemis/repos/flioops/trunk
$ hg log | more
changeset:   49:39cfb58b31b6
branch:      trunk
tag:         tip
tag:         svn.91
user:        olemis
date:        Wed Sep 17 10:34:32 2008 -0500
summary:     [svn r91] - Bug fixed... The class PackageTestLoader loaded the tests defined in

changeset:   48:5595c9f190ca
branch:      trunk
tag:         svn.88
user:        olemis
date:        Thu Sep 11 14:38:56 2008 -0500
summary:     [svn r88] Starting dutest release 0.2.1.

changeset:   47:ac470dd94852
branch:      trunk
tag:         svn.86
user:        olemis
date:        Thu Sep 11 12:10:25 2008 -0500
summary:     [svn r86] - PackageTestLoader class added to oop.utils.dutest module. It loads

$ hg branches
trunk                         49:39cfb58b31b6

$ hg tags -v | more
tip                               49:39cfb58b31b6
svn.91                            49:39cfb58b31b6 local
svn.88                            48:5595c9f190ca local
svn.86                            47:ac470dd94852 local
svn.83                            46:0f8df4313c98 local
svn.82                            45:287eef1166bf local
svn.80                            44:7accc993aad9 local
svn.78                            43:9ab9af3bbbfd local
svn.77                            42:4678ec09ef71 local

$ hg log -r svn.88 -v
changeset:   48:5595c9f190ca
branch:      trunk
tag:         svn.88
user:        olemis
date:        Thu Sep 11 14:38:56 2008 -0500
files:       utils/dutest.py
description:
[svn r88] Starting dutest release 0.2.1.

Como se puede observar solo obtenemos una sola rama llamada trunk. Su nombre se selecciona de manera análoga a partir de la URL. También se crea una marca local para cada revisión que se encuentra en el repositorio original. De esta forma es más fácil relacionar los cambios en un repositorio y otro. Por ejemplo la versión 88 en el repositorio central se corresponde con la versión 48 localmente, cuyo identificador global sería 5595c9f190ca. Además, si se nos olvidan estos detalles (de más está decir que yo me los sé de memoria :P ) pero conocemos la versión en el repositorio de Subversion, entonces es posible hacer referencia a su contra-parte en el repositorio de Mercurial utilizando el nombre (i.e. marca) svn.88.

Como se puede observar se mantiene el nombre del autor de los cambios y la fecha. Quizás le puedan quedar dudas ... pues ¡aclarémoslas!.

$ svn info
Path: .
URL: https://flioops.svn.sourceforge.net/svnroot/flioops/py/trunk
Repository Root: https://flioops.svn.sourceforge.net/svnroot/flioops
Repository UUID: 878b2ac5-cb47-0410-a52a-c42bf53788ce
Revision: 91
Node Kind: directory
Schedule: normal
Last Changed Author: olemis
Last Changed Rev: 91
Last Changed Date: 2008-09-17 10:34:32 -0500 (Wed, 17 Sep 2008)

Pues bien, en un mismo espacio coexisten ambos repositorios. Compare esta fecha con la que reporta Mercurial y verá que son idénticas. Después de satisfacer su curiosidad inicial, es hora entonces de clonar el resto de los cambios.

Clonando las ramas con Hgsvn

Como hemos visto antes, Hgsvn solo clona una rama a la vez. El proceso para recuperar los cambios restantes es análogo.

$ cd ..
$ pwd
/home/olemis/repos/flioops
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/add_opts > ./add_opts.log
$ cd add_opts && hgpullsvn >> ../add_opts.log && cd ..
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/mdl_inv > ./mdl_inv.log
$ cd mdl_inv && hgpullsvn >> ../mdl_inv.log && cd ..
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/pep302 > ./pep302.log
$ cd pep302 && hgpullsvn >> ../pep302.log && cd ..
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/test_pycontract > ./test_pycontract.log
$ cd test_pycontract && hgpullsvn >> ../test_pycontract.log && cd ..
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/guarded_synch > ./guarded_synch.log
$ cd guarded_synch && hgpullsvn >> ../guarded_synch.log && cd ..
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/ochk_arch > ./ochk_arch.log
$ cd ochk_arch && hgpullsvn >> ../ochk_arch.log && cd ..
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/pysimrec > ./pysimrec.log
$ cd pysimrec && hgpullsvn >> ../pysimrec.log && cd ..
$ ls
add_opts          mdl_inv          pep302           test_pycontract
guarded_synch     ochk_arch        pysimrec         trunk
add_opts.log      mdl_inv.log      pep302.log       test_pycontract.log
guarded_synch.log ochk_arch.log    pysimrec.log     trunk.log

Ahora todos esperamos efectos similares (i.e. marcas locales, una sola rama, fechas consistentes y dos repositorios en fraternal coexistencia).

$ cd add_opts
$ hg log | more
changeset:   36:660d2a6c5f05
branch:      add_opts
tag:         tip
tag:         svn.54
user:        olemis
date:        Thu Aug 07 09:42:48 2008 -0500
summary:     [svn r54] The error messages can be formatted now by specifying the templates

changeset:   35:0243dc200611
branch:      add_opts
tag:         svn.53
user:        olemis
date:        Thu Aug 07 09:36:52 2008 -0500
summary:     [svn r53] The whole assertion checking infrastructure is prepared for

changeset:   34:84a28bf08590
branch:      add_opts
tag:         svn.52
user:        olemis
date:        Thu Aug 07 09:22:20 2008 -0500
summary:     [svn r52] The function oop.utils.NormalizeException is prepared for assuming

$ svn info
Path: .
URL: https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/add_opts
Repository Root: https://flioops.svn.sourceforge.net/svnroot/flioops
Repository UUID: 878b2ac5-cb47-0410-a52a-c42bf53788ce
Revision: 54
Node Kind: directory
Schedule: normal
Last Changed Author: olemis
Last Changed Rev: 54
Last Changed Date: 2008-08-07 09:42:48 -0500 (Thu, 07 Aug 2008)

$ hg branches
add_opts                      36:660d2a6c5f05
trunk                         29:536184bf7e16 (inactive)

$ hg heads | grep changeset
changeset:   36:660d2a6c5f05

$ hg tags -v
tip                               36:660d2a6c5f05
svn.54                            36:660d2a6c5f05 local
svn.53                            35:0243dc200611 local
svn.52                            34:84a28bf08590 local
svn.50                            33:7e5b743efd37 local
svn.49                            32:901f61877293 local
svn.48                            31:51e7d78f26d8 local
svn.47                            30:336637e2a0f1 local

$ hg log -b trunk | grep changeset
changeset:   29:536184bf7e16
changeset:   28:ec176d410993
changeset:   27:d6ddd117926d
changeset:   26:1f89e8573172
changeset:   25:7b0847a67a80
changeset:   24:23571cc5686b
changeset:   23:cad71e6a4e34
changeset:   22:2d9e43109e87
changeset:   21:2a77c9c5e6ad
changeset:   20:c177d00b5a4e
changeset:   19:64eb1c7cdd91
changeset:   18:7c941992dbe5
changeset:   17:4dd286854034
changeset:   16:548c66a002c2
changeset:   15:7ad0521ce2b4
changeset:   14:f0087f96d466
changeset:   13:2540d610c198
changeset:   12:ee7e670e9b88
changeset:   11:8ba039171b0a
changeset:   10:af39f33a1776
changeset:   9:c95d926d676d
changeset:   8:28535deb9fc9
changeset:   7:1ddfa139931c
changeset:   6:84e23e55166e
changeset:   5:e1d36b91fb4a
changeset:   4:80257d1256a9
changeset:   3:6c81277c9392
changeset:   2:82714a5663d1
changeset:   1:89d45b024c67
changeset:   0:51750d40d056

$ cd ..\trunk
$ hg log -r 29:25 | grep changeset
changeset:   29:536184bf7e16
changeset:   28:ec176d410993
changeset:   27:d6ddd117926d
changeset:   26:1f89e8573172
changeset:   25:7b0847a67a80

Si bien hay varias semejanzas con respecto a los meta-datos, las diferencias son notables. Por lo visto hay menos revisiones y ... ¿dos ramas? ¿De dónde salió trunk si clonamos a add_opts? ¿Qué se puede deducir después de ejecutar los comandos anteriores? Primeramente, como las revisiones tienen el mismo identificador en ambos repositorios (trunk y add_opts) se puede deducir que se clonó el primer repositorio que teníamos localmente hasta la revisión 29:536184bf7e16. De ahi en adelante hgsvn detectó que en el repositorio SVN se había hecho una copia de los contenidos de la carpeta trunk y por tanto interpreta este hecho como la creación de una nueva rama y lo refleja localmente. En lo sucesivo solo se añaden las versiones en la rama add_opts. Es por esto que solo se crea una cabeza (a.k.a. head). La misma situación se repite para los otros repositorios.

Si Usted prefiere tener varias ramas en un solo repositorio (como yo ;o) solo resta unir todas las ramas.

$ pwd
/home/olemis/repos/flioops/trunk
$ find ../* -type d -print0  | xargs -0 -I repos hg pull repos
$ hg branches
ochk_arch                     76:6ac33679d5c3
guarded_synch                 75:386b8f866218
test_pycontract               73:d80a5ccc6153
mdl_inv                       69:a9b380084029
pep302                        62:00b7244f3642
add_opts                      60:660d2a6c5f05
pysimrec                      53:93e9562bfb41
trunk                         49:39cfb58b31b6

$ hg heads | grep changeset
changeset:   76:6ac33679d5c3
changeset:   75:386b8f866218
changeset:   73:d80a5ccc6153
changeset:   69:a9b380084029
changeset:   62:00b7244f3642
changeset:   60:660d2a6c5f05
changeset:   53:93e9562bfb41
changeset:   49:39cfb58b31b6

Ya tenemos el repositorio con todas las de la ley. Lo único que es de resaltar es que desaparecen las marcas que indicaban los números de revisión SVN para las ramas, pues son locales a los repositorios clonados.

Conclusiones

Hgsvn solo clona una rama a la vez. Subversion no tiene implementada una noción de ramas, por lo tanto el comando hgimportsvn considera como nombre el último tramo de la URL especificada. La herramienta se destaca por mantener la mayor fidelidad posible con respecto al repositorio SVN original y recuperar la mayor cantidad posible de meta-datos para incorporarlos en la copia local hecha con Mercurial. Hgsvn analiza las operaciones realizadas en el repositorio SVN original e infiere las relaciones entre las ramas para reflejarlas en el clon que se crea localmente.

miércoles, 16 de diciembre de 2009

Mejorando nuestro software libre con Bitbucket : Mercurial Queues

Bitbucket

Es un servicio de hospedaje de proyectos basado en Mercurial y enriquecido con características sociales. A continuación les describo una funcionalidad que puede ser muy útil en los proyectos de software libre para manejar los parches (patches) y propuestas de mejoras. Las potencialidades de Mercurial, de Python y la web 2.0, se combinan una vez más para salvarnos.

¿Qué es Bitbucket?

No está muy alejado de la verdad mencionar que Bitbucket está inspirado en Github, otro excelente sitio con características similares pero orientado al uso de Git (y este es nada más y nada menos que el sistema de control de versiones utilizado para desarrollar el kernel de Linux ;o). Entre las diferencias fundamentales entre ambos tenemos que Mercurial es más fácil de mantener, puede ser adaptado más fácilmente a entornos distribuidos, especialmente por estar hecho en Python (al igual que Bazaar) y posee un número de comandos más reducido y similar al de Subversion, por lo que la transición es menos traumática para los usuarios de este popular sistema. Sin embargo esto es solo una breve introducción. Hay comparaciones entre Mercurial y Bazaar y también otras entre Bazaar y Mercurial :P. Se pueden encontrar más detalles en el libro Distributed revision control with Mercurial escrito por Bryan O'Sullivan. Por cierto solo se utilizó software libre para confeccionarlo ;o).

Por su parte Bitbucket todavía está en su versión beta y están trabajando rápidamente para realizar majoras y eliminar defectos. Pero ya se han puesto en funcionamiento una buena cantidad de posibilidades maravillosas. Una de ellas es el soporte para la extensión Mercurial Queues.

Mercurial Queues

Mercurial Queues no es más que la evolución de quilt, un enfoque iniciado con Git. Muy brevemente MQ es una extensión que permite mantener una pila (i.e. LIFO) de parches que se aplican sobre un repositorio. Esto permite trabajar en unas cuantas mejoras que se construyen una sobre la otra en la forma de parches, en vez de mantener cambios más abultados y poco cohesionados. Por ejemplo si se trabaja en la interfaz de usuario y se descubre un error en capas inferiores de la aplicación entonces es posible congelar los cambios para la interfaz de usuarios, e ir liberando parches hasta llegar hasta el nivel dónde se encuentra el error. Luego se arregla el error y se vuelven a incluir en la pila los cambios que fueron extraídos anteriormente, para así proseguir con el desarrollo de la interfaz de usuarios. De este modo los parches pueden ser considerados como revisiones temporales o flotantes que no se reflejan en el repositorio hasta que no se confirma que todo marcha bien o que un experimento realmente dará resultados. Como ventaja resulta que la historia del repositorio se simplifica, y desaparecen versiones irrelevantes.

Similares beneficios se pueden obtener en proyectos en los que se mantienen parches sobre un código de base (por ejemplo, optimizaciones para dispositivos o arquitecturas específicas), al menos hasta que son revisados y aprobados por los mantenedores oficiales o hasta que se terminan. En estos casos esta extensión también permite mezclar los parches (rebasing) e incorporarlos en el repositorio.

Normalmente se trabaja localmente sobre un conjunto de parches, pero es posible establecer un control de versiones sobre los propios parches de forma tal que otros también puedan perfeccionar aspectos en pleno desarrollo.

¿Cómo funciona MQ con Bitbucket?

A continuación ilustro el camino más corto para configurar una pila de parches en Bitbucket:

  • Cree el repositorio principal. Este contiene los ficheros del proyecto.
  • Si ya se tiene parte del trabajo en un repositorio local, pues le hacemos push especificando la URL del proyecto (llamémosle project-repo).
  • Presione el botón patch queue en la página principal del repositorio.

Activando el repositorio de parches en Bitbucket

  • Teclee los datos que son solicitados y cree el nuevo repositorio (llamémosle por ejemplo project-repo-patches) para manejar versiones de los parches.
  • Asegúrese de marcar la opción Omit series si Usted desea añadir parches previos.
  • Active la extensión MQ.
  • Ejecute el comando hg qclone como se muestra a continuación (si el repositorio es de nuestra propiedad entonces owner=user)

$ hg qclone https://user@bitbucket.org/owner/project-repo-patches

Como resultado obtendrá una copia local del repositorio y la pila de parches preparada para incluir nuevas versiones. Eso es todo si no se van a incluir parches ya existentes. Luego se utiliza hg qnew para comenzar un nuevo parche y se continua con hg qref y hg qcommit para refrescarlo y para añadir una versión del parche. El repositorio de los parches se encuentra dentro de la carpeta `./.hg/patches. Para distribuir el nuevo parche a los demás se hace lo siguiente.

$ cd ./.hg/patches 
$ hg push 

Las nuevas versiones del parche deben aparecer en el repositorio de parches preparado por Bitbucket :o). La URL del repositorio remoto es configurada automáticamente.

Si se desea publicar varios parches que ya existen entonces solo hace falta copiarlos en ./.hg/patches junto con el fichero series, pero nunca copie el fichero status. Agréguelos al repositorio de parches. Todo sería más o menos así.

$ cp /path/to/old/repos/.hg/patches/*.diff ./.hg/patches 
$ cp /path/to/old/repos/.hg/patches/series ./.hg/patches 
$ cd ./.hg/patches 
$ hg add ./*.diff

¿A qué se deben las diferencias?

Lo primero que resulta diferente con respecto al comportamiento habitual es que hay que hacer hg qclone al repositorio de parches y como consecuencia se obtiene todo el repositorio del proyecto. Esto se debe a que Bitbucket brinda soporte para asociar varias pilas de parches con un único proyecto. Esto es algo muy útil. Es por esto que el servidor no almacena el repositorio de parches dentro del proyecto como ocurre normalmente. Sino, ¿cómo saber cuál pila de parches se desea clonar localmente?

Para descubrir los detalles de implementación observemos el fichero ./.hg/patches/.hg/hgrc. Allí encontraremos algo más o menos así

[paths]
default = https://bitbucket.org/owner/project-repo-patches/.hg/patches

Interesante ¿no es así? En vez de enviar los contenidos hacia el repositorio de parches (como se podría imaginar inicialmente ;o), estos son enviados hacia una pila dentro del repositorio de parches. ¿Una pila de parches dentro de una pila de parches? ¿Por qué? Si observamos el fichero ./.hg/hgrc veremos algo más o menos así.

[paths]
default = https://bitbucket.org/owner/project-repo-patches/

Por tanto los cambios deberían ser enviados a la raíz del repositorio de parches. Sin embargo si se modifican localmente los ficheros del proyecto y se publican los cambios, entonces estos se reflejan en el repositorio original (i.e. https://bitbucket.org/owner/project-repo). ¿Estarán redireccionando la raíz del repositorio de parches hacia el repositorio del proyecto? Todo parece indicar que sí.

¿Qué pasa con SSH?

Hasta ahora los ejemplos han utilizado el protocolo HTTP y esto es por una razón: la magia descrita anteriormente para clonar un repositorio de parches no está implementada para SSH. Este protocolo es útil puesto a que permite facilidades adicionales (e.g. compresión) que no permite el protocolo HTTP. Una posible solución sería :

$ hg clone ssh://hg@bitbucket.org/user/project-repo .
$ cd .hg
$ hg clone ssh://hg@bitbucket.org/user/project-repo-patches/.hg/patches .

Al menos de esta forma queda todo preparado de forma equivalente al caso de la versión HTTP, solo que se utiliza la URL del repositorio real para saltarse el paso de la redirección.

Conclusiones

Es mejor poner el parche antes que salga el hueco. Pero si hay demasiados o si queremos compartir nuestras mejoras con otras personas para colaborar y refinarlas o si se torna complicado tener todo bajo de control entonces Bitbucket está aquí para salvarnos.

:P

lunes, 1 de junio de 2009

PyUMLGraph : Otra manera de contemplar el código

El viernes pude descargar una librería llamada PyUMLGraph. Su propósito es hacerle ingeniería inversa al código de Python para obtener diagramas UML. Si quiere constatar las mejoras recientes que incluí y las potencialidades del depurador de Python, este es el artículo que Ud debe leer, no lo dude. Claro que simelo pide también habrá otros comentarios y respuestas a sus inquietudes. Con más tiempo espero poder añadirle más funcionalidades a este maravilloso paquete que estaba en peligro de extinción en el ciberespacio. Por suerte existimos muchos con el firme propósito de mantener al software libre con vida. Esta vez creo que encontré una joya que solo requiere pulirla un poco. De más está decir que puedo hacerlo gracias a que se distribuye bajo la licencia GPL 2.0. Sí señores, el software libre es algo mágico.

¿Qué es PyUMLGraph?

PyUMLGraph, como ya dije, no es más que un paquete (que contiene un comando) que permite hacerle ingeniería inversa al código de Python para obtener diagramas de clase que se asemejen a los de UML. Hasta ahora lo que se obtiene es solo los aspectos visuales del diagrama y no el formato UML en sí. De hecho, todo parece indicar que por ahora los formatos que se pueden utilizar son PNG, GIF, SVG, SVGZ, PostScript, XFIG, MIF (i.e. FrameMaker), los ficheros HPGL y PCL utilizados por los plotters e impresoras láser de la compañía HP, diagramas de GTK+, mapas de imágenes para servidores web y para clientes (X)HTML, y en la mayoría de los casos también otros formatos (la lista es MUY larga ;^) como JPEG, PDF, PIC, VML, VRML y bitmaps.

Para ser más precisos, si queremos obtener un diagrama que muestre en un color amarillo claro (#FFFFCC) los atributos y métodos de las clases del módulo estándar unittest entonces una manera muy sencilla de hacerlo es ejecutando el siguiente comando:

$ pyumlgraph.py --all --nodefillcolor=#FFFFCC --bgcolor white -o ~/Desktop/unittest.graph /usr/lib/python2.5/unittest.py

Así se obtendría un gráfico como el siguiente ( ver con más detalle ) :

Un poco de historia

Desde el punto de vista cronológico el surgimiento del paquete se remonta apróximadamente al año 2003 . En sus orígenes fue el embrión de un intento para reproducir la funcionalidad que ya ofrecía UMLGraph para representar el código de Java. Este último es un proyecto mucho más abarcador y muchísimo más activo. De echo fue toda una Odisea encontrar el código de la versión 0.1.10 de PyUMLGraph y hasta ahora no he logrado saber si hay otras más recientes. Todo apunta a que está abandonado su desarrollo. Pero a pesar de estar en fase de gestación, tiene un diseño modular y características notables. Mejorar estos aspectos y adicionar otros, es solo cuestión de tiempo. Además la idea subyacente es simplemente genial ...

¿Cómo funciona PyUMLGraph?

Una de las primeras tareas que tiene que enfrentar este módulo es la de descubrir las clases, sus elementos y establecer relaciones entre ellas. Hay dos enfoques principales para abordar este tema. El primero consiste en realizar un análisis sintáctico del código Python y, a partir de los resultados, generar el diagrama.

La segunda es la que utiliza PyUMLGraph y consiste en tracear o emular la ejecución del código e incorporar las funciones y clases a medida que se vayan llamando. Al final (y esta es una de las características más importantes del lenguaje) en Python la declaración de una clase o función es también una instrucción y todo se va creando en tiempo de ejecución. Es por esto que estamos ante un comportamiento más parecido al del depurador de Python que al de un analizador sintáctico. De hecho, básicamente tanto el depurador de Python como el depurador de PyDev, como PyUMLGraph hacen exactamente la misma cosa : monitorean la ejecución de un programa. Las únicas diferencias residen en la manera en que reaccionan. En el primer caso, al encontrar un punto de ruptura (en inglés breakpoint) se interrumpe la ejecución del script y se lanza una interfaz de ejecución de comandos en modo texto para continuar paso a paso, ver el estado del programa, los valores de las variables locales y globales, y el millón de cosas que se puede hacer con el depurador. En el segundo caso ocurre algo muy parecido, solo que la interfaz se encuentra en varias pestañas del IDE Eclipse. Finalmente la tarea de PyUMLGraph consiste en monitorear las llamadas que se van sucediendo e ir creando un catálogo de clases y funciones. A partir de esta información se obtienen todos los restantes datos que se necsitan y se genera posteriormente el diagrama en cuestión.

Algo que también es preciso resaltar es la facilidad con la que se logra todo esto. ¡Compruébelo Usted mismo!

# Fragmentos relevantes del código de PyUMLGraph 
# para monitorear la ejecución del programa objetivo.

class InfoCollector:
 def collectInfoYes(self):
    sys.settrace(self.globalTrace)

 def collectInfoNo(self):
    sys.settrace(None)

 def globalTrace(self, frame, event, arg):
    "private"
    if event == "call":
       self.collectInfo(frame, event, arg)
    return self.localTrace

 def localTrace(self, frame, event, arg):
    "private"
    if event == "return":
       className, functionName = self.getClassAndFunctionName(frame)
       locals = frame.f_locals
       globals = frame.f_globals
       self.collectInfo(frame, event, arg)

Mis aportes

Si se preguntan cuales fueron mis aportes, aquí les enumero los que voy recordando (sí ... me estoy poniendo viejo XD ) :

  • Contabilizar en el modelo a los descriptores. Anteriormente la presencia de métodos estáticos en una clase, por ejemplo, hacía que fracasará la ejecución del comando.
  • Utilizar el módulo optparse en vez de getopt.
  • Arreglar un defecto que impedía que muchas clases no fueran detectadas en casos especiales (por ejemplo, con el módulo trac.core el diagrama quedaba vacío).
  • Incorporar la capacidad de filtrar los elementos del diagrama.
  • Cambiar el nombre de varias opciones para que sean compatibles con los estándares.
  • Una nueva opción para mostrar en el diagrama solamente las clases de módulos especificados como parámetros.
  • Mostrar los parámetros de los métodos junto con los tipos inferidos en tiempo de ejecución.
  • Añadir varios niveles de registro de eventos.

... en fin, nuevos bríos :). Por ahora el comando luce más o menos así :

$ pyumlgraph.py --help
Usage: pyumlgraph.py [OPTIONS] <Python program> [ARGS]

Options:
--version             show program's version number and exit
-h, --help            show this help message and exit

Diagram settings:
  --methods           Show class methods.
  --attributes        Show class attributes.
  --all               Equivalent to --types --methods --attributes
                      --references.
  --references        Distinguish between self and local references.

Color & styles:
  --node-fill-color=COLOR
                      Set the node fill color to this string.COLOR can be
                      Unix color name or a hex 3-tuple.
  --bgcolor=COLOR     Set the diagram's background color to this
                      string.COLOR can be Unix color name or a hex 3-tuple.

Files & Directories:
  -o FILE, --output-file=FILE
                      write dot language UML output to this file.

Class filters:
  --types             Include common types (str, int, dict, etc.).
  --include-modules=LIST
                      Consider only classes found in modules listed in LIST.

En el punto actual, el siguiente comando

$ pyumlgraph.py --all -v --node-fill-color=#FFFFCC -v --include-modules=unittest,__main__ -o ~/Desktop/unittest.dot /usr/lib/python2.5/unittest.py > ~/Desktop/unittest.log

... presentará el siguiente diagrama ( ver con más detalle )

Pero sucede que todavía hay muchas tareas por hacer, por ejemplo :

  • Mejorar la visualización del diagrama, especialmente el solapamiento de las relaciones de agregación por referencia y por valor.
  • Mejorar el layout, a veces las líneas quedan muy cerca, afectándose la legibilidad del modelo.
  • Resolver el error que ocurre al confeccionar modelos a partir de paquetes que utilizan pkg_resources.
  • Otras de las que ni siquiera me he dado cuenta ;^)

Conclusiones

El módulo analizado es un ejemplo de las posibilidades y la sencillez de la metaprogramación e introspección que ofrece Python. Entre sus virtudes se encuentra su naturaleza modular y las posibilidades de generación hacia cualquier otro formato con gran facilidad. Quizás en este último aspecto se necesite un mínimo de trabajo todavía para que se agilice aún más esta tarea ;o).

Sin embargo todo no es color de rosa. Como se ejecuta íntegramente el código de la librería analizada, sucede que los tiempos de ejecución pueden resultar elevados. Además esto puede traer efectos colaterales (e.g. conexiones de red, acceso a ficheros y recursos, cambios en el sistema). Por esta razón y otras, puede suceder que no se cubran todas las partes del programa durante la ejecución. En estos casos hay elementos que quedarían fuera del diagrama. Además, como se interceptan determinadas funciones básicas, se suplanta a cualquier depurador que se esté utilizando. De todas formas hay algunos casos de uso interesantes que pueden ser muy útiles. Por tanto, simelo preguntan, les diría que PyUMLGraph es uno de esos softwares que ilumina el camino para crecer y acercarnos a un fin bien claro. Espero poder recorrerlo con Ustedes, los lectores, aquí en el blog de Simelo.