Skip to main content

Ralsina.Me — Roberto Alsina's website

So, rst2pdf is now 0.91

Turns out there was a ma­jor, show stop­per bug in rst2pdf 0.90: sphinx sup­port was ab­so­lute­ly bro­ken. Not bro­ken as in bug­gy, bro­ken as in it had syn­tax er­rors.

So, 0.91 is now re­leased, and on­ly bro­ken in the tra­di­tion­al sense. En­joy!

rst2pdf 0.90 is out

Yes, af­ter many moon­s, it's out. Here is the (as usu­al) in­com­plete changel­og:

  • Added raw HTML sup­­port, by Dim­itri Christodoulou

  • Fixed Is­­sue 422: Hav­ing no .afm files made font lookup slow.

  • Fixed Is­­sue 411: Some­­times the win­­dows reg­istry has the font's ab­s­path.

  • Fixed Is­­sue 430: Us­ing --­­con­­fig op­­tion caused oth­­er op­­tions to be ig­nored (by charles at cstan­hope dot com)

  • Fixed Is­­sue 436: Add pdf_style_­­path to sphinx (by tyler@­­datas­­tax.­­com)

  • Fixed Is­­sue 428: page num­bers logged as er­rors

  • Added sup­­port for many pyg­­ments op­­tions in code-block (by Joaquin So­ri­anel­lo)

  • Im­­ple­­men­t­ed Is­­sue 404: plan­­tuml sup­­port

  • Is­­sue 399: sup­­port sphinx's tem­­plate path op­­tion

  • Fixed Is­­sue 406: calls to the wrong log­ging func­­tion

  • Im­­ple­­men­t­ed Is­­sue 391: New --­sec­­tion-­­head­­er-depth op­­tion.

  • Fixed Is­­sue 390: the --­­con­­fig op­­tion was ig­nored.

  • Added sup­­port for many pyg­­ments op­­tions in code-block (by Joaquin So­ri­anel­lo)

  • Fixed Is­­sue 379: Wrong style ap­­plied to para­­graphs in de­f­i­ni­­tion­s.

  • Fixed Is­­sue 378: Mul­ti­­line :ad­­dress: were shown col­lapsed.

  • Im­­ple­­men­t­ed Is­­sue 11: Frame­Break (and con­di­­tion­al Frame­Break)

  • The de­scrip­­tion of frames in page tem­­plates was just wrong.

  • Fixed Is­­sue 374: in some cas­es, lit­er­al blocks were split in­­­side a page, or the page­break came too ear­­ly.

  • Fixed Is­­sue 370: warn­ing about sphinx.addnodes.high­­­light­lang not be­ing han­­dled re­­moved.

  • Fixed Is­­sue 369: crash in hy­phen­a­tor when spec­i­­fy­ing "en" as a lan­guage.

  • Com­­pat­i­­bil­i­­ty fix to Sphinx 0.6.x (For python 2.7 doc­s)

This re­lease did not fo­cus on Sphinx bugs, so those are prob­a­bly still there. Hope­ful­ly the next round is at­tack­ing those.

Extendiendo rst2pdf y sphinx

Sor­ry, span­ish on­ly post!


Es­ti­ma­do públi­co lec­tor, una cosa muy es­pe­cial: un post que no es­cribí yo! Dé­mosle una bi­en­veni­da al au­tor in­vi­ta­do, Juan BC!


Una de mis prome­sas en el año fue ter­mi­nar uno de los proyec­tos mas am­bi­ciosos que me había prop­uesto: ter­mi­nar mi am­bi­entación de rol uti­lizan­do el sis­tema matemáti­co que había dis­eña­do jun­to con un ami­go tiem­po atras lla­ma­do Cros aprovechan­do la bar­bari­dad de im­a­genes que tenía guarda­da de mi tesis de gra­do.

Co­mo no puede ser de otra man­era co­mo miem­bro de Python Ar­genti­na util­ice el pro­gra­ma uti­liza­do por rst2pdf crea­do por Rober­to Alsi­na el cual es el dueño de este blog

