Mariano Guerra: Creemos en la Web: HTML atributos y meta datos

En la sección anterior vimos como crear una pagina web que replicaba un documento de Google Docs con formato básico, en esta sección vamos a aprender sobre dos conceptos con nombres tenebrosos pero que como todo lo tenebroso cuando se los entiende resulta ser bastante inofensivo.

Necesitamos saber sobre atributos y meta datos para poder resolver dos problemas en nuestra pagina web:

  1. Como le digo al navegador cual es el titulo de mi pagina web sin que lo muestre en el contenido del documento?
  2. Como le indico información extra sobre un tag sin que se vea en el documento? por ejemplo, como separo el texto de un enlace y la dirección a la que apunta?

Para el primero vamos a usar meta datos, que son datos sobre el documento pero que no son parte del contenido del mismo.

Para lo segundo vamos a usar atributos, que son datos sobre los tags pero no son parte del contenido.

Meta datos

Para separar los meta datos del documento del contenido hacemos lo usual, rodeamos los metadatos en un tag y el contenido en otro.

En la terminología HTML un documento esta separado en dos partes, la cabeza o cabecera (head en ingles), la cual contiene los datos sobre el documento y el cuerpo (body), el cual tiene el contenido del mismo.

Como estos dos son parte de un documento HTML, ambos están contenidos en el tag raíz de todo documento HTML, el tag html.

Veamos el documento correctamente estructurado mas simple que podamos tener:

<html>
    <head></head>
    <body></body>
</html>

Si queremos definir el titulo del documento, el cual se va a mostrar en la barra de títulos del navegador cuando la pagina tiene el foco y también en la pestaña de la pagina en el navegador, usamos el tag title.

<html>
    <head>
        <title>Mi Pagina</title>
    </head>

    <body>
    </body>
</html>

Otro tag que es recomendable poner en el documento si no queremos tener problemas con como el navegador muestra las tildes y la ñ es un tag que le indica como interpretar tildes y otros caracteres especiales, utf-8 es un estándar que define como interpretar caracteres especiales y es el mas usado actualmente, simplemente copialo en todos tus documentos para evitar dolores de cabeza.

<html>
    <head>
        <meta chartset="utf-8">
        <title>Mi Pagina</title>
    </head>

    <body>
    </body>
</html>

Para terminar, dado que HTML es un estándar que ha evolucionado en el tiempo, el navegador soporta múltiples versiones y si no se le indica la versión intenta adivinar.

Normalmente adivina bien, pero para facilitarle el trabajo y evitar confusiones podemos ser buenas personas e indicarselo explícitamente.

<!doctype html>
<html>
    <head>
        <meta chartset="utf-8">
        <title>Mi Pagina</title>
    </head>

    <body>
    </body>
</html>

La primera linea le indica que el documento es de tipo HTML 5, la ultima versión del estándar.

No te preocupes en memorizar estos tags, yo simplemente copio de algún documento anterior las partes comunes, nunca empiezo de cero :)

La evolución de HTML y su énfasis en mantener compatibilidad hace que todavía puedas visitar la primera pagina web publicada en 1991: http://info.cern.ch/hypertext/WWW/TheProject.html

Atributos

Ahora al segundo problema, como indicamos información sobre un tag que no es el contenido principal, por ejemplo, si queremos crear un enlace a https://google.com pero queremos que el texto del enlace diga "Google", como hacemos esto?

Quizás lo notaste en algunos de los ejemplos hasta ahora.

Para hacer eso usamos lo que se llaman atributos, que son información extra que agregamos a un tag, la mayoría son opcionales, de manera que los vamos agregando y aprendiendo a medida que los vamos necesitando.

Veamos como resolver el problema del enlace.

<a href="https://google.com">Google</a>

Que se ve así:

Google

Los atributos van luego del identificador del tag de apertura, separados por espacios, primero va el nombre del atributo, luego un = y luego el valor, normalmente entre comillas.

Por si no te diste cuenta, acabamos de aprender un nuevo tag, el tag a (de a nchor que significa ancla en ingles) con su atributo href (de h ypertext ref erence en ingles).

Ya que aprendimos un tag nuevo, aprendamos otro similar y muy útil, el tag para mostrar imágenes [1]:

<img src="http://marianoguerra.org/galleries/cew/3/cube.jpg">

Así se ve:

Agreguemos mas atributos, uno para el tooltip (title), y dos para definir el alto (height) y el ancho (width).

<img title="un cubo" width="200" height="200" src="http://marianoguerra.org/galleries/cew/3/cube.jpg">

El resultado:

Ahora pongamos todo junto en una pagina:

<!doctype html>
<html>
    <head>
        <meta chartset="utf-8">
        <title>Mi Pagina</title>
    </head>

    <body>
        <p>Un link a <a href="https://google.com">Google</a></p>

        <p>Una imagen:</p>

        <img title="un cubo" width="200" height="200" src="http://marianoguerra.org/galleries/cew/3/cube.jpg">
    </body>
</html>

Que se ve algo así:

/galleries/cew/3/01-page.png

Notar que subí la imagen al proyecto, haciendo click en el icono de archivo y seleccionando Subir un archivo..., la dirección de la imagen es simplemente el nombre del archivo ya que esta en el mismo lugar que la pagina que la muestra.

/galleries/cew/3/02-upload.png
[1] “DOF Example” by Owen Byrne is licensed under CC BY 2.0

Mariano Guerra: Creemos en la Web: HTML

Qué es HTML?

Crear paginas web involucra normalmente 3 "lenguajes" (formas que tenemos los humanos de decirle a una computadora que queremos que haga).

El único necesario es el que vamos a cubrir en esta sección: HTML

HTML permite describir el contenido de una pagina web que un programa especial llamado normalmente navegador web interpreta y muestra en la pantalla.

El contenido de un archivo HTML es texto con un formato especial, pero que podemos inspeccionar y editar con cualquier editor de texto.

Normalmente cuando la gente quiere crear un documento de texto que tenga cierto formato usa un programa como Microsoft Word, Google Docs o Libre Office Writer, en estos programas le indicamos con acciones al editor que partes del texto tienen que formato, algo como lo siguiente:

/galleries/cew/2/01-editor-visual.gif

Para indicarle al editor que una linea es un titulo, la seleccionamos, demarcando los limites y luego le indicamos que queremos que se muestre como un titulo.

Los párrafos simplemente los separamos con saltos de linea, si queremos texto en negrita o itálica, al igual que con el titulo, seleccionamos e indicamos que formato queremos para la selección.

HTML es un lenguaje que inicialmente fue pensado para escribir en editores de texto y luego evoluciono para ser generado por programas, que toman datos de una base de datos y generan como salida texto en formato HTML.

Si bien existen algunos editores visuales para HTML, normalmente este se edita a mano con editores de texto, y eso es lo que vamos a hacer.

La pagina mas simple del mundo

Escribí lo siguiente en la barra de dirección de tu navegador:

data:text/html, hola mundo

Felicitaciones! acabas de crear tu primera pagina web!

Normalmente lo que escribimos en la barra de direcciones del navegador es la ubicación de la pagina web, la primera parte (http: o https:) le indica al navegador que lo que sigue es la ubicación de la pagina que queremos ver y que la puede solicitar usando el "protocolo" [1] HTTP (Protocolo de Transferencia de Hiper Texto), lo que sigue es la dirección de la pagina, similar a la ubicación de un archivo en tu computadora, pero empezando con la pagina web que contiene la pagina.

En este caso le decimos que le vamos a indicar la pagina directamente, y que esta en formato HTML, luego escribimos el contenido de la misma.

Si bien no es una forma ideal de crear paginas web, a veces es útil para tareas especificas, por ejemplo:

Selector de colores:

data:text/html, <input type="color">

Calendario:

data:text/html, <input type="date">

Bloc de notas:

data:text/html, <body contenteditable style="max-width:60rem;margin:0 auto;padding:4rem;">bloc de notas

No te preocupes por la parte style="max-width:60rem;margin:0 auto;padding:4rem;" eso es el segundo lenguaje que vamos a ver en la próxima sección.

Nuestra primera pagina web

Nuestra primera pagina web va a intentar replicar el ejemplo de Google Docs que vimos mas arriba, con esta pagina vamos a cubrir los principales elementos de HTML.

Empezamos creando un nuevo proyecto en Thimble, si tenes dudas de como hacerlo revisa la sección anterior que contiene una introducción a Thimble.

El nuevo proyecto comienza con un contenido por defecto:

<!DOCTYPE html>
<html>
  <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Made with Thimble</title>
        <link rel="stylesheet" href="style.css">
  </head>
  <body>
        <h1>Welcome to Thimble</h1>
        <p>
          Make something <strong>amazing</strong> with the web!
        </p>
  </body>
</html>

Lo vamos a borrar y escribir el siguiente texto:

Esto es un título

Esto es un párrafo, la siguiente palabra es en negrita, la siguiente en itálica

