Maquetación de múltiples ficheros de texto usando LibreOffice

Cartolab está colaborando con Ingeniería Sin Fronteras en un proyecto financiado por la Xunta de Galicia para la elaboración de materiales para un curso de Sistemas de Información Geográfica y Cooperación al Desarrollo. En la elaboración del curso tenemos la suerte de colaborar también con iCarto y con el Laborate. Además el SIGTE se encarga de revisar el trabajo y poner la plataforma de teleformación. Gracias a todos ellos por hacer posible este artículo.

Maquetar múltiples ficheros con LibreOffice

Uno de los puntos que defendemos en el curso es de la importancia del uso de software libre en los proyectos de cooperación, y por coherencia, tratamos en la medida de los posible de emplear unicamente herramientas libres para elaborar los contenidos. Nos encontramos por tanto con un equipo de alrededor de 9 personas en varias instituciones distintas editando cerca de 20 ficheros de texto distintos con LibreOffice, lo cual entre otras cosas genera el problema de obtener al final una maquetación coherente. Está claro que existen soluciones más imaginativas por ejemplo usar latex con git como repositorio de información pero en ciertos contextos esto es simplemente imposible.

Cuando queremos que un documento de texto tenga un formato consistente empleamos estilos, cuando necesitamos un formato consistente en múltiples ficheros empleamos plantillas.

Que es una plantilla y como se crea

Apurando la definición se podría decir que una plantilla no es más que un documento de texto de LibreOffice con extensión .ott en lugar de .odt en el que se han definido los estilos del documento, las cabeceras, ciertos textos, … El truco está en que cuando creamos un nuevo documento a partir de una plantilla este documento se queda en cierta manera vinculado a la plantilla. Por tanto cuando modifiquemos la plantilla se modificarán los documentos asociados.

Al menos esa es la teoría en realidad hay algunos handicaps:
– No todo lo modificado en la plantilla se actualiza en los documentos. Los estilos si que son actualizados pero textos que podamos tener por las páginas por ejemplo una portada no se actualizan. Aunque hay una extensión que lo arregla.
– La actualización no es automática. Cuando abrimos el documento vinculado, si la plantilla ha cambiado nos pregunta si queremos actualizar los estilos, no se actualizan por si mismos.
– Las plantillas pueden ser compartidas pero no es lo más cómodo del mundo.
– Una precaución que habría que tener es que en general no debemos modificar el estilo en el documento. Debemos: Cerrar el documento, modificar la plantilla y volver a abrir el documento para que se actualicen.

A pesar de estos handicaps el disponer de plantillas para los documentos corporativos de tu empresa puede acabar ahorrándote mucho tiempo.

  • Para crear una plantilla lo más fácil es crear un nuevo documento de texto y luego Archivo -> Plantilla -> Guardar.
  • Para crear un documento a partir de una plantilla Archivo -> Nuevo -> Plantillas de documentos.
  • Para editar una plantilla, desde cualquier documento, Archivo -> Plantilla -> Administrar, seleccionamos la que nos interese y en botón desplegable llamado Comandos le damos a editar. Para guardarla usamos el mismo procedimiento que para crear una nueva (Archivo -> Plantilla -> Guardar) pero seleccionando la ya existente para que la sobreescriba.

Compartir la plantilla

Compartir la plantilla no es del todo necesario. Si una persona tiene la plantilla en su ordenador y maqueta todos los documentos, aunque el resto no tengan la plantilla cada documento individual si que conservará los atributos de los estilos. Por tanto se puede continuar editando el documento usando los estilos predefinidos. Pero no podrá crear nuevos estilos o modificarlos porque creará inconsistencias. Tendrá que solicitar a quien tiene la plantilla que la modifique y abra todos los documentos para que estos se actualicen.

Si no disponemos de la plantilla en nuestro ordenador tampoco podremos crear nuevos documentos a partir de la plantilla. Una solución por supuesto es mandarla por correo-e cada vez que haya un cambio, pero existe un método algo más sofisticado.

En nuestro caso concreto empleamos DropBox (nadie es perfecto) para compartir los materiales del curso, de modo que podemos dejar la propia plantilla en una carpeta compartida. La persona que crea la plantilla la exporta (Archivo -> Plantilla -> Administrar, seleccionamos la que queremos y en el botón de Comandos->Exportar).

LibreOffice nos da la oportunidad de importar plantillas (misma secuencia de menus que para exportar) pero si hacemos esto creará una copia local de la plantilla y las modificaciones en el original no se verán reflejadas. Lo que debemos hacer es decirle que consideré la carpeta que nosotros queramos como una carpeta válida de plantillas. Para ello:

Herramientas -> Opciones -> Rutas -> Marcamos plantillas, le damos a editar, agregamos como ruta nueva la de nuestra carpeta compartida y la seleccionamos.

