Ir al contenido principal

This is my blog, more about me at marianoguerra.github.io

🦋 @marianoguerra.org 🐘 @marianoguerra@hachyderm.io 🐦 @warianoguerra

Making a Chat App with Erlang, Rebar, Cowboy and Bullet

this is a continuation of the post about erlang, cowboy and rebar.

let' start by adding the bullet dep:

{sub_dirs, ["rel"]}.

{deps, [
    {cowboy, "1.0.0", {git, "https://github.com/ninenines/cowboy", {tag, "1.0.0"}}},
    {bullet, "0.4.1", {git, "https://github.com/extend/bullet", {tag, "0.4.1"}}}
]}.

now get the deps:

./rebar get-deps

add bullet as a dependency on disrupt.app.src:

{application, disrupt,
 [
  {description, ""},
  {vsn, "1"},
  {registered, []},
  {applications, [
                  kernel,
                  stdlib,
                  cowboy,
                  bullet
                 ]},
  {mod, { disrupt_app, []}},
  {env, []}
 ]}.

register our bullet handler with cowboy:

start(_StartType, _StartArgs) ->
    {ok, ChannelPid} = disrupt_channel:new(),
    Dispatch = cowboy_router:compile([
        {'_', [
               {"/chat", bullet_handler, [{handler, disrupt_chat_handler}, {channel, ChannelPid}]},
               {"/ui/[...]", cowboy_static, {priv_dir, disrupt, "assets",
                                             [{mimetypes, cow_mimetypes, all}]}}
        ]}
    ]),
    {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [
        {env, [{dispatch, Dispatch}]}
    ]),
    disrupt_sup:start_link().

the important lines are:

{ok, ChannelPid} = disrupt_channel:new(),

were we create a new chat channel that will be passed to all disrupt_chat_handler instances here:

{"/chat", bullet_handler, [{handler, disrupt_chat_handler}, {channel, ChannelPid}]},

also note that I changed the path of the ui to /ui/... instead of it being at the root.