Esto es otro párrafo

Una lista no ordenada:

Manzana
Durazno
Banana

Una lista ordenada:

Uno
Dos
Tres

El resultado debería ser similar al siguiente:

/galleries/cew/2/02-plain-text.png

Si la vista previa no se actualiza automáticamente podes hacer click en el botón de refrescar vista previa.

Como podemos ver la vista previa muestra todo el texto junto y sin formato, algo así:

Esto es un título Esto es un párrafo, la siguiente palabra es en negrita, la siguiente en itálica Esto es otro párrafo Una lista no ordenada: Manzana Durazno Banana Una lista ordenada: Uno Dos Tres

Sin formato es esperable ya que no le indicamos ninguno, pero porque todo junto?

Porque HTML "junta" todos los espacios y saltos de lineas a un solo espacio, si queremos especificar algo distinto lo tenemos que hacer explícitamente.

Esto nos va a permitir estructurar el documento con claridad y estructura sabiendo que el navegador no va a reflejar nuestros espacios y saltos de linea en el documento final.

Ya tenemos el contenido de nuestra primera pagina web, es un avance! pero no es muy diferente a un documento de texto, como agregamos el formato?

En el ejemplo de Google Docs mas arriba para indicar el formato de las distintas partes lo que hacíamos era indicar el principio y el fin de la sección a la que le queriamos aplicar formato con el mouse y luego seleccionamos una operación en el menú para indicarle que tipo de formato queremos.

En HTML es casi igual, salvo que no tenemos mouse ni menú :)

Como HTML es un formato de texto, tenemos que trabajar con lo que tenemos, pero la forma es muy parecida, primero indicamos el principio y final de una sección a la que le queremos aplicar una operación y luego le indicamos cual es esa operación.

En HTML el indicador para el inicio de una sección es <> y el indicador de fin es </>

Pero eso no funciona porque todavía tenemos que indicar que operación aplicar al texto entre <> y </>, para eso escribimos la operación entre el < y el >.

Empecemos con el titulo, si notas en la animación de Google Docs, la operación se llama Heading 1, es medio largo para escribir todo eso cada vez que queremos un titulo de nivel 1, así que lo acortamos a h1.

Cambiemos

Esto es un título

Por

<h1>Esto es un título</h1>

El resultado debería verse algo así:

/galleries/cew/2/03-h1.png

Tenemos el titulo!

Y ese es el primer tag (etiqueta en inglés) que aprendimos:

h1
Formatea el texto delimitado como un titulo de nivel 1

Si digo titulo de nivel 1 podemos imaginarnos que hay mas niveles, es como el indice de un libro, las secciones tienen sub secciones y cada sección tiene un titulo de un nivel mas alto.

En HTML tenemos 6 niveles: h1, h2, h3, h4, h5, h6.

Continuemos.

La siguiente linea dice:

Esto es un párrafo, la siguiente palabra es en negrita, la siguiente en itálica

Como le indicamos que es un párrafo? como con el titulo, lo rodeamos de una etiqueta de apertura y una de cierre y le indicamos que es un párrafo, pero de nuevo, escribir párrafo en español o ingles por cada párrafo es bastante largo así que lo vamos a abreviar a p

<p>Esto es un párrafo, la siguiente palabra es en negrita, la siguiente en itálica</p>

El resultado debería verse similar al siguiente:

/galleries/cew/2/04-p.png

Notaras que ahora el primer párrafo tiene espacio con respecto al titulo y al resto del texto, ahora hacemos lo mismo con los siguientes párrafos:

<h1>Esto es un título</h1>

<p>Esto es un párrafo, la siguiente palabra es en negrita, la siguiente en itálica</p>

<p>Esto es otro párrafo</p>

<p>
  Una lista no ordenada:

Manzana
Durazno
Banana
</p>

<p>
Una lista ordenada:

Uno
Dos
Tres
</p>

Como veras los saltos de linea y los espacios no afectan el formato final.

/galleries/cew/2/05-p.png

Tenemos el titulo y los párrafos, ya casi que podemos escribir un cuento en HTML :)

Pero un poco mas de formato no vendría mal, sigamos con negrita e itálica.

No hay nada de magia, es igual a las anteriores, rodeamos la sección que queremos formatear y le indicamos que formato queremos aplicarle.

A ver si te podes imaginar que identificador lleva negrita (b old en ingles) e itálica (i talic en ingles)?

<p>Esto es un párrafo, la siguiente palabra es en <b>negrita</b>, la siguiente en <i>itálica</i></p>

El resultado debería verse algo así:

/galleries/cew/2/06-bi.png

Como podes ver en el párrafo, podemos tener tags/etiquetas dentro de otros tags/etiquetas, en este caso tenemos tags negrita e itálica dentro del tag de párrafo.

Ya casi estamos!

Solo faltan las listas, en este caso tenemos que indicar dos cosas distintas con tags:

  1. Que queremos una lista

    • Ordenada: Numerada
    • No Ordenada: Sin Numeración
  2. Cuales son los elementos de la lista

Empecemos con la lista no ordenada, en ingles u nordered l ist, ya te podes imaginar como se identifica el tag:

<ul>
Manzana
Durazno
Banana
</ul>

Pero esto no es suficiente, todavía le tenemos que decir cuales son los elementos de la lista (l ist i tem en ingles):

<ul>
        <li>Manzana</li>
        <li>Durazno</li>
        <li>Banana</li>
</ul>

De nuevo tenemos tags dentro de otro tag, en este caso el tag li (list item) dentro del tag ul (unordered list)

Para la lista ordenada es igual, pero en lugar de indicar que queremos una lista no ordenada, le indicamos que queremos una ordenada (o rdered l list en ingles)

<ol>
        <li>Uno</li>
        <li>Dos</li>
        <li>Tres</li>
</ol>

El código completo:

<h1>Esto es un título</h1>

<p>Esto es un párrafo, la siguiente palabra es en <b>negrita</b>, la siguiente en <i>itálica</i></p>

<p>Esto es otro párrafo</p>

<p>
  Una lista no ordenada:
</p>

<ul>
  <li>Manzana</li>
  <li>Durazno</li>
  <li>Banana</li>
</ul>


<p>Una lista ordenada:</p>

<ol>
  <li>Uno</li>
  <li>Dos</li>
  <li>Tres</li>
</ol>

Que debería verse similar a esto:

/galleries/cew/2/07-lists.png

Y con esto replicamos el documento de Google Docs y sabemos un poco mas cual es el contenido de los archivos .doc, .odt y similares, solo que esos están en formato binario (unos y ceros) ya que las computadoras los prefieren en lugar del formato texto, que preferimos los humanos :)

[1] Un protocolo es un acuerdo entre dos o mas partes que establece la forma en la que se van a comunicar, en este caso establece como un navegador solicita un documento HTML y como el otro se lo envía.

Mariano Guerra: Creemos en la Web: HTML

Qué es HTML?

Crear paginas web involucra normalmente 3 "lenguajes" (formas que tenemos los humanos de decirle a una computadora que queremos que haga).

El único necesario es el que vamos a cubrir en esta sección: HTML

HTML permite describir el contenido de una pagina web que un programa especial llamado normalmente navegador web interpreta y muestra en la pantalla.

El contenido de un archivo HTML es texto con un formato especial, pero que podemos inspeccionar y editar con cualquier editor de texto.

Normalmente cuando la gente quiere crear un documento de texto que tenga cierto formato usa un programa como Microsoft Word, Google Docs o Libre Office Writer, en estos programas le indicamos con acciones al editor que partes del texto tienen que formato, algo como lo siguiente:

/galleries/cew/2/01-editor-visual.gif

Para indicarle al editor que una linea es un titulo, la seleccionamos, demarcando los limites y luego le indicamos que queremos que se muestre como un titulo.

Los párrafos simplemente los separamos con saltos de linea, si queremos texto en negrita o itálica, al igual que con el titulo, seleccionamos e indicamos que formato queremos para la selección.

HTML es un lenguaje que inicialmente fue pensado para escribir en editores de texto y luego evoluciono para ser generado por programas, que toman datos de una base de datos y generan como salida texto en formato HTML.

Si bien existen algunos editores visuales para HTML, normalmente este se edita a mano con editores de texto, y eso es lo que vamos a hacer.

La pagina mas simple del mundo

Escribí lo siguiente en la barra de dirección de tu navegador:

data:text/html, hola mundo

Felicitaciones! acabas de crear tu primera pagina web!

Normalmente lo que escribimos en la barra de direcciones del navegador es la ubicación de la pagina web, la primera parte (http: o https:) le indica al navegador que lo que sigue es la ubicación de la pagina que queremos ver y que la puede solicitar usando el "protocolo" [1] HTTP (Protocolo de Transferencia de Hiper Texto), lo que sigue es la dirección de la pagina, similar a la ubicación de un archivo en tu computadora, pero empezando con la pagina web que contiene la pagina.

