Mostrando las entradas con la etiqueta sandro. Mostrar todas las entradas
Mostrando las entradas con la etiqueta sandro. Mostrar todas las entradas

2016-07-08

Sandro en otros contextos

Hasta hace 5 minutos sandro sólo andaba si el WAR que deployás lo deployás en el contexto ROOT. Ahora ya no es más así. A partir de ahora es posible correr sandro con toda su magia para compartir código entre cliente y server desde cualquier contexto. Eso permite que haya más de una aplicación de sandro en el mismo Tomcat.

Enjoy!

2016-02-08

Regalos de carnaval para sandro

Hice 2 arreglos en sandro.

  • Cambié la forma de usar rhino y ahora puede procesar archivos grandes de javascript de nuevo. Eso hace que ande d3 en el server nuevamente.
  • Ahora los archivos .js que usa sandro tienen que estar encodeados en utf-8, permitiendo escribir en el código cosas como ( ͡° ͜ʖ ͡°).
Happy hacking,
Aureliano.

2016-01-31

Ciclo completo para autenticarse por oauth2 en línea de comando

Hace un tiempito que estoy jugando con el API de blogger para hacer posts automaticamente. Lo que me parece más complicado es autenticarse vía oauth2 desde una aplicación de línea de comando. Por eso hice un módulo de sandro que lo hace por mi, pidiendo el código de autorización solo cuando es necesario

Se puede usar como módulo de sandro así:

define({
  accessToken: './accessToken'
},
function(m) {
  var accessToken = m.accessToken(
    'refresh_token_fname.txt', 
    {
      clientId: 'CLIENT ID HERE',
      clientSecret: 'CLIENT SECRET HERE'
    }
  )
  // Use accessToken here!
})

Abajo les dejo accessToken.js completo (cuidado, son 134 líneas de código):

define({
  javaPackages: "javaPackages",
  ary: "sandro/nadaMas/array",
  json: "sandro/nadaMas/json",
  object: "sandro/nadaMas/object"
}, function(m) {
 
  var URL = m.javaPackages.java.net.URL
  var String = m.javaPackages.java.lang.String
  var URLEncoder = m.javaPackages.java.net.URLEncoder
  var StandardCharsets = m.javaPackages.java.nio.charset.StandardCharsets
  var InputStreamReader = m.javaPackages.java.io.InputStreamReader
  var BufferedReader = m.javaPackages.java.io.BufferedReader
  var Scanner = m.javaPackages.java.util.Scanner
  var System = m.javaPackages.java.lang.System
  var File = java.io.File
  var FileOutputStream = java.io.FileOutputStream
  var OutputStreamWriter = java.io.OutputStreamWriter
  var out = System.out
  var in_ = new Scanner(System["in"])

  var paramBytes = function(p) {
    var s = new String( Object.keys(p).map(function(k) {
      return "" + URLEncoder.encode(k, "UTF-8") + "=" + URLEncoder.encode(p[k])
    }).join("&") )
    return s.getBytes(StandardCharsets.UTF_8)
  }
 
  var postJsonResult = function(url, params) {
    var parameters = paramBytes(params)
    var c = new URL(url).openConnection()
    try {
    c.requestMethod = "POST"
    c.doOutput = true
    c.fixedLengthStreamingMode = parameters.length
    c.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
    c.connect()
    c.outputStream.write(parameters)
  
    if (200 != c.responseCode) {
      return null
    }
    var response = ""
    var reader = new BufferedReader( new InputStreamReader( c.inputStream ) )
  
    while( true ) {
      var buff = reader.readLine()
      if (buff == null) {
        break
      }
      response += buff
    }
    return m.json.parse( "" + response )
    } finally {
      c.disconnect()
    }
  
  }
  var authCode2refreshToken = function(authCode, credentials) {
    var rv = postJsonResult(
      "https://www.googleapis.com/oauth2/v4/token", {
        code: authCode,
        redirect_uri: "urn:ietf:wg:oauth:2.0:oob",
        client_id: credentials.clientId,
        client_secret: credentials.clientSecret,
        grant_type: "authorization_code"
      }
    )
  
    return rv ? rv["refresh_token"] : null
  }
 
  var refreshAccessToken = function(refreshToken, credentials) {
    var rv = postJsonResult(
      "https://www.googleapis.com/oauth2/v4/token", {
        refresh_token: refreshToken,
        client_id: credentials.clientId,
        client_secret: credentials.clientSecret,
        grant_type: "refresh_token"
      }
    )
 
    return rv ? rv["access_token"] : null
  }
 
  var readFileLine = function(fname) {
    var sc = new Scanner(new File(fname))
    try {
      return sc.nextLine()
    } finally {
      sc.close()
    }
  }
 
  var writeFileLine = function(fname, line) {
    try {
      var w = new OutputStreamWriter( new FileOutputStream(fname) )
      line = "" + line
      w.write(line, 0, line.length)
    } finally {
      w.close()
    }
  }
 
  var obtainAccessToken = function(refreshTokenFname, credentials) {
    var accessToken = null
    var refreshToken = null
    try {
      refreshToken = readFileLine(refreshTokenFname)
    } catch (e) {
      // ignore
    }
  
    while (true) {
      if (refreshToken) {
        accessToken = refreshAccessToken(refreshToken, credentials)
      }
      if (accessToken) {
        return accessToken
      }
    
      var authorizationUrl =
        "https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/blogger&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&client_id=" +
        credentials.clientId
      out.println("Anda a este URL, completa la autorizacion y pega el codigo de autorizacion aca. Despues presiona ENTER")
      out.println(authorizationUrl)
      var authCode = in_.nextLine()
      refreshToken = authCode2refreshToken(authCode, credentials)
      writeFileLine(refreshTokenFname, refreshToken)
    }
  }
 
  return obtainAccessToken
})