En un pun­to nece­sita­ba hac­er una es­pecie de plan­til­las de dibu­jos para repe­tir el es­ti­lo donde hay de­scrip­ciones de un per­son­aje pero el es­ti­lo se mantiene, co­mo por ejem­p­lo las tar­je­tas de este man­u­al de DC Uni­verse RPG :

/static/cards_dc.jpg

y me di­je a mi mis­mo

¿Por que de­mo­ni­os no puedo dibu­jar un svg_ de­jar es­pa­cios en blan­co y luego los lleno den­tro del svg?

En defini­ti­va, me imag­in­a­ba al­go así:

.. template_svg: path/a/mi/archivo/svg/que/tiene/las/variables/definidas/adentro.svg
    :nombre_variable_1: valor_de_variable_1
    :nombre_variable_2: valor_de_variable_2
    ...
    :nombre_variable_N: valor_de_variable_N

En­tonces me puse en cam­paña para poder pro­gra­mar el códi­go python que re­al­iza esa tarea co­mo una ex­ten­sión de rst2pdf.

AVI­SO!!! este tex­to lo ll­e­vara a ust­ed por ca­da una de las desi­ciones de dis­eño que in­volu­cran la creación de mi ex­ten­sión para rst2pdf

Definiendo sintaxis y comportamiento

Por em­pezar aprendí de las lim­ita­ciones de la di­rec­ti­va, de­cidí un nom­bre para el­la y definí cual era el com­por­tamien­to que hi­ba a ten­er.

  • Da­do que una di­rec­ti­va co­mo tem­plate_svg me son­a­ba a larga de­cidí que el nom­bre sería svgt (SVG tem­plate).

  • Las di­rec­ti­vas so­por­tan parámet­ros fi­jos, no puedo ten­er un número vari­able de ar­gu­men­tos para pasar­le: nom­bre_­vari­able_1, nom­bre_­vari­able_2, vari­able_N a al­go co­mo di­rec­ti­­va(**k­wargs).

    Mi solu­­ción fue pasar­le to­­do en un json.

  • Por úl­ti­mo definí que svgt gener­aría un no­do de tipo fig­ure con lo cual mi di­rec­ti­va acep­taría, además de mis parámet­ros, los ar­gu­men­tos que ya tenía in­cor­po­ra­dos en dicha di­rec­ti­va.

    Es­ta úl­ti­ma de­cisión me llevó tam­bién a con­tem­plar que el archi­vo svg de­bería con­ver­tirse en un png, y para es­to uti­lizaría la her­ramien­ta Inkscape con una lla­ma­da al sis­tema para re­alizar dicha con­ver­sión (la gran ven­ta­ja de inkscape es que puede cor­rer to­tal­mente head­less con el parámetro -z).

En defini­ti­va mi di­rec­ti­va tomaría un no­do asi:

.. svgt:: file.svg
    :vars:
        { "nombre_variable_1": "valor_de_variable_1",
          "nombre_variable_2": "valor_de_variable_2",
          ...,
          "nombre_variable_N": "valor_de_variable_N"}

     <figure parameters>

Y lo con­vert­ería en un no­do así

.. figure:: file_con_parametros_resueltos.png
    <figure parameters>

Creando el template

Yo uso para crear svg a Inkscape y no pens­a­ba en ningún mo­men­to de­jar de hac­er­lo para crear este proyec­to. Así que so­lo era cuestión de abrir el pro­gra­ma y pon­er en la parte de donde se ref­er­en­cia a una url de una im­a­gen o en un tex­to al­gún for­ma­to que in­dique que eso es una vari­able (recorde­mos que por den­tro el svg no de­ja de ser tex­to plano).

Para elegir el lenguaje de template decidí utilizar el formato que propone la clase Template que viene en la librería estandar de; la cual dispone que las declaraciones de hacen anteponiendo el símbolo $ al nombre de la variable y pudiendo o no este nombre estar encerrado entre { y }.

Por ejem­p­lo en el sigu­iente im­a­gen se ve co­mo declaro dos vari­ables con Inkscape