En este caso le decimos que le vamos a indicar la pagina directamente, y que esta en formato HTML, luego escribimos el contenido de la misma.

Si bien no es una forma ideal de crear paginas web, a veces es útil para tareas especificas, por ejemplo:

Selector de colores:

data:text/html, <input type="color">

Calendario:

data:text/html, <input type="date">

Bloc de notas:

data:text/html, <body contenteditable style="max-width:60rem;margin:0 auto;padding:4rem;">bloc de notas

No te preocupes por la parte style="max-width:60rem;margin:0 auto;padding:4rem;" eso es el segundo lenguaje que vamos a ver en la próxima sección.

Nuestra primera pagina web

Nuestra primera pagina web va a intentar replicar el ejemplo de Google Docs que vimos mas arriba, con esta pagina vamos a cubrir los principales elementos de HTML.

Empezamos creando un nuevo proyecto en Thimble, si tenes dudas de como hacerlo revisa la sección anterior que contiene una introducción a Thimble.

El nuevo proyecto comienza con un contenido por defecto:

<!DOCTYPE html>
<html>
  <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Made with Thimble</title>
        <link rel="stylesheet" href="style.css">
  </head>
  <body>
        <h1>Welcome to Thimble</h1>
        <p>
          Make something <strong>amazing</strong> with the web!
        </p>
  </body>
</html>

Lo vamos a borrar y escribir el siguiente texto:

Esto es un título

Esto es un párrafo, la siguiente palabra es en negrita, la siguiente en itálica

Esto es otro párrafo

Una lista no ordenada:

Manzana
Durazno
Banana

Una lista ordenada:

Uno
Dos
Tres

El resultado debería ser similar al siguiente:

/galleries/cew/2/02-plain-text.png

Si la vista previa no se actualiza automáticamente podes hacer click en el botón de refrescar vista previa.

Como podemos ver la vista previa muestra todo el texto junto y sin formato, algo así:

Esto es un título Esto es un párrafo, la siguiente palabra es en negrita, la siguiente en itálica Esto es otro párrafo Una lista no ordenada: Manzana Durazno Banana Una lista ordenada: Uno Dos Tres

Sin formato es esperable ya que no le indicamos ninguno, pero porque todo junto?

Porque HTML "junta" todos los espacios y saltos de lineas a un solo espacio, si queremos especificar algo distinto lo tenemos que hacer explícitamente.

Esto nos va a permitir estructurar el documento con claridad y estructura sabiendo que el navegador no va a reflejar nuestros espacios y saltos de linea en el documento final.

Ya tenemos el contenido de nuestra primera pagina web, es un avance! pero no es muy diferente a un documento de texto, como agregamos el formato?

En el ejemplo de Google Docs mas arriba para indicar el formato de las distintas partes lo que hacíamos era indicar el principio y el fin de la sección a la que le queriamos aplicar formato con el mouse y luego seleccionamos una operación en el menú para indicarle que tipo de formato queremos.

En HTML es casi igual, salvo que no tenemos mouse ni menú :)

Como HTML es un formato de texto, tenemos que trabajar con lo que tenemos, pero la forma es muy parecida, primero indicamos el principio y final de una sección a la que le queremos aplicar una operación y luego le indicamos cual es esa operación.

En HTML el indicador para el inicio de una sección es <> y el indicador de fin es </>

Pero eso no funciona porque todavía tenemos que indicar que operación aplicar al texto entre <> y </>, para eso escribimos la operación entre el < y el >.

Empecemos con el titulo, si notas en la animación de Google Docs, la operación se llama Heading 1, es medio largo para escribir todo eso cada vez que queremos un titulo de nivel 1, así que lo acortamos a h1.

Cambiemos

Esto es un título

Por

<h1>Esto es un título</h1>

El resultado debería verse algo así:

/galleries/cew/2/03-h1.png

Tenemos el titulo!

Y ese es el primer tag (etiqueta en inglés) que aprendimos:

h1
Formatea el texto delimitado como un titulo de nivel 1

Si digo titulo de nivel 1 podemos imaginarnos que hay mas niveles, es como el indice de un libro, las secciones tienen sub secciones y cada sección tiene un titulo de un nivel mas alto.

En HTML tenemos 6 niveles: h1, h2, h3, h4, h5, h6.

Continuemos.

La siguiente linea dice:

Esto es un párrafo, la siguiente palabra es en negrita, la siguiente en itálica

Como le indicamos que es un párrafo? como con el titulo, lo rodeamos de una etiqueta de apertura y una de cierre y le indicamos que es un párrafo, pero de nuevo, escribir párrafo en español o ingles por cada párrafo es bastante largo así que lo vamos a abreviar a p

<p>Esto es un párrafo, la siguiente palabra es en negrita, la siguiente en itálica</p>

El resultado debería verse similar al siguiente:

/galleries/cew/2/04-p.png

Notaras que ahora el primer párrafo tiene espacio con respecto al titulo y al resto del texto, ahora hacemos lo mismo con los siguientes párrafos:

<h1>Esto es un título</h1>

<p>Esto es un párrafo, la siguiente palabra es en negrita, la siguiente en itálica</p>

<p>Esto es otro párrafo</p>

<p>
  Una lista no ordenada:

Manzana
Durazno
Banana
</p>

<p>
Una lista ordenada:

Uno
Dos
Tres
</p>

Como veras los saltos de linea y los espacios no afectan el formato final.

/galleries/cew/2/05-p.png

Tenemos el titulo y los párrafos, ya casi que podemos escribir un cuento en HTML :)

Pero un poco mas de formato no vendría mal, sigamos con negrita e itálica.

No hay nada de magia, es igual a las anteriores, rodeamos la sección que queremos formatear y le indicamos que formato queremos aplicarle.

A ver si te podes imaginar que identificador lleva negrita (b old en ingles) e itálica (i talic en ingles)?

<p>Esto es un párrafo, la siguiente palabra es en <b>negrita</b>, la siguiente en <i>itálica</i></p>

El resultado debería verse algo así:

/galleries/cew/2/06-bi.png

Como podes ver en el párrafo, podemos tener tags/etiquetas dentro de otros tags/etiquetas, en este caso tenemos tags negrita e itálica dentro del tag de párrafo.

Ya casi estamos!

Solo faltan las listas, en este caso tenemos que indicar dos cosas distintas con tags:

  1. Que queremos una lista

    • Ordenada: Numerada
    • No Ordenada: Sin Numeración
  2. Cuales son los elementos de la lista

Empecemos con la lista no ordenada, en ingles u nordered l ist, ya te podes imaginar como se identifica el tag:

<ul>
Manzana
Durazno
Banana
</ul>

Pero esto no es suficiente, todavía le tenemos que decir cuales son los elementos de la lista (l ist i tem en ingles):

<ul>
        <li>Manzana</li>
        <li>Durazno</li>
        <li>Banana</li>
</ul>

De nuevo tenemos tags dentro de otro tag, en este caso el tag li (list item) dentro del tag ul (unordered list)

Para la lista ordenada es igual, pero en lugar de indicar que queremos una lista no ordenada, le indicamos que queremos una ordenada (o rdered l list en ingles)

<ol>
        <li>Uno</li>
        <li>Dos</li>
        <li>Tres</li>
</ol>

El código completo:

<h1>Esto es un título</h1>

<p>Esto es un párrafo, la siguiente palabra es en <b>negrita</b>, la siguiente en <i>itálica</i></p>

<p>Esto es otro párrafo</p>

<p>
  Una lista no ordenada:
</p>

<ul>
  <li>Manzana</li>
  <li>Durazno</li>
  <li>Banana</li>
</ul>


<p>Una lista ordenada:</p>

<ol>
  <li>Uno</li>
  <li>Dos</li>
  <li>Tres</li>
</ol>

Que debería verse similar a esto:

/galleries/cew/2/07-lists.png

Y con esto replicamos el documento de Google Docs y sabemos un poco mas cual es el contenido de los archivos .doc, .odt y similares, solo que esos están en formato binario (unos y ceros) ya que las computadoras los prefieren en lugar del formato texto, que preferimos los humanos :)

[1] Un protocolo es un acuerdo entre dos o mas partes que establece la forma en la que se van a comunicar, en este caso establece como un navegador solicita un documento HTML y como el otro se lo envía.

Mariano Guerra: Creemos en la Web

Este es el primero de lo que espero sera una serie de artículos sobre como aprender a hacer paginas web para personas sin ningún conocimiento previo de tecnología o programación.

Si sabes de tecnología recomendaselo a la mayor cantidad de personas posible, no sabemos cuantos grandes diseñadores y programadores web se encuentran escondidos por ahí.

Si te recomendaron esto y pensás que no es para vos, la cuestión es que si es para vos, y si algo es confuso no es tu culpa, es miá, así que contactame y decime que parte no esta clara así mejoramos esta guiá para todos.

