Anonymous avatar Anonymous committed 0571f73

rebarized webmachine: use rebar to build, and make skeleton projects that use rebar to build

Comments (0)

Files changed (38)

 ERL          ?= erl
-EBIN_DIRS    := $(wildcard deps/*/ebin)
 APP          := webmachine
 
-all: mochi erl ebin/$(APP).app
+all: 
+	@(./rebar compile)
 
-mochi:
-	@(cd deps/mochiweb;$(MAKE))
-
-erl:
-	@$(ERL) -pa $(EBIN_DIRS) -noinput +B \
-	  -eval 'case make:all() of up_to_date -> halt(0); error -> halt(1) end.'
+clean:
+	@(./rebar clean)
 
 edoc:
 	@$(ERL) -noshell -run edoc_run application '$(APP)' '"."' '[{preprocess, true},{includes, ["."]}]'
 
-clean: 
-	@echo "removing:"
-	@rm -fv ebin/*.beam ebin/*.app
-
-ebin/$(APP).app: src/$(APP).app
-	@cp -v src/$(APP).app $@
-
-test: erl
+test: all
 	scripts/run_tests.escript ebin | tee test.log
 

priv/skel/Emakefile

-% -*- mode: erlang -*-
-{["src/*"], 
- [{i, "include"},
-  {outdir, "ebin"},
-  debug_info]
-}.

priv/skel/Makefile

-ERL          ?= erl
-EBIN_DIRS    := $(wildcard deps/*/ebin)
-APP          := skel
-
-all: erl ebin/$(APP).app
-
-erl:
-	@$(ERL) -pa $(EBIN_DIRS) -noinput +B \
-	  -eval 'case make:all() of up_to_date -> halt(0); error -> halt(1) end.'
-
-docs:
-	@erl -noshell -run edoc_run application '$(APP)' '"."' '[]'
-
-clean: 
-	@echo "removing:"
-	@rm -fv ebin/*.beam ebin/*.app
-
-ebin/$(APP).app: src/$(APP).app
-	@cp -v src/$(APP).app $@
Add a comment to this file

priv/skel/deps/.empty

Empty file removed.

Add a comment to this file

priv/skel/ebin/.empty

Empty file removed.

priv/skel/priv/dispatch.conf

-{[], skel_resource, []}.

priv/skel/priv/www/index.html

-<html>
-<head>
-<title>It Worked</title>
-</head>
-<body>
-MochiWeb running.
-</body>
-</html>

priv/skel/src/skel.app

-{application, skel,
- [{description, "skel"},
-  {vsn, "0.1"},
-  {modules, [
-    skel,
-    skel_app,
-    skel_sup,
-    skel_deps,
-    skel_resource
-  ]},
-  {registered, []},
-  {mod, {skel_app, []}},
-  {env, []},
-  {applications, [kernel, stdlib, crypto]}]}.

priv/skel/src/skel.erl

-%% @author author <author@example.com>
-%% @copyright YYYY author.
-
-%% @doc TEMPLATE.
-
--module(skel).
--author('author <author@example.com>').
--export([start/0, start_link/0, stop/0]).
-
-ensure_started(App) ->
-    case application:start(App) of
-	ok ->
-	    ok;
-	{error, {already_started, App}} ->
-	    ok
-    end.
-
-%% @spec start_link() -> {ok,Pid::pid()}
-%% @doc Starts the app for inclusion in a supervisor tree
-start_link() ->
-    skel_deps:ensure(),
-    ensure_started(crypto),
-    application:set_env(webmachine, webmachine_logger_module, 
-                        webmachine_logger),
-    ensure_started(webmachine),
-    skel_sup:start_link().
-
-%% @spec start() -> ok
-%% @doc Start the skel server.
-start() ->
-    skel_deps:ensure(),
-    ensure_started(crypto),
-    application:set_env(webmachine, webmachine_logger_module, 
-                        webmachine_logger),
-    ensure_started(webmachine),
-    application:start(skel).
-
-%% @spec stop() -> ok
-%% @doc Stop the skel server.
-stop() ->
-    Res = application:stop(skel),
-    application:stop(webmachine),
-    application:stop(crypto),
-    Res.

priv/skel/src/skel.hrl

-

priv/skel/src/skel_app.erl

-%% @author author <author@example.com>
-%% @copyright YYYY author.
-
-%% @doc Callbacks for the skel application.
-
--module(skel_app).
--author('author <author@example.com>').
-
--behaviour(application).
--export([start/2,stop/1]).
-
-
-%% @spec start(_Type, _StartArgs) -> ServerRet
-%% @doc application start callback for skel.
-start(_Type, _StartArgs) ->
-    skel_deps:ensure(),
-    skel_sup:start_link().
-
-%% @spec stop(_State) -> ServerRet
-%% @doc application stop callback for skel.
-stop(_State) ->
-    ok.

priv/skel/src/skel_deps.erl

-%% @author author <author@example.com>
-%% @copyright YYYY author.
-
-%% @doc Ensure that the relatively-installed dependencies are on the code
-%%      loading path, and locate resources relative
-%%      to this application's path.
-
--module(skel_deps).
--author('author <author@example.com>').
-
--export([ensure/0, ensure/1]).
--export([get_base_dir/0, get_base_dir/1]).
--export([local_path/1, local_path/2]).
--export([deps_on_path/0, new_siblings/1]).
-
-%% @spec deps_on_path() -> [ProjNameAndVers]
-%% @doc List of project dependencies on the path.
-deps_on_path() ->
-    F = fun (X, Acc) ->
-                ProjDir = filename:dirname(X),
-                case {filename:basename(X),
-                      filename:basename(filename:dirname(ProjDir))} of
-                    {"ebin", "deps"} ->
-                        [filename:basename(ProjDir) | Acc];
-                    _ ->
-                        Acc
-                end
-        end,
-    ordsets:from_list(lists:foldl(F, [], code:get_path())).
-    
-%% @spec new_siblings(Module) -> [Dir]
-%% @doc Find new siblings paths relative to Module that aren't already on the
-%%      code path.
-new_siblings(Module) ->
-    Existing = deps_on_path(),
-    SiblingEbin = filelib:wildcard(local_path(["deps", "*", "ebin"], Module)),
-    Siblings = [filename:dirname(X) || X <- SiblingEbin,
-                           ordsets:is_element(
-                             filename:basename(filename:dirname(X)),
-                             Existing) =:= false],
-    lists:filter(fun filelib:is_dir/1, 
-                 lists:append([[filename:join([X, "ebin"]),
-                                filename:join([X, "include"])] ||
-                                  X <- Siblings])).
-        
-
-%% @spec ensure(Module) -> ok
-%% @doc Ensure that all ebin and include paths for dependencies
-%%      of the application for Module are on the code path.
-ensure(Module) ->
-    code:add_paths(new_siblings(Module)),
-    code:clash(),
-    ok.
-
-%% @spec ensure() -> ok
-%% @doc Ensure that the ebin and include paths for dependencies of
-%%      this application are on the code path. Equivalent to
-%%      ensure(?Module).
-ensure() ->
-    ensure(?MODULE).
-
-%% @spec get_base_dir(Module) -> string()
-%% @doc Return the application directory for Module. It assumes Module is in
-%%      a standard OTP layout application in the ebin or src directory.
-get_base_dir(Module) ->
-    {file, Here} = code:is_loaded(Module),
-    filename:dirname(filename:dirname(Here)).
-
-%% @spec get_base_dir() -> string()
-%% @doc Return the application directory for this application. Equivalent to
-%%      get_base_dir(?MODULE).
-get_base_dir() ->
-    get_base_dir(?MODULE).
-
-%% @spec local_path([string()], Module) -> string()
-%% @doc Return an application-relative directory from Module's application.
-local_path(Components, Module) ->
-    filename:join([get_base_dir(Module) | Components]).
-
-%% @spec local_path(Components) -> string()
-%% @doc Return an application-relative directory for this application.
-%%      Equivalent to local_path(Components, ?MODULE).
-local_path(Components) ->
-    local_path(Components, ?MODULE).

priv/skel/src/skel_resource.erl

-%% @author author <author@example.com>
-%% @copyright YYYY author.
-%% @doc Example webmachine_resource.
-
--module(skel_resource).
--export([init/1, to_html/2]).
-
--include_lib("webmachine/include/webmachine.hrl").
-
-init([]) -> {ok, undefined}.
-
-to_html(ReqData, State) ->
-    {"<html><body>Hello, new world</body></html>", ReqData, State}.

priv/skel/src/skel_sup.erl

-%% @author author <author@example.com>
-%% @copyright YYYY author.
-
-%% @doc Supervisor for the skel application.
-
--module(skel_sup).
--author('author <author@example.com>').
-
--behaviour(supervisor).
-
-%% External exports
--export([start_link/0, upgrade/0]).
-
-%% supervisor callbacks
--export([init/1]).
-
-%% @spec start_link() -> ServerRet
-%% @doc API for starting the supervisor.
-start_link() ->
-    supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-
-%% @spec upgrade() -> ok
-%% @doc Add processes if necessary.
-upgrade() ->
-    {ok, {_, Specs}} = init([]),
-
-    Old = sets:from_list(
-	    [Name || {Name, _, _, _} <- supervisor:which_children(?MODULE)]),
-    New = sets:from_list([Name || {Name, _, _, _, _, _} <- Specs]),
-    Kill = sets:subtract(Old, New),
-
-    sets:fold(fun (Id, ok) ->
-		      supervisor:terminate_child(?MODULE, Id),
-		      supervisor:delete_child(?MODULE, Id),
-		      ok
-	      end, ok, Kill),
-
-    [supervisor:start_child(?MODULE, Spec) || Spec <- Specs],
-    ok.
-
-%% @spec init([]) -> SupervisorTree
-%% @doc supervisor callback.
-init([]) ->
-    Ip = case os:getenv("WEBMACHINE_IP") of false -> "0.0.0.0"; Any -> Any end,
-    {ok, Dispatch} = file:consult(filename:join(
-                         [filename:dirname(code:which(?MODULE)),
-                          "..", "priv", "dispatch.conf"])),
-    WebConfig = [
-		 {ip, Ip},
-		 {port, 8000},
-                 {log_dir, "priv/log"},
-		 {dispatch, Dispatch}],
-    Web = {webmachine_mochiweb,
-	   {webmachine_mochiweb, start, [WebConfig]},
-	   permanent, 5000, worker, dynamic},
-    Processes = [Web],
-    {ok, {{one_for_one, 10, 10}, Processes}}.

priv/skel/start-dev.sh

-#!/bin/sh
-cd `dirname $0`
-exec erl -pa $PWD/ebin $PWD/deps/*/ebin $PWD/deps/*/deps/*/ebin -boot start_sasl -s reloader -s skel