/static/making_template.jpeg

Por otra parte inkscape des­de con­so­la se eje­cu­ta de la sigu­iente man­era para con­ver­tir un svg a png

::

$ inkscape -z file.svg -e file.p­ng -d 300

Don­de:

  • inkscape es el co­­man­­do para cor­r­er inkscape.

  • -z sirve para de­sha­­bil­i­­tar el en­­torno grá­­fi­­co.

  • file.svg es el archi­­vo a con­ver­tir.

  • -e file.p­ng in­di­ca que se va a ex­por­tar el archi­vo file.svg al for­ma­to png y se guardara en el archi­vo file.p­ng.

  • -d 300 dice que el archi­vo file.p­ng se creara con 300 dpi de cal­i­dad.

Con es­to me gen­ero los sigu­ientes prob­le­mas:

  • ¿Qué sucede si otro me pasa un tem­­plate y no se cuales son las var­i­ables que tiene aden­tro?

  • ¿Y si inkscape no es­­tá en el path de eje­cu­­ción?

  • ¿Y si no me gus­­tan los 300 dpi de cal­i­­dad?

A este pun­to ust­ed lec­tor ya se dara cuen­ta que to­do se tra­ta de agre­gar­le mas vari­ables a nues­tra sin­taxis ya definida: con lo cual to­do este cachibache quedaría así:

.. svgt:: file.svg
    :vars:
        { "nombre_variable_1": "valor_de_variable_1",
          "nombre_variable_2": "valor_de_variable_2",
          ...,
          "nombre_variable_N": "valor_de_variable_N"}
    :dpi: 72
    :inkscape_dir: /usr/bin
    :list_vars_and_exit:

     <figure parameters>

Sien­do:

  • :d­pi: 72 dice que el archi­­vo gen­er­a­­do para la figu­ra re­­sul­­tante ten­­dra 72d­pi.

  • :inkscape_dir: /us­r/bin in­di­ca que el co­man­do inkscape vive den­tro de la car­pe­ta /us­r/bin

  • :list_­vars_and_ex­it: es­tablece que svgt so­lo lis­tara por std­out la lista de var­i­ales ex­is­tente den­tro de file.svg y luego el pro­gra­ma ter­mi­nara (notese que so­lo tiene mo­tivos de de­bug­ing).

En códi­go python só­lo hay que crear un string con es­os hue­cos y luego llenar­los co­mo en el sigu­iente ejem­plo:

import os
cmd = "{isp} {svg} -z -e {png} -d {dpi}"
print cmd.format(isp=os.path.join("/usr/bin/", "inkscape"),
                 svg="file.svg",
                 png="file.png",
                 dpi="72")

[out] /usr/bin/inkscape file.svg -z -e file.png -d 72

A los bifes

Aho­ra si leyen­do el man­u­al de co­mo crear di­rec­ti­vas para do­cu­tils uno se en­cuen­tra que el es­quele­to mín­i­mo para crear es­tos bi­chos es el sigu­ien­te:

# imports necesarios
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst import Directive
from docutils.statemachine import StringList

# la directiva es una clase
class SVGTemplate(Directive):
    """ The svgt directive"""

    # cuantos argumentos obligatorios tenemos
    required_arguments = 0

    # cuantos argumentos opcionales
    optional_arguments = 0

    # si la directiva termina con una linea en blanco
    final_argument_whitespace = False

    # funciones de validación para los argumentos
    option_spec = {}

    # si puede tener mas rst adentro de la directiva
    has_content = True

    # método que hace el procesamiento
    def run(self):
        return []

# le pone nombre a la directiva y la registra
directives.register_directive("svgt", SVGTemplate)

Los parámetros

Primero em­pece­mos definien­do un dic­cionario que rela­ciona ca­da nom­bre de parámetro (los cuales yo defi­no que son TO­DOS op­cionales) con una fun­ción que los val­i­da. Por otra parte da­do la es­truc­tura que quer­e­mos gener­ar de fig­ure posee con­tenido y la nues­tra tam­bién debe poseer­lo.

