Manuel Kaufmann (Humitos): Crear GIFs de comandos en terminal

Hace un tiempo que tengo curiosidad por saber cómo crean esos "screencast" de los comandos que van tipeando en una terminal y los meten en un blog en formato gif.

Hoy encontré ttygif que te deja crear un GIF a partir de lo grabado con ttyrec -que basicamente permite grabar los comandos que vas tipeando en la consola y luego reproducirlo con el comando ttyplay.

Bueno ttygif mientras lo va reproduciendo va grabando una pequeña imagen de lo que se va mostrando junto con un tiempo en milisegundos. Luego, ejecutando su comando concat.sh te permite guardar eso en un gif.

Para instalarlo:

sudo apt-get install imagemagick ttyrec gcc
git clone https://github.com/icholy/ttygif
cd ttygif
make

Para usarlo:

ttyrec fades-django
./ttygif fades-django
./concat.sh fades-django.gif

¡Listo!

Aquí en el resultado, muestro fades con Django:

fades-django.gif

Manuel Kaufmann (Humitos): Crear GIFs de comandos en terminal

Hace un tiempo que tengo curiosidad por saber cómo crean esos "screencast" de los comandos que van tipeando en una terminal y los meten en un blog en formato gif.

Hoy encontré ttygif que te deja crear un GIF a partir de lo grabado con ttyrec -que basicamente permite grabar los comandos que vas tipeando en la consola y luego reproducirlo con el comando ttyplay.

Bueno ttygif mientras lo va reproduciendo va grabando una pequeña imagen de lo que se va mostrando junto con un tiempo en milisegundos. Luego, ejecutando su comando concat.sh te permite guardar eso en un gif.

Para instalarlo:

sudo apt-get install imagemagick ttyrec gcc
git clone https://github.com/icholy/ttygif
cd ttygif
make

Para usarlo:

ttyrec fades-django
./ttygif fades-django
./concat.sh fades-django.gif

¡Listo!

Aquí en el resultado, muestro fades con Django:

fades-django.gif

Manuel Kaufmann (Humitos): Actualizar a una versión nueva de Nikola

Nikola es un generador de sitios web estáticos que está buenísimo. Además, es el que uso para mantener este blog y el sitio web de Argentina en Python de una forma sencilla y sin mucho esfuerzo, que me permite focalizarme en el contenido sin molestarme en recursos de máquina, base de datos y otros dolores de cabeza. Además, como trabaja con reStructuredText me permite aprovechar todo su poder.

Como decía, es muy simple. Y así también lo es su actualización:

pip install -U nikola

Sin embargo, si tenés un custom theme y querés beneficiarte de los cambios en el tema del que heredás (yo me baso en bootstrap3) es necesario hacer un paso extra. Ese paso extra consiste en ver las diferencias de los templates que nosotros hemos modificado con respecto al tema heredado contra los originales y agregar los cambios que sean necesarios/compatibles. Para eso yo uso meld:

meld nikola-git-repo/nikola/data/themes/bootstrap3/templates themes/elblogdehumitos/templates

Y luego contra el theme del que hereda bootstrap3 que es base:

meld nikola-git-repo/nikola/data/themes/base/templates themes/elblogdehumitos/templates

De esa forma controlo las diferencias que haga falta y voy copiando hacia mis templates las que me benefician.

Por último, es posible que hayan agregado algunas características nuevas que requieran alguna configuración extra. Para saber cuáles son esas configuraciones podemos leer el CHANGES.txt para buscar algo puntal y agregar la que necesitemos, o bien podemos hacer un proyecto demo (con la última versión de Nikola instalada) y comparar su archivo conf.py:

nikola version
nikola init --demo -q /tmp/nikola-demo
meld /tmp/nikola-demo/conf.py conf.py

¡Ahora sí! Tenemos nuestro sitio Nikola actualizado a la última versión disponible.