Por desgracia, la cosa no iba a ser tan fácil, las plantillas que pueda haber en esa carpeta no son reconocidas automáticamente y ni no vale con importalas así que hay que hacer un pequeño truco.

  1. Configuramos la ruta a la carpeta compartida si no lo hemos hecho todavía
  2. Cortamos y pegamos en otro sitio la plantilla que está en la carpeta compartida. Es importante por tanto que no haya en en la carpeta de plantillas una que ya tenga el nombre que vamos a usar, si no LibreOffice le asignará otro distinto y el fichero compartido tendrá un nombre distinto por tanto.
  3. Abrimos mediante doble click el fichero que contiene la plantilla y guardamos la plantilla a partir de él (Archivo -> plantilla -> guardar). De nombre le pondremos el que hayamos acordado con el resto de gente respetando mayúsculas etc … Muy importante respetar escrupulosamente el nombre
  4. Con eso estaría listo. Ya tendriamos esa plantilla disponible en nuestro sistema y cada vez que la modificáramos estaríamos tocando el fichero compartido de modo que se le actualizaría a los demás.

En próximos artículos seguiremos contando algunos trucos de los estilos que podemos usar a la hora de hacer la plantilla el uso de campos, …

Python Scripting for Students of Remote Sensing

Python Scripting for Students of Remote Sensing es un manual de python orientado a la teledetección. Si no sabes nada de python es mejor empezar por otro manual y luego saltar directamente al capítulo 5, «Plottin». La parte de python en sí es muy básica y no está muy bien explicada. Del capítulo 5 al 10 se muestran algunas librerías interesantes de python para mostrar gráficos (matplotlib), para estadística y calculos científicos (numpy), para procesado de raster (gdal), o ejemplos sencillos de trabajo con datos lidar en ficheros ascii. La información que dan es bastante escueta, pero si ya conoces python en un par de horas puedes leerlo para hacerte una idea de que posibilidades hay en estos campos.

En la misma página se pueden encontrar otros manuales interesantes sobre teledetección.

Descubrí el curso gracias al comentario de José Guerrero.

 

 

 

Como carga gvSIG las extensiones

En la línea abierta por Andrés de «Como gvSIG hace xxx» os presento un artículo que explica como se gestiona la carga de las extensiones en gvSIG. Antes de nada debo agradecer a Cartolab cederme tiempo para poder escribirlo.

Lo que sucede cuando lanzamos gvSIG es que la máquina virtual de java ejecuta el método main() de la clase Launcher de andami, el framework que sostiene el sistema de ventanas y plugins de gvSIG.

A efectos de este artículo sobre la carga de extensiones podemos decir que ese método main() es el responsable de lo siguiente:

  1. Procesar los parámetros que se le pasan por línea de comandos
  2. Cargar los plugins
  3. Cargar las clases de los plugins
  4. Recuperar la configuración persistida de los plugins
  5. Inicializar las extensiones
  6. Cargar menus y toolbars
  7. Hacer la postinicialización de las extensiones

1.- Procesar los parámetros que se le pasan por línea de comandos

Lo más habitual es que la orden que ejecute el sistema operativo (quitando los parametros que se pasan a la jvm) sea algo parecido a:

$ java com.iver.andami.Launcher gvSIG gvSIG/extensiones «$@»

donde la parte en negrita serían los parámetros que pasamos al main(). En concreto ese segundo parámetro gvSIG/extensiones es el que está definiendo donde está la carpeta de plugins de gvSIG, como ruta relativa desde donde se encuentra el fichero andami.jar.

Es decir, que si bien el directorio de extensiones suele estar en un punto predefinido nosotros podriamos escoger otra ubicación.

2.- Carga de los plugins

Procesados los parámetros de entrada la aplicación recorre todos los directorios contenidos en la carpeta de plugins. Si dentro del directorio existe un fichero de nombre «config.xml» lo parsea y en caso de ser correcto añade el par [«nombre del directorio», objeto PluginConfig] al campo de la clase Launcher


private static HashMap pluginsConfig = new HashMap();

Donde PluginConfig es una clase que se encarga de mantener toda la información que aportan los ficheros config.xml.

Lo más interesante de esta fase es entender que el orden en que se recorren los directorios, y por tanto el orden en que luego se inicializarán las extensiones, es aleatorio, o en realidad es el orden que devuelve el método lisFiles() de la clase File de Java, que especifica en su documentación que el orden en se devuelven los ficheros no está asegurado y supongo, dependerá de la implementación concreta de la máquina virtual y del sistema operativo que se emplee.

Hay que aclarar que en este contexto, un plugin, es igual a un directorio contenido dentro del directorio de extensiones/plugins definido como parámetro.

3.- Carga de las clases de los plugins