import json

# docutils imports

SPECS = {

    # variables mías y funciones que la validan y convierten en datos utiles
    # para nuestro procesamiento

    # convierte vars a un json
    "vars": json.loads,
    # convierte dpi a entero positivo
    "dpi": directives.nonnegative_int,
    # preprocesa el directorio quitandole espacios finales e iniciales
    # en blanco
    "inkscape_dir": directives.path,
    # se fija que por ser una bandera no tenga ni un valor asignado
    # si eso sucede retorna None sino tira un ValueError
    "list_vars_and_exit": directives.flag,

    # variables de figure
    "alt": directives.unchanged,
    "height": directives.length_or_percentage_or_unitless,
    "width": directives.length_or_percentage_or_unitless,
    "scale": directives.percentage,
    "align": lambda align: directives.choice(align, ('left', 'center', 'right')),
    "target": directives.uri,
    "class": directives.unchanged,
    "name": directives.unchanged,
    "figwidth": directives.length_or_percentage_or_unitless,
    "figclass": directives.unchanged,
}

# la directiva es una clase
class SVGTemplate(Directive):
    """ The svgt directive"""

    required_arguments = 0

    # cuantos argumentos opcionales
    optional_arguments = len(SPECS)

    final_argument_whitespace = False

    # funciones de validacion para los argumentos
    option_spec = SPECS

    has_content = True

    def run(self):
        return []

directives.register_directive("svgt", SVGTemplate)

Todas las funciones de validación (menos json.load) estan muy bien explicadas en la documentación de docutils .

Entendiendo los Nodos

La creación de nodos se hace con funciones que habitan en el modulo from docutils import nodes y todos los nodos se crean así (tomando de ejemplo figure):

my_new_node = figure(self, rawsource='', *children, **attributes)

don­de:

  • raw­­source: es el codi­­go fuente del no­­do que sirve para mo­­tivos de de­bug­in­ng, si salta un er­ror te mues­­tra donde es­­­ta la fal­la basan­­dose en este string. Así que mien­­tras mas rep­re­sen­­ta al no­­do este ar­gu­­men­­to mejor.

  • *chil­­dren: son los no­­dos hi­jos.

  • **at­tribute: los atrib­u­­tos y op­­ciones del nue­­vo no­­do. si se pasa una op­­cion in­­­val­i­­da sim­­ple­­mente se ig­no­ra.

Entendienfo figure

Las fig­uras son un no­do que con­tiene 3 hi­jos:

  • im­age que con­­tiene la im­a­­gen propi­a­­mente dicha.

  • cap­­tion que es un pár­rafo que le da una eti­que­­ta a la im­a­­gen.

  • leg­end que con­­tiene to­­do los par­rafos so­brantes que no son el cap­­tion

Os­ea al­go asi:

+---------------------------+
|        figure             |
|                           |
|<------ figwidth --------->|
|                           |
|  +---------------------+  |
|  |     image           |  |
|  |                     |  |
|  |<--- width --------->|  |
|  +---------------------+  |
|                           |
|The figure's caption should|
|wrap at this width.        |
|                           |
|Legend                     |
+---------------------------+

Con es­to en mente y con el con­cep­to de que TO­DO NO­DO DEBE SER PROCE­SA­DO O DE­STRU­I­DO MAN­UAL­MENTE va­mos ya a en­car­ar el al­go­rit­mo.

Explicando el método run

La fun­ción run só­lo tiene una condi­ción: debe de­volver un lista que con­tiene no­dos de do­cu­tils a ser ren­der­iza­dos

Así que a mano alza­da run sería al­go mas o menos así:

def run(self):
    # sacamos la direccion donde se encuentra el svg
    uri = self.arguments[0]

    # extraemos nuestras variables y les asignamos un valor por defecto en
    # caso de no existir
    options = dict(self.options)
    svgt_vars = options.pop("vars") if "vars" in options else {}
    svgt_dpi = options.pop("dpi") if "dpi" in options else 72
    svgt_isd = options.pop("inkscape_dir") if "inkscape_dir" in options else ""
    svgt_lvae = options.pop("list_vars_and_exit") == None \
                if "list_vars_and_exit" in options else False

    # si tenemos seteado el flag list_vars_and_exit mostramos las variables
    # y salimos del programa
    if svgt_lvae:
        self._show_vars(uri)
        sys.exit(0)

    # por como esta diseñado figure hay que evitar que la propiedad align
    # le llege a su imagen interior.
    fig_align = options.pop("align") if "align" in options else None

    # pasamos a crear el archivo png
    png_path = self._render_svg(uri, svgt_isd, svgt_dpi, svgt_vars)

    # agremamos la uri del png a las opciones
    options["uri"] = png_path

    # creamos el nodo imagen
    image_node = nodes.image(self.block_text, **options)

    # el contenido de caption y legend viene todo mezclado
    # como un iterable llamado docutils.statemachine.StringList
    # hay que separarlo en dos partes para crear la figure.
    caption_content, legend_content = self._separate_content(self.content)

    # creamos el nodo caption procesando su contenido con
    # nested_parse
    caption_node = nodes.caption("\n".join(caption_content))
    self.state.nested_parse(caption_content, self.content_offset, caption_node)

    # creamos el nodo legend y procesamos su contenido
    legend_node = nodes.legend("\n".join(legend_content))
    self.state.nested_parse(legend_content, self.content_offset, legend_node)

    # restautamos la variable align para crear el nodo figure
    if fig_align != None:
        options["align"] = fig_align

    # creaamos el susodicho nodo figure pasandole sus hijos
    figure_node = nodes.figure(self.block_text, image_node,
                               caption_node, legend_node, **options)

    # retornamos una lista con el nodo resultante
    return [figure_node]

Explicando el método _render_svg

Este método utiliza otros dos, _resolve_render_name que sera explicado después y el método _call que debido a su extrema simplicidad se recomienda leer directamente la documentació del clase subprocess.Popen

CMD = "{isp} {svg} -z -e {png} -d {dpi}"

# donde:
#   - uri es la direccion donde esta el svg a renderizar
#   - svgt_isd es el lugar donde esta inkscape
#   - svgt_dpi son los dpi del png a renderizar
#   - svgt_vars es un diccionario que contiene los valores de las variables del svg
def _render_svg(self, uri, svgt_isd, svgt_dpi, svgt_vars):

    # abrimos el svg y lo cargamos en un Template
    with open(uri) as fp:
        svg_tplt = string.Template(fp.read())

    # reemplazamos las variables con sus valores
    svg_src = svg_tplt.safe_substitute(svgt_vars)

    # obtenemos paths donde guardar:
    #   - El svg con las variables reemplazadas (fname_svg)
    #   - El png resultante (fname_png)
    fname_svg, fname_png = self._resolve_render_name(uri)

    # guardamos el svg reemplazado en su lugar
    with open(fname_svg, "w") as fp:
        fp.write(svg_src)

    # Ponemos los parametros de ejecucion de inkscape
    cmd = CMD.format(isp=os.path.join(svgt_isd, "inkscape"),
                     svg=fname_svg,
                     png=fname_png,
                     dpi=svgt_dpi)

    # ejecutamos inkscape
    self._call(cmd)

    # retornamos la direccion del nuevo png
    return fname_png

El método _resolve_render_name

Este méto­do a primera vista es in­ece­sar­i­o, ya que po­dríamos gener­ar archivos tem­po­rales efi­cien­te­mente con el mod­u­lo _tem­plate, pero da­do co­mo tra­ba­ja sphinx es­to no es posi­ble de primera mano.