Manuel Kaufmann (Humitos): Actualizar a una versión nueva de Nikola

Nikola es un generador de sitios web estáticos que está buenísimo. Además, es el que uso para mantener este blog y el sitio web de Argentina en Python de una forma sencilla y sin mucho esfuerzo, que me permite focalizarme en el contenido sin molestarme en recursos de máquina, base de datos y otros dolores de cabeza. Además, como trabaja con reStructuredText me permite aprovechar todo su poder.

Como decía, es muy simple. Y así también lo es su actualización:

pip install -U nikola

Sin embargo, si tenés un custom theme y querés beneficiarte de los cambios en el tema del que heredás (yo me baso en bootstrap3) es necesario hacer un paso extra. Ese paso extra consiste en ver las diferencias de los templates que nosotros hemos modificado con respecto al tema heredado contra los originales y agregar los cambios que sean necesarios/compatibles. Para eso yo uso meld:

meld nikola-git-repo/nikola/data/themes/bootstrap3/templates themes/elblogdehumitos/templates

Y luego contra el theme del que hereda bootstrap3 que es base:

meld nikola-git-repo/nikola/data/themes/base/templates themes/elblogdehumitos/templates

De esa forma controlo las diferencias que haga falta y voy copiando hacia mis templates las que me benefician.

Por último, es posible que hayan agregado algunas características nuevas que requieran alguna configuración extra. Para saber cuáles son esas configuraciones podemos leer el CHANGES.txt para buscar algo puntal y agregar la que necesitemos, o bien podemos hacer un proyecto demo (con la última versión de Nikola instalada) y comparar su archivo conf.py:

nikola version
nikola init --demo -q /tmp/nikola-demo
meld /tmp/nikola-demo/conf.py conf.py

¡Ahora sí! Tenemos nuestro sitio Nikola actualizado a la última versión disponible.

Marcos Dione: ayrton-0.7.1