Si quieren, es bastante fácil transliterarlo a código Java.

Espero que les sirva,
Aureliano.

2016-01-30

Obteniendo el access token

Sigo en mi cruzada para hacer cosas automáticamente en blogger usando sandro. En este caso, hice el código para obtener un access token a partir de un authorization code. Para eso, primero necesitás registrar una aplicación de línea de comando. Una vez que lo hayas hecho, vas a tener el client id y el client secret. Pero eso no alcanza, para obtener el access token, como el que usamos en el post anterior, hace falta un authorization code. Para obtenerlo, conectate a este URL:

https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/blogger&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&client_id=ACA_VA_TU_CLIENT_ID

Y te va a mostrar el authorization code después de loguearte a google y autorizar a tu aplicación a hacer cosas de blogger en tu nombre.

Una vez que obtuviste tu authorization code, hay que obtener un access token. El access token te sirve para manipular blogger con tu usuario por la próxima hora. Para renovarlo, hay que usar un refresh token, que también obtendremos, pero voy a mostrar cómo usarlo en otro post.

Acá abajo dejo el código de ejemplo para obtener estos tokens.

define({
  javaPackages: "javaPackages",
  ary: "sandro/nadaMas/array",

  json: "sandro/nadaMas/json",
  object: "sandro/nadaMas/object"
}, function(m) {
 
  var URL = m.javaPackages.java.net.URL
  var out = m.javaPackages.java.lang.System.out
  var String = m.javaPackages.java.lang.String
  var URLEncoder = m.javaPackages.java.net.URLEncoder
  var StandardCharsets = m.javaPackages.java.nio.charset.StandardCharsets
  var InputStreamReader = m.javaPackages.java.io.InputStreamReader
  var BufferedReader = m.javaPackages.java.io.BufferedReader
 
  var tokenUrl = new URL("https://www.googleapis.com/oauth2/v3/token")

  var paramBytes = function(p) {
    var s = Object.keys(p).map(function(k) {
      return "" + URLEncoder.encode(k, "UTF-8") + "=" + URLEncoder.encode(p[k])
    }).join("&")
    out.println(s)
    s = new String(s)
    return s.getBytes(StandardCharsets.UTF_8)
  
  }
 
  return function(code, client_id, client_secret) {
    var parameters = paramBytes({
      code: code,
      redirect_uri: "urn:ietf:wg:oauth:2.0:oob",
      client_id: client_id,
      client_secret: client_secret,
      grant_type: "authorization_code"
    })
    var c = tokenUrl.openConnection()
    c.requestMethod = "POST"
    c.doOutput = true
    c.fixedLengthStreamingMode = parameters.length
    c.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
    c.connect()
    c.outputStream.write(parameters)
  
    var response = ""
    var reader = new BufferedReader( new InputStreamReader( c.inputStream ) )
  
    while( true ) {
      var buff = reader.readLine()
      if (buff == null) {
        break
      }
      response += buff
    }
    var jsonResponse = m.json.parse( "" + response )

    out.println("Tokens: " + m.json.stringify(jsonResponse))

  }
})