Lo siguiente que intenta hacer es indexar todas las clases de todos los plugins. Para ello recorre pluginsConfig, solicitando el valor de la etiqueta del config <libraries library-dir=»VALOR» />. Lo más habitual es que ese VALOR sea «./», es decir el propio directorio del plugin. Para cada plugin mantiene un objeto PluginClassLoader que basicamente contiene un índice de todos ficheros .class que están dentro de los ficheros .zip o .jar que están en ese library-dir.

Para cada plugin se crea un objeto PluginServices que a su vez mantiene la instancia del PluginClassLoader que le corresponde. Esos PluginServices se almacenan en el campo

private static HashMap pluginsServices= new HashMap();

de la clase Launcher.

Hay que tener en cuenta que a la hora de cargar las clases de un plugin, si en el config se ha definido que depende de otro mediante una etiquetacarga la clases de la dependencia (de forma recursiva) antes que el nuestro. Por tanto, en pluginsServices los plugins se reordenan respecto a pluginsConfig poniendo los plugins de los que se depende antes. Se mantiene también una variable pluginsOrdered con el nombre de los plugins en el mismo orden que pluginsServices

4.- Persistencia de los plugins

Se actualiza el andamiConfig que contiene una lista (el fichero andami-config.xml habitualmente) que persiste con todos los plugins presentes el directorio de plugins.

Luego se recupera de disco la configuración persistida de los plugins en el fichero plugins-persistance.xml, y se setea esa configuración en el PluginServices asociado a cada plugin.

5.- Inicialización de las Extensiones

Es en esta fase cuando salen por consola los mensajes de:

Initializing extensions from «Nombre_del_plugin» (para cada plugin)

y

Initializing «Nombre_de_la_Extension» … (para cada extensión)

Se recorre pluginsOrdered, y para cada plugin se crea una instancia de cada extensión (en el orden que se define en el config del plugin) y se llama a su método initialize(). Además se almacena esas instancia en el campo de Launcher:

private static ArrayList extensions=new ArrayList();

Por el medio crea también un objeto ExtensionDecorator que nos da un control adicional sobre las extensiones pero esto no nos interesa a los efectos de este artículo.

6.- Menus y Toolbars

Se cargarn los menus y los toolbars definidos por los plugins a través de los métodos installPluginsMenus() y installPluginsControls().

7.- Post-inicialización de las extensiones

Se va llamando al método postInitialize de cada extensión en el orden en que aparecen en el array extensions. Con este paso finalizaría el proceso de carga.

Una cosa que no he investigado a fondo pero que intuyo que es así, es que en algún momento de este proceso se asocian las instancias del array extensions a los menús y botones del toolbar. De este modo las extensiones (clases que heredan de extension y aparecen en los config) se comportan como una especie de singleton, porque gvSIG siempre la recupera de ese array de extensiones, y cuando el desarrollador externo llama a PluginServices.getExtension(Class) la está recuperando de ahí también (del ExtensionDecorator del que hablamos antes en realidad, pero la idea es la misma)

Esto explica también porque cuando recompilamos nuestro código, si cambiamos algo en la clase de la extensión debemos cerrar y abrir gvSIG. La instancia de la extensión se crea durante la carga de gvSIG y se mantiene hasta que cerramos gvSIG, y por tanto no es «recargada» de los nuevos .class

Me quedan un par de dudas que quedan para estudiar para próximos posts o para quien se anime a escribir más artículos de la serie de «Como hace gvSIG XXX»:

  • Al parecer existe una extension del tipo ExclusiveUIExtension a la que se hace referencia en Launcher.initializeExclusiveUIExtension() que no tengo muy claro como funciona
  • Estaría bien explicar en detalle el comportamiento de los ExtensionDecorator
  • Estaría bien explicar detalladamente el proceso de creación y ubicación de los menús y toolbars
  • Algunas partes del código son un poco liosas. Tengo algunas ideas de como refactorizarlo así que si alguien se anima que pregunte

Quien tiene la culpa de la crisis

Aviso antes de que sigas leyendo: Este artículo contine cierta dosis de demagogia, pero a veces hace falta llegar al extremo para saber donde está el centro.

Andrés publico hace poco un artículo, titulado los culpables de la deuda que dió pie a algo de debate en los comentarios de su blog y que hace que revisite algunas lecturas y descubra otras.

Gracias a esta pequeña investigación he descubierto que la culpa la tienen los gobiernos, ya sean los del psoe, o los del pp; los bancos, los endeudados, el capitalismo, … O en resumen, la culpa la tenemos todos. Queda a juício del lector que porcentaje de responsabilidad corresponde a cada cual, aunque yo creo que lo tengo más o menos claro.

Aunque lo que en realidad me preocupa no es quien tiene la culpa, si no quien la va a pagar.