priv/skel/start.sh

-#!/bin/sh
-cd `dirname $0`
-exec erl -pa $PWD/ebin $PWD/deps/*/ebin $PWD/deps/*/deps/*/ebin -boot start_sasl -s skel

priv/templates/Makefile

+ERL ?= erl
+APP := {{appid}}
+
+all:
+	@./rebar compile
+
+clean:
+	@./rebar clean
+
+docs:
+	@erl -noshell -run edoc_run application '$(APP)' '"."' '[]'

priv/templates/README

+Project Skeleton for the {{appid}} app.
+
+You should find in this directory:
+
+README : this file
+Makefile : simple make commands
+rebar : the Rebar build tool for Erlang applications
+rebar.config : configuration for Rebar
+start.sh : simple startup script for running {{appid}}
+start-debug.sh : run {{appid}} in "debug" mode (automatic module
+                 reloading on compilation)
+/ebin
+  /{{appid}}.app : the Erlang app specification
+/src
+  /{{appid}}_app.erl : base module for the Erlang application
+  /{{appid}}_sup.erl : OTP supervisor for the application
+  /{{appid}}_resource.erl : a simple example Webmachine resource
+/priv
+  /dispatch.conf : the Webmachine URL-dispatching table
+  /www : a convenient place to put your static web content
+
+You probably want to do one of a couple of things at this point:
+
+0. Build the skeleton application:
+   $ make
+   - or -
+   $ ./rebar compile
+
+1. Start up the skeleton application:
+   $ ./start.sh
+
+2. Change the basic application:
+   edit src/{{appid}}_resource.erl
+
+3. Add some new resources:
+   edit src/YOUR_NEW_RESOURCE.erl
+   edit priv/dispatch.conf

priv/templates/ebin/wmskel.app

+%%-*- mode: erlang -*-
+{application, {{appid}},
+ [
+  {description, "{{appid}}"},
+  {vsn, "1"},
+  {modules, [
+             {{appid}},
+             {{appid}}_app,
+             {{appid}}_sup,
+             {{appid}}_resource
+            ]},
+  {registered, []},
+  {applications, [
+                  kernel,
+                  stdlib,
+                  crypto,
+                  mochiweb,
+                  webmachine
+                 ]},
+  {mod, { {{appid}}_app, []}},
+  {env, []}
+ ]}.

priv/templates/priv/dispatch.conf

+%%-*- mode: erlang -*-
+{[], {{appid}}_resource, []}.

priv/templates/rebar.config

+%%-*- mode: erlang -*-
+{sub_dirs, ["apps/mochiweb",
+            "apps/webmachine"]}.
+{lib_dirs, ["apps"]}.

priv/templates/src/wmskel.erl

+%% @author author <author@example.com>
+%% @copyright YYYY author.
+
+%% @doc {{appid}} startup code
+
+-module({{appid}}).
+-author('author <author@example.com>').
+-export([start/0, start_link/0, stop/0]).
+
+ensure_started(App) ->
+    case application:start(App) of
+	ok ->
+	    ok;
+	{error, {already_started, App}} ->
+	    ok
+    end.
+
+%% @spec start_link() -> {ok,Pid::pid()}
+%% @doc Starts the app for inclusion in a supervisor tree
+start_link() ->
+    ensure_started(crypto),
+    ensure_started(mochiweb),
+    application:set_env(webmachine, webmachine_logger_module, 
+                        webmachine_logger),
+    ensure_started(webmachine),
+    {{appid}}_sup:start_link().
+
+%% @spec start() -> ok
+%% @doc Start the skel server.
+start() ->
+    ensure_started(crypto),
+    ensure_started(mochiweb),
+    application:set_env(webmachine, webmachine_logger_module, 
+                        webmachine_logger),
+    ensure_started(webmachine),
+    application:start({{appid}}).
+
+%% @spec stop() -> ok
+%% @doc Stop the skel server.
+stop() ->
+    Res = application:stop({{appid}}),
+    application:stop(webmachine),
+    application:stop(mochiweb),
+    application:stop(crypto),
+    Res.

priv/templates/src/wmskel_app.erl

+%% @author author <author@example.com>
+%% @copyright YYYY author.
+
+%% @doc Callbacks for the {{appid}} application.
+
+-module({{appid}}_app).
+-author('author <author@example.com>').
+
+-behaviour(application).
+-export([start/2,stop/1]).
+
+
+%% @spec start(_Type, _StartArgs) -> ServerRet
+%% @doc application start callback for skel.
+start(_Type, _StartArgs) ->
+    {{appid}}_sup:start_link().
+
+%% @spec stop(_State) -> ServerRet
+%% @doc application stop callback for skel.
+stop(_State) ->
+    ok.

priv/templates/src/wmskel_resource.erl

+%% @author author <author@example.com>
+%% @copyright YYYY author.
+%% @doc Example webmachine_resource.
+
+-module({{appid}}_resource).
+-export([init/1, to_html/2]).
+
+-include_lib("webmachine/include/webmachine.hrl").
+
+init([]) -> {ok, undefined}.
+
+to_html(ReqData, State) ->
+    {"<html><body>Hello, new world</body></html>", ReqData, State}.

priv/templates/src/wmskel_sup.erl

+%% @author author <author@example.com>
+%% @copyright YYYY author.
+
+%% @doc Supervisor for the {{appid}} application.
+
+-module({{appid}}_sup).
+-author('author <author@example.com>').
+
+-behaviour(supervisor).
+
+%% External exports
+-export([start_link/0, upgrade/0]).
+
+%% supervisor callbacks
+-export([init/1]).
+
+%% @spec start_link() -> ServerRet
+%% @doc API for starting the supervisor.
+start_link() ->
+    supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%% @spec upgrade() -> ok
+%% @doc Add processes if necessary.
+upgrade() ->
+    {ok, {_, Specs}} = init([]),
+
+    Old = sets:from_list(
+	    [Name || {Name, _, _, _} <- supervisor:which_children(?MODULE)]),
+    New = sets:from_list([Name || {Name, _, _, _, _, _} <- Specs]),
+    Kill = sets:subtract(Old, New),
+
+    sets:fold(fun (Id, ok) ->
+		      supervisor:terminate_child(?MODULE, Id),
+		      supervisor:delete_child(?MODULE, Id),
+		      ok
+	      end, ok, Kill),
+
+    [supervisor:start_child(?MODULE, Spec) || Spec <- Specs],
+    ok.
+
+%% @spec init([]) -> SupervisorTree
+%% @doc supervisor callback.
+init([]) ->
+    Ip = case os:getenv("WEBMACHINE_IP") of false -> "0.0.0.0"; Any -> Any end,
+    {ok, Dispatch} = file:consult(filename:join(
+                         [filename:dirname(code:which(?MODULE)),
+                          "..", "priv", "dispatch.conf"])),
+    WebConfig = [
+		 {ip, Ip},
+		 {port, 8000},
+                 {log_dir, "priv/log"},
+		 {dispatch, Dispatch}],
+    Web = {webmachine_mochiweb,
+	   {webmachine_mochiweb, start, [WebConfig]},
+	   permanent, 5000, worker, dynamic},
+    Processes = [Web],
+    {ok, { {one_for_one, 10, 10}, Processes} }.

priv/templates/start-dev.sh

+#!/bin/sh
+cd `dirname $0`
+exec erl -pa $PWD/ebin $PWD/apps/*/ebin -boot start_sasl -s reloader -s {{appid}}