No uso la google-client-api de java porque es un quilombo y no entendí cómo usarla, así que me conecto a los webservices de google directamente.


2016-01-24

Accediendo al API de blogger desde sandro

Hoy estuve jugando con el API de blogger desde un script que hice usando sandro. Como puedo usar las APIs de java desde el código JavaScript que implementa los módulos de Sandro, decidí usar la biblioteca que google provee para Java.
Lo que más me costó fue toda la parte de oauth2. Como estoy solo probando, no quise armar todo un webserver para hacer todo el camino de oauth. En vez de eso, usé el oauth playground para obtener un access-token y programé desde ahí.

Con todo esto, y afanando bastante de acá hice un scriptcito que obtiene algunos datos de este blog.

define({
  javaPackages : "javaPackages",
  ary : "sandro/nadaMas/array"

}, function(m) {
 
  var Blogger = m.javaPackages.com.google.api.services.blogger.Blogger
  var NetHttpTransport = m.javaPackages.com.google.api.client.http.javanet.NetHttpTransport
  var JacksonFactory = com.google.api.client.json.jackson2.JacksonFactory
  var GoogleCredential = com.google.api.client.googleapis.auth.oauth2.GoogleCredential

  var HTTP_TRANSPORT = new NetHttpTransport();
  var JSON_FACTORY = new JacksonFactory();
  var CREDENTIAL = GoogleCredential().setAccessToken("HERE GOES THE ACCESS TOKEN")
 
  var buildBlogger = function() {
    var bb = new Blogger.Builder(HTTP_TRANSPORT, JSON_FACTORY, CREDENTIAL)
    bb.applicationName = "Testing blogger"
    return bb.build()
  }

  return function() {
    var out = m.javaPackages.java.lang.System.out
   
    var blogger = buildBlogger()
   
    var action = blogger.blogs().getByUrl("http://aurelianito.blogspot.com")
 

    action.fields = "description,name,posts/totalItems,updated"

    var blog = action.execute();

    // Now we can navigate the response.
    out.println("Name: " + blog.name)
    out.println("Description: " + blog.description)
    out.println("Post Count: " + blog.posts.totalItems)
    out.println("Last Updated: " + blog.updated)
   
  }

})


Si lo corren debería mostrarles algo muy parecido a:

Name: aurelianito
Description: Blog de Aureliano Calvo.
Aureliano Calvo's blog.
Post Count: 318
Last Updated: 2016-01-20T19:49:14.000-03:00


Después tengo que ver cómo hacer para hacer que el flujo del oauth ande bien.

Happy hacking,
Aureliano

2015-12-11

Cambio en handlers de sandro