Si debo escoger entre que la pague una familia que se queda en la calle o los responsables de la política económica de la entidad que hizo el prestamo, escogeré a quien tenga que vender el chalet de la playa para pagar su irresponsabilidad.

Si hay que decidir entre recortar la ayuda al desarrollo o suprimir el >presentar máquinas destinadas a matar como espectáculo para niños la decisión también la tengo clara…

Dan Pink en la sorprendente ciencia de la motivación

Sergio me pasa un vídeo que da los puntos básicos sobre como debería ser una gestión empresarial moderna. Promoción horizontal, incentivos, gestión, productividad, … son temas de moda sobre los que espero seguir escribiendo pero las premisas básicas son fáciles de condensar:

Cuando las tareas a realizar involucran sólo habilidades mecánicas los incentivos económicos funcionan muy bien. A mayor recompensa mayor rendimiento.

Pero en general una vez alcanzado un incentivo económico razonable, un aumento de este en forma de recopensa no se traduce en un mayor rendimiento, si no que de hecho, en muchas ocasiones reduce la productividad.

La motivación extrínseca (como el dinero) sólo funciona para alcanzar cierto nivel de rendimiento, a partir de ese nivel la motivación intrínseca es más importante. Por tanto, los incentivos otorgados para incrementar el rendimiento deberían basarse en estas tres premisas

  • Autonomía: El impulso de dirigir nuestra propia vida. Mientras que la gestión tradicional funciona cuando lo que necesitas es conformismo, otorgar autonomía tiende a funcionar cuando lo que necesitas es autonomía.
  • Maestría: El deseo de ser mejor y mejor en algo que importa
  • Propósito: El deseo de hacer lo que hacemos al servicio de algo más grande que nosotros mismos

Comentario sobre el diseño de bases de datos PostGIS

Un par de comentarios muy rápidso sobre una práctica que considero acertada a la hora del diseño con bases de datos PostGIS.

No uses el esquema public

PostGIS emplea de forma intensiva el esquema public para crear funciones, tipos de datos etc… Si tu también empleas ese esquema cuando vuelques tu base de datos ese esquema tendrá que ser incluído y no sólo irán tus datos si no también toda la parte de PostGIS lo que incrementará el tamaño del dump y el tiempo de restauración.

Además si el usuario quiere restaurar la bd sobre una ya existente con PostGIS saldrán un montón de errores de que las funciones ya existen, con lo que se hace difícil depurar otros posibles errores. También se pueden producir errores si tu dump corresponde a una versión de PostGIS distinta a la que tiene el usuario.

Cuando vuelques tu base de datos excluye el esquema public del volcado

Por lo ya explicado en el punto 1. Hacer esto es tan sencillo como:


pg_dump --no-owner -N public -h servidor -U usuario -f /tmp/base_de_datos.sql base_de_datos

Vuelca también la tabla geometry_columns

Los únicos datos que estarán en el esquema público que te hacen falta serán los de la tabla geometry_columns. Vuelca por tanto esos datos (no hace falta la estructura) a otro fichero sql.


pg_dump --no-owner -a -t geometry_columns -h servidor -U usuario -f /tmp/geometry_columns.sql base_de_datos

Teniendo en cuenta estos consejos apenas tendrás trabajo extra y harás la vida mucho más fácil a quien tenga que restaurar la base de datos (que puede que seas tu mismo)

Actualización 17/Febrero/2013. Acabo de encontarme por primera vez desde que escribí esto con alguien que también declara en un post lo importante de no usar el schema public cuando se trabaja con postgis.

Um curso objetivo de programação em Python

Gracias al blog de Anderson Medeiros encuentro un interesante tutorial de python escrito en Portugués pero muy facilito de seguir.

Es un tutorial de introducción, rápido de leer y que me ha ayudado a recordar algunos de esos conceptos que por estar más habituado a otros lenguajes tipo Java no empleas a menudo.

Algunas cosillas que he recordado, aprendido o me han gustado.

Listas, tupas y strings

Explica bien que son listas, tuplas, diccionarios y strings. A listas y tuplas se las llama en ocasiones secuencias puesto que sus propiedades son muy parecidas. Una tupla es en realidad una lista inmutable.

Conviene tener en la cabeza lo fácil que es hacer slices (subconjuntos de secuencias) y substrings en python, incluso empleando índices negativos para empezar a contar por el final. LLega con escribir:

>>> lista = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
>>> lista[2:5]
['c', 'd', 'e']
>>> lista[2:]
['c', 'd', 'e', 'f', 'g']
>>> lista[:5]
['a', 'b', 'c', 'd', 'e']
>>> lista[:-2]
['a', 'b', 'c', 'd', 'e']
>>> lista[-2:]
['f', 'g']