add the js libs we need to use bullet (yes, I could use bower, but let's keep it simple):

mkdir priv/assets/vendor
mkdir priv/assets/js
cp deps/bullet/priv/bullet.js priv/assets/vendor
wget http://code.jquery.com/jquery-2.1.1.min.js -O priv/assets/vendor/jquery.js

we create a really simple chat page at index.html:

<!doctype html>
<html>
 <head>
  <title>Chat!</title>
  <meta charset="utf-8">
  <script src="vendor/jquery.js"></script>
  <script src="vendor/bullet.js"></script>
  <script src="js/app.js"></script>
  <style>
   body{ font-family: helvetica; color: #333; background-color: #fefefe;
    margin-left: 25%; width: 50%; }
   p, textarea{ padding: 0; }
   p, button, textarea{ width: 100%; margin: 1em 0; }
   label{ float: left; width: 45%; }
   input{ width: 50%; float: right; }
   input, textarea{ border: 1px solid #ddd; }
  </style>
 </head>
 <body>
  <p>
  <label for="input">Nickname</label>
  <input type="text" id="nick" value="anonymous"/>
  </p>
  <textarea id="output" cols="80" rows="25"></textarea>
  <p>
  <label for="input">Input</label>
  <input type="text" id="input"/>
  <button id="send">Send</button>
  </p>
 </body>
</html>

add code to handle the chat app on priv/assets/js/app.js:

/*globals $, document, window*/
function disruptApp(document, window, $) {
    'use strict';
    var input = document.getElementById('input'),
        output = document.getElementById('output'),
        nickInput = document.getElementById('nick'),
        send = document.getElementById('send'),

        connection;

    function sendMessage(text) {
        var nick = getNick();
        connection.send(nick + ': ' + text);
    }

    function onSendClicked() {
        var text = input.value.trim();

        if (text !== '') {
            sendMessage(text);
        }

        input.value = '';
    }

    function getNick() {
        var nick = nickInput.value.trim();

        if (nick === '') {
            return 'anonymous';
        } else {
            return nick;
        }
    }

    function notify(text) {
        var date = (new Date()).toLocaleString();
        output.innerHTML = output.innerHTML + '[' + date + '] ' + text + '\n';
    }

    function onData(data) {
        notify(data);
    }

    send.addEventListener('click', onSendClicked);

    function start(url, options, notify, onData) {
        var connection = $.bullet(url, options);

        connection.onopen = function(){
            notify('online');
        };

        connection.onclose = connection.ondisconnect = function(){
            notify('offline');
        };

        connection.onmessage = function(e){
            if (e.data === 'pong'){
                notify('pong');
            } else {
                onData(e.data);
            }
        };

        connection.onheartbeat = function(){
            connection.send('ping');
            notify('ping');
        };

        return connection;
    }

    connection = start('ws://localhost:8080/chat', {}, notify, onData);
}

document.addEventListener("DOMContentLoaded", function() {
    'use strict';
    disruptApp(document, window, $);
});

we need a simple pubsub mechanism for channels, I won't explain it here in detail but you can read about erlang's gen_event behaviour which is the one that does all the work:

-module(disrupt_channel).
-behaviour(gen_event).

-export([new/0, subscribe/2, unsubscribe/2, send/2]).

-export([init/1, handle_event/2, handle_call/2, handle_info/2, code_change/3,
         terminate/2]).
%% API

new() -> gen_event:start_link().

subscribe(Channel, Pid) ->
    gen_event:add_handler(Channel, {disrupt_channel, Pid}, [Pid]).

unsubscribe(Channel, Pid) ->
    gen_event:delete_handler(Channel, {disrupt_channel, Pid}, [Pid]).

send(Channel, Event) ->
    gen_event:notify(Channel, Event).

-record(state, {pid}).
%% callbacks
init([Pid]) -> {ok, #state{pid=Pid}}.

handle_event(Msg, State=#state{pid=Pid}) ->
    Pid ! Msg,
    {ok, State}.

handle_call(_, State) -> {ok, ok, State}.

handle_info(_, State) -> {ok, State}.

code_change(_OldVsn, State, _Extra) -> {ok, State}.

terminate(_Reason, _State) -> ok.

the important part from the code above is the fact that we store the pid of the bullet handler on the gen_event instance so we can send the message back when we get notified:

handle_event(Msg, State=#state{pid=Pid}) ->
    Pid ! Msg,
    {ok, State}.

the bullet handler for the chat channel is:

-module(disrupt_chat_handler).

-export([init/4, stream/3, info/3, terminate/2]).

-record(state, {channel}).

init(_Transport, Req, Opts, _Active) ->
    io:format("channel init ~p~n", [Opts]),
    {channel, ChannelPid} = lists:keyfind(channel, 1, Opts),
    disrupt_channel:subscribe(ChannelPid, self()),
    {ok, Req, #state{channel=ChannelPid}}.

stream(<<"ping">>, Req, State) ->
    io:format("ping received~n"),
    {reply, <<"pong">>, Req, State};

stream(Data, Req, State=#state{channel=ChannelPid}) ->
    io:format("message received ~s~n", [Data]),
    disrupt_channel:send(ChannelPid, {msg, self(), Data}),
    {ok, Req, State}.

info({msg, _Sender, Data}, Req, State) ->
    io:format("msg received ~p~n", [Data]),
    {reply, Data, Req, State}.

terminate(_Req, #state{channel=ChannelPid}) ->
    io:format("unsubscribing from channel~n"),
    disrupt_channel:unsubscribe(ChannelPid, self()),
    ok.

we subscribe on init, unsubscribe on terminate, when we receive a ping message we reply it only to the sender with pong, if we receive something else we send the message to the channel so it gets sent to all subscribers, that get the message on the info function where they send it to the browsers.

build:

rm -rf rel/disrupt && ./rebar compile generate

run:

./rel/disrupt/bin/disrupt console

open http://localhost:8080/ui/index.html in two or more browsers and chat!

/galleries/misc/bullet-chat.png

Serving Static Files with Erlang, Cowboy and Rebar, (raw material)

the "serving static files from a server" market needs some disruption, let's tackle that problem with some erlang.

create the folder:

mkdir disrupt
cd disrupt

get rebar:

wget http://cloud.github.com/downloads/basho/rebar/rebar
chmod u+x rebar

generate app:

./rebar create-app appid=disrupt

add dependencies:

vim rebar.config

the file should contain the following code:

{deps, [
    {cowboy, "1.0.0", {git, "https://github.com/ninenines/cowboy", {tag, "1.0.0"}}}
]}.

this tells to get cowboy 1.0.0 from git as dependency, let's fetch the dependencies:

./rebar get-deps

now let's serve some static files, open src/disrupt_app.erl, change the start function so it looks like this:

start(_StartType, _StartArgs) ->
    Dispatch = cowboy_router:compile([
        {'_', [
            {"/[...]", cowboy_static, {priv_dir, disrupt, "assets",
                [{mimetypes, cow_mimetypes, all}]}}
        ]}
    ]),
    {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [
        {env, [{dispatch, Dispatch}]}
    ]),
    disrupt_sup:start_link().

the code above is taken from the static example from cowboy adapted to our needs: https://github.com/ninenines/cowboy/tree/master/examples/static_world

docs here: http://ninenines.eu/docs/en/cowboy/1.0/guide/static_handlers/

now make the priv/assets folder:

mkdir -p priv/assets

and put some content in the index file:

echo "hello static world" > priv/assets/index.html

let's make a release for the project, first create the release files:

mkdir rel
cd rel
../rebar create-node nodeid=disrupt
cd ..

edit rel/reltool.config, the line:

{lib_dirs, []},

should change to:

{lib_dirs, ["../deps"]},

add the line:

sub_dirs, ["rel"]}.

to the top of the rebar.config file

if not you get this error:

Command 'generate' not understood or not applicable

yay helpfulness!

now we are one confusing error closer to our goal, but now we get:

ERROR: generate failed while processing /home/mariano/tmp/disrupt/rel: {'EXIT',{{badmatch,{error,"disrupt: Missing application directory."}},
         [{rebar_reltool,generate,2,[]},
          {rebar_core,run_modules,4,[]},
          {rebar_core,execute,5,[]},
          {rebar_core,process_dir0,6,[]},
          {rebar_core,process_dir,4,[]},
          {rebar_core,process_each,5,[]},
          {rebar_core,process_dir0,6,[]},
          {rebar_core,process_dir,4,[]}]}}

only because I've seen some other erlang projects I decided that this may be the solution:

mkdir -p apps/disrupt
cd apps/disrupt
ln -s ../../src
ln -s ../../ebin
ln -s ../../priv
cd ../..

we try again to make the release and we get the error again, nice!

for no apparent reason that the fact that I saw it on some other project, change the line on rel/reltool.config:

{lib_dirs, ["../deps"]},

to:

{lib_dirs, ["../deps", "../apps"]},

trying again:

./rebar compile generate

success!

let's try running it:

./rel/disrupt/bin/disrupt console

and what do we get? well, a crash! \o/:

Exec: /home/mariano/tmp/disrupt/rel/disrupt/erts-5.10.4/bin/erlexec -boot /home/mariano/tmp/disrupt/rel/disrupt/releases/1/disrupt -mode embedded -config /home/mariano/tmp/disrupt/rel/disrupt/releases/1/sys.config -args_file /home/mariano/tmp/disrupt/rel/disrupt/releases/1/vm.args -- console
Root: /home/mariano/tmp/disrupt/rel/disrupt
Erlang R16B03 (erts-5.10.4) [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.4  (abort with ^G)
(disrupt@127.0.0.1)1>
=INFO REPORT==== 29-Aug-2014::11:49:32 ===
    application: disrupt
    exited: {bad_return,
             {{disrupt_app,start,[normal,[]]},
              {'EXIT',
               {undef,
                [{cowboy_router,compile,
                  [[{'_',
                     [{"/[...]",cowboy_static,
                       {priv_dir,disrupt,"assets",
                        [{mimetypes,cow_mimetypes,all}]}}]}]],
                  []},
                 {disrupt_app,start,2,
                  [{file,"src/disrupt_app.erl"},{line,13}]},
                 {application_master,start_it_old,4,
                  [{file,"application_master.erl"},{line,269}]}]}}}}
    type: permanent
{"Kernel pid terminated",application_controller,"{application_start_failure,disrupt,{bad_return,{{disrupt_app,start,[normal,[]]},{'EXIT',{undef,[{cowboy_router,compile,[[{'_',[{\"/[...]\",cowboy_static,{priv_dir,disrupt,\"assets\",[{mimetypes,cow_mimetypes,all}]}}]}]],[]},{disrupt_app,start,2,[{file,\"src/disrupt_app.erl\"},{line,13}]},{application_master,start_it_old,4,[{file,\"application_master.erl\"},{line,269}]}]}}}}}"}

Crash dump was written to: erl_crash.dump
Kernel pid terminated (application_controller) ({application_start_failure,disrupt,{bad_return,{{disrupt_app,start,[normal,[]]},{'EXIT',{undef,[{cowboy_router,compile,[[{'_',[{"/[...]",cowboy_static

with all my time reading erlang crashes I see an undef there, it seems the app cant find cowboy_router:compile

but I have it on my deps..

well, let's put it on src/disrupt.app.src applications for no other reason that I've seen people do that, the file should look like this:

{application, disrupt,
 [
  {description, ""},
  {vsn, "1"},
  {registered, []},
  {applications, [
                  kernel,
                  stdlib,
                  cowboy
                 ]},
  {mod, { disrupt_app, []}},
  {env, []}
 ]}.

see the cowboy as last element on the applications list? that's what you should add.

let's try again:

./rebar compile generate

and I get:

<some output removed here>

==> rel (generate)
ERROR: generate failed while processing /home/mariano/tmp/disrupt/rel: {'EXIT',{{badmatch,{error,"Application cowboy is used in release \"disrupt\" and cannot be excluded"}},
         [{rebar_reltool,generate,2,[]},
          {rebar_core,run_modules,4,[]},
          {rebar_core,execute,5,[]},
          {rebar_core,process_dir0,6,[]},
          {rebar_core,process_dir,4,[]},
          {rebar_core,process_each,5,[]},
          {rebar_core,process_dir0,6,[]},
          {rebar_core,process_dir,4,[]}]}}

"Application cowboy is used in release "disrupt" and cannot be excluded"

who told you to exclude it?

let's apply a technic we already used a lot before, let's make up reasons for what it may be failing, it says exclude there and I've seen a lot of exclude and include atoms in rel/reltool.config, maybe it's that?

let's layer some other technic I use a lot, let's try to make that file look as similar as another one I've seen that works, in this case it's the reltool.config file generated by riak_core rebar template:

https://github.com/basho/rebar_riak_core/blob/master/riak_core.reltool.config

ok, that one seems to have less stuff than ours, let's start commenting everything that looks different until we reach this point:

{sys, [
    {lib_dirs, ["../deps", "../apps"]},
        %{erts, [{mod_cond, derived}, {app_file, strip}]},
        %{app_file, strip},
        {rel, "disrupt", "1",
            [
                kernel,
            stdlib,
            sasl,
            disrupt
                ]},
        {rel, "start_clean", "",
            [
                kernel,
            stdlib
                ]},
        {boot_rel, "disrupt"},
        {profile, embedded},
        %{incl_cond, exclude},
        {excl_archive_filters, [".*"]}, %% Do not archive built libs
    {excl_sys_filters, ["^bin/.*", "^erts.*/bin/(dialyzer|typer)",
        "^erts.*/(doc|info|include|lib|man|src)"]},
        {excl_app_filters, ["\.gitignore"]},
        {app, sasl,   [{incl_cond, include}]},
        %{app, stdlib, [{incl_cond, include}]},
        %{app, kernel, [{incl_cond, include}]},
        {app, disrupt, [{incl_cond, include}]}
    ]}.

{target_dir, "disrupt"}.

{overlay, [
    {mkdir, "log/sasl"},
        {copy, "files/erl", "\{\{erts_vsn\}\}/bin/erl"},
        {copy, "files/nodetool", "\{\{erts_vsn\}\}/bin/nodetool"},
        {copy, "files/disrupt", "bin/disrupt"},
        {copy, "files/disrupt.cmd", "bin/disrupt.cmd"},
        {copy, "files/start_erl.cmd", "bin/start_erl.cmd"},
        {copy, "files/install_upgrade.escript", "bin/install_upgrade.escript"},
        {copy, "files/sys.config", "releases/\{\{rel_vsn\}\}/sys.config"},
        {copy, "files/vm.args", "releases/\{\{rel_vsn\}\}/vm.args"}
    ]}.

trying again:

rm -rf rel/disrupt
./rebar compile generate

success!

now let's try running it:

./rel/disrupt/bin/disrupt console

the output we get:

Exec: /home/mariano/tmp/disrupt/rel/disrupt/erts-5.10.4/bin/erlexec -boot /home/mariano/tmp/disrupt/rel/disrupt/releases/1/disrupt -mode embedded -config /home/mariano/tmp/disrupt/rel/disrupt/releases/1/sys.config -args_file /home/mariano/tmp/disrupt/rel/disrupt/releases/1/vm.args -- console
Root: /home/mariano/tmp/disrupt/rel/disrupt
Erlang R16B03 (erts-5.10.4) [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.4  (abort with ^G)
(disrupt@127.0.0.1)1>

that's what I would call a non crashing system, let's see if it works:

curl http://localhost:8080/index.html

we get:

hello static world

success!

and that's how you serve a static file with erlang, cowboy and rebar :)

How To Build Twister Distributed Microblog on Ubuntu 13.10 (Saucy)

today I found out about twisterd and I wanted to give it a try (and reserve my username ;) so I tried and it took a while to get all the dependencies right, to avoid you the pain here is the guide.

take into account the comments in the script.

just a console dump, it should work just by pasting the commands in order:

mkdir twister
cd twister
sudo apt-get update
sudo apt-get install libssl-dev libdb-dev libdb++-dev libminiupnpc-dev libboost-all-dev build-essential git autoconf libtool
git clone https://github.com/miguelfreitas/twister-core.git
git clone https://github.com/miguelfreitas/twister-html.git
mkdir ~/.twister
mv twister-html ~/.twister/html
cd twister-core/libtorrent

# NOTE: the following command will fail with an error about boost, ignore
# it and run the following commands

./bootstrap.sh
./configure --enable-logging --enable-debug --enable-dht --with-boost-libdir=/usr/lib/x86_64-linux-gnu/
make
cd ../src
make -f makefile.unix
./twisterd -daemon -rpcuser=user -rpcpassword=pwd -rpcallowip=127.0.0.1

BTW I'm @mariano :)

Install Immutant 1.0 full as a service on debian/ubuntu

Small dump with instructions to install immutant as a service on debian, ubuntu or derivatives.

I adapted the init script that comes with immutant to work on debian:

# install needed packages
sudo apt-get install wget unzip openjdk-7-jdk daemon

# create a dir to download files
mkdir ~/soft
cd ~/soft

# download immutant 1.0 full
wget http://repository-projectodd.forge.cloudbees.com/release/org/immutant/immutant-dist/1.0.0/immutant-dist-1.0.0-full.zip

unzip immutant-dist-1.0.0-full.zip

# create user that will run the service
sudo adduser jboss-as

# create required folders
sudo cp -r immutant-1.0.0-full/jboss /usr/share/jboss-as/
sudo mkdir -p /var/log/jboss-as /var/run/jboss-as/

# set permissions
sudo chown -R jboss-as.jboss-as /var/log/jboss-as /var/run/jboss-as /usr/share/jboss-as/

# get and install init.d script
sudo wget https://gist.github.com/marianoguerra/6211268/raw/6e248a66670f0ef247d9aa169873c011b275da86/jboss-as-standalone.sh -O /etc/init.d/jboss-as-standalone
sudo chmod u+x /etc/init.d/jboss-as-standalone

# register the service
update-rc.d jboss-as-standalone default

# start it
sudo /etc/init.d/jboss-as-standalone start

Viaje de navidad - Alghero

Cuando contamos que nos íbamos a Italia alguien dijo que en Serdegna estaba bueno Alghero.

/galleries/Alghero/IMG_5638.JPG

FreeBSD.it

Resultó que Alghero queda en la otra punta de la isla y que la isla no es algo así como pequeña.

/galleries/Alghero/IMG_5641.JPG

Ida en colectivo el 24 de Diciembre, vuelta en tren el 26 (feriado).

La ciudad muy linda, la parte que da al mar esta muy buena.

/galleries/Alghero/IMG_5645.JPG

Ahí el 25 como no teníamos nada que hacer decidimos caminar hasta las grutas de Neptuno, pero dado que era feriado no había ni medio de transporte y estábamos casi seguros que estaba cerrada.

/galleries/Alghero/IMG_5652.JPG

Luego de 4 horas de caminar una media maratón (21 Km) por la ruta llegamos para enfrentarnos con la esperada realidad, las grutas estaban cerradas.

Durante la ida habíamos decidido, dado nuestro estado, intentar hacer dedo a la vuelta.

/galleries/Alghero/IMG_5667.JPG

Primer intento... exitoso! nos volvimos con un italiano que vivía en una ciudad cercana hablando mi cocoliche aprendido días antes de un libro germanamente titulado "Italienish für Dummies".

En un principio nos iba a llevar hasta un pueblo cercano ya que le quedaba a contramano, pero se copó y nos llevo hasta la entrada de Alghero.

/galleries/Alghero/IMG_5653.JPG

A la vuelta a las 6 de la mañana en la estación de trenes donde no había trenes (por el feriado) y donde el reloj de la estación no andaba (?) esperamos el colectivo con dos norteamericanas, que luego volvimos a encontrar en la estación del siguiente pueblo, que luego volvimos a encontrar en el tren a Cagliari, que luego volvimos a encontrar en la fila para abordar el vuelo...

/galleries/Alghero/IMG_5654.JPG

En el avión les pregunto si por casualidad iban a Stuttgart así compartíamos ticket de tren, pero ya como lo dijo Heisenberg, al intentar intervenir en este proceso de coincidencias las interrumpí, iban a Freiburg.

/galleries/Alghero/IMG_5656.JPG

Llegados al aeropuerto fui identificado como un potencial terrorista* y me solicitaron amablemente vaya a una fila donde todos los seres "aleatoriamente" seleccionados eran idénticos a mi (no solo notado por mi sino por el primero en la fila quien dijo "somos todos iguales!") excepto una blonda alemana que fue introducida en el set para romper el patrón** intentando así evitar que nuestras mentes inferiores identificaran el patrón.

/galleries/Alghero/IMG_5657.JPG

Luego del chequeo de pasaporte nos damos con que habíamos perdido el ultimo colectivo a la ciudad mas cercana con estación de tren, por suerte unos amigos (Lucas y Liza) nos habían ido a buscar de semi sorpresa para invitarnos a cenar.

Y así termina el primer tramo del viaje de receso festivo invernal.

/galleries/Alghero/IMG_5663.JPG

desde aca hasta alla

* esta parte la deduje yo

** esta parte también deducida por quien escribe

Viaje de navidad - Cagliari

Vengo un poquito atrasado con los posts, creo que se llama fiaca (o mas modernamente procrastinacion)

/galleries/Cagliari/IMG_5603.JPG

Para navidad buscamos con pato los lugares que no conozcamos, que sean templados y no caros que pudiéramos ir.

/galleries/Cagliari/IMG_5614.JPG

La búsqueda devolvió pocas opciones, una de las cuales nos pareció interesante, ir a la isla de serdenia.

/galleries/Cagliari/IMG_5623.JPG

El primer destino del viaje fue cagliari, donde nos hospedamos en un hotel con una vista muy linda pero bastante alejado de la ciudad y con alternativas de transporte publico reducidas, lo que nos "motivo" a caminar como unos desgraciados.

/galleries/Cagliari/IMG_5626.JPG

Lo mejor de la ciudad es el centro histórico, lo 'peor' es que no parece estar muy preparada para el turismo, puede haber sido porque fuimos en invierno pero era época de navidad así que debería ser un punto alto del invierno.

/galleries/Cagliari/IMG_5629.JPG

Dado todo el tiempo que paso la única anécdota que me acuerdo es ir a un restaurant donde el duenio era de ahí pero había hecho un intercambio en espania y había conocido a un marplatense.

/galleries/Cagliari/IMG_5632.JPG

El resultado es que nos atendieron re bien, le tuvimos que pedir que el ultimo plato nos traiga solo uno porque estábamos llenos y incluso con un solo plato no lo pudimos terminar.

/galleries/Cagliari/IMG_5605.JPG

Al final pago un shot para todos los que estaban en la barra "por los argentinos" y nos hizo un descuento porque no comimos todo :D.

clj-rhino - easy api to use rhino from clojure

clj-rhino

a nice wrapper to handle rhino from clojure

project home: https://github.com/marianoguerra/clj-rhino

who?

marianoguerra

why?

the java api for rhino is not really nice

how?

you can see the tests for some usage, here are some REPL examples:

user=> (require '[clj-rhino :as js])
nil
user=> (def sc (js/new-safe-scope))
#'user/sc
user=> (js/eval sc "1 + 1")
2
user=> (js/eval sc "a = 1 + 1")
2
user=> (js/get sc "a")
2
user=> (js/get sc "b")
#<UniqueTag org.mozilla.javascript.UniqueTag@172897f: NOT_FOUND>
user=> (js/undefined? (js/get sc "b"))
true
user=> (js/get sc "b" :w00t?)
:w00t?
user=> (js/defined? (js/get sc "b"))
false
user=> (js/set! sc "b" 42)
nil
user=> (js/defined? (js/get sc "b"))
true
user=> (js/get sc "b" :w00t?)
42
user=> (js/eval sc "a = {name: 'spongebob'}")
#<NativeObject [object Object]>
user=> (js/get-in sc [:a :name])
"spongebob"
user=> (js/get-in sc [:a :age])
#<UniqueTag org.mozilla.javascript.UniqueTag@172897f: NOT_FOUND>
user=> (js/get-in sc [:a :age] :dont-know)
:dont-know
user=> (def compiled-fun (js/compile-function sc "function (a, b) { return a + b; }" :filename "foo.js"))
#'user/compiled-fun
user=> (js/set! sc "add" compiled-fun)
nil
user=> (js/eval sc "add(1, 3)")
4.0
user=>

license?

it seems the clojure people under this circumstances say something like:

Copyright © 2013 marianoguerra

Distributed under the Eclipse Public License, the same as Clojure.

couchdb on openshift (aka compile all the things!)

lo que costo esto no tiene nombre, al final el script queda sencillo pero lo que costo encontrar las razones para varias lineas es incontable.

en resumen, openshift esta muy bueno pero no tiene couchdb y yo lo necesitaba, asi que como no se puede instalar nada decidi hacer la gran linux from scratch y compilar couchdb y sus dependencias...

aca esta el resultado, maniana quizas haga un cartridge para que sea mas simple reusarlo:

#!/usr/bin/env sh

# bailout on first error
set -e
# create dir to put all our stuff
mkdir couchdb
cd couchdb
# handy variable
BASEPATH=$PWD

# get spidermonkey
wget http://ftp.mozilla.org/pub/mozilla.org/js/js185-1.0.0.tar.gz
# get erlang
wget http://www.erlang.org/download/otp_src_R15B03.tar.gz
# get couchdb
wget http://apache.lehtivihrea.org/couchdb/releases/1.2.0/apache-couchdb-1.2.0.tar.gz
# get ICU
wget http://download.icu-project.org/files/icu4c/4.8.1.1/icu4c-4_8_1_1-src.tgz

# unpack all
tar -xzf js185-1.0.0.tar.gz
tar -xzf otp_src_R15B03.tar.gz
tar -xzf apache-couchdb-1.2.0.tar.gz
tar -xzf icu4c-4_8_1_1-src.tgz

# remove to avoid hiting the quota
rm js185-1.0.0.tar.gz
rm otp_src_R15B03.tar.gz
rm apache-couchdb-1.2.0.tar.gz
rm icu4c-4_8_1_1-src.tgz

# build and install ICU
cd icu/source
./configure --prefix=$BASEPATH/usr/
make && make install

# build and install spidermonkey
cd ../../js-1.8.5/js/src
./configure --prefix=$BASEPATH/usr/
make && make install
# some step ahead chokes if this file exists
rm js

# build and install erlang
cd ../../../otp_src_R15B03
./configure --prefix=$BASEPATH/usr/
make && make install

# build couchdb
cd ../apache-couchdb-1.2.0
export PATH=$PATH:$BASEPATH/usr/bin/:$BASEPATH/usr/local/bin
export ICU_CONFIG="$BASEPATH/usr/bin/icu-config --detect-prefix"
export ICU_CPPFLAGS="-I$BASEPATH/usr/include/"
./configure --prefix=$BASEPATH/usr/ --with-js-lib=$BASEPATH/usr/lib/ --with-js-include=$BASEPATH/usr/include/js/ --with-erlang=$BASEPATH/usr/lib/erlang/usr/include/
make && make install


# do some customizations


cd $BASEPATH
# customize couchdb defaults
cat > usr/etc/default/couchdb << EOM
# Sourced by init script for configuration.

# we use the current username to run couchdb
#COUCHDB_USER=couchdb
COUCHDB_STDOUT_FILE=/dev/null
COUCHDB_STDERR_FILE=/dev/null
COUCHDB_RESPAWN_TIMEOUT=5
COUCHDB_OPTIONS=
EOM

# change port and bind_address where openshift expects DIY apps to serve
cat > usr/etc/couchdb/local.ini << EOM
[httpd]
port = 8080
bind_address = $OPENSHIFT_INTERNAL_IP
EOM

# export this path to make couchdb load ICU lib
export LD_LIBRARY_PATH=$BASEPATH/usr/lib/

# finally start couchdb!
usr/etc/rc.d/couchdb start

lo subo no vaya ser que un meteorito impacte en mi maquina y esto se pierda

Luzern, Suiza

este post tendria que haber salido hace dos semanas mas o menos, pero que se le va a hacer...

/galleries/Luzern/IMG_5407.JPG
/galleries/Luzern/IMG_5408.JPG

fuimos un fin de semana a luzerna haciendo conexiones de tren magicas para minimizar el precio y tiempo de viaje

/galleries/Luzern/IMG_5414.JPG
/galleries/Luzern/IMG_5407.JPG

por desgracia estuvo nublado y lluvioso la mayoria del tiempo asi que las fotos no le hacen honor a la ciudad.

/galleries/Luzern/IMG_5419.JPG
/galleries/Luzern/IMG_5430.JPG

incluso cuando el clima no ayudaba la ciudad esta muy buena.

/galleries/Luzern/IMG_5435.JPG

rojo, verde, amarillo

/galleries/Luzern/IMG_5436.JPG

nos hospedamos en lo que fue hasta 1998 la carcel de luzerna

/galleries/Luzern/IMG_5437.JPG

"que tiene usted contra los empleados publicos? que no hacen absolutamente nada!"

/galleries/Luzern/IMG_5440.JPG

la puerta de la habitacion

la ciudad esta en la costa de un lago, rodeada por montanias con una gran montania de un lado (mis descripciones reflejan lo poco que lei en mapas o folletos de la ciudad :P)

/galleries/Luzern/IMG_5444.JPG

la habitacion

/galleries/Luzern/IMG_5454.JPG

incluso en esta epoca habia muchos colores, que combinado con nubes bajisimas hacian algunos paisajes muy copados que mi vieja camara no pudo capturar.

/galleries/Luzern/IMG_5459.JPG

el patio de la escuela...

/galleries/Luzern/IMG_5465.JPG

una pintura...

nos dedicamos a caminar como siempre, con paradas estrategicas para tomar cafe cuando se ponia muy fresco a lluvioso.

/galleries/Luzern/IMG_5466.JPG

reflejando la calle

/galleries/Luzern/IMG_5407.JPG

por primera vez y como no podia ser de otra manera, comi fondue (en suiza ;) hasta quedarme ciego de tanto queso.

/galleries/Luzern/IMG_5469.JPG

parientes

/galleries/Luzern/IMG_5520.JPG

Utrecht

Nuevo viaje de domingo a viernes a holanda, esta vez por cuestiones de disponibilidad y comodidad, me hospede en Utrecht, mas cerca del cliente.

/galleries/Utrecht/IMG_5377.JPG

Dado que había mucho trabajo y el clima no ayudaba no fui a Amsterdam y aproveche las tardes para pasear por Utrecht.

/galleries/Utrecht/IMG_5379.JPG

Al principio no le tenia mucha fe a la ciudad pero termino gustándome mucho.

/galleries/Utrecht/IMG_5380.JPG

Las dos partes interesantes por razones completamente distintas son el centro histórico y la parte donde están todas las empresas.

/galleries/Utrecht/IMG_5381.JPG

Como ya dije el clima no ayudo y la mayoría de las fotos son de los pocos momentos con sol y algunas de noche.

/galleries/Utrecht/IMG_5382.JPG

Las del momento de sol fue como a las 7:30, vi que había sol y decidí irme caminando, resulto que el tramo de ruta no tiene parte peatonal así que termine caminando por el pasto húmedo por un par de kilometros :D

/galleries/Utrecht/IMG_5383.JPG

Las de la noche no salieron buenas pero realmente el centro histórico con los canales es muy lindo, bastante parecido a Brugge en Belgica.

/galleries/Utrecht/IMG_5384.JPG

Ya no se que decir y quedan algunas fotos :P

/galleries/Utrecht/IMG_5385.JPG

Las fotos son de un lugar que es para empresas, principalmente de software.

/galleries/Utrecht/IMG_5386.JPG

Tienen los estacionamientos bajo los edificios y bastante verde y lagunas entre los edificios.

/galleries/Utrecht/IMG_5391.JPG
/galleries/Utrecht/IMG_5400.JPG