Hasta hace un ratito, un handler era una función que tomaba un request y un response. Ahora en vez de tomar esos 2 parámetros toma un diccionario. Los campos request y response tienen lo mismo de antes, pero ahora podemos en el encadenamiento de handlers agregar información. Por ejemplo, el json parseado de un request y/o el usuario que obtuvimos de la autenticación van a estar también en ese diccionario cuando sea apropiado.

Proximamente voy a estar agregando más cosas que andan por ahí.

Happy hacking,
Aureliano.


2015-10-19

Tengo, queries a la base de datos en sandro

En los últimos días implementé el módulo sandro/tengo. Lo que hice fue una forma "objetosa" de contruir queries de SQL y procesar los resultados, usando como base JDBC. La idea es evitar los problemas de seguridad por inyección que plagan los usos de las bases de datos sin restringir la funcionalidad. El código que hice lo probé con sqlite3, y debería andar con cualquier base de datos que sea ANSI-SQL y tenga driver de JDBC.

Acá les dejo un ejemplo de cómo hacer un query usando tengo. Asumiendo que connection es una conexión a la base de datos y hay una tabla person (id integer, name string, rank integer), así hago un query:

define(
  {
    tengo: "sandro/tengo"
  },
  function(m) {

    // Some code before, constructing the JDBC connection

    var _ = m.tengo.ansiBuilder

    // SELECT * FROM person ORDER BY id DESC;
    var results = _.select({
      from: "person",
      order_by: _.desc("id")
    }).map(connection, function(rs) {
      return {

        name: "" + rs.getString("name"), 
        id: rs.getInt("id")
      }
    })


    // SELECT count(1) as c, rank FROM person GROUP BY rank HAVING c > 1
    var results2 = _.select({
      columns: [ _.alias("c",_.invoke("count", _.int(1))), "rank" ],
      from: "person",
      group_by: "rank",
      having: _.gt("c", _.int(1))
    }).map(connection, function(rs) {
      return {

        c: rs.getInt("c"), 
        rank:rs.getInt("rank")
      }
    })
 


    // You can process the results here
  }
)

En la implementación actual podés hacer SELECTs con un montón de opciones (TODO: documentar todo). La idea subyacente es que las queries se arman haciendo árboles de objetos que generan los queries con objetos inmutables siguiendo la sintaxis de SQL (para que puedan usar los conocimientos de SQL que ya tienen), y estos objetos se encargan de escapar el SQL. Para que sea menos verbose, los strings son transformados en símbolos de SQL (como columnas o tablas), los números en expresiones que representan el número en punto flotante y los arrays en expresiones separadas por comas, que está buenísimo para hacer queries con varias tablas, requerir varias columnas o hacer ORDER BY con varios criterios.

Lo siguiente que toca es hacer que se pueda hacer UPDATE, DELETE, INSERT. Y después toca CREATE TABLE y DROP TABLE.

¿Qué frameworks conocen que les dejen hacer queries tan fácilmente? Lo más parecido que se me ocurre es el API de bajo nivel de SQLAlchemy, pero como los elementos de las queries son stateful, es mucho más quilombo.

2015-09-25

Al final sandro tiene basic authentication

Sandro tiene soporte para basic authentication. Para usarlo hay que importar el módulo sandro/alFinal. Se usa así:

alFinal.basicAuth( my_handler, function(user, pass) { return user === pass }, "sample realm")

Los tres parámetros que recibe son:

  • Un handler para ejecutar si la autenticación funciona. Es una función con 3 parámetros; request, response y user (un string)  
  • Una función para decidir si el acceso es autorizado o no, recibe usuario y password.
  • Un realm (string)
La implementación del feature está en este commit.

Happy hacking,
Aureliano.

2015-09-11

2 nuevos features para sandro