Los operadores * y + se pueden usar sobre listas, tuplas y strings. * replica n veces el elemento y + concatena.

Ejemplo para strings:

>>> a = "exato"
>>> print a * 2
exatoexato
>>> print "quase " + a
quase exato

Ejemplo para listas:

>>> a = [-1, 0]
>>> b = [1, 2, 3]
>>> print b * 3
[1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> print a + b
[-1, 0, 1, 2, 3]

Para chequear si un elemento está contenido en una secuencia o diccionario se usa el operador in

>>> "x" in "cha"
False
>>> 1 in [1,2,3]
True

Combinar de forma implícita operaciones lógicas

Además de los operadores lógicos not, or, and python permite combinar ciertas operaciones lógicas de forma implícita:

Por ejemplo podemos comprobar un número está en un determinado rango de esta forma:

a = 5
if 3 < a < 9:
print "Entre 3 y 9"

a = 3; b = 3; c = 2;
if a == b <= c:
print "a igual a b y b menor o igual que c"

Clausula else en bloques for y while

En los for y los while se puede emplear una claúsula else que se ejecutará cuando se salga del bloque de iteración por haber acabado la secuencia (en lugar de salir por un break)

valores = [2, 4, 5, 2, -1]
for i in valores:
if i < 0:
print "Negativo encontrado: %d" %i
break
else:
print "Nenhum negativo encontrado"

Valores booleanos

No está de más recordar que en python se considera falso a:

  • el booleano False
  • el valor 0 (zero)
  • una lista, diccionario, tupla o string vacios (de tamaño cero)
  • el valor especial None

Así por ejemplo para comprobar si una lista no está vacía mejor que emplear

lista = ['a']
if (len(lista)) != 0:
print "forma poco apropiada de comprobar si una lista no está vacia"

usaremos directamente

if lista:
print "Lista no vacia"

Argumentos de funciones

Existen dos formas de pasar un número variable de argumentos a una función:

def desculpa(alvo, *motivos):
d = "Desculpas %s, mas estou doente" % alvo
for motivo in motivos:
d = d + " e %s" % motivo
return d + "."

>>> desculpa("senhor", "meu gato fugiu",
... "minha tia veio visitar")

o bien

def equipe(diretor, produtor, **atores):
for personagem in atores.keys():
print "%s: %s" % (personagem, atores[personagem])
print "-" * 20
print "Diretor: %s" % diretor
print "Produtor: %s" % produtor

>>> equipe(diretor="Paul Anderson",
... produtor="Paul Anderson",
... Frank="Tom Cruise", Edmund="Pat Healy",
... Linda="Julianne Moore")

Frank: Tom Cruise
Edmund: Pat Healy
Linda: Julianne Moore
--------------------
Diretor: Paul Anderson
Produtor: Paul Anderson

Si tienes algún tutoirial de python que te haya gustado o algún truquillo que quieres compartir deja un comentario.

Preparar un ordenador con Windows para asistir al curso de OpenLayers.

Un par de instrucciones rápidas para el curso de OpenLayers que habrá mañana en la Escuela de Caminos en Coruña impulsado por xeoinquedos, No te asustes por el texto es mucho más fácil de lo que parece. En resumen baja los archivos que se enlazan en este post y haz doble click sobre ellos :) Los enlaces son para Windows de 32bits, si usas 64 deja un comentario y te ayudamos.

1.- Instalar Apache.

La forma más sencilla es descargar Xampp desde esta dirección [1] e instalarlo haciendo doble click. Las opciones que trae por defecto son adecuadas para la mayoría de los equipos.

Para lanzar Apache ejecuta el acceso directo que aparece en tu escritorio y luego pulsa en el botón «Start» que está a la derecha de Apache. Si salta un mensaje del antivirus pulsa en desbloquear. Para comprobar que funciona abre un navegador y escribe en la barra de direcciones http://localhost

Si se abre una página naranja ya lo tienes :)

Si tienes algún problema, tienes instrucciones más detalladas en este enlace [3]

2.- Instalar un intérprete de Python

Descarga python 2.7.1 para windows desde este enlace [2]. Instálalo mediante doble click, las opciones por defecto son una vez más adecuadas para la mayoría de los equipos.

3.- Instalar python como módulo cgi para Apache.

Descárgate este fichero [4] y cópialo dentro de la carpeta c:xamppapachemodules. Cámbiale el nombre de mod_wsgi-win32-ap22py27-3.3.so a mod_wsgi.so

A continuación abre con el WordPad el fichero c:xamppapacheconfighttpd.conf Localiza el conjunto de líneas que empiezan por el texto LoadModule y añade al final

LoadModule wsgi_module modules/mod_wsgi.so

Salva el fichero y vuelve a probar si funciona Apache.

4.- Firebug.