Este articulo es una introducción a la herramienta que vamos a usar para los siguientes ejemplos, un proyecto llamado Mozilla Thimble, el cual facilita el proceso de crear, compartir y remixar paginas de otros.

Para los que prefieren ver videos, acá hay uno con el mismo contenido que este articulo:

Lo primero que vamos a necesitar hacer es crear una cuenta en Mozilla Thimble, visitando https://thimble.mozilla.org/es/, vamos a ver algo similar a la siguiente imagen:

/galleries/cew/1/01-landing.png

Creando una cuenta

/galleries/cew/1/02-sign-up.png

Click en Crea una cuenta en la parte superior derecha

Llena el formulario para crear una nueva cuenta:

  • Nombre de usuario
  • Tu dirección de correo electrónico
  • Contraseña (al menos una mayúscula, una minúscula y un numero)
/galleries/cew/1/03-sign-up-form.png

Luego de crear la cuenta debería ir directamente a la pagina principal de tu cuenta.

Iniciando sesión

Si en otro momento querés acceder de nuevo desde la pagina principal de Mozilla Thimble:

/galleries/cew/1/04-sign-in.png

Click en Inicia sesión

/galleries/cew/1/05-sign-in-form.png

Llena el formulario con tu usuario y contraseña

Espacio de trabajo principal

Espacio de trabajo vacío una vez que ingresamos a nuestra cuenta, para trabajar en un nuevo proyecto hay que hacer click en el botón verde Crear un nuevo proyecto

/galleries/cew/1/06-workspace.png

El espacio de trabajo de un proyecto tiene 3 paneles, de izquierda a derecha:

  • Explorador de archivos de proyecto
  • Editor de código
  • Vista previa del proyecto
/galleries/cew/1/07-new-project.png

Para hacer visible el proyecto en la web y compartirlo con un enlace, hacemos click en el botón blanco Publicar arriba a la derecha en el entorno de trabajo de nuestro proyecto.

/galleries/cew/1/08-publish-project.png

Ingresamos la descripción de nuestro proyecto y hacemos click en el botón verde Publicar

/galleries/cew/1/09-publish-project.png

Al finalizar el proceso podemos hacer click o copiar el enlace a nuestro proyecto publico en la web.

Si hacemos click en el botón rojo Eliminar la versión publicada* nuestro proyecto ya no sera accesible en la web.

/galleries/cew/1/10-publish-project.png
/galleries/cew/1/11-public-project.png

Nuestro proyecto de prueba en la web.

Si hacemos click en el botón verde Remix de cualquier proyecto publicado con Thimble, vamos a poder acceder a el desde nuestro entorno de trabajo, hacer nuestros cambios y publicar nuestros cambios.

/galleries/cew/1/12-remix-project.png

Podemos buscar proyectos para modificar en la página principal de Thimble

/galleries/cew/1/13-remix-landing.png

Si vamos a la sección Mezcla un proyecto para comenzar... podemos buscar por tema o filtrar por etiqueta, si seleccionamos la etiqueta html podemos ver proyectos que usen principalmente HTML, buscamos el proyecto Keep Calm and Carry On y clickeamos en el botón verde Mezclar.

/galleries/cew/1/14-remix-landing.png

Esto hará una copia del proyecto en nuestro usuario y abrirá el editor.

/galleries/cew/1/15-remix-change.png

Podemos cambiar el texto del poster a

Calma<br>
<span>y</span>
crea<br> en la web
/galleries/cew/1/16-remix-change.png

Y luego republicarlo con nuestros cambios

Mariano Guerra: Creemos en la Web

Este es el primero de lo que espero sera una serie de artículos sobre como aprender a hacer paginas web para personas sin ningún conocimiento previo de tecnología o programación.

Si sabes de tecnología recomendaselo a la mayor cantidad de personas posible, no sabemos cuantos grandes diseñadores y programadores web se encuentran escondidos por ahí.

Si te recomendaron esto y pensás que no es para vos, la cuestión es que si es para vos, y si algo es confuso no es tu culpa, es miá, así que contactame y decime que parte no esta clara así mejoramos esta guiá para todos.

Este articulo es una introducción a la herramienta que vamos a usar para los siguientes ejemplos, un proyecto llamado Mozilla Thimble, el cual facilita el proceso de crear, compartir y remixar paginas de otros.

Para los que prefieren ver videos, acá hay uno con el mismo contenido que este articulo:

Lo primero que vamos a necesitar hacer es crear una cuenta en Mozilla Thimble, visitando https://thimble.mozilla.org/es/, vamos a ver algo similar a la siguiente imagen:

/galleries/cew/1/01-landing.png

Creando una cuenta

/galleries/cew/1/02-sign-up.png

Click en Crea una cuenta en la parte superior derecha

Llena el formulario para crear una nueva cuenta:

  • Nombre de usuario
  • Tu dirección de correo electrónico
  • Contraseña (al menos una mayúscula, una minúscula y un numero)
/galleries/cew/1/03-sign-up-form.png

Luego de crear la cuenta debería ir directamente a la pagina principal de tu cuenta.

Iniciando sesión

Si en otro momento querés acceder de nuevo desde la pagina principal de Mozilla Thimble:

/galleries/cew/1/04-sign-in.png

Click en Inicia sesión

/galleries/cew/1/05-sign-in-form.png

Llena el formulario con tu usuario y contraseña

Espacio de trabajo principal

Espacio de trabajo vacío una vez que ingresamos a nuestra cuenta, para trabajar en un nuevo proyecto hay que hacer click en el botón verde Crear un nuevo proyecto

/galleries/cew/1/06-workspace.png

El espacio de trabajo de un proyecto tiene 3 paneles, de izquierda a derecha:

  • Explorador de archivos de proyecto
  • Editor de código
  • Vista previa del proyecto
/galleries/cew/1/07-new-project.png

Para hacer visible el proyecto en la web y compartirlo con un enlace, hacemos click en el botón blanco Publicar arriba a la derecha en el entorno de trabajo de nuestro proyecto.

/galleries/cew/1/08-publish-project.png

Ingresamos la descripción de nuestro proyecto y hacemos click en el botón verde Publicar

/galleries/cew/1/09-publish-project.png

Al finalizar el proceso podemos hacer click o copiar el enlace a nuestro proyecto publico en la web.

Si hacemos click en el botón rojo Eliminar la versión publicada* nuestro proyecto ya no sera accesible en la web.

/galleries/cew/1/10-publish-project.png
/galleries/cew/1/11-public-project.png

Nuestro proyecto de prueba en la web.

Si hacemos click en el botón verde Remix de cualquier proyecto publicado con Thimble, vamos a poder acceder a el desde nuestro entorno de trabajo, hacer nuestros cambios y publicar nuestros cambios.

/galleries/cew/1/12-remix-project.png

Podemos buscar proyectos para modificar en la página principal de Thimble

/galleries/cew/1/13-remix-landing.png

Si vamos a la sección Mezcla un proyecto para comenzar... podemos buscar por tema o filtrar por etiqueta, si seleccionamos la etiqueta html podemos ver proyectos que usen principalmente HTML, buscamos el proyecto Keep Calm and Carry On y clickeamos en el botón verde Mezclar.

/galleries/cew/1/14-remix-landing.png

Esto hará una copia del proyecto en nuestro usuario y abrirá el editor.

/galleries/cew/1/15-remix-change.png

Podemos cambiar el texto del poster a

Calma<br>
<span>y</span>
crea<br> en la web
/galleries/cew/1/16-remix-change.png

Y luego republicarlo con nuestros cambios

Facundo Batista: De trabajo en Hungría


Estuve una semana en Budapest, trabajando en un sprint con otros compañeros de equipo y de otros equipos, en general.