Recientemente le agregué 2 nuevos features a sandro.

  1. alFinal.fullJson es una utilidad para manejar servicios que reciben en el body (por POST) un request en JSON y devuelven otro JSON. Para implementar el servicio le pasás una función, que recibe 3 parámetros: jsonRequest (objecto con el request del body), request (el objeto request del servlet) y response (el objeto response del servlet). La función tiene que retornar un objeto JavaScript a ser JSONificado para mandar al cliente
  2. El web.xml soporta el parámetro do-not-reload para el RhinoServlet. Ahí pasás una lista de módulos, separados por comas, que no querés que se recarguen cuando llega el request.
Happy hacking,
Aureliano.

2015-08-23

Sandro tiene json al final

Le agregué a sandro en el módulo alFinal,  que es el que tiene todas las utilidades para hacer handlers, una utilidad para hacer webservices que devuelven json.

Para usarlo, hay que hacer algo como:
alFinal.json( function(request) {
  return { "it worked": true, echo: request.getParameter("echo") })

Por supuesto que pueden hacer la función que prefieran ;).

La implementación de la utilidad para hacer json es re complicada. Acá les dejo el código completo :p

function(f) {
  return function(request, response) {
    response.setContentType("application/json")
    response.writer.print( m.json.stringify( f(request, response) ) )
  }
}


Happy hacking,
Aureliano.

2015-06-23

Sandro en la consola

En los últimos días hice que sea posible correr la biblioteca de sandro de línea de comando y tenga una consola. Para correr la consola, usando todos los módulos de sandro, ponelo en el classpath (con sus dependencias) y corré:

$ java sandro.RhinoConsole

Eso abre una consola donde podés correr código, que es una consola como la de rhino pero customizada para poder importar los módulos de sandro usando penumbras.

js> var funMod = penumbras.loadModule("sandro/nadaMas/function")
js> funMod.isFun(5)
false
js> funMod.isFun(function(a) {} )
true


Pero esto no es todo, ¡también se pueden correr módulos de sandro desde la línea de comandos! Para eso, hay que poner los módulos en el classpath (en un jar o un directorio), aparte de sandro y sus dependencias, y correr:

$ java sandro.RhinoRunner my/custom/module param1 param2

Para que corran, el módulo tiene que exportar una función, que es la que llama el runner. Pueden mirar el módulo helloWorld (está en el proyecto de la biblioteca por ahora) para ver un ejemplo tonto:

define({
  javaPackages : "javaPackages",
  ary : "sandro/nadaMas/array"
}, function(m) {

  return function() {
    var out = m.javaPackages.java.lang.System.out
    out.println("Hello!")
    out.println(m.ary.fromArgs(arguments).join())
  }

})


Y si lo invocan de línea de comando, pasa esto:

$ java sandro.RhinoRunner helloWorld Hola Manola
Hello!
Hola,Manola


Usando esto, hice que los tests de la biblioteca corran de línea de comando así:

$ java sandro.RhinoRunner sandro/testRunner

Por último, saqué la dependencia de servlets del cargador de módulos, así que ahora no hace falta tener el API de servlets en el classpath para usar todas las cosas que no tienen que ver con la web.

Happy hacking,
Aureliano.

2015-06-17

eclipse@docker

Estuve scripteando un poquito, e hice que eclipse corra en un container de docker (base, ubuntu:trusty), con las cosas que necesito para desarrollar sandro

Primero hice un dockerfile bastante trivial:

FROM ubuntu:trusty
RUN apt-get update && apt-get install -y \
  git \
  mercurial \
  default-jdk


con su correspondiente .dockerignore

dockerfile
.dockerignore


en un directorio que nombré docker

Después hice una imagen con ese container (a la que llamé, sandro:base).

$ docker build -t sandro:base docker

Y después vino la parte complicada.