Lo mejor es que traigas instalado un navegador como firefox con el complemento firebug. Para instalar firebug simplemente pincha en este enlace [5] desde firefox y sigue las instrucciones.

Si usas firefox 3.6 debes descargar la versión 1.6, si usas firefox 4 debes usar la 1.7 preferiblemente. Para saber que versión de firefox tienes instalada pincha en Ayuda -> Acerca de

Aviso: En este momento la página de firebug parece caida así que si no funciona prueba con este otro enlace [8]

5.- Editor de texto

Con el bloc de notas o el WordPad es suficiente para lo que vamos a hacer pero si quieres instalar uno más potente puedes probar notepad++ [9]

6.- Copia el archivo proxy.cgi que se proporciona en los materiales del curso al directorio c:xamppcgi-bin

Y listo. Como es habitual en estas cosas, esto más difícil de escribir que de hacer.

Por último agradecer a Micho, Xurxo, Gracia, Cartolab y la Escuela de Caminos, sin los que esté curso no sería posible.

Como usar GIT tras no haber seguido el flujo de trabajo idóneo

GIT es una herramienta genial cuando eres un desarrollador disciplinado y sigues el flujo de trabajo recomendado:


$ git checkout -b nuevaFuncionalidad
... programar
$ git add -u # añadimos automaticamente todos los ficheros indexados que han sido modificados
$ git add fichero # añadimos los no indexados
$ git commit -m "Mensaje del commit"
... más commits
$ git checkout master
$ git pull --rebase
$ git checkout nuevaFuncionalidad
$ git rebase master
... (solucionar posibles conflictos)
$ git checkout master
$ git rebase nuevaFuncionalidad
$ git push

Pero si hay algo que me gusta, y aquí se nota que es una herramienta hecha por desarrolladores para desarrolladores, es lo bien que se comporta cuando no eres tan disciplinado o has metido la pata. Vayamos con un ejemplo (casi) real que se inicia con un viaje en tren Coruña – Vigo.

El estado del repositorio al empezar era este:


# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
#
# Changes to be committed:
# (use "git reset HEAD ..." to unstage)
#
# modified: config/text_en.properties
# modified: config/text_es.properties
# modified: config/text_gl.properties
# new file: src/es/udc/cartolab/gvsig/tocextra/ReloadVectorialDBLayerTocMenuEntry.java
#
# Untracked files:
# (use "git add ..." to include in what will be committed)
#
# bin/

Se trata de la extensión para gvSIG, extTOCextra desarrollada inicialmente por Javi y liberada por Cartolab como parte del proyecto gvSIG-EIEL. Como vemos en algún momento del pasado se hizo un commit directamente sobre master que no se subió al repositorio y tenemos cuatro ficheros preparados para hacer un commit que todavía no se ha hecho.

En el viaje de vuelta Vigo – Coruña, nuestro desarrollador se pone a acabar lo que había empezado a la ida, hace un git status y se encuentra que el estado del repositorio es este:

# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
#
# Changes to be committed:
# (use "git reset HEAD ..." to unstage)
#
# modified: config/text_en.properties
# modified: config/text_es.properties
# modified: config/text_gl.properties
# new file: src/es/udc/cartolab/gvsig/tocextra/ReloadVectorialDBLayerTocMenuEntry.java
#
# Changed but not updated:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: .classpath
# modified: config/text_es.properties
# modified: src/es/udc/cartolab/gvsig/tocextra/ShowActivesTocMenuEntry.java
# modified: src/es/udc/cartolab/gvsig/tocextra/TocExtraExtension.java
#
# Untracked files:
# (use "git add ..." to include in what will be committed)
#
# bin/
# src/es/udc/cartolab/gvsig/tocextra/preferences/

A base de memoria y git diff sabemos que:

  • El commit ya realizado y los archivos preparados para hacer commit constituyen una nueva funcionalidad (funcionalidad 1) que no nos interesa subir al repo por ahora
  • La modificación del .classpath y parte de lo modificado en TocExtraExtension son una pequeña refactorización que tiene sentido en si misma y que nos interesa subir como un commit separado del resto
  • El nuevo paqueta es.udc.cartolab.gvsig.tocextra.preferences, y los cambios ShowActivesTocMenuEntry, text_es.properties, y parte de los de TocExtraExtension son parte de una nueva funcionalidad (funcionalidad 2) que se empezó a programar y que todavía no está terminada. Así que nos interesa tenerla en una rama disinta de master para poder seguir trabajando sobre ella.

A la vista de esto nuestro objetivo será:

  • Tener una nueva rama con la funcionalidad 1 conservando la diferencia entre el commit ya realizado y el que tenemos preparado.
  • Una rama nueva con la refactorización en un sólo commit para luego traérnosla a master (tras haber sincronizado master con el repo) y subirla
  • Una rama nueva con la funcionalidad 2 en tantos commits como queramos para seguir trabajando.

