pip, apurate por favor

pip es una herramienta esencial para el trabajo diario de un programador python: es el manejador de paquetes de nuestro entorno de trabajo (¡virtual por favor!), con el que instalamos, actualizamos o eliminamos las dependencias de nuestro proyecto (y, recursivamente, las dependencias que estas pudieran tener).

Leer más…

Desambiguando un hash en Git

Soy vago. Por eso me llevo bien con Git, que permite reconocer un changeset con un pedacito de su hash sha1 :

Git is smart enough to figure out what commit you meant to type if you provide the first few characters, as long as your partial SHA-1 is at least four characters long and unambiguous — that is, only one object in the current repository begins with that partial SHA-1.

Leer más…

Cómo funciona Cuevana

Estimando cuanto trabajo significa actualizar Cuevana sources y/o Cuevanalib investigué cómo funciona la nueva versión de cuevana.

Estas notas son el resultado de lo que fui observando.

Una vez que se elige un contenido, un iframe apunta a una URL con este forma:

http://www.cuevana.tv/player/sources?id=4773&tipo=pelicula

En código javascript inline define las fuentes disponibles para ese contenido

var plugin_ver = 5, plugin_rev = 0;
var actual_ver, actual_rev;

var sources = {
    "2": {
        "360": ["uptobox", "uploadcore", "vidbull", "bayfiles", "filebox", "cramit", "zalaa"],
        "720": ["uploadcore", "vidbull", "bayfiles", "cramit"]
    }
}, sel_source = 0;

La primer clave (en este caso 2, inglés) es el idioma del audio, y la segunda la calidad del video

Luego define diferentes constates:

var label = {
    '360': 'SD (360p)',
    '480': 'SD (480p)',
    '720': 'HD (720p)',
    '1080': 'HD (1080p)'
};
var labeli = {
    "1": "Espa\u00f1ol",
    "2": "Ingl\u00e9s",
    "3": "Portugu\u00e9s",
    "4": "Alem\u00e1n",
    "5": "Franc\u00e9s",
    "6": "Coreano",
    "7": "Italiano",
    "8": "Tailand\u00e9s",
    "9": "Ruso",
    "10": "Mongol",
    "11": "Polaco",
    "12": "Esloveno",
    "13": "Sueco",
    "14": "Griego",
    "15": "Canton\u00e9s",
    "16": "Japon\u00e9s",
    "17": "Dan\u00e9s",
    "18": "Neerland\u00e9s",
    "19": "Hebreo",
    "20": "Serbio",
    "21": "\u00c1rabe",
    "22": "Hindi",
    "23": "Noruego",
    "24": "Turco",
    "26": "Mandar\u00edn",
    "27": "Nepal\u00e9s",
    "28": "Rumano",
    "29": "Iran\u00ed",
    "30": "Est\u00f3n",
    "31": "Bosnio",
    "32": "Checo",
    "33": "Croata",
    "34": "Fin\u00e9s",
    "35": "H\u00fanagro",
    "36": "Persa",
    "38": "Indonesio"
};
var labelh = {
    'filebox': 'Filebox',
    'uptobox': 'Uptobox (NUEVO)',
    'uploadcore': 'Uploadcore (NUEVO)',
    'vidbull': 'Vidbull (NUEVO)',
    'zalaa': 'Zalaa',
    'cramit': 'Cramit',
    '180upload': '180upload',
    'bayfiles': 'Bayfiles'
};

El usuario selecciona mediante un menú donde se define audio, quality y source que se configuran como atributos data del link:

.. code-block::

<a class="sel" data-type="quality" data-id="360">SD (360p)</a>

Donde data-type es el tipo de variable, data-id el valor para esa opción y class="sel" determina si esa es la opción seleccionada.

Cuando se hace click en el botón Play se invoca la URL:

http://www.cuevana.tv/player/source_get?def=**quality**&audio=**audio**&host=**source**&id=4773&tipo=pelicula

Por ejemplo:

http://www.cuevana.tv/player/source_get?def=360&audio=2&host=bayfiles&id=4773&tipo=pelicula

Esta página presenta el captcha, que una vez superado redirige a la URL:

http://go.cuevana.tv/?*URL_DESTINO*

Por ejemplo:

http://go.cuevana.tv/?http%3A%2F%2Fbayfiles.com%2Ffile%2FvIsf%2FkTvfNj%2Fthe.apparition.2012.bdrip.xvid-sparks.mp4%3Fcid%3D4773%26ctipo%3Dpelicula%26cdef%3D360