priv/templates/start.sh

+#!/bin/sh
+cd `dirname $0`
+exec erl -pa $PWD/ebin $PWD/apps/*/ebin -boot start_sasl -s {{appid}}

priv/templates/wmskel.template

+%%-*- mode: erlang -*-
+%% Basic Webmachine application skeleton
+
+%% Variables:
+%%   appid: name of the application to build
+%%          default = "wmskel"
+%%   webmachine: path to webmachine from this template
+%%               default = "../.."
+%%   prefix: path where the application should be created
+%%           default = "."
+{variables, [{appid, "wmskel"},
+             {webmachine, "../.."},
+             {prefix, "."}]}.
+
+%% main project files
+{file, "README", "{{prefix}}/README"}.
+{file, "Makefile", "{{prefix}}/Makefile"}.
+{file, "rebar.config", "{{prefix}}/rebar.config"}.
+{file, "{{webmachine}}/rebar", "{{prefix}}/rebar", false}.
+{chmod, 8#744, "{{prefix}}/rebar"}.
+{file, "start.sh", "{{prefix}}/start.sh"}.
+{chmod, 8#744, "{{prefix}}/start.sh"}.
+{file, "start-dev.sh", "{{prefix}}/start-dev.sh"}.
+{chmod, 8#744, "{{prefix}}/start-dev.sh"}.
+
+{file, "ebin/wmskel.app", "{{prefix}}/ebin/{{appid}}.app"}.
+
+{file, "src/wmskel.erl", "{{prefix}}/src/{{appid}}.erl"}.
+{file, "src/wmskel_app.erl", "{{prefix}}/src/{{appid}}_app.erl"}.
+{file, "src/wmskel_sup.erl", "{{prefix}}/src/{{appid}}_sup.erl"}.
+{file, "src/wmskel_resource.erl", "{{prefix}}/src/{{appid}}_resource.erl"}.
+
+{file, "priv/dispatch.conf", "{{prefix}}/priv/dispatch.conf"}.
+{dir, "{{prefix}}/priv/www"}.
+
+%% dependencies
+{dir, "{{prefix}}/apps"}.
+
+%% Webmachine
+{file, "{{webmachine}}/ebin/webmachine.app",
+       "{{prefix}}/apps/webmachine/ebin/webmachine.app", false}.
+
+{file, "{{webmachine}}/include/webmachine.hrl",
+       "{{prefix}}/apps/webmachine/include/webmachine.hrl", false}.
+{file, "{{webmachine}}/include/wm_reqdata.hrl",
+       "{{prefix}}/apps/webmachine/include/wm_reqdata.hrl", false}.
+{file, "{{webmachine}}/include/wm_reqstate.hrl",
+       "{{prefix}}/apps/webmachine/include/wm_reqstate.hrl", false}.
+
+{file, "{{webmachine}}/priv/trace/wmtrace.css",
+       "{{prefix}}/apps/webmachine/priv/trace/wmtrace.css", false}.
+{file, "{{webmachine}}/priv/trace/wmtrace.js",
+       "{{prefix}}/apps/webmachine/priv/trace/wmtrace.js", false}.
+{file, "{{webmachine}}/priv/trace/http-headers-status-v3.png",
+       "{{prefix}}/apps/webmachine/priv/trace/http-headers-status-v3.png", false}.
+
+{file, "{{webmachine}}/src/wrq.erl",
+       "{{prefix}}/apps/webmachine/src/wrq.erl", false}.
+{file, "{{webmachine}}/src/wmtrace_resource.erl",
+       "{{prefix}}/apps/webmachine/src/wmtrace_resource.erl", false}.
+{file, "{{webmachine}}/src/webmachine_util.erl",
+       "{{prefix}}/apps/webmachine/src/webmachine_util.erl", false}.
+{file, "{{webmachine}}/src/webmachine_sup.erl",
+       "{{prefix}}/apps/webmachine/src/webmachine_sup.erl", false}.
+{file, "{{webmachine}}/src/webmachine_skel.erl",
+       "{{prefix}}/apps/webmachine/src/webmachine_skel.erl", false}.
+{file, "{{webmachine}}/src/webmachine_resource.erl",
+       "{{prefix}}/apps/webmachine/src/webmachine_resource.erl", false}.
+{file, "{{webmachine}}/src/webmachine_request.erl",
+       "{{prefix}}/apps/webmachine/src/webmachine_request.erl", false}.
+{file, "{{webmachine}}/src/webmachine_perf_logger.erl",
+       "{{prefix}}/apps/webmachine/src/webmachine_perf_logger.erl", false}. 
+{file, "{{webmachine}}/src/webmachine_multipart.erl",
+       "{{prefix}}/apps/webmachine/src/webmachine_multipart.erl", false}.
+{file, "{{webmachine}}/src/webmachine_mochiweb.erl",
+       "{{prefix}}/apps/webmachine/src/webmachine_mochiweb.erl", false}.
+{file, "{{webmachine}}/src/webmachine_logger.hrl",
+       "{{prefix}}/apps/webmachine/src/webmachine_logger.hrl", false}.
+{file, "{{webmachine}}/src/webmachine_logger.erl",
+       "{{prefix}}/apps/webmachine/src/webmachine_logger.erl", false}.
+{file, "{{webmachine}}/src/webmachine_error_handler.erl",
+       "{{prefix}}/apps/webmachine/src/webmachine_error_handler.erl", false}.
+{file, "{{webmachine}}/src/webmachine_dispatcher.erl",
+       "{{prefix}}/apps/webmachine/src/webmachine_dispatcher.erl", false}.
+{file, "{{webmachine}}/src/webmachine_deps.erl",
+       "{{prefix}}/apps/webmachine/src/webmachine_deps.erl", false}.
+{file, "{{webmachine}}/src/webmachine_decision_core.erl",
+       "{{prefix}}/apps/webmachine/src/webmachine_decision_core.erl", false}.
+{file, "{{webmachine}}/src/webmachine_app.erl",
+       "{{prefix}}/apps/webmachine/src/webmachine_app.erl", false}.
+{file, "{{webmachine}}/src/webmachine.erl",
+       "{{prefix}}/apps/webmachine/src/webmachine.erl", false}.
+
+%% Mochiweb
+{file, "{{webmachine}}/deps/mochiweb/ebin/mochiweb.app",
+       "{{prefix}}/apps/mochiweb/ebin/mochiweb.app", false}.
+
+{file, "{{webmachine}}/deps/mochiweb/src/reloader.erl",
+       "{{prefix}}/apps/mochiweb/src/reloader.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochiweb_util.erl",
+       "{{prefix}}/apps/mochiweb/src/mochiweb_util.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochiweb_sup.erl",
+       "{{prefix}}/apps/mochiweb/src/mochiweb_sup.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochiweb_socket_server.erl",
+       "{{prefix}}/apps/mochiweb/src/mochiweb_socket_server.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochiweb_skel.erl",
+       "{{prefix}}/apps/mochiweb/src/mochiweb_skel.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochiweb_response.erl",
+       "{{prefix}}/apps/mochiweb/src/mochiweb_response.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochiweb_request.erl",
+       "{{prefix}}/apps/mochiweb/src/mochiweb_request.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochiweb_multipart.erl",
+       "{{prefix}}/apps/mochiweb/src/mochiweb_multipart.erl", false}. 
+{file, "{{webmachine}}/deps/mochiweb/src/mochiweb_http.erl",
+       "{{prefix}}/apps/mochiweb/src/mochiweb_http.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochiweb_html.erl",
+       "{{prefix}}/apps/mochiweb/src/mochiweb_html.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochiweb_headers.erl",
+       "{{prefix}}/apps/mochiweb/src/mochiweb_headers.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochiweb_echo.erl",
+       "{{prefix}}/apps/mochiweb/src/mochiweb_echo.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochiweb_cookies.erl",
+       "{{prefix}}/apps/mochiweb/src/mochiweb_cookies.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochiweb_charref.erl",
+       "{{prefix}}/apps/mochiweb/src/mochiweb_charref.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochiweb_app.erl",
+       "{{prefix}}/apps/mochiweb/src/mochiweb_app.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochiweb.erl",
+       "{{prefix}}/apps/mochiweb/src/mochiweb.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochinum.erl",
+       "{{prefix}}/apps/mochiweb/src/mochinum.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochijson2.erl",
+       "{{prefix}}/apps/mochiweb/src/mochijson2.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochijson.erl",
+       "{{prefix}}/apps/mochiweb/src/mochijson.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochihex.erl",
+       "{{prefix}}/apps/mochiweb/src/mochihex.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochifmt_std.erl",
+       "{{prefix}}/apps/mochiweb/src/mochifmt_std.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochifmt_records.erl",
+       "{{prefix}}/apps/mochiweb/src/mochifmt_records.erl", false}.
+{file, "{{webmachine}}/deps/mochiweb/src/mochifmt.erl",
+       "{{prefix}}/apps/mochiweb/src/mochifmt.erl", false}.
Add a comment to this file

priv/trace/http-headers-status-v3.png

Added
New image

priv/trace/wmtrace.css

+body {
+    margin:0px;
+    padding:0px;
+}
+
+canvas#v3map {
+    margin-top:2em;
+    z-index: 1;
+}
+
+div#sizetest {
+    width:100%;
+}
+
+div#zoompanel {
+    height:2em;
+    position:fixed;
+    z-index:10;
+}
+
+div#preview {
+    position:absolute;
+    display:none;
+    background:#dddddd;
+    border:1px solid #999999;
+}
+
+div#preview ul {
+    padding: 0px 0px 0px 0.5em;
+    margin: 0px;
+    list-style: none;
+}
+
+div#infopanel {
+    z-index:20;
+    background:#dddddd;
+    position:fixed;
+    top:0px;
+    right:0px;
+    bottom:0px;
+    left:75%;
+    min-width:30em;
+    padding:5px;
+}
+
+div#infocontrols {
+    position:absolute;
+    top:0px;
+    bottom:0px;
+    left:-5px;
+    width:5px;
+    background:#999999;
+    cursor:ew-resize;
+}
+
+div#infocontrols div {
+    position:absolute;
+    left:-15px;
+    width:20px;
+    height:49px;
+    background:#999999;
+    cursor:pointer;
+}
+
+div#infocontrols div.selectedtab {
+    background:#dddddd;
+    border-top: 1px solid #999999;
+    border-left: 1px solid #999999;
+    border-bottom: 1px solid #999999;
+}
+
+div#requesttab {
+    top:2px;
+}
+
+div#responsetab {
+    top:54px;
+}
+
+div#decisiontab {
+    top:106px;
+}
+
+div#requestdetail, div#responsedetail, div#decisiondetail {
+    height:100%;
+}
+
+div#responsedetail, div#decisiondetail {
+    display:none;
+}
+
+div#infopanel ul {
+    list-style:none;
+    padding-left:0px;
+    height:5em;
+    overflow-y:scroll;
+}
+
+pre {
+    height:40%;
+    overflow:scroll;
+}
+
+div#responsebody, div#requestbody {
+    height:70%;
+    overflow-y:scroll;
+}

priv/trace/wmtrace.js

+var HIGHLIGHT = '#cc00cc';
+var REGULAR = '#666666';
+
+var cols = {
+    'a':173,
+    'b':325,
+    'c':589,
+    'd':797,
+    'e':1005,
+    'f':1195,
+    'g':1402,
+    'gg':1515,
+    'h':1572,
+    'i':1799,
+    'j':1893,
+    'k':1988,
+    'l':2157,
+    'll':2346,
+    'm':2403,
+    'mm':2535,
+    'n':2554,
+    'o':2649,
+    'oo':2781,
+    'ooo':2801,
+    'p':2894,
+    'q':3007
+};
+
+var rows = {
+    '1':221,
+    '2':298,
+    '3':373,
+    '4':448,
+    '5':524,
+    '6':599,
+    '7':675,
+    '8':751,
+    '9':826,
+    '10':902,
+    '11':977,
+    '12':1053,
+    '13':1129,
+    '14':1204,
+    '15':1280,
+    '16':1355,
+    '17':1431,
+    '18':1506,
+    '19':1583,
+    '20':1658,
+    '21':1734,
+    '22':1809,
+    '23':1885,
+    '24':1961,
+    '25':2036,
+    '26':2112
+};
+
+var edges = {
+    'b14b13':['b14','b13'],
+
+    'b13b12':['b13','b12'],
+    'b13503':['b13','503'],
+
+    'b12b11':['b12','b11'],
+    'b12501':['b12','501'],
+
+    'b11b10':['b11','b10'],
+    'b11414':['b11','414'],
+
+    'b10b9':['b10','b9'],
+    'b10405':['b10','405'],
+
+    'b9b8':['b9','b8'],
+    'b9400':['b9','400'],
+
+    'b8b7':['b8','b7'],
+    'b8401':['b8','401'],
+
+    'b7b6':['b7','b6'],
+    'b7403':['b7','403'],
+
+    'b6b5':['b6','b5'],
+    'b6501':['b6','501a'],
+
+    'b5b4':['b5','b4'],
+    'b5415':['b5','415'],
+
+    'b4b3':['b4','b3'],
+    'b4413':['b4','b4'],
+
+    'b3c3':['b3','c3'],
+    'b3200':['b3','200'],
+
+    'c3c4':['c3','c4'],
+    'c3d4':['c3','d3','d4'],
+
+    'c4d4':['c4','d4'],
+    'c4406':['c4','406'],
+
+    'd4d5':['d4','d5'],
+    'd4e5':['d4','e4','e5'],
+
+    'd5e5':['d5','e5'],
+    'd5406':['d5','d7','406'],
+
+    'e5e6':['e5','e6'],
+    'e5f6':['e5','f5','f6'],
+
+    'e6f6':['e6','f6'],
+    'e6406':['e6','e7','406'],
+
+    'f6f7':['f6','f7'],
+    'f6g7':['f6','g6','g7'],
+
+    'f7g7':['f7','g7'],
+    'f7406':['f7','406'],
+
+    'g7g8':['g7','g8'],
+    'g7h7':['g7','h7'],
+
+    'g8g9':['g8','g9'],
+    'g8h10':['g8','h8','h10'],
+
+    'g9g11':['g9','g11'],
+    'g9h10':['g9','gg9','gg10','h10'],
+
+    'g11h10':['g11','gg11','gg10','h10'],
+    'g11412':['g11','g18','412a'],
+
+    'h7i7':['h7','i7'],
+    'h7412':['h7','412'],
+
+    'h10h11':['h10','h11'],
+    'h10i12':['h10','i10','i12'],
+
+    'h11h12':['h11','h12'],
+    'h11i12':['h11','i11','i12'],
+
+    'h12i12':['h12','i12'],
+    'h12412':['h12','412a'],
+
+    'i4p3':['i4','i3','p3'],
+    'i4301':['i4','301'],
+
+    'i7i4':['i7','i4'],
+    'i7k7':['i7','k7'],
+
+    'i12l13':['i12','l12','l13'],
+    'i12i13':['i12','i13'],
+
+    'i13k13':['i13','k13'],
+    'i13j18':['i13','i17','j17','j18'],
+
+    'j18412':['j18','412a'],
+    'j18304':['j18','304'],
+
+    'k5l5':['k5','l5'],
+    'k5301':['k5','301'],
+
+    'k7k5':['k7','k5'],
+    'k7l7':['k7','l7'],
+
+    'k13j18':['k13','k17','j17','j18'],
+    'k13l13':['k13','l13'],
+
+    'l5m5':['l5','m5'],
+    'l5307':['l5','307'],
+
+    'l7m7':['l7','m7'],
+    'l7404':['l7','l8','404'],
+
+    'l13l14':['l13','l14'],
+    'l13m16':['l13','m13','m16'],
+
+    'l14l15':['l14','l15'],
+    'l14m16':['l14','m14','m16'],
+
+    'l15l17':['l15','l17'],
+    'l15m16':['l15','ll15','ll16','m16'],
+
+    'l17m16':['l17','ll17','ll16','m16'],
+    'l17304':['l17','304'],
+
+    'm5n5':['m5','n5'],
+    'm5410':['m5','m4','410'],
+
+    'm7n11':['m7','n7','n11'],
+    'm7404':['m7','404'],
+
+    'm16m20':['m16','m20'],
+    'm16n16':['m16','n16'],
+
+    'm20o20':['m20','o20'],
+    'm20202':['m20','202'],
+
+    'n5n11':['n5','n11'],
+    'n5410':['n5','410'],
+
+    'n11p11':['n11','p11'],
+    'n11303':['n11','303'],
+
+    'n16n11':['n16','n11'],
+    'n16o16':['n16','o16'],
+
+    'o14p11':['o14','o11','p11'],
+    'o14409':['o14','409a'],
+
+    'o16o14':['o16','o14'],
+    'o16o18':['o16','o18'],
+
+    'o18200':['o18','200a'],
+    'o18300':['o18','oo18','300'],
+
+    'o20o18':['o20','o18'],
+    'o20204':['o20','204'],
+
+    'p3p11':['p3','p11'],
+    'p3409':['p3','409'],
+
+    'p11o20':['p11','p20','o20'],
+    'p11201':['p11','q11','201']
+};
+
+var ends = {
+    '200': {col:'a', row:'3', width:190},
+    '200a': {col:'mm', row:'18', width:116},
+    '201': {col:'q', row:'12', width:154},
+    '202': {col:'m', row:'21', width:116},
+    '204': {col:'o', row:'21', width:152},
+
+    '300': {col:'oo', row:'19', width:152},
+    '301': {col:'k', row:'4', width:154},
+    '303': {col:'m', row:'11', width:116},
+    '304': {col:'l', row:'18', width:116},
+    '307': {col:'l', row:'4', width:154},
+
+    '400': {col:'a', row:'9', width:190},
+    '401': {col:'a', row:'8', width:190},
+    '403': {col:'a', row:'7', width:190},
+    '404': {col:'m', row:'8', width:116},
+    '405': {col:'a', row:'10', width:190},
+    '406': {col:'c', row:'7', width:152},
+    '409': {col:'p', row:'2', width:116},
+    '409a': {col:'oo', row:'14', width:116},
+    '410': {col:'n', row:'4', width:116},
+    '412': {col:'h', row:'6', width:152},
+    '412a': {col:'h', row:'18', width:152},
+    '413': {col:'a', row:'4', width:190},
+    '414': {col:'a', row:'11', width:190},
+    '415': {col:'a', row:'5', width:190},
+
+    '501a': {col:'a', row:'6', width:190},
+    '501': {col:'a', row:'12', width:190},
+    '503': {col:'a', row:'13', width:190}
+};
+
+var canvas;
+
+function decorateTrace() {
+    trace[0].x = cols[trace[0].d[0]];
+    trace[0].y = rows[trace[0].d.slice(1)];
+    trace[0].previewCalls = previewCalls(trace[0]);
+
+    for (var i = 1; i < trace.length; i++) {
+        trace[i].x = cols[trace[i].d[0]];
+        trace[i].y = rows[trace[i].d.slice(1)];
+        trace[i].previewCalls = previewCalls(trace[i]);
+        
+        var path = edges[trace[i-1].d+trace[i].d];
+        if (path) {
+            trace[i].path = [path.length-1];
+            for (var p = 1; p < path.length; p++) {
+                trace[i].path[p-1] = getSeg(path[p-1], path[p], p == path.length-1);
+            }
+        } else {
+            trace[i].path = [];
+        }
+    }
+    
+    var path = edges[trace[i-1].d+response.code];
+    if (path) {
+        var end = ends[path[path.length-1]];
+        response.x = cols[end.col];
+        response.y = rows[end.row];
+        response.width = end.width;
+        response.type = 'normal';
+
+        response.path = [path.length-1];
+        for (var p = 1; p < path.length; p++) {
+            response.path[p-1] = getSeg(path[p-1], path[p], p == path.length-1);
+        }
+    } else {
+        var ld = trace[trace.length-1];
+        response.x = ld.x+50;
+        response.y = ld.y-50;
+        response.width = 38;
+        response.type = 'other';
+
+        response.path = [
+            {x1: ld.x+10, y1: ld.y-10,
+             x2: ld.x+36, y2: ld.y-36}
+        ];
+    }
+};
+
+function previewCalls(dec) {
+    var prev = '';
+    for (var i = 0; i < dec.calls.length; i++) {
+        if (dec.calls[i].output != "wmtrace_not_exported")
+            prev += '<li>'+dec.calls[i].module+':'+dec.calls[i]['function']+'</li>';
+    }
+    return prev;
+};
+
+function drawTrace() {
+    drawDecision(trace[0]);
+    for (var i = 1; i < trace.length; i++) {
+        drawPath(trace[i].path);
+        drawDecision(trace[i]);
+    }
+
+    drawPath(response.path);
+    drawResponse();
+};
+
+function drawResponse() {
+    if (response.type == 'normal') {
+        var context = canvas.getContext('2d');
+        context.strokeStyle=HIGHLIGHT;
+        context.lineWidth=4;
+
+        context.beginPath();
+        context.rect(response.x-(response.width/2),
+                     response.y-19,
+                     response.width,
+                     38);
+        context.stroke();
+    } else {
+        var context = canvas.getContext('2d');
+        context.strokeStyle='#ff0000';
+        context.lineWidth=4;
+
+        context.beginPath();
+        context.arc(response.x, response.y, 19,
+                    0, 2*3.14159, false);
+        context.stroke();
+
+    }
+};
+
+function drawDecision(dec) {
+    var context = canvas.getContext('2d');
+
+    if (dec.previewCalls == '')
+        context.strokeStyle=REGULAR;
+    else
+        context.strokeStyle=HIGHLIGHT;
+    context.lineWidth=4;
+
+    context.beginPath();
+    context.moveTo(dec.x,    dec.y-19);
+    context.lineTo(dec.x+19, dec.y);
+    context.lineTo(dec.x,    dec.y+19);
+    context.lineTo(dec.x-19, dec.y);
+    context.closePath();
+    context.stroke();
+};
+
+function drawPath(path) {
+    var context = canvas.getContext('2d');
+    context.strokeStyle=REGULAR;
+    context.lineWidth=4;
+
+    context.beginPath();
+    context.moveTo(path[0].x1, path[0].y1);
+    for (var p = 0; p < path.length; p++) {
+        context.lineTo(path[p].x2, path[p].y2);
+    }
+    context.stroke();
+};
+
+function getSeg(p1, p2, last) {
+    var seg = {
+        x1:cols[p1[0]],
+        y1:rows[p1.slice(1)]
+    };
+    if (ends[p2]) {
+        seg.x2 = cols[ends[p2].col];
+        seg.y2 = rows[ends[p2].row];
+    } else {
+        seg.x2 = cols[p2[0]];
+        seg.y2 = rows[p2.slice(1)];
+    }
+
+    if (seg.x1 == seg.x2) {
+        if (seg.y1 < seg.y2) {
+            seg.y1 = seg.y1+19;
+            if (last) seg.y2 = seg.y2-19;
+        } else {
+            seg.y1 = seg.y1-19;
+            if (last) seg.y2 = seg.y2+19;
+        }
+    } else {
+        //assume seg.y1 == seg.y2
+        if (seg.x1 < seg.x2) {
+            seg.x1 = seg.x1+19;
+            if (last) seg.x2 = seg.x2-(ends[p2] ? (ends[p2].width/2) : 19);
+        } else {
+            seg.x1 = seg.x1-19;
+            if (last) seg.x2 = seg.x2+(ends[p2] ? (ends[p2].width/2) : 19);
+        }
+    }
+    return seg;
+};
+
+function traceDecision(name) {
+    for (var i = trace.length-1; i >= 0; i--)
+        if (trace[i].d == name) return trace[i];
+};
+
+var detailPanels = {};
+function initDetailPanels() {
+    var windowWidth = document.getElementById('sizetest').clientWidth;
+    var infoPanel = document.getElementById('infopanel');
+    var panelWidth = windowWidth-infoPanel.offsetLeft;
+
+    var panels = {
+        'request': document.getElementById('requestdetail'),
+        'response': document.getElementById('responsedetail'),
+        'decision': document.getElementById('decisiondetail')
+    };
+
+    var tabs = {
+        'request': document.getElementById('requesttab'),
+        'response': document.getElementById('responsetab'),
+        'decision': document.getElementById('decisiontab')
+    };
+
+    var decisionId = document.getElementById('decisionid');
+    var decisionCalls = document.getElementById('decisioncalls');
+    var callInput = document.getElementById('callinput');
+    var callOutput = document.getElementById('calloutput');
+
+    var lastUsedPanelWidth = windowWidth-infoPanel.offsetLeft;
+
+    var setPanelWidth = function(width) {
+        infoPanel.style.left = (windowWidth-width)+'px';
+        canvas.style.marginRight = (width+20)+'px';
+        panelWidth = width;
+    };
+    setPanelWidth(panelWidth);
+
+    var ensureVisible = function() {
+        if (windowWidth-infoPanel.offsetLeft < 10)
+            setPanelWidth(lastUsedPanelWidth);
+    };
+
+    var decChoices = '';
+    for (var i = 0; i < trace.length; i++) {
+        decChoices += '<option value="'+trace[i].d+'">'+trace[i].d+'</option>';
+    }
+    decisionId.innerHTML = decChoices;
+    decisionId.selectedIndex = -1;
+
+    decisionId.onchange = function() {
+        detailPanels.setDecision(traceDecision(decisionId.value));
+    }
+
+    detailPanels.setDecision = function(dec) {
+        decisionId.value = dec.d;
+
+        var calls = [];
+        for (var i = 0; i < dec.calls.length; i++) {
+            calls.push('<option value="'+dec.d+'-'+i+'">');
+            calls.push(dec.calls[i].module+':'+dec.calls[i]['function']);
+            calls.push('</option>');
+        }
+        decisionCalls.innerHTML = calls.join('');
+        decisionCalls.selectedIndex = 0;
+
+        decisionCalls.onchange();
+    };
+
+    detailPanels.show = function(name) {
+        for (p in panels) {
+            if (p == name) {
+                panels[p].style.display = 'block';
+                tabs[p].className = 'selectedtab';
+            }
+            else {
+                panels[p].style.display = 'none';
+                tabs[p].className = '';
+            }
+        }
+        ensureVisible();
+    };
+
+    detailPanels.hide = function() {
+        setPanelWidth(0);
+    }
+
+    decisionCalls.onchange = function() {
+        var val = decisionCalls.value;
+        if (val) {
+            var dec = traceDecision(val.substring(0, val.indexOf('-')));
+            var call = dec.calls[parseInt(val.substring(val.indexOf('-')+1, val.length))];
+
+            if (call.output != "wmtrace_not_exported") {
+                callInput.style.color='#000000';
+                callInput.innerHTML = call.input;
+                if (call.output != null) {
+                    callOutput.style.color = '#000000';
+                    callOutput.innerHTML = call.output;
+                } else {
+                    callOutput.style.color = '#ff0000';
+                    callOutput.textContent = 'Error: '+call.module+':'+call['function']+' never returned';
+                }
+            } else {
+                callInput.style.color='#999999';
+                callInput.textContent = call.module+':'+call['function']+' was not exported';
+                callOutput.textContent = '';
+            }
+        } else {
+            callInput.textContent = '';
+            callOutput.textContent = '';
+        }
+    };
+
+    var headersList = function(headers) {
+        var h = '';
+        for (n in headers) h += '<li>'+n+': '+headers[n];
+        return h;
+    };
+
+    document.getElementById('requestmethod').innerHTML = request.method;
+    document.getElementById('requestpath').innerHTML = request.path;
+    document.getElementById('requestheaders').innerHTML = headersList(request.headers);
+    document.getElementById('requestbody').innerHTML = request.body;
+
+    document.getElementById('responsecode').innerHTML = response.code;
+    document.getElementById('responseheaders').innerHTML = headersList(response.headers);
+    document.getElementById('responsebody').innerHTML = response.body;
+
+
+    var infoControls = document.getElementById('infocontrols');
+    var md = false;
+    var dragged = false;
+    var msoff = 0;
+    infoControls.onmousedown = function(ev) {
+        md = true;
+        dragged = false;
+        msoff = ev.clientX-infoPanel.offsetLeft;
+    };
+
+    infoControls.onclick = function(ev) {
+        if (dragged) {
+            lastUsedPanelWidth = panelWidth;
+        }
+        else if (panelWidth < 10) {
+            switch(ev.target.id) {
+            case 'requesttab': detailPanels.show('request'); break;
+            case 'responsetab': detailPanels.show('response'); break;
+            case 'decisiontab': detailPanels.show('decision'); break;
+            default: ensureVisible();
+            }
+        } else {
+            var name = 'none';
+            switch(ev.target.id) {
+            case 'requesttab': name = 'request'; break;
+            case 'responsetab': name = 'response'; break;
+            case 'decisiontab': name = 'decision'; break;
+            }
+
+            if (panels[name] && panels[name].style.display != 'block')
+                detailPanels.show(name);
+            else
+                detailPanels.hide();
+        }
+
+        return false;
+    };
+
+    document.onmousemove = function(ev) {
+        if (md) {
+            dragged = true;
+            panelWidth = windowWidth-(ev.clientX-msoff);
+            if (panelWidth < 0) {
+                panelWidth = 0;
+                infoPanel.style.left = windowWidth+"px";
+            }
+            else if (panelWidth > windowWidth-21) {
+                panelWidth = windowWidth-21;
+                infoPanel.style.left = '21px';
+            }
+            else
+                infoPanel.style.left = (ev.clientX-msoff)+"px";
+
+            canvas.style.marginRight = panelWidth+20+"px";
+            return false;
+        }
+    };
+
+    document.onmouseup = function() { md = false; };
+
+    window.onresize = function() {
+        windowWidth = document.getElementById('sizetest').clientWidth;
+        infoPanel.style.left = windowWidth-panelWidth+'px';
+    };
+};
+
+window.onload = function() {
+    canvas = document.getElementById('v3map');
+
+    initDetailPanels();
+
+    var scale = 0.25;
+    var coy = canvas.offsetTop;
+    function findDecision(ev) {
+        var x = (ev.clientX+window.pageXOffset)/scale;
+        var y = (ev.clientY+window.pageYOffset-coy)/scale;
+
+        for (var i = trace.length-1; i >= 0; i--) {
+            if (x >= trace[i].x-19 && x <= trace[i].x+19 &&
+                y >= trace[i].y-19 && y <= trace[i].y+19)
+                return trace[i];
+        }
+    };
+
+    var preview = document.getElementById('preview');
+    var previewId = document.getElementById('previewid');
+    var previewCalls = document.getElementById('previewcalls');
+    function previewDecision(dec) {
+        preview.style.left = (dec.x*scale)+'px';
+        preview.style.top = (dec.y*scale+coy+15)+'px';
+        preview.style.display = 'block';
+        previewId.textContent = dec.d;
+
+        previewCalls.innerHTML = dec.previewCalls;
+    };
+
+    function overResponse(ev) {
+        var x = (ev.clientX+window.pageXOffset)/scale;
+        var y = (ev.clientY+window.pageYOffset-coy)/scale;
+        
+        return (x >= response.x-(response.width/2)
+                && x <= response.x+(response.width/2)
+                && y >= response.y-19 && y <= response.y+19);
+    };
+
+    decorateTrace();
+
+    var bg = new Image(3138, 2184);
+
+    function drawMap() {
+        var ctx = canvas.getContext("2d");
+
+        ctx.save();
+        ctx.scale(1/scale, 1/scale);
+        ctx.fillStyle = '#ffffff';
+        ctx.fillRect(0, 0, 3138, 2184);
+        ctx.restore();
+
+        ctx.drawImage(bg, 0, 0);
+        drawTrace();
+    };
+
+    bg.onload = function() {
+        canvas.getContext("2d").scale(scale, scale);
+        drawMap(scale);
+
+        canvas.onmousemove = function(ev) {
+            if (findDecision(ev)) {
+                canvas.style.cursor = 'pointer';
+                previewDecision(findDecision(ev));
+            }
+            else {
+                preview.style.display = 'none';
+                if (overResponse(ev))
+                    canvas.style.cursor = 'pointer';
+                else
+                    canvas.style.cursor = 'default';
+            }
+        };
+
+        canvas.onclick = function(ev) {
+            var dec = findDecision(ev);
+            if (dec) {
+                detailPanels.setDecision(dec);
+                detailPanels.show('decision');
+            } else if (overResponse(ev)) {
+                detailPanels.show('response');
+            }
+        };
+
+        document.getElementById('zoomin').onclick = function() {
+            scale = scale*2;
+            canvas.getContext("2d").scale(2, 2);
+            drawMap();
+        };
+ 
+        document.getElementById('zoomout').onclick = function() {
+            scale = scale/2;
+            canvas.getContext("2d").scale(0.5, 0.5);
+            drawMap();
+        };
+    };
+
+    bg.onerror = function() {
+        alert('Failed to load background image.');
+    };
+
+    bg.src = 'static/map.png';
+};

Binary file added.

+%%-*- mode: erlang -*-
+{sub_dirs, ["deps/mochiweb"]}.
+{fail_on_warning, true}.
+

scripts/new_webmachine.erl

-#!/usr/bin/env escript
-%% -*- mode: erlang -*-
--export([main/1]).
-
-%% External API
-
-main([Name]) ->
-    case Name of
-        "." ++ _Rest -> usage();
-        "~" ++ _Rest -> usage();
-        "/" ++ _Rest -> usage();
-        _Any         -> main([Name, "."])
-    end;
-main([Name, Dest]) ->
-    ensure(),
-    DestDir = filename:absname(Dest),
-    ok = webmachine_skel:skelcopy(DestDir, Name);
-main(_) ->
-    usage().
-
-%% Internal API
-
-ensure() ->
-    code:add_patha(filename:join(filename:dirname(escript:script_name()),
-                                 "../ebin")).
-
-usage() ->
-    io:format("usage: ~s name [destdir]~n",
-              [filename:basename(escript:script_name())]),
-    halt(1).
-
-

scripts/new_webmachine.sh

+#!/bin/bash
+
+NAME=$1
+DESTDIR=$2
+
+if [ -z $NAME ] || [[ $NAME =~ ^[\.\~\/] ]]; then
+    echo "usage: new_webmachine.sh name [destdir]"
+    exit 1
+fi
+
+if [ -z $DESTDIR ]; then
+    DESTDIR="."
+elif [[ $DESTDIR =~ /${NAME}$ ]]; then
+    DESTDIR=${DESTDIR%/*}
+fi
+
+if [ ! -e $DESTDIR ]; then
+    $(mkdir -p $DESTDIR)
+fi
+
+ABSDEST=$(cd $DESTDIR && pwd)
+
+cd ${0%/*}/../priv
+
+../rebar create template=wmskel appid=$NAME prefix=$ABSDEST/$NAME

src/wmtrace_resource.erl

 -record(ctx, {trace_dir, trace}).
 
 -define(MAP_EXTERNAL, "static/map.png").
--define(MAP_INTERNAL, "docs/http-headers-status-v3.png").
+-define(MAP_INTERNAL, "http-headers-status-v3.png").
 -define(SCRIPT_EXTERNAL, "static/wmtrace.js").
--define(SCRIPT_INTERNAL, "trace/wmtrace.js").
+-define(SCRIPT_INTERNAL, "wmtrace.js").
 -define(STYLE_EXTERNAL, "static/wmtrace.css").
--define(STYLE_INTERNAL, "trace/wmtrace.css").
+-define(STYLE_INTERNAL, "wmtrace.css").
 
 %%
 %% Dispatch Modifiers
     end.
 
 wm_path(File) ->
-    filename:join(code:lib_dir(webmachine), File).
+    filename:join([code:priv_dir(webmachine), "trace", File]).
 
 content_types_provided(RD, Ctx) ->
     case wrq:disp_path(RD) of

trace/wmtrace.css

-body {
-    margin:0px;
-    padding:0px;
-}
-
-canvas#v3map {
-    margin-top:2em;
-    z-index: 1;
-}
-
-div#sizetest {
-    width:100%;
-}
-
-div#zoompanel {
-    height:2em;
-    position:fixed;
-    z-index:10;
-}
-
-div#preview {
-    position:absolute;
-    display:none;
-    background:#dddddd;
-    border:1px solid #999999;
-}
-
-div#preview ul {
-    padding: 0px 0px 0px 0.5em;
-    margin: 0px;
-    list-style: none;
-}
-
-div#infopanel {
-    z-index:20;
-    background:#dddddd;
-    position:fixed;
-    top:0px;
-    right:0px;
-    bottom:0px;
-    left:75%;
-    min-width:30em;
-    padding:5px;
-}
-
-div#infocontrols {
-    position:absolute;
-    top:0px;
-    bottom:0px;
-    left:-5px;
-    width:5px;
-    background:#999999;
-    cursor:ew-resize;
-}
-
-div#infocontrols div {
-    position:absolute;
-    left:-15px;
-    width:20px;
-    height:49px;
-    background:#999999;
-    cursor:pointer;
-}
-
-div#infocontrols div.selectedtab {
-    background:#dddddd;
-    border-top: 1px solid #999999;
-    border-left: 1px solid #999999;
-    border-bottom: 1px solid #999999;
-}
-
-div#requesttab {
-    top:2px;
-}
-
-div#responsetab {
-    top:54px;
-}
-
-div#decisiontab {
-    top:106px;
-}
-
-div#requestdetail, div#responsedetail, div#decisiondetail {
-    height:100%;
-}
-
-div#responsedetail, div#decisiondetail {
-    display:none;
-}
-
-div#infopanel ul {
-    list-style:none;
-    padding-left:0px;
-    height:5em;
-    overflow-y:scroll;
-}
-
-pre {
-    height:40%;
-    overflow:scroll;
-}
-
-div#responsebody, div#requestbody {
-    height:70%;
-    overflow-y:scroll;
-}

trace/wmtrace.js

-var HIGHLIGHT = '#cc00cc';
-var REGULAR = '#666666';
-
-var cols = {
-    'a':173,
-    'b':325,
-    'c':589,
-    'd':797,
-    'e':1005,
-    'f':1195,
-    'g':1402,
-    'gg':1515,
-    'h':1572,
-    'i':1799,
-    'j':1893,
-    'k':1988,
-    'l':2157,
-    'll':2346,
-    'm':2403,
-    'mm':2535,
-    'n':2554,
-    'o':2649,
-    'oo':2781,
-    'ooo':2801,
-    'p':2894,
-    'q':3007
-};
-
-var rows = {
-    '1':221,
-    '2':298,
-    '3':373,
-    '4':448,
-    '5':524,
-    '6':599,
-    '7':675,
-    '8':751,
-    '9':826,
-    '10':902,
-    '11':977,
-    '12':1053,
-    '13':1129,
-    '14':1204,
-    '15':1280,
-    '16':1355,
-    '17':1431,
-    '18':1506,
-    '19':1583,
-    '20':1658,
-    '21':1734,
-    '22':1809,
-    '23':1885,
-    '24':1961,
-    '25':2036,
-    '26':2112
-};
-
-var edges = {
-    'b14b13':['b14','b13'],
-
-    'b13b12':['b13','b12'],
-    'b13503':['b13','503'],
-
-    'b12b11':['b12','b11'],
-    'b12501':['b12','501'],
-
-    'b11b10':['b11','b10'],
-    'b11414':['b11','414'],
-
-    'b10b9':['b10','b9'],
-    'b10405':['b10','405'],
-
-    'b9b8':['b9','b8'],
-    'b9400':['b9','400'],
-
-    'b8b7':['b8','b7'],
-    'b8401':['b8','401'],
-
-    'b7b6':['b7','b6'],
-    'b7403':['b7','403'],
-
-    'b6b5':['b6','b5'],
-    'b6501':['b6','501a'],
-
-    'b5b4':['b5','b4'],
-    'b5415':['b5','415'],
-
-    'b4b3':['b4','b3'],
-    'b4413':['b4','b4'],
-
-    'b3c3':['b3','c3'],
-    'b3200':['b3','200'],
-
-    'c3c4':['c3','c4'],
-    'c3d4':['c3','d3','d4'],
-
-    'c4d4':['c4','d4'],
-    'c4406':['c4','406'],
-
-    'd4d5':['d4','d5'],
-    'd4e5':['d4','e4','e5'],
-
-    'd5e5':['d5','e5'],
-    'd5406':['d5','d7','406'],
-
-    'e5e6':['e5','e6'],
-    'e5f6':['e5','f5','f6'],
-
-    'e6f6':['e6','f6'],
-    'e6406':['e6','e7','406'],
-
-    'f6f7':['f6','f7'],
-    'f6g7':['f6','g6','g7'],
-
-    'f7g7':['f7','g7'],
-    'f7406':['f7','406'],
-
-    'g7g8':['g7','g8'],
-    'g7h7':['g7','h7'],
-
-    'g8g9':['g8','g9'],
-    'g8h10':['g8','h8','h10'],
-
-    'g9g11':['g9','g11'],
-    'g9h10':['g9','gg9','gg10','h10'],
-
-    'g11h10':['g11','gg11','gg10','h10'],
-    'g11412':['g11','g18','412a'],
-
-    'h7i7':['h7','i7'],
-    'h7412':['h7','412'],
-
-    'h10h11':['h10','h11'],
-    'h10i12':['h10','i10','i12'],
-
-    'h11h12':['h11','h12'],
-    'h11i12':['h11','i11','i12'],
-
-    'h12i12':['h12','i12'],
-    'h12412':['h12','412a'],
-
-    'i4p3':['i4','i3','p3'],
-    'i4301':['i4','301'],
-
-    'i7i4':['i7','i4'],
-    'i7k7':['i7','k7'],
-
-    'i12l13':['i12','l12','l13'],
-    'i12i13':['i12','i13'],
-
-    'i13k13':['i13','k13'],
-    'i13j18':['i13','i17','j17','j18'],
-
-    'j18412':['j18','412a'],
-    'j18304':['j18','304'],
-
-    'k5l5':['k5','l5'],
-    'k5301':['k5','301'],
-
-    'k7k5':['k7','k5'],
-    'k7l7':['k7','l7'],
-
-    'k13j18':['k13','k17','j17','j18'],
-    'k13l13':['k13','l13'],
-
-    'l5m5':['l5','m5'],
-    'l5307':['l5','307'],
-
-    'l7m7':['l7','m7'],
-    'l7404':['l7','l8','404'],
-
-    'l13l14':['l13','l14'],
-    'l13m16':['l13','m13','m16'],
-
-    'l14l15':['l14','l15'],
-    'l14m16':['l14','m14','m16'],
-
-    'l15l17':['l15','l17'],
-    'l15m16':['l15','ll15','ll16','m16'],
-
-    'l17m16':['l17','ll17','ll16','m16'],
-    'l17304':['l17','304'],
-
-    'm5n5':['m5','n5'],
-    'm5410':['m5','m4','410'],
-
-    'm7n11':['m7','n7','n11'],
-    'm7404':['m7','404'],
-
-    'm16m20':['m16','m20'],
-    'm16n16':['m16','n16'],
-
-    'm20o20':['m20','o20'],
-    'm20202':['m20','202'],
-
-    'n5n11':['n5','n11'],
-    'n5410':['n5','410'],
-
-    'n11p11':['n11','p11'],
-    'n11303':['n11','303'],
-
-    'n16n11':['n16','n11'],
-    'n16o16':['n16','o16'],
-
-    'o14p11':['o14','o11','p11'],
-    'o14409':['o14','409a'],
-
-    'o16o14':['o16','o14'],
-    'o16o18':['o16','o18'],
-
-    'o18200':['o18','200a'],
-    'o18300':['o18','oo18','300'],
-
-    'o20o18':['o20','o18'],
-    'o20204':['o20','204'],
-
-    'p3p11':['p3','p11'],
-    'p3409':['p3','409'],
-
-    'p11o20':['p11','p20','o20'],
-    'p11201':['p11','q11','201']
-};
-
-var ends = {
-    '200': {col:'a', row:'3', width:190},
-    '200a': {col:'mm', row:'18', width:116},
-    '201': {col:'q', row:'12', width:154},
-    '202': {col:'m', row:'21', width:116},
-    '204': {col:'o', row:'21', width:152},
-
-    '300': {col:'oo', row:'19', width:152},
-    '301': {col:'k', row:'4', width:154},
-    '303': {col:'m', row:'11', width:116},
-    '304': {col:'l', row:'18', width:116},
-    '307': {col:'l', row:'4', width:154},
-
-    '400': {col:'a', row:'9', width:190},
-    '401': {col:'a', row:'8', width:190},
-    '403': {col:'a', row:'7', width:190},
-    '404': {col:'m', row:'8', width:116},
-    '405': {col:'a', row:'10', width:190},
-    '406': {col:'c', row:'7', width:152},
-    '409': {col:'p', row:'2', width:116},
-    '409a': {col:'oo', row:'14', width:116},
-    '410': {col:'n', row:'4', width:116},
-    '412': {col:'h', row:'6', width:152},
-    '412a': {col:'h', row:'18', width:152},
-    '413': {col:'a', row:'4', width:190},
-    '414': {col:'a', row:'11', width:190},
-    '415': {col:'a', row:'5', width:190},
-
-    '501a': {col:'a', row:'6', width:190},
-    '501': {col:'a', row:'12', width:190},
-    '503': {col:'a', row:'13', width:190}
-};
-
-var canvas;
-
-function decorateTrace() {
-    trace[0].x = cols[trace[0].d[0]];
-    trace[0].y = rows[trace[0].d.slice(1)];
-    trace[0].previewCalls = previewCalls(trace[0]);
-
-    for (var i = 1; i < trace.length; i++) {
-        trace[i].x = cols[trace[i].d[0]];
-        trace[i].y = rows[trace[i].d.slice(1)];
-        trace[i].previewCalls = previewCalls(trace[i]);
-        
-        var path = edges[trace[i-1].d+trace[i].d];
-        if (path) {
-            trace[i].path = [path.length-1];
-            for (var p = 1; p < path.length; p++) {
-                trace[i].path[p-1] = getSeg(path[p-1], path[p], p == path.length-1);
-            }
-        } else {
-            trace[i].path = [];
-        }
-    }
-    
-    var path = edges[trace[i-1].d+response.code];
-    if (path) {
-        var end = ends[path[path.length-1]];
-        response.x = cols[end.col];
-        response.y = rows[end.row];
-        response.width = end.width;
-        response.type = 'normal';
-
-        response.path = [path.length-1];
-        for (var p = 1; p < path.length; p++) {
-            response.path[p-1] = getSeg(path[p-1], path[p], p == path.length-1);
-        }
-    } else {
-        var ld = trace[trace.length-1];
-        response.x = ld.x+50;
-        response.y = ld.y-50;
-        response.width = 38;
-        response.type = 'other';
-
-        response.path = [
-            {x1: ld.x+10, y1: ld.y-10,
-             x2: ld.x+36, y2: ld.y-36}
-        ];
-    }
-};
-
-function previewCalls(dec) {
-    var prev = '';
-    for (var i = 0; i < dec.calls.length; i++) {
-        if (dec.calls[i].output != "wmtrace_not_exported")
-            prev += '<li>'+dec.calls[i].module+':'+dec.calls[i]['function']+'</li>';
-    }
-    return prev;
-};
-
-function drawTrace() {
-    drawDecision(trace[0]);
-    for (var i = 1; i < trace.length; i++) {
-        drawPath(trace[i].path);
-        drawDecision(trace[i]);
-    }
-
-    drawPath(response.path);
-    drawResponse();
-};
-
-function drawResponse() {
-    if (response.type == 'normal') {
-        var context = canvas.getContext('2d');
-        context.strokeStyle=HIGHLIGHT;
-        context.lineWidth=4;
-
-        context.beginPath();
-        context.rect(response.x-(response.width/2),
-                     response.y-19,
-                     response.width,
-                     38);
-        context.stroke();
-    } else {
-        var context = canvas.getContext('2d');
-        context.strokeStyle='#ff0000';
-        context.lineWidth=4;
-
-        context.beginPath();
-        context.arc(response.x, response.y, 19,
-                    0, 2*3.14159, false);
-        context.stroke();
-
-    }
-};
-
-function drawDecision(dec) {
-    var context = canvas.getContext('2d');
-
-    if (dec.previewCalls == '')
-        context.strokeStyle=REGULAR;
-    else
-        context.strokeStyle=HIGHLIGHT;
-    context.lineWidth=4;
-
-    context.beginPath();
-    context.moveTo(dec.x,    dec.y-19);
-    context.lineTo(dec.x+19, dec.y);
-    context.lineTo(dec.x,    dec.y+19);
-    context.lineTo(dec.x-19, dec.y);
-    context.closePath();
-    context.stroke();
-};
-
-function drawPath(path) {
-    var context = canvas.getContext('2d');
-    context.strokeStyle=REGULAR;
-    context.lineWidth=4;
-
-    context.beginPath();
-    context.moveTo(path[0].x1, path[0].y1);
-    for (var p = 0; p < path.length; p++) {
-        context.lineTo(path[p].x2, path[p].y2);
-    }
-    context.stroke();
-};
-
-function getSeg(p1, p2, last) {
-    var seg = {
-        x1:cols[p1[0]],
-        y1:rows[p1.slice(1)]
-    };
-    if (ends[p2]) {
-        seg.x2 = cols[ends[p2].col];
-        seg.y2 = rows[ends[p2].row];
-    } else {
-        seg.x2 = cols[p2[0]];
-        seg.y2 = rows[p2.slice(1)];
-    }
-
-    if (seg.x1 == seg.x2) {
-        if (seg.y1 < seg.y2) {
-            seg.y1 = seg.y1+19;
-            if (last) seg.y2 = seg.y2-19;
-        } else {
-            seg.y1 = seg.y1-19;
-            if (last) seg.y2 = seg.y2+19;
-        }
-    } else {
-        //assume seg.y1 == seg.y2
-        if (seg.x1 < seg.x2) {
-            seg.x1 = seg.x1+19;
-            if (last) seg.x2 = seg.x2-(ends[p2] ? (ends[p2].width/2) : 19);
-        } else {
-            seg.x1 = seg.x1-19;
-            if (last) seg.x2 = seg.x2+(ends[p2] ? (ends[p2].width/2) : 19);
-        }
-    }
-    return seg;
-};
-
-function traceDecision(name) {
-    for (var i = trace.length-1; i >= 0; i--)
-        if (trace[i].d == name) return trace[i];
-};
-
-var detailPanels = {};
-function initDetailPanels() {
-    var windowWidth = document.getElementById('sizetest').clientWidth;
-    var infoPanel = document.getElementById('infopanel');
-    var panelWidth = windowWidth-infoPanel.offsetLeft;
-
-    var panels = {
-        'request': document.getElementById('requestdetail'),
-        'response': document.getElementById('responsedetail'),
-        'decision': document.getElementById('decisiondetail')
-    };
-
-    var tabs = {
-        'request': document.getElementById('requesttab'),
-        'response': document.getElementById('responsetab'),
-        'decision': document.getElementById('decisiontab')
-    };