Para conseguirlo tenemos muchos caminos alternativos, provemos uno en el que usemos distintos comandos que nos permitan ver la potencialidad de git.

Acabamos el commit que tenemos preparado

$ git commit -m "i18n for ReloadVectorialDBLayerTocMenuEntry"

Ocultamos temporalmente los cambios que nos quedan para que no nos molesten, creamos una nueva rama para la funcionalidad 1, y limpiamos el master.


$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 2 commits.
#
# Changed but not updated:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: .classpath
# modified: config/text_es.properties
# modified: src/es/udc/cartolab/gvsig/tocextra/ShowActivesTocMenuEntry.java
# modified: src/es/udc/cartolab/gvsig/tocextra/TocExtraExtension.java
#
# Untracked files:
# (use "git add ..." to include in what will be committed)
#
# bin/
# src/es/udc/cartolab/gvsig/tocextra/preferences/
$ git stash
# On branch master
# Your branch is ahead of 'origin/master' by 2 commits.
#
# Untracked files:
# (use "git add ..." to include in what will be committed)
#
# bin/
# src/es/udc/cartolab/gvsig/tocextra/preferences/

$ git checkout -b reloadDBLayers
$ git checkout master
$ git reset -hard HEAD^^ # Devolvemos la rama master al estado que tenía hace dos commits, es decir, eliminamos los cambios en local

Creamos una nueva rama para la refactorización y deshacemos la ocultación


$ git checkout -b refactor
Switched to a new branch 'refactor'
$ git stash apply
Auto-merging .classpath
CONFLICT (content): Merge conflict in .classpath
Auto-merging config/text_es.properties
CONFLICT (content): Merge conflict in config/text_es.properties
$ git st
# On branch refactor
# Changes to be committed:
# (use "git reset HEAD ..." to unstage)
#
# modified: src/es/udc/cartolab/gvsig/tocextra/ShowActivesTocMenuEntry.java
# modified: src/es/udc/cartolab/gvsig/tocextra/TocExtraExtension.java
#
# Unmerged paths:
# (use "git reset HEAD ..." to unstage)
# (use "git add/rm ..." as appropriate to mark resolution)
#
# both modified: config/text_es.properties
#
# Untracked files:
# (use "git add ..." to include in what will be committed)
#
# bin/
# src/es/udc/cartolab/gvsig/tocextra/preferences/

Ups, el stash apply nos ha provocado un conflicto. ¿Por qué?. Tómate 15 sg para pensarlo y luego sigue leyendo…

En el commit que teniamos sin hacer había modificaciones sobre text_es.properties, cuando lo comiteamos dejamos algunas modificaciones sobre ese archivo que estaban basadas en los cambios comiteados. Como la rama «refactor» la creamos a partir de un master limpio, sin esas modificaciones cuando ejecutamos stash apply ese fichero se encuentra en un estado distinto al esperado y se produce un conflicto que no es capaz de resolver automáticamente. Si hubieramos creado la rama refactor a partir de la rama reloadDBLayers el conflicto no se hubiera producido, pero en esa rama hay cambios que no nos interesan por lo que no podemos hacer esto.

Tras la solución del conflicto el estado de repo es este:

# On branch refactor
# Changed but not updated:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: .classpath
# modified: config/text_es.properties
# modified: src/es/udc/cartolab/gvsig/tocextra/ShowActivesTocMenuEntry.java
# modified: src/es/udc/cartolab/gvsig/tocextra/TocExtraExtension.java
#
# Untracked files:
# (use "git add ..." to include in what will be committed)
#
# bin/
# src/es/udc/cartolab/gvsig/tocextra/preferences/
# no changes added to commit (use "git add" and/or "git commit -a")

Separar los cambios realizados sobre TocExtraExtension.java

Como los cambios de la refactorización para el archivo TocExtraExtension.java están mezclados con los de la funcionalidad 2, usaremos la opción –patch de git add para separarlos separarlos. Esto nos permitirá escoger que cambios (hunks) de un archivo queremos preparar para comitear en lugar de añadir todo el archivo.

$ git add -i src/es/udc/cartolab/gvsig/tocextra/TocExtraExtension.java
$ git status
# On branch refactor
# Changes to be committed:
# (use "git reset HEAD ..." to unstage)
#
# modified: src/es/udc/cartolab/gvsig/tocextra/TocExtraExtension.java
#
# Changed but not updated:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: .classpath
# modified: config/text_es.properties
# modified: src/es/udc/cartolab/gvsig/tocextra/ShowActivesTocMenuEntry.java
# modified: src/es/udc/cartolab/gvsig/tocextra/TocExtraExtension.java
#
# Untracked files:
# (use "git add ..." to include in what will be committed)
#
# bin/
# src/es/udc/cartolab/gvsig/tocextra/preferences/
$ git add .classpath
$ git commit -m "Small refactor"
[refactor 715b4da] Small refactor
1 files changed, 18 insertions(+), 14 deletions(-)