Que a su vez redirige a URL_DESTINO que es la URL del servicio donde el video está hosteado con parámetros extra: ?cid=4773&ctipo=pelicula&cdef=360. En el ejemplo anterior:

http://bayfiles.com/file/vIsf/kTvfNj/the.apparition.2012.bdrip.xvid-sparks.mp4**?cid=4773&ctipo=pelicula&cdef=360**

Aquí entra en juego el "plugin de cuevana". Se puede bajar por ejemplo la versión para Firefox desde http://www.cuevana.tv/player/plugins/cstream-5.0.xpi Descomprimirlo con unzip y abrir el archivo content/cuevanastream.js

La presencia de los parámetros cid``y ``ctipo y una url de alguno de los servicios que usa Cuevana hace que se inyecte un javascript en la URL del servicio.

var loc = (window.location.href.match(/cid=/i) && window.location.href.match(/ctipo=/i));
if (window.location.href.match(/^http:\/\/(www\.)?bayfiles\.com/i) && loc) {
    addScript('bayfiles');
}

    // más servicios

  else if (window.location.href.match(/^http:\/\/(www\.|beta\.)?cuevana\.(com|co|tv|me)/i)) {
    var n = document.createElement('div');
    n.id = 'plugin_ok';
    n.setAttribute('data-version', '5');
    n.setAttribute('data-revision', '0');
    document.body.appendChild(n);
}

function addScript(id) {
    var s = document.createElement('script');
    s.setAttribute('type', 'text/javascript');
    s.setAttribute('src', 'http://sc.cuevana.tv/player/scripts/5/' + id + '.js');
    document.getElementsByTagName('head')[0].appendChild(s);
}

En ese caso se inyecta el javascript:

http://sc.cuevana.tv/player/scripts/5/bayfiles.js

Que es el encargado de parsear html para obtener la url real de descarga, resolver/exponer el captcha si existiera, esperar el tiempo de guarda del servicio y redirigir al reproductor de cuevana:

window.location.href = 'http://www.cuevana.tv/#!/' + tipo + '/' + id + '/play/url:' + encodeURIComponent(a) + '/def:' + vars['cdef'];

Donde tipo es series o peliculas, id es el identificador del contenido, def es 360 o 720 y a es la url final del archivo mp4

http://www.cuevana.tv/#!/' + tipo + '/' + id + '/play/url:' + encodeURIComponent(a) + '/def:' + vars['cdef'];

El reproductor carga el subtitulo desde la siguientes URL.

Para series:

http://sc.cuevana.tv/files/s/sub/ID**_**LANG.srt

Donde ID es el identificador del contenido y LANG es el código del idioma en 2 letras mayúsculas (ES, EN, etc.)

Para contenidos HD se agrega el sufijo _720:

http://sc.cuevana.tv/files/s/sub/**ID**_**LANG**_720.srt

Para peliculas es análogo pero un nivel más arriba:

http://sc.cuevana.tv/files/sub/**ID**_**LANG**.srt

Y eso es todo lo que necesitamos saber.

Metiéndose cosas en la nariz

nose es un excelente framework para testing en python. Es mucho más que un testrunner inteligente: tiene un conjunto de extensiones y shortcuts de aserciones de unittest, soporta fixtures y setups/teardown por paquetes, y viene con un montón de plugins incorporados.

"nose extends unittest to make testing easier"

dice el eslogan y eso es lo que queremos: tests más fáciles de escribir, más rápidos de ejecutar.

De plugins y opciones que potencian esta idea se trata este post.

nose-progressive

Ah, beautiful dots

Erik Rose

"Puntito, puntito, puntito, puntito...", cuenta mi mente en standby cuando los test van pasando, como ovejitas que saltan un cerco. Pero, oh desdicha, una "efe", la oveja negra que falla, aparece en la pantalla y la ansiedad por saber cuál nos invade.

El problema es que por default (con --verbose=1), nose pospone el detalle de errores y fallos hasta el final de la corrida. En suites gigantes (que demoran más de 30 minutos en completarse, por ejemplo) esto es un engorro.

nose-progressive reemplaza la interfaz de usuario por una mucho más amigable y bonita. En vez de puntitos, una barra de progreso al pie de la pantalla va indicando el progreso satisfactorio, y deja el resto de la pantalla para mostrar errores y fallos on the fly.