Decidí correr el eclipse en volumenes afuera del container, con la idea de que quizás sea una buena idea inyectarlo en muchos distintos y poder instalar fácilmente plug-ins y esas cosas y que siga andando todo cuando haga otros containers. Para eso hice 4 directorios:

  • eclipse: donde puse un eclipse luna
  • workspace: donde va el workspace del eclipse
  • devel: donde pongo todo mi código
  • apache-tomcat-7.0.62: acá puse un tomcat bajado de apache.org
 Usando esa estructura hice 2 scripts:
  • dev.sh corre cualquier programa en un container nuevo, setupeando todo para tener un ambiente razonable para laburar.
  • eclipse.sh arranca eclipse usando dev.sh
Primero les muestro eclipse.sh

#!/usr/bin/env bash 

./dev.sh $1 eclipse/eclipse -data /workspace 

Le pasás por parámetro la imagen que querés usar para arrancarlo, y usa como workspace el directorio workspace que les conté más arriba.

Y ahora, la posta. dev.sh

#!/usr/bin/env bash

xhost +SI:localuser:root
ID=$( docker run \
  -d --name sandro-dev -h sandro-dev \
  -e SSH_AUTH_SOCK=$SSH_AUTH_SOCK -v $(dirname $SSH_AUTH_SOCK):$(dirname $SSH_AUTH_SOCK) \
  -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix \
  -e GIT_SSH=/usr/bin/ssh \
  -v `pwd`/devel:/devel -v `pwd`/eclipse:/eclipse -v `pwd`/workspace:/workspace -v `pwd`/apache-tomcat-7.0.62:/tomcat \
  -p 127.0.0.1:8080:8080 \
  $@ )
docker wait $ID
docker rm $ID


Mejor voy línea por línea porque es un bardo

  1. xhost +SI:localuser:root habilita al container a poner aplicaciones en la GUI (las apps de los containers corren como root).
  2. ID=$( docker run \ arranco el container y guardo su id
  3. -d --name sandro-dev -h sandro-dev \ hace que el container corra desattacheado y setea nombre y hostname a sandro-dev para hacerme la vida más fácil
  4. -e SSH_AUTH_SOCK=$SSH_AUTH_SOCK -v $(dirname $SSH_AUTH_SOCK):$(dirname $SSH_AUTH_SOCK) \ setupea el forwardeo del agente de ssh. Puede resultar útil si quieren pushear código vía ssh sin usar passwords
  5. -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix \ Comparte el display del desktop con el container
  6. -e GIT_SSH=/usr/bin/ssh \ para que si usan eclipse con git use el agente de ssh
  7. -v `pwd`/devel:/devel -v `pwd`/eclipse:/eclipse -v `pwd`/workspace:/workspace -v `pwd`/apache-tomcat-7.0.62:/tomcat \ monta todos los directorios que quedan afuera del container
  8. -p 127.0.0.1:8080:8080 \ forwardea el puerto 8080 (donde va a correr el tomcat de desarrollo) a localhost. Lo bindeo a 127.0.0.1 para que no se puedan conectar desde afuera.
  9. $@ ) recibe más parámetros para docker run, incluyendo los comandos para correr y el nombre de la imagen a usar
  10. docker wait $ID espera a que termine de correr el container
  11. docker rm $ID lo borra
Espero que les resulte útil, y espero mejoras y críticas,
Aureliano.

2015-03-19

Persistencia para sandro

Hace un tiempo que vengo pensando cómo quiero que sea la persistencia en el server en sandro. Últimamente vengo escuchando mucho sobre las bases de datos de grafos, así que estoy pensando en usar una de ellas para hacer la nueva versión.
Entonces lo trivial sería usar neo4j, que corre en la JVM (igual que sandro) y es la más conocida. Pero el problema es que la versión "community" de neo4j ¡no deja hacer backups!.
No me hace feliz hacer que mi framework tenga esa limitación, así que tengo que usar otra cosa. Y creo que la encontré. Una empresa que se llama ThinkAurelius hace una base de datos de objetos que se llama Titan, no tiene las limitaciones de neo4j y soporta varios back-ends incluyendo casandra, por lo que escala infinitamente y tiene todos los temas de backup resueltos.
Creo que voy a usarla. ¿Saben de alguna limitación importante?