# uri es la direccioón del svg original
def _resolve_render_name(self, uri):

    # leemos el directorio temporal de la variable global _tempdir
    # que pudo haber sido modificada por sphinx o tomamos el dir temporal
    # del sistema operativo. Esto se debe a que sphinx corre en un sandbox
    # y todos los archivos deben estar en la misma carpeta donde se
    # encuentra el archivo de configuración conf.py
    tempdir = _tempdir if _tempdir != None else tempfile.gettempdir()

    # extraemos el nombre del archivo sin su extensión
    basename = os.path.basename(uri).rsplit(".", 1)[0]

    # agregamos al directorio temporal el nombre del archivo sin la
    # extensión
    render_name = os.path.join(tempdir, basename)

    # generamos un nuevo nombre para el archivo svg agregando un entero
    # al final para evitar coliciones
    idx = ""
    unique_svg = render_name + "{idx}" + ".svg"
    while os.path.exists(unique_svg.format(idx=idx)):
       idx = idx + 1 if isinstance(idx, int) else 1
    unique_svg = unique_svg.format(idx=idx)

    # lo mismo para el png
    idx = ""
    unique_png = render_name + "{idx}" + ".png"
    while os.path.exists(unique_png.format(idx=idx)):
       idx = idx + 1 if isinstance(idx, int) else 1
    unique_png = unique_png.format(idx=idx)

    # retornamos los dos valores
    return unique_svg, unique_png

El método _separate_content

Este es el último método que utiliza el método run y es lo último que necesitamos para hacer el trabajo de svgt

# recibe por parámetro un string list con todos los nodos de texto del
# contenido de svgt
def _separate_content(self, content):

    # primero creamos un nodo stringlist exclusivamente para el caption
    caption_cnt = StringList(parent=content.parent,
                           parent_offset=content.parent_offset)

    # ahora algo IMPORTANTE.. la leyenda va a ser el mismo nodo contenido
    # por que la otra alternativa es crear uno nuevo y borrar el viejo
    # por que TODO NODO TIENE QUE SER PROCESADO O ELIMINADO MANUALMENTE
    legend_cnt = content # all nodes need to be procesed

    # si tenemos contenido copiamos el primer elemento al caption y quitamos
    # ese elemento de la leyenda (que es lo mismo que el content)
    if content:
        caption_cnt.append(content[0], content.source(0), content.offset(0))
        content.pop(0)
    return caption_cnt, legend_cnt

Pensando en sphinx

Para que este bicho funcione en sphinx es necesario agregar a nivel de modulo una función setup que recibe un único parámetro que es la instancia de sphinx corriendo.

def setup(app):

    # toma el valor la referencia a la variable de módulo _tempdir
    global _tempdir

    # esta función reci be dos parámetros requeridos para usarse como
    # slot de la señal build-finished de sphinx y sirve para limpiar
    # el directorio temporal.
    #   - la aplicación que ejecuto la señal
    #   - la exception que genero el fin del procesamiento o None si
    #     finalizo normalmente.
    def reset(app, ex):
        if _tempdir != None and os.path.isdir(_tempdir):
            shutil.rmtree(_tempdir)
        if ex:
            raise ex

    # agregamos una configuracion mas a sphinx llamada tempdir y asignamos
    # como valor por defecto _temp
    app.add_config_value("tempdir", "_temp", "")

    # tomamos el valor de tempdir de la configuración
    _tempdir = app.config["tempdir"]

    # reiniciamos el directorio temporal
    reset(app, None)

    # lo volvemos a crear
    os.mkdir(_tempdir)

    # conectamos la señal build-finished con la función reset
    app.connect("build-finished", reset)

Si de­sean mas in­for­ma­ción lean la doc­u­mentación del api de ex­ten­siones de sphinx

Y como se usa todo esto

Bueno con rst2pdf o bi­en lo tiran en la car­pe­ta de ex­ten­siones, o en el path donde tienen su rst y luego eje­cu­tan

$ rst2pdf archivo.rst -e svgt

Tam­bién pueden in­sta­lar­lo des­de pypi con easy_in­stall o pip con los co­man­dos:

$ pip install docutils_ext

o

$ easy_install docutils_ext

En el ca­so de sphinx, tienen que agre­gar el path donde se en­cuen­tre a sys.­path y luego agre­gar­la a la lista de ex­ten­siones.

Y funciona?