https://raw.github.com/erikrose/nose-progressive/master/in_progress.png

Como si fuera poco, te da shortcuts a la línea donde se produjo un fallo (por ejemplo: vim +258 common/tests/test_guideplan.py), simplifica los tracebacks a la parte útil, e incluye colores usados con criterio (basada en la genial biblioteca blessing, del mismo autor).

Para instalarlo, lo de siempre:

(env) $ pip install nose-progressive

Para usarlo, agregar --with-progressive como parámetros de nosetests, o bien, para usarlo siempre, configurarlo en .noserc o en la variable NOSE_ARGS de django-nose

django-nose

Si trabajás con Django esto quizas suene a perogrullada, pero django-nose es la vuelta de tuerca que faltaba para que la nariz brille en este entorno. Con django-nose por defecto sólo se corren los tests del proyecto y no los de todas las aplicaciones instaladas (que, se supone, tienen sus propios tests).

Y entre otras opciones o, tiene un modo de reuso de la base de datos, (configurando REUSE_DB=1 como variable de entorno), que nos evita la creación de tablas en cada corrida, ahorrando unos cuantos segundos de setup en aplicaciones como muchos modelos. Por supuesto, si la base de datos cambia (porque se agregó un modelo o se se migró con South) tenés que asegurarte que la variable está en 0 para que cree la última version

with-id + failed

La opción --with-id (estrictamente el plugin TestId, que viene en las baterías incluídas de nose) le asigna un número único a cada test ejecutado, y los guarda en el archivito .noseids junto con su path y el resultado de la última corrida.

Lo bueno es que pasando --with-id --failed nose usa esa información y ejecuta solamente los tests que fallaron en la última corrida, permitiendo una rápida iteración "falla/corrección".

nose-selecttests

Frecuentemente sucede que tenemos caso (una subclase de TestCase) con muchas pruebas (métodos) que están lógicamente agrupadas porque comparten un setup, por ejemplo, y sin embargo sólo queremos correr un subconjunto.

Sabemos que como es inteligente y poco burocrático, nose no sólo ejecuta tests definidos como subclases de unittest.TestCase sino cualquier paquete, módulo, función o clase que matchee una expresión regular, que por defecto es (?:^|[b_./-])[Tt]est Con el parámetro -m se puede redefinir este patrón, pero igualmente no aplica a nivel métodos.

Por eso existe nose-selecttests, que permite filtrar pruebas con determinada subcadena en el nombre del método.

Por ejemplo:

$ djntest -v 2 common/tests/test_sequence_diff.py

#2976 test_differ_in_fk (cpi_mrp.apps.common.tests.test_sequence_diff.TestInstanceDiff) ... ok
#2977 test_differ_in_m2m (cpi_mrp.apps.common.tests.test_sequence_diff.TestInstanceDiff) ... ok
#2979 test_multiple_diff (cpi_mrp.apps.common.tests.test_sequence_diff.TestInstanceDiff) ... ok
#2980 test_only_compare_same_type (cpi_mrp.apps.common.tests.test_sequence_diff.TestInstanceDiff) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.375s

En cambio:

$ djntest --select-tests=differ -v 2 apps/common/tests/test_sequence_diff.py

#2976 test_differ_in_fk (cpi_mrp.apps.common.tests.test_sequence_diff.TestInstanceDiff) ... ok
#2977 test_differ_in_m2m (cpi_mrp.apps.common.tests.test_sequence_diff.TestInstanceDiff) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.088s

django-tests-autocomplete

Esta herramienta no es estrictamente un plugin de nose, sino un helper que aprovecha las posibilidades de bash para autocompletar usando la tecla tab.

¿Para qué sirve? nose acepta un path (o muchos) para especificar qué test correr:

$ nosetests path/to/test.py

Pero también permite afinar la puntería y "meterse" adentro del módulo:

$ nosetests path/to/test.py:MyImportantTestCase

E incluso adentro del testcase:

$ nosetests path/to/test.py:MyImportantTestCase.test_super_important

Desde el : en adelante estamos en Python y Bash ya no sabe autocompletar, salvo que usemos esta herramienta que instrospecciona y "alimenta" el autocompleter.

Lo hizo Javi Mansilla en Machinalis y busca ayuda para mejorarlo y generalizarlo.