El viaje largo, pero sin sorpresas... sólo el detalle que me perdieron la valija en el viaje de ida :(. Cuando fui a hacer el reclamo, se fijaron y la ubicaron en Frankfurt (donde era la escala) y me dijeron que llegaba esa noche. Incluso me dieron un pelpa para que el hotel pueda recibir la valija por mí. Obviamente, cuando hice el checkin les comenté la situación. A las diez de la noche golpearon la puerta de la habitación y era alguien del hotel con mi valija \o/.

Así y todo tuve que salir vestido como venía (pantalón finito y zapatillas náuticas) a pasear durante la tarde... y me cagué de frío, aunque tenía polar y campera. Salimos a pegar una vuelta con Naty, Matías y Guillo, y caminamos un par de horas a la tardecita, antes de que caiga el sol, porque luego teníamos el coctel de bienvenida de la empresa. Aunque era "de día", estaba muy nublado, y eso, hacía mucho frío...

El auto tapado de hielo, ese frío hacía

El agua se congelaba a mitad del chorro (?)

En general no paseé demasiado, porque los días eran grises y fríos, y cuando terminábamos el día laboral (entre las 17:30 y las 18) ya era de noche. Excepto el viernes, que terminamos a las 16hs, y encima salió el sol. Y el sábado, claro, que salí a pegar una vuelta durante la mañana y mediodía. A diferencia de los primeros días, ya teníamos como 12°C, estábamos como queríamos (?)

Los primeros días nos poníamos toda la ropa que teníamos

Nerdeando en un bar de cerveza artesanal; indoor las temperaturas eran otras, claro

El sábado hice paseo por la zona del Danubio, subí un montecito donde estaba la Estatua de la Libertad (levantada originalmente en 1947, en recuerdo a la liberación soviética de Hungría durante la Segunda Guerra Mundial, finalizando la ocupación nazi), fui al mercado central de la ciudad, y caminé bastante para un lado y para el otro.

La gente, en general, educada. La mayoría no sabe inglés, incluso en zonas turísticas y en lugares como para comer o comprar cosas "de turistas", así que a veces uno vuelve a la típica charla de señas y sonidos varios. O se termina hablando en italiano, como nos pasó en una heladería :p.

Estatua de la Libertad

Claro, los otros días también estuvimos caminando por acá y por allá, pero en general de noche y con todos los negocios (excepto los relacionados a comer y beber) cerrados... Budapest realmente es una ciudad distinta antes y después de las 18 horas (porque a las seis cierran la mayoría de los negocios, y ya es de noche...).

Pero bueno, eso obviamente no impidió que saliéramos a comer, y yo me dediqué a los gulashs. El gulash, originario de Hungría, justamente, es simplemente un estofado de carne, y de ahí salen muchas variantes... con papa, sin papa, con spätzl chicos, grandes o directamente sin nada de eso, con cebolla o sin, etc... siempre con carne, cocida varias horas, apenas picante (por eso se lo acompaña con alguna salsita para apicantarlo, como hacemos nosotros con el locro), y MUY RICO.

A varios gulashs, le entré

Todas las fotos, acá.

Facundo Batista: Actualización y nuevo proyecto

fades

Después de casi un año, con Nico liberamos una nueva versión de fades.

¿Qué hay de nuevo en esta release?

  • Revisar si todo lo pedido está realmente disponible en PyPI antes de comenzar a instalarlo
  • Ignora dependencias duplicadas
  • Varias mejoras y correcciones en los mensajes que fades muestra en modo verbose
  • Prohibimos el mal uso de fades: instalarlo en legacy Python y ejecutarlo desde adentro de otro virtualenv
  • Un montón de mejoras relacionadas al proyecto en sí (pero no directamente visibles para el usuario final) y algunas pequeñas otras correcciones

Pruébenlo.

Loguito de fades :)

infoauth

infoauth es un un pequeño pero práctico módulo de Python y script para grabar/cargar tokens a/desde disco.

Esto es lo que hace:

  • graba tokens en un archivo en disco, pickleado y zippeado
  • cambia el archivo a sólo lectura, y sólo legible por vos
  • carga los tokens de ese archivo en disco

En qué casos este módulo es útil? Digamos que tenés un script o programa que necesita algunos tokens secretos (autenticación de mail, tokens de Twitter, la info para conectarse a una base de datos, etc...), pero no querés incluir estos tokens en el código, porque el mismo es público, entonces con este módulo harías:

tokens = infoauth.load(os.path.expanduser("~/.my-tokens"))

Fijate que el archivo va a quedar legible sólo por vos y no en el directorio del proyecto (así no tenés el riesgo de compartirlo por accidente).

CUIDADO: infoauth NO protege tus secretos con una clave o algo así, este módulo NO asegura tus secretos de ninguna manera. Sí, los tokens están enmarañados (porque se picklean y comprimen) y otra gente quizás no pueda accederlos fácilmente (legible sólo por vos), pero no hay más protección que esa. Usalo bajo tu propio riesgo.

Entonces, ¿cómo usarlo desde un programa en Python? Es fácil, para cargar la data:

import infoauth
auth = infoauth.load(os.path.expanduser("~/.my-mail-auth"))
# ...
mail.auth(auth['user'], auth['password'])

Para grabarla:

import infoauth
secrets = {'some-stuff': 'foo', 'code': 67}
infoauth.dump(secrets, os.path.expanduser("~/.secrets"))

Fijate que como grabar los tokens es algo que normalmente se hace una sola vez, seguro es más práctico hacerlo desde la linea de comandos, como se muestra a continuación...

Por eso, ¿cómo usarlo desde la linea de comandos? Para mostrar la info:

$ infoauth show ~/.my-mail-auth
password: ...
user: ...

Y para grabar un archivo con los datos:

$ infoauth create ~/.secrets some-stuff=foo code=67

Fijate que al crear el archivo desde la linea de comandos tenemos la limitación de que todos los valores almacenados van a ser cadenas de texto; si querés grabar otros tipos de datos, como enteros, listas, o lo que quieras, tendrías que usar la forma programática que se muestra arriba.

Esta es la página del proyecto, y claro que está en PyPI así que se puede usar sin problema desde fades (guiño, guiño).

Facundo Batista: De trabajo en Hungría

Estuve una semana en Budapest, trabajando en un sprint con otros compañeros de equipo y de otros equipos, en general.