Pueden ver el códi­go com­ple­to al mo­men­to de la pub­li­cación de este artícu­lo aca además de poder descar­gar la úl­ti­ma ver­sión es­table en la pes­tañi­ta de down­load­s.

Lo de aca aba­jo dice

.. svgt:: img/temp.svg
    :vars: {"name": "that's all folks", "url": "img/troll.png"}
troll

Going out

When we were not mar­ried, Rosario and I lived very far from each oth­er which meant that when we met, we stuck to­geth­er for 24, or 36 hours. Our 3rd date last­ed al­most 3 days.

A few days ago, we de­cid­ed to have one of those, even though we were with Tato for a part of it.

So, we went from home to the Museo Par­tic­i­pa­ti­vo de Cien­ci­as, in Reco­le­ta, a cool kids sci­ence mu­se­um, full of cool stuff.

First we went to Buenos Aires De­sign, place full of cool and very over­priced de­sign­er stuff. Bought two pen­s.

Since Georgina, a friend of ours who lives *very* far away from us works at the Cen­tro Cul­tur­al Reco­le­ta, we de­cid­ed to kid­nap her for a cup of cof­fee

IMAG0365

In the mean­time, Tato ac­quired two bal­loons and start­ed mak­ing in­ap­propi­ate things with them:

IMAG0366

But that was just a mo­men­t. Then... I'm an ele­phan­t!!!!

IMAG0369

We en­tered the Cen­tro Cul­tur­al Reco­le­ta, which is a very cool place...

IMAG0370

Tato and us per­formed a bunch of ex­per­i­ments. He even got to play with a Van­der­graaf gen­er­a­tor!!!!

IMAG0373

Then we wan­dered the scary ar­eas...

IMAG0374

When we got out, it was get­ting dark­...

IMAG0375

And Tato dis­cov­ered the sad truth about can­died ap­ples: they are crap...

IMAG0377

We left tato with his aun­ties Lau­ra and Agusti­na and start­ed the grownup part of the date. First, we saw Anony­mous protest­ing sci­en­tol­o­gy (and a troll­face!)...

IMAG0378

We had a pi­ca­da at a place called Ba­bieca. Great mor­tade­la! I saw an ex­am­ple of ram­pant cap­i­tal­is­m...

IMAG0380

Then our bank heist was foiled by a tell­er not giv­ing us the "s­mall de­nom­i­na­tion, un­marked, not-se­quen­tial" bills we want­ed...

IMAG0381

Turns out Rosario had nev­er been to the Ate­neo Grand Splen­did, the pret­ti­est book­store I have ev­er seen. So we went there to see some books and get a so­da or some­thing...

IMAG0384

The roof is pret­ty spe­cial...

IMAG0383

This is a re­stored teather, and the bar is in the stage...

IMAG0389

And you can see the an­cient lights board (luck­i­ly: dis­abled)...

IMAG0386

So, we walked on Cor­ri­entes av­enue and saw this (and yes, you can fake 7 sec­onds of tan­go by fol­low­ing the step­s, but it's very hard for the la­dy)...

IMAG0391

Then there was a guy, walk­ing in an ele­phan­t's trunk thong, while his friends (who had shorn him and cov­ered him in flour) whis­tled and shout­ed: a "de­s­pe­di­da de soltero", rit­u­al haz­ing for a guy get­ting mar­ried...

IMAG0392

He end­ed talk­ing with a 7-­foot-­tall trans­ves­tite in red se­quin­s. The trans­ves­tite shout­ing "y­ou are a healthy boy!"...

IMAG0393

So, af­ter a nice evening at a bor­rowed ap­part­men­t, next morn­ing we got Tato back!

IMAG0394

A hob­bit­sian 3-hour break­fast en­sued...

IMAG0395 IMAG0396 IMAG0397

Then tax­i, train home, and we got back al­most ex­act­ly 26 hours af­ter we left. Ev­ery­one had fun, and thanks to Agusti­na and Lau­ra (or Lau­ra and Agusti­na), be­cause with­out the mel­lis, this would have been im­pos­si­ble.


Contents © 2000-2021 Roberto Alsina