¿No sería buenísimo que esto estuviese built-in en nose? ¿Nos ayudarías?

nose-timer

¿Cuánto tiempo insume cada test? Instalá nose-timer y activalo (con --with-timer) para saber la respuesta.

nose-ipdb

nose tiene una opción --pdb (o la más estricta --ipdb-failures) que nos manda al debugger cuando un test falla o da error.

nose-ipdb lo imita, pero usando el más pulenta de los debuggers: ipdb.

Suficiente por hoy. Pero ¿me estoy perdiendo alguna cosa interesante para meter en mi nariz?

Creando un nuevo proyecto Python

¿Así que querés comenzar un nuevo proyecto usando Python? ¡Felicitaciones! ¿Querés que tenga la estructura recomendada para proyectos modernos? ¿Querés que sea instalable fácilmente? ¿Querés que no se te arme lio de dependencias que colisionan con las de otros proyectos ? Bien, te propongo esta receta.

Las herramientas

  • Virtualenv: es una herramienta para aislar tu entorno de desarrollo python. Es muy pero muy útil para evitar conflictos entre las dependencias de tus distintos proyectos.

  • virtualenvwrapper: es un conjunto de extensiones que hacen la vida del usuario virtualenv aun más feliz, permitiendo crear y borrar entornos virtuales, asociarlos a un proyecto, automatizar tareas al activar o desactivar uno, etc.

  • pip: es la herramienta moderna, correcta y recomendada para administrar los paquetes python instalados en tu sistema/virtualenv. Es un reemplazo de easy_install

  • Distribute: es la herramienta moderna y recomendada para distribuir tu paquete python. Es un fork de setuptools (que es, a su vez, una mejora sobre el módulo estándar distutils)

  • skeleton es una herramienta que define plantillas para iniciar un proyecto, generando la estructura básica necesaria. Es similar a PasteScript pero enfocado en esta tarea concreta, sin dependencias y compatible con Python 3.x

La receta

  1. Instalá pip:

    $ sudo apt-get install python-pip

    o bien directamente:

    $ sudo curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py | python
  2. Instalá virtualenwrapper y skeleton:

    $ sudo pip install virtualenwrapper
    $ sudo pip install git+git://github.com/stumitchell/skeleton.git#egg=skeleton

Nota