El viaje largo, pero sin sorpresas... sólo el detalle que me perdieron la valija en el viaje de ida :(. Cuando fui a hacer el reclamo, se fijaron y la ubicaron en Frankfurt (donde era la escala) y me dijeron que llegaba esa noche. Incluso me dieron un pelpa para que el hotel pueda recibir la valija por mí. Obviamente, cuando hice el checkin les comenté la situación. A las diez de la noche golpearon la puerta de la habitación y era alguien del hotel con mi valija \o/.

Así y todo tuve que salir vestido como venía (pantalón finito y zapatillas náuticas) a pasear durante la tarde... y me cagué de frío, aunque tenía polar y campera. Salimos a pegar una vuelta con Naty, Matías y Guillo, y caminamos un par de horas a la tardecita, antes de que caiga el sol, porque luego teníamos el coctel de bienvenida de la empresa. Aunque era "de día", estaba muy nublado, y eso, hacía mucho frío...

El auto tapado de hielo, ese frío hacíaEl agua se congelaba a mitad del chorro (?)

En general no paseé demasiado, porque los días eran grises y fríos, y cuando terminábamos el día laboral (entre las 17:30 y las 18) ya era de noche. Excepto el viernes, que terminamos a las 16hs, y encima salió el sol. Y el sábado, claro, que salí a pegar una vuelta durante la mañana y mediodía. A diferencia de los primeros días, ya teníamos como 12°C, estábamos como queríamos (?)

Los primeros días nos poníamos toda la ropa que teníamosNerdeando en un bar de cerveza artesanal; indoor las temperaturas eran otras, claro

El sábado hice paseo por la zona del Danubio, subí un montecito donde estaba la Estatua de la Libertad (levantada originalmente en 1947, en recuerdo a la liberación soviética de Hungría durante la Segunda Guerra Mundial, finalizando la ocupación nazi), fui al mercado central de la ciudad, y caminé bastante para un lado y para el otro.

La gente, en general, educada. La mayoría no sabe inglés, incluso en zonas turísticas y en lugares como para comer o comprar cosas "de turistas", así que a veces uno vuelve a la típica charla de señas y sonidos varios. O se termina hablando en italiano, como nos pasó en una heladería :p.

Estatua de la Libertad

Claro, los otros días también estuvimos caminando por acá y por allá, pero en general de noche y con todos los negocios (excepto los relacionados a comer y beber) cerrados... Budapest realmente es una ciudad distinta antes y después de las 18 horas (porque a las seis cierran la mayoría de los negocios, y ya es de noche...).

Pero bueno, eso obviamente no impidió que saliéramos a comer, y yo me dediqué a los gulashs. El gulash, originario de Hungría, justamente, es simplemente un estofado de carne, y de ahí salen muchas variantes... con papa, sin papa, con spätzl chicos, grandes o directamente sin nada de eso, con cebolla o sin, etc... siempre con carne, cocida varias horas, apenas picante (por eso se lo acompaña con alguna salsita para apicantarlo, como hacemos nosotros con el locro), y MUY RICO.

A varios gulashs, le entré

Todas las fotos, acá.

Mariano Guerra: How to use leveled, a pure erlang leveldb implementation

Yesterday at the riak_core tutorial at CodeBEAMSF I was trying to implement a leveled based backend for the key value store we were building, I was having troubles with leveled crashing when trying to destroy it (stop and remove files in leveled parlance), after fighting for a while I needed a smaller example to see if it was my mistake or not.

I decided to do the smaller example and to share the process here.

First we need some erlang application to hold our leveled dependency and configuration, let's do it by creating an erlang release with rebar3:

rebar3 new release name=lvld
cd lvld

Now that the skeleton is ready, we need to change rebar.config to add the information to use leveled, the resulting rebar.config below, see comments:

{erl_opts, [debug_info]}.

{deps, [
    % add leveled dependency
    {leveled, {git, "https://github.com/martinsumner/leveled.git", {branch, "master"}}}
]}.

{relx, [{release, { lvld, "0.1.0" },
    [lvld,
    % leveled needs crypto
    crypto,
    % make sure to load leveled, don't start it, it's not an app
    {leveled, load},
    % required by leveled
    {lz4, load},
    sasl]},

    {sys_config, "./config/sys.config"},
    {vm_args, "./config/vm.args"},

    {dev_mode, true},
    {include_erts, false},

    {extended_start_script, true}]
}.

{profiles, [{prod, [{relx, [{dev_mode, false},
    {include_erts, true}]}]
            }]
}.

% leveled generates lots of warnings and has warnings_as_errors set, we need
% to override that by copying the erl_opts field without warnings_as_errors
{overrides,
    [{override, leveled,
        [{erl_opts, [{platform_define, "^1[7-8]{1}", old_rand},
            {platform_define, "^R", old_rand},
            {platform_define, "^R", no_sync}]}]}
    ]}.

We will build a wrapper for leveled that exposes a simple kv store in apps/lvld/src/lvld_kv.erl:

-module(lvld_kv).
-export([new/1, get/3, put/4, delete/3, keys/2, close/1, delete/1, is_empty/1, foldl/3]).

-include_lib("leveled/include/leveled.hrl").

-record(state, {bookie, base_path}).

new(Opts=#{path := Path}) ->
    LedgerCacheSize = maps:get(ledger_cache_size, Opts, 2000),
    JournalSize = maps:get(journal_size, Opts, 500000000),
    SyncStrategy = maps:get(sync_strategy, Opts, none),
    {ok, Bookie} = leveled_bookie:book_start(Path, LedgerCacheSize,
                                             JournalSize, SyncStrategy),
    State = #state{bookie=Bookie, base_path=Path},
    {ok, State}.

put(State=#state{bookie=Bookie}, Bucket, Key, Value) ->
    R = leveled_bookie:book_put(Bookie, Bucket, Key, Value, []),
    {R, State}.

get(State=#state{bookie=Bookie}, Bucket, Key) ->
    K = {Bucket, Key},
    Res = case leveled_bookie:book_get(Bookie, Bucket, Key) of
              not_found -> {not_found, K};
              {ok, Value} -> {found, {K, Value}}
          end,
    {Res, State}.

delete(State=#state{bookie=Bookie}, Bucket, Key) ->
    R = leveled_bookie:book_delete(Bookie, Bucket, Key, []),
    {R, State}.

keys(State=#state{bookie=Bookie}, Bucket) ->
    FoldHeadsFun = fun(_B, K, _ProxyV, Acc) -> [K | Acc] end,
    {async, FoldFn} = leveled_bookie:book_returnfolder(Bookie,
                            {foldheads_bybucket,
                                ?STD_TAG,
                                Bucket,
                                all,
                                FoldHeadsFun,
                                true, true, false}),
    Keys = FoldFn(),
    {Keys, State}.

is_empty(State=#state{bookie=Bookie}) ->
    FoldBucketsFun = fun(B, Acc) -> [B | Acc] end,
    {async, FoldFn} = leveled_bookie:book_returnfolder(Bookie,
                                                       {binary_bucketlist,
                                                        ?STD_TAG,
                                                        {FoldBucketsFun, []}}),
    IsEmpty = case FoldFn() of
                  [] -> true;
                  _ -> false
              end,
    {IsEmpty, State}.

close(State=#state{bookie=Bookie}) ->
    R = leveled_bookie:book_close(Bookie),
    {R, State}.

delete(State=#state{base_path=Path}) ->
    R = remove_path(Path),
    {R, State}.


foldl(Fun, Acc0, State=#state{bookie=Bookie}) ->
    FoldObjectsFun = fun(B, K, V, Acc) -> Fun({{B, K}, V}, Acc) end,
    {async, FoldFn} = leveled_bookie:book_returnfolder(Bookie, {foldobjects_allkeys,
                                                                ?STD_TAG,
                                                                {FoldObjectsFun, Acc0},
                                                                true}),
    AccOut = FoldFn(),
    {AccOut, State}.

% private functions

sub_files(From) ->
    {ok, SubFiles} = file:list_dir(From),
    [filename:join(From, SubFile) || SubFile <- SubFiles].

remove_path(Path) ->
    case filelib:is_dir(Path) of
        false ->
            file:delete(Path);
        true ->
            lists:foreach(fun(ChildPath) -> remove_path(ChildPath) end,
                          sub_files(Path)),
            file:del_dir(Path)
    end.

We are ready to build a release and try our kv api on the repl:

rebar3 release
./_build/default/rel/lvld/bin/lvld console

This is the code we will run in the repl, I put it here so it's easy to read and copy and paste:

Nums = lists:seq(1, 10).
Buckets = lists:map(fun (N) -> list_to_binary("bucket-" ++ integer_to_list(N)) end,
Nums).
Keys = lists:map(fun (N) -> list_to_binary("key-" ++ integer_to_list(N)) end, Nums).

GenValue = fun (Bucket, Key) -> <<"v/", Bucket/binary, "/", Key/binary>> end.

{ok, Kv} = lvld_kv:new(#{path => "/tmp/lvld_test"}).
lists:foreach(fun (Bucket) ->
        lists:foreach(fun (Key) ->
                Val = GenValue(Bucket, Key),
                lvld_kv:put(Kv, Bucket, Key, Val)
        end, Keys)
end, Buckets).


B1 = <<"bucket-1">>.
K1 = <<"key-1">>.
V1 = <<"value-1">>.
B2 = <<"bucket-2">>.
K2 = <<"key-2">>.

FoldFn = fun ({{B, K}, V}, AccIn) -> [{B, K, V} | AccIn] end.
lvld_kv:foldl(FoldFn, [], Kv).

lvld_kv:put(Kv, B1, K1, V1).
lvld_kv:get(Kv, B1, K1).
lvld_kv:delete(Kv, B1, K1).
lvld_kv:get(Kv, B1, K1).

lvld_kv:keys(Kv, B1).

lvld_kv:close(Kv).
lvld_kv:delete(Kv).

The results of running it (removing some of the verbose logging):

(lvld@ganesha)1> Nums = lists:seq(1, 10).

[1,2,3,4,5,6,7,8,9,10]


(lvld@ganesha)2> Buckets = lists:map(fun (N) -> list_to_binary("bucket-" ++ integer_to_list(N)) end, Nums).

[<<"bucket-1">>,<<"bucket-2">>,<<"bucket-3">>,
 <<"bucket-4">>,<<"bucket-5">>,<<"bucket-6">>,<<"bucket-7">>,
  <<"bucket-8">>,<<"bucket-9">>,<<"bucket-10">>]


(lvld@ganesha)3> Keys = lists:map(fun (N) -> list_to_binary("key-" ++ integer_to_list(N)) end, Nums).

  [<<"key-1">>,<<"key-2">>,<<"key-3">>,<<"key-4">>,
   <<"key-5">>,<<"key-6">>,<<"key-7">>,<<"key-8">>,<<"key-9">>,
    <<"key-10">>]


(lvld@ganesha)4> GenValue = fun (Bucket, Key) -> <<"v/", Bucket/binary, "/", Key/binary>> end.

#Fun<erl_eval.12.99386804>


(lvld@ganesha)5> {ok, Kv} = lvld_kv:new(#{path => "/tmp/lvld_test"}).

{ok,{state,<0.264.0>,"/tmp/lvld_test"}}

(lvld@ganesha)6> B1 = <<"bucket-1">>.
<<"bucket-1">>

(lvld@ganesha)7> K1 = <<"key-1">>.
<<"key-1">>

(lvld@ganesha)8> V1 = <<"value-1">>.
<<"value-1">>

(lvld@ganesha)9> B2 = <<"bucket-2">>.
<<"bucket-2">>

(lvld@ganesha)10> K2 = <<"key-2">>.
<<"key-2">>

(lvld@ganesha)11> FoldFn = fun ({{B, K}, V}, AccIn) -> [{B, K, V} | AccIn] end.
#Fun<erl_eval.12.99386804>

(lvld@ganesha)13> lists:foreach(fun (Bucket) ->
(lvld@ganesha)13>         lists:foreach(fun (Key) ->
(lvld@ganesha)13>                 Val = GenValue(Bucket, Key),
(lvld@ganesha)13>                 lvld_kv:put(Kv, Bucket, Key, Val)
(lvld@ganesha)13>         end, Keys)
(lvld@ganesha)13> end, Buckets).

(lvld@ganesha)14> lvld_kv:foldl(FoldFn, [], Kv).

{[{<<"bucket-9">>,<<"key-9">>,<<"v/bucket-9/key-9">>},
  {<<"bucket-9">>,<<"key-8">>,<<"v/bucket-9/key-8">>},
  {<<"bucket-9">>,<<"key-7">>,<<"v/bucket-9/key-7">>},
  {<<"bucket-9">>,<<"key-6">>,<<"v/bucket-9/key-6">>},
  {<<"bucket-9">>,<<"key-5">>,<<"v/bucket-9/key-5">>},
  {<<"bucket-9">>,<<"key-4">>,<<"v/bucket-9/key-4">>},
  {<<"bucket-9">>,<<"key-3">>,<<"v/bucket-9/key-3">>},
  {<<"bucket-9">>,<<"key-2">>,<<"v/bucket-9/key-2">>},
  {<<"bucket-9">>,<<"key-10">>,<<"v/bucket-9/key-10">>},
  {<<"bucket-9">>,<<"key-1">>,<<"v/bucket-9/key-1">>},
  {<<"bucket-8">>,<<"key-9">>,<<"v/bucket-8/key-9">>},
  {<<"bucket-8">>,<<"key-8">>,<<"v/bucket-8/key-8">>},
  {<<"bucket-8">>,<<"key-7">>,<<"v/bucket-8/key-7">>},
  {<<"bucket-8">>,<<"key-6">>,<<"v/bucket-8/key-6">>},
  {<<"bucket-8">>,<<"key-5">>,<<"v/bucket-8/key-5">>},
  {<<"bucket-8">>,<<"key-4">>,<<"v/bucket-8/key-4">>},
  {<<"bucket-8">>,<<"key-3">>,<<"v/bucket-8/key-3">>},
  {<<"bucket-8">>,<<"key-2">>,<<"v/bucket-8/key-2">>},
  {<<"bucket-8">>,<<"key-10">>,<<"v/bucket-8/key-10">>},
  {<<"bucket-8">>,<<"key-1">>,<<"v/bucket-8/key-1">>},
  {<<"bucket-7">>,<<"key-9">>,<<"v/bucket-7/key-9">>},
  {<<"bucket-7">>,<<"key-8">>,<<"v/bucket-7/k"...>>},
  {<<"bucket-7">>,<<"key-7">>,<<"v/bucket"...>>},
  {<<"bucket-7">>,<<"key-6">>,<<"v/bu"...>>},
  {<<"bucket-7">>,<<"key-"...>>,<<...>>},
  {<<"buck"...>>,<<...>>,...},
  {<<...>>,...},
  {...}|...],

 {state,<0.264.0>,"/tmp/lvld_test"}}

(lvld@ganesha)15> lvld_kv:put(Kv, B1, K1, V1).
{ok,{state,<0.264.0>,"/tmp/lvld_test"}}

(lvld@ganesha)16> lvld_kv:get(Kv, B1, K1).
{{found,{{<<"bucket-1">>,<<"key-1">>},<<"value-1">>}},
 {state,<0.264.0>,"/tmp/lvld_test"}}

(lvld@ganesha)17> lvld_kv:delete(Kv, B1, K1).
{ok,{state,<0.264.0>,"/tmp/lvld_test"}}

(lvld@ganesha)18> lvld_kv:get(Kv, B1, K1).
{{not_found,{<<"bucket-1">>,<<"key-1">>}},
 {state,<0.264.0>,"/tmp/lvld_test"}}

(lvld@ganesha)19> lvld_kv:keys(Kv, B1).
{<"key-9">>,<<"key-8">>,<<"key-7">>,<<"key-6">>,
  <<"key-5">>,<<"key-4">>,<<"key-3">>,<<"key-2">>,
  <<"key-10">>],
 {state,<0.264.0>,"/tmp/lvld_test"}}

(lvld@ganesha)20> lvld_kv:close(Kv).
{ok,{state,<0.264.0>,"/tmp/lvld_test"}}

(lvld@ganesha)21> lvld_kv:delete(Kv).
{ok,{state,<0.264.0>,"/tmp/lvld_test"}}

In case you want to know the case for the crashing, when calling destroy on leveled, it returns destroy as reason for gen_server stop, which doesn't seem to make Erlang happy and it crashes the process and propagates the error.

The solution here is to just close it and remove the files myself (the difference between close and destroy is file removal).

Mariano Guerra: How to use leveled, a pure erlang leveldb implementation

Yesterday at the riak_core tutorial at CodeBEAMSF I was trying to implement a leveled based backend for the key value store we were building, I was having troubles with leveled crashing when trying to destroy it (stop and remove files in leveled parlance), after fighting for a while I needed a smaller example to see if it was my mistake or not.

I decided to do the smaller example and to share the process here.

First we need some erlang application to hold our leveled dependency and configuration, let's do it by creating an erlang release with rebar3:

rebar3 new release name=lvld
cd lvld

Now that the skeleton is ready, we need to change rebar.config to add the information to use leveled, the resulting rebar.config below, see comments:

{erl_opts, [debug_info]}.

{deps, [
    % add leveled dependency
    {leveled, {git, "https://github.com/martinsumner/leveled.git", {branch, "master"}}}
]}.

{relx, [{release, { lvld, "0.1.0" },
    [lvld,
    % leveled needs crypto
    crypto,
    % make sure to load leveled, don't start it, it's not an app
    {leveled, load},
    % required by leveled
    {lz4, load},
    sasl]},

    {sys_config, "./config/sys.config"},
    {vm_args, "./config/vm.args"},

    {dev_mode, true},
    {include_erts, false},

    {extended_start_script, true}]
}.

{profiles, [{prod, [{relx, [{dev_mode, false},
    {include_erts, true}]}]
            }]
}.

% leveled generates lots of warnings and has warnings_as_errors set, we need
% to override that by copying the erl_opts field without warnings_as_errors
{overrides,
    [{override, leveled,
        [{erl_opts, [{platform_define, "^1[7-8]{1}", old_rand},
            {platform_define, "^R", old_rand},
            {platform_define, "^R", no_sync}]}]}
    ]}.

We will build a wrapper for leveled that exposes a simple kv store in apps/lvld/src/lvld_kv.erl:

-module(lvld_kv).
-export([new/1, get/3, put/4, delete/3, keys/2, close/1, delete/1, is_empty/1, foldl/3]).

-include_lib("leveled/include/leveled.hrl").

-record(state, {bookie, base_path}).

new(Opts=#{path := Path}) ->
    LedgerCacheSize = maps:get(ledger_cache_size, Opts, 2000),
    JournalSize = maps:get(journal_size, Opts, 500000000),
    SyncStrategy = maps:get(sync_strategy, Opts, none),
    {ok, Bookie} = leveled_bookie:book_start(Path, LedgerCacheSize,
                                             JournalSize, SyncStrategy),
    State = #state{bookie=Bookie, base_path=Path},
    {ok, State}.

put(State=#state{bookie=Bookie}, Bucket, Key, Value) ->
    R = leveled_bookie:book_put(Bookie, Bucket, Key, Value, []),
    {R, State}.

get(State=#state{bookie=Bookie}, Bucket, Key) ->
    K = {Bucket, Key},
    Res = case leveled_bookie:book_get(Bookie, Bucket, Key) of
              not_found -> {not_found, K};
              {ok, Value} -> {found, {K, Value}}
          end,
    {Res, State}.

delete(State=#state{bookie=Bookie}, Bucket, Key) ->
    R = leveled_bookie:book_delete(Bookie, Bucket, Key, []),
    {R, State}.

keys(State=#state{bookie=Bookie}, Bucket) ->
    FoldHeadsFun = fun(_B, K, _ProxyV, Acc) -> [K | Acc] end,
    {async, FoldFn} = leveled_bookie:book_returnfolder(Bookie,
                            {foldheads_bybucket,
                                ?STD_TAG,
                                Bucket,
                                all,
                                FoldHeadsFun,
                                true, true, false}),
    Keys = FoldFn(),
    {Keys, State}.

is_empty(State=#state{bookie=Bookie}) ->
    FoldBucketsFun = fun(B, Acc) -> [B | Acc] end,
    {async, FoldFn} = leveled_bookie:book_returnfolder(Bookie,
                                                       {binary_bucketlist,
                                                        ?STD_TAG,
                                                        {FoldBucketsFun, []}}),
    IsEmpty = case FoldFn() of
                  [] -> true;
                  _ -> false
              end,
    {IsEmpty, State}.

close(State=#state{bookie=Bookie}) ->
    R = leveled_bookie:book_close(Bookie),
    {R, State}.

delete(State=#state{base_path=Path}) ->
    R = remove_path(Path),
    {R, State}.


foldl(Fun, Acc0, State=#state{bookie=Bookie}) ->
    FoldObjectsFun = fun(B, K, V, Acc) -> Fun({{B, K}, V}, Acc) end,
    {async, FoldFn} = leveled_bookie:book_returnfolder(Bookie, {foldobjects_allkeys,
                                                                ?STD_TAG,
                                                                {FoldObjectsFun, Acc0},
                                                                true}),
    AccOut = FoldFn(),
    {AccOut, State}.

% private functions

sub_files(From) ->
    {ok, SubFiles} = file:list_dir(From),
    [filename:join(From, SubFile) || SubFile <- SubFiles].

remove_path(Path) ->
    case filelib:is_dir(Path) of
        false ->
            file:delete(Path);
        true ->
            lists:foreach(fun(ChildPath) -> remove_path(ChildPath) end,
                          sub_files(Path)),
            file:del_dir(Path)
    end.

We are ready to build a release and try our kv api on the repl:

rebar3 release
./_build/default/rel/lvld/bin/lvld console

This is the code we will run in the repl, I put it here so it's easy to read and copy and paste:

Nums = lists:seq(1, 10).
Buckets = lists:map(fun (N) -> list_to_binary("bucket-" ++ integer_to_list(N)) end,
Nums).
Keys = lists:map(fun (N) -> list_to_binary("key-" ++ integer_to_list(N)) end, Nums).

GenValue = fun (Bucket, Key) -> <<"v/", Bucket/binary, "/", Key/binary>> end.

{ok, Kv} = lvld_kv:new(#{path => "/tmp/lvld_test"}).
lists:foreach(fun (Bucket) ->
        lists:foreach(fun (Key) ->
                Val = GenValue(Bucket, Key),
                lvld_kv:put(Kv, Bucket, Key, Val)
        end, Keys)
end, Buckets).


B1 = <<"bucket-1">>.
K1 = <<"key-1">>.
V1 = <<"value-1">>.
B2 = <<"bucket-2">>.
K2 = <<"key-2">>.

FoldFn = fun ({{B, K}, V}, AccIn) -> [{B, K, V} | AccIn] end.
lvld_kv:foldl(FoldFn, [], Kv).

lvld_kv:put(Kv, B1, K1, V1).
lvld_kv:get(Kv, B1, K1).
lvld_kv:delete(Kv, B1, K1).
lvld_kv:get(Kv, B1, K1).

lvld_kv:keys(Kv, B1).

lvld_kv:close(Kv).
lvld_kv:delete(Kv).

The results of running it (removing some of the verbose logging):

(lvld@ganesha)1> Nums = lists:seq(1, 10).

[1,2,3,4,5,6,7,8,9,10]


(lvld@ganesha)2> Buckets = lists:map(fun (N) -> list_to_binary("bucket-" ++ integer_to_list(N)) end, Nums).

[<<"bucket-1">>,<<"bucket-2">>,<<"bucket-3">>,
 <<"bucket-4">>,<<"bucket-5">>,<<"bucket-6">>,<<"bucket-7">>,
  <<"bucket-8">>,<<"bucket-9">>,<<"bucket-10">>]


(lvld@ganesha)3> Keys = lists:map(fun (N) -> list_to_binary("key-" ++ integer_to_list(N)) end, Nums).

  [<<"key-1">>,<<"key-2">>,<<"key-3">>,<<"key-4">>,
   <<"key-5">>,<<"key-6">>,<<"key-7">>,<<"key-8">>,<<"key-9">>,
    <<"key-10">>]


(lvld@ganesha)4> GenValue = fun (Bucket, Key) -> <<"v/", Bucket/binary, "/", Key/binary>> end.

#Fun<erl_eval.12.99386804>


(lvld@ganesha)5> {ok, Kv} = lvld_kv:new(#{path => "/tmp/lvld_test"}).

{ok,{state,<0.264.0>,"/tmp/lvld_test"}}

(lvld@ganesha)6> B1 = <<"bucket-1">>.
<<"bucket-1">>

(lvld@ganesha)7> K1 = <<"key-1">>.
<<"key-1">>

(lvld@ganesha)8> V1 = <<"value-1">>.
<<"value-1">>

(lvld@ganesha)9> B2 = <<"bucket-2">>.
<<"bucket-2">>

(lvld@ganesha)10> K2 = <<"key-2">>.
<<"key-2">>

(lvld@ganesha)11> FoldFn = fun ({{B, K}, V}, AccIn) -> [{B, K, V} | AccIn] end.
#Fun<erl_eval.12.99386804>

(lvld@ganesha)13> lists:foreach(fun (Bucket) ->
(lvld@ganesha)13>         lists:foreach(fun (Key) ->
(lvld@ganesha)13>                 Val = GenValue(Bucket, Key),
(lvld@ganesha)13>                 lvld_kv:put(Kv, Bucket, Key, Val)
(lvld@ganesha)13>         end, Keys)
(lvld@ganesha)13> end, Buckets).

(lvld@ganesha)14> lvld_kv:foldl(FoldFn, [], Kv).

{[{<<"bucket-9">>,<<"key-9">>,<<"v/bucket-9/key-9">>},
  {<<"bucket-9">>,<<"key-8">>,<<"v/bucket-9/key-8">>},
  {<<"bucket-9">>,<<"key-7">>,<<"v/bucket-9/key-7">>},
  {<<"bucket-9">>,<<"key-6">>,<<"v/bucket-9/key-6">>},
  {<<"bucket-9">>,<<"key-5">>,<<"v/bucket-9/key-5">>},
  {<<"bucket-9">>,<<"key-4">>,<<"v/bucket-9/key-4">>},
  {<<"bucket-9">>,<<"key-3">>,<<"v/bucket-9/key-3">>},
  {<<"bucket-9">>,<<"key-2">>,<<"v/bucket-9/key-2">>},
  {<<"bucket-9">>,<<"key-10">>,<<"v/bucket-9/key-10">>},
  {<<"bucket-9">>,<<"key-1">>,<<"v/bucket-9/key-1">>},
  {<<"bucket-8">>,<<"key-9">>,<<"v/bucket-8/key-9">>},
  {<<"bucket-8">>,<<"key-8">>,<<"v/bucket-8/key-8">>},
  {<<"bucket-8">>,<<"key-7">>,<<"v/bucket-8/key-7">>},
  {<<"bucket-8">>,<<"key-6">>,<<"v/bucket-8/key-6">>},
  {<<"bucket-8">>,<<"key-5">>,<<"v/bucket-8/key-5">>},
  {<<"bucket-8">>,<<"key-4">>,<<"v/bucket-8/key-4">>},
  {<<"bucket-8">>,<<"key-3">>,<<"v/bucket-8/key-3">>},
  {<<"bucket-8">>,<<"key-2">>,<<"v/bucket-8/key-2">>},
  {<<"bucket-8">>,<<"key-10">>,<<"v/bucket-8/key-10">>},
  {<<"bucket-8">>,<<"key-1">>,<<"v/bucket-8/key-1">>},
  {<<"bucket-7">>,<<"key-9">>,<<"v/bucket-7/key-9">>},
  {<<"bucket-7">>,<<"key-8">>,<<"v/bucket-7/k"...>>},
  {<<"bucket-7">>,<<"key-7">>,<<"v/bucket"...>>},
  {<<"bucket-7">>,<<"key-6">>,<<"v/bu"...>>},
  {<<"bucket-7">>,<<"key-"...>>,<<...>>},
  {<<"buck"...>>,<<...>>,...},
  {<<...>>,...},
  {...}|...],

 {state,<0.264.0>,"/tmp/lvld_test"}}

(lvld@ganesha)15> lvld_kv:put(Kv, B1, K1, V1).
{ok,{state,<0.264.0>,"/tmp/lvld_test"}}

(lvld@ganesha)16> lvld_kv:get(Kv, B1, K1).
{{found,{{<<"bucket-1">>,<<"key-1">>},<<"value-1">>}},
 {state,<0.264.0>,"/tmp/lvld_test"}}

(lvld@ganesha)17> lvld_kv:delete(Kv, B1, K1).
{ok,{state,<0.264.0>,"/tmp/lvld_test"}}

(lvld@ganesha)18> lvld_kv:get(Kv, B1, K1).
{{not_found,{<<"bucket-1">>,<<"key-1">>}},
 {state,<0.264.0>,"/tmp/lvld_test"}}

(lvld@ganesha)19> lvld_kv:keys(Kv, B1).
{<"key-9">>,<<"key-8">>,<<"key-7">>,<<"key-6">>,
  <<"key-5">>,<<"key-4">>,<<"key-3">>,<<"key-2">>,
  <<"key-10">>],
 {state,<0.264.0>,"/tmp/lvld_test"}}

(lvld@ganesha)20> lvld_kv:close(Kv).
{ok,{state,<0.264.0>,"/tmp/lvld_test"}}

(lvld@ganesha)21> lvld_kv:delete(Kv).
{ok,{state,<0.264.0>,"/tmp/lvld_test"}}

In case you want to know the case for the crashing, when calling destroy on leveled, it returns destroy as reason for gen_server stop, which doesn't seem to make Erlang happy and it crashes the process and propagates the error.

The solution here is to just close it and remove the files myself (the difference between close and destroy is file removal).