2015-03-13

Nuevo hogar para la primera versión de sandro

Hoy me llegó un mail que dice que van a cerrar google code. Por lo tanto, tuve que mover mi proyecto original de sandro (el que anda en appengine). Lo puse en bitbucket.

Happy hacking,
Aureliano.

2014-11-22

Projecto de ejemplo de sandro

Acabo de hacer público el projecto taleca. Ahí uso sandro para hacer una visualización de algunos datos que se usaron en el Hackatón de agro de la Fundación Sadosky. Es un ejemplo simple pero muestra cómo usar el general update pattern para hacer una visualización de d3 en sandro.

Te propongo que saquemos a sandro de las penumbras,
Aureliano.

2014-11-13

Dependencias con nombre en sandro

En requirejs cuando un módulo tiene muchas dependencias a veces se complica saber qué variable corresponde a qué módulo. Para simplificar esto, ahora en sandro se pueden definir módulos así:

define({name1:"./dependency1", name2: "app/dependency2"}, function(m) {
   // m.name1 refers to ./dependency1 and m.name2 refers to app/dependency2
})

Podés usarlo en define y require, tanto en el cliente como el server.
Happy hacking,
Aureliano.

2014-11-06

Utilidades para d3 en sandro

Estoy trabajando en sandro, y surgen cosas interesantes. Ahora empecé a hacer utilidades para d3. Lo más interesante es que hice un método que encapsula y da entidad al General Update Pattern de d3 e hice que los nodos updateados tengan que sí o sí ser hijos del nodo referenciado (en vez de cualquier descendiente), para evitar un montón de bugs que se dan eligiendo nodos.