A weird release, written from Russia via ssh on a tablet. The changelog is enough to show what's new:

  • Iterable parameters to executables are expanded in situ, so foo(..., i, ...) is expanded to foo (..., i[0], i[1], ... and foo (..., k=i, ...) is expanded to foo (..., k=i[0], k=i[1], ....
  • -x|--trace allows for minimal execution tracing.
  • -xx|--trace-with-linenos allows for execution tracing that also prints the line number.

Get it on github or pypi!


python ayrton

Manuel Kaufmann (Humitos): Maneja los archivos requirements con pip-tools

El tema de los paquetes de Python, es un tema. Normalmente yo instalo los paquetes que necesto a mano con pip install, eso a su vez me instala un montón de dependencias. Luego, pinneo los paquetes que uso en el proyecto haciendo pip freeze para que luego esa instalación sea reproducible en otro equipo.

Sin embargo, con esto te perdés de un montón de cosas. Sobre todo, las actualizaciones de las dependencias de los paquetes que instalaste a mano. Vincent Driessen lo explica mucho mejor que yo en su blog post: Pin Your Packages.

Para facilitarnos esta tarea, este flaco Vincent creó la herramienta llamada pip-tools que soluciona este problema entre otros.

Básicamente consiste en dos herramientas: pip-compile y pip-sync.

Para poder mantener en orden nuestro entorno virtual y que todo vaya de forma correcta y actualizada, debemos crear un archivo requirements.in que será donde indicamos qué paquetes necesitamos. Por ejemplo en un proyecto que necesitemos Django y django-phonenumber-field:

# requirements.in
Django
django-phonenumber-field

y luego:

$ pip-compile
#
# This file is autogenerated by pip-compile
# Make changes in requirements.in, then run this to update:
#
#    pip-compile requirements.in
#

babel==2.2.0              # via django-phonenumber-field
django-phonenumber-field==1.0.0
Django==1.9.2
phonenumbers==7.2.4       # via django-phonenumber-field
pytz==2015.7              # via babel

Este comando nos va a generar nuestro archivo requirements.txt con todas las versiones de los paquetes necesarios, y sus dependecias, pinneados como corresponde. Pero además, cada una de esas dependencias tendrá un comentario que va a indicar porqué es necesaria o de dónde viene.

Finalmente, para poner a tiro nuestro entorno e instalar todas los requerimientos necesarios con esas versiones específicas, ejecutamos:

pip-sync

Pasan los días, las semanas y los meses. Salen nuevas versiones de los paquetes y no nos enteramos. Ahí volvemos a utilizar el comando pip-compile para que compruebe todos los paquetes necesarios y se fije si hay una nueva versión y demás. Crea nuevamente el requirements.txt y si vemos que queremos actualizar todos esos paquetes, hacemos un pip-sync.

¡De puta madre pip-tools!

Manuel Kaufmann (Humitos): Maneja los archivos requirements con pip-tools

El tema de los paquetes de Python, es un tema. Normalmente yo instalo los paquetes que necesto a mano con pip install, eso a su vez me instala un montón de dependencias. Luego, pinneo los paquetes que uso en el proyecto haciendo pip freeze para que luego esa instalación sea reproducible en otro equipo.

Sin embargo, con esto te perdés de un montón de cosas. Sobre todo, las actualizaciones de las dependencias de los paquetes que instalaste a mano. Vincent Driessen lo explica mucho mejor que yo en su blog post: Pin Your Packages.

Para facilitarnos esta tarea, este flaco Vincent creó la herramienta llamada pip-tools que soluciona este problema entre otros.

Básicamente consiste en dos herramientas: pip-compile y pip-sync.

Para poder mantener en orden nuestro entorno virtual y que todo vaya de forma correcta y actualizada, debemos crear un archivo requirements.in que será donde indicamos qué paquetes necesitamos. Por ejemplo en un proyecto que necesitemos Django y django-phonenumber-field:

# requirements.in
Django
django-phonenumber-field

y luego:

$ pip-compile
#
# This file is autogenerated by pip-compile
# Make changes in requirements.in, then run this to update:
#
#    pip-compile requirements.in
#

babel==2.2.0              # via django-phonenumber-field
django-phonenumber-field==1.0.0
Django==1.9.2
phonenumbers==7.2.4       # via django-phonenumber-field
pytz==2015.7              # via babel

Este comando nos va a generar nuestro archivo requirements.txt con todas las versiones de los paquetes necesarios, y sus dependecias, pinneados como corresponde. Pero además, cada una de esas dependencias tendrá un comentario que va a indicar porqué es necesaria o de dónde viene.

Finalmente, para poner a tiro nuestro entorno e instalar todas los requerimientos necesarios con esas versiones específicas, ejecutamos:

pip-sync

Pasan los días, las semanas y los meses. Salen nuevas versiones de los paquetes y no nos enteramos. Ahí volvemos a utilizar el comando pip-compile para que compruebe todos los paquetes necesarios y se fije si hay una nueva versión y demás. Crea nuevamente el requirements.txt y si vemos que queremos actualizar todos esos paquetes, hacemos un pip-sync.

¡De puta madre pip-tools!

Manuel Kaufmann (Humitos): Historial de seguidores en Twitter

Desde que empecé a usar Twitter de forma activa me llamó mucho la atención cómo funciona, pero no técnicamente, sino más bien en lo social. El alcance que tiene a poder ver el contenido de casi cualquier persona y hacer re-tuits de personas a las que no seguís. De hecho, gracias a ese feature hemos podido llegar a muchos más lugares con el proyecto.

Si bien no he podido dedicarme mucho tiempo a escribir algunos scripts que hagan este análisis por mí, comencé con algo simple: el historial de seguidores.

¿Cómo es eso? La idea es poder saber quienes te siguen, quién es un nuevo seguidor y quién te dejó de seguir. Claramente, los últimos son los más importantes para evaluar: ¿porqué perdí a este seguidor? es la pregunta del millón.

El problema se dividió -automáticamente, en dos partes. La primera: obtener todos los seguidores de la cuenta @argenpython de forma periódica. La segunda: hacer diff de esos seguidores de forma incremental. ¿Qué quiero decir con incremental? Que no necesito saber la diferencia entre el primer reporte y el último, porque si en los reportes del medio alguien me empezó a seguir y luego me dejó de seguir, también quiero saberlo.

La obtención de los seguidores lo resolví con este pequeño script (en un @daily de CRON):

fades -d twitter -x twitter-follow --oauth argenpython > argenpython.followers_`date +"\%Y\%m\%d"`.txt

Para hacer la diferencia de todos esos seguidores recolectados diariamente con el script anterior, "escribí" [1] un script en Python:

listings/historial-de-seguidores-en-twitter/multiplediff.py

import difflib
import os


def get_files():
    return sorted([f for f in os.listdir() if f.startswith('argenpython.followers_')])


def get_date(filename):
    return filename[:-4].split('_')[1]


def get_diff(old, new):
    # Since Twitter API doesn't give as the followers list sorted, we
    # need to sorted it because if not there will be removed and added users
    # with the same name between 2 different files
    old_lines = sorted(open(old).readlines())
    new_lines = sorted(open(new).readlines())
    return ''.join(difflib.unified_diff(old_lines, new_lines))


def compare_files():
    files = get_files()
    olds = ['/dev/null'] + files[:-1]
    for old, new in zip(olds, files):
        print('Changes in', get_date(new))
        print(get_diff(old, new))

if __name__ == '__main__':
    compare_files()

Finalmente, para saber cuáles son los seguidores que hemos perdido en el camino utilicé este comando de bash:

python3 multiplediff.py | grep "^-b" | wc -l

Eso me arrojó 39 como resultado. Por lo tanto, como este script está corriendo desde el 1 de Enero de 2016, puedo decir que hemos perdido 39 seguidores en este mes.

[1] dijo el más mentiroso del mundo, el código me lo pasó Ariel Rossanigo por Twitter, justamente. Yo solo le hice unas pequeñas y mínimas modificaciones.

Manuel Kaufmann (Humitos): Historial de seguidores en Twitter

Desde que empecé a usar Twitter de forma activa me llamó mucho la atención cómo funciona, pero no técnicamente, sino más bien en lo social. El alcance que tiene a poder ver el contenido de casi cualquier persona y hacer re-tuits de personas a las que no seguís. De hecho, gracias a ese feature hemos podido llegar a muchos más lugares con el proyecto.

Si bien no he podido dedicarme mucho tiempo a escribir algunos scripts que hagan este análisis por mí, comencé con algo simple: el historial de seguidores.

¿Cómo es eso? La idea es poder saber quienes te siguen, quién es un nuevo seguidor y quién te dejó de seguir. Claramente, los últimos son los más importantes para evaluar: ¿porqué perdí a este seguidor? es la pregunta del millón.

El problema se dividió -automáticamente, en dos partes. La primera: obtener todos los seguidores de la cuenta @argenpython de forma periódica. La segunda: hacer diff de esos seguidores de forma incremental. ¿Qué quiero decir con incremental? Que no necesito saber la diferencia entre el primer reporte y el último, porque si en los reportes del medio alguien me empezó a seguir y luego me dejó de seguir, también quiero saberlo.

La obtención de los seguidores lo resolví con este pequeño script (en un @daily de CRON):

fades -d twitter -x twitter-follow --oauth argenpython > argenpython.followers_`date +"\%Y\%m\%d"`.txt

Para hacer la diferencia de todos esos seguidores recolectados diariamente con el script anterior, "escribí" [1] un script en Python:

listings/historial-de-seguidores-en-twitter/multiplediff.py

import difflib
import os


def get_files():
    return sorted([f for f in os.listdir() if f.startswith('argenpython.followers_')])


def get_date(filename):
    return filename[:-4].split('_')[1]


def get_diff(old, new):
    # Since Twitter API doesn't give as the followers list sorted, we
    # need to sorted it because if not there will be removed and added users
    # with the same name between 2 different files
    old_lines = sorted(open(old).readlines())
    new_lines = sorted(open(new).readlines())
    return ''.join(difflib.unified_diff(old_lines, new_lines))


def compare_files():
    files = get_files()
    olds = ['/dev/null'] + files[:-1]
    for old, new in zip(olds, files):
        print('Changes in', get_date(new))
        print(get_diff(old, new))

if __name__ == '__main__':
    compare_files()

Finalmente, para saber cuáles son los seguidores que hemos perdido en el camino utilicé este comando de bash:

python3 multiplediff.py | grep "^-b" | wc -l

Eso me arrojó 39 como resultado. Por lo tanto, como este script está corriendo desde el 1 de Enero de 2016, puedo decir que hemos perdido 39 seguidores en este mes.

[1] dijo el más mentiroso del mundo, el código me lo pasó Ariel Rossanigo por Twitter, justamente. Yo solo le hice unas pequeñas y mínimas modificaciones.

Manuel Kaufmann (Humitos): Hotkeys for the win

¿Cuántas aplicaciones utilizás en el día a día? Digamos que nos limitamos a las más comúnes únicamente. En ese caso, mi respuesta sería: 6 (emacs, firefox, terminator, pidgin, thunar y thunderbird)

Esas aplicaciones las tengo abiertas casi todo el día. Además, a medida que voy trabajando comienzo a abrir otras: Google Chrome, LibreOffice, JOSM, gThumb, inkscape, Skype y algunas más. A mitad de la tarde, ¿cuántas aplicaciones tenés abiertas? Y lo más importante: ¿cuántas veces hiciste Alt + TAB? Aunque lo peor de todo es: ¿cuántas veces hiciste Alt + TAB y te pasaste por una ventana y diste toda la vuelta hasta la correcta o bien, te fracturaste un dedo haciendo Alt + Shift + TAB para volver una selección?

Bueno, hace varios años que yo me cansé de eso y decidí buscar una solución. La solución vino exactamente con la primera pregunta que te hice: ¿cuántas aplicaciones utilizás en el día a día?. Básicamente quería poder acceder a cualquiera de esas 6 aplicaciones sin que importara en qué orden las había abierto o había estando navegando por ellas. Digamos, que si fui a firefox, luego a pidgin y luego a thunderbird; quería poder saltar en un solo hotkey a terminator -que si usamos Alt + TAB tendríamos que presionarlo 3 veces consecutivas.

Primero me puse a investigar cómo se puede hacer para cambiar de ventanas utilizando la línea de comandos. Ahí llegué a wmctrl, que te permite listar las ventanas activas y también cambiar entre cada una de ellas mediante una interfaz de línea de comandos.

$ wmctrl -l -x
0x01600003  0 Thunar.Thunar         victoria humitos - Administrador de archivos
0x040000a3  0 emacs.Emacs           victoria emacs-victoria: ~/bin/change-window [-]
0x024000ab  0 Navigator.Firefox     victoria Inbox (0) - someone@gmail.com - Gmail - Mozilla Firefox
0x04200db8  0 Pidgin.Pidgin         victoria (someone@gmail.com)
0x0420006a  0 Pidgin.Pidgin         victoria Lista de amigos
0x03e00001  0 Google-chrome.Google-chrome  victoria Nueva pestana - Google Chrome

Ahora bien, con esa información de WINDOW_ID, NAME y WINDOW_TITLE ya puedo cambiar de una ventana a la otra utilizando su WINDOW_ID así -por ejemplo para ir a la lista de amigos de Pidgin:

$ wmctrl -i -a 0x0420006a

Ahora necesitaba presionar un hotkey y que ejecute algo que parsee esa salida y cambie a la ventana que quiero utilizando wmctrl. La ventana que "quiero" dependerá del hotkey presionado.

Entonces, ¡escribí un script en Python, por supuesto!

listings/hotkeys-for-the-win/change-window

#!/usr/bin/env python

import os
import sys
import commands

LIST_WINDOWS = 'wmctrl -l -x | grep -i {} | grep -v grep | tail -n {}'
CHANGE_WINDOW = 'wmctrl -i -a {}'
COMMANDS_TO_WMCMD = {
    'pidgin': 'Pidgin.Pidgin',
    'emacs': 'emacs.Emacs',
    'thunar': 'Thunar.Thunar',
    'firefox': 'Navigator.Firefox',
    'terminator': 'terminator.Terminator',
}


def get_window_id(command):
    # This function is horrible but it's late at night and I just want
    # this shit working :)
    if command == 'pidgin':
        n = 2
    else:
        n = 1
    output = commands.getoutput(
        LIST_WINDOWS.format(COMMANDS_TO_WMCMD[command], n))
    if n > 1:
        if command == 'pidgin':
            lines = output.splitlines()
            for l in lines:
                if 'Lista de amigos' not in l:
                    output = l
                    break
    print(output)
    win_id = output.split(' ')[0]
    return win_id


def main():
    program = sys.argv[1]
    win_id = get_window_id(program)
    if win_id != '':
        os.system(CHANGE_WINDOW.format(win_id))
    else:
        os.system(program)


if __name__ == '__main__':
    main()

Básicamente hace eso que dijimos. Sin embargo, le agregué una cosita más. A veces pasa que tengo abierta la "Lista de amigos" y una "Ventana de conversación" en Pidgin y necesitaba poder decirle que vaya a la ventana de conversación de alguna forma. Esto es porque cuando estás chateando con alguien querés poder presionar el hotkey, contestarle, presionar el hotkey del emacs y seguir codeando; sin necesidad de pasar por la lista de amigos. Entonces, hace eso: si hay una ventana con "Lista de amigos" en su WINDOW_TITLE, elije la otra que sea de Pidgin :)

Nota

No te olvides de asignarle permisos de ejecución a tu programa python con:

chmod +x change-window

Perfecto, lo único que queda es decirle a nuestro entorno gráfico que cuando presionemos nuestro hotkey maravilla ejecute nuestro programa. Yo uso xfce por lo tanto, tengo que modificar el archivo xml de mi directorio personal ~/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-keyboard-shortcuts.xml y agregar estas líneas en la sección <property name="custom" type="empty">:

<property name="&lt;Super&gt;t" type="string" value="/home/humitos/bin/change-window terminator"/>
<property name="&lt;Super&gt;e" type="string" value="/home/humitos/bin/change-window emacs"/>
<property name="&lt;Super&gt;f" type="string" value="/home/humitos/bin/change-window firefox"/>
<property name="&lt;Super&gt;a" type="string" value="/home/humitos/bin/change-window thunar"/>
<property name="&lt;Super&gt;c" type="string" value="/home/humitos/bin/change-window pidgin"/>

Ahora sí, con Super+t voy a la terminal de Terminator sin importar dónde esté en ese momento. Lo mismo para Super+f, Super+c y demás. Antes de que pestañés, yo ya cambié 5 veces de ventana y siempre exactamente a la que quería ;)

Te aseguro que te vas a ahorar milisegundos con esto. Luego segundos, luego minutos y finalmente unas cuantas horas y puteadas de haberte pasado por uno con el Alt + TAB y tener que dar toda la vuelta cuando tenés 15 ventanas abiertas.

¡Ah! Y una cosa que me olvidaba: si el programa del cual presionamos el hotkey no está abierto, lo abre ;)

Vos, ¿cómo resolvés este problema?