Como se puede ver, hemos hecho commit de sólo una parte de los cambios realizados en TocExtraExtension. Los cambios que quedan son todos los de la funcionalidad 2, así que los comiteamos a una nueva rama

$ git checkout -b newFeature
M config/text_es.properties
M src/es/udc/cartolab/gvsig/tocextra/ShowActivesTocMenuEntry.java
M src/es/udc/cartolab/gvsig/tocextra/TocExtraExtension.java
Switched to a new branch 'newFeature'
$ git add -u
$ git add src/es/udc/cartolab/gvsig/tocextra/preferences/
$ git status
# On branch newFeature
# Changes to be committed:
# (use "git reset HEAD ..." to unstage)
#
# modified: config/text_es.properties
# modified: src/es/udc/cartolab/gvsig/tocextra/ShowActivesTocMenuEntry.java
# modified: src/es/udc/cartolab/gvsig/tocextra/TocExtraExtension.java
# new file: src/es/udc/cartolab/gvsig/tocextra/preferences/TOCExtraPreferencesPage.java
#
# Untracked files:
# (use "git add ..." to include in what will be committed)
#
# bin/
$ git commit -m "WIP. new feature"
[newFeature eea0ced] WIP. new feature
4 files changed, 137 insertions(+), 13 deletions(-)
create mode 100644 src/es/udc/cartolab/gvsig/tocextra/preferences/TOCExtraPreferencesPage.java

Subimos al repositorio la refactorización


$ git checkout master
$ git pull --rebase
$ git checkout refactor
$ git rebase master
$ git checkout master
$ git merge refactor
$ git push
$ git branch -D refactor
$ git checkout newFeature # para seguir trabajando

Parece complicado, pero en realidad es más difícil de leer que de escribir una vez coges un poco de costumbre.

SigLibre2011: Taller Linked Data

Hace poco CartoLab me dió la oportunidad de asistir a las V Jornadas de SIG Libre de Girona. El primer día, y a pesar de los temores de Gonzalo, llegamos a la sede de los talleres sin perdernos, aunque es de recibo decir que caimos de nuevo en el error del año pasado de intentar aparcar en el campus de la Facultat de Letres, lo que roza lo imposible, por lo que al final aparcamos en la ciudad y subimos andando (en realidad son apenas 10 minutitos de caminata).

El taller al que fuí por la mañana iba sobre Linked Data. Para mi gusto el taller acabo siendo más bien una clase teórica, por lo que por ahora para mi se queda en el cajón de “una esas tecnologías que habría que probar un día”.

LinkedData viene siendo un avance de cara a la web semántica, que se podría explicar diciendo algo así como que el objetivo es las webs no tengan datos si no información. Y esta información pueda estar enlazada y ser enlazable, no sólo en un formato entendible por los humanos, si no en un formato apto para que las máquinas puedan relacionarse entre sí. Ese lenguaje común que las máquinas podrían interpretar y en que se asienta el linked data es el RDF. La página de preguntas frecuentes de la web de Linked Data lo deja más claro de como lo puedo hacer yo.

Un ejemplo de las posibilidades sería por ejemplo que el IGN publicara un nomenclator en RDF. Cada uno de estos ficheritos RDF podría incluir el nombre del lugar y sus coordenadas geográficas. El INE en lugar de permitir bajarte csv podría publicar ficheros en RDF donde las estadísticas estuvieran enlazadas a los RDF que publica el nomeclator, lo que aportaría componente geográfica a los datos del INE sin que el INE se tuviera que preocupar de esta componente, sólo tendrían que enlazar una fuente fiable. Un tercero podría tomar ambos datos y proporcionar un servicio con ellos, por ejemplo rastrear las ofertas de trabajo de infojobs mezcladas con los datos del paro del INE y mostrar en un mapa que tipos de trabajo se ofrecen en los sitios donde hay más paro.

Me quedo con una de las frases del ponente que decía más o menos así:

Lo bueno o lo malo de LinkedData es que el producir la información es muy costoso pero el consumirla es relativamente sencillo.

La verdad es que es una tecnología con muy buena pinta, por ejemplo ya existen herramientas que permiten servir la información de una base de datos relacional en formato RDF, e interrogar a la BD mediante SPARQL.

Para saber más:

  • Linked Data.
  • CKAN. The data hub. Ejemplos de gente usando Linked Data, a los que podríamos enlazar nuestra propia información.
  • http://geosparql.appspot.com/