Para usarlo en sandro es así:
define(["sandro/nadaMas/d3", function(d3h) {
  // Acá hay código tuyo, que hace aSelection
  aSelection.call( d3h.gupChildren, {
    tag: "line", // nombre del tag de la entidad a insertar (obligatorio)
    klass: "aClass", // nombre de la clase de las entidades insertadas (optional, sin clase si no está escrita)
    values: [1,2,3,4], // datos para la visualización (función o array, igual que en d3)
    key: function(d,i) {.....}, // key para los datos, opcional. Funciona igual que en d3
    onEnter: function(s, defaultOnEnter) {....}, // acciones a tomar con la selección de los nodos nuevos. Opcional. Si no es seteada solo hace append de los nodos y les setea la clase si querés hacer eso y algo más defaultOnEnter(s) hace eso y devuelve la selección para seguir operando.
    onUpdate: function(s, defaultOnUpdate) {.....}, // acciones a tomar sobre la selección de los nodos modificados (tanto agregados, como cambiados). Opcional.
    onExit: function(s, defaultOnExit) {.....}, // acciones a tomar con la selección de los nodos que no matchean con los nuevos datos. Opcional. Si no es seteada solo hace remove. Si querés hacer eso y algo más defaultOnExit(s) hace eso y devuelve la selección para seguir operando.
    onUpdateOld: function(s, defaultOnUpdateOld) {.....} // Igual que onUpdate pero solo aplica a los nodos viejos
  })
  // Y acá más código
}
Si quieren, pueden usar el código as-is si usan requirejs, o adaptarlo un toque y usarlo en el browser como más les plazca. Pero, si quieren usarlo en el server también, la mejor forma que conozco es que usen sandro.

Espero que les guste!

2014-11-01

Como lo hice yo

Estuve avanzando y empecé un módulo nuevo en sandro. Se llama comoLoHiceYo en honor a la canción de Sandro con el mismo nombre y tiene las configuraciones "opinionated" que hacen falta para unir cosas y que sea fácil compartir código entre cliente y server. Antes era posible, ahora es una pavada.
Éste es el código de una página de ejemplo que corre un pedacito en el cliente y otro en el server. Primero en el server, app/endpoint/clientCode.js es así:
define([], function() {
  return function(params) {
    var d3 = params.d3
    var startClientJs = params.startClientJs
   
    d3.select("body").append("div").text("Generated on the server")
    startClientJs("app/shared/clientCode")
  }
})

Y para el cliente está app/shared/clientCode.js:
define(["external/d3"], function(d3) {
  d3.select("body").append("div").text("Generated on the client")
})

Ya se empieza a ver que hay un montón de cosas que se pueden hacer en los dos lados. En este ejemplo, uso d3 tanto en el cliente como en el server para manipular el DOM.
¿Querés usar sandro? Decime y te ayudo.

2014-10-26

Novedades en sandro

Tengo varias novedades de sandro. La primera es que separé el desarrollo del site de la biblioteca. Para eso hice el proyecto sandro-lib, donde tiene el proyecto sandro y sus tests. Por lo tanto, el proyecto queda así:

  • sandro: Site principal. Corre los tests. En un futuro va a tener toda la documentación. Es el primer site hecho en sandro y siempre va a usar la última versión. Si tenés alguna duda sobre cómo hacer algo, podés mirar el código acá.
  • sandro-lib: Biblioteca de base para hacer sites con sandro
  • domino: Port del proyecto domino para correr en sandro
La segunda es que hice mi primer visualización en d3 corriendo en el server, como parte de las cosas que hice en el Hackaton de la fundación Sadosky sobre agro. Lo siento, no tengo commit de esto por ahora.

La tercera es que cambié la forma de exponer los scripts que se ejecutan en el cliente. Ahora hay una acción del módulo alFinal que expone los scripts, tomando por parámetro una función que decide si un módulo puede ser expuesto dado el nombre de su recurso. Acá el ejemplo de cómo anda:
      router = c.camino()
      router.register(c.prefixPath("/js"), alFinal.jsCode({
        published: function(resource) {
          resource = "" + resource
          return (
            resource.startsWith("/sandro/") ||
            resource.startsWith("/app/shared/") ||
            resource.startsWith("/external/")
          )
        },
        prefix: "/js"
      }))

Muchos avances en muy poco tiempo. Espero poder seguir así,
Aureliano

2014-10-24

Financiación de sandro

Como ya conté en este blog, hace un tiempo que estoy trabajando en hacer un framework para hacer páginas web que permita compartir fácilmente código entre cliente y server. A este trabajo lo denominé sandro, en analogía a los ya existentes sinatra y dyango.
Sandro, ya tiene bastante esfuerzo dedicado, pero necesita mucho más. Por lo tanto, está llegando el momento en que tengo que encontrar alternativas que me permitan financiar el proyecto, porque tengo que seguir comiendo pero quiero seguir haciendolo, y aparte en cualquier momento va a empezar a generar gastos (por ejemplo, hosting).
Estas son mis ideas, si se les ocurre algo más soy todo oídos:

  • Donaciones
    • gratipay
    • vía Paypal
    • ¿se les ocurren otros mecanismos fáciles de implementar?
  • Features requests/bug fixes con recompensa o donación.
    • Estos tendrían prioridad ("put your money where your mouth is")
  • Consultoría, capacitación, etc sobre sandro
    • Incluye dar cursos, desarrollar sites, dar charlas.
  • Libro sobre sandro (¿alguien gana plata escribiendo un libro sobre su propio proyecto?)
  • Venta de discos/canciones de sandro a través del site, cobrando una comisión.
    • Creo que Amazon, iTunes y MercadoLibre ofrecen algo así en sus programas de afiliados.
  • Inversión de venture capital en sandro
Si se les ocurre algo más, o quieren poner plata con alguno de los mecanismos arriba mencionados (o algún otro) no duden en escribir.

Happy hacking,
Aureliano.