Notar que skeleton se está instalando desde un fork del proyecto original (aparentemente desmantenida), que resuelve bugs importantes

  1. Configurá virtualenvwrapper

    $ mkdir ~/.virtualenvs           # acá se van a guardar tus entornos virtuales
    $ mkdir ~/proyectos              # acá se van a guardar tus proyectos
    $ mkdir ~/.pip_download_cache     # para no bajar paquetes cada vez
    

    Luego editá tu ~/.bashrc agregando las siguientes líneas

    export WORKON_HOME=$HOME/.virtualenvs
    export PROJECT_HOME=$HOME/proyectos
    export VIRTUALENV_DISTRIBUTE=true
    export PIP_DOWNLOAD_CACHE=$HOME/.pip_download_cache
    source /usr/local/bin/virtualenvwrapper.sh
    

    y recargá tus cambios:

    $ source ~/.bashrc
  2. Inicializá tu proyecto. Por ejemplo el proyecto zaraza

    $ mkproject -t package zaraza

    Se te solicitarán algunos datos (nombre del proyecto, autor, licencia, etc.) y ¡(casi) listo! Estarás trabajando en tu proyecto zaraza. Tu prompt se verá así

    (zaraza)tin@morocha:~/proyectos/zaraza$

    ¿Qué sucedió? Se creó un directorio ~/proyectos/zaraza para tu proyecto, asociado a un virtualenv ubicado en ~/.virtualenvs/zaraza. skeleton automáticamente creó una estructura básica de paquete python en ~/proyectos/zaraza/src incluyendo un setup.py basado en distribute.

  3. Instalá tu paquete en el virtualenv, para poder importarlo desde cualquier lado:

    (zaraza) $ cd  ~/proyectos/zaraza/src
    (zaraza) $ pip install -e .

    Esto agrega el directorio de desarrollo de tu proyecto al PYTHONPATH del virtualenv, de modo que puedes importar zaraza desde cualquier lado dentro del virtualenv (por ejemplo, cuando hagas una carpeta src/test al nivel de '/src/zaraza'

¿Y ahora?

Cada vez que quieras trabajar en tu proyecto zaraza ejecutás:

$ workon zaraza

Para salir del virtualenv:

(zaraza) $ deactivate

Algunos tips más a modo de despedida

Virtualenwrapper es totalmente hookeable y extensible. Esta receta propone usar skeleton (que funciona como plugin de virtualenvwrapper.project) para crear una estructura de paquete estándar básica, pero hay plugins para proyectos más específicos. Por ejemplo virtualenwrapper.django

El comando mkproject es un wrapper sobre el comando principal de virtualenvwrapper mkvirtualenv, que acepta muchos parámetros opcionales. Ejecutá mkproject_help o mkvirtualenv --help para saber más.

Y ya sabés ...

http://python-distribute.org/pip_distribute.png

Este artículo lo escribí originalmente para el Recetario de PyAr

Migrando issues entre proyectos de Bitbucket

Hace un tiempo conté como migré un repositorio Mercurial a Git . Se trataba de un proyecto hospedado en Bitbucket y para cambiar de DVCS tuve que crear un proyecto nuevo, que tambien hospedamos allí porque somos pobretones y nos da repos privados gratis. En la mudanza se me quedaron varios issues que necesitaba migrar. Y no era el único.

Buscando un rato encontré scriptcitos para migrar desde o hacia GitHub pero no había para migrar entre proyectos de Bitbucket, algo bastante común desde que empezaron a ofrecer soporte Git.

Decidí entonces que debía hacer mi propio scriptcito migrador. La cosa se complicaba porque el par de bibliotecas python que interactuan con la API de Bitbucket no tenian, hasta el momento, soporte para "postear" issues

Pero no hay darse por vencido: se me ocurrió mirar el par de forks de cada proyecto y encontré justo lo que estaba buscando.

Entonces bastó con instalar el fork de David Paz Reyes

$ pip install git+https://github.com/davidmpaz/BitBucket-api.git

he hice un script que migra todo los issues en estado new del repo original (gpec) al nuevo (gpec) y los potenciales comentarios que tenga. Como el autor se pierde (dado que el nuevo quedará publicado con mi usuario) agrego un comentario avisando que es un issue migrado.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from bitbucket import bitbucket

gpec = bitbucket.Bitbucket('tin_nqn', '***', 'gpec')
gpec3 = bitbucket.Bitbucket('tin_nqn', '***', 'gpec3')

# request original ISSUES
_, result = gpec.get_issues()

for issue in result['issues'][:]:
    original_id = issue['local_id']
    if issue['status'] != 'new':
        continue

    # and post to the new repo
    ok, new_issue = gpec3.add_issue(**issue)

    if not ok:
        print 'Fail migrating #%d' % original_id
        continue

    new_id = new_issue['local_id']

    print 'Migrated #%d as #%d in the new project' % (original_id, new_id)

    # add a comment to mark the migration
    who = issue.get('reported_by', None)
    who = who['username'] if who else 'anonymous'
    gpec3.add_issue_comment(new_id, content="Issue migrated from the original repo. "
            "Was #%d reported by %s" % (original_id, who))

    # migrate comments
    result, comments = gpec.get_issue_comments(original_id)
    for c in comments:
        if not c['content']:
            continue
        gpec3.add_issue_comment(new_id, **c)

¡Disfruten!

Jquery, una introducción

jQuery, una librería javascript ligera y sumamente útil, desarrollada por el talentoso programador John Resig.

Como se explica en el sitio web:

jQuery es una biblioteca javascript ligera y concisa que simplifica como atraviesas tus documentos HTML, manejas eventos, realizas animaciones, y agregas interacción Ajax a tus páginas web. jQuery está diseñada para cambiar la manera en que escribes JavaScript

¿Necesitamos otra biblioteca?

El Toolkit Dojo está maduro e incluye hasta el los hielos del whisky. Es ampliamente popular y muchas interfaces UI fueron construidas sobre este toolkit, incluyendo script.aculo.us, Rico, y otros. Por supuesto, no podemos dejar afuera a mootools. Su biblioteca UI, moo.fx, puede apoyarse sobre Prototype si lo deseas. Tambiñen está Y!UI the Yahoo! User Interface library. Y tenemos ExtJs, un robusto framework de interfaz que se basa en Y!UI, Prototype, o jQuery.

jQuery

Lo que nos trae a... jQuery. Sí, otro framework JavaScript. jQuery, sin embargo, es a mi criterio el más rápido y elegante del montón. Soporta CSS3, detección del navegador, encadenamiento de métodos, plugins, Ajax, selectores flexibles, y efectos UI basicos. Todo en menos de 30Kb.

Pues bien, engolosinemos los ojos un poco. Hacé click acá para ver lo que jQuery puede hacer con un par de líneas de código.

Wow! Jquery en acción. ¿Lo ves? Ese efecto se logra con este sencillo código:

$("a.intro").click( function() {
    $("div.introtarget").toggle(100);return false;

Ext4 superblock failure y la cola del Diablo

Domingo, ciudad de Guayaquil, todo dispuesto para salir a conocer una ciudad que en las guías de viaje suena intererante y bonita. Se me ocurre prender la compu para ver si ese email que mandé la noche anterior, recién llegado después de 14 horas de viaje (5 de espera en el aeropuerto de Santiago) y deseando únicamente ducha y colchón, había sido contestado.

"No pusheé", fué lo primero que pensé, o me lo dije, en voz inaudible, como eufemismo de un gran insulto y síntesis de "Martincito querido, ya sabés que el booteo está tardando mucho, el material para el taller_ que tenés que dar mañana está en esa computadora y tiene al menos 10 horas de trabajo offline que está en la última versión online: no podés ser tan pelotudo y tener tan mal orto a la vez". Mi voz interior sí que sabe resumir.

Por suerte, no perdí la calma. Apreté el 1 en el teléfono: "sí señor", el Bussiness Center queda en el 4 piso". Ahí fui y habia computadoras con las que pude contactarme con Houston.

Grulic, Perrito, Mariano, Rudi, y especialmente mi cuñado que trabaja en Ontrack y era algo así como un Messi sentado en mi banco de suplentes para este partido que yo no hubiese querido jugar, fueron los primeros con quienes me comuniqué.

Mandé este mensaje:

Gente, les mando una de Murphy y el diablo y toda la mala leche junta. Estoy en Ecuador invitado a dar un curso-taller de Python y Django.

Rodo venía fantastico , hasta que hoy enciendo la laptop y no y no entra el sistema operativo!.

Entro en modo recuperacion (ubuntu 11.10) y se clava en el fsck hasta que me aparece un menucito que me deja entrar a una consola root. Lo peor sucedió /home/ no monta , pero el / parece estar sano. Ambas son una particion ext4

Si intento montar a mano o un fsck, el output dice muchas cosas que suenan mal pero no tengo puta idea qué significan:

STATUS { DRDY }
ERROR { UNC }
configured for UDMA/133
failed command READ DMA
exception Emask 0x0 Sact 0x0 Serr 0x0 action 0x0
res 51/40... Emask 0x9 (media error)

Por favor, iluminenmé sobre cómo proceder.

Ernesto contestó. Primero intentar hacer un un live-usb con algunas herramientas de diagnostico: Ubuntu-rescue-remix podría funcionar, pero las maquinas del Bussiness center no me dejan instalar algun programita para pasar la ISO al pendrive.

Empiezo a deseperar ya pensar si no conviene aprovechar el tiempo que me llevará intentar un salvataje con alta probabilidad de fracaso para rehacer el trabajo inaccesible.

Desactivar journal, no. Forzar montado, no. No se qué otra cosa, no.

Ernesto pregunta si me animo a ir por el camino duro.

Cómo estás de fresco ara leer hexadecimal ? hexdump y dd, la navaja suiza. que necesitamos

Intentamos algunas cosas, calculadora en mano, pero la mayoría de los intentos de acceso al disco caen en el mismo bucle de errores horribles. Mucho calor en Guayaquil.

Le digo a Ernesto (domingo, dia de descanso) que no se preocupe, que intentemos eso último que se le ocurrió pero que si no funciona voy a ponerme a trabajar de nuevo. Mientras tanto, el Dios en el que no creo me pone este link en el camino de resultados de Google.

"Ernesto, prabamos esto?" y no alcanzó a contestar antes de que esté poniendo los primeros comandos

$ sudo mke2fs -n /dev/sda3

Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208

Me la juego con el primer backup:

sudo e2fsck -b 32768 /dev/sda3

Cientos de Y Enter para mensajes estilo "está roto, desea repararlo?" (no había una opción "Sí a todo", como si uno fuese capaz de arrepentirse a mitad de camino)

Anduvo. Uff. Dos simples comandos. El Ingeniero Lobo festeja desde Londres, Skype mediante, que podía volver a limpiar su moto sin quedar mal con su cuñado con mala pata. Me dijo antes:

Si no hubiese sido una situadion del tipo mañana ya no me sirven los datos yo te hubiera recomendado en contra de herramienta como e2fsck. Esos programas sólo se encargan de asegurar que el filesystem esté íntegro, pero si para eso tienen que cortarlo a la mitad lo hacen sin dudar. Son como doctores de guerra: te dejan vivo, pero sin una pierna y un brazo.

Querido Guayaquil: prometo visitar tu Malecón la próxima, push por adelantado.

Notas rápidas de una mudanza

Hace unos días abría este espacio prometiendo migrar contenidos y tratar de que se vea un poco más bonito.

Este post incluye algunas notas sobre lo que he hecho hasta el momento.

Github ♥ Nikola

Este blog que estás leyendo está hospedado en Github Pages , el servicio de hosting gratuito de contenido estático que ofrece Github.

A diferencia de cuando se usa Gh-pages para proyectos, donde se sirve el contenido del branch gh-page del proyecto en cuestión, para usar gh-pages por usuario hay que crear un repo llamado <username>.github.com (donde <username> es el tuyo) y lo que sirve en http://<username>.github.com es directamente el branch master (referencia)

Por este motivo creé otro branch (que sin mucha imaginación denominé writing ) donde tengo todo el mi "proyecto" hecho con Nikola

Allí escribo, intento mejorar mi theme, y corro nikola build.

El branch master es el de deploy y sirve todo lo que haya en la carpeta output/ del branch writing. Para hacer esto hay que ir a master, borrar todo y leer desde el árbol del otro branch con read-tree

git checkout master
git rm *    # sólo la primera vez
git read-tree writing:output
git commit -m 'deploying last build'
git push

Migrando desde Spip

Uno de los motivos que me llevaron a migrar desde Spip es que el markup que usa es ad hoc y muy feo. Algo así

{{{ Un intertítulo }}}

Esto es {{negrita}} y este [mi twitter->http://twitter.com/tin_nqn_]

Hasta ahí no se ve tan mal, pero es muy limitado cuando se trata de mostrar código, necesario en todo blog mas o menos técnico.

Para migrar esquivé la idea de convertir el markup de spip y opté por un scrapping, limpieza y conversión a restructuredText usando el mágico Pandoc (y la ayuda de PyQuery)

El script que hice está acá . No es perfecto, pero está lo importante: contenidos, imágenes, adjuntos, convertido a restructuredText bastante decente que mejoraré poco a poco a mano.

Estilos

Es mentira que a los ñoños no nos gustan las cosas (los blogs, entre otras) que se ven bonitos. Sucede, en realidad, que la mayoría de las veces no venimos con esa habilidad y el esfuerzo que nos implica intentarlo preferimos ponerlo en otra cosa. No gusta, pero no es lo más importante.

No obstante, yo quiero que este espacio sea lindo y legible. Y como hay muchos otros ñoños con sitios lindos y legibles que son tan amables de compartir sus estilos (y existe bootstrap, claro) voy a ir intentándolo, incrementalmente.

Por ahora tomé ideas y CSS de stevelosh.com 1 y de la documentación de Flask 2 basado en el theme Readable de bootswatch.com

Disqus

El blog viejo usaba Disqus, con un plugin que hice hace algun tiempo. Para migrar los comentarios utilicé el método de subir un CSV con el formato que genera el mismo script de migración:

URL_POST_X_OLD, URL_POST_X_NEW
URL_POST_Y_OLD, URL_POST_Y_NEW

En cuestión de minutos los (pocos) comentarios estaban migrados.

Redirección

Como mantuve el slug de los viejos artículos, una redirección 301 via .htaccess redirige del viejo blog al nuevo:

# nqnwebs.com blog rules
RewriteCond %{HTTP_HOST} ^nqnwebs [nc]
RewriteRule ^blog[/]?$ http://mgaitan.github.com/ [R=301]
RewriteRule ^blog/article/(.*)$ http://mgaitan.github.com/posts/$1.html [R=301]

¿Cómo se va viendo?

1

https://bitbucket.org/sjl/stevelosh/src/6432040d5154/LICENSE?at=default

2

https://github.com/mitsuhiko/flask-sphinx-themes/blob/master/LICENSE