summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMagnus Feuer <mfeuer@jaguarlandrover.com>2015-04-14 08:26:38 -0700
committerMagnus Feuer <mfeuer@jaguarlandrover.com>2015-04-14 08:26:38 -0700
commit024ab61c898e782dc40e2269ad0f5a23009bcd6b (patch)
tree0fde33ef3c1dbdc54d15c91549a987fc1f47fbf9
parent7feddc0a5538037acda260ea4dd6b504989bc61c (diff)
downloadrvi_core-024ab61c898e782dc40e2269ad0f5a23009bcd6b.tar.gz
Initial commit of deps
-rw-r--r--deps/dthread/LICENSE24
-rw-r--r--deps/dthread/README.md11
-rw-r--r--deps/dthread/c_src/Makefile.cross40
-rw-r--r--deps/dthread/c_src/Makefile.mingw34
-rw-r--r--deps/dthread/c_src/dlib.c239
-rw-r--r--deps/dthread/c_src/dlog.c59
-rw-r--r--deps/dthread/c_src/dterm.c410
-rw-r--r--deps/dthread/c_src/dthread.c791
-rw-r--r--deps/dthread/c_src/dthread_drv.c358
-rw-r--r--deps/dthread/include/ddata.h519
-rw-r--r--deps/dthread/include/dlib.h69
-rw-r--r--deps/dthread/include/dlog.h57
-rw-r--r--deps/dthread/include/dterm.h269
-rw-r--r--deps/dthread/include/dthread.h174
-rw-r--r--deps/dthread/rebar.config28
-rw-r--r--deps/dthread/src/dthread.app.src8
-rw-r--r--deps/dthread/src/dthread.erl93
-rw-r--r--deps/edown/Makefile12
-rw-r--r--deps/edown/NOTICE10
-rw-r--r--deps/edown/README.md180
-rw-r--r--deps/edown/bin/MARKEDOC-README.md196
-rw-r--r--deps/edown/bin/markedoc.sed338
-rw-r--r--deps/edown/doc/README.md180
-rw-r--r--deps/edown/doc/edoc-info4
-rw-r--r--deps/edown/doc/edown_doclet.md140
-rw-r--r--deps/edown/doc/edown_layout.md141
-rw-r--r--deps/edown/doc/edown_lib.md45
-rw-r--r--deps/edown/doc/edown_make.md82
-rw-r--r--deps/edown/doc/edown_xmerl.md49
-rw-r--r--deps/edown/doc/overview.edoc159
-rwxr-xr-xdeps/edown/edown_make42
-rwxr-xr-xdeps/edown/make_doc49
-rw-r--r--deps/edown/priv/scripts/check_edown.script39
-rw-r--r--deps/edown/priv/scripts/rebar.config.script18
-rw-r--r--deps/edown/priv/scripts/remove_deps.script18
-rwxr-xr-xdeps/edown/rebarbin0 -> 134535 bytes
-rw-r--r--deps/edown/rebar.config16
-rw-r--r--deps/edown/samples/README.md1
-rw-r--r--deps/edown/samples/markedoc/SAMPLE1.md313
-rw-r--r--deps/edown/samples/markedoc/SAMPLE2.md50
-rw-r--r--deps/edown/samples/markedoc/SAMPLE3.md247
-rw-r--r--deps/edown/samples/markedoc/doc/erlang.pngbin0 -> 2109 bytes
-rw-r--r--deps/edown/samples/markedoc/doc/markedoc-footer.pngbin0 -> 4335 bytes
-rw-r--r--deps/edown/samples/markedoc/doc/markedoc.css202
-rw-r--r--deps/edown/samples/markedoc/doc/overview.edoc4
-rwxr-xr-xdeps/edown/samples/markedoc/make_samples.sh19
-rwxr-xr-xdeps/edown/samples/markedoc/test-bsd.sh23
-rwxr-xr-xdeps/edown/samples/markedoc/test-linux.sh23
-rwxr-xr-xdeps/edown/samples/markedoc/test-macosx.sh23
-rw-r--r--deps/edown/samples/markedoc/what-you-should-see/SAMPLE1.edoc346
-rw-r--r--deps/edown/samples/markedoc/what-you-should-see/SAMPLE2.edoc53
-rw-r--r--deps/edown/samples/markedoc/what-you-should-see/SAMPLE3.edoc280
-rw-r--r--deps/edown/samples/markedoc/what-you-should-see/erlang.pngbin0 -> 2109 bytes
-rw-r--r--deps/edown/samples/markedoc/what-you-should-see/markedoc-footer.pngbin0 -> 4335 bytes
-rw-r--r--deps/edown/samples/markedoc/what-you-should-see/markedoc.css202
-rw-r--r--deps/edown/samples/markedoc/what-you-should-see/sample1.html321
-rw-r--r--deps/edown/samples/markedoc/what-you-should-see/sample2.html68
-rw-r--r--deps/edown/samples/markedoc/what-you-should-see/sample3.html255
-rw-r--r--deps/edown/samples/markedoc/what-you-should-see/stylesheet.css55
-rw-r--r--deps/edown/samples/markedoc/your-test-results/erlang.pngbin0 -> 2109 bytes
-rw-r--r--deps/edown/samples/markedoc/your-test-results/markedoc-footer.pngbin0 -> 4335 bytes
-rw-r--r--deps/edown/samples/markedoc/your-test-results/markedoc.css202
-rw-r--r--deps/edown/samples/markedoc/your-test-results/stylesheet.css55
-rw-r--r--deps/edown/src/edown.app.src25
-rw-r--r--deps/edown/src/edown_doclet.erl601
-rw-r--r--deps/edown/src/edown_layout.erl1323
-rw-r--r--deps/edown/src/edown_lib.erl97
-rw-r--r--deps/edown/src/edown_make.erl64
-rw-r--r--deps/edown/src/edown_xmerl.erl285
-rw-r--r--deps/exo/LICENSE373
-rw-r--r--deps/exo/Makefile5
-rw-r--r--deps/exo/README4
-rw-r--r--deps/exo/include/exo_http.hrl87
-rw-r--r--deps/exo/include/exo_url.hrl29
-rw-r--r--deps/exo/priv/host.cert18
-rw-r--r--deps/exo/priv/host.key15
-rw-r--r--deps/exo/rebar.config5
-rw-r--r--deps/exo/src/exo.app.src12
-rw-r--r--deps/exo/src/exo_app.erl25
-rw-r--r--deps/exo/src/exo_http.erl1064
-rw-r--r--deps/exo/src/exo_http_server.erl291
-rw-r--r--deps/exo/src/exo_json.erl722
-rw-r--r--deps/exo/src/exo_socket.erl557
-rw-r--r--deps/exo/src/exo_socket.hrl37
-rw-r--r--deps/exo/src/exo_socket_cache.erl282
-rw-r--r--deps/exo/src/exo_socket_server.erl439
-rw-r--r--deps/exo/src/exo_socket_session.erl432
-rw-r--r--deps/exo/src/exo_ssh.erl181
-rw-r--r--deps/exo/src/exo_sup.erl37
-rw-r--r--deps/exo/src/exo_url.erl112
-rw-r--r--deps/exo/src/log.hrl44
-rw-r--r--deps/exo/tetrapak/config.ini8
-rw-r--r--deps/gsms/LICENSE24
-rw-r--r--deps/gsms/README.md32
-rw-r--r--deps/gsms/include/gsms.hrl144
-rw-r--r--deps/gsms/rebar.config9
-rw-r--r--deps/gsms/src/ERRORCODES30
-rw-r--r--deps/gsms/src/README141
-rw-r--r--deps/gsms/src/gsms.app.src9
-rw-r--r--deps/gsms/src/gsms.erl110
-rw-r--r--deps/gsms/src/gsms_0338.erl370
-rw-r--r--deps/gsms/src/gsms_0705.erl972
-rw-r--r--deps/gsms/src/gsms_app.erl83
-rw-r--r--deps/gsms/src/gsms_codec.erl1067
-rw-r--r--deps/gsms/src/gsms_if_sup.erl78
-rw-r--r--deps/gsms/src/gsms_router.erl464
-rw-r--r--deps/gsms/src/gsms_sup.erl68
-rw-r--r--deps/gsms/src/gsms_uart.erl834
-rw-r--r--deps/gsms/src/log.hrl44
-rwxr-xr-xdeps/gsms/src/sim90080
-rwxr-xr-xdeps/gsms/src/sim900_ft1075k75
-rw-r--r--deps/gsms/sys.config47
-rw-r--r--deps/gsms/tetrapak/config.ini13
-rw-r--r--deps/lager/LICENSE178
-rw-r--r--deps/lager/Makefile52
-rw-r--r--deps/lager/README.org222
-rw-r--r--deps/lager/TODO3
-rw-r--r--deps/lager/dialyzer.ignore-warnings0
-rw-r--r--deps/lager/include/lager.hrl100
-rw-r--r--deps/lager/include/log.hrl44
-rwxr-xr-xdeps/lager/rebarbin0 -> 108239 bytes
-rw-r--r--deps/lager/rebar.config4
-rw-r--r--deps/lager/src/error_logger_lager_h.erl281
-rw-r--r--deps/lager/src/lager.app.src41
-rw-r--r--deps/lager/src/lager.erl395
-rw-r--r--deps/lager/src/lager_app.erl77
-rw-r--r--deps/lager/src/lager_console_backend.erl273
-rw-r--r--deps/lager/src/lager_crash_log.erl339
-rw-r--r--deps/lager/src/lager_file_backend.erl392
-rw-r--r--deps/lager/src/lager_format.erl523
-rw-r--r--deps/lager/src/lager_handler_watcher.erl159
-rw-r--r--deps/lager/src/lager_handler_watcher_sup.erl39
-rw-r--r--deps/lager/src/lager_mochiglobal.erl107
-rw-r--r--deps/lager/src/lager_stdlib.erl507
-rw-r--r--deps/lager/src/lager_sup.erl80
-rw-r--r--deps/lager/src/lager_transform.erl264
-rw-r--r--deps/lager/src/lager_trunc_io.erl576
-rw-r--r--deps/lager/src/lager_util.erl471
-rw-r--r--deps/lager/test/crash.erl96
-rw-r--r--deps/lager/test/lager_crash_backend.erl68
-rw-r--r--deps/lager/test/lager_test_backend.erl735
-rw-r--r--deps/lager/test/special_process.erl28
-rw-r--r--deps/lager/test/sync_error_logger.erl89
-rw-r--r--deps/lager/test/trunc_io_eqc.erl208
-rw-r--r--deps/resource/Makefile18
-rw-r--r--deps/resource/README13
-rw-r--r--deps/resource/c_src/resource_nif.c155
-rw-r--r--deps/resource/rebar.config9
-rw-r--r--deps/resource/src/resource.app.src11
-rw-r--r--deps/resource/src/resource.erl29
-rw-r--r--deps/setup/LICENSE373
-rw-r--r--deps/setup/Makefile51
-rw-r--r--deps/setup/README.md109
-rw-r--r--deps/setup/doc/README.md109
-rw-r--r--deps/setup/doc/edoc-info4
-rw-r--r--deps/setup/doc/erlang.pngbin0 -> 2109 bytes
-rw-r--r--deps/setup/doc/overview.edoc88
-rw-r--r--deps/setup/doc/setup.md434
-rw-r--r--deps/setup/doc/setup_gen.md76
-rw-r--r--deps/setup/doc/setup_lib.md75
-rw-r--r--deps/setup/doc/stylesheet.css55
-rw-r--r--deps/setup/doc/template.conf63
-rw-r--r--deps/setup/examples/gproc.conf5
-rwxr-xr-xdeps/setup/rebarbin0 -> 168629 bytes
-rw-r--r--deps/setup/rebar.config14
-rw-r--r--deps/setup/rebar.config.script61
-rw-r--r--deps/setup/src/setup.app.src28
-rw-r--r--deps/setup/src/setup.erl1116
-rw-r--r--deps/setup/src/setup_gen.erl770
-rw-r--r--deps/setup/src/setup_lib.erl171
-rw-r--r--deps/setup/xtest/test.conf30
-rw-r--r--deps/setup/xtest/testapp-1/src/testapp.app.src14
-rw-r--r--deps/setup/xtest/testapp-1/src/testapp_app.erl16
-rw-r--r--deps/setup/xtest/testapp-1/src/testapp_sup.erl27
-rw-r--r--deps/setup/xtest/testapp-2/src/testapp.app.src14
-rw-r--r--deps/setup/xtest/testapp-2/src/testapp_app.erl16
-rw-r--r--deps/setup/xtest/testapp-2/src/testapp_sup.erl27
-rw-r--r--deps/uart/README.md80
-rw-r--r--deps/uart/c_src/Makefile.cross51
-rw-r--r--deps/uart/c_src/Makefile.mingw47
-rw-r--r--deps/uart/c_src/dosmap.c281
-rw-r--r--deps/uart/c_src/uart_api.h0
-rw-r--r--deps/uart/c_src/uart_buf.c363
-rw-r--r--deps/uart/c_src/uart_com_state.c56
-rw-r--r--deps/uart/c_src/uart_drv.c436
-rw-r--r--deps/uart/c_src/uart_drv.h386
-rw-r--r--deps/uart/c_src/uart_ftdi.c34
-rw-r--r--deps/uart/c_src/uart_message.c352
-rw-r--r--deps/uart/c_src/uart_modem_state.c36
-rw-r--r--deps/uart/c_src/uart_options.c273
-rw-r--r--deps/uart/c_src/uart_queue.c84
-rw-r--r--deps/uart/c_src/uart_unix.c1362
-rw-r--r--deps/uart/c_src/uart_win32.c1194
-rw-r--r--deps/uart/doc/README.md12
-rw-r--r--deps/uart/doc/uart.md407
-rw-r--r--deps/uart/priv/uart.yang174
-rw-r--r--deps/uart/rebar.config44
-rw-r--r--deps/uart/src/uart.app.src8
-rw-r--r--deps/uart/src/uart.erl958
-rw-r--r--deps/uart/src/uart_devices.erl575
-rw-r--r--deps/uart/src/uart_win32_devices.erl129
-rw-r--r--deps/uart/test/Makefile34
-rw-r--r--deps/uart/test/uart.cfg12
-rw-r--r--deps/uart/test/uart.spec7
-rw-r--r--deps/uart/test/uart_SUITE.erl562
-rw-r--r--deps/uart/test/uart_hw.spec6
-rw-r--r--deps/wse/LICENSE24
-rw-r--r--deps/wse/README.md88
-rw-r--r--deps/wse/priv/base64.js83
-rw-r--r--deps/wse/priv/bd.jpgbin0 -> 22732 bytes
-rw-r--r--deps/wse/priv/button_demo.html22
-rw-r--r--deps/wse/priv/document_demo.html21
-rw-r--r--deps/wse/priv/ej.js672
-rw-r--r--deps/wse/priv/fish.html19
-rw-r--r--deps/wse/priv/fish/fish0001.pngbin0 -> 8525 bytes
-rw-r--r--deps/wse/priv/fish/fish0002.pngbin0 -> 9342 bytes
-rw-r--r--deps/wse/priv/fish/fish0003.pngbin0 -> 6937 bytes
-rw-r--r--deps/wse/priv/fish/fish0004.pngbin0 -> 7584 bytes
-rw-r--r--deps/wse/priv/fish/fish0005.pngbin0 -> 9756 bytes
-rw-r--r--deps/wse/priv/fish/fish0006.pngbin0 -> 6421 bytes
-rw-r--r--deps/wse/priv/fish/fish0007.pngbin0 -> 7065 bytes
-rw-r--r--deps/wse/priv/fish/fish0008.pngbin0 -> 6157 bytes
-rw-r--r--deps/wse/priv/fish/fish0009.pngbin0 -> 2412 bytes
-rw-r--r--deps/wse/priv/fish/fish0010.pngbin0 -> 6136 bytes
-rw-r--r--deps/wse/priv/fish/fish0011.pngbin0 -> 6575 bytes
-rw-r--r--deps/wse/priv/fish/fish0012.pngbin0 -> 6088 bytes
-rw-r--r--deps/wse/priv/fish/fish0013.pngbin0 -> 10450 bytes
-rw-r--r--deps/wse/priv/fish/fish0014.pngbin0 -> 8429 bytes
-rw-r--r--deps/wse/priv/fish/fish0015.pngbin0 -> 6744 bytes
-rw-r--r--deps/wse/priv/fish/fish0016.pngbin0 -> 9061 bytes
-rw-r--r--deps/wse/priv/fish/fish0017.pngbin0 -> 8073 bytes
-rw-r--r--deps/wse/priv/fish/fish0018.pngbin0 -> 4662 bytes
-rw-r--r--deps/wse/priv/fish/fish0019.pngbin0 -> 3822 bytes
-rw-r--r--deps/wse/priv/fish/fish0020.pngbin0 -> 2083 bytes
-rw-r--r--deps/wse/priv/fish/fish0021.pngbin0 -> 2120 bytes
-rw-r--r--deps/wse/priv/fish/fish0022.pngbin0 -> 4025 bytes
-rw-r--r--deps/wse/priv/fish/fish0023.pngbin0 -> 5189 bytes
-rw-r--r--deps/wse/priv/frame1.html20
-rw-r--r--deps/wse/priv/frame2.html20
-rw-r--r--deps/wse/priv/frame_demo.html23
-rw-r--r--deps/wse/priv/location.html30
-rw-r--r--deps/wse/priv/locationX.html9
-rw-r--r--deps/wse/priv/page_a.html20
-rw-r--r--deps/wse/priv/page_b.html20
-rw-r--r--deps/wse/priv/raphael-min.js11
-rw-r--r--deps/wse/priv/raphael.html21
-rw-r--r--deps/wse/priv/table_demo.html21
-rw-r--r--deps/wse/priv/utf8.js73
-rw-r--r--deps/wse/priv/wse.js725
-rw-r--r--deps/wse/priv/wse_demo.html13
-rw-r--r--deps/wse/rebar.config2
-rw-r--r--deps/wse/src/raphael.erl106
-rw-r--r--deps/wse/src/wse.app.src7
-rw-r--r--deps/wse/src/wse.erl453
-rw-r--r--deps/wse/src/wse_button_demo.erl50
-rw-r--r--deps/wse/src/wse_demo.erl24
-rw-r--r--deps/wse/src/wse_document_demo.erl71
-rw-r--r--deps/wse/src/wse_fish_demo.erl54
-rw-r--r--deps/wse/src/wse_frame_demo.erl137
-rw-r--r--deps/wse/src/wse_location_demo.erl46
-rw-r--r--deps/wse/src/wse_raphael_demo.erl73
-rw-r--r--deps/wse/src/wse_server.erl696
-rw-r--r--deps/wse/src/wse_table_demo.erl109
-rw-r--r--packaging/README.md37
264 files changed, 41606 insertions, 0 deletions
diff --git a/deps/dthread/LICENSE b/deps/dthread/LICENSE
new file mode 100644
index 0000000..68ed063
--- /dev/null
+++ b/deps/dthread/LICENSE
@@ -0,0 +1,24 @@
+Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Except as contained in this notice, the name(s) of the above copyright holders
+shall not be used in advertising or otherwise to promote the sale, use or other
+dealings in this Software without prior written authorization.
+
diff --git a/deps/dthread/README.md b/deps/dthread/README.md
new file mode 100644
index 0000000..e64c8b4
--- /dev/null
+++ b/deps/dthread/README.md
@@ -0,0 +1,11 @@
+
+
+#The dthread application#
+
+
+##Modules##
+
+
+<table width="100%" border="0" summary="list of modules">
+<tr><td><a href="http://github.com/tonyrog/uart/blob/master/doc/dthread.md" class="module">dthread</a></td></tr></table>
+
diff --git a/deps/dthread/c_src/Makefile.cross b/deps/dthread/c_src/Makefile.cross
new file mode 100644
index 0000000..0e929eb
--- /dev/null
+++ b/deps/dthread/c_src/Makefile.cross
@@ -0,0 +1,40 @@
+#
+# Make the dthread files
+# example usage: TARGET=arm-none-linux-gnueabi make -f Makefile.cross
+#
+
+OSNAME := $(shell uname -s)
+MACHINE := $(shell uname -m)
+ERLDIR := $(shell erl -noshell -eval "io:format([126,115,126,110],[code:root_dir()])" -s erlang halt)
+ERL_C_INCLUDE_DIR := $(ERLDIR)/usr/include
+CC = $(TARGET)-gcc
+LD = $(TARGET)-ld
+PRIVDIR=../priv/$(TARGET)
+OBJDIR=$(TARGET)
+
+OBJS = \
+ $(OBJDIR)/dlib.o \
+ $(OBJDIR)/dterm.o \
+ $(OBJDIR)/dthread.o \
+ $(OBJDIR)/dthread_drv.o
+
+LDFLAGS = -shared -fpic
+
+all : $(OBJDIR) $(PRIVDIR) $(OBJS) $(PRIVDIR)/dthread_drv.so
+
+override CFLAGS += -Wall -Wextra -Wswitch-default -Wswitch-enum -I$(ERL_C_INCLUDE_DIR) -DDLOG_DEFAULT=DLOG_NONE
+
+$(OBJDIR):
+ @mkdir -p $(OBJDIR)
+
+$(PRIVDIR):
+ @mkdir -p $(PRIVDIR)
+
+$(PRIVDIR)/dthread_drv.so : $(OBJS)
+ $(CC) $(LDFLAGS) -o $@ $^
+
+$(OBJDIR)/%.o: %.c
+ $(CC) $(CFLAGS) -c -o $@ $<
+
+lean:
+ $(RM) -f $(PRIVDIR)/dthread_drv.so $(OBJS)
diff --git a/deps/dthread/c_src/Makefile.mingw b/deps/dthread/c_src/Makefile.mingw
new file mode 100644
index 0000000..cf71a1d
--- /dev/null
+++ b/deps/dthread/c_src/Makefile.mingw
@@ -0,0 +1,34 @@
+#
+# Makefile for Mingw
+#
+OSNAME := $(shell uname -s)
+MACHINE := $(shell uname -m)
+ERLDIR := $(shell erl -noshell -eval "io:format([126,115,126,110],[code:root_dir()])" -s erlang halt)
+ERL_C_INCLUDE_DIR := $(ERLDIR)/usr/include
+CC = gcc
+PRIVDIR=../priv
+
+CFLAGS += -D__WIN32__ -DWIN32
+CFLAGS += -Wall -Wextra -Wswitch-default -Wswitch-enum -D_THREAD_SAFE -D_REENTRANT -fno-common
+CFLAGS += -I$(ERL_C_INCLUDE_DIR)
+LDFLAGS += -Wl--enable-stdcall-fixup
+
+EXT = dll
+LD_SHARED := $(CC) -shared
+
+debug: CFLAGS += -g
+debug: LDFLAGS += -g
+
+
+DTHREAD_DRV = $(PRIVDIR)/dthread_drv.$(EXT)
+
+DTHREAD_DRV_OBJS = dlib.obj dterm.obj dthread.obj dthread_drv.obj
+
+debug release all: $(DTHREAD_DRV)
+
+
+$(DTHREAD_DRV): $(DTHREAD_DRV_OBJS)
+ $(LD_SHARED) -o $@ $(DTHREAD_DRV_OBJS) $(LDFLAGS)
+
+%.obj: %.c
+ $(CC) -c -o $@ $(CFLAGS) $< \ No newline at end of file
diff --git a/deps/dthread/c_src/dlib.c b/deps/dthread/c_src/dlib.c
new file mode 100644
index 0000000..38abba7
--- /dev/null
+++ b/deps/dthread/c_src/dlib.c
@@ -0,0 +1,239 @@
+/****** BEGIN COPYRIGHT *******************************************************
+ *
+ * Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+ *
+ * This software is licensed as described in the file COPYRIGHT, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://www.rogvall.se/docs/copyright.txt.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYRIGHT file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****** END COPYRIGHT ********************************************************/
+#include <stddef.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <memory.h>
+#include <errno.h>
+
+#include "../include/dlog.h"
+#include "../include/dlib.h"
+
+static size_t dlib_num_allocated = 0;
+static size_t dlib_tot_allocated = 0;
+
+#ifndef NO_ERL_DRIVER
+static ErlDrvMutex* dlib_mtx; // USE ME
+#endif
+
+#if defined(NO_ERL_DRIVER)
+#define MEM_ALLOC(sz) malloc((sz))
+#define MEM_ZALLOC(sz) zalloc((sz))
+#define MEM_FREE(ptr) free((ptr))
+#define MEM_REALLOC(ptr,sz) realloc((ptr),(sz))
+#define MEM_ZERO(ptr,sz) memset((ptr),'\0',(sz))
+#else
+#define MEM_ALLOC(sz) driver_alloc((sz))
+#define MEM_ZALLOC(sz) zalloc((sz))
+#define MEM_FREE(ptr) driver_free((ptr))
+#define MEM_REALLOC(ptr,sz) driver_realloc((ptr),(sz))
+#define MEM_ZERO(ptr,sz) memset((ptr),'\0',(sz))
+#endif
+
+#define MARK 0x5A5A5A5A
+#define FREE 0x0
+#define MAGIC 0xCAFEFEED
+
+typedef struct _dheader_t {
+ uint32_t mark;
+ uint32_t magic;
+ size_t sz;
+ char* file;
+ int line;
+ double pad;
+ char data[0];
+} dheader_t;
+
+void dlib_init()
+{
+ dlib_num_allocated = 0;
+ dlib_tot_allocated = 0;
+#ifndef NO_ERL_DRIVER
+ dlib_mtx = erl_drv_mutex_create("dlib_mtx");
+#endif
+}
+
+void dlib_finish()
+{
+#ifndef NO_ERL_DRIVER
+ erl_drv_mutex_destroy(dlib_mtx);
+#endif
+}
+
+// return (known) number of allocated memory
+size_t dlib_allocated(void)
+{
+ return dlib_num_allocated;
+}
+
+// return the the sum of all memory allocations so far
+size_t dlib_total_allocated(void)
+{
+ return dlib_tot_allocated;
+}
+
+void dlib_break_here()
+{
+ exit(1);
+}
+
+// allocate a memory block
+// store | mark | magic | sz | file | line | .... |
+void* dlib_alloc(size_t sz, char* file, int line)
+{
+ dheader_t* dptr;
+ if ((dptr = MEM_ALLOC(sizeof(dheader_t)+sz)) == NULL) {
+ dlog_emit_error(DLOG_ALERT, file, line, "allocation failed");
+ return NULL;
+ }
+ else {
+ dptr->mark = MARK;
+ dptr->magic = MAGIC;
+ dptr->sz = sz;
+ dptr->file = file;
+ dptr->line = line;
+ dlib_num_allocated += sz;
+ dlib_tot_allocated += sz;
+ return (void*) &dptr->data[0];
+ }
+}
+
+void* dlib_zalloc(size_t sz, char* file, int line)
+{
+ dheader_t* dptr;
+ if ((dptr = MEM_ALLOC(sizeof(dheader_t)+sz)) == NULL) {
+ dlog_emit_error(DLOG_ALERT, file, line, "allocation failed");
+ return NULL;
+ }
+ else {
+ dptr->mark = MARK;
+ dptr->magic = MAGIC;
+ dptr->sz = sz;
+ dptr->file = file;
+ dptr->line = line;
+ memset(&dptr->data[0], '\0', sz);
+ dlib_num_allocated += sz;
+ dlib_tot_allocated += sz;
+ return (void*) &dptr->data[0];
+ }
+}
+
+void dlib_free(void* ptr, char* file, int line)
+{
+ if (ptr) {
+ dheader_t* dptr = (dheader_t*) ((char*)ptr - sizeof(dheader_t));
+ if ((dptr->mark == MARK) && (dptr->magic == MAGIC)) {
+ if (dptr->sz > dlib_num_allocated) {
+ dlog_emit_error(DLOG_EMERGENCY, file, line,
+ "free more data than allocated");
+ dlib_break_here();
+ }
+ dptr->mark = FREE;
+ dlib_num_allocated -= dptr->sz;
+ MEM_FREE(dptr);
+ }
+ else if (dptr->magic == MAGIC) {
+ dlog_emit_error(DLOG_EMERGENCY, file, line,
+ "block %p already free, allocated by %s:%d",
+ dptr, dptr->file, dptr->line);
+ dlib_break_here();
+ }
+ else {
+ dlog_emit_error(DLOG_EMERGENCY, file, line,
+ "block %p mark=%x, magic=%x, not dallocated",
+ dptr, dptr->mark, dptr->magic);
+ dlib_break_here();
+ }
+ }
+}
+
+void* dlib_realloc(void* ptr, size_t sz, char* file, int line)
+{
+ if (ptr) {
+ dheader_t* dptr = (dheader_t*) ((char*)ptr - sizeof(dheader_t));
+ ptr = (void*) dptr;
+ if ((dptr->mark == MARK) && (dptr->magic == MAGIC)) {
+ dptr->mark = FREE; // mark potential old segment as FREE
+ if (dptr->sz > dlib_num_allocated) {
+ dlog_emit_error(DLOG_EMERGENCY, file, line,
+ "realloc release more data than allocated");
+ dlib_break_here();
+ }
+ dlib_num_allocated -= dptr->sz;
+ }
+ else if (dptr->magic == MAGIC) {
+ dlog_emit_error(DLOG_EMERGENCY, file, line,
+ "block is free, allocated by %s:%d",
+ dptr->file, dptr->line);
+ dlib_break_here();
+ }
+ else {
+ dlog_emit_error(DLOG_EMERGENCY, file, line,
+ "block %p mark=%x, magic=%x, not dallocated",
+ dptr, dptr->mark, dptr->magic);
+ dlib_break_here();
+ }
+ }
+ if ((ptr = MEM_REALLOC(ptr, sizeof(dheader_t)+sz)) == NULL) {
+ dlog_emit_error(DLOG_ALERT, file, line, "reallocation failed");
+ return ptr;
+ }
+ else {
+ dheader_t* dptr = (dheader_t*) ptr;
+ dptr->mark = MARK;
+ dptr->magic = MAGIC;
+ dptr->sz = sz;
+ // set last alloaction/reallocation position
+ dptr->file = file;
+ dptr->line = line;
+ dlib_num_allocated += sz;
+ dlib_tot_allocated += sz;
+ return (void*) &dptr->data[0];
+ }
+}
+
+void dlib_zero(void* ptr, size_t sz, char* file, int line)
+{
+ if (ptr) {
+ dheader_t* dptr = (dheader_t*) ((char*)ptr - sizeof(dheader_t));
+ if ((dptr->mark == MARK) && (dptr->magic == MAGIC)) {
+ if (sz > dptr->sz) {
+ dlog_emit_error(DLOG_EMERGENCY, file, line, "overwrite heap");
+ dlib_break_here();
+ }
+ memset(&dptr->data[0], '\0', sz);
+ }
+ else if (dptr->magic == MAGIC) {
+ dlog_emit_error(DLOG_EMERGENCY, file, line,
+ "zero free block, allocated by %s:%d",
+ dptr->file, dptr->line);
+ dlib_break_here();
+ }
+ else {
+ dlog_emit_error(DLOG_EMERGENCY, file, line,
+ "block %p mark=%x, magic=%x, not dallocated",
+ dptr, dptr->mark, dptr->magic);
+ dlib_break_here();
+ }
+ }
+ else {
+ dlog_emit_error(DLOG_EMERGENCY, file, line,
+ "null pointer");
+ dlib_break_here();
+ }
+}
diff --git a/deps/dthread/c_src/dlog.c b/deps/dthread/c_src/dlog.c
new file mode 100644
index 0000000..6c21f30
--- /dev/null
+++ b/deps/dthread/c_src/dlog.c
@@ -0,0 +1,59 @@
+/****** BEGIN COPYRIGHT *******************************************************
+ *
+ * Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+ *
+ * This software is licensed as described in the file COPYRIGHT, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://www.rogvall.se/docs/copyright.txt.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYRIGHT file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****** END COPYRIGHT ********************************************************/
+#include <stddef.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <memory.h>
+#include <errno.h>
+
+#include "../include/dlog.h"
+
+int dlog_debug_level = DLOG_NONE;
+
+void dlog_init()
+{
+ dlog_debug_level = DLOG_NONE;
+}
+
+void dlog_finish()
+{
+}
+
+
+void dlog_set_debug(int level)
+{
+ dlog_debug_level = level;
+}
+
+void dlog_emit_error(int level, char* file, int line, ...)
+{
+ va_list ap;
+ char* fmt;
+
+ if ((level == DLOG_EMERGENCY) ||
+ ((dlog_debug_level >= 0) && (level <= dlog_debug_level))) {
+ int save_errno = errno;
+ va_start(ap, line);
+ fmt = va_arg(ap, char*);
+ fprintf(stderr, "%s:%d: ", file, line);
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\r\n");
+ va_end(ap);
+ errno = save_errno;
+ }
+}
diff --git a/deps/dthread/c_src/dterm.c b/deps/dthread/c_src/dterm.c
new file mode 100644
index 0000000..3832738
--- /dev/null
+++ b/deps/dthread/c_src/dterm.c
@@ -0,0 +1,410 @@
+/****** BEGIN COPYRIGHT *******************************************************
+ *
+ * Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+ *
+ * This software is licensed as described in the file COPYRIGHT, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://www.rogvall.se/docs/copyright.txt.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYRIGHT file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****** END COPYRIGHT ********************************************************/
+#include <stddef.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <memory.h>
+#include <inttypes.h>
+#include "../include/dterm.h"
+
+static ErlDrvTermData am_true;
+static ErlDrvTermData am_false;
+
+
+/******************************************************************************
+ *
+ * Term (dterm_t)
+ *
+ *****************************************************************************/
+
+void dterm_lib_init()
+{
+ dlib_init();
+ am_true = driver_mk_atom("true");
+ am_false = driver_mk_atom("false");
+}
+
+void dterm_lib_finish()
+{
+ dlib_finish();
+}
+
+void dterm_init(dterm_t* p)
+{
+ p->dyn_alloc = 0;
+ p->dyn_size = DTERM_FIXED;
+ p->base = p->data;
+ p->ptr = p->data;
+ p->ptr_end = p->data + DTERM_FIXED;
+ p->head = 0;
+ p->mark = 0;
+}
+
+int dterm_dump(FILE* f, ErlDrvTermData* spec, int len)
+{
+ int i = 0;
+ while(i < len) {
+ switch(spec[i]) {
+ case ERL_DRV_NIL:
+ fprintf(f, "%d: NIL\r\n", i);
+ i += 1;
+ break;
+ case ERL_DRV_INT:
+ fprintf(f, "%d: INT %d\r\n", i, (int)spec[i+1]);
+ i += 2;
+ break;
+ case ERL_DRV_UINT:
+ fprintf(f, "%d: UINT %u\r\n", i, (unsigned)spec[i+1]);
+ i += 2;
+ break;
+ case ERL_DRV_ATOM:
+ fprintf(f, "%d: ATOM %u\r\n", i, (unsigned)spec[i+1]);
+ i += 2;
+ break;
+ case ERL_DRV_PORT:
+ fprintf(f, "%d: PORT %u\r\n", i, (unsigned)spec[i+1]);
+ i += 2;
+ break;
+ case ERL_DRV_PID:
+ fprintf(f, "%d: PID %u\r\n", i, (unsigned)spec[i+1]);
+ i += 2;
+ break;
+ case ERL_DRV_TUPLE:
+ fprintf(f, "%d: TUPLE %u\r\n", i,(unsigned)spec[i+1]);
+ i += 2;
+ break;
+ case ERL_DRV_LIST:
+ fprintf(f, "%d: LIST %u\r\n", i, (unsigned)spec[i+1]);
+ i += 2;
+ break;
+ case ERL_DRV_INT64:
+ fprintf(f, "%d: INT64 %" PRId64 "\r\n", i,
+ *(int64_t*)spec[i+1]);
+ i += 2;
+ break;
+ case ERL_DRV_UINT64:
+ fprintf(f, "%d: UINT64 %" PRIu64 "\r\n", i,
+ *(uint64_t*)spec[i+1]);
+ i += 2;
+ break;
+ case ERL_DRV_FLOAT:
+ fprintf(f, "%d: FLOAT %f\r\n", i,
+ *(double*)spec[i+1]);
+ i += 2;
+ break;
+ case ERL_DRV_STRING:
+ fprintf(f, "%d: STRING %d \"%s\"\r\n", i,
+ (int)spec[i+2], (char*)spec[i+1]);
+ i += 3;
+ break;
+ case ERL_DRV_STRING_CONS:
+ fprintf(f, "%d: STRING_CONS %d \"%s\"\r\n", i,
+ (int)spec[i+2], (char*)spec[i+1]);
+ i += 3;
+ break;
+ case ERL_DRV_BUF2BINARY:
+ fprintf(f, "%d: BUF2BINARY_CONS %d <<%s>>\r\n", i,
+ (int)spec[i+2], (char*)spec[i+1]);
+ i += 3;
+ break;
+ case ERL_DRV_BINARY:
+ fprintf(f, "%d: BINARY\r\n", i);
+ // not yet!!!
+ i += 4;
+ return -1;
+ default:
+ return -1;
+ }
+ }
+ return (int) 0;
+}
+
+
+// Calculate the size of pointer portion
+// When sending a message in the non SMP buffer and data are copied
+int dterm_dyn_size(ErlDrvTermData* spec, int len)
+{
+ size_t n = 0;
+ int i = 0;
+
+ while(i < len) {
+ switch(spec[i]) {
+ case ERL_DRV_NIL:
+ i++;
+ break;
+ case ERL_DRV_INT:
+ case ERL_DRV_UINT:
+ case ERL_DRV_ATOM:
+ case ERL_DRV_PORT:
+ case ERL_DRV_PID:
+ case ERL_DRV_TUPLE:
+ case ERL_DRV_LIST:
+ i += 2;
+ break;
+ case ERL_DRV_INT64:
+ n += sizeof(ErlDrvSInt64);
+ i += 2;
+ break;
+ case ERL_DRV_UINT64:
+ n += sizeof(ErlDrvUInt64);
+ i += 2;
+ break;
+ case ERL_DRV_FLOAT:
+ n += sizeof(double);
+ i += 2;
+ break;
+ case ERL_DRV_STRING:
+ n += spec[i+2];
+ i += 3;
+ break;
+ case ERL_DRV_STRING_CONS:
+ n += spec[i+2];
+ i += 3;
+ break;
+ case ERL_DRV_BUF2BINARY:
+ n += spec[i+2];
+ i += 3;
+ break;
+ case ERL_DRV_BINARY:
+ // not yet!!!
+ n += spec[i+2];
+ i += 4;
+ return -1;
+ break;
+ default:
+ return -1;
+ }
+ }
+ return (int) n;
+}
+
+// When sending a message in the non SMP buffer and data are copied
+char* dterm_dyn_copy(ErlDrvTermData* spec, int len, char* ptr)
+{
+ int i = 0;
+ while(i < len) {
+ switch(spec[i]) {
+ case ERL_DRV_NIL:
+ i++;
+ break;
+ case ERL_DRV_INT:
+ case ERL_DRV_UINT:
+ case ERL_DRV_ATOM:
+ case ERL_DRV_PORT:
+ case ERL_DRV_PID:
+ case ERL_DRV_TUPLE:
+ case ERL_DRV_LIST:
+ i += 2;
+ break;
+ case ERL_DRV_INT64:
+ memcpy(ptr, (void*)spec[i+1], sizeof(ErlDrvSInt64));
+ spec[i+1] = (ErlDrvTermData) ptr;
+ ptr += sizeof(ErlDrvSInt64);
+ i += 2;
+ break;
+ case ERL_DRV_UINT64:
+ memcpy(ptr, (void*)spec[i+1], sizeof(ErlDrvUInt64));
+ spec[i+1] = (ErlDrvTermData) ptr;
+ ptr += sizeof(ErlDrvUInt64);
+ i += 2;
+ break;
+ case ERL_DRV_FLOAT:
+ memcpy(ptr, (void*)spec[i+1], sizeof(double));
+ spec[i+1] = (ErlDrvTermData) ptr;
+ ptr += sizeof(double);
+ i += 2;
+ break;
+ case ERL_DRV_STRING:
+ memcpy(ptr, (void*)spec[i+1], spec[i+2]);
+ spec[i+1] = (ErlDrvTermData) ptr;
+ ptr += spec[i+2];
+ i += 3;
+ break;
+ case ERL_DRV_STRING_CONS:
+ memcpy(ptr, (void*)spec[i+1], spec[i+2]);
+ spec[i+1] = (ErlDrvTermData) ptr;
+ ptr += spec[i+2];
+ i += 3;
+ break;
+ case ERL_DRV_BUF2BINARY:
+ memcpy(ptr, (void*)spec[i+1], spec[i+2]);
+ spec[i+1] = (ErlDrvTermData) ptr;
+ ptr += spec[i+2];
+ i += 3;
+ break;
+ case ERL_DRV_BINARY:
+ // wont work yet! (maybe change to BUF2BINARY)
+ ptr += spec[i+2];
+ i += 4;
+ return NULL;
+ break;
+ default:
+ return NULL;
+ }
+ }
+ return ptr;
+}
+
+
+
+// dynamic allocation of dterm_t structure, the data part is
+// can be less the DTERM_FIXED in this case
+dterm_t* dterm_alloc(size_t size)
+{
+ size_t sz = (sizeof(dterm_t) - DTERM_FIXED*sizeof(ErlDrvTermData)) +
+ size*sizeof(ErlDrvTermData);
+ dterm_t* p;
+
+ if ((p = DALLOC(sz)) != NULL) {
+ p->dyn_alloc = 1;
+ p->dyn_size = size;
+ p->base = p->data;
+ p->ptr = p->data;
+ p->ptr_end = p->data + p->dyn_size;
+ p->head = 0;
+ p->mark = 0;
+ }
+ return p;
+}
+
+void dterm_reset_links(dterm_t* p)
+{
+ if (p->head) {
+ dterm_link_t* lp = p->head;
+ while(lp) {
+ dterm_link_t* nlp = lp->next;
+ DFREE(lp);
+ lp = nlp;
+ }
+ p->head = NULL;
+ }
+}
+
+void dterm_finish(dterm_t* p)
+{
+ if (p->base != p->data)
+ DFREE(p->base);
+ dterm_reset_links(p);
+}
+
+void dterm_free(dterm_t* p)
+{
+ dterm_finish(p);
+ if (p->dyn_alloc)
+ DFREE(p);
+}
+
+// reset base pointer & clear link space
+void dterm_reset(dterm_t* p)
+{
+ p->ptr = p->base; // restart allocation
+ dterm_reset_links(p);
+}
+
+int dterm_expand(dterm_t* p, size_t n)
+{
+ ErlDrvTermData* new_base;
+ size_t old_size = dterm_allocated_size(p);
+ size_t new_size = old_size + n;
+ size_t old_sz = old_size * sizeof(ErlDrvTermData);
+ size_t new_sz = new_size * sizeof(ErlDrvTermData);
+ ptrdiff_t offset = p->ptr - p->base; // offset of ptr
+
+ if (p->base == p->data) {
+ if ((new_base = DALLOC(new_sz)) == NULL)
+ return 0;
+ memcpy(new_base, p->base, old_sz);
+ }
+ else if ((new_base = DREALLOC(p->base, new_sz)) == NULL)
+ return 0;
+ p->base = new_base;
+ p->ptr = p->base + offset;
+ p->ptr_end = new_base + new_size;
+ p->base = new_base;
+ return 1;
+}
+
+// auxillary space
+void* dterm_link_alloc_data(dterm_t* p, size_t size)
+{
+ dterm_link_t* lp = DALLOC(sizeof(dterm_link_t)+size);
+ lp->next = p->head;
+ p->head = lp;
+ return (void*) &lp->data[0];
+}
+
+// auxillary space
+void* dterm_link_copy_data(dterm_t* p, void* src, size_t size)
+{
+ void* dst = dterm_link_alloc_data(p, size);
+ memcpy(dst, src, size);
+ return dst;
+}
+
+
+void dterm_kv_int(dterm_t* t,ErlDrvTermData key, ErlDrvSInt value)
+{
+ dterm_mark_t m;
+ dterm_tuple_begin(t, &m); {
+ dterm_atom(t, key);
+ dterm_int(t, value);
+ }
+ dterm_tuple_end(t, &m);
+}
+
+void dterm_kv_uint(dterm_t* t,ErlDrvTermData key, ErlDrvUInt value)
+{
+ dterm_mark_t m;
+ dterm_tuple_begin(t, &m); {
+ dterm_atom(t, key);
+ dterm_uint(t, value);
+ }
+ dterm_tuple_end(t, &m);
+}
+
+void dterm_kv_atom(dterm_t* t,ErlDrvTermData key, ErlDrvTermData value)
+{
+ dterm_mark_t m;
+ dterm_tuple_begin(t, &m); {
+ dterm_atom(t, key);
+ dterm_atom(t, value);
+ }
+ dterm_tuple_end(t, &m);
+}
+
+void dterm_kv_bool(dterm_t* t,ErlDrvTermData key, int value)
+{
+ dterm_mark_t m;
+ dterm_tuple_begin(t, &m); {
+ dterm_atom(t, key);
+ dterm_atom(t, value ? am_true : am_false);
+ }
+ dterm_tuple_end(t, &m);
+}
+
+void dterm_kv_string(dterm_t* t,ErlDrvTermData key, char* value)
+{
+ dterm_mark_t m;
+ size_t len = strlen(value);
+ char* dst = (char*) dterm_link_copy_data(t, value, len);
+
+ dterm_tuple_begin(t, &m); {
+ dterm_atom(t, key);
+ dterm_string(t, dst, len);
+ }
+ dterm_tuple_end(t, &m);
+}
diff --git a/deps/dthread/c_src/dthread.c b/deps/dthread/c_src/dthread.c
new file mode 100644
index 0000000..3fc5676
--- /dev/null
+++ b/deps/dthread/c_src/dthread.c
@@ -0,0 +1,791 @@
+/****** BEGIN COPYRIGHT *******************************************************
+ *
+ * Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+ *
+ * This software is licensed as described in the file COPYRIGHT, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://www.rogvall.se/docs/copyright.txt.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYRIGHT file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****** END COPYRIGHT ********************************************************/
+/*
+ * Reusable? API to handle commands from an
+ * Erlang drivers in a thread. This thread can be used for
+ * blocking operations towards operating system, replies go
+ * either through port to process or straight to calling process depending
+ * on SMP is supported or not.
+ */
+
+#include "../include/dthread.h"
+#include "../include/dlog.h"
+
+
+#ifdef __WIN32__
+#include <windows.h>
+#else
+#include <unistd.h>
+#include <sys/socket.h>
+#endif
+
+#include <stddef.h>
+#include <stdio.h>
+#include <ctype.h>
+
+static ErlDrvTermData am_data;
+static ErlDrvTermData am_ok;
+static ErlDrvTermData am_error;
+
+void dthread_lib_init()
+{
+ dterm_lib_init();
+ am_data = driver_mk_atom("data");
+ am_ok = driver_mk_atom("ok");
+ am_error = driver_mk_atom("error");
+}
+
+void dthread_lib_finish()
+{
+ dterm_lib_finish();
+}
+
+/******************************************************************************
+ *
+ * Messages
+ *
+ *****************************************************************************/
+
+dmessage_t* dmessage_alloc(size_t n)
+{
+ size_t sz = sizeof(dmessage_t) + n;
+ dmessage_t* mp = DZALLOC(sz);
+ if (mp) {
+ mp->buffer = mp->data;
+ mp->used = 0;
+ mp->size = n;
+ }
+ return mp;
+}
+
+void dmessage_free(dmessage_t* mp)
+{
+ if (mp->release)
+ (*mp->release)(mp);
+ if ((mp->buffer < mp->data) || (mp->buffer > mp->data+mp->size))
+ DFREE(mp->buffer);
+ DFREE(mp);
+}
+
+// create a message with optional dynamic buffer
+dmessage_t* dmessage_create_r(int cmd,
+ void (*release)(dmessage_t*),
+ void* udata,
+ char* buf, size_t len)
+{
+ dmessage_t* mp;
+ if (release) {
+ if ((mp = dmessage_alloc(0))) {
+ mp->cmd = cmd;
+ mp->udata = udata;
+ mp->release = release;
+ mp->buffer = buf;
+ mp->size = len;
+ mp->used = len;
+ }
+ }
+ else {
+ if ((mp = dmessage_alloc(len+8))) {
+ mp->cmd = cmd;
+ mp->udata = udata;
+ mp->buffer += 8;
+ memcpy(mp->buffer, buf, len);
+ mp->used = len;
+ }
+ }
+ return mp;
+}
+
+// simple version of dmessage_create_r
+dmessage_t* dmessage_create(int cmd,char* buf, size_t len)
+{
+ return dmessage_create_r(cmd, NULL, NULL, buf, len);
+}
+
+/******************************************************************************
+ *
+ * Threads
+ *
+ *****************************************************************************/
+
+extern void dthread_event_close(ErlDrvEvent event)
+{
+ INFOF("event_close: %d", DTHREAD_EVENT(event));
+ DTHREAD_CLOSE_EVENT(event);
+}
+
+void dthread_signal_init(dthread_t* thr)
+{
+ thr->iq_signal[0] = (ErlDrvEvent) DTHREAD_INVALID_EVENT;
+ thr->iq_signal[1] = (ErlDrvEvent) DTHREAD_INVALID_EVENT;
+}
+
+void dthread_signal_select(dthread_t* thr, int on)
+{
+ DEBUGF("dthread_signal_select: fd=%d",
+ DTHREAD_EVENT(thr->iq_signal[0]));
+#ifdef __WIN32__
+ driver_select(thr->port,thr->iq_signal[0],ERL_DRV_READ,on);
+#else
+ driver_select(thr->port,thr->iq_signal[0],ERL_DRV_READ,on);
+#endif
+}
+
+void dthread_signal_use(dthread_t* thr, int on)
+{
+#ifdef __WIN32__
+ driver_select(thr->port,thr->iq_signal[0],ERL_DRV_USE,on);
+#else
+ driver_select(thr->port,thr->iq_signal[1],ERL_DRV_USE,on);
+ driver_select(thr->port,thr->iq_signal[0],ERL_DRV_USE,on);
+#endif
+}
+
+
+int dthread_signal_set(dthread_t* thr)
+{
+#ifdef __WIN32__
+ DEBUGF("dthread_signal_set: handle=%d", DTHREAD_EVENT(thr->iq_signal[0]));
+ SetEvent(DTHREAD_EVENT(thr->iq_signal[0]));
+ return 1;
+#else
+ DEBUGF("dthread_signal_set: fd=%d", DTHREAD_EVENT(thr->iq_signal[1]));
+ return write(DTHREAD_EVENT(thr->iq_signal[1]), "!", 1);
+#endif
+}
+
+// consume wakeup token
+int dthread_signal_reset(dthread_t* thr)
+{
+#ifdef __WIN32__
+ DEBUGF("dthread_signal_reset: handle=%d", DTHREAD_EVENT(thr->iq_signal[0]));
+ ResetEvent(DTHREAD_EVENT(thr->iq_signal[0]));
+ return 0;
+#else
+ {
+ char buf[1];
+ DEBUGF("dthread_signal_reset: fd=%d", DTHREAD_EVENT(thr->iq_signal[0]));
+ return read(DTHREAD_EVENT(thr->iq_signal[0]), buf, 1);
+ }
+#endif
+}
+
+void dthread_signal_finish(dthread_t* thr, int and_close)
+{
+ if (thr->iq_signal[0] != (ErlDrvEvent)DTHREAD_INVALID_EVENT) {
+ if (and_close) {
+ DEBUGF("dthread_signal_finish: close iq_signal[0]=%d",
+ DTHREAD_EVENT(thr->iq_signal[0]));
+ DTHREAD_CLOSE_EVENT(thr->iq_signal[0]);
+ }
+ thr->iq_signal[0] = (ErlDrvEvent)DTHREAD_INVALID_EVENT;
+ }
+
+ if (thr->iq_signal[1] != (ErlDrvEvent)DTHREAD_INVALID_EVENT) {
+ if (and_close) {
+ DEBUGF("dthread_signal_finish: iq_signal[1]=%d",
+ DTHREAD_EVENT(thr->iq_signal[1]));
+ DTHREAD_CLOSE_EVENT(thr->iq_signal[1]);
+ }
+ thr->iq_signal[1] = (ErlDrvEvent)DTHREAD_INVALID_EVENT;
+ }
+}
+
+
+// Cleanup the mess
+void dthread_finish(dthread_t* thr)
+{
+ dmessage_t* mp;
+
+ if (thr->iq_mtx) {
+ erl_drv_mutex_destroy(thr->iq_mtx);
+ thr->iq_mtx = NULL;
+ }
+ mp = thr->iq_front;
+ while(mp) {
+ dmessage_t* tmp = mp->next;
+ dmessage_free(mp);
+ mp = tmp;
+ }
+ thr->iq_front = thr->iq_rear = NULL;
+ dthread_signal_finish(thr, 0);
+}
+
+// Initialize thread structure
+int dthread_init(dthread_t* thr, ErlDrvPort port)
+{
+ ErlDrvSysInfo sys_info;
+
+ memset(thr, 0, sizeof(dthread_t));
+ dthread_signal_init(thr);
+ driver_system_info(&sys_info, sizeof(ErlDrvSysInfo));
+ // smp_support is used for message passing from thread to
+ // calling process. if SMP is supported the message will go
+ // directly to sender, otherwise it must be sent to port
+ thr->smp_support = sys_info.smp_support;
+ thr->port = port;
+ thr->dport = driver_mk_port(port);
+ thr->owner = driver_connected(port);
+
+ if (!(thr->iq_mtx = erl_drv_mutex_create("iq_mtx")))
+ return -1;
+#ifdef __WIN32__
+ // create a manual reset event
+ if (!(thr->iq_signal[0] = (ErlDrvEvent)
+ CreateEvent(NULL, TRUE, FALSE, NULL))) {
+ dthread_finish(thr);
+ return -1;
+ }
+ DEBUGF("dthread_init: handle=%d", DTHREAD_EVENT(thr->iq_signal[0]));
+#else
+ {
+ int pfd[2];
+ if (pipe(pfd) < 0) {
+ dthread_finish(thr);
+ return -1;
+ }
+ DEBUGF("dthread_init: pipe[0]=%d,pidp[1]=%d", pfd[0], pfd[1]);
+ thr->iq_signal[0] = (ErlDrvEvent) ((long)pfd[0]);
+ thr->iq_signal[1] = (ErlDrvEvent) ((long)pfd[1]);
+ INFOF("pipe: %d,%d", pfd[0], pfd[1]);
+ }
+#endif
+ return 0;
+}
+
+dthread_t* dthread_start(ErlDrvPort port,
+ void* (*func)(void* arg),
+ void* arg, int stack_size)
+{
+ ErlDrvThreadOpts* opts = NULL;
+ dthread_t* thr = NULL;
+
+ if (!(thr = DALLOC(sizeof(dthread_t))))
+ return 0;
+
+ if (dthread_init(thr, port) < 0)
+ goto error;
+
+ if (!(opts = erl_drv_thread_opts_create("dthread_opts")))
+ goto error;
+
+ opts->suggested_stack_size = stack_size;
+ thr->arg = arg;
+
+ if (erl_drv_thread_create("dthread", &thr->tid, func, thr, opts) < 0)
+ goto error;
+ erl_drv_thread_opts_destroy(opts);
+ return thr;
+
+error:
+ dthread_finish(thr);
+ if (opts)
+ erl_drv_thread_opts_destroy(opts);
+ dthread_finish(thr);
+ DFREE(thr);
+ return 0;
+}
+
+int dthread_stop(dthread_t* target, dthread_t* source,
+ void** exit_value)
+{
+ dmessage_t* mp;
+ int r;
+
+ if (!(mp = dmessage_create(DTHREAD_STOP, NULL, 0)))
+ return -1;
+ dthread_send(target, source, mp);
+
+ DEBUGF("dthread_stop: wait to join");
+ r = erl_drv_thread_join(target->tid, exit_value);
+ DEBUGF("dthread_stop: thread_join: return=%d, exit_value=%p", r, *exit_value);
+
+ dthread_signal_finish(target, 1);
+ dthread_finish(target);
+ DFREE(target);
+ return 0;
+}
+
+void dthread_exit(void* value)
+{
+ erl_drv_thread_exit(value);
+}
+
+//
+// Poll dthread queue and optionally other INPUT! events given by events
+// number of input events are given in *nevents and number of ready
+// events are also outputed there.
+//
+// return -1 on error
+// 0 timeout | no messages in queue (maybe in in nevents)
+// n number of messages ready to be read (0 also means timeout!)
+//
+#ifdef __WIN32__
+
+int dthread_poll(dthread_t* thr, dthread_poll_event_t* events, size_t* nevents,
+ int timeout)
+{
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS];
+ int eindex[MAXIMUM_WAIT_OBJECTS];
+ DWORD nCount = 0;
+ DWORD iq_len = 0;
+ DWORD nready = 0;
+ DWORD dwMilliseconds;
+ DWORD res;
+ int i,n;
+
+ if (timeout < 0)
+ dwMilliseconds = INFINITE;
+ else
+ dwMilliseconds = (DWORD) timeout;
+
+ // install handles to wait for
+ if (DTHREAD_EVENT(thr->iq_signal[0]) != DTHREAD_INVALID_EVENT) {
+ eindex[nCount] = -1; // -1 == signal queue event
+ handles[nCount] = DTHREAD_EVENT(thr->iq_signal[0]);
+ nCount++;
+ }
+
+ if (events && nevents && *nevents) {
+ n = (DWORD) (*nevents);
+ for (i = 0; (nCount < MAXIMUM_WAIT_OBJECTS) && (i < n); i++) {
+ events[i].revents = 0; // clear here in case of timeout etc
+ if (events[i].events) {
+ eindex[nCount] = i; // index in event array
+ handles[nCount] = DTHREAD_EVENT(events[i].event);
+ nCount++;
+ }
+ }
+ }
+
+ DEBUGF("WaitForMultipleObjects nCount=%d, timeout=%d", nCount,
+ dwMilliseconds);
+ // wait for first event that is signaled
+ res = WaitForMultipleObjects(nCount, handles, FALSE, dwMilliseconds);
+ DEBUGF("WaitForMultipleObjects result=%d", res);
+
+ if (res == WAIT_TIMEOUT)
+ return 0;
+ else if (res == WAIT_FAILED)
+ return -1;
+ else if ((res >= WAIT_OBJECT_0) && (res < (WAIT_OBJECT_0+nCount))) {
+ DWORD j = res - WAIT_OBJECT_0;
+
+ if ((i = eindex[j]) < 0) {
+ erl_drv_mutex_lock(thr->iq_mtx);
+ iq_len = thr->iq_len;
+ erl_drv_mutex_unlock(thr->iq_mtx);
+ }
+ else if (events != NULL) {
+ events[i].revents |= ERL_DRV_READ; // event is ready
+ nready++;
+ }
+ j++;
+
+ // must scan rest of the events as well, else starvation may occure
+ while (j < nCount) {
+ if (WaitForSingleObject(handles[j], 0) == WAIT_OBJECT_0) {
+ if ((i = eindex[j]) < 0) {
+ erl_drv_mutex_lock(thr->iq_mtx);
+ iq_len = thr->iq_len;
+ erl_drv_mutex_unlock(thr->iq_mtx);
+ }
+ else if (events != NULL) {
+ events[i].revents |= ERL_DRV_READ; // event is ready
+ nready++;
+ }
+ }
+ j++;
+ }
+ }
+ if (nevents)
+ *nevents = nready;
+ return iq_len;
+}
+#else
+
+int dthread_poll(dthread_t* thr, dthread_poll_event_t* events, size_t* nevents,
+ int timeout)
+{
+ struct timeval tm;
+ struct timeval* tp;
+ fd_set readfds;
+ fd_set writefds;
+ fd_set errorfds;
+ int fd,nfds = 0;
+ int ready;
+ int i,n,iq_len=0;
+
+ if (timeout < 0)
+ tp = NULL;
+ else {
+ tm.tv_sec = timeout / 1000;
+ tm.tv_usec = (timeout - tm.tv_sec*1000) * 1000;
+ tp = &tm;
+ }
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+ FD_ZERO(&errorfds);
+
+ if ((fd = DTHREAD_EVENT(thr->iq_signal[0])) >= 0) {
+ FD_SET(fd, &readfds);
+ FD_SET(fd, &errorfds);
+ DEBUGF("FD_SET: iq_signal[0] = %d", fd);
+ if (fd > nfds) nfds = fd;
+ }
+
+ if (events && nevents && *nevents) {
+ n = (int) (*nevents);
+ for (i = 0; i < n; i++) {
+ events[i].revents = 0; // clear here in case of timeout etc
+ if (events[i].events) {
+ fd = DTHREAD_EVENT(events[i].event);
+ if (events[i].events & ERL_DRV_READ) {
+ FD_SET(fd, &readfds);
+ FD_SET(fd, &errorfds);
+ }
+ if (events[i].events & ERL_DRV_WRITE)
+ FD_SET(fd, &writefds);
+ if (fd > nfds) nfds = fd;
+ }
+ }
+ }
+
+ DEBUGF("select nfds=%d, tp=%p", nfds, tp);
+ ready = select(nfds+1, &readfds, &writefds, &errorfds, tp);
+ DEBUGF("select result r=%d", ready);
+ if (ready <= 0) {
+ if (nevents)
+ *nevents = 0;
+ return ready;
+ }
+
+ // check queue !
+ fd = DTHREAD_EVENT(thr->iq_signal[0]);
+ if (FD_ISSET(fd, &readfds)) {
+ erl_drv_mutex_lock(thr->iq_mtx);
+ iq_len = thr->iq_len;
+ erl_drv_mutex_unlock(thr->iq_mtx);
+ ready--;
+ }
+
+ // check io events
+ if (ready && events && nevents && *nevents) {
+ size_t nready = 0;
+ n = (int) (*nevents);
+ for (i = 0; ready && (i < n); i++) {
+ size_t fd_ready = 0;
+ fd = DTHREAD_EVENT(events[i].event);
+ if (FD_ISSET(fd, &readfds) || FD_ISSET(fd, &errorfds)) {
+ events[i].revents |= ERL_DRV_READ;
+ if (FD_ISSET(fd, &errorfds))
+ events[i].revents |= ERL_DRV_EXCEP;
+ fd_ready = 1;
+ }
+ if (FD_ISSET(fd, &writefds)) {
+ events[i].revents |= ERL_DRV_WRITE;
+ fd_ready = 1;
+ }
+ nready += fd_ready;
+ ready--;
+ }
+ *nevents = nready;
+ }
+ return iq_len;
+}
+#endif
+
+
+int dthread_send(dthread_t* thr, dthread_t* source, dmessage_t* mp)
+{
+ dmessage_t* mr;
+ int len;
+ int r = 0;
+
+ erl_drv_mutex_lock(thr->iq_mtx);
+
+ mp->next = NULL;
+ mp->source = source;
+
+ if ((mr = thr->iq_rear) != NULL)
+ mr->next = mp;
+ else
+ thr->iq_front = mp;
+ thr->iq_rear = mp;
+ len = ++thr->iq_len;
+ if (len == 1)
+ r = dthread_signal_set(thr);
+ erl_drv_mutex_unlock(thr->iq_mtx);
+ DEBUGF("dthread_send: iq_len=%d", len);
+ return r;
+}
+
+dmessage_t* dthread_recv(dthread_t* thr, dthread_t** source)
+{
+ dmessage_t* mp;
+
+ erl_drv_mutex_lock(thr->iq_mtx);
+ if ((mp = thr->iq_front) != NULL) {
+ if (!(thr->iq_front = mp->next))
+ thr->iq_rear = NULL;
+ thr->iq_len--;
+ if (thr->iq_len == 0)
+ dthread_signal_reset(thr);
+ }
+ erl_drv_mutex_unlock(thr->iq_mtx);
+
+ if (mp && source)
+ *source = mp->source;
+ return mp;
+}
+
+
+int dthread_control(dthread_t* thr, dthread_t* source,
+ int cmd, char* buf, int len)
+{
+ dmessage_t* mp;
+
+ if (!(mp = dmessage_create(cmd, buf, len)))
+ return -1;
+ mp->from = source->caller;
+ mp->ref = ++source->ref;
+ return dthread_send(thr, source, mp);
+}
+
+int dthread_output(dthread_t* thr, dthread_t* source,
+ char* buf, int len)
+{
+ return dthread_control(thr, source, DTHREAD_OUTPUT, buf, len);
+}
+
+static void release_xptr_func(dmessage_t* mp)
+{
+ DEBUGF("release_xptr_func called");
+ DFREE(mp->udata);
+}
+
+int dthread_port_send_term(dthread_t* thr, dthread_t* source,
+ ErlDrvTermData target,
+ ErlDrvTermData* spec, int len)
+{
+ if (thr->smp_support)
+ return DSEND_TERM(thr, target, spec, len);
+ else {
+ dmessage_t* mp;
+ int xsz = dterm_dyn_size(spec, len);
+ if (xsz < 0)
+ return -1;
+ mp = dmessage_create(DTHREAD_SEND_TERM,(char*)spec,
+ len*sizeof(ErlDrvTermData));
+ if (xsz > 0) {
+ char* xptr = DALLOC(xsz);
+ if ((dterm_dyn_copy((ErlDrvTermData*)mp->buffer,
+ len, xptr)) == NULL)
+ return -1;
+ mp->udata = xptr;
+ mp->release = release_xptr_func;
+ }
+ mp->to = target;
+ // dterm_dump(stderr, (ErlDrvTermData*) mp->buffer, mp->used / sizeof(ErlDrvTermData));
+ return dthread_send(thr, source, mp);
+ }
+}
+
+int dthread_port_output_term(dthread_t* thr, dthread_t* source,
+ ErlDrvTermData* spec, int len)
+{
+ return dthread_port_send_term(thr, source, thr->owner, spec, len);
+}
+
+int dthread_port_send_dterm(dthread_t* thr, dthread_t* source,
+ ErlDrvTermData target, dterm_t* p)
+{
+ return dthread_port_send_term(thr, source, target,
+ dterm_data(p),
+ dterm_used_size(p));
+}
+
+int dthread_port_output_dterm(dthread_t* thr, dthread_t* source, dterm_t* p)
+{
+ return dthread_port_send_dterm(thr, source, thr->owner, p);
+}
+
+// send {Ref, ok}
+int dthread_port_send_ok(dthread_t* thr, dthread_t* source,
+ ErlDrvTermData target, ErlDrvTermData ref)
+{
+ dterm_t t;
+ dterm_mark_t m;
+ int r;
+
+ dterm_init(&t);
+ dterm_tuple_begin(&t, &m); {
+ dterm_int(&t, ref);
+ dterm_atom(&t, am_ok);
+ }
+ dterm_tuple_end(&t, &m);
+
+ r = dthread_port_send_term(thr, source, target,
+ dterm_data(&t), dterm_used_size(&t));
+ dterm_finish(&t);
+ return r;
+}
+
+
+static ErlDrvTermData error_atom(int err)
+{
+ char errstr[256];
+ char* s;
+ char* t;
+
+ for (s = erl_errno_id(err), t = errstr; *s; s++, t++)
+ *t = tolower(*s);
+ *t = '\0';
+ return driver_mk_atom(errstr);
+}
+
+// send {Ref, {error,Reason}}
+int dthread_port_send_error(dthread_t* thr, dthread_t* source,
+ ErlDrvTermData target,
+ ErlDrvTermData ref, int error)
+{
+ dterm_t t;
+ dterm_mark_t m,e;
+ int r;
+
+ dterm_init(&t);
+ dterm_tuple_begin(&t, &m); {
+ dterm_int(&t, ref);
+ dterm_tuple_begin(&t, &e); {
+ dterm_atom(&t, am_error);
+ dterm_atom(&t, error_atom(error));
+ }
+ dterm_tuple_end(&t,&e);
+ }
+ dterm_tuple_end(&t,&m);
+
+ r = dthread_port_send_term(thr, source, target,
+ dterm_data(&t), dterm_used_size(&t));
+ dterm_finish(&t);
+ return r;
+}
+
+//
+// Generate output that looks like port data
+//
+int dthread_port_output(dthread_t* thr, dthread_t* source,
+ char* buf, int len)
+{
+ // generate {Port, {data, Data}}
+ ErlDrvTermData spec[11];
+
+ spec[0] = ERL_DRV_PORT;
+ spec[1] = thr->dport;
+ spec[2] = ERL_DRV_ATOM;
+ spec[3] = am_data;
+ spec[4] = ERL_DRV_STRING; // ERL_DRV_BUF2BINARY;
+ spec[5] = (ErlDrvTermData) buf;
+ spec[6] = (ErlDrvTermData) len;
+ spec[7] = ERL_DRV_TUPLE;
+ spec[8] = 2;
+ spec[9] = ERL_DRV_TUPLE;
+ spec[10] = 2;
+
+ return dthread_port_send_term(thr, source, thr->owner, spec, 11);
+}
+
+//
+// Generate output that looks like port data
+//
+int dthread_port_output2(dthread_t* thr, dthread_t* source,
+ char* hbuf, int hlen,
+ char* buf, int len)
+
+{
+ // generate {Port, {data, Data}}
+ int i = 0;
+ ErlDrvTermData spec[16];
+
+ spec[i++] = ERL_DRV_PORT;
+ spec[i++] = thr->dport;
+ spec[i++] = ERL_DRV_ATOM;
+ spec[i++] = am_data;
+ if (len == 0) {
+ spec[i++] = ERL_DRV_STRING; // ERL_DRV_BUF2BINARY;
+ spec[i++] = (ErlDrvTermData) hbuf;
+ spec[i++] = (ErlDrvTermData) hlen;
+ }
+ else {
+ spec[i++] = ERL_DRV_STRING; // ERL_DRV_BUF2BINARY;
+ spec[i++] = (ErlDrvTermData) buf;
+ spec[i++] = (ErlDrvTermData) len;
+ if (hlen) {
+ spec[i++] = ERL_DRV_STRING_CONS;
+ spec[i++] = (ErlDrvTermData) hbuf;
+ spec[i++] = (ErlDrvTermData) hlen;
+ }
+ }
+ spec[i++] = ERL_DRV_TUPLE;
+ spec[i++] = 2;
+ spec[i++] = ERL_DRV_TUPLE;
+ spec[i++] = 2;
+
+ return dthread_port_send_term(thr, source, thr->owner, spec, i);
+}
+
+//
+// Generate output that looks like port data
+//
+int dthread_port_output_binary(dthread_t* thr, dthread_t* source,
+ char* hbuf, ErlDrvSizeT hlen,
+ ErlDrvBinary* bin, ErlDrvSizeT offset,
+ ErlDrvSizeT len)
+{
+ // generate {Port, {data, Data}}
+ int i = 0;
+ ErlDrvTermData spec[16];
+ char* buf = bin->orig_bytes + offset;
+
+ spec[i++] = ERL_DRV_PORT;
+ spec[i++] = thr->dport;
+ spec[i++] = ERL_DRV_ATOM;
+ spec[i++] = am_data;
+ if (len == 0) {
+ spec[i++] = ERL_DRV_STRING;
+ spec[i++] = (ErlDrvTermData) hbuf;
+ spec[i++] = (ErlDrvTermData) hlen;
+ }
+ else {
+ // FIXME select binary|list here!
+ // ERL_DRV_STRING | ERL_DRV_BUF2BINARY;
+ spec[i++] = ERL_DRV_STRING;
+ spec[i++] = (ErlDrvTermData) buf;
+ spec[i++] = (ErlDrvTermData) len;
+ if (hlen) {
+ spec[i++] = ERL_DRV_STRING_CONS;
+ spec[i++] = (ErlDrvTermData) hbuf;
+ spec[i++] = (ErlDrvTermData) hlen;
+ }
+ }
+ spec[i++] = ERL_DRV_TUPLE;
+ spec[i++] = 2;
+ spec[i++] = ERL_DRV_TUPLE;
+ spec[i++] = 2;
+ return dthread_port_send_term(thr, source, thr->owner, spec, i);
+}
+
+
diff --git a/deps/dthread/c_src/dthread_drv.c b/deps/dthread/c_src/dthread_drv.c
new file mode 100644
index 0000000..bffd367
--- /dev/null
+++ b/deps/dthread/c_src/dthread_drv.c
@@ -0,0 +1,358 @@
+/****** BEGIN COPYRIGHT *******************************************************
+ *
+ * Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+ *
+ * This software is licensed as described in the file COPYRIGHT, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://www.rogvall.se/docs/copyright.txt.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYRIGHT file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****** END COPYRIGHT ********************************************************/
+//
+// Test driver using dthread library
+//
+
+#include <stdio.h>
+
+#include "erl_driver.h"
+#include "../include/dlog.h"
+#include "../include/dthread.h"
+
+#include <ctype.h>
+#include <stdint.h>
+#include <memory.h>
+
+
+#define DTHREAD_OK 0
+#define DTHREAD_ERROR 1
+
+typedef struct _drv_ctx_t
+{
+ dthread_t self; // me
+ dthread_t* other; // the thread
+} drv_ctx_t;
+
+ErlDrvEntry dthread_drv_entry;
+
+#ifdef DEBUG
+#include <stdarg.h>
+
+void emit_error(int level, char* file, int line, ...)
+{
+ va_list ap;
+ char* fmt;
+
+ va_start(ap, line);
+ fmt = va_arg(ap, char*);
+
+ fprintf(stderr, "%s:%d: ", file, line);
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\r\n");
+ va_end(ap);
+}
+#endif
+
+/* general control reply function */
+static ErlDrvSSizeT ctl_reply(int rep, char* buf, ErlDrvSizeT len,
+ char** rbuf, ErlDrvSizeT rsize)
+{
+ char* ptr;
+
+ if ((len+1) > rsize) {
+ ptr = driver_alloc(len+1);
+ *rbuf = ptr;
+ }
+ else
+ ptr = *rbuf;
+ *ptr++ = rep;
+ memcpy(ptr, buf, len);
+ return len+1;
+}
+
+//
+// Main thread function
+//
+void* dthread_dispatch(void* arg)
+{
+ dthread_t* self = (dthread_t*) arg;
+ dterm_t tsender;
+
+ DEBUGF("dthread_drv: dthread_dispatch started");
+
+ dterm_init(&tsender);
+
+ while(1) {
+ int r;
+
+ r = dthread_poll(self, NULL, NULL, -1);
+ if (r < 0) {
+ DEBUGF("dthread_drv: dthread_dispatch select failed=%d", r);
+ continue;
+ }
+ else if (r == 0) {
+ DEBUGF("dthread_drv: dthread_dispatch timeout");
+ continue;
+ }
+ else {
+ dmessage_t* mp;
+
+ DEBUGF("dthread_drv: dthread_dispatch r=%d", r);
+ if ((mp = dthread_recv(self, NULL)) == NULL) {
+ DEBUGF("dthread_drv: message was NULL");
+ continue;
+ }
+
+ switch(mp->cmd) {
+ case DTHREAD_STOP:
+ DEBUGF("dthread_drv: dthread_dispatch STOP");
+ dmessage_free(mp);
+ dterm_finish(&tsender);
+ dthread_exit(0);
+ break;
+
+ case DTHREAD_OUTPUT:
+ DEBUGF("dthread_drv: dthread_dispatch OUTPUT");
+ break;
+
+ case 1:
+ DEBUGF("dthread_drv: dthread_dispatch cmd=1");
+ dthread_port_output(mp->source, self, "HELLO WORLD", 11);
+ break;
+
+ case 2: {
+ DEBUGF("dthread_drv: dthread_dispatch cmd=2");
+ dterm_put2(&tsender, ERL_DRV_PORT, self->dport);
+ dterm_put2(&tsender, ERL_DRV_ATOM, driver_mk_atom("data"));
+ dterm_put3(&tsender, ERL_DRV_STRING,
+ (ErlDrvTermData) "NEW WORLD", (ErlDrvTermData) 9);
+ dterm_put2(&tsender, ERL_DRV_TUPLE, 2);
+ dterm_put2(&tsender, ERL_DRV_TUPLE, 2);
+
+ dthread_port_send_dterm(mp->source, self, mp->from, &tsender);
+ dterm_reset(&tsender);
+ break;
+ }
+
+ case 3: {
+ DEBUGF("dthread_drv: dthread_dispatch cmd=3");
+ dterm_put2(&tsender, ERL_DRV_ATOM, driver_mk_atom("x"));
+ dterm_put2(&tsender, ERL_DRV_ATOM, driver_mk_atom("y"));
+ dterm_put2(&tsender, ERL_DRV_ATOM, driver_mk_atom("z"));
+ dterm_put2(&tsender, ERL_DRV_TUPLE, 3);
+ dthread_port_output_dterm(mp->source, self, &tsender);
+ dterm_reset(&tsender);
+ break;
+ }
+
+ case 100: {
+ DEBUGF("dthread_dispatch cmd=100");
+ if (mp->used == 4) {
+ uint8_t* ptr = (uint8_t*)mp->buffer;
+ uint32_t value = (ptr[0]<<24) | (ptr[1]<<16) |
+ (ptr[2]<<8) | (ptr[3]<<0);
+
+ // usleep(10); unix only
+ dterm_put2(&tsender, ERL_DRV_UINT, mp->ref);
+ dterm_put2(&tsender, ERL_DRV_UINT, value + 1);
+ dterm_put2(&tsender, ERL_DRV_TUPLE, 2);
+
+ dthread_port_send_dterm(mp->source,self,mp->from,&tsender);
+ dterm_reset(&tsender);
+ }
+ break;
+ }
+
+ default:
+ DEBUGF("dthread_drv: dthread_dispatch cmd=%d", mp->cmd);
+ break;
+ }
+ dmessage_free(mp);
+ }
+ }
+}
+
+
+// setup global object area
+// load atoms etc.
+
+static int dthread_drv_init(void)
+{
+ DEBUGF("dthread_drv: driver init");
+ dthread_lib_init();
+ return 0;
+}
+
+// clean up global settings
+static void dthread_drv_finish(void)
+{
+ DEBUGF("dthread_drv: finish");
+ dthread_lib_finish();
+}
+
+static ErlDrvData dthread_drv_start(ErlDrvPort port, char* command)
+{
+ (void) command;
+ drv_ctx_t* ctx;
+
+ DEBUGF("dthread_drv: start");
+
+ ctx = DALLOC(sizeof(drv_ctx_t));
+
+ dthread_init(&ctx->self, port);
+
+ ctx->other = dthread_start(port, dthread_dispatch, ctx, 4096);
+
+ dthread_signal_use(&ctx->self, 1);
+ dthread_signal_select(&ctx->self, 1);
+
+ set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
+
+ return (ErlDrvData) ctx;
+}
+
+
+static void dthread_drv_stop(ErlDrvData d)
+{
+ drv_ctx_t* ctx = (drv_ctx_t*) d;
+ void* value;
+
+ DEBUGF("dthread_drv: stop");
+
+ dthread_stop(ctx->other, &ctx->self, &value);
+
+ dthread_signal_use(&ctx->self, 0);
+
+ dthread_finish(&ctx->self);
+ DFREE(ctx);
+}
+
+static ErlDrvSSizeT dthread_drv_control(ErlDrvData d, unsigned int cmd,
+ char* buf, ErlDrvSizeT len,
+ char** rbuf, ErlDrvSizeT rsize)
+{
+ drv_ctx_t* ctx = (drv_ctx_t*) d;
+ char ref_buf[sizeof(uint32_t)];
+ uint32_t r;
+
+ DEBUGF("dthread_drv: ctl: cmd=%u, len=%d", cmd, len);
+
+ ctx->self.caller = driver_caller(ctx->self.port);
+ dthread_control(ctx->other, &ctx->self, cmd, buf, len);
+
+ r = (uint32_t) ctx->self.ref;
+ ref_buf[0] = r >> 24;
+ ref_buf[1] = r >> 16;
+ ref_buf[2] = r >> 8;
+ ref_buf[3] = r;
+ return ctl_reply(DTHREAD_OK, ref_buf, sizeof(ref_buf), rbuf, rsize);
+}
+
+static void dthread_drv_output(ErlDrvData d, char* buf, ErlDrvSizeT len)
+{
+ drv_ctx_t* ctx = (drv_ctx_t*) d;
+
+ DEBUGF("dthread_drv: output");
+
+ ctx->self.caller = driver_caller(ctx->self.port);
+ dthread_output(ctx->other, &ctx->self, buf, len);
+}
+
+static void dthread_drv_timeout(ErlDrvData d)
+{
+ (void) d;
+ // drv_ctx_t* ctx = (drv_ctx_t*) d;
+ DEBUGF("dthread_drv: output");
+}
+
+static void dthread_drv_ready_input(ErlDrvData d, ErlDrvEvent e)
+{
+ drv_ctx_t* ctx = (drv_ctx_t*) d;
+
+ if (ctx->self.iq_signal[0] == e) { // got input !
+ dmessage_t* mp;
+
+ DEBUGF("dthread_drv: ready_input handle=%d",
+ DTHREAD_EVENT(ctx->self.iq_signal[0]));
+
+ if (!(mp = dthread_recv(&ctx->self, NULL))) {
+ DEBUGF("dthread_drv: ready_input signaled with no event! handle=%d",
+ DTHREAD_EVENT(ctx->self.iq_signal[0]));
+ return;
+ }
+
+ switch(mp->cmd) {
+ case DTHREAD_OUTPUT_TERM:
+ DEBUGF("dthread_drv: ready_input (OUTPUT_TERM)");
+ DOUTPUT_TERM(&ctx->self, (ErlDrvTermData*) mp->buffer,
+ mp->used / sizeof(ErlDrvTermData));
+ break;
+ case DTHREAD_SEND_TERM:
+ DEBUGF("dthread_drv: ready_input (SEND_TERM)");
+ DSEND_TERM(&ctx->self, mp->to, /* orignal from ! */
+ (ErlDrvTermData*) mp->buffer,
+ mp->used / sizeof(ErlDrvTermData));
+ break;
+ case DTHREAD_OUTPUT:
+ DEBUGF("dthread_drv: ready_input (OUTPUT)");
+ driver_output(ctx->self.port, mp->buffer, mp->used);
+ break;
+ default:
+ DEBUGF("dthread_drv: read_input cmd=%d not matched",
+ mp->cmd);
+ break;
+ }
+ dmessage_free(mp);
+ }
+ else {
+ DEBUGF("dthread_drv: ready_input (NO MATCH)");
+ }
+}
+
+static void dthread_drv_ready_output(ErlDrvData d, ErlDrvEvent e)
+{
+ (void) d;
+ (void) e;
+ DEBUGF("dthread_drv: read_output");
+}
+
+static void dthread_drv_stop_select(ErlDrvEvent event, void* arg)
+{
+ (void) arg;
+ DEBUGF("dthread_drv: stop_select");
+ DEBUGF("dthread_drv: close event=%d", DTHREAD_EVENT(event));
+ dthread_event_close(event);
+}
+
+
+DRIVER_INIT(dthread_drv)
+{
+ ErlDrvEntry* ptr = &dthread_drv_entry;
+
+ DEBUGF("DRIVER_INIT");
+
+ memset(ptr, 0, sizeof(ErlDrvEntry));
+
+ ptr->init = dthread_drv_init;
+ ptr->start = dthread_drv_start;
+ ptr->stop = dthread_drv_stop;
+ ptr->output = dthread_drv_output;
+ ptr->ready_input = dthread_drv_ready_input;
+ ptr->ready_output = dthread_drv_ready_output;
+ ptr->finish = dthread_drv_finish;
+ ptr->driver_name = "dthread_drv";
+ ptr->control = dthread_drv_control;
+ ptr->timeout = dthread_drv_timeout;
+ ptr->extended_marker = ERL_DRV_EXTENDED_MARKER;
+ ptr->major_version = ERL_DRV_EXTENDED_MAJOR_VERSION;
+ ptr->minor_version = ERL_DRV_EXTENDED_MINOR_VERSION;
+ ptr->driver_flags = ERL_DRV_FLAG_USE_PORT_LOCKING;
+ ptr->process_exit = 0;
+ ptr->stop_select = dthread_drv_stop_select;
+ return ptr;
+}
diff --git a/deps/dthread/include/ddata.h b/deps/dthread/include/ddata.h
new file mode 100644
index 0000000..fbe77cf
--- /dev/null
+++ b/deps/dthread/include/ddata.h
@@ -0,0 +1,519 @@
+/****** BEGIN COPYRIGHT *******************************************************
+ *
+ * Copyright (C) 2007 - 2014, Rogvall Invest AB, <tony@rogvall.se>
+ *
+ * This software is licensed as described in the file COPYRIGHT, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://www.rogvall.se/docs/copyright.txt.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYRIGHT file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****** END COPYRIGHT ********************************************************/
+
+#ifndef __DDATA_H__
+#define __DDATA_H__
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "dlib.h"
+
+typedef struct _ddata_t
+{
+ int dyn_alloc;
+ uint8_t* base; /* base pointer */
+ uint8_t* rd; /* read pointer */
+ uint8_t* wr; /* write pointer */
+ uint8_t* eob; /* end of buffer (+1) */
+ uint8_t buf[1]; /* used in some dynamic cases */
+} ddata_t;
+
+static void ddata_init(ddata_t* data, uint8_t* buf, uint32_t len,
+ int dynamic) __attribute__((unused));
+static void ddata_r_init(ddata_t* data, uint8_t* buf, uint32_t len,
+ int dynamic) __attribute__((unused));
+static void ddata_reset(ddata_t* data) __attribute__((unused));
+
+static ddata_t* ddata_new(uint8_t* buf, uint32_t len) __attribute__((unused));
+static void ddata_final(ddata_t* data) __attribute__((unused));
+static void ddata_free(ddata_t* data) __attribute__((unused));
+static int ddata_compact(ddata_t* data) __attribute__((unused));
+static int ddata_realloc(ddata_t* data, size_t need) __attribute__((unused));
+static inline size_t ddata_r_avail(ddata_t* data) __attribute__((unused));
+static inline size_t ddata_w_avail(ddata_t* data) __attribute__((unused));
+
+static inline intptr_t ddata_used(ddata_t* data)__attribute__((unused));
+static inline uint8_t* ddata_alloc(ddata_t* data, size_t len) __attribute__((unused));
+static inline void ddata_add(ddata_t* data, uint8_t* buf, uint32_t len) __attribute__((unused));
+static inline void ddata_forward(ddata_t* data, uint32_t len) __attribute__((unused));
+static inline void ddata_backward(ddata_t* data, uint32_t len) __attribute__((unused));
+static void ddata_send(ddata_t* data, int fd) __attribute__((unused));
+
+#define BOOLEAN 0 /* uint8_t */
+#define UINT8 1 /* uint8_t */
+#define UINT16 2 /* uint16_t */
+#define UINT32 3 /* uint32_t */
+#define UINT64 4 /* uint64_t */
+#define STRING1 5 /* len byte followed by UTF-8 chars */
+#define LIST 6 /* list begin */
+#define LIST_END 7 /* list end */
+#define TUPLE 8 /* tuple begin */
+#define TUPLE_END 9 /* tuple end */
+#define ATOM 10 /* len bytes followed by ASCII chars */
+#define BINARY 11 /* binary 4-byte len followed by Octets */
+#define INT8 12
+#define INT16 13
+#define INT32 14
+#define INT64 15
+#define FLOAT32 16
+#define FLOAT64 17
+#define STRING4 18 /* 4-byte len followed by UTF-8 string */
+
+#define DDATA_PUT_UINT8(ptr, n) do { \
+ ((uint8_t*)(ptr))[0] = ((n) & 0xff); \
+ } while(0)
+
+#define DDATA_GET_UINT8(ptr) \
+ (((uint8_t*)(ptr))[0])
+
+#define DDATA_PUT_UINT16(ptr, n) do { \
+ ((uint8_t*)(ptr))[0] = (((n) >> 8) & 0xff); \
+ ((uint8_t*)(ptr))[1] = ((n) & 0xff); \
+ } while(0)
+
+#define DDATA_GET_UINT16(ptr) \
+ ((((uint8_t*)(ptr))[0] << 8) + ((uint8_t*)(ptr))[1])
+
+#define DDATA_PUT_UINT32(ptr, n) do { \
+ ((uint8_t*)(ptr))[0] = (((n) >> 24) & 0xff); \
+ ((uint8_t*)(ptr))[1] = (((n) >> 16) & 0xff); \
+ ((uint8_t*)(ptr))[2] = (((n) >> 8) & 0xff); \
+ ((uint8_t*)(ptr))[3] = ((n) & 0xff); \
+ } while(0)
+
+#define DDATA_GET_UINT32(ptr) \
+ ((((uint8_t*)(ptr))[0] << 24) + (((uint8_t*)(ptr))[1] << 16) + \
+ (((uint8_t*)(ptr))[2] << 8) + ((uint8_t*)(ptr))[3])
+
+#define DDATA_GET_UINT64(ptr) \
+ ((((uint64_t)DDATA_GET_UINT32(ptr)) << 32) | \
+ DDATA_GET_UINT32(ptr+4))
+
+#ifndef _QUAD_HIGHWORD
+#define _QUAD_HIGHWORD 1
+#endif
+
+#ifndef _QUAD_LOWWORD
+#define _QUAD_LOWWORD 0
+#endif
+
+#define DDATA_PUT_UINT64(ptr, n) do { \
+ union { uint64_t u64; uint32_t u32[2]; } iu; \
+ uint32_t n32; \
+ iu.u64 = (n); \
+ n32=iu.u32[_QUAD_HIGHWORD]; DDATA_PUT_UINT32((ptr), n32); \
+ n32=iu.u32[_QUAD_LOWWORD]; DDATA_PUT_UINT32((ptr)+4, n32); \
+ } while(0)
+
+#define DDATA_PUT_FLOAT32(ptr, n) do { \
+ union { float f32; uint32_t u32; } fu; \
+ uint32_t n32; \
+ fu.f32 = (n); \
+ n32=fu.f32; DDATA_PUT_UINT32((ptr), n32); \
+ } while(0)
+
+#define DDATA_PUT_FLOAT64(ptr, n) do { \
+ union { double f64; uint32_t u32[2]; } fu; \
+ uint32_t n32; \
+ fu.f64 = (n); \
+ n32=fu.u32[_QUAD_HIGHWORD]; DDATA_PUT_UINT32((ptr), n32); \
+ n32=fu.u32[_QUAD_LOWWORD]; DDATA_PUT_UINT32((ptr)+4, n32); \
+ } while(0)
+
+
+static void ddata_reset(ddata_t* data)
+{
+ data->wr = data->base;
+ data->rd = data->wr;
+}
+
+static void ddata_init(ddata_t* data, uint8_t* buf, uint32_t len, int dynamic)
+{
+ data->dyn_alloc = dynamic;
+ data->base = buf;
+ data->rd = data->base;
+ data->wr = data->base;
+ data->eob = data->base + len;
+}
+
+static void ddata_r_init(ddata_t* data, uint8_t* buf, uint32_t len, int dynamic)
+{
+ data->dyn_alloc = dynamic;
+ data->base = buf;
+ data->rd = data->base;
+ data->wr = data->base + len;
+ data->eob = data->base + len;
+}
+
+static ddata_t* ddata_new(uint8_t* buf, uint32_t len)
+{
+ ddata_t* data = DALLOC(sizeof(ddata_t)+len-1);
+ if (data == NULL)
+ return NULL;
+ data->dyn_alloc = 0; /* dyn_alloc=1 only when buffer is separate! */
+ data->base = data->buf;
+ data->rd = data->base;
+ data->wr = data->base;
+ if (buf != NULL) {
+ memcpy(data->rd, buf, len);
+ data->wr += len;
+ }
+ data->eob = data->base + len;
+ return data;
+}
+
+static void ddata_final(ddata_t* data)
+{
+ if (data->dyn_alloc && (data->base != NULL)) {
+ DFREE(data->base);
+ data->base = NULL;
+ }
+}
+
+static void ddata_free(ddata_t* data)
+{
+ ddata_final(data);
+ DFREE(data);
+}
+
+static int ddata_compact(ddata_t* data)
+{
+ size_t used, roffs, woffs;
+
+ roffs = data->rd - data->base;
+ woffs = data->wr - data->base;
+ used = woffs-roffs;
+ memmove(data->base, data->rd, used);
+ data->rd = data->base;
+ data->wr = data->base+used;
+ return 0;
+}
+
+static int ddata_realloc(ddata_t* data, size_t need)
+{
+ uint8_t* base0;
+ size_t roffs, woffs;
+ size_t wavail = data->eob - data->wr;
+ size_t old_size;
+ size_t new_size;
+
+ if (wavail >= need)
+ return 0;
+ if (need < 256)
+ need += 256;
+ old_size = data->eob - data->base;
+ new_size = old_size + need;
+ base0 = data->base;
+ roffs = data->rd - data->base;
+ woffs = data->wr - data->base;
+ if (data->dyn_alloc) {
+ void* ptr = DREALLOC(base0, new_size);
+ if (ptr == NULL)
+ return -1;
+ data->base = ptr;
+ }
+ else {
+ if ((data->base = DALLOC(new_size)) == NULL)
+ return -1;
+ memcpy(data->base, base0, old_size);
+ }
+ data->rd = data->base + roffs;
+ data->wr = data->base + woffs;
+ data->eob = data->base + new_size;
+ data->dyn_alloc = 1;
+ return 0;
+}
+
+static inline size_t ddata_r_avail(ddata_t* data)
+{
+ return (data->wr - data->rd);
+}
+
+static inline size_t ddata_w_avail(ddata_t* data)
+{
+ return (data->eob - data->wr);
+}
+
+static inline intptr_t ddata_used(ddata_t* data)
+{
+ return (data->wr - data->rd);
+}
+
+static inline uint8_t* ddata_alloc(ddata_t* data, size_t len)
+{
+ uint8_t* ptr;
+
+ if (ddata_w_avail(data) < len)
+ ddata_realloc(data, len);
+ ptr = data->wr;
+ data->wr += len;
+ return ptr;
+}
+
+/* add "raw" data to Data buffer */
+static inline void ddata_add(ddata_t* data, uint8_t* buf, uint32_t len)
+{
+ uint8_t* ptr = ddata_alloc(data, len);
+ memcpy(ptr, buf, len);
+}
+
+/* skip "data" moving ptr forward */
+static inline void ddata_forward(ddata_t* data, uint32_t len)
+{
+ /* uint8_t* ptr = */ ddata_alloc(data, len);
+ /* just use the side effect of data_alloc */
+}
+
+static inline void ddata_backward(ddata_t* data, uint32_t len)
+{
+ uint8_t* ptr = data->wr - len;
+ if (ptr < data->base)
+ data->wr = data->base;
+ else
+ data->wr = ptr;
+}
+
+static void ddata_send(ddata_t* data, int fd)
+{
+ uint32_t len = (data->wr - data->rd) - 4;
+ DDATA_PUT_UINT32(data->rd, len);
+ write(fd, data->rd, len+4);
+}
+
+/*******************************************************************************
+ *
+ * PUT Untagged data
+ *
+ *******************************************************************************/
+
+static inline void ddata_put_UINT8(ddata_t* data, uint8_t n)
+{
+ uint8_t* ptr = ddata_alloc(data, 1);
+ DDATA_PUT_UINT8(ptr, n);
+}
+
+static inline void ddata_put_UINT16(ddata_t* data, uint16_t n)
+{
+ uint8_t* ptr = ddata_alloc(data, 2);
+ DDATA_PUT_UINT16(ptr, n);
+}
+
+static inline void ddata_put_UINT32(ddata_t* data, uint32_t n)
+{
+ uint8_t* ptr = ddata_alloc(data, 4);
+ DDATA_PUT_UINT32(ptr, n);
+}
+
+static inline void ddata_put_UINT64(ddata_t* data, uint64_t n)
+{
+ uint8_t* ptr = ddata_alloc(data, 8);
+ DDATA_PUT_UINT64(ptr, n);
+}
+
+/*******************************************************************************
+ *
+ * PUT tagged data
+ *
+ *******************************************************************************/
+
+static inline void ddata_put_boolean(ddata_t* data, uint8_t value)
+{
+ uint8_t* ptr = ddata_alloc(data, 2);
+ ptr[0] = BOOLEAN;
+ ptr[1] = value;
+}
+
+static inline void ddata_put_int8(ddata_t* data, int8_t n)
+{
+ uint8_t* ptr = ddata_alloc(data, 2);
+ *ptr++ = INT8;
+ DDATA_PUT_UINT8(ptr, n);
+}
+
+static inline void ddata_put_int16(ddata_t* data, int16_t n)
+{
+ uint8_t* ptr = ddata_alloc(data, 3);
+ *ptr++ = INT16;
+ DDATA_PUT_UINT16(ptr, n);
+}
+
+static inline void ddata_put_int32(ddata_t* data, int32_t n)
+{
+ uint8_t* ptr = ddata_alloc(data, 5);
+ *ptr++ = INT32;
+ DDATA_PUT_UINT32(ptr, n);
+}
+
+static inline void ddata_put_int64(ddata_t* data, int64_t n)
+{
+ uint8_t* ptr = ddata_alloc(data, 9);
+ *ptr++ = INT64;
+ DDATA_PUT_UINT64(ptr, n);
+}
+
+static inline void ddata_put_float32(ddata_t* data, float n)
+{
+ uint8_t* ptr = ddata_alloc(data, 5);
+ *ptr++ = FLOAT32;
+ DDATA_PUT_FLOAT32(ptr, n);
+}
+
+static inline void ddata_put_float64(ddata_t* data, double n)
+{
+ uint8_t* ptr = ddata_alloc(data, 9);
+ *ptr++ = FLOAT64;
+ DDATA_PUT_FLOAT64(ptr, n);
+}
+
+static inline void ddata_put_uint8(ddata_t* data, uint8_t n)
+{
+ uint8_t* ptr = ddata_alloc(data, 2);
+ *ptr++ = UINT8;
+ DDATA_PUT_UINT8(ptr, n);
+}
+
+static inline void ddata_put_uint16(ddata_t* data, uint16_t n)
+{
+ uint8_t* ptr = ddata_alloc(data, 3);
+ *ptr++ = UINT16;
+ DDATA_PUT_UINT16(ptr, n);
+}
+
+static inline void ddata_put_uint32(ddata_t* data, uint32_t n)
+{
+ uint8_t* ptr = ddata_alloc(data, 5);
+ *ptr++ = UINT32;
+ DDATA_PUT_UINT32(ptr, n);
+}
+
+static inline void ddata_put_uint64(ddata_t* data, uint64_t n)
+{
+ uint8_t* ptr = ddata_alloc(data, 9);
+ *ptr++ = UINT64;
+ DDATA_PUT_UINT64(ptr, n);
+}
+
+/* put special tag like TUPLE/LIST/TUPLE_END/TUPLE_END */
+static inline void ddata_put_tag(ddata_t* data, uint8_t tag)
+{
+ uint8_t* ptr = ddata_alloc(data, 1);
+ *ptr = tag;
+}
+
+static inline void ddata_put_atom(ddata_t* data, const char* atom)
+{
+ uint8_t* ptr;
+ uint32_t n = strlen(atom);
+
+ if (n > 0xff)
+ n = 0xff; /* truncate */
+ ptr = ddata_alloc(data, n+2);
+ *ptr++ = ATOM;
+ *ptr++ = n;
+ memcpy(ptr, atom, n);
+}
+
+static inline void ddata_put_string(ddata_t* data, const char* string)
+{
+ if (string == NULL) {
+ uint8_t* ptr = ddata_alloc(data, 2);
+ *ptr++ = STRING1;
+ *ptr++ = 0;
+ }
+ else {
+ uint32_t n = strlen(string);
+ if (n <= 0xff) {
+ uint8_t* ptr = ddata_alloc(data, n+2);
+ *ptr++ = STRING1;
+ *ptr++ = n;
+ memcpy(ptr, string, n);
+ }
+ else {
+ uint8_t* ptr = ddata_alloc(data, n+5);
+ *ptr++ = STRING4;
+ DDATA_PUT_UINT32(ptr, n);
+ ptr += 4;
+ memcpy(ptr, string, n);
+ }
+ }
+}
+
+static inline void ddata_put_binary(ddata_t* data, const uint8_t* buf, uint32_t len)
+{
+ uint8_t* ptr = ddata_alloc(data, len+5);
+ *ptr++ = BINARY;
+ DDATA_PUT_UINT32(ptr, len);
+ ptr += 4;
+ memcpy(ptr, buf, len);
+}
+
+/*******************************************************************************
+ *
+ * GET untagged data
+ *
+ *******************************************************************************/
+
+static inline int ddata_get_boolean(ddata_t* data, uint8_t* val)
+{
+ if (ddata_r_avail(data) < sizeof(uint8_t)) return 0;
+ *val = (DDATA_GET_UINT8(data->rd) != 0);
+ data->rd += sizeof(uint8_t);
+ return 1;
+}
+
+static inline int ddata_get_uint8(ddata_t* data, uint8_t* val)
+{
+ if (ddata_r_avail(data) < sizeof(uint8_t)) return 0;
+ *val = DDATA_GET_UINT8(data->rd);
+ data->rd += sizeof(uint8_t);
+ return 1;
+}
+
+static inline int ddata_get_uint16(ddata_t* data, uint16_t* val)
+{
+ if (ddata_r_avail(data) < sizeof(uint16_t)) return 0;
+ *val = DDATA_GET_UINT16(data->rd);
+ data->rd += sizeof(uint16_t);
+ return 1;
+}
+
+static inline int ddata_get_uint32(ddata_t* data, uint32_t* val)
+{
+ if (ddata_r_avail(data) < sizeof(uint32_t)) return 0;
+ *val = DDATA_GET_UINT32(data->rd);
+ data->rd += sizeof(uint32_t);
+ return 1;
+}
+
+static inline int ddata_get_int32(ddata_t* data, int32_t* val)
+{
+ if (ddata_r_avail(data) < sizeof(int32_t)) return 0;
+ *val = (int32_t) DDATA_GET_UINT32(data->rd);
+ data->rd += sizeof(int32_t);
+ return 1;
+}
+
+static inline int ddata_get_uint64(ddata_t* data, uint64_t* val)
+{
+ if (ddata_r_avail(data) < sizeof(uint64_t)) return 0;
+ *val = DDATA_GET_UINT64(data->rd);
+ data->rd += sizeof(uint64_t);
+ return 1;
+}
+#endif
diff --git a/deps/dthread/include/dlib.h b/deps/dthread/include/dlib.h
new file mode 100644
index 0000000..056d842
--- /dev/null
+++ b/deps/dthread/include/dlib.h
@@ -0,0 +1,69 @@
+/****** BEGIN COPYRIGHT *******************************************************
+ *
+ * Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+ *
+ * This software is licensed as described in the file COPYRIGHT, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://www.rogvall.se/docs/copyright.txt.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYRIGHT file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****** END COPYRIGHT ********************************************************/
+#ifndef __DLIB_H__
+#define __DLIB_H__
+
+#include <string.h>
+
+#ifndef NO_ERL_DRIVER
+#include "erl_driver.h"
+#endif
+
+extern void dlib_init(void);
+extern void dlib_finish(void);
+
+extern void* dlib_alloc(size_t sz, char* file, int line);
+extern void* dlib_zalloc(size_t sz, char* file, int line);
+extern void dlib_free(void* ptr, char* file, int line);
+extern void* dlib_realloc(void* ptr, size_t sz, char* file, int line);
+extern void dlib_zero(void* ptr, size_t sz, char* file, int line);
+extern size_t dlib_allocated(void);
+extern size_t dlib_total_allocated(void);
+
+#ifdef DEBUG_MEM
+#define DALLOC(sz) dlib_alloc((sz),__FILE__,__LINE__)
+#define DZALLOC(sz) dlib_zalloc((sz),__FILE__,__LINE__)
+#define DFREE(ptr) dlib_free((ptr),__FILE__,__LINE__)
+#define DREALLOC(ptr,sz) dlib_realloc((ptr),(sz),__FILE__,__LINE__)
+#define DZERO(ptr,sz) dlib_zero((ptr),(sz),__FILE__,__LINE__)
+#else
+
+#if defined(NO_ERL_DRIVER)
+#define DALLOC(sz) malloc((sz))
+#define DZALLOC(sz) zalloc((sz))
+#define DFREE(ptr) free((ptr))
+#define DREALLOC(ptr,sz) realloc((ptr),(sz))
+#define DZERO(ptr,sz) memset((ptr),'\0',(sz))
+#else
+#define DALLOC(sz) driver_alloc((sz))
+#define DZALLOC(sz) zalloc((sz))
+#define DFREE(ptr) driver_free((ptr))
+#define DREALLOC(ptr,sz) driver_realloc((ptr),(sz))
+#define DZERO(ptr,sz) memset((ptr),'\0',(sz))
+#endif
+
+static inline void* zalloc(size_t sz)
+{
+ void* ptr = DALLOC(sz);
+ if (ptr != NULL)
+ DZERO(ptr,sz);
+ return ptr;
+}
+
+#endif
+
+#endif
diff --git a/deps/dthread/include/dlog.h b/deps/dthread/include/dlog.h
new file mode 100644
index 0000000..b2bdfeb
--- /dev/null
+++ b/deps/dthread/include/dlog.h
@@ -0,0 +1,57 @@
+/****** BEGIN COPYRIGHT *******************************************************
+ *
+ * Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+ *
+ * This software is licensed as described in the file COPYRIGHT, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://www.rogvall.se/docs/copyright.txt.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYRIGHT file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****** END COPYRIGHT ********************************************************/
+#ifndef __DLOG_H__
+#define __DLOG_H__
+
+#include <string.h>
+
+extern void dlog_init(void);
+extern void dlog_finish(void);
+
+extern void dlog_emit_error(int level, char* file, int line, ...);
+extern void dlog_set_debug(int level);
+
+extern int dlog_debug_level;
+
+#define DLOG_DEBUG 7
+#define DLOG_INFO 6
+#define DLOG_NOTICE 5
+#define DLOG_WARNING 4
+#define DLOG_ERROR 3
+#define DLOG_CRITICAL 2
+#define DLOG_ALERT 1
+#define DLOG_EMERGENCY 0
+#define DLOG_NONE -1
+
+#define DLOG(level,file,line,args...) do { \
+ if (((level) == DLOG_EMERGENCY) || \
+ ((dlog_debug_level >= 0) && ((level) <= dlog_debug_level))) { \
+ dlog_emit_error((level),(file),(line),args); \
+ } \
+ } while(0)
+
+#define DEBUGF(args...) DLOG(DLOG_DEBUG,__FILE__,__LINE__,args)
+#define INFOF(args...) DLOG(DLOG_INFO,__FILE__,__LINE__,args)
+#define NOTICEF(args...) DLOG(DLOG_NOTICE,__FILE__,__LINE__,args)
+#define WARNINGF(args...) DLOG(DLOG_WARNING,__FILE__,__LINE__,args)
+#define ERRORF(args...) DLOG(DLOG_ERROR,__FILE__,__LINE__,args)
+#define CRITICALF(args...) DLOG(DLOG_CRITICAL,__FILE__,__LINE__,args)
+#define ALERTF(args...) DLOG(DLOG_ALERT,__FILE__,__LINE__,args)
+#define EMERGENCYF(args...) DLOG(DLOG_EMERGENCY,__FILE__,__LINE__,args)
+
+#endif
+
diff --git a/deps/dthread/include/dterm.h b/deps/dthread/include/dterm.h
new file mode 100644
index 0000000..e5576ee
--- /dev/null
+++ b/deps/dthread/include/dterm.h
@@ -0,0 +1,269 @@
+/****** BEGIN COPYRIGHT *******************************************************
+ *
+ * Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+ *
+ * This software is licensed as described in the file COPYRIGHT, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://www.rogvall.se/docs/copyright.txt.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYRIGHT file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****** END COPYRIGHT ********************************************************/
+#ifndef __DTERM_H__
+#define __DTERM_H__
+
+#include <stdio.h>
+#include "dlib.h"
+
+struct _dterm_t;
+
+// ErlDrvTerm construction
+#define DTERM_EXTRA 64
+#define DTERM_FIXED 256
+
+typedef struct _dterm_link_t {
+ struct _dterm_link_t* next;
+ unsigned char data[];
+} dterm_link_t;
+
+typedef struct _dterm_mark_t {
+ struct _dterm_mark_t* next;
+ ErlDrvTermData type; // TUPLE | LIST ?RECORD?
+ size_t count; // number of elements
+} dterm_mark_t;
+
+typedef struct _dterm_t {
+ int dyn_alloc;
+ int dyn_size; // real size of data (if dynamic)
+ ErlDrvTermData* base;
+ ErlDrvTermData* ptr;
+ ErlDrvTermData* ptr_end;
+ dterm_link_t* head;
+ dterm_mark_t* mark;
+ ErlDrvTermData data[DTERM_FIXED];
+} dterm_t;
+
+extern void dterm_lib_init(void);
+extern void dterm_lib_finish(void);
+
+extern void dterm_init(dterm_t* p);
+extern void dterm_reset(dterm_t* p);
+extern void dterm_finish(dterm_t* p);
+extern int dterm_expand(dterm_t* p, size_t n);
+extern void* dterm_link_alloc_data(dterm_t* p, size_t size);
+extern void* dterm_link_copy_data(dterm_t* p, void* src, size_t size);
+
+extern int dterm_dyn_size(ErlDrvTermData* spec, int len);
+extern char* dterm_dyn_copy(ErlDrvTermData* spec, int len, char* ptr);
+extern int dterm_dump(FILE*, ErlDrvTermData* spec, int len);
+
+extern void dterm_kv_int(dterm_t* t,ErlDrvTermData key, ErlDrvSInt value);
+extern void dterm_kv_uint(dterm_t* t,ErlDrvTermData key, ErlDrvUInt value);
+extern void dterm_kv_atom(dterm_t* t,ErlDrvTermData key, ErlDrvTermData value);
+extern void dterm_kv_bool(dterm_t* t,ErlDrvTermData key, int value);
+extern void dterm_kv_string(dterm_t* t,ErlDrvTermData key, char* value);
+
+
+static inline ErlDrvTermData* dterm_data(dterm_t* p)
+{
+ return p->base;
+}
+
+static inline size_t dterm_allocated_size(dterm_t* p)
+{
+ return (p->ptr_end - p->base);
+}
+
+static inline size_t dterm_used_size(dterm_t* p)
+{
+ return (p->ptr - p->base);
+}
+
+static inline size_t dterm_remain(dterm_t* p)
+{
+ return (p->ptr_end - p->ptr);
+}
+
+static inline int dterm_need(dterm_t* p, size_t need)
+{
+ size_t remain = dterm_remain(p);
+ if (remain < need)
+ return dterm_expand(p, (need-remain)+DTERM_EXTRA);
+ return 1;
+}
+
+static inline int dterm_put(dterm_t* p, ErlDrvTermData d1)
+{
+ if (dterm_need(p, 1)) {
+ p->ptr[0] = d1;
+ p->ptr += 1;
+ if (p->mark) p->mark->count++;
+ return 1;
+ }
+ return 0;
+}
+
+static inline int dterm_put2(dterm_t* p, ErlDrvTermData d1, ErlDrvTermData d2)
+{
+ if (dterm_need(p, 2)) {
+ p->ptr[0] = d1;
+ p->ptr[1] = d2;
+ p->ptr += 2;
+ if (p->mark) p->mark->count++;
+ return 1;
+ }
+ return 0;
+}
+
+static inline int dterm_put3(dterm_t* p, ErlDrvTermData d1, ErlDrvTermData d2,
+ ErlDrvTermData d3)
+{
+ if (dterm_need(p, 3)) {
+ p->ptr[0] = d1;
+ p->ptr[1] = d2;
+ p->ptr[2] = d3;
+ p->ptr += 3;
+ if (p->mark) p->mark->count++;
+ return 1;
+ }
+ return 0;
+}
+
+static inline int dterm_put4(dterm_t* p, ErlDrvTermData d1, ErlDrvTermData d2,
+ ErlDrvTermData d3,ErlDrvTermData d4)
+{
+ if (dterm_need(p, 4)) {
+ p->ptr[0] = d1;
+ p->ptr[1] = d2;
+ p->ptr[2] = d3;
+ p->ptr[3] = d4;
+ p->ptr += 4;
+ if (p->mark) p->mark->count++;
+ return 1;
+ }
+ return 0;
+}
+
+static inline int dterm_int(dterm_t* p, ErlDrvSInt val)
+{
+ return dterm_put2(p, ERL_DRV_INT, val);
+}
+
+static inline int dterm_uint(dterm_t* p, ErlDrvUInt val)
+{
+ return dterm_put2(p, ERL_DRV_UINT, val);
+}
+
+static inline int dterm_int64(dterm_t* p, ErlDrvSInt64 val)
+{
+ void* ptr = dterm_link_copy_data(p, (void*) &val, sizeof(val));
+ return dterm_put2(p, ERL_DRV_INT64, (ErlDrvTermData)ptr);
+}
+
+static inline int dterm_uint64(dterm_t* p, ErlDrvUInt64 val)
+{
+ void* ptr = dterm_link_copy_data(p, (void*) &val, sizeof(val));
+ return dterm_put2(p, ERL_DRV_UINT64, (ErlDrvTermData)ptr);
+}
+
+static inline int dterm_float(dterm_t* p, double val)
+{
+ void* ptr = dterm_link_copy_data(p, (void*) &val, sizeof(val));
+ return dterm_put2(p, ERL_DRV_FLOAT, (ErlDrvTermData)ptr);
+}
+
+static inline int dterm_atom(dterm_t* p, ErlDrvTermData atom)
+{
+ return dterm_put2(p, ERL_DRV_ATOM, atom);
+}
+
+static inline int dterm_port(dterm_t* p, ErlDrvTermData port)
+{
+ return dterm_put2(p, ERL_DRV_PORT, port);
+}
+
+static inline int dterm_pid(dterm_t* p, ErlDrvTermData pid)
+{
+ return dterm_put2(p, ERL_DRV_PID, pid);
+}
+
+static inline int dterm_binary(dterm_t* p, ErlDrvBinary* bin, ErlDrvUInt size,
+ ErlDrvUInt offs)
+{
+ return dterm_put4(p, ERL_DRV_BINARY, (ErlDrvTermData)bin, size, offs);
+}
+
+static inline int dterm_string(dterm_t* p, const char* ptr, size_t len)
+{
+ return dterm_put3(p, ERL_DRV_STRING, (ErlDrvTermData)ptr, len);
+}
+
+static inline int dterm_string_cons(dterm_t* p, const char* ptr, size_t len)
+{
+ if (!dterm_put3(p, ERL_DRV_STRING_CONS, (ErlDrvTermData)ptr, len))
+ return 0;
+ if (p->mark) {
+ if (p->mark->count == 0)
+ return 0;
+ p->mark->count--;
+ }
+ return 1;
+}
+
+static inline int dterm_buf_binary(dterm_t* p, const char* ptr, size_t len)
+{
+ return dterm_put3(p, ERL_DRV_BUF2BINARY, (ErlDrvTermData)ptr, len);
+}
+
+static inline int dterm_tuple(dterm_t* p, size_t size)
+{
+ return dterm_put2(p, ERL_DRV_TUPLE, size);
+}
+
+static inline int dterm_nil(dterm_t* p)
+{
+ return dterm_put(p, ERL_DRV_NIL);
+}
+
+static inline int dterm_list(dterm_t* p, size_t size)
+{
+ return dterm_put2(p, ERL_DRV_LIST, size);
+}
+
+static inline int dterm_list_begin(dterm_t* p, dterm_mark_t* mark)
+{
+ mark->next = p->mark;
+ p->mark = mark;
+ mark->type = ERL_DRV_LIST;
+ mark->count = 0;
+ return 1;
+}
+
+static inline int dterm_list_end(dterm_t* p, dterm_mark_t* mark)
+{
+ dterm_nil(p);
+ p->mark = mark->next; // count the list creation below
+ return dterm_list(p, mark->count);
+}
+
+static inline int dterm_tuple_begin(dterm_t* p, dterm_mark_t* mark)
+{
+ mark->next = p->mark;
+ p->mark = mark;
+ mark->type = ERL_DRV_TUPLE;
+ mark->count = 0;
+ return 1;
+}
+
+static inline int dterm_tuple_end(dterm_t* p, dterm_mark_t* mark)
+{
+ p->mark = mark->next;
+ return dterm_tuple(p, mark->count);
+}
+
+#endif
diff --git a/deps/dthread/include/dthread.h b/deps/dthread/include/dthread.h
new file mode 100644
index 0000000..b265cb8
--- /dev/null
+++ b/deps/dthread/include/dthread.h
@@ -0,0 +1,174 @@
+/****** BEGIN COPYRIGHT *******************************************************
+ *
+ * Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+ *
+ * This software is licensed as described in the file COPYRIGHT, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://www.rogvall.se/docs/copyright.txt.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYRIGHT file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****** END COPYRIGHT ********************************************************/
+#ifndef __DTHREAD_H__
+#define __DTHREAD_H__
+
+struct _dthread_t;
+
+#include "erl_driver.h"
+#include "dterm.h"
+
+// Hack to handle R15 driver used with pre R15 driver
+#if ERL_DRV_EXTENDED_MAJOR_VERSION == 1
+typedef int ErlDrvSizeT;
+typedef int ErlDrvSSizeT;
+#endif
+
+#if (ERL_DRV_EXTENDED_MAJOR_VERSION > 2) || ((ERL_DRV_EXTENDED_MAJOR_VERSION == 2) && (ERL_DRV_EXTENDED_MINOR_VERSION >= 1))
+#define DOUTPUT_TERM(thr, message, len) erl_drv_output_term((thr)->dport,(message),(len))
+#define DSEND_TERM(thr, to, message, len) erl_drv_send_term((thr)->dport,(to),(message),(len))
+
+#else
+#define DOUTPUT_TERM(thr, message, len) driver_output_term((thr)->port,(message),(len))
+#define DSEND_TERM(thr, to, message, len) driver_send_term((thr)->port,(to),(message),(len))
+#endif
+
+
+#ifdef __WIN32__
+#include <windows.h>
+#define DTHREAD_EVENT(e) ((HANDLE)(e))
+#define DTHREAD_INVALID_EVENT ((HANDLE)(-1))
+#define DTHREAD_CLOSE_EVENT(e) CloseHandle(DTHREAD_EVENT(e))
+#else
+#define DTHREAD_EVENT(e) ((int)((long)(e)))
+#define DTHREAD_INVALID_EVENT ((long)(-1))
+#define DTHREAD_CLOSE_EVENT(e) close(DTHREAD_EVENT(e))
+#endif
+
+// Builtin commands are negative, user command must > 0
+
+#define DTHREAD_STOP -1
+#define DTHREAD_SEND_TERM -2
+#define DTHREAD_OUTPUT_TERM -3
+#define DTHREAD_OUTPUT -4
+
+typedef struct _dmessage_t
+{
+ struct _dmessage_t* next; // next message in queue
+ int cmd; // same as ctl
+ struct _dthread_t* source; // sender thread
+ void (*release)(struct _dmessage_t*); // release hook
+ ErlDrvTermData from; // sender pid
+ ErlDrvTermData to; // receiver pid (if any)
+ ErlDrvTermData ref; // sender ref
+ void* udata; // user data
+ size_t size; // total allocated size of buffer
+ size_t used; // total used part of buffer
+ char* buffer; // points to data or allocated
+ char data[0];
+} dmessage_t;
+
+typedef struct _dthread_t {
+ ErlDrvTid tid; // thread id
+ void* arg; // thread init argument
+ ErlDrvPort port; // port controling the thread
+ ErlDrvTermData dport; // the port identifier as DriverTermData
+ ErlDrvTermData owner; // owner process pid
+ ErlDrvTermData caller; // last caller (driver_caller)
+ ErlDrvTermData ref; // last sender ref
+ int smp_support; // SMP support or not
+
+ // Input queue
+ ErlDrvMutex* iq_mtx; // message queue lock
+ int iq_len; // message queue length
+ dmessage_t* iq_front; // get from front
+ dmessage_t* iq_rear; // put to rear
+
+ ErlDrvEvent iq_signal[2]; // event signaled when items is enqueued
+} dthread_t;
+
+#define ERL_DRV_EXCEP (1 << 7)
+
+typedef struct _dthread_poll_event_t {
+ ErlDrvEvent event;
+ int events; // ERL_DRV_READ | WRITE
+ int revents; // ERL_DRV_READ | WRITE
+} dthread_poll_event_t;
+
+extern void dthread_lib_init(void);
+extern void dthread_lib_finish(void);
+
+extern dmessage_t* dmessage_alloc(size_t n);
+extern void dmessage_free(dmessage_t* mp);
+extern dmessage_t* dmessage_create_r(int cmd,
+ void (*release)(dmessage_t*),
+ void* udata,
+ char* buf, size_t len);
+extern dmessage_t* dmessage_create(int cmd,char* buf, size_t len);
+
+extern void dthread_event_close(ErlDrvEvent);
+extern int dthread_signal_set(dthread_t* thr);
+extern int dthread_signal_reset(dthread_t* thr);
+
+extern void dthread_signal_init(dthread_t* thr);
+extern void dthread_signal_select(dthread_t* thr, int on);
+extern void dthread_signal_use(dthread_t* thr, int on);
+extern void dthread_signal_finish(dthread_t* thr, int and_close);
+
+extern int dthread_poll(dthread_t* thr,
+ dthread_poll_event_t* events,
+ size_t* npevs, int timeout);
+
+extern int dthread_send(dthread_t* thr, dthread_t* source,
+ dmessage_t* mp);
+
+extern int dthread_control(dthread_t* thr, dthread_t* source,
+ int cmd, char* buf, int len);
+extern int dthread_output(dthread_t* thr, dthread_t* source,
+ char* buf, int len);
+
+extern int dthread_port_send_dterm(dthread_t* thr, dthread_t* source,
+ ErlDrvTermData target, dterm_t* p);
+extern int dthread_port_output_dterm(dthread_t* thr, dthread_t* source,
+ dterm_t* p);
+
+extern int dthread_port_send_term(dthread_t* thr, dthread_t* source,
+ ErlDrvTermData target,
+ ErlDrvTermData* spec, int len);
+extern int dthread_port_output_term(dthread_t* thr, dthread_t* source,
+ ErlDrvTermData* spec, int len);
+extern int dthread_port_output(dthread_t* thr, dthread_t* source,
+ char* buf, int len);
+extern int dthread_port_output2(dthread_t* thr, dthread_t* source,
+ char* buf, int len,
+ char* buf2, int len2);
+extern int dthread_port_output_binary(dthread_t* thr, dthread_t* source,
+ char *hbuf, ErlDrvSizeT hlen,
+ ErlDrvBinary* bin,
+ ErlDrvSizeT offset, ErlDrvSizeT len);
+
+// send {Ref::uint32(), ok}
+extern int dthread_port_send_ok(dthread_t* thr, dthread_t* source,
+ ErlDrvTermData target, ErlDrvTermData ref);
+// send {Ref::uint32(), {error,Reason::atom()}}
+extern int dthread_port_send_error(dthread_t* thr, dthread_t* source,
+ ErlDrvTermData target,
+ ErlDrvTermData ref, int error);
+
+
+extern dmessage_t* dthread_recv(dthread_t* self, dthread_t** source);
+
+extern int dthread_init(dthread_t* thr, ErlDrvPort port);
+extern void dthread_finish(dthread_t* thr);
+extern dthread_t* dthread_start(ErlDrvPort port,
+ void* (*func)(void* arg),
+ void* arg, int stack_size);
+extern int dthread_stop(dthread_t* target, dthread_t* source,
+ void** exit_value);
+extern void dthread_exit(void* value);
+
+#endif
diff --git a/deps/dthread/rebar.config b/deps/dthread/rebar.config
new file mode 100644
index 0000000..de08381
--- /dev/null
+++ b/deps/dthread/rebar.config
@@ -0,0 +1,28 @@
+%% -*- erlang -*-
+%% Config file for dthread-application
+{deps_dir, ["deps"]}.
+{erl_opts, [debug_info, fail_on_warning]}.
+{sub_dirs, ["src"]}.
+
+%% -DDEBUG -DDEBUG_MEM
+{port_env, [
+ {"CFLAGS", "$CFLAGS -D_THREAD_SAFE"},
+ {"win32", "CFLAGS", "$CFLAGS -D__WIN32__"},
+ {"win32", "LDFLAGS", "$LDFLAGS -Wl--enable-stdcall-fixup"}
+ ]}.
+
+{port_specs, [
+ {"(win32)","priv/dthread_drv.so",
+ ["c_src/dlib.c",
+ "c_src/dlog.c",
+ "c_src/dterm.c",
+ "c_src/dthread.c",
+ "c_src/dthread_drv.c"]},
+
+ {"(linux|freebsd|darwin)","priv/dthread_drv.so",
+ ["c_src/dlib.c",
+ "c_src/dlog.c",
+ "c_src/dterm.c",
+ "c_src/dthread.c",
+ "c_src/dthread_drv.c"]}
+ ]}.
diff --git a/deps/dthread/src/dthread.app.src b/deps/dthread/src/dthread.app.src
new file mode 100644
index 0000000..d039330
--- /dev/null
+++ b/deps/dthread/src/dthread.app.src
@@ -0,0 +1,8 @@
+{application, dthread,
+ [{description, "dthread test"},
+ {vsn, git},
+ {modules, [dthread]},
+ {registered, []},
+ {env, []},
+ {applications,[kernel,stdlib]}
+ ]}.
diff --git a/deps/dthread/src/dthread.erl b/deps/dthread/src/dthread.erl
new file mode 100644
index 0000000..b5b66a4
--- /dev/null
+++ b/deps/dthread/src/dthread.erl
@@ -0,0 +1,93 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2012, Tony Rogvall
+%%% @doc
+%%% dthread test
+%%% @end
+%%% Created : 6 May 2012 by Tony Rogvall <tony@rogvall.se>
+
+-module(dthread).
+
+-compile(export_all).
+
+
+open() ->
+ case erl_ddll:load_driver(code:priv_dir(dthread), "dthread_drv") of
+ ok ->
+ open_port({spawn_driver, dthread_drv}, [binary]);
+ {error,Error} ->
+ io:format("erl_ddll: error:\n~s\n",
+ [erl_ddll:format_error(Error)]),
+ {error, Error}
+ end.
+
+close(Port) ->
+ port_close(Port).
+
+ctl1(Port) ->
+ port_control(Port, 1, "hello").
+
+ctl2(Port) ->
+ port_control(Port, 2, "olleh").
+
+command1(Port) ->
+ port_command(Port, "Hello").
+
+call1(Port, Value) when is_integer(Value) ->
+ ValueBin = <<Value:32>>,
+ Resp = port_control(Port, 100, [ValueBin]),
+ %% io:format("port_control: value=~w, resp = ~w\n", [ValueBin,Resp]),
+ case Resp of
+ <<0, RefNum:32>> ->
+ %% io:format("wait for ref = ~p\n", [RefNum]),
+ receive
+ {RefNum, Value1} ->
+ {ok,Value1};
+ Other ->
+ io:format("Got ~p\n", [Other]),
+ {error, Other}
+ end;
+ <<1, Error/binary>> ->
+ {error, binary_to_atom(Error, latin1)}
+ end.
+
+seq_call(Port, N) ->
+ lists:foreach(
+ fun(I) ->
+ case call1(Port, I) of
+ {ok,I1} ->
+ io:format("~w: result (~w)\n", [I,I1]);
+ Error ->
+ io:format("~w: result error=~w\n",[I,Error])
+ end
+ end, lists:seq(1, N)).
+
+par_call(Port, N) ->
+ lists:foreach(
+ fun(I) ->
+ spawn(fun() ->
+ io:format("~w: call\n", [I]),
+ case call1(Port, I) of
+ {ok,I1} ->
+ io:format("~w: result (~w)\n", [I,I1]);
+ Error ->
+ io:format("~w: result error=~w\n",[I,Error])
+ end
+ end)
+ end, lists:seq(1, N)).
+
diff --git a/deps/edown/Makefile b/deps/edown/Makefile
new file mode 100644
index 0000000..0b1f2c9
--- /dev/null
+++ b/deps/edown/Makefile
@@ -0,0 +1,12 @@
+.PHONY: all compile clean doc
+
+all: compile
+
+compile:
+ ./rebar compile
+
+clean:
+ ./rebar clean
+
+doc: compile
+ ./make_doc
diff --git a/deps/edown/NOTICE b/deps/edown/NOTICE
new file mode 100644
index 0000000..974350c
--- /dev/null
+++ b/deps/edown/NOTICE
@@ -0,0 +1,10 @@
+EDown is derived from code in EDoc, and designed to be an extension
+to EDoc.
+
+EDoc was written by Richard Carlsson, (c) 2001-2007 Richard Carlsson.
+It is part of Erlang/OTP (http://www.erlang.org) and available
+via the Erlang Public License (EPL).
+
+Original idea and implementation for EDown: ulf@wiger.net
+
+Markedoc contributed by Henning Dietrich <hd2010@eonblast.com> \ No newline at end of file
diff --git a/deps/edown/README.md b/deps/edown/README.md
new file mode 100644
index 0000000..df8e8ec
--- /dev/null
+++ b/deps/edown/README.md
@@ -0,0 +1,180 @@
+
+
+# Edown - Markdown generated from Edoc #
+
+Copyright (c) 2014 Ulf Wiger
+
+
+__Authors:__ [`ulf@wiger.net`](mailto:ulf@wiger.net).
+
+
+Status:
+------
+More-or-less readable Markdown can be generated.
+A doclet needs to be written that also creates
+a markdown-based index and overview. Currently, the
+edoc_doclet creates an index.html and overview.html,
+which do not point to the .md files.
+
+To generate markdown edoc, run:
+
+```
+
+edoc:application(App, [{doclet, edown_doclet} | OtherOpts]).
+
+```
+
+The `edown_xmerl` module is used as an xmerl export module.
+It converts xmerl's "simple xml" to Markdown syntax. Note that
+GH-flavored Markdown allows HTML markup (at least common tags),
+but doesn't expand markdown markup inside HTML markup, so the
+`edown_xmerl` module has to know the context in which it operates.
+
+Top-level README
+----------------
+
+Using the option `{top_level_readme, {File, BaseHref}}`, a github-friendly
+`README.md` in the top directory can be generated from the `overview.edoc`.
+This file is the same as the `doc/README.md` file already generated,
+but with relative links corrected (using `BaseHref`) so that they actually
+work. This step is needed since Github doesn't support relative paths in
+Markdown links.
+
+Example:
+
+`{top_level_readme, {"./README.md", "http://github.com/uwiger/edown"}}`
+
+The conversion function will fetch the current branch name from git,
+and fail if it cannot do so.
+
+It is also possible to add the branch information specifically:
+`{top_level_readme, {File, BaseHref, Branch}}`, although this shouldn't be
+necessary as long as edown can derive the branch name from git.
+
+Using Atlassian Stash as target
+-------------------------------
+
+The option `{edown_target, github | stash}` can be used to control which
+is the intended host repository. This affects how links are rewritten in
+order to find related files and stay on the correct branch.
+
+The default value is `github`.
+
+Note that at the moment, the
+[Markdown viewer plugin](https://bitbucket.org/atlassianlabs/stash-markdown-viewer-plugin) will be needed in order to render the generated documentation
+as Markdown on Stash.
+Github customizations
+=====================
+`pre` tags are converted into github "fenced" code blocks, i.e.
+
+```
+```...'''
+```
+
+. If language-specific syntax highlighting is desired,
+this can be achieved by adding a 'lang' attribute, e.g.
+
+```
+<pre lang="erlang">
+incr(X) ->
+ %% This should be formatted with Erlang syntax highlighting
+ X + 1.
+</pre>
+```
+
+which should format like this:
+
+```erlang
+
+incr(X) ->
+ %% This should be formatted with Erlang syntax highlighting
+ X + 1.
+
+```
+
+Rebar customizations
+====================
+A set of escripts can be found under
+[edown/priv/scripts/](http://github.com/uwiger/edown/blob/master/priv/scripts/), which
+can be used to customize the `rebar` built process. The
+[rebar.config.script](http://github.com/uwiger/edown/blob/master/priv/scripts/rebar.config.script)
+file should be copied into your application, next to `rebar.config`.
+It will sense if `doc` is a current target, and will then include
+`edown` in the `deps`; otherwise, it removes it. This way, you will
+not have to pull down `edown` unless you really want to build the
+docs. It will also locate edown along your path, in which case
+it doesn't need to pull it down again.
+
+The script will also start the `inets` application, so that you
+can include URLs as part of a `doc_path` option (see below).
+
+Links to other EDown-generated docs
+===================================
+There is a way to configure Edoc/Edown to get URLs right even
+when linking to other Edown-generated docs on Github.
+
+First, you need to specify paths to the `edoc-info` files for
+each repository as part of `edoc_opts` in your rebar.config, e.g.
+
+```
+ {doc_path, ["http://raw.github.com/uwiger/setup/master/doc",
+ "http://raw.github.com/uwiger/gproc/master/doc"]}
+```
+
+Note (1) that we use "http:://...", not "https://...", since
+Edoc doesn't recognize the latter. Also note that we use URLs
+to the raw files. This is for Edoc as it fetches the `edoc-info`
+files. Edown will detect and rewrite such links in the generated
+output, since "raw" links wouldn't work for the markdown files.
+
+The next issue is that Edoc uses httpd_client to fetch the
+`edoc-info` files, which requires `inets` to be started. To
+further complicate matters, `ssl` (and thus `crypto`, 'asn1' and
+`public_key`) must also be started, since Github will
+redirect to https.
+
+One way to solve this is to use the escripts found under
+`edown/priv/scripts`.
+
+NOTE
+====
+EDoc provides a plugin structure, so that one may specify own
+layout modules, export modules, and doclets. However, there is
+some overlap esp. between the layout and doclet modules, and
+several functions are expected to produce files on their own.
+This causes a problem for EDown, since it cannot handle frames.
+Instead, it would probably like to create one overview file with
+different sections. It would have been better to have a framework
+where some plugin functions identify the different files to be
+written, and the outline of each, other plugins convert to suitable
+content representation (e.g. HTML or Markdown), and EDoc then
+writes the files necessary.
+
+For now, EDown focuses on producing reasonable Markdown, rather
+than complying fully with the plugin framework. That is, the
+edown_doclet module will not go out of its way to function together
+with any other layout module than edown_layout, and vice versa.
+
+markedoc
+========
+
+The sed script bin/markedoc works in the opposite direction and converts
+your `README.md` to an `EDoc` file.
+
+See [bin/MARKEDOC-README.md](http://github.com/uwiger/edown/blob/master/bin/MARKEDOC-README.md).
+
+**FreeBSD, Mac OS X**`$ sed -E -f markedoc.sed <markdown file> > <edoc file>`
+
+**Linux**`$ sed -r -f markedoc.sed <markdown file> > <edoc file>`
+
+
+## Modules ##
+
+
+<table width="100%" border="0" summary="list of modules">
+<tr><td><a href="http://github.com/uwiger/edown/blob/master/doc/edown_doclet.md" class="module">edown_doclet</a></td></tr>
+<tr><td><a href="http://github.com/uwiger/edown/blob/master/doc/edown_layout.md" class="module">edown_layout</a></td></tr>
+<tr><td><a href="http://github.com/uwiger/edown/blob/master/doc/edown_lib.md" class="module">edown_lib</a></td></tr>
+<tr><td><a href="http://github.com/uwiger/edown/blob/master/doc/edown_make.md" class="module">edown_make</a></td></tr>
+<tr><td><a href="http://github.com/uwiger/edown/blob/master/doc/edown_xmerl.md" class="module">edown_xmerl</a></td></tr></table>
+
diff --git a/deps/edown/bin/MARKEDOC-README.md b/deps/edown/bin/MARKEDOC-README.md
new file mode 100644
index 0000000..acdefd1
--- /dev/null
+++ b/deps/edown/bin/MARKEDOC-README.md
@@ -0,0 +1,196 @@
+edown markedoc 0.3.2
+====================
+
+**markedoc helps you keep your project's README.md in sync with your overview.edoc.**
+
+This is the opposite direction from what **edown** otherwise does.
+
+markedoc translates [Markdown][] formatted texts into [Erlang][] [EDoc][] format, for inclusion into [EDoc][] generated html docs. It is for use on Linux, FreeBSD and Mac OS X and any system that you can install **[sed][Requirements]** on.
+
+Status: [pre-beta][Status]. Quite stable and usable. See [Status][].
+
+markedoc is a mere [sed][] command file to convert markdown to edoc. It is part of the **[edown][]** project. The actual script file is in the bin folder: bin/markedoc.sed. Your contribution to make markedoc stable is highly [welcome][issues].
+
+[issues]: https://github.com/hdiedrich/markedoc/issues "Issue tracker"
+
+Use <a name=Use></a>
+---
+At the command line for
+
+**FreeBSD, Mac OS X**
+ $ sed -E -f markedoc.sed <markdown file> > <edoc file>
+
+**Linux**
+ $ sed -r -f markedoc.sed <markdown file> > <edoc file>
+
+Usage for Linux and FreeBSD and Mac OS X is completely the same, except for the -r instead of the -E parameter. Both mean the same but happen to have a different name. In the examples below, replace -E with -r where necessary.
+
+Requirements <a name=Requirements></a>
+------------
+* **[sed][]**: is part of any Linux, FreeBSD and Mac OSX distribution, also see [Notes][].
+
+* **[Erlang/OTP][Erlang]**, see [Notes][].
+
+Test <a name=Test></a>
+----
+
+ **FreeBSD, Mac OS X**
+ $ samples/markedoc/test-bsd.sh
+
+ **Linux**
+ $ samples/markedoc/test-linux.sh
+
+Then check html files as listed in the output.
+
+Sample <a name=Sample></a>
+------
+
+From edown project root, try out:
+
+ **FreeBSD, Mac OS X**
+ $ sed -E -f bin/markedoc.sed samples/markedoc/SAMPLE1.md > samples/markedoc/doc/SAMPLE.edoc
+ $ erl -noshell -run edoc_run application "'myapp'" '"samples/markedoc"' '[]'
+
+ **Linux**
+ $ sed -r -f bin/markedoc.sed samples/markedoc/SAMPLE1.md > samples/markedoc/doc/SAMPLE.edoc
+ $ erl -noshell -run edoc_run application "'myapp'" '"samples/markedoc"' '[]'
+
+This creates a SAMPLE.edoc file from SAMPLE1.md, which is then included in the EDoc generation. Point your browser at
+
+ samples/markedoc/doc/overview-summary.html
+
+to see the result. For something only vaguely related but pretty, try:
+
+ $ erl -noshell -run edoc_run application "'myapp'" '"samples/markedoc"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+
+This illustrates the motivation for the markedoc as it is now: to have all code lines in one block in order to be able to address them as one united div from css.
+
+For your own projects you'd copy markedoc.sed in the right place and do something like:
+
+ **FreeBSD, Mac OS X**
+ $ sed -E -f bin/markedoc.sed README.md > doc/README.edoc
+ $ erl -noshell -run edoc_run application "'myapp'" '"."' '[]'
+
+ **Linux**
+ $ sed -r -f bin/markedoc.sed README.md > doc/README.edoc
+ $ erl -noshell -run edoc_run application "'myapp'" '"."' '[]'
+
+And that's it. This could also be part of your Makefile. For the intermediary README.edoc to automatically become part of your generated EDoc html pages, you would use a @docfile tag in your overview.edoc file, like so:
+
+ @docfile "doc/README.edoc"
+
+By running sed, then edoc, this makes the README.edoc part of the overview page. You could also make the README.md straight into an overview.edoc but the way it is allows allows to embedd it into additional context information that should be useful for a proper html doc.
+
+Accordingly, the sample stub overview.edoc used for the samples here, looks like this:
+
+ @author You
+ @title a markedoc sample doc
+ @version 0.2
+ @docfile "samples/markedoc/doc/SAMPLE.edoc"
+
+Tricks <a name=Tricks></a>
+------
+
+Markdown cannot jump to headlines as anchors, while edoc makes headlines into anchors automatically. To allow for meaningful anchor jumps like [sample][] within a page, the following workaround makes sense. It is 'weeded out' by markedoc so that it does not trip up edoc. But it makes for local jumps in
+both worlds:
+
+ ## Examples <a name=example></a>
+
+ ...
+
+ [sample]: #sample
+
+
+This makes a tag `[example][]' into a direct jump to the headline 'Example', in both markdown and edoc.
+Markdown actually uses the `[sample]: #sample' reference. EDoc, however, automatically inserts an anchor for 'Example' being a headline, and of the same name. (The links are not case sensitive.)
+If you get the reference wrong or forget to make it, the link tag will be displayed in the open, as actual `[example][]'.
+
+
+Status <a name=Status></a>
+------
+
+ **Pre-Beta**. Quite usable, but still likes to trip up EDoc now and then, which is kind of easy to do.
+
+There are many ways to create formats that will make the EDoc generator tilt and unfortunately, the errors it throws are sometimes not quite so illuminating to the reader. But why not try an incremental approach and see what works. As you can see from this [source sample][sample], which works alright, it's quite a lot that *does* work and the murky bits can usally be worked out fast. Sometimes an additional line helps, some spaces at the end of a line, general intuitive stuff. Please experiment and push your fixes to me.
+
+ **Thanks!**
+
+Notes <a name=Notes></a>
+-----
+
+ **[Erlang][]** is a programming language used to build massively scalable soft real-time systems with requirements on high availability. Some of its uses are in telecom, banking, e-commerce, computer telephony and instant messaging. Erlang's runtime system has built-in support for concurrency, distribution and fault tolerance. Erlang comes bundled with the Open Telecom Platform, OTP.
+
+[Erlang]: http://www.erlang.org/doc/
+
+ **[EDoc][]** is the Erlang program documentation generator. Inspired by the Javadoc tool for the Java programming language, EDoc is adapted to the conventions of the Erlang world, and has several features not found in Javadoc. Edoc is part of the Erlang/OTP distribution.
+
+[EDoc]: http://www.erlang.org/doc/apps/edoc/chapter.html
+
+ **[edown][]** is an EDoc extension for generating Github-flavored Markdown. It uses edoc-style commented Erlang sources to create markdown files from them.
+
+[edown]: https://github.com/uwiger/edown
+
+ **[Markdown][]** is a text-to-HTML conversion tool for web writers. Markdown allows you to write using an easy-to-read, easy-to-write plain text format, then convert it to structurally valid XHTML (or HTML).
+
+[Markdown]: http://daringfireball.net/projects/markdown/
+
+ **[sed][]** ('stream editor') is a Unix utility that parses text files and implements a programming language which can apply textual transformations to such files. It reads input files line by line (sequentially), applying the operation which has been specified via the command line (or a sed script), and then outputs the line. It is available today for most operating systems. There seems to be [one for Windows][winsed], too.
+
+[sed]: http://en.wikipedia.org/wiki/Sed
+[winsed]: http://gnuwin32.sourceforge.net/packages/sed.htm
+[sample]: https://github.com/Eonblast/Emysql/raw/master/README.md "This markdown file is translated alright by markedoc."
+
+
+License
+-------
+This script is free software. It comes without any warranty.
+
+Author
+------
+H. Diedrich <hd2010@eonblast.com>
+
+History
+-------
+
+02/18/11 - 0.3.2 - **edown**
+
+* integrated into edown
+
+02/05/11 - 0.3.1 - **more polish** - Linux, FreeBSD, Mac OS X
+
+* added weeding out of markdown anchor references (an md workaround)
+* added protection for & (but edoc still only accepts number codes)
+* fixed trip up by trailing spaces in underline headline format
+* checked commented out alternate code for code blocks and references.
+
+02/03/11 - 0.3 - **rough edges polished** - Linux, FreeBSD, Mac OS X
+
+* added doc for Linux use
+* added support for multi-line '[..]: ... "..."' references
+* added footnote signs and sepcial chars:
+* dagger, double dagger: (+), (++), stars: (\*), (\*\*), (\*\*\*)
+* superscript 1, 2, 3: (\*1), (\*2), (\*3), copyright (C), (R), (TM),
+* guillemots <<, >> and middle dot ::
+* added test batches etc/test-bsd.sh and etc/test-linux.sh
+* added css sample in samples/markedoc/what-you-could-see/
+* added classes for ``< li >'' list item tags for '[..]:...'-references
+* fixed italic and bold merker interference bullet points
+* eliminated [..]: part of '[..]:...'-references, flipping "..." to lead
+* dev: sample creation batch make_samples.sh added
+
+02/02/11 - 0.2 - **basics complete** - FreeBSD / Mac OS X
+
+* added support for === and --- headline format
+* fixed cutting off of last lines
+* fixed page-local anchor jumps
+* fixed space in javascript links
+* eliminated end-space requirement at end of '[..]:...'-style references.
+* eliminated need for echoing '@doc' first into edoc output file
+* added javascript title tag setting for '[..]:...'-style references.
+
+01/31/11 - 0.1 - **first release:** FreeBSD / Mac OS X
+
+[Requirements]: #Requirements
+[Status]: #Status
+[Notes]: #Notes
+[Test]: #Test
diff --git a/deps/edown/bin/markedoc.sed b/deps/edown/bin/markedoc.sed
new file mode 100644
index 0000000..7c12911
--- /dev/null
+++ b/deps/edown/bin/markedoc.sed
@@ -0,0 +1,338 @@
+# markedoc 0.3.2 - 02/18/11 H. Diedrich <hd2010@eonblast.com>
+# ----------------------------------------------------------
+# sed command file to convert markdown format to edoc format
+# Linux, FreeBSD and Mac OS X. Windows must install sed.
+# ----------------------------------------------------------
+# Use it to make a markdown readme file part of an edoc file:
+# FrBSD: sed -E -f <this file> <markdown file> > <edoc file>
+# MacOS: sed -E -f <this file> <markdown file> > <edoc file>
+# Linux: sed -r -f <this file> <markdown file> > <edoc file>
+# As only difference, Linux uses -r where the others use -E.
+# ----------------------------------------------------------
+# SAMPLE USE (FreeBSD / Mac OS X):
+# sed -E -f markedoc.sed README.markdown > overview.edoc
+# SAMPLE USE (Linux):
+# sed -r -f markedoc.sed README.markdown > overview.edoc
+# ----------------------------------------------------------
+# SAMPLE FILES:
+# https://github.com/hdiedrich/markedoc/tree/master/samples
+# SAMPLE RESULTS:
+# samples/what-you-should-see/ & samples/what-you-could-see/
+# ----------------------------------------------------------
+# SAMPLE WORKFLOW (change -r to -E for FreeBSD / Mac OS X):
+# sed -r -f markedoc.sed README.md > doc/README.edoc
+# erl -noshell -run edoc_run application "'myapp'" '"."' '[]'
+# ----------------------------------------------------------
+# REQUIREMENTS: sed, Erlang
+# Windows: http://gnuwin32.sourceforge.net/packages/sed.htm
+# ----------------------------------------------------------
+# STATUS: Pre-Beta.
+# It can reliably do nice things but likes to trip up EDoc.
+# With a bit of patience, and mostly with pretty clean md
+# markup, and some blank lines sometimes, most things work.
+# ----------------------------------------------------------
+# LICENSE: Free software, no warranties.
+# ----------------------------------------------------------
+# On edown: https://github.com/uwiger/edown
+# On Markdown: http://daringfireball.net/projects/markdown/
+# On Edoc: http://www.erlang.org/doc/apps/edoc/
+# On sed: http://www.gnu.org/software/sed/manual/sed.html
+# ----------------------------------------------------------
+# Repository: https://github.com/hdiedrich/markedoc/
+# Issues: https://github.com/hdiedrich/markedoc/issues
+# Please experiment and push your fixes. - Thanks!
+# ----------------------------------------------------------
+
+# **********************************************************
+# SCRIPT
+# **********************************************************
+# Ach, da kommt der Meister! Herr, die Not ist groß! ~~~
+# ~~~ Die ich rief, die Geister, Werd ich nun nicht los.
+# ----------------------------------------------------------
+# This is a sed script for use with -E/-r regexes & NOT -n.
+# s/<find>/<replace>/<flag> is the basic sed regex replace
+# command. sed normally works strictly line by line. 'N'
+# is used to join lines. 't' is a conditional branch. ':'
+# is a label. The order of replacement functions matters.
+# There are tabs in some patterns that may look like spaces.
+# See 'man sed' for more info. If you are a sed master,
+# your help making this better is much appreciated.
+# **********************************************************
+
+
+# as first line, make the @doc tag
+# --------------------------------
+1 i\
+@doc\
+
+# code sample blocks, trying to get them into one <pre> block
+# -----------------------------------------------------------
+# tabs are consumed for 'navigation'. sed is Turing complete.
+# inserted space is needed by edocs.
+# There are tabs in this pattern.
+/^ / {
+ # break ... on last line ('N' would exit)
+ $ b end_collect_with_last_line_hit
+ s/^ (.*)$/ \1/
+ # do ...
+ : do_collect
+ # append next line
+ N
+ # break ... if we are now into the last line
+ # (or the test below will eat the tab away.)
+ $ b end_collect_with_last_line_hit
+ # does the current last line start with a tab, too?
+ s/(\n) (.*)$/\1 \2/
+ # while: ... yes, then loop
+ t do_collect
+ # normal end of collect: got all indendet lines, plus one too many.
+ # -----------------------------------------------------------------
+ b normal_course
+ #
+ # Run into file end while looping
+ # -------------------------------
+ : end_collect_with_last_line_hit
+ # and does that last line start with a tab, too?
+ s/(\n) (.*)$/\1 \2/
+ s/^ (.*)$/ \1/
+ # yes, then we're done actually
+ t wrap_rest_and_done
+ # else, cut it off and such, as normal
+ # debug i\
+ # debug normal
+ #
+ : normal_course
+ # ... ok, we have multiple lines, and we have one line too much, back it all up.
+ h
+ # Handle the <pre> block to be (*):
+ # ---------------------------------
+ # cut off the last line, that doesn't belong and insert newlines
+ s/^(.*)(\n)(.*)$/\2\1\2/
+ # wrap all in the docs code tags ```...'''
+ s/^(.*)$/```\1'''/
+ # protect @ (for edoc related texts that explain @-tags). There is a tab in [].
+ s/([ \"\'\`]+@)/\1@/g
+ # send result to stdout
+ p
+ # Now make sure that that last line is not lost:
+ # ----------------------------------------------
+ # get stored back
+ g
+ # this time discard all but the last line, which is processed further
+ s/^.*\n(.*)$/\1/
+ # jump to end
+ b end_of_code_blocks_handling
+ #
+ # File End Remedy: wrap all to end and done.
+ # ------------------------------------------
+ : wrap_rest_and_done
+ # debug i\
+ # debug rest and done
+ # wrap all in the docs code tags ```...'''
+ s/^(.*)$/```\1'''/
+ # protect @ (for edoc related texts that explain @-tags). There is a tab in [].
+ s/([ \"\'\`]+@)/\1@/g
+ b end
+ #
+}
+
+:end_of_code_blocks_handling
+
+# robust alternate for code blocks: each tabbed line
+# --------------------------------------------------
+# If the above keeps being difficult, use this more robust
+# version. The main difference is simply that it will tag each
+# line separately. If you work out the right margins and
+# paddings for <pre> in your css file, that might give just as
+# nice results as the above. There are tabs in this pattern.
+# s/^ (.+)$/``` \1'''/
+
+# footnote signs
+# --------------
+# superscript 1
+s/\(\*1\)/\&#185;/g
+# superscript 2
+s/\(\*2\)/\&#178;/g
+# superscript 3
+s/\(\*3\)/\&#179;/g
+# dagger
+s/\(\+\)/\&#134;/g
+# double dagger
+s/\(\+\+\)/\&#135;/g
+# star
+s/\(\*\)/\&#42;/g
+# double star
+s/\(\*\*\)/\&#42;\&#42;/g
+# triple star
+s/\(\*\*\*\)/\&#42;\&#42;\&#42;/g
+
+# special chars
+# -------------
+# middle dot
+s/::/\&#183;/g
+# guillemot
+s/<</\&#171;/g
+s/>>/\&#187;/g
+
+
+# copy right
+# ----------
+s/\(c)/\&#169;/g
+s/\(C)/\&#169;/g
+s/\(R)/\&#174;/g
+s/\(r)/\&#174;/g
+s/\(tm)/\&#153;/g
+s/\(TM)/\&#153;/g
+
+# links
+# -----
+# external links
+s/\[([^]]+)\]\(([^)]+)\)/<a href=\"\2\">\1<\/a>/
+
+# references, '[..]:...'-style
+# ----------------------------
+# urls
+s/(\[([^]]+)\]): +\[?(http[s]?:\/\/[^.>" ]+\.[^>" ]+)\]? * *("([^"]+)") * *$/<li class="ref url"> \5:<a name="\2" id="\2" href="\3" target="_parent">\3<\/a><\/li>/
+# check next line "..." description
+/(\[([^]]+)\]): +\[?(http[s]?:\/\/[^.>" ]+\.[^>" ]+)\]? *$/ {
+ # get next line, if the current is not the last
+ $!N
+ # try two line spanning, or single (last) line
+ s/(\[([^]]+)\]): +\[?(http[s]?:\/\/[^.>" ]+\.[^>" ]+)\]? * *\n * *("([^"]*)") * *$/<li class="ref url"> \5:<a name="\2" id="\2" href="\3" target="_parent">\3<\/a><\/li>/
+ t double_line_url_done
+ # try one line only, rest to be saved
+ s/(\[([^]]+)\]): +\[?(http[s]?:\/\/[^.>" ]+\.[^>" ]+)\]? * *(\n)/<li class="ref url"> <a name="\2" id="\2" href="\3" target="_parent">\3<\/a><\/li>\4/
+ t double_line_url_done
+ # case of last line, single, no "..." description
+ s/(\[([^]]+)\]): +\[?(http[s]?:\/\/[^.>" ]+\.[^>" ]+)\]? * *$/<li class="ref url"> <a name="\2" id="\2" href="\3" target="_parent">\3<\/a><\/li>/
+ : double_line_url_done
+ # print out up to first \n, delete, start from top with the rest
+ P
+ D
+}
+
+# email addresses
+s/(\[([^]]+)\]): +<?([^@>" ]+@[^.>" ]+\.[^>" ]+)>? * *("([^"]+)") * *$/<li class="ref email"> \5:<a name="\2" id="\2" href="mailto:\3">\3<\/a><\/li>/
+# check next line "..." description
+/(\[([^]]+)\]): +<?([^@>" ]+@[^.>" ]+\.[^>" ]+)>? * *("([^"]+)")? * *$/ {
+ # get next line, if the current is not the last
+ $!N
+ # try two line spanning, or single (last) line
+ s/(\[([^]]+)\]): +<?([^@>" ]+@[^.>" ]+\.[^>" ]+)>? * *\n * *("([^"]+)") * *$/<li class="ref email"> \5:<a name="\2" id="\2" href="mailto:\3">\3<\/a><\/li>/
+ t double_line_mail_done
+ # try one line only, rest to be saved
+ s/(\[([^]]+)\]): +<?([^@>" ]+@[^.>" ]+\.[^>" ]+)>? * *(\n)/<li class="ref email"> <a name="\2" id="\2" href="mailto:\3">\3<\/a><\/li>\4/
+ t double_line_mail_done
+ # case of last line, single, no "..." description
+ s/(\[([^]]+)\]): +<?([^@>" ]+@[^.>" ]+\.[^>" ]+)>? * *$/<li class="ref email"> <a name="\2" id="\2" href="mailto:\3">\3<\/a><\/li>/
+ : double_line_mail_done
+ # print out up to first \n, delete, start from top with the rest
+ P
+ D
+}
+
+# smart reference for the [x]: ... format, jumping right to the referenced page.
+# ------------------------------------------------------------------------------
+s/\[([^]]+)\]\[\]/<a href="javascript:goto('\1')" onMouseOver="this.title=url('\1')">\1<\/a>/g
+s/\[([^]]+)\]\[([^]]+)\]/<a href="javascript:goto('\2')" onMouseOver="this.title=url('\2')">\1<\/a>/g
+
+# robust alternate reference for the [x]: ... format, jumping to footnote.
+# ------------------------------------------------------------------------
+# If you don't like the javascript tags, comment out the previous 'smart'
+# reference patterns and uncomment these.
+# s/\[([^]]+)\]\[\]/<a href="#\1">\1<\/a>/g
+# s/\[([^]]+)\]\[([^]]+)\]/<a href="#\2">\1<\/a>/g
+
+# headlines by #
+# --------------
+# h1 demoted to h2 as h1 is reserved in edoc
+s/^####(.+)$/====\1 ====/
+s/^###(.+)$/===\1 ===/
+s/^##(.+)$/==\1 ==/
+s/^#(.+)$/==\1 ==/
+
+# italics, bold
+# -------------
+s/\*\*([^*]+)\*\*/<b>\1<\/b>/g
+s/\*([^*]+)\*/<em>\1<\/em>/g
+
+# bullet points
+# -------------
+# edoc must see closing </li>
+s/^\*(.+)$/<li>\1<\/li>/
+
+# emails, urls
+# ------------
+s/<([^aA][^@>]+@[^.>]+.[^>]+)>/<a href=\"mailto:\1\">\1<\/a>/
+s/<(http[s]?:\/\/[^.>]+.[^>]+)>/<a href=\"\1\">\1<\/a>/
+
+# line breaks
+# -----------
+s/ $/<br \/>/
+
+# single backticks
+# ----------------
+# make code quotes
+s/`([^`]+)`/<code>\1<\/code>/g
+
+# protect @
+# ---------
+# leading space or tab indicates use as code sample for, well, edoc
+# itself most likely, so escape it.
+s/([ \"\'\`]+@)/\1@/g
+
+# headlines by underline === or ---
+# ---------------------------------
+# demoted to h2 and h3, as h1 is reserved in edoc
+{
+ # don't check this for the last line ('N' would exit)
+ $ b skip_alt_headlines
+ # get next line
+ N
+ # contract === with previous to headline h2
+ s/^(.+)\n=+ *$/== \1 ==/
+ # if substitution took place, goto ...
+ t substi
+ # contract --- with previous to headline h2
+ s/^(.+)\n-+ *$/=== \1 ===/g
+ # if substitution took place, goto ...
+ t substi
+ # no substitution: print the previous line and start with latest from top
+ # -----------------------------------------------------------------------
+ # store the two lines we have now, one is the one formatting is done with
+ # the next is the fresh one we just pulled.
+ h
+ # cut off the last line, print the ready formatted one
+ P
+ D
+ # and this is the goto for successful headline substitutions above:
+ :substi
+}
+
+:skip_alt_headlines
+:end
+
+# at the bottom, add JS for the 'smart' direct jump
+# -------------------------------------------------
+# to a reference url in trailing '[]:...'-notation
+$ a\
+<script>\
+// Jump directly to a referenced url given in trailing '[]:...'-notation\
+function goto(tag) { parent.document.location.href = url(tag); }\
+function url(tag) { var o=document.getElementById(tag); return o ? o.href : '#'+tag; }\
+</script>
+
+# debugger stable
+# ---------------
+# i\
+# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+# p
+# i\
+# <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+
+# -----------------------------------------------------------------
+# t,b "In most cases, use of these commands indicates that you are
+# probably better off programming in something like awk or Perl."
+# sed manual: http://www.gnu.org/software/sed/manual/sed.html
+# -----------------------------------------------------------------
+# 'powered by Eonblast' http://www.eonblast.com - all the new tech \ No newline at end of file
diff --git a/deps/edown/doc/README.md b/deps/edown/doc/README.md
new file mode 100644
index 0000000..37dba3f
--- /dev/null
+++ b/deps/edown/doc/README.md
@@ -0,0 +1,180 @@
+
+
+# Edown - Markdown generated from Edoc #
+
+Copyright (c) 2014 Ulf Wiger
+
+
+__Authors:__ [`ulf@wiger.net`](mailto:ulf@wiger.net).
+
+
+Status:
+------
+More-or-less readable Markdown can be generated.
+A doclet needs to be written that also creates
+a markdown-based index and overview. Currently, the
+edoc_doclet creates an index.html and overview.html,
+which do not point to the .md files.
+
+To generate markdown edoc, run:
+
+```
+
+edoc:application(App, [{doclet, edown_doclet} | OtherOpts]).
+
+```
+
+The `edown_xmerl` module is used as an xmerl export module.
+It converts xmerl's "simple xml" to Markdown syntax. Note that
+GH-flavored Markdown allows HTML markup (at least common tags),
+but doesn't expand markdown markup inside HTML markup, so the
+`edown_xmerl` module has to know the context in which it operates.
+
+Top-level README
+----------------
+
+Using the option `{top_level_readme, {File, BaseHref}}`, a github-friendly
+`README.md` in the top directory can be generated from the `overview.edoc`.
+This file is the same as the `doc/README.md` file already generated,
+but with relative links corrected (using `BaseHref`) so that they actually
+work. This step is needed since Github doesn't support relative paths in
+Markdown links.
+
+Example:
+
+`{top_level_readme, {"./README.md", "http://github.com/uwiger/edown"}}`
+
+The conversion function will fetch the current branch name from git,
+and fail if it cannot do so.
+
+It is also possible to add the branch information specifically:
+`{top_level_readme, {File, BaseHref, Branch}}`, although this shouldn't be
+necessary as long as edown can derive the branch name from git.
+
+Using Atlassian Stash as target
+-------------------------------
+
+The option `{edown_target, github | stash}` can be used to control which
+is the intended host repository. This affects how links are rewritten in
+order to find related files and stay on the correct branch.
+
+The default value is `github`.
+
+Note that at the moment, the
+[Markdown viewer plugin](https://bitbucket.org/atlassianlabs/stash-markdown-viewer-plugin) will be needed in order to render the generated documentation
+as Markdown on Stash.
+Github customizations
+=====================
+`pre` tags are converted into github "fenced" code blocks, i.e.
+
+```
+```...'''
+```
+
+. If language-specific syntax highlighting is desired,
+this can be achieved by adding a 'lang' attribute, e.g.
+
+```
+<pre lang="erlang">
+incr(X) ->
+ %% This should be formatted with Erlang syntax highlighting
+ X + 1.
+</pre>
+```
+
+which should format like this:
+
+```erlang
+
+incr(X) ->
+ %% This should be formatted with Erlang syntax highlighting
+ X + 1.
+
+```
+
+Rebar customizations
+====================
+A set of escripts can be found under
+[edown/priv/scripts/](priv/scripts/), which
+can be used to customize the `rebar` built process. The
+[rebar.config.script](priv/scripts/rebar.config.script)
+file should be copied into your application, next to `rebar.config`.
+It will sense if `doc` is a current target, and will then include
+`edown` in the `deps`; otherwise, it removes it. This way, you will
+not have to pull down `edown` unless you really want to build the
+docs. It will also locate edown along your path, in which case
+it doesn't need to pull it down again.
+
+The script will also start the `inets` application, so that you
+can include URLs as part of a `doc_path` option (see below).
+
+Links to other EDown-generated docs
+===================================
+There is a way to configure Edoc/Edown to get URLs right even
+when linking to other Edown-generated docs on Github.
+
+First, you need to specify paths to the `edoc-info` files for
+each repository as part of `edoc_opts` in your rebar.config, e.g.
+
+```
+ {doc_path, ["http://raw.github.com/uwiger/setup/master/doc",
+ "http://raw.github.com/uwiger/gproc/master/doc"]}
+```
+
+Note (1) that we use "http:://...", not "https://...", since
+Edoc doesn't recognize the latter. Also note that we use URLs
+to the raw files. This is for Edoc as it fetches the `edoc-info`
+files. Edown will detect and rewrite such links in the generated
+output, since "raw" links wouldn't work for the markdown files.
+
+The next issue is that Edoc uses httpd_client to fetch the
+`edoc-info` files, which requires `inets` to be started. To
+further complicate matters, `ssl` (and thus `crypto`, 'asn1' and
+`public_key`) must also be started, since Github will
+redirect to https.
+
+One way to solve this is to use the escripts found under
+`edown/priv/scripts`.
+
+NOTE
+====
+EDoc provides a plugin structure, so that one may specify own
+layout modules, export modules, and doclets. However, there is
+some overlap esp. between the layout and doclet modules, and
+several functions are expected to produce files on their own.
+This causes a problem for EDown, since it cannot handle frames.
+Instead, it would probably like to create one overview file with
+different sections. It would have been better to have a framework
+where some plugin functions identify the different files to be
+written, and the outline of each, other plugins convert to suitable
+content representation (e.g. HTML or Markdown), and EDoc then
+writes the files necessary.
+
+For now, EDown focuses on producing reasonable Markdown, rather
+than complying fully with the plugin framework. That is, the
+edown_doclet module will not go out of its way to function together
+with any other layout module than edown_layout, and vice versa.
+
+markedoc
+========
+
+The sed script bin/markedoc works in the opposite direction and converts
+your `README.md` to an `EDoc` file.
+
+See [bin/MARKEDOC-README.md](bin/MARKEDOC-README.md).
+
+**FreeBSD, Mac OS X**`$ sed -E -f markedoc.sed <markdown file> > <edoc file>`
+
+**Linux**`$ sed -r -f markedoc.sed <markdown file> > <edoc file>`
+
+
+## Modules ##
+
+
+<table width="100%" border="0" summary="list of modules">
+<tr><td><a href="edown_doclet.md" class="module">edown_doclet</a></td></tr>
+<tr><td><a href="edown_layout.md" class="module">edown_layout</a></td></tr>
+<tr><td><a href="edown_lib.md" class="module">edown_lib</a></td></tr>
+<tr><td><a href="edown_make.md" class="module">edown_make</a></td></tr>
+<tr><td><a href="edown_xmerl.md" class="module">edown_xmerl</a></td></tr></table>
+
diff --git a/deps/edown/doc/edoc-info b/deps/edown/doc/edoc-info
new file mode 100644
index 0000000..87b2449
--- /dev/null
+++ b/deps/edown/doc/edoc-info
@@ -0,0 +1,4 @@
+%% encoding: UTF-8
+{application,edown}.
+{packages,[]}.
+{modules,[edown_doclet,edown_layout,edown_lib,edown_make,edown_xmerl]}.
diff --git a/deps/edown/doc/edown_doclet.md b/deps/edown/doc/edown_doclet.md
new file mode 100644
index 0000000..fcf0178
--- /dev/null
+++ b/deps/edown/doc/edown_doclet.md
@@ -0,0 +1,140 @@
+
+
+# Module edown_doclet #
+* [Description](#description)
+* [Function Index](#index)
+* [Function Details](#functions)
+
+
+EDoc Doclet module for producing Markdown.
+Copyright (c) 2014 Ulf Wiger
+
+__Authors:__ Ulf Wiger ([`ulf@wiger.net`](mailto:ulf@wiger.net)).
+<a name="index"></a>
+
+## Function Index ##
+
+
+<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#run-2">run/2</a></td><td>Main doclet entry point.</td></tr></table>
+
+
+<a name="functions"></a>
+
+## Function Details ##
+
+<a name="run-2"></a>
+
+### run/2 ###
+
+
+<pre><code>
+run(Command::<a href="#type-doclet_gen">doclet_gen()</a> | <a href="#type-doclet_toc">doclet_toc()</a>, Ctxt::<a href="#type-edoc_context">edoc_context()</a>) -&gt; ok
+</code></pre>
+<br />
+
+
+Main doclet entry point.
+
+
+
+Also see [`//edoc/edoc:layout/2`](http://www.erlang.org/doc/man/edoc.html#layout-2) for layout-related options, and
+[`//edoc/edoc:get_doc/2`](http://www.erlang.org/doc/man/edoc.html#get_doc-2) for options related to reading source
+files.
+
+
+Options:
+
+
+
+<dt><code>{file_suffix, string()}</code>
+</dt>
+
+
+
+
+<dd>Specifies the suffix used for output files. The default value is
+<code>".md"</code>.
+</dd>
+
+
+
+
+<dt><code>{hidden, bool()}</code>
+</dt>
+
+
+
+
+<dd>If the value is <code>true</code>, documentation of hidden modules and
+functions will also be included. The default value is <code>false</code>.
+</dd>
+
+
+
+
+<dt><code>{overview, <a href="http://www.erlang.org/doc/man/edoc.html#type-filename">//edoc/edoc:filename()</a>}</code>
+</dt>
+
+
+
+
+<dd>Specifies the name of the overview-file. By default, this doclet
+looks for a file <code>"overview.edoc"</code> in the target directory.
+</dd>
+
+
+
+
+<dt><code>{private, bool()}</code>
+</dt>
+
+
+
+
+<dd>If the value is <code>true</code>, documentation of private modules and
+functions will also be included. The default value is <code>false</code>.
+</dd>
+
+
+
+
+<dt><code>{stylesheet, string()}</code>
+</dt>
+
+
+
+
+<dd>Specifies the URI used for referencing the stylesheet. The
+default value is <code>"stylesheet.css"</code>. If an empty string is
+specified, no stylesheet reference will be generated.
+</dd>
+
+
+
+
+<dt><code>{stylesheet_file, <a href="http://www.erlang.org/doc/man/edoc.html#type-filename">//edoc/edoc:filename()</a>}</code>
+</dt>
+
+
+
+
+<dd>Specifies the name of the stylesheet file. By default, this
+doclet uses the file <code>"stylesheet.css"</code> in the <code>priv</code>
+subdirectory of the EDoc installation directory. The named file
+will be copied to the target directory.
+</dd>
+
+
+
+
+<dt><code>{title, string()}</code>
+</dt>
+
+
+
+
+<dd>Specifies the title of the overview-page.
+</dd>
+
+
+
diff --git a/deps/edown/doc/edown_layout.md b/deps/edown/doc/edown_layout.md
new file mode 100644
index 0000000..10c00c1
--- /dev/null
+++ b/deps/edown/doc/edown_layout.md
@@ -0,0 +1,141 @@
+
+
+# Module edown_layout #
+* [Description](#description)
+* [Function Index](#index)
+* [Function Details](#functions)
+
+
+Markdown layout module for EDoc.
+Copyright (c) 2014 Ulf Wiger
+
+__Authors:__ Ulf Wiger ([`ulf@wiger.net`](mailto:ulf@wiger.net)).
+<a name="description"></a>
+
+## Description ##
+ Derived from `edoc_layout`, which is part of the Erlang/OTP application EDoc.
+The module is intended to be used together with edoc.<a name="index"></a>
+
+## Function Index ##
+
+
+<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#markdown-3">markdown/3</a></td><td></td></tr><tr><td valign="top"><a href="#module-2">module/2</a></td><td>The layout function.</td></tr><tr><td valign="top"><a href="#overview-2">overview/2</a></td><td></td></tr><tr><td valign="top"><a href="#package-2">package/2</a></td><td></td></tr><tr><td valign="top"><a href="#type-1">type/1</a></td><td></td></tr></table>
+
+
+<a name="functions"></a>
+
+## Function Details ##
+
+<a name="markdown-3"></a>
+
+### markdown/3 ###
+
+`markdown(Title, CSS, Body) -> any()`
+
+
+<a name="module-2"></a>
+
+### module/2 ###
+
+`module(Element, Options) -> any()`
+
+
+The layout function.
+
+
+Options to the standard layout:
+
+
+
+<dt><code>{index_columns, integer()}</code>
+</dt>
+
+
+
+
+<dd>Specifies the number of column pairs used for the function
+index tables. The default value is 1.
+</dd>
+
+
+
+
+<dt><code>{pretty_printer, atom()}</code>
+</dt>
+
+
+
+
+<dd>Specifies how types and specifications are pretty printed.
+If the value <code>erl_pp</code> is specified the Erlang pretty printer
+(the module <code>erl_pp</code>) will be used. The default is to do
+no pretty printing which implies that lines can be very long.
+</dd>
+
+
+
+
+<dt><code>{stylesheet, string()}</code>
+</dt>
+
+
+
+
+<dd>Specifies the URI used for referencing the stylesheet. The
+default value is <code>"stylesheet.css"</code>. If an empty string is
+specified, no stylesheet reference will be generated.
+</dd>
+
+
+
+
+<dt><code>{sort_functions, boolean()}</code>
+</dt>
+
+
+
+
+<dd>If <code>true</code>, the detailed function descriptions are listed by
+name, otherwise they are listed in the order of occurrence in
+the source file. The default value is <code>true</code>.
+</dd>
+
+
+
+
+<dt><code>{xml_export, Module::atom()}</code>
+</dt>
+
+
+
+
+<dd>Specifies an <a href="http://www.erlang.org/doc/man/index.html" target="_top"><code>xmerl</code></a> callback module to be
+used for exporting the documentation. See <a href="http://www.erlang.org/doc/man/xmerl.html#export_simple_content-2"><code>//xmerl/xmerl:export_simple_content/2</code></a> for details.
+</dd>
+
+
+
+
+
+__See also:__ [//edoc/edoc:layout/2](http://www.erlang.org/doc/man/edoc.html#layout-2), [edown_doclet:layout/2](edown_doclet.md#layout-2).
+<a name="overview-2"></a>
+
+### overview/2 ###
+
+`overview(E, Options) -> any()`
+
+
+<a name="package-2"></a>
+
+### package/2 ###
+
+`package(E, Options) -> any()`
+
+
+<a name="type-1"></a>
+
+### type/1 ###
+
+`type(E) -> any()`
+
+
diff --git a/deps/edown/doc/edown_lib.md b/deps/edown/doc/edown_lib.md
new file mode 100644
index 0000000..4db33d3
--- /dev/null
+++ b/deps/edown/doc/edown_lib.md
@@ -0,0 +1,45 @@
+
+
+# Module edown_lib #
+* [Description](#description)
+* [Function Index](#index)
+* [Function Details](#functions)
+
+
+Markdown for EDoc - common support functions.
+Copyright (c) 2014 Ulf Wiger
+
+__Authors:__ Ulf Wiger ([`ulf@wiger.net`](mailto:ulf@wiger.net)).
+<a name="index"></a>
+
+## Function Index ##
+
+
+<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#export-1">export/1</a></td><td></td></tr><tr><td valign="top"><a href="#get_attrval-2">get_attrval/2</a></td><td></td></tr><tr><td valign="top"><a href="#redirect_uri-1">redirect_uri/1</a></td><td></td></tr></table>
+
+
+<a name="functions"></a>
+
+## Function Details ##
+
+<a name="export-1"></a>
+
+### export/1 ###
+
+`export(Data) -> any()`
+
+
+<a name="get_attrval-2"></a>
+
+### get_attrval/2 ###
+
+`get_attrval(Name, XmlElement) -> any()`
+
+
+<a name="redirect_uri-1"></a>
+
+### redirect_uri/1 ###
+
+`redirect_uri(XmlElement) -> any()`
+
+
diff --git a/deps/edown/doc/edown_make.md b/deps/edown/doc/edown_make.md
new file mode 100644
index 0000000..09f30f4
--- /dev/null
+++ b/deps/edown/doc/edown_make.md
@@ -0,0 +1,82 @@
+
+
+# Module edown_make #
+* [Function Index](#index)
+* [Function Details](#functions)
+
+
+<a name="index"></a>
+
+## Function Index ##
+
+
+<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#from_script-1">from_script/1</a></td><td>Reads ConfigFile and calls <a href="edoc.md#application-3"><code>edoc:application/3</code></a></td></tr><tr><td valign="top"><a href="#main-1">main/1</a></td><td>Escript entry point for building edown (or edoc) documentation.</td></tr></table>
+
+
+<a name="functions"></a>
+
+## Function Details ##
+
+<a name="from_script-1"></a>
+
+### from_script/1 ###
+
+
+<pre><code>
+from_script(Config::ConfigFile) -&gt; ok | {error, Reason}
+</code></pre>
+<br />
+
+
+Reads ConfigFile and calls [`edoc:application/3`](edoc.md#application-3)
+
+
+
+The ConfigFile will be read using [`file:script/1`](file.md#script-1), and should return
+`{App, Dir, Options}`, as required by [`edoc:application/3`](edoc.md#application-3).
+
+
+This function does not manage dependencies. It is simply a wrapper around
+[`edoc:application/3`](edoc.md#application-3).
+<a name="main-1"></a>
+
+### main/1 ###
+
+
+<pre><code>
+main(Args::[Config]) -&gt; no_return()
+</code></pre>
+<br />
+
+
+Escript entry point for building edown (or edoc) documentation
+
+
+
+Usage: edown_make -config ConfigFile [-pa P] [-pz P]
+
+
+
+Calls [from_script(ConfigFile)](#from_script-1) and then terminates,
+with a normal or non-normal exit code, depending on the outcome.
+
+
+
+Make sure `$EDOWN/edown_make` is runnable, and in the command path, and
+that the edown BEAM files are in the Erlang path (e.g. using $ERL_LIBS).
+The `edown_make` escript also accepts `-pa P` and/or `-pz P` flags as a
+means of locating the edown byte code.
+
+
+
+Note, however, that the function `edoc_make:main/1` only expects the
+config file as an input argument, corresponding to
+
+
+
+`escript edoc_make.beam ConfigFile`
+
+
+(The reason for this is that if the beam file can be passed directly to
+the escript command, setting the path should also be doable that way).
+
diff --git a/deps/edown/doc/edown_xmerl.md b/deps/edown/doc/edown_xmerl.md
new file mode 100644
index 0000000..1ad3f3b
--- /dev/null
+++ b/deps/edown/doc/edown_xmerl.md
@@ -0,0 +1,49 @@
+
+
+# Module edown_xmerl #
+* [Function Index](#index)
+* [Function Details](#functions)
+
+Copyright (c) 2014 Ulf Wiger
+
+__Authors:__ Ulf Wiger ([`ulf@wiger.net`](mailto:ulf@wiger.net)).
+<a name="index"></a>
+
+## Function Index ##
+
+
+<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#%23element%23-5">'#element#'/5</a></td><td></td></tr><tr><td valign="top"><a href="#%23root%23-4">'#root#'/4</a></td><td></td></tr><tr><td valign="top"><a href="#%23text%23-1">'#text#'/1</a></td><td></td></tr><tr><td valign="top"><a href="#%23xml-inheritance%23-0">'#xml-inheritance#'/0</a></td><td></td></tr></table>
+
+
+<a name="functions"></a>
+
+## Function Details ##
+
+<a name="%23element%23-5"></a>
+
+### '#element#'/5 ###
+
+`#element#(Tag, Data, Attrs, Parents, E) -> any()`
+
+
+<a name="%23root%23-4"></a>
+
+### '#root#'/4 ###
+
+`#root#(Data, Attrs, X3, E) -> any()`
+
+
+<a name="%23text%23-1"></a>
+
+### '#text#'/1 ###
+
+`#text#(Text) -> any()`
+
+
+<a name="%23xml-inheritance%23-0"></a>
+
+### '#xml-inheritance#'/0 ###
+
+`#xml-inheritance#() -> any()`
+
+
diff --git a/deps/edown/doc/overview.edoc b/deps/edown/doc/overview.edoc
new file mode 100644
index 0000000..5542cc5
--- /dev/null
+++ b/deps/edown/doc/overview.edoc
@@ -0,0 +1,159 @@
+@author <ulf@wiger.net>
+@copyright 2014 Ulf Wiger
+
+@title Edown - Markdown generated from Edoc
+
+@doc
+Status:
+------
+More-or-less readable Markdown can be generated.
+A doclet needs to be written that also creates
+a markdown-based index and overview. Currently, the
+edoc_doclet creates an index.html and overview.html,
+which do not point to the .md files.
+
+To generate markdown edoc, run:
+
+<pre>
+edoc:application(App, [{doclet, edown_doclet} | OtherOpts]).
+</pre>
+
+The `edown_xmerl' module is used as an xmerl export module.
+It converts xmerl's "simple xml" to Markdown syntax. Note that
+GH-flavored Markdown allows HTML markup (at least common tags),
+but doesn't expand markdown markup inside HTML markup, so the
+`edown_xmerl' module has to know the context in which it operates.
+
+Top-level README
+----------------
+
+Using the option `{top_level_readme, {File, BaseHref}}', a github-friendly
+`README.md' in the top directory can be generated from the `overview.edoc'.
+This file is the same as the `doc/README.md' file already generated,
+but with relative links corrected (using `BaseHref') so that they actually
+work. This step is needed since Github doesn't support relative paths in
+Markdown links.
+
+Example:
+
+`{top_level_readme, {"./README.md", "http://github.com/uwiger/edown"}}'
+
+The conversion function will fetch the current branch name from git,
+and fail if it cannot do so.
+
+It is also possible to add the branch information specifically:
+`{top_level_readme, {File, BaseHref, Branch}}', although this shouldn't be
+necessary as long as edown can derive the branch name from git.
+
+Using Atlassian Stash as target
+-------------------------------
+
+The option `{edown_target, github | stash}' can be used to control which
+is the intended host repository. This affects how links are rewritten in
+order to find related files and stay on the correct branch.
+
+The default value is `github'.
+
+Note that at the moment, the
+<a href="https://bitbucket.org/atlassianlabs/stash-markdown-viewer-plugin">Markdown viewer plugin</a> will be needed in order to render the generated documentation
+as Markdown on Stash.
+
+Github customizations
+=====================
+`pre' tags are converted into github "fenced" code blocks, i.e.
+<pre>&#96;&#96;&#96;...'''</pre>. If language-specific syntax highlighting is desired,
+this can be achieved by adding a 'lang' attribute, e.g.
+
+<pre>&lt;pre lang="erlang"&gt;
+incr(X) ->
+ %% This should be formatted with Erlang syntax highlighting
+ X + 1.
+&lt;/pre&gt;</pre>
+
+which should format like this:
+
+<pre lang="erlang">
+incr(X) ->
+ %% This should be formatted with Erlang syntax highlighting
+ X + 1.
+</pre>
+
+
+Rebar customizations
+====================
+A set of escripts can be found under
+<a href="priv/scripts/">edown/priv/scripts/</a>, which
+can be used to customize the `rebar' built process. The
+<a href="priv/scripts/rebar.config.script">rebar.config.script</a>
+file should be copied into your application, next to `rebar.config'.
+It will sense if `doc' is a current target, and will then include
+`edown' in the `deps'; otherwise, it removes it. This way, you will
+not have to pull down `edown' unless you really want to build the
+docs. It will also locate edown along your path, in which case
+it doesn't need to pull it down again.
+
+The script will also start the `inets' application, so that you
+can include URLs as part of a `doc_path' option (see below).
+
+Links to other EDown-generated docs
+===================================
+There is a way to configure Edoc/Edown to get URLs right even
+when linking to other Edown-generated docs on Github.
+
+First, you need to specify paths to the `edoc-info' files for
+each repository as part of `edoc_opts' in your rebar.config, e.g.
+
+```{doc_path, ["http://raw.github.com/uwiger/setup/master/doc",
+ "http://raw.github.com/uwiger/gproc/master/doc"]}'''
+
+Note (1) that we use "http:://...", not "https://...", since
+Edoc doesn't recognize the latter. Also note that we use URLs
+to the raw files. This is for Edoc as it fetches the `edoc-info'
+files. Edown will detect and rewrite such links in the generated
+output, since "raw" links wouldn't work for the markdown files.
+
+The next issue is that Edoc uses httpd_client to fetch the
+`edoc-info' files, which requires `inets' to be started. To
+further complicate matters, `ssl' (and thus `crypto', 'asn1' and
+`public_key') must also be started, since Github will
+redirect to https.
+
+One way to solve this is to use the escripts found under
+`edown/priv/scripts'.
+
+NOTE
+====
+EDoc provides a plugin structure, so that one may specify own
+layout modules, export modules, and doclets. However, there is
+some overlap esp. between the layout and doclet modules, and
+several functions are expected to produce files on their own.
+This causes a problem for EDown, since it cannot handle frames.
+Instead, it would probably like to create one overview file with
+different sections. It would have been better to have a framework
+where some plugin functions identify the different files to be
+written, and the outline of each, other plugins convert to suitable
+content representation (e.g. HTML or Markdown), and EDoc then
+writes the files necessary.
+
+For now, EDown focuses on producing reasonable Markdown, rather
+than complying fully with the plugin framework. That is, the
+edown_doclet module will not go out of its way to function together
+with any other layout module than edown_layout, and vice versa.
+
+markedoc
+========
+
+The sed script bin/markedoc works in the opposite direction and converts
+your `README.md' to an `EDoc' file.
+
+See <a href="bin/MARKEDOC-README.md">bin/MARKEDOC-README.md</a>.
+
+**FreeBSD, Mac OS X**
+ `$ sed -E -f markedoc.sed <markdown file> > <edoc file>'
+
+**Linux**
+ `$ sed -r -f markedoc.sed <markdown file> > <edoc file>'
+
+
+
+@end \ No newline at end of file
diff --git a/deps/edown/edown_make b/deps/edown/edown_make
new file mode 100755
index 0000000..f4100d1
--- /dev/null
+++ b/deps/edown/edown_make
@@ -0,0 +1,42 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+
+%% @spec main() -> no_return()
+%% @doc Escript for building edown (or edoc) documentation
+%%
+%% Usage: edown_make ConfigFile
+%%
+%% The ConfigFile will be read using {@link file:script/1}, and should return
+%% `{App, Dir, Options}', as required by {@link edoc:application/3}.
+%%
+%% This function does not manage dependencies. It is simply a wrapper around
+%% {@link edoc:application/3}.
+%% @end
+%%
+main(Args) ->
+ Config = parse_args(Args),
+ edown_make:main([Config]).
+
+parse_args(Args) ->
+ parse_args(Args, "edown.config").
+
+parse_args([], Config) ->
+ Config;
+parse_args(["-config", Config|Args], _) ->
+ parse_args(Args, Config);
+parse_args(["-pa", P|Args], Config) ->
+ code:add_patha(P),
+ parse_args(Args, Config);
+parse_args(["-pz", P|Args], Config) ->
+ code:add_pathz(P),
+ parse_args(Args, Config);
+parse_args(Args, _) ->
+ io:fwrite("Unknown options: ~p~n", [Args]),
+ usage(),
+ halt(1).
+
+usage() ->
+ Full = escript:script_name(),
+ Base = filename:basename(Full),
+ io:fwrite("~s~nUsage: ~s -config Config [-pa Path] [-pz Path]~n",
+ [Full, Base]).
diff --git a/deps/edown/make_doc b/deps/edown/make_doc
new file mode 100755
index 0000000..8ba01c2
--- /dev/null
+++ b/deps/edown/make_doc
@@ -0,0 +1,49 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+
+%% edown is designed to work well with rebar, but in order to use edown
+%% to document itself, we need to explicitly set the path to ebin/, so
+%% that we pick up the newly built edown doclet. I haven't found a way
+%% to do this with 'rebar doc'.
+%%
+main([]) ->
+ code:add_patha("ebin"),
+ Default = "uwiger",
+ U = case os:getenv("TGT") of
+ [] -> Default;
+ false -> Default;
+ Str -> Str
+ end,
+ TopURL = top_url(U),
+ io:fwrite("Making edown docs for [~s]...~n", [TopURL]),
+ R = edoc:application(edown, ".",
+ [{doclet, edown_doclet},
+ {source_path, ["src"]},
+ {app_default,"http://www.erlang.org/doc/man"},
+ {stylesheet, ""}, % don't copy stylesheet.css
+ {image, ""}, % don't copy erlang.png
+ {edown_target, target()},
+ {top_level_readme,
+ {"./README.md", TopURL}}]),
+ case R of
+ ok ->
+ halt();
+ Err ->
+ io:fwrite("~p~n", [Err]),
+ halt(1)
+ end.
+
+top_url(User) ->
+ case os:getenv("EDOWN_TOP_URL") of
+ false ->
+ "http://github.com/" ++ User ++ "/edown";
+ URL ->
+ URL
+ end.
+
+target() ->
+ case os:getenv("EDOWN_TARGET") of
+ false -> github;
+ "github" -> github;
+ "stash" -> stash
+ end.
diff --git a/deps/edown/priv/scripts/check_edown.script b/deps/edown/priv/scripts/check_edown.script
new file mode 100644
index 0000000..636f8bb
--- /dev/null
+++ b/deps/edown/priv/scripts/check_edown.script
@@ -0,0 +1,39 @@
+%% -*- erlang -*-
+%%
+%% This helper script checks if doc is being built, otherwise removes edoc dep.
+%% To build docs, call `rebar get-deps doc`
+%% Assumes that the rebar config is bound to CONFIG
+
+[_|Args] = init:get_plain_arguments(). % rebar 'commands' and options
+case lists:member("doc", Args) of
+ false ->
+ {ok,C1} = file:script(filename:join(filename:dirname(SCRIPT),
+ "remove_deps.script"),
+ [{'CONFIG', CONFIG}, {'DEPS', [edown]}]),
+ C1;
+ true ->
+ %% We actually only need to start inets if we have a doc path with http URIS
+ application:start(crypto),
+ application:start(asn1),
+ application:start(public_key),
+ application:start(ssl),
+ application:start(inets),
+ case code:lib_dir(edown) of
+ {error, bad_name} ->
+ io:fwrite("cannot find edown~n", []),
+ D = {edown, ".*",
+ {git, "git://github.com/uwiger/edown.git", "HEAD"}},
+ Deps = case lists:keyfind(deps, 1, CONFIG) of
+ false -> [D];
+ {_, Ds} ->
+ case lists:keymember(edown, 1, Ds) of
+ true -> Ds;
+ false -> [D|Ds]
+ end
+ end,
+ lists:keystore(deps, 1, CONFIG, {deps, Deps});
+ _ ->
+ io:fwrite("edown in path~n", []),
+ CONFIG
+ end
+end.
diff --git a/deps/edown/priv/scripts/rebar.config.script b/deps/edown/priv/scripts/rebar.config.script
new file mode 100644
index 0000000..200a9da
--- /dev/null
+++ b/deps/edown/priv/scripts/rebar.config.script
@@ -0,0 +1,18 @@
+%% -*- erlang -*-
+Script = fun(D,S,Vs) ->
+ Scr = filename:join(D, S),
+ case file:script(Scr, orddict:store('SCRIPT', Scr, Vs)) of
+ {ok, Res} -> Res;
+ {error,_} = Err ->
+ io:fwrite("Error evaluating script ~s~n", [S]),
+ Err
+ end
+ end.
+CFG1 = case os:getenv("REBAR_DEPS") of
+ false -> CONFIG;
+ [] -> CONFIG;
+ Dir ->
+ lists:keystore(deps_dir, 1, CONFIG, {deps_dir, Dir})
+ end.
+Priv = filename:join(filename:dirname(SCRIPT), "priv").
+Script(Priv, "check_edown.script", [{'CONFIG', CFG1}]).
diff --git a/deps/edown/priv/scripts/remove_deps.script b/deps/edown/priv/scripts/remove_deps.script
new file mode 100644
index 0000000..7e62122
--- /dev/null
+++ b/deps/edown/priv/scripts/remove_deps.script
@@ -0,0 +1,18 @@
+%% -*- erlang -*-
+%%
+%% Assumes the following bound variables:
+%% CONFIG - a rebar.config options list
+%% DEPS :: [atom()] - a list of deps to remove
+case lists:keyfind(deps, 1, CONFIG) of
+ {_, Deps0} ->
+ Deps1 = lists:filter(
+ fun(D) when is_atom(D) ->
+ not lists:member(D, DEPS);
+ (D) when is_tuple(D) ->
+ not lists:member(element(1,D), DEPS)
+ end, Deps0),
+ lists:keyreplace(deps, 1, CONFIG, {deps, Deps1});
+ false ->
+ CONFIG
+end.
+
diff --git a/deps/edown/rebar b/deps/edown/rebar
new file mode 100755
index 0000000..bc0e53b
--- /dev/null
+++ b/deps/edown/rebar
Binary files differ
diff --git a/deps/edown/rebar.config b/deps/edown/rebar.config
new file mode 100644
index 0000000..a5268b8
--- /dev/null
+++ b/deps/edown/rebar.config
@@ -0,0 +1,16 @@
+%% -*- erlang -*-
+{erl_opts, [debug_info]}.
+{xref_checks, [undefined_function_calls]}.
+
+{cover_enabled, true}.
+{eunit_opts, [verbose]}.
+
+{clean_files, ["*~","*/*~","ebin/*.beam"]}.
+
+%% This is a reasonable set of options to use for edown, but edown
+%% doesn't actually use rebar to make its own docs.
+%% {edoc_opts, [{doclet, edown_doclet},
+%% {source_path, ["src", "test"]},
+%% {stylesheet, ""},
+%% {image, ""},
+%% {app_default,"http://www.erlang.org/doc/man"}]}.
diff --git a/deps/edown/samples/README.md b/deps/edown/samples/README.md
new file mode 100644
index 0000000..8eaf3ef
--- /dev/null
+++ b/deps/edown/samples/README.md
@@ -0,0 +1 @@
+At this point, there are samples in this directory only for the 'markedoc' script, which complements edoc by allowing for converting markdown format to edoc format.
diff --git a/deps/edown/samples/markedoc/SAMPLE1.md b/deps/edown/samples/markedoc/SAMPLE1.md
new file mode 100644
index 0000000..52c99fe
--- /dev/null
+++ b/deps/edown/samples/markedoc/SAMPLE1.md
@@ -0,0 +1,313 @@
+# SAMPLE 1: Emysql Readme of Jan 2011
+```
+ --------------------------------------------------------------
+| THIS TEXT IS USED AS A SAMPLE TO ILLUSTRATE MARKEDOC USAGE. |
+| If you see this in your browser, you succeeded compiling it |
+| from markdown into an edoc. As you see it's quite complex |
+| and there is no 'cheating' involved. |
+ --------------------------------------------------------------
+'''
+
+Erlang MySQL driver, based on a rewrite at Electronic Arts(tm). Supports prepared statements and stored procedures. For [samples][] and [docs][] see below.
+
+While you can use mysql via ODBC, using a driver like Emysql should perform better.
+
+This is a continuation fork of [emysql][1] with [fixes][], [updates][], more [docs][] and [samples][]. [emysql][1] is a clean rewrite of [erlang-mysql-driver][2].
+
+<hr/>
+
+ **<<Which fork should I use?>>** See [history][].
+ **<<Who used this fork?>>** Electronic Arts.
+ **<<How do I ...?>>** See [samples][].
+
+ **Download:** <https://github.com/Eonblast/Emysql/archives/master>
+ **Repository:** <https://github.com/Eonblast/Emysql>
+ **Docs:** <http://eonblast.github.com/Emysql/>
+
+<hr/>
+
+## Contents
+
+* [History][]
+* [Usage][]
+* [Samples][]
+* [Links][]
+* [Todo][]
+* [License][]
+
+<hr/>
+
+## History
+
+Open Source Erlang MySQL driver efforts are currently a fractured matter, at least for the higher functionality. There are four main choices:
+
+* **Yxa**: The first Erlang MySQL driver seems to have been written in 2005 by [Magnus Ahltorp][ma] at the [Royal Institute of Technology][3]. It is the basis for the following two. The [original mysql driver source][4] is stable since at least 2007, it is available as part of the SIP proxy [Yxa 1.0][5] (hosted [on github][6]).
+
+* **ejabberd**: Already in 2006, a [fork][7] was created by [Mickael Remond][mr] at [Process One][8] to become part of the successful instant messaging server [ejabberd][9] (also hosted [at github][10]). It can be assumed to be as stable as the Yxa branch, and it didn't change anything in the lowest level, mysql_recv.erl. The differences to the original Yxa branch mainly consists of added inspection functions that help using query results, and an [independent adoption][11] to the MySQL 4.1 client-server protocol. Also, the original Yxa branch has meanwhile adopted edoc comment format. Find a diff [here][12], one ignoring comment lines [here][13].
+
+* **erlang-mysql-driver**: in 2006/07 [Yariv Sadan][ys] created a fork from the ejabberd branch, made it a standalone project, gave it the name that stuck, and hosted it at [Google Code][15]. Before he moved on to work at Facebook, he had added higher level handling of prepared statements and transactions, and stated that he had improved connection pooling and logging. There were changes both in the original Yxa and the ejabberd branch after the forking off that seem to never have made their way into the erlang-mysql-driver branch, which now lies dormant since Oct '07. Docs were somewhat unsatisfying, as much as for the earlier branches. In Feb '10, Dave Smith started making some
+[updates][15] and put them on github, were the driver is now enjoying a couple of [active forks][16] that make for a convincing case in favor of the github Network graph.
+
+* **Emysql** was started from scratch in 2009 by [Jacob Vorreuter][jv] and [Bill Warnecke][bw] at Electronic Arts, who rewrote the erlang-mysql-driver code because they felt it had been touched by so many people that it had become more complicated than necessary. In a way, the third layer module, mysql.erl, had over time started to become badly entangled with the second, mysql_conn.erl. According to Jacob, Emysql is pretty stable and ran without issue in a production environment at Electronic Arts. This fork is a continuation of [their work][1], including all their commits and adding [documentation][docs], [samples], [fixes][] and extensions.
+
+[Vitaliy Batichko][vb] and
+[Chris Rempel][cr] have contributed updates to this branch. Thank you!
+
+[1]: http://github.com/JacobVorreuter/emysql "emysql"
+[2]: http://github.com/dizzyd/erlang-mysql-driver "erlang-mysql-driver"
+[3]: http://www.kth.se/ "Royal Institure of Technology"
+[4]: https://github.com/fredrikt/yxa/tree/master/src/mysql "Yxa mysql driver"
+[5]: http://www.stacken.kth.se/project/yxa/index.html "Yxa Home"
+[6]: https://github.com/fredrikt/yxa "Yxa repository at github"
+[7]: http://svn.process-one.net/ejabberd-modules/mysql/trunk/
+ "ejabberd mysql driver"
+[8]: https://support.process-one.net "Process One Home"
+[9]: http://www.process-one.net/en/ejabberd/ "ejabberd Home"
+[10]: https://github.com/processone/ejabberd/ "ejabberd repository at github"
+[11]: https://support.process-one.net/doc/display/CONTRIBS/Yxa
+ "ejabberd MySQL 4.1. patch"
+[12]: https://github.com/Eonblast/Emysql/tree/master/doc/diff-ejabberd-yxa.txt
+ "Diff of Yxa and ejabberd mysql drivers"
+[13]: https://github.com/Eonblast/Emysql/tree/master/doc/diff-ejabberd-yxa-2.txt
+ "Diff of Yxa and ejabberd mysql drivers ignoring comment changes"
+[14]: http://code.google.com/p/erlang-mysql-driver/
+ "original erlang-mysql-driver"
+[15]: http://github.com/dizzyd/erlang-mysql-driver
+ "Dave Smith's erlang-mysql-driver at github, currently not maintained"
+[16]: https://github.com/dizzyd/erlang-mysql-driver/network
+ "Fork graph of erlang-mysql-driver at github"
+
+[ma]: ahltorp@nada.kth.se "Magnus Ahltorp"
+[ys]: http://yarivsblog.blogspot.com/
+[bw]: bill@rupture.com
+[jv]: https://github.com/JacobVorreuter
+[vb]: https://github.com/bva
+[cr]: https://github.com/csrl "Chris Rempel"
+[hd]: hd2010@eonblast.com "Henning Diedrich"
+[mr]: mickael.remond@process-one.net "Mickael Remond"
+
+[fixes]: https://github.com/Eonblast/Emysql/issues/closed
+ "Emysql fixes"
+[docs]: http://eonblast.github.com/Emysql/
+ "Emysql online docs"
+
+## Usage
+
+#### Start the application
+
+ crypto:start(),
+ application:start(emysql).
+
+#### Add a pool
+ % emysql:add_pool(PoolName, PoolSize, Username, Password, Host, Port, Database, Encoding) ->
+ % ok | {error, pool_already_exists}
+ % PoolName = atom()
+ % PoolSize = integer()
+ % Username = string()
+ % Password = string()
+ % Host = string()
+ % Port = integer()
+ % Database = string()
+ % Encoding = atom()
+
+ emysql:add_pool(mypoolname, 1, "username", "mypassword", "localhost", 3306, "mydatabase", utf8).
+
+#### Record Types
+ -record(ok_packet, {seq_num, affected_rows, insert_id, status, warning_count, msg}).
+ -record(error_packet, {seq_num, code, msg}).
+ -record(result_packet, {seq_num, field_list, rows, extra}).
+
+#### Executing SQL statements
+ % emysql:execute(PoolName, Statement) -> result_packet() | ok_packet() | error_packet()
+ % PoolName = atom()
+ % Statement = string() | binary()
+
+ emysql:execute(mypoolname, <<"SELECT * from mytable">>).
+ #result_packet{field_list=[...], rows=[...]}
+
+ emysql:execute(mypoolname, <<"UPDATE mytable SET bar = 'baz' WHERE id = 1">>).
+ #ok_packet{affected_rows=1}
+
+#### Executing prepared statements
+ % emysql:prepare(StmtName, Statement) -> ok
+ % StmtName = atom()
+ % Statement = binary() | string()
+
+ emysql:prepare(my_stmt, <<"SELECT * from mytable WHERE id = ?">>).
+ ok
+
+ % emysql:execute(PoolName, StmtName, Args) -> result_packet() | ok_packet() | error_packet()
+ % StmtName = atom()
+ % Args = [term()]
+
+ emysql:execute(mypoolname, my_stmt, [1]).
+ #result_packet{field_list=[...], rows=[...]}
+
+#### Executing stored procedures
+
+ % emysql:execute(PoolName, StmtName, Args) -> result_packet() | ok_packet() | error_packet()
+ % StmtName = atom()
+ % Args = [term()]
+
+ emysql:execute(hello_pool,
+ <<"create procedure sp_hello() begin select * from hello_table; end">>).
+ {ok_packet,1,0,0,2,0,[]}
+
+ emysql:execute(hello_pool, <<"call sp_hello();">>).
+ [{result_packet,6,
+ [{field,2,<<"def">>,<<"hello_database">>,<<"hello_table">>,
+ <<"hello_table">>,<<"hello_text">>,<<"hello_text">>,
+ 254,<<>>,33,60,0,0}],
+ [[<<"Hello World!">>],[<<"Hello World!">>]],
+ <<>>},
+ {ok_packet,7,0,0,34,0,[]}]
+
+#### Converting Row Data To Records
+ % emysql_util:as_record(ResultPacket, RecordName, Fields) -> Result
+ % ResultPacket = result_packet()
+ % RecordName = atom() (the name of the record to generate)
+ % Fields = [atom()] (the field names to generate for each record)
+ % Result = [record()]
+
+ -module(fetch_example).
+ -record(foo, {bar, baz, bat}).
+
+ fetch_foo() ->
+ Result = emysql:execute(pool1, <<"select bar, baz, bat from foo">>),
+ Recs = emysql_util:as_record(Result, foo, record_info(fields, foo)),
+ [begin
+ io:format("foo: ~p, ~p, ~p~n", [Foo#foo.bar, Foo#foo.baz, Foo#foo.bat])
+ end || Foo <- Recs].
+
+## Getting Emysql
+
+ $ git clone git://github.com/Eonblast/Emysql.git Emysql
+
+## Samples
+
+#### Hello World(*)
+
+This is a hello world program. Follow the three steps below to try it out.
+
+ -module(a_hello).
+ -export([run/0]).
+
+ run() ->
+
+ crypto:start(),
+ application:start(emysql),
+
+ emysql:add_pool(hello_pool, 1,
+ "hello_username", "hello_password", "localhost", 3306,
+ "hello_database", utf8),
+
+ emysql:execute(hello_pool,
+ <<"INSERT INTO hello_table SET hello_text = 'Hello World!'">>),
+
+ Result = emysql:execute(hello_pool,
+ <<"select hello_text from hello_table">>),
+
+ io:format("~n~p~n", [Result]).
+
+
+We come back to that source, but first:
+
+#### Building Emysql
+
+Build emysql.app, using make:
+
+ $ cd Emysql
+ $ make
+
+
+#### Sample database
+
+For the above sample, create a local mysql database. You should have a mysql server installed and running:
+
+ $ mysql [-u<user> -p]
+ mysql> create database hello_database;
+ mysql> use hello_database;
+ mysql> create table hello_table (hello_text char(20));
+ mysql> grant all privileges on hello_database.* to hello_username@localhost identified by 'hello_password';
+
+#### Run Hello
+
+Be sure to have ./ebin in your Erlang path. Now copy the Hello World source above at '(*)' into a file hello.erl and run it (in the Emysql root directory):
+
+ $ erlc hello.erl
+ $ erl -pa ../ebin -s hello run -s init stop -noshell
+
+See more sample programms, below.
+
+#### Running the Samples
+Sample programs are in ./samples.
+
+* [a_hello](http://github.com/Eonblast/Emysql/blob/master/samples/a_hello.erl) - Hello World
+* [a_hello2](http://github.com/Eonblast/Emysql/blob/master/samples/a_hello2.erl) - Hello World somewhat rawer
+* [b_rows_as_records](http://github.com/Eonblast/Emysql/blob/master/samples/b_rows_as_records.erl) - Using Erlang records to access result rows
+* [c_stored_procedure](http://github.com/Eonblast/Emysql/blob/master/samples/c_stored_procedure.erl) - Using Stored procedures
+
+To run the samples, create the database as listed above at localhost, and:
+
+ $ cd samples
+ $ ./a_hello
+ $ ./b_raw
+ $ ./d_rows_as_records
+ $ ./e_prepared_statement
+ $ ./e_stored_procedure
+
+or make emysql.app and start a_hello etc. manually along these lines (but
+first create the database as listed above):
+
+ $ make
+ $ cd samples
+ $ erlc a_hello.erl
+ $ erl -pa ../ebin -s a_hello run -s init stop -noshell
+
+## Links
+
+* [Emysql on Github](http://github.com/Eonblast/Emysql)
+* [Original Yxa](https://github.com/fredrikt/yxa/tree/master/src/mysql) mysql driver
+* [ejabberd fork](http://svn.process-one.net/ejabberd-modules/mysql/trunk/)
+* ['erlang-mysql-driver'](http://code.google.com/p/erlang-mysql-driver/)
+* [Dave Smith's erlang-mysql-driver fork](http://github.com/dizzyd/erlang-mysql-driver)
+* [A maintained erlang-mysql-driver](https://github.com/JoelPM/erlang-mysql-driver) fork
+* [Another maintained&#134; erlang-mysql-driver](https://github.com/chernomor/erlang-mysql-driver) fork
+* [MySQL Client Server Protocol](http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol)
+* [MySQL 5.5 Source](ftp://ftp.fu-berlin.de/unix/databases/mysql/Downloads/MySQL-5.5/mysql-5.5.8.tar.gz)
+
+&#134;maintained at the time of writing, Jan 2011.
+
+## TODO
+* decrementing pool size could close sockets that are in use
+* spawn individual conn_mgr gen_server processes for each pool
+* allow row results to be returned as binary
+
+## License
+
+Copyright (c) 2009-2011
+Bill Warnecke <bill@rupture.com>,
+Jacob Vorreuter <jacob.vorreuter@gmail.com>,
+Henning Diedrich <hd2010@eonblast.com>,
+Eonblast Corporation [http://www.eonblast.com].
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom
+the Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
diff --git a/deps/edown/samples/markedoc/SAMPLE2.md b/deps/edown/samples/markedoc/SAMPLE2.md
new file mode 100644
index 0000000..3fae25a
--- /dev/null
+++ b/deps/edown/samples/markedoc/SAMPLE2.md
@@ -0,0 +1,50 @@
+SAMPLE 2: EDown Readme of Jan 2011
+==================================
+```
+ --------------------------------------------------------------
+| THIS TEXT IS USED AS A SAMPLE TO ILLUSTRATE MARKEDOC USAGE. |
+| If you see this in your browser, you succeeded compiling it |
+| from markdown into an edoc. |
+ --------------------------------------------------------------
+'''
+
+<ulf@wiger.net>
+
+Status:
+-------
+
+More-or-less readable Markdown can be generated.
+A doclet needs to be written that also creates
+a markdown-based index and overview. Currently, the
+edoc_doclet creates an index.html and overview.html,
+which do not point to the .md files.
+
+To generate markdown edoc, run:
+
+`edoc:application(App, [{doclet, edown_doclet} | OtherOpts]).`
+
+The `edown_xmerl` module is used as an xmerl export module.
+It converts xmerl's "simple xml" to Markdown syntax. Note that
+GH-flavored Markdown allows HTML markup (at least common tags),
+but doesn't expand markdown markup inside HTML markup, so the
+`edown_xmerl` module has to know the context in which it operates.
+
+NOTE
+----
+
+EDoc provides a plugin structure, so that one may specify own
+layout modules, export modules, and doclets. However, there is
+some overlap esp. between the layout and doclet modules, and
+several functions are expected to produce files on their own.
+This causes a problem for EDown, since it cannot handle frames.
+Instead, it would probably like to create one overview file with
+different sections. It would have been better to have a framework
+where some plugin functions identify the different files to be
+written, and the outline of each, other plugins convert to suitable
+content representation (e.g. HTML or Markdown), and EDoc then
+writes the files necessary.
+
+For now, EDown focuses on producing reasonable Markdown, rather
+than complying fully with the plugin framework. That is, the
+edown_doclet module will not go out of its way to function together
+with any other layout module than edown_layout, and vice versa. \ No newline at end of file
diff --git a/deps/edown/samples/markedoc/SAMPLE3.md b/deps/edown/samples/markedoc/SAMPLE3.md
new file mode 100644
index 0000000..3f223c9
--- /dev/null
+++ b/deps/edown/samples/markedoc/SAMPLE3.md
@@ -0,0 +1,247 @@
+SAMPLE 3: markedoc 0.3 README of Feb 2011
+=========================================
+
+```
+ --------------------------------------------------------------
+| THIS TEXT IS USED AS A SAMPLE TO ILLUSTRATE MARKEDOC USAGE. |
+| If you see this in your browser, you succeeded compiling it |
+| from markdown into an edoc. As you see it's complex enough. |
+ --------------------------------------------------------------
+'''
+
+ **markedoc helps you keep your project's README.md in sync with your overview.edoc.**
+
+It is for use on Linux, FreeBSD and Mac OS X and any system that you can install **[sed][Requirements]** on.
+
+Status: [pre-beta][Status]. Quite stable and usable. See [Status][].
+
+markedoc translates [Markdown][] formatted texts into [Erlang][] [EDoc][] format, for inclusion into [EDoc][] generated html docs.
+
+The actual script file is in the bin folder: bin/markedoc.sed.
+
+markedoc is a mere [sed][] command file to convert markdown to edoc. It is part of the **[edown][]** project.
+
+Your contribution to make markedoc stable is highly [welcome][issues].
+
+[issues]: https://github.com/hdiedrich/markedoc/issues "Issue tracker"
+
+Use
+---
+At the command line for
+
+**FreeBSD, Mac OS X**
+ $ sed -E -f markedoc.sed <markdown file> > <edoc file>
+
+**Linux**
+ $ sed -r -f markedoc.sed <markdown file> > <edoc file>
+
+Usage for Linux and FreeBSD and Mac OS X is completely the same, except for the -r instead of the -E parameter. Both mean the same but happen to have a different name. In the examples below, replace -E with -r where necessary.
+
+Requirements
+------------
+* **[sed][]**: is part of any Linux, FreeBSD and Mac OSX distribution, also see [Notes][].
+
+* **[Erlang/OTP][Erlang]**, see [Notes][].
+
+Test
+----
+
+ **FreeBSD, Mac OS X**
+ $ etc/test-bsd.sh
+
+ **Linux**
+ $ etc/test-linux.sh
+
+Then check html files as listed in the output.
+
+Sample
+------
+
+From project root (were the README.md file is), try out:
+
+ **FreeBSD, Mac OS X**
+ $ sed -E -f bin/markedoc.sed samples/SAMPLE1.md > samples/doc/SAMPLE.edoc
+ $ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[]'
+
+ **Linux**
+ $ sed -r -f bin/markedoc.sed samples/SAMPLE1.md > samples/doc/SAMPLE.edoc
+ $ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[]'
+
+This creates a SAMPLE.edoc file from SAMPLE1.md, which is then included in the EDoc generation. Point your browser at
+
+ samples/doc/overview-summary.html
+
+to see the result. For something only vaguely related but pretty, try:
+
+ $ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+
+This illustrates the motivation for the markedoc as it is now: to have all code lines in one block in order to be able to address them as one united div from css.
+
+For your own projects you'd copy markedoc.sed in the right place and do something like:
+
+ **FreeBSD, Mac OS X**
+ $ sed -E -f bin/markedoc.sed README.md > doc/README.edoc
+ $ erl -noshell -run edoc_run application "'myapp'" '"."' '[]'
+
+ **Linux**
+ $ sed -r -f bin/markedoc.sed README.md > doc/README.edoc
+ $ erl -noshell -run edoc_run application "'myapp'" '"."' '[]'
+
+And that's it. This could also be part of your Makefile. For the intermediary README.edoc to automatically become part of your generated EDoc html pages, you would use a @docfile tag in your overview.edoc file, like so:
+
+ @docfile "doc/README.edoc"
+
+By running sed, then edoc, this makes the README.edoc part of the overview page. You could also make the README.md straight into an overview.edoc but the way it is allows allows to embedd it into additional context information that should be useful for a proper html doc.
+
+Accordingly, the sample stub overview.edoc used for the samples here, looks like this:
+
+ @author You
+ @title a markedoc sample doc
+ @version 0.2
+ @docfile "samples/doc/SAMPLE.edoc"
+
+Status
+------
+
+ **Pre-Beta**. Quite usable, but still likes to trip up EDoc now and then, which is kind of easy to do.
+
+There are many ways to create formats that will make the EDoc generator tilt and unfortunately, the errors it throws are sometimes not quite so illuminating to the reader. But why not try an incremental approach and see what works. As you can see from this [source sample][sample], which works alright, it's quite a lot that *does* work and the murky bits can usally be worked out fast. Sometimes an additional line helps, some spaces at the end of a line, general intuitive stuff. Please experiment and push your fixes to me.
+
+ **Thanks!**
+
+Notes
+-----
+
+ **[Erlang][]** is a programming language used to build massively scalable soft real-time systems with requirements on high availability. Some of its uses are in telecom, banking, e-commerce, computer telephony and instant messaging. Erlang's runtime system has built-in support for concurrency, distribution and fault tolerance. Erlang comes bundled with the Open Telecom Platform, OTP.
+
+[Erlang]: http://www.erlang.org/doc/
+
+ **[EDoc][]** is the Erlang program documentation generator. Inspired by the Javadoc tool for the Java programming language, EDoc is adapted to the conventions of the Erlang world, and has several features not found in Javadoc. Edoc is part of the Erlang/OTP distribution.
+
+[EDoc]: http://www.erlang.org/doc/apps/edoc/chapter.html
+
+ **[edown][]** is an EDoc extension for generating Github-flavored Markdown. It uses edoc-style commented Erlang sources to create markdown files from them.
+
+[edown]: https://github.com/uwiger/edown
+
+ **[Markdown][]** is a text-to-HTML conversion tool for web writers. Markdown allows you to write using an easy-to-read, easy-to-write plain text format, then convert it to structurally valid XHTML (or HTML).
+
+[Markdown]: http://daringfireball.net/projects/markdown/
+
+ **[sed][]** ('stream editor') is a Unix utility that parses text files and implements a programming language which can apply textual transformations to such files. It reads input files line by line (sequentially), applying the operation which has been specified via the command line (or a sed script), and then outputs the line. It is available today for most operating systems. There seems to be [one for Windows][winsed], too.
+
+[sed]: http://en.wikipedia.org/wiki/Sed
+[winsed]: http://gnuwin32.sourceforge.net/packages/sed.htm
+[sample]: https://github.com/Eonblast/Emysql/raw/master/README.md "This markdown file is translated alright by markedoc."
+
+Todo
+----
+* make work with non-FreeBSD sed
+* robust alternates not tested for some time
+* protect ampersands
+
+Development
+-----------
+To test markedoc, see '[Test][]', above. Or use
+
+ **FreeBSD**
+ sed -E -f bin/markedoc.sed samples/SAMPLE1.md > samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+ mv samples/doc/overview-summary.html samples/your-test-results/sample1.html
+ mv samples/doc/SAMPLE.edoc samples/your-test-results/SAMPLE1.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE2.md > samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[]'
+ mv samples/doc/overview-summary.html samples/your-test-results/sample2.html
+ mv samples/doc/SAMPLE.edoc samples/your-test-results/SAMPLE2.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE3.md > samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+ mv samples/doc/overview-summary.html samples/your-test-results/sample3.html
+ mv samples/doc/SAMPLE.edoc samples/your-test-results/SAMPLE3.edoc
+
+Then check samples/your-test-results/sample1.html - sample3.html and compare with samples/what-you-should-see/sample1.html, sample2.html and samples/what-you-could-see/sample3.html.
+
+To create the reference samples:
+
+ **FreeBSD**
+ etc/make_samples.sh
+
+or do the following to create six samples and save the results into samples/what-you-should-see/ and samples/what-you-could-see/
+
+ **FreeBSD**
+ sed -E -f bin/markedoc.sed samples/SAMPLE1.md > samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[]'
+ mv samples/doc/overview-summary.html samples/what-you-could-see/sample1.html
+ mv samples/doc/SAMPLE.edoc samples/what-you-should-see/SAMPLE1.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE2.md > samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[]'
+ mv samples/doc/overview-summary.html samples/what-you-could-see/sample2.html
+ mv samples/doc/SAMPLE.edoc samples/what-you-should-see/SAMPLE2.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE3.md > samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[]'
+ mv samples/doc/overview-summary.html samples/what-you-could-see/sample3.html
+ mv samples/doc/SAMPLE.edoc samples/what-you-should-see/SAMPLE3.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE1.md > samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+ mv samples/doc/overview-summary.html samples/what-you-could-see/sample1.html
+ mv samples/doc/SAMPLE.edoc samples/what-you-could-see/SAMPLE1.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE2.md > samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+ mv samples/doc/overview-summary.html samples/what-you-could-see/sample2.html
+ mv samples/doc/SAMPLE.edoc samples/what-you-could-see/SAMPLE2.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE3.md > samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+ mv samples/doc/overview-summary.html samples/what-you-could-see/sample3.html
+ mv samples/doc/SAMPLE.edoc samples/what-you-could-see/SAMPLE3.edoc
+
+To test this very README.md, use markdown.lua, credit Niklas Frykholm, <niklas@frykholm.se>:
+
+ lua etc/markdown.lua README.md
+
+### HTML Special Signs
+http://www.mountaindragon.com/html/iso.htm
+
+
+License
+-------
+This script is free software. It comes without any warranty.
+
+Author
+------
+H. Diedrich <hd2010@eonblast.com>
+
+History
+-------
+
+02/03/11 - 0.3 - **rough edges polished:** Linux, FreeBSD, Mac OS X
+
+* added doc for Linux use
+* added support for multi-line '[..]: ... "..."' references
+* added footnote signs and sepcial chars:
+* dagger, double dagger: (+), (++), stars: (*), (**), (***)
+* superscript 1, 2, 3: (*1), (*2), (*3), copyright (C), (R), (TM),
+* guillemots <<, >> and middle dot ::
+* added test batches etc/test-bsd.sh and etc/test-linux.sh
+* added css sample in samples/what-you-could-see/
+* added classes for `<li>' list item tags for '[..]:...'-references
+* fixed italic and bold merker interference bullet points
+* eliminated [..]: part of '[..]:...'-references, flipping "..." to lead
+* dev: sample creation batch make_samples.sh added
+
+02/02/11 - 0.2 - **basics complete:** FreeBSD / Mac OS X
+
+* added support for === and --- headline format
+* fixed cutting off of last lines
+* fixed page-local anchor jumps
+* fixed space in javascript links
+* eliminated end-space requirement at end of '[..]:...'-style references.
+* eliminated need for echoing '@doc' first into edoc output file
+* added javascript title tag setting for '[..]:...'-style references.
+
+01/31/11 - 0.1 - **first release:** FreeBSD / Mac OS X
diff --git a/deps/edown/samples/markedoc/doc/erlang.png b/deps/edown/samples/markedoc/doc/erlang.png
new file mode 100644
index 0000000..987a618
--- /dev/null
+++ b/deps/edown/samples/markedoc/doc/erlang.png
Binary files differ
diff --git a/deps/edown/samples/markedoc/doc/markedoc-footer.png b/deps/edown/samples/markedoc/doc/markedoc-footer.png
new file mode 100644
index 0000000..27414c1
--- /dev/null
+++ b/deps/edown/samples/markedoc/doc/markedoc-footer.png
Binary files differ
diff --git a/deps/edown/samples/markedoc/doc/markedoc.css b/deps/edown/samples/markedoc/doc/markedoc.css
new file mode 100644
index 0000000..36e6ee6
--- /dev/null
+++ b/deps/edown/samples/markedoc/doc/markedoc.css
@@ -0,0 +1,202 @@
+/* standard EDoc style sheet */
+body {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: .9em;
+ margin-left: 2em;
+ margin-right: 2em;
+ margin-top: 2em;
+ margin-bottom: 2em;
+ color: #000000;
+ background-color: #ffffff;
+}
+h1 {
+ margin-left: 0;
+ font-size: 3em;
+}
+h2 {
+ margin-left: -.3em;
+ font-size: 2em;
+}
+
+h2.indextitle {
+
+ font-size: 1em;
+}
+
+h2.indextitle table {
+ border-collapse: separate;
+ border-bottom-right-radius: 1em;
+ border: 1px solid #3070a0;
+ padding: 1em;
+
+ font-size: 0.9em;
+}
+
+h2.indextitle table tr td {
+ background-color: #deeefe;
+ border: 0;
+ border-bottom: 1px solid white;
+}
+
+h3 {
+ margin-left: -.3em;
+ font-size: 1.8em;
+}
+
+h4 {
+ margin-left: -.3em;
+ font-size: 1.5em;
+}
+
+div.navbar {
+ color: white;
+ font-weight: bold;
+ text-size: 0.9em;
+ background-color: #3070a0;
+ padding: 0.2em;
+ border-bottom: 1em solid #f09840;
+ border-top-left-radius: 1em;
+}
+h2.indextitle {
+ color: white;
+ padding: 0.4em;
+ background-color: #3070a0;
+ border-bottom: 10px solid #f09840;
+ border-top-left-radius: 1em;
+}
+
+h3.function,h3.typedecl {
+ color: white;
+ background-color: #3070a0;
+ padding-left: 1em;
+ border-bottom: 5px solid #f09840;
+ border-top-left-radius: 1em;
+}
+
+div.spec {
+ font-weight: bold;
+ margin-left: 0em;
+ padding: .2em 1.1em .2em;
+ background-color: #eeeeee;
+ border-bottom-right-radius: 1em;
+
+}
+a { color: inherit; padding: 2px; text-decoration: none; }
+
+a.module,a.package {
+ text-decoration:none
+}
+a:hover {
+ background-color: #deeefe;
+ color: navy;
+}
+
+a.module:hover,a.package:hover {
+ background-color: #deeefe;
+}
+ul.definitions {
+ list-style-type: none;
+}
+
+ul.index {
+ list-style-type: none;
+ border-collapse: separate;
+ border-bottom-right-radius: 1em;
+ border: 1px solid #3070a0;
+ padding: 1em;
+
+ font-size: 1.1em;
+}
+
+ul.index li {
+ background-color: #feeede;
+ border: 0;
+ border-bottom: 1px solid white;
+ padding: 2px;
+}
+
+ul {
+ list-style-type: square;
+}
+table {
+ border-collapse: collapse;
+}
+
+table.index {
+ border-collapse: separate;
+ border-bottom-right-radius: 1em;
+ border: 1px solid #3070a0;
+ padding: 1em;
+
+ font-size: 0.9em;
+}
+
+table.index td {
+ background-color: #deeefe;
+ border: 0;
+ border-bottom: 1px solid white;
+}
+
+
+table.index td a:hover {
+ background-color: #feeede;
+}
+
+td {
+ padding: 2px;
+}
+
+code {
+ font-size: 1.1em;
+}
+
+div.navbar {
+ background:url("markedoc-footer.png") no-repeat right top;
+}
+
+
+div.navbar a {
+ color: lightblue;
+ font-size: 0.9em;
+ font-weight: bold;
+ margin-left:1em;
+ line-height: 1em;
+}
+
+img {
+ margin-right: 10px;
+
+ }
+
+a {
+ color: navy;
+}
+
+h1 a, h2 a, h3 a, h4 a, h5 a {
+ color: black;
+ }
+
+h3.function a {
+ color: white;
+ }
+
+pre { margin: 0;
+ margin-top: 1em;
+ margin-bottom: 1em;
+ padding: 1.2em;
+ background-color: #f0f4f8;
+ border-radius: 1em;
+ line-height: 1.3em;
+ color: #101000; }
+span.comment {
+ color: dimgrey;
+ }
+
+li.ref {
+ background-color: #fff8e8;
+ padding: 0.1em;
+ margin-left: 0em;
+ padding-left: 1em;
+ list-style-type:square;
+ font-variant: small-caps;
+ }
diff --git a/deps/edown/samples/markedoc/doc/overview.edoc b/deps/edown/samples/markedoc/doc/overview.edoc
new file mode 100644
index 0000000..a6bc1c2
--- /dev/null
+++ b/deps/edown/samples/markedoc/doc/overview.edoc
@@ -0,0 +1,4 @@
+@author You
+@title A Markedoc Sample Doc
+@version 0.3 / edown
+@docfile "samples/markedoc/doc/SAMPLE.edoc"
diff --git a/deps/edown/samples/markedoc/make_samples.sh b/deps/edown/samples/markedoc/make_samples.sh
new file mode 100755
index 0000000..a7d3fbd
--- /dev/null
+++ b/deps/edown/samples/markedoc/make_samples.sh
@@ -0,0 +1,19 @@
+echo "making markedoc samples"
+
+sed -E -f bin/markedoc.sed samples/markedoc/SAMPLE1.md > samples/markedoc/doc/SAMPLE.edoc
+erl -noshell -run edoc_run application "'myapp'" '"samples/markedoc"' '[]'
+mv samples/markedoc/doc/overview-summary.html samples/markedoc/what-you-should-see/sample1.html
+mv samples/markedoc/doc/SAMPLE.edoc samples/markedoc/what-you-should-see/SAMPLE1.edoc
+
+sed -E -f bin/markedoc.sed samples/markedoc/SAMPLE2.md > samples/markedoc/doc/SAMPLE.edoc
+erl -noshell -run edoc_run application "'myapp'" '"samples/markedoc"' '[]'
+mv samples/markedoc/doc/overview-summary.html samples/markedoc/what-you-should-see/sample2.html
+mv samples/markedoc/doc/SAMPLE.edoc samples/markedoc/what-you-should-see/SAMPLE2.edoc
+
+sed -E -f bin/markedoc.sed samples/markedoc/SAMPLE3.md > samples/markedoc/doc/SAMPLE.edoc
+erl -noshell -run edoc_run application "'myapp'" '"samples/markedoc"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+mv samples/markedoc/doc/overview-summary.html samples/markedoc/what-you-should-see/sample3.html
+mv samples/markedoc/doc/SAMPLE.edoc samples/markedoc/what-you-should-see/SAMPLE3.edoc
+
+echo "done"
+echo "see samples/markedoc/what-you-should-see/sample1.html - sample3.html"
diff --git a/deps/edown/samples/markedoc/test-bsd.sh b/deps/edown/samples/markedoc/test-bsd.sh
new file mode 100755
index 0000000..ecf6c7d
--- /dev/null
+++ b/deps/edown/samples/markedoc/test-bsd.sh
@@ -0,0 +1,23 @@
+echo -n "testing markedoc - FreeBSD / Mac OS X: "
+
+echo -n "1 ... "
+sed -E -f bin/markedoc.sed samples/markedoc/SAMPLE1.md > samples/markedoc/doc/SAMPLE.edoc
+erl -noshell -run edoc_run application "'myapp'" '"samples/markedoc"' '[{def,{vsn,""}}]'
+mv samples/markedoc/doc/overview-summary.html samples/markedoc/your-test-results/sample1.html
+mv samples/markedoc/doc/SAMPLE.edoc samples/markedoc/your-test-results/SAMPLE1.edoc
+
+echo -n "2 ... "
+sed -E -f bin/markedoc.sed samples/markedoc/SAMPLE2.md > samples/markedoc/doc/SAMPLE.edoc
+erl -noshell -run edoc_run application "'myapp'" '"samples/markedoc"' '[]'
+mv samples/markedoc/doc/overview-summary.html samples/markedoc/your-test-results/sample2.html
+mv samples/markedoc/doc/SAMPLE.edoc samples/markedoc/your-test-results/SAMPLE2.edoc
+
+echo -n "3 ... "
+sed -E -f bin/markedoc.sed samples/markedoc/SAMPLE3.md > samples/markedoc/doc/SAMPLE.edoc
+erl -noshell -run edoc_run application "'myapp'" '"samples/markedoc"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+mv samples/markedoc/doc/overview-summary.html samples/markedoc/your-test-results/sample3.html
+mv samples/markedoc/doc/SAMPLE.edoc samples/markedoc/your-test-results/SAMPLE3.edoc
+
+echo "done."
+echo "=> now check samples/markedoc/your-test-results/sample1.html - sample3.html"
+echo "compare with samples/markedoc/what-you-should-see/sample1.html - sample2.html"
diff --git a/deps/edown/samples/markedoc/test-linux.sh b/deps/edown/samples/markedoc/test-linux.sh
new file mode 100755
index 0000000..c904281
--- /dev/null
+++ b/deps/edown/samples/markedoc/test-linux.sh
@@ -0,0 +1,23 @@
+echo -n "testing markedoc - Linux: "
+
+echo -n "1 ... "
+sed -r -f bin/markedoc.sed samples/markedoc/SAMPLE1.md > samples/markedoc/doc/SAMPLE.edoc
+erl -noshell -run edoc_run application "'myapp'" '"samples/markedoc"' '[{def,{vsn,""}}]'
+mv samples/markedoc/doc/overview-summary.html samples/markedoc/your-test-results/sample1.html
+mv samples/markedoc/doc/SAMPLE.edoc samples/markedoc/your-test-results/SAMPLE1.edoc
+
+echo -n "2 ... "
+sed -r -f bin/markedoc.sed samples/markedoc/SAMPLE2.md > samples/markedoc/doc/SAMPLE.edoc
+erl -noshell -run edoc_run application "'myapp'" '"samples/markedoc"' '[]'
+mv samples/markedoc/doc/overview-summary.html samples/markedoc/your-test-results/sample2.html
+mv samples/markedoc/doc/SAMPLE.edoc samples/markedoc/your-test-results/SAMPLE2.edoc
+
+echo -n "3 ... "
+sed -r -f bin/markedoc.sed samples/markedoc/SAMPLE3.md > samples/markedoc/doc/SAMPLE.edoc
+erl -noshell -run edoc_run application "'myapp'" '"samples/markedoc"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+mv samples/markedoc/doc/overview-summary.html samples/markedoc/your-test-results/sample3.html
+mv samples/markedoc/doc/SAMPLE.edoc samples/markedoc/your-test-results/SAMPLE3.edoc
+
+echo "done."
+echo "=> now check samples/markedoc/your-test-results/sample1.html - sample3.html"
+echo "compare with samples/markedoc/what-you-should-see/sample1.html - sample2.html"
diff --git a/deps/edown/samples/markedoc/test-macosx.sh b/deps/edown/samples/markedoc/test-macosx.sh
new file mode 100755
index 0000000..ecf6c7d
--- /dev/null
+++ b/deps/edown/samples/markedoc/test-macosx.sh
@@ -0,0 +1,23 @@
+echo -n "testing markedoc - FreeBSD / Mac OS X: "
+
+echo -n "1 ... "
+sed -E -f bin/markedoc.sed samples/markedoc/SAMPLE1.md > samples/markedoc/doc/SAMPLE.edoc
+erl -noshell -run edoc_run application "'myapp'" '"samples/markedoc"' '[{def,{vsn,""}}]'
+mv samples/markedoc/doc/overview-summary.html samples/markedoc/your-test-results/sample1.html
+mv samples/markedoc/doc/SAMPLE.edoc samples/markedoc/your-test-results/SAMPLE1.edoc
+
+echo -n "2 ... "
+sed -E -f bin/markedoc.sed samples/markedoc/SAMPLE2.md > samples/markedoc/doc/SAMPLE.edoc
+erl -noshell -run edoc_run application "'myapp'" '"samples/markedoc"' '[]'
+mv samples/markedoc/doc/overview-summary.html samples/markedoc/your-test-results/sample2.html
+mv samples/markedoc/doc/SAMPLE.edoc samples/markedoc/your-test-results/SAMPLE2.edoc
+
+echo -n "3 ... "
+sed -E -f bin/markedoc.sed samples/markedoc/SAMPLE3.md > samples/markedoc/doc/SAMPLE.edoc
+erl -noshell -run edoc_run application "'myapp'" '"samples/markedoc"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+mv samples/markedoc/doc/overview-summary.html samples/markedoc/your-test-results/sample3.html
+mv samples/markedoc/doc/SAMPLE.edoc samples/markedoc/your-test-results/SAMPLE3.edoc
+
+echo "done."
+echo "=> now check samples/markedoc/your-test-results/sample1.html - sample3.html"
+echo "compare with samples/markedoc/what-you-should-see/sample1.html - sample2.html"
diff --git a/deps/edown/samples/markedoc/what-you-should-see/SAMPLE1.edoc b/deps/edown/samples/markedoc/what-you-should-see/SAMPLE1.edoc
new file mode 100644
index 0000000..75c364d
--- /dev/null
+++ b/deps/edown/samples/markedoc/what-you-should-see/SAMPLE1.edoc
@@ -0,0 +1,346 @@
+@doc
+== SAMPLE 1: Emysql Readme of Jan 2011 ==
+```
+ --------------------------------------------------------------
+| THIS TEXT IS USED AS A SAMPLE TO ILLUSTRATE MARKEDOC USAGE. |
+| If you see this in your browser, you succeeded compiling it |
+| from markdown into an edoc. As you see it's quite complex |
+| and there is no 'cheating' involved. |
+ --------------------------------------------------------------
+'''
+
+Erlang MySQL driver, based on a rewrite at Electronic Arts&#153;. Supports prepared statements and stored procedures. For <a href="javascript:goto('samples')" onMouseOver="this.title=url('samples')">samples</a> and <a href="javascript:goto('docs')" onMouseOver="this.title=url('docs')">docs</a> see below.
+
+While you can use mysql via ODBC, using a driver like Emysql should perform better.
+
+This is a continuation fork of <a href="javascript:goto('1')" onMouseOver="this.title=url('1')">emysql</a> with <a href="javascript:goto('fixes')" onMouseOver="this.title=url('fixes')">fixes</a>, <a href="javascript:goto('updates')" onMouseOver="this.title=url('updates')">updates</a>, more <a href="javascript:goto('docs')" onMouseOver="this.title=url('docs')">docs</a> and <a href="javascript:goto('samples')" onMouseOver="this.title=url('samples')">samples</a>. <a href="javascript:goto('1')" onMouseOver="this.title=url('1')">emysql</a> is a clean rewrite of <a href="javascript:goto('2')" onMouseOver="this.title=url('2')">erlang-mysql-driver</a>.
+
+<hr/>
+
+ <b>&#171;Which fork should I use?&#187;</b> See <a href="javascript:goto('history')" onMouseOver="this.title=url('history')">history</a>.<br />
+ <b>&#171;Who used this fork?&#187;</b> Electronic Arts.<br />
+ <b>&#171;How do I ...?&#187;</b> See <a href="javascript:goto('samples')" onMouseOver="this.title=url('samples')">samples</a>.<br />
+
+ <b>Download:</b> <a href="https://github.com/Eonblast/Emysql/archives/master">https://github.com/Eonblast/Emysql/archives/master</a><br />
+ <b>Repository:</b> <a href="https://github.com/Eonblast/Emysql">https://github.com/Eonblast/Emysql</a><br />
+ <b>Docs:</b> <a href="http://eonblast.github.com/Emysql/">http://eonblast.github.com/Emysql/</a><br />
+
+<hr/>
+
+== Contents ==
+
+<li> <a href="javascript:goto('History')" onMouseOver="this.title=url('History')">History</a></li>
+<li> <a href="javascript:goto('Usage')" onMouseOver="this.title=url('Usage')">Usage</a></li>
+<li> <a href="javascript:goto('Samples')" onMouseOver="this.title=url('Samples')">Samples</a></li>
+<li> <a href="javascript:goto('Links')" onMouseOver="this.title=url('Links')">Links</a></li>
+<li> <a href="javascript:goto('Todo')" onMouseOver="this.title=url('Todo')">Todo</a></li>
+<li> <a href="javascript:goto('License')" onMouseOver="this.title=url('License')">License</a></li>
+
+<hr/>
+
+== History ==
+
+Open Source Erlang MySQL driver efforts are currently a fractured matter, at least for the higher functionality. There are four main choices:
+
+<li> <b>Yxa</b>: The first Erlang MySQL driver seems to have been written in 2005 by <a href="javascript:goto('ma')" onMouseOver="this.title=url('ma')">Magnus Ahltorp</a> at the <a href="javascript:goto('3')" onMouseOver="this.title=url('3')">Royal Institute of Technology</a>. It is the basis for the following two. The <a href="javascript:goto('4')" onMouseOver="this.title=url('4')">original mysql driver source</a> is stable since at least 2007, it is available as part of the SIP proxy <a href="javascript:goto('5')" onMouseOver="this.title=url('5')">Yxa 1.0</a> (hosted <a href="javascript:goto('6')" onMouseOver="this.title=url('6')">on github</a>).</li>
+
+<li> <b>ejabberd</b>: Already in 2006, a <a href="javascript:goto('7')" onMouseOver="this.title=url('7')">fork</a> was created by <a href="javascript:goto('mr')" onMouseOver="this.title=url('mr')">Mickael Remond</a> at <a href="javascript:goto('8')" onMouseOver="this.title=url('8')">Process One</a> to become part of the successful instant messaging server <a href="javascript:goto('9')" onMouseOver="this.title=url('9')">ejabberd</a> (also hosted <a href="javascript:goto('10')" onMouseOver="this.title=url('10')">at github</a>). It can be assumed to be as stable as the Yxa branch, and it didn't change anything in the lowest level, mysql_recv.erl. The differences to the original Yxa branch mainly consists of added inspection functions that help using query results, and an <a href="javascript:goto('11')" onMouseOver="this.title=url('11')">independent adoption</a> to the MySQL 4.1 client-server protocol. Also, the original Yxa branch has meanwhile adopted edoc comment format. Find a diff <a href="javascript:goto('12')" onMouseOver="this.title=url('12')">here</a>, one ignoring comment lines <a href="javascript:goto('13')" onMouseOver="this.title=url('13')">here</a>.</li>
+
+<li> <b>erlang-mysql-driver</b>: in 2006/07 <a href="javascript:goto('ys')" onMouseOver="this.title=url('ys')">Yariv Sadan</a> created a fork from the ejabberd branch, made it a standalone project, gave it the name that stuck, and hosted it at <a href="javascript:goto('15')" onMouseOver="this.title=url('15')">Google Code</a>. Before he moved on to work at Facebook, he had added higher level handling of prepared statements and transactions, and stated that he had improved connection pooling and logging. There were changes both in the original Yxa and the ejabberd branch after the forking off that seem to never have made their way into the erlang-mysql-driver branch, which now lies dormant since Oct '07. Docs were somewhat unsatisfying, as much as for the earlier branches. In Feb '10, Dave Smith started making some</li>
+<a href="javascript:goto('15')" onMouseOver="this.title=url('15')">updates</a> and put them on github, were the driver is now enjoying a couple of <a href="javascript:goto('16')" onMouseOver="this.title=url('16')">active forks</a> that make for a convincing case in favor of the github Network graph.
+
+<li> <b>Emysql</b> was started from scratch in 2009 by <a href="javascript:goto('jv')" onMouseOver="this.title=url('jv')">Jacob Vorreuter</a> and <a href="javascript:goto('bw')" onMouseOver="this.title=url('bw')">Bill Warnecke</a> at Electronic Arts, who rewrote the erlang-mysql-driver code because they felt it had been touched by so many people that it had become more complicated than necessary. In a way, the third layer module, mysql.erl, had over time started to become badly entangled with the second, mysql_conn.erl. According to Jacob, Emysql is pretty stable and ran without issue in a production environment at Electronic Arts. This fork is a continuation of <a href="javascript:goto('1')" onMouseOver="this.title=url('1')">their work</a>, including all their commits and adding <a href="javascript:goto('docs')" onMouseOver="this.title=url('docs')">documentation</a>, [samples], <a href="javascript:goto('fixes')" onMouseOver="this.title=url('fixes')">fixes</a> and extensions. </li>
+<br />
+<a href="javascript:goto('vb')" onMouseOver="this.title=url('vb')">Vitaliy Batichko</a> and
+<a href="javascript:goto('cr')" onMouseOver="this.title=url('cr')">Chris Rempel</a> have contributed updates to this branch. Thank you!
+
+<li class="ref url"> emysql:<a name="1" id="1" href="http://github.com/JacobVorreuter/emysql" target="_parent">http://github.com/JacobVorreuter/emysql</a></li>
+<li class="ref url"> erlang-mysql-driver:<a name="2" id="2" href="http://github.com/dizzyd/erlang-mysql-driver" target="_parent">http://github.com/dizzyd/erlang-mysql-driver</a></li>
+<li class="ref url"> Royal Institure of Technology:<a name="3" id="3" href="http://www.kth.se/" target="_parent">http://www.kth.se/</a></li>
+<li class="ref url"> Yxa mysql driver:<a name="4" id="4" href="https://github.com/fredrikt/yxa/tree/master/src/mysql" target="_parent">https://github.com/fredrikt/yxa/tree/master/src/mysql</a></li>
+<li class="ref url"> Yxa Home:<a name="5" id="5" href="http://www.stacken.kth.se/project/yxa/index.html" target="_parent">http://www.stacken.kth.se/project/yxa/index.html</a></li>
+<li class="ref url"> Yxa repository at github:<a name="6" id="6" href="https://github.com/fredrikt/yxa" target="_parent">https://github.com/fredrikt/yxa</a></li>
+<li class="ref url"> ejabberd mysql driver:<a name="7" id="7" href="http://svn.process-one.net/ejabberd-modules/mysql/trunk/" target="_parent">http://svn.process-one.net/ejabberd-modules/mysql/trunk/</a></li>
+<li class="ref url"> Process One Home:<a name="8" id="8" href="https://support.process-one.net" target="_parent">https://support.process-one.net</a></li>
+<li class="ref url"> ejabberd Home:<a name="9" id="9" href="http://www.process-one.net/en/ejabberd/" target="_parent">http://www.process-one.net/en/ejabberd/</a></li>
+<li class="ref url"> ejabberd repository at github:<a name="10" id="10" href="https://github.com/processone/ejabberd/" target="_parent">https://github.com/processone/ejabberd/</a></li>
+<li class="ref url"> ejabberd MySQL 4.1. patch:<a name="11" id="11" href="https://support.process-one.net/doc/display/CONTRIBS/Yxa" target="_parent">https://support.process-one.net/doc/display/CONTRIBS/Yxa</a></li>
+<li class="ref url"> Diff of Yxa and ejabberd mysql drivers:<a name="12" id="12" href="https://github.com/Eonblast/Emysql/tree/master/doc/diff-ejabberd-yxa.txt" target="_parent">https://github.com/Eonblast/Emysql/tree/master/doc/diff-ejabberd-yxa.txt</a></li>
+<li class="ref url"> Diff of Yxa and ejabberd mysql drivers ignoring comment changes:<a name="13" id="13" href="https://github.com/Eonblast/Emysql/tree/master/doc/diff-ejabberd-yxa-2.txt" target="_parent">https://github.com/Eonblast/Emysql/tree/master/doc/diff-ejabberd-yxa-2.txt</a></li>
+<li class="ref url"> original erlang-mysql-driver:<a name="14" id="14" href="http://code.google.com/p/erlang-mysql-driver/" target="_parent">http://code.google.com/p/erlang-mysql-driver/</a></li>
+<li class="ref url"> Dave Smith's erlang-mysql-driver at github, currently not maintained:<a name="15" id="15" href="http://github.com/dizzyd/erlang-mysql-driver" target="_parent">http://github.com/dizzyd/erlang-mysql-driver</a></li>
+<li class="ref url"> Fork graph of erlang-mysql-driver at github:<a name="16" id="16" href="https://github.com/dizzyd/erlang-mysql-driver/network" target="_parent">https://github.com/dizzyd/erlang-mysql-driver/network</a></li>
+
+<li class="ref email"> Magnus Ahltorp:<a name="ma" id="ma" href="mailto:ahltorp@nada.kth.se">ahltorp@nada.kth.se</a></li>
+<li class="ref url"> <a name="ys" id="ys" href="http://yarivsblog.blogspot.com/" target="_parent">http://yarivsblog.blogspot.com/</a></li>
+<li class="ref email"> <a name="bw" id="bw" href="mailto:bill@rupture.com">bill@rupture.com</a></li>
+<li class="ref url"> <a name="jv" id="jv" href="https://github.com/JacobVorreuter" target="_parent">https://github.com/JacobVorreuter</a></li>
+<li class="ref url"> <a name="vb" id="vb" href="https://github.com/bva" target="_parent">https://github.com/bva</a></li>
+<li class="ref url"> Chris Rempel:<a name="cr" id="cr" href="https://github.com/csrl" target="_parent">https://github.com/csrl</a></li>
+<li class="ref email"> Henning Diedrich:<a name="hd" id="hd" href="mailto:hd2010@eonblast.com">hd2010@eonblast.com</a></li>
+<li class="ref email"> Mickael Remond:<a name="mr" id="mr" href="mailto:mickael.remond@process-one.net">mickael.remond@process-one.net</a></li>
+
+<li class="ref url"> Emysql fixes:<a name="fixes" id="fixes" href="https://github.com/Eonblast/Emysql/issues/closed" target="_parent">https://github.com/Eonblast/Emysql/issues/closed</a></li>
+<li class="ref url"> Emysql online docs:<a name="docs" id="docs" href="http://eonblast.github.com/Emysql/" target="_parent">http://eonblast.github.com/Emysql/</a></li>
+
+== Usage ==
+
+==== Start the application ====
+
+```
+ crypto:start(),
+ application:start(emysql).
+'''
+
+==== Add a pool ====
+```
+ % emysql:add_pool(PoolName, PoolSize, Username, Password, Host, Port, Database, Encoding) ->
+ % ok | {error, pool_already_exists}
+ % PoolName = atom()
+ % PoolSize = integer()
+ % Username = string()
+ % Password = string()
+ % Host = string()
+ % Port = integer()
+ % Database = string()
+ % Encoding = atom()
+
+ emysql:add_pool(mypoolname, 1, "username", "mypassword", "localhost", 3306, "mydatabase", utf8).
+'''
+
+==== Record Types ====
+```
+ -record(ok_packet, {seq_num, affected_rows, insert_id, status, warning_count, msg}).
+ -record(error_packet, {seq_num, code, msg}).
+ -record(result_packet, {seq_num, field_list, rows, extra}).
+'''
+
+==== Executing SQL statements ====
+```
+ % emysql:execute(PoolName, Statement) -> result_packet() | ok_packet() | error_packet()
+ % PoolName = atom()
+ % Statement = string() | binary()
+
+ emysql:execute(mypoolname, <<"SELECT * from mytable">>).
+ #result_packet{field_list=[...], rows=[...]}
+
+ emysql:execute(mypoolname, <<"UPDATE mytable SET bar = 'baz' WHERE id = 1">>).
+ #ok_packet{affected_rows=1}
+'''
+
+==== Executing prepared statements ====
+```
+ % emysql:prepare(StmtName, Statement) -> ok
+ % StmtName = atom()
+ % Statement = binary() | string()
+'''
+
+```
+ emysql:prepare(my_stmt, <<"SELECT * from mytable WHERE id = ?">>).
+ ok
+
+ % emysql:execute(PoolName, StmtName, Args) -> result_packet() | ok_packet() | error_packet()
+ % StmtName = atom()
+ % Args = [term()]
+'''
+
+```
+ emysql:execute(mypoolname, my_stmt, [1]).
+ #result_packet{field_list=[...], rows=[...]}
+'''
+
+==== Executing stored procedures ====
+
+```
+ % emysql:execute(PoolName, StmtName, Args) -> result_packet() | ok_packet() | error_packet()
+ % StmtName = atom()
+ % Args = [term()]
+'''
+
+```
+ emysql:execute(hello_pool,
+ <<"create procedure sp_hello() begin select * from hello_table; end">>).
+ {ok_packet,1,0,0,2,0,[]}
+'''
+
+```
+ emysql:execute(hello_pool, <<"call sp_hello();">>).
+ [{result_packet,6,
+ [{field,2,<<"def">>,<<"hello_database">>,<<"hello_table">>,
+ <<"hello_table">>,<<"hello_text">>,<<"hello_text">>,
+ 254,<<>>,33,60,0,0}],
+ [[<<"Hello World!">>],[<<"Hello World!">>]],
+ <<>>},
+ {ok_packet,7,0,0,34,0,[]}]
+'''
+
+==== Converting Row Data To Records ====
+```
+ % emysql_util:as_record(ResultPacket, RecordName, Fields) -> Result
+ % ResultPacket = result_packet()
+ % RecordName = atom() (the name of the record to generate)
+ % Fields = [atom()] (the field names to generate for each record)
+ % Result = [record()]
+
+ -module(fetch_example).
+ -record(foo, {bar, baz, bat}).
+
+ fetch_foo() ->
+ Result = emysql:execute(pool1, <<"select bar, baz, bat from foo">>),
+ Recs = emysql_util:as_record(Result, foo, record_info(fields, foo)),
+ [begin
+ io:format("foo: ~p, ~p, ~p~n", [Foo#foo.bar, Foo#foo.baz, Foo#foo.bat])
+ end || Foo <- Recs].
+'''
+
+== Getting Emysql ==
+
+```
+ $ git clone git://github.com/Eonblast/Emysql.git Emysql
+
+'''
+== Samples ==
+
+==== Hello World&#42; ====
+
+This is a hello world program. Follow the three steps below to try it out.
+```
+
+ -module(a_hello).
+ -export([run/0]).
+
+ run() ->
+
+ crypto:start(),
+ application:start(emysql),
+
+ emysql:add_pool(hello_pool, 1,
+ "hello_username", "hello_password", "localhost", 3306,
+ "hello_database", utf8),
+
+ emysql:execute(hello_pool,
+ <<"INSERT INTO hello_table SET hello_text = 'Hello World!'">>),
+
+ Result = emysql:execute(hello_pool,
+ <<"select hello_text from hello_table">>),
+
+ io:format("~n~p~n", [Result]).
+'''
+
+
+We come back to that source, but first:
+
+==== Building Emysql ====
+
+Build emysql.app, using make:
+
+```
+ $ cd Emysql
+ $ make
+'''
+
+
+==== Sample database ====
+
+For the above sample, create a local mysql database. You should have a mysql server installed and running:
+```
+
+ $ mysql [-u<user> -p]
+ mysql> create database hello_database;
+ mysql> use hello_database;
+ mysql> create table hello_table (hello_text char(20));
+ mysql> grant all privileges on hello_database.* to hello_username@localhost identified by 'hello_password';
+'''
+
+==== Run Hello ====
+
+Be sure to have ./ebin in your Erlang path. Now copy the Hello World source above at '&#42;' into a file hello.erl and run it (in the Emysql root directory):
+
+```
+ $ erlc hello.erl
+ $ erl -pa ../ebin -s hello run -s init stop -noshell
+'''
+
+See more sample programms, below.
+
+==== Running the Samples ====
+Sample programs are in ./samples.
+
+<li> <a href="http://github.com/Eonblast/Emysql/blob/master/samples/a_hello.erl">a_hello</a> - Hello World</li>
+<li> <a href="http://github.com/Eonblast/Emysql/blob/master/samples/a_hello2.erl">a_hello2</a> - Hello World somewhat rawer</li>
+<li> <a href="http://github.com/Eonblast/Emysql/blob/master/samples/b_rows_as_records.erl">b_rows_as_records</a> - Using Erlang records to access result rows</li>
+<li> <a href="http://github.com/Eonblast/Emysql/blob/master/samples/c_stored_procedure.erl">c_stored_procedure</a> - Using Stored procedures</li>
+
+To run the samples, create the database as listed above at localhost, and:
+
+```
+ $ cd samples
+ $ ./a_hello
+ $ ./b_raw
+ $ ./d_rows_as_records
+ $ ./e_prepared_statement
+ $ ./e_stored_procedure
+
+'''
+or make emysql.app and start a_hello etc. manually along these lines (but
+first create the database as listed above):
+
+```
+ $ make
+ $ cd samples
+ $ erlc a_hello.erl
+ $ erl -pa ../ebin -s a_hello run -s init stop -noshell
+'''
+
+== Links ==
+
+<li> <a href="http://github.com/Eonblast/Emysql">Emysql on Github</a></li>
+<li> <a href="https://github.com/fredrikt/yxa/tree/master/src/mysql">Original Yxa</a> mysql driver</li>
+<li> <a href="http://svn.process-one.net/ejabberd-modules/mysql/trunk/">ejabberd fork</a></li>
+<li> <a href="http://code.google.com/p/erlang-mysql-driver/">'erlang-mysql-driver'</a></li>
+<li> <a href="http://github.com/dizzyd/erlang-mysql-driver">Dave Smith's erlang-mysql-driver fork</a></li>
+<li> <a href="https://github.com/JoelPM/erlang-mysql-driver">A maintained erlang-mysql-driver</a> fork</li>
+<li> <a href="https://github.com/chernomor/erlang-mysql-driver">Another maintained&#134; erlang-mysql-driver</a> fork</li>
+<li> <a href="http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol">MySQL Client Server Protocol</a></li>
+<li> <a href="ftp://ftp.fu-berlin.de/unix/databases/mysql/Downloads/MySQL-5.5/mysql-5.5.8.tar.gz">MySQL 5.5 Source</a></li>
+
+&#134;maintained at the time of writing, Jan 2011.
+
+== TODO ==
+<li> decrementing pool size could close sockets that are in use</li>
+<li> spawn individual conn_mgr gen_server processes for each pool</li>
+<li> allow row results to be returned as binary</li>
+
+== License ==
+
+Copyright &#169; 2009-2011
+Bill Warnecke <a href="mailto:bill@rupture.com">bill@rupture.com</a>,
+Jacob Vorreuter <a href="mailto:jacob.vorreuter@gmail.com">jacob.vorreuter@gmail.com</a>,
+Henning Diedrich <a href="mailto:hd2010@eonblast.com">hd2010@eonblast.com</a>,
+Eonblast Corporation [http://www.eonblast.com].
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom
+the Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+<script>
+// Jump directly to a referenced url given in trailing '[]:...'-notation
+function goto(tag) { parent.document.location.href = url(tag); }
+function url(tag) { var o=document.getElementById(tag); return o ? o.href : '#'+tag; }
+</script>
diff --git a/deps/edown/samples/markedoc/what-you-should-see/SAMPLE2.edoc b/deps/edown/samples/markedoc/what-you-should-see/SAMPLE2.edoc
new file mode 100644
index 0000000..e65c0f3
--- /dev/null
+++ b/deps/edown/samples/markedoc/what-you-should-see/SAMPLE2.edoc
@@ -0,0 +1,53 @@
+@doc
+== SAMPLE 2: EDown Readme of Jan 2011 ==
+```
+ --------------------------------------------------------------
+| THIS TEXT IS USED AS A SAMPLE TO ILLUSTRATE MARKEDOC USAGE. |
+| If you see this in your browser, you succeeded compiling it |
+| from markdown into an edoc. |
+ --------------------------------------------------------------
+'''
+
+<a href="mailto:ulf@wiger.net">ulf@wiger.net</a>
+
+=== Status: ===
+
+More-or-less readable Markdown can be generated.
+A doclet needs to be written that also creates
+a markdown-based index and overview. Currently, the
+edoc_doclet creates an index.html and overview.html,
+which do not point to the .md files.
+
+To generate markdown edoc, run:
+
+<code>edoc:application(App, [{doclet, edown_doclet} | OtherOpts]).</code>
+
+The <code>edown_xmerl</code> module is used as an xmerl export module.
+It converts xmerl's "simple xml" to Markdown syntax. Note that
+GH-flavored Markdown allows HTML markup (at least common tags),
+but doesn't expand markdown markup inside HTML markup, so the
+<code>edown_xmerl</code> module has to know the context in which it operates.
+
+=== NOTE ===
+
+EDoc provides a plugin structure, so that one may specify own
+layout modules, export modules, and doclets. However, there is
+some overlap esp. between the layout and doclet modules, and
+several functions are expected to produce files on their own.
+This causes a problem for EDown, since it cannot handle frames.
+Instead, it would probably like to create one overview file with
+different sections. It would have been better to have a framework
+where some plugin functions identify the different files to be
+written, and the outline of each, other plugins convert to suitable
+content representation (e.g. HTML or Markdown), and EDoc then
+writes the files necessary.
+
+For now, EDown focuses on producing reasonable Markdown, rather
+than complying fully with the plugin framework. That is, the
+edown_doclet module will not go out of its way to function together
+with any other layout module than edown_layout, and vice versa.
+<script>
+// Jump directly to a referenced url given in trailing '[]:...'-notation
+function goto(tag) { parent.document.location.href = url(tag); }
+function url(tag) { var o=document.getElementById(tag); return o ? o.href : '#'+tag; }
+</script>
diff --git a/deps/edown/samples/markedoc/what-you-should-see/SAMPLE3.edoc b/deps/edown/samples/markedoc/what-you-should-see/SAMPLE3.edoc
new file mode 100644
index 0000000..b1fdd30
--- /dev/null
+++ b/deps/edown/samples/markedoc/what-you-should-see/SAMPLE3.edoc
@@ -0,0 +1,280 @@
+@doc
+== SAMPLE 3: markedoc 0.3 README of Feb 2011 ==
+
+```
+ --------------------------------------------------------------
+| THIS TEXT IS USED AS A SAMPLE TO ILLUSTRATE MARKEDOC USAGE. |
+| If you see this in your browser, you succeeded compiling it |
+| from markdown into an edoc. As you see it's complex enough. |
+ --------------------------------------------------------------
+'''
+
+ <b>markedoc helps you keep your project's README.md in sync with your overview.edoc.</b>
+
+It is for use on Linux, FreeBSD and Mac OS X and any system that you can install <b><a href="javascript:goto('Requirements')" onMouseOver="this.title=url('Requirements')">sed</a></b> on.
+
+Status: <a href="javascript:goto('Status')" onMouseOver="this.title=url('Status')">pre-beta</a>. Quite stable and usable. See <a href="javascript:goto('Status')" onMouseOver="this.title=url('Status')">Status</a>.
+
+markedoc translates <a href="javascript:goto('Markdown')" onMouseOver="this.title=url('Markdown')">Markdown</a> formatted texts into <a href="javascript:goto('Erlang')" onMouseOver="this.title=url('Erlang')">Erlang</a> <a href="javascript:goto('EDoc')" onMouseOver="this.title=url('EDoc')">EDoc</a> format, for inclusion into <a href="javascript:goto('EDoc')" onMouseOver="this.title=url('EDoc')">EDoc</a> generated html docs.
+
+The actual script file is in the bin folder: bin/markedoc.sed.
+
+markedoc is a mere <a href="javascript:goto('sed')" onMouseOver="this.title=url('sed')">sed</a> command file to convert markdown to edoc. It is part of the <b><a href="javascript:goto('edown')" onMouseOver="this.title=url('edown')">edown</a></b> project.
+
+Your contribution to make markedoc stable is highly <a href="javascript:goto('issues')" onMouseOver="this.title=url('issues')">welcome</a>.
+
+<li class="ref url"> Issue tracker:<a name="issues" id="issues" href="https://github.com/hdiedrich/markedoc/issues" target="_parent">https://github.com/hdiedrich/markedoc/issues</a></li>
+
+=== Use ===
+At the command line for
+
+<b>FreeBSD, Mac OS X</b>
+```
+ $ sed -E -f markedoc.sed <markdown file> > <edoc file>
+'''
+
+<b>Linux</b>
+```
+ $ sed -r -f markedoc.sed <markdown file> > <edoc file>
+'''
+
+Usage for Linux and FreeBSD and Mac OS X is completely the same, except for the -r instead of the -E parameter. Both mean the same but happen to have a different name. In the examples below, replace -E with -r where necessary.
+
+=== Requirements ===
+<li> <b><a href="javascript:goto('sed')" onMouseOver="this.title=url('sed')">sed</a></b>: is part of any Linux, FreeBSD and Mac OSX distribution, also see <a href="javascript:goto('Notes')" onMouseOver="this.title=url('Notes')">Notes</a>.</li>
+
+<li> <b><a href="javascript:goto('Erlang')" onMouseOver="this.title=url('Erlang')">Erlang/OTP</a></b>, see <a href="javascript:goto('Notes')" onMouseOver="this.title=url('Notes')">Notes</a>.</li>
+
+=== Test ===
+
+ <b>FreeBSD, Mac OS X</b>
+```
+ $ etc/test-bsd.sh
+'''
+
+ <b>Linux</b>
+```
+ $ etc/test-linux.sh
+'''
+
+Then check html files as listed in the output.
+
+=== Sample ===
+
+From project root (were the README.md file is), try out:
+
+ <b>FreeBSD, Mac OS X</b>
+```
+ $ sed -E -f bin/markedoc.sed samples/SAMPLE1.md > samples/doc/SAMPLE.edoc
+ $ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[]'
+'''
+
+ <b>Linux</b>
+```
+ $ sed -r -f bin/markedoc.sed samples/SAMPLE1.md > samples/doc/SAMPLE.edoc
+ $ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[]'
+'''
+
+This creates a SAMPLE.edoc file from SAMPLE1.md, which is then included in the EDoc generation. Point your browser at
+
+```
+ samples/doc/overview-summary.html
+'''
+
+to see the result. For something only vaguely related but pretty, try:
+
+```
+ $ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+'''
+
+This illustrates the motivation for the markedoc as it is now: to have all code lines in one block in order to be able to address them as one united div from css.
+
+For your own projects you'd copy markedoc.sed in the right place and do something like:
+
+ <b>FreeBSD, Mac OS X</b>
+```
+ $ sed -E -f bin/markedoc.sed README.md > doc/README.edoc
+ $ erl -noshell -run edoc_run application "'myapp'" '"."' '[]'
+'''
+
+ <b>Linux</b>
+```
+ $ sed -r -f bin/markedoc.sed README.md > doc/README.edoc
+ $ erl -noshell -run edoc_run application "'myapp'" '"."' '[]'
+'''
+
+And that's it. This could also be part of your Makefile. For the intermediary README.edoc to automatically become part of your generated EDoc html pages, you would use a @@docfile tag in your overview.edoc file, like so:
+
+```
+ @@docfile "doc/README.edoc"
+'''
+
+By running sed, then edoc, this makes the README.edoc part of the overview page. You could also make the README.md straight into an overview.edoc but the way it is allows allows to embedd it into additional context information that should be useful for a proper html doc.
+
+Accordingly, the sample stub overview.edoc used for the samples here, looks like this:
+
+```
+ @@author You
+ @@title a markedoc sample doc
+ @@version 0.2
+ @@docfile "samples/doc/SAMPLE.edoc"
+'''
+
+=== Status ===
+
+ <b>Pre-Beta</b>. Quite usable, but still likes to trip up EDoc now and then, which is kind of easy to do.
+
+There are many ways to create formats that will make the EDoc generator tilt and unfortunately, the errors it throws are sometimes not quite so illuminating to the reader. But why not try an incremental approach and see what works. As you can see from this <a href="javascript:goto('sample')" onMouseOver="this.title=url('sample')">source sample</a>, which works alright, it's quite a lot that <em>does</em> work and the murky bits can usally be worked out fast. Sometimes an additional line helps, some spaces at the end of a line, general intuitive stuff. Please experiment and push your fixes to me.
+
+ <b>Thanks!</b>
+
+=== Notes ===
+
+ <b><a href="javascript:goto('Erlang')" onMouseOver="this.title=url('Erlang')">Erlang</a></b> is a programming language used to build massively scalable soft real-time systems with requirements on high availability. Some of its uses are in telecom, banking, e-commerce, computer telephony and instant messaging. Erlang's runtime system has built-in support for concurrency, distribution and fault tolerance. Erlang comes bundled with the Open Telecom Platform, OTP.
+
+<li class="ref url"> <a name="Erlang" id="Erlang" href="http://www.erlang.org/doc/" target="_parent">http://www.erlang.org/doc/</a></li>
+
+ <b><a href="javascript:goto('EDoc')" onMouseOver="this.title=url('EDoc')">EDoc</a></b> is the Erlang program documentation generator. Inspired by the Javadoc tool for the Java programming language, EDoc is adapted to the conventions of the Erlang world, and has several features not found in Javadoc. Edoc is part of the Erlang/OTP distribution.
+
+<li class="ref url"> <a name="EDoc" id="EDoc" href="http://www.erlang.org/doc/apps/edoc/chapter.html" target="_parent">http://www.erlang.org/doc/apps/edoc/chapter.html</a></li>
+
+ <b><a href="javascript:goto('edown')" onMouseOver="this.title=url('edown')">edown</a></b> is an EDoc extension for generating Github-flavored Markdown. It uses edoc-style commented Erlang sources to create markdown files from them.
+
+<li class="ref url"> <a name="edown" id="edown" href="https://github.com/uwiger/edown" target="_parent">https://github.com/uwiger/edown</a></li>
+
+ <b><a href="javascript:goto('Markdown')" onMouseOver="this.title=url('Markdown')">Markdown</a></b> is a text-to-HTML conversion tool for web writers. Markdown allows you to write using an easy-to-read, easy-to-write plain text format, then convert it to structurally valid XHTML (or HTML).
+
+<li class="ref url"> <a name="Markdown" id="Markdown" href="http://daringfireball.net/projects/markdown/" target="_parent">http://daringfireball.net/projects/markdown/</a></li>
+
+ <b><a href="javascript:goto('sed')" onMouseOver="this.title=url('sed')">sed</a></b> ('stream editor') is a Unix utility that parses text files and implements a programming language which can apply textual transformations to such files. It reads input files line by line (sequentially), applying the operation which has been specified via the command line (or a sed script), and then outputs the line. It is available today for most operating systems. There seems to be <a href="javascript:goto('winsed')" onMouseOver="this.title=url('winsed')">one for Windows</a>, too.
+
+<li class="ref url"> <a name="sed" id="sed" href="http://en.wikipedia.org/wiki/Sed" target="_parent">http://en.wikipedia.org/wiki/Sed</a></li>
+<li class="ref url"> <a name="winsed" id="winsed" href="http://gnuwin32.sourceforge.net/packages/sed.htm" target="_parent">http://gnuwin32.sourceforge.net/packages/sed.htm</a></li>
+<li class="ref url"> This markdown file is translated alright by markedoc.:<a name="sample" id="sample" href="https://github.com/Eonblast/Emysql/raw/master/README.md" target="_parent">https://github.com/Eonblast/Emysql/raw/master/README.md</a></li>
+
+=== Todo ===
+<li> make work with non-FreeBSD sed</li>
+<li> robust alternates not tested for some time</li>
+<li> protect ampersands</li>
+
+=== Development ===
+To test markedoc, see '<a href="javascript:goto('Test')" onMouseOver="this.title=url('Test')">Test</a>', above. Or use
+
+ <b>FreeBSD</b>
+```
+ sed -E -f bin/markedoc.sed samples/SAMPLE1.md > samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+ mv samples/doc/overview-summary.html samples/your-test-results/sample1.html
+ mv samples/doc/SAMPLE.edoc samples/your-test-results/SAMPLE1.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE2.md > samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[]'
+ mv samples/doc/overview-summary.html samples/your-test-results/sample2.html
+ mv samples/doc/SAMPLE.edoc samples/your-test-results/SAMPLE2.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE3.md > samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+ mv samples/doc/overview-summary.html samples/your-test-results/sample3.html
+ mv samples/doc/SAMPLE.edoc samples/your-test-results/SAMPLE3.edoc
+
+'''
+Then check samples/your-test-results/sample1.html - sample3.html and compare with samples/what-you-should-see/sample1.html, sample2.html and samples/what-you-could-see/sample3.html.
+
+To create the reference samples:
+
+ <b>FreeBSD</b>
+```
+ etc/make_samples.sh
+'''
+
+or do the following to create six samples and save the results into samples/what-you-should-see/ and samples/what-you-could-see/
+
+ <b>FreeBSD</b>
+```
+ sed -E -f bin/markedoc.sed samples/SAMPLE1.md > samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[]'
+ mv samples/doc/overview-summary.html samples/what-you-could-see/sample1.html
+ mv samples/doc/SAMPLE.edoc samples/what-you-should-see/SAMPLE1.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE2.md > samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[]'
+ mv samples/doc/overview-summary.html samples/what-you-could-see/sample2.html
+ mv samples/doc/SAMPLE.edoc samples/what-you-should-see/SAMPLE2.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE3.md > samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[]'
+ mv samples/doc/overview-summary.html samples/what-you-could-see/sample3.html
+ mv samples/doc/SAMPLE.edoc samples/what-you-should-see/SAMPLE3.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE1.md > samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+ mv samples/doc/overview-summary.html samples/what-you-could-see/sample1.html
+ mv samples/doc/SAMPLE.edoc samples/what-you-could-see/SAMPLE1.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE2.md > samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+ mv samples/doc/overview-summary.html samples/what-you-could-see/sample2.html
+ mv samples/doc/SAMPLE.edoc samples/what-you-could-see/SAMPLE2.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE3.md > samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+ mv samples/doc/overview-summary.html samples/what-you-could-see/sample3.html
+ mv samples/doc/SAMPLE.edoc samples/what-you-could-see/SAMPLE3.edoc
+'''
+
+To test this very README.md, use markdown.lua, credit Niklas Frykholm, <a href="mailto:niklas@frykholm.se">niklas@frykholm.se</a>:
+
+```
+ lua etc/markdown.lua README.md
+
+'''
+=== HTML Special Signs ===
+http://www.mountaindragon.com/html/iso.htm
+
+
+=== License ===
+This script is free software. It comes without any warranty.
+
+=== Author ===
+H. Diedrich <a href="mailto:hd2010@eonblast.com">hd2010@eonblast.com</a>
+
+=== History ===
+```
+
+'''
+02/03/11 - 0.3 - <b>rough edges polished:</b> Linux, FreeBSD, Mac OS X
+
+<li> added doc for Linux use</li>
+<li> added support for multi-line '[..]: ... "..."' references</li>
+<li> added footnote signs and sepcial chars:</li>
+<li> dagger, double dagger: &#134;, &#135;, stars: &#42;, &#42;&#42;, &#42;&#42;&#42; </li>
+<li> superscript 1, 2, 3: &#185;, &#178;, &#179;, copyright &#169;, &#174;, &#153;, </li>
+<li> guillemots &#171;, &#187; and middle dot &#183;</li>
+<li> added test batches etc/test-bsd.sh and etc/test-linux.sh</li>
+<li> added css sample in samples/what-you-could-see/ </li>
+<li> added classes for `<li>' list item tags for '[..]:...'-references</li>
+<li> fixed italic and bold merker interference bullet points</li>
+<li> eliminated [..]: part of '[..]:...'-references, flipping "..." to lead</li>
+<li> dev: sample creation batch make_samples.sh added</li>
+```
+
+'''
+02/02/11 - 0.2 - <b>basics complete:</b> FreeBSD / Mac OS X
+
+<li> added support for === and --- headline format</li>
+<li> fixed cutting off of last lines </li>
+<li> fixed page-local anchor jumps</li>
+<li> fixed space in javascript links</li>
+<li> eliminated end-space requirement at end of '[..]:...'-style references.</li>
+<li> eliminated need for echoing '@@doc' first into edoc output file</li>
+<li> added javascript title tag setting for '[..]:...'-style references.</li>
+```
+
+'''
+01/31/11 - 0.1 - <b>first release:</b> FreeBSD / Mac OS X
+``` '''
+<script>
+// Jump directly to a referenced url given in trailing '[]:...'-notation
+function goto(tag) { parent.document.location.href = url(tag); }
+function url(tag) { var o=document.getElementById(tag); return o ? o.href : '#'+tag; }
+</script>
diff --git a/deps/edown/samples/markedoc/what-you-should-see/erlang.png b/deps/edown/samples/markedoc/what-you-should-see/erlang.png
new file mode 100644
index 0000000..987a618
--- /dev/null
+++ b/deps/edown/samples/markedoc/what-you-should-see/erlang.png
Binary files differ
diff --git a/deps/edown/samples/markedoc/what-you-should-see/markedoc-footer.png b/deps/edown/samples/markedoc/what-you-should-see/markedoc-footer.png
new file mode 100644
index 0000000..27414c1
--- /dev/null
+++ b/deps/edown/samples/markedoc/what-you-should-see/markedoc-footer.png
Binary files differ
diff --git a/deps/edown/samples/markedoc/what-you-should-see/markedoc.css b/deps/edown/samples/markedoc/what-you-should-see/markedoc.css
new file mode 100644
index 0000000..36e6ee6
--- /dev/null
+++ b/deps/edown/samples/markedoc/what-you-should-see/markedoc.css
@@ -0,0 +1,202 @@
+/* standard EDoc style sheet */
+body {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: .9em;
+ margin-left: 2em;
+ margin-right: 2em;
+ margin-top: 2em;
+ margin-bottom: 2em;
+ color: #000000;
+ background-color: #ffffff;
+}
+h1 {
+ margin-left: 0;
+ font-size: 3em;
+}
+h2 {
+ margin-left: -.3em;
+ font-size: 2em;
+}
+
+h2.indextitle {
+
+ font-size: 1em;
+}
+
+h2.indextitle table {
+ border-collapse: separate;
+ border-bottom-right-radius: 1em;
+ border: 1px solid #3070a0;
+ padding: 1em;
+
+ font-size: 0.9em;
+}
+
+h2.indextitle table tr td {
+ background-color: #deeefe;
+ border: 0;
+ border-bottom: 1px solid white;
+}
+
+h3 {
+ margin-left: -.3em;
+ font-size: 1.8em;
+}
+
+h4 {
+ margin-left: -.3em;
+ font-size: 1.5em;
+}
+
+div.navbar {
+ color: white;
+ font-weight: bold;
+ text-size: 0.9em;
+ background-color: #3070a0;
+ padding: 0.2em;
+ border-bottom: 1em solid #f09840;
+ border-top-left-radius: 1em;
+}
+h2.indextitle {
+ color: white;
+ padding: 0.4em;
+ background-color: #3070a0;
+ border-bottom: 10px solid #f09840;
+ border-top-left-radius: 1em;
+}
+
+h3.function,h3.typedecl {
+ color: white;
+ background-color: #3070a0;
+ padding-left: 1em;
+ border-bottom: 5px solid #f09840;
+ border-top-left-radius: 1em;
+}
+
+div.spec {
+ font-weight: bold;
+ margin-left: 0em;
+ padding: .2em 1.1em .2em;
+ background-color: #eeeeee;
+ border-bottom-right-radius: 1em;
+
+}
+a { color: inherit; padding: 2px; text-decoration: none; }
+
+a.module,a.package {
+ text-decoration:none
+}
+a:hover {
+ background-color: #deeefe;
+ color: navy;
+}
+
+a.module:hover,a.package:hover {
+ background-color: #deeefe;
+}
+ul.definitions {
+ list-style-type: none;
+}
+
+ul.index {
+ list-style-type: none;
+ border-collapse: separate;
+ border-bottom-right-radius: 1em;
+ border: 1px solid #3070a0;
+ padding: 1em;
+
+ font-size: 1.1em;
+}
+
+ul.index li {
+ background-color: #feeede;
+ border: 0;
+ border-bottom: 1px solid white;
+ padding: 2px;
+}
+
+ul {
+ list-style-type: square;
+}
+table {
+ border-collapse: collapse;
+}
+
+table.index {
+ border-collapse: separate;
+ border-bottom-right-radius: 1em;
+ border: 1px solid #3070a0;
+ padding: 1em;
+
+ font-size: 0.9em;
+}
+
+table.index td {
+ background-color: #deeefe;
+ border: 0;
+ border-bottom: 1px solid white;
+}
+
+
+table.index td a:hover {
+ background-color: #feeede;
+}
+
+td {
+ padding: 2px;
+}
+
+code {
+ font-size: 1.1em;
+}
+
+div.navbar {
+ background:url("markedoc-footer.png") no-repeat right top;
+}
+
+
+div.navbar a {
+ color: lightblue;
+ font-size: 0.9em;
+ font-weight: bold;
+ margin-left:1em;
+ line-height: 1em;
+}
+
+img {
+ margin-right: 10px;
+
+ }
+
+a {
+ color: navy;
+}
+
+h1 a, h2 a, h3 a, h4 a, h5 a {
+ color: black;
+ }
+
+h3.function a {
+ color: white;
+ }
+
+pre { margin: 0;
+ margin-top: 1em;
+ margin-bottom: 1em;
+ padding: 1.2em;
+ background-color: #f0f4f8;
+ border-radius: 1em;
+ line-height: 1.3em;
+ color: #101000; }
+span.comment {
+ color: dimgrey;
+ }
+
+li.ref {
+ background-color: #fff8e8;
+ padding: 0.1em;
+ margin-left: 0em;
+ padding-left: 1em;
+ list-style-type:square;
+ font-variant: small-caps;
+ }
diff --git a/deps/edown/samples/markedoc/what-you-should-see/sample1.html b/deps/edown/samples/markedoc/what-you-should-see/sample1.html
new file mode 100644
index 0000000..941bf0a
--- /dev/null
+++ b/deps/edown/samples/markedoc/what-you-should-see/sample1.html
@@ -0,0 +1,321 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<title> A Markedoc Sample Doc</title>
+<link rel="stylesheet" type="text/css" href="stylesheet.css" title="EDoc">
+</head>
+<body bgcolor="white">
+<div class="navbar"><a name="#navbar_top"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
+<h1> A Markedoc Sample Doc</h1>
+<p><b>Version:</b> 0.3 / edown</p>
+<p><b>Authors:</b> You!.</p>
+
+<h3><a name="SAMPLE_1:_Emysql_Readme_of_Jan_2011">SAMPLE 1: Emysql Readme of Jan 2011</a></h3>
+<pre> --------------------------------------------------------------
+| THIS TEXT IS USED AS A SAMPLE TO ILLUSTRATE MARKEDOC USAGE. |
+| If you see this in your browser, you succeeded compiling it |
+| from markdown into an edoc. As you see it's quite complex |
+| and there is no 'cheating' involved. |
+ --------------------------------------------------------------</pre>
+
+<p>Erlang MySQL driver, based on a rewrite at Electronic Arts™. Supports prepared statements and stored procedures. For <a href="javascript:goto('samples')" onMouseOver="this.title=url('samples')">samples</a> and <a href="javascript:goto('docs')" onMouseOver="this.title=url('docs')">docs</a> see below.</p>
+
+<p>While you can use mysql via ODBC, using a driver like Emysql should perform better. </p>
+
+<p>This is a continuation fork of <a href="javascript:goto('1')" onMouseOver="this.title=url('1')">emysql</a> with <a href="javascript:goto('fixes')" onMouseOver="this.title=url('fixes')">fixes</a>, <a href="javascript:goto('updates')" onMouseOver="this.title=url('updates')">updates</a>, more <a href="javascript:goto('docs')" onMouseOver="this.title=url('docs')">docs</a> and <a href="javascript:goto('samples')" onMouseOver="this.title=url('samples')">samples</a>. <a href="javascript:goto('1')" onMouseOver="this.title=url('1')">emysql</a> is a clean rewrite of <a href="javascript:goto('2')" onMouseOver="this.title=url('2')">erlang-mysql-driver</a>. </p>
+
+<hr>
+
+ <p><b>«Which fork should I use?»</b> See <a href="javascript:goto('history')" onMouseOver="this.title=url('history')">history</a>.<br>
+ <b>«Who used this fork?»</b> Electronic Arts.<br>
+ <b>«How do I ...?»</b> See <a href="javascript:goto('samples')" onMouseOver="this.title=url('samples')">samples</a>.<br></p>
+
+ <p><b>Download:</b> <a href="https://github.com/Eonblast/Emysql/archives/master">https://github.com/Eonblast/Emysql/archives/master</a><br>
+ <b>Repository:</b> <a href="https://github.com/Eonblast/Emysql">https://github.com/Eonblast/Emysql</a><br>
+ <b>Docs:</b> <a href="http://eonblast.github.com/Emysql/">http://eonblast.github.com/Emysql/</a><br></p>
+
+<hr>
+
+<h3><a name="Contents">Contents</a></h3>
+
+<p><li> <a href="javascript:goto('History')" onMouseOver="this.title=url('History')">History</a></li>
+<li> <a href="javascript:goto('Usage')" onMouseOver="this.title=url('Usage')">Usage</a></li>
+<li> <a href="javascript:goto('Samples')" onMouseOver="this.title=url('Samples')">Samples</a></li>
+<li> <a href="javascript:goto('Links')" onMouseOver="this.title=url('Links')">Links</a></li>
+<li> <a href="javascript:goto('Todo')" onMouseOver="this.title=url('Todo')">Todo</a></li>
+<li> <a href="javascript:goto('License')" onMouseOver="this.title=url('License')">License</a></li></p>
+
+<hr>
+
+<h3><a name="History">History</a></h3>
+
+<p>Open Source Erlang MySQL driver efforts are currently a fractured matter, at least for the higher functionality. There are four main choices:</p>
+
+<p><li> <b>Yxa</b>: The first Erlang MySQL driver seems to have been written in 2005 by <a href="javascript:goto('ma')" onMouseOver="this.title=url('ma')">Magnus Ahltorp</a> at the <a href="javascript:goto('3')" onMouseOver="this.title=url('3')">Royal Institute of Technology</a>. It is the basis for the following two. The <a href="javascript:goto('4')" onMouseOver="this.title=url('4')">original mysql driver source</a> is stable since at least 2007, it is available as part of the SIP proxy <a href="javascript:goto('5')" onMouseOver="this.title=url('5')">Yxa 1.0</a> (hosted <a href="javascript:goto('6')" onMouseOver="this.title=url('6')">on github</a>).</li></p>
+
+<p><li> <b>ejabberd</b>: Already in 2006, a <a href="javascript:goto('7')" onMouseOver="this.title=url('7')">fork</a> was created by <a href="javascript:goto('mr')" onMouseOver="this.title=url('mr')">Mickael Remond</a> at <a href="javascript:goto('8')" onMouseOver="this.title=url('8')">Process One</a> to become part of the successful instant messaging server <a href="javascript:goto('9')" onMouseOver="this.title=url('9')">ejabberd</a> (also hosted <a href="javascript:goto('10')" onMouseOver="this.title=url('10')">at github</a>). It can be assumed to be as stable as the Yxa branch, and it didn't change anything in the lowest level, mysql_recv.erl. The differences to the original Yxa branch mainly consists of added inspection functions that help using query results, and an <a href="javascript:goto('11')" onMouseOver="this.title=url('11')">independent adoption</a> to the MySQL 4.1 client-server protocol. Also, the original Yxa branch has meanwhile adopted edoc comment format. Find a diff <a href="javascript:goto('12')" onMouseOver="this.title=url('12')">here</a>, one ignoring comment lines <a href="javascript:goto('13')" onMouseOver="this.title=url('13')">here</a>.</li></p>
+
+<p><li> <b>erlang-mysql-driver</b>: in 2006/07 <a href="javascript:goto('ys')" onMouseOver="this.title=url('ys')">Yariv Sadan</a> created a fork from the ejabberd branch, made it a standalone project, gave it the name that stuck, and hosted it at <a href="javascript:goto('15')" onMouseOver="this.title=url('15')">Google Code</a>. Before he moved on to work at Facebook, he had added higher level handling of prepared statements and transactions, and stated that he had improved connection pooling and logging. There were changes both in the original Yxa and the ejabberd branch after the forking off that seem to never have made their way into the erlang-mysql-driver branch, which now lies dormant since Oct '07. Docs were somewhat unsatisfying, as much as for the earlier branches. In Feb '10, Dave Smith started making some</li>
+<a href="javascript:goto('15')" onMouseOver="this.title=url('15')">updates</a> and put them on github, were the driver is now enjoying a couple of <a href="javascript:goto('16')" onMouseOver="this.title=url('16')">active forks</a> that make for a convincing case in favor of the github Network graph.</p>
+
+<p><li> <b>Emysql</b> was started from scratch in 2009 by <a href="javascript:goto('jv')" onMouseOver="this.title=url('jv')">Jacob Vorreuter</a> and <a href="javascript:goto('bw')" onMouseOver="this.title=url('bw')">Bill Warnecke</a> at Electronic Arts, who rewrote the erlang-mysql-driver code because they felt it had been touched by so many people that it had become more complicated than necessary. In a way, the third layer module, mysql.erl, had over time started to become badly entangled with the second, mysql_conn.erl. According to Jacob, Emysql is pretty stable and ran without issue in a production environment at Electronic Arts. This fork is a continuation of <a href="javascript:goto('1')" onMouseOver="this.title=url('1')">their work</a>, including all their commits and adding <a href="javascript:goto('docs')" onMouseOver="this.title=url('docs')">documentation</a>, [samples], <a href="javascript:goto('fixes')" onMouseOver="this.title=url('fixes')">fixes</a> and extensions. </li>
+<br>
+<a href="javascript:goto('vb')" onMouseOver="this.title=url('vb')">Vitaliy Batichko</a> and
+<a href="javascript:goto('cr')" onMouseOver="this.title=url('cr')">Chris Rempel</a> have contributed updates to this branch. Thank you!</p>
+
+<p><li class="ref url"> emysql:<a name="1" id="1" href="http://github.com/JacobVorreuter/emysql" target="_parent">http://github.com/JacobVorreuter/emysql</a></li>
+<li class="ref url"> erlang-mysql-driver:<a name="2" id="2" href="http://github.com/dizzyd/erlang-mysql-driver" target="_parent">http://github.com/dizzyd/erlang-mysql-driver</a></li>
+<li class="ref url"> Royal Institure of Technology:<a name="3" id="3" href="http://www.kth.se/" target="_parent">http://www.kth.se/</a></li>
+<li class="ref url"> Yxa mysql driver:<a name="4" id="4" href="https://github.com/fredrikt/yxa/tree/master/src/mysql" target="_parent">https://github.com/fredrikt/yxa/tree/master/src/mysql</a></li>
+<li class="ref url"> Yxa Home:<a name="5" id="5" href="http://www.stacken.kth.se/project/yxa/index.html" target="_parent">http://www.stacken.kth.se/project/yxa/index.html</a></li>
+<li class="ref url"> Yxa repository at github:<a name="6" id="6" href="https://github.com/fredrikt/yxa" target="_parent">https://github.com/fredrikt/yxa</a></li>
+<li class="ref url"> ejabberd mysql driver:<a name="7" id="7" href="http://svn.process-one.net/ejabberd-modules/mysql/trunk/" target="_parent">http://svn.process-one.net/ejabberd-modules/mysql/trunk/</a></li>
+<li class="ref url"> Process One Home:<a name="8" id="8" href="https://support.process-one.net" target="_parent">https://support.process-one.net</a></li>
+<li class="ref url"> ejabberd Home:<a name="9" id="9" href="http://www.process-one.net/en/ejabberd/" target="_parent">http://www.process-one.net/en/ejabberd/</a></li>
+<li class="ref url"> ejabberd repository at github:<a name="10" id="10" href="https://github.com/processone/ejabberd/" target="_parent">https://github.com/processone/ejabberd/</a></li>
+<li class="ref url"> ejabberd MySQL 4.1. patch:<a name="11" id="11" href="https://support.process-one.net/doc/display/CONTRIBS/Yxa" target="_parent">https://support.process-one.net/doc/display/CONTRIBS/Yxa</a></li>
+<li class="ref url"> Diff of Yxa and ejabberd mysql drivers:<a name="12" id="12" href="https://github.com/Eonblast/Emysql/tree/master/doc/diff-ejabberd-yxa.txt" target="_parent">https://github.com/Eonblast/Emysql/tree/master/doc/diff-ejabberd-yxa.txt</a></li>
+<li class="ref url"> Diff of Yxa and ejabberd mysql drivers ignoring comment changes:<a name="13" id="13" href="https://github.com/Eonblast/Emysql/tree/master/doc/diff-ejabberd-yxa-2.txt" target="_parent">https://github.com/Eonblast/Emysql/tree/master/doc/diff-ejabberd-yxa-2.txt</a></li>
+<li class="ref url"> original erlang-mysql-driver:<a name="14" id="14" href="http://code.google.com/p/erlang-mysql-driver/" target="_parent">http://code.google.com/p/erlang-mysql-driver/</a></li>
+<li class="ref url"> Dave Smith's erlang-mysql-driver at github, currently not maintained:<a name="15" id="15" href="http://github.com/dizzyd/erlang-mysql-driver" target="_parent">http://github.com/dizzyd/erlang-mysql-driver</a></li>
+<li class="ref url"> Fork graph of erlang-mysql-driver at github:<a name="16" id="16" href="https://github.com/dizzyd/erlang-mysql-driver/network" target="_parent">https://github.com/dizzyd/erlang-mysql-driver/network</a></li></p>
+
+<p><li class="ref email"> Magnus Ahltorp:<a name="ma" id="ma" href="mailto:ahltorp@nada.kth.se">ahltorp@nada.kth.se</a></li>
+<li class="ref url"> <a name="ys" id="ys" href="http://yarivsblog.blogspot.com/" target="_parent">http://yarivsblog.blogspot.com/</a></li>
+<li class="ref email"> <a name="bw" id="bw" href="mailto:bill@rupture.com">bill@rupture.com</a></li>
+<li class="ref url"> <a name="jv" id="jv" href="https://github.com/JacobVorreuter" target="_parent">https://github.com/JacobVorreuter</a></li>
+<li class="ref url"> <a name="vb" id="vb" href="https://github.com/bva" target="_parent">https://github.com/bva</a></li>
+<li class="ref url"> Chris Rempel:<a name="cr" id="cr" href="https://github.com/csrl" target="_parent">https://github.com/csrl</a></li>
+<li class="ref email"> Henning Diedrich:<a name="hd" id="hd" href="mailto:hd2010@eonblast.com">hd2010@eonblast.com</a></li>
+<li class="ref email"> Mickael Remond:<a name="mr" id="mr" href="mailto:mickael.remond@process-one.net">mickael.remond@process-one.net</a></li></p>
+
+<p><li class="ref url"> Emysql fixes:<a name="fixes" id="fixes" href="https://github.com/Eonblast/Emysql/issues/closed" target="_parent">https://github.com/Eonblast/Emysql/issues/closed</a></li>
+<li class="ref url"> Emysql online docs:<a name="docs" id="docs" href="http://eonblast.github.com/Emysql/" target="_parent">http://eonblast.github.com/Emysql/</a></li></p>
+
+<h3><a name="Usage">Usage</a></h3>
+
+<h5><a name="Start_the_application">Start the application</a></h5>
+
+<pre> crypto:start(),
+ application:start(emysql).</pre>
+
+<h5><a name="Add_a_pool">Add a pool</a></h5>
+<pre> % emysql:add_pool(PoolName, PoolSize, Username, Password, Host, Port, Database, Encoding) -&gt;
+ % ok | {error, pool_already_exists}
+ % PoolName = atom()
+ % PoolSize = integer()
+ % Username = string()
+ % Password = string()
+ % Host = string()
+ % Port = integer()
+ % Database = string()
+ % Encoding = atom()
+
+ emysql:add_pool(mypoolname, 1, "username", "mypassword", "localhost", 3306, "mydatabase", utf8).</pre>
+
+<h5><a name="Record_Types">Record Types</a></h5>
+<pre> -record(ok_packet, {seq_num, affected_rows, insert_id, status, warning_count, msg}).
+ -record(error_packet, {seq_num, code, msg}).
+ -record(result_packet, {seq_num, field_list, rows, extra}).</pre>
+
+<h5><a name="Executing_SQL_statements">Executing SQL statements</a></h5>
+<pre> % emysql:execute(PoolName, Statement) -&gt; result_packet() | ok_packet() | error_packet()
+ % PoolName = atom()
+ % Statement = string() | binary()
+
+ emysql:execute(mypoolname, &lt;&lt;"SELECT * from mytable"&gt;&gt;).
+ #result_packet{field_list=[...], rows=[...]}
+
+ emysql:execute(mypoolname, &lt;&lt;"UPDATE mytable SET bar = 'baz' WHERE id = 1"&gt;&gt;).
+ #ok_packet{affected_rows=1}</pre>
+
+<h5><a name="Executing_prepared_statements">Executing prepared statements</a></h5>
+<pre> % emysql:prepare(StmtName, Statement) -&gt; ok
+ % StmtName = atom()
+ % Statement = binary() | string()</pre>
+
+<pre> emysql:prepare(my_stmt, &lt;&lt;"SELECT * from mytable WHERE id = ?"&gt;&gt;).
+ ok
+
+ % emysql:execute(PoolName, StmtName, Args) -&gt; result_packet() | ok_packet() | error_packet()
+ % StmtName = atom()
+ % Args = [term()]</pre>
+
+<pre> emysql:execute(mypoolname, my_stmt, [1]).
+ #result_packet{field_list=[...], rows=[...]}</pre>
+
+<h5><a name="Executing_stored_procedures">Executing stored procedures</a></h5>
+
+<pre> % emysql:execute(PoolName, StmtName, Args) -&gt; result_packet() | ok_packet() | error_packet()
+ % StmtName = atom()
+ % Args = [term()]</pre>
+
+<pre> emysql:execute(hello_pool,
+ &lt;&lt;"create procedure sp_hello() begin select * from hello_table; end"&gt;&gt;).
+ {ok_packet,1,0,0,2,0,[]}</pre>
+
+<pre> emysql:execute(hello_pool, &lt;&lt;"call sp_hello();"&gt;&gt;).
+ [{result_packet,6,
+ [{field,2,&lt;&lt;"def"&gt;&gt;,&lt;&lt;"hello_database"&gt;&gt;,&lt;&lt;"hello_table"&gt;&gt;,
+ &lt;&lt;"hello_table"&gt;&gt;,&lt;&lt;"hello_text"&gt;&gt;,&lt;&lt;"hello_text"&gt;&gt;,
+ 254,&lt;&lt;&gt;&gt;,33,60,0,0}],
+ [[&lt;&lt;"Hello World!"&gt;&gt;],[&lt;&lt;"Hello World!"&gt;&gt;]],
+ &lt;&lt;&gt;&gt;},
+ {ok_packet,7,0,0,34,0,[]}]</pre>
+
+<h5><a name="Converting_Row_Data_To_Records">Converting Row Data To Records</a></h5>
+<pre> % emysql_util:as_record(ResultPacket, RecordName, Fields) -&gt; Result
+ % ResultPacket = result_packet()
+ % RecordName = atom() (the name of the record to generate)
+ % Fields = [atom()] (the field names to generate for each record)
+ % Result = [record()]
+
+ -module(fetch_example).
+ -record(foo, {bar, baz, bat}).
+
+ fetch_foo() -&gt;
+ Result = emysql:execute(pool1, &lt;&lt;"select bar, baz, bat from foo"&gt;&gt;),
+ Recs = emysql_util:as_record(Result, foo, record_info(fields, foo)),
+ [begin
+ io:format("foo: ~p, ~p, ~p~n", [Foo#foo.bar, Foo#foo.baz, Foo#foo.bat])
+ end || Foo &lt;- Recs].</pre>
+
+<h3><a name="Getting_Emysql">Getting Emysql</a></h3>
+
+<pre> $ git clone git://github.com/Eonblast/Emysql.git Emysql</pre>
+<h3><a name="Samples">Samples</a></h3>
+
+<h5><a name="Hello_World*">Hello World*</a></h5>
+
+This is a hello world program. Follow the three steps below to try it out.
+<pre> -module(a_hello).
+ -export([run/0]).
+
+ run() -&gt;
+
+ crypto:start(),
+ application:start(emysql),
+
+ emysql:add_pool(hello_pool, 1,
+ "hello_username", "hello_password", "localhost", 3306,
+ "hello_database", utf8),
+
+ emysql:execute(hello_pool,
+ &lt;&lt;"INSERT INTO hello_table SET hello_text = 'Hello World!'"&gt;&gt;),
+
+ Result = emysql:execute(hello_pool,
+ &lt;&lt;"select hello_text from hello_table"&gt;&gt;),
+
+ io:format("~n~p~n", [Result]).</pre>
+
+
+<p>We come back to that source, but first:</p>
+
+<h5><a name="Building_Emysql">Building Emysql</a></h5>
+
+<p>Build emysql.app, using make:</p>
+
+<pre> $ cd Emysql
+ $ make</pre>
+
+
+<h5><a name="Sample_database">Sample database</a></h5>
+
+For the above sample, create a local mysql database. You should have a mysql server installed and running:
+<pre> $ mysql [-u&lt;user&gt; -p]
+ mysql&gt; create database hello_database;
+ mysql&gt; use hello_database;
+ mysql&gt; create table hello_table (hello_text char(20));
+ mysql&gt; grant all privileges on hello_database.* to hello_username@localhost identified by 'hello_password';</pre>
+
+<h5><a name="Run_Hello">Run Hello</a></h5>
+
+<p>Be sure to have ./ebin in your Erlang path. Now copy the Hello World source above at '*' into a file hello.erl and run it (in the Emysql root directory):</p>
+
+<pre> $ erlc hello.erl
+ $ erl -pa ../ebin -s hello run -s init stop -noshell</pre>
+
+<p>See more sample programms, below.</p>
+
+<h5><a name="Running_the_Samples">Running the Samples</a></h5><p>
+Sample programs are in ./samples. </p>
+
+<p><li> <a href="http://github.com/Eonblast/Emysql/blob/master/samples/a_hello.erl">a_hello</a> - Hello World</li>
+<li> <a href="http://github.com/Eonblast/Emysql/blob/master/samples/a_hello2.erl">a_hello2</a> - Hello World somewhat rawer</li>
+<li> <a href="http://github.com/Eonblast/Emysql/blob/master/samples/b_rows_as_records.erl">b_rows_as_records</a> - Using Erlang records to access result rows</li>
+<li> <a href="http://github.com/Eonblast/Emysql/blob/master/samples/c_stored_procedure.erl">c_stored_procedure</a> - Using Stored procedures</li></p>
+
+<p>To run the samples, create the database as listed above at localhost, and:</p>
+
+<pre> $ cd samples
+ $ ./a_hello
+ $ ./b_raw
+ $ ./d_rows_as_records
+ $ ./e_prepared_statement
+ $ ./e_stored_procedure</pre><p>
+or make emysql.app and start a_hello etc. manually along these lines (but
+first create the database as listed above):</p>
+
+<pre> $ make
+ $ cd samples
+ $ erlc a_hello.erl
+ $ erl -pa ../ebin -s a_hello run -s init stop -noshell</pre>
+
+<h3><a name="Links">Links</a></h3>
+
+<p><li> <a href="http://github.com/Eonblast/Emysql">Emysql on Github</a></li>
+<li> <a href="https://github.com/fredrikt/yxa/tree/master/src/mysql">Original Yxa</a> mysql driver</li>
+<li> <a href="http://svn.process-one.net/ejabberd-modules/mysql/trunk/">ejabberd fork</a></li>
+<li> <a href="http://code.google.com/p/erlang-mysql-driver/">'erlang-mysql-driver'</a></li>
+<li> <a href="http://github.com/dizzyd/erlang-mysql-driver">Dave Smith's erlang-mysql-driver fork</a></li>
+<li> <a href="https://github.com/JoelPM/erlang-mysql-driver">A maintained erlang-mysql-driver</a> fork</li>
+<li> <a href="https://github.com/chernomor/erlang-mysql-driver">Another maintained† erlang-mysql-driver</a> fork</li>
+<li> <a href="http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol">MySQL Client Server Protocol</a></li>
+<li> <a href="ftp://ftp.fu-berlin.de/unix/databases/mysql/Downloads/MySQL-5.5/mysql-5.5.8.tar.gz">MySQL 5.5 Source</a></li></p>
+
+<p>†maintained at the time of writing, Jan 2011.</p>
+
+<h3><a name="TODO">TODO</a></h3><p>
+<li> decrementing pool size could close sockets that are in use</li>
+<li> spawn individual conn_mgr gen_server processes for each pool</li>
+<li> allow row results to be returned as binary</li></p>
+
+<h3><a name="License">License</a></h3>
+
+<p>Copyright © 2009-2011
+Bill Warnecke <a href="mailto:bill@rupture.com">bill@rupture.com</a>,
+Jacob Vorreuter <a href="mailto:jacob.vorreuter@gmail.com">jacob.vorreuter@gmail.com</a>,
+Henning Diedrich <a href="mailto:hd2010@eonblast.com">hd2010@eonblast.com</a>,
+Eonblast Corporation <a href="http://www.eonblast.com" target="_top"><tt>http://www.eonblast.com</tt></a>.</p>
+
+<p>Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom
+the Software is furnished to do so, subject to the following
+conditions:</p>
+
+<p>The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.</p>
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+<script>
+// Jump directly to a referenced url given in trailing '[]:...'-notation
+function goto(tag) { parent.document.location.href = url(tag); }
+function url(tag) { var o=document.getElementById(tag); return o ? o.href : '#'+tag; }
+</script>
+
+<hr>
+<div class="navbar"><a name="#navbar_bottom"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
+<p><i>Generated by EDoc, Feb 18 2011, 13:04:43.</i></p>
+</body>
+</html>
diff --git a/deps/edown/samples/markedoc/what-you-should-see/sample2.html b/deps/edown/samples/markedoc/what-you-should-see/sample2.html
new file mode 100644
index 0000000..fca95e4
--- /dev/null
+++ b/deps/edown/samples/markedoc/what-you-should-see/sample2.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<title> A Markedoc Sample Doc</title>
+<link rel="stylesheet" type="text/css" href="stylesheet.css" title="EDoc">
+</head>
+<body bgcolor="white">
+<div class="navbar"><a name="#navbar_top"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
+<h1> A Markedoc Sample Doc</h1>
+<p><b>Version:</b> 0.3 / edown</p>
+<p><b>Authors:</b> You!.</p>
+
+<h3><a name="SAMPLE_2:_EDown_Readme_of_Jan_2011">SAMPLE 2: EDown Readme of Jan 2011</a></h3>
+<pre> --------------------------------------------------------------
+| THIS TEXT IS USED AS A SAMPLE TO ILLUSTRATE MARKEDOC USAGE. |
+| If you see this in your browser, you succeeded compiling it |
+| from markdown into an edoc. |
+ --------------------------------------------------------------</pre>
+
+<p><a href="mailto:ulf@wiger.net">ulf@wiger.net</a></p>
+
+<h4><a name="Status:">Status:</a></h4>
+
+<p>More-or-less readable Markdown can be generated.
+A doclet needs to be written that also creates
+a markdown-based index and overview. Currently, the
+edoc_doclet creates an index.html and overview.html,
+which do not point to the .md files.</p>
+
+<p>To generate markdown edoc, run:</p>
+
+<p><code>edoc:application(App, [{doclet, edown_doclet} | OtherOpts]).</code></p>
+
+<p>The <code>edown_xmerl</code> module is used as an xmerl export module.
+It converts xmerl's "simple xml" to Markdown syntax. Note that
+GH-flavored Markdown allows HTML markup (at least common tags),
+but doesn't expand markdown markup inside HTML markup, so the
+<code>edown_xmerl</code> module has to know the context in which it operates.</p>
+
+<h4><a name="NOTE">NOTE</a></h4>
+
+<p>EDoc provides a plugin structure, so that one may specify own
+layout modules, export modules, and doclets. However, there is
+some overlap esp. between the layout and doclet modules, and
+several functions are expected to produce files on their own.
+This causes a problem for EDown, since it cannot handle frames.
+Instead, it would probably like to create one overview file with
+different sections. It would have been better to have a framework
+where some plugin functions identify the different files to be
+written, and the outline of each, other plugins convert to suitable
+content representation (e.g. HTML or Markdown), and EDoc then
+writes the files necessary.</p>
+
+For now, EDown focuses on producing reasonable Markdown, rather
+than complying fully with the plugin framework. That is, the
+edown_doclet module will not go out of its way to function together
+with any other layout module than edown_layout, and vice versa.
+<script>
+// Jump directly to a referenced url given in trailing '[]:...'-notation
+function goto(tag) { parent.document.location.href = url(tag); }
+function url(tag) { var o=document.getElementById(tag); return o ? o.href : '#'+tag; }
+</script>
+
+<hr>
+<div class="navbar"><a name="#navbar_bottom"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
+<p><i>Generated by EDoc, Feb 18 2011, 13:04:45.</i></p>
+</body>
+</html>
diff --git a/deps/edown/samples/markedoc/what-you-should-see/sample3.html b/deps/edown/samples/markedoc/what-you-should-see/sample3.html
new file mode 100644
index 0000000..0429208
--- /dev/null
+++ b/deps/edown/samples/markedoc/what-you-should-see/sample3.html
@@ -0,0 +1,255 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<title> A Markedoc Sample Doc</title>
+<link rel="stylesheet" type="text/css" href="markedoc.css" title="EDoc">
+</head>
+<body bgcolor="white">
+<div class="navbar"><a name="#navbar_top"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
+<h1> A Markedoc Sample Doc</h1>
+<p><b>Version:</b> 0.3 / edown</p>
+<p><b>Authors:</b> You!.</p>
+
+<h3><a name="SAMPLE_3:_markedoc_0.3_README_of_Feb_2011">SAMPLE 3: markedoc 0.3 README of Feb 2011</a></h3>
+
+<pre> --------------------------------------------------------------
+| THIS TEXT IS USED AS A SAMPLE TO ILLUSTRATE MARKEDOC USAGE. |
+| If you see this in your browser, you succeeded compiling it |
+| from markdown into an edoc. As you see it's complex enough. |
+ --------------------------------------------------------------</pre>
+
+ <p><b>markedoc helps you keep your project's README.md in sync with your overview.edoc.</b></p>
+
+<p>It is for use on Linux, FreeBSD and Mac OS X and any system that you can install <b><a href="javascript:goto('Requirements')" onMouseOver="this.title=url('Requirements')">sed</a></b> on.</p>
+
+<p>Status: <a href="javascript:goto('Status')" onMouseOver="this.title=url('Status')">pre-beta</a>. Quite stable and usable. See <a href="javascript:goto('Status')" onMouseOver="this.title=url('Status')">Status</a>.</p>
+
+<p>markedoc translates <a href="javascript:goto('Markdown')" onMouseOver="this.title=url('Markdown')">Markdown</a> formatted texts into <a href="javascript:goto('Erlang')" onMouseOver="this.title=url('Erlang')">Erlang</a> <a href="javascript:goto('EDoc')" onMouseOver="this.title=url('EDoc')">EDoc</a> format, for inclusion into <a href="javascript:goto('EDoc')" onMouseOver="this.title=url('EDoc')">EDoc</a> generated html docs.</p>
+
+<p>The actual script file is in the bin folder: bin/markedoc.sed.</p>
+
+<p>markedoc is a mere <a href="javascript:goto('sed')" onMouseOver="this.title=url('sed')">sed</a> command file to convert markdown to edoc. It is part of the <b><a href="javascript:goto('edown')" onMouseOver="this.title=url('edown')">edown</a></b> project.</p>
+
+<p>Your contribution to make markedoc stable is highly <a href="javascript:goto('issues')" onMouseOver="this.title=url('issues')">welcome</a>.</p>
+
+<p><li class="ref url"> Issue tracker:<a name="issues" id="issues" href="https://github.com/hdiedrich/markedoc/issues" target="_parent">https://github.com/hdiedrich/markedoc/issues</a></li></p>
+
+<h4><a name="Use">Use</a></h4><p>
+At the command line for</p>
+
+<b>FreeBSD, Mac OS X</b>
+<pre> $ sed -E -f markedoc.sed &lt;markdown file&gt; &gt; &lt;edoc file&gt;</pre>
+
+<b>Linux</b>
+<pre> $ sed -r -f markedoc.sed &lt;markdown file&gt; &gt; &lt;edoc file&gt;</pre>
+
+<p>Usage for Linux and FreeBSD and Mac OS X is completely the same, except for the -r instead of the -E parameter. Both mean the same but happen to have a different name. In the examples below, replace -E with -r where necessary.</p>
+
+<h4><a name="Requirements">Requirements</a></h4><p>
+<li> <b><a href="javascript:goto('sed')" onMouseOver="this.title=url('sed')">sed</a></b>: is part of any Linux, FreeBSD and Mac OSX distribution, also see <a href="javascript:goto('Notes')" onMouseOver="this.title=url('Notes')">Notes</a>.</li></p>
+
+<p><li> <b><a href="javascript:goto('Erlang')" onMouseOver="this.title=url('Erlang')">Erlang/OTP</a></b>, see <a href="javascript:goto('Notes')" onMouseOver="this.title=url('Notes')">Notes</a>.</li></p>
+
+<h4><a name="Test">Test</a></h4>
+
+ <b>FreeBSD, Mac OS X</b>
+<pre> $ etc/test-bsd.sh</pre>
+
+ <b>Linux</b>
+<pre> $ etc/test-linux.sh</pre>
+
+<p>Then check html files as listed in the output.</p>
+
+<h4><a name="Sample">Sample</a></h4>
+
+<p>From project root (were the README.md file is), try out:</p>
+
+ <b>FreeBSD, Mac OS X</b>
+<pre> $ sed -E -f bin/markedoc.sed samples/SAMPLE1.md &gt; samples/doc/SAMPLE.edoc
+ $ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[]'</pre>
+
+ <b>Linux</b>
+<pre> $ sed -r -f bin/markedoc.sed samples/SAMPLE1.md &gt; samples/doc/SAMPLE.edoc
+ $ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[]'</pre>
+
+<p>This creates a SAMPLE.edoc file from SAMPLE1.md, which is then included in the EDoc generation. Point your browser at</p>
+
+<pre> samples/doc/overview-summary.html</pre>
+
+<p>to see the result. For something only vaguely related but pretty, try:</p>
+
+<pre> $ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'</pre>
+
+<p>This illustrates the motivation for the markedoc as it is now: to have all code lines in one block in order to be able to address them as one united div from css. </p>
+
+<p>For your own projects you'd copy markedoc.sed in the right place and do something like:</p>
+
+ <b>FreeBSD, Mac OS X</b>
+<pre> $ sed -E -f bin/markedoc.sed README.md &gt; doc/README.edoc
+ $ erl -noshell -run edoc_run application "'myapp'" '"."' '[]'</pre>
+
+ <b>Linux</b>
+<pre> $ sed -r -f bin/markedoc.sed README.md &gt; doc/README.edoc
+ $ erl -noshell -run edoc_run application "'myapp'" '"."' '[]'</pre>
+
+<p>And that's it. This could also be part of your Makefile. For the intermediary README.edoc to automatically become part of your generated EDoc html pages, you would use a @docfile tag in your overview.edoc file, like so:</p>
+
+<pre> @docfile "doc/README.edoc"</pre>
+
+<p>By running sed, then edoc, this makes the README.edoc part of the overview page. You could also make the README.md straight into an overview.edoc but the way it is allows allows to embedd it into additional context information that should be useful for a proper html doc.</p>
+
+<p>Accordingly, the sample stub overview.edoc used for the samples here, looks like this:</p>
+
+<pre> @author You
+ @title a markedoc sample doc
+ @version 0.2
+ @docfile "samples/doc/SAMPLE.edoc"</pre>
+
+<h4><a name="Status">Status</a></h4>
+
+ <p><b>Pre-Beta</b>. Quite usable, but still likes to trip up EDoc now and then, which is kind of easy to do. </p>
+
+<p>There are many ways to create formats that will make the EDoc generator tilt and unfortunately, the errors it throws are sometimes not quite so illuminating to the reader. But why not try an incremental approach and see what works. As you can see from this <a href="javascript:goto('sample')" onMouseOver="this.title=url('sample')">source sample</a>, which works alright, it's quite a lot that <em>does</em> work and the murky bits can usally be worked out fast. Sometimes an additional line helps, some spaces at the end of a line, general intuitive stuff. Please experiment and push your fixes to me.</p>
+
+ <p><b>Thanks!</b></p>
+
+<h4><a name="Notes">Notes</a></h4>
+
+ <p><b><a href="javascript:goto('Erlang')" onMouseOver="this.title=url('Erlang')">Erlang</a></b> is a programming language used to build massively scalable soft real-time systems with requirements on high availability. Some of its uses are in telecom, banking, e-commerce, computer telephony and instant messaging. Erlang's runtime system has built-in support for concurrency, distribution and fault tolerance. Erlang comes bundled with the Open Telecom Platform, OTP.</p>
+
+<p><li class="ref url"> <a name="Erlang" id="Erlang" href="http://www.erlang.org/doc/" target="_parent">http://www.erlang.org/doc/</a></li></p>
+
+ <p><b><a href="javascript:goto('EDoc')" onMouseOver="this.title=url('EDoc')">EDoc</a></b> is the Erlang program documentation generator. Inspired by the Javadoc tool for the Java programming language, EDoc is adapted to the conventions of the Erlang world, and has several features not found in Javadoc. Edoc is part of the Erlang/OTP distribution.</p>
+
+<p><li class="ref url"> <a name="EDoc" id="EDoc" href="http://www.erlang.org/doc/apps/edoc/chapter.html" target="_parent">http://www.erlang.org/doc/apps/edoc/chapter.html</a></li></p>
+
+ <p><b><a href="javascript:goto('edown')" onMouseOver="this.title=url('edown')">edown</a></b> is an EDoc extension for generating Github-flavored Markdown. It uses edoc-style commented Erlang sources to create markdown files from them. </p>
+
+<p><li class="ref url"> <a name="edown" id="edown" href="https://github.com/uwiger/edown" target="_parent">https://github.com/uwiger/edown</a></li></p>
+
+ <p><b><a href="javascript:goto('Markdown')" onMouseOver="this.title=url('Markdown')">Markdown</a></b> is a text-to-HTML conversion tool for web writers. Markdown allows you to write using an easy-to-read, easy-to-write plain text format, then convert it to structurally valid XHTML (or HTML).</p>
+
+<p><li class="ref url"> <a name="Markdown" id="Markdown" href="http://daringfireball.net/projects/markdown/" target="_parent">http://daringfireball.net/projects/markdown/</a></li></p>
+
+ <p><b><a href="javascript:goto('sed')" onMouseOver="this.title=url('sed')">sed</a></b> ('stream editor') is a Unix utility that parses text files and implements a programming language which can apply textual transformations to such files. It reads input files line by line (sequentially), applying the operation which has been specified via the command line (or a sed script), and then outputs the line. It is available today for most operating systems. There seems to be <a href="javascript:goto('winsed')" onMouseOver="this.title=url('winsed')">one for Windows</a>, too.</p>
+
+<p><li class="ref url"> <a name="sed" id="sed" href="http://en.wikipedia.org/wiki/Sed" target="_parent">http://en.wikipedia.org/wiki/Sed</a></li>
+<li class="ref url"> <a name="winsed" id="winsed" href="http://gnuwin32.sourceforge.net/packages/sed.htm" target="_parent">http://gnuwin32.sourceforge.net/packages/sed.htm</a></li>
+<li class="ref url"> This markdown file is translated alright by markedoc.:<a name="sample" id="sample" href="https://github.com/Eonblast/Emysql/raw/master/README.md" target="_parent">https://github.com/Eonblast/Emysql/raw/master/README.md</a></li></p>
+
+<h4><a name="Todo">Todo</a></h4><p>
+<li> make work with non-FreeBSD sed</li>
+<li> robust alternates not tested for some time</li>
+<li> protect ampersands</li></p>
+
+<h4><a name="Development">Development</a></h4><p>
+To test markedoc, see '<a href="javascript:goto('Test')" onMouseOver="this.title=url('Test')">Test</a>', above. Or use</p>
+
+ <b>FreeBSD</b>
+<pre> sed -E -f bin/markedoc.sed samples/SAMPLE1.md &gt; samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+ mv samples/doc/overview-summary.html samples/your-test-results/sample1.html
+ mv samples/doc/SAMPLE.edoc samples/your-test-results/SAMPLE1.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE2.md &gt; samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[]'
+ mv samples/doc/overview-summary.html samples/your-test-results/sample2.html
+ mv samples/doc/SAMPLE.edoc samples/your-test-results/SAMPLE2.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE3.md &gt; samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+ mv samples/doc/overview-summary.html samples/your-test-results/sample3.html
+ mv samples/doc/SAMPLE.edoc samples/your-test-results/SAMPLE3.edoc</pre><p>
+Then check samples/your-test-results/sample1.html - sample3.html and compare with samples/what-you-should-see/sample1.html, sample2.html and samples/what-you-could-see/sample3.html.</p>
+
+<p>To create the reference samples:</p>
+
+ <b>FreeBSD</b>
+<pre> etc/make_samples.sh</pre>
+
+<p>or do the following to create six samples and save the results into samples/what-you-should-see/ and samples/what-you-could-see/</p>
+
+ <b>FreeBSD</b>
+<pre> sed -E -f bin/markedoc.sed samples/SAMPLE1.md &gt; samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[]'
+ mv samples/doc/overview-summary.html samples/what-you-could-see/sample1.html
+ mv samples/doc/SAMPLE.edoc samples/what-you-should-see/SAMPLE1.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE2.md &gt; samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[]'
+ mv samples/doc/overview-summary.html samples/what-you-could-see/sample2.html
+ mv samples/doc/SAMPLE.edoc samples/what-you-should-see/SAMPLE2.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE3.md &gt; samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[]'
+ mv samples/doc/overview-summary.html samples/what-you-could-see/sample3.html
+ mv samples/doc/SAMPLE.edoc samples/what-you-should-see/SAMPLE3.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE1.md &gt; samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+ mv samples/doc/overview-summary.html samples/what-you-could-see/sample1.html
+ mv samples/doc/SAMPLE.edoc samples/what-you-could-see/SAMPLE1.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE2.md &gt; samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+ mv samples/doc/overview-summary.html samples/what-you-could-see/sample2.html
+ mv samples/doc/SAMPLE.edoc samples/what-you-could-see/SAMPLE2.edoc
+
+ sed -E -f bin/markedoc.sed samples/SAMPLE3.md &gt; samples/doc/SAMPLE.edoc
+ erl -noshell -run edoc_run application "'myapp'" '"samples"' '[{def,{vsn,""}},{stylesheet, "markedoc.css"}]'
+ mv samples/doc/overview-summary.html samples/what-you-could-see/sample3.html
+ mv samples/doc/SAMPLE.edoc samples/what-you-could-see/SAMPLE3.edoc</pre>
+
+<p>To test this very README.md, use markdown.lua, credit Niklas Frykholm, <a href="mailto:niklas@frykholm.se">niklas@frykholm.se</a>:</p>
+
+<pre> lua etc/markdown.lua README.md</pre>
+<h4><a name="HTML_Special_Signs">HTML Special Signs</a></h4><p>
+http://www.mountaindragon.com/html/iso.htm</p>
+
+
+<h4><a name="License">License</a></h4><p>
+This script is free software. It comes without any warranty.</p>
+
+<h4><a name="Author">Author</a></h4><p>
+H. Diedrich <a href="mailto:hd2010@eonblast.com">hd2010@eonblast.com</a></p>
+
+<h4><a name="History">History</a></h4>
+<pre></pre><p>
+02/03/11 - 0.3 - <b>rough edges polished:</b> Linux, FreeBSD, Mac OS X</p>
+
+<li> added doc for Linux use</li>
+<li> added support for multi-line '[..]: ... "..."' references</li>
+<li> added footnote signs and sepcial chars:</li>
+<li> dagger, double dagger: †, ‡, stars: *, **, *** </li>
+<li> superscript 1, 2, 3: ¹, ², ³, copyright ©, ®, ™, </li>
+<li> guillemots «, » and middle dot ·</li>
+<li> added test batches etc/test-bsd.sh and etc/test-linux.sh</li>
+<li> added css sample in samples/what-you-could-see/ </li>
+<li> added classes for <code>&lt;li&gt;</code> list item tags for '[..]:...'-references</li>
+<li> fixed italic and bold merker interference bullet points</li>
+<li> eliminated [..]: part of '[..]:...'-references, flipping "..." to lead</li>
+<li> dev: sample creation batch make_samples.sh added</li>
+<pre></pre><p>
+02/02/11 - 0.2 - <b>basics complete:</b> FreeBSD / Mac OS X</p>
+
+<li> added support for === and --- headline format</li>
+<li> fixed cutting off of last lines </li>
+<li> fixed page-local anchor jumps</li>
+<li> fixed space in javascript links</li>
+<li> eliminated end-space requirement at end of '[..]:...'-style references.</li>
+<li> eliminated need for echoing '@doc' first into edoc output file</li>
+<li> added javascript title tag setting for '[..]:...'-style references.</li>
+<pre></pre>
+01/31/11 - 0.1 - <b>first release:</b> FreeBSD / Mac OS X
+<pre></pre>
+<script>
+// Jump directly to a referenced url given in trailing '[]:...'-notation
+function goto(tag) { parent.document.location.href = url(tag); }
+function url(tag) { var o=document.getElementById(tag); return o ? o.href : '#'+tag; }
+</script>
+
+<hr>
+<div class="navbar"><a name="#navbar_bottom"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
+<p><i>Generated by EDoc, Feb 18 2011, 13:04:48.</i></p>
+</body>
+</html>
diff --git a/deps/edown/samples/markedoc/what-you-should-see/stylesheet.css b/deps/edown/samples/markedoc/what-you-should-see/stylesheet.css
new file mode 100644
index 0000000..e426a90
--- /dev/null
+++ b/deps/edown/samples/markedoc/what-you-should-see/stylesheet.css
@@ -0,0 +1,55 @@
+/* standard EDoc style sheet */
+body {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ margin-left: .25in;
+ margin-right: .2in;
+ margin-top: 0.2in;
+ margin-bottom: 0.2in;
+ color: #000000;
+ background-color: #ffffff;
+}
+h1,h2 {
+ margin-left: -0.2in;
+}
+div.navbar {
+ background-color: #add8e6;
+ padding: 0.2em;
+}
+h2.indextitle {
+ padding: 0.4em;
+ background-color: #add8e6;
+}
+h3.function,h3.typedecl {
+ background-color: #add8e6;
+ padding-left: 1em;
+}
+div.spec {
+ margin-left: 2em;
+ background-color: #eeeeee;
+}
+a.module,a.package {
+ text-decoration:none
+}
+a.module:hover,a.package:hover {
+ background-color: #eeeeee;
+}
+ul.definitions {
+ list-style-type: none;
+}
+ul.index {
+ list-style-type: none;
+ background-color: #eeeeee;
+}
+
+/*
+ * Minor style tweaks
+ */
+ul {
+ list-style-type: square;
+}
+table {
+ border-collapse: collapse;
+}
+td {
+ padding: 3
+}
diff --git a/deps/edown/samples/markedoc/your-test-results/erlang.png b/deps/edown/samples/markedoc/your-test-results/erlang.png
new file mode 100644
index 0000000..987a618
--- /dev/null
+++ b/deps/edown/samples/markedoc/your-test-results/erlang.png
Binary files differ
diff --git a/deps/edown/samples/markedoc/your-test-results/markedoc-footer.png b/deps/edown/samples/markedoc/your-test-results/markedoc-footer.png
new file mode 100644
index 0000000..27414c1
--- /dev/null
+++ b/deps/edown/samples/markedoc/your-test-results/markedoc-footer.png
Binary files differ
diff --git a/deps/edown/samples/markedoc/your-test-results/markedoc.css b/deps/edown/samples/markedoc/your-test-results/markedoc.css
new file mode 100644
index 0000000..36e6ee6
--- /dev/null
+++ b/deps/edown/samples/markedoc/your-test-results/markedoc.css
@@ -0,0 +1,202 @@
+/* standard EDoc style sheet */
+body {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: .9em;
+ margin-left: 2em;
+ margin-right: 2em;
+ margin-top: 2em;
+ margin-bottom: 2em;
+ color: #000000;
+ background-color: #ffffff;
+}
+h1 {
+ margin-left: 0;
+ font-size: 3em;
+}
+h2 {
+ margin-left: -.3em;
+ font-size: 2em;
+}
+
+h2.indextitle {
+
+ font-size: 1em;
+}
+
+h2.indextitle table {
+ border-collapse: separate;
+ border-bottom-right-radius: 1em;
+ border: 1px solid #3070a0;
+ padding: 1em;
+
+ font-size: 0.9em;
+}
+
+h2.indextitle table tr td {
+ background-color: #deeefe;
+ border: 0;
+ border-bottom: 1px solid white;
+}
+
+h3 {
+ margin-left: -.3em;
+ font-size: 1.8em;
+}
+
+h4 {
+ margin-left: -.3em;
+ font-size: 1.5em;
+}
+
+div.navbar {
+ color: white;
+ font-weight: bold;
+ text-size: 0.9em;
+ background-color: #3070a0;
+ padding: 0.2em;
+ border-bottom: 1em solid #f09840;
+ border-top-left-radius: 1em;
+}
+h2.indextitle {
+ color: white;
+ padding: 0.4em;
+ background-color: #3070a0;
+ border-bottom: 10px solid #f09840;
+ border-top-left-radius: 1em;
+}
+
+h3.function,h3.typedecl {
+ color: white;
+ background-color: #3070a0;
+ padding-left: 1em;
+ border-bottom: 5px solid #f09840;
+ border-top-left-radius: 1em;
+}
+
+div.spec {
+ font-weight: bold;
+ margin-left: 0em;
+ padding: .2em 1.1em .2em;
+ background-color: #eeeeee;
+ border-bottom-right-radius: 1em;
+
+}
+a { color: inherit; padding: 2px; text-decoration: none; }
+
+a.module,a.package {
+ text-decoration:none
+}
+a:hover {
+ background-color: #deeefe;
+ color: navy;
+}
+
+a.module:hover,a.package:hover {
+ background-color: #deeefe;
+}
+ul.definitions {
+ list-style-type: none;
+}
+
+ul.index {
+ list-style-type: none;
+ border-collapse: separate;
+ border-bottom-right-radius: 1em;
+ border: 1px solid #3070a0;
+ padding: 1em;
+
+ font-size: 1.1em;
+}
+
+ul.index li {
+ background-color: #feeede;
+ border: 0;
+ border-bottom: 1px solid white;
+ padding: 2px;
+}
+
+ul {
+ list-style-type: square;
+}
+table {
+ border-collapse: collapse;
+}
+
+table.index {
+ border-collapse: separate;
+ border-bottom-right-radius: 1em;
+ border: 1px solid #3070a0;
+ padding: 1em;
+
+ font-size: 0.9em;
+}
+
+table.index td {
+ background-color: #deeefe;
+ border: 0;
+ border-bottom: 1px solid white;
+}
+
+
+table.index td a:hover {
+ background-color: #feeede;
+}
+
+td {
+ padding: 2px;
+}
+
+code {
+ font-size: 1.1em;
+}
+
+div.navbar {
+ background:url("markedoc-footer.png") no-repeat right top;
+}
+
+
+div.navbar a {
+ color: lightblue;
+ font-size: 0.9em;
+ font-weight: bold;
+ margin-left:1em;
+ line-height: 1em;
+}
+
+img {
+ margin-right: 10px;
+
+ }
+
+a {
+ color: navy;
+}
+
+h1 a, h2 a, h3 a, h4 a, h5 a {
+ color: black;
+ }
+
+h3.function a {
+ color: white;
+ }
+
+pre { margin: 0;
+ margin-top: 1em;
+ margin-bottom: 1em;
+ padding: 1.2em;
+ background-color: #f0f4f8;
+ border-radius: 1em;
+ line-height: 1.3em;
+ color: #101000; }
+span.comment {
+ color: dimgrey;
+ }
+
+li.ref {
+ background-color: #fff8e8;
+ padding: 0.1em;
+ margin-left: 0em;
+ padding-left: 1em;
+ list-style-type:square;
+ font-variant: small-caps;
+ }
diff --git a/deps/edown/samples/markedoc/your-test-results/stylesheet.css b/deps/edown/samples/markedoc/your-test-results/stylesheet.css
new file mode 100644
index 0000000..e426a90
--- /dev/null
+++ b/deps/edown/samples/markedoc/your-test-results/stylesheet.css
@@ -0,0 +1,55 @@
+/* standard EDoc style sheet */
+body {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ margin-left: .25in;
+ margin-right: .2in;
+ margin-top: 0.2in;
+ margin-bottom: 0.2in;
+ color: #000000;
+ background-color: #ffffff;
+}
+h1,h2 {
+ margin-left: -0.2in;
+}
+div.navbar {
+ background-color: #add8e6;
+ padding: 0.2em;
+}
+h2.indextitle {
+ padding: 0.4em;
+ background-color: #add8e6;
+}
+h3.function,h3.typedecl {
+ background-color: #add8e6;
+ padding-left: 1em;
+}
+div.spec {
+ margin-left: 2em;
+ background-color: #eeeeee;
+}
+a.module,a.package {
+ text-decoration:none
+}
+a.module:hover,a.package:hover {
+ background-color: #eeeeee;
+}
+ul.definitions {
+ list-style-type: none;
+}
+ul.index {
+ list-style-type: none;
+ background-color: #eeeeee;
+}
+
+/*
+ * Minor style tweaks
+ */
+ul {
+ list-style-type: square;
+}
+table {
+ border-collapse: collapse;
+}
+td {
+ padding: 3
+}
diff --git a/deps/edown/src/edown.app.src b/deps/edown/src/edown.app.src
new file mode 100644
index 0000000..27b9d99
--- /dev/null
+++ b/deps/edown/src/edown.app.src
@@ -0,0 +1,25 @@
+%% -*- erlang -*-
+%%==============================================================================
+%% Copyright 2014 Ulf Wiger
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%==============================================================================
+
+{application, edown,
+ [
+ {vsn, git},
+ {description, "Markdown extension for EDoc"},
+ {applications, [kernel, stdlib, edoc]},
+ {registered, []},
+ {env, []}
+ ]}.
diff --git a/deps/edown/src/edown_doclet.erl b/deps/edown/src/edown_doclet.erl
new file mode 100644
index 0000000..cc3bc92
--- /dev/null
+++ b/deps/edown/src/edown_doclet.erl
@@ -0,0 +1,601 @@
+%%==============================================================================
+%% Copyright 2014 Ulf Wiger
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%==============================================================================
+%% @author Ulf Wiger <ulf@wiger.net>
+%% @copyright 2014 Ulf Wiger
+%% @end
+%% =============================================================================
+%% Modified 2012 by Beads Land-Trujillo: get_git_branch/0, redirect_href/3
+%% =============================================================================
+
+%% @doc EDoc Doclet module for producing Markdown.
+
+
+-module(edown_doclet).
+
+-export([run/2]).
+
+-import(edoc_report, [report/2, warning/2]).
+
+-include_lib("edoc/include/edoc_doclet.hrl").
+
+-define(EDOC_APP, edoc).
+-define(DEFAULT_FILE_SUFFIX, ".md").
+-define(INDEX_FILE, "README.md").
+-define(OVERVIEW_FILE, "overview.edoc").
+-define(PACKAGE_SUMMARY, "package-summary.md").
+-define(OVERVIEW_SUMMARY, "overview-summary.md").
+-define(PACKAGES_FRAME, "packages-frame.md").
+-define(MODULES_FRAME, "modules-frame.md").
+-define(STYLESHEET, "stylesheet.css").
+-define(IMAGE, "erlang.png").
+-define(NL, "\n").
+
+-include_lib("xmerl/include/xmerl.hrl").
+
+%% Sources is the list of inputs in the order they were found. Packages
+%% and Modules are sorted lists of atoms without duplicates. (They
+%% usually include the data from the edoc-info file in the target
+%% directory, if it exists.) Note that the "empty package" is never
+%% included in Packages!
+
+%% @spec (Command::doclet_gen() | doclet_toc(), edoc_context()) -> ok
+%% @doc Main doclet entry point.
+%%
+%% Also see {@link //edoc/edoc:layout/2} for layout-related options, and
+%% {@link //edoc/edoc:get_doc/2} for options related to reading source
+%% files.
+%%
+%% Options:
+%% <dl>
+%% <dt>{@type {file_suffix, string()@}}
+%% </dt>
+%% <dd>Specifies the suffix used for output files. The default value is
+%% `".md"'.
+%% </dd>
+%% <dt>{@type {hidden, bool()@}}
+%% </dt>
+%% <dd>If the value is `true', documentation of hidden modules and
+%% functions will also be included. The default value is `false'.
+%% </dd>
+%% <dt>{@type {overview, //edoc/edoc:filename()@}}
+%% </dt>
+%% <dd>Specifies the name of the overview-file. By default, this doclet
+%% looks for a file `"overview.edoc"' in the target directory.
+%% </dd>
+%% <dt>{@type {private, bool()@}}
+%% </dt>
+%% <dd>If the value is `true', documentation of private modules and
+%% functions will also be included. The default value is `false'.
+%% </dd>
+%% <dt>{@type {stylesheet, string()@}}
+%% </dt>
+%% <dd>Specifies the URI used for referencing the stylesheet. The
+%% default value is `"stylesheet.css"'. If an empty string is
+%% specified, no stylesheet reference will be generated.
+%% </dd>
+%% <dt>{@type {stylesheet_file, //edoc/edoc:filename()@}}
+%% </dt>
+%% <dd>Specifies the name of the stylesheet file. By default, this
+%% doclet uses the file `"stylesheet.css"' in the `priv'
+%% subdirectory of the EDoc installation directory. The named file
+%% will be copied to the target directory.
+%% </dd>
+%% <dt>{@type {title, string()@}}
+%% </dt>
+%% <dd>Specifies the title of the overview-page.
+%% </dd>
+%% </dl>
+
+%% INHERIT-OPTIONS: title/2
+%% INHERIT-OPTIONS: sources/5
+%% INHERIT-OPTIONS: overview/4
+%% INHERIT-OPTIONS: copy_stylesheet/2
+%% INHERIT-OPTIONS: stylesheet/1
+
+run(#doclet_gen{}=Cmd, Ctxt) ->
+ %% dbg:tracer(),
+ %% dbg:tpl(?MODULE,x),
+ %% dbg:tpl(edown_layout,x),
+ %% dbg:tpl(edown_xmerl, x),
+ %% dbg:p(all,[c]),
+ gen(Cmd#doclet_gen.sources,
+ Cmd#doclet_gen.app,
+ Cmd#doclet_gen.packages,
+ Cmd#doclet_gen.modules,
+ Cmd#doclet_gen.filemap,
+ Ctxt);
+run(#doclet_toc{}=Cmd, Ctxt) ->
+ toc(Cmd#doclet_toc.paths, Ctxt).
+
+gen(Sources, App, Packages, Modules, FileMap, Ctxt) ->
+ Dir = Ctxt#context.dir,
+ Env = Ctxt#context.env,
+ Options0 = Ctxt#context.opts,
+ Options = set_app_default([{layout,edown_layout} |
+ Options0] ++
+ [{file_suffix,".md"}]),
+
+ Title = title(App, Options),
+ %% CSS = stylesheet(Options),
+ {Modules1, Error} = sources(Sources, Dir, Modules, Env, Options),
+ packages(Packages, Dir, FileMap, Env, Options),
+ Overview = overview(Dir, Title, Env, Options),
+ Data =
+ Overview
+ ++ lists:concat([packages_frame(Packages) || Packages =/= []])
+ ++ lists:concat([modules_frame(Modules1) || Modules1 =/= []]),
+
+ Text = xmerl:export_simple_content(Data, edown_xmerl),
+ write_file(Text, Dir, right_suffix(?INDEX_FILE, Options), '', ''),
+ edoc_lib:write_info_file(App, Packages, Modules1, Dir),
+ copy_stylesheet(Dir, Options),
+ copy_image(Dir, Options),
+ make_top_level_README(Data, Options),
+ %% handle postponed error during processing of source files
+ case Error of
+ true -> exit(error);
+ false -> ok
+ end.
+
+right_suffix(File, Options) ->
+ Base = filename:basename(File, filename:extension(File)),
+ Ext = proplists:get_value(file_suffix, Options),
+ Base ++ Ext.
+
+set_app_default(Opts) ->
+ case lists:keyfind(app_default,1,Opts) of
+ false ->
+ [{app_default, "http://www.erlang.org/doc/man"}|Opts];
+ _ ->
+ Opts
+ end.
+
+make_top_level_README(Data, Options) ->
+ case proplists:get_value(top_level_readme, Options) of
+ undefined ->
+ ok;
+ {Path, BaseHRef} ->
+ Dir = filename:dirname(Path),
+ Filename = filename:basename(Path),
+ make_top_level_README(Data, Dir, Filename, BaseHRef,
+ get_git_branch(), target(Options));
+ {Path, BaseHRef, Branch} ->
+ Dir = filename:dirname(Path),
+ Filename = filename:basename(Path),
+ make_top_level_README(Data, Dir, Filename, BaseHRef, Branch,
+ target(Options))
+ end.
+
+target(Options) ->
+ proplists:get_value(edown_target, Options, github).
+
+%% make_top_level_README(Data, Dir, F, BaseHRef) ->
+%% Branch = get_git_branch(),
+%% make_top_level_README(Data, Dir, F, BaseHRef, Branch).
+
+make_top_level_README(Data, Dir, F, BaseHRef, Branch, Target) ->
+ Exp = [xmerl_lib:expand_element(D) || D <- Data],
+ New = [xmerl_lib:mapxml(
+ fun(#xmlElement{name = a,
+ attributes = Attrs} = E) ->
+ case redirect_href(Attrs, Branch, BaseHRef, Target) of
+ {true, Attrs1} ->
+ E#xmlElement{attributes = Attrs1};
+ false ->
+ E
+ end;
+ (Other) ->
+ Other
+ end, Exp1) || Exp1 <- Exp],
+ Text = xmerl:export_simple_content(New, edown_xmerl),
+ write_file(Text, Dir, F).
+
+redirect_href(Attrs, Branch, BaseHRef, Target) ->
+ {Prefix, URIArgs} = href_redirect_parts(Target, BaseHRef, Branch),
+ case lists:keyfind(href, #xmlAttribute.name, Attrs) of
+ false ->
+ false;
+ #xmlAttribute{value = "/" ++ _} ->
+ false;
+ #xmlAttribute{value = Href} = A ->
+ case re:run(Href, ":", []) of
+ {match, _} ->
+ false;
+ nomatch ->
+ case Href of
+ [$# | _] ->
+ HRef1 = do_redirect(?INDEX_FILE ++ Href,
+ Prefix, URIArgs);
+ _Else ->
+ HRef1 = do_redirect(Href, Prefix, URIArgs)
+ end,
+ {true,
+ lists:keyreplace(
+ href, #xmlAttribute.name, Attrs,
+ A#xmlAttribute{value = HRef1})}
+ end
+ end.
+
+href_redirect_parts(github, BaseHRef, Branch) ->
+ {BaseHRef ++ "/blob/" ++ Branch ++ "/", []};
+href_redirect_parts(stash, BaseHRef, Branch) ->
+ {BaseHRef ++ "/browse/", "?at=refs/heads/" ++ Branch}.
+
+
+do_redirect(Href, Prefix, Args) ->
+ case filename:split(Href) of
+ [_] ->
+ Prefix ++ "doc/" ++ Href ++ Args;
+ _ ->
+ Prefix ++ Href ++ Args
+ end.
+
+get_git_branch() ->
+ case os:cmd("git rev-parse --abbrev-ref HEAD") of
+ "fatal:" ++ _ -> "master"; % sensible default
+ Git ->
+ case string:tokens(Git, " \n") of
+ [Branch] -> Branch;
+ Other -> erlang:error({cannot_get_git_branch, Other})
+ end
+ end.
+
+%% Tried to display logo in a table on top of page, but not working.
+%% Presumably, this hits some limitation of Markdown
+%%
+%% logo() ->
+%% {img, [{src, "erlang.png"},{alt,["Erlang logo"]}],[]}.
+
+%% NEW-OPTIONS: title
+%% DEFER-OPTIONS: run/2
+
+title(App, Options) ->
+ proplists:get_value(title, Options,
+ if App == ?NO_APP ->
+ "Overview";
+ true ->
+ io_lib:fwrite("Application: ~s", [App])
+ end).
+
+
+%% Processing the individual source files.
+
+%% NEW-OPTIONS: file_suffix, private, hidden
+%% INHERIT-OPTIONS: edoc:layout/2
+%% INHERIT-OPTIONS: edoc:get_doc/3
+%% DEFER-OPTIONS: run/2
+
+sources(Sources, Dir, Modules, Env, Options) ->
+ Suffix = proplists:get_value(file_suffix, Options,
+ ?DEFAULT_FILE_SUFFIX),
+ Private = proplists:get_bool(private, Options),
+ Hidden = proplists:get_bool(hidden, Options),
+ {Ms, E} = lists:foldl(fun (Src, {Set, Error}) ->
+ source(Src, Dir, Suffix, Env, Set,
+ Private, Hidden, Error, Options)
+ end,
+ {sets:new(), false}, Sources),
+ {[M || M <- Modules, sets:is_element(M, Ms)], E}.
+
+
+%% Generating documentation for a source file, adding its name to the
+%% set if it was successful. Errors are just flagged at this stage,
+%% allowing all source files to be processed even if some of them fail.
+
+source({M, P, Name, Path}, Dir, Suffix, Env, Set, Private, Hidden,
+ Error, Options) ->
+ File = filename:join(Path, Name),
+ Enc = guess_encoding(File),
+ case catch {ok, edoc:get_doc(File, Env, Options)} of
+ {ok, {Module, Doc}} ->
+ check_name(Module, M, P, File),
+ case ((not is_private(Doc)) orelse Private)
+ andalso ((not is_hidden(Doc)) orelse Hidden) of
+ true ->
+ Text = edoc:layout(Doc, Options),
+ Name1 = packages_last(M) ++ Suffix,
+ write_file(Text, Dir, Name1, Name, P, Enc),
+ {sets:add_element(Module, Set), Error};
+ false ->
+ {Set, Error}
+ end;
+ R ->
+ report("skipping source file '~s': ~W.", [File, R, 15]),
+ {Set, true}
+ end.
+
+guess_encoding(File) ->
+ try epp:read_encoding(File) of
+ none -> epp:default_encoding();
+ Enc -> Enc
+ catch
+ _:_ ->
+ epp:default_encoding()
+ end.
+
+write_file(Text, Dir, F) ->
+ write_file(Text, Dir, F, F, '', auto).
+
+write_file(Text, Dir, Name, P) ->
+ write_file(Text, Dir, Name, Name, P, auto).
+
+write_file(Text, Dir, LastName, Name, P) ->
+ write_file(Text, Dir, LastName, Name, P, auto).
+
+write_file(Text, Dir, LastName, Name, P, Enc) ->
+ %% edoc_lib:write_file/5 (with encoding support) was added in OTP R16B
+ case lists:member({write_file,5}, edoc_lib:module_info(exports)) of
+ true ->
+ edoc_lib:write_file(Text, Dir, LastName, P,
+ [{encoding, encoding(Enc, Name)}]);
+ false ->
+ edoc_lib:write_file(Text, Dir, LastName, P)
+ end.
+
+encoding(auto, Name) ->
+ edoc_lib:read_encoding(Name, []);
+encoding(Enc, _) ->
+ Enc.
+
+
+check_name(M, M0, P0, File) ->
+ case erlang:function_exported(packages, strip_last, 1) of
+ true ->
+ check_name_(M, M0, P0, File);
+ false ->
+ ok
+ end.
+
+%% If running pre-R16B OTP, where packages are still "supported".
+packages_last(M) ->
+ case erlang:function_exported(packages, last, 1) of
+ true -> packages:last(M);
+ false -> atom_to_list(M)
+ end.
+
+check_name_(M, M0, P0, File) ->
+ P = list_to_atom(packages:strip_last(M)),
+ N = packages:last(M),
+ N0 = packages:last(M0),
+ case N of
+ [$? | _] ->
+ %% A module name of the form '?...' is assumed to be caused
+ %% by the epp_dodger parser when the module declaration has
+ %% the form '-module(?MACRO).'; skip the filename check.
+ ok;
+ _ ->
+ if N =/= N0 ->
+ warning("file '~s' actually contains module '~s'.",
+ [File, M]);
+ true ->
+ ok
+ end
+ end,
+ if P =/= P0 ->
+ warning("file '~s' belongs to package '~s', not '~s'.",
+ [File, P, P0]);
+ true ->
+ ok
+ end.
+
+
+%% Generating the summary files for packages.
+
+%% INHERIT-OPTIONS: read_file/4
+%% INHERIT-OPTIONS: edoc_lib:run_layout/2
+
+packages(Packages, Dir, FileMap, Env, Options) ->
+ lists:foreach(fun (P) ->
+ package(P, Dir, FileMap, Env, Options)
+ end,
+ Packages).
+
+package(P, Dir, FileMap, Env, Opts) ->
+ Tags = case FileMap(P) of
+ "" ->
+ [];
+ File ->
+ read_file(File, package, Env, Opts)
+ end,
+ Data = edoc_data:package(P, Tags, Env, Opts),
+ F = fun (M) ->
+ M:package(Data, Opts)
+ end,
+ Text = edoc_lib:run_layout(F, Opts),
+ write_file(Text, Dir, ?PACKAGE_SUMMARY, P).
+
+
+
+packages_frame(Ps) ->
+ [{h2, [{class, "indextitle"}], ["Packages"]},
+ {table, [{width, "100%"}, {border, 0},
+ {summary, "list of packages"}],
+ lists:concat(
+ [[{tr, [{td, [], [{a, [{href, package_ref(P)},
+ {class, "package"}],
+ [atom_to_list(P)]}]}]}]
+ || P <- Ps])}].
+
+
+modules_frame(Ms) ->
+ [{h2, [{class, "indextitle"}], ["Modules"]},
+ {table, [{width, "100%"}, {border, 0},
+ {summary, "list of modules"}],
+ lists:concat(
+ [[?NL,
+ {tr, [{td, [],
+ [{a, [{href, module_ref(M)},
+ {class, "module"}],
+ [atom_to_list(M)]}]}]}]
+ || M <- Ms])}].
+
+module_ref(M) ->
+ edoc_refs:relative_package_path(M, '') ++ ?DEFAULT_FILE_SUFFIX.
+
+package_ref(P) ->
+ edoc_lib:join_uri(edoc_refs:relative_package_path(P, ''),
+ ?PACKAGE_SUMMARY).
+
+
+%% xhtml(Title, CSS, Content) ->
+%% xhtml_1(Title, CSS, {body, [{bgcolor, "white"}], Content}).
+
+%% xhtml_1(Title, CSS, Body) ->
+%% {html, [?NL,
+%% {head, [?NL, {title, [Title]}, ?NL] ++ CSS},
+%% ?NL,
+%% Body,
+%% ?NL]
+%% }.
+
+%% NEW-OPTIONS: overview
+%% INHERIT-OPTIONS: read_file/4
+%% INHERIT-OPTIONS: edoc_lib:run_layout/2
+%% INHERIT-OPTIONS: edoc_extract:file/4
+%% DEFER-OPTIONS: run/2
+
+overview(Dir, Title, Env, Opts) ->
+ File = proplists:get_value(overview, Opts,
+ filename:join(Dir, ?OVERVIEW_FILE)),
+ Tags = read_file(File, overview, Env, Opts),
+ Data = edoc_data:overview(Title, Tags, Env, Opts),
+ F = fun (M) ->
+ M:overview(Data, Opts)
+ end,
+ _Markdown = edoc_lib:run_layout(F, Opts).
+ %% edoc_lib:write_file(Text, Dir, ?OVERVIEW_SUMMARY).
+
+
+copy_image(Dir, Options) ->
+ case proplists:get_value(image, Options) of
+ O when O==undefined; O==?IMAGE ->
+ case code:priv_dir(?EDOC_APP) of
+ PrivDir when is_list(PrivDir) ->
+ From = filename:join(PrivDir, ?IMAGE),
+ edoc_lib:copy_file(From, filename:join(Dir, ?IMAGE));
+ _ ->
+ report("cannot find default image file.", []),
+ exit(error)
+ end;
+ "" ->
+ ok
+ end.
+
+%% NEW-OPTIONS: stylesheet_file
+%% DEFER-OPTIONS: run/2
+
+copy_stylesheet(Dir, Options) ->
+ case proplists:get_value(stylesheet, Options) of
+ O when O==undefined; O==?STYLESHEET ->
+ From = case proplists:get_value(stylesheet_file, Options) of
+ File when is_list(File) ->
+ File;
+ _ ->
+ case code:priv_dir(?EDOC_APP) of
+ PrivDir when is_list(PrivDir) ->
+ filename:join(PrivDir, ?STYLESHEET);
+ _ ->
+ report("cannot find default "
+ "stylesheet file.", []),
+ exit(error)
+ end
+ end,
+ edoc_lib:copy_file(From, filename:join(Dir, ?STYLESHEET));
+ "" ->
+ ok
+ end.
+
+%% NEW-OPTIONS: stylesheet
+%% DEFER-OPTIONS: run/2
+
+%% stylesheet(Options) ->
+%% case proplists:get_value(stylesheet, Options) of
+%% "" ->
+%% [];
+%% S ->
+%% Ref = case S of
+%% undefined ->
+%% ?STYLESHEET;
+%% "" ->
+%% ""; % no stylesheet
+%% S when is_list(S) ->
+%% S;
+%% _ ->
+%% report("bad value for option 'stylesheet'.",
+%% []),
+%% exit(error)
+%% end,
+%% [{link, [{rel, "stylesheet"},
+%% {type, "text/css"},
+%% {href, Ref},
+%% {title, "EDoc"}], []},
+%% ?NL]
+%% end.
+
+is_private(E) ->
+ case get_attrval(private, E) of
+ "yes" -> true;
+ _ -> false
+ end.
+
+is_hidden(E) ->
+ case get_attrval(hidden, E) of
+ "yes" -> true;
+ _ -> false
+ end.
+
+get_attrval(Name, #xmlElement{attributes = As}) ->
+ case get_attr(Name, As) of
+ [#xmlAttribute{value = V}] ->
+ V;
+ [] -> ""
+ end.
+
+get_attr(Name, [#xmlAttribute{name = Name} = A | As]) ->
+ [A | get_attr(Name, As)];
+get_attr(Name, [_ | As]) ->
+ get_attr(Name, As);
+get_attr(_, []) ->
+ [].
+
+%% Read external source file. Fails quietly, returning empty tag list.
+
+%% INHERIT-OPTIONS: edoc_extract:file/4
+
+read_file(File, Context, Env, Opts) ->
+ case edoc_extract:file(File, Context, Env, Opts) of
+ {ok, Tags} ->
+ Tags;
+ {error, _} ->
+ []
+ end.
+
+
+%% TODO: FIXME: meta-level index generation
+
+%% Creates a Table of Content from a list of Paths (ie paths to applications)
+%% and an overview file.
+
+-define(EDOC_DIR, "doc").
+-define(INDEX_DIR, "doc/index").
+-define(CURRENT_DIR, ".").
+
+toc(_Paths, _Ctxt) ->
+ erlang:error(nyi).
+ %% Opts = Ctxt#context.opts,
+ %% Dir = Ctxt#context.dir,
+ %% Env = Ctxt#context.env,
+ %% app_index_file(Paths, Dir, Env, Opts).
diff --git a/deps/edown/src/edown_layout.erl b/deps/edown/src/edown_layout.erl
new file mode 100644
index 0000000..84f7246
--- /dev/null
+++ b/deps/edown/src/edown_layout.erl
@@ -0,0 +1,1323 @@
+%%==============================================================================
+%% Copyright 2014 Ulf Wiger
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%==============================================================================
+%% @author Ulf Wiger <ulf@wiger.net>
+%% @copyright 2014 Ulf Wiger
+%% @end
+%% =====================================================================
+
+%% @doc Markdown layout module for EDoc.
+%% Derived from `edoc_layout', which is part of the Erlang/OTP application EDoc.
+%% The module is intended to be used together with edoc.
+%% @end
+
+-module(edown_layout).
+
+-export([module/2, package/2, overview/2, type/1]).
+-export([markdown/3]).
+
+-import(edoc_report, [report/2]).
+
+-include_lib("xmerl/include/xmerl.hrl").
+
+-define(HTML_EXPORT, edown_xmerl).
+-define(DEFAULT_XML_EXPORT, ?HTML_EXPORT).
+-define(OVERVIEW_SUMMARY, "overview-summary.html").
+-define(STYLESHEET, "stylesheet.css").
+-define(NL, "\n").
+-define(DESCRIPTION_TITLE, "Description").
+-define(DESCRIPTION_LABEL, "description").
+-define(DATA_TYPES_TITLE, "Data Types").
+-define(DATA_TYPES_LABEL, "types").
+-define(FUNCTION_INDEX_TITLE, "Function Index").
+-define(FUNCTION_INDEX_LABEL, "index").
+-define(FUNCTIONS_TITLE, "Function Details").
+-define(FUNCTIONS_LABEL, "functions").
+
+
+%% @doc The layout function.
+%%
+%% Options to the standard layout:
+%% <dl>
+%% <dt>{@type {index_columns, integer()@}}
+%% </dt>
+%% <dd>Specifies the number of column pairs used for the function
+%% index tables. The default value is 1.
+%% </dd>
+%% <dt>{@type {pretty_printer, atom()@}}
+%% </dt>
+%% <dd>Specifies how types and specifications are pretty printed.
+%% If the value `erl_pp' is specified the Erlang pretty printer
+%% (the module `erl_pp') will be used. The default is to do
+%% no pretty printing which implies that lines can be very long.
+%% </dd>
+%% <dt>{@type {stylesheet, string()@}}
+%% </dt>
+%% <dd>Specifies the URI used for referencing the stylesheet. The
+%% default value is `"stylesheet.css"'. If an empty string is
+%% specified, no stylesheet reference will be generated.
+%% </dd>
+%% <dt>{@type {sort_functions, boolean()@}}
+%% </dt>
+%% <dd>If `true', the detailed function descriptions are listed by
+%% name, otherwise they are listed in the order of occurrence in
+%% the source file. The default value is `true'.
+%% </dd>
+%% <dt>{@type {xml_export, Module::atom()@}}
+%% </dt>
+%% <dd>Specifies an {@link //xmerl. `xmerl'} callback module to be
+%% used for exporting the documentation. See {@link
+%% //xmerl/xmerl:export_simple_content/2} for details.
+%% </dd>
+%% </dl>
+%%
+%% @see edown_doclet:layout/2
+%% @see //edoc/edoc:layout/2
+
+%% NEW-OPTIONS: xml_export, index_columns, stylesheet
+
+module(Element, Options) ->
+ XML = layout_module(Element, init_opts(Element, Options)),
+ Export = proplists:get_value(xml_export, Options,
+ ?DEFAULT_XML_EXPORT),
+ xmerl:export_simple(XML, Export, []).
+
+% Put layout options in a data structure for easier access.
+
+%% %Commented out until it can be made private
+%% %@type opts() = #opts{root = string(),
+%% % stylesheet = string(),
+%% % index_columns = integer()}
+
+-record(opts, {root,
+ stylesheet,
+ index_columns,
+ sort_functions,
+ pretty_printer}).
+
+init_opts(Element, Options) ->
+ R = #opts{root = get_attrval(root, Element),
+ index_columns = proplists:get_value(index_columns,
+ Options, 1),
+ sort_functions = proplists:get_value(sort_functions,
+ Options, true),
+ pretty_printer = proplists:get_value(pretty_printer,
+ Options, '')
+ },
+ case proplists:get_value(stylesheet, Options) of
+ undefined ->
+ S = edoc_lib:join_uri(R#opts.root, ?STYLESHEET),
+ R#opts{stylesheet = S};
+ "" ->
+ R; % don't use any stylesheet
+ S when is_list(S) ->
+ R#opts{stylesheet = S};
+ _ ->
+ report("bad value for option `stylesheet'.", []),
+ exit(error)
+ end.
+
+
+%% =====================================================================
+%% XML-BASED LAYOUT ENGINE
+%% =====================================================================
+
+%% We assume that we have expanded XML data.
+
+%% <!ELEMENT module (behaviour*, description?, author*, copyright?,
+%% version?, since?, deprecated?, see*, reference*,
+%% todo?, typedecls?, functions)>
+%% <!ATTLIST module
+%% name CDATA #REQUIRED
+%% private NMTOKEN(yes | no) #IMPLIED
+%% root CDATA #IMPLIED>
+%% <!ELEMENT behaviour (#PCDATA)>
+%% <!ATTLIST behaviour
+%% href CDATA #IMPLIED>
+%% <!ELEMENT description (briefDescription, fullDescription?)>
+%% <!ELEMENT briefDescription (#PCDATA)>
+%% <!ELEMENT fullDescription (#PCDATA)>
+%% <!ELEMENT author EMPTY>
+%% <!ATTLIST author
+%% name CDATA #REQUIRED
+%% email CDATA #IMPLIED
+%% website CDATA #IMPLIED>
+%% <!ELEMENT version (#PCDATA)>
+%% <!ELEMENT since (#PCDATA)>
+%% <!ELEMENT copyright (#PCDATA)>
+%% <!ELEMENT deprecated (description)>
+%% <!ELEMENT see (#PCDATA)>
+%% <!ATTLIST see
+%% name CDATA #REQUIRED
+%% href CDATA #IMPLIED>
+%% <!ELEMENT reference (#PCDATA)>
+%% <!ELEMENT todo (#PCDATA)>
+%% <!ELEMENT typedecls (typedecl+)>
+%% <!ELEMENT functions (function+)>
+
+%% TODO: improve layout of parameterized modules
+
+layout_module(#xmlElement{name = module, content = Es}=E, Opts) ->
+ Args = module_params(get_content(args, Es)),
+ Name = get_attrval(name, E),
+ Title = case get_elem(args, Es) of
+ [] -> ["Module ", Name];
+ _ -> ["Abstract module ", Name, " [", {Args}, "]"]
+ end,
+ Desc = get_content(description, Es),
+ FullDesc = get_content(fullDescription, Desc),
+ {ShortDesc, RestDesc} = get_first_sentence(FullDesc),
+ Functions = [{function_name(Ex), Ex} || Ex <- get_content(functions, Es)],
+ Types = [{type_name(Ex), Ex} || Ex <- get_content(typedecls, Es)],
+ SortedFs = lists:sort(Functions),
+ Body = ([] % navigation("top")
+ ++ [{h1, Title}]
+ ++ doc_index(FullDesc, Functions, Types)
+ ++ [{p,[]}]
+ ++ ShortDesc
+ ++ [{p,[]}]
+ ++ copyright(Es)
+ ++ deprecated(Es, "module")
+ ++ version(Es)
+ ++ since(Es)
+ ++ behaviours(Es, Name)
+ ++ authors(Es)
+ ++ references(Es)
+ ++ sees(Es)
+ ++ todos(Es)
+ ++ if RestDesc == [] -> [];
+ true -> [
+ {a, [{name, "description"}], []},
+ {h2, ["Description"]}
+ | RestDesc]
+ end
+ ++ types(lists:sort(Types), Opts)
+ ++ function_index(SortedFs, Opts#opts.index_columns)
+ ++ if Opts#opts.sort_functions -> functions(SortedFs, Opts);
+ true -> functions(Functions, Opts)
+ end),
+ %% ++ navigation("bottom")
+ %% ++ timestamp()),
+ %% if Name == "edown_doclet" ->
+ %% io:fwrite("edown_doclet:~n"
+ %% "-----------------~n"
+ %% "~p~n"
+ %% "-----------------~n", [Body]);
+ %% true ->
+ %% io:fwrite("not edown_doclet (~p)~n", [Name])
+ %% end,
+ %% xhtml(Title, stylesheet(Opts), Body).
+ Res = to_simple(markdown(Title, stylesheet(Opts), Body)),
+ Res.
+
+%% This function is a workaround for a bug in xmerl_lib:expand_content/1 that
+%% causes it to lose track of the parents if #xmlElement{} records are
+%% encountered in the structure.
+%%
+to_simple([#xmlElement{name = Name, attributes = Attrs, content = Content}|T]) ->
+ [{Name, to_simple_attrs(Attrs), to_simple(Content)} | to_simple(T)];
+to_simple([#xmlText{value = "\n" ++ _ = Txt} = H|T]) ->
+ %% Treat explicit double-newlines specially, as they are otherwise converted
+ %% in the next stage, causing trouble for Markdown
+ case [C || C <- Txt, C =/= $\s] of
+ "\n\n" ->
+ [{p, []} | to_simple(T)];
+ _ ->
+ [text_to_simple(H) | to_simple(T)]
+ end;
+to_simple([#xmlText{} = H | T]) ->
+ [text_to_simple(H) | to_simple(T)];
+to_simple([{N,C} | T]) ->
+ [{N, to_simple(C)} | to_simple(T)];
+to_simple([{N,As,C} | T]) ->
+ [{N, As, to_simple(C)} | to_simple(T)];
+to_simple([[_|_] = L | T]) ->
+ [to_simple(lists:flatten(L)) | to_simple(T)];
+to_simple([H|T]) ->
+ [H | to_simple(T)];
+to_simple([]) ->
+ [].
+
+text_to_simple(#xmlText{parents = Ps, value = Text} = X) ->
+ case [P || {P,_} <- Ps, lists:member(P, [pre,tt])] of
+ [] ->
+ X#xmlText{value = normalize_text(Text)};
+ _ ->
+ X
+ end.
+
+
+to_simple_attrs(As) ->
+ [{K,V} || #xmlAttribute{name = K, value = V} <- As].
+
+normalize_text(Text) ->
+ try normalize(Text)
+ catch
+ error:_ ->
+ lists:flatten(io_lib:fwrite("~p", [Text]))
+ end.
+
+normalize(S) ->
+ normalize1(to_string(S)).
+
+normalize1("\n" ++ [H|T]) when H==$\s;
+ H==$\t ->
+ normalize1("\n" ++ T);
+normalize1([H|T]) ->
+ [H|normalize1(T)];
+normalize1([]) ->
+ [].
+
+to_string(S) ->
+ unicode:characters_to_list([S]).
+
+module_params(Es) ->
+ As = [{get_text(argName, Es1),
+ get_content(fullDescription, get_content(description, Es1))}
+ || #xmlElement{content = Es1} <- Es],
+ case As of
+ [] -> [];
+ [First | Rest] ->
+ [element(1, First) | [ {[", ",A]} || {A, _D} <- Rest]]
+ end.
+
+%% timestamp() ->
+%% [{p, [{i, [io_lib:fwrite("Generated by EDoc, ~s, ~s.",
+%% [edoc_lib:datestr(date()),
+%% edoc_lib:timestr(time())])
+%% ]}]}].
+
+stylesheet(Opts) ->
+ case Opts#opts.stylesheet of
+ undefined ->
+ [];
+ CSS ->
+ [{link, [{rel, "stylesheet"},
+ {type, "text/css"},
+ {href, CSS},
+ {title, "EDoc"}], []}]
+ end.
+
+%% navigation(Where) ->
+%% [{p, []},
+%% {'div', [{class, "navbar"}],
+%% [{a, [{name, "#navbar_" ++ Where}], []},
+%% {table, [{width, "100%"}, {border,0},
+%% {cellspacing, 0}, {cellpadding, 2},
+%% {summary, "navigation bar"}],
+%% [{tr,
+%% [{td, [{a, [{href, ?OVERVIEW_SUMMARY}, {target,"overviewFrame"}],
+%% ["Overview"]}]},
+%% {td, [{a, [{href, "http://www.erlang.org/"}],
+%% [{img, [{src, "erlang.png"}, {align, "right"},
+%% {border, 0}, {alt, "erlang logo"}],
+%% []}]}
+%% ]}
+%% ]}
+%% ]}
+%% ]}
+%% ].
+
+doc_index(FullDesc, Functions, Types) ->
+ case doc_index_rows(FullDesc, Functions, Types) of
+ [] -> [];
+ Rs ->
+ [{ul, [{class, "index"}],
+ [{li, [{a, [{href, local_label(R)}], [T]}]}
+ || {T, R} <- Rs]}]
+ end.
+
+doc_index_rows(FullDesc, Functions, Types) ->
+ (if FullDesc == [] -> [];
+ true -> [{?DESCRIPTION_TITLE, ?DESCRIPTION_LABEL}]
+ end
+ ++ if Types == [] -> [];
+ true -> [{?DATA_TYPES_TITLE, ?DATA_TYPES_LABEL}]
+ end
+ ++ if Functions == [] -> [];
+ true -> [{?FUNCTION_INDEX_TITLE, ?FUNCTION_INDEX_LABEL},
+ {?FUNCTIONS_TITLE, ?FUNCTIONS_LABEL}]
+ end).
+
+function_index(Fs, Cols) ->
+ case function_index_rows(Fs, Cols, []) of
+ [] -> [];
+ Rows ->
+ [
+ {a, [{name, ?FUNCTION_INDEX_LABEL}], []},
+ {h2, [?FUNCTION_INDEX_TITLE]},
+ {table, [{width, "100%"}, {border, 1},
+ {cellspacing,0}, {cellpadding,2},
+ {summary, "function index"}],
+ Rows}]
+ end.
+
+function_index_rows(Fs, Cols, Title) ->
+ Rows = (length(Fs) + (Cols - 1)) div Cols,
+ (if Title == [] -> [];
+ true -> [{tr, [{th, [{colspan, Cols * 2}, {align, left}],
+ [Title]}]}]
+ end
+ ++ lists:flatmap(fun index_row/1,
+ edoc_lib:transpose(edoc_lib:segment(Fs, Rows)))).
+
+index_row(Fs) ->
+ [{tr, lists:flatmap(fun index_col/1, Fs)}].
+
+index_col({Name, F=#xmlElement{content = Es}}) ->
+ [{td, [{valign, "top"}],
+ label_href(function_header(Name, F, "*"), F)},
+ {td, index_desc(Es)}].
+
+index_desc(Es) ->
+ Desc = get_content(description, Es),
+ (case get_content(deprecated, Es) of
+ [] -> [];
+ _ -> ["(", {em, ["Deprecated"]}, ".) "]
+ end
+ ++ case get_content(briefDescription, Desc) of
+ [] ->
+ equiv(Es); % no description at all if no equiv
+ ShortDesc ->
+ ShortDesc
+ end).
+
+label_href(Content, F) ->
+ case get_attrval(label, F) of
+ "" -> Content;
+ Ref -> [{a, [{href, local_label(Ref)}], Content}]
+ end.
+
+%% <!ELEMENT function (args, typespec?, returns?, throws?, equiv?,
+%% description?, since?, deprecated?, see*, todo?)>
+%% <!ATTLIST function
+%% name CDATA #REQUIRED
+%% arity CDATA #REQUIRED
+%% exported NMTOKEN(yes | no) #REQUIRED
+%% label CDATA #IMPLIED>
+%% <!ELEMENT args (arg*)>
+%% <!ELEMENT equiv (expr, see?)>
+%% <!ELEMENT expr (#PCDATA)>
+
+functions(Fs, Opts) ->
+ Es = lists:flatmap(fun ({Name, E}) -> function(Name, E, Opts) end, Fs),
+ if Es == [] -> [];
+ true ->
+ [?NL,
+ {a, [{name, ?FUNCTIONS_LABEL}], []},
+ {h2, [?FUNCTIONS_TITLE]},
+ ?NL | Es]
+ end.
+
+function(Name, E=#xmlElement{content = Es}, Opts) ->
+ FHead = function_header(Name, E, " *"),
+ %% (label_anchor(FHead, E)
+ (label_anchor("", E)
+ ++ [{h3, [lists:flatten(FHead)]},
+ {p, []},
+ {p,
+ case typespec(get_content(typespec, Es), Opts) of
+ [] ->
+ signature(get_content(args, Es),
+ get_attrval(name, E));
+ Spec -> Spec
+ end},
+ {p, []}]
+ ++ case params(get_content(args, Es)) of
+ [] -> [];
+ Ps -> [{p, Ps}]
+ end
+ ++ case returns(get_content(returns, Es)) of
+ [] -> [];
+ Rs -> [{p, Rs}]
+ end
+ ++ throws(Es, Opts)
+ ++ equiv_p(Es)
+ ++ deprecated(Es, "function")
+ ++ fulldesc(Es)
+ ++ since(Es)
+ ++ sees(Es)
+ ++ todos(Es)).
+
+function_name(E) ->
+ atom(get_attrval(name, E)) ++ "/" ++ get_attrval(arity, E).
+
+function_header(Name, E, Private) ->
+ case is_exported(E) of
+ true -> [Name];
+ false -> [Name, Private]
+ end.
+
+is_exported(E) ->
+ case get_attrval(exported, E) of
+ "yes" -> true;
+ _ -> false
+ end.
+
+label_anchor(Content, E) ->
+%% io:fwrite("label_anchor(~p, ~p)~n", [Content, E]),
+ case get_attrval(label, E) of
+ "" -> Content;
+ Ref -> [{a, [{name, Ref}], Content}]
+ end.
+
+%% <!ELEMENT args (arg*)>
+%% <!ELEMENT arg (argName, description?)>
+%% <!ELEMENT argName (#PCDATA)>
+
+%% This is currently only done for functions without type spec.
+
+signature(Es, Name) ->
+ [{tt, [Name, "("] ++ seq(fun arg/1, Es) ++ [") -> any()"]}].
+
+arg(#xmlElement{content = Es}) ->
+ [get_text(argName, Es)].
+
+%% parameter and return value descriptions (if any)
+
+params(Es) ->
+ As = [{get_text(argName, Es1),
+ get_content(fullDescription, get_content(description, Es1))}
+ || #xmlElement{content = Es1} <- Es],
+ As1 = [A || A <- As, element(2, A) /= []],
+ if As1 == [] ->
+ [];
+ true ->
+ [ { [{tt, [A]}, ": "] ++ D ++ [br] }
+ || {A, D} <- As1]
+ end.
+
+returns(Es) ->
+ case get_content(fullDescription, get_content(description, Es)) of
+ [] ->
+ [];
+ D ->
+ ["returns: "] ++ D
+ end.
+
+%% <!ELEMENT throws (type, localdef*)>
+
+throws(Es, Opts) ->
+ case get_content(throws, Es) of
+ [] -> [];
+ Es1 ->
+ %% Doesn't use format_type; keep it short!
+ [{p, (["throws ", {tt, t_utype(get_elem(type, Es1))}]
+ ++ local_defs(get_elem(localdef, Es1), Opts))},
+ ?NL]
+ end.
+
+%% <!ELEMENT typespec (erlangName, type, localdef*)>
+
+typespec([], _Opts) -> [];
+typespec(Es, Opts) ->
+ Name = t_name(get_elem(erlangName, Es)),
+ Defs = get_elem(localdef, Es),
+ [Type] = get_elem(type, Es),
+ format_spec(Name, Type, Defs, Opts) ++ local_defs(Defs, Opts).
+
+%% <!ELEMENT typedecl (typedef, description?)>
+%% <!ELEMENT typedef (erlangName, argtypes, type?, localdef*)>
+
+types([], _Opts) -> [];
+types(Ts, Opts) ->
+ Es = lists:flatmap(fun ({Name, E}) -> typedecl(Name, E, Opts) end, Ts),
+ [?NL,
+ {a, [{name, ?DATA_TYPES_LABEL}], []},
+ {h2, [?DATA_TYPES_TITLE]},
+ ?NL | Es].
+
+typedecl(Name, E=#xmlElement{content = Es}, Opts) ->
+ ([?NL, {h3, [{class, "typedecl"}], label_anchor([Name, "()"], E)}, ?NL]
+ ++ [{p, typedef(get_content(typedef, Es), Opts)}, ?NL]
+ ++ fulldesc(Es)).
+
+type_name(#xmlElement{content = Es}) ->
+ t_name(get_elem(erlangName, get_content(typedef, Es))).
+
+typedef(Es, Opts) ->
+ Name = ([t_name(get_elem(erlangName, Es)), "("]
+ ++ seq(fun t_utype_elem/1, get_content(argtypes, Es), [")"])),
+ (case get_elem(type, Es) of
+ [] -> [{b, ["abstract datatype"]}, ": ", {tt, Name}];
+ Type -> format_type(Name, Name, Type, [], Opts)
+ end
+ ++ local_defs(get_elem(localdef, Es), Opts)).
+
+local_defs(Es, Opts) ->
+ local_defs(Es, [], Opts).
+
+local_defs([], _, _Opts) -> [];
+local_defs(Es0, Last, Opts) ->
+ [E | Es] = lists:reverse(Es0),
+ [?NL,
+ {local_defs, [{class, "definitions"}],
+ lists:reverse(lists:append([localdef(E1, [], Opts) || E1 <- Es]),
+ localdef(E, Last, Opts))}].
+
+localdef(E = #xmlElement{content = Es}, Last, Opts) ->
+ Name = case get_elem(typevar, Es) of
+ [] ->
+ R = label_anchor(N0 = t_abstype(get_content(abstype, Es)), E),
+ R;
+ [V] ->
+ N0 = t_var(V)
+ end,
+ [{localdef, format_type(Name, N0, get_elem(type, Es), Last, Opts)}].
+
+%% Use the default formatting of EDoc, which creates references, and
+%% then insert newlines and indentation according to erl_pp (the
+%% (fast) Erlang pretty printer).
+format_spec(Name, Type, Defs, #opts{pretty_printer = erl_pp}=Opts) ->
+ try
+ L = t_clause(Name, Type),
+ O = pp_clause(Name, Type),
+ {R, ".\n"} = etypef(L, O),
+ [{pre_pre, R}]
+ catch _:_ ->
+ %% Example: "@spec ... -> record(a)"
+ format_spec(Name, Type, Defs, Opts#opts{pretty_printer=''})
+ end;
+format_spec(Sep, Type, Defs, _Opts) ->
+ %% Very limited formatting.
+ Br = if Defs =:= [] -> br; true -> [] end,
+ [{pre_pre, t_clause(Sep, Type)}, Br].
+
+t_clause(Name, Type) ->
+ #xmlElement{content = [#xmlElement{name = 'fun', content = C}]} = Type,
+ [Name] ++ t_fun(C).
+
+pp_clause(Pre, Type) ->
+ Types = ot_utype([Type]),
+ Atom = lists:duplicate(iolist_size(Pre), $a),
+ L1 = erl_pp:attribute({attribute,0,spec,{{list_to_atom(Atom),0},[Types]}}),
+ "-spec " ++ L2 = lists:flatten(L1),
+ L3 = Pre ++ lists:nthtail(length(Atom), L2),
+ re:replace(L3, "\n ", "\n", [{return,list},global,unicode]).
+
+format_type(Prefix, Name, Type, Last, #opts{pretty_printer = erl_pp}=Opts) ->
+ try
+ L = t_utype(Type),
+ O = pp_type(Name, Type),
+ {R, ".\n"} = etypef(L, O),
+ [{pre_pre, Prefix ++ [" = "] ++ R ++ Last}]
+ catch _:_ ->
+ %% Example: "t() = record(a)."
+ format_type(Prefix, Name, Type, Last, Opts#opts{pretty_printer =''})
+ end;
+format_type(Prefix, _Name, Type, Last, _Opts) ->
+ [{pre_pre, Prefix ++ [" = "] ++ t_utype(Type) ++ Last}].
+
+pp_type(Prefix, Type) ->
+ Atom = list_to_atom(lists:duplicate(iolist_size(Prefix), $a)),
+ L1 = erl_pp:attribute({attribute,0,type,{Atom,ot_utype(Type),[]}}),
+ {L2,N} = case lists:dropwhile(fun(C) -> C =/= $: end, lists:flatten(L1)) of
+ ":: " ++ L3 -> {L3,9}; % compensation for extra "()" and ":"
+ "::\n" ++ L3 -> {"\n"++L3,6}
+ end,
+ Ss = lists:duplicate(N, $\s),
+ re:replace(L2, "\n"++Ss, "\n", [{return,list},global,unicode]).
+
+etypef(L, O0) ->
+ {R, O} = etypef(L, [], O0, []),
+ {lists:reverse(R), O}.
+
+etypef([C | L], St, [C | O], R) ->
+ etypef(L, St, O, [[C] | R]);
+etypef(" "++L, St, O, R) ->
+ etypef(L, St, O, R);
+etypef("", [Cs | St], O, R) ->
+ etypef(Cs, St, O, R);
+etypef("", [], O, R) ->
+ {R, O};
+etypef(L, St, " "++O, R) ->
+ etypef(L, St, O, [" " | R]);
+etypef(L, St, "\n"++O, R) ->
+ Ss = lists:takewhile(fun(C) -> C =:= $\s end, O),
+ etypef(L, St, lists:nthtail(length(Ss), O), ["\n"++Ss | R]);
+etypef([{a, HRef, S0} | L], St, O0, R) ->
+ {S, O} = etypef(S0, app_fix(O0)),
+ etypef(L, St, O, [{a, HRef, S} | R]);
+etypef("="++L, St, "::"++O, R) ->
+ %% EDoc uses "=" for record field types; Erlang types use "::".
+ %% Maybe there should be an option for this, possibly affecting
+ %% other similar discrepancies.
+ etypef(L, St, O, ["=" | R]);
+etypef([Cs | L], St, O, R) ->
+ etypef(Cs, [L | St], O, R).
+
+app_fix(L) ->
+ try
+ {"//" ++ R1,L2} = app_fix(L, 1),
+ [App, Mod] = string:tokens(R1, "/"),
+ "//" ++ atom(App) ++ "/" ++ atom(Mod) ++ L2
+ catch _:_ -> L
+ end.
+
+app_fix(L, I) -> % a bit slow
+ {L1, L2} = lists:split(I, L),
+ case erl_scan:tokens([], L1 ++ ". ", 1) of
+ {done, {ok,[{atom,_,Atom}|_],_}, _} -> {atom_to_list(Atom), L2};
+ _ -> app_fix(L, I+1)
+ end.
+
+fulldesc(Es) ->
+ case get_content(fullDescription, get_content(description, Es)) of
+ [] -> [];
+ Desc -> [{p, Desc}]
+ end.
+
+sees(Es) ->
+ case get_elem(see, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, [{b, ["See also:"]}, " "] ++ seq(fun see/1, Es1, ["."])}]
+ end.
+
+see(E=#xmlElement{content = Es}) ->
+ see(E, Es).
+
+
+see(E, Es) ->
+ case href(E) of
+ [] -> Es;
+ Ref ->
+ [{a, Ref, Es}]
+ end.
+
+href(E) ->
+ case get_attrval(href, E) of
+ "" -> [];
+ URI ->
+ T = case get_attrval(target, E) of
+ "" -> [];
+ S -> [{target, S}]
+ end,
+ [{href, URI} | T]
+ end.
+
+equiv_p(Es) ->
+ equiv(Es, true).
+
+equiv(Es) ->
+ equiv(Es, false).
+
+equiv(Es, P) ->
+ case get_content(equiv, Es) of
+ [] -> [];
+ Es1 ->
+ case get_content(expr, Es1) of
+ [] -> [];
+ [Expr] ->
+ Expr1 = [{esc_tt, [Expr]}],
+ Expr2 = case get_elem(see, Es1) of
+ [] ->
+ Expr1;
+ [E=#xmlElement{}] ->
+ see(E, Expr1)
+ end,
+ Txt = ["Equivalent to "] ++ Expr2 ++ ["."],
+ (case P of
+ true -> [{p, Txt}];
+ false -> Txt
+ end)
+ end
+ end.
+
+copyright(Es) ->
+ case get_content(copyright, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, ["Copyright (c) " | Es1]}]
+ end.
+
+version(Es) ->
+ case get_content(version, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, [{b, ["Version:"]}, " " | Es1]}]
+ end.
+
+since(Es) ->
+ case get_content(since, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, [{b, ["Introduced in:"]}, " " | Es1]}]
+ end.
+
+deprecated(Es, S) ->
+ Es1 = get_content(description, get_content(deprecated, Es)),
+ case get_content(fullDescription, Es1) of
+ [] -> [];
+ Es2 ->
+ [{p, [{b, ["This " ++ S ++ " is deprecated:"]}, " " | Es2]}]
+ end.
+
+behaviours(Es, Name) ->
+ (case get_elem(behaviour, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, ([{b, ["Behaviours:"]}, " "]
+ ++ seq(fun behaviour/1, Es1, ["."]))}]
+ end
+ ++
+ case get_content(callbacks, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, ([{b, ["This module defines the ", {tt, [Name]},
+ " behaviour."]},
+ br, " Required callback functions: "]
+ ++ seq(fun callback/1, Es1, ["."]))}]
+ end).
+
+behaviour(E=#xmlElement{content = Es}) ->
+ see(E, [{tt, Es}]).
+
+callback(E=#xmlElement{}) ->
+ Name = get_attrval(name, E),
+ Arity = get_attrval(arity, E),
+ [{tt, [Name, "/", Arity]}].
+
+authors(Es) ->
+ case get_elem(author, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, [{b, ["Authors:"]}, " "] ++ seq(fun author/1, Es1, ["."])}]
+ end.
+
+atom(String) ->
+ io_lib:write_atom(list_to_atom(String)).
+
+%% <!ATTLIST author
+%% name CDATA #REQUIRED
+%% email CDATA #IMPLIED
+%% website CDATA #IMPLIED>
+
+author(E=#xmlElement{}) ->
+ Name = get_attrval(name, E),
+ Mail = get_attrval(email, E),
+ URI = get_attrval(website, E),
+ (if Name == Mail ->
+ [{a, [{href, "mailto:" ++ Mail}],[{tt, [Mail]}]}];
+ true ->
+ if Mail == "" -> [Name];
+ true -> [Name, " (", {a, [{href, "mailto:" ++ Mail}],
+ [{tt, [Mail]}]}, ")"]
+ end
+ end
+ ++ if URI == "" ->
+ [];
+ true ->
+ %% io:fwrite("URI = ~p~n", [URI]),
+ [" (", {em, ["web site:"]}, " ",
+ %% {tt, [{a, [{href, URI}, {target, "_top"}], [URI]}]},
+ {a, [{href, URI}], [{tt, [URI]}]},
+ ")"]
+ end).
+
+references(Es) ->
+ case get_elem(reference, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, [{b, ["References"]},
+ {ul, [{li, C} || #xmlElement{content = C} <- Es1]}]}]
+ end.
+
+todos(Es) ->
+ case get_elem(todo, Es) of
+ [] -> [];
+ Es1 ->
+ Todos = [{li, [{font, [{color,red}], C}]}
+ || #xmlElement{content = C} <- Es1],
+ [{p, [{b, [{font, [{color,red}], ["To do"]}]},
+ br,
+ {ul, Todos}]}]
+ end.
+
+t_name([E]) ->
+ N = get_attrval(name, E),
+ case get_attrval(module, E) of
+ "" -> atom(N);
+ M ->
+ S = atom(M) ++ ":" ++ atom(N),
+ case get_attrval(app, E) of
+ "" -> S;
+ A -> "//" ++ atom(A) ++ "/" ++ S
+ end
+ end.
+
+t_utype([E]) ->
+ t_utype_elem(E).
+
+t_utype_elem(E=#xmlElement{content = Es}) ->
+ case get_attrval(name, E) of
+ "" -> t_type(Es);
+ Name ->
+ T = t_type(Es),
+ case T of
+ [Name] -> T; % avoid generating "Foo::Foo"
+ T -> [Name] ++ ["::"] ++ T
+ end
+ end.
+
+t_type([E=#xmlElement{name = typevar}]) ->
+ t_var(E);
+t_type([E=#xmlElement{name = atom}]) ->
+ t_atom(E);
+t_type([E=#xmlElement{name = integer}]) ->
+ t_integer(E);
+t_type([E=#xmlElement{name = range}]) ->
+ t_range(E);
+t_type([E=#xmlElement{name = binary}]) ->
+ t_binary(E);
+t_type([E=#xmlElement{name = float}]) ->
+ t_float(E);
+t_type([#xmlElement{name = nil}]) ->
+ t_nil();
+t_type([#xmlElement{name = list, content = Es}]) ->
+ t_list(Es);
+t_type([#xmlElement{name = paren, content = Es}]) ->
+ t_paren(Es);
+t_type([#xmlElement{name = nonempty_list, content = Es}]) ->
+ t_nonempty_list(Es);
+t_type([#xmlElement{name = tuple, content = Es}]) ->
+ t_tuple(Es);
+t_type([#xmlElement{name = map, content = Es}]) ->
+ t_map(Es);
+t_type([#xmlElement{name = map_field, content=Es}]) ->
+ t_map_field(Es);
+t_type([#xmlElement{name = 'fun', content = Es}]) ->
+ ["fun("] ++ t_fun(Es) ++ [")"];
+t_type([E = #xmlElement{name = record, content = Es}]) ->
+ t_record(E, Es);
+t_type([E = #xmlElement{name = abstype, content = Es}]) ->
+ t_abstype(E, Es);
+t_type([#xmlElement{name = union, content = Es}]) ->
+ t_union(Es).
+
+t_var(E) ->
+ [get_attrval(name, E)].
+
+t_atom(E) ->
+ [get_attrval(value, E)].
+
+t_integer(E) ->
+ [get_attrval(value, E)].
+
+t_range(E) ->
+ [get_attrval(value, E)].
+
+t_binary(E) ->
+ [get_attrval(value, E)].
+
+t_float(E) ->
+ [get_attrval(value, E)].
+
+t_nil() ->
+ ["[]"].
+
+t_paren(Es) ->
+ ["("] ++ t_utype(get_elem(type, Es)) ++ [")"].
+
+t_list(Es) ->
+ ["["] ++ t_utype(get_elem(type, Es)) ++ ["]"].
+
+t_nonempty_list(Es) ->
+ ["["] ++ t_utype(get_elem(type, Es)) ++ [", ...]"].
+
+t_tuple(Es) ->
+ ["{"] ++ seq(fun t_utype_elem/1, Es, ["}"]).
+
+t_fun(Es) ->
+ ["("] ++ seq(fun t_utype_elem/1, get_content(argtypes, Es),
+ [") -> "] ++ t_utype(get_elem(type, Es))).
+
+t_record(E, Es) ->
+ Name = ["#"] ++ t_type(get_elem(atom, Es)),
+ case get_elem(field, Es) of
+ [] ->
+ see(E, [Name, "{}"]);
+ Fs ->
+ see(E, Name) ++ ["{"] ++ seq(fun t_field/1, Fs, ["}"])
+ end.
+
+t_field(#xmlElement{content = Es}) ->
+ t_type(get_elem(atom, Es)) ++ [" = "] ++ t_utype(get_elem(type, Es)).
+
+t_abstype(E, Es) ->
+ Name = t_name(get_elem(erlangName, Es)),
+ case get_elem(type, Es) of
+ [] ->
+ see(E, [Name, "()"]);
+ Ts ->
+ see(E, [Name]) ++ ["("] ++ seq(fun t_utype_elem/1, Ts, [")"])
+ end.
+
+t_abstype(Es) ->
+ ([t_name(get_elem(erlangName, Es)), "("]
+ ++ seq(fun t_utype_elem/1, get_elem(type, Es), [")"])).
+
+t_union(Es) ->
+ seq(fun t_utype_elem/1, Es, " | ", []).
+
+seq(F, Es) ->
+ seq(F, Es, []).
+
+seq(F, Es, Tail) ->
+ seq(F, Es, ", ", Tail).
+
+seq(F, [E], _Sep, Tail) ->
+ F(E) ++ Tail;
+seq(F, [E | Es], Sep, Tail) ->
+ F(E) ++ [Sep] ++ seq(F, Es, Sep, Tail);
+seq(_F, [], _Sep, Tail) ->
+ Tail.
+
+get_elem(Name, [#xmlElement{name = Name} = E | Es]) ->
+ [E | get_elem(Name, Es)];
+get_elem(Name, [_ | Es]) ->
+ get_elem(Name, Es);
+get_elem(_, []) ->
+ [].
+
+get_attr(Name, [#xmlAttribute{name = Name} = A | As]) ->
+ [A | get_attr(Name, As)];
+get_attr(Name, [_ | As]) ->
+ get_attr(Name, As);
+get_attr(_, []) ->
+ [].
+
+get_attrval(Name, #xmlElement{attributes = As}) ->
+ case get_attr(Name, As) of
+ [#xmlAttribute{value = V}] ->
+ V;
+ [] -> ""
+ end.
+
+get_content(Name, Es) ->
+ case get_elem(Name, Es) of
+ [#xmlElement{content = Es1}] ->
+ Es1;
+ [] -> []
+ end.
+
+get_text(Name, Es) ->
+ case get_content(Name, Es) of
+ [#xmlText{value = Text}] ->
+ Text;
+ [] -> ""
+ end.
+
+local_label(R) ->
+ "#" ++ R.
+
+
+markdown(_Title, _CSS, Body) ->
+ %% [{title, [lists:flatten(Title)]}|
+ %% Body].
+ collapse_ps(Body).
+
+collapse_ps([{p,[]},Next|T]) when element(1,Next) == p ->
+ collapse_ps([Next|T]);
+collapse_ps([{Tag,Content}|T]) ->
+ [{Tag, collapse_ps(Content)} | collapse_ps(T)];
+collapse_ps([{Tag,As,Content}|T]) ->
+ [{Tag, As, collapse_ps(Content)} | collapse_ps(T)];
+collapse_ps([H|T]) ->
+ [H | collapse_ps(T)];
+collapse_ps([]) ->
+ [].
+
+
+
+
+%% xhtml(Title, CSS, Body) ->
+%% [{html, [?NL,
+%% {head, [?NL,
+%% {title, Title},
+%% ?NL] ++ CSS},
+%% ?NL,
+%% {body, [{bgcolor, "white"}], Body},
+%% ?NL]
+%% },
+%% ?NL].
+
+%% ---------------------------------------------------------------------
+
+type(E) ->
+ type(E, []).
+
+type(E, Ds) ->
+ Opts = [],
+ xmerl:export_simple_content(t_utype_elem(E) ++ local_defs(Ds, Opts),
+ ?HTML_EXPORT).
+
+package(E=#xmlElement{name = package, content = Es}, Options) ->
+ Opts = init_opts(E, Options),
+ Name = get_text(packageName, Es),
+ Title = ["Package ", Name],
+ Desc = get_content(description, Es),
+% ShortDesc = get_content(briefDescription, Desc),
+ FullDesc = get_content(fullDescription, Desc),
+ Body = ([{h1, [Title]}]
+% ++ ShortDesc
+ ++ copyright(Es)
+ ++ deprecated(Es, "package")
+ ++ version(Es)
+ ++ since(Es)
+ ++ authors(Es)
+ ++ references(Es)
+ ++ sees(Es)
+ ++ todos(Es)
+ ++ FullDesc),
+ %% XML = xhtml(Title, stylesheet(Opts), Body),
+ XML = markdown(Title, stylesheet(Opts), Body),
+ xmerl:export_simple_content(XML, ?HTML_EXPORT).
+
+overview(E=#xmlElement{name = overview, content = Es}, Options) ->
+ Opts = init_opts(E, Options),
+ Title = [get_text(title, Es)],
+ Desc = get_content(description, Es),
+ FullDesc = get_content(fullDescription, Desc),
+ Body = ([]
+ ++ [{h1, [Title]}]
+ ++ copyright(Es)
+ ++ version(Es)
+ ++ since(Es)
+ ++ authors(Es)
+ ++ references(Es)
+ ++ sees(Es)
+ ++ todos(Es)
+ ++ FullDesc),
+ %% ++ [hr]
+ %% ++ navigation("bottom")
+ %% ++ timestamp()),
+ %% XML = xhtml(Title, stylesheet(Opts), Body),
+ _XML = markdown(Title, stylesheet(Opts), Body).
+ %% xmerl:export_simple_content(XML, ?HTML_EXPORT).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% NYTT
+
+ot_utype([E]) ->
+ ot_utype_elem(E).
+
+ot_utype_elem(E=#xmlElement{content = Es}) ->
+ case get_attrval(name, E) of
+ "" -> ot_type(Es);
+ N ->
+ Name = {var,0,list_to_atom(N)},
+ T = ot_type(Es),
+ case T of
+ Name -> T;
+ T -> {ann_type,0,[Name, T]}
+ end
+ end.
+
+ot_type([E=#xmlElement{name = typevar}]) ->
+ ot_var(E);
+ot_type([E=#xmlElement{name = atom}]) ->
+ ot_atom(E);
+ot_type([E=#xmlElement{name = integer}]) ->
+ ot_integer(E);
+ot_type([E=#xmlElement{name = range}]) ->
+ ot_range(E);
+ot_type([E=#xmlElement{name = binary}]) ->
+ ot_binary(E);
+ot_type([E=#xmlElement{name = float}]) ->
+ ot_float(E);
+ot_type([#xmlElement{name = nil}]) ->
+ ot_nil();
+ot_type([#xmlElement{name = paren, content = Es}]) ->
+ ot_paren(Es);
+ot_type([#xmlElement{name = list, content = Es}]) ->
+ ot_list(Es);
+ot_type([#xmlElement{name = nonempty_list, content = Es}]) ->
+ ot_nonempty_list(Es);
+ot_type([#xmlElement{name = tuple, content = Es}]) ->
+ ot_tuple(Es);
+ot_type([#xmlElement{name = map, content = Es}]) ->
+ ot_map(Es);
+ot_type([#xmlElement{name = map_field, content = Es}]) ->
+ ot_map_field(Es);
+ot_type([#xmlElement{name = 'fun', content = Es}]) ->
+ ot_fun(Es);
+ot_type([#xmlElement{name = record, content = Es}]) ->
+ ot_record(Es);
+ot_type([#xmlElement{name = abstype, content = Es}]) ->
+ ot_abstype(Es);
+ot_type([#xmlElement{name = union, content = Es}]) ->
+ ot_union(Es).
+
+ot_var(E) ->
+ {var,0,list_to_atom(get_attrval(name, E))}.
+
+ot_atom(E) ->
+ {ok, [Atom], _} = erl_scan:string(get_attrval(value, E), 0),
+ Atom.
+
+ot_integer(E) ->
+ {integer,0,list_to_integer(get_attrval(value, E))}.
+
+ot_range(E) ->
+ [I1, I2] = string:tokens(get_attrval(value, E), "."),
+ {type,0,range,[{integer,0,list_to_integer(I1)},
+ {integer,0,list_to_integer(I2)}]}.
+
+ot_binary(E) ->
+ {Base, Unit} =
+ case string:tokens(get_attrval(value, E), ",:*><") of
+ [] ->
+ {0, 0};
+ ["_",B] ->
+ {list_to_integer(B), 0};
+ ["_","_",U] ->
+ {0, list_to_integer(U)};
+ ["_",B,_,"_",U] ->
+ {list_to_integer(B), list_to_integer(U)}
+ end,
+ {type,0,binary,[{integer,0,Base},{integer,0,Unit}]}.
+
+ot_float(E) ->
+ {float,0,list_to_float(get_attrval(value, E))}.
+
+ot_nil() ->
+ {nil,0}.
+
+ot_paren(Es) ->
+ {paren_type,0,[ot_utype(get_elem(type, Es))]}.
+
+ot_list(Es) ->
+ {type,0,list,[ot_utype(get_elem(type, Es))]}.
+
+ot_nonempty_list(Es) ->
+ {type,0,nonempty_list,[ot_utype(get_elem(type, Es))]}.
+
+ot_tuple(Es) ->
+ {type,0,tuple,[ot_utype_elem(E) || E <- Es]}.
+
+ot_map(Es) ->
+ {type,0,map,[ot_utype_elem(E) || E <- Es]}.
+
+ot_map_field(Es) ->
+ {type,0,map_field_assoc,[ot_utype_elem(E) || E <- Es]}.
+
+
+ot_fun(Es) ->
+ Range = ot_utype(get_elem(type, Es)),
+ Args = [ot_utype_elem(A) || A <- get_content(argtypes, Es)],
+ {type,0,'fun',[{type,0,product,Args},Range]}.
+
+t_map(Es) ->
+ ["#{"] ++ seq(fun t_utype_elem/1, Es, ["}"]).
+
+t_map_field([K,V]) ->
+ t_utype_elem(K) ++ [" => "] ++ t_utype_elem(V).
+
+ot_record(Es) ->
+ {type,0,record,[ot_type(get_elem(atom, Es)) |
+ [ot_field(F) || F <- get_elem(field, Es)]]}.
+
+ot_field(#xmlElement{content = Es}) ->
+ {type,0,field_type,
+ [ot_type(get_elem(atom, Es)), ot_utype(get_elem(type, Es))]}.
+
+ot_abstype(Es) ->
+ ot_name(get_elem(erlangName, Es),
+ [ot_utype_elem(Elem) || Elem <- get_elem(type, Es)]).
+
+ot_union(Es) ->
+ {type,0,union,[ot_utype_elem(E) || E <- Es]}.
+
+ot_name(Es, T) ->
+ case ot_name(Es) of
+ [Mod, ":", Atom] ->
+ {remote_type,0,[{atom,0,list_to_atom(Mod)},
+ {atom,0,list_to_atom(Atom)},T]};
+ "tuple" when T =:= [] ->
+ {type,0,tuple,any};
+ Atom ->
+ {type,0,list_to_atom(Atom),T}
+ end.
+
+ot_name([E]) ->
+ Atom = get_attrval(name, E),
+ case get_attrval(module, E) of
+ "" -> Atom;
+ M ->
+ case get_attrval(app, E) of
+ "" ->
+ [M, ":", Atom];
+ A ->
+ ["//"++A++"/" ++ M, ":", Atom] % EDoc only!
+ end
+ end.
+
+get_first_sentence([#xmlElement{name = p, content = Es} | Tail]) ->
+ %% Descend into initial paragraph.
+ {First, Rest} = get_first_sentence_1(Es),
+ {First,
+ [#xmlElement{name = p, content = Rest} || Rest =/= []] ++ Tail};
+get_first_sentence(Es) ->
+ get_first_sentence_1(Es).
+
+get_first_sentence_1(Es) ->
+ get_first_sentence_1(Es, []).
+
+get_first_sentence_1([E = #xmlText{value = Txt} | Es], Acc) ->
+ Last = case Es of
+ [#xmlElement{name = p} | _] -> true;
+ [#xmlElement{name = br} | _] -> true;
+ [] -> true;
+ _ -> false
+ end,
+ case end_of_sentence(Txt, Last) of
+ {value, Txt1, Rest} ->
+ {lists:reverse([E#xmlText{value = Txt1}|Acc]),
+ if Rest == [] ->
+ Es;
+ true ->
+ [#xmlText{value=Rest} | Es]
+ end};
+ none ->
+ get_first_sentence_1(Es, [E | Acc])
+ end;
+get_first_sentence_1([E | Es], Acc) ->
+ % Skip non-text segments - don't descend further
+ get_first_sentence_1(Es, [E | Acc]);
+get_first_sentence_1([], Acc) ->
+ {lists:reverse(Acc), []}.
+
+end_of_sentence(Cs, Last) ->
+ end_of_sentence(Cs, Last, []).
+
+%% We detect '.' and '!' as end-of-sentence markers.
+
+end_of_sentence([$.=A, B | Cs], _, As) when B==$\s; B==$\t; B==$\n ->
+ end_of_sentence_1(A, Cs, true, As);
+end_of_sentence([$.=A], Last, As) ->
+ end_of_sentence_1(A, [], Last, As);
+end_of_sentence([$!=A, B | Cs], _, As) when B==$\s; B==$\t; B==$\n ->
+ end_of_sentence_1(A, Cs, true, As);
+end_of_sentence([$!=A], Last, As) ->
+ end_of_sentence_1(A, [], Last, As);
+end_of_sentence([C | Cs], Last, As) ->
+ end_of_sentence(Cs, Last, [C | As]);
+end_of_sentence([], Last, As) ->
+ end_of_sentence_1($., [], Last, edoc_lib:strip_space(As)). % add a '.'
+
+end_of_sentence_1(C, Cs, true, As) ->
+ {value, lists:reverse([C | As]), Cs};
+end_of_sentence_1(_, _, false, _) ->
+ none.
diff --git a/deps/edown/src/edown_lib.erl b/deps/edown/src/edown_lib.erl
new file mode 100644
index 0000000..7f82996
--- /dev/null
+++ b/deps/edown/src/edown_lib.erl
@@ -0,0 +1,97 @@
+%%==============================================================================
+%% Copyright 2014 Ulf Wiger
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%==============================================================================
+%% @author Ulf Wiger <ulf@wiger.net>
+%% @copyright 2014 Ulf Wiger
+%% @end
+%% =====================================================================
+
+%% @doc Markdown for EDoc - common support functions
+%% @end
+
+-module(edown_lib).
+
+-export([export/1, redirect_uri/1, get_attrval/2]).
+
+-include_lib("xmerl/include/xmerl.hrl").
+
+
+export(Data) ->
+ xmerl:export_simple_content(Data, edown_xmerl).
+
+
+redirect_uri(#xmlElement{} = E) ->
+ redirect_uri(get_attrval(href, E), get_attrval(name, E), E);
+redirect_uri(_E) ->
+ false.
+
+redirect_uri("http://www.erlang.org" ++ _ = URI, _, E) ->
+ %% abusing the filename API a little - but whatever works...
+ case filename:split(URI) of
+ [_,"www.erlang.org","doc","man",_,"doc",Mod] ->
+ NewURI = "http://www.erlang.org/doc/man/" ++ Mod,
+ replace_uri(NewURI, E);
+ _Split ->
+ false
+ end;
+redirect_uri("/" ++ _ = URI, "//" ++ _, E) ->
+ case lists:prefix(otp_root(), URI) of
+ true ->
+ case lists:reverse(filename:split(URI)) of
+ [Mod, "doc", _App | _] ->
+ NewURI = "http://www.erlang.org/doc/man/" ++ Mod,
+ replace_uri(NewURI, E);
+ _ ->
+ false
+ end;
+ false ->
+ false
+ end;
+redirect_uri("", _, _) ->
+ false;
+redirect_uri(Href, _Name, E) ->
+ case lists:member("/", Href) of
+ false ->
+ [_|_] = URI = get_attrval(href, E),
+ NewURI = re:replace(URI,".html",".md",[{return,list},unicode]),
+ replace_uri(NewURI, E);
+ true ->
+ false
+ end.
+
+replace_uri(URI, #xmlElement{attributes = As} = E) ->
+ #xmlAttribute{} = A = lists:keyfind(href, #xmlAttribute.name, As),
+ As1 = lists:keyreplace(href, #xmlAttribute.name, As,
+ A#xmlAttribute{value = URI}),
+ E#xmlElement{attributes = As1}.
+
+get_attrval(Name, #xmlElement{attributes = As}) ->
+ case get_attr(Name, As) of
+ [#xmlAttribute{value = V}] ->
+ V;
+ [] -> ""
+ end.
+
+get_attr(Name, [#xmlAttribute{name = Name} = A | As]) ->
+ [A | get_attr(Name, As)];
+get_attr(Name, [_ | As]) ->
+ get_attr(Name, As);
+get_attr(_, []) ->
+ [].
+
+
+otp_root() ->
+ {ok, [[Root]]} = init:get_argument(root),
+ Root.
diff --git a/deps/edown/src/edown_make.erl b/deps/edown/src/edown_make.erl
new file mode 100644
index 0000000..5c4e19b
--- /dev/null
+++ b/deps/edown/src/edown_make.erl
@@ -0,0 +1,64 @@
+%% -*- erlang -*-
+-module(edown_make).
+
+-export([from_script/1]).
+-export([main/1]).
+
+%% @spec main(Args::[Config]) -> no_return()
+%% @doc Escript entry point for building edown (or edoc) documentation
+%%
+%% Usage: edown_make -config ConfigFile [-pa P] [-pz P]
+%%
+%% Calls {@link from_script/1. from_script(ConfigFile)} and then terminates,
+%% with a normal or non-normal exit code, depending on the outcome.
+%%
+%% Make sure `$EDOWN/edown_make' is runnable, and in the command path, and
+%% that the edown BEAM files are in the Erlang path (e.g. using $ERL_LIBS).
+%% The `edown_make' escript also accepts `-pa P' and/or `-pz P' flags as a
+%% means of locating the edown byte code.
+%%
+%% Note, however, that the function `edoc_make:main/1' only expects the
+%% config file as an input argument, corresponding to
+%%
+%% `escript edoc_make.beam ConfigFile'
+%%
+%% (The reason for this is that if the beam file can be passed directly to
+%% the escript command, setting the path should also be doable that way).
+%%
+%% @end
+%%
+main([Config]) ->
+ case from_script(Config) of
+ ok ->
+ halt();
+ {error, _} ->
+ halt(1)
+ end.
+
+%% @spec from_script(ConfigFile) -> ok | {error, Reason}
+%% @doc Reads ConfigFile and calls {@link edoc:application/3}
+%%
+%% The ConfigFile will be read using {@link file:script/1}, and should return
+%% `{App, Dir, Options}', as required by {@link edoc:application/3}.
+%%
+%% This function does not manage dependencies. It is simply a wrapper around
+%% {@link edoc:application/3}.
+%% @end
+%%
+from_script(Config) ->
+ case file:script(Config) of
+ {ok, {App, Dir, Options}} ->
+ R = edoc:application(App, Dir, Options),
+ case R of
+ ok ->
+ ok;
+ Err ->
+ io:fwrite("~p~n", [Err]),
+ Err
+ end;
+ Other ->
+ io:fwrite("Error reading config ~s:~n"
+ "~p~n", [Config, Other]),
+ {error, {config, Other}}
+ end.
+
diff --git a/deps/edown/src/edown_xmerl.erl b/deps/edown/src/edown_xmerl.erl
new file mode 100644
index 0000000..c11e808
--- /dev/null
+++ b/deps/edown/src/edown_xmerl.erl
@@ -0,0 +1,285 @@
+%%==============================================================================
+%% Copyright 2014 Ulf Wiger
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%==============================================================================
+%% @author Ulf Wiger <ulf@wiger.net>
+%% @copyright 2014 Ulf Wiger
+%% @end
+%% =============================================================================
+%% Modified 2012 by Beads Land-Trujillo: '#text#'/1, brstrip/1
+%% =============================================================================
+
+%% Description : Callback module for exporting XML to Markdown.
+
+-module(edown_xmerl).
+
+-export(['#xml-inheritance#'/0]).
+
+%% Note: we assume XML data, so all tags are lowercase!
+
+-export(['#root#'/4,
+ '#element#'/5,
+ '#text#'/1]).
+
+-import(xmerl_lib, [find_attribute/2]).
+
+-include_lib("xmerl/include/xmerl.hrl").
+
+
+'#xml-inheritance#'() -> [].
+
+
+%% The '#text#' function is called for every text segment.
+
+'#text#'(Text) ->
+ brstrip(to_string(Text)).
+
+to_string(S) ->
+ %%binary_to_list(iolist_to_binary([S])).
+ unicode:characters_to_list([S]).
+
+strip(Str) -> lstrip(rstrip(Str)).
+lstrip(Str) -> re:replace(Str,"^\\s","", [unicode]).
+rstrip(Str) -> re:replace(Str, "\\s\$", "", [unicode]).
+
+% Strip double spaces at end of line -- markdown reads as hard return.
+brstrip(Str) -> re:replace(Str, "\\s+\\s\$", "", [global, multiline, unicode]).
+
+%% The '#root#' tag is called when the entire structure has been
+%% exported. It does not appear in the structure itself.
+
+'#root#'(Data, Attrs, [], _E) ->
+ case find_attribute(header, Attrs) of
+ {value, Hdr} ->
+ [lists:flatten(io_lib:fwrite("HEADER: ~p~n", [Hdr])), Data];
+ false ->
+ Data
+ end.
+
+%% Note that SGML does not have the <Tag/> empty-element form.
+%% Furthermore, for some element types, the end tag may be forbidden -
+%% this can be handled by extending this module - see xmerl_otpsgml.erl
+%% for an example. (By default, we always generate the end tag, to make
+%% sure that the scope of a markup is not extended by mistake.)
+
+'#element#'('esc_tt', Data, Attrs, Parents, E) ->
+ case within_html(Parents) of
+ true ->
+ '#element#'(tt, escape_pre(Data), Attrs, Parents, E);
+ false ->
+ '#element#'(tt, Data, Attrs, Parents, E)
+ end;
+'#element#'('div', Data, _, _Parents, _E) ->
+ %% special case - we use 'div' to enforce html encoding
+ Data;
+'#element#'('pretext', Data, _Attrs, _Parents, _E) ->
+ Data;
+'#element#'(a, Data, Attrs, Parents, E) ->
+ case edown_lib:redirect_uri(E) of
+ false ->
+ elem(a, Data, Attrs, Parents, E);
+ #xmlElement{attributes = Attrs1, parents = Parents1} = E1 ->
+ elem(a, Data, Attrs1, Parents1, E1)
+ end;
+'#element#'(br, _, _, _, _) ->
+ ["<br />"];
+'#element#'(Tag, Data, Attrs, Parents, E) ->
+ elem(Tag, Data, Attrs, Parents, E).
+
+%% the alias_for/5 function is used to force HTML rendering of things we
+%% know are likely to cause problems with Markdown.
+elem(local_defs, Data, Attrs, Parents, E) ->
+ alias_for(ul, Data, Attrs, Parents, E);
+elem(localdef, Data, Attrs, Parents, E) ->
+ alias_for(li, Data, Attrs, Parents, E);
+elem(Tag, Data, Attrs, Parents, E) ->
+ case needs_html(Tag, Attrs) orelse within_html(Parents) of
+ true ->
+ html_elem(Tag, Data, Attrs, Parents, E);
+ false ->
+ md_elem(Tag, Data, Attrs, Parents, E)
+ end.
+
+escape_pre(Data) ->
+ re:replace(re:replace(Data, "<", "\\&lt;", [global, unicode]), ">", "\\&gt;", [global, unicode]).
+
+%% Given content of a pre tag in `Data', entity escape angle brackets
+%% but leave anchor tags alone. This is less than pretty, but is
+%% useful for processing function descriptions with embedded links for
+%% type definitions.
+escape_pre_with_a(Data) ->
+ escape_pre_with_a(lists:flatten(Data), [], out).
+
+escape_pre_with_a([$<, $a | Rest], Acc, out) ->
+ escape_pre_with_a(Rest, ["<a" | Acc], in);
+escape_pre_with_a([$<, $/, $a, $> | Rest], Acc, in) ->
+ escape_pre_with_a(Rest, ["</a>"|Acc], out);
+escape_pre_with_a([C|Rest], Acc, in) ->
+ escape_pre_with_a(Rest, [C|Acc], in);
+escape_pre_with_a([$<|Rest], Acc, out) ->
+ escape_pre_with_a(Rest, ["&lt;"|Acc], out);
+escape_pre_with_a([$> | Rest], Acc, out) ->
+ escape_pre_with_a(Rest, ["&gt;"|Acc], out);
+escape_pre_with_a([C|Rest], Acc, out) ->
+ escape_pre_with_a(Rest, [C|Acc], out);
+escape_pre_with_a([], Acc, _) ->
+ lists:reverse(Acc).
+
+alias_for(Tag, Data, Attrs, Parents, E) ->
+ xmerl_html:'#element#'(Tag, Data, Attrs, Parents, E#xmlElement{name = Tag}).
+
+html_elem(Tag, Data, Attrs, Parents, E) ->
+ HTML = fun() ->
+ {Tag1, Data1} = case Tag of
+ pre_pre ->
+ %% If pre_pre is
+ %% encountered within other
+ %% HTML markup, just use
+ %% code, no pre.
+ {code, escape_pre_with_a(Data)};
+ T ->
+ {T, Data}
+ end,
+ xmerl_html:'#element#'(Tag1, Data1, Attrs, Parents, E)
+ end,
+ case within_html(Parents) of
+ true ->
+ HTML();
+ false ->
+ ["\n\n", HTML(), "\n\n"]
+ end.
+
+md_elem(a, Data, Attrs, _Parents, _E) ->
+ %% io:fwrite("A TAG = ~p~nPs = ~p~n", [_E, _Parents]),
+ case lists:keyfind(href, #xmlAttribute.name, Attrs) of
+ #xmlAttribute{value = HRef} ->
+ ["[", Data, "](", rewrite_github_url(HRef), ")"];
+ false ->
+ case lists:keyfind(name, #xmlAttribute.name, Attrs) of
+ #xmlAttribute{} ->
+ [ %%"\n",
+ xmerl_lib:start_tag(a,Attrs),
+ Data,
+ xmerl_lib:end_tag(a)
+ %%"\n"]
+ ]
+ end
+ end;
+md_elem(img, _Data, Attrs, _Parents, _E) ->
+ #xmlAttribute{value = Src} = lists:keyfind(src,#xmlAttribute.name,Attrs),
+ #xmlAttribute{value = Alt} = lists:keyfind(alt,#xmlAttribute.name,Attrs),
+ ["![", Alt, "](", Src, ")"];
+md_elem(li, Data, _Attrs, [{ul,_}|_], _E) ->
+ ["* ", strip(Data), "\n"];
+md_elem(li, Data, _Attrs, [{ol,_}|_], _E) ->
+ ["1. ", strip(Data), "\n"];
+md_elem(Tag, Data, Attrs, Parents, E) ->
+ case Tag of
+ title ->
+ %% io:fwrite("TITLE = |~s|~n", [Data]),
+ Str = lists:flatten(Data),
+ [Str, "\n", [$= || _ <- to_string(Str)], "\n"];
+ html -> Data;
+ body -> Data;
+ 'div' -> Data;
+ ul -> Data;
+ ol -> Data;
+ p -> ["\n", Data, "\n"];
+ b -> ["__", no_nl(Data), "__"];
+ em -> ["_", no_nl(Data), "_"];
+ i -> ["_", no_nl(Data), "_"];
+ tt -> ["`", no_nl(Data), "`"];
+ code ->
+ %% edoc_macros.erl hard-codes expansion of the {@type ...} macro
+ %% as a HTML href inside <code>...</code>
+ case re:run(Data, "<a href=", []) of
+ {match,_} ->
+ %% ["<code>", no_nl(Data), "</code>"];
+ ["<code>", no_nl(Data), "</code>"];
+ _ ->
+ %% ["`", no_nl(Data), "`"]
+ %% Don't strip newlines here, as it messes up the specs
+ ["`", Data, "`"]
+ end;
+ dl -> Data;
+ dt -> html_elem(dt, Data, Attrs, Parents, E);
+ dd -> html_elem(dd, Data, Attrs, Parents, E);
+ h1 -> ["\n\n# ", no_nl(Data), " #\n"];
+ h2 -> ["\n\n## ", no_nl(Data), " ##\n"];
+ h3 -> ["\n\n### ", no_nl(Data), " ###\n"];
+ h4 -> ["\n\n#### ", no_nl(Data), " ####\n"];
+ hr -> "---------\n";
+ head -> [];
+ pre_pre ->
+ %% markdown expects inline block-level HTML elements to be
+ %% separated from content by blank lines see
+ %% http://daringfireball.net/projects/markdown/syntax.
+ ["\n<pre><code>\n", escape_pre_with_a(Data), "\n</code></pre>\n"];
+ pre ->
+ %% github flavored markdown "fenced" code block. The
+ %% advantage of rendering literal blocks this way is that
+ %% the resulting markdown will be closer to how a human
+ %% would have written it and markdown rendering will take
+ %% care of entity escaping so that a code block describing
+ %% XML or HTML will get rendered properly.
+ Lang = case lists:keyfind(lang, #xmlAttribute.name, Attrs) of
+ #xmlAttribute{value = Lang1} -> Lang1;
+ false -> ""
+ end,
+ ["\n```", Lang, "\n", Data, "\n```\n"];
+ _ ->
+ ["\n",
+ xmerl_lib:start_tag(Tag,Attrs),
+ Data,
+ xmerl_lib:end_tag(Tag),
+ "\n"]
+ end.
+
+within_html(Tags) ->
+ lists:any(fun({pre,_}) -> true;
+ ({pre_pre,_}) -> true;
+ ({code,_}) -> true;
+ ({T,_}) -> needs_html(T)
+ end, Tags).
+
+needs_html(T) ->
+ needs_html(T, []).
+
+needs_html(T, _Attrs) ->
+ lists:member(T, [table,'div',dd,dt,local_defs,localdef]).
+
+no_nl(S) ->
+ string:strip([C || C <- to_string(S),
+ C =/= $\n], both).
+
+%% attr(#xmlAttribute{name = N, value = V}) ->
+%% "(" ++ atom_to_list(N) ++ "=" ++ [a_val(V)] ++ ")".
+
+%% a_val(V) when is_atom(V) ->
+%% atom_to_list(V);
+%% a_val(V) when is_integer(V) ->
+%% integer_to_list(V);
+%% a_val(V) ->
+%% V.
+
+rewrite_github_url("http://raw.github.com" ++ _ = URL) ->
+ case filename:split(URL) of
+ ["http:","raw.github.com",User,App,Label,"doc"|Rest] ->
+ "https://" ++ filename:join(
+ ["github.com",User,App,"blob",Label,"doc"|Rest]);
+ _ ->
+ URL
+ end;
+rewrite_github_url(URL) ->
+ URL.
diff --git a/deps/exo/LICENSE b/deps/exo/LICENSE
new file mode 100644
index 0000000..14e2f77
--- /dev/null
+++ b/deps/exo/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/deps/exo/Makefile b/deps/exo/Makefile
new file mode 100644
index 0000000..1f4d7fa
--- /dev/null
+++ b/deps/exo/Makefile
@@ -0,0 +1,5 @@
+all:
+ rebar compile
+
+pkg: all
+ tetrapak pkg:ipkg
diff --git a/deps/exo/README b/deps/exo/README
new file mode 100644
index 0000000..fa81a08
--- /dev/null
+++ b/deps/exo/README
@@ -0,0 +1,4 @@
+exo socket library
+handles tcp,ssl and http connections.
+support ssl probed upgrades and normal ssl upgrades.
+exo_socket_server allow simple server implementations
diff --git a/deps/exo/include/exo_http.hrl b/deps/exo/include/exo_http.hrl
new file mode 100644
index 0000000..576a1aa
--- /dev/null
+++ b/deps/exo/include/exo_http.hrl
@@ -0,0 +1,87 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved.
+%%%
+%%% This Source Code Form is subject to the terms of the Mozilla Public
+%%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @doc
+%%% EXO http definition
+%%% @end
+%%% Created : 15 Dec 2011 by Tony Rogvall <tony@rogvall.se>
+
+-ifndef(_EXO_HTTP_HRL_).
+-define(_EXO_HTTP_HRL_, true).
+
+-include("exo_url.hrl").
+
+-ifndef(CRNL).
+-define(CRNL, "\r\n").
+-endif.
+
+-ifndef(NL).
+-define(NL, "\n").
+-endif.
+
+-ifndef(CR).
+-define(CR, "\r").
+-endif.
+
+-record(http_request,
+ {
+ method, %% 'GET' 'POST' ...
+ uri, %% #url | string path
+ version, %% version {Major,Minor}
+ headers %% #http_chdr
+ }).
+
+-record(http_response,
+ {
+ version, %% version {Major,Minor}
+ status, %% status
+ phrase, %% status text
+ headers %% #http_shdr
+ }).
+
+%% client headers
+-record(http_chdr,
+ {
+ host,
+ connection,
+ transfer_encoding,
+ accept,
+ if_modified_since,
+ if_match,
+ if_none_match,
+ if_range,
+ if_unmodified_since,
+ range,
+ referer,
+ user_agent,
+ accept_ranges,
+ cookie = [],
+ keep_alive,
+ content_length,
+ content_type,
+ authorization,
+ other = [] %% misc other headers
+ }).
+
+%% server headers
+-record(http_shdr,
+ {
+ connection,
+ transfer_encoding,
+ location,
+ set_cookie,
+ content_length,
+ content_type,
+ other = [] %% misc other headers
+ }).
+
+
+-endif.
+
diff --git a/deps/exo/include/exo_url.hrl b/deps/exo/include/exo_url.hrl
new file mode 100644
index 0000000..9b9aeaf
--- /dev/null
+++ b/deps/exo/include/exo_url.hrl
@@ -0,0 +1,29 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved.
+%%%
+%%% This Source Code Form is subject to the terms of the Mozilla Public
+%%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @doc
+%%% URL definition
+%%% @end
+%%% Created : 16 Dec 2011 by Tony Rogvall <tony@rogvall.se>
+
+-ifndef(_EXO_URL_HRL_).
+-define(_EXO_URL_HRL_, true).
+
+-record(url,
+ {
+ scheme,
+ host,
+ port, %% undefined means not set
+ path = "",
+ querypart = ""
+ }).
+
+-endif.
+
diff --git a/deps/exo/priv/host.cert b/deps/exo/priv/host.cert
new file mode 100644
index 0000000..da5bfb1
--- /dev/null
+++ b/deps/exo/priv/host.cert
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8jCCAlugAwIBAgIJAKNMeoW2t5RiMA0GCSqGSIb3DQEBBQUAMFoxCzAJBgNV
+BAYTAlNFMRQwEgYDVQQIEwtHdXN0YXZzYmVyZzESMBAGA1UEBxMJU3RvY2tob2xt
+MQ0wCwYDVQQKEwRUZXN0MRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMTAwNDExMjIw
+ODQ2WhcNMTEwNDExMjIwODQ2WjBaMQswCQYDVQQGEwJTRTEUMBIGA1UECBMLR3Vz
+dGF2c2JlcmcxEjAQBgNVBAcTCVN0b2NraG9sbTENMAsGA1UEChMEVGVzdDESMBAG
+A1UEAxMJbG9jYWxob3N0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDpnzGQ
+chnCZcydtyXWRuI1XLSFUabv7eLm0VsViuPogVBGbnBXJb4ICsNgt1CQRO41w3BV
+3MUDYShvV97N82dBF44DCdaG5Wkiu5ZE0vB7AIVhGgCftpFonUK97PzwXKI3NevB
+FNkpTIYdQkTG41ynKH40ExjXuwS+VEaplRQ1JQIDAQABo4G/MIG8MB0GA1UdDgQW
+BBT7i4ovd1yl03LW5h38luAgw2Jy6zCBjAYDVR0jBIGEMIGBgBT7i4ovd1yl03LW
+5h38luAgw2Jy66FepFwwWjELMAkGA1UEBhMCU0UxFDASBgNVBAgTC0d1c3RhdnNi
+ZXJnMRIwEAYDVQQHEwlTdG9ja2hvbG0xDTALBgNVBAoTBFRlc3QxEjAQBgNVBAMT
+CWxvY2FsaG9zdIIJAKNMeoW2t5RiMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF
+BQADgYEA43HlNUYCpjrZhJtk9Zkjsq4rimClkOYaDtHU57SiYMkp2OIuNNX4CXU6
+UtB6YMa1564dypXPTSoCJCfNu/IdiB0zhXqObwbF6NsiXHDbFCnXZ1PMzqqbJ/9q
+JkXGNcq8iUIlAcSv4FMmiDnWPUl9x2hit3LfMPO6LmVGzMD1/wg=
+-----END CERTIFICATE-----
diff --git a/deps/exo/priv/host.key b/deps/exo/priv/host.key
new file mode 100644
index 0000000..37bd102
--- /dev/null
+++ b/deps/exo/priv/host.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDpnzGQchnCZcydtyXWRuI1XLSFUabv7eLm0VsViuPogVBGbnBX
+Jb4ICsNgt1CQRO41w3BV3MUDYShvV97N82dBF44DCdaG5Wkiu5ZE0vB7AIVhGgCf
+tpFonUK97PzwXKI3NevBFNkpTIYdQkTG41ynKH40ExjXuwS+VEaplRQ1JQIDAQAB
+AoGBAM5xv8HOIlc30BX6tzPgFZtkHZ3GJsfUpnKjxGKIBpf6Va63fwxzrLa8lCRO
+OaITV5Eo8IbffTVcUPf7ZUcHdVBOtDuBUqEe9CfxTpPFafaHTnXT+qWHoU04jtsI
+6gW+zxX/zb/TXGrxL228YhX7MVckl6C0Qqav4K2aD+4Tc8YBAkEA+F82NFG3mvPs
+Hr9/U2Vib1bQ8Y16k/GMOumhc/qBfa7SNgCUN5RN2SyjFnv2c0ZMAKXxZ+4ya/hg
+fFHpT+yhgQJBAPDMAvbQFUpEPWsF3KXkRhrp2xq2o2UD+s5fwucVDIsynuYdA0Nj
+doDMmmRv92y8N71QRgvbfCpiOg10P6yEnaUCQEpwbtR7cWX1mV0V09lw13G7OWBM
+IOLaUEcP4p3aBJlgngwlXEbUIgjnAfIpMRIXjYvcMGAP2fjWNkTMX/reVwECQEZk
+7cznogG3F1ltOkcJB/GgQhx6HiRO2IhyeFED10nuP4O4IZcWwbL/nXcVQHj6QTVH
+/WJEBPWDB8OdI2w0zL0CQC+HJMbV5k7ZYzmsdUMwo5TrDqId6LD97b0kCM+QdF/w
+SbPXgEqIkVgzDaXmrdgj+9nfXo3r0EMnO1JY0emlQec=
+-----END RSA PRIVATE KEY-----
diff --git a/deps/exo/rebar.config b/deps/exo/rebar.config
new file mode 100644
index 0000000..e00ca57
--- /dev/null
+++ b/deps/exo/rebar.config
@@ -0,0 +1,5 @@
+%% Config file for exo application
+{deps, [{lager, ".*", {git, "git://github.com/Feuerlabs/lager.git", "1.1"}}]}.
+%% {erl_opts, [debug_info, fail_on_warning, {d,debug}]}.
+{erl_opts, [debug_info, fail_on_warning]}.
+{sub_dirs, ["src"]}.
diff --git a/deps/exo/src/exo.app.src b/deps/exo/src/exo.app.src
new file mode 100644
index 0000000..bdd25e3
--- /dev/null
+++ b/deps/exo/src/exo.app.src
@@ -0,0 +1,12 @@
+{application, exo,
+ [
+ {description, ""},
+ {vsn, git},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]},
+ {mod, { exo_app, []}},
+ {env, []}
+ ]}.
diff --git a/deps/exo/src/exo_app.erl b/deps/exo/src/exo_app.erl
new file mode 100644
index 0000000..a1cf3c3
--- /dev/null
+++ b/deps/exo/src/exo_app.erl
@@ -0,0 +1,25 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved.
+%%%
+%%% This Source Code Form is subject to the terms of the Mozilla Public
+%%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+-module(exo_app).
+
+-behaviour(application).
+
+%% Application callbacks
+-export([start/2, stop/1]).
+
+%% ===================================================================
+%% Application callbacks
+%% ===================================================================
+
+start(_StartType, _StartArgs) ->
+ exo_sup:start_link().
+
+stop(_State) ->
+ ok.
diff --git a/deps/exo/src/exo_http.erl b/deps/exo/src/exo_http.erl
new file mode 100644
index 0000000..8aea78a
--- /dev/null
+++ b/deps/exo/src/exo_http.erl
@@ -0,0 +1,1064 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved.
+%%%
+%%% This Source Code Form is subject to the terms of the Mozilla Public
+%%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @doc
+%%% Hyper text transport protocol
+%%% @end
+%%% Created : 16 Dec 2011 by Tony Rogvall <tony@rogvall.se>
+
+-module(exo_http).
+
+-include("../include/exo_http.hrl").
+
+%% simple client interface
+-export([wget/1, wget/2, wget/3, wget/4]).
+-export([wpost/2, wpost/3, wpost/4, wpost/5, wpost_body/2]).
+-export([wxget/3, wxget/4, wxget/5, wxget/6]).
+-export([woptions/1, woptions/2, woptions/3, woptions/4]).
+
+-export([wtrace/1, wtrace/2, wtrace/3, wtrace/4]).
+-export([open/1, open/2, close/3]).
+-export([request/2, request/3, request/4, request/5]).
+-export([send/2, send/3, send/4, send/7,
+ send_body/2, send_chunk/2, send_chunk_end/2]).
+
+%% message interface
+-export([recv_response/1, recv_response/2,
+ recv_request/1, recv_request/2,
+ recv_body/2,
+ recv_body/3,
+ recv_body_eof/1,
+ recv_body_data/2,
+ recv_body_chunks/1,
+ recv_body_chunks/2,
+ recv_headers/2,
+ recv_headers/3
+ ]).
+
+%% parse interface
+-export([convert_uri/1]).
+-export([tokens/1]).
+
+-export([set_chdr/3,
+ set_shdr/3]).
+
+%% format interface
+-export([format_response/1, format_response/3,
+ format_request/1, format_request/2, format_request/4,
+ format_query/1,
+ format_headers/1,
+ format_hdr/1,
+ fmt_chdr/1,
+ fmt_shdr/1,
+ make_request/4,
+ make_response/4,
+ auth_basic_encode/2,
+ url_encode/1,
+ make_headers/2
+ ]).
+
+-import(lists, [map/2, reverse/1]).
+
+-ifdef(debug).
+-define(dbg(F, A), io:format((F), (A))).
+-else.
+-define(dbg(F, A), ok).
+-endif.
+
+
+%%
+%% Perform a HTTP/1.1 GET
+%%
+wget(Url) ->
+ wget(Url,{1,1}, [], infinity).
+
+wget(Url, Hs) ->
+ wget(Url, {1,1}, Hs, infinity).
+
+wget(Url, Version, Hs) ->
+ wget(Url, Version, Hs, infinity).
+
+wget(Url, Version, Hs, Timeout) ->
+ Req = make_request('GET',Url,Version,Hs),
+ request(Req,[],Timeout).
+
+%% Proxy version
+wxget(Proxy,Port,Url) ->
+ wxget(Proxy,Port,Url,{1,1},[],infinity).
+
+wxget(Proxy,Port,Url, Hs) ->
+ wxget(Proxy,Port,Url, {1,1}, Hs,infinity).
+
+wxget(Proxy,Port,Url, Version, Hs) ->
+ wxget(Proxy,Port,Url, Version, Hs,infinity).
+
+wxget(Proxy,Port,Url, Version, Hs,Timeout) ->
+ Req = make_request('GET',Url,Version,Hs),
+ xrequest(Proxy,Port,Req,[],Timeout).
+
+%%
+%% HTTP/1.1 OPTIONS
+%%
+woptions(Url) ->
+ woptions(Url,{1,1},[],infinity).
+woptions(Url, Hs) ->
+ woptions(Url,{1,1},Hs,infinity).
+
+woptions(Url, Version, Hs) ->
+ woptions(Url, Version, Hs,infinity).
+
+woptions(Url, Version, Hs, Timeout) ->
+ Req = make_request('OPTIONS',Url,Version,Hs),
+ request(Req,[],Timeout).
+
+%%
+%% HTTP/1.1 TRACE
+%%
+wtrace(Url) ->
+ wtrace(Url,{1,1},[],infinity).
+
+wtrace(Url, Hs) ->
+ wtrace(Url,{1,1},Hs,infinity).
+
+wtrace(Url, Version, Hs) ->
+ wtrace(Url, Version, Hs, infinity).
+
+wtrace(Url, Version, Hs,Timeout) ->
+ Req = make_request('TRACE',Url,Version,Hs),
+ request(Req,[],Timeout).
+
+%%
+%% HTTP/1.1 POST
+%% 1. Content-type: application/x-www-form-urlencoded
+%% - Data = [{key,value}] => key=valye&...
+%% - Data = [{file,Name,FileName} | {binary,Name,<<bin>>} | <<bin>>
+%%
+%% 2.
+%% Content-type: multipart/form-data; boundary=XYZ
+%%
+%% Content-type: multipart/<form>
+%%
+%% - Data = [{file,ContentType,DispositionName,FileName} |
+%% {data,ContentType,DispositionName,<<bin>>} |
+%% <<bin>>]
+%%
+%%
+wpost(Url,Data) ->
+ wpost(Url,{1,1},[],Data).
+
+wpost(Url,Hs,Data) ->
+ wpost(Url,{1,1},Hs,Data,infinity).
+
+wpost(Url,Version,Hs,Data) ->
+ wpost(Url,Version,Hs,Data,infinity).
+
+wpost(Url,Version,Hs,Data,Timeout) ->
+ Req = make_request('POST',Url,Version,Hs),
+ {ok,Req1,Body} = wpost_body(Req, Data),
+ request(Req1, Body, Timeout).
+
+wpost_body(Req, Data) ->
+ Headers = Req#http_request.headers,
+ case Headers#http_chdr.content_type of
+ undefined ->
+ wpost_form_body(Req, Data);
+ "application/x-www-form-urlencoded" ->
+ wpost_form_body(Req, Data);
+ "multipart/"++_ ->
+ wpost_multi_body(Req, Data);
+ _ ->
+ wpost_plain_body(Req, Data)
+ end.
+
+
+wpost_form_body(Req, Data) ->
+ {ok,Req,format_query(Data)}.
+
+wpost_multi_body(Req, Data) ->
+ H = Req#http_request.headers,
+ Ct0 = H#http_chdr.content_type,
+ {Boundary,Req1} =
+ case string:str(Ct0, "boundary=") of
+ 0 ->
+ {MS,S,US} = now(),
+ Bnd = integer_to_list((1000000*(1000000*MS+S) + US)),
+ Ct1 = H#http_chdr.content_type ++
+ "; boundary=\""++Bnd ++"\"",
+ H1 = set_chdr('Content-Type', Ct1, H),
+ {Bnd, Req#http_request { headers = H1 }};
+ I ->
+ Str = string:sub_string(Ct0, I, length(Ct0)),
+ ["boundary", QBnd | _] = string:tokens(Str, " ;="),
+ {unquote(QBnd), Req}
+ end,
+ {ok,Req1,multi_data(Data, Boundary)}.
+
+
+unquote([$" | Str]) ->
+ case reverse(Str) of
+ [$" | RStr] -> reverse(RStr);
+ _ -> Str
+ end;
+unquote(Str) -> Str.
+
+
+wpost_plain_body(Req, Data) ->
+ Body = case Data of
+ Bin when is_binary(Bin) ->
+ Bin;
+ [{file,_,FileName}] ->
+ {ok,Bin} = file:read_file(FileName),
+ Bin;
+ [{file,_,_,FileName}] ->
+ {ok,Bin} = file:read_file(FileName),
+ Bin;
+ [{data,_,Bin}] ->
+ Bin;
+ [{data,_,_,Bin}] ->
+ Bin;
+ List when is_list(List) ->
+ list_to_binary(List)
+ end,
+ {ok,Req,Body}.
+
+
+multi_data(Data, Boundary) ->
+ list_to_binary(
+ [
+ lists:map(
+ fun(Bin) when is_binary(Bin) ->
+ [
+ "--",Boundary,?CRNL,
+ "Content-Type: text/plain",?CRNL,
+ "Content-Transfer-Encoding: 8bit",?CRNL,
+ ?CRNL,
+ Bin,
+ ?CRNL
+ ];
+ ({file,ContentType,FileName}) ->
+ {ok,Bin} = file:read_file(FileName),
+ [
+ "--",Boundary,?CRNL,
+ "Content-Type: ",ContentType,?CRNL,
+ "Content-Transfer-Encoding: 8bit",?CRNL,
+ ?CRNL,
+ Bin,
+ ?CRNL
+ ];
+ ({file,ContentType,DispositionName,FileName}) ->
+ {ok,Bin} = file:read_file(FileName),
+ [
+ "--",Boundary,?CRNL,
+ "Content-Type: ",ContentType,?CRNL,
+ "Content-Disposition: filename=\"",DispositionName,"\"",?CRNL,
+ "Content-Transfer-Encoding: 8bit",?CRNL,
+ ?CRNL,
+ Bin,
+ ?CRNL
+ ];
+ ({data,ContentType,DispositionName,Bin}) ->
+ [
+ "--",Boundary,?CRNL,
+ "Content-Type: ",ContentType,?CRNL,
+ "Content-Disposition: filename=\"",DispositionName,"\"",?CRNL,
+ "Content-Transfer-Encoding: 8bit",?CRNL,
+ ?CRNL,
+ Bin,
+ ?CRNL
+ ];
+ ({data,ContentType,Bin}) ->
+ [
+ "--",Boundary,?CRNL,
+ "Content-Type: ",ContentType,?CRNL,
+ "Content-Transfer-Encoding: 8bit",?CRNL,
+ ?CRNL,
+ Bin,
+ ?CRNL
+ ]
+ end, Data),
+ "--",Boundary,"--",?CRNL]).
+
+request(Req, Body) ->
+ request(Req, Body,infinity).
+
+request(Req, Body,Timeout) ->
+ case open(Req,Timeout) of
+ {ok, S} ->
+ case request(S, Req, Body, false, Timeout) of
+ {ok,Resp,RespBody} ->
+ close(S,Req,Resp),
+ {ok,Resp,RespBody};
+ Error ->
+ exo_socket:close(S),
+ Error
+ end;
+ Error ->
+ Error
+ end.
+
+
+xrequest(Proxy,Port,Req,Body,Timeout) ->
+ Proto = case Req#http_request.uri of
+ #url { scheme = http } -> [tcp,http];
+ #url { scheme = https } -> [tcp,ssl,http];
+ _ -> [tcp,http]
+ end,
+ case exo_socket_cache:open(Proto,Req#http_request.version,
+ Proxy,Port,Timeout) of
+ {ok,S} ->
+ exo_socket:setopts(S, [{mode,binary},{packet,http}]),
+ case request(S, Req, Body, true, Timeout) of
+ {ok,Resp,RespBody} ->
+ close(S,Req,Resp),
+ {ok,Resp,RespBody};
+ _Error ->
+ exo_socket:close(S)
+ end;
+ Error ->
+ Error
+ end.
+
+request(S, Req, Body, Proxy) ->
+ request(S, Req, Body, Proxy, infinity).
+
+request(S, Req, Body, Proxy, Timeout) ->
+ case send(S, Req, Body, Proxy) of
+ ok ->
+ %% FIXME: take care of POST 100-continue
+ case recv_response(S, Timeout) of
+ {ok, Resp} ->
+ ?dbg("response: ~p\n", [Resp]),
+ case recv_body(S, Resp, Timeout) of
+ {ok,RespBody} ->
+ {ok,Resp,RespBody};
+ Error ->
+ ?dbg("body: ~p\n", [Error]),
+ Error
+ end;
+ Error ->
+ ?dbg("response: ~p\n", [Error]),
+ Error
+ end;
+ Error -> Error
+ end.
+
+open(Request) ->
+ open(Request,infinity).
+
+open(Request,Timeout) ->
+ URI = Request#http_request.uri,
+ Url = if is_record(URI, url) -> URI;
+ is_list(URI) -> exo_url:parse(URI, sloppy)
+ end,
+ Scheme = if Url#url.scheme == undefined -> http;
+ true -> Url#url.scheme
+ end,
+ Port = if Url#url.port == undefined ->
+ case Scheme of
+ http -> 80;
+ https -> 443;
+ ftp -> 21
+ end;
+ true ->
+ Url#url.port
+ end,
+ Proto = case Scheme of
+ https -> [tcp,ssl,http];
+ _ -> [tcp,http]
+ end,
+ case exo_socket_cache:open(Proto,Request#http_request.version,
+ Url#url.host,Port,Timeout) of
+ {ok,S} ->
+ exo_socket:setopts(S, [{mode,binary},{packet,http}]),
+ {ok,S};
+ Error ->
+ Error
+ end.
+
+close(S, Req, Resp) ->
+ case do_close(Req,Resp) of
+ true ->
+ ?dbg("real close\n",[]),
+ exo_socket:close(S);
+ false ->
+ ?dbg("session close\n",[]),
+ exo_socket_cache:close(S)
+ end.
+
+do_close(Req, Res) ->
+ _ReqH = Req#http_request.headers,
+ ResH = Res#http_response.headers,
+ case tokens(ResH#http_shdr.connection) of
+ ["close"] -> true;
+ ["keep-alive"] ->
+ %% Check {1,0} and keep-alive requested
+ false;
+ _ ->
+ case Req#http_request.version of
+ {1,1} -> false;
+ _ -> true
+ end
+ end.
+
+%%
+%% Send the HTTP request on a open connection
+%%
+send(Socket, Request) ->
+ send(Socket, Request, false).
+
+send(Socket, Request, Proxy) ->
+ send(Socket, Request, [], Proxy).
+
+send(Socket, Request, Body, Proxy) ->
+ send(Socket,
+ Request#http_request.method,
+ Request#http_request.uri,
+ Request#http_request.version,
+ Request#http_request.headers,
+ Body, Proxy).
+
+send(Socket, Method, URI, Version, H, Body, Proxy) ->
+ Url = if is_record(URI, url) -> URI;
+ is_list(URI) -> exo_url:parse(URI, sloppy)
+ end,
+ %% FIXME: why is not port used?
+ _Port = if Url#url.port == undefined ->
+ case Url#url.scheme of
+ undefined -> 80;
+ http -> 80;
+ https -> 443;
+ ftp -> 21
+ end;
+ true ->
+ Url#url.port
+ end,
+ H1 =
+ if H#http_chdr.host == undefined ->
+ H#http_chdr { host = Url#url.host };
+ true ->
+ H
+ end,
+ H2 = if is_binary(Body), size(Body) > 0,
+ H1#http_chdr.content_length == undefined ->
+ H1#http_chdr { content_length = size(Body) };
+ is_list(Body), Body =/= [],
+ H1#http_chdr.content_length == undefined ->
+ H1#http_chdr { content_length = lists:flatlength(Body) };
+ true ->
+ H1
+ end,
+ H3 = if Version == {1,0},
+ H1#http_chdr.connection == undefined ->
+ H2#http_chdr { connection = "keep-alive" };
+ true ->
+ H2
+ end,
+ Request = [format_request(Method,Url,Version,Proxy),?CRNL,
+ format_hdr(H3),?CRNL, Body],
+ ?dbg("> ~p\n", [Request]),
+ exo_socket:send(Socket, Request).
+
+%%
+%% Send "extra" body data not sent in the original send
+%%
+send_body(Socket, Body) ->
+ exo_socket:send(Socket, Body).
+
+%%
+%% Send chunks
+%%
+send_chunk(Socket, Chunk) when is_binary(Chunk) ->
+ Sz = size(Chunk),
+ if Sz > 0 ->
+ ChunkSize = erlang:integer_to_list(Sz,16),
+ ChunkExt = "",
+ exo_socket:send(Socket, [ChunkSize,ChunkExt,?CRNL,Chunk,?CRNL]);
+ Sz == 0 ->
+ ok
+ end.
+
+send_chunk_end(Socket, _Trailer) ->
+ ChunkSize = "0",
+ ChunkExt = "",
+ exo_socket:send(Socket, [ChunkSize, ChunkExt, ?CRNL,
+ %% Trailer is put here
+ ?CRNL]).
+
+%%
+%% Receive a http/https request
+%%
+recv_request(S) ->
+ recv_request(S, infinity).
+
+recv_request(S, Timeout) ->
+ case exo_socket:recv(S, 0, Timeout) of
+ {ok, {http_request, Method, Uri, Version}} ->
+ CUri = convert_uri(Uri),
+ recv_headers(S, #http_request { method = Method,
+ uri = CUri,
+ version = Version });
+ {ok, Data} ->
+ io:format("Request data: ~p\n", [Data]),
+ {error, sync_error };
+ {error, {http_error, ?CRNL}} -> recv_request(S);
+ {error, {http_error, ?NL}} -> recv_request(S);
+ Error ->
+ Error
+ end.
+
+%%
+%% Receive a http/https response
+%%
+recv_response(S) ->
+ recv_response(S, infinity).
+
+recv_response(S,Timeout) ->
+ case exo_socket:recv(S, 0, Timeout) of
+ {ok, {http_response, Version, Status, Phrase}} ->
+ recv_headers(S, #http_response { version = Version,
+ status = Status,
+ phrase = Phrase },Timeout);
+ {ok, _} ->
+ {error, sync_error };
+ {error, {http_error, ?CRNL}} -> recv_response(S,Timeout);
+ {error, {http_error, ?NL}} -> recv_response(S,Timeout);
+ Error ->
+ Error
+ end.
+
+%%
+%% Receive a body for a request or a response
+%%
+recv_body(S, R) ->
+ recv_body(S, R, infinity).
+
+recv_body(S, Request, Timeout) when is_record(Request, http_request) ->
+ Method = Request#http_request.method,
+ if Method == 'POST' ->
+ H = Request#http_request.headers,
+ case Request#http_request.version of
+ {0,9} ->
+ recv_body_eof(S, Timeout);
+ {1,0} ->
+ case H#http_chdr.content_length of
+ undefined -> recv_body_eof(S,Timeout);
+ Len -> recv_body_data(S, list_to_integer(Len),Timeout)
+ end;
+ {1,1} ->
+ case H#http_chdr.content_length of
+ undefined ->
+ case H#http_chdr.transfer_encoding of
+ undefined -> recv_body_eof(S,Timeout);
+ "chunked" -> recv_body_chunks(S,Timeout)
+ end;
+ Len -> recv_body_data(S, list_to_integer(Len),Timeout)
+ end
+ end;
+ %% FIXME: handle GET/XXX with body
+ true ->
+ {ok, <<>>}
+ end;
+recv_body(S, Response,Timeout) when is_record(Response, http_response) ->
+ %% version 0.9 => read until eof
+ %% version 1.0 => read either Content-Length or until eof
+ %% version 1.1 => read Content-Length or Chunked or eof
+ H = Response#http_response.headers,
+ case Response#http_response.version of
+ {0,9} ->
+ recv_body_eof(S,Timeout);
+ {1,0} ->
+ case H#http_shdr.content_length of
+ undefined -> recv_body_eof(S,Timeout);
+ Len -> recv_body_data(S, list_to_integer(Len),Timeout)
+ end;
+ {1,1} ->
+ case H#http_shdr.content_length of
+ undefined ->
+ case H#http_shdr.transfer_encoding of
+ undefined -> recv_body_eof(S,Timeout);
+ "chunked" -> recv_body_chunks(S,Timeout)
+ end;
+ Len -> recv_body_data(S, list_to_integer(Len),Timeout)
+ end
+ end.
+
+recv_body_eof(Socket) ->
+ recv_body_eof(Socket,infinity).
+
+recv_body_eof(Socket,Timeout) ->
+ ?dbg("RECV_BODY_EOF: tmo=~w\n", [Timeout]),
+ exo_socket:setopts(Socket, [{packet,raw},{mode,binary}]),
+ recv_body_eof1(Socket, [], Timeout).
+
+recv_body_eof1(Socket, Acc,Timeout) ->
+ case exo_socket:recv(Socket, 0, Timeout) of
+ {ok, Bin} ->
+ recv_body_eof1(Socket, [Bin|Acc],Timeout);
+ {error, closed} ->
+ {ok, list_to_binary(reverse(Acc))};
+ Error ->
+ Error
+ end.
+
+recv_body_data(Socket, Len) ->
+ recv_body_data(Socket, Len, infinity).
+
+recv_body_data(_Socket, 0, _Timeout) ->
+ ?dbg("RECV_BODY_DATA: len=0, tmo=~w\n", [_Timeout]),
+ {ok, <<>>};
+recv_body_data(Socket, Len, Timeout) ->
+ ?dbg("RECV_BODY_DATA: len=~p, tmo=~w\n", [Len,Timeout]),
+ exo_socket:setopts(Socket, [{packet,raw},{mode,binary}]),
+ case exo_socket:recv(Socket, Len, Timeout) of
+ {ok, Bin} ->
+ exo_socket:setopts(Socket, [{packet,http}]),
+ {ok,Bin};
+ Error ->
+ Error
+ end.
+
+
+recv_body_chunks(Socket) ->
+ recv_body_chunks(Socket, infinity).
+
+recv_body_chunks(Socket, Timeout) ->
+ exo_socket:setopts(Socket, [{packet,line},{mode,list}]),
+ ?dbg("RECV_BODY_CHUNKS: tmo=~w\n", [Timeout]),
+ recv_body_chunk(Socket, [], Timeout).
+
+recv_body_chunk(S, Acc, Timeout) ->
+ case exo_socket:recv(S, 0, Timeout) of
+ {ok,Line} ->
+ ?dbg("CHUNK-Line: ~p\n", [Line]),
+ {ChunkSize,_Ext} = chunk_size(Line),
+ ?dbg("CHUNK: ~w\n", [ChunkSize]),
+ if ChunkSize == 0 ->
+ exo_socket:setopts(S, [{packet,httph}]),
+ case recv_chunk_trailer(S, [], Timeout) of
+ {ok,_TR} ->
+ ?dbg("CHUNK TRAILER: ~p\n", [_TR]),
+ exo_socket:setopts(S, [{packet,http},
+ {mode,binary}]),
+ {ok,list_to_binary(reverse(Acc))};
+ Error ->
+ Error
+ end;
+ ChunkSize > 0 ->
+ exo_socket:setopts(S, [{packet,raw},{mode,binary}]),
+ case exo_socket:recv(S, ChunkSize, Timeout) of
+ {ok,Bin} ->
+ exo_socket:setopts(S, [{packet,line},{mode,list}]),
+ case exo_socket:recv(S, 0, Timeout) of
+ {ok, ?NL} ->
+ recv_body_chunk(S,[Bin|Acc],Timeout);
+ {ok, ?CRNL} ->
+ recv_body_chunk(S,[Bin|Acc],Timeout);
+ {ok, _Data} ->
+ ?dbg("out of sync ~p\n", [_Data]),
+ {error, sync_error};
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end
+ end;
+ Error ->
+ Error
+ end.
+
+
+recv_chunk_trailer(S, Acc, Timeout) ->
+ case exo_socket:recv(S, 0, Timeout) of
+ {ok,{http_header,_,K,_,V}} ->
+ recv_chunk_trailer(S,[{K,V}|Acc],Timeout);
+ {ok,http_eoh} ->
+ {ok, reverse(Acc)};
+ Error ->
+ Error
+ end.
+
+recv_headers(S, R) ->
+ recv_headers(S, R, infinity).
+
+recv_headers(S, R, Timeout) ->
+ if is_record(R, http_request) ->
+ recv_hc(S, R, #http_chdr { },Timeout);
+ is_record(R, http_response) ->
+ recv_hs(S, R, #http_shdr { },Timeout)
+ end.
+
+
+recv_hc(S, R, H, Timeout) ->
+ case exo_socket:recv(S, 0, Timeout) of
+ {ok, Hdr} ->
+ case Hdr of
+ http_eoh ->
+ ?dbg("EOH <\n", []),
+ Other = reverse(H#http_chdr.other),
+ H1 = H#http_chdr { other = Other },
+ R1 = R#http_request { headers = H1 },
+ ?dbg("< ~s~s\n", [format_request(R1,true),
+ format_headers(fmt_chdr(H1))]),
+ {ok, R1};
+ {http_header,_,K,_,V} ->
+ ?dbg("HEADER < ~p ~p\n", [K, V]),
+ recv_hc(S,R,set_chdr(K,V,H), Timeout);
+ Got ->
+ {error, Got}
+ end;
+ {error, {http_error, ?CRNL}} ->
+ ?dbg("ERROR CRNL <\n", []),
+ recv_hc(S, R, H,Timeout);
+ {error, {http_error, ?NL}} ->
+ ?dbg("ERROR NL <\n", []),
+ recv_hc(S, R, H,Timeout);
+ Error -> Error
+ end.
+
+recv_hs(S, R, H, Timeout) ->
+ case exo_socket:recv(S, 0, Timeout) of
+ {ok, Hdr} ->
+ case Hdr of
+ http_eoh ->
+ ?dbg("EOH <\n", []),
+ Other = reverse(H#http_shdr.other),
+ H1 = H#http_shdr { other = Other },
+ R1 = R#http_response { headers = H1 },
+ ?dbg("< ~s~s\n", [format_response(R1),
+ format_hdr(H1)]),
+ {ok, R1};
+ {http_header,_,K,_,V} ->
+ ?dbg("HEADER < ~p ~p\n", [K, V]),
+ recv_hs(S,R,set_shdr(K,V,H),Timeout);
+ Got ->
+ {error, Got}
+ end;
+ {error, {http_error, ?CRNL}} ->
+ ?dbg("ERROR CRNL <\n", []),
+ recv_hs(S, R, H,Timeout);
+ {error, {http_error, ?NL}} ->
+ ?dbg("ERROR NL <\n", []),
+ recv_hs(S, R, H, Timeout);
+ Error -> Error
+ end.
+
+
+make_request(Method, Url, Version, Hs) ->
+ U = exo_url:parse(Url, sloppy),
+ #http_request { method = Method,
+ uri = U,
+ version = Version,
+ headers = mk_chdr(Hs) }.
+
+make_response(Version, Status, Phrase, Hs) ->
+ #http_response { version = Version,
+ status = Status,
+ phrase = Phrase,
+ headers = mk_shdr(Hs)}.
+
+%%
+%% Format http_request
+%%
+format_request(R) ->
+ format_request(R, false).
+
+format_request(R, Proxy) ->
+ format_request(R#http_request.method,
+ R#http_request.uri,
+ R#http_request.version,
+ Proxy).
+
+format_request(Method, Url, Version, Proxy) ->
+ [if is_atom(Method) -> atom_to_list(Method);
+ is_list(Method) -> Method
+ end,
+ " ",
+ if is_record(Url, url) ->
+ if Proxy == true ->
+ exo_url:format(Url);
+ true ->
+ exo_url:format_path(Url)
+ end;
+ is_list(Url) -> Url
+ end,
+ case Version of
+ {0,9} -> "";
+ {1,0} -> " HTTP/1.0";
+ {1,1} -> " HTTP/1.1"
+ end].
+
+format_response(R) ->
+ format_response(R#http_response.version,
+ R#http_response.status,
+ R#http_response.phrase).
+
+format_response({0,9}, _Status, _Phrase) -> "";
+format_response(Version, Status, Phrase) ->
+ [case Version of
+ {1,0} -> "HTTP/1.0";
+ {1,1} -> "HTTP/1.1"
+ end,
+ " ", integer_to_list(Status),
+ case Phrase of
+ "" -> "";
+ _ -> [$\s|Phrase]
+ end
+ ].
+
+format_query([Item]) ->
+ case Item of
+ {Key,Value} ->
+ [url_encode(to_list(Key)),"=",url_encode(to_list(Value))];
+ Key ->
+ url_encode(to_list(Key))
+ end;
+format_query([Item|Vs]) ->
+ case Item of
+ {Key,Value} ->
+ [url_encode(to_list(Key)),"=",url_encode(to_list(Value)),"&" |
+ format_query(Vs)];
+ Key ->
+ [url_encode(to_list(Key)), "&" |
+ format_query(Vs)]
+ end;
+format_query([]) ->
+ [].
+
+%%
+%% Encode basic authorization
+%%
+auth_basic_encode(User,undefined) ->
+ base64:encode_to_string(to_list(User)++":");
+auth_basic_encode(User,Pass) ->
+ base64:encode_to_string(to_list(User)++":"++to_list(Pass)).
+
+make_headers(undefined, _Pass) -> [];
+make_headers(User, Pass) ->
+ [{"Authorization", "Basic "++auth_basic_encode(User, Pass)}].
+
+%%
+%% Url encode a string
+%%
+url_encode([C|T]) ->
+ if C >= $a, C =< $z -> [C|url_encode(T)];
+ C >= $A, C =< $Z -> [C|url_encode(T)];
+ C >= $0, C =< $9 -> [C|url_encode(T)];
+ C == $\s -> [$+|url_encode(T)];
+ C == $_; C == $.; C == $-; C == $/; C == $: -> % FIXME: more..
+ [C|url_encode(T)];
+ true ->
+ case erlang:integer_to_list(C, 16) of
+ [C1] -> [$%,$0,C1 | url_encode(T)];
+ [C1,C2] ->[$%,C1,C2 | url_encode(T)]
+ end
+ end;
+url_encode([]) ->
+ [].
+
+to_list(X) when is_integer(X) -> integer_to_list(X);
+to_list(X) when is_atom(X) -> atom_to_list(X);
+to_list(X) when is_list(X) -> X.
+
+convert_uri({abs_path, Path}) ->
+ #url{ path = Path };
+convert_uri({absoluteURI, Scheme, Host, Port, Path}) ->
+ exo_url:parse_path(#url{ scheme = Scheme,host = Host, port = Port}, Path);
+convert_uri({scheme, Scheme, Request}) ->
+ #url{ scheme = Scheme, path = Request }.
+
+format_field(Key,Value) ->
+ K = if is_atom(Key) -> atom_to_list(Key);
+ is_list(Key) -> Key;
+ is_binary(Key) -> Key
+ end,
+ V = if is_integer(Value) -> integer_to_list(Value);
+ is_atom(Value) -> atom_to_list(Value);
+ is_list(Value) -> Value;
+ is_binary(Value) -> Value
+ end,
+ [K,": ",V,"\r\n"].
+
+format_headers([{Key,Value}|Hs]) ->
+ [format_field(Key,Value) | format_headers(Hs)];
+format_headers([]) ->
+ [].
+
+
+mk_shdr(Hs) ->
+ mk_shdr(Hs, #http_shdr { }).
+
+mk_shdr([{K,V}|Hs], H) ->
+ mk_shdr(Hs, set_shdr(K,V,H));
+mk_shdr([], H) ->
+ H.
+
+set_shdr(K,V,H) ->
+ case K of
+ 'Connection' -> H#http_shdr { connection = V };
+ 'Transfer-Encoding' -> H#http_shdr { transfer_encoding = V };
+ 'Location' -> H#http_shdr { location = V };
+ 'Set-Cookie' -> H#http_shdr { set_cookie = V };
+ 'Content-Length' -> H#http_shdr { content_length = V };
+ 'Content-Type' -> H#http_shdr { content_type = V };
+ _ ->
+ Hs = [{K,V} | H#http_shdr.other],
+ H#http_shdr { other = Hs }
+ end.
+
+mk_chdr(Hs) ->
+ mk_chdr(Hs, #http_chdr { }).
+
+mk_chdr([{K,V}|Hs], H) ->
+ mk_chdr(Hs, set_chdr(K,V,H));
+mk_chdr([], H) ->
+ H.
+
+set_chdr(K,V,H) ->
+ case K of
+ 'Host' -> H#http_chdr { host = V };
+ 'Connection' -> H#http_chdr { connection = V };
+ 'Transfer-Encoding' -> H#http_chdr { transfer_encoding = V };
+ 'Accept' -> H#http_chdr { accept = V };
+ 'If-Modified-Since' -> H#http_chdr { if_modified_since = V };
+ 'If-Match' -> H#http_chdr { if_match = V };
+ 'If-None-Match' -> H#http_chdr { if_none_match = V };
+ 'If-Range' -> H#http_chdr { if_range = V };
+ 'If-Unmodified-Since' -> H#http_chdr { if_unmodified_since = V };
+ 'Range' -> H#http_chdr { range = V };
+ 'Referer' -> H#http_chdr { referer = V };
+ 'User-Agent' -> H#http_chdr { user_agent = V };
+ 'Accept-Ranges' -> H#http_chdr { accept_ranges = V };
+ 'Cookie' ->
+ V1 = [V | H#http_chdr.cookie],
+ H#http_chdr { cookie = V1 };
+ 'Keep-Alive' -> H#http_chdr { keep_alive = V };
+ 'Content-Length' -> H#http_chdr { content_length = V };
+ 'Content-Type' -> H#http_chdr { content_type = V };
+ 'Authorization' -> H#http_chdr { authorization = V };
+ _ ->
+ Hs = [{K,V} | H#http_chdr.other],
+ H#http_chdr { other = Hs }
+ end.
+
+format_hdr(H) when is_record(H, http_chdr) ->
+ fcons('Host', H#http_chdr.host,
+ fcons('Connection', H#http_chdr.connection,
+ fcons('Transfer-Encoding', H#http_chdr.transfer_encoding,
+ fcons('Accept', H#http_chdr.accept,
+ fcons('If-Modified-Since', H#http_chdr.if_modified_since,
+ fcons('If-Match', H#http_chdr.if_match,
+ fcons('If-None-Match', H#http_chdr.if_none_match,
+ fcons('If-Range', H#http_chdr.if_range,
+ fcons('If-Unmodified-Since', H#http_chdr.if_unmodified_since,
+ fcons('Range', H#http_chdr.range,
+ fcons('Referer', H#http_chdr.referer,
+ fcons('User-Agent', H#http_chdr.user_agent,
+ fcons('Accept-Ranges', H#http_chdr.accept_ranges,
+ fcons_list('Cookie', H#http_chdr.cookie,
+ fcons('Keep-Alive', H#http_chdr.keep_alive,
+ fcons('Content-Length', H#http_chdr.content_length,
+ fcons('Content-Type', H#http_chdr.content_type,
+ fcons('Authorization', H#http_chdr.authorization,
+ format_headers(H#http_chdr.other)))))))))))))))))));
+format_hdr(H) when is_record(H, http_shdr) ->
+ fcons('Connection', H#http_shdr.connection,
+ fcons('Transfer-Encoding', H#http_shdr.transfer_encoding,
+ fcons('Location', H#http_shdr.location,
+ fcons('Set-Cookie', H#http_shdr.set_cookie,
+ fcons('Content-Length', H#http_shdr.content_length,
+ fcons('Content-Type', H#http_shdr.content_type,
+ format_headers(H#http_shdr.other))))))).
+
+
+%%
+%% Convert the http_chdr (client header) structure into a
+%% key value list suitable for formatting.
+%% returns [ {Key,Value} ]
+%% Looks a bit strange, but is done this way to avoid creation
+%% of garabge.
+fmt_chdr(H) ->
+ hcons('Host', H#http_chdr.host,
+ hcons('Connection', H#http_chdr.connection,
+ hcons('Transfer-Encoding', H#http_chdr.transfer_encoding,
+ hcons('Accept', H#http_chdr.accept,
+ hcons('If-Modified-Since', H#http_chdr.if_modified_since,
+ hcons('If-Match', H#http_chdr.if_match,
+ hcons('If-None-Match', H#http_chdr.if_none_match,
+ hcons('If-Range', H#http_chdr.if_range,
+ hcons('If-Unmodified-Since', H#http_chdr.if_unmodified_since,
+ hcons('Range', H#http_chdr.range,
+ hcons('Referer', H#http_chdr.referer,
+ hcons('User-Agent', H#http_chdr.user_agent,
+ hcons('Accept-Ranges', H#http_chdr.accept_ranges,
+ hcons_list('Cookie', H#http_chdr.cookie,
+ hcons('Keep-Alive', H#http_chdr.keep_alive,
+ hcons('Content-Length', H#http_chdr.content_length,
+ hcons('Content-Type', H#http_chdr.content_type,
+ hcons('Authorization', H#http_chdr.authorization,
+ H#http_chdr.other)))))))))))))))))).
+
+%% Convert the http_shdr (server header) structure into a
+%% key value list suitable for formatting.
+fmt_shdr(H) ->
+ hcons('Connection', H#http_shdr.connection,
+ hcons('Transfer-Encoding', H#http_shdr.transfer_encoding,
+ hcons('Location', H#http_shdr.location,
+ hcons('Set-Cookie', H#http_shdr.set_cookie,
+ hcons('Content-Length', H#http_shdr.content_length,
+ hcons('Content-Type', H#http_shdr.content_type,
+ H#http_shdr.other)))))).
+
+hcons(_Key, undefined, Hs) -> Hs;
+hcons(Key, Val, Hs) ->
+ [{Key,Val} | Hs].
+
+hcons_list(Key, [V|Vs], Hs) ->
+ [{Key,V} | hcons_list(Key,Vs,Hs)];
+hcons_list(_Key, [], Hs) ->
+ Hs.
+
+fcons(_Key, undefined, Hs) -> Hs;
+fcons(Key, Val, Hs) ->
+ [format_field(Key,Val) | Hs].
+
+fcons_list(Key, [V|Vs], Hs) ->
+ [format_field(Key,V) | fcons_list(Key,Vs,Hs)];
+fcons_list(_Key, [], Hs) ->
+ Hs.
+
+%%
+%% Parse chunk-size [ chunk-extension ] CRLF
+%% return {chunk-size, chunk-extension}
+%%
+chunk_size(Line) ->
+ chunk_size(Line, 0).
+
+chunk_size([H|Hs], N) ->
+ if
+ H >= $0, H =< $9 ->
+ chunk_size(Hs, (N bsl 4)+(H-$0));
+ H >= $a, H =< $f ->
+ chunk_size(Hs, (N bsl 4)+((H-$a)+10));
+ H >= $A, H =< $F ->
+ chunk_size(Hs, (N bsl 4)+((H-$A)+10));
+ H == $\r -> {N, ""};
+ H == $\n -> {N, ""};
+ H == $\s -> {N, Hs};
+ H == $; -> {N, [H|Hs]}
+ end;
+chunk_size([], N) ->
+ {N, ""}.
+
+tokens(undefined) ->
+ [];
+tokens(Line) ->
+ string:tokens(string:to_lower(Line), ";").
+
diff --git a/deps/exo/src/exo_http_server.erl b/deps/exo/src/exo_http_server.erl
new file mode 100644
index 0000000..2d08f8e
--- /dev/null
+++ b/deps/exo/src/exo_http_server.erl
@@ -0,0 +1,291 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved.
+%%%
+%%% This Source Code Form is subject to the terms of the Mozilla Public
+%%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @author Marina Westman Lönne <malotte@malotte.net>
+%%% @copyright (C) 2012, Feuerlabs, Inc. All rights reserved.
+%%% @doc
+%%% Simple exo_http_server
+%%% @end
+%%% Created : 2010 by Tony Rogvall <tony@rogvall.se>
+
+-module(exo_http_server).
+
+-behaviour(exo_socket_server).
+
+%% exo_socket_server callbacks
+-export([init/2,
+ data/3,
+ close/2,
+ error/3]).
+
+-export([control/4]).
+
+-include("log.hrl").
+-include("exo_socket.hrl").
+-include("exo_http.hrl").
+
+-record(state,
+ {
+ request,
+ response,
+ access = [],
+ request_handler
+ }).
+
+%% Configurable start
+-export([start/2,
+ start_link/2,
+ response/5, response/6]).
+
+%% For testing
+-export([test/0]).
+
+%%-----------------------------------------------------------------------------
+%% @doc
+%% Starts a socket server on port Port with server options ServerOpts
+%% that are sent to the server when a connection is established,
+%% i.e init is called.
+%%
+%% @end
+%%-----------------------------------------------------------------------------
+-spec start(Port::integer(),
+ ServerOptions::list({Option::atom(), Value::term()})) ->
+ {ok, ChildPid::pid()} |
+ {error, Reason::term()}.
+
+start(Port, ServerOptions) ->
+ ?debug("exo_http_server: start: port ~p, server options ~p",
+ [Port, ServerOptions]),
+ Dir = code:priv_dir(exo),
+ exo_socket_server:start(Port, [tcp,probe_ssl,http],
+ [{active,once},{reuseaddr,true},
+ {verify, verify_none},
+ {keyfile, filename:join(Dir, "host.key")},
+ {certfile, filename:join(Dir, "host.cert")}],
+ ?MODULE, ServerOptions).
+
+%%-----------------------------------------------------------------------------
+%% @doc
+%% Starts and links a socket server on port Port with server options ServerOpts
+%% that are sent to the server when a connection is established,
+%% i.e init is called.
+%%
+%% @end
+%%-----------------------------------------------------------------------------
+-spec start_link(Port::integer(),
+ ServerOptions::list({Option::atom(), Value::term()})) ->
+ {ok, ChildPid::pid()} |
+ {error, Reason::term()}.
+
+start_link(Port, ServerOptions) ->
+ ?debug("exo_http_server: start: port ~p, server options ~p",
+ [Port, ServerOptions]),
+ Dir = code:priv_dir(exo),
+ exo_socket_server:start_link(Port, [tcp,probe_ssl,http],
+ [{active,once},{reuseaddr,true},
+ {verify, verify_none},
+ {keyfile, filename:join(Dir, "host.key")},
+ {certfile, filename:join(Dir, "host.cert")}],
+ ?MODULE, ServerOptions).
+
+%%-----------------------------------------------------------------------------
+%% @doc
+%% Init function called when a connection is established.
+%%
+%% @end
+%%-----------------------------------------------------------------------------
+-spec init(Socket::#exo_socket{},
+ ServerOptions::list({Option::atom(), Value::term()})) ->
+ {ok, State::#state{}}.
+
+init(Socket, Options) ->
+ {ok,{_IP,_Port}} = exo_socket:peername(Socket),
+ ?debug("exo_http_server: connection from: ~p : ~p,\n options ~p",
+ [_IP, _Port, Options]),
+ Access = proplists:get_value(access, Options, []),
+ Module = proplists:get_value(request_handler, Options, undefined),
+ {ok, #state{ access = Access, request_handler = Module}}.
+
+
+%% To avoid a compiler warning. Should we actually support something here?
+control(_Socket, _Request, _From, State) ->
+ {ignore, State}.
+
+%%-----------------------------------------------------------------------------
+%% @doc
+%% Data function called when data is received.
+%%
+%% @end
+%%-----------------------------------------------------------------------------
+-spec data(Socket::#exo_socket{},
+ Data::term(),
+ State::#state{}) ->
+ {ok, NewState::#state{}} |
+ {stop, {error, Reason::term()}, NewState::#state{}}.
+
+data(Socket, Data, State) ->
+ ?debug("exo_http_server:~w: data = ~w\n", [self(),Data]),
+ case Data of
+ {http_request, Method, Uri, Version} ->
+ CUri = exo_http:convert_uri(Uri),
+ Req = #http_request { method=Method,uri=CUri,version=Version},
+ case exo_http:recv_headers(Socket, Req) of
+ {ok, Req1} ->
+ handle_request(Socket, Req1, State);
+ Error ->
+ {stop, Error, State}
+ end;
+ {http_error, ?CRNL} ->
+ {ok, State};
+ {http_error, ?NL} ->
+ {ok, State};
+ _ when is_list(Data); is_binary(Data) ->
+ ?debug("exo_http_server: request data: ~p\n", [Data]),
+ {stop, {error,sync_error}, State};
+ Error ->
+ {stop, Error, State}
+ end.
+
+%%-----------------------------------------------------------------------------
+%% @doc
+%% Close function called when a connection is closed.
+%%
+%% @end
+%%-----------------------------------------------------------------------------
+-spec close(Socket::#exo_socket{},
+ State::#state{}) ->
+ {ok, NewState::#state{}}.
+
+close(_Socket, State) ->
+ ?debug("exo_http_server: close\n", []),
+ {ok,State}.
+
+%%-----------------------------------------------------------------------------
+%% @doc
+%% Error function called when an error is detected.
+%% Stops the server.
+%%
+%% @end
+%%-----------------------------------------------------------------------------
+-spec error(Socket::#exo_socket{},
+ Error::term(),
+ State::#state{}) ->
+ {stop, {error, Reason::term()}, NewState::#state{}}.
+
+error(_Socket,Error,State) ->
+ ?debug("exo_http_serber: error = ~p\n", [Error]),
+ {stop, Error, State}.
+
+
+handle_request(Socket, R, State) ->
+ ?debug("exo_http_server: request = ~s\n",
+ [[exo_http:format_request(R),?CRNL,
+ exo_http:format_hdr(R#http_request.headers),
+ ?CRNL]]),
+ case exo_http:recv_body(Socket, R) of
+ {ok, Body} ->
+ handle_body(Socket, R, Body, State);
+ {error, closed} ->
+ {stop, normal,State};
+ Error ->
+ {stop, Error, State}
+ end.
+
+handle_body(Socket, Request, Body,
+ State=#state {request_handler = RH}) when is_tuple(RH) ->
+ {M, F, As} = request_handler(RH, Socket, Request, Body),
+ ?debug("exo_http_server: calling ~p with -BODY:\n~s\n-END-BODY\n",
+ [RH, Body]),
+ case apply(M, F, As) of
+ ok -> {ok, State};
+ stop -> {stop, normal, State};
+ {error, Error} -> {stop, Error, State}
+ end;
+handle_body(Socket, Request, Body, State) ->
+ Url = Request#http_request.uri,
+ ?debug("exo_http_server: -BODY:\n~s\n-END-BODY\n", [Body]),
+ if Request#http_request.method == 'GET',
+ Url#url.path == "/quit" ->
+ response(Socket, "close", 200, "OK", "QUIT"),
+ exo_socket:shutdown(Socket, write),
+ {stop, normal, State};
+ Url#url.path == "/test" ->
+ response(Socket, undefined, 200, "OK", "OK"),
+ {ok, State};
+ true ->
+ response(Socket, undefined, 404, "Not Found",
+ "Object not found"),
+ {ok, State}
+ end.
+
+%% @private
+request_handler({Module, Function}, Socket, Request, Body) ->
+ {Module, Function, [Socket, Request, Body]};
+request_handler({Module, Function, XArgs}, Socket, Request, Body) ->
+ {Module, Function, [Socket, Request, Body | XArgs]}.
+
+
+%%-----------------------------------------------------------------------------
+%% @doc
+%% Support function for sending a response.
+%%
+%% @end
+%%-----------------------------------------------------------------------------
+-spec response(Socket::#exo_socket{},
+ Connection::string() | undefined,
+ Status::integer(),
+ Phrase::string(),
+ Status::string()) ->
+ ok |
+ {error, Reason::term()}.
+
+response(S, Connection, Status, Phrase, String) ->
+ response(S, Connection, Status, Phrase, String, []).
+
+response(S, Connection, Status, Phrase, Body, Opts) ->
+ ContentType = opt(content_type, Opts, "text/plain"),
+ H = #http_shdr { connection = Connection,
+ content_length = content_length(Body),
+ content_type = ContentType },
+ R = #http_response { version = {1, 1},
+ status = Status,
+ phrase = Phrase,
+ headers = H },
+ Response = [exo_http:format_response(R),
+ ?CRNL,
+ exo_http:format_hdr(H),
+ ?CRNL,
+ Body],
+ ?debug("exo_http_server: response:\n~s\n", [Response]),
+ exo_socket:send(S, Response).
+
+content_length(B) when is_binary(B) ->
+ byte_size(B);
+content_length(L) when is_list(L) ->
+ iolist_size(L).
+
+
+opt(K, L, Def) ->
+ case lists:keyfind(K, 1, L) of
+ {_, V} -> V;
+ false -> Def
+ end.
+
+
+%% @private
+test() ->
+ Dir = code:priv_dir(exo),
+ exo_socket_server:start(9000, [tcp,probe_ssl,http],
+ [{active,once},{reuseaddr,true},
+ {verify, verify_none},
+ {keyfile, filename:join(Dir, "host.key")},
+ {certfile, filename:join(Dir, "host.cert")}],
+ ?MODULE, []).
+
diff --git a/deps/exo/src/exo_json.erl b/deps/exo/src/exo_json.erl
new file mode 100644
index 0000000..13714e0
--- /dev/null
+++ b/deps/exo/src/exo_json.erl
@@ -0,0 +1,722 @@
+%%% Copyright (c) 2005-2006, A2Z Development USA, Inc. All Rights Reserved.
+%%%
+%%% The contents of this file are subject to the Erlang Public License,
+%%% Version 1.1, (the "License"); you may not use this file except in
+%%% compliance with the License. You should have received a copy of the
+%%% Erlang Public License along with this software. If not, it can be
+%%% retrieved via the world wide web at http://www.erlang.org/.
+%%%
+%%% Software distributed under the License is distributed on an "AS IS"
+%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%%% the License for the specific language governing rights and limitations
+%%% under the License.
+%%%
+%%% The Initial Developer of the Original Code is A2Z Development USA, Inc.
+%%% All Rights Reserved.
+%%%
+%%% Originally json2.erl, from the Yaws repository
+
+-module(exo_json).
+-export([encode/1, decode_string/1, decode/2]).
+-export([is_obj/1, obj_new/0, obj_fetch/2, obj_find/2, obj_is_key/2]).
+-export([obj_store/3, obj_from_list/1, obj_fold/3]).
+-export([test/0]).
+-author("Jim Larson <jalarson@amazon.com>, Robert Wai-Chi Chu <robchu@amazon.com>").
+-author("Gaspar Chilingarov <nm@web.am>, Gurgen Tumanyan <barbarian@armkb.com>").
+-author("Steve Vinoski <vinoski@ieee.org>").
+-vsn("3").
+
+%%% JavaScript Object Notation ("JSON", http://www.json.org) is a simple
+%%% data syntax meant as a lightweight alternative to other representations,
+%%% such as XML. JSON is natively supported by JavaScript, but many
+%%% other languages have conversion libraries available.
+%%%
+%%% This module translates JSON types into the following Erlang types:
+%%%
+%%% JSON Erlang
+%%% ---- ------
+%%% number number
+%%% string string
+%%% array {array, ElementList}
+%%% object tagged proplist with string keys (i.e. {struct, PropList} )
+%%% true, false, null atoms 'true', 'false', and 'null'
+%%%
+%%% Character Sets: the external representation, and the internal
+%%% representation of strings, are lists of UTF-8 code units.
+%%%
+%%% Numbers: Thanks to Erlang's bignums, JSON-encoded integers of any
+%%% size can be parsed. Conversely, extremely large integers may
+%%% be JSON-encoded. This may cause problems for interoperability
+%%% with JSON parsers which can't handle arbitrary-sized integers.
+%%% Erlang's floats are of fixed precision and limited range, so
+%%% syntactically valid JSON floating-point numbers could silently
+%%% lose precision or noisily cause an overflow. However, most
+%%% other JSON libraries are likely to behave in the same way.
+%%%
+%%% Strings: If we represented JSON string data as Erlang binaries,
+%%% we would have to choose a particular unicode format. Instead,
+%%% we use lists of UTF-16 code units, which applications may then
+%%% change to binaries in their application-preferred manner.
+%%%
+%%% Arrays: Because of the string decision above, and Erlang's
+%%% lack of a distinguished string datatype, JSON arrays map
+%%% to {array, ArrayElementList}, where ArrayElementList -> list.
+%%%
+%%% Objects: Though not explicitly stated in the JSON "spec",
+%%% JSON's JavaScript heritage mandates that member names must
+%%% be unique within an object. The object/tuple ambiguity is
+%%% not a problem, since the atom 'struct' is not an
+%%% allowable value. Object keys may be atoms or strings on
+%%% encoding but are always decoded as strings.
+%%%
+
+%%% ENCODING
+
+%% Encode an erlang number, string, tuple, or object to JSON syntax, as a
+%% possibly deep list of UTF-8 code units, throwing a runtime error in the
+%% case of un-convertible input.
+%% Note: object keys may be either strings or atoms.
+
+encode(true) -> "true";
+encode(false) -> "false";
+encode(null) -> "null";
+encode(undefined) -> "null";
+encode(B) when is_binary(B) -> encode_string(B);
+encode(I) when is_integer(I) -> integer_to_list(I);
+encode(F) when is_float(F) -> float_to_list(F);
+encode(L) when is_list(L) ->
+ case is_string(L) of
+ yes -> encode_string(L);
+ unicode -> encode_string(xmerl_ucs:to_utf8(L));
+ no -> encode({array, L})
+ end;
+encode({array, Props}) when is_list(Props) -> encode_array(Props);
+encode({struct, Props} = T) when is_list(Props) -> encode_object(T);
+encode(Bad) -> exit({json_encode, {bad_term, Bad}}).
+
+%% Encode an Erlang string to JSON.
+%% Accumulate strings in reverse.
+
+encode_string(B) when is_binary(B) -> encode_string(binary_to_list(B));
+encode_string(S) -> encode_string(S, [$"]).
+
+encode_string([], Acc) -> lists:reverse([$" | Acc]);
+encode_string([C | Cs], Acc) ->
+ case C of
+ $" -> encode_string(Cs, [$", $\\ | Acc]);
+ % (don't escape solidus on encode)
+ $\\ -> encode_string(Cs, [$\\, $\\ | Acc]);
+ $\b -> encode_string(Cs, [$b, $\\ | Acc]); % note missing \
+ $\f -> encode_string(Cs, [$f, $\\ | Acc]);
+ $\n -> encode_string(Cs, [$n, $\\ | Acc]);
+ $\r -> encode_string(Cs, [$r, $\\ | Acc]);
+ $\t -> encode_string(Cs, [$t, $\\ | Acc]);
+ C when C >= 0, C < $\s ->
+ % Control characters must be unicode-encoded.
+ Hex = lists:flatten(io_lib:format("~4.16.0b", [C])),
+ encode_string(Cs, lists:reverse(Hex) ++ "u\\" ++ Acc); % "
+ C when C =< 16#FFFF -> encode_string(Cs, [C | Acc]);
+ _ -> exit({json_encode, {bad_char, C}})
+ end.
+
+%% Encode an Erlang object as a JSON object, allowing string or atom keys.
+%% Note that order is irrelevant in both internal and external object
+%% representations. Nevertheless, the output will respect the order
+%% of the input.
+
+encode_object({struct, _Props} = Obj) ->
+ M = obj_fold(fun({Key, Value}, Acc) ->
+ S = case Key of
+ B when is_binary(B) -> encode_string(B);
+ L when is_list(L) ->
+ case is_string(L) of
+ yes -> encode_string(L);
+ unicode -> encode_string(xmerl_ucs:to_utf8(L));
+ no -> exit({json_encode, {bad_key, Key}})
+ end;
+ A when is_atom(A) -> encode_string(atom_to_list(A));
+ _ -> exit({json_encode, {bad_key, Key}})
+ end,
+ V = encode(Value),
+ case Acc of
+ [] -> [S, $:, V];
+ _ -> [Acc, $,, S, $:, V]
+ end
+ end, [], Obj),
+ [${, M, $}].
+
+%% Encode an Erlang tuple as a JSON array.
+%% Order *is* significant in a JSON array!
+
+encode_array(T) ->
+ M = lists:foldl(fun(E, Acc) ->
+ V = encode(E),
+ case Acc of
+ [] -> V;
+ _ -> [Acc, $,, V]
+ end
+ end, [], T),
+ [$[, M, $]].
+
+%%% SCANNING
+%%%
+%%% Scanning funs return either:
+%%% {done, Result, LeftOverChars}
+%%% if a complete token is recognized, or
+%%% {more, Continuation}
+%%% if more input is needed.
+%%% Result is {ok, Term}, 'eof', or {error, Reason}.
+%%% Here, the Continuation is a simple Erlang string.
+%%%
+%%% Currently, error handling is rather crude - errors are recognized
+%%% by match failures. EOF is handled only by number scanning, where
+%%% it can delimit a number, and otherwise causes a match failure.
+%%%
+%%% Tokens are one of the following
+%%% JSON string -> erlang string
+%%% JSON number -> erlang number
+%%% true, false, null -> erlang atoms
+%%% { } [ ] : , -> lcbrace rcbrace lsbrace rsbrace colon comma
+
+token([]) -> {more, []};
+token(eof) -> {done, eof, []};
+
+token("true" ++ Rest) -> {done, {ok, true}, Rest};
+token("tru") -> {more, "tru"};
+token("tr") -> {more, "tr"};
+token("t") -> {more, "t"};
+
+token("false" ++ Rest) -> {done, {ok, false}, Rest};
+token("fals") -> {more, "fals"};
+token("fal") -> {more, "fal"};
+token("fa") -> {more, "fa"};
+token("f") -> {more, "f"};
+
+token("null" ++ Rest) -> {done, {ok, null}, Rest};
+token("nul") -> {more, "nul"};
+token("nu") -> {more, "nu"};
+token("n") -> {more, "n"};
+
+token([C | Cs] = Input) ->
+ case C of
+ $\s -> token(Cs); % eat whitespace
+ $\t -> token(Cs); % eat whitespace
+ $\n -> token(Cs); % eat whitespace
+ $\r -> token(Cs); % eat whitespace
+ $" -> scan_string(Input);
+ $- -> scan_number(Input);
+ D when D >= $0, D =< $9-> scan_number(Input);
+ ${ -> {done, {ok, lcbrace}, Cs};
+ $} -> {done, {ok, rcbrace}, Cs};
+ $[ -> {done, {ok, lsbrace}, Cs};
+ $] -> {done, {ok, rsbrace}, Cs};
+ $: -> {done, {ok, colon}, Cs};
+ $, -> {done, {ok, comma}, Cs};
+ _ -> {done, {error, {bad_char, C}}, Cs}
+ end.
+
+scan_string([$" | Cs] = Input) ->
+ scan_string(Cs, [], Input).
+
+%% Accumulate in reverse order, save original start-of-string for continuation.
+
+scan_string([], _, X) -> {more, X};
+scan_string(eof, _, X) -> {done, {error, missing_close_quote}, X};
+scan_string([$" | Rest], A, _) -> {done, {ok, lists:reverse(A)}, Rest};
+scan_string([$\\], _, X) -> {more, X};
+scan_string([$\\, $u, U1, U2, U3, U4 | Rest], A, X) ->
+ scan_string(Rest, [uni_char([U1, U2, U3, U4]) | A], X);
+scan_string([$\\, $u | _], _, X) -> {more, X};
+scan_string([$\\, C | Rest], A, X) ->
+ scan_string(Rest, [esc_to_char(C) | A], X);
+scan_string([C | Rest], A, X) ->
+ scan_string(Rest, [C | A], X).
+
+%% Given a list of hex characters, convert to the corresponding integer.
+
+uni_char(HexList) ->
+ erlang:list_to_integer(HexList, 16).
+
+esc_to_char($") -> $";
+esc_to_char($/) -> $/;
+esc_to_char($\\) -> $\\;
+esc_to_char($b) -> $\b;
+esc_to_char($f) -> $\f;
+esc_to_char($n) -> $\n;
+esc_to_char($r) -> $\r;
+esc_to_char($t) -> $\t.
+
+scan_number([]) -> {more, []};
+scan_number(eof) -> {done, {error, incomplete_number}, []};
+scan_number([$-, $- | _Ds]) -> {done, {error, invalid_number}, []};
+scan_number([$- | Ds] = Input) ->
+ case scan_number(Ds) of
+ {more, _Cont} -> {more, Input};
+ {done, {ok, N}, CharList} -> {done, {ok, -1 * N}, CharList};
+ {done, Other, Chars} -> {done, Other, Chars}
+ end;
+scan_number([D | Ds] = Input) when D >= $0, D =< $9 ->
+ scan_number(Ds, D - $0, Input).
+
+%% Numbers don't have a terminator, so stop at the first non-digit,
+%% and ask for more if we run out.
+
+scan_number([], _A, X) -> {more, X};
+scan_number(eof, A, _X) -> {done, {ok, A}, eof};
+scan_number([$.], _A, X) -> {more, X};
+scan_number([$., D | Ds], A, X) when D >= $0, D =< $9 ->
+ scan_fraction([D | Ds], A, X);
+scan_number([D | Ds], A, X) when A > 0, D >= $0, D =< $9 ->
+ % Note that nonzero numbers can't start with "0".
+ scan_number(Ds, 10 * A + (D - $0), X);
+scan_number([D | Ds], A, X) when D == $E; D == $e ->
+ scan_exponent_begin(Ds, integer_to_list(A) ++ ".0", X);
+scan_number([D | _] = Ds, A, _X) when D < $0; D > $9 ->
+ {done, {ok, A}, Ds}.
+
+scan_fraction(Ds, I, X) -> scan_fraction(Ds, [], I, X).
+
+scan_fraction([], _Fs, _I, X) -> {more, X};
+scan_fraction(eof, Fs, I, _X) ->
+ R = list_to_float(lists:append([integer_to_list(I), ".", lists:reverse(Fs)])),
+ {done, {ok, R}, eof};
+scan_fraction([D | Ds], Fs, I, X) when D >= $0, D =< $9 ->
+ scan_fraction(Ds, [D | Fs], I, X);
+scan_fraction([D | Ds], Fs, I, X) when D == $E; D == $e ->
+ R = lists:append([integer_to_list(I), ".", lists:reverse(Fs)]),
+ scan_exponent_begin(Ds, R, X);
+scan_fraction(Rest, Fs, I, _X) ->
+ R = list_to_float(lists:append([integer_to_list(I), ".", lists:reverse(Fs)])),
+ {done, {ok, R}, Rest}.
+
+scan_exponent_begin(Ds, R, X) ->
+ scan_exponent_begin(Ds, [], R, X).
+
+scan_exponent_begin([], _Es, _R, X) -> {more, X};
+scan_exponent_begin(eof, _Es, _R, X) -> {done, {error, missing_exponent}, X};
+scan_exponent_begin([D | Ds], Es, R, X) when D == $-;
+ D == $+;
+ D >= $0, D =< $9 ->
+ scan_exponent(Ds, [D | Es], R, X).
+
+scan_exponent([], _Es, _R, X) -> {more, X};
+scan_exponent(eof, Es, R, _X) ->
+ X = list_to_float(lists:append([R, "e", lists:reverse(Es)])),
+ {done, {ok, X}, eof};
+scan_exponent([D | Ds], Es, R, X) when D >= $0, D =< $9 ->
+ scan_exponent(Ds, [D | Es], R, X);
+scan_exponent(Rest, Es, R, _X) ->
+ X = list_to_float(lists:append([R, "e", lists:reverse(Es)])),
+ {done, {ok, X}, Rest}.
+
+%%% PARSING
+%%%
+%%% The decode function takes a char list as input, but
+%%% interprets the end of the list as only an end to the available
+%%% input, and returns a "continuation" requesting more input.
+%%% When additional characters are available, they, and the
+%%% continuation, are fed into decode/2. You can use the atom 'eof'
+%%% as a character to signal a true end to the input stream, and
+%%% possibly flush out an unfinished number. The decode_string/1
+%%% function appends 'eof' to its input and calls decode/1.
+%%%
+%%% Parsing and scanning errors are handled only by match failures.
+%%% The external caller must take care to wrap the call in a "catch"
+%%% or "try" if better error-handling is desired. Eventually parse
+%%% or scan errors will be returned explicitly with a description,
+%%% and someday with line numbers too.
+%%%
+%%% The parsing code uses a continuation-passing style to allow
+%%% for the parsing to suspend at any point and be resumed when
+%%% more input is available.
+%%% See http://en.wikipedia.org/wiki/Continuation_passing_style
+
+%% Return the first JSON value decoded from the input string.
+%% The string must contain at least one complete JSON value.
+
+decode_string(CharList) ->
+ {done, V, _} = decode([], CharList ++ eof),
+ V.
+
+%% Attempt to decode a JSON value from the input string
+%% and continuation, using empty list for the initial continuation.
+%% Return {done, Result, LeftoverChars} if a value is recognized,
+%% or {more, Continuation} if more input characters are needed.
+%% The Result can be {ok, Value}, eof, or {error, Reason}.
+%% The Continuation is then fed as an argument to decode/2 when
+%% more input is available.
+%% Use the atom 'eof' instead of a char list to signal
+%% a true end to the input, and may flush a final number.
+
+decode([], CharList) ->
+ decode(first_continuation(), CharList);
+
+decode(Continuation, CharList) ->
+ {OldChars, Kt} = Continuation,
+ get_token(OldChars ++ CharList, Kt).
+
+first_continuation() ->
+ {[], fun
+ (eof, Cs) ->
+ {done, eof, Cs};
+ (T, Cs) ->
+ parse_value(T, Cs, fun(V, C2) ->
+ {done, {ok, V}, C2}
+ end)
+ end}.
+
+%% Continuation Kt must accept (TokenOrEof, Chars)
+
+get_token(Chars, Kt) ->
+ case token(Chars) of
+ {done, {ok, T}, Rest} -> Kt(T, Rest);
+ {done, eof, Rest} -> Kt(eof, Rest);
+ {done, {error, Reason}, Rest} -> {done, {error, Reason}, Rest};
+ {more, X} -> {more, {X, Kt}}
+ end.
+
+%% Continuation Kv must accept (Value, Chars)
+
+parse_value(eof, C, _Kv) -> {done, {error, premature_eof}, C};
+parse_value(true, C, Kv) -> Kv(true, C);
+parse_value(false, C, Kv) -> Kv(false, C);
+parse_value(null, C, Kv) -> Kv(null, C);
+parse_value(S, C, Kv) when is_list(S) -> Kv(S, C);
+parse_value(N, C, Kv) when is_number(N) -> Kv(N, C);
+parse_value(lcbrace, C, Kv) -> parse_object(C, Kv);
+parse_value(lsbrace, C, Kv) -> parse_array(C, Kv);
+parse_value(_, C, _Kv) -> {done, {error, syntax_error}, C}.
+
+%% Continuation Kv must accept (Value, Chars)
+
+parse_object(Chars, Kv) ->
+ get_token(Chars, fun(T, C2) ->
+ Obj = obj_new(),
+ case T of
+ rcbrace -> Kv(Obj, C2); % empty object
+ _ -> parse_object(Obj, T, C2, Kv) % token must be string
+ end
+ end).
+
+parse_object(_Obj, eof, C, _Kv) ->
+ {done, {error, premature_eof}, C};
+
+parse_object(Obj, S, C, Kv) when is_list(S) -> % S is member name
+ get_token(C, fun
+ (colon, C2) ->
+ parse_object2(Obj, S, C2, Kv);
+ (T, C2) ->
+ {done, {error, {expecting_colon, T}}, C2}
+ end);
+
+parse_object(_Obj, M, C, _Kv) ->
+ {done, {error, {member_name_not_string, M}}, C}.
+
+parse_object2(Obj, S, C, Kv) ->
+ get_token(C, fun
+ (eof, C2) ->
+ {done, {error, premature_eof}, C2};
+ (T, C2) ->
+ parse_value(T, C2, fun(V, C3) -> % V is member value
+ Obj2 = obj_store(S, V, Obj),
+ get_token(C3, fun
+ (rcbrace, C4) -> % "}" end of object
+ {struct, PropList1} = Obj2,
+ Kv({struct, lists:reverse(PropList1)}, C4);
+ (comma, C4) -> % "," another member follows
+ get_token(C4, fun(T3, C5) ->
+ parse_object(Obj2, T3, C5, Kv)
+ end);
+ (eof, C4) ->
+ {done, {error, premature_eof}, C4};
+ (T2, C4) ->
+ {done, {error, {expecting_comma_or_curly, T2}}, C4}
+ end)
+ end)
+ end).
+
+%% Continuation Kv must accept (Value, Chars)
+
+parse_array(C, Kv) ->
+ get_token(C, fun
+ (eof, C2) -> {done, {error, premature_eof}, C2};
+ (rsbrace, C2) -> Kv({array, []}, C2); % empty array
+ (T, C2) -> parse_array([], T, C2, Kv)
+ end).
+
+parse_array(E, T, C, Kv) ->
+ parse_value(T, C, fun(V, C2) ->
+ E2 = [V | E],
+ get_token(C2, fun
+ (rsbrace, C3) -> % "]" end of array
+ Kv({array, lists:reverse(E2)}, C3);
+
+ (comma, C3) -> % "," another value follows
+ get_token(C3, fun(T3, C4) ->
+ parse_array(E2, T3, C4, Kv)
+ end);
+ (eof, C3) ->
+ {done, {error, premature_eof}, C3};
+ (T2, C3) ->
+ {done, {error, {expecting_comma_or_close_array, T2}}, C3}
+ end)
+ end).
+
+%%% OBJECTS
+%%%
+%%% We'll use tagged property lists as the internal representation
+%%% of JSON objects. Unordered lists perform worse than trees for
+%%% lookup and modification of members, but we expect objects to be
+%%% have only a few members. Lists also print better.
+
+%% Is this a proper JSON object representation?
+
+is_obj({struct, Props}) when is_list(Props) ->
+ lists:all(fun
+ ({Member, _Value}) when is_atom(Member); is_list(Member) -> true;
+ (_) -> false
+ end, Props);
+
+is_obj(_) ->
+ false.
+
+%% Create a new, empty object.
+
+obj_new() ->
+ {struct, []}.
+
+%% Fetch an object member's value, expecting it to be in the object.
+%% Return value, runtime error if no member found with that name.
+
+obj_fetch(Key, {struct, Props}) when is_list(Props) ->
+ case proplists:get_value(Key, Props) of
+ undefined ->
+ exit({struct_no_key, Key});
+ Value ->
+ Value
+ end.
+
+%% Fetch an object member's value, or indicate that there is no such member.
+%% Return {ok, Value} or 'error'.
+
+obj_find(Key, {struct, Props}) when is_list(Props) ->
+ case proplists:get_value(Key, Props) of
+ undefined ->
+ error;
+ Value ->
+ {ok, Value}
+ end.
+
+obj_is_key(Key, {struct, Props}) ->
+ proplists:is_defined(Key, Props).
+
+%% Store a new member in an object. Returns a new object.
+
+obj_store(Key, Value, {struct, Props}) when is_list(Props) ->
+ {struct, [{Key, Value} | proplists:delete(Key, Props)]}.
+
+%% Create an object from a list of Key/Value pairs.
+
+obj_from_list(Props) ->
+ Obj = {struct, Props},
+ case is_obj(Obj) of
+ true -> Obj;
+ false -> exit(json_bad_object)
+ end.
+
+%% Fold Fun across object, with initial accumulator Acc.
+%% Fun should take (Value, Acc) as arguments and return Acc.
+
+obj_fold(Fun, Acc, {struct, Props}) ->
+ lists:foldl(Fun, Acc, Props).
+
+is_string([]) -> yes;
+is_string(List) -> is_string(List, non_unicode).
+
+is_string([C|Rest], non_unicode) when is_integer(C), C >= 0, C =< 255 ->
+ is_string(Rest, non_unicode);
+is_string([C|Rest], _) when is_integer(C), C =< 65000 -> is_string(Rest, unicode);
+is_string([], non_unicode) -> yes;
+is_string([], unicode) -> unicode;
+is_string(_, _) -> no.
+
+
+%%% TESTING
+%%%
+%%% We can't expect to round-trip from JSON -> Erlang -> JSON,
+%%% due to the degrees of freedom in the JSON syntax: whitespace,
+%%% and ordering of object members. We can, however, expect to
+%%% round-trip from Erlang -> JSON -> Erlang, so the JSON parsing
+%%% tests will in fact test the Erlang equivalence of the
+%%% JSON -> Erlang -> JSON -> Erlang coding chain.
+
+%% Test driver. Return 'ok' or {failed, Failures}.
+
+test() ->
+ E2Js = e2j_test_vec(),
+ Failures =
+ lists:foldl(
+ fun({E, J}, Fs) ->
+ case (catch test_e2j(E, J)) of
+ ok ->
+ case (catch round_trip(E)) of
+ ok ->
+ case (catch round_trip_one_char(E)) of
+ ok ->
+ Fs;
+ Reason ->
+ [{round_trip_one_char, E, Reason} | Fs]
+ end;
+ Reason ->
+ [{round_trip, E, Reason} | Fs]
+ end;
+ Reason ->
+ [{erlang_to_json, E, J, Reason} | Fs]
+ end;
+ (end_of_tests, Fs) ->
+ Fs
+ end, [], E2Js),
+ case Failures of
+ [] -> ok;
+ _ -> {failed, Failures}
+ end.
+
+%% Test for conversion from Erlang to JSON. Note that unequal strings
+%% may represent equal JSON data, due to discretionary whitespace,
+%% object member order, trailing zeroes in floating point, etc.
+%% Legitimate changes to the encoding routines may require tweaks to
+%% the reference JSON strings in e2j_test_vec().
+
+%% This clause handles floats specially due to the need for fuzzy matching
+%% to avoid slight differences due to conversions. Rather than direct
+%% comparison as done in the more general clause below, here we allow a
+%% small relative difference between expected and actual.
+test_e2j(E, J) when is_float(E) ->
+ J2 = lists:flatten(encode(E)),
+ E2 = list_to_float(J2),
+ E1 = list_to_float(J),
+ Rel = abs(E2 - E1)/E,
+ true = Rel < 0.005,
+ ok;
+test_e2j(E, J) ->
+ J2 = lists:flatten(encode(E)),
+ J = J2, % raises error if unequal
+ ok.
+
+%% Test that Erlang -> JSON -> Erlang round-trip yields equivalent term.
+
+round_trip(E) ->
+ J2 = lists:flatten(encode(E)),
+ {ok, E2} = decode_string(J2),
+ true = equiv(E, E2), % raises error if false
+ ok.
+
+%% Round-trip with one character at a time to test all continuations.
+
+round_trip_one_char(E) ->
+ J = lists:flatten(encode(E)),
+ {done, {ok, E2}, _} = lists:foldl(fun(C, Ret) ->
+ case Ret of
+ {done, _, _} -> Ret;
+ {more, Cont} -> decode(Cont, [C])
+ end
+ end, {more, first_continuation()}, J ++ [eof]),
+ true = equiv(E, E2), % raises error if false
+ ok.
+
+%% Test for equivalence of Erlang terms.
+%% Due to arbitrary order of construction, equivalent objects might
+%% compare unequal as erlang terms, so we need to carefully recurse
+%% through aggregates (arrays and objects).
+
+equiv({struct, Props1}, {struct, Props2}) ->
+ equiv_object(Props1, Props2);
+equiv({array, ArrayList1}, {array, ArrayList2}) ->
+ equiv_array(ArrayList1, ArrayList2);
+equiv(N1, N2) when is_number(N1), is_number(N2) -> N1 == N2;
+equiv(S1, S2) when is_list(S1), is_list(S2) ->
+ case {is_string(S1), is_string(S2)} of
+ {unicode, unicode} ->
+ xmerl_ucs:to_utf8(S1) == xmerl_ucs:to_utf8(S2);
+ {unicode, _} ->
+ xmerl_ucs:to_utf8(S1) == S2;
+ {_, unicode} ->
+ S1 == xmerl_ucs:to_utf8(S2);
+ _ ->
+ S1 == S2
+ end;
+equiv(true, true) -> true;
+equiv(false, false) -> true;
+equiv(null, null) -> true.
+
+%% Object representation and traversal order is unknown.
+%% Use the sledgehammer and sort property lists.
+
+equiv_object(Props1, Props2) ->
+ L1 = lists:keysort(1, Props1),
+ L2 = lists:keysort(1, Props2),
+ Pairs = lists:zip(L1, L2),
+ true = lists:all(fun({{K1, V1}, {K2, V2}}) ->
+ equiv(K1, K2) and equiv(V1, V2)
+ end, Pairs).
+
+%% Recursively compare array elements for equivalence.
+
+equiv_array([], []) ->
+ true;
+equiv_array(A1, A2) when length(A1) == length(A2) ->
+ lists:all(fun({E1,E2}) ->
+ equiv(E1, E2)
+ end, lists:zip(A1, A2)).
+
+e2j_test_vec() -> [
+ {1, "1"},
+ {3.1416, "3.14160"}, % text representation may truncate, trail zeroes
+ {-1, "-1"},
+ {-3.1416, "-3.14160"},
+ {12.0e10, "1.20000e+11"},
+ {1.234E+10, "1.23400e+10"},
+ {-1.234E-10, "-1.23400e-10"},
+ {"foo", "\"foo\""},
+ {"foo" ++ [500] ++ "bar", [$", $f, $o, $o, $\307, $\264, $b, $a, $r, $"]},
+ {"foo" ++ [5] ++ "bar", "\"foo\\u0005bar\""},
+ {"", "\"\""},
+ {[], "\"\""},
+ {"\n\n\n", "\"\\n\\n\\n\""},
+ {obj_new(), "{}"},
+ {obj_from_list([{"foo", "bar"}]), "{\"foo\":\"bar\"}"},
+ {obj_from_list([{"foo", "bar"}, {"baz", 123}]),
+ "{\"foo\":\"bar\",\"baz\":123}"},
+ {{array, []}, "[]"},
+ {{array, [{array, []}]}, "[[]]"},
+ {{array, [1, "foo"]}, "[1,\"foo\"]"},
+
+ % json array in a json object
+ {obj_from_list([{"foo", {array, [123]}}]),
+ "{\"foo\":[123]}"},
+
+ % json object in a json object
+ {obj_from_list([{"foo", obj_from_list([{"bar", true}])}]),
+ "{\"foo\":{\"bar\":true}}"},
+
+ % fold evaluation order
+ {obj_from_list([{"foo", {array, []}},
+ {"bar", obj_from_list([{"baz", true}])},
+ {"alice", "bob"}]),
+ "{\"foo\":[],\"bar\":{\"baz\":true},\"alice\":\"bob\"}"},
+
+ % json object in a json array
+ {{array, [-123, "foo", obj_from_list([{"bar", {array, []}}]), null]},
+ "[-123,\"foo\",{\"bar\":[]},null]"},
+
+ end_of_tests
+].
+
+%%% TODO:
+%%%
+%%% Measure the overhead of the CPS-based parser by writing a conventional
+%%% scanner-parser that expects all input to be available.
+%%%
+%%% Allow a compile-time option to decode object member names as atoms,
+%%% to reduce the internal representation overheads when communicating
+%%% with trusted peers.
diff --git a/deps/exo/src/exo_socket.erl b/deps/exo/src/exo_socket.erl
new file mode 100644
index 0000000..dc961ac
--- /dev/null
+++ b/deps/exo/src/exo_socket.erl
@@ -0,0 +1,557 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved.
+%%%
+%%% This Source Code Form is subject to the terms of the Mozilla Public
+%%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @doc
+%%% EXO socket
+%%% @end
+%%% Created : 15 Dec 2011 by Tony Rogvall <tony@rogvall.se>
+
+-module(exo_socket).
+
+
+-export([listen/1, listen/2, listen/3]).
+-export([accept/1, accept/2]).
+-export([async_accept/1, async_accept/2]).
+-export([connect/2, connect/3, connect/4, connect/5]).
+%% -export([async_connect/2, async_connect/3, async_connect/4]).
+-export([async_socket/2, async_socket/3]).
+-export([close/1, shutdown/2]).
+-export([send/2, recv/2, recv/3]).
+-export([getopts/2, setopts/2, sockname/1, peername/1]).
+-export([controlling_process/2]).
+-export([pair/0]).
+-export([stats/0, getstat/2]).
+-export([tags/1, socket/1]).
+-export([auth_incoming/2, authenticate/1]).
+
+-include("exo_socket.hrl").
+-include("log.hrl").
+
+-define(dbg(F, A), ?debug("~p " ++ F, [self()|A])).
+%% -ifdef(debug).
+%% -define(dbg(F, A), io:format((F), (A))).
+%% -else.
+%% -define(dbg(F, A), ok).
+%% -endif.
+
+%%
+%% List of protocols supported
+%% [tcp]
+%% [tcp,ssl]
+%% [tcp,ssl,http]
+%% [tcp,propbe_ssl,http]
+%% [tcp,http]
+%%
+%% coming soon: sctcp, ssh
+%%
+%%
+listen(Port) ->
+ listen(Port, [tcp], []).
+
+listen(Port, Opts) ->
+ listen(Port,[tcp], Opts).
+
+listen(Port, Protos=[tcp|_], Opts0) ->
+ Opts1 = proplists:expand([{binary, [{mode, binary}]},
+ {list, [{mode, list}]}], Opts0),
+ {TcpOpts, Opts2} = split_options(tcp_listen_options(), Opts1),
+ ?dbg("exo_socket: listen options=~w, other=~w\n", [TcpOpts, Opts2]),
+ Active = proplists:get_value(active, TcpOpts, false),
+ Mode = proplists:get_value(mode, TcpOpts, list),
+ Packet = proplists:get_value(packet, TcpOpts, 0),
+ {_, TcpOpts1} = split_options([active,packet,mode], TcpOpts),
+ TcpListenOpts = [{active,false},{packet,0},{mode,binary}|TcpOpts1],
+ case gen_tcp:listen(Port, TcpListenOpts) of
+ {ok, L} ->
+ {ok, #exo_socket { mdata = gen_tcp,
+ mctl = inet,
+ protocol = Protos,
+ transport = L,
+ socket = L,
+ active = Active,
+ mode = Mode,
+ packet = Packet,
+ opts = Opts2,
+ tags = {tcp,tcp_closed,tcp_error}
+ }};
+ Error ->
+ Error
+ end.
+
+%%
+%%
+%%
+connect(Host, Port) ->
+ connect(Host, Port, [tcp], [], infinity).
+
+connect(Host, Port, Opts) ->
+ connect(Host, Port, [tcp], Opts, infinity).
+
+connect(Host, Port, Opts, Timeout) ->
+ connect(Host, Port, [tcp], Opts, Timeout).
+
+connect(Host, Port, Protos=[tcp|_], Opts0, Timeout) ->
+ Opts1 = proplists:expand([{binary, [{mode, binary}]},
+ {list, [{mode, list}]}], Opts0),
+ {TcpOpts, Opts2} = split_options(tcp_connect_options(), Opts1),
+ Active = proplists:get_value(active, TcpOpts, false),
+ Mode = proplists:get_value(mode, TcpOpts, list),
+ Packet = proplists:get_value(packet, TcpOpts, 0),
+ {_, TcpOpts1} = split_options([active,packet,mode], TcpOpts),
+ TcpConnectOpts = [{active,false},{packet,0},{mode,binary}|TcpOpts1],
+ case gen_tcp:connect(Host, Port, TcpConnectOpts, Timeout) of
+ {ok, S} ->
+ X =
+ #exo_socket { mdata = gen_tcp,
+ mctl = inet,
+ protocol = Protos,
+ transport = S,
+ socket = S,
+ active = Active,
+ mode = Mode,
+ packet = Packet,
+ opts = Opts2,
+ tags = {tcp,tcp_closed,tcp_error}
+ },
+ maybe_auth(connect_upgrade(X, tl(Protos), Timeout), client, Opts2);
+ Error ->
+ Error
+ end.
+
+maybe_auth(X, Opts) ->
+ maybe_auth(X, undefined, Opts).
+
+maybe_auth(X, Role, Opts) ->
+ case proplists:get_bool(delay_auth, Opts) of
+ true ->
+ ?dbg("Delaying authentication~n", []),
+ X;
+ false ->
+ maybe_auth_(X, Role, Opts)
+ end.
+
+maybe_auth_({ok,X}, Role0, Opts) ->
+ case proplists:get_value(auth, Opts, false) of
+ false ->
+ {ok, X};
+ L when is_list(L) ->
+ Role = proplists:get_value(role, L, Role0),
+ ?dbg("auth opts = ~p~nRole = ~p~n", [L, Role]),
+ %% Here, we should check if the session is already authenticated
+ %% Otherwise, initiate user-level authentication.
+ case lists:keyfind(Role, 1, L) of
+ false -> {ok, X};
+ {_, ROpts} ->
+ ?dbg("ROpts = ~p~n", [ROpts]),
+ case lists:keyfind(mod, 1, ROpts) of
+ {_, M} ->
+ ?dbg("will authenticate (M = ~p~n", [M]),
+ try preserve_active(
+ fun() ->
+ M:authenticate(X, Role, ROpts)
+ end, X) of
+ {ok, Info} ->
+ {ok, X#exo_socket{mauth = M,
+ auth_state = Info}};
+ error ->
+ shutdown(X, write),
+ {error, einval};
+ Other ->
+ ?error("authenticate returned ~p~n",
+ [Other]),
+ {error, Other}
+ catch
+ error:Err ->
+ ?dbg("Caught error: ~p~n"
+ "Trace = ~p~n",
+ [Err, erlang:get_stacktrace()]),
+ shutdown(X, write),
+ {error, einval}
+ end;
+ false ->
+ shutdown(X, write),
+ {error, einval}
+ end
+ end
+ end.
+
+preserve_active(F, S) ->
+ {ok, [{active,A}]} = exo_socket:getopts(S, [active]),
+ Res = F(),
+ exo_socket:setopts(S, [{active,A}]),
+ Res.
+
+authenticate(#exo_socket{mauth = undefined} = XS) ->
+ ?dbg("authenticate(~p)~n", [XS]),
+ maybe_auth({ok,XS}, XS#exo_socket.opts);
+authenticate(#exo_socket{} = XS) ->
+ ?dbg("No authentication options defined.~n", []),
+ {ok, XS}.
+
+auth_incoming(#exo_socket{mauth = undefined}, Data) ->
+ Data;
+auth_incoming(#exo_socket{mauth = M, auth_state = Sa} = X, Data) ->
+ try M:incoming(Data, Sa)
+ catch
+ error:E ->
+ shutdown(X, write),
+ erlang:error(E)
+ end.
+
+
+connect_upgrade(X, Protos0, Timeout) ->
+ ?dbg("exo_socket: connect protos=~w\n", [Protos0]),
+ case Protos0 of
+ [ssl|Protos1] ->
+ Opts = X#exo_socket.opts,
+ {SSLOpts0,Opts1} = split_options(ssl_connect_opts(),Opts),
+ {_,SSLOpts} = split_options([ssl_imp], SSLOpts0),
+ ?dbg("SSL upgrade, options = ~w\n", [SSLOpts]),
+ ?dbg("exo_socket: before ssl:connect opts=~w\n",
+ [getopts(X, [active,packet,mode])]),
+ case ssl_connect(X#exo_socket.socket, SSLOpts, Timeout) of
+ {ok,S1} ->
+ ?dbg("exo_socket: ssl:connect opt=~w\n",
+ [ssl:getopts(S1, [active,packet,mode])]),
+ X1 = X#exo_socket { socket=S1,
+ mdata = ssl,
+ mctl = ssl,
+ opts=Opts1,
+ tags={ssl,ssl_closed,ssl_error}},
+ connect_upgrade(X1, Protos1, Timeout);
+ Error={error,_Reason} ->
+ ?dbg("exo_socket: ssl:connect error=~w\n",
+ [_Reason]),
+ Error
+ end;
+ [http|Protos1] ->
+ {_, Close,Error} = X#exo_socket.tags,
+ X1 = X#exo_socket { packet = http,
+ tags = {http, Close, Error }},
+ connect_upgrade(X1, Protos1, Timeout);
+ [] ->
+ setopts(X, [{mode,X#exo_socket.mode},
+ {packet,X#exo_socket.packet},
+ {active,X#exo_socket.active}]),
+ ?dbg("exo_socket: after upgrade opts=~w\n",
+ [getopts(X, [active,packet,mode])]),
+ {ok,X}
+ end.
+
+ssl_connect(Socket, Options, Timeout) ->
+ case ssl:connect(Socket, Options, Timeout) of
+ {error, ssl_not_started} ->
+ ssl:start(),
+ ssl:connect(Socket, Options, Timeout);
+ Result ->
+ Result
+ end.
+
+%% using this little trick we avoid code loading
+%% problem in a module doing blocking accept call
+async_accept(X) ->
+ async_accept(X,infinity).
+
+async_accept(X,infinity) ->
+ async_accept(X, -1);
+async_accept(X,Timeout) when
+ is_integer(Timeout), Timeout >= -1, is_record(X, exo_socket) ->
+ case X#exo_socket.protocol of
+ [tcp|_] ->
+ case prim_inet:async_accept(X#exo_socket.socket, Timeout) of
+ {ok,Ref} ->
+ {ok, Ref};
+ Error ->
+ Error
+ end;
+ _ ->
+ {error, proto_not_supported}
+ end.
+
+async_socket(Listen, Socket) ->
+ async_socket(Listen, Socket, []).
+
+async_socket(Listen, Socket, AuthOpts)
+ when is_record(Listen, exo_socket), is_port(Socket) ->
+ Inherit = [nodelay,keepalive,delay_send,priority,tos],
+ case getopts(Listen, Inherit) of
+ {ok, Opts} -> %% transfer listen options
+ %% FIXME: here inet is assume, and currentl the only option
+ case inet:setopts(Socket, Opts) of
+ ok ->
+ {ok,Mod} = inet_db:lookup_socket(Listen#exo_socket.socket),
+ inet_db:register_socket(Socket, Mod),
+ X = Listen#exo_socket { transport=Socket, socket=Socket },
+ maybe_auth(
+ accept_upgrade(X, tl(X#exo_socket.protocol), infinity),
+ server,
+ X#exo_socket.opts ++ AuthOpts);
+ Error ->
+ prim_inet:close(Socket),
+ Error
+ end;
+ Error ->
+ prim_inet:close(Socket),
+ Error
+ end.
+
+
+accept(X) when is_record(X, exo_socket) ->
+ accept_upgrade(X, X#exo_socket.protocol, infinity).
+
+accept(X, Timeout) when
+ is_record(X, exo_socket),
+ (Timeout =:= infnity orelse (is_integer(Timeout) andalso Timeout >= 0)) ->
+ accept_upgrade(X, X#exo_socket.protocol, Timeout).
+
+accept_upgrade(X=#exo_socket { mdata = M }, Protos0, Timeout) ->
+ ?dbg("exo_socket: accept protos=~w\n", [Protos0]),
+ case Protos0 of
+ [tcp|Protos1] ->
+ case M:accept(X#exo_socket.socket, Timeout) of
+ {ok,A} ->
+ X1 = X#exo_socket {transport=A,socket=A},
+ accept_upgrade(X1,Protos1,Timeout);
+ Error ->
+ Error
+ end;
+ [ssl|Protos1] ->
+ Opts = X#exo_socket.opts,
+ {SSLOpts0,Opts1} = split_options(ssl_listen_opts(),Opts),
+ {_,SSLOpts} = split_options([ssl_imp], SSLOpts0),
+ ?dbg("SSL upgrade, options = ~w\n", [SSLOpts]),
+ ?dbg("exo_socket: before ssl_accept opt=~w\n",
+ [getopts(X, [active,packet,mode])]),
+ case ssl_accept(X#exo_socket.socket, SSLOpts, Timeout) of
+ {ok,S1} ->
+ ?dbg("exo_socket: ssl_accept opt=~w\n",
+ [ssl:getopts(S1, [active,packet,mode])]),
+ X1 = X#exo_socket{socket=S1,
+ mdata = ssl,
+ mctl = ssl,
+ opts=Opts1,
+ tags={ssl,ssl_closed,ssl_error}},
+ accept_upgrade(X1, Protos1, Timeout);
+ Error={error,_Reason} ->
+ ?dbg("exo_socket: ssl:ssl_accept error=~w\n",
+ [_Reason]),
+ Error
+ end;
+ [probe_ssl|Protos1] ->
+ accept_probe_ssl(X,Protos1,Timeout);
+ [http|Protos1] ->
+ {_, Close,Error} = X#exo_socket.tags,
+ X1 = X#exo_socket { packet = http,
+ tags = {http, Close, Error }},
+ accept_upgrade(X1,Protos1,Timeout);
+ [] ->
+ setopts(X, [{mode,X#exo_socket.mode},
+ {packet,X#exo_socket.packet},
+ {active,X#exo_socket.active}]),
+ ?dbg("exo_socket: after upgrade opts=~w\n",
+ [getopts(X, [active,packet,mode])]),
+ {ok,X}
+ end.
+
+accept_probe_ssl(X=#exo_socket { mdata=M, socket=S,
+ tags = {TData,TClose,TError}},
+ Protos,
+ Timeout) ->
+ ?dbg("exo_socket: accept_probe_ssl protos=~w\n", [Protos]),
+ setopts(X, [{active,once}]),
+ receive
+ {TData, S, Data} ->
+ ?dbg("Accept data=~w\n", [Data]),
+ case request_type(Data) of
+ ssl ->
+ ?dbg("request type: ssl\n",[]),
+ ok = M:unrecv(S, Data),
+ ?dbg("~w:unrecv(~w, ~w)\n", [M,S,Data]),
+ %% insert ssl after transport
+ Protos1 = X#exo_socket.protocol--([probe_ssl|Protos]),
+ Protos2 = Protos1 ++ [ssl|Protos],
+ accept_upgrade(X#exo_socket{protocol=Protos2},
+ [ssl|Protos],Timeout);
+ _ -> %% not ssl
+ ?dbg("request type: NOT ssl\n",[]),
+ ok = M:unrecv(S, Data),
+ ?dbg("~w:unrecv(~w, ~w)\n", [M,S,Data]),
+ accept_upgrade(X,Protos,Timeout)
+ end;
+ {TClose, S} ->
+ ?dbg("accept_probe_ssl: closed\n", []),
+ {error, closed};
+ {TError, S, Error} ->
+ ?dbg("accept_probe_ssl: error ~w\n", [Error]),
+ Error
+ end.
+
+ssl_accept(Socket, Options, Timeout) ->
+ case ssl:ssl_accept(Socket, Options, Timeout) of
+ {error, ssl_not_started} ->
+ ssl:start(),
+ ssl:ssl_accept(Socket, Options, Timeout);
+ Result ->
+ Result
+ end.
+
+
+request_type(<<"GET", _/binary>>) -> http;
+request_type(<<"POST", _/binary>>) -> http;
+request_type(<<"OPTIONS", _/binary>>) -> http;
+request_type(<<"TRACE", _/binary>>) -> http;
+request_type(<<1:1,_Len:15,1:8,_Version:16, _/binary>>) ->
+ ssl;
+request_type(<<ContentType:8, _Version:16, _Length:16, _/binary>>) ->
+ if ContentType == 22 -> %% HANDSHAKE
+ ssl;
+ true ->
+ undefined
+ end;
+request_type(_) ->
+ undefined.
+
+%%
+%% exo_socket wrapper for socket operations
+%%
+close(#exo_socket { mdata = M, socket = S}) ->
+ M:close(S).
+
+shutdown(#exo_socket { mdata = M, socket = S}, How) ->
+ M:shutdown(S, How).
+
+send(#exo_socket { mdata = M,socket = S, mauth = A,auth_state = Sa} = X, Data) ->
+ if A == undefined ->
+ M:send(S, Data);
+ true ->
+ try M:send(S, A:outgoing(Data, Sa))
+ catch
+ error:_ ->
+ shutdown(X, write)
+ end
+ end.
+
+recv(HSocket, Size) ->
+ recv(HSocket, Size, infinity).
+
+recv(#exo_socket { mdata = M, socket = S,
+ mauth = A, auth_state = Sa} = X, Size, Timeout) ->
+ if A == undefined ->
+ M:recv(S, Size, Timeout);
+ true ->
+ try A:incoming(M:recv(S, Size, Timeout), Sa)
+ catch
+ error:E ->
+ shutdown(X, write),
+ erlang:error(E)
+ end
+ end.
+
+setopts(#exo_socket { mctl = M, socket = S}, Opts) ->
+ M:setopts(S, Opts).
+
+getopts(#exo_socket { mctl = M, socket = S}, Opts) ->
+ M:getopts(S, Opts).
+
+controlling_process(#exo_socket { mdata = M, socket = S}, NewOwner) ->
+ M:controlling_process(S, NewOwner).
+
+sockname(#exo_socket { mctl = M, socket = S}) ->
+ M:sockname(S).
+
+peername(#exo_socket { mctl = M, socket = S}) ->
+ M:peername(S).
+
+stats() ->
+ inet:stats().
+
+getstat(#exo_socket { transport = Socket}, Stats) ->
+ inet:getstat(Socket, Stats).
+
+pair() ->
+ pair(inet).
+pair(Family) -> %% inet|inet6
+ {ok,L} = gen_tcp:listen(0, [{active,false}]),
+ {ok,{IP,Port}} = inet:sockname(L),
+ {ok,S1} = gen_tcp:connect(IP, Port, [Family,{active,false}]),
+ {ok,S2} = gen_tcp:accept(L),
+ gen_tcp:close(L),
+ X1 = #exo_socket{socket=S1,
+ mdata = gen_tcp,
+ mctl = inet,
+ protocol=[tcp],
+ opts=[],
+ tags={tcp,tcp_closed,tcp_error}},
+ X2 = #exo_socket{socket=S2,
+ mdata = gen_tcp,
+ mctl = inet,
+ protocol=[tcp],
+ opts=[],
+ tags={tcp,tcp_closed,tcp_error}},
+ {ok,{X1,X2}}.
+
+tags(#exo_socket { tags=Tags}) ->
+ Tags.
+
+socket(#exo_socket { socket=Socket }) ->
+ Socket.
+
+%% Utils
+tcp_listen_options() ->
+ [ifaddr, ip, port, fd, inet, inet6,
+ tos, priority, reuseaddr, keepalive, linger, sndbuf, recbuf, nodelay,
+ header, active, packet, buffer, mode, deliver, backlog,
+ exit_on_close, high_watermark, low_watermark, send_timeout,
+ send_timeout_close, delay_send, packet_size, raw].
+
+tcp_connect_options() ->
+ [ifaddr, ip, port, fd, inet, inet6,
+ tos, priority, reuseaddr, keepalive, linger, sndbuf, recbuf, nodelay,
+ header, active, packet, packet_size, buffer, mode, deliver,
+ exit_on_close, high_watermark, low_watermark, send_timeout,
+ send_timeout_close, delay_send,raw].
+
+
+ssl_listen_opts() ->
+ [versions, verify, verify_fun,
+ fail_if_no_peer_cert, verify_client_once,
+ depth, cert, certfile, key, keyfile,
+ password, cacerts, cacertfile, dh, dhfile, cihpers,
+ %% deprecated soon
+ ssl_imp, %% always new!
+ %% server
+ verify_client_once,
+ reuse_session, reuse_sessions,
+ secure_renegotiate, renegotiate_at,
+ debug, hibernate_after, erl_dist ].
+
+ssl_connect_opts() ->
+ [versions, verify, verify_fun,
+ fail_if_no_peer_cert,
+ depth, cert, certfile, key, keyfile,
+ password, cacerts, cacertfile, dh, dhfile, cihpers,
+ debug].
+
+
+split_options(Keys, Opts) ->
+ split_options(Keys, Opts, [], []).
+
+split_options(Keys, [{Key,Value}|KVs], List1, List2) ->
+ case lists:member(Key, Keys) of
+ true -> split_options(Keys, KVs, [{Key,Value}|List1], List2);
+ false -> split_options(Keys, KVs, List1, [{Key,Value}|List2])
+ end;
+split_options(Keys, [Key|KVs], List1, List2) ->
+ case lists:member(Key, Keys) of
+ true -> split_options(Keys, KVs, [Key|List1], List2);
+ false -> split_options(Keys, KVs, List1, [Key|List2])
+ end;
+split_options(_Keys, [], List1, List2) ->
+ {lists:reverse(List1), lists:reverse(List2)}.
diff --git a/deps/exo/src/exo_socket.hrl b/deps/exo/src/exo_socket.hrl
new file mode 100644
index 0000000..74b083c
--- /dev/null
+++ b/deps/exo/src/exo_socket.hrl
@@ -0,0 +1,37 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved.
+%%%
+%%% This Source Code Form is subject to the terms of the Mozilla Public
+%%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2011, Tony Rogvall
+%%% @doc
+%%% EXO socket definition
+%%% @end
+%%% Created : 15 Dec 2011 by Tony Rogvall <tony@rogvall.se>
+
+-ifndef(_EXO_SOCKET_HRL_).
+-define(_EXO_SOCKET_HRL_, true).
+
+-record(exo_socket,
+ {
+ mdata, %% data module (e.g gen_tcp, ssl ...)
+ mctl, %% control module (e.g inet, ssl ...)
+ protocol=[], %% [tcp|ssl|http]
+ version, %% Http version in use (1.0/keep-alive or 1.1)
+ transport, %% ::port() - transport socket
+ socket, %% ::port() || Pid/Port/SSL/ etc
+ active=false, %% ::boolean() is active
+ mode=list, %% :: list|binary
+ packet=0, %% packet mode
+ opts = [], %% extra options
+ tags = {data,close,error}, %% data tags used
+ mauth, %% user-provided auth module - if any
+ auth_state %% state for user-provided auth module.
+ }).
+
+-endif.
diff --git a/deps/exo/src/exo_socket_cache.erl b/deps/exo/src/exo_socket_cache.erl
new file mode 100644
index 0000000..c953068
--- /dev/null
+++ b/deps/exo/src/exo_socket_cache.erl
@@ -0,0 +1,282 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved.
+%%%
+%%% This Source Code Form is subject to the terms of the Mozilla Public
+%%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @doc
+%%% Cache open sockets that can be reused (like http/https connections)
+%%% @end
+%%% Created : 16 Dec 2011 by Tony Rogvall <tony@rogvall.se>
+
+-module(exo_socket_cache).
+
+-behaviour(gen_server).
+
+%%--------------------------------------------------------------------
+%% Include files
+%%--------------------------------------------------------------------
+
+-include("exo_socket.hrl").
+
+-define(DEFAULT_CACHE_SIZE, 256).
+
+-compile(export_all).
+
+-ifdef(debug).
+-define(dbg(F, A), io:format((F), (A))).
+-else.
+-define(dbg(F, A), ok).
+-endif.
+
+%%--------------------------------------------------------------------
+%% External exports
+-export([start/0, start_link/0, stop/0]).
+-export([open/4, open/5, close/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-record(state,
+ {
+ cache_size = ?DEFAULT_CACHE_SIZE,
+ ref_tab, %% bag: {Proto,Vsn,Addr,Port} => Socket
+ sock_tab %% set: Socket => {Proto,Vsn,Addr,Port},#exo_socket
+ }).
+
+-define(SERVER, exo_socket_cache).
+
+%%====================================================================
+%% External functions
+%%====================================================================
+
+start_link() ->
+ gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
+
+start() ->
+ gen_server:start({local, ?SERVER}, ?MODULE, [], []).
+
+stop() ->
+ gen_server:call(?SERVER, stop).
+
+open(Proto, Vsn, Host, Port) ->
+ open(Proto, Vsn, Host, Port, infinity).
+
+
+open(Proto, Vsn, Host, Port, Timeout) ->
+ start(), %% fixme: add is supervisor
+ case inet:getaddrs(Host, inet, Timeout) of
+ {ok,IPs} ->
+ case gen_server:call(?SERVER,{alloc,Proto,Vsn,IPs,Port,self()}) of
+ {ok, HS} ->
+ {ok,HS};
+ {error,_} ->
+ connect(IPs,Port,Proto,Vsn,[{active,false}],
+ Timeout, undefined)
+ end;
+ Error ->
+ Error
+ end.
+
+close(HS) ->
+ exo_socket:setopts(HS, [{active,false}]),
+ case sync_socket(HS) of
+ true ->
+ exo_socket:controlling_process(HS,whereis(?SERVER)),
+ case gen_server:call(?SERVER, {release, HS}) of
+ ok ->
+ ?dbg("close: socket save ~p\n", [HS]),
+ ok;
+ Error ->
+ ?dbg("close: socket ignored ~p: ~p\n", [HS,Error]),
+ exo_socket:close(HS),
+ Error
+ end;
+ false ->
+ Error = {error,sync_error},
+ ?dbg("close: socket ignored ~p: ~p\n", [HS,Error]),
+ exo_socket:close(HS),
+ Error
+ end.
+
+connect([IP|IPs],Port,Proto,Vsn,Opts,Timeout,_Err) ->
+ case exo_socket:connect(IP, Port, Proto, Opts, Timeout) of
+ {ok, S} ->
+ {ok, S#exo_socket { version = Vsn }};
+ {error,Reason} ->
+ connect(IPs,Port,Proto,Vsn,Opts,Timeout,Reason)
+ end;
+connect([],_Port,_Proto,_Vsn,_Opts,_Timeout,Reason) ->
+ {error, Reason}.
+
+%%====================================================================
+%% Server functions
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init/1
+%% Description: Initiates the server
+%% Returns: {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%%--------------------------------------------------------------------
+init([]) ->
+ RefTab = ets:new(exo_socket_cache_ref, [bag]),
+ SockTab = ets:new(exo_socket_cache_sock, [set]),
+ {ok, #state{ ref_tab = RefTab, sock_tab = SockTab }}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_call/3
+%% Description: Handling call messages
+%% Returns: {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} | (terminate/2 is called)
+%% {stop, Reason, State} (terminate/2 is called)
+%%--------------------------------------------------------------------
+
+handle_call({alloc,Proto,Vsn,IPs,Port,Pid}, _From, State) ->
+ case alloc(Proto,Vsn,IPs,Port,State) of
+ {ok,HS} ->
+ exo_socket:controlling_process(HS, Pid),
+ {reply, {ok,HS}, State};
+ Error ->
+ {reply, Error, State}
+ end;
+
+handle_call({release,HS}, _From, State) ->
+ %% If any thing comes from the socket it will be closed, while
+ %% in the session cache.
+ exo_socket:setopts(HS, [{active,once},{packet,0}]),
+ case exo_socket:peername(HS) of
+ {ok, {Address, Port}} ->
+ #exo_socket{socket=S,version=Vsn,protocol=Proto} = HS,
+ Key = {Proto,Vsn,Address,Port},
+ ets:insert(State#state.sock_tab, {S,Key,HS}),
+ ets:insert(State#state.ref_tab,{Key,S}),
+ {reply, ok, State};
+ Error ->
+ {reply, Error, State}
+ end;
+handle_call(stop, _From, State) ->
+ {stop, normal, ok, State};
+handle_call(_Request, _From, State) ->
+ {reply, {error, bad_call}, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_cast/2
+%% Description: Handling cast messages
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%%--------------------------------------------------------------------
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_info/2
+%% Description: Handling all non call/cast messages
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%%--------------------------------------------------------------------
+
+%% Any kind of activity from a socket in the cache will
+%% lead to it's death.
+handle_info({tcp,S,_}, State) ->
+ delete_socket(S, State),
+ {noreply,State};
+handle_info({tcp_closed,S}, State) ->
+ delete_socket(S, State),
+ {noreply,State};
+handle_info({tcp_error,S,_},State) ->
+ delete_socket(S, State),
+ {noreply,State};
+handle_info({ssl,S,_},State) ->
+ delete_socket(S, State),
+ {noreply,State};
+handle_info({ssl_closed,S},State) ->
+ delete_socket(S, State),
+ {noreply,State};
+handle_info({ssl_error,S,_},State) ->
+ delete_socket(S, State),
+ {noreply,State};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: terminate/2
+%% Description: Shutdown the server
+%% Returns: any (ignored by gen_server)
+%%--------------------------------------------------------------------
+terminate(_Reason, _State) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Func: code_change/3
+%% Purpose: Convert process state when code is changed
+%% Returns: {ok, NewState}
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+%% Locate a persistent socket connection to server IP:Port
+%% using protocol Proto and version Vsn
+alloc(Proto, Vsn,[IP|IPs], Port, State) ->
+ Refs = ets:lookup(State#state.ref_tab,{Proto,Vsn,IP,Port}),
+ alloc_ref(Refs,Proto,Vsn,IPs,Port,State);
+alloc(_Proto, _Vsn, [], _Port, _State) ->
+ {error, not_found}.
+
+alloc_ref([{Key,S}|Refs],Proto,Vsn,IPs,Port,State) ->
+ [{_,_,HS}] = ets:lookup(State#state.sock_tab, S),
+ ets:delete_object(State#state.ref_tab, {Key,S}),
+ ets:delete(State#state.sock_tab, S),
+ exo_socket:setopts(HS, [{active, false}]),
+ case sync_socket(HS) of
+ false ->
+ ?dbg("alloc: socket unsynced ~p\n", [HS]),
+ alloc_ref(Refs,Proto,Vsn,IPs,Port,State);
+ true ->
+ ?dbg("alloc: got socket socket ~p\n", [HS]),
+ {ok, HS}
+ end;
+alloc_ref([],Proto,Vsn,IPs,Port,State) ->
+ alloc(Proto,Vsn,IPs,Port,State).
+
+%%
+%% Check that no events from the socket is received
+%% (after passive is set to true)
+%%
+sync_socket(#exo_socket { socket=S, tags={Tag,Tag_closed,Tag_error}}) ->
+ receive
+ {Tag,S,_} -> false;
+ {Tag_closed,S} -> false;
+ {Tag_error,S,_} -> false
+ %%
+ after 0 ->
+ true
+ end.
+
+delete_socket(S,State) ->
+ case ets:lookup(State#state.sock_tab, S) of
+ [] ->
+ ok;
+ [{_,Key,HS}] ->
+ ?dbg("socket ~p ~p delete\n", [Key,HS]),
+ exo_socket:close(HS),
+ ets:delete(State#state.sock_tab, S),
+ ets:delete_object(State#state.ref_tab,{Key,S}),
+ ok
+ end.
diff --git a/deps/exo/src/exo_socket_server.erl b/deps/exo/src/exo_socket_server.erl
new file mode 100644
index 0000000..08a3e4c
--- /dev/null
+++ b/deps/exo/src/exo_socket_server.erl
@@ -0,0 +1,439 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved.
+%%%
+%%% This Source Code Form is subject to the terms of the Mozilla Public
+%%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%%-------------------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @doc
+%%% General socket server
+%%% @end
+%%% Created : 22 Aug 2011 by Tony Rogvall <tony@rogvall.se>
+%%%-------------------------------------------------------------------
+-module(exo_socket_server).
+
+-behaviour(gen_server).
+
+%%
+%% methods
+%% init(Socket, Args) ->
+%% {ok, State'}
+%% {stop, Reason, State'}
+%%
+%% data(Socket, Data, State) ->
+%% {ok, State'}
+%% {stop, Reason, State'};
+%%
+%% close(Socket, State) ->
+%% {ok, State'}
+%%
+%% error(Socket, Error, State) ->
+%% {ok, State'}
+%% {stop, Reason, State'}
+%%
+
+%% API
+-export([start_link/5, start_link/6]).
+-export([start/5, start/6]).
+-export([reusable_sessions/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-export([behaviour_info/1]).
+
+-include("exo_socket.hrl").
+-include("log.hrl").
+
+%% -define(debug(Fmt,Args), ok).
+%% -define(error(Fmt,Args), error_logger:format(Fmt, Args)).
+
+-define(SERVER, ?MODULE).
+
+-record(state, {
+ listen, %% #exo_socket{}
+ active, %% default active mode for socket
+ socket_reuse = none, %% 'none' | #reuse{}
+ ref, %% prim_inet internal accept ref number
+ module, %% session module
+ args %% session init args
+ }).
+
+-record(reuse, {
+ mode,
+ port,
+ sessions = dict:new(),
+ session_pids = dict:new(),
+ state
+ }).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%--------------------------------------------------------------------
+%% @doc
+%% The plugin behaviour:<br>
+%% init(Socket::socket(), Args::[term()] <br>
+%% -> {ok,NewState::state()} | <br>
+%% {stop,Reason::term(),NewState::state()}<br>
+%% data(Socket::socket(), Data::io_list(), State::state()) <br>
+%% -> {ok,NewState::state()}|<br>
+%% {close,NewState::state()}|<br>
+%% {stop,Reason::term(),NewState::state()}<br>
+%% close(Socket::socket(), State::state())<br>
+%% -> {ok,state()}<br>
+%% error(Socket::socket(),Error::error(), State::state())<br>
+%% -> {ok,NewState::state()} | <br>
+%% {stop,Reason::term(),NewState::state()}<br>
+%% control(Socket::socket(), Request::term(), From::term(), State::state())<br>
+%% -> {reply, Reply::term(),NewState::state()} | <br>
+%% {noreply, NewState::state()} |<br>
+%% {ignore, NewState::state()} | <br>
+%% {send, Bin::binary(), NewState::state()} |<br>
+%% {data, Data::term(), NewState::state()} |<br>
+%% {stop, Reason::term(),NewState::state()}<br>
+%% @end
+%%--------------------------------------------------------------------
+-spec behaviour_info(callbacks) -> list().
+behaviour_info(callbacks) ->
+ [
+ {init, 2}, %% init(Socket::socket(), Args::[term()]
+ %% -> {ok,state()} | {stop,reason(),state()}
+ {data, 3}, %% data(Socket::socket(), Data::io_list(), State::state())
+ %% -> {ok,state()}|{close,state()}|{stop,reason(),state()}
+ {close, 2}, %% close(Socket::socket(), State::state())
+ %% -> {ok,state()}
+ {error, 3}, %% error(Socket::socket(),Error::error(), State:state())
+ %% -> {ok,state()} | {stop,reason(),state()}
+ {control, 4} %% control(Socket::socket(), Request::term(),
+ %% From::term(), State:state())
+ %% -> {reply, Reply::term(),state()} | {noreply, state()} |
+ %% {ignore, state()} | {send, Bin::binary(), state()} |
+ %% {data, Data::trem()} |{stop,reason(),state()}
+ ];
+behaviour_info(_Other) ->
+ undefined.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server
+%% @end
+%%--------------------------------------------------------------------
+
+start_link(Port, Protos, Options, Module, Args) ->
+ gen_server:start_link(?MODULE, [Port,Protos,Options,Module,Args], []).
+
+start_link(ServerName, Protos, Port, Options, Module, Args) ->
+ gen_server:start_link(ServerName, ?MODULE, [Port,Protos,Options,Module,Args], []).
+
+start(Port, Protos, Options, Module, Args) ->
+ gen_server:start(?MODULE, [Port,Protos,Options,Module,Args], []).
+
+start(ServerName, Protos, Port, Options, Module, Args) ->
+ gen_server:start(ServerName, ?MODULE, [Port,Protos,Options,Module,Args], []).
+
+-spec reusable_sessions(Process::pid() | atom()) ->
+ list({{IpAddress::tuple(), Port::integer}, Pid::pid}).
+
+reusable_sessions(P) ->
+ gen_server:call(P, reusable_sessions).
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Initializes the server
+%%
+%% @spec init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% @end
+%%--------------------------------------------------------------------
+init([Port,Protos,Options,Module,Args] = _X) ->
+ ?debug("~p: init(~p)~n", [?MODULE, _X]),
+ Active = proplists:get_value(active, Options, true),
+ ReuseMode = proplists:get_value(reuse_mode, Options, none),
+ Options1 = proplists:delete(reuse_mode, proplists:delete(active, Options)),
+ Reuse = case ReuseMode of
+ none -> none;
+ _ when ReuseMode==client; ReuseMode==server ->
+ {ok, RUSt} = Module:reuse_init(ReuseMode, Args),
+ #reuse{mode = ReuseMode,
+ port = Port,
+ state = RUSt}
+ end,
+ case exo_socket:listen(Port,Protos,Options1) of
+ {ok,Listen} ->
+ case exo_socket:async_accept(Listen) of
+ {ok, Ref} ->
+ {ok, #state{ listen = Listen,
+ active = Active,
+ socket_reuse = Reuse,
+ ref=Ref,
+ module=Module,
+ args=Args
+ }};
+ {error, Reason} ->
+ {stop,Reason}
+ end;
+ {error,Reason} ->
+ {stop,Reason}
+ end.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling call messages. <br>
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec handle_call(Request::term(),
+ From::{pid(), Tag::term()},
+ State::#state{}) ->
+ {reply, Reply::term(), State::#state{}} |
+ {noreply, State::#state{}} |
+ {stop, Reason::atom(), Reply::term(), State::#state{}}.
+
+handle_call({get_session, Host, Port, Opts}, From,
+ #state{socket_reuse = Reuse} = State) ->
+ Key = {Host, Port},
+ case Reuse of
+ none ->
+ {reply, connect, State};
+ #reuse{mode = client, sessions = Sessions,
+ session_pids = Pids} = R ->
+ case dict:find(Key, Sessions) of
+ error ->
+ ConnPid = start_connector(Host, Port, Opts, self(), State),
+ Sessions1 = dict:store(Key, {ConnPid, [From]}, Sessions),
+ Pids1 = dict:store(ConnPid, Key, Pids),
+ R1 = R#reuse{sessions = Sessions1,
+ session_pids = Pids1},
+ {noreply, State#state{socket_reuse = R1}};
+ {ok, Pid} when is_pid(Pid) ->
+ {reply, Pid, State};
+ {ok, {CPid, Pending}} ->
+ Sessions1 = dict:store(
+ Key, {CPid, [From|Pending]}, Sessions),
+ R1 = R#reuse{sessions = Sessions1},
+ {noreply, State#state{socket_reuse = R1}}
+ end;
+ #reuse{mode = server, sessions = Sessions} ->
+ case dict:find(Key, Sessions) of
+ error ->
+ %% server never initiates connection when in reuse mode
+ {reply, rejected, State};
+ {ok, Pid} when is_pid(Pid) ->
+ {reply, Pid, State}
+ end
+ end;
+handle_call(reusable_sessions, _From, #state{socket_reuse = R} = State) ->
+ case R of
+ #reuse{sessions = Sessions} ->
+ {reply, dict:to_list(Sessions), State};
+ _ ->
+ {reply, [], State}
+ end;
+handle_call(Request, _From, State) ->
+ ?debug("~p: handle_call(~p) not implemented!!", [?MODULE, Request]),
+ Reply = ok,
+ {reply, Reply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling cast messages
+%%
+%% @spec handle_cast(Msg, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling all non call/cast messages
+%%
+%% @spec handle_info(Info, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_info({inet_async, LSocket, Ref, {ok,Socket}} = _Msg, State) when
+ (State#state.listen)#exo_socket.socket =:= LSocket,
+ Ref =:= State#state.ref ->
+ ?debug("<-- ~p~n", [_Msg]),
+ Listen = State#state.listen,
+ NewAccept = exo_socket:async_accept(Listen),
+ case exo_socket:async_socket(Listen, Socket, [delay_auth]) of
+ {ok, XSocket} ->
+ case exo_socket_session:start(XSocket,
+ State#state.module,
+ State#state.args) of
+ {ok,Pid} ->
+ exo_socket:controlling_process(XSocket, Pid),
+ gen_server:cast(Pid, {activate,State#state.active});
+ _Error ->
+ exo_socket:close(XSocket)
+ end;
+ _Error ->
+ error
+ end,
+ case NewAccept of
+ {ok,Ref1} ->
+ {noreply, State#state { ref = Ref1 }};
+ {error, Reason} ->
+ {stop, Reason, State}
+ end;
+%% handle {ok,Socket} on bad ref ?
+handle_info({inet_async, _LSocket, Ref, {error,Reason}}, State) when
+ Ref =:= State#state.ref ->
+ case exo_socket:async_accept(State#state.listen) of
+ {ok,Ref} ->
+ {noreply, State#state { ref = Ref }};
+ {error, Reason} ->
+ {stop, Reason, State}
+ %% {noreply, State#state { ref = undefined }}
+ end;
+handle_info({Pid, ?MODULE, connected, Host, Port},
+ #state{socket_reuse = #reuse{sessions = Sessions} = R} = State) ->
+ Session = dict:fetch(Key = {Host, Port}, Sessions),
+ case Session of
+ {_, Pending} ->
+ [gen_server:reply(From, Pid) || From <- Pending];
+ _ -> ok
+ end,
+ Sessions1 = dict:store(Key, Pid, Sessions),
+ %% Pids = dict:store(Pid, {Host,Port}, R#reuse.session_pids),
+ R1 = R#reuse{sessions = Sessions1},
+ {noreply, State#state{socket_reuse = R1}};
+handle_info({Pid, reuse, Config},
+ #state{socket_reuse = #reuse{mode = server,
+ sessions = Sessions,
+ session_pids = Pids} = R} = State) ->
+ {_, Port} = lists:keyfind(port, 1, Config),
+ case [H || {host, H} <- Config] of
+ [Host|_] ->
+ %% we could possibly handle aliases, and thus multiple host names
+ Key = {Host, Port},
+ Sessions1 = dict:store(Key, Pid, Sessions),
+ Pids1 = dict:store(Pid, Key, Pids),
+ R1 = R#reuse{sessions = Sessions1, session_pids = Pids1},
+ {noreply, State#state{socket_reuse = R1}};
+ _Other ->
+ ?error("strange reuse config: ~p~n", [_Other]),
+ {noreply, State}
+ end;
+handle_info({'DOWN', _, process, Pid, _},
+ #state{socket_reuse = #reuse{sessions = Sessions,
+ session_pids = Pids} = R} = State) ->
+ ?debug("~p got DOWN - Pid = ~p~n"
+ "Sessions = ~p~n"
+ "Pids = ~p~n", [?MODULE, Pid, dict:to_list(Sessions),
+ dict:to_list(Pids)]),
+ case dict:find(Pid, Pids) of
+ error ->
+ {noreply, State};
+ {ok, {_Host,_Port} = Key} ->
+ Session = dict:fetch(Key, Sessions),
+ case Session of
+ {_, Pending} ->
+ [gen_server:reply(From, rejected) || From <- Pending];
+ _ -> ok
+ end,
+ Sessions1 = dict:erase(Key, Sessions),
+ Pids1 = dict:erase(Pid, Pids),
+ R1 = R#reuse{sessions = Sessions1, session_pids = Pids1},
+ {noreply, State#state{socket_reuse = R1}}
+ end;
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any
+%% necessary cleaning up. When it returns, the gen_server terminates
+%% with Reason. The return value is ignored.
+%%
+%% @spec terminate(Reason, State) -> void()
+%% @end
+%%--------------------------------------------------------------------
+terminate(_Reason, State) ->
+ exo_socket:close(State#state.listen),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Convert process state when code is changed
+%%
+%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% @end
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+
+
+start_connector(Host, Port, ConnArgs, Parent,
+ #state{module = M, args = Args, active = Active,
+ socket_reuse = #reuse{port = MyPort,
+ state = RUSt}}) ->
+ F = fun() ->
+ case open_reuse_connector(Host, Port, ConnArgs) of
+ {ok, XSocket} ->
+ send_reuse_message(
+ Host, Port, Args, M, MyPort, XSocket, RUSt),
+ case exo_socket_session:init(
+ [XSocket, M, Args]) of
+ {ok, XSt} ->
+ {noreply, XSt1} =
+ exo_socket_session:handle_cast(
+ {activate, Active}, XSt),
+ Parent ! {self(), ?MODULE, connected,
+ Host, Port},
+ gen_server:enter_loop(
+ exo_socket_session, [], XSt1);
+ {error, InitError} ->
+ exit({InitError, [{exo_socket_session,init}]})
+ end;
+ {error, ConnectError} ->
+ exit({ConnectError, [{exo_socket, connect}]})
+ end
+ end,
+ Pid = proc_lib:spawn(F),
+ erlang:monitor(process, Pid),
+ Pid.
+
+open_reuse_connector(Host, Port, ConnArgs) ->
+ case ConnArgs of
+ [Protos, Opts, Timeout] ->
+ exo_socket:connect(
+ Host, Port, Protos, Opts, Timeout);
+ [Protos, Opts] ->
+ exo_socket:connect(Host, Port, Protos, Opts)
+ end.
+
+send_reuse_message(Host, Port, Args, M, MyPort, XSocket, RUSt) ->
+ ReuseOpts = M:reuse_options(Host, Port, Args, RUSt),
+ ReuseMsg = exo_socket_session:encode_reuse(
+ MyPort, ReuseOpts),
+ exo_socket:send(XSocket, ReuseMsg).
+
diff --git a/deps/exo/src/exo_socket_session.erl b/deps/exo/src/exo_socket_session.erl
new file mode 100644
index 0000000..623b301
--- /dev/null
+++ b/deps/exo/src/exo_socket_session.erl
@@ -0,0 +1,432 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved.
+%%%
+%%% This Source Code Form is subject to the terms of the Mozilla Public
+%%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%%-------------------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @doc
+%%% EXO TCP session
+%%% @end
+%%% Created : 22 Aug 2011 by Tony Rogvall <tony@rogvall.se>
+%%%-------------------------------------------------------------------
+-module(exo_socket_session).
+
+-behaviour(gen_server).
+
+%% API
+-export([start/3,
+ start_link/3]).
+
+%% gen_server callbacks
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
+
+-export([encode_reuse/2,
+ decode_reuse_config/1]).
+
+-define(SERVER, ?MODULE).
+
+-record(state, {
+ module,
+ args,
+ socket,
+ active,
+ state,
+ pending = [],
+ idle_timeout
+ }).
+
+-include("exo_socket.hrl").
+
+-include("log.hrl").
+-define(dbg(F, A), ?debug("~p " ++ F, [self()|A])).
+
+-type exo_socket() :: #exo_socket {}.
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server
+%% @end
+%%--------------------------------------------------------------------
+
+-spec start_link(Socket::exo_socket(), Module::atom(), Args::[term()]) ->
+ {ok, pid()} | ignore | {error, Error::term()}.
+
+start_link(XSocket,Module,Args) ->
+ gen_server:start_link(?MODULE, [XSocket,Module,Args, []], []).
+
+-spec start(Socket::exo_socket(), Module::atom(), Args::[term()]) ->
+ {ok, pid()} | ignore | {error, Error::term()}.
+
+start(XSocket, Module, Args) ->
+ gen_server:start(?MODULE, [XSocket,Module,Args], []).
+
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Initializes the server
+%%
+%% @spec init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% @end
+%%--------------------------------------------------------------------
+init([XSocket, Module, Args]) ->
+ {ok, #state{ socket=XSocket,
+ module=Module,
+ args=Args,
+ state=undefined}}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling call messages.<br>
+%% First take care of messages specific for exo_socket_session,
+%% then call control in #state.module.
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec handle_call(Request::term(),
+ From::{pid(), Tag::term()},
+ State::#state{}) ->
+ {reply, Reply::term(), State::#state{}} |
+ {noreply, State::#state{}} |
+ {stop, Reason::atom(), Reply::term(), State::#state{}}.
+
+%% No 'local' handle_call
+handle_call(Request, From,
+ State=#state{module = M, state = MSt, socket = Socket}) ->
+ ?dbg("handle_call: ~p", [Request]),
+ try M:control(Socket, Request, From, MSt) of
+ Result ->
+ ?dbg("handle_call: reply ~p", [Result]),
+ mod_reply(Result, From, State)
+ catch
+ error:_Error ->
+ ?dbg("handle_call: catch reason ~p", [_Error]),
+ ret({reply, {error, unknown_call}, State})
+ end.
+
+mod_reply({data, Data, MSt}, _, State) ->
+ %% This case is when data has come from an external source
+ %% that needs an additonal ok reply
+ case handle_socket_data(Data, State#state{state = MSt}) of
+ {noreply, NewState} -> {reply, ok, NewState};
+ Other -> Other
+ end;
+mod_reply({ignore, MSt}, _, State) ->
+ ret({noreply, State#state{state = MSt}});
+mod_reply({ignore, MSt, Timeout}, _, State) ->
+ ret({noreply, State#state{state = MSt}, Timeout});
+mod_reply({reply, Reply, MSt}, _, State) ->
+ ret({reply, Reply, State#state{state = MSt}});
+mod_reply({reply, Reply, MSt, Timeout}, _, State) ->
+ ret({reply, Reply, State#state{state = MSt}, Timeout});
+mod_reply({send, Bin, MSt}, From, State) ->
+ State1 = send_(Bin, From, State#state{state = MSt}),
+ ret({noreply, State1});
+mod_reply({send, Bin, MSt, Timeout}, From, State) ->
+ State1 = send_(Bin, From, State#state{state = MSt}),
+ ret({noreply, State1, Timeout});
+mod_reply({stop, Reason, Reply, _MSt}, _From, State) ->
+ %% Terminating
+ ?dbg("handle_call: stopping ~p with reason ~p", [self(), Reason]),
+ {stop, Reason, Reply, State}.
+
+send_(Bin, From, #state{socket = S, pending = P} = State) ->
+ P1 = if P == [] ->
+ exo_socket:send(S, Bin),
+ ?dbg("send: bin sent to ~p", [S]),
+ [{From,Bin}|P];
+ true -> P
+ end,
+ State#state{pending = P1}.
+
+
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling cast messages
+%%
+%% @spec handle_cast(Msg, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_cast({activate,Active}, State0) ->
+ ?dbg("activate~n", []),
+ try exo_socket:authenticate(State0#state.socket) of
+ {ok, S} ->
+ ?dbg("authentication done~n", []),
+ State = State0#state{socket = S},
+ case apply(State#state.module, init,
+ [State#state.socket,State#state.args]) of
+ Ok when element(1, Ok) == ok ->
+ CSt0 = element(2, Ok),
+ %% enable active mode here (if ever wanted) once is handled,
+ %% automatically anyway. exit_on_close is default and
+ %% allow session statistics retrieval in the close callback
+ SessionOpts = [{active,Active},{exit_on_close, false}],
+
+ _Res = exo_socket:setopts(State#state.socket, SessionOpts),
+ ?dbg("exo_socket:setopts(~w) = ~w\n", [SessionOpts, _Res]),
+ State1 = State#state { active = Active, state = CSt0 },
+ case Ok of
+ {_, _, Timeout} ->
+ ret({noreply, State1, Timeout});
+ {_, _} ->
+ ret({noreply, State1})
+ end;
+ {stop,Reason,CSt1} ->
+ {stop, Reason, State#state { state = CSt1 }}
+ end;
+ {error, Reason} ->
+ {stop, {auth_failure, Reason}, State0}
+ catch
+ error:Crash ->
+ {stop, {auth_failure, Crash}, State0}
+ end;
+
+handle_cast(_Msg, State) ->
+ ret({noreply, State}).
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling all non call/cast messages
+%%
+%% @spec handle_info(Info, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_info(timeout, State) ->
+ exo_socket:shutdown(State#state.socket, write),
+ ?dbg("exo_socket_session: idle_timeout~p~n", [self()]),
+ {stop, normal, State};
+handle_info({Tag,Socket,Data0}, State) when
+ %% FIXME: put socket tag in State for correct matching
+ (Tag =:= tcp orelse Tag =:= ssl orelse Tag =:= http),
+ Socket =:= (State#state.socket)#exo_socket.socket ->
+ ?dbg("exo_socket_session: got data ~p\n", [{Tag,Socket,Data0}]),
+ try exo_socket:auth_incoming(State#state.socket, Data0) of
+ <<"reuse%", Rest/binary>> ->
+ handle_reuse_data(Rest, State);
+ Data ->
+ handle_socket_data(Data, State)
+ catch
+ error:_ ->
+ exo_socket:shutdown(State#state.socket, write),
+ ret({noreply, State})
+ end;
+handle_info({Tag,Socket}, State) when
+ (Tag =:= tcp_closed orelse Tag =:= ssl_closed),
+ Socket =:= (State#state.socket)#exo_socket.socket ->
+ ?dbg("exo_socket_session: got tag ~p\n", [{Tag,Socket}]),
+ CSt0 = State#state.state,
+ case apply(State#state.module, close, [State#state.socket,CSt0]) of
+ {ok,CSt1} ->
+ {stop, normal, State#state { state = CSt1 }}
+ end;
+handle_info({Tag,Socket,Error}, State) when
+ (Tag =:= tcp_error orelse Tag =:= ssl_error),
+ Socket =:= (State#state.socket)#exo_socket.socket ->
+ ?dbg("exo_socket_session: got error ~p\n", [{Tag,Socket,Error}]),
+ CSt0 = State#state.state,
+ case apply(State#state.module, error, [State#state.socket,Error,CSt0]) of
+ {ok,CSt1} ->
+ ret({noreply, State#state { state = CSt1 }});
+ {stop,Reason,CSt1} ->
+ {stop, Reason, State#state { state = CSt1 }}
+ end;
+
+handle_info(_Info, State) ->
+ ?dbg("Got info: ~p\n", [_Info]),
+ ret({noreply, State}).
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any
+%% necessary cleaning up. When it returns, the gen_server terminates
+%% with Reason. The return value is ignored.
+%%
+%% @spec terminate(Reason, State) -> void()
+%% @end
+%%--------------------------------------------------------------------
+terminate(_Reason, State) ->
+ exo_socket:close(State#state.socket),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Convert process state when code is changed
+%%
+%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% @end
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+
+ret({noreply, #state{idle_timeout = T} = S}) ->
+ if T==undefined -> {noreply, S};
+ true -> {noreply, S, T}
+ end;
+ret({reply, R, #state{idle_timeout = T} = S}) ->
+ if T==undefined -> {reply, R, S};
+ true -> {reply, R, S, T}
+ end;
+ret({noreply, S, T}) ->
+ S1 = S#state{idle_timeout = T},
+ if T==undefined -> {noreply, S1};
+ true -> {noreply, S1, T}
+ end;
+ret({reply, R, S, T}) ->
+ S1 = S#state{idle_timeout = T},
+ if T==undefined -> {reply, R, S1};
+ true -> {reply, R, S1, T}
+ end;
+ret(R) ->
+ R.
+
+
+%% continued from handle_info/2
+handle_reuse_data(Rest, #state{module = M, state = MSt} = State) ->
+ Config = decode_reuse_config(Rest),
+ ?dbg("Decoded reuse config: ~p~n"
+ "State = ~p~n", [Config, State]),
+ {ok, MSt1} = M:received_reuse_info(Config, MSt),
+ State1 = State#state{state = MSt1},
+ {ok, {Host,_}} = exo_socket:peername(State1#state.socket),
+ get_parent(State1) ! {self(), reuse, [{host, Host}|Config]},
+ if State1#state.active == once ->
+ exo_socket:setopts(State#state.socket, [{active,once}]);
+ true ->
+ ok
+ end,
+ ret({noreply, State1}).
+
+handle_socket_data(Data, State) ->
+ CSt0 = State#state.state,
+ ModResult = apply(State#state.module, data,
+ [State#state.socket,Data,CSt0]),
+ ?dbg("handle_socket_data: result ~p", [ModResult]),
+ handle_module_result(ModResult, State).
+
+handle_module_result({ok,CSt1}, State) ->
+ if State#state.active == once ->
+ exo_socket:setopts(State#state.socket, [{active,once}]);
+ true ->
+ ok
+ end,
+ ret({noreply, State#state { state = CSt1 }});
+handle_module_result({close, CSt1}, State) ->
+ exo_socket:shutdown(State#state.socket, write),
+ ret({noreply, State#state { state = CSt1 }});
+handle_module_result({stop,Reason,CSt1}, State) ->
+ {stop, Reason, State#state { state = CSt1 }};
+handle_module_result({reply, Rep, CSt1}, State) ->
+ if State#state.active == once ->
+ exo_socket:setopts(State#state.socket, [{active,once}]);
+ true ->
+ ok
+ end,
+ case State#state.pending of
+ [{From,_}|Rest] ->
+ gen_server:reply(From, Rep),
+ send_next(Rest, State#state.socket),
+ ret({noreply, State#state { pending = Rest, state = CSt1 }});
+ [] ->
+ %% huh?
+ ret({noreply, State#state { state = CSt1 }})
+ end.
+
+send_next([{_From, Msg}|_], Socket) ->
+ exo_socket:send(Socket, Msg);
+send_next([], _) ->
+ ok.
+
+get_parent(_) ->
+ hd(get('$ancestors')).
+
+%% The reuse service message must at a minimum include Port.
+%% Other options can be passed, as [{Module, Key, Value}]. These are
+%% encoded at Module:encode({Key, Value}) -> Bin, and decoded on the other
+%% end as Module:decode(Bin) -> {Key, Value}.
+%%
+encode_reuse(Port, Opts) when is_integer(Port), is_list(Opts) ->
+ Custom = lists:map(
+ fun({M,K,V}) ->
+ Bin = M:encode({K,V}),
+ Sz = byte_size(Bin),
+ <<"%|", (atom_to_binary(M,latin1))/binary, "|",
+ Sz:16/integer, Bin/binary>>
+ end, Opts),
+ <<"reuse%port:", (list_to_binary(integer_to_list(Port)))/binary,
+ (iolist_to_binary(Custom))/binary>>.
+
+decode_reuse_config(Bin) ->
+ decode_reuse_config(Bin, []).
+
+decode_reuse_config(<<"port:", Rest/binary>>, Acc) ->
+ {P, Rest1} = decode_get_value(Rest),
+ Port = list_to_integer(binary_to_list(P)),
+ decode_reuse_config(Rest1, [{port, Port}|Acc]);
+decode_reuse_config(<<"|", Rest/binary>>, Acc) ->
+ {M, <<Sz:16/integer, Rest1/binary>>} = decode_get_value($|, Rest),
+ <<Data:Sz/binary, Rest2/binary>> = Rest1,
+ {Key, Value} = (binary_to_atom(M,latin1)):decode(Data),
+ decode_reuse_config(parse_next(Rest2), [{Key,Value}|Acc]);
+decode_reuse_config(<<>>, Acc) ->
+ lists:reverse(Acc).
+
+parse_next(<<"%", Rest/binary>>) -> Rest;
+parse_next(<<>>) -> <<>>.
+
+decode_get_value(Bin) ->
+ decode_get_value($%, Bin).
+
+decode_get_value(Delim, Bin) ->
+ case re:split(Bin, <<$\\,Delim>>, [{return,binary},{parts,2}]) of
+ [V] ->
+ {V, <<>>};
+ [V, Rest] ->
+ {V, Rest}
+ end.
+
+%% decode_reuse_config(Bin) ->
+%% Items = re:split(Bin, "%", [{return, list}]),
+%% lists:map(
+%% fun(I) ->
+%% case re:split(I, ":", [{return, list}]) of
+%% ["host", Host] ->
+%% {host, Host};
+%% ["port", P] ->
+%% {port, list_to_integer(P)}
+%% end
+%% end, Items).
diff --git a/deps/exo/src/exo_ssh.erl b/deps/exo/src/exo_ssh.erl
new file mode 100644
index 0000000..739666c
--- /dev/null
+++ b/deps/exo/src/exo_ssh.erl
@@ -0,0 +1,181 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved.
+%%%
+%%% This Source Code Form is subject to the terms of the Mozilla Public
+%%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @doc
+%%% exo client direct tcpip socket
+%%% @end
+%%% Created : 24 May 2012 by Tony Rogvall <tony@rogvall.se>
+
+-module(exo_ssh).
+
+-behaviour(ssh_channel).
+
+%% ssh_channel callbacks
+-export([init/1, handle_msg/2, handle_ssh_msg/2, terminate/2]).
+
+%% external api
+-export([connect/3]).
+-export([send/2]).
+-export([call/4]).
+
+-record(state,
+ {
+ owner,
+ channel, %% Id of the ssh channel
+ cm %% Ssh connection manager
+ }
+ ).
+
+%% -include_lib("ssh/src/ssh_connect.hrl").
+%% fixme: ?
+-define(SSH_EXTENDED_DATA_DEFAULT, 0).
+-define(SSH_EXTENDED_DATA_STDERR, 1).
+
+
+init() ->
+ application:start(crypto),
+ application:start(ssh).
+
+%%
+%% On mac osx the {ip_v6_disabled, true} is buggy and causes
+%% problem
+connect(Host, Port, Options0) ->
+ init(),
+ Options =
+ case os:type() of
+ {unix,darwin} -> [{ip_v6_disabled, true}|Options0];
+ _ -> Options0
+ end,
+ Pid = spawn_channel_proc(Host, 22, "localhost", Port, Options),
+ receive
+ {Pid,Result} ->
+ Result
+ end.
+
+spawn_channel_proc(Host, Port, RHost, RPort, Options) ->
+ Caller = self(),
+ proc_lib:spawn_link(
+ fun() ->
+ case ssh:connect(Host, Port, Options) of
+ {ok, Cm} ->
+ io:format("ssh connected to ~s:~w\n", [Host,Port]),
+ case ssh_connection:direct_tcpip(Cm,
+ RHost, RPort,
+ {127,0,0,1}, 1234,
+ 5000) of
+ {ok, Ci} ->
+ io:format("ssh: ~w connected to port ~w\n",
+ [Ci, Port]),
+ Args = [{channel_cb, ?MODULE},
+ {init_args,[Cm, Ci, Caller]},
+ {cm, Cm}, {channel_id, Ci}],
+ case ssh_channel:init([Args]) of
+ {ok, State} ->
+ Caller ! {self(), {ok, self()}},
+ io:format("ssh: enter loop\n"),
+ ssh_channel:enter_loop(State);
+ Error ->
+ Caller ! {self(), Error}
+ end;
+ Error ->
+ Caller ! {self(), Error}
+ end;
+ Error ->
+ Caller ! {self(), Error}
+ end
+ end).
+
+send(ChannelPid, Data) ->
+ ChannelPid ! {send, Data},
+ Data.
+
+%% BERT call over SSH
+call(ChannelPid, M, F, A) ->
+ Bin = term_to_binary({call,M,F,A}),
+ Sz = byte_size(Bin),
+ send(ChannelPid, <<Sz:32, Bin/binary>>),
+ receive
+ {ChannelPid,{data,<<RSz:32, Reply:RSz/binary>>}} ->
+ binary_to_term(Reply);
+ {ChannelPid,Other} ->
+ {error, Other}
+ end.
+
+
+init([ConnectionManager, ChannelId, Owner] = _Args) ->
+ io:format("ssh: init loop\n"),
+ {ok, #state{ owner = Owner,
+ channel = ChannelId,
+ cm = ConnectionManager}}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_ssh_msg(Args) -> {ok, State} | {stop, ChannelId, State}
+%%
+%% Description: Handles channel messages received on the ssh-connection.
+%%--------------------------------------------------------------------
+handle_ssh_msg({ssh_cm, _, {data, _ChannelId, 0, Data}}, State) ->
+ State#state.owner ! {self(), {data, Data}},
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _,
+ {data, _ChannelId, ?SSH_EXTENDED_DATA_STDERR, Data}},
+ State) ->
+ io:put_chars(Data),
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _, {eof, _ChannelId}}, State) ->
+ State#state.owner ! {self(), eof},
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) ->
+ %% Ignore signals according to RFC 4254 section 6.9.
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}}, State) ->
+ io:put_chars("Connection closed by peer"),
+ io:put_chars(Error),
+ State#state.owner ! {self(), {error,Error}},
+ {stop, ChannelId, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, 0}}, State) ->
+ io:put_chars("logout"),
+ io:put_chars("Connection closed"),
+ State#state.owner ! {self(), closed},
+ {stop, ChannelId, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, Status}}, State) ->
+ io:put_chars("Connection closed by peer"),
+ io:put_chars("Status: " ++ integer_to_list(Status)),
+ State#state.owner ! {self(), {error,Status}},
+ {stop, ChannelId, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_msg(Args) -> {ok, State} | {stop, ChannelId, State}
+%%
+%% Description: Handles other channel messages
+%%--------------------------------------------------------------------
+handle_msg({ssh_channel_up, ChannelId, ConnectionManager},
+ #state{channel = ChannelId,
+ cm = ConnectionManager} = State) ->
+ {ok, State};
+
+handle_msg({send, Data}, #state{ channel = ChannelId,
+ cm = ConnectionManager} = State) ->
+ ssh_connection:send(ConnectionManager, ChannelId, Data),
+ {ok, State}.
+
+%%--------------------------------------------------------------------
+%% Function: terminate(Reasons, State) -> _
+%%
+%% Description: Cleanup when shell channel is terminated
+%%--------------------------------------------------------------------
+terminate(_Reason, #state{}) ->
+ ok.
+
diff --git a/deps/exo/src/exo_sup.erl b/deps/exo/src/exo_sup.erl
new file mode 100644
index 0000000..43cbeb3
--- /dev/null
+++ b/deps/exo/src/exo_sup.erl
@@ -0,0 +1,37 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved.
+%%%
+%%% This Source Code Form is subject to the terms of the Mozilla Public
+%%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+
+-module(exo_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+%% Helper macro for declaring children of supervisor
+-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
+
+%% ===================================================================
+%% API functions
+%% ===================================================================
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%% ===================================================================
+%% Supervisor callbacks
+%% ===================================================================
+
+init([]) ->
+ {ok, { {one_for_one, 5, 10}, []} }.
+
diff --git a/deps/exo/src/exo_url.erl b/deps/exo/src/exo_url.erl
new file mode 100644
index 0000000..e904baa
--- /dev/null
+++ b/deps/exo/src/exo_url.erl
@@ -0,0 +1,112 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved.
+%%%
+%%% This Source Code Form is subject to the terms of the Mozilla Public
+%%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @doc
+%%% URL parsing
+%%% @end
+%%% Created : 16 Dec 2011 by Tony Rogvall <tony@rogvall.se>
+
+-module(exo_url).
+
+-include("../include/exo_url.hrl").
+
+-export([parse/1, parse/2, format/1, format_path/1]).
+-export([parse_path/2]).
+
+%% returns a #url{}
+parse(Str) ->
+ parse(Str, strict).
+
+parse(Str, Strict) ->
+ case Str of
+ "http://" ++ Rest ->
+ parse_host(Strict, #url{scheme = http}, Rest, []);
+ "HTTP://" ++ Rest ->
+ parse_host(Strict, #url{scheme = http}, Rest, []);
+ "https://" ++ Rest ->
+ parse_host(Strict, #url{scheme = https}, Rest, []);
+ "HTTPS://" ++ Rest ->
+ parse_host(Strict, #url{scheme = https}, Rest, []);
+ "ftp://" ++ Rest ->
+ parse_host(Strict, #url{scheme = ftp}, Rest, []);
+ "file://" ++ Rest ->
+ parse_host(Strict, #url{scheme = file}, Rest, []);
+ _ when Strict == sloppy ->
+ parse_host(Strict, #url{scheme = http}, Str, [])
+ end.
+
+parse_host(Strict, U, Str, Ack) ->
+ case Str of
+ [] ->
+ U#url{host = lists:reverse(Ack),
+ path = "/"
+ };
+ [$/|Tail] ->
+ U2 = U#url{host = lists:reverse(Ack)},
+ parse_path(Strict, U2, Tail,"/");
+ [$:|T] ->
+ U2 = U#url{host = lists:reverse(Ack)},
+ parse_port(Strict, U2, T,[]);
+ [H|T] ->
+ parse_host(Strict, U, T, [H|Ack])
+ end.
+
+parse_port(Strict, U, Str, Ack) ->
+ case Str of
+ [] ->
+ U#url{port = list_to_integer(lists:reverse(Ack)),
+ path = "/"};
+ [$/|T] ->
+ U2 = U#url{port = list_to_integer(lists:reverse(Ack))},
+ parse_path(Strict, U2, T,"/");
+ [H|T] ->
+ parse_port(Strict, U,T,[H|Ack])
+ end.
+
+parse_path(U, Str) ->
+ parse_path(false,U,Str,[]).
+
+parse_path(Strict, U, Str, Ack) ->
+ case Str of
+ [] ->
+ U#url{path = lists:reverse(Ack)};
+ [$?|T] ->
+ U#url{path = lists:reverse(Ack), querypart = T};
+ [H|T] ->
+ parse_path(Strict, U, T, [H|Ack])
+ end.
+
+
+format(Url) when is_record(Url, url) ->
+ if Url#url.scheme == undefined ->
+ format_path(Url);
+ true ->
+ [
+ atom_to_list(Url#url.scheme), "://",
+ Url#url.host,
+ if
+ Url#url.port == undefined ->
+ [];
+ true ->
+ [$: | integer_to_list(Url#url.port)]
+ end,
+ format_path(Url)
+ ]
+ end.
+
+format_path(Url) when is_record(Url, url) ->
+ [
+ Url#url.path,
+ if
+ Url#url.querypart == [] ->
+ [];
+ true ->
+ [$?|Url#url.querypart]
+ end].
diff --git a/deps/exo/src/log.hrl b/deps/exo/src/log.hrl
new file mode 100644
index 0000000..270f06c
--- /dev/null
+++ b/deps/exo/src/log.hrl
@@ -0,0 +1,44 @@
+%%
+%% Log macros
+%%
+-ifndef(__LOG_HRL__).
+-define(__LOG_HRL__, true).
+
+-compile({parse_transform, lager_transform}).
+
+%% Lager logging levels
+%% debug, info, notice, warning, error, critical, alert, emergency, none.
+
+-define(debug(Fmt), lager:debug(Fmt)).
+-define(debug(Fmt, Args), lager:debug(Fmt, Args)).
+-define(debug(Attrs, Fmt, Args), lager:debug(Attrs, Fmt, Args)).
+
+-define(info(Fmt), lager:info(Fmt)).
+-define(info(Fmt, Args), lager:info(Fmt, Args)).
+-define(info(Attrs, Fmt, Args), lager:info(Attrs, Fmt, Args)).
+
+-define(notice(Fmt), lager:notice(Fmt)).
+-define(notice(Fmt, Args), lager:notice(Fmt, Args)).
+-define(notice(Attrs, Fmt, Args), lager:notice(Attrs, Fmt, Args)).
+
+-define(warning(Fmt), lager:warning(Fmt)).
+-define(warning(Fmt, Args), lager:warning(Fmt, Args)).
+-define(warning(Attrs, Fmt, Args), lager:warning(Attrs, Fmt, Args)).
+
+-define(error(Fmt), lager:error(Fmt)).
+-define(error(Fmt, Args), lager:error(Fmt, Args)).
+-define(error(Attrs, Fmt, Args), lager:error(Attrs, Fmt, Args)).
+
+-define(critical(Fmt), lager:critical(Fmt)).
+-define(critical(Fmt, Args), lager:critical(Fmt, Args)).
+-define(critical(Attrs, Fmt, Args), lager:critical(Attrs, Fmt, Args)).
+
+-define(alert(Fmt), lager:alert(Fmt)).
+-define(alert(Fmt, Args), lager:alert(Fmt, Args)).
+-define(alert(Attrs, Fmt, Args), lager:alert(Attrs, Fmt, Args)).
+
+-define(emergency(Fmt), lager:emergency(Fmt)).
+-define(emergency(Fmt, Args), lager:emergency(Fmt, Args)).
+-define(emergency(Attrs, Fmt, Args), lager:emergency(Attrs, Fmt, Args)).
+
+-endif.
diff --git a/deps/exo/tetrapak/config.ini b/deps/exo/tetrapak/config.ini
new file mode 100644
index 0000000..bda62a7
--- /dev/null
+++ b/deps/exo/tetrapak/config.ini
@@ -0,0 +1,8 @@
+[build]
+version = "~t.~o~~~c"
+
+[package]
+maintainer = "Tony Rogvall <tony@feuerlabs.com>"
+include_src = false
+exclude = "rebar.config|\\.gitignore|Makefile|Emakefile|debughelper.*|status.*"
+deb.section = "devel"
diff --git a/deps/gsms/LICENSE b/deps/gsms/LICENSE
new file mode 100644
index 0000000..68ed063
--- /dev/null
+++ b/deps/gsms/LICENSE
@@ -0,0 +1,24 @@
+Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Except as contained in this notice, the name(s) of the above copyright holders
+shall not be used in advertising or otherwise to promote the sale, use or other
+dealings in this Software without prior written authorization.
+
diff --git a/deps/gsms/README.md b/deps/gsms/README.md
new file mode 100644
index 0000000..9fb01ab
--- /dev/null
+++ b/deps/gsms/README.md
@@ -0,0 +1,32 @@
+
+# Start
+
+# Send SMS
+
+ gsms_router:send([{addr,"<number>"}], "Hello").
+
+# Receive SMS
+
+ {ok,Ref} = gsms_router:subscribe([]).
+ Pdu = receive {gsms,Ref,Pdu1} -> Pdu1 end.
+ Text = Pdu#gsms_deliver_pdu.ud.
+
+
+# Raspberry pi setup
+
+Setup internal serial port /dev/ttyAMA0 for use as
+modem or other serial com line then ttyAMA0 must be
+disable in /etc/inittab
+
+ # comment out in /etc/inittab
+ # T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100
+
+Also remove rerences to ttyAMA0 in /boot/cmdline.txt
+If cmdline looks like
+
+ dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait
+
+After change it should look like
+
+ dwc_otg.lpm_enable=0 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait
+
diff --git a/deps/gsms/include/gsms.hrl b/deps/gsms/include/gsms.hrl
new file mode 100644
index 0000000..72b0dea
--- /dev/null
+++ b/deps/gsms/include/gsms.hrl
@@ -0,0 +1,144 @@
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2012, Tony Rogvall
+%%% @doc
+%%% sms pdu format (ETSI 03.40)
+%%% @end
+%% ref: http://www.dreamfabric.com/sms/vp.html
+%%% Created : 15 Oct 2012 by Tony Rogvall <tony@rogvall.se>
+
+-ifndef(__GSMS_HRL__).
+-define(__GSMS_HRL__, true).
+
+-record(gsms_addr,
+ {
+ type = unknown,
+ addr = ""
+ }).
+
+-type dcs_type() :: message|message_waiting|data.
+-type dcs_compression() :: compressed | uncompressed.
+-type dcs_alphabet() :: default|ucs2|octet.
+-type dcs_class() :: alert|me|sim|te.
+-type dcs_store() :: store | discard.
+-type dcs_active() :: active | inactive.
+-type dcs_wait_type() :: voicemail | fax | email | other.
+
+-record(gsms_dcs,
+ {
+ type = message :: dcs_type(),
+ compression = uncompressed :: dcs_compression(),
+ alphabet = default :: dcs_alphabet(),
+ class = alert :: dcs_class(),
+ store = store :: dcs_store(),
+ active = inactive :: dcs_active(), %% type = message_waiting
+ wait_type = other :: dcs_wait_type() %% type = message_waiting
+ }).
+
+-define(IE_CONCAT8, 16#00).
+-define(IE_PORT8, 16#04).
+-define(IE_PORT16, 16#05).
+-define(IE_CONCAT16, 16#08).
+
+-define(MAX_7BIT_LEN, 160). %% 153 with concat8 header
+-define(MAX_8BIT_LEN, 140). %% 134 with concat8 header
+-define(MAX_16BIT_LEN, 70). %% 67 with concat8 header
+
+
+-define(VP_RELATIVE, (60*60*24)). %% 1 day
+-define(DEFAULT_PID, 0).
+
+-define(MTI_SMS_DELIVER, 2#00).
+
+-type uint() :: non_neg_integer().
+-type uint8() :: 0..255.
+-type uint16() :: 0..65535.
+
+-type gsms_ie() ::
+ {concat,Ref::uint(),N::uint8(),I::uint8()} |
+ {concat16,Ref::uint16(),N::uint8(),I::uint8()} |
+ {concat8,Ref::uint8(),N::uint8(),I::uint8()} |
+ {port8, Dst::uint8(),Src::uint8()} |
+ {port16,Dst::uint16(),Src::uint16()}
+ .
+
+-record(gsms_deliver_pdu, {
+ smsc, %% :: #gsms_addr{} smsc information
+ rp=false, %% :1 reply path exists
+ udhi=false, %% :1 user data header exists
+ sri=false, %% :1 status report indication
+ res1=0, %% 0:1
+ res2=0, %% 0:1
+ mms=false, %% :1 more messages to send
+ addr, %% :: #gsms_addr{}
+ pid=?DEFAULT_PID, %% protocol identifier
+ dcs=#gsms_dcs{}, %% data coding scheme
+ scts, %% :7/binary
+ udh=[] :: [gsms_ie()], %% user data header
+ udl, %% length in septets/octets (depend on dcs)
+ ud
+ }).
+
+-define(MTI_SMS_SUBMIT, 2#01).
+
+-record(gsms_submit_pdu, {
+ smsc, %% ::gsms_addr{} smsc information
+ rp=false, %% :1 reply path exists
+ udhi=false, %% :1 user data header exists
+ srr=false, %% :1 status report request
+ vpf=relative, %% :2 validity periad format 0..3
+ rd=true, %% :1 reject duplicates
+ mref=0, %% :8
+ addr, %% ::gsms_addr{}
+ pid=?DEFAULT_PID, %% protocol identifier
+ dcs=#gsms_dcs{}, %% data coding scheme
+ vp=?VP_RELATIVE, %% vary depend on vpf
+ udh=[] :: [gsms_ie()], %% user data header
+ udl,
+ ud
+ }).
+
+-type gsms_addr() :: #gsms_addr{} | string().
+-type gsms_pid() :: uint8().
+-type gsms_port() :: uint16().
+-type gsms_vpf() :: none|relative|enhanced|absolute.
+
+-type gsms_pdu_option() ::
+ {smsc, gsms_addr()} |
+ {rp, boolean()} |
+ {udhi, boolean()} |
+ {udh, [gsms_ie()]} |
+ {srr, boolean()} |
+ {mref, uint8()} |
+ {vpf, gsms_vpf()} |
+ {vp, term()} | %% fixme
+ {addr, gsms_addr()} |
+ {pid, gsms_pid()} |
+ {type, dcs_type()} |
+ {class, dcs_class()} |
+ {alphabet, dcs_alphabet() } |
+ {compression, dcs_compression() } |
+ {store, dcs_store()} |
+ {active, dcs_active()} |
+ {wait_type, dcs_wait_type()} |
+ {dcs, #gsms_dcs{}}
+ .
+
+-type filter() ::
+ [filter()] |
+ {type, dcs_type()} |
+ {class, dcs_class()} |
+ {alphabet, dcs_alphabet()} |
+ {pid, gsms_pid()} |
+ {src, gsms_port()} |
+ {dst, gsms_port()} |
+ {anumber, gsms_addr()} |
+ {bnumber, gsms_addr()} |
+ {smsc, gsms_addr()} |
+ {reg_exp, re:mp() | iodata()} |
+ {'not', filter()} |
+ {'and', filter(), filter()} |
+ {'or', filter(), filter()}.
+
+-endif.
+
+
diff --git a/deps/gsms/rebar.config b/deps/gsms/rebar.config
new file mode 100644
index 0000000..5f6d3b4
--- /dev/null
+++ b/deps/gsms/rebar.config
@@ -0,0 +1,9 @@
+%% -*- erlang -*-
+%% Config file for gsms application
+{deps, [ {uart, ".*", {git, "git@github.com:tonyrog/uart.git"}},
+ {lager, ".*", {git, "git://github.com/Feuerlabs/lager.git"}}]}.
+
+
+%% {erl_opts, [debug_info, fail_on_warning, {d,debug}]}.
+{erl_opts, [debug_info, fail_on_warning]}.
+{sub_dirs, ["src"]}.
diff --git a/deps/gsms/src/ERRORCODES b/deps/gsms/src/ERRORCODES
new file mode 100644
index 0000000..0f8d184
--- /dev/null
+++ b/deps/gsms/src/ERRORCODES
@@ -0,0 +1,30 @@
+
+
++CMS ERROR
+Message service error
+Description
+These are the error codes for +CMS ERROR.
+
+Error code Meaning
+0-127 GSM 04.11 Annex E-2 values
+128-255 GSM 03.40 section 9.2.3.22 values
+300 Phone failure
+301 SMS service of phone reserved
+302 Operation not allowed
+303 Operation not supported
+304 Invalid PDU mode parameter
+305 Invalid text mode parameter
+310 SIM not inserted
+311 SIM PIN necessary
+312 PH-SIM PIN necessary
+313 SIM failure
+314 SIM busy
+315 SIM wrong
+320 Memory failure
+321 Invalid memory index
+322 Memory full
+330 SMSC (message service center) address unknown
+331 No network service
+332 Network timeout
+500 Unknown error
+512 Manufacturer specific
diff --git a/deps/gsms/src/README b/deps/gsms/src/README
new file mode 100644
index 0000000..7331707
--- /dev/null
+++ b/deps/gsms/src/README
@@ -0,0 +1,141 @@
+Turn off echo
+=============
+ ATE
+ |
+ | OK
+
+Check if pin code is needed
+===========================
+ AT+CPIN?
+ |
+ | +CPIN: READY
+ |
+ | OK
+
+Set pin code if not ready
+=========================
+ AT+CPIN="1234"
+ | ERROR
+
+Disable pin code when ready
+===========================
+ AT+CLCK="SC",0,"1234"
+ |
+
+Register in network
+===================
+ AT+CREG=1
+ | OK
+
+Check SMS capability
+====================
+ AT+CSMS=0
+ | +CSMS: 1,1,1
+ | ERROR
+
+Write message to storage
+========================
+ AT+CMGW="+46702575687"
+ > Hej <ctl-z>
+ | +CMGW: 1
+
+Send message from storage (1)
+=============================
+ AT+CMSS=1
+ | +CMS ERROR: 331 (network not available)
+ | +CMS ERROR: 303 (....)
+
+Service center
+==============
+ AT+CSCA?
+ | +CSCA: "+46705008999",145
+
+Text/Pdu mode
+=============
+ AT+CMGF=1 text mode
+ AT+CMGF=0 pdu mode
+
+Receive SMS notification
+========================
+ AT+CNMI=1,1 - New incoming SMS is displayed like
+
+ | +CMTI: "SM",<x> (x = counter)
+
+ AT+CNMI=1,2 - Display incoming messages on arrival (unsolicited)
+
+List SMS
+========
+ AT+CMGL
+ | +CMGL:<x>,"REC UNREAD","+number"
+ | message
+
+ AT+CMGL="ALL"
+
+ AT+CMGL=1 (pdu mode)
+ | +CMGL: <x>,<y>,<str>,<len>
+ | <PDU>
+ |
+
+Read SMS
+========
+ AT+CMGR=<x>
+
+Delete SMS
+==========
+ AT+CMGD=<x>(,<x>)*
+
+
+Send SMS in PDU mode
+====================
+
+
+ AT+CMGS=23 //Send message, 23 octets (excluding the two initial zeros)
+%% >0011000B916407281553F80000AA0AE8329BFD4697D9EC37<ctrl-z>
+%%)
+%% There are 23 octets in this message (46 'characters'). The first octet ("00")
+%% doesn't count, it is only an indicator of the length of the SMSC information
+%% supplied (0).
+%%
+%%
+
+Sigal quality
+
+AT+CSQ?
++CSQ:18,99
+
+AT+CSQ
+
++CSQ: 4,0
+
+OK
+
+Value RSSI dBm Condition
+2 -109 Marginal
+3 -107 Marginal
+4 -105 Marginal
+5 -103 Marginal
+6 -101 Marginal
+7 -99 Marginal
+8 -97 Marginal
+9 -95 Marginal
+10 -93 OK
+11 -91 OK
+12 -89 OK
+13 -87 OK
+14 -85 OK
+15 -83 Good
+16 -81 Good
+17 -79 Good
+18 -77 Good
+19 -75 Good
+20 -73 Excellent
+21 -71 Excellent
+22 -69 Excellent
+23 -67 Excellent
+24 -65 Excellent
+25 -63 Excellent
+26 -61 Excellent
+27 -59 Excellent
+28 -57 Excellent
+29 -55 Excellent
+30 -53 Excellent \ No newline at end of file
diff --git a/deps/gsms/src/gsms.app.src b/deps/gsms/src/gsms.app.src
new file mode 100644
index 0000000..357ec77
--- /dev/null
+++ b/deps/gsms/src/gsms.app.src
@@ -0,0 +1,9 @@
+{application, gsms,
+ [
+ {description, "ETSI 03.40 Sender & Reciver"},
+ {vsn, git},
+ {registered, [gsms_router]},
+ {mod, {gsms_app,[]}},
+ {env, []},
+ {applications, [kernel,stdlib,uart]}
+ ]}.
diff --git a/deps/gsms/src/gsms.erl b/deps/gsms/src/gsms.erl
new file mode 100644
index 0000000..e682672
--- /dev/null
+++ b/deps/gsms/src/gsms.erl
@@ -0,0 +1,110 @@
+%%%---- BEGIN COPYRIGHT --------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ----------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2013, Tony Rogvall
+%%% @doc
+%%%
+%%% Created : 18 Apr 2013 by Tony Rogvall
+%%% @end
+
+-module(gsms).
+-include("gsms.hrl").
+
+%% Interface
+-export([send/2,
+ subscribe/1,
+ unsubscribe/1]).
+
+%% For testing
+%% @private
+-export([start/0,
+ stop/0]).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%--------------------------------------------------------------------
+%% @doc
+%% Send an SMS.
+%% @end
+%%--------------------------------------------------------------------
+-spec send(Options::list({Key::atom(), Value::term()}), Body::string()) ->
+ {ok, Ref::reference()} |
+ {error, Reason::term()}.
+
+send(Opts, Body) ->
+ gsms_router:send(Opts, Body).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Subscribe to incoming SMS:s filtered by Filter.
+%% @end
+%%--------------------------------------------------------------------
+-spec subscribe(Filter::list(filter())) ->
+ {ok,Ref::reference()} |
+ {error,Reason::term()}.
+
+subscribe(Filter) ->
+ case lists:keytake(reg_exp, 1, Filter) of
+ {value, RegExp, Rest} when is_list(RegExp) ->
+ %% Convert to mp-format (and verify format)
+ case re:compile(RegExp, [unicode]) of
+ {ok, MP} ->
+ gsms_router:subscribe([{reg_exp, MP} | Rest]);
+ {error, _ErrSpec} = E->
+ E
+ end;
+ {value, _RegExp, _Rest} ->
+ %% Assuming mp-format
+ gsms_router:subscribe(Filter);
+ false ->
+ gsms_router:subscribe(Filter)
+ end.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Remove previous subscription refered to by Ref.
+%% @end
+%%--------------------------------------------------------------------
+-spec unsubscribe(Ref::reference()) -> ok.
+
+unsubscribe(Ref) ->
+ gsms_router:unsubscribe(Ref).
+
+%%%===================================================================
+start() ->
+ call([lager,ale,uart,gsms], start).
+
+stop() ->
+ call([gsms,uart,ale,lager], stop).
+
+call([App|Apps], F) ->
+ error_logger:info_msg("~p: ~p\n", [F,App]),
+ case application:F(App) of
+ {error,{not_started,App1}} ->
+ call([App1,App|Apps], F);
+ {error,{already_started,App}} ->
+ call(Apps, F);
+ ok ->
+ call(Apps, F);
+ Error ->
+ Error
+ end;
+call([],_F) ->
+ ok.
+
+
diff --git a/deps/gsms/src/gsms_0338.erl b/deps/gsms/src/gsms_0338.erl
new file mode 100644
index 0000000..6dadd5b
--- /dev/null
+++ b/deps/gsms/src/gsms_0338.erl
@@ -0,0 +1,370 @@
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2012, Tony Rogvall
+%%% @doc
+%%% GSM 03.38 codec
+%%% @end
+%%% Created : 16 Oct 2012 by Tony Rogvall <tony@rogvall.se>
+
+-module(gsms_0338).
+
+-export([decode/1, decode_char/1]).
+-export([encode/1, encode/2, encode_char/1]).
+
+decode(Bin) when is_binary(Bin) ->
+ decode_list(binary_to_list(Bin));
+decode(Cs) when is_list(Cs) ->
+ decode_list(Cs).
+
+%%
+decode_list([16#1B,C|Cs]) ->
+ case C of
+ 16#0A -> [16#000C|decode_list(Cs)]; %% FORM FEED
+ 16#14 -> [16#005E|decode_list(Cs)]; %% CIRCUMFLEX ACCENT
+ 16#28 -> [16#007B|decode_list(Cs)]; %% LEFT CURLY BRACKET
+ 16#29 -> [16#007D|decode_list(Cs)]; %% RIGHT CURLY BRACKET
+ 16#2F -> [16#005C|decode_list(Cs)]; %% REVERSE SOLIDUS
+ 16#3C -> [16#005B|decode_list(Cs)]; %% LEFT SQUARE BRACKET
+ 16#3D -> [16#007E|decode_list(Cs)]; %% TILDE
+ 16#3E -> [16#005D|decode_list(Cs)]; %% RIGHT SQUARE BRACKET
+ 16#40 -> [16#007C|decode_list(Cs)]; %% VERTICAL LINE
+ 16#65 -> [16#20AC|decode_list(Cs)]; %% EURO SIGN
+ _ -> [16#A0|decode_list(Cs)]
+ end;
+decode_list([C|Cs]) ->
+ [decode_char(C)|decode_list(Cs)];
+decode_list([]) ->
+ [].
+
+decode_char(C) ->
+ case C band 16#7f of
+ 16#00 -> 16#0040; %% COMMERCIAL AT
+ %%16#00 16#0000 %% NULL
+ 16#01 -> 16#00A3; %% POUND SIGN
+ 16#02 -> 16#0024; %% DOLLAR SIGN
+ 16#03 -> 16#00A5; %% YEN SIGN
+ 16#04 -> 16#00E8; %% LATIN SMALL LETTER E WITH GRAVE
+ 16#05 -> 16#00E9; %% LATIN SMALL LETTER E WITH ACUTE
+ 16#06 -> 16#00F9; %% LATIN SMALL LETTER U WITH GRAVE
+ 16#07 -> 16#00EC; %% LATIN SMALL LETTER I WITH GRAVE
+ 16#08 -> 16#00F2; %% LATIN SMALL LETTER O WITH GRAVE
+ 16#09 -> 16#00E7; %% LATIN SMALL LETTER C WITH CEDILLA
+ %%16#09 16#00C7 %% LATIN CAPITAL LETTER C WITH CEDILLA
+ 16#0A -> 16#000A; %% LINE FEED
+ 16#0B -> 16#00D8; %% LATIN CAPITAL LETTER O WITH STROKE
+ 16#0C -> 16#00F8; %% LATIN SMALL LETTER O WITH STROKE
+ 16#0D -> 16#000D; %% CARRIAGE RETURN
+ 16#0E -> 16#00C5; %% LATIN CAPITAL LETTER A WITH RING ABOVE
+ 16#0F -> 16#00E5; %% LATIN SMALL LETTER A WITH RING ABOVE
+ 16#10 -> 16#0394; %% GREEK CAPITAL LETTER DELTA
+ 16#11 -> 16#005F; %% LOW LINE
+ 16#12 -> 16#03A6; %% GREEK CAPITAL LETTER PHI
+ 16#13 -> 16#0393; %% GREEK CAPITAL LETTER GAMMA
+ 16#14 -> 16#039B; %% GREEK CAPITAL LETTER LAMDA
+ 16#15 -> 16#03A9; %% GREEK CAPITAL LETTER OMEGA
+ 16#16 -> 16#03A0; %% GREEK CAPITAL LETTER PI
+ 16#17 -> 16#03A8; %% GREEK CAPITAL LETTER PSI
+ 16#18 -> 16#03A3; %% GREEK CAPITAL LETTER SIGMA
+ 16#19 -> 16#0398; %% GREEK CAPITAL LETTER THETA
+ 16#1A -> 16#039E; %% GREEK CAPITAL LETTER XI
+ 16#1C -> 16#00C6; %% LATIN CAPITAL LETTER AE
+ 16#1D -> 16#00E6; %% LATIN SMALL LETTER AE
+ 16#1E -> 16#00DF; %% LATIN SMALL LETTER SHARP S (German)
+ 16#1F -> 16#00C9; %% LATIN CAPITAL LETTER E WITH ACUTE
+ 16#20 -> 16#0020; %% SPACE
+ 16#21 -> 16#0021; %% EXCLAMATION MARK
+ 16#22 -> 16#0022; %% QUOTATION MARK
+ 16#23 -> 16#0023; %% NUMBER SIGN
+ 16#24 -> 16#00A4; %% CURRENCY SIGN
+ 16#25 -> 16#0025; %% PERCENT SIGN
+ 16#26 -> 16#0026; %% AMPERSAND
+ 16#27 -> 16#0027; %% APOSTROPHE
+ 16#28 -> 16#0028; %% LEFT PARENTHESIS
+ 16#29 -> 16#0029; %% RIGHT PARENTHESIS
+ 16#2A -> 16#002A; %% ASTERISK
+ 16#2B -> 16#002B; %% PLUS SIGN
+ 16#2C -> 16#002C; %% COMMA
+ 16#2D -> 16#002D; %% HYPHEN-MINUS
+ 16#2E -> 16#002E; %% FULL STOP
+ 16#2F -> 16#002F; %% SOLIDUS
+ 16#30 -> 16#0030; %% DIGIT ZERO
+ 16#31 -> 16#0031; %% DIGIT ONE
+ 16#32 -> 16#0032; %% DIGIT TWO
+ 16#33 -> 16#0033; %% DIGIT THREE
+ 16#34 -> 16#0034; %% DIGIT FOUR
+ 16#35 -> 16#0035; %% DIGIT FIVE
+ 16#36 -> 16#0036; %% DIGIT SIX
+ 16#37 -> 16#0037; %% DIGIT SEVEN
+ 16#38 -> 16#0038; %% DIGIT EIGHT
+ 16#39 -> 16#0039; %% DIGIT NINE
+ 16#3A -> 16#003A; %% COLON
+ 16#3B -> 16#003B; %% SEMICOLON
+ 16#3C -> 16#003C; %% LESS-THAN SIGN
+ 16#3D -> 16#003D; %% EQUALS SIGN
+ 16#3E -> 16#003E; %% GREATER-THAN SIGN
+ 16#3F -> 16#003F; %% QUESTION MARK
+ 16#40 -> 16#00A1; %% INVERTED EXCLAMATION MARK
+ 16#41 -> 16#0041; %% LATIN CAPITAL LETTER A
+ %%16#41 16#0391 %% GREEK CAPITAL LETTER ALPHA
+ 16#42 -> 16#0042; %% LATIN CAPITAL LETTER B
+ %%16#42 16#0392 %% GREEK CAPITAL LETTER BETA
+ 16#43 -> 16#0043; %% LATIN CAPITAL LETTER C
+ 16#44 -> 16#0044; %% LATIN CAPITAL LETTER D
+ 16#45 -> 16#0045; %% LATIN CAPITAL LETTER E
+ %%16#45 16#0395 %% GREEK CAPITAL LETTER EPSILON
+ 16#46 -> 16#0046; %% LATIN CAPITAL LETTER F
+ 16#47 -> 16#0047; %% LATIN CAPITAL LETTER G
+ 16#48 -> 16#0048; %% LATIN CAPITAL LETTER H
+ %%16#48 16#0397 %% GREEK CAPITAL LETTER ETA
+ 16#49 -> 16#0049; %% LATIN CAPITAL LETTER I
+ %%16#49 16#0399 %% GREEK CAPITAL LETTER IOTA
+ 16#4A -> 16#004A; %% LATIN CAPITAL LETTER J
+ 16#4B -> 16#004B; %% LATIN CAPITAL LETTER K
+ %%16#4B 16#039A %% GREEK CAPITAL LETTER KAPPA
+ 16#4C -> 16#004C; %% LATIN CAPITAL LETTER L
+ 16#4D -> 16#004D; %% LATIN CAPITAL LETTER M
+ %%16#4D 16#039C %% GREEK CAPITAL LETTER MU
+ 16#4E -> 16#004E; %% LATIN CAPITAL LETTER N
+ %%16#4E 16#039D %% GREEK CAPITAL LETTER NU
+ 16#4F -> 16#004F; %% LATIN CAPITAL LETTER O
+ %%16#4F 16#039F %% GREEK CAPITAL LETTER OMICRON
+ 16#50 -> 16#0050; %% LATIN CAPITAL LETTER P
+ %%16#50 16#03A1 %% GREEK CAPITAL LETTER RHO
+ 16#51 -> 16#0051; %% LATIN CAPITAL LETTER Q
+ 16#52 -> 16#0052; %% LATIN CAPITAL LETTER R
+ 16#53 -> 16#0053; %% LATIN CAPITAL LETTER S
+ 16#54 -> 16#0054; %% LATIN CAPITAL LETTER T
+ %%16#54 16#03A4 %% GREEK CAPITAL LETTER TAU
+ 16#55 -> 16#0055; %% LATIN CAPITAL LETTER U
+ 16#56 -> 16#0056; %% LATIN CAPITAL LETTER V
+ 16#57 -> 16#0057; %% LATIN CAPITAL LETTER W
+ 16#58 -> 16#0058; %% LATIN CAPITAL LETTER X
+ %%16#58 16#03A7 %% GREEK CAPITAL LETTER CHI
+ 16#59 -> 16#0059; %% LATIN CAPITAL LETTER Y
+ %%16#59 16#03A5 %% GREEK CAPITAL LETTER UPSILON
+ 16#5A -> 16#005A; %% LATIN CAPITAL LETTER Z
+ %%16#5A 16#0396 %% GREEK CAPITAL LETTER ZETA
+ 16#5B -> 16#00C4; %% LATIN CAPITAL LETTER A WITH DIAERESIS
+ 16#5C -> 16#00D6; %% LATIN CAPITAL LETTER O WITH DIAERESIS
+ 16#5D -> 16#00D1; %% LATIN CAPITAL LETTER N WITH TILDE
+ 16#5E -> 16#00DC; %% LATIN CAPITAL LETTER U WITH DIAERESIS
+ 16#5F -> 16#00A7; %% SECTION SIGN
+ 16#60 -> 16#00BF; %% INVERTED QUESTION MARK
+ 16#61 -> 16#0061; %% LATIN SMALL LETTER A
+ 16#62 -> 16#0062; %% LATIN SMALL LETTER B
+ 16#63 -> 16#0063; %% LATIN SMALL LETTER C
+ 16#64 -> 16#0064; %% LATIN SMALL LETTER D
+ 16#65 -> 16#0065; %% LATIN SMALL LETTER E
+ 16#66 -> 16#0066; %% LATIN SMALL LETTER F
+ 16#67 -> 16#0067; %% LATIN SMALL LETTER G
+ 16#68 -> 16#0068; %% LATIN SMALL LETTER H
+ 16#69 -> 16#0069; %% LATIN SMALL LETTER I
+ 16#6A -> 16#006A; %% LATIN SMALL LETTER J
+ 16#6B -> 16#006B; %% LATIN SMALL LETTER K
+ 16#6C -> 16#006C; %% LATIN SMALL LETTER L
+ 16#6D -> 16#006D; %% LATIN SMALL LETTER M
+ 16#6E -> 16#006E; %% LATIN SMALL LETTER N
+ 16#6F -> 16#006F; %% LATIN SMALL LETTER O
+ 16#70 -> 16#0070; %% LATIN SMALL LETTER P
+ 16#71 -> 16#0071; %% LATIN SMALL LETTER Q
+ 16#72 -> 16#0072; %% LATIN SMALL LETTER R
+ 16#73 -> 16#0073; %% LATIN SMALL LETTER S
+ 16#74 -> 16#0074; %% LATIN SMALL LETTER T
+ 16#75 -> 16#0075; %% LATIN SMALL LETTER U
+ 16#76 -> 16#0076; %% LATIN SMALL LETTER V
+ 16#77 -> 16#0077; %% LATIN SMALL LETTER W
+ 16#78 -> 16#0078; %% LATIN SMALL LETTER X
+ 16#79 -> 16#0079; %% LATIN SMALL LETTER Y
+ 16#7A -> 16#007A; %% LATIN SMALL LETTER Z
+ 16#7B -> 16#00E4; %% LATIN SMALL LETTER A WITH DIAERESIS
+ 16#7C -> 16#00F6; %% LATIN SMALL LETTER O WITH DIAERESIS
+ 16#7D -> 16#00F1; %% LATIN SMALL LETTER N WITH TILDE
+ 16#7E -> 16#00FC; %% LATIN SMALL LETTER U WITH DIAERESIS
+ 16#7F -> 16#00E0 %% LATIN SMALL LETTER A WITH GRAVE
+ end.
+
+encode(Data) ->
+ encode(Data, -1).
+
+encode(Bin,Max) when is_binary(Bin), is_integer(Max) ->
+ encode_list(binary_to_list(Bin), Max);
+encode(Cs,Max) when is_list(Cs), is_integer(Max) ->
+ encode_list(Cs,Max).
+
+
+encode_list(Cs,Max) ->
+ encode_list_(Cs,Max,[]).
+
+encode_list_(Cs,0,Acc) ->
+ {iolist_to_binary(lists:reverse(Acc)),Cs};
+encode_list_(Cs1=[C|Cs],I,Acc) ->
+ try encode_char(C) of
+ [Esc,Y] ->
+ if I >= 2 -> encode_list_(Cs,I-2,[Y,Esc|Acc]);
+ true -> {iolist_to_binary(lists:reverse(Acc)),Cs1}
+ end;
+ Y -> encode_list_(Cs,I-1,[Y|Acc])
+ catch
+ error:_ ->
+ encode_list_(Cs,I-2,[16#A0,16#1B|Acc])
+ end;
+encode_list_([],_I,Acc) ->
+ {iolist_to_binary(lists:reverse(Acc)),[]}.
+
+
+encode_char(C) ->
+ case C of
+ 16#0040 -> 16#00; %% COMMERCIAL AT
+ 16#0000 -> 16#00; %% NULL
+ 16#00A3 -> 16#01; %% POUND SIGN
+ 16#0024 -> 16#02; %% DOLLAR SIGN
+ 16#00A5 -> 16#03; %% YEN SIGN
+ 16#00E8 -> 16#04; %% LATIN SMALL LETTER E WITH GRAVE
+ 16#00E9 -> 16#05; %% LATIN SMALL LETTER E WITH ACUTE
+ 16#00F9 -> 16#06; %% LATIN SMALL LETTER U WITH GRAVE
+ 16#00EC -> 16#07; %% LATIN SMALL LETTER I WITH GRAVE
+ 16#00F2 -> 16#08; %% LATIN SMALL LETTER O WITH GRAVE
+ 16#00E7 -> 16#09; %% LATIN SMALL LETTER C WITH CEDILLA
+ 16#00C7 -> 16#09; %% LATIN CAPITAL LETTER C WITH CEDILLA
+ 16#000A -> 16#0A; %% LINE FEED
+ 16#00D8 -> 16#0B; %% LATIN CAPITAL LETTER O WITH STROKE
+ 16#00F8 -> 16#0C; %% LATIN SMALL LETTER O WITH STROKE
+ 16#000D -> 16#0D; %% CARRIAGE RETURN
+ 16#00C5 -> 16#0E; %% LATIN CAPITAL LETTER A WITH RING ABOVE
+ 16#00E5 -> 16#0F; %% LATIN SMALL LETTER A WITH RING ABOVE
+ 16#0394 -> 16#10; %% GREEK CAPITAL LETTER DELTA
+ 16#005F -> 16#11; %% LOW LINE
+ 16#03A6 -> 16#12; %% GREEK CAPITAL LETTER PHI
+ 16#0393 -> 16#13; %% GREEK CAPITAL LETTER GAMMA
+ 16#039B -> 16#14; %% GREEK CAPITAL LETTER LAMDA
+ 16#03A9 -> 16#15; %% GREEK CAPITAL LETTER OMEGA
+ 16#03A0 -> 16#16; %% GREEK CAPITAL LETTER PI
+ 16#03A8 -> 16#17; %% GREEK CAPITAL LETTER PSI
+ 16#03A3 -> 16#18; %% GREEK CAPITAL LETTER SIGMA
+ 16#0398 -> 16#19; %% GREEK CAPITAL LETTER THETA
+ 16#039E -> 16#1A; %% GREEK CAPITAL LETTER XI
+ 16#00C6 -> 16#1C; %% LATIN CAPITAL LETTER AE
+ 16#00E6 -> 16#1D; %% LATIN SMALL LETTER AE
+ 16#00DF -> 16#1E; %% LATIN SMALL LETTER SHARP S (German)
+ 16#00C9 -> 16#1F; %% LATIN CAPITAL LETTER E WITH ACUTE
+ 16#0020 -> 16#20; %% SPACE
+ 16#0021 -> 16#21; %% EXCLAMATION MARK
+ 16#0022 -> 16#22; %% QUOTATION MARK
+ 16#0023 -> 16#23; %% NUMBER SIGN
+ 16#00A4 -> 16#24; %% CURRENCY SIGN
+ 16#0025 -> 16#25; %% PERCENT SIGN
+ 16#0026 -> 16#26; %% AMPERSAND
+ 16#0027 -> 16#27; %% APOSTROPHE
+ 16#0028 -> 16#28; %% LEFT PARENTHESIS
+ 16#0029 -> 16#29; %% RIGHT PARENTHESIS
+ 16#002A -> 16#2A; %% ASTERISK
+ 16#002B -> 16#2B; %% PLUS SIGN
+ 16#002C -> 16#2C; %% COMMA
+ 16#002D -> 16#2D; %% HYPHEN-MINUS
+ 16#002E -> 16#2E; %% FULL STOP
+ 16#002F -> 16#2F; %% SOLIDUS
+ 16#0030 -> 16#30; %% DIGIT ZERO
+ 16#0031 -> 16#31; %% DIGIT ONE
+ 16#0032 -> 16#32; %% DIGIT TWO
+ 16#0033 -> 16#33; %% DIGIT THREE
+ 16#0034 -> 16#34; %% DIGIT FOUR
+ 16#0035 -> 16#35; %% DIGIT FIVE
+ 16#0036 -> 16#36; %% DIGIT SIX
+ 16#0037 -> 16#37; %% DIGIT SEVEN
+ 16#0038 -> 16#38; %% DIGIT EIGHT
+ 16#0039 -> 16#39; %% DIGIT NINE
+ 16#003A -> 16#3A; %% COLON
+ 16#003B -> 16#3B; %% SEMICOLON
+ 16#003C -> 16#3C; %% LESS-THAN SIGN
+ 16#003D -> 16#3D; %% EQUALS SIGN
+ 16#003E -> 16#3E; %% GREATER-THAN SIGN
+ 16#003F -> 16#3F; %% QUESTION MARK
+ 16#00A1 -> 16#40; %% INVERTED EXCLAMATION MARK
+ 16#0041 -> 16#41; %% LATIN CAPITAL LETTER A
+ 16#0391 -> 16#41; %% GREEK CAPITAL LETTER ALPHA
+ 16#0042 -> 16#42; %% LATIN CAPITAL LETTER B
+ 16#0392 -> 16#42; %% GREEK CAPITAL LETTER BETA
+ 16#0043 -> 16#43; %% LATIN CAPITAL LETTER C
+ 16#0044 -> 16#44; %% LATIN CAPITAL LETTER D
+ 16#0045 -> 16#45; %% LATIN CAPITAL LETTER E
+ 16#0395 -> 16#45; %% GREEK CAPITAL LETTER EPSILON
+ 16#0046 -> 16#46; %% LATIN CAPITAL LETTER F
+ 16#0047 -> 16#47; %% LATIN CAPITAL LETTER G
+ 16#0048 -> 16#48; %% LATIN CAPITAL LETTER H
+ 16#0397 -> 16#48; %% GREEK CAPITAL LETTER ETA
+ 16#0049 -> 16#49; %% LATIN CAPITAL LETTER I
+ 16#0399 -> 16#49; %% GREEK CAPITAL LETTER IOTA
+ 16#004A -> 16#4A; %% LATIN CAPITAL LETTER J
+ 16#004B -> 16#4B; %% LATIN CAPITAL LETTER K
+ 16#039A -> 16#4B; %% GREEK CAPITAL LETTER KAPPA
+ 16#004C -> 16#4C; %% LATIN CAPITAL LETTER L
+ 16#004D -> 16#4D; %% LATIN CAPITAL LETTER M
+ 16#039C -> 16#4D; %% GREEK CAPITAL LETTER MU
+ 16#004E -> 16#4E; %% LATIN CAPITAL LETTER N
+ 16#039D -> 16#4E; %% GREEK CAPITAL LETTER NU
+ 16#004F -> 16#4F; %% LATIN CAPITAL LETTER O
+ 16#039F -> 16#4F; %% GREEK CAPITAL LETTER OMICRON
+ 16#0050 -> 16#50; %% LATIN CAPITAL LETTER P
+ 16#03A1 -> 16#50; %% GREEK CAPITAL LETTER RHO
+ 16#0051 -> 16#51; %% LATIN CAPITAL LETTER Q
+ 16#0052 -> 16#52; %% LATIN CAPITAL LETTER R
+ 16#0053 -> 16#53; %% LATIN CAPITAL LETTER S
+ 16#0054 -> 16#54; %% LATIN CAPITAL LETTER T
+ 16#03A4 -> 16#54; %% GREEK CAPITAL LETTER TAU
+ 16#0055 -> 16#55; %% LATIN CAPITAL LETTER U
+ 16#0056 -> 16#56; %% LATIN CAPITAL LETTER V
+ 16#0057 -> 16#57; %% LATIN CAPITAL LETTER W
+ 16#0058 -> 16#58; %% LATIN CAPITAL LETTER X
+ 16#03A7 -> 16#58; %% GREEK CAPITAL LETTER CHI
+ 16#0059 -> 16#59; %% LATIN CAPITAL LETTER Y
+ 16#03A5 -> 16#59; %% GREEK CAPITAL LETTER UPSILON
+ 16#005A -> 16#5A; %% LATIN CAPITAL LETTER Z
+ 16#0396 -> 16#5A; %% GREEK CAPITAL LETTER ZETA
+ 16#00C4 -> 16#5B; %% LATIN CAPITAL LETTER A WITH DIAERESIS
+ 16#00D6 -> 16#5C; %% LATIN CAPITAL LETTER O WITH DIAERESIS
+ 16#00D1 -> 16#5D; %% LATIN CAPITAL LETTER N WITH TILDE
+ 16#00DC -> 16#5E; %% LATIN CAPITAL LETTER U WITH DIAERESIS
+ 16#00A7 -> 16#5F; %% SECTION SIGN
+ 16#00BF -> 16#60; %% INVERTED QUESTION MARK
+ 16#0061 -> 16#61; %% LATIN SMALL LETTER A
+ 16#0062 -> 16#62; %% LATIN SMALL LETTER B
+ 16#0063 -> 16#63; %% LATIN SMALL LETTER C
+ 16#0064 -> 16#64; %% LATIN SMALL LETTER D
+ 16#0065 -> 16#65; %% LATIN SMALL LETTER E
+ 16#0066 -> 16#66; %% LATIN SMALL LETTER F
+ 16#0067 -> 16#67; %% LATIN SMALL LETTER G
+ 16#0068 -> 16#68; %% LATIN SMALL LETTER H
+ 16#0069 -> 16#69; %% LATIN SMALL LETTER I
+ 16#006A -> 16#6A; %% LATIN SMALL LETTER J
+ 16#006B -> 16#6B; %% LATIN SMALL LETTER K
+ 16#006C -> 16#6C; %% LATIN SMALL LETTER L
+ 16#006D -> 16#6D; %% LATIN SMALL LETTER M
+ 16#006E -> 16#6E; %% LATIN SMALL LETTER N
+ 16#006F -> 16#6F; %% LATIN SMALL LETTER O
+ 16#0070 -> 16#70; %% LATIN SMALL LETTER P
+ 16#0071 -> 16#71; %% LATIN SMALL LETTER Q
+ 16#0072 -> 16#72; %% LATIN SMALL LETTER R
+ 16#0073 -> 16#73; %% LATIN SMALL LETTER S
+ 16#0074 -> 16#74; %% LATIN SMALL LETTER T
+ 16#0075 -> 16#75; %% LATIN SMALL LETTER U
+ 16#0076 -> 16#76; %% LATIN SMALL LETTER V
+ 16#0077 -> 16#77; %% LATIN SMALL LETTER W
+ 16#0078 -> 16#78; %% LATIN SMALL LETTER X
+ 16#0079 -> 16#79; %% LATIN SMALL LETTER Y
+ 16#007A -> 16#7A; %% LATIN SMALL LETTER Z
+ 16#00E4 -> 16#7B; %% LATIN SMALL LETTER A WITH DIAERESIS
+ 16#00F6 -> 16#7C; %% LATIN SMALL LETTER O WITH DIAERESIS
+ 16#00F1 -> 16#7D; %% LATIN SMALL LETTER N WITH TILDE
+ 16#00FC -> 16#7E; %% LATIN SMALL LETTER U WITH DIAERESIS
+ 16#00E0 -> 16#7F; %% LATIN SMALL LETTER A WITH GRAVE
+ %% extensions
+ 16#000C -> [16#1B,16#0A]; %% FORM FEED
+ 16#005E -> [16#1B,16#14]; %% CIRCUMFLEX ACCENT
+ 16#007B -> [16#1B,16#28]; %% LEFT CURLY BRACKET
+ 16#007D -> [16#1B,16#29]; %% RIGHT CURLY BRACKET
+ 16#005C -> [16#1B,16#2F]; %% REVERSE SOLIDUS
+ 16#005B -> [16#1B,16#3C]; %% LEFT SQUARE BRACKET
+ 16#007E -> [16#1B,16#3D]; %% TILDE
+ 16#005D -> [16#1B,16#3E]; %% RIGHT SQUARE BRACKET
+ 16#007C -> [16#1B,16#40]; %% VERTICAL LINE
+ 16#20AC -> [16#1B,16#65] %% EURO SIGN
+ end.
diff --git a/deps/gsms/src/gsms_0705.erl b/deps/gsms/src/gsms_0705.erl
new file mode 100644
index 0000000..8756d3c
--- /dev/null
+++ b/deps/gsms/src/gsms_0705.erl
@@ -0,0 +1,972 @@
+%%%-------------------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2012, Tony Rogvall
+%%% @doc
+%%% SMS service using gsms_uart to access 07.05 commands
+%%% @end
+%%% Created : 24 Oct 2012 by Tony Rogvall <tony@rogvall.se>
+%%%-------------------------------------------------------------------
+-module(gsms_0705).
+
+-behaviour(gen_server).
+
+-export([start/2]).
+-export([start_link/2]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+%% api from gsms_router
+-export([send/3]). %% send straight
+-export([write/3]). %% write to sim but do not send
+-export([scan_input/1]).
+
+
+%% access 07.05 commands
+-export([get_version/1]).
+-export([get_manufacturer/1, get_model/1]).
+-export([get_imei/1, get_msisdn/1, get_imsi/1]).
+-export([get_activity_status/1]).
+-export([get_network_registration_status/1]).
+-export([get_signal_strength/1]).
+-export([get_battery_status/1]).
+-export([get_smsc/1]).
+
+-export([list_unread_messages/1]).
+-export([list_read_messages/1]).
+-export([list_unsent_messages/1]).
+-export([list_sent_messages/1]).
+-export([list_all_messages/1]).
+-export([list_indices/1]).
+
+-export([delete_message/2]).
+-export([delete_read_messages/1]).
+-export([delete_sent_messages/1]).
+-export([delete_unsent_messages/1]).
+-export([delete_all_messages/1]).
+
+-export([read_message/2]).
+
+%% access 07.05 commands given the driver pid
+-export([drv_reset/1]).
+-export([drv_init_csms_service/1]).
+-export([drv_check_csms_capability/1]).
+-export([drv_set_csms_notification/1]).
+-export([drv_set_csms_pdu_mode/1]).
+-export([drv_get_version/1]).
+-export([drv_get_manufacturer/1, drv_get_model/1]).
+-export([drv_get_imei/1, drv_get_msisdn/1, drv_get_imsi/1]).
+-export([drv_get_activity_status/1]).
+-export([drv_get_network_registration_status/1]).
+-export([drv_get_signal_strength/1]).
+-export([drv_get_battery_status/1]).
+-export([drv_get_smsc/1]).
+
+-export([drv_list_unread_messages/1]).
+-export([drv_list_read_messages/1]).
+-export([drv_list_unsent_messages/1]).
+-export([drv_list_sent_messages/1]).
+-export([drv_list_all_messages/1]).
+-export([drv_list_indices/1]).
+
+-export([drv_delete_message/2]).
+-export([drv_delete_message/3]).
+-export([drv_delete_all_message/1]).
+-export([drv_read_message/2]).
+-export([drv_write_message/3]).
+-export([drv_send_message/3]).
+
+-include("../include/gsms.hrl").
+
+-type qkey_t() :: {#gsms_addr{},MRef::integer}.
+-type isegment_t() :: {I::integer(),Ix::integer(),Pdu::#gsms_deliver_pdu{}}.
+-type qelem_t() :: {qkey_t(),TRef::reference(),N::integer,[isegment_t()]}.
+
+-type osegment_t() :: {send | write,
+ I::integer,
+ N::integer,
+ SRef::reference(),
+ Notify::boolean(),
+ Sender::pid(),
+ Pdu::#gsms_submit_pdu{}}.
+
+-type uart_driver() :: pid() | atom().
+
+-define(DEFAULT_SEND_DELAY, 0).
+-define(DEFAULT_SEGMENT_TIMEOUT, 60000).
+-define(DEFAULT_CONCAT_8BIT, true).
+-define(DEFAULT_CONCAT_SEQUENCE, true).
+-define(DEFAULT_CONCAT_REF, 0).
+-define(DEFAULT_SIMPIN, "").
+
+-type gsms_option() ::
+ {simpin, Pin::string()} |
+ {bnumber, Number::string()} |
+ {attributes, [{Key::atom(),Value::term()}]} |
+ {segment_timeout, Timeout::timeout()} |
+ {send_delay, Delay::timeout()} |
+ {concat_8bit, EightBit :: boolean()} |
+ {concat_seq, Sequential :: boolean()}
+ .
+
+-type gsms_send_option() ::
+ {ref, ConCatRef::uint16()} |
+ {notify, boolean()}.
+
+-record(state,
+ {
+ id :: integer(), %% id in gsms_router
+ drv :: pid(), %% pid of the gsms_uart AT driver
+ ref :: reference(), %% notification reference
+ %% state
+ drv_up = false, %% drv ready to process
+ sending = false :: boolean(), %% sending message outstanding?
+ inq = [] :: [qelem_t()],
+ outq = [] :: [osegment_t()],
+ concat_ref = ?DEFAULT_CONCAT_REF :: integer(),
+ %% config
+ bnumber = "" :: string(), %% modem phone number
+ simpin = "" :: string(), %% SIM pin when needed
+ segment_timeout :: timeout(),%% max wait for segment
+ send_delay :: timeout(), %% delay between sending segments
+ concat_seq :: boolean(), %% concat ref is sequence or random
+ concat_8bit :: boolean(), %% 8bit or 16bit
+ attributes = [] :: [{atom(),term()}]
+ }).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+-spec send(Pid::pid(), Opts::[gsms_pdu_option()|gsms_send_option()],
+ Message::list()) ->
+ {ok, Ref::reference()} | {error, Reason::term()}.
+
+%% send options: pdu_options ++ [{notify,boolean()},{ref,ConCatRef}]
+
+send(Pid, Opts, Message) ->
+ gen_server:call(Pid, {send, self(), Opts, Message}).
+
+-spec write(Pid::pid(),
+ Opts::[gsms_pdu_option()|gsms_send_option()],
+ Message::list()) ->
+ {ok, Ref::reference()} | {error, Reason::term()}.
+
+write(Pid, Opts, Message) ->
+ gen_server:call(Pid, {write, self(), Opts, Message}).
+
+%%
+%% Check if we have messages to process in the inbox
+%%
+scan_input(Pid) ->
+ Pid ! scan_input.
+
+get_version(Pid) ->
+ gen_server:call(Pid, get_version).
+get_manufacturer(Pid) ->
+ gen_server:call(Pid, get_manufacturer).
+get_model(Pid) ->
+ gen_server:call(Pid, get_model).
+get_imei(Pid) ->
+ gen_server:call(Pid, get_imei).
+get_msisdn(Pid) ->
+ gen_server:call(Pid, get_msisdn).
+get_imsi(Pid) ->
+ gen_server:call(Pid, get_imsi).
+get_activity_status(Pid) ->
+ gen_server:call(Pid, get_activity_status).
+get_network_registration_status(Pid) ->
+ gen_server:call(Pid, get_network_registration_status).
+get_signal_strength(Pid) ->
+ gen_server:call(Pid, get_signal_strength).
+get_battery_status(Pid) ->
+ gen_server:call(Pid, get_battery_status).
+get_smsc(Pid) ->
+ gen_server:call(Pid, get_smsc).
+
+list_unread_messages(Pid) ->
+ gen_server:call(Pid, list_unread_messages).
+list_read_messages(Pid) ->
+ gen_server:call(Pid, list_read_messages).
+list_unsent_messages(Pid) ->
+ gen_server:call(Pid, list_unsent_messages).
+list_sent_messages(Pid) ->
+ gen_server:call(Pid, list_sent_messages).
+list_all_messages(Pid) ->
+ gen_server:call(Pid, list_all_messages).
+list_indices(Pid) ->
+ gen_server:call(Pid, list_indices).
+
+delete_message(Pid, I) when is_integer(I), I>=0 ->
+ gen_server:call(Pid, {delete_message, I}).
+delete_read_messages(Pid) ->
+ gen_server:call(Pid, {delete_messages,1}).
+delete_sent_messages(Pid) -> %% and read messages
+ gen_server:call(Pid, {delete_messages,2}).
+delete_unsent_messages(Pid) -> %% and read,sent messages
+ gen_server:call(Pid, {delete_messages,3}).
+delete_all_messages(Pid) ->
+ gen_server:call(Pid, {delete_messages,4}).
+
+read_message(Pid,I) when is_integer(I), I>=0 ->
+ gen_server:call(Pid, {read_messages,I}).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server
+%%
+%% @spec start(Options) -> {ok, Pid} | ignore | {error, Error}
+%% @end
+%%--------------------------------------------------------------------
+
+-spec start(Id::integer(),Opts::[gsms_option()]) ->
+ {ok,pid()} | {error,Reason::term()}.
+
+start(Id, Opts) when is_integer(Id), is_list(Opts) ->
+ gsms:start(),
+ ChildSpec= {{?MODULE,Id}, {?MODULE, start_link, [Id,Opts]},
+ permanent, 5000, worker, [?MODULE]},
+ supervisor:start_child(gsms_if_sup, ChildSpec).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server
+%%
+%% @spec start_link() -> {ok, Pid} | ignore | {error, Error}
+%% @end
+%%--------------------------------------------------------------------
+start_link(Id, Opts) ->
+ gen_server:start_link(?MODULE, [Id,Opts], []).
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Initializes the server
+%%
+%% @spec init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% @end
+%%--------------------------------------------------------------------
+init([Id,Opts]) ->
+ State0 = #state { simpin = ?DEFAULT_SIMPIN,
+ segment_timeout = ?DEFAULT_SEGMENT_TIMEOUT,
+ send_delay = ?DEFAULT_SEND_DELAY,
+ concat_seq = ?DEFAULT_CONCAT_SEQUENCE,
+ concat_8bit = ?DEFAULT_CONCAT_8BIT },
+ {Opts1,State1} = setopts(Opts, State0),
+ {ok,Pid} = gsms_uart:start_link(Opts1),
+ {ok,Ref} = gsms_uart:subscribe(Pid), %% subscribe to all events
+ {ok, State1#state { id = Id,
+ drv = Pid,
+ ref = Ref }}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling call messages
+%%
+%% @spec handle_call(Request, From, State) ->
+%% {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_call({send,Sender,Opts,Body}, _From, State) ->
+ case gsms_codec:make_sms_submit(Opts, Body) of
+ {ok,PduList} ->
+ {CRef,Opts1} = next_concat_ref(Opts,State),
+ Notify = proplists:get_bool(notify, Opts1),
+ N = length(PduList),
+ SRef = make_ref(),
+ OutQ = State#state.outq ++
+ [{send,I,N,SRef,Notify,Sender,Pdu} ||
+ {Pdu,I} <- lists:zip(PduList, lists:seq(1,N))],
+ State1 = sending(OutQ,State),
+ {reply, {ok,SRef}, State1#state { concat_ref = CRef}};
+ Error ->
+ {reply, Error, State}
+ end;
+handle_call({write,Sender,Opts,Body}, _From, State) ->
+ case gsms_codec:make_sms_submit(Opts, Body) of
+ {ok,PduList} ->
+ {CRef,Opts1} = next_concat_ref(Opts,State),
+ Notify = proplists:get_bool(notify, Opts1),
+ N = length(PduList),
+ WRef = make_ref(),
+ OutQ = State#state.outq ++
+ [{write,I,N,WRef,Notify,Sender,Pdu} ||
+ {Pdu,I} <- lists:zip(PduList, lists:seq(1,N))],
+ State1 = sending(OutQ,State),
+ {reply, {ok,WRef}, State1#state { concat_ref = CRef}};
+ Error ->
+ {reply, Error, State}
+ end;
+handle_call({cancel,Ref}, _From, State) ->
+ %% Remove and segments not sent for SRef
+ %% Possibly sent a cancel command ? if supported
+ OutQ =
+ lists:foldr(
+ fun(E={_Operation,_I,_N,Ref1,_Notify,_Sender,_Pdu}, Acc) ->
+ if Ref1 =:= Ref -> Acc;
+ true -> [E|Acc]
+ end
+ end, [], State#state.outq),
+ {reply, ok, State#state { outq = OutQ }};
+
+handle_call(get_version, _From, State) ->
+ if State#state.drv_up ->
+ {reply, drv_get_version(State#state.drv), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call(get_manufacturer, _From, State) ->
+ if State#state.drv_up ->
+ {reply, drv_get_manufacturer(State#state.drv), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call(get_model, _From, State) ->
+ if State#state.drv_up ->
+ {reply, drv_get_model(State#state.drv), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call(get_imei, _From, State) ->
+ if State#state.drv_up ->
+ {reply, drv_get_imei(State#state.drv), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call(get_msisdn, _From, State) ->
+ if State#state.drv_up ->
+ {reply, drv_get_msisdn(State#state.drv), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call(get_imsi, _From, State) ->
+ if State#state.drv_up ->
+ {reply, drv_get_imsi(State#state.drv), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call(get_network_registration_status, _From, State) ->
+ if State#state.drv_up ->
+ {reply, drv_get_network_registration_status(State#state.drv),
+ State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call(get_signal_strength, _From, State) ->
+ if State#state.drv_up ->
+ {reply, drv_get_signal_strength(State#state.drv), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call(get_battery_status, _From, State) ->
+ if State#state.drv_up ->
+ {reply, drv_get_battery_status(State#state.drv), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call(get_smsc, _From, State) ->
+ if State#state.drv_up ->
+ {reply, drv_get_smsc(State#state.drv), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call({list_messages,N},_From,State) ->
+ if State#state.drv_up ->
+ {reply, drv_list_messages(State#state.drv, N), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call({delete_message,I},_From,State) ->
+ if State#state.drv_up ->
+ {reply, drv_delete_message(State#state.drv,I), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call({delete_messages,F},_From,State) when F>0 ->
+ if State#state.drv_up ->
+ {reply, drv_delete_message(State#state.drv,0,F), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+
+
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling cast messages
+%%
+%% @spec handle_cast(Msg, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling all non call/cast messages
+%%
+%% @spec handle_info(Info, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_info({gsms_event,Ref,Event}, State) when State#state.ref =:= Ref ->
+ case Event of
+ {cmti,[{"store",_Name},{"index", Ix}]} ->
+ %% read a stored message
+ case drv_read_message(State#state.drv, Ix) of
+ {ok, Sms} ->
+ lager:debug("read_message: ~p", [Sms]),
+ State1 = handle_sms(Sms, Ix, State),
+ {noreply, State1};
+ Error ->
+ lager:info("read_message failed: ~p\n", [Error]),
+ {noreply, State}
+ end;
+ {data,"+CREG:"++Params} ->
+ case erl_scan:string(Params) of
+ {ok,[{integer,_,Status}|_],_} ->
+ %% we do not use the the access value
+ gsms_router:input_from(State#state.bnumber,{creg,Status}),
+ {noreply, State};
+ _ ->
+ lager:info("event ignored ~p\n", [Event]),
+ {noreply, State}
+ end;
+ _ ->
+ lager:info("event ignored ~p\n", [Event]),
+ {noreply, State}
+ end;
+handle_info({gsms_uart, Pid, up}, State) when State#state.drv =:= Pid ->
+ %% gsms_uart is up and running
+ %% FIXME: check if sim is locked & unlock if possible
+ gsms_uart:at(Pid,"E0"), %% disable echo (again)
+ timer:sleep(100), %% help?
+ ok = drv_check_csms_capability(Pid),
+ ok = drv_set_csms_pdu_mode(Pid),
+ ok = drv_set_csms_notification(Pid),
+ BNumber = if State#state.bnumber =:= "" ->
+ case drv_get_msisdn(Pid) of
+ ok -> "";
+ {ok,B} -> B
+ end;
+ true ->
+ State#state.bnumber
+ end,
+ State1 = State#state { bnumber = BNumber, drv_up = true },
+ ok = gsms_router:join(BNumber, State1#state.attributes),
+ %% make sure we scan messages that arrived while we where gone,
+ scan_input(self()),
+ lager:debug("running state = ~p", [State1]),
+ {noreply, State1};
+
+handle_info({timeout,TRef,{cancel,Key}}, State) ->
+ %% reject incoming message because of timeout
+ Q = State#state.inq,
+ case lists:keytake(Key, 1, Q) of
+ false ->
+ lager:warning("message from ~p not present in timeout", [Key]),
+ {noreply, State};
+ {value,{_,TRef,_N,Segments},Q0} ->
+ lager:warning("message from ~p dropped, timeout", [Key]),
+ lists:foreach(
+ fun({_I,Ix,_}) ->
+ drv_delete_message(State#state.drv, Ix)
+ end, Segments),
+ {noreply, State#state {inq = Q0}}
+ end;
+handle_info(scan_input, State) ->
+ %% scan_input should be run after startup to get buffered
+ %% messages stored in SIM card while application was down
+ case drv_list_indices(State#state.drv) of
+ {ok,[Ixs | _]} ->
+ Pid = self(),
+ Ref = State#state.ref,
+ lists:foreach(
+ fun(Ix) ->
+ Event = {cmti,[{"store","SM"},{"index",Ix}]},
+ Pid ! {gsms_event,Ref,Event}
+ end, expand_index_list(Ixs)),
+ {noreply, State};
+ Reply ->
+ lager:debug("list_indices reply ~p", [Reply]),
+ {noreply, State}
+ end;
+handle_info(send, State) ->
+ %% send next PDU
+ case State#state.outq of
+ [{send,I,N,SRef,Notify,Sender,Pdu}|OutQ] ->
+ gsms_codec:dump_yang(Pdu),
+ Bin = gsms_codec:encode_sms(Pdu),
+ Hex = gsms_codec:binary_to_hex(Bin),
+ Len = (length(Hex)-2) div 2,
+ Reply =
+ gsms_uart:atd(State#state.drv,
+ "+CMGS="++integer_to_list(Len),Hex),
+ lager:debug("send status segment ~w of ~w response=~p\n",
+ [I,N,Reply]),
+ %% Fixme handle Reply=error!!! cancel rest of segments etc
+ if I =:= N, Notify =:= true ->
+ Sender ! {gsms_notify, SRef, ok};
+ true ->
+ ok
+ end,
+ {noreply, sending(OutQ, State)};
+ [{write,I,N,SRef,Notify,Sender,Pdu}|OutQ] ->
+ gsms_codec:dump_yang(Pdu),
+ Bin = gsms_codec:encode_sms(Pdu),
+ Hex = gsms_codec:binary_to_hex(Bin),
+ Len = (length(Hex)-2) div 2,
+ Reply = gsms_uart:atd(State#state.drv,
+ "+CMGW="++integer_to_list(Len),Hex),
+ lager:debug("wrote status segment ~w of ~w response=~p\n",
+ [I,N,Reply]),
+ %% Fixme handle Reply=error!!! cancel rest of segments etc
+ if I =:= N, Notify -> %% assume ok
+ Sender ! {gsms_notify, SRef, ok};
+ true ->
+ ok
+ end,
+ {noreply, sending(OutQ, State)};
+ [] ->
+ {noreply, State#state { sending = false }}
+ end;
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any
+%% necessary cleaning up. When it returns, the gen_server terminates
+%% with Reason. The return value is ignored.
+%%
+%% @spec terminate(Reason, State) -> void()
+%% @end
+%%--------------------------------------------------------------------
+terminate(_Reason, _State) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Convert process state when code is changed
+%%
+%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% @end
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+
+
+-spec drv_reset(Drv::uart_driver()) -> ok.
+
+drv_reset(Drv) ->
+ gsms_uart:at(Drv,"Z"),
+ gsms_uart:at(Drv,"E0"),
+ drv_set_csms_pdu_mode(Drv),
+ drv_set_csms_notification(Drv).
+
+
+-spec drv_init_csms_service(Drv::uart_driver()) -> ok.
+drv_init_csms_service(Drv) ->
+ ok = drv_check_csms_capability(Drv),
+ ok = drv_set_csms_pdu_mode(Drv),
+ ok = drv_set_csms_notification(Drv),
+ %% how do we read the stored pdu like they where receive and when?
+ %% trigger automatic read of stored SMS when a subsrciber is
+ %% registered? subscriber need to ack in order to delete the
+ %% stored SMS
+ ok.
+
+
+-spec drv_check_csms_capability(Drv::uart_driver()) -> ok.
+drv_check_csms_capability(Drv) ->
+ case gsms_uart:at(Drv,"+CSMS=0") of
+ {ok, "+CSMS:"++Storage} ->
+ lager:debug("sms_capability: +CSMS: ~s", [Storage]);
+ Error ->
+ Error
+ end.
+
+-spec drv_set_csms_pdu_mode(Drv::uart_driver()) -> ok.
+
+drv_set_csms_pdu_mode(Drv) ->
+ gsms_uart:at(Drv,"+CMGF=0").
+
+%% AT+CNMI=1,1,0,0,0 Set the new message indicators.
+%%
+%% AT+CNMI=<mode>,<mt>,<bm>,<ds>,<bfr>
+%%
+%% <mode>=1 discard unsolicited result codes indication when TA –
+%% TE link is reserved.
+%% <mt>=1 SMS-DELIVERs are delivered to the SIM and routed using
+%% unsolicited code.
+%% <bm>=0 no CBM indications are routed to the TE.
+%% <ds>=0 no SMS-STATUS-REPORTs are routed.
+%% <bfr>=0 TA buffer of unsolicited result codes defined within this
+%% command is flushed to the TE.
+%% OK Modem Response.
+drv_set_csms_notification(Drv) ->
+ gsms_uart:at(Drv,"+CNMI=1,1,0,0,0").
+
+%% pick up information about various things
+drv_get_version(Drv) ->
+ gsms_uart:at(Drv,"+CGMR").
+
+drv_get_manufacturer(Drv) ->
+ gsms_uart:at(Drv,"+CGMI").
+
+drv_get_model(Drv) ->
+ gsms_uart:at(Drv,"+CGMM").
+
+drv_get_imei(Drv) ->
+ gsms_uart:at(Drv,"+CGSN").
+
+drv_get_msisdn(Drv) ->
+ gsms_uart:at(Drv,"+CNUM").
+
+drv_get_imsi(Drv) ->
+ gsms_uart:at(Drv,"+CIMI").
+
+drv_get_activity_status(Drv) ->
+ gsms_uart:eat(Drv,"+CPAS").
+
+drv_get_network_registration_status(Drv) ->
+ gsms_uart:at(Drv,"+CREG?").
+
+drv_get_signal_strength(Drv) ->
+ gsms_uart:at(Drv,"+CSQ").
+
+drv_get_battery_status(Drv) ->
+ gsms_uart:at(Drv,"+CBC").
+
+drv_get_smsc(Drv) ->
+ gsms_uart:at(Drv, "+CSCA?").
+
+%% SMS commands
+
+drv_list_unread_messages(Drv) ->
+ drv_list_messages(Drv, 0).
+drv_list_read_messages(Drv) ->
+ drv_list_messages(Drv, 1).
+drv_list_unsent_messages(Drv) ->
+ drv_list_messages(Drv, 2).
+drv_list_sent_messages(Drv) ->
+ drv_list_messages(Drv, 3).
+drv_list_all_messages(Drv) ->
+ drv_list_messages(Drv, 4).
+
+drv_list_messages(Drv, N) when is_integer(N), N>=0, N=<4 ->
+ gsms_uart:at(Drv, "+CMGL="++integer_to_list(N)).
+
+%% message list
+drv_list_indices(Drv) ->
+ case gsms_uart:at(Drv,"+CMGD=?") of
+ {ok,"+CMGD:"++Params} ->
+ case erl_scan:string(Params) of
+ {ok,Ts,_} ->
+ parse_index_lists(Ts);
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end.
+
+
+%% +CMGD=I == +CMDG=I,0 only delete message I
+%% +CMGD=I,1 == +CMGD=0,1 delete ALL "read" messages
+%% +CMGD=I,2 == +CMGD=0,2 delete ALL "read","sent" messages
+%% +CMGD=I,3 == +CMGD=0,3 delete ALL "read","sent", "unsent" messages
+%% +CMFD=I,4 == +CMGD=0,4 delete ALL messages
+
+drv_delete_message(Drv,I) when is_integer(I), I>=0 ->
+ drv_delete_message(Drv,I,0).
+
+drv_delete_message(Drv,I,F) when is_integer(I), I>=0, F>=0,F=<4 ->
+ gsms_uart:at(Drv,"+CMGD="++integer_to_list(I)++","++integer_to_list(F)).
+
+drv_delete_all_message(Drv) ->
+ drv_delete_message(Drv,1,4).
+
+drv_read_message(Drv,I) when is_integer(I), I>=0 ->
+ case gsms_uart:at(Drv,"+CMGR="++integer_to_list(I)) of
+ ok ->
+ {error, no_such_index};
+ {ok,["+CMGR: "++_StatStoreLen,HexPdu]} ->
+ gsms_codec:decode_in_hex(HexPdu);
+ {error, Error} ->
+ {error, cms_error(Error)}
+ end.
+
+
+%% test send message
+drv_send_message(Drv,Opts,Body) ->
+ case gsms_codec:make_sms_submit(Opts, Body) of
+ {ok,PduList} ->
+ lists:foreach(
+ fun(Pdu) ->
+ gsms_codec:dump_yang(Pdu),
+ Bin = gsms_codec:encode_sms(Pdu),
+ Hex = gsms_codec:binary_to_hex(Bin),
+ Len = (length(Hex)-2) div 2,
+ gsms_uart:atd(Drv,"+CMGS="++integer_to_list(Len),Hex)
+ end, PduList),
+ ok;
+ Error ->
+ Error
+ end.
+
+%% test write message
+drv_write_message(Drv,Opts,Body) ->
+ case gsms_codec:make_sms_submit(Opts, Body) of
+ {ok,PduList} ->
+ lists:foreach(
+ fun(Pdu) ->
+ gsms_codec:dump_yang(Pdu),
+ Bin = gsms_codec:encode_sms(Pdu),
+ Hex = gsms_codec:binary_to_hex(Bin),
+ Len = (length(Hex)-2) div 2,
+ gsms_uart:atd(Drv,"+CMGW="++integer_to_list(Len),Hex)
+ end, PduList),
+ ok;
+ Error ->
+ Error
+ end.
+
+parse_index_lists(Ts) ->
+ parse_index_lists(Ts, []).
+
+parse_index_lists([{'(',_}|Ts], Acc) -> parse_index_list(Ts, [], Acc);
+parse_index_lists([], Acc) -> {ok,lists:reverse(Acc)};
+parse_index_lists(_Ts, _Acc) -> {error, {syntax_error, _Ts}}.
+
+parse_index_lists1([{',',_}|Ts], Acc) -> parse_index_lists(Ts, Acc);
+parse_index_lists1([], Acc) -> {ok,lists:reverse(Acc)};
+parse_index_lists1(_Ts, _Acc) -> {error, {syntax_error, _Ts}}.
+
+%%
+%% ival = <i> '-' <j>
+%% ival = <i>
+%% ival-list = '(' (ival (',' ival)*) ? ')'
+%%
+parse_index_list([{integer,_,I},{'-',_},{integer,_,J}|Ts],Iv,Acc) ->
+ parse_index_list1(Ts,[{I,J}|Iv],Acc);
+parse_index_list([{integer,_,I}|Ts],Iv,Acc) ->
+ parse_index_list1(Ts,[I|Iv],Acc);
+parse_index_list([{')',_}|Ts],Iv,Acc) ->
+ parse_index_lists1(Ts,[lists:reverse(Iv)|Acc]);
+parse_index_list(_Ts, _Iv, _Acc) ->
+ {error, {syntax_error,_Ts}}.
+
+parse_index_list1([{',',_},{integer,_,I},{'-',_},{integer,_,J}|Ts],Iv,Acc) ->
+ parse_index_list1(Ts,[{I,J}|Iv],Acc);
+parse_index_list1([{',',_},{integer,_,I}|Ts],Iv,Acc) ->
+ parse_index_list1(Ts,[I|Iv],Acc);
+parse_index_list1([{')',_}|Ts],Iv,Acc) ->
+ parse_index_lists1(Ts,[lists:reverse(Iv)|Acc]);
+parse_index_list1(_Ts,_Iv,_Acc) ->
+ {error, {syntax_error,_Ts}}.
+
+expand_index_list([{I,J}|Is]) when is_integer(I),is_integer(J),I>=0,I=<J ->
+ lists:seq(I,J) ++ expand_index_list(Is);
+expand_index_list([I|Is]) when is_integer(I), I>=0 ->
+ [I|expand_index_list(Is)];
+expand_index_list([]) ->
+ [].
+
+cms_error(Code) when is_list(Code) ->
+ Code ++ ": " ++ cms_error_string(list_to_integer(Code)).
+
+cms_error_string(300) -> "Phone failure";
+cms_error_string(301) -> "SMS service of phone reserved";
+cms_error_string(302) -> "Operation not allowed";
+cms_error_string(303) -> "Operation not supported";
+cms_error_string(304) -> "Invalid PDU mode parameter";
+cms_error_string(305) -> "Invalid text mode parameter";
+cms_error_string(310) -> "SIM not inserted";
+cms_error_string(311) -> "SIM PIN necessary";
+cms_error_string(312) -> "PH-SIM PIN necessary";
+cms_error_string(313) -> "SIM failure";
+cms_error_string(314) -> "SIM busy";
+cms_error_string(315) -> "SIM wrong";
+cms_error_string(320) -> "Memory failure";
+cms_error_string(321) -> "Invalid memory index";
+cms_error_string(322) -> "Memory full";
+cms_error_string(330) -> "SMSC (message service center) address unknown";
+cms_error_string(331) -> "No network service";
+cms_error_string(332) -> "Network timeout";
+cms_error_string(500) -> "Unknown error";
+cms_error_string(512) -> "Manufacturer specific";
+cms_error_string(_) -> "Unknown".
+
+
+setopts(Opts, State) ->
+ setopts(Opts, State, []).
+
+setopts([Opt|Opts], State, Opts1) ->
+ case Opt of
+ {simpin,Pin} when is_list(Pin) ->
+ setopts(Opts, State#state { simpin = Pin }, Opts1);
+ {bnumber,BNumber} when is_list(BNumber) ->
+ setopts(Opts, State#state { bnumber = BNumber }, Opts1);
+ {attributes,As} when is_list(As) ->
+ setopts(Opts, State#state { attributes = As }, Opts1);
+ {segment_timeout,T} when is_integer(T), T >= 0 ->
+ setopts(Opts, State#state { segment_timeout = T }, Opts1);
+ {send_delay, T} when is_integer(T), T >= 0 ->
+ setopts(Opts, State#state { send_delay = T }, Opts1);
+ {concat_8bit, B} when is_boolean(B) ->
+ setopts(Opts, State#state { concat_8bit = B }, Opts1);
+ {concat_seq, B} when is_boolean(B) ->
+ setopts(Opts, State#state { concat_seq = B }, Opts1);
+ _ ->
+ setopts(Opts, State, [Opt|Opts1])
+ end;
+setopts([], State, Opts1) ->
+ {lists:reverse(Opts1), State}.
+
+
+next_concat_ref(Opts, State) ->
+ case lists:keymember(ref,1,Opts) of
+ true -> %% ref is givent in the pdu
+ {State#state.concat_ref, Opts};
+ false ->
+ CRef0 = if State#state.concat_seq ->
+ State#state.concat_ref + 1;
+ true ->
+ random:unifrom(16#10000)-1
+ end,
+ CRef1 = if State#state.concat_8bit ->
+ CRef0 band 16#ff;
+ true ->
+ CRef0 band 16#ffff
+ end,
+ {CRef1, [{ref,CRef1} | Opts]}
+ end.
+
+%%
+%% Continue send segments or we are done ?
+%%
+sending([], State) ->
+ State#state { sending=false, outq = []};
+sending(OutQ, State) ->
+ erlang:send_after(State#state.send_delay, self(), send),
+ State#state { sending=true, outq = OutQ }.
+
+handle_sms(Sms, Ix, State) ->
+ %% check if this is Sms is part of an concatenated message
+ case lists:keyfind(concat, 1, Sms#gsms_deliver_pdu.udh) of
+ false -> %% singleton message, forward
+ forward_sms(Sms, [Ix], State);
+ {concat,_MRef,1,1} -> %% singleton message, forward !
+ forward_sms(Sms, [Ix], State);
+ {concat,MRef,N,I} when I > 0, I =< N ->
+ %% check if we already have segments stored
+ Key = {Sms#gsms_deliver_pdu.addr,MRef},
+ Q = State#state.inq,
+ Tmo = State#state.segment_timeout,
+ case lists:keytake(Key, 1, Q) of
+ false -> %% new enqueue
+ TRef = start_timer(Tmo,{cancel,Key}),
+ Segments = [{I,Ix,Sms}],
+ Q1 = [{Key,TRef,N,Segments} | Q],
+ State#state { inq = Q1 };
+ {value,{_,TRef,N,Segments},Q0} ->
+ case lists:keymember(I,1,Segments) of
+ false ->
+ stop_timer(TRef),
+ Segments1 = [{I,Ix,Sms}|Segments],
+ case length(Segments1) of
+ N ->
+ {Sms1,Ixs} = assemble_sms(Segments1),
+ State1 = State#state { inq=Q0},
+ forward_sms(Sms1,Ixs,State1);
+ _ ->
+ TRef1 = start_timer(Tmo,{cancel,Key}),
+ Q1 = [{Key,TRef1,N,Segments1} | Q0],
+ State#state { inq=Q1 }
+ end;
+ true ->
+ lager:warning("segment ~w already received!", [I]),
+ State
+ end
+ end;
+ Concat ->
+ lager:warning("bad concat udh element ~p", [Concat]),
+ State
+ end.
+
+%%
+%% Send sms to gsms_router & delete stored segments
+%%
+forward_sms(Sms, Ixs, State) ->
+ gsms_router:input_from(State#state.bnumber, Sms),
+ lists:foreach(
+ fun(Ix) ->
+ drv_delete_message(State#state.drv, Ix)
+ end, Ixs),
+ State.
+
+%% assemble sms segments into one message,
+%% assume all I 1..N are present (by pigeon hole principle)
+-spec assemble_sms([isegment_t()]) ->
+ {#gsms_deliver_pdu{}, [Ix::integer()]}.
+
+assemble_sms(Segments) ->
+ [{1,Ix,Sms} | Segments1] = lists:keysort(1, Segments),
+ Udh0 = lists:keydelete(concat, 1, Sms#gsms_deliver_pdu.udh),
+ Sms0 = Sms#gsms_deliver_pdu { udh=Udh0, udl=0, ud=[] },
+ assemble_(Segments1,
+ [Sms#gsms_deliver_pdu.ud], Sms#gsms_deliver_pdu.udl,
+ [Ix], Sms0).
+
+assemble_([{_,Ix,Sms}|Segments], Uds, Udl, Ixs, Sms0) ->
+ assemble_(Segments,
+ [Sms#gsms_deliver_pdu.ud|Uds], Sms#gsms_deliver_pdu.udl + Udl,
+ [Ix|Ixs], Sms0);
+assemble_([], Uds, Udl, Ixs, Sms0) ->
+ Ud = lists:append(lists:reverse(Uds)),
+ {Sms0#gsms_deliver_pdu { udl = Udl, ud = Ud }, lists:reverse(Ixs)}.
+
+
+start_timer(Time, Message) ->
+ erlang:start_timer(Time, self(), Message).
+
+stop_timer(undefined) ->
+ ok;
+stop_timer(Ref) ->
+ erlang:cancel_timer(Ref),
+ receive
+ {timeout, Ref, _} ->
+ ok
+ after 0 ->
+ ok
+ end.
diff --git a/deps/gsms/src/gsms_app.erl b/deps/gsms/src/gsms_app.erl
new file mode 100644
index 0000000..700561b
--- /dev/null
+++ b/deps/gsms/src/gsms_app.erl
@@ -0,0 +1,83 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%%-------------------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2010, Tony Rogvall
+%%% @doc
+%%%
+%%% @end
+%%% Created : 24 May 2010 by Tony Rogvall <tony@rogvall.se>
+%%%-------------------------------------------------------------------
+-module(gsms_app).
+
+-behaviour(application).
+
+-include("log.hrl").
+
+%% Application callbacks
+-export([start/2,
+ start/0,
+ stop/1,
+ stop/0]).
+
+%%%===================================================================
+%%% Application callbacks
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @spec start(StartType, StartArgs) -> {ok, Pid} |
+%% {ok, Pid, State} |
+%% {error, Reason}
+%% StartType = normal | {takeover, Node} | {failover, Node}
+%% StartArgs = term()
+%% @doc
+%% This function is called whenever an application is started using
+%% application:start/[1,2], and should start the processes of the
+%% application. If the application is structured according to the OTP
+%% design principles as a supervision tree, this means starting the
+%% top supervisor of the tree.
+%%
+%% @end
+%%--------------------------------------------------------------------
+start(_StartType, _StartArgs) ->
+ Args = case application:get_env(gsms, arguments) of
+ undefined -> [];
+ {ok,As} -> As
+ end,
+ gsms_sup:start_link(Args).
+
+%% @private
+start() ->
+ application:start(uart),
+ application:start(gsms).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% This function is called whenever an application has stopped. It
+%% is intended to be the opposite of Module:start/2 and should do
+%% any necessary cleaning up. The return value is ignored.
+%%
+%% @spec stop(State) -> void()
+%% @end
+%%--------------------------------------------------------------------
+stop(_State) ->
+ ?debug("gsms_app: stop/1.", []),
+ ok.
+
+%% @private
+stop() ->
+ application:stop(gsms).
diff --git a/deps/gsms/src/gsms_codec.erl b/deps/gsms/src/gsms_codec.erl
new file mode 100644
index 0000000..c904f77
--- /dev/null
+++ b/deps/gsms/src/gsms_codec.erl
@@ -0,0 +1,1067 @@
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2012, Tony Rogvall
+%%% @doc
+%%% Send/Recive SMS with GSM modem
+%%% @end
+%%% Created : 15 Oct 2012 by Tony Rogvall <tony@rogvall.se>
+
+-module(gsms_codec).
+
+-compile(export_all).
+
+-export([decode_in/1, decode_in_hex/1]).
+-export([decode_out/1, decode_out_hex/1]).
+-export([hex_to_binary/1]).
+-export([is_valid_scts/1]).
+-export([decode_dcs/1]).
+
+-import(lists, [reverse/1]).
+
+-include("log.hrl").
+-include("../include/gsms.hrl").
+
+-define(Q, $").
+
+-define(decode_bool(X), ((X) =/= 0)).
+-define(encode_bool(X), if (X) -> 1; true -> 0 end).
+-define(is_byte(X), (((X) band (bnot 16#ff)) =:= 0)).
+-define(is_short(X), (((X) band (bnot 16#ffff)) =:= 0)).
+
+decode_in_hex(RawData) ->
+ decode_in(hex_to_binary(RawData)).
+
+decode_out_hex(RawData) ->
+ decode_out(hex_to_binary(RawData)).
+
+-spec decode_in(Message::binary()) ->
+ {ok, #gsms_deliver_pdu{}} |
+ {error,Reason::atom()}.
+
+decode_in(<<L1,SmscAddr:L1/binary,
+ TP_RP:1,TP_UDHI:1,TP_SRI:1,R1:1,R2:1,TP_MMS:1,?MTI_SMS_DELIVER:2,
+ AddrLen,Data0/binary>>) ->
+ ALen = (AddrLen + 1) bsr 1, %% number of bytes
+ <<AType,Addr:ALen/binary,TP_PID,TP_DCS,
+ TP_SCTS:7/binary,TP_UDL,TP_UD/binary>> = Data0,
+ AddrType = decode_addr_type(AType),
+ Dcs = decode_dcs(TP_DCS),
+ {UDH,UD} = decode_ud(Dcs,TP_UDHI,TP_UDL,TP_UD),
+ {ok, #gsms_deliver_pdu {
+ smsc = decode_smsc_addr(SmscAddr),
+ rp = ?decode_bool(TP_RP),
+ udhi = ?decode_bool(TP_UDHI),
+ sri = ?decode_bool(TP_SRI),
+ res1 = R1,
+ res2 = R2,
+ mms = ?decode_bool(TP_MMS),
+ addr = decode_addr(AddrType,Addr),
+ pid = TP_PID,
+ dcs = Dcs,
+ scts = decode_scts(TP_SCTS),
+ udl = TP_UDL,
+ udh = UDH,
+ ud = UD
+ }};
+decode_in(Binary) when is_binary(Binary) ->
+ {error, einval}.
+
+
+-spec decode_out(Message::binary()) ->
+ {ok, #gsms_submit_pdu{}} |
+ {error,Reason::atom()}.
+
+decode_out(<<L1,SmscAddr:L1/binary,
+ TP_RP:1,TP_UDHI:1,TP_SRR:1,TP_VPF:2,TP_RD:1,?MTI_SMS_SUBMIT:2,
+ TP_MREF,
+ AddrLen, %% in semi octets
+ Data0/binary>>) ->
+ ALen = (AddrLen + 1) bsr 1, %% number of bytes
+ <<AType,Addr:ALen/binary,TP_PID,TP_DCS,Data1/binary>> = Data0,
+ AddrType = decode_addr_type(AType),
+ VPF = decode_vpf(TP_VPF),
+ VPLen = case VPF of
+ none -> 0;
+ relative -> 1;
+ enhanced -> 7;
+ absolute -> 7
+ end,
+ <<VP:VPLen/binary, TP_UDL:8, TP_UD/binary>> = Data1,
+ Dcs = decode_dcs(TP_DCS),
+ {UDH,UD} = decode_ud(Dcs,TP_UDHI,TP_UDL,TP_UD),
+ {ok, #gsms_submit_pdu {
+ smsc = decode_smsc_addr(SmscAddr),
+ rp = ?decode_bool(TP_RP),
+ udhi = ?decode_bool(TP_UDHI),
+ srr = ?decode_bool(TP_SRR),
+ vpf = VPF,
+ rd = ?decode_bool(TP_RD),
+ mref = TP_MREF,
+ addr = decode_addr(AddrType,Addr),
+ pid = TP_PID,
+ dcs = Dcs,
+ vp = decode_vp(VPF,VP),
+ udl = TP_UDL,
+ udh = UDH,
+ ud = UD
+ }};
+decode_out(Binary) when is_binary(Binary) ->
+ {error, einval}.
+
+
+
+%% return a list of pdu's
+-spec make_sms_submit(Opts::[gsms_pdu_option()],
+ Message::[integer()]) ->
+ {ok,[#gsms_submit_pdu{}]} |
+ {error, term()}.
+
+make_sms_submit(Opts,Message) ->
+ case set_pdu_opts(Opts,#gsms_submit_pdu{}) of
+ {ok,Pdu} ->
+ Ref = proplists:get_value(ref, Opts, 1),
+ {Dcs,UDList} = encode_ud(Pdu#gsms_submit_pdu.dcs, Message,
+ Pdu#gsms_submit_pdu.udh, Ref),
+ {ok,lists:map(
+ fun({Udl,Ud,Udhi0}) ->
+ Udhi = Udhi0 or Pdu#gsms_submit_pdu.udhi,
+ Pdu#gsms_submit_pdu { dcs=Dcs, udl=Udl,
+ ud=Ud, udhi=Udhi }
+ end, UDList)};
+ Error ->
+ Error
+ end.
+
+
+-spec set_pdu_opts(Opts::[gsms_pdu_option()], Pdu::#gsms_submit_pdu{}) ->
+ {ok,Pdu::#gsms_submit_pdu{}} |
+ {error, badarg}.
+
+
+set_pdu_opts([{Key,Value}|Kvs], R=#gsms_submit_pdu{}) ->
+ case Key of
+ smsc when is_record(Value,gsms_addr) ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { smsc=Value });
+ smsc when is_list(Value),hd(Value)=:=$+ ->
+ Addr = #gsms_addr { type=international, addr=Value },
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { smsc=Addr });
+ smsc when is_list(Value) ->
+ Addr = #gsms_addr { type=unknown, addr=Value },
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { smsc=Addr});
+ rp when is_boolean(Value) ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { rp=Value });
+ udhi when is_boolean(Value) ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { udhi=Value });
+ udh when is_list(Value), Value =/= [] ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { udhi=true, udh=Value });
+ udh when Value =:= [] ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { udhi=false, udh=Value });
+ srr when is_boolean(Value) ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { srr=Value });
+ mref when ?is_byte(Value) ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { mref=Value });
+ vpf when Value =:= none;
+ Value =:= relative;
+ Value =:= enhanced;
+ Value =:= absolute ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { vpf=Value });
+ vp ->
+ case Value of
+ none ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu
+ { vpf = none, vp=undefined });
+ {relative, Seconds} when is_integer(Seconds), Seconds>=0 ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu
+ { vpf = relative, vp=Seconds });
+ {absolute,DateTimeTz} ->
+ case is_valid_scts(DateTimeTz) of
+ true ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu
+ { vpf = absolute,
+ vp=DateTimeTz });
+ false ->
+ {error, badarg}
+ end;
+ {enhanced,_} ->
+ {error, not_supported} %% yet
+ end;
+ addr when is_record(Value,gsms_addr) ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { addr=Value });
+ addr when is_list(Value),hd(Value)=:=$+ ->
+ Addr = #gsms_addr { type=international, addr=Value },
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { addr=Addr});
+ addr when is_list(Value) ->
+ Addr = #gsms_addr { type=unknown, addr=Value },
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { addr=Addr});
+ pid when ?is_byte(Value) ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { pid=Value });
+ dcs when is_record(Value,gsms_dcs) ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { dcs=Value });
+ dcs when is_integer(Value) ->
+ Dcs = decode_dcs(Value),
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { dcs=Dcs });
+ type -> %% fixme test
+ Dcs = R#gsms_submit_pdu.dcs,
+ Dcs1 = Dcs#gsms_dcs { type = Value },
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { dcs=Dcs1 });
+ class -> %% fixme test
+ Dcs = R#gsms_submit_pdu.dcs,
+ Dcs1 = Dcs#gsms_dcs { class = Value },
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { dcs=Dcs1 });
+ alphabet -> %% fixme test
+ Dcs = R#gsms_submit_pdu.dcs,
+ Dcs1 = Dcs#gsms_dcs { alphabet = Value },
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { dcs=Dcs1 });
+ compression -> %% fixme test
+ Dcs = R#gsms_submit_pdu.dcs,
+ Dcs1 = Dcs#gsms_dcs { compression = Value },
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { dcs=Dcs1 });
+ store -> %% fixme test
+ Dcs = R#gsms_submit_pdu.dcs,
+ Dcs1 = Dcs#gsms_dcs { store = Value },
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { dcs=Dcs1 });
+ wait_type -> %% fixme test
+ Dcs = R#gsms_submit_pdu.dcs,
+ Dcs1 = Dcs#gsms_dcs { wait_type = Value },
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { dcs=Dcs1 });
+ %% recognized options, but not for pdu
+ notify -> %% fixme test?
+ set_pdu_opts(Kvs, R);
+ ref -> %% fixme test?
+ set_pdu_opts(Kvs, R);
+ _ ->
+ lager:debug("set_pdu_opts: unknown pdu option ~p",
+ [{Key,Value}]),
+ {error, badarg}
+ end;
+set_pdu_opts([], R) ->
+ {ok,R}.
+
+is_valid_scts({{Date,{H,M,S}},Tz}) ->
+ try calendar:valid_date(Date) of
+ true ->
+ if is_integer(H), H >= 0, H =< 23,
+ is_integer(M), M >= 0, M =< 59,
+ is_integer(S), S >= 0, S =< 59,
+ is_float(Tz) ->
+ true;
+ true ->
+ false
+ end;
+ false -> false
+ catch
+ error:_ -> false
+ end;
+is_valid_scts(_) ->
+ false.
+
+
+
+
+encode_sms(R=#gsms_submit_pdu{}) ->
+ SmscAddr = encode_smsc_addr(R#gsms_submit_pdu.smsc),
+ TP_RP = ?encode_bool(R#gsms_submit_pdu.rp),
+ TP_UDHI = ?encode_bool(R#gsms_submit_pdu.udhi),
+ TP_SRR = ?encode_bool(R#gsms_submit_pdu.srr),
+ TP_VPF = encode_vpf(R#gsms_submit_pdu.vpf),
+ TP_RD = ?encode_bool(R#gsms_submit_pdu.rd),
+ TP_MREF = R#gsms_submit_pdu.mref,
+ {AType,Addr,AddrLen} = encode_addr(R#gsms_submit_pdu.addr),
+ TP_PID = R#gsms_submit_pdu.pid,
+ TP_DCS = encode_dcs(R#gsms_submit_pdu.dcs),
+ TP_VP = encode_vp(R#gsms_submit_pdu.vpf,R#gsms_submit_pdu.vp),
+ TP_UDL = R#gsms_submit_pdu.udl,
+ TP_UD = R#gsms_submit_pdu.ud,
+ ?debug("SmscAddr=~p,Addr=~p,TP_VP=~p,TP_UD=~p\n",
+ [SmscAddr,Addr,TP_VP,TP_UD]),
+ L1 = byte_size(SmscAddr),
+ <<L1,SmscAddr:L1/binary,
+ TP_RP:1,TP_UDHI:1,TP_SRR:1,TP_VPF:2,TP_RD:1,?MTI_SMS_SUBMIT:2,
+ TP_MREF,
+ AddrLen, %% in semi octets
+ AType,
+ Addr/binary,
+ TP_PID,
+ TP_DCS,
+ TP_VP/binary,
+ TP_UDL,
+ TP_UD/binary>>.
+
+
+binary_to_hex(Bin) ->
+ [ element(I+1,{$0,$1,$2,$3,$4,$5,$6,$7,$8,$9,
+ $A,$B,$C,$D,$E,$F}) || <<I:4>> <= Bin ].
+
+hex_to_binary(RawData) ->
+ << << H:4 >> || H <- hex_norm_nibbles(RawData) >>.
+
+hex_norm_nibbles([$\s|Cs]) -> hex_norm_nibbles(Cs);
+hex_norm_nibbles([$\t|Cs]) -> hex_norm_nibbles(Cs);
+hex_norm_nibbles([$\r|Cs]) -> hex_norm_nibbles(Cs);
+hex_norm_nibbles([$\n|Cs]) -> hex_norm_nibbles(Cs);
+hex_norm_nibbles([C|Cs]) ->
+ if C >= $0, C =< $9 -> [C-$0 | hex_norm_nibbles(Cs)];
+ C >= $A, C =< $F -> [(C-$A)+10 | hex_norm_nibbles(Cs)];
+ C >= $a, C =< $f -> [(C-$A)+10 | hex_norm_nibbles(Cs)]
+ end;
+hex_norm_nibbles([]) ->
+ [].
+
+%%
+%% addresses
+%%
+
+decode_smsc_addr_type(<<AType,_/binary>>) ->
+ decode_addr_type(AType);
+decode_smsc_addr_type(<<>>) ->
+ undefined.
+
+decode_smsc_addr(<<AType,Ds/binary>>) ->
+ T = decode_addr_type(AType),
+ A = decode_addr_semi(T, Ds),
+ #gsms_addr { type=T, addr=A };
+decode_smsc_addr(<<>>) ->
+ undefined.
+
+decode_vpf(2#00) -> none;
+decode_vpf(2#10) -> relative;
+decode_vpf(2#01) -> enhanced;
+decode_vpf(2#11) -> absolute.
+
+encode_vpf(none) -> 2#00;
+encode_vpf(relative) -> 2#10;
+encode_vpf(enhanced) -> 2#01;
+encode_vpf(absolute) -> 2#11.
+
+%% validity period in seconds
+decode_vp(none,<<>>) -> 0;
+decode_vp(relative,<<VP>>) ->
+ if VP =< 143 -> (VP+1)*5*60; %% 1..12 hours (+ 5min)
+ VP =< 167 -> (12*60 + (VP-143)*30)*60; %% 12..24 hours (+ 30min)
+ VP =< 196 -> (VP-166)*24*60*60; %% 2 .. 30 days
+ true -> ((VP-192)*7*24)*60*60 %% 5 .. 63 weeks
+ end;
+decode_vp(absolute,V) ->
+ decode_scts(V).
+
+encode_vp(none, _) -> <<>>;
+encode_vp(relative, V) ->
+ Min_0 = V div 60, %% number of minutes
+ Hour_1 = Min_0 div 60, %% number of hours
+ _Min_1 = Min_0 rem 60, %% mintes with in the hour
+ Day_2 = Hour_1 div 24, %% days
+ _Hour_2 = Hour_1 rem 24, %% hour with in day
+ Week_3 = Day_2 div 7, %% weeks
+ _Day_3 = Day_2 rem 7, %% day within week
+ if Week_3 >= 5, Week_3 =< 64 ->
+ <<((Week_3-5)+197)>>;
+ Day_2 >= 2, Day_2 =< 30 ->
+ <<((Day_2-2)+168)>>;
+ Min_0 >= 12*60, Min_0 =< 24*60 ->
+ <<(((Min_0 - 12*60) div 30) + 143)>>;
+ Min_0 >= 5 ->
+ <<((Min_0 div 5)-1)>>;
+ true ->
+ <<0>>
+ end;
+encode_vp(absolute,V) ->
+ encode_scts(V).
+
+encode_smsc_addr(undefined) ->
+ <<>>;
+encode_smsc_addr(#gsms_addr {type=T,addr=A}) ->
+ AType = encode_addr_type(T),
+ Ds = encode_addr_semi(T, A),
+ <<AType,Ds/binary>>.
+
+%% Handle the ones in use only! (FIXME)
+decode_addr_type(2#10000001) -> unknown;
+decode_addr_type(2#10010001) -> international;
+decode_addr_type(2#10101000) -> national;
+decode_addr_type(T) -> T.
+
+encode_addr_type(unknown) -> 16#81; %% 129
+encode_addr_type(international) -> 16#91; %% 145
+encode_addr_type(national) -> 16#A8;
+encode_addr_type(T) -> T.
+
+decode_addr(T, <<>>) ->
+ #gsms_addr { type=T, addr=[] };
+decode_addr(T, Data) ->
+ A = decode_addr_semi(T, Data),
+ #gsms_addr { type=T, addr=A }.
+
+encode_addr(#gsms_addr { type=T, addr=A }) ->
+ Addr = encode_addr_semi(T, A),
+ AddrLen = length(A), %% number semi digits
+ {encode_addr_type(T),Addr,AddrLen}.
+
+decode_addr_semi(international, SemiOctets) ->
+ [$+|decode_semi_octets(SemiOctets)];
+decode_addr_semi(_, SemiOctets) ->
+ decode_semi_octets(SemiOctets).
+
+encode_addr_semi(international, [$+|Ds]) ->
+ encode_semi_octets(Ds);
+encode_addr_semi(_, Ds) ->
+ encode_semi_octets(Ds).
+
+
+decode_bcd(<<H:4,L:4, T/binary>>) ->
+ [H*10+L | decode_bcd(T)];
+decode_bcd(<<>>) ->
+ [].
+
+decode_swapped_bcd(Bin) ->
+ [(H*10+L) || <<L:4,H:4>> <= Bin].
+
+encode_swapped_bcd(Ds) ->
+ << <<(D rem 10):4, (D div 10):4>> || D <- Ds >>.
+
+decode_semi_octets(<<>>) -> [];
+decode_semi_octets(<<16#F:4,D:4>>) -> [D+$0];
+decode_semi_octets(<<L:4,H:4,Ds/binary>>) -> [H+$0,L+$0|decode_semi_octets(Ds)].
+
+encode_semi_octets([]) -> <<>>;
+encode_semi_octets([D]) -> <<16#F:4,(D-$0):4>>;
+encode_semi_octets([H,L|Ds]) ->
+ <<(L-$0):4,(H-$0):4, (encode_semi_octets(Ds))/binary>>.
+
+decode_scts(Bin) ->
+ [YY,Mon,Day,Hour,Min,Sec,Tz] = decode_swapped_bcd(Bin),
+ {Year0,_,_} = date(), %% Year0 is current year
+ YY0 = Year0 rem 100, %% YY0 curent year two digits
+ Century = Year0 - YY0, %% Current Century,
+ Year = if YY > YY0+1 ->
+ YY + (Century - 100);
+ true ->
+ YY + Century
+ end,
+ Date = {Year,Mon,Day},
+ Time = {Hour,Min,Sec},
+ Tz0 = (15*(Tz band 16#7f))/60,
+ TzH = if (Tz band 16#80) =/= 0 -> -Tz0; true -> Tz0 end,
+ {{Date,Time},TzH}.
+
+encode_scts({{{Year0,Mon,Day},{Hour,Min,Sec}},TzH}) ->
+ Year = Year0 - 2000,
+ Tz = if TzH < 0 ->
+ 16#80 bor (trunc((TzH * 60)/15) band 16#7f);
+ true ->
+ trunc((TzH * 60)/15) band 16#7f
+ end,
+ encode_swapped_bcd([Year,Mon,Day,Hour,Min,Sec,Tz]).
+
+
+decode_dcs(V) ->
+ if (V bsr 6) =:= 2#00 -> %% 00xxxxxx
+ #gsms_dcs { type=message,
+ compression = if V band 2#00100000 =:= 0 ->
+ uncompressed;
+ true -> compressed
+ end,
+ alphabet = case V band 2#1100 of
+ 2#0000 -> default;
+ 2#0100 -> octet;
+ 2#1000 -> ucs2;
+ 2#1100 -> reserved
+ end,
+ class = case V band 2#11 of
+ 2#00 -> alert;
+ 2#01 -> me;
+ 2#10 -> sim;
+ 2#11 -> te
+ end };
+ (V bsr 4) =:= 2#1100 -> %% message waiting, discard
+ dcs_message_waiting(V,discard,default);
+ (V bsr 4) =:= 2#1101 -> %% message waiting, store
+ dcs_message_waiting(V,store,default);
+ (V bsr 4) =:= 2#1110 -> %% message waiting, store
+ dcs_message_waiting(V,store,ucs2);
+ (V bsr 4) =:= 2#1111 ->
+ #gsms_dcs { type=data,compression=uncompressed,
+ alphabet=if V band 2#0100 =:= 0 -> octet;
+ true -> default
+ end,
+ class = case V band 2#11 of
+ 2#00 -> alert;
+ 2#01 -> me;
+ 2#10 -> sim;
+ 2#11 -> te
+ end
+ };
+ true ->
+ lager:error("unknown dcs value ~p", [V]),
+ #gsms_dcs {}
+ end.
+
+dcs_message_waiting(V,Store,Alphabet) ->
+ #gsms_dcs {type=message_waiting,
+ compression=uncompressed,
+ alphabet=Alphabet,
+ store=Store,
+ active=if V band 2#0100 =:= 0 ->
+ inactive;
+ true -> active
+ end,
+ wait_type = case V band 2#11 of
+ 2#00 -> voicemail;
+ 2#01 -> fax;
+ 2#10 -> email;
+ 2#11 -> other
+ end
+ }.
+
+
+encode_dcs(#gsms_dcs{type=message,compression=Comp,
+ alphabet=Alpha,class=Class}) ->
+ 2#00000000 +
+ if Comp =:= compressed -> 2#00100000;
+ true -> 2#00000000
+ end +
+ case Alpha of
+ default -> 2#0000;
+ octet -> 2#0100;
+ ucs2 -> 2#1000;
+ reserved -> 2#1100
+ end +
+ case Class of
+ alert -> 2#00;
+ me -> 2#01;
+ sim -> 2#10;
+ te -> 2#11
+ end;
+encode_dcs(#gsms_dcs {type=message_waiting,compression=uncompressed,
+ alphabet=Alphabet,store=Store,active=Active,
+ wait_type=WaitType}) ->
+ if Alphabet =:= ucs2, Store =:= store ->
+ 2#11110000;
+ Alphabet =:= default, Store =:= store ->
+ 2#11100000;
+ Alphabet =:= default, Store =:= discard ->
+ 2#11000000
+ end +
+ if Active =:= active -> 2#0100;
+ true -> 2#0000
+ end +
+ case WaitType of
+ voicemail -> 2#00;
+ fax -> 2#01;
+ email -> 2#10;
+ other -> 2#11
+ end;
+encode_dcs(#gsms_dcs{type=data,compression=uncompressed,
+ alphabet=Alphabet,class=Class}) ->
+ 2#11110000 +
+ if Alphabet =:= octet -> 2#100;
+ true -> 2#000
+ end +
+ case Class of
+ alert -> 2#00;
+ me -> 2#01;
+ sim -> 2#10;
+ te -> 2#11
+ end.
+
+
+decode_ud(#gsms_dcs{compression=uncompressed,alphabet=default},
+ 0, UDL, Data) ->
+ L7 = UDL, %% number of septets
+ <<Gsm8:L7/binary,_/binary>> = decode_gsm7(Data),
+ {[],gsms_0338:decode(Gsm8)};
+decode_ud(#gsms_dcs{compression=uncompressed,alphabet=default},
+ 1, UDL, Data = <<UDHL,_/binary>>) ->
+ %% UDHL as number of septets and including it self
+ UDHL7 = ((UDHL+1)*8 + 6) div 7,
+ L7 = UDL-UDHL7, %% number of septets in message
+ <<_:UDHL7/binary,Gsm8:L7/binary,_/binary>> = decode_gsm7(Data),
+ <<_,UDHBin:UDHL/binary,_/binary>> = Data,
+ UDH = decode_udh(UDHBin),
+ {UDH,gsms_0338:decode(Gsm8)};
+decode_ud(#gsms_dcs{compression=uncompressed,alphabet=ucs2},
+ 0,UDL,Data1) ->
+ case Data1 of
+ <<Data2:UDL/binary, _/binary>> ->
+ Ucs = [ U || <<U:16/big>> <= Data2 ],
+ {[],Ucs}
+ end;
+decode_ud(#gsms_dcs{compression=uncompressed,alphabet=ucs2},
+ 1, UDL, Data1 = <<UDHL,UDHBin:UDHL/binary,_/binary>>) ->
+ Pad = (UDHL+1) band 1,
+ UDL8 = UDL - (UDHL+1+Pad),
+ %% io:format("UDL=~w, Pad=~w, UDL8=~w\n", [UDL,Pad,UDL8]),
+ case Data1 of
+ <<_,_:UDHL/binary,_:Pad/unit:8,Data2:UDL8/binary>> ->
+ UDH = decode_udh(UDHBin),
+ {UDH, [ U || <<U:16/big>> <= Data2 ]}
+ end;
+decode_ud(#gsms_dcs{compression=uncompressed,alphabet=octet},
+ 0,UDL, Data1) ->
+ case Data1 of
+ <<Data2:UDL/binary>> ->
+ {[], binary_to_list(Data2)}
+ end;
+decode_ud(#gsms_dcs{compression=uncompressed,alphabet=octet},
+ 1, UDL, <<UDHL,UDHBin:UDHL/binary,Data1/binary>>) ->
+ UDL8 = UDL - (UDHL+1),
+ case Data1 of
+ <<_,_:UDHL/binary,Data2:UDL8/binary>> ->
+ UDH = decode_udh(UDHBin),
+ {UDH, binary_to_list(Data2)}
+ end.
+
+
+
+encode_ud(Dcs0=#gsms_dcs{alphabet=Alpha0},
+ Message, UDH, Ref) ->
+ %% fixme: check that there are no concat header ?
+ %% calculate total number of letters in message and the encoding
+ {Alpha,_TotLen} = message_len(Message,Alpha0),
+ Dcs = Dcs0#gsms_dcs{alphabet=Alpha},
+ case encode_ud_(Dcs, Message, UDH) of
+ {UDL,UD,[],UDHI} ->
+ {Dcs, [{UDL,iolist_to_binary(UD),UDHI}]};
+ {_UDL,_UD,_Message1,_} ->
+ {Dcs,encode_ud_loop(Dcs, Message, UDH, Ref, 1, [])}
+ end.
+
+encode_ud_loop(Dcs, Message, UDH, Ref, I, Acc) ->
+ case encode_ud_(Dcs, Message, [{concat,Ref,0,I}|UDH]) of
+ {UDL,UD,[],UDHI} ->
+ ud_patch([{UDL,UD,UDHI}|Acc], I, []);
+ {UDL,UD,Message1,UDHI} ->
+ UDH1 = [], %% kill header for segments
+ encode_ud_loop(Dcs,Message1,UDH1,Ref,I+1,[{UDL,UD,UDHI} | Acc])
+ end.
+
+%% patch concat and reverse list
+ud_patch([{UDL,UD,UDHI}|Tail], N, Acc) ->
+ case UD of
+ [UDHL,[<<?IE_CONCAT8,3,Ref:8,_N,I>>|UDH],Data] ->
+ UD1 = iolist_to_binary([UDHL,[<<?IE_CONCAT8,3,Ref:8,N,I>>|UDH],
+ Data]),
+ ud_patch(Tail, N, [{UDL,UD1,UDHI}|Acc]);
+ [UDHL,[<<?IE_CONCAT16,4,Ref:16,_N,I>>|UDH],Data] ->
+ UD1 = iolist_to_binary([UDHL,[<<?IE_CONCAT16,4,Ref:16,N,I>>|UDH],
+ Data]),
+ ud_patch(Tail, N, [{UDL,UD1,UDHI}|Acc])
+ end;
+ud_patch([], _N, Acc) ->
+ Acc.
+
+
+encode_ud_(#gsms_dcs{compression=uncompressed,alphabet=default},
+ Message, UDH) ->
+ case len_udh(UDH) of
+ 0 ->
+ {Gsm8,Message1} = gsms_0338:encode(Message,?MAX_7BIT_LEN),
+ L7 = byte_size(Gsm8), %% number of septets
+ L8 = (L7*7 + 7) div 8, %% number of octets needed
+ <<Gsm7:L8/binary,_/binary>> = encode_gsm7(Gsm8),
+ {L7,Gsm7,Message1,false};
+ UDHL ->
+ %% UDHL as number of septets and including it self
+ UDHL8 = (UDHL+1)*8, %% number of bits including self
+ UDHL7 = (UDHL8 + 6) div 7, %% number of septets
+ UDHData = encode_udh(UDH),
+ Message0 = prepend(UDHL7,0,Message),
+ {Gsm8,Message1} = gsms_0338:encode(Message0,?MAX_7BIT_LEN-UDHL7),
+ Pad = UDHL7*7 - UDHL8,
+ L7 = byte_size(Gsm8)-UDHL7, %% number of septets in message
+ L8 = (L7*7+Pad+7) div 8, %% number of octets needed
+ <<_,_:UDHL/binary,Gsm7:L8/binary,_/binary>> = encode_gsm7(Gsm8),
+ {UDHL7+L7,[UDHL,UDHData,Gsm7],Message1,true}
+ end;
+encode_ud_(#gsms_dcs{compression=uncompressed,alphabet=ucs2}, Message, UDH) ->
+ case len_udh(UDH) of
+ 0 ->
+ {Ucs2,Message1} = encode_ucs2(Message,?MAX_16BIT_LEN,[]),
+ N = byte_size(Ucs2),
+ {N, Ucs2, Message1, false};
+ UDHL ->
+ UDHData = encode_udh(UDH),
+ Pad = (UDHL+1) band 1,
+ UDHL16 = (UDHL+1+Pad) div 2, %% check this
+ {Ucs2,Message1} = encode_ucs2(Message,?MAX_16BIT_LEN-UDHL16,[]),
+ N = byte_size(Ucs2),
+ Ucs21 = prepend(Pad,0,Ucs2),
+ UDL = N + UDHL + 1 + Pad,
+ {UDL,[UDHL,UDHData,Ucs21],Message1,true}
+ end;
+encode_ud_(#gsms_dcs{compression=uncompressed,alphabet=octet}, Message, UDH) ->
+ case len_udh(UDH) of
+ 0 ->
+ {Octets,Message1} = encode_octets(Message,?MAX_8BIT_LEN,[]),
+ N = byte_size(Octets),
+ {N, Octets, Message1, false};
+ UDHL ->
+ UDHData = encode_udh(UDH),
+ {Octets,Message1} = encode_octets(Message,?MAX_8BIT_LEN-UDHL,[]),
+ N = byte_size(Octets),
+ {UDHL+N,[UDHL,UDHData,Octets],Message1,true}
+ end.
+
+
+
+encode_octets(Cs, 0, Acc) ->
+ {iolist_to_binary(lists:reverse(Acc)), Cs};
+encode_octets([C|Cs], I, Acc) ->
+ encode_octets(Cs, I-1, [C|Acc]);
+encode_octets([], _I, Acc) ->
+ {iolist_to_binary(lists:reverse(Acc)), []}.
+
+%%
+%% Fixme: add BOM (byte order mark) character
+%% BOM = 0xFEFF, if BOM is received as 0xFEFF then
+%% it is coded in default big endian, else it
+%% will be read as 0xFFFE and is coded in little endian.
+%%
+encode_ucs2(Cs, 0, Acc) ->
+ {iolist_to_binary(lists:reverse(Acc)), Cs};
+encode_ucs2([C|Cs], I, Acc) ->
+ encode_ucs2(Cs, I-1, [<<C:16/big>>|Acc]);
+encode_ucs2([], _I, Acc) ->
+ {iolist_to_binary(lists:reverse(Acc)), []}.
+
+
+
+message_len(Cs) ->
+ message_len_auto_(Cs,0).
+
+message_len(Cs,auto) ->
+ message_len_auto_(Cs,0);
+message_len(Cs,default) ->
+ {default,message_len_default_(Cs,0)};
+message_len(Cs,octet) ->
+ {octet,message_len_octet_(Cs,0)};
+message_len(Cs,ucs2) ->
+ {ucs2,message_len_ucs2_(Cs,0)}.
+
+
+message_len_auto_(Cs,N) ->
+ try message_len_default_(Cs,N) of
+ Len7 -> {default,Len7}
+ catch
+ error:_ ->
+ {ucs2,message_len_ucs2_(Cs,N)}
+ end.
+
+message_len_default_([C|Cs], Len) ->
+ case gsms_0338:encode_char(C) of
+ [_Esc,_Y] -> message_len_default_(Cs, Len+2);
+ _Y -> message_len_default_(Cs, Len+1)
+ end;
+message_len_default_([], Len) ->
+ Len.
+
+message_len_octet_([C|Cs], Len) when ?is_byte(C) ->
+ message_len_octet_(Cs, Len+1);
+message_len_octet_([], Len) ->
+ Len.
+
+message_len_ucs2_([C|Cs], Len) when ?is_short(C) ->
+ message_len_ucs2_(Cs, Len+1);
+message_len_ucs2_([], Len) ->
+ Len.
+
+
+%%
+%% Calculate number of bytes in UDH (not incuding the length byte)
+%%
+-spec len_udh(IEs::[gsms_ie()]) -> integer().
+
+len_udh([]) ->
+ 0;
+len_udh(IEs) ->
+ len_udh(IEs, 0).
+
+len_udh([{concat,Ref,_N,_I}|IEs],Len) ->
+ if Ref >= 0, Ref =< 16#ff -> len_udh(IEs, Len+5);
+ Ref >= 0, Ref =< 16#ffff -> len_udh(IEs, Len+6)
+ end;
+len_udh([{concat8,_Ref,_N,_I}|IEs], Len) -> len_udh(IEs,Len+5);
+len_udh([{concat16,_Ref,_N,_I}|IEs], Len) -> len_udh(IEs,Len+6);
+len_udh([{port,Dst,Src}|IEs], Len) ->
+ if Dst >= 0, Dst =< 16#ff,
+ Src >= 0, Src =< 16#ff ->
+ len_udh(IEs,Len+4);
+ Dst >= 0, Dst =< 16#ffff,
+ Src >= 0, Src =< 16#ffff ->
+ len_udh(IEs,Len+6)
+ end;
+len_udh([{port8,_Dst,_Src}|IEs], Len) -> len_udh(IEs,Len+4);
+len_udh([{port16,_Dst,_Src}|IEs],Len) -> len_udh(IEs,Len+6);
+len_udh([], Len) -> Len.
+
+%%
+%% Encode user data header IEs
+%%
+-spec encode_udh(IEs::[gsms_ie()]) -> [binary()].
+
+encode_udh([]) ->
+ [];
+encode_udh(IEs) ->
+ encode_udh(IEs, []).
+
+encode_udh([{concat,Ref,N,I}|IEs], Acc) ->
+ if Ref >= 0, Ref =< 16#ff ->
+ encode_udh(IEs,[ <<?IE_CONCAT8,3,Ref,N,I>>|Acc]);
+ Ref >= 0, Ref =< 16#ffff ->
+ encode_udh(IEs,[ <<?IE_CONCAT16,4,Ref:16,N,I>> | Acc ])
+ end;
+encode_udh([{concat8,Ref,N,I}|IEs], Acc) ->
+ encode_udh(IEs,[ <<?IE_CONCAT8,3,Ref,N,I>>|Acc]);
+encode_udh([{concat16,Ref,N,I}|IEs], Acc) ->
+ encode_udh(IEs,[ <<?IE_CONCAT16,4,Ref:16,N,I>> | Acc ]);
+encode_udh([{port,Dst,Src}|IEs], Acc) ->
+ if Dst >= 0, Dst =< 16#ff,
+ Src >= 0, Src =< 16#ff ->
+ encode_udh(IEs,[ <<?IE_PORT8,2,Dst,Src>> | Acc]);
+ Dst >= 0, Dst =< 16#ffff,
+ Src >= 0, Src =< 16#ffff ->
+ encode_udh(IEs,[<<?IE_PORT16,4,Dst:16,Src:16>> | Acc])
+ end;
+encode_udh([{port8,DstPort,SrcPort}|IEs], Acc) ->
+ encode_udh(IEs,[ <<?IE_PORT8,2,DstPort,SrcPort>> | Acc]);
+encode_udh([{port16,DstPort,SrcPort}|IEs],Acc) ->
+ encode_udh(IEs,[<<?IE_PORT16,4,DstPort:16,SrcPort:16>> | Acc]);
+encode_udh([{ie,IE,Args}|IEs], Acc) when is_binary(Args) ->
+ encode_udh(IEs,[<<IE,(byte_size(Args)),Args/binary>> | Acc]);
+encode_udh([], Acc) ->
+ reverse(Acc).
+
+
+decode_udh(UDHBin) when is_binary(UDHBin) ->
+ decode_udh(UDHBin, []).
+
+decode_udh(<<?IE_CONCAT8,3,Ref,N,I,IEs/binary>>, Acc) ->
+ decode_udh(IEs,[{concat,Ref,N,I}|Acc]);
+decode_udh(<<?IE_CONCAT16,4,Ref:16,N,I,IEs/binary>>,Acc) ->
+ decode_udh(IEs,[{concat,Ref,N,I}|Acc ]);
+decode_udh(<<?IE_PORT8,2,DstPort,SrcPort,IEs/binary>>, Acc) ->
+ decode_udh(IEs,[{port,DstPort,SrcPort}| Acc]);
+decode_udh(<<?IE_PORT16,4,DstPort:16,SrcPort:16,IEs/binary>>,Acc) ->
+ decode_udh(IEs,[{port,DstPort,SrcPort} | Acc]);
+decode_udh(<<IE,N,Args:N/binary,IEs/binary>>,Acc) ->
+ decode_udh(IEs,[{ie,IE,Args}|Acc]);
+decode_udh(<<>>, Acc) ->
+ reverse(Acc).
+
+
+decode_gsm7(<<X1:1,Y7:7, X2:2,Y6:6, X3:3,Y5:5, X4:4,Y4:4,
+ X5:5,Y3:3, X6:6,Y2:2, X7:7,Y1:1, More/binary>>) ->
+ <<0:1,Y7:7,
+ 0:1,Y6:6,X1:1,
+ 0:1,Y5:5,X2:2,
+ 0:1,Y4:4,X3:3,
+ 0:1,Y3:3,X4:4,
+ 0:1,Y2:2,X5:5,
+ 0:1,Y1:1,X6:6,
+ 0:1,X7:7,
+ (decode_gsm7(More))/binary>>;
+decode_gsm7(<<>>) ->
+ <<>>;
+decode_gsm7(More) when byte_size(More) > 0,
+ byte_size(More) < 7 ->
+ Pad = 7 - byte_size(More), %% 6..1
+ decode_gsm7(<<More/binary, 0:Pad/unit:8>>).
+
+
+
+%% encode gsm7 bit encoding
+encode_gsm7(<<0:1,Y7:7, 0:1,Y6:6,X1:1, 0:1,Y5:5,X2:2, 0:1,Y4:4,X3:3,
+ 0:1,Y3:3,X4:4, 0:1,Y2:2,X5:5, 0:1,Y1:1,X6:6, 0:1, X7:7,
+ More/binary >>) ->
+ <<X1:1,Y7:7, X2:2,Y6:6, X3:3,Y5:5, X4:4,Y4:4,
+ X5:5,Y3:3, X6:6,Y2:2, X7:7,Y1:1,
+ (encode_gsm7(More))/binary >>;
+encode_gsm7(<<>>) ->
+ <<0>>; %% extra bits to compensate for UDH padding.
+encode_gsm7(More) when byte_size(More) > 0,
+ byte_size(More) < 8 ->
+ Pad = 8 - byte_size(More), %% 7..1
+ encode_gsm7(<<More/binary, 0:Pad/unit:8>>).
+
+%% prepend N number of E's to a list
+prepend(N,E,List) when is_list(List) ->
+ prepend_list(N,E,List);
+prepend(N,E,Binary) when is_binary(Binary) ->
+ <<E:N/unit:8, Binary/binary>>.
+
+prepend_list(0,_,Acc) -> Acc;
+prepend_list(I,E,Acc) -> prepend_list(I-1,E,[E|Acc]).
+
+
+
+is_gsms_record(P) ->
+ case P of
+ #gsms_addr{} -> true;
+ #gsms_submit_pdu{} -> true;
+ #gsms_deliver_pdu{} -> true;
+ _ -> false
+ end.
+
+separator(D,S) ->
+ case is_gsms_record(D) of
+ true -> "";
+ false -> S
+ end.
+
+indent(I) ->
+ lists:duplicate(I, $\s).
+
+dump(P) ->
+ dump_json(P).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Format values and records in JSON format
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+dump_json(P) ->
+ io:format("~s\n", [fmt_json(P)]).
+
+fmt_json(P) ->
+ case is_gsms_record(P) of
+ true -> fmt_json_record(0,P,"\n");
+ false -> fmt_json_value(0,P,"\n")
+ end.
+
+fmt_json_record(I,P=#gsms_addr{},N) ->
+ fmt_json_record(I,P,record_info(fields,gsms_addr),N);
+fmt_json_record(I,P=#gsms_submit_pdu{},N) ->
+ fmt_json_record(I,P,record_info(fields,gsms_submit_pdu),N);
+fmt_json_record(I,P=#gsms_deliver_pdu{},N) ->
+ fmt_json_record(I,P,record_info(fields,gsms_deliver_pdu),N).
+
+fmt_json_record(I,P,Fs,N) ->
+ [R|Ds] = tuple_to_list(P),
+ [ "{",N,
+ fmt_json_fields(I+2, R, [struct|Fs], [R|Ds], N),
+ N, indent(I), "}" ].
+
+fmt_json_fields(I,R,[F],[D],N) ->
+ [ [indent(I),fmt_json_field(I,R,F,D,N) ] ];
+fmt_json_fields(I,R,[F|Fs],[D|Ds],N) ->
+ [ [indent(I),fmt_json_field(I,R,F,D,N),",",N] |
+ fmt_json_fields(I,R,Fs,Ds,N)];
+fmt_json_fields(_I,_R,[],[],_N) ->
+ [].
+
+fmt_json_field(I,R,F,D,N) ->
+ Fk = atom_to_list(F),
+ Dk = fmt_json_value(I,R,F,D,N),
+ [Fk,": ",Dk].
+
+fmt_json_value(I,D,N) ->
+ fmt_json_value(I,undefined,undefined,D,N).
+
+fmt_json_value(I,R,F,D,N) ->
+ if
+ is_boolean(D) -> [atom_to_list(D)];
+ is_integer(D) -> [integer_to_list(D)];
+ is_atom(D) -> [?Q,atom_to_list(D),?Q];
+ is_binary(D), R =:= ipv4, (F =:= src orelse F =:= dst) ->
+ <<X1,X2,X3,X4>> = D,
+ [?Q,io_lib:format("~w.~w.~w.~w", [X1,X2,X3,X4]),?Q];
+ is_binary(D) ->
+ io_lib:format("~p", [D]);
+ is_tuple(D) ->
+ case is_gsms_record(D) of
+ true ->
+ fmt_json_record(I+2,D,N);
+ false ->
+ io_lib:format("~p", [D])
+ end;
+ is_list(D) ->
+ try iolist_size(D) of
+ _Sz -> [?Q,D,?Q]
+ catch
+ error:_ -> %% formt as JSON array?
+ fmt_json_array(D)
+ end
+ end.
+
+fmt_json_array(Ds) ->
+ ["[", fmt_json_elems(Ds), "]"].
+
+fmt_json_elems([D]) ->
+ fmt_json_value(0,D,"");
+fmt_json_elems([D|Ds]) ->
+ [fmt_json_value(0,D,""),"," | fmt_json_elems(Ds)];
+fmt_json_elems([]) -> [].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% YANG FORMAT
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+dump_yang(P) ->
+ io:format("~s\n", [fmt_yang(P)]).
+
+fmt_yang(P) ->
+ case is_gsms_record(P) of
+ true -> fmt_yang_record(0,P,"\n");
+ false -> fmt_yang_value(0,P,"\n")
+ end.
+
+fmt_yang_record(I,P=#gsms_addr{},N) ->
+ fmt_yang_record(I,P,record_info(fields,gsms_addr),N);
+fmt_yang_record(I,P=#gsms_submit_pdu{},N) ->
+ fmt_yang_record(I,P,record_info(fields,gsms_submit_pdu),N);
+fmt_yang_record(I,P=#gsms_deliver_pdu{},N) ->
+ fmt_yang_record(I,P,record_info(fields,gsms_deliver_pdu),N).
+
+fmt_yang_record(I,P,Fs,N) ->
+ [R|Ds] = tuple_to_list(P),
+ [ atom_to_list(R), " {", N,
+ fmt_yang_fields(I+2, R, Fs, Ds, N),
+ indent(I), "}" ].
+
+fmt_yang_fields(I,R,[F|Fs],[D|Ds],N) ->
+ [ [indent(I),fmt_yang_field(I,R,F,D,N),separator(D,";"),N] |
+ fmt_yang_fields(I,R,Fs,Ds,N)];
+fmt_yang_fields(_I,_R,[],[],_N) ->
+ [].
+
+fmt_yang_field(I,R,F,D,N) ->
+ Fk = atom_to_list(F),
+ Dk = fmt_yang_value(I,R,F,D,N),
+ [Fk," ",Dk].
+
+fmt_yang_value(I,D,N) ->
+ fmt_yang_value(I,undefined,undefined,D,N).
+
+fmt_yang_value(I,R,F,D,N) ->
+ if
+ is_boolean(D) -> [atom_to_list(D)];
+ is_integer(D) -> [integer_to_list(D)];
+ is_atom(D) -> [atom_to_list(D)];
+ is_binary(D), R =:= ipv4, (F =:= src orelse F =:= dst) ->
+ <<X1,X2,X3,X4>> = D,
+ [?Q,io_lib:format("~w.~w.~w.~w", [X1,X2,X3,X4]),?Q];
+ is_binary(D) -> %% fixme!
+ [?Q,io_lib:format("~p", [D]),?Q];
+ is_tuple(D) ->
+ case is_gsms_record(D) of
+ true ->
+ fmt_yang_record(I+2,D,N);
+ false ->
+ io_lib:format("~p", [D])
+ end;
+ is_list(D) ->
+ try iolist_size(D) of
+ _Sz -> [?Q,D,?Q]
+ catch
+ error:_ -> %% formt as YANG array?
+ fmt_yang_array(D)
+ end
+ end.
+
+fmt_yang_array(Ds) ->
+ fmt_yang_elems(Ds).
+
+fmt_yang_elems([D]) ->
+ fmt_yang_value(0,D,"");
+fmt_yang_elems([D|Ds]) ->
+ [fmt_yang_value(0,D,"")," " | fmt_yang_elems(Ds)];
+fmt_yang_elems([]) -> [].
diff --git a/deps/gsms/src/gsms_if_sup.erl b/deps/gsms/src/gsms_if_sup.erl
new file mode 100644
index 0000000..3916a96
--- /dev/null
+++ b/deps/gsms/src/gsms_if_sup.erl
@@ -0,0 +1,78 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2013, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Malotte W Lönne <malotte@malotte.net>
+%%% @copyright (C) 2013, Tony Rogvall
+%%% @doc
+%%% sms interface supervisor
+%%%
+%%% Created: 2013 by Malotte W Lönne
+%%% @end
+
+-module(gsms_if_sup).
+
+-behaviour(supervisor).
+
+-include("log.hrl").
+%% external exports
+-export([start_link/0,
+ start_link/1,
+ stop/1]).
+
+%% supervisor callbacks
+-export([init/1]).
+
+%%%----------------------------------------------------------------------
+%%% API
+%%%----------------------------------------------------------------------
+start_link(Args) ->
+ ?info("~p: start_link: args = ~p\n", [?MODULE, Args]),
+ case supervisor:start_link({local, ?MODULE}, ?MODULE, Args) of
+ {ok, Pid} ->
+ {ok, Pid, {normal, Args}};
+ Error ->
+ Error
+ end.
+
+start_link() ->
+ supervisor:start_link({local,?MODULE}, ?MODULE, []).
+
+stop(_StartArgs) ->
+ ok.
+
+%%%----------------------------------------------------------------------
+%%% Callback functions from supervisor
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%%----------------------------------------------------------------------
+init(Args) ->
+ ?info("~p: init: args = ~p,\n pid = ~p\n", [?MODULE, Args, self()]),
+ Interfaces =
+ lists:foldr(
+ fun({Mod,I,Opts},Acc) ->
+ Spec={{Mod,I},
+ {Mod, start_link, [I,Opts]},
+ permanent, 5000, worker, [Mod]},
+ [Spec | Acc]
+ end, [],
+ case application:get_env(gsms, interfaces) of
+ undefined -> [];
+ {ok,IfList} when is_list(IfList) ->
+ IfList
+ end),
+ ?info("~p: init: starting interfaces ~p", [?MODULE, Interfaces]),
+ {ok,{{one_for_one,3,5}, Interfaces}}.
diff --git a/deps/gsms/src/gsms_router.erl b/deps/gsms/src/gsms_router.erl
new file mode 100644
index 0000000..bb027f8
--- /dev/null
+++ b/deps/gsms/src/gsms_router.erl
@@ -0,0 +1,464 @@
+%%%---- BEGIN COPYRIGHT --------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ----------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @doc
+%%% Message router for SMS
+%%%
+%%% Created : 17 Apr 2013 by Tony Rogvall
+%%% @end
+%%%-------------------------------------------------------------------
+-module(gsms_router).
+
+-behaviour(gen_server).
+
+-include("log.hrl").
+-include("../include/gsms.hrl").
+
+%% API
+-export([start_link/1,
+ send/2,
+ subscribe/1,
+ unsubscribe/1,
+ join/2,
+ input_from/2]).
+
+%% gen_server callbacks
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
+
+%% testing
+-export([dump/0]).
+
+-define(SERVER, ?MODULE).
+
+-record(subscription,
+ {
+ pid :: pid(), %% subscriber process
+ ref :: reference(), %% reference / monitor
+ filter = [] :: filter()
+ }).
+
+-record(interface,
+ {
+ pid :: pid(), %% interface pid
+ mon :: reference(), %% monitor reference
+ bnumber :: gsms_addr(), %% modem msisdn
+ rssi = 99 :: integer(), %% last known rssi value
+ attributes = [] :: [{atom(),term()}] %% general match keys
+ }).
+
+-record(state,
+ {
+ csq_ival = 0,
+ csq_tmr,
+ subs = [] :: [#subscription{}],
+ ifs = [] :: [#interface{}]
+ }).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+-spec send(Options::list({Key::atom(), Value::term()}), Body::string()) ->
+ {ok, Ref::reference()} |
+ {error, Reason::term()}.
+
+send(Opts, Body) ->
+ gen_server:call(?SERVER, {send, Opts, Body}).
+
+-spec subscribe(Filter::[filter()]) -> {ok,Ref::reference()} |
+ {error,Reason::term()}.
+
+subscribe(Filter) ->
+ gen_server:call(?SERVER, {subscribe, self(), Filter}).
+
+-spec unsubscribe(Ref::reference()) -> ok.
+
+unsubscribe(Ref) ->
+ gen_server:call(?SERVER, {unsubscribe, Ref}).
+
+join(BNumber,Attributes) ->
+ gen_server:call(?SERVER, {join,self(),BNumber,Attributes}).
+
+%%
+%% Called from gsms_0705 backend to enter incoming message
+%%
+input_from(BNumber, Sms) ->
+ lager:debug("message input modem:~s, message = ~p\n",
+ [BNumber, Sms]),
+ ?SERVER ! {input_from, BNumber, Sms},
+ ok.
+
+%% Test functions
+%% @private
+dump() ->
+ gen_server:call(?SERVER, dump).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server
+%%
+%% @spec start_link() -> {ok, Pid} | ignore | {error, Error}
+%% @end
+%%--------------------------------------------------------------------
+start_link(Args) ->
+ gen_server:start_link({local, ?SERVER}, ?MODULE, Args, []).
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Initializes the server
+%%
+%% @spec init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% @end
+%%--------------------------------------------------------------------
+init(Args) ->
+ Csq_ival = proplists:get_value(csq_ival, Args, 0),
+ Csq_tmr = if is_integer(Csq_ival), Csq_ival > 0 ->
+ erlang:start_timer(Csq_ival, self(), csq);
+ true -> undefined
+ end,
+ process_flag(trap_exit, true),
+ {ok, #state{ csq_ival=Csq_ival, csq_tmr=Csq_tmr}}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling call messages
+%%
+%% @spec handle_call(Request, From, State) ->
+%% {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_call({send,Opts,Body}, _From, State) ->
+ %% forward to the correct interface handler
+ %% FIXME: add code to match attributes!
+ case proplists:get_value(bnumber, Opts) of
+ undefined ->
+ case State#state.ifs of
+ [I|_] ->
+ Reply = gsms_0705:send(I#interface.pid, Opts, Body),
+ {reply, Reply, State};
+ [] ->
+ {reply, {error,enoent}, State}
+ end;
+ BNumber ->
+ case lists:keyfind(BNumber,#interface.bnumber,State#state.ifs) of
+ false ->
+ {reply, {error,enoent}, State};
+ I ->
+ Reply = gsms_0705:send(I#interface.pid, Opts, Body),
+ {reply, Reply, State}
+ end
+ end;
+handle_call({subscribe,Pid,Filter}, _From, State) ->
+ Ref = erlang:monitor(process, Pid),
+ Subs = [#subscription { pid = Pid,
+ ref = Ref,
+ filter = Filter } | State#state.subs],
+ {reply, {ok,Ref}, State#state { subs = Subs} };
+handle_call({unsubscribe,Ref}, _From, State) ->
+ case lists:keytake(Ref, #subscription.ref, State#state.subs) of
+ false ->
+ {reply, ok, State};
+ {value,_S,Subs} ->
+ erlang:demonitor(Ref, [flush]),
+ {reply, ok, State#state { subs = Subs} }
+ end;
+handle_call({join,Pid,BNumber,Attributes}, _From, State) ->
+ case lists:keytake(BNumber, #interface.bnumber, State#state.ifs) of
+ false ->
+ ?debug("gsms_router: process ~p, bnumber ~p joined.",
+ [Pid, BNumber]),
+ State1 = add_interface(Pid,BNumber,Attributes,State),
+ {reply, ok, State1};
+ {value,I,IFs} ->
+ receive
+ {'EXIT', OldPid, _Reason} when I#interface.pid =:= OldPid ->
+ ?debug("join: restart detected", []),
+ State1 = add_interface(Pid,BNumber,Attributes,
+ State#state { ifs=IFs} ),
+ {reply, ok, State1}
+ after 0 ->
+ {reply, {error,ealready}, State}
+ end
+ end;
+handle_call(dump, _From, State=#state {subs = Subs, ifs= Ifs}) ->
+ io:format("LoopData:\n", []),
+ io:format("Subscriptions:\n", []),
+ lists:foreach(fun(_Sub=#subscription {pid = Pid, ref = Ref, filter = F}) ->
+ io:format("pid ~p, ref ~p, filter ~p~n",
+ [Pid, Ref, F])
+ end,
+ Subs),
+ io:format("Interfaces:\n", []),
+ lists:foreach(fun(_If=#interface {pid = Pid, mon = Ref,
+ bnumber = B, attributes = A}) ->
+ io:format("pid ~p, ref ~p, bnumber ~p, attributes ~p~n",
+ [Pid, Ref, B, A])
+ end,
+ Ifs),
+ {reply, ok, State};
+
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling cast messages
+%%
+%% @spec handle_cast(Msg, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling all non call/cast messages
+%%
+%% @spec handle_info(Info, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_info({'DOWN',Ref,process,Pid,_Reason}, State) ->
+ case lists:keytake(Ref, #subscription.ref, State#state.subs) of
+ false ->
+ case lists:keytake(Pid, #interface.pid, State#state.ifs) of
+ false ->
+ {noreply, State};
+ {value,_If,Ifs} ->
+ ?debug("gsms_router: interface ~p died, reason ~p\n",
+ [_If, _Reason]),
+ %% Restart done by gsms_if_sup
+ {noreply,State#state { ifs = Ifs }}
+ end;
+ {value,_S,Subs} ->
+ {noreply, State#state { subs = Subs} }
+ end;
+
+handle_info({input_from, BNumber, Pdu}, State) ->
+ lager:debug("input bnumber: ~p, pdu=~p\n", [BNumber,Pdu]),
+ lists:foreach(fun(S) -> match_filter(S, BNumber, Pdu) end,
+ State#state.subs),
+ {noreply, State};
+handle_info({timeout, Tmr, csq}, State) when State#state.csq_tmr =:= Tmr ->
+ Is =
+ lists:map(
+ fun(I) ->
+ R = gsms_0705:get_signal_strength(I#interface.pid),
+ lager:debug("csq result: ~p\n", [R]),
+ case R of
+ {ok,"+CSQ:"++Params} ->
+ case erl_scan:string(Params) of
+ {ok,[{integer,_,Rssi}|_],_} ->
+ Pdu = {rssi, Rssi},
+ BNumber = I#interface.bnumber,
+ if I#interface.rssi =/= Rssi ->
+ lists:foreach(
+ fun(S) ->
+ match_filter(S,BNumber,Pdu)
+ end, State#state.subs),
+ I#interface { rssi = Rssi };
+ true ->
+ I
+ end;
+ _Error ->
+ lager:error("unable to read rssi: ~p",
+ [_Error]),
+ I
+ end;
+ _Error ->
+ lager:error("unable to read rssi: ~p",[_Error]),
+ I
+ end
+ end, State#state.ifs),
+ Csq_ival = State#state.csq_ival,
+ Csq_tmr = if is_integer(Csq_ival), Csq_ival > 0 ->
+ erlang:start_timer(Csq_ival, self(), csq);
+ true -> undefined
+ end,
+ {noreply, State#state { ifs = Is, csq_tmr=Csq_tmr }};
+handle_info({'EXIT', Pid, Reason}, State) ->
+ case lists:keytake(Pid, #interface.pid, State#state.ifs) of
+ {value,_If,Ifs} ->
+ %% One of our interfaces died, log and ignore
+ ?debug("gsms_router: interface ~p died, reason ~p\n",
+ [_If, Reason]),
+ {noreply,State#state { ifs = Ifs }};
+ false ->
+ %% Someone else died, log and terminate
+ ?debug("gsms_router: linked process ~p died, reason ~p, terminating\n",
+ [Pid, Reason]),
+ {stop, Reason, State}
+ end;
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any
+%% necessary cleaning up. When it returns, the gen_server terminates
+%% with Reason. The return value is ignored.
+%%
+%% @spec terminate(Reason, State) -> void()
+%% @end
+%%--------------------------------------------------------------------
+terminate(_Reason, _State) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Convert process state when code is changed
+%%
+%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% @end
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+
+add_interface(Pid,BNumber,Attributes,State) ->
+ Mon = erlang:monitor(process, Pid),
+ I = #interface { pid=Pid, mon=Mon,
+ bnumber=BNumber, attributes=Attributes },
+ link(Pid),
+ State#state { ifs = [I | State#state.ifs ] }.
+
+match_filter(_S=#subscription {filter = Filter,
+ pid = Pid,
+ ref = Ref},
+ BNumber, Pdu) ->
+ lager:debug("match filter: ~p", [Filter]),
+ case match(Filter, BNumber, Pdu) of
+ true ->
+ lager:debug("filter match success.", []),
+ lager:debug("send to ~p", [Pid]),
+ Pid ! {gsms, Ref, Pdu};
+ false ->
+ lager:debug("filter match fail"),
+ ok
+ end.
+
+match(As, BNum, Sms) when is_list(As) ->
+ match_clause(As, BNum, Sms);
+match({'not',Filter}, BNum, Sms) ->
+ not match(Filter, BNum, Sms);
+match({'and',A,B}, BNum, Sms) ->
+ match(A,BNum,Sms) andalso match(B,BNum,Sms);
+match({'or',A,B}, BNum, Sms) ->
+ match(A,BNum,Sms) orelse match(B,BNum,Sms);
+match({bnumber,Addr}, BNum, _Sms) ->
+ %% receiving modem
+ match_addr(Addr, BNum);
+match(Match, _BNum, Sms) when is_record(Sms,gsms_deliver_pdu) ->
+ match_sms(Match, Sms);
+match({rssi,true}, _BNum, {rssi,_}) ->
+ true;
+match({creg,true}, _BNum, {creg,_}) ->
+ true;
+match(_, _BNum, _)->
+ false.
+
+match_clause([A|As], BNum, Sms) ->
+ case match(A, BNum, Sms) of
+ true -> match_clause(As, BNum, Sms);
+ false -> false
+ end;
+match_clause([], _BNum, _Sms) ->
+ true.
+
+match_sms({type,Type}, Sms) ->
+ case Sms#gsms_deliver_pdu.dcs of
+ [Type|_] -> true;
+ _ -> false
+ end;
+match_sms({alphabet,Alphabet}, Sms) ->
+ case Sms#gsms_deliver_pdu.dcs of
+ [_Type,_Compress,Alphabet|_] -> true;
+ _ -> false
+ end;
+match_sms({class,Class}, Sms) ->
+ case Sms#gsms_deliver_pdu.dcs of
+ [_Type,_Compress,_Alphabet,Class|_] -> true;
+ _ -> false
+ end;
+match_sms({pid,Pid}, Sms) ->
+ Sms#gsms_deliver_pdu.pid =:= Pid;
+match_sms({dst,Port}, Sms) ->
+ case lists:keyfind(1, port, Sms#gsms_deliver_pdu.udh) of
+ {port,Port,_} -> true;
+ _ -> false
+ end;
+match_sms({src,Port}, Sms) ->
+ case lists:keyfind(1, port, Sms#gsms_deliver_pdu.udh) of
+ {port,_,Port} -> true;
+ _ -> false
+ end;
+match_sms({anumber,Addr}, Sms) ->
+ match_addr(Addr, Sms#gsms_deliver_pdu.addr);
+match_sms({smsc,Addr}, Sms) ->
+ match_addr(Addr, Sms#gsms_deliver_pdu.smsc);
+match_sms({reg_exp, RegExp}, Sms) ->
+ match_body(RegExp, Sms#gsms_deliver_pdu.ud);
+match_sms({rssi,_}, _Sms) ->
+ false;
+match_sms({creg,_}, _Sms) ->
+ false.
+
+
+%% Add some more smart matching here to select international / national
+%% country suffix etc.
+match_addr(Addr, Addr) -> true;
+match_addr(Addr, #gsms_addr { addr = Addr }) when is_list(Addr) -> true;
+match_addr(#gsms_addr { type=unknown, addr=Addr},
+ #gsms_addr { addr=Addr}) -> true;
+match_addr(_, _) -> false.
+
+match_body(RegExp, UserData) ->
+ lager:debug("match regular expression: ~p", [RegExp]),
+ case re:run(UserData, RegExp, [{capture, none}]) of
+ match -> true;
+ nomatch -> false
+ end.
diff --git a/deps/gsms/src/gsms_sup.erl b/deps/gsms/src/gsms_sup.erl
new file mode 100644
index 0000000..7687476
--- /dev/null
+++ b/deps/gsms/src/gsms_sup.erl
@@ -0,0 +1,68 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2013, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2013, Tony Rogvall
+%%% @doc
+%%% Gsms supervisor
+%%%
+%%% Created: 28 Aug 2006 by Tony Rogvall
+%%% @end
+
+-module(gsms_sup).
+
+-behaviour(supervisor).
+
+-include("log.hrl").
+%% external exports
+-export([start_link/0,
+ start_link/1,
+ stop/0]).
+
+%% supervisor callbacks
+-export([init/1]).
+
+%%%----------------------------------------------------------------------
+%%% API
+%%%----------------------------------------------------------------------
+start_link(Args) ->
+ ?info("~p: start_link: args = ~p\n", [?MODULE, Args]),
+ case supervisor:start_link({local, ?MODULE}, ?MODULE, Args) of
+ {ok, Pid} ->
+ {ok, Pid, {normal, Args}};
+ Error ->
+ Error
+ end.
+
+start_link() ->
+ supervisor:start_link({local,?MODULE}, ?MODULE, []).
+
+stop() ->
+ exit(normal).
+
+%%%----------------------------------------------------------------------
+%%% Callback functions from supervisor
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%%----------------------------------------------------------------------
+init(Args) ->
+ ?info("~p: init: args = ~p,\n pid = ~p\n", [?MODULE, Args, self()]),
+ GsmsRouter = {gsms_router, {gsms_router, start_link, [Args]},
+ permanent, 5000, worker, [gsms_router]},
+ GsmsIfSup = {gsms_if_sup, {gsms_if_sup, start_link, []},
+ permanent, 5000, worker, [gsms_if_sup]},
+ {ok,{{one_for_all,3,5}, [GsmsRouter, GsmsIfSup]}}.
diff --git a/deps/gsms/src/gsms_uart.erl b/deps/gsms/src/gsms_uart.erl
new file mode 100644
index 0000000..4c5d0ee
--- /dev/null
+++ b/deps/gsms/src/gsms_uart.erl
@@ -0,0 +1,834 @@
+%%%---- BEGIN COPYRIGHT --------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ----------------------------------------------------------
+%%%-------------------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @author Malotte Westman Lönne <malotte@malotte.net>
+%%% @copyright (C) 2012, Tony Rogvall
+%%% @doc
+%%% SMS command driver.
+%%%
+%%% Created : 1 Jul 2010 by Tony Rogvall
+%%% @end
+%%%-------------------------------------------------------------------
+-module(gsms_uart).
+
+-behaviour(gen_server).
+
+-include("log.hrl").
+
+%% API
+-export([start_link/1,
+ stop/1,
+ subscribe/1,
+ subscribe/2,
+ unsubscribe/2,
+ setopts/2,
+ at/2, atd/3, send/2]).
+
+%% Option processing
+-export([options/0,
+ split_opts/2,
+ normalise_opts/1,
+ validate_opts/1,
+ validate_opt/2]).
+
+-export([trimhd/1,trimtl/1,trim/1]).
+
+%% gen_server callbacks
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
+
+-define(SERVER, ?MODULE).
+-define(UART_DEFAULT_OPTS,
+ [{baud,115200},{mode,list},{active,true},
+ {packet,line},{csize,8},{parity,none},{stopb,1}]).
+-define(CTRL_Z, 16#1A).
+-define(ESC, 16#1B).
+
+%% For dialyzer
+-type gsms_uart_option() :: device | reopen_timeout | reply_timeout.
+
+-type gsms_uart_config() ::
+ {device, string()} |
+ {reopen_timeout, timeout()} |
+ {reply_timeout, timeout()} |
+ {smsc, string()}.
+
+-record(subscription,
+ {
+ pid,
+ mon,
+ pattern
+ }).
+
+-record(qent,
+ {
+ from, %% caller tag
+ mon, %% caller/pid monitor
+ command %% call or cast
+ }).
+
+-record(ctx,
+ {
+ uart, %% serial port descriptor
+ device, %% device string
+ caller, %% parent pid
+ uopts=[], %% uart options
+ opts=[], %% sms options
+ command="", %% last command
+ reply=[], %% list of reply line data
+ client, %% last client
+ client_data, %% ATD hex data if or undefined
+ queue = [], %% request queue [#qent{}]
+ reply_timer, %% timeout waiting for reply
+ reopen_timer, %% timer ref
+ subs = [] %% #subscription{}
+ }).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+-spec options() -> [uart:uart_option()|gsms_uart_option()].
+
+options() ->
+ uart:options() ++
+ [
+ smsc,
+ reopen_timeout,
+ reply_timeout
+ ].
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server.
+%%
+%% Device contains the path to the Device. <br/>
+%% reopen_timeout =/= 0 means that if the driver fails to open the device it
+%% will try again in Timeout milliseconds.<br/>
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec start_link([gsms_uart_config()]) ->
+ {ok, Pid::pid()} |
+ ignore |
+ {error, Error::term()}.
+%%
+%% HUAWEI: uses device /dev/tty.HUAWEIMobile-Pcui
+%% for SMS services to be able to get notifications which are
+%% NOT available on /dev/tty.HUAWEIMobile-Modem
+%%
+start_link(Opts) ->
+ lager:info("~p: start_link: args = ~p\n", [?MODULE, Opts]),
+ gen_server:start_link(?MODULE, [self(),Opts], []).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Stops the server.
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec stop(Drv::pid()) -> ok | {error, Error::term()}.
+
+stop(Drv) ->
+ gen_server:call(Drv, stop).
+
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Subscribe to sms events.
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec subscribe(Drv::pid()) -> {ok,reference()} | {error, Error::term()}.
+
+subscribe(Drv) ->
+ subscribe(Drv, []).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Subscribe to sms events.
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec subscribe(Drv::pid(),Pattern::[{atom(),string()}]) ->
+ {ok,reference()} | {error, Error::term()}.
+subscribe(Drv,Pattern) ->
+ gen_server:call(Drv, {subscribe,self(),Pattern}).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Unsubscribe from sms events.
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec unsubscribe(Drv::pid(),Ref::reference()) -> ok | {error, Error::term()}.
+unsubscribe(Drv,Ref) ->
+ gen_server:call(Drv, {unsubscribe,Ref}).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Set various options from uart options to smsc options
+%%
+%% @end
+%%--------------------------------------------------------------------
+
+setopts(Drv,Opts) when is_list(Opts) ->
+ gen_server:call(Drv, {setopts, Opts}).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Run a raw command
+%%
+%% @end
+%%--------------------------------------------------------------------
+
+%% normal at command
+at(Drv,Command) ->
+ gen_server:call(Drv, {at,Command}).
+
+%% send command and data in data-enter state
+atd(Drv, Command, Hex) ->
+ gen_server:call(Drv, {atd,Command,Hex}, 20000).
+
+send(Drv, Data) ->
+ gen_server:call(Drv, {send,Data}).
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+%% @private
+%%--------------------------------------------------------------------
+%% @doc
+%% Initializes the server
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec init([Caller::pid()|gsms_uart_config()]) ->
+ {ok, Ctx::#ctx{}} |
+ {ok, Ctx::#ctx{}, Timeout::timeout()} |
+ ignore |
+ {stop, Reason::term()}.
+
+init([Caller,Opts]) ->
+ lager:info("~p: init: args = ~p,\n pid = ~p\n",
+ [?MODULE, Opts, self()]),
+ Opts1 = normalise_opts(?UART_DEFAULT_OPTS ++ Opts),
+ {Uopts0,Opts2} = split_opts(Opts1, uart:options()),
+ {Gopts0,Opts3} = split_opts(Opts2, options()),
+ case check_options(Uopts0,Gopts0,Opts3) of
+ ok ->
+ Uopts1 = proplists:delete(device, Uopts0),
+ Device = case proplists:get_value(device, Uopts0) of
+ undefined ->
+ case os:getenv("GSMS_DEVICE") of
+ false -> "";
+ Name -> Name
+ end;
+ Name -> Name
+ end,
+ S = #ctx { device = Device,
+ uopts = Uopts1,
+ caller = Caller,
+ opts = Gopts0,
+ queue = []
+ },
+ case open(S) of
+ {ok, S1} -> {ok, S1};
+ Error -> {stop, Error}
+ end;
+ Error ->
+ {stop, Error}
+ end.
+
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling call messages
+%%
+%% @end
+%%--------------------------------------------------------------------
+
+handle_call({setopts, Opts},_From, Ctx=#ctx { uart = U}) ->
+ lager:debug("setopts ~p", [Opts]),
+ Opts1 = normalise_opts(Opts),
+ {Uopts0,Opts2} = split_opts(Opts1, uart:options()),
+ {Gopts0,Opts3} = split_opts(Opts2, options()),
+ case check_options(Uopts0,Gopts0,Opts3) of
+ ok ->
+ Uopts1 = proplists:delete(device, Uopts0),
+ case proplists:get_value(device, Uopts0) of
+ Device when is_list(Device), Device =/= Ctx#ctx.device ->
+ Uopts2 = normalise_opts(Ctx#ctx.uopts ++ Uopts1),
+ Ctx1 = close(Ctx),
+ Ctx2 = Ctx1#ctx { uart=undefined,
+ reopen_timer = undefined,
+ device=Device,
+ uopts=Uopts2,
+ opts=Gopts0 },
+ case open(Ctx2) of
+ {ok, Ctx3} -> {reply,ok,Ctx3};
+ Error -> {stop, Error, Ctx2}
+ end;
+ _ ->
+ case U of
+ undefined ->
+ {reply, {error,no_port}, Ctx};
+ simulated ->
+ {reply, ok, Ctx#ctx { uopts=Uopts1, opts=Gopts0} };
+ _ ->
+ lager:debug("uart:setopts ~p", [Uopts1]),
+ case uart:setopts(U, Uopts1) of
+ ok ->
+ {reply, ok, Ctx#ctx { uopts=Uopts1,
+ opts=Gopts0} };
+ Error ->
+ {reply, Error, Ctx}
+ end
+ end
+ end;
+ Error ->
+ {reply, Error, Ctx}
+ end;
+
+handle_call({subscribe,Pid,Pattern},_From,Ctx=#ctx { subs=Subs}) ->
+ Mon = erlang:monitor(process, Pid),
+ Subs1 = [#subscription { pid = Pid, mon = Mon, pattern = Pattern}|Subs],
+ {reply, {ok,Mon}, Ctx#ctx { subs = Subs1}};
+
+handle_call({unsubscribe,Ref},_From,Ctx) ->
+ erlang:demonitor(Ref),
+ Ctx1 = remove_subscription(Ref,Ctx),
+ {reply, ok, Ctx1};
+handle_call(stop, _From, Ctx) ->
+ {stop, normal, ok, Ctx};
+
+
+%% other commands we queue if gsms_uart is busy processing command
+handle_call(Call,From,Ctx=#ctx {client = Client})
+ when Client =/= undefined andalso Call =/= stop ->
+ %% Driver is busy ..
+ lager:debug("handle_call: Driver busy, store call ~p", [Call]),
+ %% set timer already here? probably!
+ {Pid,_Tag} = From,
+ Mon = erlang:monitor(process, Pid),
+ Timeout=proplists:get_value(reply_timeout,Ctx#ctx.opts,5000),
+ QE = #qent { from=From, mon=Mon, command={call,Call}},
+ Q = Ctx#ctx.queue ++ [QE],
+ {noreply, Ctx#ctx { queue = Q }, Timeout};
+
+handle_call({at,Command},From,Ctx) ->
+ lager:debug("handle_call: command ~p", [Command]),
+ case Ctx#ctx.uart of
+ simulated ->
+ lager:info("simulated output ~p\n", [Command]),
+ {reply, ok, Ctx};
+ undefined ->
+ lager:info("~p: No port defined yet.\n", [?MODULE]),
+ {reply, {error,no_port}, Ctx};
+ U ->
+ case uart:send(U, ["AT",Command,"\r\n"]) of
+ ok ->
+ lager:debug("command: sent"),
+ %% Wait for confirmation
+ Tm=proplists:get_value(reply_timeout,Ctx#ctx.opts,5000),
+ TRef = erlang:start_timer(Tm, self(), reply),
+ {noreply,Ctx#ctx {command = Command,
+ client=From,
+ client_data = undefined,
+ reply = [],
+ reply_timer = TRef}};
+ Other ->
+ lager:debug("command: send failed, reason ~p", [Other]),
+ {reply, Other, Ctx}
+ end
+ end;
+
+handle_call({atd,Command,Hex},From,Ctx) ->
+ lager:debug("handle_call: command ~p", [Command]),
+ case Ctx#ctx.uart of
+ simulated ->
+ lager:info("simulated output ~p\n", [Command]),
+ {reply, ok, Ctx};
+ undefined ->
+ lager:info("~p: No port defined yet.\n", [?MODULE]),
+ {reply, {error,no_port}, Ctx};
+ U ->
+ uart:setopts(U, [{active,false}]),
+ case uart:send(U, ["AT",Command,"\r\n"]) of
+ ok ->
+ lager:debug("command: sent\n", []),
+ %% wait for exacly ">\r\n"
+ uart:setopts(U, [{active,once},{packet,{size,3}}]),
+ %% Wait for confirmation
+ Tm=proplists:get_value(reply_timeout,Ctx#ctx.opts,5000),
+ TRef = erlang:start_timer(Tm, self(), reply),
+ {noreply,Ctx#ctx {command = Command,
+ client=From,
+ client_data=Hex,
+ reply = [],
+ reply_timer = TRef}};
+ Other ->
+ uart:setopts(U, [{active,true}]),
+ lager:debug("command: send failed, reason ~p", [Other]),
+ {reply, Other, Ctx}
+ end
+ end;
+
+handle_call({send,Data},_From,Ctx) ->
+ lager:debug("handle_call: send ~p", [Data]),
+ case Ctx#ctx.uart of
+ simulated ->
+ lager:info("simulated output ~p\n", [Data]),
+ {reply, ok, Ctx};
+ undefined ->
+ lager:info("~p: No port defined yet.\n", [?MODULE]),
+ {reply, {error,no_port}, Ctx};
+ U ->
+ Reply = uart:send(U, Data),
+ {reply, Reply, Ctx}
+ end;
+handle_call(_Request, _From, Ctx) ->
+ {reply, {error,bad_call}, Ctx}.
+
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling cast messages
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec handle_cast(Msg::term(), Ctx::#ctx{}) ->
+ {noreply, Ctx::#ctx{}} |
+ {stop, Reason::term(), Ctx::#ctx{}}.
+
+
+handle_cast(Cast, Ctx=#ctx {uart = U, client=Client})
+ when U =/= undefined, Client =/= undefined ->
+ lager:debug("handle_cast: Driver busy, store cast ~p", [Cast]),
+ %% FIXME: add timer when/if we start using this
+ QE = #qent { from=undefined, mon=undefined, command = {cast,Cast}},
+ Q = Ctx#ctx.queue ++ [QE],
+ {noreply, Ctx#ctx { queue = Q }};
+
+handle_cast(_Msg, Ctx) ->
+ lager:debug("handle_cast: Unknown message ~p", [_Msg]),
+ {noreply, Ctx}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling all non call/cast messages
+%%
+%% @end
+%%--------------------------------------------------------------------
+-type info()::
+ {uart, U::port(), Data::binary()} |
+ {uart_error, U::port(), Reason::term()} |
+ {uart_closed, U::port()} |
+ {timeout, reference(), reply} |
+ {timeout, reference(), reopen} |
+ {'DOWN',Ref::reference(),process,pid(),Reason::term()}.
+
+-spec handle_info(Info::info(), Ctx::#ctx{}) ->
+ {noreply, Ctx::#ctx{}} |
+ {stop, Reason::term(), Ctx::#ctx{}}.
+
+handle_info({timeout,TRef,reply},
+ Ctx=#ctx {client=Client, reply_timer=TRef}) ->
+ lager:debug("handle_info: timeout waiting for port", []),
+ gen_server:reply(Client, {error, port_timeout}),
+ Ctx1 = Ctx#ctx { reply_timer=undefined, reply=[], client = undefined},
+ next_command(Ctx1);
+
+handle_info({uart,U,Data}, Ctx) when U =:= Ctx#ctx.uart, is_binary(Data) ->
+ lager:debug("handle_info: noreply ~p", [Data]),
+ send_event(Ctx#ctx.subs, {data,Data}),
+ {noreply,Ctx};
+handle_info({uart,U,Data}, Ctx) when U =:= Ctx#ctx.uart ->
+ lager:debug("got uart data: ~p\n", [Data]),
+ case trim(Data) of
+ "" -> %% empty line (may add this later?)
+ {noreply, Ctx};
+ ">" when Ctx#ctx.client_data =/= undefined ->
+ uart:setopts(U, [{active,true},{packet,line}]),
+ case uart:send(U, [Ctx#ctx.client_data,?CTRL_Z]) of
+ ok ->
+ lager:debug("data sent ~p\n", [Ctx#ctx.client_data]),
+ stop_timer(Ctx#ctx.reply_timer),
+ Tm=proplists:get_value(reply_timeout,Ctx#ctx.opts,10000),
+ TRef = start_timer(Tm, reply),
+ {noreply,Ctx#ctx { reply_timer=TRef }};
+ Error ->
+ lager:debug("command: send failed, reason ~p", [Error]),
+ reply(Error, Ctx)
+ end;
+ "OK" ->
+ reply(ok, Ctx);
+ "ERROR" ->
+ reply(error, Ctx);
+ "+CMS ERROR:"++Code ->
+ reply(error, Ctx#ctx { reply=[trimhd(Code)|Ctx#ctx.reply]});
+ "+CMTI:"++EventData -> %% new SMS message (stored) arrived
+ Ctx1 = event_notify(cmti,trimhd(EventData), Ctx),
+ {noreply, Ctx1};
+ "+CMT:"++EventData -> %% new SMS message arrived
+ Ctx1 = event_notify(cmt,trimhd(EventData), Ctx),
+ {noreply, Ctx1};
+ "+CMGS:"++EventData -> %% Send SMS response code
+ Ctx1 = event_notify(cmgs,trimhd(EventData), Ctx),
+ {noreply, Ctx1};
+ "+CDSI:"++EventData -> %% SMS status report (stored) arrived
+ Ctx1 = event_notify(cdsi,trimhd(EventData), Ctx),
+ {noreply, Ctx1};
+ "+CDS:"++EventData -> %% SMS status report
+ Ctx1 = event_notify(cds,trimhd(EventData), Ctx),
+ {noreply, Ctx1};
+ "^"++EventData -> %% Periodic information
+ Ctx1 = event_notify(periodic,trimhd(EventData), Ctx),
+ {noreply,Ctx1};
+ Reply ->
+ if Ctx#ctx.client =/= undefined ->
+ lager:debug("handle_info: data ~p", [Reply]),
+ {noreply,Ctx#ctx { reply=[Reply|Ctx#ctx.reply]}};
+ true ->
+ lager:debug("handle_info: noreply ~p", [Reply]),
+ send_event(Ctx#ctx.subs, {data,Data}),
+ {noreply,Ctx}
+ end
+ end;
+
+handle_info({uart_error,U,Reason}, Ctx) when U =:= Ctx#ctx.uart ->
+ if Reason =:= enxio ->
+ lager:error("uart error ~p device ~s unplugged?",
+ [Reason,Ctx#ctx.device]);
+ true ->
+ lager:error("uart error ~p for device ~s",
+ [Reason,Ctx#ctx.device])
+ end,
+ {noreply, Ctx};
+
+handle_info({uart_closed,U}, Ctx) when U =:= Ctx#ctx.uart ->
+ uart:close(U),
+ lager:error("uart close device ~s will retry", [Ctx#ctx.device]),
+ case open(Ctx#ctx { uart=undefined}) of
+ {ok, Ctx1} -> {noreply, Ctx1};
+ Error -> {stop, Error, Ctx}
+ end;
+
+handle_info({timeout,Ref,reopen}, Ctx) when Ctx#ctx.reopen_timer =:= Ref ->
+ case open(Ctx#ctx { uart=undefined, reopen_timer=undefined}) of
+ {ok, Ctx1} -> {noreply, Ctx1};
+ Error -> {stop, Error, Ctx}
+ end;
+
+handle_info({'DOWN',Ref,process,_Pid,_Reason},Ctx) ->
+ lager:debug("handle_info: subscriber ~p terminated: ~p",
+ [_Pid, _Reason]),
+ case lists:keytake(Ref,#qent.mon, Ctx#ctx.queue) of
+ false -> remove_subscription(Ref,Ctx);
+ {value,_QE,Q} ->
+ {noreply,Ctx#ctx { queue=Q}}
+ end;
+handle_info(_Info, Ctx) ->
+ lager:debug("handle_info: Unknown info ~p", [_Info]),
+ {noreply, Ctx}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any
+%% necessary cleaning up. When it returns, the gen_server terminates
+%% with Reason. The return value is ignored.
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec terminate(Reason::term(), Ctx::#ctx{}) ->
+ ok.
+
+terminate(_Reason, Ctx) ->
+ close(Ctx),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Convert process ctx when code is changed
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec code_change(OldVsn::term(), Ctx::#ctx{}, Extra::term()) ->
+ {ok, NewCtx::#ctx{}}.
+
+code_change(_OldVsn, Ctx, _Extra) ->
+ {ok, Ctx}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+
+check_options(Uopts,Gopts,[]) ->
+ case uart:validate_opts(Uopts) of
+ ok -> validate_opts(Gopts);
+ Error -> Error
+ end;
+check_options(_Uopt,_Gopts,Opts) ->
+ {error, {unknown_opts,Opts}}.
+
+
+open(Ctx=#ctx {device = ""}) ->
+ lager:debug("open: simulated\n", []),
+ {ok, Ctx#ctx { uart=simulated }};
+
+open(Ctx=#ctx {device = Name, uopts=UOpts }) ->
+ case uart:open(Name,UOpts) of
+ {ok,U} ->
+ lager:debug("open: ~s [~w] [~w]: ~p",
+ [Name,UOpts,uart:getopts(U,uart:options()),U]),
+ flush_uart(U),
+ lager:debug("sync start"),
+ %% waky, waky ? do not echo
+ uart:send(U, [?ESC|"AT\r\n"]), %% escape if stuck in message send
+ flush_uart(U, 1000, 0),
+ uart:send(U, "ATZ\r\n"), %% reset
+ flush_uart(U, 2000, 0),
+ uart:send(U, "ATE0\r\n"), %% echo off
+ flush_uart(U, 1000, 0),
+ uart:send(U, "AT\r\n"), %% empty
+ flush_uart(U, 100, 0),
+ uart:send(U, "AT\r\n"), %% empty
+ flush_uart(U, 100, 0),
+ lager:debug("sync stop"),
+ %% signal that the the uart device is up running
+ Ctx#ctx.caller ! {gsms_uart, self(), up},
+ {ok, Ctx#ctx { uart=U }};
+ {error, E} when E == eaccess;
+ E == enoent ->
+ case proplists:get_value(reopen_timeout,Ctx#ctx.opts,infinity) of
+ infinity ->
+ lager:debug("open: Driver not started, reason = ~p.\n",[E]),
+ {error, E};
+ Ival ->
+ lager:debug("open: uart could not be opened, will try again"
+ " in ~p millisecs.\n", [Ival]),
+ Reopen_timer = start_timer(Ival, reopen),
+ {ok, Ctx#ctx { reopen_timer = Reopen_timer }}
+ end;
+
+ Error ->
+ lager:debug("open: Driver not started, reason = ~p.\n",
+ [Error]),
+ Error
+ end.
+
+close(Ctx=#ctx {uart = U}) when is_port(U) ->
+ lager:debug("close: ~p", [U]),
+ uart:close(U),
+ {ok, Ctx#ctx { uart=undefined }};
+close(Ctx) ->
+ {ok, Ctx}.
+
+
+%% flush uart data messages
+flush_uart(U) ->
+ flush_uart(U, 1000, 250).
+
+flush_uart(U,T0,T1) ->
+ receive
+ {uart,U,_Data} ->
+ lager:debug("flush: uart ~p\n", [_Data]),
+ flush_uart(U,T1)
+ after T0 ->
+ ok
+ end.
+
+flush_uart(U,T) ->
+ receive
+ {uart,U,_Data} ->
+ lager:debug("flush: uart ~p\n", [_Data]),
+ flush_uart(U,T)
+ after T ->
+ ok
+ end.
+
+start_timer(0, _Message) -> undefined;
+start_timer(infinity, _Message) -> undefined;
+start_timer(Time, Message) -> erlang:start_timer(Time, self(), Message).
+
+stop_timer(undefined) -> ok;
+stop_timer(Ref) when is_reference(Ref) ->
+ erlang:cancel_timer(Ref),
+ receive
+ {timeout,Ref,_} -> ok
+ after 0 ->
+ ok
+ end.
+
+
+reply(Tag, Ctx) ->
+ if Ctx#ctx.client =/= undefined ->
+ stop_timer(Ctx#ctx.reply_timer),
+ case lists:reverse(Ctx#ctx.reply) of
+ [] ->
+ gen_server:reply(Ctx#ctx.client, Tag);
+ [Response] ->
+ gen_server:reply(Ctx#ctx.client, {Tag,Response});
+ MultiResponse ->
+ gen_server:reply(Ctx#ctx.client, {Tag,MultiResponse})
+ end,
+ Ctx1 = Ctx#ctx { client=undefined,
+ client_data = undefined,
+ reply_timer=undefined,
+ command = "", reply=[] },
+ next_command(Ctx1);
+ true ->
+ {noreply, Ctx}
+ end.
+
+next_command(Ctx) ->
+ case Ctx#ctx.queue of
+ [#qent{from=From,mon=Mon,command={call,Call}} | Q1] ->
+ case handle_call(Call, From, Ctx#ctx { queue=Q1}) of
+ {reply,Reply,Ctx1} ->
+ gen_server:reply(From,Reply),
+ erlang:demonitor(Mon,[flush]),
+ next_command(Ctx1);
+ CallResult ->
+ CallResult
+ end;
+ [#qent {command={cast,Cast}} | Q1] ->
+ handle_cast(Cast, Ctx#ctx { queue=Q1});
+ [] ->
+ uart:setopts(Ctx#ctx.uart, [{active,true}]),
+ {noreply, Ctx#ctx { queue=[]}}
+ end.
+
+trimhd([$\s|Cs]) -> trimhd(Cs);
+trimhd([$\t|Cs]) -> trimhd(Cs);
+trimhd([$\r|Cs]) -> trimhd(Cs);
+trimhd([$\n|Cs]) -> trimhd(Cs);
+trimhd([0|Cs]) -> trimhd(Cs);
+trimhd(Cs) -> Cs.
+
+trimtl(Cs) -> lists:reverse(trimhd(lists:reverse(Cs))).
+
+trim(Cs) -> trimtl(trimhd(Cs)).
+
+unquote([$"|Cs]) ->
+ case lists:reverse(Cs) of
+ [$"|Cs1] -> lists:reverse(Cs1);
+ Cs1 -> Cs1
+ end;
+unquote(Cs1) -> Cs1.
+
+to_integer(Cs) ->
+ try erlang:list_to_integer(Cs, 10) of
+ Value -> Value
+ catch
+ error:_ -> Cs
+ end.
+
+remove_subscription(Ref, Ctx=#ctx { subs=Subs}) ->
+ Subs1 = lists:keydelete(Ref, #subscription.mon, Subs),
+ Ctx#ctx { subs = Subs1 }.
+
+
+event_notify(Name,String, Ctx) ->
+ Args =
+ case string:tokens(String, ",") of
+ [Store,Index] ->
+ [{"store",unquote(Store)},{"index",to_integer(Index)}];
+ Items ->
+ [{"items", Items}]
+ end,
+ Event = {Name,Args},
+ lager:debug("Event: ~p", [Event]),
+ send_event(Ctx#ctx.subs, Event),
+ Ctx.
+
+send_event([#subscription{pid=Pid,mon=Ref,pattern=Pattern}|Tail], Event) ->
+ case match_event(Pattern, Event) of
+ true -> Pid ! {gsms_event,Ref,Event};
+ false -> false
+ end,
+ send_event(Tail,Event);
+send_event([],_Event) ->
+ ok.
+
+match_event([], _) -> true;
+match_event([{Key,ValuePat}|Kvs],Event) ->
+ case lists:keyfind(Key, 1, Event) of
+ {Key,ValuePat} -> match_event(Kvs, Event);
+ _ -> false
+ end.
+
+%% split options in two groups {A,B}
+%% where A is the group with all keys in Keys B are the rest of the options
+split_opts(List, Keys) ->
+ {Lists,Rest} = proplists:split(List, Keys),
+ List1 = %% get last element from each list, simulate seq setting
+ lists:foldr(fun([],Acc) -> Acc;
+ (L,Acc) -> [lists:last(L)|Acc]
+ end, [], Lists),
+ {List1,Rest}.
+
+%% remove duplicate options keep later than earlier options
+%% normalise boolean options
+normalise_opts([Opt|Opts]) ->
+ case Opt of
+ Kv={Key,_} ->
+ case proplists:is_defined(Key, Opts) of
+ true -> normalise_opts(Opts);
+ false -> [Kv|normalise_opts(Opts)]
+ end;
+ Key ->
+ case proplists:is_defined(Key, Opts) of
+ true -> normalise_opts(Opts);
+ false -> [{Key,true}|normalise_opts(Opts)]
+ end
+ end;
+normalise_opts([]) ->
+ [].
+
+validate_opts([{K,V}|Kvs]) ->
+ case validate_opt(K,V) of
+ true -> validate_opts(Kvs);
+ false -> {error,{type_error,K,V}};
+ undefined -> {error,{unknown_opt,K}};
+ Error -> Error
+ end;
+validate_opts([]) ->
+ ok.
+
+validate_opt(device, Arg) -> is_list(Arg);
+validate_opt(reopen_timeout, Arg) -> is_timeout(Arg);
+validate_opt(reply_timeout, Arg) -> is_timeout(Arg);
+validate_opt(smsc, Arg) -> is_list(Arg);
+validate_opt(_,_Arg) -> undefined.
+
+is_timeout(T) ->
+ (T =:= infinity) orelse
+ (is_integer(T) andalso (T>=0)).
+
diff --git a/deps/gsms/src/log.hrl b/deps/gsms/src/log.hrl
new file mode 100644
index 0000000..270f06c
--- /dev/null
+++ b/deps/gsms/src/log.hrl
@@ -0,0 +1,44 @@
+%%
+%% Log macros
+%%
+-ifndef(__LOG_HRL__).
+-define(__LOG_HRL__, true).
+
+-compile({parse_transform, lager_transform}).
+
+%% Lager logging levels
+%% debug, info, notice, warning, error, critical, alert, emergency, none.
+
+-define(debug(Fmt), lager:debug(Fmt)).
+-define(debug(Fmt, Args), lager:debug(Fmt, Args)).
+-define(debug(Attrs, Fmt, Args), lager:debug(Attrs, Fmt, Args)).
+
+-define(info(Fmt), lager:info(Fmt)).
+-define(info(Fmt, Args), lager:info(Fmt, Args)).
+-define(info(Attrs, Fmt, Args), lager:info(Attrs, Fmt, Args)).
+
+-define(notice(Fmt), lager:notice(Fmt)).
+-define(notice(Fmt, Args), lager:notice(Fmt, Args)).
+-define(notice(Attrs, Fmt, Args), lager:notice(Attrs, Fmt, Args)).
+
+-define(warning(Fmt), lager:warning(Fmt)).
+-define(warning(Fmt, Args), lager:warning(Fmt, Args)).
+-define(warning(Attrs, Fmt, Args), lager:warning(Attrs, Fmt, Args)).
+
+-define(error(Fmt), lager:error(Fmt)).
+-define(error(Fmt, Args), lager:error(Fmt, Args)).
+-define(error(Attrs, Fmt, Args), lager:error(Attrs, Fmt, Args)).
+
+-define(critical(Fmt), lager:critical(Fmt)).
+-define(critical(Fmt, Args), lager:critical(Fmt, Args)).
+-define(critical(Attrs, Fmt, Args), lager:critical(Attrs, Fmt, Args)).
+
+-define(alert(Fmt), lager:alert(Fmt)).
+-define(alert(Fmt, Args), lager:alert(Fmt, Args)).
+-define(alert(Attrs, Fmt, Args), lager:alert(Attrs, Fmt, Args)).
+
+-define(emergency(Fmt), lager:emergency(Fmt)).
+-define(emergency(Fmt, Args), lager:emergency(Fmt, Args)).
+-define(emergency(Attrs, Fmt, Args), lager:emergency(Attrs, Fmt, Args)).
+
+-endif.
diff --git a/deps/gsms/src/sim900 b/deps/gsms/src/sim900
new file mode 100755
index 0000000..40f646f
--- /dev/null
+++ b/deps/gsms/src/sim900
@@ -0,0 +1,80 @@
+#!/usr/bin/env escript
+
+%% GPIO map
+-define(NRST, 23). %% output pin
+-define(PWRKEY, 24). %% output pin
+-define(RI, 17). %% input
+-define(DTR, 22). %% output pin
+
+main([]) ->
+ usage(0);
+main(["help"|_Options]) ->
+ usage(0);
+main(["-h"|_Options]) ->
+ usage(0);
+main(Options) ->
+ case string:to_integer(os:cmd("id -u")) of
+ {0, _} ->
+ %% require root access
+ application:start(gpio),
+ command(Options),
+ halt(0);
+ {_,_} ->
+ io:fwrite(escript:script_name() ++ " need root privileges\n"),
+ halt(1)
+ end.
+
+command(["init"|Cmds]) -> %% init only
+ gpio:init(?PWRKEY),
+ gpio:set_direction(?PWRKEY,low),
+ gpio:init(?NRST),
+ gpio:set_direction(?NRST,low),
+ gpio:init(?RI),
+ gpio:init(?DTR),
+ gpio:set_direction(?DTR,low),
+ command(Cmds);
+command(["pwron"|Cmds]) ->
+ gpio:init(?PWRKEY),
+ gpio:set_direction(?PWRKEY,out),
+ gpio:set(?PWRKEY),
+ timer:sleep(1000+10), %% >1s
+ gpio:clr(?PWRKEY),
+ timer:sleep(2200+10),
+ command(Cmds);
+command(["pwroff"|Cmds]) ->
+ gpio:init(?PWRKEY),
+ gpio:set_direction(?PWRKEY,out),
+ gpio:set(?PWRKEY),
+ timer:sleep(1000+10), %% >1s
+ gpio:clr(?PWRKEY),
+ timer:sleep(1700+10),
+ command(Cmds);
+command(["pwrrst"|Cmds]) -> %% combined pwr
+ command(["pwroff"]),
+ timer:sleep(500+10), %% >500ms
+ command(["pwron"]),
+ command(Cmds);
+command(["rst"|Cmds]) ->
+ gpio:init(?NRST),
+ gpio:set_direction(?NRST, out),
+ gpio:set(?NRST),
+ timer:sleep(1000+10), %% >1s
+ gpio:clr(?NRST),
+ timer:sleep(1700+10),
+ command(Cmds);
+command(["help"|_Cmds]) ->
+ usage(0);
+command(["-h"|_Cmds]) ->
+ usage(0);
+command([_|_Cmds]) ->
+ usage(1);
+command([]) ->
+ ok.
+
+help() ->
+ io:fwrite("usage: "++ escript:script_name() ++
+ " [-h] help|init|pwron|pwroff|pwrrst|rst\n").
+
+usage(N) ->
+ help(),
+ halt(N).
diff --git a/deps/gsms/src/sim900_ft1075k b/deps/gsms/src/sim900_ft1075k
new file mode 100755
index 0000000..98a7c51
--- /dev/null
+++ b/deps/gsms/src/sim900_ft1075k
@@ -0,0 +1,75 @@
+#!/usr/bin/env escript
+
+%% GPIO map
+-define(NRST, 22). %% output pin 15
+-define(PWRKEY, 27). %% output pin 13
+
+main([]) ->
+ usage(0);
+main(["help"|_Options]) ->
+ usage(0);
+main(["-h"|_Options]) ->
+ usage(0);
+main(Options) ->
+ case string:to_integer(os:cmd("id -u")) of
+ {0, _} ->
+ %% require root access
+ application:start(gpio),
+ command(Options),
+ halt(0);
+ {_,_} ->
+ io:fwrite(escript:script_name() ++ " need root privileges\n"),
+ halt(1)
+ end.
+
+command(["init"|Cmds]) -> %% init only
+ gpio:init(?PWRKEY),
+ gpio:set_direction(?PWRKEY,low),
+ gpio:init(?NRST),
+ gpio:set_direction(?NRST,low),
+ command(Cmds);
+command(["pwron"|Cmds]) ->
+ gpio:init(?PWRKEY),
+ gpio:set_direction(?PWRKEY,out),
+ gpio:set(?PWRKEY),
+ timer:sleep(1000+10), %% >1s
+ gpio:clr(?PWRKEY),
+ timer:sleep(2200+10),
+ command(Cmds);
+command(["pwroff"|Cmds]) ->
+ gpio:init(?PWRKEY),
+ gpio:set_direction(?PWRKEY,out),
+ gpio:set(?PWRKEY),
+ timer:sleep(1000+10), %% >1s
+ gpio:clr(?PWRKEY),
+ timer:sleep(1700+10),
+ command(Cmds);
+command(["pwrrst"|Cmds]) -> %% combined pwr
+ command(["pwroff"]),
+ timer:sleep(500+10), %% >500ms
+ command(["pwron"]),
+ command(Cmds);
+command(["rst"|Cmds]) ->
+ gpio:init(?NRST),
+ gpio:set_direction(?NRST, out),
+ gpio:set(?NRST),
+ timer:sleep(1000+10), %% >1s
+ gpio:clr(?NRST),
+ timer:sleep(1700+10),
+ command(Cmds);
+command(["help"|_Cmds]) ->
+ usage(0);
+command(["-h"|_Cmds]) ->
+ usage(0);
+command([_|_Cmds]) ->
+ usage(1);
+command([]) ->
+ ok.
+
+help() ->
+ io:fwrite("usage: "++ escript:script_name() ++
+ " [-h] help|init|pwron|pwroff|pwrrst|rst\n").
+
+usage(N) ->
+ help(),
+ halt(N).
diff --git a/deps/gsms/sys.config b/deps/gsms/sys.config
new file mode 100644
index 0000000..3db5e31
--- /dev/null
+++ b/deps/gsms/sys.config
@@ -0,0 +1,47 @@
+%% -*- erlang -*-
+%%
+%% Configuration file for included erlang applications.
+%%
+[
+ %% SASL config
+ {sasl, [
+ {sasl_error_logger, {file, "log/sasl-error.log"}},
+ {errlog_type, error},
+ {error_logger_mf_dir, "log/sasl"}, % Log directory
+ {error_logger_mf_maxbytes, 10485760}, % 10 MB max file size
+ {error_logger_mf_maxfiles, 5} % 5 files max
+ ]},
+ {lager, [
+ {handlers, [
+ {lager_console_backend, info},
+ {lager_file_backend,
+ [
+ {"log/lager/error.log", error, 10485760, "$D0", 5},
+ {"log/lager/console.log", info, 10485760, "$D0", 5}
+ ]}
+ ]}
+ ]},
+{ale,
+ [{init_traces, [
+%% {[{module, gsms_uart}], debug},
+%% {[{module, gsms_0705}], debug},
+%% {[{module, gsms_router}], debug}
+ ]}]},
+ %%
+ %% GSMS interfaces (example!)
+ %%
+ {gsms, [{interfaces,
+ [
+ {gsms_0705, 1, [{device,"/dev/tty.usbserial-FTF5DP2J"},
+ {bnumber, "<phone-number>"},{baud,19200},
+ {reopen_timeout, 5000}]}
+ {gsms_0705, 2, [{device,"/dev/tty.HUAWEIMobile-Pcui"},
+ {bnumber, "<phone-number>"},
+ {reopen_timeout, 5000}]}
+ {gsms_0705, 3, [{device, "/dev/tty.usbserial"},
+ {bnumber, "<phone-number>"},
+ {baud, 9600}]}
+ ]}
+ ]}
+
+].
diff --git a/deps/gsms/tetrapak/config.ini b/deps/gsms/tetrapak/config.ini
new file mode 100644
index 0000000..1574279
--- /dev/null
+++ b/deps/gsms/tetrapak/config.ini
@@ -0,0 +1,13 @@
+[build]
+version = "~t.~o~~~c"
+
+[package]
+maintainer = "Magnus Feuer <magnus@feuerlabs.com>"
+exclude = "\\.gitignore|README.md"
+architecture = host
+
+[xref]
+ignore_undef = [
+ { gsms_uart,eat, 2 },
+ { random, unifrom, 1 }
+ ]
diff --git a/deps/lager/LICENSE b/deps/lager/LICENSE
new file mode 100644
index 0000000..e454a52
--- /dev/null
+++ b/deps/lager/LICENSE
@@ -0,0 +1,178 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/deps/lager/Makefile b/deps/lager/Makefile
new file mode 100644
index 0000000..560bc84
--- /dev/null
+++ b/deps/lager/Makefile
@@ -0,0 +1,52 @@
+.PHONY: rel stagedevrel deps test
+
+all: deps compile
+
+compile:
+ ./rebar compile
+
+deps:
+ ./rebar get-deps
+
+clean:
+ ./rebar clean
+
+distclean: clean
+ ./rebar delete-deps
+
+test:
+ ./rebar compile eunit
+
+##
+## Doc targets
+##
+docs:
+ ./rebar doc
+
+APPS = kernel stdlib sasl erts ssl tools os_mon runtime_tools crypto inets \
+ xmerl webtool snmp public_key mnesia eunit syntax_tools compiler
+COMBO_PLT = $(HOME)/.riak_combo_dialyzer_plt
+
+check_plt: compile
+ dialyzer --check_plt --plt $(COMBO_PLT) --apps $(APPS)
+
+build_plt: compile
+ dialyzer --build_plt --output_plt $(COMBO_PLT) --apps $(APPS)
+
+dialyzer: compile
+ @echo
+ @echo Use "'make check_plt'" to check PLT prior to using this target.
+ @echo Use "'make build_plt'" to build PLT prior to using this target.
+ @echo
+ @sleep 1
+ dialyzer -Wno_return --plt $(COMBO_PLT) ebin | \
+ fgrep -v -f ./dialyzer.ignore-warnings
+
+cleanplt:
+ @echo
+ @echo "Are you sure? It takes about 1/2 hour to re-build."
+ @echo Deleting $(COMBO_PLT) in 5 seconds.
+ @echo
+ sleep 5
+ rm $(COMBO_PLT)
+
diff --git a/deps/lager/README.org b/deps/lager/README.org
new file mode 100644
index 0000000..691dd96
--- /dev/null
+++ b/deps/lager/README.org
@@ -0,0 +1,222 @@
+* Overview
+ Lager (as in the beer) is a logging framework for Erlang. Its purpose is
+ to provide a more traditional way to perform logging in an erlang application
+ that plays nicely with traditional UNIX logging tools like logrotate and
+ syslog.
+
+* Features
+ - Finer grained log levels (debug, info, notice, warning, error, critical,
+ alert, emergency)
+ - Logger calls are transformed using a parse transform to allow capturing
+ Module/Function/Line/Pid information
+ - When no handler is consuming a log level (eg. debug) no event is even sent
+ to the log handler
+ - Supports multiple backends, including console and file. More are planned.
+
+* Usage
+ To use lager in your application, you need to define it as a rebar dep or have
+ some other way of including it in erlang's path. You can then add the
+ following option to the erlang compiler flags
+
+#+BEGIN_EXAMPLE
+ {parse_transform, lager_transform}
+#+END_EXAMPLE
+
+ Alternately, you can add it to the module you wish to compile with logging
+ enabled:
+
+#+BEGIN_EXAMPLE
+ -compile([{parse_transform, lager_transform}]).
+#+END_EXAMPLE
+
+ Once you have built your code with lager, you can then generate log messages
+ by doing the following:
+
+#+BEGIN_EXAMPLE
+ lager:error("Some message")
+#+END_EXAMPLE
+
+ Or:
+
+#+BEGIN_EXAMPLE
+ lager:warning("Some message with a term: ~p", [Term])
+#+END_EXAMPLE
+
+ The general form is lager:Severity() where Severity is one of the log levels
+ mentioned above.
+
+* Configuration
+ To configure lager's backends, you use an application variable (probably in
+ your app.config):
+
+#+BEGIN_EXAMPLE
+ {lager, [
+ {handlers, [
+ {lager_console_backend, info},
+ {lager_file_backend, [
+ {"error.log", error, 10485760, "$D0", 5},
+ {"console.log", info, 10485760, "$D0", 5}
+ ]}
+ ]}
+ ]}.
+#+END_EXAMPLE
+
+ The available configuration options for each backend are listed in their
+ module's documentation.
+
+* Error logger integration
+ Lager is also supplied with a error_logger handler module that translates
+ traditional erlang error messages into a friendlier format and sends them into
+ lager itself to be treated like a regular lager log call. To disable this, set
+ the lager application variable `error_logger_redirect' to `false'.
+
+ The error_logger handler will also log more complete error messages (protected
+ with use of trunc_io) to a "crash log" which can be referred to for further
+ information. The location of the crash log can be specified by the crash_log
+ application variable. If undefined it is not written at all.
+
+ Messages in the crash log are subject to a maximum message size which can be
+ specified via the crash_log_msg_size application variable.
+
+* Runtime loglevel changes
+ You can change the log level of any lager backend at runtime by doing the
+ following:
+
+#+BEGIN_EXAMPLE
+ lager:set_loglevel(lager_console_backend, debug).
+#+END_EXAMPLE
+
+ Or, for the backend with multiple handles (files, mainly):
+
+#+BEGIN_EXAMPLE
+ lager:set_loglevel(lager_file_backend, "console.log", debug).
+#+END_EXAMPLE
+
+ Lager keeps track of the minium log level being used by any backend and
+ supresses generation of messages lower than that level. This means that debug
+ log messages, when no backend is consuming debug messages, are effectively
+ free. A simple benchmark of doing 1 million debug log messages while the
+ minimum threshold was above that takes less than half a second.
+
+* Internal log rotation
+ Lager can rotate its own logs or have it done via an external process. To
+ use internal rotation, use the last 3 values in the file backend's
+ configuration tuple. For example
+
+#+BEGIN_EXAMPLE
+ {"error.log", error, 10485760, "$D0", 5}
+#+END_EXAMPLE
+
+ This tells lager to log error and above messages to "error.log" and to
+ rotate the file at midnight or when it reaches 10mb, whichever comes first
+ and to keep 5 rotated logs, in addition to the current one. Setting the
+ count to 0 does not disable rotation, it instead rotates the file and keeps
+ no previous versions around. To disable rotation set the size to 0 and the
+ date to "".
+
+ The "$D0" syntax is taken from the syntax newsyslog uses in newsyslog.conf.
+ The relevant extract follows:
+
+#+BEGIN_EXAMPLE
+ Day, week and month time format: The lead-in character
+ for day, week and month specification is a `$'-sign.
+ The particular format of day, week and month
+ specification is: [Dhh], [Ww[Dhh]] and [Mdd[Dhh]],
+ respectively. Optional time fields default to
+ midnight. The ranges for day and hour specifications
+ are:
+
+ hh hours, range 0 ... 23
+ w day of week, range 0 ... 6, 0 = Sunday
+ dd day of month, range 1 ... 31, or the
+ letter L or l to specify the last day of
+ the month.
+
+ Some examples:
+ $D0 rotate every night at midnight
+ $D23 rotate every day at 23:00 hr
+ $W0D23 rotate every week on Sunday at 23:00 hr
+ $W5D16 rotate every week on Friday at 16:00 hr
+ $M1D0 rotate on the first day of every month at
+ midnight (i.e., the start of the day)
+ $M5D6 rotate on every 5th day of the month at
+ 6:00 hr
+#+END_EXAMPLE
+
+ To configure the crash log rotation, the following application variables are
+ used:
+ - crash_log_size
+ - crash_log_date
+ - crash_log_count
+
+ See the .app.src file for further details.
+
+* Syslog Support
+ Lager syslog output is provided as a separate application;
+ [[https://github.com/basho/lager_syslog][lager_syslog]]. It is packaged as a
+ separate application so Lager itself doesn't have an indirect dependancy on a
+ port driver. Please see the lager_syslog README for configuration information.
+
+* AMQP Support
+ Jon Brisbin has written a lager backend to send lager messages into AMQP, so
+ you can aggregate logs from a cluster into a central point. You can find it
+ under the [[https://github.com/jbrisbin/lager_amqp_backend][lager_amqp_backend]]
+ project on github.
+
+* Tracing
+ Lager supports basic support for redirecting log messages based on log message
+ attributes. Lager automatically captures the pid, module, function and line at the
+ log message callsite. However, you can add any additional attributes you wish:
+
+#+BEGIN_EXAMPLE
+ lager:warning([{request, RequestID},{vhost, Vhost}], "Permission denied to ~s", [User])
+#+END_EXAMPLE
+
+ Then, in addition to the default trace attributes, you'll be able to trace
+ based on request or vhost:
+
+#+BEGIN_EXAMPLE
+ lager:trace_file("logs/example.com.error", [{vhost, "example.com"}], error)
+#+END_EXAMPLE
+
+ You can also omit the final argument, and the loglevel will default to
+ 'debug'.
+
+ Tracing to the console is similar:
+
+#+BEGIN_EXAMPLE
+ lager:trace_console([{request, 117}])
+#+END_EXAMPLE
+
+ In the above example, the loglevel is omitted, but it can be specified as the
+ second argument if desired.
+
+ You can also specify multiple expressions in a filter, or use the '*' atom as
+ a wildcard to match any message that has that attribute, regardless of its
+ value.
+
+ Tracing to an existing logfile is also supported, if you wanted to log
+ warnings from a particular module to the default error.log:
+
+#+BEGIN_EXAMPLE
+ lager:trace_file("log/error.log", [{module, mymodule}], warning)
+#+END_EXAMPLE
+
+ To view the active log backends and traces, you can use the lager:status()
+ function. To clear all active traces, you can use lager:clear_all_traces().
+
+ To delete a specific trace, store a handle for the trace when you create it,
+ that you later pass to lager:stop_trace/1:
+
+#+BEGIN_EXAMPLE
+ {ok, Trace} = lager:trace_file("log/error.log", [{module, mymodule}]),
+ ...
+ lager:stop_trace(Trace)
+#+END_EXAMPLE
+
+ Tracing to a pid is somewhat of a special case, since a pid is not a
+ data-type that serializes well. To trace by pid, use the pid as a string:
+
+#+BEGIN_EXAMPLE
+ lager:trace_console([{pid, "<0.410.0>"}])
+#+END_EXAMPLE
diff --git a/deps/lager/TODO b/deps/lager/TODO
new file mode 100644
index 0000000..ebd0998
--- /dev/null
+++ b/deps/lager/TODO
@@ -0,0 +1,3 @@
+Time based log rotation
+Syslog backends (local & remote)
+debug_module & debug_pid
diff --git a/deps/lager/dialyzer.ignore-warnings b/deps/lager/dialyzer.ignore-warnings
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/deps/lager/dialyzer.ignore-warnings
diff --git a/deps/lager/include/lager.hrl b/deps/lager/include/lager.hrl
new file mode 100644
index 0000000..7ea7b8c
--- /dev/null
+++ b/deps/lager/include/lager.hrl
@@ -0,0 +1,100 @@
+%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you under the Apache License,
+%% Version 2.0 (the "License"); you may not use this file
+%% except in compliance with the License. You may obtain
+%% a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+-ifndef(__LAGER_HRL__).
+-define(__LAGER_HRL__, true).
+
+-define(LEVELS,
+ [debug, info, notice, warning, error, critical, alert, emergency, none]).
+
+-define(DEBUG, 7).
+-define(INFO, 6).
+-define(NOTICE, 5).
+-define(WARNING, 4).
+-define(ERROR, 3).
+-define(CRITICAL, 2).
+-define(ALERT, 1).
+-define(EMERGENCY, 0).
+-define(LOG_NONE, -1).
+
+-define(LEVEL2NUM(Level),
+ case Level of
+ debug -> ?DEBUG;
+ info -> ?INFO;
+ notice -> ?NOTICE;
+ warning -> ?WARNING;
+ error -> ?ERROR;
+ critical -> ?CRITICAL;
+ alert -> ?ALERT;
+ emergency -> ?EMERGENCY
+ end).
+
+-define(NUM2LEVEL(Num),
+ case Num of
+ ?DEBUG -> debug;
+ ?INFO -> info;
+ ?NOTICE -> notice;
+ ?WARNING -> warning;
+ ?ERROR -> error;
+ ?CRITICAL -> critical;
+ ?ALERT -> alert;
+ ?EMERGENCY -> emergency
+ end).
+
+
+-define(SHOULD_LOG(Level),
+ lager_util:level_to_num(Level) =< element(1, lager_mochiglobal:get(loglevel, {?LOG_NONE, []}))).
+
+-define(NOTIFY(Level, Pid, Format, Args),
+ gen_event:notify(lager_event, {log, lager_util:level_to_num(Level),
+ lager_util:format_time(), [io_lib:format("[~p] ", [Level]),
+ io_lib:format("~p ", [Pid]), io_lib:format(Format, Args)]})).
+
+%% FOR INTERNAL USE ONLY
+%% internal non-blocking logging call
+%% there's some special handing for when we try to log (usually errors) while
+%% lager is still starting.
+-ifdef(TEST).
+-define(INT_LOG(Level, Format, Args),
+ case ?SHOULD_LOG(Level) of
+ true ->
+ ?NOTIFY(Level, self(), Format, Args);
+ _ ->
+ ok
+ end).
+-else.
+-define(INT_LOG(Level, Format, Args),
+ Self = self(),
+ %% do this in a spawn so we don't cause a deadlock calling gen_event:which_handlers
+ %% from a gen_event handler
+ spawn(fun() ->
+ case catch(gen_event:which_handlers(lager_event)) of
+ X when X == []; X == {'EXIT', noproc} ->
+ %% there's no handlers yet or lager isn't running, try again
+ %% in half a second.
+ timer:sleep(500),
+ ?NOTIFY(Level, Self, Format, Args);
+ _ ->
+ case ?SHOULD_LOG(Level) of
+ true ->
+ ?NOTIFY(Level, Self, Format, Args);
+ _ ->
+ ok
+ end
+ end
+ end)).
+-endif.
+
+-endif.
diff --git a/deps/lager/include/log.hrl b/deps/lager/include/log.hrl
new file mode 100644
index 0000000..270f06c
--- /dev/null
+++ b/deps/lager/include/log.hrl
@@ -0,0 +1,44 @@
+%%
+%% Log macros
+%%
+-ifndef(__LOG_HRL__).
+-define(__LOG_HRL__, true).
+
+-compile({parse_transform, lager_transform}).
+
+%% Lager logging levels
+%% debug, info, notice, warning, error, critical, alert, emergency, none.
+
+-define(debug(Fmt), lager:debug(Fmt)).
+-define(debug(Fmt, Args), lager:debug(Fmt, Args)).
+-define(debug(Attrs, Fmt, Args), lager:debug(Attrs, Fmt, Args)).
+
+-define(info(Fmt), lager:info(Fmt)).
+-define(info(Fmt, Args), lager:info(Fmt, Args)).
+-define(info(Attrs, Fmt, Args), lager:info(Attrs, Fmt, Args)).
+
+-define(notice(Fmt), lager:notice(Fmt)).
+-define(notice(Fmt, Args), lager:notice(Fmt, Args)).
+-define(notice(Attrs, Fmt, Args), lager:notice(Attrs, Fmt, Args)).
+
+-define(warning(Fmt), lager:warning(Fmt)).
+-define(warning(Fmt, Args), lager:warning(Fmt, Args)).
+-define(warning(Attrs, Fmt, Args), lager:warning(Attrs, Fmt, Args)).
+
+-define(error(Fmt), lager:error(Fmt)).
+-define(error(Fmt, Args), lager:error(Fmt, Args)).
+-define(error(Attrs, Fmt, Args), lager:error(Attrs, Fmt, Args)).
+
+-define(critical(Fmt), lager:critical(Fmt)).
+-define(critical(Fmt, Args), lager:critical(Fmt, Args)).
+-define(critical(Attrs, Fmt, Args), lager:critical(Attrs, Fmt, Args)).
+
+-define(alert(Fmt), lager:alert(Fmt)).
+-define(alert(Fmt, Args), lager:alert(Fmt, Args)).
+-define(alert(Attrs, Fmt, Args), lager:alert(Attrs, Fmt, Args)).
+
+-define(emergency(Fmt), lager:emergency(Fmt)).
+-define(emergency(Fmt, Args), lager:emergency(Fmt, Args)).
+-define(emergency(Attrs, Fmt, Args), lager:emergency(Attrs, Fmt, Args)).
+
+-endif.
diff --git a/deps/lager/rebar b/deps/lager/rebar
new file mode 100755
index 0000000..8645775
--- /dev/null
+++ b/deps/lager/rebar
Binary files differ
diff --git a/deps/lager/rebar.config b/deps/lager/rebar.config
new file mode 100644
index 0000000..bfaa56f
--- /dev/null
+++ b/deps/lager/rebar.config
@@ -0,0 +1,4 @@
+{erl_opts, [debug_info]}.
+{erl_first_files, ["src/lager_util.erl"]}.
+
+{cover_enabled, true}.
diff --git a/deps/lager/src/error_logger_lager_h.erl b/deps/lager/src/error_logger_lager_h.erl
new file mode 100644
index 0000000..f52fed5
--- /dev/null
+++ b/deps/lager/src/error_logger_lager_h.erl
@@ -0,0 +1,281 @@
+%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you under the Apache License,
+%% Version 2.0 (the "License"); you may not use this file
+%% except in compliance with the License. You may obtain
+%% a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+
+%% @doc A error_logger backend for redirecting events into lager.
+%% Error messages and crash logs are also optionally written to a crash log.
+
+%% @see lager_crash_log
+
+%% @private
+
+-module(error_logger_lager_h).
+
+-include("lager.hrl").
+
+-behaviour(gen_event).
+
+-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-export([format_reason/1]).
+
+-define(LOG(Level, Pid, Msg),
+ case ?SHOULD_LOG(Level) of
+ true ->
+ lager:log(Level, Pid, Msg);
+ _ -> ok
+ end).
+
+-define(LOG(Level, Pid, Fmt, Args),
+ case ?SHOULD_LOG(Level) of
+ true ->
+ lager:log(Level, Pid, Fmt, Args);
+ _ -> ok
+ end).
+
+-ifdef(TEST).
+%% Make CRASH synchronous when testing, to avoid timing headaches
+-define(CRASH_LOG(Event),
+ catch(gen_server:call(lager_crash_log, {log, Event}))).
+-else.
+-define(CRASH_LOG(Event),
+ gen_server:cast(lager_crash_log, {log, Event})).
+-endif.
+
+-spec init(any()) -> {ok, {}}.
+init(_) ->
+ {ok, {}}.
+
+handle_call(_Request, State) ->
+ {ok, ok, State}.
+
+handle_event(Event, State) ->
+ case Event of
+ {error, _GL, {Pid, Fmt, Args}} ->
+ case Fmt of
+ "** Generic server "++_ ->
+ %% gen_server terminate
+ [Name, _Msg, _State, Reason] = Args,
+ ?CRASH_LOG(Event),
+ ?LOG(error, Pid, "gen_server ~w terminated with reason: ~s",
+ [Name, format_reason(Reason)]);
+ "** State machine "++_ ->
+ %% gen_fsm terminate
+ [Name, _Msg, StateName, _StateData, Reason] = Args,
+ ?CRASH_LOG(Event),
+ ?LOG(error, Pid, "gen_fsm ~w in state ~w terminated with reason: ~s",
+ [Name, StateName, format_reason(Reason)]);
+ "** gen_event handler"++_ ->
+ %% gen_event handler terminate
+ [ID, Name, _Msg, _State, Reason] = Args,
+ ?CRASH_LOG(Event),
+ ?LOG(error, Pid, "gen_event ~w installed in ~w terminated with reason: ~s",
+ [ID, Name, format_reason(Reason)]);
+ _ ->
+ ?CRASH_LOG(Event),
+ ?LOG(error, Pid, lager:safe_format(Fmt, Args, 4096))
+ end;
+ {error_report, _GL, {Pid, std_error, D}} ->
+ ?CRASH_LOG(Event),
+ ?LOG(error, Pid, print_silly_list(D));
+ {error_report, _GL, {Pid, supervisor_report, D}} ->
+ ?CRASH_LOG(Event),
+ case lists:sort(D) of
+ [{errorContext, Ctx}, {offender, Off}, {reason, Reason}, {supervisor, Name}] ->
+ Offender = format_offender(Off),
+ ?LOG(error, Pid,
+ "Supervisor ~w had child ~s exit with reason ~s in context ~w",
+ [element(2, Name), Offender, format_reason(Reason), Ctx]);
+ _ ->
+ ?LOG(error, Pid, ["SUPERVISOR REPORT ", print_silly_list(D)])
+ end;
+ {error_report, _GL, {Pid, crash_report, [Self, Neighbours]}} ->
+ ?CRASH_LOG(Event),
+ ?LOG(error, Pid, ["CRASH REPORT ", format_crash_report(Self, Neighbours)]);
+ {warning_msg, _GL, {Pid, Fmt, Args}} ->
+ ?LOG(warning, Pid, lager:safe_format(Fmt, Args, 4096));
+ {warning_report, _GL, {Pid, std_warning, Report}} ->
+ ?LOG(warning, Pid, print_silly_list(Report));
+ {info_msg, _GL, {Pid, Fmt, Args}} ->
+ ?LOG(info, Pid, lager:safe_format(Fmt, Args, 4096));
+ {info_report, _GL, {Pid, std_info, D}} when is_list(D) ->
+ Details = lists:sort(D),
+ case Details of
+ [{application, App}, {exited, Reason}, {type, _Type}] ->
+ ?LOG(info, Pid, "Application ~w exited with reason: ~s",
+ [App, format_reason(Reason)]);
+ _ ->
+ ?LOG(info, Pid, print_silly_list(D))
+ end;
+ {info_report, _GL, {Pid, std_info, D}} ->
+ ?LOG(info, Pid, "~w", [D]);
+ {info_report, _GL, {P, progress, D}} ->
+ Details = lists:sort(D),
+ case Details of
+ [{application, App}, {started_at, Node}] ->
+ ?LOG(info, P, "Application ~w started on node ~w",
+ [App, Node]);
+ [{started, Started}, {supervisor, Name}] ->
+ MFA = format_mfa(proplists:get_value(mfargs, Started)),
+ Pid = proplists:get_value(pid, Started),
+ ?LOG(debug, P, "Supervisor ~w started ~s at pid ~w",
+ [element(2, Name), MFA, Pid]);
+ _ ->
+ ?LOG(info, P, ["PROGRESS REPORT ", print_silly_list(D)])
+ end;
+ _ ->
+ ?LOG(warning, self(), "Unexpected error_logger event ~w", [Event])
+ end,
+ {ok, State}.
+
+handle_info(_Info, State) ->
+ {ok, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%% internal functions
+
+format_crash_report(Report, Neighbours) ->
+ Name = case proplists:get_value(registered_name, Report, []) of
+ [] ->
+ %% process_info(Pid, registered_name) returns [] for unregistered processes
+ proplists:get_value(pid, Report);
+ Atom -> Atom
+ end,
+ {_Class, Reason, Trace} = proplists:get_value(error_info, Report),
+ ReasonStr = case is_atom(Reason) of
+ true ->
+ format_reason({Reason, Trace});
+ _ ->
+ format_reason(Reason)
+ end,
+ io_lib:format("Process ~w with ~w neighbours crashed with reason: ~s",
+ [Name, length(Neighbours), ReasonStr]).
+
+format_offender(Off) ->
+ case proplists:get_value(mfargs, Off) of
+ undefined ->
+ %% supervisor_bridge
+ io_lib:format("at module ~w at ~w",
+ [proplists:get_value(mod, Off), proplists:get_value(pid, Off)]);
+ MFArgs ->
+ %% regular supervisor
+ MFA = format_mfa(MFArgs),
+ Name = proplists:get_value(name, Off),
+ io_lib:format("~p started with ~s at ~w",
+ [Name, MFA, proplists:get_value(pid, Off)])
+ end.
+
+format_reason({'function not exported', [{M, F, A},MFA|_]}) ->
+ ["call to undefined function ", format_mfa({M, F, length(A)}),
+ " from ", format_mfa(MFA)];
+format_reason({undef, [MFA|_]}) ->
+ ["call to undefined function ", format_mfa(MFA)];
+format_reason({bad_return_value, Val}) ->
+ ["bad return value: ", print_val(Val)];
+format_reason({{bad_return_value, Val}, MFA}) ->
+ ["bad return value: ", print_val(Val), " in ", format_mfa(MFA)];
+format_reason({{case_clause, Val}, [MFA|_]}) ->
+ ["no case clause matching ", print_val(Val), " in ", format_mfa(MFA)];
+format_reason({function_clause, [MFA|_]}) ->
+ ["no function clause matching ", format_mfa(MFA)];
+format_reason({if_clause, [MFA|_]}) ->
+ ["no true branch found while evaluating if expression in ", format_mfa(MFA)];
+format_reason({{try_clause, Val}, [MFA|_]}) ->
+ ["no try clause matching ", print_val(Val), " in ", format_mfa(MFA)];
+format_reason({badarith, [MFA|_]}) ->
+ ["bad arithmetic expression in ", format_mfa(MFA)];
+format_reason({{badmatch, Val}, [MFA|_]}) ->
+ ["no match of right hand value ", print_val(Val), " in ", format_mfa(MFA)];
+format_reason({emfile, _Trace}) ->
+ "maximum number of file descriptors exhausted, check ulimit -n";
+format_reason({system_limit, [{M, F, _}|_] = Trace}) ->
+ Limit = case {M, F} of
+ {erlang, open_port} ->
+ "maximum number of ports exceeded";
+ {erlang, spawn} ->
+ "maximum number of processes exceeded";
+ {erlang, spawn_opt} ->
+ "maximum number of processes exceeded";
+ {erlang, list_to_atom} ->
+ "tried to create an atom larger than 255, or maximum atom count exceeded";
+ {ets, new} ->
+ "maximum number of ETS tables exceeded";
+ _ ->
+ {Str, _} = lager_trunc_io:print(Trace, 500),
+ Str
+ end,
+ ["system limit: ", Limit];
+format_reason({badarg, [MFA,MFA2|_]}) ->
+ case MFA of
+ {_M, _F, A} when is_list(A) ->
+ ["bad argument in call to ", format_mfa(MFA), " in ", format_mfa(MFA2)];
+ _ ->
+ %% seems to be generated by a bad call to a BIF
+ ["bad argument in ", format_mfa(MFA)]
+ end;
+format_reason({{badarity, {Fun, Args}}, [MFA|_]}) ->
+ {arity, Arity} = lists:keyfind(arity, 1, erlang:fun_info(Fun)),
+ [io_lib:format("fun called with wrong arity of ~w instead of ~w in ",
+ [length(Args), Arity]), format_mfa(MFA)];
+format_reason({noproc, MFA}) ->
+ ["no such process or port in call to ", format_mfa(MFA)];
+format_reason({{badfun, Term}, [MFA|_]}) ->
+ ["bad function ", print_val(Term), " in ", format_mfa(MFA)];
+format_reason(Reason) ->
+ {Str, _} = lager_trunc_io:print(Reason, 500),
+ Str.
+
+format_mfa({M, F, A}) when is_list(A) ->
+ {FmtStr, Args} = format_args(A, [], []),
+ io_lib:format("~w:~w("++FmtStr++")", [M, F | Args]);
+format_mfa({M, F, A}) when is_integer(A) ->
+ io_lib:format("~w:~w/~w", [M, F, A]);
+format_mfa(Other) ->
+ io_lib:format("~w", [Other]).
+
+format_args([], FmtAcc, ArgsAcc) ->
+ {string:join(lists:reverse(FmtAcc), ", "), lists:reverse(ArgsAcc)};
+format_args([H|T], FmtAcc, ArgsAcc) ->
+ {Str, _} = lager_trunc_io:print(H, 100),
+ format_args(T, ["~s"|FmtAcc], [Str|ArgsAcc]).
+
+print_silly_list(L) when is_list(L) ->
+ case lager_stdlib:string_p(L) of
+ true ->
+ lager_trunc_io:format("~s", [L], 4096);
+ _ ->
+ print_silly_list(L, [], [])
+ end;
+print_silly_list(L) ->
+ {Str, _} = lager_trunc_io:print(L, 4096),
+ Str.
+
+print_silly_list([], Fmt, Acc) ->
+ lager_trunc_io:format(string:join(lists:reverse(Fmt), ", "),
+ lists:reverse(Acc), 4096);
+print_silly_list([{K,V}|T], Fmt, Acc) ->
+ print_silly_list(T, ["~p: ~p" | Fmt], [V, K | Acc]);
+print_silly_list([H|T], Fmt, Acc) ->
+ print_silly_list(T, ["~p" | Fmt], [H | Acc]).
+
+print_val(Val) ->
+ {Str, _} = lager_trunc_io:print(Val, 500),
+ Str.
diff --git a/deps/lager/src/lager.app.src b/deps/lager/src/lager.app.src
new file mode 100644
index 0000000..18bbadd
--- /dev/null
+++ b/deps/lager/src/lager.app.src
@@ -0,0 +1,41 @@
+%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*-
+%% ex: ts=4 sw=4 et
+{application, lager,
+ [
+ {description, "Erlang logging framework"},
+ {vsn, git},
+ {modules, []},
+ {applications, [
+ kernel,
+ stdlib,
+ compiler,
+ syntax_tools
+ ]},
+ {registered, []},
+ {mod, {lager_app, []}},
+ {env, [
+ %% What handlers to install with what arguments
+ {handlers, [
+ {lager_console_backend, info},
+ {lager_file_backend, [
+ {"log/error.log", error, 10485760, "$D0", 5},
+ {"log/console.log", info, 10485760, "$D0", 5}
+ ]}
+ ]},
+ %% Whether to write a crash log, and where. Undefined means no crash logger.
+ {crash_log, "log/crash.log"},
+ %% Maximum size in bytes of events in the crash log - defaults to 65536
+ {crash_log_msg_size, 65536},
+ %% Maximum size of the crash log in bytes, before its rotated, set
+ %% to 0 to disable rotation - default is 0
+ {crash_log_size, 10485760},
+ %% What time to rotate the crash log - default is no time
+ %% rotation. See the README for a description of this format.
+ {crash_log_date, "$D0"},
+ %% Number of rotated crash logs to keep, 0 means keep only the
+ %% current one - default is 0
+ {crash_log_count, 5},
+ %% Whether to redirect error_logger messages into lager - defaults to true
+ {error_logger_redirect, true}
+ ]}
+ ]}.
diff --git a/deps/lager/src/lager.erl b/deps/lager/src/lager.erl
new file mode 100644
index 0000000..db40103
--- /dev/null
+++ b/deps/lager/src/lager.erl
@@ -0,0 +1,395 @@
+%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you under the Apache License,
+%% Version 2.0 (the "License"); you may not use this file
+%% except in compliance with the License. You may obtain
+%% a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+
+%% @doc The lager logging framework.
+
+-module(lager).
+
+-include("lager.hrl").
+
+%% API
+-export([start/0,
+ log/8, log_dest/9, log/3, log/4,
+ trace_file/2, trace_file/3, trace_console/1, trace_console/2,
+ clear_all_traces/0, stop_trace/1, status/0,
+ get_loglevel/1, set_loglevel/2, set_loglevel/3, get_loglevels/0,
+ minimum_loglevel/1, posix_error/1,
+ safe_format/3, safe_format_chop/3,dispatch_log/8,
+ dispatch_log1/9]).
+
+%% Fallback when parse transform is not available
+-export([debug/1,debug/2,debug/3]).
+-export([info/1,info/2,info/3]).
+-export([notice/1,notice/2,notice/3]).
+-export([warning/1,warning/2,warning/3]).
+-export([error/1,error/2,error/3]).
+-export([critical/1,critical/2,critical/3]).
+-export([alert/1,alert/2,alert/3]).
+-export([emergency/1,emergency/2,emergency/3]).
+-export([none/1,none/2,none/3]).
+
+-type log_level() :: debug | info | notice | warning | error | critical | alert | emergency.
+-type log_level_number() :: 0..7.
+
+-export_type([log_level/0, log_level_number/0]).
+
+%% API
+
+%% @doc Start the application. Mainly useful for using `-s lager' as a command
+%% line switch to the VM to make lager start on boot.
+start() -> start(lager).
+
+start(App) ->
+ start_ok(App, application:start(App, permanent)).
+
+start_ok(_App, ok) -> ok;
+start_ok(_App, {error, {already_started, _App}}) -> ok;
+start_ok(App, {error, {not_started, Dep}}) ->
+ ok = start(Dep),
+ start(App);
+start_ok(App, {error, Reason}) ->
+ erlang:error({app_start_failed, App, Reason}).
+
+-spec dispatch_log(log_level(), atom(), atom(), pos_integer(), pid(), list(), string(), list()) ->
+ ok | {error, lager_not_running}.
+%% Still used by dyn_log
+dispatch_log(Severity, Module, Function, Line, Pid, Traces, Format, Args) ->
+ {LevelThreshold,TraceFilters} = lager_mochiglobal:get(loglevel,{?LOG_NONE,[]}),
+ Result=
+ case LevelThreshold >= lager_util:level_to_num(Severity) of
+ true -> lager:log(Severity,Module,Function,Line,Pid,
+ lager_util:maybe_utc(lager_util:localtime_ms()),
+ Format,Args);
+ _ -> ok
+ end,
+ case TraceFilters of
+ [] -> Result;
+ Match when is_list(Match) ->
+ lager:log_dest(Severity,Module,Function,Line,Pid,
+ lager_util:maybe_utc(lager_util:localtime_ms()),
+ lager_util:check_traces(Traces,
+ lager_util:level_to_num(Severity),
+ TraceFilters,
+ []),
+ Format,Args);
+ _ -> ok
+ end.
+
+%%
+%% Like dispatch_log/8 but uses a new transform where
+%% level is checked before arguments are evaluated.
+%%
+dispatch_log1([],Severity,Mod,Fun,Line,Pid,_FTraces,Format,Args) ->
+ lager:log(Severity,Mod,Fun,Line,Pid,
+ lager_util:maybe_utc(lager_util:localtime_ms()),
+ Format,Args);
+dispatch_log1(Match,Severity,Mod,Fun,Line,Pid,FTraces,Format,Args)
+ when is_list(Match) ->
+ lager:log(Severity,Mod,Fun,Line,Pid,
+ lager_util:maybe_utc(lager_util:localtime_ms()),
+ Format,Args),
+ lager:log_dest(Severity,Mod,Fun,Line,Pid,
+ lager_util:maybe_utc(lager_util:localtime_ms()),
+ lager_util:check_f_traces(FTraces,
+ lager_util:level_to_num(Severity),
+ Match,[]),
+ Format,Args);
+dispatch_log1(_,_Severity,_Mod,_Func,_Line,_Pid,_FTraces,_Format,_Args) ->
+ ok.
+
+
+%% @private
+-spec log(log_level(), atom(), atom(), pos_integer(), pid(), tuple(), string(), list()) ->
+ ok | {error, lager_not_running}.
+log(Level, Module, Function, Line, Pid, Time, Format, Args) ->
+ Timestamp = lager_util:format_time(Time),
+ Msg = [["[", atom_to_list(Level), "] "],
+ io_lib:format("~p@~p:~p:~p ", [Pid, Module, Function, Line]),
+ safe_format_chop(Format, Args, 4096)],
+ safe_notify({log, lager_util:level_to_num(Level), Timestamp, Msg}).
+
+%% @private
+-spec log_dest(log_level(), atom(), atom(), pos_integer(), pid(), tuple(), list(), string(), list()) ->
+ ok | {error, lager_not_running}.
+log_dest(_Level, _Module, _Function, _Line, _Pid, _Time, [], _Format, _Args) ->
+ ok;
+log_dest(Level, Module, Function, Line, Pid, Time, Dest, Format, Args) ->
+ Timestamp = lager_util:format_time(Time),
+ Msg = [["[", atom_to_list(Level), "] "],
+ io_lib:format("~p@~p:~p:~p ", [Pid, Module, Function, Line]),
+ safe_format_chop(Format, Args, 4096)],
+ safe_notify({log, Dest, lager_util:level_to_num(Level), Timestamp, Msg}).
+
+
+%% @doc Manually log a message into lager without using the parse transform.
+-spec log(log_level(), pid(), list()) -> ok | {error, lager_not_running}.
+log(Level, Pid, Message) ->
+ Timestamp = lager_util:format_time(),
+ Msg = [["[", atom_to_list(Level), "] "], io_lib:format("~p ", [Pid]),
+ safe_format_chop("~s", [Message], 4096)],
+ safe_notify({log, lager_util:level_to_num(Level), Timestamp, Msg}).
+
+%% @doc Manually log a message into lager without using the parse transform.
+-spec log(log_level(), pid(), string(), list()) -> ok | {error, lager_not_running}.
+log(Level, Pid, Format, Args) ->
+ Timestamp = lager_util:format_time(),
+ Msg = [["[", atom_to_list(Level), "] "], io_lib:format("~p ", [Pid]),
+ safe_format_chop(Format, Args, 4096)],
+ safe_notify({log, lager_util:level_to_num(Level), Timestamp, Msg}).
+
+trace_file(File, Filter) ->
+ trace_file(File, Filter, debug).
+
+trace_file(File, Filter, Level) ->
+ Trace0 = {Filter, Level, {lager_file_backend, File}},
+ case lager_util:validate_trace(Trace0) of
+ {ok, Trace} ->
+ Handlers = gen_event:which_handlers(lager_event),
+ %% check if this file backend is already installed
+ case lists:member({lager_file_backend, File}, Handlers) of
+ false ->
+ %% install the handler
+ supervisor:start_child(lager_handler_watcher_sup,
+ [lager_event, {lager_file_backend, File}, {File, none}]);
+ _ ->
+ ok
+ end,
+ %% install the trace.
+ {MinLevel, Traces} = lager_mochiglobal:get(loglevel),
+ case lists:member(Trace, Traces) of
+ false ->
+ lager_mochiglobal:put(loglevel, {MinLevel, [Trace|Traces]});
+ _ -> ok
+ end,
+ {ok, Trace};
+ Error ->
+ Error
+ end.
+
+trace_console(Filter) ->
+ trace_console(Filter, debug).
+
+trace_console(Filter, Level) ->
+ Trace0 = {Filter, Level, lager_console_backend},
+ case lager_util:validate_trace(Trace0) of
+ {ok, Trace} ->
+ {MinLevel, Traces} = lager_mochiglobal:get(loglevel),
+ case lists:member(Trace, Traces) of
+ false ->
+ lager_mochiglobal:put(loglevel, {MinLevel, [Trace|Traces]});
+ _ -> ok
+ end,
+ {ok, Trace};
+ Error ->
+ Error
+ end.
+
+stop_trace({_Filter, _Level, Target} = Trace) ->
+ {MinLevel, Traces} = lager_mochiglobal:get(loglevel),
+ NewTraces = lists:delete(Trace, Traces),
+ lager_mochiglobal:put(loglevel, {MinLevel, NewTraces}),
+ case get_loglevel(Target) of
+ none ->
+ %% check no other traces point here
+ case lists:keyfind(Target, 3, NewTraces) of
+ false ->
+ gen_event:delete_handler(lager_event, Target, []);
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
+ end,
+ ok.
+
+clear_all_traces() ->
+ {MinLevel, _Traces} = lager_mochiglobal:get(loglevel),
+ lager_mochiglobal:put(loglevel, {MinLevel, []}),
+ [begin
+ case get_loglevel(Handler) of
+ none ->
+ gen_event:delete_handler(lager_event, Handler, []);
+ _ ->
+ ok
+ end
+ end || Handler <- gen_event:which_handlers(lager_event)],
+ ok.
+
+status() ->
+ Handlers = gen_event:which_handlers(lager_event),
+ Status = ["Lager status:\n",
+ [begin
+ Level = get_loglevel(Handler),
+ case Handler of
+ {lager_file_backend, File} ->
+ io_lib:format("File ~s at level ~p\n", [File, Level]);
+ lager_console_backend ->
+ io_lib:format("Console at level ~p\n", [Level]);
+ _ ->
+ []
+ end
+ end || Handler <- Handlers],
+ "Active Traces:\n",
+ [begin
+ io_lib:format("Tracing messages matching ~p at level ~p to ~p\n",
+ [Filter, lager_util:num_to_level(Level), Destination])
+ end || {Filter, Level, Destination} <- element(2, lager_mochiglobal:get(loglevel))]],
+ io:put_chars(Status).
+
+%% @doc Set the loglevel for a particular backend.
+set_loglevel(Handler, Level) when is_atom(Level) ->
+ Reply = gen_event:call(lager_event, Handler, {set_loglevel, Level}, infinity),
+ %% recalculate min log level
+ MinLog = minimum_loglevel(get_loglevels()),
+ {_, Traces} = lager_mochiglobal:get(loglevel),
+ lager_mochiglobal:put(loglevel, {MinLog, Traces}),
+ Reply.
+
+%% @doc Set the loglevel for a particular backend that has multiple identifiers
+%% (eg. the file backend).
+set_loglevel(Handler, Ident, Level) when is_atom(Level) ->
+ io:format("handler: ~p~n", [{Handler, Ident}]),
+ Reply = gen_event:call(lager_event, {Handler, Ident}, {set_loglevel, Level}, infinity),
+ %% recalculate min log level
+ MinLog = minimum_loglevel(get_loglevels()),
+ {_, Traces} = lager_mochiglobal:get(loglevel),
+ lager_mochiglobal:put(loglevel, {MinLog, Traces}),
+ Reply.
+
+%% @doc Get the loglevel for a particular backend. In the case that the backend
+%% has multiple identifiers, the lowest is returned
+get_loglevel(Handler) ->
+ case gen_event:call(lager_event, Handler, get_loglevel, infinity) of
+ X when is_integer(X) ->
+ lager_util:num_to_level(X);
+ Y -> Y
+ end.
+
+%% @doc Try to convert an atom to a posix error, but fall back on printing the
+%% term if its not a valid posix error code.
+posix_error(Error) when is_atom(Error) ->
+ case erl_posix_msg:message(Error) of
+ "unknown POSIX error" -> atom_to_list(Error);
+ Message -> Message
+ end;
+posix_error(Error) ->
+ safe_format_chop("~p", [Error], 4096).
+
+%% @private
+get_loglevels() ->
+ [gen_event:call(lager_event, Handler, get_loglevel, infinity) ||
+ Handler <- gen_event:which_handlers(lager_event)].
+
+%% @private
+minimum_loglevel([]) ->
+ -1; %% lower than any log level, logging off
+minimum_loglevel(Levels) ->
+ erlang:hd(lists:reverse(lists:sort(Levels))).
+
+safe_notify(Event) ->
+ case whereis(lager_event) of
+ undefined ->
+ %% lager isn't running
+ {error, lager_not_running};
+ Pid ->
+ gen_event:sync_notify(Pid, Event)
+ end.
+
+%% @doc Print the format string `Fmt' with `Args' safely with a size
+%% limit of `Limit'. If the format string is invalid, or not enough
+%% arguments are supplied 'FORMAT ERROR' is printed with the offending
+%% arguments. The caller is NOT crashed.
+
+safe_format(Fmt, Args, Limit) ->
+ safe_format(Fmt, Args, Limit, []).
+
+safe_format(Fmt, Args, Limit, Options) ->
+ try lager_trunc_io:format(Fmt, Args, Limit, Options) of
+ Result -> Result
+ catch
+ _:_ -> lager_trunc_io:format("FORMAT ERROR: ~p ~p", [Fmt, Args], Limit)
+ end.
+
+%% @private
+safe_format_chop(Fmt, Args, Limit) ->
+ safe_format(Fmt, Args, Limit, [{chomp, true}]).
+%%
+%% when code is not compiled with parse_transform the following code
+%% is used instead. maybe warn about the fact?
+%%
+debug(Fmt) -> dyn_log(debug, [], Fmt, []).
+debug(Fmt,Args) -> dyn_log(debug, [], Fmt, Args).
+debug(Attrs,Fmt,Args) -> dyn_log(debug, Attrs, Fmt, Args).
+
+info(Fmt) -> dyn_log(info, [], Fmt, []).
+info(Fmt,Args) -> dyn_log(info, [], Fmt, Args).
+info(Attrs,Fmt,Args) -> dyn_log(info, Attrs, Fmt, Args).
+
+notice(Fmt) -> dyn_log(notice, [], Fmt, []).
+notice(Fmt,Args) -> dyn_log(notice, [], Fmt, Args).
+notice(Attrs,Fmt,Args) -> dyn_log(notice, Attrs, Fmt, Args).
+
+warning(Fmt) -> dyn_log(warning, [], Fmt, []).
+warning(Fmt,Args) -> dyn_log(warning, [], Fmt, Args).
+warning(Attrs,Fmt,Args) -> dyn_log(warning, Attrs, Fmt, Args).
+
+error(Fmt) -> dyn_log(error, [], Fmt, []).
+error(Fmt,Args) -> dyn_log(error, [], Fmt, Args).
+error(Attrs,Fmt,Args) -> dyn_log(error, Attrs, Fmt, Args).
+
+critical(Fmt) -> dyn_log(critical, [], Fmt, []).
+critical(Fmt,Args) -> dyn_log(critical, [], Fmt, Args).
+critical(Attrs,Fmt,Args) -> dyn_log(critical, Attrs, Fmt, Args).
+
+alert(Fmt) -> dyn_log(alert, [], Fmt, []).
+alert(Fmt,Args) -> dyn_log(alert, [], Fmt, Args).
+alert(Attrs,Fmt,Args) -> dyn_log(alert, Attrs, Fmt, Args).
+
+emergency(Fmt) -> dyn_log(emergency, [], Fmt, []).
+emergency(Fmt,Args) -> dyn_log(emergency, [], Fmt, Args).
+emergency(Attrs,Fmt,Args) -> dyn_log(emergency, Attrs, Fmt, Args).
+
+none(Fmt) -> dyn_log(none, [], Fmt, []).
+none(Fmt,Args) -> dyn_log(none, [], Fmt, Args).
+none(Attrs,Fmt,Args) -> dyn_log(none, Attrs, Fmt, Args).
+
+%% @private
+-spec dyn_log(log_level(), list(), string(), list()) ->
+ ok | {error, lager_not_running}.
+
+dyn_log(Severity, Attrs, Fmt, Args) ->
+ try erlang:error(fail) of
+ _ -> strange
+ catch
+ error:_ ->
+ case erlang:get_stacktrace() of
+ [_,{M,F,_A}|_] ->
+ dispatch_log(Severity, M, F, 0, self(),
+ [{module,M},{function,F},
+ {pid,pid_to_list(self())}|
+ Attrs],
+ Fmt, Args);
+ [_,{M,F,_A,Loc}|_] ->
+ L = proplists:get_value(line,Loc,0),
+ dispatch_log(Severity, M, F, L, self(),
+ [{module,M},{function,F},
+ {line,L},{pid,pid_to_list(self())}|
+ Attrs],
+ Fmt, Args);
+ [] ->
+ erlang:display({Severity, Fmt})
+ end
+ end.
diff --git a/deps/lager/src/lager_app.erl b/deps/lager/src/lager_app.erl
new file mode 100644
index 0000000..3a4f3fe
--- /dev/null
+++ b/deps/lager/src/lager_app.erl
@@ -0,0 +1,77 @@
+%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you under the Apache License,
+%% Version 2.0 (the "License"); you may not use this file
+%% except in compliance with the License. You may obtain
+%% a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+
+%% @doc Lager's application module. Not a lot to see here.
+
+%% @private
+
+-module(lager_app).
+
+-behaviour(application).
+-include("lager.hrl").
+
+-export([start/0,
+ start/2,
+ stop/1]).
+
+start() ->
+ application:start(lager).
+
+start(_StartType, _StartArgs) ->
+ %% until lager is completely started, allow all messages to go through
+ lager_mochiglobal:put(loglevel, {?DEBUG, []}),
+ {ok, Pid} = lager_sup:start_link(),
+ Handlers = case application:get_env(lager, handlers) of
+ undefined ->
+ [{lager_console_backend, info},
+ {lager_file_backend, [{"log/error.log", error, 10485760, "", 5},
+ {"log/console.log", info, 10485760, "", 5}]}];
+ {ok, Val} ->
+ Val
+ end,
+
+ [supervisor:start_child(lager_handler_watcher_sup, [lager_event, Module, Config]) ||
+ {Module, Config} <- expand_handlers(Handlers)],
+
+ %% mask the messages we have no use for
+ MinLog = lager:minimum_loglevel(lager:get_loglevels()),
+ {_, Traces} = lager_mochiglobal:get(loglevel),
+ lager_mochiglobal:put(loglevel, {MinLog, Traces}),
+
+ SavedHandlers = case application:get_env(lager, error_logger_redirect) of
+ {ok, false} ->
+ [];
+ _ ->
+ supervisor:start_child(lager_handler_watcher_sup, [error_logger, error_logger_lager_h, []]),
+ %% Should we allow user to whitelist handlers to not be removed?
+ [begin error_logger:delete_report_handler(X), X end ||
+ X <- gen_event:which_handlers(error_logger) -- [error_logger_lager_h]]
+ end,
+
+ {ok, Pid, SavedHandlers}.
+
+
+stop(Handlers) ->
+ [error_logger:add_report_handler(Handler) || Handler <- Handlers],
+ ok.
+
+expand_handlers([]) ->
+ [];
+expand_handlers([{lager_file_backend, Configs}|T]) ->
+ [{{lager_file_backend, element(1, Config)}, Config} || Config <- Configs] ++
+ expand_handlers(T);
+expand_handlers([H|T]) ->
+ [H | expand_handlers(T)].
diff --git a/deps/lager/src/lager_console_backend.erl b/deps/lager/src/lager_console_backend.erl
new file mode 100644
index 0000000..1e98fe5
--- /dev/null
+++ b/deps/lager/src/lager_console_backend.erl
@@ -0,0 +1,273 @@
+%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you under the Apache License,
+%% Version 2.0 (the "License"); you may not use this file
+%% except in compliance with the License. You may obtain
+%% a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+
+%% @doc Console backend for lager. Configured with a single option, the loglevel
+%% desired.
+
+-module(lager_console_backend).
+
+-behaviour(gen_event).
+
+-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-record(state, {level, verbose}).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-compile([{parse_transform, lager_transform}]).
+-endif.
+
+-include("lager.hrl").
+
+%% @private
+init(Level) when is_atom(Level) ->
+ case lists:member(Level, ?LEVELS) of
+ true ->
+ {ok, #state{level=lager_util:level_to_num(Level), verbose=false}};
+ _ ->
+ {error, bad_log_level}
+ end;
+init([Level, Verbose]) ->
+ case lists:member(Level, ?LEVELS) of
+ true ->
+ {ok, #state{level=lager_util:level_to_num(Level), verbose=Verbose}};
+ _ ->
+ {error, bad_log_level}
+ end.
+
+
+%% @private
+handle_call(get_loglevel, #state{level=Level} = State) ->
+ {ok, Level, State};
+handle_call({set_loglevel, Level}, State) ->
+ case lists:member(Level, ?LEVELS) of
+ true ->
+ {ok, ok, State#state{level=lager_util:level_to_num(Level)}};
+ _ ->
+ {ok, {error, bad_log_level}, State}
+ end;
+handle_call(_Request, State) ->
+ {ok, ok, State}.
+
+%% @private
+handle_event({log, Dest, Level, {Date, Time}, [LevelStr, Location, Message]},
+ #state{level=L, verbose=Verbose} = State) when Level > L ->
+ case lists:member(lager_console_backend, Dest) of
+ true ->
+ case Verbose of
+ true ->
+ io:put_chars([Date, " ", Time, " ", LevelStr, Location, Message, "\n"]);
+ _ ->
+ io:put_chars([Time, " ", LevelStr, Message, "\n"])
+ end,
+ {ok, State};
+ false ->
+ {ok, State}
+ end;
+handle_event({log, Level, {Date, Time}, [LevelStr, Location, Message]},
+ #state{level=LogLevel, verbose=Verbose} = State) when Level =< LogLevel ->
+ case Verbose of
+ true ->
+ io:put_chars([Date, " ", Time, " ", LevelStr, Location, Message, "\n"]);
+ _ ->
+ io:put_chars([Time, " ", LevelStr, Message, "\n"])
+ end,
+ {ok, State};
+handle_event(_Event, State) ->
+ {ok, State}.
+
+%% @private
+handle_info(_Info, State) ->
+ {ok, State}.
+
+%% @private
+terminate(_Reason, _State) ->
+ ok.
+
+%% @private
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+-ifdef(TEST).
+console_log_test_() ->
+ %% tiny recursive fun that pretends to be a group leader
+ F = fun(Self) ->
+ fun() ->
+ YComb = fun(Fun) ->
+ receive
+ {io_request, From, ReplyAs, {put_chars, unicode, _Msg}} = Y ->
+ From ! {io_reply, ReplyAs, ok},
+ Self ! Y,
+ Fun(Fun);
+ Other ->
+ ?debugFmt("unexpected message ~p~n", [Other]),
+ Self ! Other
+ end
+ end,
+ YComb(YComb)
+ end
+ end,
+
+ {foreach,
+ fun() ->
+ error_logger:tty(false),
+ application:load(lager),
+ application:set_env(lager, handlers, []),
+ application:set_env(lager, error_logger_redirect, false),
+ application:start(compiler),
+ application:start(syntax_tools),
+ application:start(lager)
+ end,
+ fun(_) ->
+ application:stop(lager),
+ error_logger:tty(true)
+ end,
+ [
+ {"regular console logging",
+ fun() ->
+ Pid = spawn(F(self())),
+ gen_event:add_handler(lager_event, lager_console_backend, info),
+ erlang:group_leader(Pid, whereis(lager_event)),
+ lager:log(info, self(), "Test message"),
+ receive
+ {io_request, From, ReplyAs, {put_chars, unicode, Msg}} ->
+ From ! {io_reply, ReplyAs, ok},
+ ?assertMatch([_, "[info]", "Test message\n"], re:split(Msg, " ", [{return, list}, {parts, 3}]))
+ after
+ 500 ->
+ ?assert(false)
+ end
+ end
+ },
+ {"verbose console logging",
+ fun() ->
+ Pid = spawn(F(self())),
+ erlang:group_leader(Pid, whereis(lager_event)),
+ gen_event:add_handler(lager_event, lager_console_backend, [info, true]),
+ lager:log(info, self(), "Test message"),
+ lager:log(info, self(), "Test message"),
+ PidStr = pid_to_list(self()),
+ receive
+ {io_request, _, _, {put_chars, unicode, Msg}} ->
+ ?assertMatch([_, _, "[info]", PidStr, "Test message\n"], re:split(Msg, " ", [{return, list}, {parts, 5}]))
+ after
+ 500 ->
+ ?assert(false)
+ end
+ end
+ },
+ {"tracing should work",
+ fun() ->
+ Pid = spawn(F(self())),
+ gen_event:add_handler(lager_event, lager_console_backend, info),
+ erlang:group_leader(Pid, whereis(lager_event)),
+ lager_mochiglobal:put(loglevel, {?INFO, []}),
+ lager:debug("Test message"),
+ receive
+ {io_request, From, ReplyAs, {put_chars, unicode, _Msg}} ->
+ From ! {io_reply, ReplyAs, ok},
+ ?assert(false)
+ after
+ 500 ->
+ ?assert(true)
+ end,
+ {ok, _} = lager:trace_console([{module, ?MODULE}]),
+ lager:debug("Test message"),
+ receive
+ {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} ->
+ From1 ! {io_reply, ReplyAs1, ok},
+ ?assertMatch([_, "[debug]", "Test message\n"], re:split(Msg1, " ", [{return, list}, {parts, 3}]))
+ after
+ 500 ->
+ ?assert(false)
+ end
+ end
+ },
+ {"tracing doesn't duplicate messages",
+ fun() ->
+ Pid = spawn(F(self())),
+ gen_event:add_handler(lager_event, lager_console_backend, info),
+ lager_mochiglobal:put(loglevel, {?INFO, []}),
+ erlang:group_leader(Pid, whereis(lager_event)),
+ lager:debug("Test message"),
+ receive
+ {io_request, From, ReplyAs, {put_chars, unicode, _Msg}} ->
+ From ! {io_reply, ReplyAs, ok},
+ ?assert(false)
+ after
+ 500 ->
+ ?assert(true)
+ end,
+ {ok, _} = lager:trace_console([{module, ?MODULE}]),
+ lager:error("Test message"),
+ receive
+ {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} ->
+ From1 ! {io_reply, ReplyAs1, ok},
+ ?assertMatch([_, "[error]", "Test message\n"], re:split(Msg1, " ", [{return, list}, {parts, 3}]))
+ after
+ 1000 ->
+ ?assert(false)
+ end,
+ %% make sure this event wasn't duplicated
+ receive
+ {io_request, From2, ReplyAs2, {put_chars, unicode, _Msg2}} ->
+ From2 ! {io_reply, ReplyAs2, ok},
+ ?assert(false)
+ after
+ 500 ->
+ ?assert(true)
+ end
+ end
+ }
+
+ ]
+ }.
+
+set_loglevel_test_() ->
+ {foreach,
+ fun() ->
+ error_logger:tty(false),
+ application:load(lager),
+ application:set_env(lager, handlers, [{lager_console_backend, info}]),
+ application:set_env(lager, error_logger_redirect, false),
+ application:start(lager)
+ end,
+ fun(_) ->
+ application:stop(lager),
+ error_logger:tty(true)
+ end,
+ [
+ {"Get/set loglevel test",
+ fun() ->
+ ?assertEqual(info, lager:get_loglevel(lager_console_backend)),
+ lager:set_loglevel(lager_console_backend, debug),
+ ?assertEqual(debug, lager:get_loglevel(lager_console_backend))
+ end
+ },
+ {"Get/set invalid loglevel test",
+ fun() ->
+ ?assertEqual(info, lager:get_loglevel(lager_console_backend)),
+ ?assertEqual({error, bad_log_level},
+ lager:set_loglevel(lager_console_backend, fatfinger)),
+ ?assertEqual(info, lager:get_loglevel(lager_console_backend))
+ end
+ }
+
+ ]
+ }.
+
+-endif.
diff --git a/deps/lager/src/lager_crash_log.erl b/deps/lager/src/lager_crash_log.erl
new file mode 100644
index 0000000..e4aaa33
--- /dev/null
+++ b/deps/lager/src/lager_crash_log.erl
@@ -0,0 +1,339 @@
+%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you under the Apache License,
+%% Version 2.0 (the "License"); you may not use this file
+%% except in compliance with the License. You may obtain
+%% a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+
+%% @doc Lager crash log writer. This module implements a gen_server which writes
+%% error_logger error messages out to a file in their original format. The
+%% location to which it logs is configured by the application var `crash_log'.
+%% Omitting this variable disables crash logging. Crash logs are printed safely
+%% using trunc_io via code mostly lifted from riak_err.
+%%
+%% The `crash_log_msg_size' application var is used to specify the maximum
+%% size of any message to be logged. `crash_log_size' is used to specify the
+%% maximum size of the crash log before it will be rotated (0 will disable).
+%% Time based rotation is configurable via `crash_log_date', the syntax is
+%% documented in the README. To control the number of rotated files to be
+%% retained, use `crash_log_count'.
+
+-module(lager_crash_log).
+
+-include("lager.hrl").
+
+-behaviour(gen_server).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-include_lib("kernel/include/file.hrl").
+-endif.
+
+%% callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-export([start_link/5, start/5]).
+
+-record(state, {
+ name,
+ fd,
+ inode,
+ fmtmaxbytes,
+ size,
+ date,
+ count,
+ flap=false
+ }).
+
+%% @private
+start_link(Filename, MaxBytes, Size, Date, Count) ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [Filename, MaxBytes,
+ Size, Date, Count], []).
+
+%% @private
+start(Filename, MaxBytes, Size, Date, Count) ->
+ gen_server:start({local, ?MODULE}, ?MODULE, [Filename, MaxBytes, Size,
+ Date, Count], []).
+
+%% @private
+init([Filename, MaxBytes, Size, Date, Count]) ->
+ case lager_util:open_logfile(Filename, false) of
+ {ok, {FD, Inode, _}} ->
+ schedule_rotation(Date),
+ {ok, #state{name=Filename, fd=FD, inode=Inode,
+ fmtmaxbytes=MaxBytes, size=Size, count=Count, date=Date}};
+ {error, Reason} ->
+ ?INT_LOG(error, "Failed to open crash log file ~s with error: ~s",
+ [Filename, file:format_error(Reason)]),
+ {ok, #state{name=Filename, fmtmaxbytes=MaxBytes, flap=true,
+ size=Size, count=Count, date=Date}}
+ end.
+
+%% @private
+handle_call({log, _} = Log, _From, State) ->
+ {Reply, NewState} = do_log(Log, State),
+ {reply, Reply, NewState};
+handle_call(_Call, _From, State) ->
+ {reply, ok, State}.
+
+%% @private
+handle_cast({log, _} = Log, State) ->
+ {_, NewState} = do_log(Log, State),
+ {noreply, NewState};
+handle_cast(_Request, State) ->
+ {noreply, State}.
+
+%% @private
+handle_info(rotate, #state{name=Name, count=Count, date=Date} = State) ->
+ lager_util:rotate_logfile(Name, Count),
+ schedule_rotation(Date),
+ {noreply, State};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+%% @private
+terminate(_Reason, _State) ->
+ ok.
+
+%% @private
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+schedule_rotation(undefined) ->
+ undefined;
+schedule_rotation(Date) ->
+ erlang:send_after(lager_util:calculate_next_rotation(Date) * 1000, self(), rotate).
+
+%% ===== Begin code lifted from riak_err =====
+
+-spec limited_fmt(string(), list(), integer()) -> iolist().
+%% @doc Format Fmt and Args similar to what io_lib:format/2 does but with
+%% limits on how large the formatted string may be.
+%%
+%% If the Args list's size is larger than TermMaxSize, then the
+%% formatting is done by trunc_io:print/2, where FmtMaxBytes is used
+%% to limit the formatted string's size.
+
+limited_fmt(Fmt, Args, FmtMaxBytes) ->
+ lager:safe_format(Fmt, Args, FmtMaxBytes).
+
+limited_str(Term, FmtMaxBytes) ->
+ {Str, _} = lager_trunc_io:print(Term, FmtMaxBytes),
+ Str.
+
+other_node_suffix(Pid) when node(Pid) =/= node() ->
+ "** at node " ++ atom_to_list(node(Pid)) ++ " **\n";
+other_node_suffix(_) ->
+ "".
+
+perhaps_a_sasl_report(error_report, {Pid, Type, Report}, FmtMaxBytes) ->
+ case lager_stdlib:is_my_error_report(Type) of
+ true ->
+ {sasl_type_to_report_head(Type), Pid,
+ sasl_limited_str(Type, Report, FmtMaxBytes), true};
+ false ->
+ {ignore, ignore, ignore, false}
+ end;
+%perhaps_a_sasl_report(info_report, {Pid, Type, Report}, FmtMaxBytes) ->
+ %case lager_stdlib:is_my_info_report(Type) of
+ %true ->
+ %{sasl_type_to_report_head(Type), Pid,
+ %sasl_limited_str(Type, Report, FmtMaxBytes), false};
+ %false ->
+ %{ignore, ignore, ignore, false}
+ %end;
+perhaps_a_sasl_report(_, _, _) ->
+ {ignore, ignore, ignore, false}.
+
+sasl_type_to_report_head(supervisor_report) ->
+ "SUPERVISOR REPORT";
+sasl_type_to_report_head(crash_report) ->
+ "CRASH REPORT";
+sasl_type_to_report_head(progress) ->
+ "PROGRESS REPORT".
+
+sasl_limited_str(supervisor_report, Report, FmtMaxBytes) ->
+ Name = lager_stdlib:sup_get(supervisor, Report),
+ Context = lager_stdlib:sup_get(errorContext, Report),
+ Reason = lager_stdlib:sup_get(reason, Report),
+ Offender = lager_stdlib:sup_get(offender, Report),
+ FmtString = " Supervisor: ~p~n Context: ~p~n Reason: "
+ "~s~n Offender: ~s~n~n",
+ {ReasonStr, _} = lager_trunc_io:print(Reason, FmtMaxBytes),
+ {OffenderStr, _} = lager_trunc_io:print(Offender, FmtMaxBytes),
+ io_lib:format(FmtString, [Name, Context, ReasonStr, OffenderStr]);
+sasl_limited_str(progress, Report, FmtMaxBytes) ->
+ [begin
+ {Str, _} = lager_trunc_io:print(Data, FmtMaxBytes),
+ io_lib:format(" ~16w: ~s~n", [Tag, Str])
+ end || {Tag, Data} <- Report];
+sasl_limited_str(crash_report, Report, FmtMaxBytes) ->
+ lager_stdlib:proc_lib_format(Report, FmtMaxBytes).
+
+do_log({log, Event}, #state{name=Name, fd=FD, inode=Inode, flap=Flap,
+ fmtmaxbytes=FmtMaxBytes, size=RotSize, count=Count} = State) ->
+ %% borrowed from riak_err
+ {ReportStr, Pid, MsgStr, _ErrorP} = case Event of
+ {error, _GL, {Pid1, Fmt, Args}} ->
+ {"ERROR REPORT", Pid1, limited_fmt(Fmt, Args, FmtMaxBytes), true};
+ {error_report, _GL, {Pid1, std_error, Rep}} ->
+ {"ERROR REPORT", Pid1, limited_str(Rep, FmtMaxBytes), true};
+ {error_report, _GL, Other} ->
+ perhaps_a_sasl_report(error_report, Other, FmtMaxBytes);
+ _ ->
+ {ignore, ignore, ignore, false}
+ end,
+ if ReportStr == ignore ->
+ {ok, State};
+ true ->
+ case lager_util:ensure_logfile(Name, FD, Inode, false) of
+ {ok, {_, _, Size}} when RotSize /= 0, Size > RotSize ->
+ lager_util:rotate_logfile(Name, Count),
+ handle_cast({log, Event}, State);
+ {ok, {NewFD, NewInode, _Size}} ->
+ {Date, TS} = lager_util:format_time(
+ lager_stdlib:maybe_utc(erlang:localtime())),
+ Time = [Date, " ", TS," =", ReportStr, "====\n"],
+ NodeSuffix = other_node_suffix(Pid),
+ Msg = io_lib:format("~s~s~s", [Time, MsgStr, NodeSuffix]),
+ case file:write(NewFD, Msg) of
+ {error, Reason} when Flap == false ->
+ ?INT_LOG(error, "Failed to write log message to file ~s: ~s",
+ [Name, file:format_error(Reason)]),
+ {ok, State#state{fd=NewFD, inode=NewInode, flap=true}};
+ ok ->
+ {ok, State#state{fd=NewFD, inode=NewInode, flap=false}};
+ _ ->
+ {ok, State#state{fd=NewFD, inode=NewInode}}
+ end;
+ {error, Reason} ->
+ case Flap of
+ true ->
+ {ok, State};
+ _ ->
+ ?INT_LOG(error, "Failed to reopen crash log ~s with error: ~s",
+ [Name, file:format_error(Reason)]),
+ {ok, State#state{flap=true}}
+ end
+ end
+ end.
+
+
+-ifdef(TEST).
+
+filesystem_test_() ->
+ {foreach,
+ fun() ->
+ file:write_file("crash_test.log", ""),
+ error_logger:tty(false),
+ application:load(lager),
+ application:set_env(lager, handlers, [{lager_test_backend, info}]),
+ application:set_env(lager, error_logger_redirect, true),
+ application:unset_env(lager, crash_log),
+ application:start(compiler),
+ application:start(syntax_tools),
+ application:start(lager),
+ timer:sleep(100),
+ lager_test_backend:flush()
+ end,
+ fun(_) ->
+ case whereis(lager_crash_log) of
+ P when is_pid(P) ->
+ exit(P, kill);
+ _ -> ok
+ end,
+ file:delete("crash_test.log"),
+ application:stop(lager),
+ error_logger:tty(true)
+ end,
+ [
+ {"under normal circumstances, file should be opened",
+ fun() ->
+ {ok, _} = ?MODULE:start_link("crash_test.log", 65535, 0, undefined, 0),
+ _ = gen_event:which_handlers(error_logger),
+ sync_error_logger:error_msg("Test message\n"),
+ {ok, Bin} = file:read_file("crash_test.log"),
+ ?assertMatch([_, "Test message\n"], re:split(Bin, "\n", [{return, list}, {parts, 2}]))
+ end
+ },
+ {"file can't be opened on startup triggers an error message",
+ fun() ->
+ {ok, FInfo} = file:read_file_info("crash_test.log"),
+ file:write_file_info("crash_test.log", FInfo#file_info{mode = 0}),
+ {ok, _} = ?MODULE:start_link("crash_test.log", 65535, 0, undefined, 0),
+ ?assertEqual(1, lager_test_backend:count()),
+ {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ ?assertEqual("Failed to open crash log file crash_test.log with error: permission denied", lists:flatten(Message))
+ end
+ },
+ {"file that becomes unavailable at runtime should trigger an error message",
+ fun() ->
+ {ok, _} = ?MODULE:start_link("crash_test.log", 65535, 0, undefined, 0),
+ ?assertEqual(0, lager_test_backend:count()),
+ sync_error_logger:error_msg("Test message\n"),
+ _ = gen_event:which_handlers(error_logger),
+ ?assertEqual(1, lager_test_backend:count()),
+ file:delete("crash_test.log"),
+ file:write_file("crash_test.log", ""),
+ {ok, FInfo} = file:read_file_info("crash_test.log"),
+ file:write_file_info("crash_test.log", FInfo#file_info{mode = 0}),
+ sync_error_logger:error_msg("Test message\n"),
+ _ = gen_event:which_handlers(error_logger),
+ ?assertEqual(3, lager_test_backend:count()),
+ lager_test_backend:pop(),
+ {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ ?assertEqual("Failed to reopen crash log crash_test.log with error: permission denied", lists:flatten(Message))
+ end
+ },
+ {"unavailable files that are fixed at runtime should start having log messages written",
+ fun() ->
+ {ok, FInfo} = file:read_file_info("crash_test.log"),
+ OldPerms = FInfo#file_info.mode,
+ file:write_file_info("crash_test.log", FInfo#file_info{mode = 0}),
+ {ok, _} = ?MODULE:start_link("crash_test.log", 65535, 0, undefined, 0),
+ ?assertEqual(1, lager_test_backend:count()),
+ {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ ?assertEqual("Failed to open crash log file crash_test.log with error: permission denied", lists:flatten(Message)),
+ file:write_file_info("crash_test.log", FInfo#file_info{mode = OldPerms}),
+ sync_error_logger:error_msg("Test message~n"),
+ _ = gen_event:which_handlers(error_logger),
+ {ok, Bin} = file:read_file("crash_test.log"),
+ ?assertMatch([_, "Test message\n"], re:split(Bin, "\n", [{return, list}, {parts, 2}]))
+ end
+ },
+ {"external logfile rotation/deletion should be handled",
+ fun() ->
+ {ok, _} = ?MODULE:start_link("crash_test.log", 65535, 0, undefined, 0),
+ ?assertEqual(0, lager_test_backend:count()),
+ sync_error_logger:error_msg("Test message~n"),
+ _ = gen_event:which_handlers(error_logger),
+ {ok, Bin} = file:read_file("crash_test.log"),
+ ?assertMatch([_, "Test message\n"], re:split(Bin, "\n", [{return, list}, {parts, 2}])),
+ file:delete("crash_test.log"),
+ file:write_file("crash_test.log", ""),
+ sync_error_logger:error_msg("Test message1~n"),
+ _ = gen_event:which_handlers(error_logger),
+ {ok, Bin1} = file:read_file("crash_test.log"),
+ ?assertMatch([_, "Test message1\n"], re:split(Bin1, "\n", [{return, list}, {parts, 2}])),
+ file:rename("crash_test.log", "crash_test.log.0"),
+ sync_error_logger:error_msg("Test message2~n"),
+ _ = gen_event:which_handlers(error_logger),
+ {ok, Bin2} = file:read_file("crash_test.log"),
+ ?assertMatch([_, "Test message2\n"], re:split(Bin2, "\n", [{return, list}, {parts, 2}]))
+ end
+ }
+ ]
+ }.
+
+-endif.
+
diff --git a/deps/lager/src/lager_file_backend.erl b/deps/lager/src/lager_file_backend.erl
new file mode 100644
index 0000000..1faceea
--- /dev/null
+++ b/deps/lager/src/lager_file_backend.erl
@@ -0,0 +1,392 @@
+%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you under the Apache License,
+%% Version 2.0 (the "License"); you may not use this file
+%% except in compliance with the License. You may obtain
+%% a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+
+%% @doc File backend for lager, with multiple file support.
+%% Multiple files are supported, each with the path and the loglevel being
+%% configurable. The configuration paramter for this backend is a list of
+%% 5-tuples of the form
+%% `{FileName, Level, RotationSize, RotationDate, RotationCount}'.
+%% This backend supports external and internal log
+%% rotation and will re-open handles to files if the inode changes. It will
+%% also rotate the files itself if the size of the file exceeds the
+%% `RotationSize' and keep `RotationCount' rotated files. `RotationDate' is
+%% an alternate rotation trigger, based on time. See the README for
+%% documentation.
+
+-module(lager_file_backend).
+
+-include("lager.hrl").
+
+-behaviour(gen_event).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-include_lib("kernel/include/file.hrl").
+-compile([{parse_transform, lager_transform}]).
+-endif.
+
+-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-record(state, {
+ name :: string(),
+ level :: integer(),
+ fd :: file:io_device(),
+ inode :: integer(),
+ flap=false :: boolean(),
+ size = 0 :: integer(),
+ date,
+ count = 10
+ }).
+
+%% @private
+-spec init([{string(), lager:log_level()},...]) -> {ok, #state{}}.
+init(LogFile) ->
+ case validate_logfile(LogFile) of
+ {Name, Level, Size, Date, Count} ->
+ schedule_rotation(Name, Date),
+ State = case lager_util:open_logfile(Name, true) of
+ {ok, {FD, Inode, _}} ->
+ #state{name=Name, level=lager_util:level_to_num(Level),
+ fd=FD, inode=Inode, size=Size, date=Date, count=Count};
+ {error, Reason} ->
+ ?INT_LOG(error, "Failed to open log file ~s with error ~s",
+ [Name, file:format_error(Reason)]),
+ #state{name=Name, level=lager_util:level_to_num(Level),
+ flap=true, size=Size, date=Date, count=Count}
+ end,
+ {ok, State};
+ false ->
+ ignore
+ end.
+
+%% @private
+handle_call({set_loglevel, Level}, #state{name=Ident} = State) ->
+ ?INT_LOG(notice, "Changed loglevel of ~s to ~p", [Ident, Level]),
+ {ok, ok, State#state{level=lager_util:level_to_num(Level)}};
+handle_call(get_loglevel, #state{level=Level} = State) ->
+ {ok, Level, State};
+handle_call(_Request, State) ->
+ {ok, ok, State}.
+
+%% @private
+handle_event({log, Dest, Level, {Date, Time}, Message},
+ #state{name=Name, level=L} = State) when Level > L ->
+ case lists:member({lager_file_backend, Name}, Dest) of
+ true ->
+ {ok, write(State, Level, [Date, " ", Time, " ", Message, "\n"])};
+ false ->
+ {ok, State}
+ end;
+handle_event({log, Level, {Date, Time}, Message}, #state{level=L} = State) when Level =< L->
+ NewState = write(State, Level, [Date, " ", Time, " ", Message, "\n"]),
+ {ok, NewState};
+handle_event(_Event, State) ->
+ {ok, State}.
+
+%% @private
+handle_info({rotate, File}, #state{name=File,count=Count,date=Date} = State) ->
+ lager_util:rotate_logfile(File, Count),
+ schedule_rotation(File, Date),
+ {ok, State};
+handle_info(_Info, State) ->
+ {ok, State}.
+
+%% @private
+terminate(_Reason, #state{fd=FD}) ->
+ %% flush and close any file handles
+ file:datasync(FD),
+ file:close(FD),
+ ok.
+
+%% @private
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+write(#state{name=Name, fd=FD, inode=Inode, flap=Flap, size=RotSize,
+ count=Count} = State, Level, Msg) ->
+ case lager_util:ensure_logfile(Name, FD, Inode, true) of
+ {ok, {_, _, Size}} when RotSize /= 0, Size > RotSize ->
+ lager_util:rotate_logfile(Name, Count),
+ write(State, Level, Msg);
+ {ok, {NewFD, NewInode, _}} ->
+ file:write(NewFD, Msg),
+ case Level of
+ _ when Level =< ?ERROR ->
+ %% force a sync on any message at error severity or above
+ Flap2 = case file:datasync(NewFD) of
+ {error, Reason2} when Flap == false ->
+ ?INT_LOG(error, "Failed to write log message to file ~s: ~s",
+ [Name, file:format_error(Reason2)]),
+ true;
+ ok ->
+ false;
+ _ ->
+ Flap
+ end,
+ State#state{fd=NewFD, inode=NewInode, flap=Flap2};
+ _ ->
+ State#state{fd=NewFD, inode=NewInode}
+ end;
+ {error, Reason} ->
+ case Flap of
+ true ->
+ State;
+ _ ->
+ ?INT_LOG(error, "Failed to reopen log file ~s with error ~s",
+ [Name, file:format_error(Reason)]),
+ State#state{flap=true}
+ end
+ end.
+
+validate_logfile({Name, Level}) ->
+ case lists:member(Level, ?LEVELS) of
+ true ->
+ {Name, Level, 0, undefined, 0};
+ _ ->
+ ?INT_LOG(error, "Invalid log level of ~p for ~s.",
+ [Level, Name]),
+ false
+ end;
+validate_logfile({Name, Level, Size, Date, Count}) ->
+ ValidLevel = (lists:member(Level, ?LEVELS)),
+ ValidSize = (is_integer(Size) andalso Size >= 0),
+ ValidCount = (is_integer(Count) andalso Count >= 0),
+ case {ValidLevel, ValidSize, ValidCount} of
+ {false, _, _} ->
+ ?INT_LOG(error, "Invalid log level of ~p for ~s.",
+ [Level, Name]),
+ false;
+ {_, false, _} ->
+ ?INT_LOG(error, "Invalid rotation size of ~p for ~s.",
+ [Size, Name]),
+ false;
+ {_, _, false} ->
+ ?INT_LOG(error, "Invalid rotation count of ~p for ~s.",
+ [Count, Name]),
+ false;
+ {true, true, true} ->
+ case lager_util:parse_rotation_date_spec(Date) of
+ {ok, Spec} ->
+ {Name, Level, Size, Spec, Count};
+ {error, _} when Date == "" ->
+ %% blank ones are fine.
+ {Name, Level, Size, undefined, Count};
+ {error, _} ->
+ ?INT_LOG(error, "Invalid rotation date of ~p for ~s.",
+ [Date, Name]),
+ false
+ end
+ end;
+validate_logfile(H) ->
+ ?INT_LOG(error, "Invalid log file config ~p.", [H]),
+ false.
+
+schedule_rotation(_, undefined) ->
+ undefined;
+schedule_rotation(Name, Date) ->
+ erlang:send_after(lager_util:calculate_next_rotation(Date) * 1000, self(), {rotate, Name}).
+
+-ifdef(TEST).
+
+get_loglevel_test() ->
+ {ok, Level, _} = handle_call(get_loglevel,
+ #state{name="bar", level=lager_util:level_to_num(info), fd=0, inode=0}),
+ ?assertEqual(Level, lager_util:level_to_num(info)),
+ {ok, Level2, _} = handle_call(get_loglevel,
+ #state{name="foo", level=lager_util:level_to_num(warning), fd=0, inode=0}),
+ ?assertEqual(Level2, lager_util:level_to_num(warning)).
+
+rotation_test() ->
+ {ok, {FD, Inode, _}} = lager_util:open_logfile("test.log", true),
+ ?assertMatch(#state{name="test.log", level=?DEBUG, fd=FD, inode=Inode},
+ write(#state{name="test.log", level=?DEBUG, fd=FD, inode=Inode}, 0, "hello world")),
+ file:delete("test.log"),
+ Result = write(#state{name="test.log", level=?DEBUG, fd=FD, inode=Inode}, 0, "hello world"),
+ %% assert file has changed
+ ?assert(#state{name="test.log", level=?DEBUG, fd=FD, inode=Inode} =/= Result),
+ ?assertMatch(#state{name="test.log", level=?DEBUG}, Result),
+ file:rename("test.log", "test.log.1"),
+ Result2 = write(Result, 0, "hello world"),
+ %% assert file has changed
+ ?assert(Result =/= Result2),
+ ?assertMatch(#state{name="test.log", level=?DEBUG}, Result2),
+ ok.
+
+filesystem_test_() ->
+ {foreach,
+ fun() ->
+ file:write_file("test.log", ""),
+ error_logger:tty(false),
+ application:load(lager),
+ application:set_env(lager, handlers, [{lager_test_backend, info}]),
+ application:set_env(lager, error_logger_redirect, false),
+ application:start(compiler),
+ application:start(syntax_tools),
+ application:start(lager)
+ end,
+ fun(_) ->
+ file:delete("test.log"),
+ application:stop(lager),
+ error_logger:tty(true)
+ end,
+ [
+ {"under normal circumstances, file should be opened",
+ fun() ->
+ gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}),
+ lager:log(error, self(), "Test message"),
+ {ok, Bin} = file:read_file("test.log"),
+ Pid = pid_to_list(self()),
+ ?assertMatch([_, _, "[error]", Pid, "Test message\n"], re:split(Bin, " ", [{return, list}, {parts, 5}]))
+ end
+ },
+ {"file can't be opened on startup triggers an error message",
+ fun() ->
+ {ok, FInfo} = file:read_file_info("test.log"),
+ file:write_file_info("test.log", FInfo#file_info{mode = 0}),
+ gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}),
+ ?assertEqual(1, lager_test_backend:count()),
+ {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ ?assertEqual("Failed to open log file test.log with error permission denied", lists:flatten(Message))
+ end
+ },
+ {"file that becomes unavailable at runtime should trigger an error message",
+ fun() ->
+ gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}),
+ ?assertEqual(0, lager_test_backend:count()),
+ lager:log(error, self(), "Test message"),
+ ?assertEqual(1, lager_test_backend:count()),
+ file:delete("test.log"),
+ file:write_file("test.log", ""),
+ {ok, FInfo} = file:read_file_info("test.log"),
+ file:write_file_info("test.log", FInfo#file_info{mode = 0}),
+ lager:log(error, self(), "Test message"),
+ ?assertEqual(3, lager_test_backend:count()),
+ lager_test_backend:pop(),
+ lager_test_backend:pop(),
+ {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ ?assertEqual("Failed to reopen log file test.log with error permission denied", lists:flatten(Message))
+ end
+ },
+ {"unavailable files that are fixed at runtime should start having log messages written",
+ fun() ->
+ {ok, FInfo} = file:read_file_info("test.log"),
+ OldPerms = FInfo#file_info.mode,
+ file:write_file_info("test.log", FInfo#file_info{mode = 0}),
+ gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}),
+ ?assertEqual(1, lager_test_backend:count()),
+ {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ ?assertEqual("Failed to open log file test.log with error permission denied", lists:flatten(Message)),
+ file:write_file_info("test.log", FInfo#file_info{mode = OldPerms}),
+ lager:log(error, self(), "Test message"),
+ {ok, Bin} = file:read_file("test.log"),
+ Pid = pid_to_list(self()),
+ ?assertMatch([_, _, "[error]", Pid, "Test message\n"], re:split(Bin, " ", [{return, list}, {parts, 5}]))
+ end
+ },
+ {"external logfile rotation/deletion should be handled",
+ fun() ->
+ gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}),
+ ?assertEqual(0, lager_test_backend:count()),
+ lager:log(error, self(), "Test message1"),
+ ?assertEqual(1, lager_test_backend:count()),
+ file:delete("test.log"),
+ file:write_file("test.log", ""),
+ lager:log(error, self(), "Test message2"),
+ {ok, Bin} = file:read_file("test.log"),
+ Pid = pid_to_list(self()),
+ ?assertMatch([_, _, "[error]", Pid, "Test message2\n"], re:split(Bin, " ", [{return, list}, {parts, 5}])),
+ file:rename("test.log", "test.log.0"),
+ lager:log(error, self(), "Test message3"),
+ {ok, Bin2} = file:read_file("test.log"),
+ ?assertMatch([_, _, "[error]", Pid, "Test message3\n"], re:split(Bin2, " ", [{return, list}, {parts, 5}]))
+ end
+ },
+ {"runtime level changes",
+ fun() ->
+ gen_event:add_handler(lager_event, {lager_file_backend, "test.log"}, {"test.log", info}),
+ ?assertEqual(0, lager_test_backend:count()),
+ lager:log(info, self(), "Test message1"),
+ lager:log(error, self(), "Test message2"),
+ {ok, Bin} = file:read_file("test.log"),
+ Lines = length(re:split(Bin, "\n", [{return, list}, trim])),
+ ?assertEqual(Lines, 2),
+ ?assertEqual(ok, lager:set_loglevel(lager_file_backend, "test.log", warning)),
+ lager:log(info, self(), "Test message3"), %% this won't get logged
+ lager:log(error, self(), "Test message4"),
+ {ok, Bin2} = file:read_file("test.log"),
+ Lines2 = length(re:split(Bin2, "\n", [{return, list}, trim])),
+ ?assertEqual(Lines2, 3)
+ end
+ },
+ {"invalid runtime level changes",
+ fun() ->
+ gen_event:add_handler(lager_event, lager_file_backend, {"test.log", info}),
+ gen_event:add_handler(lager_event, lager_file_backend, {"test3.log", info}),
+ ?assertEqual({error, bad_module}, lager:set_loglevel(lager_file_backend, "test.log", warning))
+ end
+ },
+ {"tracing should work",
+ fun() ->
+ gen_event:add_handler(lager_event, lager_file_backend,
+ {"test.log", critical}),
+ lager:error("Test message"),
+ ?assertEqual({ok, <<>>}, file:read_file("test.log")),
+ {Level, _} = lager_mochiglobal:get(loglevel),
+ lager_mochiglobal:put(loglevel, {Level, [{[{module,
+ ?MODULE}], ?DEBUG,
+ {lager_file_backend, "test.log"}}]}),
+ lager:error("Test message"),
+ {ok, Bin} = file:read_file("test.log"),
+ ?assertMatch([_, _, "[error]", _, "Test message\n"], re:split(Bin, " ", [{return, list}, {parts, 5}]))
+ end
+ },
+ {"tracing should not duplicate messages",
+ fun() ->
+ gen_event:add_handler(lager_event, lager_file_backend,
+ {"test.log", critical}),
+ lager:critical("Test message"),
+ {ok, Bin1} = file:read_file("test.log"),
+ ?assertMatch([_, _, "[critical]", _, "Test message\n"], re:split(Bin1, " ", [{return, list}, {parts, 5}])),
+ ok = file:delete("test.log"),
+ {Level, _} = lager_mochiglobal:get(loglevel),
+ lager_mochiglobal:put(loglevel, {Level, [{[{module,
+ ?MODULE}], ?DEBUG,
+ {lager_file_backend, "test.log"}}]}),
+ lager:critical("Test message"),
+ {ok, Bin2} = file:read_file("test.log"),
+ ?assertMatch([_, _, "[critical]", _, "Test message\n"], re:split(Bin2, " ", [{return, list}, {parts, 5}])),
+ ok = file:delete("test.log"),
+ lager:error("Test message"),
+ {ok, Bin3} = file:read_file("test.log"),
+ ?assertMatch([_, _, "[error]", _, "Test message\n"], re:split(Bin3, " ", [{return, list}, {parts, 5}]))
+ end
+ },
+ {"tracing to a dedicated file should work",
+ fun() ->
+ file:delete("foo.log"),
+ {ok, _} = lager:trace_file("foo.log", [{module, ?MODULE}]),
+ lager:error("Test message"),
+ {ok, Bin3} = file:read_file("foo.log"),
+ ?assertMatch([_, _, "[error]", _, "Test message\n"], re:split(Bin3, " ", [{return, list}, {parts, 5}]))
+ end
+ }
+ ]
+ }.
+
+
+-endif.
+
diff --git a/deps/lager/src/lager_format.erl b/deps/lager/src/lager_format.erl
new file mode 100644
index 0000000..543ac07
--- /dev/null
+++ b/deps/lager/src/lager_format.erl
@@ -0,0 +1,523 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(lager_format).
+
+%% fork of io_lib_format that uses trunc_io to protect against large terms
+
+-export([format/3, format/4]).
+
+-record(options, {
+ chomp = false
+ }).
+
+format(FmtStr, Args, MaxLen) ->
+ format(FmtStr, Args, MaxLen, []).
+
+format(FmtStr, Args, MaxLen, Opts) ->
+ Options = make_options(Opts, #options{}),
+ Cs = collect(FmtStr, Args),
+ {Cs2, MaxLen2} = build(Cs, [], MaxLen, Options),
+ %% count how many terms remain
+ {Count, StrLen} = lists:foldl(
+ fun({_C, _As, _F, _Adj, _P, _Pad, _Enc}, {Terms, Chars}) ->
+ {Terms + 1, Chars};
+ (_, {Terms, Chars}) ->
+ {Terms, Chars + 1}
+ end, {0, 0}, Cs2),
+ build2(Cs2, Count, MaxLen2 - StrLen).
+
+collect([$~|Fmt0], Args0) ->
+ {C,Fmt1,Args1} = collect_cseq(Fmt0, Args0),
+ [C|collect(Fmt1, Args1)];
+collect([C|Fmt], Args) ->
+ [C|collect(Fmt, Args)];
+collect([], []) -> [].
+
+collect_cseq(Fmt0, Args0) ->
+ {F,Ad,Fmt1,Args1} = field_width(Fmt0, Args0),
+ {P,Fmt2,Args2} = precision(Fmt1, Args1),
+ {Pad,Fmt3,Args3} = pad_char(Fmt2, Args2),
+ {Encoding,Fmt4,Args4} = encoding(Fmt3, Args3),
+ {C,As,Fmt5,Args5} = collect_cc(Fmt4, Args4),
+ {{C,As,F,Ad,P,Pad,Encoding},Fmt5,Args5}.
+
+encoding([$t|Fmt],Args) ->
+ {unicode,Fmt,Args};
+encoding(Fmt,Args) ->
+ {latin1,Fmt,Args}.
+
+field_width([$-|Fmt0], Args0) ->
+ {F,Fmt,Args} = field_value(Fmt0, Args0),
+ field_width(-F, Fmt, Args);
+field_width(Fmt0, Args0) ->
+ {F,Fmt,Args} = field_value(Fmt0, Args0),
+ field_width(F, Fmt, Args).
+
+field_width(F, Fmt, Args) when F < 0 ->
+ {-F,left,Fmt,Args};
+field_width(F, Fmt, Args) when F >= 0 ->
+ {F,right,Fmt,Args}.
+
+precision([$.|Fmt], Args) ->
+ field_value(Fmt, Args);
+precision(Fmt, Args) ->
+ {none,Fmt,Args}.
+
+field_value([$*|Fmt], [A|Args]) when is_integer(A) ->
+ {A,Fmt,Args};
+field_value([C|Fmt], Args) when is_integer(C), C >= $0, C =< $9 ->
+ field_value([C|Fmt], Args, 0);
+field_value(Fmt, Args) ->
+ {none,Fmt,Args}.
+
+field_value([C|Fmt], Args, F) when is_integer(C), C >= $0, C =< $9 ->
+ field_value(Fmt, Args, 10*F + (C - $0));
+field_value(Fmt, Args, F) -> %Default case
+ {F,Fmt,Args}.
+
+pad_char([$.,$*|Fmt], [Pad|Args]) -> {Pad,Fmt,Args};
+pad_char([$.,Pad|Fmt], Args) -> {Pad,Fmt,Args};
+pad_char(Fmt, Args) -> {$\s,Fmt,Args}.
+
+%% collect_cc([FormatChar], [Argument]) ->
+%% {Control,[ControlArg],[FormatChar],[Arg]}.
+%% Here we collect the argments for each control character.
+%% Be explicit to cause failure early.
+
+collect_cc([$w|Fmt], [A|Args]) -> {$w,[A],Fmt,Args};
+collect_cc([$p|Fmt], [A|Args]) -> {$p,[A],Fmt,Args};
+collect_cc([$W|Fmt], [A,Depth|Args]) -> {$W,[A,Depth],Fmt,Args};
+collect_cc([$P|Fmt], [A,Depth|Args]) -> {$P,[A,Depth],Fmt,Args};
+collect_cc([$s|Fmt], [A|Args]) -> {$s,[A],Fmt,Args};
+collect_cc([$e|Fmt], [A|Args]) -> {$e,[A],Fmt,Args};
+collect_cc([$f|Fmt], [A|Args]) -> {$f,[A],Fmt,Args};
+collect_cc([$g|Fmt], [A|Args]) -> {$g,[A],Fmt,Args};
+collect_cc([$b|Fmt], [A|Args]) -> {$b,[A],Fmt,Args};
+collect_cc([$B|Fmt], [A|Args]) -> {$B,[A],Fmt,Args};
+collect_cc([$x|Fmt], [A,Prefix|Args]) -> {$x,[A,Prefix],Fmt,Args};
+collect_cc([$X|Fmt], [A,Prefix|Args]) -> {$X,[A,Prefix],Fmt,Args};
+collect_cc([$+|Fmt], [A|Args]) -> {$+,[A],Fmt,Args};
+collect_cc([$#|Fmt], [A|Args]) -> {$#,[A],Fmt,Args};
+collect_cc([$c|Fmt], [A|Args]) -> {$c,[A],Fmt,Args};
+collect_cc([$~|Fmt], Args) when is_list(Args) -> {$~,[],Fmt,Args};
+collect_cc([$n|Fmt], Args) when is_list(Args) -> {$n,[],Fmt,Args};
+collect_cc([$i|Fmt], [A|Args]) -> {$i,[A],Fmt,Args}.
+
+
+%% build([Control], Pc, Indentation) -> [Char].
+%% Interpret the control structures. Count the number of print
+%% remaining and only calculate indentation when necessary. Must also
+%% be smart when calculating indentation for characters in format.
+
+build([{$n, _, _, _, _, _, _}], Acc, MaxLen, #options{chomp=true}) ->
+ %% trailing ~n, ignore
+ {lists:reverse(Acc), MaxLen};
+build([{C,As,F,Ad,P,Pad,Enc}|Cs], Acc, MaxLen, O) ->
+ {S, MaxLen2} = control(C, As, F, Ad, P, Pad, Enc, MaxLen),
+ build(Cs, [S|Acc], MaxLen2, O);
+build([$\n], Acc, MaxLen, #options{chomp=true}) ->
+ %% trailing \n, ignore
+ {lists:reverse(Acc), MaxLen};
+build([$\n|Cs], Acc, MaxLen, O) ->
+ build(Cs, [$\n|Acc], MaxLen - 1, O);
+build([$\t|Cs], Acc, MaxLen, O) ->
+ build(Cs, [$\t|Acc], MaxLen - 1, O);
+build([C|Cs], Acc, MaxLen, O) ->
+ build(Cs, [C|Acc], MaxLen - 1, O);
+build([], Acc, MaxLen, _O) ->
+ {lists:reverse(Acc), MaxLen}.
+
+build2([{C,As,F,Ad,P,Pad,Enc}|Cs], Count, MaxLen) ->
+ {S, Len} = control2(C, As, F, Ad, P, Pad, Enc, MaxLen div Count),
+ [S|build2(Cs, Count - 1, MaxLen - Len)];
+build2([C|Cs], Count, MaxLen) ->
+ [C|build2(Cs, Count, MaxLen)];
+build2([], _, _) -> [].
+
+%% control(FormatChar, [Argument], FieldWidth, Adjust, Precision, PadChar,
+%% Indentation) ->
+%% [Char]
+%% This is the main dispatch function for the various formatting commands.
+%% Field widths and precisions have already been calculated.
+
+control($e, [A], F, Adj, P, Pad, _Enc, L) when is_float(A) ->
+ Res = fwrite_e(A, F, Adj, P, Pad),
+ {Res, L - lists:flatlength(Res)};
+control($f, [A], F, Adj, P, Pad, _Enc, L) when is_float(A) ->
+ Res = fwrite_f(A, F, Adj, P, Pad),
+ {Res, L - lists:flatlength(Res)};
+control($g, [A], F, Adj, P, Pad, _Enc, L) when is_float(A) ->
+ Res = fwrite_g(A, F, Adj, P, Pad),
+ {Res, L - lists:flatlength(Res)};
+control($b, [A], F, Adj, P, Pad, _Enc, L) when is_integer(A) ->
+ Res = unprefixed_integer(A, F, Adj, base(P), Pad, true),
+ {Res, L - lists:flatlength(Res)};
+control($B, [A], F, Adj, P, Pad, _Enc, L) when is_integer(A) ->
+ Res = unprefixed_integer(A, F, Adj, base(P), Pad, false),
+ {Res, L - lists:flatlength(Res)};
+control($x, [A,Prefix], F, Adj, P, Pad, _Enc, L) when is_integer(A),
+ is_atom(Prefix) ->
+ Res = prefixed_integer(A, F, Adj, base(P), Pad, atom_to_list(Prefix), true),
+ {Res, L - lists:flatlength(Res)};
+control($x, [A,Prefix], F, Adj, P, Pad, _Enc, L) when is_integer(A) ->
+ true = io_lib:deep_char_list(Prefix), %Check if Prefix a character list
+ Res = prefixed_integer(A, F, Adj, base(P), Pad, Prefix, true),
+ {Res, L - lists:flatlength(Res)};
+control($X, [A,Prefix], F, Adj, P, Pad, _Enc, L) when is_integer(A),
+ is_atom(Prefix) ->
+ Res = prefixed_integer(A, F, Adj, base(P), Pad, atom_to_list(Prefix), false),
+ {Res, L - lists:flatlength(Res)};
+control($X, [A,Prefix], F, Adj, P, Pad, _Enc, L) when is_integer(A) ->
+ true = io_lib:deep_char_list(Prefix), %Check if Prefix a character list
+ Res = prefixed_integer(A, F, Adj, base(P), Pad, Prefix, false),
+ {Res, L - lists:flatlength(Res)};
+control($+, [A], F, Adj, P, Pad, _Enc, L) when is_integer(A) ->
+ Base = base(P),
+ Prefix = [integer_to_list(Base), $#],
+ Res = prefixed_integer(A, F, Adj, Base, Pad, Prefix, true),
+ {Res, L - lists:flatlength(Res)};
+control($#, [A], F, Adj, P, Pad, _Enc, L) when is_integer(A) ->
+ Base = base(P),
+ Prefix = [integer_to_list(Base), $#],
+ Res = prefixed_integer(A, F, Adj, Base, Pad, Prefix, false),
+ {Res, L - lists:flatlength(Res)};
+control($c, [A], F, Adj, P, Pad, unicode, L) when is_integer(A) ->
+ Res = char(A, F, Adj, P, Pad),
+ {Res, L - lists:flatlength(Res)};
+control($c, [A], F, Adj, P, Pad, _Enc, L) when is_integer(A) ->
+ Res = char(A band 255, F, Adj, P, Pad),
+ {Res, L - lists:flatlength(Res)};
+control($~, [], F, Adj, P, Pad, _Enc, L) ->
+ Res = char($~, F, Adj, P, Pad),
+ {Res, L - lists:flatlength(Res)};
+control($n, [], F, Adj, P, Pad, _Enc, L) ->
+ Res = newline(F, Adj, P, Pad),
+ {Res, L - lists:flatlength(Res)};
+control($i, [_A], _F, _Adj, _P, _Pad, _Enc, L) ->
+ {[], L};
+control($s, [A], F, Adj, P, Pad, _Enc, L) when is_atom(A) ->
+ Res = string(atom_to_list(A), F, Adj, P, Pad),
+ {Res, L - lists:flatlength(Res)};
+control(C, A, F, Adj, P, Pad, Enc, L) ->
+ %% save this for later - these are all the 'large' terms
+ {{C, A, F, Adj, P, Pad, Enc}, L}.
+
+control2($w, [A], F, Adj, P, Pad, _Enc, L) ->
+ Term = lager_trunc_io:fprint(A, L, [{lists_as_strings, false}]),
+ Res = term(Term, F, Adj, P, Pad),
+ {Res, lists:flatlength(Res)};
+control2($p, [A], F, Adj, P, Pad, _Enc, L) ->
+ Term = lager_trunc_io:fprint(A, L, [{lists_as_strings, true}]),
+ Res = term(Term, F, Adj, P, Pad),
+ {Res, lists:flatlength(Res)};
+control2($W, [A,Depth], F, Adj, P, Pad, _Enc, L) when is_integer(Depth) ->
+ Term = lager_trunc_io:fprint(A, L, [{depth, Depth}, {lists_as_strings, false}]),
+ Res = term(Term, F, Adj, P, Pad),
+ {Res, lists:flatlength(Res)};
+control2($P, [A,Depth], F, Adj, P, Pad, _Enc, L) when is_integer(Depth) ->
+ Term = lager_trunc_io:fprint(A, L, [{depth, Depth}, {lists_as_strings, true}]),
+ Res = term(Term, F, Adj, P, Pad),
+ {Res, lists:flatlength(Res)};
+control2($s, [L0], F, Adj, P, Pad, latin1, L) ->
+ List = lager_trunc_io:fprint(maybe_flatten(L0), L, [{force_strings, true}]),
+ Res = string(List, F, Adj, P, Pad),
+ {Res, lists:flatlength(Res)};
+control2($s, [L0], F, Adj, P, Pad, unicode, L) ->
+ List = lager_trunc_io:fprint(unicode:characters_to_list(L0), L, [{force_strings, true}]),
+ Res = uniconv(string(List, F, Adj, P, Pad)),
+ {Res, lists:flatlength(Res)}.
+
+maybe_flatten(X) when is_list(X) ->
+ lists:flatten(X);
+maybe_flatten(X) ->
+ X.
+
+make_options([], Options) ->
+ Options;
+make_options([{chomp, Bool}|T], Options) when is_boolean(Bool) ->
+ make_options(T, Options#options{chomp=Bool}).
+
+-ifdef(UNICODE_AS_BINARIES).
+uniconv(C) ->
+ unicode:characters_to_binary(C,unicode).
+-else.
+uniconv(C) ->
+ C.
+-endif.
+%% Default integer base
+base(none) ->
+ 10;
+base(B) when is_integer(B) ->
+ B.
+
+%% term(TermList, Field, Adjust, Precision, PadChar)
+%% Output the characters in a term.
+%% Adjust the characters within the field if length less than Max padding
+%% with PadChar.
+
+term(T, none, _Adj, none, _Pad) -> T;
+term(T, none, Adj, P, Pad) -> term(T, P, Adj, P, Pad);
+term(T, F, Adj, P0, Pad) ->
+ L = lists:flatlength(T),
+ P = case P0 of none -> erlang:min(L, F); _ -> P0 end,
+ if
+ L > P ->
+ adjust(chars($*, P), chars(Pad, F-P), Adj);
+ F >= P ->
+ adjust(T, chars(Pad, F-L), Adj)
+ end.
+
+%% fwrite_e(Float, Field, Adjust, Precision, PadChar)
+
+fwrite_e(Fl, none, Adj, none, Pad) -> %Default values
+ fwrite_e(Fl, none, Adj, 6, Pad);
+fwrite_e(Fl, none, _Adj, P, _Pad) when P >= 2 ->
+ float_e(Fl, float_data(Fl), P);
+fwrite_e(Fl, F, Adj, none, Pad) ->
+ fwrite_e(Fl, F, Adj, 6, Pad);
+fwrite_e(Fl, F, Adj, P, Pad) when P >= 2 ->
+ term(float_e(Fl, float_data(Fl), P), F, Adj, F, Pad).
+
+float_e(Fl, Fd, P) when Fl < 0.0 -> %Negative numbers
+ [$-|float_e(-Fl, Fd, P)];
+float_e(_Fl, {Ds,E}, P) ->
+ case float_man(Ds, 1, P-1) of
+ {[$0|Fs],true} -> [[$1|Fs]|float_exp(E)];
+ {Fs,false} -> [Fs|float_exp(E-1)]
+ end.
+
+%% float_man([Digit], Icount, Dcount) -> {[Chars],CarryFlag}.
+%% Generate the characters in the mantissa from the digits with Icount
+%% characters before the '.' and Dcount decimals. Handle carry and let
+%% caller decide what to do at top.
+
+float_man(Ds, 0, Dc) ->
+ {Cs,C} = float_man(Ds, Dc),
+ {[$.|Cs],C};
+float_man([D|Ds], I, Dc) ->
+ case float_man(Ds, I-1, Dc) of
+ {Cs,true} when D =:= $9 -> {[$0|Cs],true};
+ {Cs,true} -> {[D+1|Cs],false};
+ {Cs,false} -> {[D|Cs],false}
+ end;
+float_man([], I, Dc) -> %Pad with 0's
+ {string:chars($0, I, [$.|string:chars($0, Dc)]),false}.
+
+float_man([D|_], 0) when D >= $5 -> {[],true};
+float_man([_|_], 0) -> {[],false};
+float_man([D|Ds], Dc) ->
+ case float_man(Ds, Dc-1) of
+ {Cs,true} when D =:= $9 -> {[$0|Cs],true};
+ {Cs,true} -> {[D+1|Cs],false};
+ {Cs,false} -> {[D|Cs],false}
+ end;
+float_man([], Dc) -> {string:chars($0, Dc),false}. %Pad with 0's
+
+%% float_exp(Exponent) -> [Char].
+%% Generate the exponent of a floating point number. Always include sign.
+
+float_exp(E) when E >= 0 ->
+ [$e,$+|integer_to_list(E)];
+float_exp(E) ->
+ [$e|integer_to_list(E)].
+
+%% fwrite_f(FloatData, Field, Adjust, Precision, PadChar)
+
+fwrite_f(Fl, none, Adj, none, Pad) -> %Default values
+ fwrite_f(Fl, none, Adj, 6, Pad);
+fwrite_f(Fl, none, _Adj, P, _Pad) when P >= 1 ->
+ float_f(Fl, float_data(Fl), P);
+fwrite_f(Fl, F, Adj, none, Pad) ->
+ fwrite_f(Fl, F, Adj, 6, Pad);
+fwrite_f(Fl, F, Adj, P, Pad) when P >= 1 ->
+ term(float_f(Fl, float_data(Fl), P), F, Adj, F, Pad).
+
+float_f(Fl, Fd, P) when Fl < 0.0 ->
+ [$-|float_f(-Fl, Fd, P)];
+float_f(Fl, {Ds,E}, P) when E =< 0 ->
+ float_f(Fl, {string:chars($0, -E+1, Ds),1}, P); %Prepend enough 0's
+float_f(_Fl, {Ds,E}, P) ->
+ case float_man(Ds, E, P) of
+ {Fs,true} -> "1" ++ Fs; %Handle carry
+ {Fs,false} -> Fs
+ end.
+
+%% float_data([FloatChar]) -> {[Digit],Exponent}
+
+float_data(Fl) ->
+ float_data(float_to_list(Fl), []).
+
+float_data([$e|E], Ds) ->
+ {lists:reverse(Ds),list_to_integer(E)+1};
+float_data([D|Cs], Ds) when D >= $0, D =< $9 ->
+ float_data(Cs, [D|Ds]);
+float_data([_|Cs], Ds) ->
+ float_data(Cs, Ds).
+
+%% fwrite_g(Float, Field, Adjust, Precision, PadChar)
+%% Use the f form if Float is >= 0.1 and < 1.0e4,
+%% and the prints correctly in the f form, else the e form.
+%% Precision always means the # of significant digits.
+
+fwrite_g(Fl, F, Adj, none, Pad) ->
+ fwrite_g(Fl, F, Adj, 6, Pad);
+fwrite_g(Fl, F, Adj, P, Pad) when P >= 1 ->
+ A = abs(Fl),
+ E = if A < 1.0e-1 -> -2;
+ A < 1.0e0 -> -1;
+ A < 1.0e1 -> 0;
+ A < 1.0e2 -> 1;
+ A < 1.0e3 -> 2;
+ A < 1.0e4 -> 3;
+ true -> fwrite_f
+ end,
+ if P =< 1, E =:= -1;
+ P-1 > E, E >= -1 ->
+ fwrite_f(Fl, F, Adj, P-1-E, Pad);
+ P =< 1 ->
+ fwrite_e(Fl, F, Adj, 2, Pad);
+ true ->
+ fwrite_e(Fl, F, Adj, P, Pad)
+ end.
+
+
+%% string(String, Field, Adjust, Precision, PadChar)
+
+string(S, none, _Adj, none, _Pad) -> S;
+string(S, F, Adj, none, Pad) ->
+ string_field(S, F, Adj, lists:flatlength(S), Pad);
+string(S, none, _Adj, P, Pad) ->
+ string_field(S, P, left, lists:flatlength(S), Pad);
+string(S, F, Adj, P, Pad) when F >= P ->
+ N = lists:flatlength(S),
+ if F > P ->
+ if N > P ->
+ adjust(flat_trunc(S, P), chars(Pad, F-P), Adj);
+ N < P ->
+ adjust([S|chars(Pad, P-N)], chars(Pad, F-P), Adj);
+ true -> % N == P
+ adjust(S, chars(Pad, F-P), Adj)
+ end;
+ true -> % F == P
+ string_field(S, F, Adj, N, Pad)
+ end.
+
+string_field(S, F, _Adj, N, _Pad) when N > F ->
+ flat_trunc(S, F);
+string_field(S, F, Adj, N, Pad) when N < F ->
+ adjust(S, chars(Pad, F-N), Adj);
+string_field(S, _, _, _, _) -> % N == F
+ S.
+
+%% unprefixed_integer(Int, Field, Adjust, Base, PadChar, Lowercase)
+%% -> [Char].
+
+unprefixed_integer(Int, F, Adj, Base, Pad, Lowercase)
+ when Base >= 2, Base =< 1+$Z-$A+10 ->
+ if Int < 0 ->
+ S = cond_lowercase(erlang:integer_to_list(-Int, Base), Lowercase),
+ term([$-|S], F, Adj, none, Pad);
+ true ->
+ S = cond_lowercase(erlang:integer_to_list(Int, Base), Lowercase),
+ term(S, F, Adj, none, Pad)
+ end.
+
+%% prefixed_integer(Int, Field, Adjust, Base, PadChar, Prefix, Lowercase)
+%% -> [Char].
+
+prefixed_integer(Int, F, Adj, Base, Pad, Prefix, Lowercase)
+ when Base >= 2, Base =< 1+$Z-$A+10 ->
+ if Int < 0 ->
+ S = cond_lowercase(erlang:integer_to_list(-Int, Base), Lowercase),
+ term([$-,Prefix|S], F, Adj, none, Pad);
+ true ->
+ S = cond_lowercase(erlang:integer_to_list(Int, Base), Lowercase),
+ term([Prefix|S], F, Adj, none, Pad)
+ end.
+
+%% char(Char, Field, Adjust, Precision, PadChar) -> [Char].
+
+char(C, none, _Adj, none, _Pad) -> [C];
+char(C, F, _Adj, none, _Pad) -> chars(C, F);
+char(C, none, _Adj, P, _Pad) -> chars(C, P);
+char(C, F, Adj, P, Pad) when F >= P ->
+ adjust(chars(C, P), chars(Pad, F - P), Adj).
+
+%% newline(Field, Adjust, Precision, PadChar) -> [Char].
+
+newline(none, _Adj, _P, _Pad) -> "\n";
+newline(F, right, _P, _Pad) -> chars($\n, F).
+
+%%
+%% Utilities
+%%
+
+adjust(Data, [], _) -> Data;
+adjust(Data, Pad, left) -> [Data|Pad];
+adjust(Data, Pad, right) -> [Pad|Data].
+
+%% Flatten and truncate a deep list to at most N elements.
+
+flat_trunc(List, N) when is_integer(N), N >= 0 ->
+ flat_trunc(List, N, [], []).
+
+flat_trunc(L, 0, _, R) when is_list(L) ->
+ lists:reverse(R);
+flat_trunc([H|T], N, S, R) when is_list(H) ->
+ flat_trunc(H, N, [T|S], R);
+flat_trunc([H|T], N, S, R) ->
+ flat_trunc(T, N-1, S, [H|R]);
+flat_trunc([], N, [H|S], R) ->
+ flat_trunc(H, N, S, R);
+flat_trunc([], _, [], R) ->
+ lists:reverse(R).
+
+%% A deep version of string:chars/2,3
+
+chars(_C, 0) ->
+ [];
+chars(C, 1) ->
+ [C];
+chars(C, 2) ->
+ [C,C];
+chars(C, 3) ->
+ [C,C,C];
+chars(C, N) when is_integer(N), (N band 1) =:= 0 ->
+ S = chars(C, N bsr 1),
+ [S|S];
+chars(C, N) when is_integer(N) ->
+ S = chars(C, N bsr 1),
+ [C,S|S].
+
+%chars(C, N, Tail) ->
+% [chars(C, N)|Tail].
+
+%% Lowercase conversion
+
+cond_lowercase(String, true) ->
+ lowercase(String);
+cond_lowercase(String,false) ->
+ String.
+
+lowercase([H|T]) when is_integer(H), H >= $A, H =< $Z ->
+ [(H-$A+$a)|lowercase(T)];
+lowercase([H|T]) ->
+ [H|lowercase(T)];
+lowercase([]) ->
+ [].
diff --git a/deps/lager/src/lager_handler_watcher.erl b/deps/lager/src/lager_handler_watcher.erl
new file mode 100644
index 0000000..32a7185
--- /dev/null
+++ b/deps/lager/src/lager_handler_watcher.erl
@@ -0,0 +1,159 @@
+%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you under the Apache License,
+%% Version 2.0 (the "License"); you may not use this file
+%% except in compliance with the License. You may obtain
+%% a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+
+%% @doc A process that does a gen_event:add_sup_handler and attempts to re-add
+%% event handlers when they exit.
+
+%% @private
+
+-module(lager_handler_watcher).
+
+-behaviour(gen_server).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+%% callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-export([start_link/3, start/3]).
+
+-record(state, {
+ module,
+ config,
+ event
+ }).
+
+start_link(Event, Module, Config) ->
+ gen_server:start_link(?MODULE, [Event, Module, Config], []).
+
+start(Event, Module, Config) ->
+ gen_server:start(?MODULE, [Event, Module, Config], []).
+
+init([Event, Module, Config]) ->
+ install_handler(Event, Module, Config),
+ {ok, #state{event=Event, module=Module, config=Config}}.
+
+handle_call(_Call, _From, State) ->
+ {reply, ok, State}.
+
+handle_cast(_Request, State) ->
+ {noreply, State}.
+
+handle_info({gen_event_EXIT, Module, normal}, #state{module=Module} = State) ->
+ {stop, normal, State};
+handle_info({gen_event_EXIT, Module, shutdown}, #state{module=Module} = State) ->
+ {stop, normal, State};
+handle_info({gen_event_EXIT, Module, Reason}, #state{module=Module,
+ config=Config, event=Event} = State) ->
+ lager:log(error, self(), "Lager event handler ~p exited with reason ~s",
+ [Module, error_logger_lager_h:format_reason(Reason)]),
+ install_handler(Event, Module, Config),
+ {noreply, State};
+handle_info(reinstall_handler, #state{module=Module, config=Config, event=Event} = State) ->
+ install_handler(Event, Module, Config),
+ {noreply, State};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%% internal
+
+install_handler(Event, Module, Config) ->
+ case gen_event:add_sup_handler(Event, Module, Config) of
+ ok ->
+ lager:log(debug, self(), "Lager installed handler ~p into ~p", [Module, Event]),
+ ok;
+ Error ->
+ %% try to reinstall it later
+ lager:log(error, self(), "Lager failed to install handler ~p into"
+ " ~p, retrying later : ~p", [Module, Event, Error]),
+ erlang:send_after(5000, self(), reinstall_handler)
+ end.
+
+-ifdef(TEST).
+
+from_now(Seconds) ->
+ {Mega, Secs, Micro} = os:timestamp(),
+ {Mega, Secs + Seconds, Micro}.
+
+reinstall_on_initial_failure_test_() ->
+ {timeout, 60000,
+ [
+ fun() ->
+ error_logger:tty(false),
+ application:load(lager),
+ application:set_env(lager, handlers, [{lager_test_backend, info}, {lager_crash_backend, [from_now(2), undefined]}]),
+ application:set_env(lager, error_logger_redirect, false),
+ application:unset_env(lager, crash_log),
+ application:start(compiler),
+ application:start(syntax_tools),
+ application:start(lager),
+ try
+ ?assertEqual(1, lager_test_backend:count()),
+ {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ ?assertMatch("Lager failed to install handler lager_crash_backend into lager_event, retrying later :"++_, lists:flatten(Message)),
+ ?assertEqual(0, lager_test_backend:count()),
+ timer:sleep(6000),
+ ?assertEqual(0, lager_test_backend:count()),
+ ?assert(lists:member(lager_crash_backend, gen_event:which_handlers(lager_event)))
+ after
+ application:stop(lager),
+ error_logger:tty(true)
+ end
+ end
+ ]
+ }.
+
+reinstall_on_runtime_failure_test_() ->
+ {timeout, 60000,
+ [
+ fun() ->
+ error_logger:tty(false),
+ application:load(lager),
+ application:set_env(lager, handlers, [{lager_test_backend, info}, {lager_crash_backend, [undefined, from_now(5)]}]),
+ application:set_env(lager, error_logger_redirect, false),
+ application:unset_env(lager, crash_log),
+ application:start(compiler),
+ application:start(syntax_tools),
+ application:start(lager),
+ try
+ ?assertEqual(0, lager_test_backend:count()),
+ ?assert(lists:member(lager_crash_backend, gen_event:which_handlers(lager_event))),
+ timer:sleep(6000),
+ ?assertEqual(2, lager_test_backend:count()),
+ {_Level, _Time, [_, _, Message]} = lager_test_backend:pop(),
+ ?assertEqual("Lager event handler lager_crash_backend exited with reason crash", lists:flatten(Message)),
+ {_Level2, _Time2, [_, _, Message2]} = lager_test_backend:pop(),
+ ?assertMatch("Lager failed to install handler lager_crash_backend into lager_event, retrying later :"++_, lists:flatten(Message2)),
+ ?assertEqual(false, lists:member(lager_crash_backend, gen_event:which_handlers(lager_event)))
+ after
+ application:stop(lager),
+ error_logger:tty(true)
+ end
+ end
+ ]
+ }.
+
+
+-endif.
diff --git a/deps/lager/src/lager_handler_watcher_sup.erl b/deps/lager/src/lager_handler_watcher_sup.erl
new file mode 100644
index 0000000..bea8a63
--- /dev/null
+++ b/deps/lager/src/lager_handler_watcher_sup.erl
@@ -0,0 +1,39 @@
+%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you under the Apache License,
+%% Version 2.0 (the "License"); you may not use this file
+%% except in compliance with the License. You may obtain
+%% a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+
+%% @doc A supervisor for monitoring lager_handler_watcher processes.
+
+%% @private
+
+-module(lager_handler_watcher_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Callbacks
+-export([init/1]).
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+init([]) ->
+ {ok, {{simple_one_for_one, 10, 60},
+ [
+ {lager_handler_watcher, {lager_handler_watcher, start_link, []},
+ transient, 5000, worker, [lager_handler_watcher]}
+ ]}}.
diff --git a/deps/lager/src/lager_mochiglobal.erl b/deps/lager/src/lager_mochiglobal.erl
new file mode 100644
index 0000000..5a92ce8
--- /dev/null
+++ b/deps/lager/src/lager_mochiglobal.erl
@@ -0,0 +1,107 @@
+%% @author Bob Ippolito <bob@mochimedia.com>
+%% @copyright 2010 Mochi Media, Inc.
+%% @doc Abuse module constant pools as a "read-only shared heap" (since erts 5.6)
+%% <a href="http://www.erlang.org/pipermail/erlang-questions/2009-March/042503.html">[1]</a>.
+-module(lager_mochiglobal).
+-author("Bob Ippolito <bob@mochimedia.com>").
+-export([get/1, get/2, put/2, delete/1]).
+
+-spec get(atom()) -> any() | undefined.
+%% @equiv get(K, undefined)
+get(K) ->
+ get(K, undefined).
+
+-spec get(atom(), T) -> any() | T.
+%% @doc Get the term for K or return Default.
+get(K, Default) ->
+ get(K, Default, key_to_module(K)).
+
+get(_K, Default, Mod) ->
+ try Mod:term()
+ catch error:undef ->
+ Default
+ end.
+
+-spec put(atom(), any()) -> ok.
+%% @doc Store term V at K, replaces an existing term if present.
+put(K, V) ->
+ put(K, V, key_to_module(K)).
+
+put(_K, V, Mod) ->
+ Bin = compile(Mod, V),
+ code:purge(Mod),
+ {module, Mod} = code:load_binary(Mod, atom_to_list(Mod) ++ ".erl", Bin),
+ ok.
+
+-spec delete(atom()) -> boolean().
+%% @doc Delete term stored at K, no-op if non-existent.
+delete(K) ->
+ delete(K, key_to_module(K)).
+
+delete(_K, Mod) ->
+ code:purge(Mod),
+ code:delete(Mod).
+
+-spec key_to_module(atom()) -> atom().
+key_to_module(K) ->
+ list_to_atom("lager_mochiglobal:" ++ atom_to_list(K)).
+
+-spec compile(atom(), any()) -> binary().
+compile(Module, T) ->
+ {ok, Module, Bin} = compile:forms(forms(Module, T),
+ [verbose, report_errors]),
+ Bin.
+
+-spec forms(atom(), any()) -> [erl_syntax:syntaxTree()].
+forms(Module, T) ->
+ [erl_syntax:revert(X) || X <- term_to_abstract(Module, term, T)].
+
+-spec term_to_abstract(atom(), atom(), any()) -> [erl_syntax:syntaxTree()].
+term_to_abstract(Module, Getter, T) ->
+ [%% -module(Module).
+ erl_syntax:attribute(
+ erl_syntax:atom(module),
+ [erl_syntax:atom(Module)]),
+ %% -export([Getter/0]).
+ erl_syntax:attribute(
+ erl_syntax:atom(export),
+ [erl_syntax:list(
+ [erl_syntax:arity_qualifier(
+ erl_syntax:atom(Getter),
+ erl_syntax:integer(0))])]),
+ %% Getter() -> T.
+ erl_syntax:function(
+ erl_syntax:atom(Getter),
+ [erl_syntax:clause([], none, [erl_syntax:abstract(T)])])].
+
+%%
+%% Tests
+%%
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+get_put_delete_test() ->
+ K = '$$test$$mochiglobal',
+ delete(K),
+ ?assertEqual(
+ bar,
+ get(K, bar)),
+ try
+ ?MODULE:put(K, baz),
+ ?assertEqual(
+ baz,
+ get(K, bar)),
+ ?MODULE:put(K, wibble),
+ ?assertEqual(
+ wibble,
+ ?MODULE:get(K))
+ after
+ delete(K)
+ end,
+ ?assertEqual(
+ bar,
+ get(K, bar)),
+ ?assertEqual(
+ undefined,
+ ?MODULE:get(K)),
+ ok.
+-endif.
diff --git a/deps/lager/src/lager_stdlib.erl b/deps/lager/src/lager_stdlib.erl
new file mode 100644
index 0000000..a492f7e
--- /dev/null
+++ b/deps/lager/src/lager_stdlib.erl
@@ -0,0 +1,507 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%% @doc Functions from Erlang OTP distribution that are really useful
+%% but aren't exported.
+%%
+%% All functions in this module are covered by the Erlang/OTP source
+%% distribution's license, the Erlang Public License. See
+%% http://www.erlang.org/ for full details.
+
+-module(lager_stdlib).
+
+-export([string_p/1]).
+-export([write_time/2, maybe_utc/1]).
+-export([is_my_error_report/1, is_my_info_report/1]).
+-export([sup_get/2]).
+-export([proc_lib_format/2]).
+
+
+%% from error_logger_file_h
+string_p([]) ->
+ false;
+string_p(Term) ->
+ string_p1(Term).
+
+string_p1([H|T]) when is_integer(H), H >= $\s, H < 255 ->
+ string_p1(T);
+string_p1([$\n|T]) -> string_p1(T);
+string_p1([$\r|T]) -> string_p1(T);
+string_p1([$\t|T]) -> string_p1(T);
+string_p1([$\v|T]) -> string_p1(T);
+string_p1([$\b|T]) -> string_p1(T);
+string_p1([$\f|T]) -> string_p1(T);
+string_p1([$\e|T]) -> string_p1(T);
+string_p1([H|T]) when is_list(H) ->
+ case string_p1(H) of
+ true -> string_p1(T);
+ _ -> false
+ end;
+string_p1([]) -> true;
+string_p1(_) -> false.
+
+%% From calendar
+-type year1970() :: 1970..10000. % should probably be 1970..
+-type month() :: 1..12.
+-type day() :: 1..31.
+-type hour() :: 0..23.
+-type minute() :: 0..59.
+-type second() :: 0..59.
+-type t_time() :: {hour(),minute(),second()}.
+-type t_datetime1970() :: {{year1970(),month(),day()},t_time()}.
+
+%% From OTP stdlib's error_logger_tty_h.erl ... These functions aren't
+%% exported.
+-spec write_time({utc, t_datetime1970()} | t_datetime1970(), string()) -> string().
+write_time({utc,{{Y,Mo,D},{H,Mi,S}}},Type) ->
+ io_lib:format("~n=~s==== ~p-~s-~p::~s:~s:~s UTC ===~n",
+ [Type,D,month(Mo),Y,t(H),t(Mi),t(S)]);
+write_time({{Y,Mo,D},{H,Mi,S}},Type) ->
+ io_lib:format("~n=~s==== ~p-~s-~p::~s:~s:~s ===~n",
+ [Type,D,month(Mo),Y,t(H),t(Mi),t(S)]).
+
+-spec maybe_utc(t_datetime1970()) -> {utc, t_datetime1970()} | t_datetime1970().
+maybe_utc(Time) ->
+ UTC = case application:get_env(sasl, utc_log) of
+ {ok, Val} ->
+ Val;
+ undefined ->
+ %% Backwards compatible:
+ case application:get_env(stdlib, utc_log) of
+ {ok, Val} ->
+ Val;
+ undefined ->
+ false
+ end
+ end,
+ if
+ UTC =:= true ->
+ UTCTime = case calendar:local_time_to_universal_time_dst(Time) of
+ [] -> calendar:local_time();
+ [T0|_] -> T0
+ end,
+ {utc, UTCTime};
+ true ->
+ Time
+ end.
+
+t(X) when is_integer(X) ->
+ t1(integer_to_list(X));
+t(_) ->
+ "".
+t1([X]) -> [$0,X];
+t1(X) -> X.
+
+month(1) -> "Jan";
+month(2) -> "Feb";
+month(3) -> "Mar";
+month(4) -> "Apr";
+month(5) -> "May";
+month(6) -> "Jun";
+month(7) -> "Jul";
+month(8) -> "Aug";
+month(9) -> "Sep";
+month(10) -> "Oct";
+month(11) -> "Nov";
+month(12) -> "Dec".
+
+%% From OTP sasl's sasl_report.erl ... These functions aren't
+%% exported.
+-spec is_my_error_report(atom()) -> boolean().
+is_my_error_report(supervisor_report) -> true;
+is_my_error_report(crash_report) -> true;
+is_my_error_report(_) -> false.
+
+-spec is_my_info_report(atom()) -> boolean().
+is_my_info_report(progress) -> true;
+is_my_info_report(_) -> false.
+
+-spec sup_get(term(), [proplists:property()]) -> term().
+sup_get(Tag, Report) ->
+ case lists:keysearch(Tag, 1, Report) of
+ {value, {_, Value}} ->
+ Value;
+ _ ->
+ ""
+ end.
+
+%% From OTP stdlib's proc_lib.erl ... These functions aren't exported.
+-spec proc_lib_format([term()], pos_integer()) -> string().
+proc_lib_format([OwnReport,LinkReport], FmtMaxBytes) ->
+ OwnFormat = format_report(OwnReport, FmtMaxBytes),
+ LinkFormat = format_report(LinkReport, FmtMaxBytes),
+ %% io_lib:format here is OK because we're limiting max length elsewhere.
+ Str = io_lib:format(" crasher:~n~s neighbours:~n~s",[OwnFormat,LinkFormat]),
+ lists:flatten(Str).
+
+format_report(Rep, FmtMaxBytes) when is_list(Rep) ->
+ format_rep(Rep, FmtMaxBytes);
+format_report(Rep, FmtMaxBytes) ->
+ {Str, _} = lager_trunc_io:print(Rep, FmtMaxBytes),
+ io_lib:format("~p~n", [Str]).
+
+format_rep([{initial_call,InitialCall}|Rep], FmtMaxBytes) ->
+ [format_mfa(InitialCall, FmtMaxBytes)|format_rep(Rep, FmtMaxBytes)];
+format_rep([{error_info,{Class,Reason,StackTrace}}|Rep], FmtMaxBytes) ->
+ [format_exception(Class, Reason, StackTrace, FmtMaxBytes)|format_rep(Rep, FmtMaxBytes)];
+format_rep([{Tag,Data}|Rep], FmtMaxBytes) ->
+ [format_tag(Tag, Data, FmtMaxBytes)|format_rep(Rep, FmtMaxBytes)];
+format_rep(_, _S) ->
+ [].
+
+format_exception(Class, Reason, StackTrace, FmtMaxBytes) ->
+ PF = pp_fun(FmtMaxBytes),
+ StackFun = fun(M, _F, _A) -> (M =:= erl_eval) or (M =:= ?MODULE) end,
+ %% EI = " exception: ",
+ EI = " ",
+ [EI, lib_format_exception(1+length(EI), Class, Reason,
+ StackTrace, StackFun, PF), "\n"].
+
+format_mfa({M,F,Args}=StartF, FmtMaxBytes) ->
+ try
+ A = length(Args),
+ [" initial call: ",atom_to_list(M),$:,atom_to_list(F),$/,
+ integer_to_list(A),"\n"]
+ catch
+ error:_ ->
+ format_tag(initial_call, StartF, FmtMaxBytes)
+ end.
+
+pp_fun(FmtMaxBytes) ->
+ fun(Term, _I) ->
+ {Str, _} = lager_trunc_io:print(Term, FmtMaxBytes),
+ io_lib:format("~s", [Str])
+ end.
+
+format_tag(Tag, Data, FmtMaxBytes) ->
+ {Str, _} = lager_trunc_io:print(Data, FmtMaxBytes),
+ io_lib:format(" ~p: ~s~n", [Tag, Str]).
+
+%% From OTP stdlib's lib.erl ... These functions aren't exported.
+
+lib_format_exception(I, Class, Reason, StackTrace, StackFun, FormatFun)
+ when is_integer(I), I >= 1, is_function(StackFun, 3),
+ is_function(FormatFun, 2) ->
+ Str = n_spaces(I-1),
+ {Term,Trace1,Trace} = analyze_exception(Class, Reason, StackTrace),
+ Expl0 = explain_reason(Term, Class, Trace1, FormatFun, Str),
+ Expl = io_lib:fwrite(<<"~s~s">>, [exited(Class), Expl0]),
+ case format_stacktrace1(Str, Trace, FormatFun, StackFun) of
+ [] -> Expl;
+ Stack -> [Expl, $\n, Stack]
+ end.
+
+analyze_exception(error, Term, Stack) ->
+ case {is_stacktrace(Stack), Stack, Term} of
+ {true, [{_M,_F,As}=MFA|MFAs], function_clause} when is_list(As) ->
+ {Term,[MFA],MFAs};
+ {true, [{shell,F,A}], function_clause} when is_integer(A) ->
+ {Term, [{F,A}], []};
+ {true, [{_M,_F,_AorAs}=MFA|MFAs], undef} ->
+ {Term,[MFA],MFAs};
+ {true, _, _} ->
+ {Term,[],Stack};
+ {false, _, _} ->
+ {{Term,Stack},[],[]}
+ end;
+analyze_exception(_Class, Term, Stack) ->
+ case is_stacktrace(Stack) of
+ true ->
+ {Term,[],Stack};
+ false ->
+ {{Term,Stack},[],[]}
+ end.
+
+is_stacktrace([]) ->
+ true;
+is_stacktrace([{M,F,A}|Fs]) when is_atom(M), is_atom(F), is_integer(A) ->
+ is_stacktrace(Fs);
+is_stacktrace([{M,F,As}|Fs]) when is_atom(M), is_atom(F), length(As) >= 0 ->
+ is_stacktrace(Fs);
+is_stacktrace(_) ->
+ false.
+
+%% ERTS exit codes (some of them are also returned by erl_eval):
+explain_reason(badarg, error, [], _PF, _Str) ->
+ <<"bad argument">>;
+explain_reason({badarg,V}, error=Cl, [], PF, Str) -> % orelse, andalso
+ format_value(V, <<"bad argument: ">>, Cl, PF, Str);
+explain_reason(badarith, error, [], _PF, _Str) ->
+ <<"bad argument in an arithmetic expression">>;
+explain_reason({badarity,{Fun,As}}, error, [], _PF, _Str)
+ when is_function(Fun) ->
+ %% Only the arity is displayed, not the arguments As.
+ io_lib:fwrite(<<"~s called with ~s">>,
+ [format_fun(Fun), argss(length(As))]);
+explain_reason({badfun,Term}, error=Cl, [], PF, Str) ->
+ format_value(Term, <<"bad function ">>, Cl, PF, Str);
+explain_reason({badmatch,Term}, error=Cl, [], PF, Str) ->
+ format_value(Term, <<"no match of right hand side value ">>, Cl, PF, Str);
+explain_reason({case_clause,V}, error=Cl, [], PF, Str) ->
+ %% "there is no case clause with a true guard sequence and a
+ %% pattern matching..."
+ format_value(V, <<"no case clause matching ">>, Cl, PF, Str);
+explain_reason(function_clause, error, [{F,A}], _PF, _Str) ->
+ %% Shell commands
+ FAs = io_lib:fwrite(<<"~w/~w">>, [F, A]),
+ [<<"no function clause matching call to ">> | FAs];
+explain_reason(function_clause, error=Cl, [{M,F,As}], PF, Str) ->
+ String = <<"no function clause matching ">>,
+ format_errstr_call(String, Cl, {M,F}, As, PF, Str);
+explain_reason(if_clause, error, [], _PF, _Str) ->
+ <<"no true branch found when evaluating an if expression">>;
+explain_reason(noproc, error, [], _PF, _Str) ->
+ <<"no such process or port">>;
+explain_reason(notalive, error, [], _PF, _Str) ->
+ <<"the node cannot be part of a distributed system">>;
+explain_reason(system_limit, error, [], _PF, _Str) ->
+ <<"a system limit has been reached">>;
+explain_reason(timeout_value, error, [], _PF, _Str) ->
+ <<"bad receive timeout value">>;
+explain_reason({try_clause,V}, error=Cl, [], PF, Str) ->
+ %% "there is no try clause with a true guard sequence and a
+ %% pattern matching..."
+ format_value(V, <<"no try clause matching ">>, Cl, PF, Str);
+explain_reason(undef, error, [{M,F,A}], _PF, _Str) ->
+ %% Only the arity is displayed, not the arguments, if there are any.
+ io_lib:fwrite(<<"undefined function ~s">>,
+ [mfa_to_string(M, F, n_args(A))]);
+explain_reason({shell_undef,F,A}, error, [], _PF, _Str) ->
+ %% Give nicer reports for undefined shell functions
+ %% (but not when the user actively calls shell_default:F(...)).
+ io_lib:fwrite(<<"undefined shell command ~s/~w">>, [F, n_args(A)]);
+%% Exit codes returned by erl_eval only:
+explain_reason({argument_limit,_Fun}, error, [], _PF, _Str) ->
+ io_lib:fwrite(<<"limit of number of arguments to interpreted function"
+ " exceeded">>, []);
+explain_reason({bad_filter,V}, error=Cl, [], PF, Str) ->
+ format_value(V, <<"bad filter ">>, Cl, PF, Str);
+explain_reason({bad_generator,V}, error=Cl, [], PF, Str) ->
+ format_value(V, <<"bad generator ">>, Cl, PF, Str);
+explain_reason({unbound,V}, error, [], _PF, _Str) ->
+ io_lib:fwrite(<<"variable ~w is unbound">>, [V]);
+%% Exit codes local to the shell module (restricted shell):
+explain_reason({restricted_shell_bad_return, V}, exit=Cl, [], PF, Str) ->
+ String = <<"restricted shell module returned bad value ">>,
+ format_value(V, String, Cl, PF, Str);
+explain_reason({restricted_shell_disallowed,{ForMF,As}},
+ exit=Cl, [], PF, Str) ->
+ %% ForMF can be a fun, but not a shell fun.
+ String = <<"restricted shell does not allow ">>,
+ format_errstr_call(String, Cl, ForMF, As, PF, Str);
+explain_reason(restricted_shell_started, exit, [], _PF, _Str) ->
+ <<"restricted shell starts now">>;
+explain_reason(restricted_shell_stopped, exit, [], _PF, _Str) ->
+ <<"restricted shell stopped">>;
+%% Other exit code:
+explain_reason(Reason, Class, [], PF, Str) ->
+ PF(Reason, (iolist_size(Str)+1) + exited_size(Class)).
+
+n_spaces(N) ->
+ lists:duplicate(N, $\s).
+
+exited_size(Class) ->
+ iolist_size(exited(Class)).
+
+exited(error) ->
+ <<"exception error: ">>;
+exited(exit) ->
+ <<"exception exit: ">>;
+exited(throw) ->
+ <<"exception throw: ">>.
+
+format_stacktrace1(S0, Stack0, PF, SF) ->
+ Stack1 = lists:dropwhile(fun({M,F,A}) -> SF(M, F, A)
+ end, lists:reverse(Stack0)),
+ S = [" " | S0],
+ Stack = lists:reverse(Stack1),
+ format_stacktrace2(S, Stack, 1, PF).
+
+format_stacktrace2(S, [{M,F,A}|Fs], N, PF) when is_integer(A) ->
+ [io_lib:fwrite(<<"~s~s ~s">>,
+ [sep(N, S), origin(N, M, F, A), mfa_to_string(M, F, A)])
+ | format_stacktrace2(S, Fs, N + 1, PF)];
+format_stacktrace2(S, [{M,F,As}|Fs], N, PF) when is_list(As) ->
+ A = length(As),
+ CalledAs = [S,<<" called as ">>],
+ C = format_call("", CalledAs, {M,F}, As, PF),
+ [io_lib:fwrite(<<"~s~s ~s\n~s~s">>,
+ [sep(N, S), origin(N, M, F, A), mfa_to_string(M, F, A),
+ CalledAs, C])
+ | format_stacktrace2(S, Fs, N + 1, PF)];
+format_stacktrace2(_S, [], _N, _PF) ->
+ "".
+
+argss(0) ->
+ <<"no arguments">>;
+argss(1) ->
+ <<"one argument">>;
+argss(2) ->
+ <<"two arguments">>;
+argss(I) ->
+ io_lib:fwrite(<<"~w arguments">>, [I]).
+
+format_value(V, ErrStr, Class, PF, Str) ->
+ Pre1Sz = exited_size(Class),
+ Str1 = PF(V, Pre1Sz + iolist_size([Str, ErrStr])+1),
+ [ErrStr | case count_nl(Str1) of
+ N1 when N1 > 1 ->
+ Str2 = PF(V, iolist_size(Str) + 1 + Pre1Sz),
+ case count_nl(Str2) < N1 of
+ true ->
+ [$\n, Str, n_spaces(Pre1Sz) | Str2];
+ false ->
+ Str1
+ end;
+ _ ->
+ Str1
+ end].
+
+format_fun(Fun) when is_function(Fun) ->
+ {module, M} = erlang:fun_info(Fun, module),
+ {name, F} = erlang:fun_info(Fun, name),
+ {arity, A} = erlang:fun_info(Fun, arity),
+ case erlang:fun_info(Fun, type) of
+ {type, local} when F =:= "" ->
+ io_lib:fwrite(<<"~w">>, [Fun]);
+ {type, local} when M =:= erl_eval ->
+ io_lib:fwrite(<<"interpreted function with arity ~w">>, [A]);
+ {type, local} ->
+ mfa_to_string(M, F, A);
+ {type, external} ->
+ mfa_to_string(M, F, A)
+ end.
+
+format_errstr_call(ErrStr, Class, ForMForFun, As, PF, Pre0) ->
+ Pre1 = [Pre0 | n_spaces(exited_size(Class))],
+ format_call(ErrStr, Pre1, ForMForFun, As, PF).
+
+format_call(ErrStr, Pre1, ForMForFun, As, PF) ->
+ Arity = length(As),
+ [ErrStr |
+ case is_op(ForMForFun, Arity) of
+ {yes,Op} ->
+ format_op(ErrStr, Pre1, Op, As, PF);
+ no ->
+ MFs = mf_to_string(ForMForFun, Arity),
+ I1 = iolist_size([Pre1,ErrStr|MFs]),
+ S1 = pp_arguments(PF, As, I1),
+ S2 = pp_arguments(PF, As, iolist_size([Pre1|MFs])),
+ Long = count_nl(pp_arguments(PF, [a2345,b2345], I1)) > 0,
+ case Long or (count_nl(S2) < count_nl(S1)) of
+ true ->
+ [$\n, Pre1, MFs, S2];
+ false ->
+ [MFs, S1]
+ end
+ end].
+
+mfa_to_string(M, F, A) ->
+ io_lib:fwrite(<<"~s/~w">>, [mf_to_string({M, F}, A), A]).
+
+mf_to_string({M, F}, A) ->
+ case erl_internal:bif(M, F, A) of
+ true ->
+ io_lib:fwrite(<<"~w">>, [F]);
+ false ->
+ case is_op({M, F}, A) of
+ {yes, '/'} ->
+ io_lib:fwrite(<<"~w">>, [F]);
+ {yes, F} ->
+ atom_to_list(F);
+ no ->
+ io_lib:fwrite(<<"~w:~w">>, [M, F])
+ end
+ end;
+mf_to_string(Fun, _A) when is_function(Fun) ->
+ format_fun(Fun);
+mf_to_string(F, _A) ->
+ io_lib:fwrite(<<"~w">>, [F]).
+
+n_args(A) when is_integer(A) ->
+ A;
+n_args(As) when is_list(As) ->
+ length(As).
+
+origin(1, M, F, A) ->
+ case is_op({M, F}, n_args(A)) of
+ {yes, F} -> <<"in operator ">>;
+ no -> <<"in function ">>
+ end;
+origin(_N, _M, _F, _A) ->
+ <<"in call from">>.
+
+sep(1, S) -> S;
+sep(_, S) -> [$\n | S].
+
+count_nl([E | Es]) ->
+ count_nl(E) + count_nl(Es);
+count_nl($\n) ->
+ 1;
+count_nl(Bin) when is_binary(Bin) ->
+ count_nl(binary_to_list(Bin));
+count_nl(_) ->
+ 0.
+
+is_op(ForMForFun, A) ->
+ try
+ {erlang,F} = ForMForFun,
+ _ = erl_internal:op_type(F, A),
+ {yes,F}
+ catch error:_ -> no
+ end.
+
+format_op(ErrStr, Pre, Op, [A1, A2], PF) ->
+ I1 = iolist_size([ErrStr,Pre]),
+ S1 = PF(A1, I1+1),
+ S2 = PF(A2, I1+1),
+ OpS = atom_to_list(Op),
+ Pre1 = [$\n | n_spaces(I1)],
+ case count_nl(S1) > 0 of
+ true ->
+ [S1,Pre1,OpS,Pre1|S2];
+ false ->
+ OpS2 = io_lib:fwrite(<<" ~s ">>, [Op]),
+ S2_2 = PF(A2, iolist_size([ErrStr,Pre,S1|OpS2])+1),
+ case count_nl(S2) < count_nl(S2_2) of
+ true ->
+ [S1,Pre1,OpS,Pre1|S2];
+ false ->
+ [S1,OpS2|S2_2]
+ end
+ end.
+
+pp_arguments(PF, As, I) ->
+ case {As, io_lib:printable_list(As)} of
+ {[Int | T], true} ->
+ L = integer_to_list(Int),
+ Ll = length(L),
+ A = list_to_atom(lists:duplicate(Ll, $a)),
+ S0 = binary_to_list(iolist_to_binary(PF([A | T], I+1))),
+ brackets_to_parens([$[,L,string:sub_string(S0, 2+Ll)]);
+ _ ->
+ brackets_to_parens(PF(As, I+1))
+ end.
+
+brackets_to_parens(S) ->
+ B = iolist_to_binary(S),
+ Sz = byte_size(B) - 2,
+ <<$[,R:Sz/binary,$]>> = B,
+ [$(,R,$)].
+
diff --git a/deps/lager/src/lager_sup.erl b/deps/lager/src/lager_sup.erl
new file mode 100644
index 0000000..7c2b29b
--- /dev/null
+++ b/deps/lager/src/lager_sup.erl
@@ -0,0 +1,80 @@
+%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you under the Apache License,
+%% Version 2.0 (the "License"); you may not use this file
+%% except in compliance with the License. You may obtain
+%% a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+
+%% @doc Lager's top level supervisor.
+
+%% @private
+
+-module(lager_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Callbacks
+-export([init/1]).
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+init([]) ->
+ Children = [
+ {lager, {gen_event, start_link, [{local, lager_event}]},
+ permanent, 5000, worker, [dynamic]},
+ {lager_handler_watcher_sup, {lager_handler_watcher_sup, start_link, []},
+ permanent, 5000, supervisor, [lager_handler_watcher_sup]}],
+
+ %% check if the crash log is enabled
+ Crash = case application:get_env(lager, crash_log) of
+ {ok, undefined} ->
+ [];
+ {ok, File} ->
+ MaxBytes = case application:get_env(lager, crash_log_msg_size) of
+ {ok, Val} when is_integer(Val) andalso Val > 0 -> Val;
+ _ -> 65536
+ end,
+ RotationSize = case application:get_env(lager, crash_log_size) of
+ {ok, Val1} when is_integer(Val1) andalso Val1 >= 0 -> Val1;
+ _ -> 0
+ end,
+ RotationCount = case application:get_env(lager, crash_log_count) of
+ {ok, Val2} when is_integer(Val2) andalso Val2 >=0 -> Val2;
+ _ -> 0
+ end,
+ RotationDate = case application:get_env(lager, crash_log_date) of
+ {ok, Val3} ->
+ case lager_util:parse_rotation_date_spec(Val3) of
+ {ok, Spec} -> Spec;
+ {error, _} when Val3 == "" -> undefined; %% blank is ok
+ {error, _} ->
+ error_logger:error_msg("Invalid date spec for "
+ "crash log ~p~n", [Val3]),
+ undefined
+ end;
+ _ -> undefined
+ end,
+
+ [{lager_crash_log, {lager_crash_log, start_link, [File, MaxBytes,
+ RotationSize, RotationDate, RotationCount]},
+ permanent, 5000, worker, [lager_crash_log]}];
+ _ ->
+ []
+ end,
+
+ {ok, {{one_for_one, 10, 60},
+ Children ++ Crash
+ }}.
diff --git a/deps/lager/src/lager_transform.erl b/deps/lager/src/lager_transform.erl
new file mode 100644
index 0000000..7cbe499
--- /dev/null
+++ b/deps/lager/src/lager_transform.erl
@@ -0,0 +1,264 @@
+%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you under the Apache License,
+%% Version 2.0 (the "License"); you may not use this file
+%% except in compliance with the License. You may obtain
+%% a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+
+%% @doc The parse transform used for lager messages.
+%% This parse transform rewrites functions calls to lager:Severity/1,2 into
+%% a more complicated function that captures module, function, line, pid and
+%% time as well. The entire function call is then wrapped in a case that
+%% checks the mochiglobal 'loglevel' value, so the code isn't executed if
+%% nothing wishes to consume the message.
+
+-module(lager_transform).
+
+-include("lager.hrl").
+
+-export([parse_transform/2]).
+-export([format_error/1]).
+
+%% @private
+parse_transform(AST, _Options) ->
+ try walk_ast([], AST) of
+ Forms -> Forms
+ catch
+ throw:E ->
+ E
+ end.
+
+
+walk_ast(Acc, []) ->
+ lists:reverse(Acc);
+walk_ast(Acc, [{attribute, _, module, {Module, _PmodArgs}}=H|T]) ->
+ %% A wild parameterized module appears!
+ put(module, Module),
+ walk_ast([H|Acc], T);
+walk_ast(Acc, [{attribute, _, module, Module}=H|T]) ->
+ put(module, Module),
+ walk_ast([H|Acc], T);
+walk_ast(Acc, [{function, Line, Name, Arity, Clauses}|T]) ->
+ put(function, Name),
+ walk_ast([{function, Line, Name, Arity,
+ walk_clauses([], Clauses)}|Acc], T);
+walk_ast(Acc, [H|T]) ->
+ walk_ast([H|Acc], T).
+
+walk_clauses(Acc, []) ->
+ lists:reverse(Acc);
+walk_clauses(Acc, [{clause, Line, Arguments, Guards, Body}|T]) ->
+ walk_clauses([{clause, Line, Arguments, Guards, walk_body([], Body)}|Acc], T).
+
+walk_body(Acc, []) ->
+ lists:reverse(Acc);
+walk_body(Acc, [H|T]) ->
+ walk_body([transform_statement(H)|Acc], T).
+%%
+%% lager:debug([{a,1},{b,2}], "Hello ~w", [world])
+%%
+%% generate:
+%% case lager_mochiglobal:get(loglevel, {- 1,[]}) of
+%% {_lager_transform_level3,[]}
+%% when _lager_transform_level3 < 7 ->
+%% ok;
+%% {_,_lager_transform_match4} ->
+%% lager:dispatch_log1(_lager_transform_match4,
+%% debug,
+%% ?MODULE,
+%% ?FUNCTION,
+%% ?LINE,
+%% self(),
+%% fun({a,1}) -> true;
+%% ({a,'*'}) -> true;
+%% ({b,2}) -> true;
+%% ({b,'*'}) -> true;
+%% ({module,?MODULE}) -> true;
+%% ({module,'*'}) -> true;
+%% ({function,?FUNCTION}) -> true;
+%% ({function,'*'}) -> true;
+%% ({line,?LINE}) -> true;
+%% ({line,'*'}) -> true;
+%% ({pid,'*'}) -> true;
+%% ({pid,PID}) ->
+%% PID =:= pid_to_list(self());
+%% (_) -> false
+%% end,
+%% "Hello ~w",
+%% [world])
+%% end
+%%
+transform_statement({call, L, {remote, L1, {atom, L2, lager},
+ {atom, L3, Severity}}, Arguments0} = Stmt) ->
+ case lists:member(Severity, ?LEVELS) of
+ true ->
+ Trace0 = [trace_clause({atom,L,module},{atom,L,'*'}),
+ trace_clause({atom,L,module},{atom,L,get(module)}),
+ trace_clause({atom,L,function},{atom,L,'*'}),
+ trace_clause({atom,L,function},{atom,L,get(function)}),
+ trace_clause({atom,L,line},{atom,L,'*'}),
+ trace_clause({atom,L,line},{integer,L,L}),
+ trace_clause({atom,L,pid}, {atom,L,'*'}),
+ trace_clause({atom,L,pid}, {atom,L,pid})
+ ],
+ {Trace1, Message, Arguments} =
+ case Arguments0 of
+ [Format] ->
+ {Trace0, Format, {nil, L}};
+ [Arg1, Arg2] ->
+ %% some ambiguity here, figure out if these arguments are
+ %% [Format, Args] or [Attr, Format].
+ %% The trace attributes will be a list of tuples, so check
+ %% for that.
+ case Arg1 of
+ {cons, _, {tuple, _, _}, _} ->
+ {concat_clauses(L,Arg1,Trace0),
+ Arg2, {nil,L}};
+ _ ->
+ {Trace0, Arg1, Arg2}
+ end;
+ [Attrs, Format, Args] ->
+ {concat_clauses(L,Attrs,Trace0), Format, Args}
+ end,
+ Trace2 = lists:ukeysort(1, Trace1),
+ Clauses = generate_clauses(L,Trace2) ++
+ [{clause,L,[{var,L,'_'}],[],
+ [{atom,L,false}]}],
+ Traces = {'fun',L,{clauses,Clauses}},
+ SeverityNum =
+ case Severity of
+ debug -> ?DEBUG;
+ info -> ?INFO;
+ notice -> ?NOTICE;
+ warning -> ?WARNING;
+ error -> ?ERROR;
+ critical -> ?CRITICAL;
+ alert -> ?ALERT;
+ emergency -> ?EMERGENCY;
+ none -> ?LOG_NONE
+ end,
+ LevelThreshold = new_var("level",L),
+ Match = new_var("match",L),
+ {block, L,
+ [
+ {'case',L,
+ {call,L,
+ {remote,L,{atom,L,lager_mochiglobal},{atom,L,get}},
+ [{atom,L,loglevel},
+ {tuple,L,[{op,L,'-',{integer,L,1}},{nil,L}]}]},
+ [{clause,L,
+ [{tuple,L,[LevelThreshold,{nil,L}]}],
+ [[{op,L,'<',LevelThreshold,{integer,L,SeverityNum}}]],
+ [{atom,L,ok}]},
+ {clause,L,
+ [{tuple,L,[{var,L,'_'},Match]}],
+ [],
+ [{call,L,
+ {remote,L1,{atom,L,lager},
+ {atom,L2,dispatch_log1}},
+ [Match,
+ {atom,L3,Severity},
+ {atom,L3,get(module)},
+ {atom,L3,get(function)},
+ {integer,L3,L},
+ {call, L3, {atom, L3 ,self}, []},
+ Traces,
+ Message,
+ Arguments
+ ]}]}]}
+ ]};
+ false ->
+ Stmt
+ end;
+transform_statement({call, L, {remote, L1, {atom, L2, boston_lager},
+ {atom, L3, Severity}}, Arguments}) ->
+ NewArgs =
+ case Arguments of
+ [{string, L, Msg}] ->
+ [{string, L, re:replace(Msg, "r", "h",
+ [{return, list}, global])}];
+ [{string, L, Format}, Args] ->
+ [{string, L, re:replace(Format, "r", "h",
+ [{return, list}, global])}, Args];
+ Other -> Other
+ end,
+ transform_statement({call, L, {remote, L1, {atom, L2, lager},
+ {atom, L3, Severity}}, NewArgs});
+transform_statement(Stmt) when is_tuple(Stmt) ->
+ list_to_tuple(transform_statement(tuple_to_list(Stmt)));
+transform_statement(Stmt) when is_list(Stmt) ->
+ [transform_statement(S) || S <- Stmt];
+transform_statement(Stmt) ->
+ Stmt.
+
+%%
+%% Generate from key value pairs
+%% remove duplicates!
+%%
+
+generate_clauses(L,[{{pid,pid},{_K,_V}}|Cs]) ->
+ make_pid_clause(L) ++ generate_clauses(L, Cs);
+generate_clauses(L,[{{_,_},{K,V}}|Cs]) ->
+ make_clause(L,K,V) ++ generate_clauses(L, Cs);
+generate_clauses(_L, []) ->
+ [].
+
+%%
+%% create trace clause
+%% ({key, Value}) -> true;
+%%
+make_clause(L,K,V) ->
+ [{clause,L,[{tuple,L,[K,V]}],[],[{atom,L,true}]}].
+
+%%
+%% create special pid clauses
+%% ({pid, PID}) -> PID =:= pid_to_list(self())
+%%
+make_pid_clause(L) ->
+ PID = new_var("pid", L),
+ K = {atom,L,pid},
+ [{clause,L,[{tuple,L,[K,PID]}],[],
+ [{op,L,'=:=', PID, {call,L,{atom,L,pid_to_list},
+ [{call,L,{atom,L,self},[]}]}}]}].
+
+%%
+%% Concat clause from input
+%%
+concat_clauses(_L,{nil, _Line}, B) ->
+ B;
+concat_clauses(L,{cons, _Line, {tuple,_L,[K,V]}, Tail}, B) ->
+ [ trace_clause(K, {atom,L,'*'}),
+ trace_clause(K,V) | concat_clauses(L,Tail, B)];
+concat_clauses(L, _, _) ->
+ Err = {"*current*",[{L,?MODULE,"traces must be key value pairs"}]},
+ throw({error,[Err],[]}).
+
+format_error(Str) ->
+ io_lib:format("~s", [Str]).
+
+trace_clause(K,V) ->
+ Kn = erl_parse:normalise(K),
+ Vn = erl_parse:normalise(V),
+ {{Kn,Vn},{K,V}}.
+
+
+new_var(Base,L) ->
+ N = case get(lager_transform_var) of
+ undefined ->
+ 1;
+ N0 ->
+ N0
+ end,
+ put(lager_transform_var, N+1),
+ Name = list_to_atom("_lager_transform_"++Base++integer_to_list(N)),
+ {var, L, Name}.
+
diff --git a/deps/lager/src/lager_trunc_io.erl b/deps/lager/src/lager_trunc_io.erl
new file mode 100644
index 0000000..a9e8819
--- /dev/null
+++ b/deps/lager/src/lager_trunc_io.erl
@@ -0,0 +1,576 @@
+%% ``The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with your Erlang distribution. If not, it can be
+%% retrieved via the world wide web at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% The Initial Developer of the Original Code is Corelatus AB.
+%% Portions created by Corelatus are Copyright 2003, Corelatus
+%% AB. All Rights Reserved.''
+%%
+%% @doc Module to print out terms for logging. Limits by length rather than depth.
+%%
+%% The resulting string may be slightly larger than the limit; the intention
+%% is to provide predictable CPU and memory consumption for formatting
+%% terms, not produce precise string lengths.
+%%
+%% Typical use:
+%%
+%% trunc_io:print(Term, 500).
+%%
+%% Source license: Erlang Public License.
+%% Original author: Matthias Lang, <tt>matthias@corelatus.se</tt>
+%%
+%% Various changes to this module, most notably the format/3 implementation
+%% were added by Andrew Thompson `<andrew@basho.com>'. The module has been renamed
+%% to avoid conflicts with the vanilla module.
+
+-module(lager_trunc_io).
+-author('matthias@corelatus.se').
+%% And thanks to Chris Newcombe for a bug fix
+-export([format/3, format/4, print/2, print/3, fprint/2, fprint/3, safe/2]). % interface functions
+-version("$Id: trunc_io.erl,v 1.11 2009-02-23 12:01:06 matthias Exp $").
+
+-ifdef(TEST).
+-export([perf/0, perf/3, perf1/0, test/0, test/2]). % testing functions
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+-record(print_options, {
+ %% negative depth means no depth limiting
+ depth = -1 :: integer(),
+ %% whether to print lists as strings, if possible
+ lists_as_strings = true :: boolean(),
+ %% force strings, or binaries to be printed as a string,
+ %% even if they're not printable
+ force_strings = false :: boolean()
+ }).
+
+format(Fmt, Args, Max) ->
+ format(Fmt, Args, Max, []).
+
+format(Fmt, Args, Max, Options) ->
+ try lager_format:format(Fmt, Args, Max, Options) of
+ Result -> Result
+ catch
+ _:_ ->
+ erlang:error(badarg, [Fmt, Args])
+ end.
+
+%% @doc Returns an flattened list containing the ASCII representation of the given
+%% term.
+-spec fprint(term(), pos_integer()) -> string().
+fprint(Term, Max) ->
+ fprint(Term, Max, []).
+
+
+%% @doc Returns an flattened list containing the ASCII representation of the given
+%% term.
+-spec fprint(term(), pos_integer(), #print_options{}) -> string().
+fprint(T, Max, Options) ->
+ {L, _} = print(T, Max, prepare_options(Options, #print_options{})),
+ lists:flatten(L).
+
+%% @doc Same as print, but never crashes.
+%%
+%% This is a tradeoff. Print might conceivably crash if it's asked to
+%% print something it doesn't understand, for example some new data
+%% type in a future version of Erlang. If print crashes, we fall back
+%% to io_lib to format the term, but then the formatting is
+%% depth-limited instead of length limited, so you might run out
+%% memory printing it. Out of the frying pan and into the fire.
+%%
+-spec safe(term(), pos_integer()) -> {string(), pos_integer()} | {string()}.
+safe(What, Len) ->
+ case catch print(What, Len) of
+ {L, Used} when is_list(L) -> {L, Used};
+ _ -> {"unable to print" ++ io_lib:write(What, 99)}
+ end.
+
+%% @doc Returns {List, Length}
+-spec print(term(), pos_integer()) -> {iolist(), pos_integer()}.
+print(Term, Max) ->
+ print(Term, Max, []).
+
+%% @doc Returns {List, Length}
+-spec print(term(), pos_integer(), #print_options{}) -> {iolist(), pos_integer()}.
+print(Term, Max, Options) when is_list(Options) ->
+ %% need to convert the proplist to a record
+ print(Term, Max, prepare_options(Options, #print_options{}));
+
+print(Term, _Max, #print_options{force_strings=true}) when not is_list(Term), not is_binary(Term), not is_atom(Term) ->
+ erlang:error(badarg);
+
+print(_, Max, _Options) when Max < 0 -> {"...", 3};
+print(_, _, #print_options{depth=0}) -> {"...", 3};
+
+print(Tuple, Max, Options) when is_tuple(Tuple) ->
+ {TC, Len} = tuple_contents(Tuple, Max-2, Options),
+ {[${, TC, $}], Len + 2};
+
+%% @doc We assume atoms, floats, funs, integers, PIDs, ports and refs never need
+%% to be truncated. This isn't strictly true, someone could make an
+%% arbitrarily long bignum. Let's assume that won't happen unless someone
+%% is being malicious.
+%%
+print(Atom, _Max, #print_options{force_strings=NoQuote}) when is_atom(Atom) ->
+ L = atom_to_list(Atom),
+ R = case atom_needs_quoting_start(L) andalso not NoQuote of
+ true -> lists:flatten([$', L, $']);
+ false -> L
+ end,
+ {R, length(R)};
+
+print(<<>>, _Max, _Options) ->
+ {"<<>>", 4};
+
+print(Binary, 0, _Options) when is_bitstring(Binary) ->
+ {"<<..>>", 6};
+
+print(Binary, Max, Options) when is_binary(Binary) ->
+ B = binary_to_list(Binary, 1, lists:min([Max, byte_size(Binary)])),
+ {L, Len} = case Options#print_options.lists_as_strings orelse
+ Options#print_options.force_strings of
+ true ->
+ alist_start(B, Max-4, Options);
+ _ ->
+ list_body(B, Max-4, Options, false)
+ end,
+ {Res, Length} = case L of
+ [91, X, 93] ->
+ {X, Len - 2};
+ X ->
+ {X, Len}
+ end,
+ case Options#print_options.force_strings of
+ true ->
+ {Res, Length};
+ _ ->
+ {["<<", Res, ">>"], Length+4}
+ end;
+
+%% bitstrings are binary's evil brother who doesn't end on an 8 bit boundary.
+%% This makes printing them extremely annoying, so list_body/list_bodyc has
+%% some magic for dealing with the output of bitstring_to_list, which returns
+%% a list of integers (as expected) but with a trailing binary that represents
+%% the remaining bits.
+print(BitString, Max, Options) when is_bitstring(BitString) ->
+ case byte_size(BitString) > Max of
+ true ->
+ BL = binary_to_list(BitString, 1, Max);
+ _ ->
+ BL = erlang:bitstring_to_list(BitString)
+ end,
+ {X, Len0} = list_body(BL, Max - 4, Options, false),
+ {["<<", X, ">>"], Len0 + 4};
+
+print(Float, _Max, _Options) when is_float(Float) ->
+ %% use the same function io_lib:format uses to print floats
+ %% float_to_list is way too verbose.
+ L = io_lib_format:fwrite_g(Float),
+ {L, length(L)};
+
+print(Fun, Max, _Options) when is_function(Fun) ->
+ L = erlang:fun_to_list(Fun),
+ case length(L) > Max of
+ true ->
+ S = erlang:max(5, Max),
+ Res = string:substr(L, 1, S) ++ "..>",
+ {Res, length(Res)};
+ _ ->
+ {L, length(L)}
+ end;
+
+print(Integer, _Max, _Options) when is_integer(Integer) ->
+ L = integer_to_list(Integer),
+ {L, length(L)};
+
+print(Pid, _Max, _Options) when is_pid(Pid) ->
+ L = pid_to_list(Pid),
+ {L, length(L)};
+
+print(Ref, _Max, _Options) when is_reference(Ref) ->
+ L = erlang:ref_to_list(Ref),
+ {L, length(L)};
+
+print(Port, _Max, _Options) when is_port(Port) ->
+ L = erlang:port_to_list(Port),
+ {L, length(L)};
+
+print(List, Max, Options) when is_list(List) ->
+ case Options#print_options.lists_as_strings orelse
+ Options#print_options.force_strings of
+ true ->
+ alist_start(List, Max, dec_depth(Options));
+ _ ->
+ {R, Len} = list_body(List, Max - 2, dec_depth(Options), false),
+ {[$[, R, $]], Len + 2}
+ end.
+
+%% Returns {List, Length}
+tuple_contents(Tuple, Max, Options) ->
+ L = tuple_to_list(Tuple),
+ list_body(L, Max, dec_depth(Options), true).
+
+%% Format the inside of a list, i.e. do not add a leading [ or trailing ].
+%% Returns {List, Length}
+list_body([], _Max, _Options, _Tuple) -> {[], 0};
+list_body(_, Max, _Options, _Tuple) when Max < 4 -> {"...", 3};
+list_body(_, _Max, #print_options{depth=0}, _Tuple) -> {"...", 3};
+list_body([B], _Max, _Options, _Tuple) when is_bitstring(B), not is_binary(B) ->
+ Size = bit_size(B),
+ <<Value:Size>> = B,
+ ValueStr = integer_to_list(Value),
+ SizeStr = integer_to_list(Size),
+ {[ValueStr, $:, SizeStr], length(ValueStr) + length(SizeStr) +1};
+list_body([H|T], Max, Options, Tuple) ->
+ {List, Len} = print(H, Max, Options),
+ {Final, FLen} = list_bodyc(T, Max - Len, Options, Tuple),
+ {[List|Final], FLen + Len};
+list_body(X, Max, Options, _Tuple) -> %% improper list
+ {List, Len} = print(X, Max - 1, Options),
+ {[$|,List], Len + 1}.
+
+list_bodyc([], _Max, _Options, _Tuple) -> {[], 0};
+list_bodyc(_, Max, _Options, _Tuple) when Max < 5 -> {",...", 4};
+list_bodyc([B], _Max, _Options, _Tuple) when is_bitstring(B), not is_binary(B) ->
+ Size = bit_size(B),
+ <<Value:Size>> = B,
+ ValueStr = integer_to_list(Value),
+ SizeStr = integer_to_list(Size),
+ {[$, , ValueStr, $:, SizeStr], length(ValueStr) + length(SizeStr) +2};
+list_bodyc([H|T], Max, #print_options{depth=Depth} = Options, Tuple) ->
+ {List, Len} = print(H, Max, dec_depth(Options)),
+ {Final, FLen} = list_bodyc(T, Max - Len - 1, Options, Tuple),
+ Sep = case Depth == 1 andalso not Tuple of
+ true -> $|;
+ _ -> $,
+ end,
+ {[Sep, List|Final], FLen + Len + 1};
+list_bodyc(X, Max, Options, _Tuple) -> %% improper list
+ {List, Len} = print(X, Max - 1, Options),
+ {[$|,List], Len + 1}.
+
+%% The head of a list we hope is ascii. Examples:
+%%
+%% [65,66,67] -> "ABC"
+%% [65,0,67] -> "A"[0,67]
+%% [0,65,66] -> [0,65,66]
+%% [65,b,66] -> "A"[b,66]
+%%
+alist_start([], _Max, #print_options{force_strings=true}) -> {"", 0};
+alist_start([], _Max, _Options) -> {"[]", 2};
+alist_start(_, Max, _Options) when Max < 4 -> {"...", 3};
+alist_start(_, _Max, #print_options{depth=0}) -> {"[...]", 5};
+alist_start(L, Max, #print_options{force_strings=true} = Options) ->
+ alist(L, Max, Options);
+alist_start([H|T], Max, Options) when is_integer(H), H >= 16#20, H =< 16#7e -> % definitely printable
+ try alist([H|T], Max -1, Options) of
+ {L, Len} ->
+ {[$"|L], Len + 1}
+ catch
+ throw:unprintable ->
+ {R, Len} = list_body([H|T], Max-2, Options, false),
+ {[$[, R, $]], Len + 2}
+ end;
+alist_start([H|T], Max, Options) when H =:= 9; H =:= 10; H =:= 13 ->
+ try alist([H|T], Max -1, Options) of
+ {L, Len} ->
+ {[$"|L], Len + 1}
+ catch
+ throw:unprintable ->
+ {R, Len} = list_body([H|T], Max-2, Options, false),
+ {[$[, R, $]], Len + 2}
+ end;
+alist_start(L, Max, Options) ->
+ {R, Len} = list_body(L, Max-2, Options, false),
+ {[$[, R, $]], Len + 2}.
+
+alist([], _Max, #print_options{force_strings=true}) -> {"", 0};
+alist([], _Max, _Options) -> {"\"", 1};
+alist(_, Max, #print_options{force_strings=true}) when Max < 4 -> {"...", 3};
+alist(_, Max, #print_options{force_strings=false}) when Max < 5 -> {"...\"", 4};
+alist([H|T], Max, Options) when is_integer(H), H >= 16#20, H =< 16#7e -> % definitely printable
+ {L, Len} = alist(T, Max-1, Options),
+ {[H|L], Len + 1};
+alist([H|T], Max, Options) when H =:= 9; H =:= 10; H =:= 13 ->
+ {L, Len} = alist(T, Max-1, Options),
+ case Options#print_options.force_strings of
+ true ->
+ {[H|L], Len + 1};
+ _ ->
+ {[escape(H)|L], Len + 1}
+ end;
+alist([H|T], Max, #print_options{force_strings=true} = Options) when is_integer(H) ->
+ {L, Len} = alist(T, Max-1, Options),
+ {[H|L], Len + 1};
+alist(_, _, #print_options{force_strings=true}) ->
+ erlang:error(badarg);
+alist(_L, _Max, _Options) ->
+ throw(unprintable).
+
+%% is the first character in the atom alphabetic & lowercase?
+atom_needs_quoting_start([H|T]) when H >= $a, H =< $z ->
+ atom_needs_quoting(T);
+atom_needs_quoting_start(_) ->
+ true.
+
+atom_needs_quoting([]) ->
+ false;
+atom_needs_quoting([H|T]) when (H >= $a andalso H =< $z);
+ (H >= $A andalso H =< $Z);
+ (H >= $0 andalso H =< $9);
+ H == $@; H == $_ ->
+ atom_needs_quoting(T);
+atom_needs_quoting(_) ->
+ true.
+
+prepare_options([], Options) ->
+ Options;
+prepare_options([{depth, Depth}|T], Options) when is_integer(Depth) ->
+ prepare_options(T, Options#print_options{depth=Depth});
+prepare_options([{lists_as_strings, Bool}|T], Options) when is_boolean(Bool) ->
+ prepare_options(T, Options#print_options{lists_as_strings = Bool});
+prepare_options([{force_strings, Bool}|T], Options) when is_boolean(Bool) ->
+ prepare_options(T, Options#print_options{force_strings = Bool}).
+
+dec_depth(#print_options{depth=Depth} = Options) when Depth > 0 ->
+ Options#print_options{depth=Depth-1};
+dec_depth(Options) ->
+ Options.
+
+escape(9) -> "\\t";
+escape(10) -> "\\n";
+escape(13) -> "\\r".
+
+-ifdef(TEST).
+%%--------------------
+%% The start of a test suite. So far, it only checks for not crashing.
+-spec test() -> ok.
+test() ->
+ test(trunc_io, print).
+
+-spec test(atom(), atom()) -> ok.
+test(Mod, Func) ->
+ Simple_items = [atom, 1234, 1234.0, {tuple}, [], [list], "string", self(),
+ <<1,2,3>>, make_ref(), fun() -> ok end],
+ F = fun(A) ->
+ Mod:Func(A, 100),
+ Mod:Func(A, 2),
+ Mod:Func(A, 20)
+ end,
+
+ G = fun(A) ->
+ case catch F(A) of
+ {'EXIT', _} -> exit({failed, A});
+ _ -> ok
+ end
+ end,
+
+ lists:foreach(G, Simple_items),
+
+ Tuples = [ {1,2,3,a,b,c}, {"abc", def, 1234},
+ {{{{a},b,c,{d},e}},f}],
+
+ Lists = [ [1,2,3,4,5,6,7], lists:seq(1,1000),
+ [{a}, {a,b}, {a, [b,c]}, "def"], [a|b], [$a|$b] ],
+
+
+ lists:foreach(G, Tuples),
+ lists:foreach(G, Lists).
+
+-spec perf() -> ok.
+perf() ->
+ {New, _} = timer:tc(trunc_io, perf, [trunc_io, print, 1000]),
+ {Old, _} = timer:tc(trunc_io, perf, [io_lib, write, 1000]),
+ io:fwrite("New code took ~p us, old code ~p\n", [New, Old]).
+
+-spec perf(atom(), atom(), integer()) -> done.
+perf(M, F, Reps) when Reps > 0 ->
+ test(M,F),
+ perf(M,F,Reps-1);
+perf(_,_,_) ->
+ done.
+
+%% Performance test. Needs a particularly large term I saved as a binary...
+-spec perf1() -> {non_neg_integer(), non_neg_integer()}.
+perf1() ->
+ {ok, Bin} = file:read_file("bin"),
+ A = binary_to_term(Bin),
+ {N, _} = timer:tc(trunc_io, print, [A, 1500]),
+ {M, _} = timer:tc(io_lib, write, [A]),
+ {N, M}.
+
+format_test() ->
+ %% simple format strings
+ ?assertEqual("foobar", lists:flatten(format("~s", [["foo", $b, $a, $r]], 50))),
+ ?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~p", [["foo", $b, $a, $r]], 50))),
+ ?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~P", [["foo", $b, $a, $r], 10], 50))),
+ ?assertEqual("[[102,111,111],98,97,114]", lists:flatten(format("~w", [["foo", $b, $a, $r]], 50))),
+
+ %% complex ones
+ ?assertEqual(" foobar", lists:flatten(format("~10s", [["foo", $b, $a, $r]], 50))),
+ ?assertEqual(" [\"foo\",98,97,114]", lists:flatten(format("~22p", [["foo", $b, $a, $r]], 50))),
+ ?assertEqual(" [\"foo\",98,97,114]", lists:flatten(format("~22P", [["foo", $b, $a, $r], 10], 50))),
+ ?assertEqual("**********", lists:flatten(format("~10W", [["foo", $b, $a, $r], 10], 50))),
+ ?assertEqual("[[102,111,111],98,97,114]", lists:flatten(format("~25W", [["foo", $b, $a, $r], 10], 50))),
+ ok.
+
+atom_quoting_test() ->
+ ?assertEqual("hello", lists:flatten(format("~p", [hello], 50))),
+ ?assertEqual("'hello world'", lists:flatten(format("~p", ['hello world'], 50))),
+ ?assertEqual("'Hello world'", lists:flatten(format("~p", ['Hello world'], 50))),
+ ?assertEqual("hello_world", lists:flatten(format("~p", ['hello_world'], 50))),
+ ?assertEqual("'node@127.0.0.1'", lists:flatten(format("~p", ['node@127.0.0.1'], 50))),
+ ?assertEqual("node@nohost", lists:flatten(format("~p", [node@nohost], 50))),
+ ?assertEqual("abc123", lists:flatten(format("~p", [abc123], 50))),
+ ok.
+
+sane_float_printing_test() ->
+ ?assertEqual("1.0", lists:flatten(format("~p", [1.0], 50))),
+ ?assertEqual("1.23456789", lists:flatten(format("~p", [1.23456789], 50))),
+ ?assertEqual("1.23456789", lists:flatten(format("~p", [1.234567890], 50))),
+ ?assertEqual("0.3333333333333333", lists:flatten(format("~p", [1/3], 50))),
+ ?assertEqual("0.1234567", lists:flatten(format("~p", [0.1234567], 50))),
+ ok.
+
+float_inside_list_test() ->
+ ?assertEqual("[97,38.233913133184835,99]", lists:flatten(format("~p", [[$a, 38.233913133184835, $c]], 50))),
+ ?assertError(badarg, lists:flatten(format("~s", [[$a, 38.233913133184835, $c]], 50))),
+ ok.
+
+quote_strip_test() ->
+ ?assertEqual("\"hello\"", lists:flatten(format("~p", ["hello"], 50))),
+ ?assertEqual("hello", lists:flatten(format("~s", ["hello"], 50))),
+ ?assertEqual("hello", lists:flatten(format("~s", [hello], 50))),
+ ?assertEqual("hello", lists:flatten(format("~p", [hello], 50))),
+ ?assertEqual("'hello world'", lists:flatten(format("~p", ['hello world'], 50))),
+ ?assertEqual("hello world", lists:flatten(format("~s", ['hello world'], 50))),
+ ok.
+
+binary_printing_test() ->
+ ?assertEqual("<<>>", lists:flatten(format("~p", [<<>>], 50))),
+ ?assertEqual("<<..>>", lists:flatten(format("~p", [<<"hi">>], 0))),
+ ?assertEqual("<<...>>", lists:flatten(format("~p", [<<"hi">>], 1))),
+ ?assertEqual("<<\"hello\">>", lists:flatten(format("~p", [<<$h, $e, $l, $l, $o>>], 50))),
+ ?assertEqual("<<\"hello\">>", lists:flatten(format("~p", [<<"hello">>], 50))),
+ ?assertEqual("<<104,101,108,108,111>>", lists:flatten(format("~w", [<<"hello">>], 50))),
+ ?assertEqual("<<1,2,3,4>>", lists:flatten(format("~p", [<<1, 2, 3, 4>>], 50))),
+ ?assertEqual([1,2,3,4], lists:flatten(format("~s", [<<1, 2, 3, 4>>], 50))),
+ ?assertEqual("hello", lists:flatten(format("~s", [<<"hello">>], 50))),
+ ?assertEqual("hello\nworld", lists:flatten(format("~s", [<<"hello\nworld">>], 50))),
+ ?assertEqual("<<\"hello\\nworld\">>", lists:flatten(format("~p", [<<"hello\nworld">>], 50))),
+ ?assertEqual(" hello", lists:flatten(format("~10s", [<<"hello">>], 50))),
+ ok.
+
+bitstring_printing_test() ->
+ ?assertEqual("<<1,2,3,1:7>>", lists:flatten(format("~p",
+ [<<1, 2, 3, 1:7>>], 100))),
+ ?assertEqual("<<1:7>>", lists:flatten(format("~p",
+ [<<1:7>>], 100))),
+ ?assertEqual("<<1,2,3,...>>", lists:flatten(format("~p",
+ [<<1, 2, 3, 1:7>>], 12))),
+ ?assertEqual("<<1,2,3,...>>", lists:flatten(format("~p",
+ [<<1, 2, 3, 1:7>>], 13))),
+ ?assertEqual("<<1,2,3,1:7>>", lists:flatten(format("~p",
+ [<<1, 2, 3, 1:7>>], 14))),
+ ?assertEqual("<<..>>", lists:flatten(format("~p", [<<1:7>>], 0))),
+ ?assertEqual("<<...>>", lists:flatten(format("~p", [<<1:7>>], 1))),
+ ?assertEqual("[<<1>>,<<2>>]", lists:flatten(format("~p", [[<<1>>, <<2>>]],
+ 100))),
+ ok.
+
+list_printing_test() ->
+ ?assertEqual("[]", lists:flatten(format("~p", [[]], 50))),
+ ?assertEqual("[]", lists:flatten(format("~w", [[]], 50))),
+ ?assertEqual("", lists:flatten(format("~s", [[]], 50))),
+ ?assertEqual("...", lists:flatten(format("~s", [[]], -1))),
+ ?assertEqual("[[]]", lists:flatten(format("~p", [[[]]], 50))),
+ ?assertEqual("[13,11,10,8,5,4]", lists:flatten(format("~p", [[13,11,10,8,5,4]], 50))),
+ ?assertEqual("\"\\rabc\"", lists:flatten(format("~p", [[13,$a, $b, $c]], 50))),
+ ?assertEqual("[1,2,3|4]", lists:flatten(format("~p", [[1, 2, 3|4]], 50))),
+ ?assertEqual("[...]", lists:flatten(format("~p", [[1, 2, 3,4]], 4))),
+ ?assertEqual("[1,...]", lists:flatten(format("~p", [[1, 2, 3, 4]], 6))),
+ ?assertEqual("[1,...]", lists:flatten(format("~p", [[1, 2, 3, 4]], 7))),
+ ?assertEqual("[1,2,...]", lists:flatten(format("~p", [[1, 2, 3, 4]], 8))),
+ ?assertEqual("[1|4]", lists:flatten(format("~p", [[1|4]], 50))),
+ ?assertEqual("[1]", lists:flatten(format("~p", [[1]], 50))),
+ ?assertError(badarg, lists:flatten(format("~s", [[1|4]], 50))),
+ ?assertEqual("\"hello...\"", lists:flatten(format("~p", ["hello world"], 10))),
+ ?assertEqual("hello w...", lists:flatten(format("~s", ["hello world"], 10))),
+ ?assertEqual("hello world\r\n", lists:flatten(format("~s", ["hello world\r\n"], 50))),
+ ?assertEqual("\rhello world\r\n", lists:flatten(format("~s", ["\rhello world\r\n"], 50))),
+ ?assertEqual("\"\\rhello world\\r\\n\"", lists:flatten(format("~p", ["\rhello world\r\n"], 50))),
+ ?assertEqual("[13,104,101,108,108,111,32,119,111,114,108,100,13,10]", lists:flatten(format("~w", ["\rhello world\r\n"], 60))),
+ ?assertEqual("...", lists:flatten(format("~s", ["\rhello world\r\n"], 3))),
+ ?assertEqual("[22835963083295358096932575511191922182123945984,...]",
+ lists:flatten(format("~p", [
+ [22835963083295358096932575511191922182123945984,
+ 22835963083295358096932575511191922182123945984]], 9))),
+ ?assertEqual("[22835963083295358096932575511191922182123945984,...]",
+ lists:flatten(format("~p", [
+ [22835963083295358096932575511191922182123945984,
+ 22835963083295358096932575511191922182123945984]], 53))),
+ ok.
+
+tuple_printing_test() ->
+ ?assertEqual("{}", lists:flatten(format("~p", [{}], 50))),
+ ?assertEqual("{}", lists:flatten(format("~w", [{}], 50))),
+ ?assertError(badarg, lists:flatten(format("~s", [{}], 50))),
+ ?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 1))),
+ ?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 2))),
+ ?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 3))),
+ ?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 4))),
+ ?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 5))),
+ ?assertEqual("{foo,...}", lists:flatten(format("~p", [{foo,bar}], 6))),
+ ?assertEqual("{foo,...}", lists:flatten(format("~p", [{foo,bar}], 7))),
+ ?assertEqual("{foo,...}", lists:flatten(format("~p", [{foo,bar}], 9))),
+ ?assertEqual("{foo,bar}", lists:flatten(format("~p", [{foo,bar}], 10))),
+ ?assertEqual("{22835963083295358096932575511191922182123945984,...}",
+ lists:flatten(format("~w", [
+ {22835963083295358096932575511191922182123945984,
+ 22835963083295358096932575511191922182123945984}], 10))),
+ ?assertEqual("{22835963083295358096932575511191922182123945984,...}",
+ lists:flatten(format("~w", [
+ {22835963083295358096932575511191922182123945984,
+ bar}], 10))),
+ ?assertEqual("{22835963083295358096932575511191922182123945984,...}",
+ lists:flatten(format("~w", [
+ {22835963083295358096932575511191922182123945984,
+ 22835963083295358096932575511191922182123945984}], 53))),
+ ok.
+
+unicode_test() ->
+ ?assertEqual([231,167,129], lists:flatten(format("~s", [<<231,167,129>>], 50))),
+ ?assertEqual([31169], lists:flatten(format("~ts", [<<231,167,129>>], 50))),
+ ok.
+
+depth_limit_test() ->
+ ?assertEqual("{...}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 1], 50))),
+ ?assertEqual("{a,...}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 2], 50))),
+ ?assertEqual("{a,[...]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 3], 50))),
+ ?assertEqual("{a,[b|...]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 4], 50))),
+ ?assertEqual("{a,[b,[...]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 5], 50))),
+ ?assertEqual("{a,[b,[c|...]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 6], 50))),
+ ?assertEqual("{a,[b,[c,[...]]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 7], 50))),
+ ?assertEqual("{a,[b,[c,[d]]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 8], 50))),
+ ?assertEqual("{a,[b,[c,[d]]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 9], 50))),
+
+ ?assertEqual("{a,{...}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 3], 50))),
+ ?assertEqual("{a,{b,...}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 4], 50))),
+ ?assertEqual("{a,{b,{...}}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 5], 50))),
+ ?assertEqual("{a,{b,{c,...}}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 6], 50))),
+ ?assertEqual("{a,{b,{c,{...}}}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 7], 50))),
+ ?assertEqual("{a,{b,{c,{d}}}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 8], 50))),
+
+ ?assertEqual("{\"a\",[...]}", lists:flatten(format("~P", [{"a", ["b", ["c", ["d"]]]}, 3], 50))),
+ ?assertEqual("{\"a\",[\"b\",[[...]|...]]}", lists:flatten(format("~P", [{"a", ["b", ["c", ["d"]]]}, 6], 50))),
+ ?assertEqual("{\"a\",[\"b\",[\"c\",[\"d\"]]]}", lists:flatten(format("~P", [{"a", ["b", ["c", ["d"]]]}, 9], 50))),
+ ok.
+
+-endif.
diff --git a/deps/lager/src/lager_util.erl b/deps/lager/src/lager_util.erl
new file mode 100644
index 0000000..f05d0e6
--- /dev/null
+++ b/deps/lager/src/lager_util.erl
@@ -0,0 +1,471 @@
+%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you under the Apache License,
+%% Version 2.0 (the "License"); you may not use this file
+%% except in compliance with the License. You may obtain
+%% a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+
+-module(lager_util).
+
+-include_lib("kernel/include/file.hrl").
+
+-export([levels/0, level_to_num/1, num_to_level/1, open_logfile/2,
+ ensure_logfile/4, rotate_logfile/2, format_time/0, format_time/1,
+ localtime_ms/0, maybe_utc/1, parse_rotation_date_spec/1,
+ calculate_next_rotation/1, validate_trace/1, check_traces/4]).
+-export([check_f_traces/4]).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+levels() ->
+ [debug, info, notice, warning, error, critical, alert, emergency].
+
+level_to_num(debug) -> 7;
+level_to_num(info) -> 6;
+level_to_num(notice) -> 5;
+level_to_num(warning) -> 4;
+level_to_num(error) -> 3;
+level_to_num(critical) -> 2;
+level_to_num(alert) -> 1;
+level_to_num(emergency) -> 0;
+level_to_num(none) -> -1.
+
+num_to_level(7) -> debug;
+num_to_level(6) -> info;
+num_to_level(5) -> notice;
+num_to_level(4) -> warning;
+num_to_level(3) -> error;
+num_to_level(2) -> critical;
+num_to_level(1) -> alert;
+num_to_level(0) -> emergency;
+num_to_level(-1) -> none.
+
+open_logfile(Name, Buffer) ->
+ case filelib:ensure_dir(Name) of
+ ok ->
+ Options = [append, raw] ++
+ if Buffer == true -> [delayed_write];
+ true -> []
+ end,
+ case file:open(Name, Options) of
+ {ok, FD} ->
+ case file:read_file_info(Name) of
+ {ok, FInfo} ->
+ Inode = FInfo#file_info.inode,
+ {ok, {FD, Inode, FInfo#file_info.size}};
+ X -> X
+ end;
+ Y -> Y
+ end;
+ Z -> Z
+ end.
+
+ensure_logfile(Name, FD, Inode, Buffer) ->
+ case file:read_file_info(Name) of
+ {ok, FInfo} ->
+ Inode2 = FInfo#file_info.inode,
+ case Inode == Inode2 of
+ true ->
+ {ok, {FD, Inode, FInfo#file_info.size}};
+ false ->
+ %% delayed write can cause file:close not to do a close
+ file:close(FD),
+ file:close(FD),
+ case open_logfile(Name, Buffer) of
+ {ok, {FD2, Inode3, Size}} ->
+ %% inode changed, file was probably moved and
+ %% recreated
+ {ok, {FD2, Inode3, Size}};
+ Error ->
+ Error
+ end
+ end;
+ _ ->
+ %% delayed write can cause file:close not to do a close
+ file:close(FD),
+ file:close(FD),
+ case open_logfile(Name, Buffer) of
+ {ok, {FD2, Inode3, Size}} ->
+ %% file was removed
+ {ok, {FD2, Inode3, Size}};
+ Error ->
+ Error
+ end
+ end.
+
+%% returns localtime with milliseconds included
+localtime_ms() ->
+ {_, _, Micro} = Now = os:timestamp(),
+ {Date, {Hours, Minutes, Seconds}} = calendar:now_to_local_time(Now),
+ {Date, {Hours, Minutes, Seconds, Micro div 1000 rem 1000}}.
+
+maybe_utc({Date, {H, M, S, Ms}}) ->
+ case lager_stdlib:maybe_utc({Date, {H, M, S}}) of
+ {utc, {Date1, {H1, M1, S1}}} ->
+ {utc, {Date1, {H1, M1, S1, Ms}}};
+ {Date1, {H1, M1, S1}} ->
+ {Date1, {H1, M1, S1, Ms}}
+ end.
+
+rotate_logfile(File, 0) ->
+ file:delete(File);
+rotate_logfile(File, 1) ->
+ file:rename(File, File++".0"),
+ rotate_logfile(File, 0);
+rotate_logfile(File, Count) ->
+ file:rename(File ++ "." ++ integer_to_list(Count - 2), File ++ "." ++
+ integer_to_list(Count - 1)),
+ rotate_logfile(File, Count - 1).
+
+format_time() ->
+ format_time(maybe_utc(localtime_ms())).
+
+format_time({utc, {{Y, M, D}, {H, Mi, S, Ms}}}) ->
+ {io_lib:format("~b-~2..0b-~2..0b", [Y, M, D]),
+ io_lib:format("~2..0b:~2..0b:~2..0b.~3..0b UTC", [H, Mi, S, Ms])};
+format_time({{Y, M, D}, {H, Mi, S, Ms}}) ->
+ {io_lib:format("~b-~2..0b-~2..0b", [Y, M, D]),
+ io_lib:format("~2..0b:~2..0b:~2..0b.~3..0b", [H, Mi, S, Ms])};
+format_time({utc, {{Y, M, D}, {H, Mi, S}}}) ->
+ {io_lib:format("~b-~2..0b-~2..0b", [Y, M, D]),
+ io_lib:format("~2..0b:~2..0b:~2..0b UTC", [H, Mi, S])};
+format_time({{Y, M, D}, {H, Mi, S}}) ->
+ {io_lib:format("~b-~2..0b-~2..0b", [Y, M, D]),
+ io_lib:format("~2..0b:~2..0b:~2..0b", [H, Mi, S])}.
+
+parse_rotation_day_spec([], Res) ->
+ {ok, Res ++ [{hour, 0}]};
+parse_rotation_day_spec([$D, D1, D2], Res) ->
+ case list_to_integer([D1, D2]) of
+ X when X >= 0, X =< 23 ->
+ {ok, Res ++ [{hour, X}]};
+ _ ->
+ {error, invalid_date_spec}
+ end;
+parse_rotation_day_spec([$D, D], Res) when D >= $0, D =< $9 ->
+ {ok, Res ++ [{hour, D - 48}]};
+parse_rotation_day_spec(_, _) ->
+ {error, invalid_date_spec}.
+
+parse_rotation_date_spec([$$, $W, W|T]) when W >= $0, W =< $6 ->
+ Week = W - 48,
+ parse_rotation_day_spec(T, [{day, Week}]);
+parse_rotation_date_spec([$$, $M, L|T]) when L == $L; L == $l ->
+ %% last day in month.
+ parse_rotation_day_spec(T, [{date, last}]);
+parse_rotation_date_spec([$$, $M, M1, M2|[$D|_]=T]) ->
+ case list_to_integer([M1, M2]) of
+ X when X >= 1, X =< 31 ->
+ parse_rotation_day_spec(T, [{date, X}]);
+ _ ->
+ {error, invalid_date_spec}
+ end;
+parse_rotation_date_spec([$$, $M, M|[$D|_]=T]) ->
+ parse_rotation_day_spec(T, [{date, M - 48}]);
+parse_rotation_date_spec([$$, $M, M1, M2]) ->
+ case list_to_integer([M1, M2]) of
+ X when X >= 1, X =< 31 ->
+ {ok, [{date, X}, {hour, 0}]};
+ _ ->
+ {error, invalid_date_spec}
+ end;
+parse_rotation_date_spec([$$, $M, M]) ->
+ {ok, [{date, M - 48}, {hour, 0}]};
+parse_rotation_date_spec([$$|X]) when X /= [] ->
+ parse_rotation_day_spec(X, []);
+parse_rotation_date_spec(_) ->
+ {error, invalid_date_spec}.
+
+calculate_next_rotation(Spec) ->
+ Now = calendar:local_time(),
+ Later = calculate_next_rotation(Spec, Now),
+ calendar:datetime_to_gregorian_seconds(Later) -
+ calendar:datetime_to_gregorian_seconds(Now).
+
+calculate_next_rotation([], Now) ->
+ Now;
+calculate_next_rotation([{hour, X}|T], {{_, _, _}, {Hour, _, _}} = Now) when Hour < X ->
+ %% rotation is today, sometime
+ NewNow = setelement(2, Now, {X, 0, 0}),
+ calculate_next_rotation(T, NewNow);
+calculate_next_rotation([{hour, X}|T], {{_, _, _}, _} = Now) ->
+ %% rotation is not today
+ Seconds = calendar:datetime_to_gregorian_seconds(Now) + 86400,
+ DateTime = calendar:gregorian_seconds_to_datetime(Seconds),
+ NewNow = setelement(2, DateTime, {X, 0, 0}),
+ calculate_next_rotation(T, NewNow);
+calculate_next_rotation([{day, Day}|T], {Date, _Time} = Now) ->
+ DoW = calendar:day_of_the_week(Date),
+ AdjustedDay = case Day of
+ 0 -> 7;
+ X -> X
+ end,
+ case AdjustedDay of
+ DoW -> %% rotation is today
+ OldDate = element(1, Now),
+ case calculate_next_rotation(T, Now) of
+ {OldDate, _} = NewNow -> NewNow;
+ {NewDate, _} ->
+ %% rotation *isn't* today! rerun the calculation
+ NewNow = {NewDate, {0, 0, 0}},
+ calculate_next_rotation([{day, Day}|T], NewNow)
+ end;
+ Y when Y > DoW -> %% rotation is later this week
+ PlusDays = Y - DoW,
+ Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
+ {NewDate, _} = calendar:gregorian_seconds_to_datetime(Seconds),
+ NewNow = {NewDate, {0, 0, 0}},
+ calculate_next_rotation(T, NewNow);
+ Y when Y < DoW -> %% rotation is next week
+ PlusDays = ((7 - DoW) + Y),
+ Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
+ {NewDate, _} = calendar:gregorian_seconds_to_datetime(Seconds),
+ NewNow = {NewDate, {0, 0, 0}},
+ calculate_next_rotation(T, NewNow)
+ end;
+calculate_next_rotation([{date, last}|T], {{Year, Month, Day}, _} = Now) ->
+ Last = calendar:last_day_of_the_month(Year, Month),
+ case Last == Day of
+ true -> %% doing rotation today
+ OldDate = element(1, Now),
+ case calculate_next_rotation(T, Now) of
+ {OldDate, _} = NewNow -> NewNow;
+ {NewDate, _} ->
+ %% rotation *isn't* today! rerun the calculation
+ NewNow = {NewDate, {0, 0, 0}},
+ calculate_next_rotation([{date, last}|T], NewNow)
+ end;
+ false ->
+ NewNow = setelement(1, Now, {Year, Month, Last}),
+ calculate_next_rotation(T, NewNow)
+ end;
+calculate_next_rotation([{date, Date}|T], {{_, _, Date}, _} = Now) ->
+ %% rotation is today
+ OldDate = element(1, Now),
+ case calculate_next_rotation(T, Now) of
+ {OldDate, _} = NewNow -> NewNow;
+ {NewDate, _} ->
+ %% rotation *isn't* today! rerun the calculation
+ NewNow = setelement(1, Now, NewDate),
+ calculate_next_rotation([{date, Date}|T], NewNow)
+ end;
+calculate_next_rotation([{date, Date}|T], {{Year, Month, Day}, _} = Now) ->
+ PlusDays = case Date of
+ X when X < Day -> %% rotation is next month
+ Last = calendar:last_day_of_the_month(Year, Month),
+ (Last - Day);
+ X when X > Day -> %% rotation is later this month
+ X - Day
+ end,
+ Seconds = calendar:datetime_to_gregorian_seconds(Now) + (86400 * PlusDays),
+ NewNow = calendar:gregorian_seconds_to_datetime(Seconds),
+ calculate_next_rotation(T, NewNow).
+
+validate_trace({Filter, Level, {Destination, ID}}) when is_list(Filter), is_atom(Level), is_atom(Destination) ->
+ case validate_trace({Filter, Level, Destination}) of
+ {ok, {F, L, D}} ->
+ {ok, {F, L, {D, ID}}};
+ Error ->
+ Error
+ end;
+validate_trace({Filter, Level, Destination}) when is_list(Filter), is_atom(Level), is_atom(Destination) ->
+ try level_to_num(Level) of
+ L ->
+ case lists:all(fun({Key, _Value}) when is_atom(Key) -> true; (_) ->
+ false end, Filter) of
+ true ->
+ {ok, {Filter, L, Destination}};
+ _ ->
+ {error, invalid_filter}
+ end
+ catch
+ _:_ ->
+ {error, invalid_level}
+ end;
+validate_trace(_) ->
+ {error, invalid_trace}.
+
+
+check_f_traces(_, _, [], Acc) ->
+ lists:flatten(Acc);
+check_f_traces(AttrFun, Level, [{_, FilterLevel, _}|Flows], Acc)
+ when Level > FilterLevel ->
+ check_f_traces(AttrFun, Level, Flows, Acc);
+check_f_traces(AttrFun, Level, [Flow|Flows], Acc) ->
+ check_f_traces(AttrFun, Level, Flows, [check_f_trace(AttrFun, Flow)|Acc]).
+
+check_f_trace(AttrFun, {Filter, _Level, Dest}) ->
+ case lists:all(AttrFun, Filter) of
+ true -> Dest;
+ false -> []
+ end.
+
+
+check_traces(_, _, [], Acc) ->
+ lists:flatten(Acc);
+check_traces(Attrs, Level, [{_, FilterLevel, _}|Flows], Acc) when Level > FilterLevel ->
+ check_traces(Attrs, Level, Flows, Acc);
+check_traces(Attrs, Level, [{Filter, _, _}|Flows], Acc) when length(Attrs) < length(Filter) ->
+ check_traces(Attrs, Level, Flows, Acc);
+check_traces(Attrs, Level, [Flow|Flows], Acc) ->
+ check_traces(Attrs, Level, Flows, [check_trace(Attrs, Flow)|Acc]).
+
+check_trace(Attrs, {Filter, _Level, Dest}) ->
+ case check_trace_iter(Attrs, Filter) of
+ true ->
+ Dest;
+ false ->
+ []
+ end.
+
+check_trace_iter(_, []) ->
+ true;
+check_trace_iter(Attrs, [{Key, Match}|T]) ->
+ case lists:keyfind(Key, 1, Attrs) of
+ {Key, _} when Match == '*' ->
+ check_trace_iter(Attrs, T);
+ {Key, Match} ->
+ check_trace_iter(Attrs, T);
+ _ ->
+ false
+ end.
+
+-ifdef(TEST).
+
+parse_test() ->
+ ?assertEqual({ok, [{hour, 0}]}, parse_rotation_date_spec("$D0")),
+ ?assertEqual({ok, [{hour, 23}]}, parse_rotation_date_spec("$D23")),
+ ?assertEqual({ok, [{day, 0}, {hour, 23}]}, parse_rotation_date_spec("$W0D23")),
+ ?assertEqual({ok, [{day, 5}, {hour, 16}]}, parse_rotation_date_spec("$W5D16")),
+ ?assertEqual({ok, [{date, 1}, {hour, 0}]}, parse_rotation_date_spec("$M1D0")),
+ ?assertEqual({ok, [{date, 5}, {hour, 6}]}, parse_rotation_date_spec("$M5D6")),
+ ?assertEqual({ok, [{date, 5}, {hour, 0}]}, parse_rotation_date_spec("$M5")),
+ ?assertEqual({ok, [{date, 31}, {hour, 0}]}, parse_rotation_date_spec("$M31")),
+ ?assertEqual({ok, [{date, 31}, {hour, 1}]}, parse_rotation_date_spec("$M31D1")),
+ ?assertEqual({ok, [{date, last}, {hour, 0}]}, parse_rotation_date_spec("$ML")),
+ ?assertEqual({ok, [{date, last}, {hour, 0}]}, parse_rotation_date_spec("$Ml")),
+ ?assertEqual({ok, [{day, 5}, {hour, 0}]}, parse_rotation_date_spec("$W5")),
+ ok.
+
+parse_fail_test() ->
+ ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D")),
+ ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D24")),
+ ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$W7")),
+ ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$W7D1")),
+ ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M32")),
+ ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M32D1")),
+ ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$")),
+ ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("")),
+ ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$D15M5")),
+ ?assertEqual({error, invalid_date_spec}, parse_rotation_date_spec("$M5W5")),
+ ok.
+
+rotation_calculation_test() ->
+ ?assertMatch({{2000, 1, 2}, {0, 0, 0}},
+ calculate_next_rotation([{hour, 0}], {{2000, 1, 1}, {12, 34, 43}})),
+ ?assertMatch({{2000, 1, 1}, {16, 0, 0}},
+ calculate_next_rotation([{hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
+ ?assertMatch({{2000, 1, 2}, {12, 0, 0}},
+ calculate_next_rotation([{hour, 12}], {{2000, 1, 1}, {12, 34, 43}})),
+ ?assertMatch({{2000, 2, 1}, {12, 0, 0}},
+ calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 1}, {12, 34, 43}})),
+ ?assertMatch({{2000, 2, 1}, {12, 0, 0}},
+ calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 15}, {12, 34, 43}})),
+ ?assertMatch({{2000, 2, 1}, {12, 0, 0}},
+ calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 2}, {12, 34, 43}})),
+ ?assertMatch({{2000, 2, 1}, {12, 0, 0}},
+ calculate_next_rotation([{date, 1}, {hour, 12}], {{2000, 1, 31}, {12, 34, 43}})),
+ ?assertMatch({{2000, 1, 1}, {16, 0, 0}},
+ calculate_next_rotation([{date, 1}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
+ ?assertMatch({{2000, 1, 15}, {16, 0, 0}},
+ calculate_next_rotation([{date, 15}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
+ ?assertMatch({{2000, 1, 31}, {16, 0, 0}},
+ calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
+ ?assertMatch({{2000, 1, 31}, {16, 0, 0}},
+ calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 31}, {12, 34, 43}})),
+ ?assertMatch({{2000, 2, 29}, {16, 0, 0}},
+ calculate_next_rotation([{date, last}, {hour, 16}], {{2000, 1, 31}, {17, 34, 43}})),
+ ?assertMatch({{2001, 2, 28}, {16, 0, 0}},
+ calculate_next_rotation([{date, last}, {hour, 16}], {{2001, 1, 31}, {17, 34, 43}})),
+
+ ?assertMatch({{2000, 1, 1}, {16, 0, 0}},
+ calculate_next_rotation([{day, 6}, {hour, 16}], {{2000, 1, 1}, {12, 34, 43}})),
+ ?assertMatch({{2000, 1, 8}, {16, 0, 0}},
+ calculate_next_rotation([{day, 6}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
+ ?assertMatch({{2000, 1, 7}, {16, 0, 0}},
+ calculate_next_rotation([{day, 5}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
+ ?assertMatch({{2000, 1, 3}, {16, 0, 0}},
+ calculate_next_rotation([{day, 1}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
+ ?assertMatch({{2000, 1, 2}, {16, 0, 0}},
+ calculate_next_rotation([{day, 0}, {hour, 16}], {{2000, 1, 1}, {17, 34, 43}})),
+ ?assertMatch({{2000, 1, 9}, {16, 0, 0}},
+ calculate_next_rotation([{day, 0}, {hour, 16}], {{2000, 1, 2}, {17, 34, 43}})),
+ ?assertMatch({{2000, 2, 3}, {16, 0, 0}},
+ calculate_next_rotation([{day, 4}, {hour, 16}], {{2000, 1, 29}, {17, 34, 43}})),
+
+ ?assertMatch({{2000, 1, 7}, {16, 0, 0}},
+ calculate_next_rotation([{day, 5}, {hour, 16}], {{2000, 1, 3}, {17, 34, 43}})),
+
+ ?assertMatch({{2000, 1, 3}, {16, 0, 0}},
+ calculate_next_rotation([{day, 1}, {hour, 16}], {{1999, 12, 28}, {17, 34, 43}})),
+ ok.
+
+rotate_file_test() ->
+ file:delete("rotation.log"),
+ [file:delete(["rotation.log.", integer_to_list(N)]) || N <- lists:seq(0, 9)],
+ [begin
+ file:write_file("rotation.log", integer_to_list(N)),
+ Count = case N > 10 of
+ true -> 10;
+ _ -> N
+ end,
+ [begin
+ FileName = ["rotation.log.", integer_to_list(M)],
+ ?assert(filelib:is_regular(FileName)),
+ %% check the expected value is in the file
+ Number = list_to_binary(integer_to_list(N - M - 1)),
+ ?assertEqual({ok, Number}, file:read_file(FileName))
+ end
+ || M <- lists:seq(0, Count-1)],
+ rotate_logfile("rotation.log", 10)
+ end || N <- lists:seq(0, 20)].
+
+check_trace_test() ->
+ ?assertEqual([foo], check_traces([{module, ?MODULE}], 0, [{[{module, ?MODULE}],
+ 0, foo},
+ {[{module, test}], 0, bar}], [])),
+ ?assertEqual([], check_traces([{module, ?MODULE}], 0, [{[{module, ?MODULE},
+ {foo, bar}], 0, foo},
+ {[{module, test}], 0, bar}], [])),
+ ?assertEqual([bar], check_traces([{module, ?MODULE}], 0, [{[{module, ?MODULE},
+ {foo, bar}], 0, foo},
+ {[{module, '*'}], 0, bar}], [])),
+ ?assertEqual([bar], check_traces([{module, ?MODULE}], 0, [{[{module, '*'},
+ {foo, bar}], 0, foo},
+ {[{module, '*'}], 0, bar}], [])),
+ ?assertEqual([bar], check_traces([{module, ?MODULE}], 0, [{[{module, '*'},
+ {foo, '*'}], 0, foo},
+ {[{module, '*'}], 0, bar}], [])),
+ ?assertEqual([bar, foo], check_traces([{module, ?MODULE}, {foo, bar}], 0, [{[{module, '*'},
+ {foo, '*'}], 0, foo},
+ {[{module, '*'}], 0, bar}], [])),
+ ?assertEqual([], check_traces([{module, ?MODULE}, {foo, bar}], 6, [{[{module, '*'},
+ {foo, '*'}], 0, foo},
+ {[{module, '*'}], 0, bar}], [])),
+ ?assertEqual([foo], check_traces([{module, ?MODULE}, {foo, bar}], 6, [{[{module, '*'},
+ {foo, '*'}], 7, foo},
+ {[{module, '*'}], 0, bar}], [])),
+ ok.
+
+-endif.
diff --git a/deps/lager/test/crash.erl b/deps/lager/test/crash.erl
new file mode 100644
index 0000000..7037379
--- /dev/null
+++ b/deps/lager/test/crash.erl
@@ -0,0 +1,96 @@
+
+%% a module that crashes in just about every way possible
+
+-module(crash).
+
+-behaviour(gen_server).
+
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
+
+-export([start/0]).
+
+start() ->
+ gen_server:start({local, ?MODULE}, ?MODULE, [], []).
+
+init(_) ->
+ {ok, {}}.
+
+handle_call(undef, _, State) ->
+ {reply, ?MODULE:booger(), State};
+handle_call(badfun, _, State) ->
+ M = booger,
+ {reply, M(), State};
+handle_call(bad_return, _, _) ->
+ bleh;
+handle_call(bad_return_string, _, _) ->
+ {tuple, {tuple, "string"}};
+handle_call(case_clause, _, State) ->
+ case State of
+ goober ->
+ {reply, ok, State}
+ end;
+handle_call(case_clause_string, _, State) ->
+ Foo = atom_to_list(?MODULE),
+ case Foo of
+ State ->
+ {reply, ok, State}
+ end;
+handle_call(if_clause, _, State) ->
+ if State == 1 ->
+ {reply, ok, State}
+ end;
+handle_call(try_clause, _, State) ->
+ Res = try tuple_to_list(State) of
+ [_A, _B] -> ok
+ catch
+ _:_ -> ok
+ end,
+ {reply, Res, State};
+handle_call(badmatch, _, State) ->
+ {A, B, C} = State,
+ {reply, [A, B, C], State};
+handle_call(function_clause, _, State) ->
+ {reply, function(State), State};
+handle_call(badarith, _, State) ->
+ Res = 1 / length(tuple_to_list(State)),
+ {reply, Res, State};
+handle_call(badarg1, _, State) ->
+ Res = list_to_binary(["foo", bar]),
+ {reply, Res, State};
+handle_call(badarg2, _, State) ->
+ Res = erlang:iolist_to_binary(["foo", bar]),
+ {reply, Res, State};
+handle_call(system_limit, _, State) ->
+ Res = list_to_atom(lists:flatten(lists:duplicate(256, "a"))),
+ {reply, Res, State};
+handle_call(process_limit, _, State) ->
+ %% run with +P 300 to make this crash
+ [erlang:spawn(fun() -> timer:sleep(5000) end) || _ <- lists:seq(0, 500)],
+ {reply, ok, State};
+handle_call(port_limit, _, State) ->
+ [erlang:open_port({spawn, "ls"}, []) || _ <- lists:seq(0, 1024)],
+ {reply, ok, State};
+handle_call(noproc, _, State) ->
+ Res = gen_event:call(foo, bar, baz),
+ {reply, Res, State};
+handle_call(badarity, _, State) ->
+ F = fun(A, B, C) -> A + B + C end,
+ Res = F(State),
+ {reply, Res, State};
+handle_call(_Call, _From, State) ->
+ {reply, ok, State}.
+
+handle_cast(_Cast, State) ->
+ {noreply, State}.
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_, _) ->
+ ok.
+
+code_change(_, State, _) ->
+ {ok, State}.
+
+function(X) when is_list(X) ->
+ ok.
diff --git a/deps/lager/test/lager_crash_backend.erl b/deps/lager/test/lager_crash_backend.erl
new file mode 100644
index 0000000..b5981cb
--- /dev/null
+++ b/deps/lager/test/lager_crash_backend.erl
@@ -0,0 +1,68 @@
+%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you under the Apache License,
+%% Version 2.0 (the "License"); you may not use this file
+%% except in compliance with the License. You may obtain
+%% a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+
+-module(lager_crash_backend).
+
+-include("lager.hrl").
+
+-behaviour(gen_event).
+
+-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+init([CrashBefore, CrashAfter]) ->
+ case is_tuple(CrashBefore) andalso (timer:now_diff(CrashBefore, os:timestamp()) > 0) of
+ true ->
+ %?debugFmt("crashing!~n", []),
+ {error, crashed};
+ _ ->
+ %?debugFmt("Not crashing!~n", []),
+ case is_tuple(CrashAfter) of
+ true ->
+ CrashTime = timer:now_diff(CrashAfter, os:timestamp()) div 1000,
+ case CrashTime > 0 of
+ true ->
+ %?debugFmt("crashing in ~p~n", [CrashTime]),
+ erlang:send_after(CrashTime, self(), crash),
+ {ok, {}};
+ _ -> {error, crashed}
+ end;
+ _ ->
+ {ok, {}}
+ end
+ end.
+
+handle_call(_Request, State) ->
+ {ok, ok, State}.
+
+handle_event(_Event, State) ->
+ {ok, State}.
+
+handle_info(crash, _State) ->
+ %?debugFmt("Time to crash!~n", []),
+ crash;
+handle_info(_Info, State) ->
+ {ok, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
diff --git a/deps/lager/test/lager_test_backend.erl b/deps/lager/test/lager_test_backend.erl
new file mode 100644
index 0000000..b6bc71c
--- /dev/null
+++ b/deps/lager/test/lager_test_backend.erl
@@ -0,0 +1,735 @@
+%% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you under the Apache License,
+%% Version 2.0 (the "License"); you may not use this file
+%% except in compliance with the License. You may obtain
+%% a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+
+-module(lager_test_backend).
+
+-include("lager.hrl").
+
+-behaviour(gen_event).
+
+-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-record(state, {level, buffer, ignored}).
+-compile([{parse_transform, lager_transform}]).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-export([pop/0, count/0, count_ignored/0, flush/0]).
+-endif.
+
+init(Level) ->
+ {ok, #state{level=lager_util:level_to_num(Level), buffer=[], ignored=[]}}.
+
+handle_call(count, #state{buffer=Buffer} = State) ->
+ {ok, length(Buffer), State};
+handle_call(count_ignored, #state{ignored=Ignored} = State) ->
+ {ok, length(Ignored), State};
+handle_call(flush, State) ->
+ {ok, ok, State#state{buffer=[], ignored=[]}};
+handle_call(pop, #state{buffer=Buffer} = State) ->
+ case Buffer of
+ [] ->
+ {ok, undefined, State};
+ [H|T] ->
+ {ok, H, State#state{buffer=T}}
+ end;
+handle_call(get_loglevel, #state{level=Level} = State) ->
+ {ok, Level, State};
+handle_call({set_loglevel, Level}, State) ->
+ {ok, ok, State#state{level=lager_util:level_to_num(Level)}};
+handle_call(_Request, State) ->
+ {ok, ok, State}.
+
+handle_event({log, [?MODULE], Level, Time, Message}, #state{buffer=Buffer} = State) ->
+ {ok, State#state{buffer=Buffer ++ [{Level, Time, Message}]}};
+handle_event({log, Level, Time, Message}, #state{level=LogLevel,
+ buffer=Buffer} = State) when Level =< LogLevel ->
+ {ok, State#state{buffer=Buffer ++ [{Level, Time, Message}]}};
+handle_event({log, _Level, _Time, _Message}, #state{ignored=Ignored} = State) ->
+ {ok, State#state{ignored=Ignored ++ [ignored]}};
+handle_event(_Event, State) ->
+ {ok, State}.
+
+handle_info(_Info, State) ->
+ {ok, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+-ifdef(TEST).
+
+pop() ->
+ gen_event:call(lager_event, ?MODULE, pop).
+
+count() ->
+ gen_event:call(lager_event, ?MODULE, count).
+
+count_ignored() ->
+ gen_event:call(lager_event, ?MODULE, count_ignored).
+
+flush() ->
+ gen_event:call(lager_event, ?MODULE, flush).
+
+not_running_test() ->
+ ?assertEqual({error, lager_not_running}, lager:log(info, self(), "not running")).
+
+lager_test_() ->
+ {foreach,
+ fun setup/0,
+ fun cleanup/1,
+ [
+ {"observe that there is nothing up my sleeve",
+ fun() ->
+ ?assertEqual(undefined, pop()),
+ ?assertEqual(0, count())
+ end
+ },
+ {"logging works",
+ fun() ->
+ lager:warning("test message"),
+ ?assertEqual(1, count()),
+ {Level, _Time, Message} = pop(),
+ ?assertMatch(Level, lager_util:level_to_num(warning)),
+ [LevelStr, _LocStr, MsgStr] = re:split(Message, " ", [{return, list}, {parts, 3}]),
+ ?assertEqual("[warning]", LevelStr),
+ ?assertEqual("test message", MsgStr),
+ ok
+ end
+ },
+ {"logging with arguments works",
+ fun() ->
+ lager:warning("test message ~p", [self()]),
+ ?assertEqual(1, count()),
+ {Level, _Time, Message} = pop(),
+ ?assertMatch(Level, lager_util:level_to_num(warning)),
+ [LevelStr, _LocStr, MsgStr] = re:split(Message, " ", [{return, list}, {parts, 3}]),
+ ?assertEqual("[warning]", LevelStr),
+ ?assertEqual(lists:flatten(io_lib:format("test message ~p", [self()])), MsgStr),
+ ok
+ end
+ },
+ {"logging works from inside a begin/end block",
+ fun() ->
+ ?assertEqual(0, count()),
+ begin
+ lager:warning("test message 2")
+ end,
+ ?assertEqual(1, count()),
+ ok
+ end
+ },
+ {"logging works from inside a list comprehension",
+ fun() ->
+ ?assertEqual(0, count()),
+ [lager:warning("test message") || _N <- lists:seq(1, 10)],
+ ?assertEqual(10, count()),
+ ok
+ end
+ },
+ {"logging works from a begin/end block inside a list comprehension",
+ fun() ->
+ ?assertEqual(0, count()),
+ [ begin lager:warning("test message") end || _N <- lists:seq(1, 10)],
+ ?assertEqual(10, count()),
+ ok
+ end
+ },
+ {"logging works from a nested list comprehension",
+ fun() ->
+ ?assertEqual(0, count()),
+ [ [lager:warning("test message") || _N <- lists:seq(1, 10)] ||
+ _I <- lists:seq(1, 10)],
+ ?assertEqual(100, count()),
+ ok
+ end
+ },
+ {"log messages below the threshold are ignored",
+ fun() ->
+ ?assertEqual(0, count()),
+ lager:debug("this message will be ignored"),
+ ?assertEqual(0, count()),
+ ?assertEqual(0, count_ignored()),
+ lager_mochiglobal:put(loglevel, {?DEBUG, []}),
+ lager:debug("this message should be ignored"),
+ ?assertEqual(0, count()),
+ ?assertEqual(1, count_ignored()),
+ lager:set_loglevel(?MODULE, debug),
+ lager:debug("this message should be logged"),
+ ?assertEqual(1, count()),
+ ?assertEqual(1, count_ignored()),
+ ?assertEqual(debug, lager:get_loglevel(?MODULE)),
+ ok
+ end
+ },
+ {"tracing works",
+ fun() ->
+ lager_mochiglobal:put(loglevel, {?ERROR, []}),
+ ok = lager:info("hello world"),
+ ?assertEqual(0, count()),
+ lager_mochiglobal:put(loglevel, {?ERROR, [{[{module,
+ ?MODULE}], ?DEBUG, ?MODULE}]}),
+ ok = lager:info("hello world"),
+ ?assertEqual(1, count()),
+ ok
+ end
+ },
+ {"tracing works with custom attributes",
+ fun() ->
+ lager_mochiglobal:put(loglevel, {?ERROR, []}),
+ lager:info([{requestid, 6}], "hello world"),
+ ?assertEqual(0, count()),
+ lager_mochiglobal:put(loglevel, {?ERROR,
+ [{[{requestid, 6}], ?DEBUG, ?MODULE}]}),
+ lager:info([{requestid, 6}, {foo, bar}], "hello world"),
+ ?assertEqual(1, count()),
+ lager_mochiglobal:put(loglevel, {?ERROR,
+ [{[{requestid, '*'}], ?DEBUG, ?MODULE}]}),
+ lager:info([{requestid, 6}], "hello world"),
+ ?assertEqual(2, count()),
+ ok
+ end
+ },
+ {"tracing honors loglevel",
+ fun() ->
+ lager_mochiglobal:put(loglevel, {?ERROR, [{[{module,
+ ?MODULE}], ?NOTICE, ?MODULE}]}),
+ ok = lager:info("hello world"),
+ ?assertEqual(0, count()),
+ ok = lager:notice("hello world"),
+ ?assertEqual(1, count()),
+ ok
+ end
+ }
+ ]
+ }.
+
+setup() ->
+ error_logger:tty(false),
+ application:load(lager),
+ application:set_env(lager, handlers, [{?MODULE, info}]),
+ application:set_env(lager, error_logger_redirect, false),
+ application:start(compiler),
+ application:start(syntax_tools),
+ application:start(lager),
+ gen_event:call(lager_event, ?MODULE, flush).
+
+cleanup(_) ->
+ application:stop(lager),
+ error_logger:tty(true).
+
+
+crash(Type) ->
+ spawn(fun() -> gen_server:call(crash, Type) end),
+ timer:sleep(100),
+ _ = gen_event:which_handlers(error_logger),
+ ok.
+
+error_logger_redirect_crash_test_() ->
+ {foreach,
+ fun() ->
+ error_logger:tty(false),
+ application:load(lager),
+ application:set_env(lager, error_logger_redirect, true),
+ application:set_env(lager, handlers, [{?MODULE, error}]),
+ application:start(compiler),
+ application:start(syntax_tools),
+ application:start(lager),
+ crash:start()
+ end,
+
+ fun(_) ->
+ application:stop(lager),
+ case whereis(crash) of
+ undefined -> ok;
+ Pid -> exit(Pid, kill)
+ end,
+ error_logger:tty(true)
+ end,
+ [
+ {"again, there is nothing up my sleeve",
+ fun() ->
+ ?assertEqual(undefined, pop()),
+ ?assertEqual(0, count())
+ end
+ },
+ {"bad return value",
+ fun() ->
+ Pid = whereis(crash),
+ crash(bad_return),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: bad return value: bleh", [Pid])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"bad return value with string",
+ fun() ->
+ Pid = whereis(crash),
+ crash(bad_return_string),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: bad return value: {tuple,{tuple,\"string\"}}", [Pid])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"case clause",
+ fun() ->
+ Pid = whereis(crash),
+ crash(case_clause),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: no case clause matching {} in crash:handle_call/3", [Pid])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"case clause string",
+ fun() ->
+ Pid = whereis(crash),
+ crash(case_clause_string),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: no case clause matching \"crash\" in crash:handle_call/3", [Pid])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"function clause",
+ fun() ->
+ Pid = whereis(crash),
+ crash(function_clause),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: no function clause matching crash:function({})", [Pid])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"if clause",
+ fun() ->
+ Pid = whereis(crash),
+ crash(if_clause),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: no true branch found while evaluating if expression in crash:handle_call/3", [Pid])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"try clause",
+ fun() ->
+ Pid = whereis(crash),
+ crash(try_clause),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: no try clause matching [] in crash:handle_call/3", [Pid])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"undefined function",
+ fun() ->
+ Pid = whereis(crash),
+ crash(undef),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: call to undefined function crash:booger/0 from crash:handle_call/3", [Pid])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"bad math",
+ fun() ->
+ Pid = whereis(crash),
+ crash(badarith),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: bad arithmetic expression in crash:handle_call/3", [Pid])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"bad match",
+ fun() ->
+ Pid = whereis(crash),
+ crash(badmatch),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: no match of right hand value {} in crash:handle_call/3", [Pid])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"bad arity",
+ fun() ->
+ Pid = whereis(crash),
+ crash(badarity),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: fun called with wrong arity of 1 instead of 3 in crash:handle_call/3", [Pid])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"bad arg1",
+ fun() ->
+ Pid = whereis(crash),
+ crash(badarg1),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: bad argument in crash:handle_call/3", [Pid])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"bad arg2",
+ fun() ->
+ Pid = whereis(crash),
+ crash(badarg2),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: bad argument in call to erlang:iolist_to_binary([\"foo\",bar]) in crash:handle_call/3", [Pid])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"noproc",
+ fun() ->
+ Pid = whereis(crash),
+ crash(noproc),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: no such process or port in call to gen_event:call(foo, bar, baz)", [Pid])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"badfun",
+ fun() ->
+ Pid = whereis(crash),
+ crash(badfun),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: bad function booger in crash:handle_call/3", [Pid])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ }
+
+ ]
+ }.
+
+error_logger_redirect_test_() ->
+ {foreach,
+ fun() ->
+ error_logger:tty(false),
+ application:load(lager),
+ application:set_env(lager, error_logger_redirect, true),
+ application:set_env(lager, handlers, [{?MODULE, info}]),
+ application:start(lager),
+ lager:log(error, self(), "flush flush"),
+ timer:sleep(100),
+ gen_event:call(lager_event, ?MODULE, flush)
+ end,
+
+ fun(_) ->
+ application:stop(lager),
+ error_logger:tty(true)
+ end,
+ [
+ {"error reports are printed",
+ fun() ->
+ sync_error_logger:error_report([{this, is}, a, {silly, format}]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w this: is, a, silly: format", [self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"string error reports are printed",
+ fun() ->
+ sync_error_logger:error_report("this is less silly"),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w this is less silly", [self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"error messages are printed",
+ fun() ->
+ sync_error_logger:error_msg("doom, doom has come upon you all"),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w doom, doom has come upon you all", [self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"error messages are truncated at 4096 characters",
+ fun() ->
+ sync_error_logger:error_msg("doom, doom has come upon you all ~p", [string:copies("doom", 10000)]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ ?assert(length(lists:flatten(Msg)) < 5100)
+ end
+ },
+ {"info reports are printed",
+ fun() ->
+ sync_error_logger:info_report([{this, is}, a, {silly, format}]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[info] ~w this: is, a, silly: format", [self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"info reports are truncated at 4096 characters",
+ fun() ->
+ sync_error_logger:info_report([[{this, is}, a, {silly, format}] || _ <- lists:seq(0, 600)]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ ?assert(length(lists:flatten(Msg)) < 5000)
+ end
+ },
+ {"single term info reports are printed",
+ fun() ->
+ sync_error_logger:info_report({foolish, bees}),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[info] ~w {foolish,bees}", [self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"single term error reports are printed",
+ fun() ->
+ sync_error_logger:error_report({foolish, bees}),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w {foolish,bees}", [self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"string info reports are printed",
+ fun() ->
+ sync_error_logger:info_report("this is less silly"),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[info] ~w this is less silly", [self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"string info reports are truncated at 4096 characters",
+ fun() ->
+ sync_error_logger:info_report(string:copies("this is less silly", 1000)),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ ?assert(length(lists:flatten(Msg)) < 5100)
+ end
+ },
+ {"strings in a mixed report are printed as strings",
+ fun() ->
+ sync_error_logger:info_report(["this is less silly", {than, "this"}]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[info] ~w \"this is less silly\", than: \"this\"", [self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"info messages are printed",
+ fun() ->
+ sync_error_logger:info_msg("doom, doom has come upon you all"),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[info] ~w doom, doom has come upon you all", [self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"info messages are truncated at 4096 characters",
+ fun() ->
+ sync_error_logger:info_msg("doom, doom has come upon you all ~p", [string:copies("doom", 10000)]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ ?assert(length(lists:flatten(Msg)) < 5100)
+ end
+ },
+
+ {"warning messages are printed at the correct level",
+ fun() ->
+ sync_error_logger:warning_msg("doom, doom has come upon you all"),
+ Map = error_logger:warning_map(),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[~w] ~w doom, doom has come upon you all", [Map, self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"warning reports are printed at the correct level",
+ fun() ->
+ sync_error_logger:warning_report([{i, like}, pie]),
+ Map = error_logger:warning_map(),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[~w] ~w i: like, pie", [Map, self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"single term warning reports are printed at the correct level",
+ fun() ->
+ sync_error_logger:warning_report({foolish, bees}),
+ Map = error_logger:warning_map(),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[~w] ~w {foolish,bees}", [Map, self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"application stop reports",
+ fun() ->
+ sync_error_logger:info_report([{application, foo}, {exited, quittin_time}, {type, lazy}]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[info] ~w Application foo exited with reason: quittin_time", [self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"supervisor reports",
+ fun() ->
+ sync_error_logger:error_report(supervisor_report, [{errorContext, france}, {offender, [{name, mini_steve}, {mfargs, {a, b, [c]}}, {pid, bleh}]}, {reason, fired}, {supervisor, {local, steve}}]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w Supervisor steve had child mini_steve started with a:b(c) at bleh exit with reason fired in context france", [self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"supervisor reports with real error",
+ fun() ->
+ sync_error_logger:error_report(supervisor_report, [{errorContext, france}, {offender, [{name, mini_steve}, {mfargs, {a, b, [c]}}, {pid, bleh}]}, {reason, {function_clause,[{crash,handle_info,[foo]}]}}, {supervisor, {local, steve}}]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w Supervisor steve had child mini_steve started with a:b(c) at bleh exit with reason no function clause matching crash:handle_info(foo) in context france", [self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+
+ {"supervisor_bridge reports",
+ fun() ->
+ sync_error_logger:error_report(supervisor_report, [{errorContext, france}, {offender, [{mod, mini_steve}, {pid, bleh}]}, {reason, fired}, {supervisor, {local, steve}}]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w Supervisor steve had child at module mini_steve at bleh exit with reason fired in context france", [self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"application progress report",
+ fun() ->
+ sync_error_logger:info_report(progress, [{application, foo}, {started_at, node()}]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[info] ~w Application foo started on node ~w", [self(), node()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"supervisor progress report",
+ fun() ->
+ lager:set_loglevel(?MODULE, debug),
+ sync_error_logger:info_report(progress, [{supervisor, {local, foo}}, {started, [{mfargs, {foo, bar, 1}}, {pid, baz}]}]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[debug] ~w Supervisor foo started foo:bar/1 at pid baz", [self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"crash report for emfile",
+ fun() ->
+ sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, {emfile, [{stack, trace, 1}]}, []}}], []]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w CRASH REPORT Process ~w with 0 neighbours crashed with reason: maximum number of file descriptors exhausted, check ulimit -n", [self(), self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"crash report for system process limit",
+ fun() ->
+ sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, {system_limit, [{erlang, spawn, 1}]}, []}}], []]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of processes exceeded", [self(), self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"crash report for system process limit2",
+ fun() ->
+ sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, {system_limit, [{erlang, spawn_opt, 1}]}, []}}], []]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of processes exceeded", [self(), self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"crash report for system port limit",
+ fun() ->
+ sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, {system_limit, [{erlang, open_port, 1}]}, []}}], []]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of ports exceeded", [self(), self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"crash report for system port limit",
+ fun() ->
+ sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, {system_limit, [{erlang, list_to_atom, 1}]}, []}}], []]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: tried to create an atom larger than 255, or maximum atom count exceeded", [self(), self()])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"crash report for system ets table limit",
+ fun() ->
+ sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, test}, {error_info, {error, {system_limit,[{ets,new,[segment_offsets,[ordered_set,public]]},{mi_segment,open_write,1},{mi_buffer_converter,handle_cast,2},{gen_server,handle_msg,5},{proc_lib,init_p_do_apply,3}]}, []}}], []]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~w CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of ETS tables exceeded", [self(), test])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"crash report for unknown system limit should be truncated at 500 characters",
+ fun() ->
+ sync_error_logger:error_report(crash_report, [[{pid, self()}, {error_info, {error, {system_limit,[{wtf,boom,[string:copies("aaaa", 4096)]}]}, []}}], []]),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ ?assert(length(lists:flatten(Msg)) > 600),
+ ?assert(length(lists:flatten(Msg)) < 650)
+ end
+ },
+ {"crash reports for 'special processes' should be handled right",
+ fun() ->
+ {ok, Pid} = special_process:start(),
+ unlink(Pid),
+ Pid ! function_clause,
+ timer:sleep(500),
+ _ = gen_event:which_handlers(error_logger),
+ {_, _, Msg} = pop(),
+ Expected = lists:flatten(io_lib:format("[error] ~p CRASH REPORT Process ~p with 0 neighbours crashed with reason: no function clause matching special_process:foo(bar)",
+ [Pid, Pid])),
+ ?assertEqual(Expected, lists:flatten(Msg))
+ end
+ },
+ {"messages should not be generated if they don't satisfy the threshold",
+ fun() ->
+ lager:set_loglevel(?MODULE, error),
+ sync_error_logger:info_report([hello, world]),
+ _ = gen_event:which_handlers(error_logger),
+ ?assertEqual(0, count()),
+ ?assertEqual(0, count_ignored()),
+ lager:set_loglevel(?MODULE, info),
+ sync_error_logger:info_report([hello, world]),
+ _ = gen_event:which_handlers(error_logger),
+ ?assertEqual(1, count()),
+ ?assertEqual(0, count_ignored()),
+ lager:set_loglevel(?MODULE, error),
+ lager_mochiglobal:put(loglevel, {?DEBUG, []}),
+ sync_error_logger:info_report([hello, world]),
+ _ = gen_event:which_handlers(error_logger),
+ ?assertEqual(1, count()),
+ ?assertEqual(1, count_ignored())
+ end
+ }
+ ]
+ }.
+
+safe_format_test() ->
+ ?assertEqual("foo bar", lists:flatten(lager:safe_format("~p ~p", [foo, bar], 1024))),
+ ?assertEqual("FORMAT ERROR: \"~p ~p ~p\" [foo,bar]", lists:flatten(lager:safe_format("~p ~p ~p", [foo, bar], 1024))),
+ ok.
+
+-endif.
+
+
diff --git a/deps/lager/test/special_process.erl b/deps/lager/test/special_process.erl
new file mode 100644
index 0000000..831b950
--- /dev/null
+++ b/deps/lager/test/special_process.erl
@@ -0,0 +1,28 @@
+-module(special_process).
+-export([start/0, init/1]).
+
+start() ->
+ proc_lib:start_link(?MODULE, init, [self()]).
+
+init(Parent) ->
+ proc_lib:init_ack(Parent, {ok, self()}),
+ loop().
+
+loop() ->
+ receive
+ function_clause ->
+ foo(bar),
+ loop();
+ exit ->
+ exit(byebye),
+ loop();
+ error ->
+ erlang:error(mybad),
+ loop();
+ _ ->
+ loop()
+ end.
+
+foo(baz) ->
+ ok.
+
diff --git a/deps/lager/test/sync_error_logger.erl b/deps/lager/test/sync_error_logger.erl
new file mode 100644
index 0000000..078000f
--- /dev/null
+++ b/deps/lager/test/sync_error_logger.erl
@@ -0,0 +1,89 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(sync_error_logger).
+
+%% The error_logger API, but synchronous!
+%% This is helpful for tests, otherwise you need lots of nasty timer:sleep.
+%% Additionally, the warning map can be set on a per-process level, for
+%% convienience, via the process dictionary value `warning_map'.
+
+-export([
+ info_msg/1, info_msg/2,
+ warning_msg/1, warning_msg/2,
+ error_msg/1,error_msg/2
+ ]).
+
+-export([
+ info_report/1, info_report/2,
+ warning_report/1, warning_report/2,
+ error_report/1, error_report/2
+ ]).
+
+info_msg(Format) ->
+ info_msg(Format, []).
+
+info_msg(Format, Args) ->
+ gen_event:sync_notify(error_logger, {info_msg, group_leader(), {self(), Format, Args}}).
+
+warning_msg(Format) ->
+ warning_msg(Format, []).
+
+warning_msg(Format, Args) ->
+ gen_event:sync_notify(error_logger, {warning_msg_tag(), group_leader(), {self(), Format, Args}}).
+
+error_msg(Format) ->
+ error_msg(Format, []).
+
+error_msg(Format, Args) ->
+ gen_event:sync_notify(error_logger, {error, group_leader(), {self(), Format, Args}}).
+
+info_report(Report) ->
+ info_report(std_info, Report).
+
+info_report(Type, Report) ->
+ gen_event:sync_notify(error_logger, {info_report, group_leader(), {self(), Type, Report}}).
+
+warning_report(Report) ->
+ warning_report(std_warning, Report).
+
+warning_report(Type, Report) ->
+ {Tag, NType} = warning_report_tag(Type),
+ gen_event:sync_notify(error_logger, {Tag, group_leader(), {self(), NType, Report}}).
+
+error_report(Report) ->
+ error_report(std_error, Report).
+
+error_report(Type, Report) ->
+ gen_event:sync_notify(error_logger, {error_report, group_leader(), {self(), Type, Report}}).
+
+warning_msg_tag() ->
+ case get(warning_map) of
+ warning -> warning_msg;
+ info -> info_msg;
+ _ -> error
+ end.
+
+warning_report_tag(Type) ->
+ case {get(warning_map), Type == std_warning} of
+ {warning, _} -> {warning_report, Type};
+ {info, true} -> {info_report, std_info};
+ {info, false} -> {info_report, Type};
+ {_, true} -> {error_report, std_error};
+ {_, false} -> {error_report, Type}
+ end.
diff --git a/deps/lager/test/trunc_io_eqc.erl b/deps/lager/test/trunc_io_eqc.erl
new file mode 100644
index 0000000..b363640
--- /dev/null
+++ b/deps/lager/test/trunc_io_eqc.erl
@@ -0,0 +1,208 @@
+%% -------------------------------------------------------------------
+%%
+%% trunc_io_eqc: QuickCheck test for trunc_io:format with maxlen
+%%
+%% Copyright (c) 2007-2011 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you under the Apache License,
+%% Version 2.0 (the "License"); you may not use this file
+%% except in compliance with the License. You may obtain
+%% a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+%%
+%% -------------------------------------------------------------------
+-module(trunc_io_eqc).
+
+-ifdef(TEST).
+-ifdef(EQC).
+-export([test/0, test/1, check/0, prop_format/0]).
+
+-include_lib("eqc/include/eqc.hrl").
+-include_lib("eunit/include/eunit.hrl").
+
+-define(QC_OUT(P),
+ eqc:on_output(fun(Str, Args) -> io:format(user, Str, Args) end, P)).
+
+%%====================================================================
+%% eunit test
+%%====================================================================
+
+eqc_test_() ->
+ {timeout, 300,
+ {spawn,
+ [?_assertEqual(true, quickcheck(numtests(500, ?QC_OUT(prop_format()))))]
+ }}.
+
+%%====================================================================
+%% Shell helpers
+%%====================================================================
+
+test() ->
+ test(100).
+
+test(N) ->
+ quickcheck(numtests(N, prop_format())).
+
+check() ->
+ check(prop_format(), current_counterexample()).
+
+%%====================================================================
+%% Generators
+%%====================================================================
+
+gen_fmt_args() ->
+ list(oneof([gen_print_str(),
+ "~~",
+ {"~p", gen_any(5)},
+ {"~w", gen_any(5)},
+ {"~s", gen_print_str()},
+ {"~P", gen_any(5), 4},
+ {"~W", gen_any(5), 4},
+ {"~i", gen_any(5)},
+ {"~B", nat()},
+ {"~b", nat()},
+ {"~X", nat(), "0x"},
+ {"~x", nat(), "0x"},
+ {"~.10#", nat()},
+ {"~.10+", nat()},
+ {"~.36B", nat()},
+ {"~62P", gen_any(5), 4},
+ {"~c", gen_char()},
+ {"~tc", gen_char()},
+ {"~f", real()},
+ {"~10.f", real()},
+ {"~g", real()},
+ {"~10.g", real()},
+ {"~e", real()},
+ {"~10.e", real()}
+ ])).
+
+
+%% Generates a printable string
+gen_print_str() ->
+ ?LET(Xs, list(char()), [X || X <- Xs, io_lib:printable_list([X]), X /= $~]).
+
+gen_any(MaxDepth) ->
+ oneof([largeint(),
+ gen_atom(),
+ nat(),
+ %real(),
+ binary(),
+ gen_pid(),
+ gen_port(),
+ gen_ref(),
+ gen_fun()] ++
+ [?LAZY(list(gen_any(MaxDepth - 1))) || MaxDepth /= 0] ++
+ [?LAZY(gen_tuple(gen_any(MaxDepth - 1))) || MaxDepth /= 0]).
+
+gen_atom() ->
+ elements([abc, def, ghi]).
+
+gen_tuple(Gen) ->
+ ?LET(Xs, list(Gen), list_to_tuple(Xs)).
+
+gen_max_len() -> %% Generate length from 3 to whatever. Needs space for ... in output
+ ?LET(Xs, int(), 3 + abs(Xs)).
+
+gen_pid() ->
+ ?LAZY(spawn(fun() -> ok end)).
+
+gen_port() ->
+ ?LAZY(begin
+ Port = erlang:open_port({spawn, "true"}, []),
+ erlang:port_close(Port),
+ Port
+ end).
+
+gen_ref() ->
+ ?LAZY(make_ref()).
+
+gen_fun() ->
+ ?LAZY(fun() -> ok end).
+
+gen_char() ->
+ oneof(lists:seq($A, $z)).
+
+%%====================================================================
+%% Property
+%%====================================================================
+
+%% Checks that trunc_io:format produces output less than or equal to MaxLen
+prop_format() ->
+ ?FORALL({FmtArgs, MaxLen}, {gen_fmt_args(), gen_max_len()},
+ begin
+ %% Because trunc_io will print '...' when its running out of
+ %% space, even if the remaining space is less than 3, it
+ %% doesn't *exactly* stick to the specified limit.
+
+ %% Also, since we don't truncate terms not printed with
+ %% ~p/~P/~w/~W/~s, we also need to calculate the wiggle room
+ %% for those. Hence the fudge factor calculated below.
+ FudgeLen = calculate_fudge(FmtArgs, 50),
+ {FmtStr, Args} = build_fmt_args(FmtArgs),
+ try
+ Str = lists:flatten(lager_trunc_io:format(FmtStr, Args, MaxLen)),
+ ?WHENFAIL(begin
+ io:format(user, "FmtStr: ~p\n", [FmtStr]),
+ io:format(user, "Args: ~p\n", [Args]),
+ io:format(user, "FudgeLen: ~p\n", [FudgeLen]),
+ io:format(user, "MaxLen: ~p\n", [MaxLen]),
+ io:format(user, "ActLen: ~p\n", [length(Str)]),
+ io:format(user, "Str: ~p\n", [Str])
+ end,
+ %% Make sure the result is a printable list
+ %% and if the format string is less than the length,
+ %% the result string is less than the length.
+ conjunction([{printable, Str == "" orelse
+ io_lib:printable_list(Str)},
+ {length, length(FmtStr) > MaxLen orelse
+ length(Str) =< MaxLen + FudgeLen}]))
+ catch
+ _:Err ->
+ io:format(user, "\nException: ~p\n", [Err]),
+ io:format(user, "FmtStr: ~p\n", [FmtStr]),
+ io:format(user, "Args: ~p\n", [Args]),
+ false
+ end
+ end).
+
+%%====================================================================
+%% Internal helpers
+%%====================================================================
+
+%% Build a tuple of {Fmt, Args} from a gen_fmt_args() return
+build_fmt_args(FmtArgs) ->
+ F = fun({Fmt, Arg}, {FmtStr0, Args0}) ->
+ {FmtStr0 ++ Fmt, Args0 ++ [Arg]};
+ ({Fmt, Arg1, Arg2}, {FmtStr0, Args0}) ->
+ {FmtStr0 ++ Fmt, Args0 ++ [Arg1, Arg2]};
+ (Str, {FmtStr0, Args0}) ->
+ {FmtStr0 ++ Str, Args0}
+ end,
+ lists:foldl(F, {"", []}, FmtArgs).
+
+calculate_fudge([], Acc) ->
+ Acc;
+calculate_fudge([{"~62P", _Arg, _Depth}|T], Acc) ->
+ calculate_fudge(T, Acc+62);
+calculate_fudge([{Fmt, Arg}|T], Acc) when
+ Fmt == "~f"; Fmt == "~10.f";
+ Fmt == "~g"; Fmt == "~10.g";
+ Fmt == "~e"; Fmt == "~10.e";
+ Fmt == "~x"; Fmt == "~X";
+ Fmt == "~B"; Fmt == "~b"; Fmt == "~36B";
+ Fmt == "~.10#"; Fmt == "~10+" ->
+ calculate_fudge(T, Acc + length(lists:flatten(io_lib:format(Fmt, [Arg]))));
+calculate_fudge([_|T], Acc) ->
+ calculate_fudge(T, Acc).
+
+-endif. % (EQC).
+-endif. % (TEST).
diff --git a/deps/resource/Makefile b/deps/resource/Makefile
new file mode 100644
index 0000000..0622dff
--- /dev/null
+++ b/deps/resource/Makefile
@@ -0,0 +1,18 @@
+
+.PHONY: all compile clean eunit test doc
+
+all: compile
+
+compile:
+ rebar compile
+
+eunit: compile
+ rebar eunit
+
+test: eunit
+
+clean:
+ rebar clean
+
+doc:
+ rebar doc \ No newline at end of file
diff --git a/deps/resource/README b/deps/resource/README
new file mode 100644
index 0000000..cf97b43
--- /dev/null
+++ b/deps/resource/README
@@ -0,0 +1,13 @@
+Resource tracking
+=================
+
+resource:notify_when_destroyed(Pid, Message) -> NifRef.
+
+notify_when_destroyed creates a nif resource that can be used as a reference
+to any kind of external data. When resource is destroyed,
+beacuse it is not referenced any where, the destructor callback will
+send the message to Pid.
+
+For example, it could be used for wrapping
+file handles. The file is then closed automatically when there are no one
+referencing the file, thereby garbage collect files.
diff --git a/deps/resource/c_src/resource_nif.c b/deps/resource/c_src/resource_nif.c
new file mode 100644
index 0000000..dc2ff6b
--- /dev/null
+++ b/deps/resource/c_src/resource_nif.c
@@ -0,0 +1,155 @@
+/****** BEGIN COPYRIGHT *******************************************************
+ *
+ * Copyright (C) 2012 Feuerlabs, Inc. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ ****** END COPYRIGHT ********************************************************/
+//
+// Resource nif
+//
+
+#include <stdint.h>
+#include "erl_nif.h"
+
+// #define DEBUG
+
+#ifdef DEBUG
+#include <stdio.h>
+#define DBG(...) printf(__VA_ARGS__)
+#else
+#define DBG(...)
+#endif
+
+// Atom macros
+#define ATOM(name) atm_##name
+
+#define DECL_ATOM(name) \
+ ERL_NIF_TERM atm_##name = 0
+
+// require env in context (ugly)
+#define LOAD_ATOM(name) \
+ atm_##name = enif_make_atom(env,#name)
+
+#define LOAD_ATOM_STRING(name,string) \
+ atm_##name = enif_make_atom(env,string)
+
+// Type names
+DECL_ATOM(resource);
+
+static int res_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info);
+static int res_reload(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info);
+static int res_upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data,
+ ERL_NIF_TERM load_info);
+static void res_unload(ErlNifEnv* env, void* priv_data);
+
+ErlNifResourceType* res_resource;
+
+static ERL_NIF_TERM notify_when_destroyed(ErlNifEnv* env, int argc,
+ const ERL_NIF_TERM argv[]);
+
+ErlNifFunc res_funcs[] =
+{
+ { "notify_when_destroyed", 2, notify_when_destroyed },
+};
+
+typedef struct {
+ ErlNifEnv* env;
+ ErlNifPid pid;
+ ERL_NIF_TERM message;
+} res_object_t;
+
+
+static void res_dtor(ErlNifEnv* env, res_object_t* obj)
+{
+ // mabe add a timestamp os:timestamp() when event was sent!?
+ enif_send(0, &obj->pid, obj->env, obj->message);
+ enif_free_env(obj->env);
+}
+
+//
+// resource:notify_when_destroyed(Pid, Message)
+// send a message to Pid when reource is destroyed
+//
+ERL_NIF_TERM notify_when_destroyed(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ res_object_t* obj;
+ ERL_NIF_TERM r;
+
+ if (!enif_is_pid(env, argv[0]))
+ return enif_make_badarg(env);
+
+ if (!(obj = enif_alloc_resource(res_resource, sizeof(res_object_t))))
+ return enif_make_badarg(env);
+
+ if (!(obj->env = enif_alloc_env())) {
+ enif_release_resource(obj);
+ return enif_make_badarg(env);
+ }
+
+ if (!enif_get_local_pid(env, argv[0], &obj->pid)) {
+ enif_release_resource(obj);
+ return enif_make_badarg(env);
+ }
+
+ obj->message = enif_make_copy(obj->env, argv[1]);
+
+ r = enif_make_tuple3(env, ATOM(resource),
+ enif_make_ulong(env, (unsigned long) obj),
+ enif_make_resource(env, obj));
+ enif_release_resource(obj);
+ return r;
+}
+
+static int res_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
+{
+ (void) load_info;
+ ErlNifResourceFlags tried;
+
+ DBG("res_load\r\n");
+
+ LOAD_ATOM(resource);
+
+ res_resource = enif_open_resource_type(env, 0, "resource",
+ (ErlNifResourceDtor*) res_dtor,
+ ERL_NIF_RT_CREATE,
+ &tried);
+
+
+ *priv_data = 0;
+ return 0;
+}
+
+static int res_reload(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
+{
+ (void) env;
+ (void) load_info;
+ DBG("res_reload\r\n");
+ return 0;
+}
+
+static int res_upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data,
+ ERL_NIF_TERM load_info)
+{
+ (void) env;
+ (void) load_info;
+ DBG("res_upgrade\r\n");
+ *priv_data = *old_priv_data;
+ return 0;
+}
+
+static void res_unload(ErlNifEnv* env, void* priv_data)
+{
+ (void) env;
+ DBG("res_unload\r\n");
+}
+
+ERL_NIF_INIT(resource, res_funcs,
+ res_load, res_reload,
+ res_upgrade, res_unload)
+
+
+
+
diff --git a/deps/resource/rebar.config b/deps/resource/rebar.config
new file mode 100644
index 0000000..16943c0
--- /dev/null
+++ b/deps/resource/rebar.config
@@ -0,0 +1,9 @@
+%% -*- erlang -*-
+{port_specs, [
+ {"priv/resource_nif.so",["c_src/*.c"]}
+ ]}.
+
+
+{edoc_opts, [{doclet, edown_doclet},
+ {src_path, ["src/"]},
+ {subpackages, true}]}. \ No newline at end of file
diff --git a/deps/resource/src/resource.app.src b/deps/resource/src/resource.app.src
new file mode 100644
index 0000000..7db9a87
--- /dev/null
+++ b/deps/resource/src/resource.app.src
@@ -0,0 +1,11 @@
+{application, resource,
+ [
+ {description, ""},
+ {vsn, git},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]},
+ {env, []}
+ ]}.
diff --git a/deps/resource/src/resource.erl b/deps/resource/src/resource.erl
new file mode 100644
index 0000000..2f712b5
--- /dev/null
+++ b/deps/resource/src/resource.erl
@@ -0,0 +1,29 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved.
+%%%
+%%% This Source Code Form is subject to the terms of the Mozilla Public
+%%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @doc
+%%% Resource event
+%%% @end
+%%% Created : 2 Jan 2012 by Tony Rogvall <tony@rogvall.se>
+
+-module(resource).
+
+-export([notify_when_destroyed/2]).
+-on_load(init/0).
+
+init() ->
+ Nif = filename:join(code:priv_dir(resource), "resource_nif"),
+ erlang:load_nif(Nif, 0).
+
+
+notify_when_destroyed(_Pid,_Message) ->
+ erlang:error(nif_not_loaded).
+
+
diff --git a/deps/setup/LICENSE b/deps/setup/LICENSE
new file mode 100644
index 0000000..14e2f77
--- /dev/null
+++ b/deps/setup/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/deps/setup/Makefile b/deps/setup/Makefile
new file mode 100644
index 0000000..77c07d5
--- /dev/null
+++ b/deps/setup/Makefile
@@ -0,0 +1,51 @@
+.PHONY: doc compile test compile_test clean_test run_test escriptize deps
+
+REBAR ?= $(shell which rebar || echo ./rebar)
+
+TESTDIRS= xtest/testapp-1 xtest/testapp-2
+
+SETUP_PLT = setup.plt
+DIALYZER_OPTS = # -Wunderspecs
+DIALYZER_APPS = erts kernel stdlib sasl
+
+all: compile
+
+compile: deps
+ ${REBAR} compile
+
+deps:
+ ${REBAR} get-deps
+
+doc:
+ ${REBAR} doc
+
+compile_test:
+ for D in $(TESTDIRS) ; do \
+ (cd $$D; ${REBAR} compile) ; \
+ done
+
+clean_test:
+ for D in $(TESTDIRS) ; do \
+ (cd $$D; ${REBAR} clean) ; \
+ done
+ rm -r xtest/releases
+
+test: compile compile_test
+ ./setup_gen test xtest/test.conf xtest/releases/1
+
+run_test:
+ erl -boot xtest/releases/1/start -config xtest/releases/1/sys
+
+escriptize:
+ ${REBAR} skip_deps=true escriptize
+
+$(SETUP_PLT):
+ rebar get-deps compile
+ ERL_LIBS=deps dialyzer --build_plt --output_plt $(SETUP_PLT) \
+ --apps $(DIALYZER_APPS)
+
+clean_plt:
+ rm -f $(SETUP_PLT)
+
+dialyzer: deps compile $(SETUP_PLT)
+ dialyzer -r ebin --plt $(SETUP_PLT) $(DIALYZER_OPTS)
diff --git a/deps/setup/README.md b/deps/setup/README.md
new file mode 100644
index 0000000..fd51708
--- /dev/null
+++ b/deps/setup/README.md
@@ -0,0 +1,109 @@
+
+
+# The setup application #
+
+__Authors:__ Ulf Wiger ([`ulf@wiger.net`](mailto:ulf@wiger.net)).
+Generic setup utility for Erlang-based systems
+
+
+## Introduction ##
+
+While Erlang/OTP comes with many wonderful applications, including the
+Mnesia DBMS, there is no standard or convention for installing a
+system. Erlang/OTP provides tools for building a boot script, and rules
+for setting environment variables, etc., and Mnesia offers an API for
+creating and modifying the database schema.
+
+However, with no convention for when these tools and API functions
+are called - and by whom - application developers are left having to
+invent a lot of code and scripts, not to mention meditate over chapters
+of Erlang/OTP documentation in order to figure out how things fit
+together.
+
+This utility offers a framework for initializing and configuring a
+system, with a set of conventions allowing each component to provide
+callbacks for different steps in the installation procedure.
+
+The callbacks are defined through OTP application environment variables,
+which can easily be overriden at install time.
+
+
+## The setup_gen utility ##
+
+The `setup_gen` utility is a simple tool to generate .rel file and
+boot script for an Erlang-based system. It fetches configuration options
+from a .conf file (read using `file:script/2`). As an example of a very
+simple build, see examples/gproc.conf':
+
+```
+
+[{apps, [kernel,
+ stdlib,
+ sasl,
+ gproc]}].
+
+```
+
+This configuration file simply lists the applications to start from the
+boot script. The `setup_gen` script can either be called from within
+Erlang as:
+
+```
+
+Eshell V5.8.1 (abort with ^G)
+1> setup_gen:run([{conf,"gproc.conf"},{outdir,"."},{name,"gproc"}]).
+Options = [{conf,"gproc.conf"},{outdir,"."},{name,"gproc"}]
+Paths = []
+add path Res = ok
+app_vsn(kernel) -> "2.14.1"
+app_vsn(stdlib) -> "1.17.1"
+app_vsn(sasl) -> "2.1.9.2"
+app_vsn(gproc) -> "0.01"
+Rel: {release,{"gproc","tmp"},
+ {erts,"5.8.1"},
+ [{kernel,"2.14.1"},
+ {stdlib,"1.17.1"},
+ {sasl,"2.1.9.2"},
+ {gproc,"0.01"}]}
+entering directory .
+Path = []
+make_script() -> ok
+ok
+
+```
+
+...or as an escript:
+
+```
+
+escript ~/git/setup/ebin/setup_gen.beam gproc gproc.conf .
+
+```
+
+If the option `-install true` is given, the `setup_gen` utility will
+generate an installation boot script, and `install.config` file, which
+can be used to install the system, using a command like:
+
+```
+
+erl -sys install -boot install
+
+```
+
+This boot script will run kernel, stdlib and sasl, then load all other
+applications, and finally run the `setup` application, which will find
+and execute any setup hooks.
+If the option `-setup stop_when_done true` is added to the command line,
+the setup application will automatically shut down all running nodes after
+running the setup hooks. Otherwise (default), it will hand over control to
+the shell rather than terminate the Erlang VM.
+
+
+## Modules ##
+
+
+<table width="100%" border="0" summary="list of modules">
+<tr><td><a href="http://github.com/uwiger/setup/blob/master/doc/setup.md" class="module">setup</a></td></tr>
+<tr><td><a href="http://github.com/uwiger/setup/blob/master/doc/setup_gen.md" class="module">setup_gen</a></td></tr>
+<tr><td><a href="http://github.com/uwiger/setup/blob/master/doc/setup_lib.md" class="module">setup_lib</a></td></tr></table>
+
diff --git a/deps/setup/doc/README.md b/deps/setup/doc/README.md
new file mode 100644
index 0000000..305520e
--- /dev/null
+++ b/deps/setup/doc/README.md
@@ -0,0 +1,109 @@
+
+
+# The setup application #
+
+__Authors:__ Ulf Wiger ([`ulf@wiger.net`](mailto:ulf@wiger.net)).
+Generic setup utility for Erlang-based systems
+
+
+## Introduction ##
+
+While Erlang/OTP comes with many wonderful applications, including the
+Mnesia DBMS, there is no standard or convention for installing a
+system. Erlang/OTP provides tools for building a boot script, and rules
+for setting environment variables, etc., and Mnesia offers an API for
+creating and modifying the database schema.
+
+However, with no convention for when these tools and API functions
+are called - and by whom - application developers are left having to
+invent a lot of code and scripts, not to mention meditate over chapters
+of Erlang/OTP documentation in order to figure out how things fit
+together.
+
+This utility offers a framework for initializing and configuring a
+system, with a set of conventions allowing each component to provide
+callbacks for different steps in the installation procedure.
+
+The callbacks are defined through OTP application environment variables,
+which can easily be overriden at install time.
+
+
+## The setup_gen utility ##
+
+The `setup_gen` utility is a simple tool to generate .rel file and
+boot script for an Erlang-based system. It fetches configuration options
+from a .conf file (read using `file:script/2`). As an example of a very
+simple build, see examples/gproc.conf':
+
+```
+
+[{apps, [kernel,
+ stdlib,
+ sasl,
+ gproc]}].
+
+```
+
+This configuration file simply lists the applications to start from the
+boot script. The `setup_gen` script can either be called from within
+Erlang as:
+
+```
+
+Eshell V5.8.1 (abort with ^G)
+1> setup_gen:run([{conf,"gproc.conf"},{outdir,"."},{name,"gproc"}]).
+Options = [{conf,"gproc.conf"},{outdir,"."},{name,"gproc"}]
+Paths = []
+add path Res = ok
+app_vsn(kernel) -> "2.14.1"
+app_vsn(stdlib) -> "1.17.1"
+app_vsn(sasl) -> "2.1.9.2"
+app_vsn(gproc) -> "0.01"
+Rel: {release,{"gproc","tmp"},
+ {erts,"5.8.1"},
+ [{kernel,"2.14.1"},
+ {stdlib,"1.17.1"},
+ {sasl,"2.1.9.2"},
+ {gproc,"0.01"}]}
+entering directory .
+Path = []
+make_script() -> ok
+ok
+
+```
+
+...or as an escript:
+
+```
+
+escript ~/git/setup/ebin/setup_gen.beam gproc gproc.conf .
+
+```
+
+If the option `-install true` is given, the `setup_gen` utility will
+generate an installation boot script, and `install.config` file, which
+can be used to install the system, using a command like:
+
+```
+
+erl -sys install -boot install
+
+```
+
+This boot script will run kernel, stdlib and sasl, then load all other
+applications, and finally run the `setup` application, which will find
+and execute any setup hooks.
+If the option `-setup stop_when_done true` is added to the command line,
+the setup application will automatically shut down all running nodes after
+running the setup hooks. Otherwise (default), it will hand over control to
+the shell rather than terminate the Erlang VM.
+
+
+## Modules ##
+
+
+<table width="100%" border="0" summary="list of modules">
+<tr><td><a href="setup.md" class="module">setup</a></td></tr>
+<tr><td><a href="setup_gen.md" class="module">setup_gen</a></td></tr>
+<tr><td><a href="setup_lib.md" class="module">setup_lib</a></td></tr></table>
+
diff --git a/deps/setup/doc/edoc-info b/deps/setup/doc/edoc-info
new file mode 100644
index 0000000..45fac83
--- /dev/null
+++ b/deps/setup/doc/edoc-info
@@ -0,0 +1,4 @@
+%% encoding: UTF-8
+{application,setup}.
+{packages,[]}.
+{modules,[setup,setup_gen,setup_lib]}.
diff --git a/deps/setup/doc/erlang.png b/deps/setup/doc/erlang.png
new file mode 100644
index 0000000..987a618
--- /dev/null
+++ b/deps/setup/doc/erlang.png
Binary files differ
diff --git a/deps/setup/doc/overview.edoc b/deps/setup/doc/overview.edoc
new file mode 100644
index 0000000..2fc993e
--- /dev/null
+++ b/deps/setup/doc/overview.edoc
@@ -0,0 +1,88 @@
+@author Ulf Wiger <ulf@wiger.net>
+
+@doc Generic setup utility for Erlang-based systems
+<h2>Introduction</h2>
+
+While Erlang/OTP comes with many wonderful applications, including the
+Mnesia DBMS, there is no standard or convention for installing a
+system. Erlang/OTP provides tools for building a boot script, and rules
+for setting environment variables, etc., and Mnesia offers an API for
+creating and modifying the database schema.
+
+However, with no convention for when these tools and API functions
+are called - and by whom - application developers are left having to
+invent a lot of code and scripts, not to mention meditate over chapters
+of Erlang/OTP documentation in order to figure out how things fit
+together.
+
+This utility offers a framework for initializing and configuring a
+system, with a set of conventions allowing each component to provide
+callbacks for different steps in the installation procedure.
+
+The callbacks are defined through OTP application environment variables,
+which can easily be overriden at install time.
+
+<h2>The setup_gen utility</h2>
+
+The `setup_gen' utility is a simple tool to generate .rel file and
+boot script for an Erlang-based system. It fetches configuration options
+from a .conf file (read using `file:script/2'). As an example of a very
+simple build, see examples/gproc.conf':
+
+<pre>
+[{apps, [kernel,
+ stdlib,
+ sasl,
+ gproc]}].
+</pre>
+
+This configuration file simply lists the applications to start from the
+boot script. The `setup_gen' script can either be called from within
+Erlang as:
+
+<pre>
+Eshell V5.8.1 (abort with ^G)
+1> setup_gen:run([{conf,"gproc.conf"},{outdir,"."},{name,"gproc"}]).
+Options = [{conf,"gproc.conf"},{outdir,"."},{name,"gproc"}]
+Paths = []
+add path Res = ok
+app_vsn(kernel) -> "2.14.1"
+app_vsn(stdlib) -> "1.17.1"
+app_vsn(sasl) -> "2.1.9.2"
+app_vsn(gproc) -> "0.01"
+Rel: {release,{"gproc","tmp"},
+ {erts,"5.8.1"},
+ [{kernel,"2.14.1"},
+ {stdlib,"1.17.1"},
+ {sasl,"2.1.9.2"},
+ {gproc,"0.01"}]}
+entering directory .
+Path = []
+make_script() -> ok
+ok
+</pre>
+
+...or as an escript:
+
+<pre>
+escript ~/git/setup/ebin/setup_gen.beam gproc gproc.conf .
+</pre>
+
+If the option `-install true' is given, the `setup_gen' utility will
+generate an installation boot script, and `install.config' file, which
+can be used to install the system, using a command like:
+
+<pre>
+erl -sys install -boot install
+</pre>
+
+This boot script will run kernel, stdlib and sasl, then load all other
+applications, and finally run the `setup' application, which will find
+and execute any setup hooks.
+
+If the option `-setup stop_when_done true' is added to the command line,
+the setup application will automatically shut down all running nodes after
+running the setup hooks. Otherwise (default), it will hand over control to
+the shell rather than terminate the Erlang VM.
+
+@end \ No newline at end of file
diff --git a/deps/setup/doc/setup.md b/deps/setup/doc/setup.md
new file mode 100644
index 0000000..df22af0
--- /dev/null
+++ b/deps/setup/doc/setup.md
@@ -0,0 +1,434 @@
+
+
+# Module setup #
+* [Description](#description)
+* [Function Index](#index)
+* [Function Details](#functions)
+
+
+Setup utility for erlang applications.
+__Behaviours:__ [`application`](application.md).
+<a name="index"></a>
+
+## Function Index ##
+
+
+<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#data_dir-0">data_dir/0</a></td><td>Returns the configured data dir, or a best guess (<code>home()/data.Node</code>).</td></tr><tr><td valign="top"><a href="#expand_value-2">expand_value/2</a></td><td></td></tr><tr><td valign="top"><a href="#find_app-1">find_app/1</a></td><td>Equivalent to <a href="#find_app-2"><tt>find_app(A, lib_dirs())</tt></a>.</td></tr><tr><td valign="top"><a href="#find_app-2">find_app/2</a></td><td>Locates application <code>A</code> along LibDirs (see <a href="#lib_dirs-0"><code>lib_dirs/0</code></a> and
+<a href="#lib_dirs-1"><code>lib_dirs/1</code></a>) or under the OTP root, returning all found candidates.</td></tr><tr><td valign="top"><a href="#find_env_vars-1">find_env_vars/1</a></td><td>Searches all loaded apps for instances of the <code>Env</code> environment variable.</td></tr><tr><td valign="top"><a href="#find_hooks-0">find_hooks/0</a></td><td>Finds all custom setup hooks in all applications.</td></tr><tr><td valign="top"><a href="#find_hooks-1">find_hooks/1</a></td><td>Find all setup hooks for <code>Mode</code> in all applications.</td></tr><tr><td valign="top"><a href="#find_hooks-2">find_hooks/2</a></td><td>Find all setup hooks for <code>Mode</code> in <code>Applications</code>.</td></tr><tr><td valign="top"><a href="#get_env-2">get_env/2</a></td><td></td></tr><tr><td valign="top"><a href="#home-0">home/0</a></td><td>Returns the configured <code>home</code> directory, or a best guess (<code>$CWD</code>).</td></tr><tr><td valign="top"><a href="#lib_dirs-0">lib_dirs/0</a></td><td>Equivalent to <a href="#lib_dirs-1"><tt>lib_dirs(concat("ERL_SETUP_LIBS", "ERL_LIBS"))</tt></a>.</td></tr><tr><td valign="top"><a href="#lib_dirs-1">lib_dirs/1</a></td><td>Returns an expanded list of application directories under a lib path.</td></tr><tr><td valign="top"><a href="#log_dir-0">log_dir/0</a></td><td>Returns the configured log dir, or a best guess (<code>home()/log.Node</code>).</td></tr><tr><td valign="top"><a href="#ok-1">ok/1</a></td><td></td></tr><tr><td valign="top"><a href="#patch_app-1">patch_app/1</a></td><td>Adds an application's "development" path to a target system.</td></tr><tr><td valign="top"><a href="#pick_vsn-3">pick_vsn/3</a></td><td>Picks the specified version out of a list returned by <a href="#find_app-1"><code>find_app/1</code></a></td></tr><tr><td valign="top"><a href="#read_config_script-3">read_config_script/3</a></td><td></td></tr><tr><td valign="top"><a href="#reload_app-1">reload_app/1</a></td><td>Equivalent to <a href="#reload_app-2"><tt>reload_app(AppName, latest)</tt></a>.</td></tr><tr><td valign="top"><a href="#reload_app-2">reload_app/2</a></td><td>Equivalent to <a href="#reload_app-3"><tt>reload_app(AppName, latest, lib_dirs())</tt></a>.</td></tr><tr><td valign="top"><a href="#reload_app-3">reload_app/3</a></td><td>Loads or upgrades an application to the specified version.</td></tr><tr><td valign="top"><a href="#run_hooks-0">run_hooks/0</a></td><td>Execute all setup hooks for current mode in order.</td></tr><tr><td valign="top"><a href="#run_hooks-1">run_hooks/1</a></td><td>Execute setup hooks for current mode in <code>Applications</code> in order.</td></tr><tr><td valign="top"><a href="#run_hooks-2">run_hooks/2</a></td><td>Execute setup hooks for <code>Mode</code> in <code>Applications</code> in order.</td></tr><tr><td valign="top"><a href="#start-2">start/2</a></td><td>Application start function.</td></tr><tr><td valign="top"><a href="#stop-1">stop/1</a></td><td>Application stop function
+end.</td></tr><tr><td valign="top"><a href="#verify_dir-1">verify_dir/1</a></td><td>Ensures that the directory Dir exists and is writable.</td></tr><tr><td valign="top"><a href="#verify_directories-0">verify_directories/0</a></td><td>Ensures that essential directories exist and are writable.</td></tr></table>
+
+
+<a name="functions"></a>
+
+## Function Details ##
+
+<a name="data_dir-0"></a>
+
+### data_dir/0 ###
+
+
+<pre><code>
+data_dir() -&gt; Directory
+</code></pre>
+<br />
+
+Returns the configured data dir, or a best guess (`home()/data.Node`).
+
+<a name="expand_value-2"></a>
+
+### expand_value/2 ###
+
+`expand_value(App, Value) -> any()`
+
+
+<a name="find_app-1"></a>
+
+### find_app/1 ###
+
+
+<pre><code>
+find_app(A::atom()) -&gt; [{Vsn, Dir}]
+</code></pre>
+<br />
+
+Equivalent to [`find_app(A, lib_dirs())`](#find_app-2).
+<a name="find_app-2"></a>
+
+### find_app/2 ###
+
+
+<pre><code>
+find_app(A::atom(), LibDirs::[string()]) -&gt; [{Vsn, Dir}]
+</code></pre>
+<br />
+
+Locates application `A` along LibDirs (see [`lib_dirs/0`](#lib_dirs-0) and
+[`lib_dirs/1`](#lib_dirs-1)) or under the OTP root, returning all found candidates.
+The version is extracted from the `.app` file; thus, no version suffix
+in the path name is required.
+<a name="find_env_vars-1"></a>
+
+### find_env_vars/1 ###
+
+
+<pre><code>
+find_env_vars(Env) -&gt; [{AppName, Value}]
+</code></pre>
+<br />
+
+
+Searches all loaded apps for instances of the `Env` environment variable.
+
+
+The environment variables may contain instances of
+`$APP`, `$PRIV_DIR`, `$LIB_DIR`, `$DATA_DIR`, `$LOG_DIR`, `$HOME`,
+inside strings or binaries, and these will be replaced with actual values
+for the current system (`$APP` simply expands to the name of the current
+application).
+<a name="find_hooks-0"></a>
+
+### find_hooks/0 ###
+
+
+<pre><code>
+find_hooks() -&gt; [{PhaseNo, [{M, F, A}]}]
+</code></pre>
+<br />
+
+Finds all custom setup hooks in all applications.
+The setup hooks must be of the form
+
+```
+{'$setup_hooks', [{PhaseNo, {M, F, A}}]}
+```
+
+,
+where PhaseNo should be (but doesn't have to be) an integer.
+
+
+
+The hooks will be called in order:
+- The phase numbers will be sorted.
+- All hooks for a specific PhaseNo will be called in sequence,
+in the same order as the applications appear in the boot script
+(and, if included applications exist, in preorder traversal order).
+
+
+A suggested convention is:
+- Create the database at phase 100
+- Create tables (or configure schema) at 200
+- Populate the database at 300
+<a name="find_hooks-1"></a>
+
+### find_hooks/1 ###
+
+
+<pre><code>
+find_hooks(Mode) -&gt; [{PhaseNo, [{M, F, A}]}]
+</code></pre>
+<br />
+
+Find all setup hooks for `Mode` in all applications
+<a name="find_hooks-2"></a>
+
+### find_hooks/2 ###
+
+
+<pre><code>
+find_hooks(Mode, Applications) -&gt; [{PhaseNo, [{M, F, A}]}]
+</code></pre>
+<br />
+
+Find all setup hooks for `Mode` in `Applications`.
+<a name="get_env-2"></a>
+
+### get_env/2 ###
+
+`get_env(A, Key) -> any()`
+
+
+<a name="home-0"></a>
+
+### home/0 ###
+
+
+<pre><code>
+home() -&gt; Directory
+</code></pre>
+<br />
+
+Returns the configured `home` directory, or a best guess (`$CWD`)
+<a name="lib_dirs-0"></a>
+
+### lib_dirs/0 ###
+
+
+<pre><code>
+lib_dirs() -&gt; [string()]
+</code></pre>
+<br />
+
+Equivalent to [`lib_dirs(concat("ERL_SETUP_LIBS", "ERL_LIBS"))`](#lib_dirs-1).
+<a name="lib_dirs-1"></a>
+
+### lib_dirs/1 ###
+
+
+<pre><code>
+lib_dirs(Env::string()) -&gt; [string()]
+</code></pre>
+<br />
+
+
+Returns an expanded list of application directories under a lib path
+
+
+This function expands the (ebin/) directories under e.g. `$ERL_SETUP_LIBS` or
+`$ERL_LIBS`. `$ERL_SETUP_LIB` has the same syntax and semantics as
+`$ERL_LIBS`, but is (hopefully) only recognized by the `setup` application.
+This can be useful e.g. when keeping a special 'extensions' or 'plugin'
+root that is handled via `setup`, but not treated as part of the normal
+'automatic code loading path'.
+<a name="log_dir-0"></a>
+
+### log_dir/0 ###
+
+
+<pre><code>
+log_dir() -&gt; Directory
+</code></pre>
+<br />
+
+Returns the configured log dir, or a best guess (`home()/log.Node`)
+<a name="ok-1"></a>
+
+### ok/1 ###
+
+`ok(Other) -> any()`
+
+
+<a name="patch_app-1"></a>
+
+### patch_app/1 ###
+
+
+<pre><code>
+patch_app(AppName::atom()) -&gt; true | {error, Reason}
+</code></pre>
+<br />
+
+
+Adds an application's "development" path to a target system
+
+
+
+This function locates the given application (`AppName`) along the `$ERL_LIBS`
+path, and prepends it to the code path of the existing system. This is useful
+not least when one wants to add e.g. a debugging or trace application to a
+target system.
+
+
+The function will not add the same path again, if the new path is already
+the 'first' path entry for the application `A`.
+<a name="pick_vsn-3"></a>
+
+### pick_vsn/3 ###
+
+
+<pre><code>
+pick_vsn(App::atom(), Dirs::[{Vsn::string(), Dir::string()}], Vsn::Which) -&gt; {Vsn, Dir}
+</code></pre>
+
+<ul class="definitions"><li><code>Which = latest | next | Regexp</code></li></ul>
+
+
+Picks the specified version out of a list returned by [`find_app/1`](#find_app-1)
+
+
+
+* If `Which` is a string, it will be used as a `re` regexp pattern, and the
+first matching version will be returned.
+
+
+
+* If `Which = latest`, the last entry in the list will be returned (assumes
+that the list is sorted in ascending version order).
+
+
+
+* If `Which = next`, the next version following the current version of the
+application `A` is returned, assuming `A` is loaded; if `A` is not loaded,
+the first entry in the list is returned.
+
+
+If no matching version is found, the function raises an exception.
+<a name="read_config_script-3"></a>
+
+### read_config_script/3 ###
+
+`read_config_script(F, Name, Opts) -> any()`
+
+
+<a name="reload_app-1"></a>
+
+### reload_app/1 ###
+
+
+<pre><code>
+reload_app(AppName::atom()) -&gt; {ok, NotPurged} | {error, Reason}
+</code></pre>
+<br />
+
+Equivalent to [`reload_app(AppName, latest)`](#reload_app-2).
+<a name="reload_app-2"></a>
+
+### reload_app/2 ###
+
+
+<pre><code>
+reload_app(AppName::atom(), ToVsn) -&gt; {ok, UnPurged} | {error, Reason}
+</code></pre>
+<br />
+
+Equivalent to [`reload_app(AppName, latest, lib_dirs())`](#reload_app-3).
+<a name="reload_app-3"></a>
+
+### reload_app/3 ###
+
+
+<pre><code>
+reload_app(AppName::atom(), ToVsn0::ToVsn, LibDirs) -&gt; {ok, Unpurged} | {error, Reason}
+</code></pre>
+
+<ul class="definitions"><li><code>ToVsn = latest | next | Vsn</code></li><li><code>LibDirs = [string()]</code></li><li><code>Vsn = string()</code></li></ul>
+
+
+Loads or upgrades an application to the specified version
+
+
+
+This function is a convenient function for 'upgrading' an application.
+It locates the given version (using [`find_app/1`](#find_app-1) and [`pick_vsn/3`](#pick_vsn-3))
+and loads it in the most appropriate way:
+
+
+
+* If the application isn't already loaded, it loads the application and
+all its modules.
+
+
+
+* If the application is loaded, it generates an appup script and performs
+a soft upgrade. If the new version of the application has an `.appup` script
+on-disk, that script is used instead.
+
+
+
+The application is searched for along the existing path (that is, under
+the roots of the existing code path, allowing for e.g. $ROOT/lib/app-1.0
+and $ROOT/lib/app-1.2 to be found and tested against the version condition),
+and also along `LibDirs` (see [`lib_dirs/0`](#lib_dirs-0) an [`lib_dirs/1`](#lib_dirs-1)).
+
+
+
+The generated appup script is of the form:
+
+
+
+* add modules not present in the previous version of the application
+
+
+
+* do a soft upgrade on pre-existing modules, using suspend-code_change-resume
+
+
+
+* delete modules that existed in the old version, but not in the new.
+
+
+
+The purge method used is `brutal_purge` - see [`//sasl/appup`](/Users/uwiger/uw/me/sasl/doc/appup.md).
+
+
+For details on how the new version is chosen, see [`find_app/1`](#find_app-1) and
+[`pick_vsn/3`](#pick_vsn-3).
+<a name="run_hooks-0"></a>
+
+### run_hooks/0 ###
+
+
+<pre><code>
+run_hooks() -&gt; ok
+</code></pre>
+<br />
+
+Execute all setup hooks for current mode in order.
+<a name="run_hooks-1"></a>
+
+### run_hooks/1 ###
+
+
+<pre><code>
+run_hooks(Apps::Applications) -&gt; ok
+</code></pre>
+<br />
+
+Execute setup hooks for current mode in `Applications` in order.
+<a name="run_hooks-2"></a>
+
+### run_hooks/2 ###
+
+
+<pre><code>
+run_hooks(Mode, Apps::Applications) -&gt; ok
+</code></pre>
+<br />
+
+Execute setup hooks for `Mode` in `Applications` in order
+<a name="start-2"></a>
+
+### start/2 ###
+
+
+<pre><code>
+start(X1::Type, Args) -&gt; {ok, pid()}
+</code></pre>
+<br />
+
+Application start function.
+<a name="stop-1"></a>
+
+### stop/1 ###
+
+
+<pre><code>
+stop(X1::State) -&gt; ok
+</code></pre>
+<br />
+
+Application stop function
+end
+
+<a name="verify_dir-1"></a>
+
+### verify_dir/1 ###
+
+
+<pre><code>
+verify_dir(Directory::Dir) -&gt; Dir
+</code></pre>
+<br />
+
+Ensures that the directory Dir exists and is writable.
+<a name="verify_directories-0"></a>
+
+### verify_directories/0 ###
+
+
+<pre><code>
+verify_directories() -&gt; ok
+</code></pre>
+<br />
+
+Ensures that essential directories exist and are writable.
+Currently, only the log directory is verified.
diff --git a/deps/setup/doc/setup_gen.md b/deps/setup/doc/setup_gen.md
new file mode 100644
index 0000000..75762d3
--- /dev/null
+++ b/deps/setup/doc/setup_gen.md
@@ -0,0 +1,76 @@
+
+
+# Module setup_gen #
+* [Function Index](#index)
+* [Function Details](#functions)
+
+
+<a name="index"></a>
+
+## Function Index ##
+
+
+<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#help-0">help/0</a></td><td></td></tr><tr><td valign="top"><a href="#main-1">main/1</a></td><td></td></tr><tr><td valign="top"><a href="#run-1">run/1</a></td><td>Generates .rel file(s) and boot scripts for a given configuration.</td></tr></table>
+
+
+<a name="functions"></a>
+
+## Function Details ##
+
+<a name="help-0"></a>
+
+### help/0 ###
+
+`help() -> any()`
+
+
+<a name="main-1"></a>
+
+### main/1 ###
+
+`main(Args) -> any()`
+
+
+<a name="run-1"></a>
+
+### run/1 ###
+
+
+<pre><code>
+run(Options) -&gt; ok
+</code></pre>
+<br />
+
+
+Generates .rel file(s) and boot scripts for a given configuration.
+
+
+
+This function reads a configuration specification and generates the
+files needed to start a node from an OTP boot script. Optionally, it can
+also generate a 'setup' script, which contains the same applications, but
+only loaded (except the `setup` application, if present, which is started).
+This way, a node can be started with all paths set, and all environment
+variables defined, such that a database can be created, and other setup
+tasks be performed.
+
+
+
+Mandatory options:
+* `{name, Name}` - Name of the release (and of the .rel and .script files)
+* `{outdir, Dir}` - Where to put the generated files. Dir is created if not
+already present.
+* `{conf, Conf}` - Config file listing apps and perhaps other options
+
+
+Additional options:
+* `{apps, [App]}` - List of applications to include in the release. Only the
+first instance of this option is considered.
+* `{add_apps, [App]}` - Adds applications to the ones given in the `apps`
+option.
+* `{include, ConfigFile}` - include options from the given file. The file
+is processed using `file:script/2`.
+* `{include_lib, ConfigFile}` - As above, but ConfigFile is named as with
+the `-include_lib(...)` directive in Erlang
+source code.
+* ...
diff --git a/deps/setup/doc/setup_lib.md b/deps/setup/doc/setup_lib.md
new file mode 100644
index 0000000..cea9d44
--- /dev/null
+++ b/deps/setup/doc/setup_lib.md
@@ -0,0 +1,75 @@
+
+
+# Module setup_lib #
+* [Function Index](#index)
+* [Function Details](#functions)
+
+
+<a name="index"></a>
+
+## Function Index ##
+
+
+<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#abort-2">abort/2</a></td><td></td></tr><tr><td valign="top"><a href="#compare_vsns-2">compare_vsns/2</a></td><td></td></tr><tr><td valign="top"><a href="#help-0">help/0</a></td><td></td></tr><tr><td valign="top"><a href="#is_string-1">is_string/1</a></td><td></td></tr><tr><td valign="top"><a href="#releases_dir-0">releases_dir/0</a></td><td></td></tr><tr><td valign="top"><a href="#sort_vsns-2">sort_vsns/2</a></td><td></td></tr><tr><td valign="top"><a href="#write_eterm-2">write_eterm/2</a></td><td></td></tr><tr><td valign="top"><a href="#write_script-2">write_script/2</a></td><td></td></tr></table>
+
+
+<a name="functions"></a>
+
+## Function Details ##
+
+<a name="abort-2"></a>
+
+### abort/2 ###
+
+`abort(Fmt, Args) -> any()`
+
+
+<a name="compare_vsns-2"></a>
+
+### compare_vsns/2 ###
+
+`compare_vsns(V1, V2) -> any()`
+
+
+<a name="help-0"></a>
+
+### help/0 ###
+
+`help() -> any()`
+
+
+<a name="is_string-1"></a>
+
+### is_string/1 ###
+
+`is_string(L) -> any()`
+
+
+<a name="releases_dir-0"></a>
+
+### releases_dir/0 ###
+
+`releases_dir() -> any()`
+
+
+<a name="sort_vsns-2"></a>
+
+### sort_vsns/2 ###
+
+`sort_vsns(Dirs, AppStr) -> any()`
+
+
+<a name="write_eterm-2"></a>
+
+### write_eterm/2 ###
+
+`write_eterm(F, Term) -> any()`
+
+
+<a name="write_script-2"></a>
+
+### write_script/2 ###
+
+`write_script(F, Script) -> any()`
+
+
diff --git a/deps/setup/doc/stylesheet.css b/deps/setup/doc/stylesheet.css
new file mode 100644
index 0000000..e426a90
--- /dev/null
+++ b/deps/setup/doc/stylesheet.css
@@ -0,0 +1,55 @@
+/* standard EDoc style sheet */
+body {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ margin-left: .25in;
+ margin-right: .2in;
+ margin-top: 0.2in;
+ margin-bottom: 0.2in;
+ color: #000000;
+ background-color: #ffffff;
+}
+h1,h2 {
+ margin-left: -0.2in;
+}
+div.navbar {
+ background-color: #add8e6;
+ padding: 0.2em;
+}
+h2.indextitle {
+ padding: 0.4em;
+ background-color: #add8e6;
+}
+h3.function,h3.typedecl {
+ background-color: #add8e6;
+ padding-left: 1em;
+}
+div.spec {
+ margin-left: 2em;
+ background-color: #eeeeee;
+}
+a.module,a.package {
+ text-decoration:none
+}
+a.module:hover,a.package:hover {
+ background-color: #eeeeee;
+}
+ul.definitions {
+ list-style-type: none;
+}
+ul.index {
+ list-style-type: none;
+ background-color: #eeeeee;
+}
+
+/*
+ * Minor style tweaks
+ */
+ul {
+ list-style-type: square;
+}
+table {
+ border-collapse: collapse;
+}
+td {
+ padding: 3
+}
diff --git a/deps/setup/doc/template.conf b/deps/setup/doc/template.conf
new file mode 100644
index 0000000..a264e4f
--- /dev/null
+++ b/deps/setup/doc/template.conf
@@ -0,0 +1,63 @@
+%% -*- erlang -*-
+%%==============================================================================
+%% Copyright 2014 Ulf Wiger
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%==============================================================================
+%% config template
+%% @author ulf@wiger.net
+
+
+%% This file is read using file:script/2 with the following
+%% variable bindings:
+%% Name Name of release
+%% CWD Current working directory</td></tr>
+%% Options List of options (proplist) given to setup_gen
+%%
+%% The file shold contain Erlang expressions, each terminated with
+%% a period and newline. The result of the last expression must be
+%% a list of {Key, Value} tuples.
+%%
+%% It is possible to add custom config values (todo: how to make these
+%% available?). Predefined config values are:
+%% - {apps, [App]}
+%% App = AppName :: atom()
+%% | {AppName, Type}
+%% | {AppName, Incls}p
+%% | {AppName, Type, Incls}
+%% - {nodes, [NodeName]}
+%% The names of the nodes that should be started
+%% - {env, [{AppName, [{Key, Value}]}]}
+%% Add or modify application environment variables. If the given
+%% variables exist, they will be shadowed. Successive env items can
+%% correspondingly shadow those given in previous env entries.
+%% The order of defining env variables is as follows:
+%% 1. Statically defined variables in the .app files
+%% 2. variables defined in releases/Vsn/Relname.config
+%% 3. env items in a target config like this one
+%% These are added to the Relname.config and written to
+%% releases/Vsn/sys.config
+%% 4. Env variables added to the command line when starting erlang.
+%% - {target_subdir, SubDir}
+%% Make all generated files appear in TargetDir/SubDir, rather than in
+%% TargetDir (argument given to make_scripts). The subdirectory will be
+%% created automatically.
+%%
+%% Example:
+%% {ok, Host} = inet:gethostname().
+%% Nodes = [list_to_atom(Name) ++ "@" ++ Host || Name <- [n1, n2]].
+%% {ok, Base}= file:script("base.conf").
+%% %% return the config
+%% [
+%% {nodes, Nodes}
+%% ] ++ Base.
diff --git a/deps/setup/examples/gproc.conf b/deps/setup/examples/gproc.conf
new file mode 100644
index 0000000..647516d
--- /dev/null
+++ b/deps/setup/examples/gproc.conf
@@ -0,0 +1,5 @@
+%% -*- erlang -*-
+[{apps, [kernel,
+ stdlib,
+ sasl,
+ gproc]}].
diff --git a/deps/setup/rebar b/deps/setup/rebar
new file mode 100755
index 0000000..d1a3437
--- /dev/null
+++ b/deps/setup/rebar
Binary files differ
diff --git a/deps/setup/rebar.config b/deps/setup/rebar.config
new file mode 100644
index 0000000..e5f2851
--- /dev/null
+++ b/deps/setup/rebar.config
@@ -0,0 +1,14 @@
+%% -*- erlang -*-
+{erl_opts, [debug_info]}.
+{deps, [{edown, ".*", {git, "git://github.com/uwiger/edown.git", "HEAD"}}]}.
+{edoc_opts, [{doclet, edown_doclet},
+ {top_level_readme,
+ {"./README.md",
+ "http://github.com/uwiger/setup"}}]}.
+{escript_name, setup_gen}.
+{post_hooks, [{compile, "make escriptize"}]}.
+
+%% This line is to ensure that any erl_first_files setting from
+%% 'above' isn't accidentally inherited, since there seems to be
+%% a strange interaction between SNMP MIB compilation and escriptize.
+{erl_first_files, []}.
diff --git a/deps/setup/rebar.config.script b/deps/setup/rebar.config.script
new file mode 100644
index 0000000..ac32801
--- /dev/null
+++ b/deps/setup/rebar.config.script
@@ -0,0 +1,61 @@
+%% -*- erlang -*-
+%%
+%% If the environment variable X_COMP is set, it is an indication that self is being compiled
+%% as a dependency under exodev. Since exodev lists all dependency in its root rebar.config
+%% file in order to have a flat view of the dependency tree, we should nil out our local
+%% deps list provided by our local rebar.config
+%%
+C0 = case os:getenv("EXODEV_COMP") of
+ false ->
+ CONFIG;
+
+ _ ->
+ case lists:keytake(deps, 1, CONFIG) of
+ { value, _, Remainder } -> Remainder;
+ _ -> CONFIG
+ end
+end,
+C1 = case os:getenv("REBAR_DEPS") of
+ false -> C0;
+ [] -> C0;
+ Dir ->
+ lists:keystore(deps_dir, 1, C0, {deps_dir, Dir})
+ end.
+
+C3 =
+case os:getenv("TGT") of
+ false ->
+ C1;
+ [] ->
+ C1;
+ Tgt ->
+ io:fwrite("Tgt = ~p~n", [Tgt]),
+ {_, Opts} = lists:keyfind(edoc_opts, 1, C1),
+ {_, {F,URL}} = lists:keyfind(top_level_readme, 1, Opts),
+ Name = lists:last(string:tokens(URL, "/")),
+ io:fwrite("Name = ~s~n", [Name]),
+ NewURL = re:replace(URL, "[^/]+/" ++ Name, Tgt ++ "/" ++ Name,
+ [{return, list}]),
+ C2 =
+ lists:keyreplace(
+ edoc_opts, 1,
+ C1,
+ {edoc_opts, lists:keyreplace(top_level_readme, 1, Opts,
+ {top_level_readme, {F, NewURL}})}),
+ io:fwrite("CONFIG1 = ~p~n", [C2]),
+ C2
+end.
+
+REBAR = escript:script_name().
+case lists:keyfind(post_hooks, 1, C3) of
+ {_, PostHooks} ->
+ PH1 = lists:map(
+ fun({compile, "make escriptize"}) ->
+ {compile, REBAR ++ " escriptize"};
+ (X) -> X
+ end, PostHooks),
+ lists:keyreplace(
+ post_hooks, 1, C3, {post_hooks, PH1});
+ false ->
+ C3
+end.
diff --git a/deps/setup/src/setup.app.src b/deps/setup/src/setup.app.src
new file mode 100644
index 0000000..8a81e86
--- /dev/null
+++ b/deps/setup/src/setup.app.src
@@ -0,0 +1,28 @@
+%% -*- erlang -*-
+%%==============================================================================
+%% Copyright 2014 Ulf Wiger
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%==============================================================================
+{application, setup,
+ [
+ {description, "Generic setup application for Erlang-based systems"},
+ {vsn, git},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]},
+ {mod, { setup, []}},
+ {env, []}
+ ]}.
diff --git a/deps/setup/src/setup.erl b/deps/setup/src/setup.erl
new file mode 100644
index 0000000..c1838a3
--- /dev/null
+++ b/deps/setup/src/setup.erl
@@ -0,0 +1,1116 @@
+%% -*- mode: erlang; indent-tabs-mode: nil; -*-
+%%=============================================================================
+%% Copyright 2014 Ulf Wiger
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%=============================================================================
+
+%% @doc Setup utility for erlang applications
+-module(setup).
+-behaviour(application).
+
+-export([start/2,
+ stop/1]).
+
+-export([home/0,
+ log_dir/0,
+ data_dir/0,
+ verify_directories/0,
+ verify_dir/1,
+ find_hooks/0, find_hooks/1, find_hooks/2,
+ run_hooks/0, run_hooks/1, run_hooks/2,
+ find_env_vars/1,
+ get_env/2,
+ expand_value/2,
+ patch_app/1,
+ find_app/1, find_app/2,
+ pick_vsn/3,
+ reload_app/1, reload_app/2, reload_app/3,
+ lib_dirs/0, lib_dirs/1]).
+-export([read_config_script/3]).
+
+-export([ok/1]).
+-compile(export_all).
+
+-export([run_setup/2]).
+
+-include_lib("kernel/include/file.hrl").
+
+%% @spec start(Type, Args) -> {ok, pid()}
+%% @doc Application start function.
+%% @end
+%%
+start(_, Args) ->
+ proc_lib:start_link(?MODULE, run_setup, [self(), Args]).
+
+%% @spec stop(State) -> ok
+%% @doc Application stop function
+%% end
+%%
+stop(_) ->
+ ok.
+
+%% @spec home() -> Directory
+%% @doc Returns the configured `home' directory, or a best guess (`$CWD')
+%% @end
+%%
+home() ->
+ case application:get_env(setup, home) of
+ U when U == {ok, undefined};
+ U == undefined ->
+ {ok, CWD} = file:get_cwd(),
+ D = filename:absname(CWD),
+ application:set_env(setup, home, D),
+ D;
+ {ok, D} ->
+ D
+ end.
+
+%% @spec log_dir() -> Directory
+%% @doc Returns the configured log dir, or a best guess (`home()/log.Node')
+%% @end
+%%
+log_dir() ->
+ setup_dir(log_dir, "log." ++ atom_to_list(node())).
+
+%% @spec data_dir() -> Directory
+%% @doc Returns the configured data dir, or a best guess (`home()/data.Node').
+%%
+%% @end
+%%
+data_dir() ->
+ setup_dir(data_dir, "data." ++ atom_to_list(node())).
+
+setup_dir(Key, Default) ->
+ case application:get_env(setup, Key) of
+ U when U == {ok, undefined};
+ U == undefined ->
+ D = filename:absname(filename:join(home(), Default)),
+ application:set_env(setup, Key, D),
+ D;
+ {ok, D} ->
+ D
+ end.
+
+%% @spec verify_directories() -> ok
+%% @doc Ensures that essential directories exist and are writable.
+%% Currently, only the log directory is verified.
+%% @end
+%%
+verify_directories() ->
+ _ = verify_dir(home()),
+ _ = verify_dir(log_dir()),
+ _ = verify_dir(data_dir()),
+ ok.
+
+%% @spec verify_dir(Dir) -> Dir
+%% @doc Ensures that the directory Dir exists and is writable.
+%% @end
+%%
+verify_dir(Directory) ->
+ ok = filelib:ensure_dir(filename:join(Directory, "dummy")),
+ Directory.
+
+ok({ok, Result}) ->
+ Result;
+ok(Other) ->
+ setup_lib:abort("Expected {ok, Value}~n", [Other]).
+
+
+%% @spec find_env_vars(Env) -> [{AppName, Value}]
+%% @doc Searches all loaded apps for instances of the `Env' environment variable.
+%%
+%% The environment variables may contain instances of
+%% `$APP', `$PRIV_DIR', `$LIB_DIR', `$DATA_DIR', `$LOG_DIR', `$HOME',
+%% inside strings or binaries, and these will be replaced with actual values
+%% for the current system (`$APP' simply expands to the name of the current
+%% application).
+%% @end
+find_env_vars(Env) ->
+ GEnv = global_env(),
+ lists:flatmap(
+ fun({A,_,_}) ->
+ case application:get_env(A, Env) of
+ {ok, Val} when Val =/= undefined ->
+ NewEnv = GEnv ++ private_env(A),
+ [{A, expand_env(NewEnv, Val)}];
+ _ ->
+ []
+ end
+ end, application:loaded_applications()).
+
+get_env(A, Key) ->
+ case application:get_env(A, Key) of
+ {ok, Val} ->
+ {ok, expand_value(A, Val)};
+ Other ->
+ Other
+ end.
+
+expand_value(App, Value) ->
+ expand_env(global_env() ++ private_env(App), Value).
+
+
+global_env() ->
+ [{K, env_value(K)} || K <- ["DATA_DIR", "LOG_DIR", "HOME"]].
+private_env(A) ->
+ [{K, env_value(K, A)} || K <- ["APP", "PRIV_DIR", "LIB_DIR"]].
+
+expand_env(Vs, T) when is_tuple(T) ->
+ list_to_tuple([expand_env(Vs, X) || X <- tuple_to_list(T)]);
+expand_env(Vs, L) when is_list(L) ->
+ case setup_lib:is_string(L) of
+ true ->
+ do_expand_env(L, Vs, list);
+ false ->
+ [expand_env(Vs, X) || X <- L]
+ end;
+expand_env(Vs, B) when is_binary(B) ->
+ do_expand_env(B, Vs, binary);
+expand_env(_, X) ->
+ X.
+
+do_expand_env(X, Vs, Type) ->
+ lists:foldl(fun({K, Val}, Xx) ->
+ re:replace(Xx, [$\\, $$ | K], Val, [{return,Type}])
+ end, X, Vs).
+
+env_value("LOG_DIR") -> log_dir();
+env_value("DATA_DIR") -> data_dir();
+env_value("HOME") -> home().
+
+env_value("APP", A) -> atom_to_list(A);
+env_value("PRIV_DIR", A) -> priv_dir(A);
+env_value("LIB_DIR" , A) -> lib_dir(A).
+
+priv_dir(A) ->
+ case code:priv_dir(A) of
+ {error, bad_name} ->
+ case is_cur_dir(A) of
+ true ->
+ filename:join(cwd(), "priv");
+ false ->
+ error({cannot_get_priv_dir, A})
+ end;
+ D -> D
+ end.
+
+lib_dir(A) ->
+ case code:lib_dir(A) of
+ {error, bad_name} ->
+ case is_cur_dir(A) of
+ true ->
+ cwd();
+ false ->
+ error({cannot_get_lib_dir, A})
+ end;
+ D -> D
+ end.
+
+cwd() ->
+ {ok, CWD} = file:get_cwd(),
+ CWD.
+
+is_cur_dir(A) ->
+ As = atom_to_list(A),
+ filename:basename(cwd()) == As.
+
+
+%% @spec patch_app(AppName::atom()) -> true | {error, Reason}
+%%
+%% @doc Adds an application's "development" path to a target system
+%%
+%% This function locates the given application (`AppName') along the `$ERL_LIBS'
+%% path, and prepends it to the code path of the existing system. This is useful
+%% not least when one wants to add e.g. a debugging or trace application to a
+%% target system.
+%%
+%% The function will not add the same path again, if the new path is already
+%% the 'first' path entry for the application `A'.
+%% @end
+patch_app(A) when is_atom(A) ->
+ patch_app(A, latest).
+
+patch_app(A, Vsn) ->
+ patch_app(A, Vsn, lib_dirs()).
+
+patch_app(A, Vsn, LibDirs) ->
+ case find_app(A, LibDirs) of
+ [_|_] = Found ->
+ {_ActualVsn, Dir} = pick_vsn(A, Found, Vsn),
+ io:fwrite("[~p vsn ~p] code:add_patha(~s)~n", [A, _ActualVsn, Dir]),
+ code:add_patha(Dir);
+ [] ->
+ error(no_matching_vsn)
+ end.
+
+%% @spec pick_vsn(App::atom(), Dirs::[{Vsn::string(),Dir::string()}], Which) ->
+%% {Vsn, Dir}
+%% where
+%% Which = 'latest' | 'next' | Regexp
+%%
+%% @doc Picks the specified version out of a list returned by {@link find_app/1}
+%%
+%% * If `Which' is a string, it will be used as a `re' regexp pattern, and the
+%% first matching version will be returned.
+%%
+%% * If `Which = latest', the last entry in the list will be returned (assumes
+%% that the list is sorted in ascending version order).
+%%
+%% * If `Which = next', the next version following the current version of the
+%% application `A' is returned, assuming `A' is loaded; if `A' is not loaded,
+%% the first entry in the list is returned.
+%%
+%% If no matching version is found, the function raises an exception.
+%% @end
+pick_vsn(_, Dirs, latest) ->
+ lists:last(Dirs);
+pick_vsn(A, Dirs, next) ->
+ case application:get_key(A, vsn) of
+ {ok, Cur} ->
+ case lists:dropwhile(fun({V, _}) -> V =/= Cur end, Dirs) of
+ [_, {_, _} = Next |_] -> Next;
+ _ -> error(no_matching_vsn)
+ end;
+ _ ->
+ hd(Dirs)
+ end;
+pick_vsn(_, Dirs, Vsn) ->
+ case [X || {V, _} = X <- Dirs,
+ re:run(V, Vsn) =/= nomatch] of
+ [Found|_] ->
+ Found;
+ [] ->
+ error(no_matching_vsn)
+ end.
+
+
+%% @spec find_app(A::atom()) -> [{Vsn, Dir}]
+%% @equiv find_app(A, lib_dirs())
+find_app(A) ->
+ find_app(A, lib_dirs()).
+
+%% @spec find_app(A::atom(), LibDirs::[string()]) -> [{Vsn, Dir}]
+%%
+%% @doc Locates application `A' along LibDirs (see {@link lib_dirs/0} and
+%% {@link lib_dirs/1}) or under the OTP root, returning all found candidates.
+%% The version is extracted from the `.app' file; thus, no version suffix
+%% in the path name is required.
+%% @end
+find_app(A, LibDirs) ->
+ Astr = to_string(A),
+ CurDir = case code:lib_dir(A) of
+ {error,_} -> [];
+ D ->
+ [filename:join(D, "ebin")]
+ end,
+ CurRoots = current_roots(),
+ InLib = [P || P <- LibDirs,
+ is_app_dir(Astr, P)],
+ InRoots = lists:append([in_root(A, R) || R <- CurRoots]),
+ setup_lib:sort_vsns(
+ lists:usort(CurDir ++ InRoots ++ InLib), atom_to_list(A)).
+
+to_string(A) when is_atom(A) ->
+ atom_to_list(A);
+to_string(A) when is_list(A) ->
+ A.
+
+is_app_dir(A, D) ->
+ case lists:reverse(filename:split(D)) of
+ ["ebin", App|_] ->
+ case re:split(App, <<"-">>, [{return,list}]) of
+ [A|_] -> true;
+ _ -> false
+ end;
+ _ ->
+ false
+ end.
+
+current_roots() ->
+ CurPath = code:get_path(),
+ roots_of(CurPath).
+
+roots_of(Path) ->
+ All = lists:foldr(
+ fun(D, Acc) ->
+ case lists:reverse(filename:split(D)) of
+ ["ebin",_|T] ->
+ [filename:join(lists:reverse(T)) | Acc];
+ _ ->
+ Acc
+ end
+ end, [], Path),
+ lists:usort(All).
+
+in_root(A, R) ->
+ Paths = filelib:wildcard(filename:join([R, "*", "ebin"])),
+ Pat = atom_to_list(A) ++ "-[\\.0-9]+/ebin\$",
+ [P || P <- Paths,
+ re:run(P, Pat) =/= nomatch].
+
+%% @spec reload_app(AppName::atom()) -> {ok, NotPurged} | {error, Reason}
+%%
+%% @equiv reload_app(AppName, latest)
+reload_app(A) ->
+ reload_app(A, latest).
+
+%% @spec reload_app(AppName::atom(), ToVsn) -> {ok,UnPurged} | {error,Reason}
+%%
+%% @equiv reload_app(AppName, latest, lib_dirs())
+reload_app(A, ToVsn) ->
+ reload_app(A, ToVsn, lib_dirs()).
+
+%% @spec reload_app(AppName::atom(), ToVsn, LibDirs) ->
+%% {ok, Unpurged} | {error, Reason}
+%% where
+%% ToVsn = 'latest' | 'next' | Vsn,
+%% LibDirs = [string()]
+%% Vsn = string()
+%%
+%% @doc Loads or upgrades an application to the specified version
+%%
+%% This function is a convenient function for 'upgrading' an application.
+%% It locates the given version (using {@link find_app/1} and {@link pick_vsn/3})
+%% and loads it in the most appropriate way:
+%%
+%% * If the application isn't already loaded, it loads the application and
+%% all its modules.
+%%
+%% * If the application is loaded, it generates an appup script and performs
+%% a soft upgrade. If the new version of the application has an `.appup' script
+%% on-disk, that script is used instead.
+%%
+%% The application is searched for along the existing path (that is, under
+%% the roots of the existing code path, allowing for e.g. $ROOT/lib/app-1.0
+%% and $ROOT/lib/app-1.2 to be found and tested against the version condition),
+%% and also along `LibDirs' (see {@link lib_dirs/0} an {@link lib_dirs/1}).
+%%
+%% The generated appup script is of the form:
+%%
+%% * add modules not present in the previous version of the application
+%%
+%% * do a soft upgrade on pre-existing modules, using suspend-code_change-resume
+%%
+%% * delete modules that existed in the old version, but not in the new.
+%%
+%% The purge method used is `brutal_purge' - see {@link //sasl/appup}.
+%%
+%% For details on how the new version is chosen, see {@link find_app/1} and
+%% {@link pick_vsn/3}.
+%% @end
+reload_app(A, ToVsn0, LibDirs) ->
+ case application:get_key(A, vsn) of
+ undefined ->
+ ok = application:load(A),
+ {ok, Modules} = application:get_key(A, modules),
+ _ = [c:l(M) || M <- Modules],
+ {ok, []};
+ {ok, FromVsn} ->
+ {ToVsn, NewPath} = pick_vsn(A, find_app(A, LibDirs), ToVsn0),
+ if ToVsn == FromVsn ->
+ {error, same_version};
+ true ->
+ io:fwrite("[~p vsn ~p] soft upgrade from ~p~n",
+ [A, ToVsn, FromVsn]),
+ reload_app(
+ A, FromVsn, filename:join(code:lib_dir(A), "ebin"),
+ NewPath, ToVsn)
+ end
+ end.
+
+reload_app(A, OldVsn, OldPath, NewPath, NewVsn) ->
+ {_NewVsn, Script, NewApp} = make_appup_script(A, OldVsn, NewPath),
+ reload_app(A, OldVsn, OldPath, NewPath, NewVsn, Script, NewApp).
+
+reload_app(A, _OldVsn, _OldPath, NewPath, NewVsn, Script, _NewApp) ->
+ LibDir = filename:dirname(NewPath),
+ _ = remove_path(NewPath, A),
+ case release_handler:eval_appup_script(A, NewVsn, LibDir, Script) of
+ {ok, Unpurged} ->
+ _ = [code:purge(M) || {M, brutal_purge} <- Unpurged],
+ {ok, [U || {_, Mode} = U <- Unpurged, Mode =/= brutal_purge]};
+ Other ->
+ Other
+ end.
+
+remove_path(P, A) ->
+ CurPath = code:get_path(),
+ case lists:member(P, CurPath) of
+ true ->
+ %% don't remove if it's the only path
+ case [Px || Px <- path_entries(A, CurPath),
+ Px =/= P] of
+ [] ->
+ true;
+ [_|_] ->
+ code:set_path([Px || Px <- CurPath,
+ Px =/= P])
+ end;
+ false ->
+ true
+ end.
+
+path_entries(A) ->
+ path_entries(A, code:get_path()).
+
+path_entries(A, Path) ->
+ Pat = atom_to_list(A) ++ "[^/]*/ebin\$",
+ [P || P <- Path,
+ re:run(P, Pat) =/= nomatch].
+
+make_appup_script(A, OldVsn, NewPath) ->
+ {application, _, NewAppTerms} = NewApp =
+ read_app(filename:join(NewPath, atom_to_list(A) ++ ".app")),
+ OldAppTerms = application:get_all_key(A),
+ _OldApp = {application, A, OldAppTerms},
+ case find_script(A, NewPath, OldVsn, up) of
+ {NewVsn, Script} ->
+ {NewVsn, Script, NewApp};
+ false ->
+ {ok, OldMods} = application:get_key(A, modules),
+ {modules, NewMods} = lists:keyfind(modules, 1, NewAppTerms),
+ {vsn, NewVsn} = lists:keyfind(vsn, 1, NewAppTerms),
+ {DelMods,AddMods,ChgMods} = {OldMods -- NewMods,
+ NewMods -- OldMods,
+ intersection(NewMods, OldMods)},
+ {NewVsn,
+ [{load_object_code,{A, NewVsn, NewMods}}]
+ ++ [point_of_no_return]
+ ++ [{load, {M, brutal_purge, brutal_purge}} || M <- AddMods]
+ ++ [{suspend, ChgMods} || ChgMods =/= []]
+ ++ [{load, {M, brutal_purge,brutal_purge}} || M <- ChgMods]
+ ++ [{code_change, up, [{M, setup} || M <- ChgMods]} ||
+ ChgMods =/= []]
+ ++ [{resume, ChgMods} || ChgMods =/= []]
+ ++ [{remove, {M, brutal_purge,brutal_purge}} || M <- DelMods]
+ ++ [{purge, DelMods} || DelMods =/= []],
+ NewApp}
+ end.
+
+read_app(F) ->
+ case file:consult(F) of
+ {ok, [App]} ->
+ App;
+ {error,_} = Error ->
+ error(Error, [F])
+ end.
+
+%% slightly modified (and corrected!) version of release_handler:find_script/4.
+find_script(App, Dir, OldVsn, UpOrDown) ->
+ Appup = filename:join([Dir, "ebin", atom_to_list(App)++".appup"]),
+ case file:consult(Appup) of
+ {ok, [{NewVsn, UpFromScripts, DownToScripts}]} ->
+ Scripts = case UpOrDown of
+ up -> UpFromScripts;
+ down -> DownToScripts
+ end,
+ case lists:dropwhile(fun({Re,_}) ->
+ re:run(OldVsn, Re) == nomatch
+ end, Scripts) of
+ [{_OldVsn, Script}|_] ->
+ {NewVsn, Script};
+ [] ->
+ false
+ end;
+ {error, enoent} ->
+ false;
+ {error, _} ->
+ false
+ end.
+
+
+%% find_procs(Mods) ->
+%% Ps = release_handler_1:get_supervised_procs(),
+%% lists:flatmap(
+%% fun({P,_,_,Ms}) ->
+%% case intersection(Ms, Mods) of
+%% [] -> [];
+%% I -> [{P, I}]
+%% end
+%% end, Ps).
+
+intersection(A, B) ->
+ A -- (A -- B).
+
+
+
+%% @hidden
+%%
+%% Called from the start function. Will verify directories, then call
+%% all setup hooks in all applications, and execute them in order.
+%% Afterwards, setup will either finish and leave the system running, or
+%% stop, terminating all nodes automatically.
+%%
+run_setup(Parent, Args) ->
+ io:fwrite("Setup running ...~n", []),
+ try run_setup_(Parent, Args)
+ catch
+ error:Error ->
+ io:fwrite("Caught exception:~n"
+ "~p~n"
+ "~p~n", [Error, erlang:get_stacktrace()])
+ end.
+
+run_setup_(Parent, _Args) ->
+ Res = rpc:multicall(?MODULE, verify_directories, []),
+ io:fwrite("Directories verified. Res = ~p~n", [Res]),
+ proc_lib:init_ack(Parent, {ok, self()}),
+ Mode = mode(),
+ Hooks = find_hooks(Mode),
+ run_selected_hooks(Hooks),
+ io:fwrite("Setup finished processing hooks ...~n", []),
+ case application:get_env(stop_when_done) of
+ {ok, true} ->
+ io:fwrite("Setup stopping...~n", []),
+ timer:sleep(timer:seconds(5)),
+ rpc:eval_everywhere(init,stop,[0]);
+ _ ->
+ timer:sleep(infinity)
+ end.
+
+%% @spec find_hooks() -> [{PhaseNo, [{M,F,A}]}]
+%% @doc Finds all custom setup hooks in all applications.
+%% The setup hooks must be of the form
+%% <pre>{'$setup_hooks', [{PhaseNo, {M, F, A}}]}</pre>,
+%% where PhaseNo should be (but doesn't have to be) an integer.
+%%
+%% The hooks will be called in order:
+%% - The phase numbers will be sorted.
+%% - All hooks for a specific PhaseNo will be called in sequence,
+%% in the same order as the applications appear in the boot script
+%% (and, if included applications exist, in preorder traversal order).
+%%
+%% A suggested convention is:
+%% - Create the database at phase 100
+%% - Create tables (or configure schema) at 200
+%% - Populate the database at 300
+%% @end
+%%
+find_hooks() ->
+ find_hooks(mode()).
+
+%% @spec find_hooks(Mode) -> [{PhaseNo, [{M, F, A}]}]
+%% @doc Find all setup hooks for `Mode' in all applications
+%% @end
+find_hooks(Mode) when is_atom(Mode) ->
+ Applications = applications(),
+ find_hooks(Mode, Applications).
+
+%% @spec find_hooks(Mode, Applications) -> [{PhaseNo, [{M, F, A}]}]
+%% @doc Find all setup hooks for `Mode' in `Applications'.
+%% @end
+find_hooks(Mode, Applications) ->
+ lists:foldl(
+ fun(A, Acc) ->
+ case application:get_env(A, '$setup_hooks') of
+ {ok, Hooks} ->
+ lists:foldl(
+ fun({Mode1, L}, Acc1) when is_atom(Mode1), is_list(L) ->
+ lists:foldl(
+ fun({N, {_,_,_} = MFA}, Acc2) ->
+ orddict:append(N, MFA, Acc2);
+ ({N, MFAs}, Acc2) when is_list(MFAs) ->
+ lists:foldl(
+ fun({_,_,_} = MFA1, Acc3) ->
+ orddict:append(
+ N, MFA1, Acc3);
+ (Other1, Acc3) ->
+ io:fwrite(
+ "Invalid hook: ~p~n"
+ " App : ~p~n"
+ " Mode : ~p~n"
+ " Phase: ~p~n",
+ [Other1, A, Mode1, N]),
+ Acc3
+ end, Acc2, MFAs)
+ end, Acc1, L);
+ ({N, {_, _, _} = MFA}, Acc1) when Mode==setup ->
+ orddict:append(N, MFA, Acc1);
+ (_, Acc1) ->
+ Acc1
+ end, Acc, Hooks);
+ _ ->
+ Acc
+ end
+ end, orddict:new(), Applications).
+
+mode() ->
+ case application:get_env(mode) of
+ {ok, M} ->
+ M;
+ _ ->
+ normal
+ end.
+
+%% @spec run_hooks() -> ok
+%% @doc Execute all setup hooks for current mode in order.
+%% @end
+run_hooks() ->
+ run_hooks(applications()).
+
+%% @spec run_hooks(Applications) -> ok
+%% @doc Execute setup hooks for current mode in `Applications' in order.
+%% @end
+run_hooks(Apps) ->
+ run_hooks(mode(), Apps).
+
+%% @spec run_hooks(Mode, Applications) -> ok
+%% @doc Execute setup hooks for `Mode' in `Applications' in order
+%% @end
+run_hooks(Mode, Apps) ->
+ Hooks = find_hooks(Mode, Apps),
+ run_selected_hooks(Hooks).
+
+%% @spec run_selected_hooks(Hooks) -> ok
+%% @doc Execute specified setup hooks in order
+%%
+%% Exceptions are caught and printed. This might/should be improved, but the
+%% general idea is to complete as much as possible of the setup, and perhaps
+%% repair afterwards. However, the fact that something went wrong should be
+%% remembered and reflected at the end.
+%% @end
+%%
+run_selected_hooks(Hooks) ->
+ AbortOnError = case application:get_env(setup, abort_on_error) of
+ {ok, F} when is_boolean(F) -> F;
+ {ok, Other} ->
+ io:fwrite("Invalid abort_on_error flag (~p)~n"
+ "Aborting...~n", [Other]),
+ error({invalid_abort_on_error, Other});
+ _ -> false
+ end,
+ lists:foreach(
+ fun({Phase, MFAs}) ->
+ io:fwrite("Setup phase ~p~n", [Phase]),
+ lists:foreach(fun({M, F, A}) ->
+ try_apply(M, F, A, AbortOnError)
+ end, MFAs)
+ end, Hooks).
+
+try_apply(M, F, A, Abort) ->
+ {_Pid, Ref} = spawn_monitor(
+ fun() ->
+ exit(try {ok, apply(M, F, A)}
+ catch
+ Type:Exception ->
+ {error, {Type, Exception}}
+ end)
+ end),
+ receive
+ {'DOWN', Ref, _, _, Return} ->
+ case Return of
+ {ok, Result} ->
+ report_result(Result, M, F, A);
+ {error, {Type, Exception}} ->
+ report_error(Type, Exception, M, F, A),
+ if Abort ->
+ io:fwrite(
+ "Abort on error is set. Terminating sequence~n",[]),
+ error(Exception);
+ true ->
+ ok
+ end
+ end
+ end.
+
+report_result(Result, M, F, A) ->
+ MFAString = format_mfa(M, F, A),
+ io:fwrite(MFAString ++ "-> ~p~n", [Result]).
+
+report_error(Type, Error, M, F, A) ->
+ ErrTypeStr = case Type of
+ error -> "ERROR: ";
+ throw -> "THROW: ";
+ exit -> "EXIT: "
+ end,
+ MFAString = format_mfa(M, F, A),
+ io:fwrite(MFAString ++ "-> " ++ ErrTypeStr ++ "~p~n~p~n",
+ [Error, erlang:get_stacktrace()]).
+
+
+format_mfa(M, F, A) ->
+ lists:flatten([atom_to_list(M),":",atom_to_list(F),
+ "(", format_args(A), ")"]).
+
+format_args([]) -> "";
+format_args([A]) -> format_arg(A);
+format_args([A, B | T]) -> [format_arg(A), "," | format_args([B | T])].
+
+format_arg(A) ->
+ io_lib:fwrite("~p", [A]).
+
+%% @spec applications() -> [atom()]
+%% @doc Find all applications - either from the boot script or all loaded apps.
+%% @end
+%%
+applications() ->
+ Apps = case init:get_argument(boot) of
+ {ok, [[Boot]]} ->
+ Script = Boot ++ ".script",
+ case file:consult(Script) of
+ {ok, [{script, _, Commands}]} ->
+ [A || {apply, {application, load, [{application, A, _}]}}
+ <- Commands];
+ Error ->
+ error_logger:format("Unable to read boot script (~s): ~p~n",
+ [Script, Error]),
+ [A || {A, _, _} <- application:loaded_applications()]
+ end;
+ _ ->
+ [A || {A, _, _} <- application:loaded_applications()]
+ end,
+ group_applications(Apps).
+
+%% Sort apps in preorder traversal order.
+%% That is, for each "top application", all included apps follow before the
+%% next top application. Normally, there will be no included apps, in which
+%% case the list will maintain its original order.
+%%
+group_applications([H | T]) ->
+ case application:get_key(H, included_applications) of
+ {ok, []} ->
+ [H | group_applications(T)];
+ {ok, Incls} ->
+ AllIncls = all_included(Incls),
+ [H | AllIncls] ++ group_applications(T -- AllIncls)
+ end;
+group_applications([]) ->
+ [].
+
+all_included([H | T]) ->
+ case application:get_key(H, included_applications) of
+ {ok, []} ->
+ [H | all_included(T)];
+ {ok, Incls} ->
+ [H | all_included(Incls)] ++ all_included(T)
+ end;
+all_included([]) ->
+ [].
+
+
+keep_release(RelVsn) ->
+ %% 0. Check
+ RelDir = setup_lib:releases_dir(),
+ case filelib:is_dir(TargetDir = filename:join(RelDir, RelVsn)) of
+ true -> error({target_dir_exists, TargetDir});
+ false -> verify_dir(TargetDir)
+ end,
+ %% 1. Collect info
+ Loaded = application:loaded_applications(),
+ LoadedNames = [element(1,A) || A <- Loaded],
+ Running = application:which_applications(),
+ RunningNames = [element(1,A) || A <- Running],
+ OnlyLoaded = LoadedNames -- RunningNames,
+ Included = lists:flatmap(
+ fun(A) ->
+ case application:get_key(A, included_applications) of
+ {ok, []} ->
+ [];
+ {ok, As} ->
+ [{A, As}]
+ end
+ end, LoadedNames),
+ {Name,_} = init:script_id(),
+ Conf = [
+ {name, Name},
+ {apps, app_list(OnlyLoaded, Loaded, Included)}
+ | [{root, R} || R <- current_roots() -- [otp_root()]]
+ ]
+ ++ [{env, env_diff(LoadedNames)}],
+ setup_lib:write_script(
+ ConfF = filename:join(TargetDir, "setup.conf"), [Conf]),
+ setup_gen:run([{name, Name}, {outdir, TargetDir}, {conf, ConfF}]).
+ %% {loaded, Loaded},
+ %% {running, Running},
+ %% {only_loaded, OnlyLoaded},
+ %% {included, Included},
+ %% {env, env_diff(LoadedNames)},
+ %% {roots, current_roots() -- [otp_root()]},
+ %% {rel_dir, setup_lib:releases_dir()}].
+
+app_list(OnlyLoaded, AllLoaded, Included) ->
+ lists:map(
+ fun({A, _, V}) ->
+ case {lists:member(A, OnlyLoaded),
+ lists:keyfind(A, 1, Included)} of
+ {true,false} -> {A, V, load};
+ {true,{_,I}} -> {A, V, load, I};
+ {false,false} -> {A, V};
+ {false,{_,I}} -> {A, V, I}
+ end
+ end, AllLoaded).
+
+env_diff([A|As]) ->
+ AppF = filename:join([code:lib_dir(A), "ebin", atom_to_list(A) ++ ".app"]),
+ LiveEnv = lists:keydelete(included_applications, 1,
+ application:get_all_env(A)),
+ DiskEnv = fetch_env(AppF),
+ case LiveEnv -- DiskEnv of
+ [_|_] = Diff ->
+ [{A, Diff}|env_diff(As)];
+ [] ->
+ env_diff(As)
+ end;
+env_diff([]) ->
+ [].
+
+fetch_env(AppF) ->
+ case file:consult(AppF) of
+ {ok, [{application,_,Terms}]} ->
+ proplists:get_value(env, Terms, []);
+ {error, Reason} ->
+ error({reading_app_file, [AppF, Reason]})
+ end.
+
+otp_root() ->
+ {ok, [[Root]]} = init:get_argument(root),
+ filename:join(Root, "lib").
+
+%% Modified from code_server:get_user_lib_dirs():
+
+%% @spec lib_dirs() -> [string()]
+%% @equiv lib_dirs(concat("ERL_SETUP_LIBS", "ERL_LIBS"))
+lib_dirs() ->
+ A = lib_dirs("ERL_SETUP_LIBS"),
+ B = lib_dirs("ERL_LIBS"),
+ A ++ (B -- A).
+
+%% @spec lib_dirs(Env::string()) -> [string()]
+%% @doc Returns an expanded list of application directories under a lib path
+%%
+%% This function expands the (ebin/) directories under e.g. `$ERL_SETUP_LIBS' or
+%% `$ERL_LIBS'. `$ERL_SETUP_LIB' has the same syntax and semantics as
+%% `$ERL_LIBS', but is (hopefully) only recognized by the `setup' application.
+%% This can be useful e.g. when keeping a special 'extensions' or 'plugin'
+%% root that is handled via `setup', but not treated as part of the normal
+%% 'automatic code loading path'.
+%% @end
+lib_dirs(Env) ->
+ case os:getenv(Env) of
+ L when is_list(L) ->
+ LibDirs = split_paths(L, path_separator(), [], []),
+ get_user_lib_dirs_1(LibDirs);
+ false ->
+ []
+ end.
+
+path_separator() ->
+ case os:type() of
+ {win32, _} -> $;;
+ _ -> $:
+ end.
+
+get_user_lib_dirs_1([Dir|DirList]) ->
+ case erl_prim_loader:list_dir(Dir) of
+ {ok, Dirs} ->
+ {Paths,_Libs} = make_path(Dir, Dirs),
+ %% Only add paths trailing with ./ebin.
+ [P || P <- Paths, filename:basename(P) =:= "ebin"] ++
+ get_user_lib_dirs_1(DirList);
+ error ->
+ get_user_lib_dirs_1(DirList)
+ end;
+get_user_lib_dirs_1([]) -> [].
+
+split_paths([S|T], S, Path, Paths) ->
+ split_paths(T, S, [], [lists:reverse(Path) | Paths]);
+split_paths([C|T], S, Path, Paths) ->
+ split_paths(T, S, [C|Path], Paths);
+split_paths([], _S, Path, Paths) ->
+ lists:reverse(Paths, [lists:reverse(Path)]).
+
+
+make_path(BundleDir, Bundles0) ->
+ Bundles = choose_bundles(Bundles0),
+ make_path(BundleDir, Bundles, [], []).
+
+choose_bundles(Bundles) ->
+ ArchiveExt = archive_extension(),
+ Bs = lists:sort([create_bundle(B, ArchiveExt) || B <- Bundles]),
+ [FullName || {_Name,_NumVsn,FullName} <-
+ choose(lists:reverse(Bs), [], ArchiveExt)].
+
+create_bundle(FullName, ArchiveExt) ->
+ BaseName = filename:basename(FullName, ArchiveExt),
+ case split(BaseName, "-") of
+ [_, _|_] = Toks ->
+ VsnStr = lists:last(Toks),
+ case vsn_to_num(VsnStr) of
+ {ok, VsnNum} ->
+ Name = join(lists:sublist(Toks, length(Toks)-1),"-"),
+ {Name,VsnNum,FullName};
+ false ->
+ {FullName,[0],FullName}
+ end;
+ _ ->
+ {FullName,[0],FullName}
+ end.
+
+%% Convert "X.Y.Z. ..." to [K, L, M| ...]
+vsn_to_num(Vsn) ->
+ case is_vsn(Vsn) of
+ true ->
+ {ok, [list_to_integer(S) || S <- split(Vsn, ".")]};
+ _ ->
+ false
+ end.
+
+is_vsn(Str) when is_list(Str) ->
+ Vsns = split(Str, "."),
+ lists:all(fun is_numstr/1, Vsns).
+
+is_numstr(Cs) ->
+ lists:all(fun (C) when $0 =< C, C =< $9 -> true;
+ (_) -> false
+ end, Cs).
+
+split(Cs, S) ->
+ split1(Cs, S, []).
+
+split1([C|S], Seps, Toks) ->
+ case lists:member(C, Seps) of
+ true -> split1(S, Seps, Toks);
+ false -> split2(S, Seps, Toks, [C])
+ end;
+split1([], _Seps, Toks) ->
+ lists:reverse(Toks).
+
+split2([C|S], Seps, Toks, Cs) ->
+ case lists:member(C, Seps) of
+ true -> split1(S, Seps, [lists:reverse(Cs)|Toks]);
+ false -> split2(S, Seps, Toks, [C|Cs])
+ end;
+split2([], _Seps, Toks, Cs) ->
+ lists:reverse([lists:reverse(Cs)|Toks]).
+
+join([H1, H2| T], S) ->
+ H1 ++ S ++ join([H2| T], S);
+join([H], _) ->
+ H;
+join([], _) ->
+ [].
+
+choose([{Name,NumVsn,NewFullName}=New|Bs], Acc, ArchiveExt) ->
+ case lists:keyfind(Name, 1, Acc) of
+ {_, NV, OldFullName} when NV =:= NumVsn ->
+ case filename:extension(OldFullName) =:= ArchiveExt of
+ false ->
+ choose(Bs,Acc, ArchiveExt);
+ true ->
+ Acc2 = lists:keystore(Name, 1, Acc, New),
+ choose(Bs,Acc2, ArchiveExt)
+ end;
+ {_, _, _} ->
+ choose(Bs,Acc, ArchiveExt);
+ false ->
+ choose(Bs,[{Name,NumVsn,NewFullName}|Acc], ArchiveExt)
+ end;
+choose([],Acc, _ArchiveExt) ->
+ Acc.
+
+make_path(_,[],Res,Bs) ->
+ {Res,Bs};
+make_path(BundleDir,[Bundle|Tail],Res,Bs) ->
+ Dir = filename:append(BundleDir,Bundle),
+ Ebin = filename:append(Dir,"ebin"),
+ %% First try with /ebin
+ case erl_prim_loader:read_file_info(Ebin) of
+ {ok,#file_info{type=directory}} ->
+ make_path(BundleDir,Tail,[Ebin|Res],[Bundle|Bs]);
+ _ ->
+ %% Second try with archive
+ Ext = archive_extension(),
+ Base = filename:basename(Dir, Ext),
+ Ebin2 = filename:join([filename:dirname(Dir), Base ++ Ext,
+ Base, "ebin"]),
+ Ebins =
+ case split(Base, "-") of
+ [_, _|_] = Toks ->
+ AppName = join(lists:sublist(Toks, length(Toks)-1),"-"),
+ Ebin3 = filename:join([filename:dirname(Dir), Base ++ Ext, AppName, "ebin"]),
+ [Ebin3, Ebin2, Dir];
+ _ ->
+ [Ebin2, Dir]
+ end,
+ try_ebin_dirs(Ebins,BundleDir,Tail,Res,Bundle, Bs)
+ end.
+
+try_ebin_dirs([Ebin | Ebins],BundleDir,Tail,Res,Bundle,Bs) ->
+ case erl_prim_loader:read_file_info(Ebin) of
+ {ok,#file_info{type=directory}} ->
+ make_path(BundleDir,Tail,[Ebin|Res],[Bundle|Bs]);
+ _ ->
+ try_ebin_dirs(Ebins,BundleDir,Tail,Res,Bundle,Bs)
+ end;
+try_ebin_dirs([],BundleDir,Tail,Res,_Bundle,Bs) ->
+ make_path(BundleDir,Tail,Res,Bs).
+
+archive_extension() ->
+ init:archive_extension().
+
+
+read_config_script(F, Name, Opts) ->
+ Dir = filename:dirname(F),
+ BaseName = filename:basename(F),
+ case file:script(F, script_vars([{'Name', Name},
+ {'SCRIPT', BaseName},
+ {'CWD', filename:absname(Dir)},
+ {'OPTIONS', Opts}])) of
+ {ok, Conf} when is_list(Conf) ->
+ expand_config_script(Conf, Name, lists:reverse(Opts));
+ Error ->
+ setup_lib:abort("Error reading conf (~s): ~p~n", [F, Error])
+ end.
+
+expand_config_script([{include, F}|Opts], Name, Acc) ->
+ Acc1 = read_config_script(F, Name, lists:reverse(Acc)),
+ expand_config_script(Opts, Name, Acc1);
+expand_config_script([{include_lib, LibF}|Opts], Name, Acc) ->
+ case filename:split(LibF) of
+ [App|Tail] ->
+ try code:lib_dir(to_atom(App)) of
+ {error, bad_name} ->
+ setup_lib:abort(
+ "Error including conf (~s): no such lib (~s)~n",
+ [LibF, App]);
+ LibDir when is_list(LibDir) ->
+ FullName = filename:join([LibDir | Tail]),
+ Acc1 = read_config_script(
+ FullName, Name, lists:reverse(Acc)),
+ expand_config_script(Opts, Name, Acc1)
+ catch
+ error:_ ->
+ setup_lib:abort(
+ "Error including conf (~s): no such lib (~s)~n",
+ [LibF, App])
+ end;
+ [] ->
+ setup_lib:abort("Invalid include conf: no file specified~n", [])
+ end;
+expand_config_script([H|T], Name, Acc) ->
+ expand_config_script(T, Name, [H|Acc]);
+expand_config_script([], _, Acc) ->
+ lists:reverse(Acc).
+
+to_atom(B) when is_binary(B) ->
+ binary_to_existing_atom(B, latin1);
+to_atom(L) when is_list(L) ->
+ list_to_existing_atom(L).
+
+
+
+script_vars(Vs) ->
+ lists:foldl(fun({K,V}, Acc) ->
+ erl_eval:add_binding(K, V, Acc)
+ end, erl_eval:new_bindings(), Vs).
diff --git a/deps/setup/src/setup_gen.erl b/deps/setup/src/setup_gen.erl
new file mode 100644
index 0000000..80c4269
--- /dev/null
+++ b/deps/setup/src/setup_gen.erl
@@ -0,0 +1,770 @@
+%% -*- mode: erlang; indent-tabs-mode: nil; -*-
+%%=============================================================================
+%% Copyright 2014 Ulf Wiger
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%=============================================================================
+-module(setup_gen).
+-export([main/1, % escript-style
+ run/1, % when called from within erlang
+ help/0]). % prints help text.
+
+-import(setup_lib, [abort/2]).
+
+-define(if_verbose(Expr),
+ case get(verbose) of
+ true -> Expr;
+ _ -> ok
+ end).
+
+
+main([]) ->
+ help(),
+ halt(1);
+main([H]) when H=="-h"; H=="-help" ->
+ help(),
+ halt(0);
+main(["-" ++ _|_] = Args) ->
+ put(is_escript, true),
+ Opts = try options(Args)
+ catch
+ error:E ->
+ abort(E, [])
+ end,
+ run(Opts);
+main([Name, Config, Out| InArgs]) ->
+ put(is_escript, true),
+ Opts = try options(InArgs)
+ catch
+ error:E ->
+ abort(E, [])
+ end,
+ run([{name, Name}, {conf, Config}, {outdir, Out} | Opts]).
+
+help() ->
+ setup_lib:help().
+
+%% @spec run(Options) -> ok
+%% @doc Generates .rel file(s) and boot scripts for a given configuration.
+%%
+%% This function reads a configuration specification and generates the
+%% files needed to start a node from an OTP boot script. Optionally, it can
+%% also generate a 'setup' script, which contains the same applications, but
+%% only loaded (except the `setup' application, if present, which is started).
+%% This way, a node can be started with all paths set, and all environment
+%% variables defined, such that a database can be created, and other setup
+%% tasks be performed.
+%%
+%% Mandatory options:
+%% * `{name, Name}' - Name of the release (and of the .rel and .script files)
+%% * `{outdir, Dir}' - Where to put the generated files. Dir is created if not
+%% already present.
+%% * `{conf, Conf}' - Config file listing apps and perhaps other options
+%%
+%% Additional options:
+%% * `{apps, [App]}' - List of applications to include in the release. Only the
+%% first instance of this option is considered.
+%% * `{add_apps, [App]}' - Adds applications to the ones given in the `apps'
+%% option.
+%% * `{include, ConfigFile}' - include options from the given file. The file
+%% is processed using `file:script/2'.
+%% * `{include_lib, ConfigFile}' - As above, but ConfigFile is named as with
+%% the `-include_lib(...)' directive in Erlang
+%% source code.
+%% * ...
+%% @end
+%%
+run(Options) ->
+ %% dbg:tracer(),
+ %% dbg:tpl(?MODULE,x),
+ %% dbg:p(all,[c]),
+ case lists:keyfind(verbose, 1, Options) of
+ {_, true} -> put(verbose, true);
+ _ -> ignore
+ end,
+ ?if_verbose(io:fwrite("Options = ~p~n", [Options])),
+ Config = read_config(Options),
+ ?if_verbose(io:fwrite("Config = ~p~n", [Config])),
+ FullOpts = Options ++ Config,
+ {Name, OutDir, RelDir, RelVsn, GenTarget} = name_and_target(FullOpts),
+ ensure_dir(RelDir),
+ Roots = roots(FullOpts),
+ ?if_verbose(io:fwrite("Roots = ~p~n", [Roots])),
+ check_config(Config),
+ Env = env_vars(FullOpts),
+ InstEnv = install_env(Env, FullOpts),
+ add_paths(Roots, FullOpts),
+ Apps0 = apps(FullOpts, Env),
+ Rel = {release, {Name, RelVsn}, {erts, erts_vsn()}, [A || {A,_} <- Apps0]},
+ ?if_verbose(io:fwrite("Rel: ~p~n", [Rel])),
+ build_target_lib(GenTarget, OutDir, Apps0),
+ copy_erts(GenTarget, OutDir),
+ in_dir(RelDir,
+ fun() ->
+ setup_lib:write_eterm("start.rel", Rel),
+ make_boot("start", GenTarget, Roots),
+ setup_lib:write_eterm("sys.config", Env),
+ if_install(FullOpts,
+ fun() ->
+ InstRel = make_install_rel(Rel),
+ setup_lib:write_eterm(
+ "install.rel", InstRel),
+ setup_lib:write_eterm(
+ "install.config", InstEnv),
+ make_boot("install", GenTarget, Roots)
+ end, ok),
+ setup_lib:write_eterm("setup_gen.eterm", FullOpts)
+ end).
+
+name_and_target(FullOpts) ->
+ Name = option(name, FullOpts),
+ case proplists:get_value(target, FullOpts, false) of
+ false ->
+ RelDir = option(outdir, FullOpts),
+ RelVsn = rel_vsn(RelDir, FullOpts),
+ {Name, RelDir, RelDir, RelVsn, false};
+ TargetDir ->
+ RelVsn = option(vsn, FullOpts),
+ RelDir = filename:join([TargetDir, "releases", RelVsn]),
+ case filelib:is_dir(TargetDir) of
+ true -> abort("Target directory exists: ~s~n", [TargetDir]);
+ false -> ok
+ end,
+ {Name, TargetDir, RelDir, RelVsn, true}
+ end.
+
+
+build_target_lib(false, _, _) ->
+ ok;
+build_target_lib(true, Root, Apps) ->
+ LibRoot = filename:join(Root, "lib"),
+ file:make_dir(LibRoot),
+ in_dir(LibRoot,
+ fun() ->
+ lists:foreach(
+ fun({A, D}) ->
+ AppName = element(1, A),
+ AppVsn = element(2, A),
+ AppD = atom_to_list(AppName) ++ "-" ++ AppVsn,
+ Dir = filename:dirname(D),
+ copy_app(Dir, AppD)
+ end, Apps)
+ end).
+
+copy_erts(false, _) ->
+ ok;
+copy_erts(true, Root) ->
+ {ok, [[ErlRoot]]} = init:get_argument(root),
+ [Erts] = filelib:wildcard(filename:join(ErlRoot, "erts-*")),
+ BaseName = filename:basename(Erts),
+ file:make_dir(filename:join(Root, BaseName)),
+ Cmd = "cp -r " ++ Erts ++ "/bin " ++ Root ++ "/" ++ BaseName ++ "/",
+ os:cmd(Cmd).
+
+
+copy_app(A0, To) ->
+ file:make_dir(To),
+ os:cmd("cp -r " ++ A0 ++ "/priv " ++ To),
+ os:cmd("cp -r " ++ A0 ++ "/ebin " ++ To).
+
+
+if_install(Options, F, Else) ->
+ case proplists:get_value(install,Options,false) of
+ true ->
+ F();
+ _ ->
+ Else
+ end.
+
+options(["-target" , D|T]) -> [{target,D}|options(T)];
+options(["-name" , N|T]) -> [{name, N}|options(T)];
+options(["-root" , D|T]) -> [{root, D}|options(T)];
+options(["-out" , D|T]) -> [{outdir, D}|options(T)];
+options(["-relconf" , F|T]) -> [{relconf, F}|options(T)];
+options(["-conf" , F|T]) -> [{conf, F}|options(T)];
+%% options(["-target_subdir", D|T]) -> [{target_subdir, D}|options(T)];
+options(["-install"]) -> [{install, true}];
+options(["-install" | ["-" ++ _|_] = T]) -> [{install, true}|options(T)];
+options(["-install" , D|T]) -> [{install, mk_bool(D)}|options(T)];
+options(["-sys" , D|T]) -> [{sys, D}|options(T)];
+options(["-vsn" , D|T]) -> [{vsn, D}|options(T)];
+options(["-pa" , D|T]) -> [{pa, D}|options(T)];
+options(["-pz" , D|T]) -> [{pz, D}|options(T)];
+options(["-v" |T]) -> [{verbose, true}|options(T)];
+options(["-V" ++ VarName, ExprStr | T]) ->
+ Var = list_to_atom(VarName),
+ Term = parse_term(ExprStr),
+ [{var, Var, Term}|options(T)];
+options([_Other|_] = L) ->
+ abort("Unknown_option: ~p~n", [L]);
+options([]) ->
+ [].
+
+mk_bool(T) when T=="true" ; T=="1" -> true;
+mk_bool(F) when F=="false"; F=="0" -> false;
+mk_bool(Other) ->
+ abort("Expected truth value (~p)~n", [Other]).
+
+parse_term(Str) ->
+ case erl_scan:string(Str) of
+ {ok, Ts, _} ->
+ case erl_parse:parse_term(ensure_dot(Ts)) of
+ {ok, T} ->
+ T;
+ {error,_} = EParse ->
+ abort(EParse, [])
+ end;
+ {error,_,_} = EScan ->
+ abort(EScan, [])
+ end.
+
+ensure_dot(Ts) ->
+ case lists:reverse(Ts) of
+ [{dot,_}|_] ->
+ Ts;
+ Rev ->
+ lists:reverse([{dot,1}|Rev])
+ end.
+
+%% target_dir(RelDir, Config) ->
+%% D = case proplists:get_value(target_subdir, Config) of
+%% undefined ->
+%% RelDir;
+%% Sub ->
+%% filename:join(RelDir, Sub)
+%% end,
+%% ensure_dir(D),
+%% D.
+
+ensure_dir(D) ->
+ case filelib:is_dir(D) of
+ true ->
+ ok;
+ false ->
+ case filelib:ensure_dir(D) of
+ ok ->
+ case file:make_dir(D) of
+ ok ->
+ ok;
+ MakeErr ->
+ abort("Could not create ~s (~p)~n", [D, MakeErr])
+ end;
+ EnsureErr ->
+ abort("Parent of ~s could not be created or is not "
+ "writeable (~p)~n", [D, EnsureErr])
+ end
+ end.
+
+read_config(Opts) ->
+ case lists:keyfind(conf, 1, Opts) of
+ false ->
+ read_rel_config(Opts);
+ {_, F} ->
+ Name = option(name, Opts),
+ setup:read_config_script(F, Name, Opts)
+ end.
+
+read_rel_config(Opts) ->
+ case lists:keyfind(relconf, 1, Opts) of
+ {relconf, F} ->
+ Name = option(name, Opts),
+ case file:consult(F) of
+ {ok, Conf} ->
+ ?if_verbose(io:fwrite("Relconf = ~p~n", [Conf])),
+ SysConf = option(sys, Conf),
+ LibDirs = option(lib_dirs, SysConf),
+ TargetOpt = rel_conf_target_dir(Conf, Opts),
+ case [{V,As} || {rel,N,V,As} <- SysConf,
+ N == Name] of
+ [] ->
+ abort("No matching 'rel' (~w) in ~s~n", [Name, F]);
+ [{V,Apps}] ->
+ TargetOpt ++
+ [{vsn, V},
+ {apps, Apps} | [{root, D} || D <- LibDirs]]
+ end;
+ Error ->
+ abort("Error reading relconf ~s:~n"
+ "~p~n", [F, Error])
+ end;
+ false ->
+ abort("No usable config file~n", [])
+ end.
+
+rel_conf_target_dir(Conf, Opts) ->
+ case lists:keyfind(target, 1, Opts) of
+ {_, _} ->
+ %% The 'target' option overrides whatever is in the relconf
+ [];
+ false ->
+ case lists:keyfind(target_dir, 1, Conf) of
+ false ->
+ [];
+ {_, TargetDir} ->
+ [{target, TargetDir}]
+ end
+ end.
+
+roots(Opts) ->
+ [R || {root, R} <- Opts].
+
+check_config(Conf) ->
+ _ = [mandatory(K, Conf) || K <- [apps]],
+ ok.
+
+option(K, Opts) ->
+ case lists:keyfind(K, 1, Opts) of
+ {_, V} ->
+ V;
+ false ->
+ abort("Mandatory: -~s~n", [atom_to_list(K)])
+ end.
+
+env_vars(Options) ->
+ Env0 = case proplists:get_value(sys, Options) of
+ undefined ->
+ [];
+ Sys ->
+ case file:consult(Sys) of
+ {ok, [E]} ->
+ E;
+ {error, Reason} ->
+ abort("Error reading ~s:~n"
+ "~p~n", [Sys, Reason])
+ end
+ end,
+ SetupEnv = if_install(Options, fun() -> [{setup,
+ [{conf,Options}]}]
+ end, []),
+ lists:foldl(
+ fun(E, Acc) ->
+ merge_env(E, Acc)
+ end, Env0, [E || {env, E} <- Options] ++ [SetupEnv]).
+
+install_env(Env, Options) ->
+ Dist =
+ case proplists:get_value(nodes, Options, []) of
+ [] -> [];
+ [_] -> [];
+ [_,_|_] = Nodes ->
+ [{sync_nodes_mandatory, Nodes},
+ {sync_nodes_timeout, infinity},
+ {distributed, [{setup, [hd(Nodes)]}]}]
+ end,
+ case lists:keyfind(kernel, 1, Env) of
+ false ->
+ [{kernel, Dist} | Env];
+ {_, KEnv} ->
+ Env1 = Dist ++
+ [E || {K,_} = E <- KEnv,
+ not lists:member(K, [sync_nodes_optional,
+ sync_nodes_mandatory,
+ sync_nodes_timeout,
+ distributed])],
+ lists:keyreplace(kernel, 1, Env, {kernel, Env1})
+ end.
+
+merge_env(E, Env) ->
+ lists:foldl(
+ fun({App, AEnv}, Acc) ->
+ case lists:keyfind(App, 1, Env) of
+ false ->
+ Acc ++ [{App, AEnv}];
+ {_, AEnv1} ->
+ New = {App, lists:foldl(
+ fun({K,V}, Acc1) ->
+ lists:keystore(K,1,Acc1,{K,V})
+ end, AEnv1, AEnv)},
+ lists:keyreplace(App, 1, Acc, New)
+ end
+ end, Env, E).
+
+
+mandatory(K, Conf) ->
+ case lists:keymember(K, 1, Conf) of
+ false ->
+ abort("missing mandatory config item: ~p~n", [K]);
+ true ->
+ ok
+ end.
+
+in_dir(D, F) ->
+ {ok, Old} = file:get_cwd(),
+ try file:set_cwd(D) of
+ ok ->
+ ?if_verbose(io:fwrite("entering directory ~s~n", [D])),
+ F();
+ Error ->
+ abort("Error entering rel dir (~p): ~p~n", [D,Error])
+ after
+ ok = file:set_cwd(Old)
+ end.
+
+-define(is_type(T), T==permanent;T==temporary;T==transient;T==load).
+
+apps(Options, Env) ->
+ %% Apps0 = proplists:get_value(apps, Options, [])
+ %% ++ lists:concat(proplists:get_all_values(add_apps, Options)),
+ {AddApps, RemoveApps} = add_remove_apps(Options, Env),
+ Apps0 = remove_apps(RemoveApps,
+ proplists:get_value(apps, Options, []) ++ AddApps),
+ Apps1 = trim_duplicates(
+ if_install(Options,
+ fun() ->
+ ensure_setup(Apps0)
+ end, Apps0)),
+ AppNames = app_names(Apps1),
+ ?if_verbose(io:fwrite("Apps1 = ~p~n", [Apps1])),
+ AppVsns = lists:flatmap(
+ fun(A) when is_atom(A) ->
+ {V,D} = app_vsn(A),
+ [{{A, V}, D}];
+ ({A,V}) when is_list(V) ->
+ {V1,D} = app_vsn(A, V),
+ [{{A, V1}, D}];
+ ({A,Type}) when ?is_type(Type) ->
+ {V1,D} = app_vsn(A),
+ [{{A, V1, Type}, D}];
+ ({A,V,Type}) when ?is_type(Type) ->
+ {V1,D} = app_vsn(A, V),
+ [{{A, V1, Type}, D}];
+ ({A,V,Incl}) when is_list(Incl) ->
+ {V1, D} = app_vsn(A, V),
+ expand_included(Incl, AppNames)
+ ++ [{{A, V1, Incl}, D}];
+ ({A,V,Type,Incl}) when ?is_type(Type) ->
+ {V1, D} = app_vsn(A, V),
+ expand_included(Incl, AppNames)
+ ++ [{{A, V1, Type, Incl}, D}]
+ end, sort_apps(Options, Apps1)),
+ ?if_verbose(io:fwrite("AppVsns = ~p~n", [AppVsns])),
+ %% setup_is_load_only(replace_versions(AppVsns, Apps1)).
+ setup_is_load_only(AppVsns).
+
+add_remove_apps(Options, _Env) ->
+ lists:foldl(
+ fun({add_apps, As}, {Incl, Excl}) ->
+ {add_to_set(As, Incl), del_from_set(As, Excl)};
+ ({remove_apps, As}, {Incl, Excl}) ->
+ {del_from_set(As, Incl), add_to_set(app_names(As), Excl)};
+ (_, Acc) ->
+ Acc
+ end, {[], []}, Options).
+
+sort_apps(Options, Apps) ->
+ lists:foldl(fun({sort_app, A, Before}, Acc) ->
+ case is_in_set(A, Acc) of
+ {true, App} ->
+ insert_before(Acc -- [App], App, Before);
+ false ->
+ abort("Cannot re-sort ~p - not found~n", [A])
+ end;
+ (_, Acc) ->
+ Acc
+ end, Apps, Options).
+
+insert_before([H|T], App, Before) ->
+ case is_in_set(H, Before) of
+ {true, _} ->
+ [App, H|T];
+ false ->
+ [H|insert_before(T, App, Before)]
+ end.
+
+add_to_set(As, Set) ->
+ lists:foldl(fun(A, Acc) ->
+ case is_in_set(A, Acc) of
+ {true, Prev} ->
+ lists:delete(Prev, Acc) ++ [A];
+ false ->
+ Acc ++ [A]
+ end
+ end, Set, As).
+
+del_from_set(As, Set) ->
+ lists:foldl(fun(A, Acc) ->
+ case is_in_set(A, Acc) of
+ {true, Prev} ->
+ lists:delete(Prev, Acc);
+ false ->
+ Acc
+ end
+ end, Set, As).
+
+is_in_set(Entry, Set) ->
+ A = if is_tuple(Entry) -> element(1, Entry);
+ is_atom(Entry) -> Entry
+ end,
+ name_in_set(A, Set).
+
+app_name(A) when is_atom(A) -> A;
+app_name(T) when is_tuple(T) -> element(1, T).
+
+app_names(As) ->
+ [app_name(A) || A <- As].
+
+name_in_set(A, [A|_]) -> {true, A};
+name_in_set(A, [H|_]) when element(1,H) == A -> {true, H};
+name_in_set(A, [_|T]) ->
+ name_in_set(A, T);
+name_in_set(_, []) ->
+ false.
+
+
+remove_apps(Remove, Apps) ->
+ lists:filter(
+ fun(Entry) ->
+ A = case Entry of
+ Am when is_atom(Am) -> Am;
+ T when is_tuple(T) -> element(1,T)
+ end,
+ not lists:member(A, Remove)
+ end, Apps).
+
+
+
+trim_duplicates([A|As0]) when is_atom(A) ->
+ As1 = [Ax || Ax <- As0, Ax =/= A],
+ case lists:keymember(A, 1, As0) of
+ false ->
+ [A|trim_duplicates(As1)];
+ true ->
+ %% a more well-defined entry exists; use that one.
+ trim_duplicates(As1)
+ end;
+trim_duplicates([At|As0]) when is_tuple(At) ->
+ %% Remove all exact duplicates
+ As1 = [Ax || Ax <- As0, Ax =/= At],
+ %% If other detailed entries (though not duplicates) exist, we should
+ %% perhaps try to combine them. For now, let's just abort.
+ case [Ay || Ay <- As1, element(1,Ay) == element(1,At)] of
+ [] ->
+ [At|trim_duplicates(As0)];
+ [_|_] = Duplicates ->
+ abort("Conflicting app entries: ~p~n", [[At|Duplicates]])
+ end;
+trim_duplicates([]) ->
+ [].
+
+
+expand_included(Incl, AppNames) ->
+ R = case Incl -- AppNames of
+ [] ->
+ [];
+ Implicit ->
+ [{A, app_vsn(A), load} || A <- Implicit]
+ end,
+ ?if_verbose(io:fwrite("expand_included(~p, ~p) -> ~p~n",
+ [Incl, AppNames, R])),
+ R.
+
+ensure_setup([setup|_] = As) -> As;
+ensure_setup([A|_] = As) when element(1,A) == setup -> As;
+ensure_setup([H|T]) -> [H|ensure_setup(T)];
+ensure_setup([]) ->
+ [setup].
+
+setup_is_load_only(Apps) ->
+ lists:map(fun({{setup,V}, D}) ->
+ {{setup,V,load}, D};
+ (A) ->
+ A
+ end, Apps).
+
+add_paths(Roots, Opts) ->
+ APaths = proplists:get_all_values(pa, Opts),
+ _ = [ true = code:add_patha(P) || P <- APaths ],
+
+ ZPaths = proplists:get_all_values(pz, Opts),
+ _ = [ true = code:add_pathz(P) || P <- ZPaths ],
+
+ Paths = case proplists:get_value(wild_roots, Opts, false) of
+ true ->
+ lists:foldl(fun(R, Acc) ->
+ expand_root(R, Acc)
+ end, [], Roots);
+ false ->
+ lists:concat(
+ lists:map(fun(R) ->
+ case filelib:wildcard(
+ filename:join(R, "lib/*/ebin")) of
+ [] ->
+ filelib:wildcard(
+ filename:join(R, "*/ebin"))
+ end
+ end, Roots))
+ end,
+ ?if_verbose(io:fwrite("Paths = ~p~n", [Paths])),
+ Res = code:set_path(Paths ++ (code:get_path() -- Paths)),
+ %% Res = code:add_pathsa(Paths -- code:get_path()),
+ ?if_verbose(io:fwrite("add path Res = ~p~n", [Res])).
+
+expand_root(R, Acc) ->
+ case filename:basename(R) of
+ "ebin" ->
+ [R|Acc];
+ _ ->
+ case file:list_dir(R) of
+ {ok, Fs} ->
+ lists:foldl(fun(F, Acc1) ->
+ expand_root(filename:join(R, F), Acc1)
+ end, Acc, Fs);
+ {error,enotdir} ->
+ Acc;
+ {error,_} = E ->
+ ?if_verbose(io:fwrite("warning: ~p (~s)~n", [E, R])),
+ Acc
+ end
+ end.
+
+rel_vsn(RelDir, Options) ->
+ case proplists:get_value(vsn, Options) of
+ undefined ->
+ Dir =
+ case RelDir of
+ "." ->
+ {ok,Cwd} = file:get_cwd(),
+ Cwd;
+ D ->
+ D
+ end,
+ filename:basename(Dir);
+ V when is_list(V) ->
+ V;
+ Other ->
+ abort("Invalid release version ~p~n", [Other])
+ end.
+
+erts_vsn() ->
+ erlang:system_info(version).
+
+app_vsn(A) ->
+ app_vsn(A, latest).
+ %% AName = if is_atom(A) -> A;
+ %% true -> element(1, A)
+ %% end,
+ %% D = code:lib_dir(AName),
+ %% AppFile = filename:join(D, "ebin/" ++ atom_to_list(AName) ++ ".app"),
+ %% case file:consult(AppFile) of
+ %% {ok, [{application, _, Opts}]} ->
+ %% V = proplists:get_value(vsn, Opts),
+ %% ?if_verbose(io:fwrite("app_vsn(~p) -> ~p~n", [A,V])),
+ %% V;
+ %% Other ->
+ %% abort("Oops reading .app file (~p): ~p~n", [AppFile, Other])
+ %% end.
+
+app_vsn(A, V) ->
+ AppStr = atom_to_list(A),
+ Path = code:get_path(),
+ Found = [D || D <- Path, is_app(AppStr, D)],
+ Sorted = setup_lib:sort_vsns(lists:usort(Found), AppStr),
+ ?if_verbose(io:fwrite("Sorted = ~p~n", [Sorted])),
+ match_app_vsn(Sorted, V, AppStr).
+
+match_app_vsn(Vsns, latest, App) ->
+ %% element(1, lists:last(Vsns));
+ case Vsns of
+ [] ->
+ abort("No version of ~s found~n", [App]);
+ [_|_] ->
+ lists:last(Vsns)
+ end;
+match_app_vsn(Vsns, V, App) when is_list(V) ->
+ case [Pair || {V1, _} = Pair <- Vsns,
+ V == V1] of
+ [FoundV] ->
+ FoundV;
+ [] ->
+ abort("Cannot find version ~s of ~s~n", [V, App])
+ end.
+
+
+is_app(A, D) ->
+ case re:run(D, A ++ "[^/]*/ebin\$") of
+ {match, _} ->
+ true;
+ nomatch ->
+ false
+ end.
+
+%% replace_versions([App|Apps], [H|T]) ->
+%% A = element(1, App),
+%% V = element(2, App),
+%% Res =
+%% if is_atom(H) ->
+%% A = H, % assertion
+%% {A, V};
+%% true ->
+%% A = element(1, H), % assertion
+%% setelement(2, H, V)
+%% end,
+%% [Res | replace_versions(Apps, T)];
+%% replace_versions([], []) ->
+%% [].
+
+make_boot(Rel, GenTarget, Roots) ->
+ Path = path(Roots),
+ Vars =
+ if GenTarget -> [];
+ true ->
+ {Vs, _} = lists:mapfoldl(
+ fun(R, N) ->
+ V = var_name(N),
+ {{V, R}, N+1}
+ end, 1, Roots),
+ Vs
+ end,
+ ?if_verbose(io:fwrite("Path = ~p~n", [Path])),
+ Opts = if GenTarget -> [no_module_tests];
+ true -> [no_module_tests, local, {variables, Vars}]
+ end,
+ Res = systools:make_script(Rel, [{path, path(Roots)}|Opts]),
+ ?if_verbose(io:fwrite("make_script() -> ~p~n", [Res])).
+
+
+make_install_rel({release, R, Erts, Apps}) ->
+ Apps1 =
+ lists:map(
+ fun({setup,V,load}) ->
+ {setup, V};
+ (A) ->
+ case lists:member(element(1,A), [stdlib,kernel,sasl]) of
+ true ->
+ A;
+ false ->
+ case A of
+ {Nm,Vsn} ->
+ {Nm,Vsn,load};
+ {Nm,Vsn,Inc} when is_list(Inc) ->
+ {Nm,Vsn,load,Inc};
+ _ ->
+ A
+ end
+ end
+ end, Apps),
+ %% Apps2 = case app_vsn(setup) of
+ %% undefined ->
+ %% Apps1;
+ %% V ->
+ %% Apps1 ++ [{setup, V}]
+ %% end,
+ {release, R, Erts, Apps1}.
+
+
+path(Roots) ->
+ [filename:join(R, "lib/*/ebin") || R <- Roots].
+
+
+var_name(N) ->
+ "V" ++ integer_to_list(N).
diff --git a/deps/setup/src/setup_lib.erl b/deps/setup/src/setup_lib.erl
new file mode 100644
index 0000000..4d80fe2
--- /dev/null
+++ b/deps/setup/src/setup_lib.erl
@@ -0,0 +1,171 @@
+%% -*- erlang -*-
+%%==============================================================================
+%% Copyright 2014 Ulf Wiger
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%==============================================================================
+-module(setup_lib).
+
+-export([is_string/1,
+ sort_vsns/2,
+ compare_vsns/2,
+ releases_dir/0,
+ write_eterm/2,
+ write_script/2,
+ abort/2, help/0]).
+
+is_string(L) ->
+ lists:all(fun(X) when 0 =< X, X =< 255 -> true;
+ (_) -> false
+ end, L).
+
+write_eterm(F, Term) ->
+ case file:open(F, [write]) of
+ {ok, Fd} ->
+ try
+ io:fwrite(Fd, "~p.~n", [Term])
+ after
+ ok = file:close(Fd)
+ end;
+ Error ->
+ abort("Error writing file (~s): ~p~n", [F, Error])
+ end.
+
+write_script(F, Script) ->
+ case file:open(F, [write]) of
+ {ok, Fd} ->
+ try
+ [io:fwrite(Fd, "~p.~n", [Term]) || Term <- Script]
+ after
+ ok = file:close(Fd)
+ end;
+ Error ->
+ abort("Error writing file (~s): ~p~n", [F, Error])
+ end.
+
+abort(Fmt, Args) ->
+ E = io_lib:fwrite(Fmt, Args),
+ case get(is_escript) of
+ true ->
+ io:fwrite(E),
+ help(),
+ halt(1);
+ _ ->
+ erlang:error(lists:flatten(E))
+ end.
+
+sort_vsns(Dirs, AppStr) ->
+ AppF = AppStr ++ ".app",
+ lists:sort(fun({Va,_}, {Vb,_}) ->
+ compare_vsns(Va, Vb)
+ end,
+ lists:foldr(
+ fun(D, Acc) ->
+ case file:consult(
+ filename:join(D, AppF)) of
+ {ok, [{_, _, Attrs}]} ->
+ {_, Vsn} = lists:keyfind(vsn, 1, Attrs),
+ [{Vsn, D} | Acc];
+ _ ->
+ Acc
+ end
+ end, [], Dirs)).
+
+compare_vsns(V1, V2) ->
+ ToS = fun(V) ->
+ [pad_x(X) || X <- string:tokens(V, ".")]
+ end,
+ ToS(V1) < ToS(V2).
+
+pad_x(S) ->
+ lists:duplicate(30 - length(S), $0) ++ [flip(C) || C <- S].
+
+flip(C) when $a =< C, C =< $z -> $A + (C - $a);
+flip(C) when $A =< C, C =< $Z -> $a + (C - $A);
+flip(C) -> C.
+
+%% Almost verbatim from release_handler:init/1
+releases_dir() ->
+ {ok, [[Root]]} = init:get_argument(root),
+ {CliDir, _Masters} = is_client(),
+ case application:get_env(sasl, releases_dir) of
+ undefined ->
+ case os:getenv("RELDIR") of
+ R when R==false; R==[] ->
+ if CliDir == false ->
+ filename:join([Root, "releases"]);
+ true ->
+ filename:join([CliDir, "releases"])
+ end;
+ RELDIR ->
+ RELDIR
+ end;
+ {ok, Dir} ->
+ Dir
+ end.
+
+%% Copy-pasted from release_handler.erl
+is_client() ->
+ case application:get_env(masters) of
+ {ok, Masters} ->
+ Alive = is_alive(),
+ case atom_list(Masters) of
+ true when Alive == true ->
+ case application:get_env(client_directory) of
+ {ok, ClientDir} ->
+ case int_list(ClientDir) of
+ true ->
+ {ClientDir, Masters};
+ _ ->
+ exit({bad_parameter, client_directory,
+ ClientDir})
+ end;
+ _ ->
+ {false, false}
+ end;
+ _ ->
+ exit({bad_parameter, masters, Masters})
+ end;
+ _ ->
+ {false, false}
+ end.
+
+atom_list([A|T]) when is_atom(A) -> atom_list(T);
+atom_list([]) -> true;
+atom_list(_) -> false.
+
+int_list([I|T]) when is_integer(I) -> int_list(T);
+int_list([]) -> true;
+int_list(_) -> false.
+%% ... end copy-paste
+
+
+help() ->
+ io:fwrite(
+ "Usage: escript setup_gen.beam Name Conf Outdir [Options]~n"
+ " or:~n"
+ " escript setup_gen.beam Options~n~n"
+ "Name : Name of release (for .rel file)~n"
+ "Conf : Name of .conf file (file:script/2 format)~n"
+ "Outdir: Where to write generated files~n~n"
+ "-name Name : Name of release (for .rel file)~n"
+ "-root Dir : Installation root directories"
+ " (multiple -root options allowed)~n"
+ "-conf F : setup-style Conf file~n"
+ "-relconf F : RelTool-style config file~n"
+ "-out OutDir: Where to write generated files~n"
+ "-sys F : Name of pre-existing sys.config file~n"
+ "-vsn V : System version (otherwise derived from outdir)~n"
+ "-install B : B:: true|false - whether to create install boot script~n"
+ "-v : Verbose - generate lots of output~n"
+ , []).
diff --git a/deps/setup/xtest/test.conf b/deps/setup/xtest/test.conf
new file mode 100644
index 0000000..6212fbb
--- /dev/null
+++ b/deps/setup/xtest/test.conf
@@ -0,0 +1,30 @@
+%% -*- erlang -*-
+%%==============================================================================
+%% Copyright 2014 Ulf Wiger
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%==============================================================================
+RelDir = filename:join(CWD, "releases").
+[
+ {name, "test"},
+ {outdir, filename:join(RelDir, "1")},
+ {root, CWD},
+ {env, [{sasl, [{releases_dir, RelDir}]}
+ ]},
+ {apps,
+ [kernel,
+ stdlib,
+ sasl,
+ {testapp, "1", [snmp]}
+ ]}
+].
diff --git a/deps/setup/xtest/testapp-1/src/testapp.app.src b/deps/setup/xtest/testapp-1/src/testapp.app.src
new file mode 100644
index 0000000..92a2d67
--- /dev/null
+++ b/deps/setup/xtest/testapp-1/src/testapp.app.src
@@ -0,0 +1,14 @@
+%% -*- erlang -*-
+{application, testapp,
+ [
+ {description, ""},
+ {vsn, "1"},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]},
+ {mod, { testapp_app, []}},
+ {included_applications, [os_mon, snmp]},
+ {env, []}
+ ]}.
diff --git a/deps/setup/xtest/testapp-1/src/testapp_app.erl b/deps/setup/xtest/testapp-1/src/testapp_app.erl
new file mode 100644
index 0000000..c5f9795
--- /dev/null
+++ b/deps/setup/xtest/testapp-1/src/testapp_app.erl
@@ -0,0 +1,16 @@
+-module(testapp_app).
+
+-behaviour(application).
+
+%% Application callbacks
+-export([start/2, stop/1]).
+
+%% ===================================================================
+%% Application callbacks
+%% ===================================================================
+
+start(_StartType, _StartArgs) ->
+ testapp_sup:start_link().
+
+stop(_State) ->
+ ok.
diff --git a/deps/setup/xtest/testapp-1/src/testapp_sup.erl b/deps/setup/xtest/testapp-1/src/testapp_sup.erl
new file mode 100644
index 0000000..9808c9f
--- /dev/null
+++ b/deps/setup/xtest/testapp-1/src/testapp_sup.erl
@@ -0,0 +1,27 @@
+
+-module(testapp_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+%% Helper macro for declaring children of supervisor
+-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
+
+%% ===================================================================
+%% API functions
+%% ===================================================================
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%% ===================================================================
+%% Supervisor callbacks
+%% ===================================================================
+
+init([]) ->
+ {ok, { {one_for_one, 5, 10}, []} }.
diff --git a/deps/setup/xtest/testapp-2/src/testapp.app.src b/deps/setup/xtest/testapp-2/src/testapp.app.src
new file mode 100644
index 0000000..c5fd064
--- /dev/null
+++ b/deps/setup/xtest/testapp-2/src/testapp.app.src
@@ -0,0 +1,14 @@
+%% -*- erlang -*-
+{application, testapp,
+ [
+ {description, ""},
+ {vsn, "2"},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]},
+ {mod, { testapp_app, []}},
+ {included_applications, [os_mon, snmp]},
+ {env, []}
+ ]}.
diff --git a/deps/setup/xtest/testapp-2/src/testapp_app.erl b/deps/setup/xtest/testapp-2/src/testapp_app.erl
new file mode 100644
index 0000000..c5f9795
--- /dev/null
+++ b/deps/setup/xtest/testapp-2/src/testapp_app.erl
@@ -0,0 +1,16 @@
+-module(testapp_app).
+
+-behaviour(application).
+
+%% Application callbacks
+-export([start/2, stop/1]).
+
+%% ===================================================================
+%% Application callbacks
+%% ===================================================================
+
+start(_StartType, _StartArgs) ->
+ testapp_sup:start_link().
+
+stop(_State) ->
+ ok.
diff --git a/deps/setup/xtest/testapp-2/src/testapp_sup.erl b/deps/setup/xtest/testapp-2/src/testapp_sup.erl
new file mode 100644
index 0000000..9808c9f
--- /dev/null
+++ b/deps/setup/xtest/testapp-2/src/testapp_sup.erl
@@ -0,0 +1,27 @@
+
+-module(testapp_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+%% Helper macro for declaring children of supervisor
+-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
+
+%% ===================================================================
+%% API functions
+%% ===================================================================
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%% ===================================================================
+%% Supervisor callbacks
+%% ===================================================================
+
+init([]) ->
+ {ok, { {one_for_one, 5, 10}, []} }.
diff --git a/deps/uart/README.md b/deps/uart/README.md
new file mode 100644
index 0000000..65f4eb0
--- /dev/null
+++ b/deps/uart/README.md
@@ -0,0 +1,80 @@
+
+
+#The uart application#
+
+
+
+### Interface description ###
+
+
+<table width="100%" border="0" summary="list of modules">
+<tr><td><a href="http://github.com/tonyrog/uart/blob/master/doc/uart.md" class="module">uart</a></td></tr></table>
+
+### Dependencies ###
+
+To build uart you will need a working installation of Erlang R15B (or
+later).<br/>
+Information on building and installing [Erlang/OTP](http://www.erlang.org)
+can be found [here](https://github.com/erlang/otp/wiki/Installation)
+([more info](https://github.com/erlang/otp/blob/master/INSTALL.md)).
+
+uart is built using rebar that can be found [here](https://github.com/rebar/rebar), with building instructions [here](https://github.com/rebar/rebar/wiki/Building-rebar). rebar's dynamic configuration mechanism, described [here](https://github.com/rebar/rebar/wiki/Dynamic-configuration), is used so the environment variable `REBAR_DEPS` should be set to the directory where your erlang applications are located.
+
+uart also requires the following applications to be installed:
+<ul>
+<li>dthread - https://github.com/tonyrog/dthread</li>
+</ul>
+
+If you use ftdi devices you can find drivers at http://ftdichip.com/.
+
+### Downloading
+
+Clone the repository in a suitable location:
+
+```sh
+$ git clone git://github.com/tonyrog/uart.git
+```
+### Configurating
+#### Concepts
+
+...
+
+#### Files
+
+...
+
+### Building
+
+Compile:
+
+```sh
+$ cd uart
+$ rebar compile
+...
+==> uart (compile)
+```
+
+### Testing
+
+#### Without hardware
+
+Basic test of uart can be done without having any dongles:
+
+```sh
+$ cd uart
+$ rebar ct
+...
+==> uart (ct)
+```
+#### With hardware
+
+If you want to test with hardware you must configure the appropriate devices in [uart.cfg](http://github.com/tonyrog/uart/blob/master/test/uart.cfg) and then run:
+```sh
+$ cd uart/test
+$ ct_run -spec uart_hw.spec
+...
+
+```
+
+
+
diff --git a/deps/uart/c_src/Makefile.cross b/deps/uart/c_src/Makefile.cross
new file mode 100644
index 0000000..b99a892
--- /dev/null
+++ b/deps/uart/c_src/Makefile.cross
@@ -0,0 +1,51 @@
+#
+# Make the uart_drv
+# example usage: TARGET=arm-none-linux-gnueabi make -f Makefile.cross
+#
+
+OSNAME := $(shell uname -s)
+MACHINE := $(shell uname -m)
+ERLDIR := $(shell erl -noshell -eval "io:format([126,115,126,110],[code:root_dir()])" -s erlang halt)
+ERL_C_INCLUDE_DIR := $(ERLDIR)/usr/include
+CC = $(TARGET)-gcc
+LD = $(TARGET)-ld
+PRIVDIR=../priv/$(TARGET)
+OBJDIR=$(TARGET)
+DTHREAD=../../dthread
+
+OBJS = \
+ $(OBJDIR)/uart_drv.o \
+ $(OBJDIR)/uart_buf.o \
+ $(OBJDIR)/uart_queue.o \
+ $(OBJDIR)/uart_com_state.o \
+ $(OBJDIR)/uart_modem_state.o \
+ $(OBJDIR)/uart_options.o \
+ $(OBJDIR)/uart_message.o \
+ $(OBJDIR)/uart_unix.o
+
+# fixme: make library
+DTHREAD_DRV_OBJS = \
+ $(DTHREAD)/c_src/$(TARGET)/dlib.o \
+ $(DTHREAD)/c_src/$(TARGET)/dterm.o \
+ $(DTHREAD)/c_src/$(TARGET)/dthread.o
+
+LDFLAGS = -shared -fpic -lutil
+
+all : $(OBJDIR) $(PRIVDIR) $(PRIVDIR)/uart_drv.so
+
+override CFLAGS += -Wall -Wextra -Wswitch-default -Wswitch-enum -I$(ERL_C_INCLUDE_DIR) -I $(DTHREAD)/include -DDLOG_DEFAULT=DLOG_NONE
+
+$(OBJDIR):
+ @mkdir -p $(OBJDIR)
+
+$(PRIVDIR):
+ @mkdir -p $(PRIVDIR)
+
+$(PRIVDIR)/uart_drv.so : $(OBJS)
+ $(CC) $(LDFLAGS) $(DTHREAD_DRV_OBJS) -o $@ $^
+
+$(OBJDIR)/%.o: %.c
+ $(CC) -c -o $@ $(CFLAGS) $<
+
+lean:
+ $(RM) -f $(PRIVDIR)/uart_drv.so $(OBJS)
diff --git a/deps/uart/c_src/Makefile.mingw b/deps/uart/c_src/Makefile.mingw
new file mode 100644
index 0000000..bd553ff
--- /dev/null
+++ b/deps/uart/c_src/Makefile.mingw
@@ -0,0 +1,47 @@
+#
+# Makefile for Mingw
+#
+OSNAME := $(shell uname -s)
+MACHINE := $(shell uname -m)
+ERLDIR := $(shell erl -noshell -eval "io:format([126,115,126,110],[code:root_dir()])" -s erlang halt)
+ERL_C_INCLUDE_DIR := $(ERLDIR)/usr/include
+CC = gcc
+PRIVDIR=../priv
+DTHREAD_LIB_DIR = ../../dthread
+
+CFLAGS += -D__WIN32__ -DWIN32 -D_MT -D_DLL -DEBUG_MEM -DDLOG_DEFAULT=DLOG_NONE
+CFLAGS += -Wall -Wextra -Wswitch-default -Wswitch-enum -D_THREAD_SAFE -D_REENTRANT -fno-common
+CFLAGS += -I$(ERL_C_INCLUDE_DIR)
+CFLAGS += -I$(DTHREAD_LIB_DIR)/include
+LDFLAGS += -Wl--enable-stdcall-fixup
+
+EXT = dll
+LD_SHARED := $(CC) -shared
+
+debug: CFLAGS += -g
+debug: LDFLAGS += -g
+
+UART_DRV = $(PRIVDIR)/uart_drv.$(EXT)
+DTHREAD_LIB_OBJS = \
+ $(DTHREAD_LIB_DIR)/c_src/dlib.obj \
+ $(DTHREAD_LIB_DIR)/c_src/dterm.obj \
+ $(DTHREAD_LIB_DIR)/c_src/dthread.obj
+
+UART_DRV_OBJS = \
+ dosmap.obj \
+ uart_drv.obj \
+ uart_buf.obj \
+ uart_queue.obj \
+ uart_com_state.obj \
+ uart_modem_state.obj \
+ uart_options.obj \
+ uart_message.obj \
+ uart_win32.obj
+
+debug release all: $(UART_DRV)
+
+$(UART_DRV): $(UART_DRV_OBJS)
+ $(LD_SHARED) -o $@ $(UART_DRV_OBJS) $(DTHREAD_LIB_OBJS) $(LDFLAGS)
+
+%.obj: %.c
+ $(CC) -c -o $@ $(CFLAGS) $< \ No newline at end of file
diff --git a/deps/uart/c_src/dosmap.c b/deps/uart/c_src/dosmap.c
new file mode 100644
index 0000000..d5a5a96
--- /dev/null
+++ b/deps/uart/c_src/dosmap.c
@@ -0,0 +1,281 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 1998-2009. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Erlang Public License,
+ * Version 1.1, (the "License"); you may not use this file except in
+ * compliance with the License. You should have received a copy of the
+ * Erlang Public License along with this software. If not, it can be
+ * retrieved online at http://www.erlang.org/.
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * %CopyrightEnd%
+ */
+/*
+ * _dosmaperr: maps Windows OS errors to Unix System V errno values
+ *
+ * Contributor: Michael Regen
+ */
+
+/* Only use for win32 if linking to MSVCR??.DLL and not if statically linking
+ to LIBCMT.LIB */
+#if defined(WIN32) && defined(_MT) && defined(_DLL)
+
+#include <errno.h>
+#include <winerror.h>
+#include <stdlib.h>
+
+/* Position in table = Windows OS error -> Posix errno
+** An exception for ERROR_NOT_ENOUGH_QUOTA - 1816 is in _dosmaperr
+*/
+static const unsigned char errMapTable[] = {
+ EINVAL, /* ERROR_SUCCESS 0 */
+ EINVAL, /* ERROR_INVALID_FUNCTION 1 */
+ ENOENT, /* ERROR_FILE_NOT_FOUND 2 */
+ ENOENT, /* ERROR_PATH_NOT_FOUND 3 */
+ EMFILE, /* ERROR_TOO_MANY_OPEN_FILES 4 */
+ EACCES, /* ERROR_ACCESS_DENIED 5 */
+ EBADF, /* ERROR_INVALID_HANDLE 6 */
+ ENOMEM, /* ERROR_ARENA_TRASHED 7 */
+ ENOMEM, /* ERROR_NOT_ENOUGH_MEMORY 8 */
+ ENOMEM, /* ERROR_INVALID_BLOCK 9 */
+ E2BIG, /* ERROR_BAD_ENVIRONMENT 10 */
+ ENOEXEC, /* ERROR_BAD_FORMAT 11 */
+ EINVAL, /* ERROR_INVALID_ACCESS 12 */
+ EINVAL, /* ERROR_INVALID_DATA 13 */
+ EINVAL, /* ERROR_OUTOFMEMORY 14 */
+ ENOENT, /* ERROR_INVALID_DRIVE 15 */
+ EACCES, /* ERROR_CURRENT_DIRECTORY 16 */
+ EXDEV, /* ERROR_NOT_SAME_DEVICE 17 */
+ ENOENT, /* ERROR_NO_MORE_FILES 18 */
+ EACCES, /* ERROR_WRITE_PROTECT 19 */
+ EACCES, /* ERROR_BAD_UNIT 20 */
+ EACCES, /* ERROR_NOT_READY 21 */
+ EACCES, /* ERROR_BAD_COMMAND 22 */
+ EACCES, /* ERROR_CRC 23 */
+ EACCES, /* ERROR_BAD_LENGTH 24 */
+ EACCES, /* ERROR_SEEK 25 */
+ EACCES, /* ERROR_NOT_DOS_DISK 26 */
+ EACCES, /* ERROR_SECTOR_NOT_FOUND 27 */
+ EACCES, /* ERROR_OUT_OF_PAPER 28 */
+ EACCES, /* ERROR_WRITE_FAULT 29 */
+ EACCES, /* ERROR_READ_FAULT 30 */
+ EACCES, /* ERROR_GEN_FAILURE 31 */
+ EACCES, /* ERROR_SHARING_VIOLATION 32 */
+ EACCES, /* ERROR_LOCK_VIOLATION 33 */
+ EACCES, /* ERROR_WRONG_DISK 34 */
+ EACCES, /* 35 */
+ EACCES, /* ERROR_SHARING_BUFFER_EXCEEDED 36 */
+ EINVAL, /* 37 */
+ EINVAL, /* ERROR_HANDLE_EOF 38 */
+ EINVAL, /* ERROR_HANDLE_DISK_FULL 39 */
+ EINVAL, /* 40 */
+ EINVAL, /* 41 */
+ EINVAL, /* 42 */
+ EINVAL, /* 43 */
+ EINVAL, /* 44 */
+ EINVAL, /* 45 */
+ EINVAL, /* 46 */
+ EINVAL, /* 47 */
+ EINVAL, /* 48 */
+ EINVAL, /* 49 */
+ EINVAL, /* ERROR_NOT_SUPPORTED 50 */
+ EINVAL, /* ERROR_REM_NOT_LIST 51 */
+ EINVAL, /* ERROR_DUP_NAME 52 */
+ ENOENT, /* ERROR_BAD_NETPATH 53 */
+ EINVAL, /* ERROR_NETWORK_BUSY 54 */
+ EINVAL, /* ERROR_DEV_NOT_EXIST 55 */
+ EINVAL, /* ERROR_TOO_MANY_CMDS 56 */
+ EINVAL, /* ERROR_ADAP_HDW_ERR 57 */
+ EINVAL, /* ERROR_BAD_NET_RESP 58 */
+ EINVAL, /* ERROR_UNEXP_NET_ERR 59 */
+ EINVAL, /* ERROR_BAD_REM_ADAP 60 */
+ EINVAL, /* ERROR_PRINTQ_FULL 61 */
+ EINVAL, /* ERROR_NO_SPOOL_SPACE 62 */
+ EINVAL, /* ERROR_PRINT_CANCELLED 63 */
+ EINVAL, /* ERROR_NETNAME_DELETED 64 */
+ EACCES, /* ERROR_NETWORK_ACCESS_DENIED 65 */
+ EINVAL, /* ERROR_BAD_DEV_TYPE 66 */
+ ENOENT, /* ERROR_BAD_NET_NAME 67 */
+ EINVAL, /* ERROR_TOO_MANY_NAMES 68 */
+ EINVAL, /* ERROR_TOO_MANY_SESS 69 */
+ EINVAL, /* ERROR_SHARING_PAUSED 70 */
+ EINVAL, /* ERROR_REQ_NOT_ACCEP 71 */
+ EINVAL, /* ERROR_REDIR_PAUSED 72 */
+ EINVAL, /* 73 */
+ EINVAL, /* 74 */
+ EINVAL, /* 75 */
+ EINVAL, /* 76 */
+ EINVAL, /* 77 */
+ EINVAL, /* 78 */
+ EINVAL, /* 79 */
+ EEXIST, /* ERROR_FILE_EXISTS 80 */
+ EINVAL, /* 81 */
+ EACCES, /* ERROR_CANNOT_MAKE 82 */
+ EACCES, /* ERROR_FAIL_I24 83 */
+ EINVAL, /* ERROR_OUT_OF_STRUCTURES 84 */
+ EINVAL, /* ERROR_ALREADY_ASSIGNED 85 */
+ EINVAL, /* ERROR_INVALID_PASSWORD 86 */
+ EINVAL, /* ERROR_INVALID_PARAMETER 87 */
+ EINVAL, /* ERROR_NET_WRITE_FAULT 88 */
+ EAGAIN, /* ERROR_NO_PROC_SLOTS 89 */
+ EINVAL, /* 90 */
+ EINVAL, /* 91 */
+ EINVAL, /* 92 */
+ EINVAL, /* 93 */
+ EINVAL, /* 94 */
+ EINVAL, /* 95 */
+ EINVAL, /* 96 */
+ EINVAL, /* 97 */
+ EINVAL, /* 98 */
+ EINVAL, /* 99 */
+ EINVAL, /* ERROR_TOO_MANY_SEMAPHORES 100 */
+ EINVAL, /* ERROR_EXCL_SEM_ALREADY_OWNED 101 */
+ EINVAL, /* ERROR_SEM_IS_SET 102 */
+ EINVAL, /* ERROR_TOO_MANY_SEM_REQUESTS 103 */
+ EINVAL, /* ERROR_INVALID_AT_INTERRUPT_TIME 104 */
+ EINVAL, /* ERROR_SEM_OWNER_DIED 105 */
+ EINVAL, /* ERROR_SEM_USER_LIMIT 106 */
+ EINVAL, /* ERROR_DISK_CHANGE 107 */
+ EACCES, /* ERROR_DRIVE_LOCKED 108 */
+ EPIPE, /* ERROR_BROKEN_PIPE 109 */
+ EINVAL, /* ERROR_OPEN_FAILED 110 */
+ EINVAL, /* ERROR_BUFFER_OVERFLOW 111 */
+ ENOSPC, /* ERROR_DISK_FULL 112 */
+ EINVAL, /* ERROR_NO_MORE_SEARCH_HANDLES 113 */
+ EBADF, /* ERROR_INVALID_TARGET_HANDLE 114 */
+ EINVAL, /* 115 */
+ EINVAL, /* 116 */
+ EINVAL, /* ERROR_INVALID_CATEGORY 117 */
+ EINVAL, /* ERROR_INVALID_VERIFY_SWITCH 118 */
+ EINVAL, /* ERROR_BAD_DRIVER_LEVEL 119 */
+ EINVAL, /* ERROR_CALL_NOT_IMPLEMENTED 120 */
+ EINVAL, /* ERROR_SEM_TIMEOUT 121 */
+ EINVAL, /* ERROR_INSUFFICIENT_BUFFER 122 */
+ EINVAL, /* ERROR_INVALID_NAME 123 */
+ EINVAL, /* ERROR_INVALID_LEVEL 124 */
+ EINVAL, /* ERROR_NO_VOLUME_LABEL 125 */
+ EINVAL, /* ERROR_MOD_NOT_FOUND 126 */
+ EINVAL, /* ERROR_PROC_NOT_FOUND 127 */
+ ECHILD, /* ERROR_WAIT_NO_CHILDREN 128 */
+ ECHILD, /* ERROR_CHILD_NOT_COMPLETE 129 */
+ EBADF, /* ERROR_DIRECT_ACCESS_HANDLE 130 */
+ EINVAL, /* ERROR_NEGATIVE_SEEK 131 */
+ EACCES, /* ERROR_SEEK_ON_DEVICE 132 */
+ EINVAL, /* ERROR_IS_JOIN_TARGET 133 */
+ EINVAL, /* ERROR_IS_JOINED 134 */
+ EINVAL, /* ERROR_IS_SUBSTED 135 */
+ EINVAL, /* ERROR_NOT_JOINED 136 */
+ EINVAL, /* ERROR_NOT_SUBSTED 137 */
+ EINVAL, /* ERROR_JOIN_TO_JOIN 138 */
+ EINVAL, /* ERROR_SUBST_TO_SUBST 139 */
+ EINVAL, /* ERROR_JOIN_TO_SUBST 140 */
+ EINVAL, /* ERROR_SUBST_TO_JOIN 141 */
+ EINVAL, /* ERROR_BUSY_DRIVE 142 */
+ EINVAL, /* ERROR_SAME_DRIVE 143 */
+ EINVAL, /* ERROR_DIR_NOT_ROOT 144 */
+ ENOTEMPTY, /* ERROR_DIR_NOT_EMPTY 145 */
+ EINVAL, /* ERROR_IS_SUBST_PATH 146 */
+ EINVAL, /* ERROR_IS_JOIN_PATH 147 */
+ EINVAL, /* ERROR_PATH_BUSY 148 */
+ EINVAL, /* ERROR_IS_SUBST_TARGET 149 */
+ EINVAL, /* ERROR_SYSTEM_TRACE 150 */
+ EINVAL, /* ERROR_INVALID_EVENT_COUNT 151 */
+ EINVAL, /* ERROR_TOO_MANY_MUXWAITERS 152 */
+ EINVAL, /* ERROR_INVALID_LIST_FORMAT 153 */
+ EINVAL, /* ERROR_LABEL_TOO_LONG 154 */
+ EINVAL, /* ERROR_TOO_MANY_TCBS 155 */
+ EINVAL, /* ERROR_SIGNAL_REFUSED 156 */
+ EINVAL, /* ERROR_DISCARDED 157 */
+ EACCES, /* ERROR_NOT_LOCKED 158 */
+ EINVAL, /* ERROR_BAD_THREADID_ADDR 159 */
+ EINVAL, /* ERROR_BAD_ARGUMENTS 160 */
+ ENOENT, /* ERROR_BAD_PATHNAME 161 */
+ EINVAL, /* ERROR_SIGNAL_PENDING 162 */
+ EINVAL, /* 163 */
+ EAGAIN, /* ERROR_MAX_THRDS_REACHED 164 */
+ EINVAL, /* 165 */
+ EINVAL, /* 166 */
+ EACCES, /* ERROR_LOCK_FAILED 167 */
+ EINVAL, /* 168 */
+ EINVAL, /* 169 */
+ EINVAL, /* ERROR_BUSY 170 */
+ EINVAL, /* 171 */
+ EINVAL, /* 172 */
+ EINVAL, /* ERROR_CANCEL_VIOLATION 173 */
+ EINVAL, /* ERROR_ATOMIC_LOCKS_NOT_SUPPORTED 174 */
+ EINVAL, /* 175 */
+ EINVAL, /* 176 */
+ EINVAL, /* 177 */
+ EINVAL, /* 178 */
+ EINVAL, /* 179 */
+ EINVAL, /* ERROR_INVALID_SEGMENT_NUMBER 180 */
+ EINVAL, /* 181 */
+ EINVAL, /* ERROR_INVALID_ORDINAL 182 */
+ EEXIST, /* ERROR_ALREADY_EXISTS 183 */
+ EINVAL, /* 184 */
+ EINVAL, /* 185 */
+ EINVAL, /* ERROR_INVALID_FLAG_NUMBER 186 */
+ EINVAL, /* ERROR_SEM_NOT_FOUND 187 */
+ ENOEXEC, /* ERROR_INVALID_STARTING_CODESEG 188 */
+ ENOEXEC, /* ERROR_INVALID_STACKSEG 189 */
+ ENOEXEC, /* ERROR_INVALID_MODULETYPE 190 */
+ ENOEXEC, /* ERROR_INVALID_EXE_SIGNATURE 191 */
+ ENOEXEC, /* ERROR_EXE_MARKED_INVALID 192 */
+ ENOEXEC, /* ERROR_BAD_EXE_FORMAT 193 */
+ ENOEXEC, /* ERROR_ITERATED_DATA_EXCEEDS_64k 194 */
+ ENOEXEC, /* ERROR_INVALID_MINALLOCSIZE 195 */
+ ENOEXEC, /* ERROR_DYNLINK_FROM_INVALID_RING 196 */
+ ENOEXEC, /* ERROR_IOPL_NOT_ENABLED 197 */
+ ENOEXEC, /* ERROR_INVALID_SEGDPL 198 */
+ ENOEXEC, /* ERROR_AUTODATASEG_EXCEEDS_64k 199 */
+ ENOEXEC, /* ERROR_RING2SEG_MUST_BE_MOVABLE 200 */
+ ENOEXEC, /* ERROR_RELOC_CHAIN_XEEDS_SEGLIM 201 */
+ ENOEXEC, /* ERROR_INFLOOP_IN_RELOC_CHAIN 202 */
+ EINVAL, /* ERROR_ENVVAR_NOT_FOUND 203 */
+ EINVAL, /* 204 */
+ EINVAL, /* ERROR_NO_SIGNAL_SENT 205 */
+ ENOENT, /* ERROR_FILENAME_EXCED_RANGE 206 */
+ EINVAL, /* ERROR_RING2_STACK_IN_USE 207 */
+ EINVAL, /* ERROR_META_EXPANSION_TOO_LONG 208 */
+ EINVAL, /* ERROR_INVALID_SIGNAL_NUMBER 209 */
+ EINVAL, /* ERROR_THREAD_1_INACTIVE 210 */
+ EINVAL, /* 211 */
+ EINVAL, /* ERROR_LOCKED 212 */
+ EINVAL, /* 213 */
+ EINVAL, /* ERROR_TOO_MANY_MODULES 214 */
+ EAGAIN /* ERROR_NESTING_NOT_ALLOWED 215 */
+};
+
+/* size of the table */
+#define ERRMAPTABLESIZE (sizeof(errMapTable)/sizeof(errMapTable[0]))
+
+/*
+** void __cdecl _dosmaperr(winerrno)
+**
+** Takes a Windows error number and tries to map it to a Unix System V errno.
+** Sets:
+** _doserrno = Windows error number
+** errno = Unix System V errno.
+*/
+void __cdecl _dosmaperr(unsigned long winerrno)
+{
+ _doserrno = winerrno;
+
+ if (winerrno >= ERRMAPTABLESIZE) {
+ if (winerrno == ERROR_NOT_ENOUGH_QUOTA) { /* exception for 1816 */
+ errno = ENOMEM;
+ } else {
+ errno = EINVAL;
+ }
+ } else {
+ errno = (unsigned int) errMapTable[winerrno];
+ }
+}
+
+#endif /* WIN32 && _MT && _DLL */
diff --git a/deps/uart/c_src/uart_api.h b/deps/uart/c_src/uart_api.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/deps/uart/c_src/uart_api.h
diff --git a/deps/uart/c_src/uart_buf.c b/deps/uart/c_src/uart_buf.c
new file mode 100644
index 0000000..d3727b6
--- /dev/null
+++ b/deps/uart/c_src/uart_buf.c
@@ -0,0 +1,363 @@
+//
+// Input buffer processing
+//
+#include "uart_drv.h"
+
+void uart_buf_init(uart_buf_t* bf)
+{
+ memset(bf, 0, sizeof(uart_buf_t));
+}
+
+void uart_buf_finish(uart_buf_t* bf)
+{
+ if (bf->base != NULL)
+ DFREE(bf->base);
+ uart_buf_init(bf);
+}
+
+void uart_buf_reset(uart_buf_t* bf)
+{
+ bf->ptr_start = bf->base;
+ bf->ptr = bf->base;
+}
+
+int uart_buf_alloc(uart_buf_t* bf, size_t sz)
+{
+ uint8_t* base;
+
+ if ((base = DALLOC(sz)) == NULL)
+ return -1;
+ bf->sz = sz;
+ bf->base = base;
+ bf->ptr_start = base;
+ bf->ptr = base;
+ return (int) sz;
+}
+
+//
+// Set new size on buffer, used when packet size is determined
+// and the buffer is to small.
+// buffer must have a size of at least len bytes (counting from ptr_start!)
+//
+int uart_buf_expand(uart_buf_t* bf, size_t len)
+{
+ uint8_t* base;
+ intptr_t offs1;
+ intptr_t offs2;
+ size_t used = bf->ptr_start - bf->base;
+ size_t ulen = used + len;
+
+ if (bf->sz >= ulen) /* packet will fit */
+ return 0;
+
+ offs1 = bf->ptr_start - bf->base;
+ offs2 = bf->ptr - bf->ptr_start;
+
+ if ((base = DREALLOC(bf->base, ulen)) == NULL)
+ return -1;
+
+ bf->base = base;
+ bf->ptr_start = bf->base + offs1;
+ bf->ptr = bf->ptr_start + offs2;
+ bf->sz = ulen;
+ return 0;
+}
+
+
+// Move data so that ptr_start point at buf->base
+void uart_buf_restart(uart_buf_t* bf)
+{
+ if (bf->ptr_start != bf->base) {
+ intptr_t n = bf->ptr - bf->ptr_start;
+ memmove(bf->base, bf->ptr_start, n);
+ bf->ptr_start = bf->base;
+ bf->ptr = bf->ptr_start + n;
+ }
+}
+
+// push data into base
+int uart_buf_push(uart_buf_t* bf, char* buf, size_t len)
+{
+ if (bf->base == NULL) {
+ if (uart_buf_alloc(bf, len) < 0)
+ return -1;
+ memcpy(bf->base, buf, len);
+ bf->ptr = bf->ptr_start + len;
+ }
+ else {
+ size_t sz_before = bf->ptr_start - bf->base;
+ size_t sz_filled = bf->ptr - bf->ptr_start;
+
+ if (len <= sz_before) {
+ memcpy(bf->ptr_start - len, buf, len);
+ bf->ptr_start -= len;
+ }
+ else {
+ uint8_t* base = DALLOC(bf->sz+len);
+ if (base == NULL)
+ return -1;
+ memcpy(base, buf, len);
+ memcpy(base+len, bf->ptr_start, sz_filled);
+ DFREE(bf->base);
+ bf->sz += len;
+ bf->base = base;
+ bf->ptr_start = base;
+ bf->ptr = base + len + sz_filled;
+ }
+ }
+ return 0;
+}
+
+// Return > 0 Total packet length.in bytes
+// = 0 Length unknown, need more data.
+// < 0 Error, invalid format.
+// psize - Max packet length, 0=no limit
+// trunc_len - Truncate (lines) if longer, 0=no limit
+int uart_buf_packet(uart_buf_t* bf, unsigned int htype, unsigned psize,
+ int eol, unsigned trunc_len)
+{
+ uint8_t* ptr = bf->ptr_start;
+ size_t n = bf->ptr - bf->ptr_start;
+ size_t hlen, plen;
+
+ switch (htype & UART_PB_TYPE_MASK) {
+ case UART_PB_RAW: {
+ unsigned m;
+ if (n == 0)
+ goto more;
+ hlen = 0;
+ m = (htype & UART_PB_FIXED_MASK) >> 16;
+ if ((plen = m) == 0) {
+ DEBUGF(" => nothing remain packet=%d", n);
+ return n;
+ }
+ goto remain;
+ }
+
+ case UART_PB_N: {
+ uint64_t pl = 0;
+ hlen = (htype & UART_PB_BYTES_MASK) >> 8;
+ if (n < hlen) goto more;
+ if (htype & UART_PB_LITTLE_ENDIAN) {
+ ptr += hlen;
+ switch(hlen) {
+ case 8: pl = (pl << 8) | *--ptr;
+ case 7: pl = (pl << 8) | *--ptr;
+ case 6: pl = (pl << 8) | *--ptr;
+ case 5: pl = (pl << 8) | *--ptr;
+ case 4: pl = (pl << 8) | *--ptr;
+ case 3: pl = (pl << 8) | *--ptr;
+ case 2: pl = (pl << 8) | *--ptr;
+ case 1: pl = (pl << 8) | *--ptr;
+ break;
+ default: return -1;
+ }
+ }
+ else {
+ switch(hlen) {
+ case 8: pl = (pl << 8) | *ptr++;
+ case 7: pl = (pl << 8) | *ptr++;
+ case 6: pl = (pl << 8) | *ptr++;
+ case 5: pl = (pl << 8) | *ptr++;
+ case 4: pl = (pl << 8) | *ptr++;
+ case 3: pl = (pl << 8) | *ptr++;
+ case 2: pl = (pl << 8) | *ptr++;
+ case 1: pl = (pl << 8) | *ptr++;
+ break;
+ default: return -1;
+ }
+ }
+ plen = (unsigned) pl;
+ goto remain;
+ }
+
+ case UART_PB_LINE_LF: {
+ /* UART_PB_LINE_LF: [Data ... \n] */
+ const uint8_t* ptr2;
+ if ((ptr2 = memchr(ptr, eol, n)) == NULL) {
+ if ((n >= trunc_len) && (trunc_len!=0)) { /* buffer full */
+ DEBUGF(" => line buffer full (no NL)=%d", n);
+ return trunc_len;
+ }
+ goto more;
+ }
+ else {
+ int len = (ptr2 - ptr) + 1; /* including newline */
+ if ((len > (int)trunc_len) && (trunc_len != 0)) {
+ DEBUGF(" => truncated line=%d", trunc_len);
+ return trunc_len;
+ }
+ DEBUGF(" => nothing remain packet=%d", len);
+ return len;
+ }
+ }
+ case UART_PB_GSM_0710: {
+ // check for either BASIC or ADVANCED mode
+ if (n > 1) {
+ if (ptr[0] == 0x7E) goto advanced_0710;
+ if (ptr[0] == 0xF9) goto basic_0710;
+ plen = 1;
+ while(plen < n) {
+ if (ptr[plen] == 0x7D) // escape
+ plen++;
+ else if ((ptr[plen] == 0x7E) ||
+ (ptr[plen] == 0xF9))
+ return plen;
+ plen++;
+ }
+ return n;
+ }
+ goto more;
+ }
+
+ case UART_PB_ADVANCED_0710: {
+ advanced_0710:
+ // 0x7E <escaped data> 0x7E
+ if (n > 1) {
+ if (ptr[0] == 0x7E) {
+ plen = 1;
+ while((plen < n) && (ptr[plen] != 0x7E)) {
+ if (ptr[plen] == 0x7D) { // escape
+ plen++;
+ if (plen == n)
+ goto more;
+ }
+ plen++;
+ }
+ return plen+1;
+ }
+ else {
+ plen = 1;
+ while(plen < n) {
+ if (ptr[plen] == 0x7D) // escape
+ plen++;
+ else if (ptr[plen] == 0x7E)
+ return plen;
+ plen++;
+ }
+ return n;
+ }
+ }
+ goto more;
+ }
+
+ case UART_PB_BASIC_0710: {
+ /* UART_BP_BASIC_0710:
+ <<0xF9,Address,Control,Len:7,1:1,Data/Len,FCS,0xF9>>
+ <<0xF9,Address,Control,L0:7,0:1,L1:8,Data/(L1<<7+L0),FCS,0xF9>>
+ */
+ basic_0710:
+ if (n >= 6) {
+ if (ptr[0] == 0xF9) {
+ if (ptr[3] & 0x1) { // short length
+ plen = ptr[3]>>1; // length of Data
+ hlen = 6; // rest of the bytes
+ if (n >= plen+hlen)
+ return (plen+hlen);
+ goto more;
+ }
+ else {
+ plen = (ptr[4]<<7)+(ptr[3]>>1); // length of Data
+ hlen = 6; // rest of the bytes
+ if (n >= plen+hlen)
+ return (plen+hlen);
+ goto more;
+ }
+ }
+ else {
+ // scan for 0xF9 or send error ?
+ plen = 1;
+ while(plen < n) {
+ if (ptr[plen] == 0xF9)
+ return plen; // deliver plen bytes!
+ plen++;
+ }
+ return n; // deliver all bytes
+ }
+ }
+ goto more;
+ }
+ default:
+ DEBUGF(" => case error");
+ return -1;
+ }
+
+more:
+ return 0;
+
+remain:
+ {
+ int tlen = hlen + plen;
+ if (((psize != 0) && (plen > psize))
+ || tlen < (int)hlen) { /* wrap-around protection */
+ return -1;
+ }
+ return tlen;
+ }
+
+
+}
+
+/*
+** Calculate number of bytes that remain to read before deliver
+** Assume buf, ptr_start, ptr has been setup
+**
+** return > 0 if more to read
+** = 0 if holding complete packet
+** < 0 on error
+**
+** if return value == 0 then *len will hold the length of the first packet
+** return value > 0 then if *len == 0 then value means upperbound
+** *len > 0 then value means exact
+**
+*/
+int uart_buf_remain(uart_buf_t* bf, int* len,
+ unsigned int htype, unsigned int psize, int eol)
+{
+ uint8_t* ptr = bf->ptr_start;
+ int nfill = (bf->ptr - bf->base); // filled
+ int nsz = bf->sz - nfill; // remain
+ int n = bf->ptr - ptr; // number of bytes read
+ int tlen;
+
+ tlen = uart_buf_packet(bf, htype, psize, eol, bf->sz);
+ if (tlen > 0) {
+ if (tlen <= n) { // got a packet
+ *len = tlen;
+ DEBUGF(" => nothing remain packet=%d", tlen);
+ return 0;
+ }
+ else { // need (known) more
+ if (uart_buf_expand(bf, tlen) < 0)
+ return -1;
+ *len = tlen - n;
+ DEBUGF(" => remain=%d", *len);
+ return *len;
+ }
+ }
+ else if (tlen == 0) { // need (unknown) more
+ *len = 0;
+ if (nsz == 0) {
+ if (nfill == n) {
+ if ((psize != 0) &&
+ ((int)psize > nfill)) {
+ if (uart_buf_expand(bf, psize) < 0)
+ return -1;
+ return psize;
+ }
+ else
+ goto error;
+ }
+ DEBUGF(" => restart more=%d", nfill - n);
+ return nfill - n;
+ }
+ else {
+ DEBUGF(" => more=%d", nsz);
+ return nsz;
+ }
+ }
+
+error:
+ DEBUGF(" => packet error");
+ return -1;
+}
diff --git a/deps/uart/c_src/uart_com_state.c b/deps/uart/c_src/uart_com_state.c
new file mode 100644
index 0000000..70f477b
--- /dev/null
+++ b/deps/uart/c_src/uart_com_state.c
@@ -0,0 +1,56 @@
+//
+// com_state utils
+//
+#include <stdio.h>
+
+#include "uart_drv.h"
+
+void com_state_dump(FILE* f, uart_com_state_t* state)
+{
+ fprintf(f, "com { ");
+ fprintf(f, "ibaud: %d,", state->ibaud);
+ fprintf(f, "obaud: %d,", state->obaud);
+ fprintf(f, "parity: %d,", state->parity);
+ fprintf(f, "stopb: %d,", state->stopb);
+ fprintf(f, "csize: %d,", state->csize);
+ fprintf(f, "bufsz: %d,", state->bufsz);
+ fprintf(f, "buftm: %d,", state->buftm);
+ fprintf(f, "xonchar: %d,", state->xonchar);
+ fprintf(f, "xoffchar: %d,", state->xoffchar);
+ fprintf(f, "iflow: %d,", state->iflow);
+ fprintf(f, "oflow: %d,", state->oflow);
+ fprintf(f, "}\r\n");
+}
+
+void com_state_init(uart_com_state_t* ptr)
+{
+ ptr->ibaud = 9600;
+ ptr->obaud = 9600;
+ ptr->parity = 0;
+ ptr->stopb = 1;
+ ptr->csize = 8;
+ ptr->bufsz = 1;
+ ptr->buftm = 0;
+ ptr->xonchar = 17;
+ ptr->xoffchar = 19;
+ ptr->iflow = 0;
+ ptr->oflow = 0;
+}
+
+// Copy changed com state configs from source to destination
+void com_state_copy(uart_com_state_t* dst,uart_com_state_t* src,
+ uint32_t sflags)
+{
+ // update the state1
+ if (sflags & (1<<UART_OPT_IBAUD)) dst->ibaud = src->ibaud;
+ if (sflags & (1<<UART_OPT_OBAUD)) dst->obaud = src->obaud;
+ if (sflags & (1<<UART_OPT_CSIZE)) dst->csize = src->csize;
+ if (sflags & (1<<UART_OPT_BUFSZ)) dst->bufsz = src->bufsz;
+ if (sflags & (1<<UART_OPT_BUFTM)) dst->buftm = src->buftm;
+ if (sflags & (1<<UART_OPT_STOPB)) dst->stopb = src->stopb;
+ if (sflags & (1<<UART_OPT_PARITY)) dst->parity = src->parity;
+ if (sflags & (1<<UART_OPT_IFLOW)) dst->iflow = src->iflow;
+ if (sflags & (1<<UART_OPT_OFLOW)) dst->oflow = src->oflow;
+ if (sflags & (1<<UART_OPT_XOFFCHAR)) dst->xoffchar = src->xoffchar;
+ if (sflags & (1<<UART_OPT_XONCHAR)) dst->xonchar = src->xonchar;
+}
diff --git a/deps/uart/c_src/uart_drv.c b/deps/uart/c_src/uart_drv.c
new file mode 100644
index 0000000..ba907fa
--- /dev/null
+++ b/deps/uart/c_src/uart_drv.c
@@ -0,0 +1,436 @@
+//
+// uart_drv.c
+//
+// Windows/Unix uart driver
+//
+//
+#include <stdio.h>
+#include <stdint.h>
+#include <stdarg.h>
+
+#include "uart_drv.h"
+
+typedef struct _drv_ctx_t
+{
+ dthread_t self; // me
+ dthread_t* other; // the thread
+} drv_ctx_t;
+
+static int uart_drv_init(void);
+static void uart_drv_finish(void);
+static void uart_drv_stop(ErlDrvData);
+static void uart_drv_output(ErlDrvData, char*, ErlDrvSizeT);
+static void uart_drv_ready_input(ErlDrvData, ErlDrvEvent);
+static void uart_drv_ready_output(ErlDrvData data, ErlDrvEvent event);
+static ErlDrvData uart_drv_start(ErlDrvPort, char* command);
+static ErlDrvSSizeT uart_drv_ctl(ErlDrvData,unsigned int,char*,ErlDrvSizeT,char**, ErlDrvSizeT);
+static void uart_drv_timeout(ErlDrvData);
+static void uart_drv_stop_select(ErlDrvEvent, void*);
+
+ErlDrvTermData am_dtr;
+ErlDrvTermData am_rts;
+ErlDrvTermData am_cts;
+ErlDrvTermData am_cd;
+ErlDrvTermData am_ri;
+ErlDrvTermData am_dsr;
+ErlDrvTermData am_sw;
+
+ErlDrvTermData am_device;
+ErlDrvTermData am_baud;
+ErlDrvTermData am_ibaud;
+ErlDrvTermData am_obaud;
+ErlDrvTermData am_csize;
+ErlDrvTermData am_bufsz;
+ErlDrvTermData am_buftm;
+ErlDrvTermData am_stopb;
+ErlDrvTermData am_parity;
+ErlDrvTermData am_iflow;
+ErlDrvTermData am_oflow;
+ErlDrvTermData am_xoffchar;
+ErlDrvTermData am_xonchar;
+ErlDrvTermData am_eolchar;
+ErlDrvTermData am_active;
+ErlDrvTermData am_delay_send;
+ErlDrvTermData am_deliver;
+ErlDrvTermData am_mode;
+ErlDrvTermData am_header;
+ErlDrvTermData am_packet;
+ErlDrvTermData am_packet_size;
+ErlDrvTermData am_high_watermark;
+ErlDrvTermData am_low_watermark;
+ErlDrvTermData am_send_timeout;
+ErlDrvTermData am_send_timeout_close;
+ErlDrvTermData am_buffer;
+ErlDrvTermData am_exit_on_close;
+ErlDrvTermData am_debug;
+ErlDrvTermData am_ptypkt;
+ErlDrvTermData am_none;
+ErlDrvTermData am_odd;
+ErlDrvTermData am_even;
+ErlDrvTermData am_mark;
+ErlDrvTermData am_space;
+ErlDrvTermData am_true;
+ErlDrvTermData am_false;
+ErlDrvTermData am_once;
+ErlDrvTermData am_port;
+ErlDrvTermData am_term;
+ErlDrvTermData am_list;
+ErlDrvTermData am_binary;
+ErlDrvTermData am_size;
+ErlDrvTermData am_line;
+ErlDrvTermData am_basic_0710;
+ErlDrvTermData am_advanced_0710;
+ErlDrvTermData am_gsm_0710;
+
+
+ErlDrvTermData am_ok;
+ErlDrvTermData am_uart;
+ErlDrvTermData am_error;
+ErlDrvTermData am_uart_async;
+ErlDrvTermData am_uart_reply;
+ErlDrvTermData am_timeout;
+ErlDrvTermData am_closed;
+ErlDrvTermData am_uart_closed;
+ErlDrvTermData am_uart_error;
+ErlDrvTermData am_undefined;
+
+static ErlDrvEntry uart_drv_entry;
+
+/* general control reply function */
+static ErlDrvSSizeT ctl_reply(int rep, char* buf, ErlDrvSizeT len,
+ char** rbuf, ErlDrvSizeT rsize)
+{
+ char* ptr;
+
+ if ((len+1) > rsize) {
+ ErlDrvBinary* bin = driver_alloc_binary(len+1);
+ if (bin == NULL)
+ return -1;
+ ptr = bin->orig_bytes;
+ *rbuf = (char*)ptr;
+ }
+ else
+ ptr = *rbuf;
+ *ptr++ = rep;
+ memcpy(ptr, buf, len);
+ return len+1;
+}
+
+
+// setup global object area
+// load atoms etc.
+
+static int uart_drv_init(void)
+{
+ dlog_set_debug(DLOG_DEFAULT);
+ DEBUGF("uart_driver_init");
+ dthread_lib_init();
+ dlog_set_debug(DLOG_DEFAULT);
+
+ INIT_ATOM(dtr);
+ INIT_ATOM(rts);
+ INIT_ATOM(cts);
+ INIT_ATOM(cd);
+ INIT_ATOM(ri);
+ INIT_ATOM(dsr);
+ INIT_ATOM(sw);
+
+ INIT_ATOM(device);
+ INIT_ATOM(baud);
+ INIT_ATOM(ibaud);
+ INIT_ATOM(obaud);
+ INIT_ATOM(csize);
+ INIT_ATOM(bufsz);
+ INIT_ATOM(buftm);
+ INIT_ATOM(stopb);
+ INIT_ATOM(parity);
+ INIT_ATOM(iflow);
+ INIT_ATOM(oflow);
+ INIT_ATOM(xoffchar);
+ INIT_ATOM(xonchar);
+ INIT_ATOM(eolchar);
+ INIT_ATOM(active);
+ INIT_ATOM(delay_send);
+ INIT_ATOM(deliver);
+ INIT_ATOM(mode);
+ INIT_ATOM(header);
+ INIT_ATOM(packet);
+ INIT_ATOM(packet_size);
+ INIT_ATOM(high_watermark);
+ INIT_ATOM(low_watermark);
+ INIT_ATOM(send_timeout);
+ INIT_ATOM(send_timeout_close);
+ INIT_ATOM(buffer);
+ INIT_ATOM(exit_on_close);
+ INIT_ATOM(ptypkt);
+
+ INIT_ATOM(none);
+ INIT_ATOM(odd);
+ INIT_ATOM(even);
+ INIT_ATOM(mark);
+ INIT_ATOM(space);
+
+ INIT_ATOM(true);
+ INIT_ATOM(false);
+ INIT_ATOM(once);
+
+ INIT_ATOM(port);
+ INIT_ATOM(term);
+
+ INIT_ATOM(list);
+ INIT_ATOM(binary);
+
+ INIT_ATOM(size);
+ INIT_ATOM(line);
+ INIT_ATOM(basic_0710);
+ INIT_ATOM(advanced_0710);
+ INIT_ATOM(gsm_0710);
+ INIT_ATOM(debug);
+
+
+ INIT_ATOM(ok);
+ INIT_ATOM(uart);
+ INIT_ATOM(error);
+ INIT_ATOM(uart_async);
+ INIT_ATOM(uart_reply);
+ INIT_ATOM(timeout);
+ INIT_ATOM(closed);
+ INIT_ATOM(uart_closed);
+ INIT_ATOM(uart_error);
+ INIT_ATOM(undefined);
+
+ return 0;
+}
+
+// clean up global settings
+static void uart_drv_finish(void)
+{
+ // cleanup global stuff!
+ dthread_lib_finish();
+}
+
+#ifdef HAVE_FTDI
+extern void* uart_ftdi_main(void* arg);
+#endif
+
+#ifdef __WIN32__
+extern void* uart_win32_main(void* arg);
+#else
+extern void* uart_unix_main(void* arg);
+#endif
+
+
+static ErlDrvData uart_drv_start(ErlDrvPort port, char* command)
+{
+ (void) command;
+ drv_ctx_t* ctx = NULL;
+
+ INFOF("memory allocated: %ld", dlib_allocated());
+ INFOF("total memory allocated: %ld", dlib_total_allocated());
+
+ ctx = DZALLOC(sizeof(drv_ctx_t));
+
+ dthread_init(&ctx->self, port);
+
+ if (strncmp(command, "uart_drv", 8) == 0)
+ command += 8;
+ if (*command == ' ')
+ command++;
+ DEBUGF("uart_drv: start (%s)", command);
+
+
+ if (strcmp(command, "ftdi") == 0) {
+#ifdef HAVE_FTDI
+ ctx->other = dthread_start(port, uart_ftdi_main, &ctx->self, 4096);
+ DEBUGF("uart_drv: ftdi thread = %p", ctx->other);
+#endif
+ }
+ else {
+#ifdef __WIN32__
+ if ((*command == '\0') || (strcmp(command, "win32") == 0)) {
+ ctx->other = dthread_start(port, uart_win32_main, &ctx->self, 4096);
+ DEBUGF("uart_drv: win32 thread = %p", ctx->other);
+ }
+#else
+ if ((*command == '\0') || (strcmp(command, "unix") == 0)) {
+ ctx->other = dthread_start(port, uart_unix_main, &ctx->self, 4096);
+ DEBUGF("uart_drv: unix thread = %p", ctx->other);
+ }
+#endif
+ }
+ if (ctx->other == NULL) {
+ dthread_finish(&ctx->self);
+ DFREE(ctx);
+ return ERL_DRV_ERROR_BADARG;
+ }
+
+ dthread_signal_use(&ctx->self, 1);
+ dthread_signal_select(&ctx->self, 1);
+
+ set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
+ return (ErlDrvData) ctx;
+}
+
+static void uart_drv_stop(ErlDrvData d)
+{
+ drv_ctx_t* ctx = (drv_ctx_t*) d;
+ void* value;
+
+ DEBUGF("uart_drv_stop: called");
+ dthread_stop(ctx->other, &ctx->self, &value);
+
+ DEBUGF("uart_drv_stop: signal_use=0");
+ dthread_signal_use(&ctx->self, 0);
+
+ DEBUGF("uart_drv_stop: dthread_finish");
+ dthread_finish(&ctx->self);
+ DFREE(ctx);
+ INFOF("memory allocated: %ld", dlib_allocated());
+ INFOF("total memory allocated: %ld", dlib_total_allocated());
+}
+
+static char* format_command(int cmd)
+{
+ switch(cmd) {
+ case UART_CMD_OPEN: return "open";
+ case UART_CMD_HANGUP: return "hangup";
+ case UART_CMD_CLOSE: return "close";
+ case UART_CMD_FLOW: return "flow";
+ case UART_CMD_BREAK: return "break";
+ case UART_CMD_SETOPTS: return "setopts";
+ case UART_CMD_GETOPTS: return "getopts";
+ case UART_CMD_SENDCHAR: return "sendchar";
+ case UART_CMD_SEND: return "send";
+ case UART_CMD_GET_MODEM: return "get_modem";
+ case UART_CMD_SET_MODEM: return "set_modem";
+ case UART_CMD_CLR_MODEM: return "clr_modem";
+ case UART_CMD_UNRECV: return "unrecv";
+ case UART_CMD_RECV: return "recv";
+ default: return "????";
+ }
+}
+
+static ErlDrvSSizeT uart_drv_ctl(ErlDrvData d,
+ unsigned int cmd, char* buf, ErlDrvSizeT len,
+ char** rbuf, ErlDrvSizeT rsize)
+{
+ drv_ctx_t* ctx = (drv_ctx_t*) d;
+ char ref_buf[sizeof(uint32_t)];
+
+ DEBUGF("uart_drv: ctl: cmd=%u(%s), len=%d", cmd, format_command(cmd), len);
+
+ ctx->self.caller = driver_caller(ctx->self.port);
+ dthread_control(ctx->other, &ctx->self, cmd, buf, len);
+
+ put_uint32((unsigned char*)ref_buf, (uint32_t) ctx->self.ref);
+ return ctl_reply(UART_OK, ref_buf, sizeof(ref_buf), rbuf, rsize);
+}
+
+
+static void uart_drv_output(ErlDrvData d, char* buf, ErlDrvSizeT len)
+{
+ drv_ctx_t* ctx = (drv_ctx_t*) d;
+
+ DEBUGF("uart_drv: output");
+
+ ctx->self.caller = driver_caller(ctx->self.port);
+ dthread_output(ctx->other, &ctx->self, buf, len);
+}
+
+// NOTE: when SMP is enabled the messages go straight to the caller
+// This code is here to allow non SMP emulator with the same code base.
+static void uart_drv_ready_input(ErlDrvData d, ErlDrvEvent e)
+{
+ drv_ctx_t* ctx = (drv_ctx_t*) d;
+
+ DEBUGF("uart_drv: ready_input called");
+
+ if (ctx->self.iq_signal[0] == e) { // got input !
+ dmessage_t* mp;
+
+ DEBUGF("uart_drv: ready_input handle=%d",
+ DTHREAD_EVENT(ctx->self.iq_signal[0]));
+
+ if ((mp = dthread_recv(&ctx->self, NULL)) == NULL) {
+ DEBUGF("uart_drv: ready_input signaled with no event! handle=%d",
+ DTHREAD_EVENT(ctx->self.iq_signal[0]));
+ return;
+ }
+
+ switch(mp->cmd) {
+ case DTHREAD_OUTPUT_TERM:
+ DEBUGF("uart_drv: ready_input (OUTPUT_TERM)");
+ DOUTPUT_TERM(&(ctx->self),
+ (ErlDrvTermData*) mp->buffer,
+ mp->used / sizeof(ErlDrvTermData));
+ break;
+ case DTHREAD_SEND_TERM:
+ DEBUGF("uart_drv: ready_input (SEND_TERM)");
+ // dterm_dump(stderr, (ErlDrvTermData*) mp->buffer,
+ // mp->used / sizeof(ErlDrvTermData));
+ DSEND_TERM(&(ctx->self), mp->to, /* orignal from ! */
+ (ErlDrvTermData*) mp->buffer,
+ mp->used / sizeof(ErlDrvTermData));
+ break;
+ case DTHREAD_OUTPUT:
+ DEBUGF("uart_drv: ready_input (OUTPUT)");
+ driver_output(ctx->self.port, mp->buffer, mp->used);
+ break;
+ default:
+ DEBUGF("uart_drv: read_input cmd=%d not matched",
+ mp->cmd);
+ break;
+ }
+ dmessage_free(mp);
+ }
+ else {
+ DEBUGF("uart_drv: ready_input (NO MATCH)");
+ }
+}
+
+static void uart_drv_ready_output(ErlDrvData d, ErlDrvEvent e)
+{
+ (void) d;
+ (void) e;
+ DEBUGF("dthread_drv: ready_output");
+}
+
+// operation timed out
+static void uart_drv_timeout(ErlDrvData d)
+{
+ (void) d;
+ DEBUGF("uart_drv: timeout");
+}
+
+
+static void uart_drv_stop_select(ErlDrvEvent event, void* arg)
+{
+ (void) arg;
+ DEBUGF("uart_drv: stop_select event=%d", DTHREAD_EVENT(event));
+ dthread_event_close(event);
+}
+
+DRIVER_INIT(uart_drv)
+{
+ ErlDrvEntry* ptr = &uart_drv_entry;
+
+ DEBUGF("driver_init");
+
+ ptr->init = uart_drv_init;
+ ptr->start = uart_drv_start;
+ ptr->stop = uart_drv_stop;
+ ptr->output = uart_drv_output;
+ ptr->ready_input = uart_drv_ready_input;
+ ptr->ready_output = uart_drv_ready_output;
+ ptr->finish = uart_drv_finish;
+ ptr->driver_name = "uart_drv";
+ ptr->control = uart_drv_ctl;
+ ptr->timeout = uart_drv_timeout;
+ ptr->extended_marker = ERL_DRV_EXTENDED_MARKER;
+ ptr->major_version = ERL_DRV_EXTENDED_MAJOR_VERSION;
+ ptr->minor_version = ERL_DRV_EXTENDED_MINOR_VERSION;
+ ptr->driver_flags = ERL_DRV_FLAG_USE_PORT_LOCKING;
+ ptr->process_exit = 0;
+ ptr->stop_select = uart_drv_stop_select;
+ return ptr;
+}
diff --git a/deps/uart/c_src/uart_drv.h b/deps/uart/c_src/uart_drv.h
new file mode 100644
index 0000000..40ab4d3
--- /dev/null
+++ b/deps/uart/c_src/uart_drv.h
@@ -0,0 +1,386 @@
+//
+// Uart interface definitions
+//
+#ifndef __UART_DRV__
+#define __UART_DRV__
+
+#include <stdio.h>
+#include <stdint.h>
+
+#include "erl_driver.h"
+#include "dthread/include/dthread.h"
+#include "dthread/include/dlog.h"
+
+static inline uint32_t get_uint32(uint8_t* ptr)
+{
+ uint32_t value = (ptr[0]<<24) | (ptr[1]<<16) | (ptr[2]<<8) | (ptr[3]<<0);
+ return value;
+}
+
+static inline uint16_t get_uint16(uint8_t* ptr)
+{
+ uint16_t value = (ptr[0]<<8) | (ptr[1]<<0);
+ return value;
+}
+
+static inline uint8_t get_uint8(uint8_t* ptr)
+{
+ uint8_t value = (ptr[0]<<0);
+ return value;
+}
+
+static inline void put_uint16(uint8_t* ptr, uint16_t v)
+{
+ ptr[0] = v>>8;
+ ptr[1] = v;
+}
+
+
+static inline void put_uint32(uint8_t* ptr, uint32_t v)
+{
+ ptr[0] = v>>24;
+ ptr[1] = v>>16;
+ ptr[2] = v>>8;
+ ptr[3] = v;
+}
+
+#define UART_CMD_OPEN 1
+#define UART_CMD_HANGUP 2
+#define UART_CMD_CLOSE 4
+#define UART_CMD_FLOW 5
+#define UART_CMD_BREAK 7
+#define UART_CMD_SETOPTS 8
+#define UART_CMD_GETOPTS 9
+#define UART_CMD_SENDCHAR 10
+#define UART_CMD_SEND 11
+#define UART_CMD_GET_MODEM 12
+#define UART_CMD_SET_MODEM 13
+#define UART_CMD_CLR_MODEM 14
+#define UART_CMD_UNRECV 15
+#define UART_CMD_RECV 16
+#define UART_CMD_CONNECT 17
+
+#define UART_MAX_OPT_BUFFER (64*1024)
+#define UART_DEF_BUFFER 1024
+#define UART_MIN_BUFFER 1
+
+#define UART_HIGH_WATERMARK (1024*2) // 2k pending high => busy
+#define UART_LOW_WATERMARK (1024*1) // 1k pending => allow more
+#define UART_INFINITY 0xffffffff // infinity value
+
+
+#define UART_OPT_COMM \
+ ((1 << UART_OPT_IBAUD) | (1 << UART_OPT_OBAUD) | \
+ (1 << UART_OPT_CSIZE) | (1 << UART_OPT_BUFSZ) | \
+ (1 << UART_OPT_BUFTM) | (1 << UART_OPT_STOPB) | \
+ (1 << UART_OPT_PARITY) | (1 << UART_OPT_IFLOW) | \
+ (1 << UART_OPT_OFLOW) | (1 << UART_OPT_XOFFCHAR) | \
+ (1 << UART_OPT_XONCHAR) | (1 << UART_OPT_EOLCHAR))
+
+
+#define UART_PB_LITTLE_ENDIAN 0x00008000 // UART_PB_<n>
+#define UART_PB_BYTES_MASK 0x00000F00 // UART_PB_<n> 0..8 allowed
+#define UART_PB_FIXED_MASK 0xFFFF0000 // UART_PB_RAW
+#define UART_PB_TYPE_MASK 0x000000FF // UART_PB_x
+
+#define UART_PB_RAW 0
+#define UART_PB_N 1
+#define UART_PB_LINE_LF 2
+#define UART_PB_BASIC_0710 3
+#define UART_PB_ADVANCED_0710 4
+#define UART_PB_GSM_0710 5
+
+#define UART_PASSIVE 0
+#define UART_ACTIVE 1
+#define UART_ONCE 2
+
+#define UART_PARITY_NONE 0
+#define UART_PARITY_ODD 1
+#define UART_PARITY_EVEN 2
+#define UART_PARITY_MARK 3
+#define UART_PARITY_SPACE 4
+
+#define UART_DELIVER_PORT 0
+#define UART_DELIVER_TERM 1
+
+#define UART_MODE_LIST 0
+#define UART_MODE_BINARY 1
+
+#define UART_OK 0
+#define UART_ERROR 1
+#define UART_OPTIONS 2
+
+#define UART_F_OPEN 0x0001
+#define UART_F_BUSY 0x0080
+#define UART_F_CLOSE_SENT 0x0002
+#define UART_F_DELAYED_CLOSE_RECV 0x0004
+#define UART_F_DELAYED_CLOSE_SEND 0x0008
+
+
+#define UART_OPT_DEVICE 1
+#define UART_OPT_IBAUD 2
+#define UART_OPT_OBAUD 3
+#define UART_OPT_CSIZE 4
+#define UART_OPT_BUFSZ 5
+#define UART_OPT_BUFTM 6
+#define UART_OPT_STOPB 7
+#define UART_OPT_PARITY 8
+#define UART_OPT_IFLOW 9
+#define UART_OPT_OFLOW 10
+#define UART_OPT_XOFFCHAR 11
+#define UART_OPT_XONCHAR 12
+#define UART_OPT_EOLCHAR 13
+// #define UART_OPT_14 14
+#define UART_OPT_ACTIVE 15
+#define UART_OPT_DELAY_SEND 16
+#define UART_OPT_DELIVER 17
+#define UART_OPT_MODE 18
+// #define UART_OPT_19 19
+#define UART_OPT_HEADER 20
+#define UART_OPT_PACKET 21
+#define UART_OPT_PSIZE 22
+#define UART_OPT_HIGH 23
+#define UART_OPT_LOW 24
+#define UART_OPT_SENDTMO 25 // send timeout
+#define UART_OPT_CLOSETMO 26 // send close timeout
+#define UART_OPT_BUFFER 27
+#define UART_OPT_DEBUG 28
+#define UART_OPT_EXITF 29
+#define UART_OPT_PTYPKT 30
+#define UART_OPT_MAX 31
+
+
+typedef struct {
+ int ibaud; // input baud rate (9600)
+ int obaud; // output baud rate (9600)
+ int parity; // parity (0)
+ int stopb; // stop bits (1)
+ int csize; // characters size (8)
+ int bufsz; // 1 size of UART controller buffer (max 255)
+ int buftm; // 1 milliseconds inter character timeout
+ int xonchar; // 0
+ int xoffchar; // 0
+ int iflow; // 0 input flow control
+ int oflow; // 0 output flow control
+} uart_com_state_t;
+
+
+#define UART_DTR 0x0002 // Data Terminal Ready
+#define UART_RTS 0x0004 // Ready To Send
+#define UART_CTS 0x0008 // Clear To Send
+#define UART_CD 0x0010 // Carrier Detect
+#define UART_RI 0x0020 // Ring Indicator
+#define UART_DSR 0x0040 // Data Set Ready
+#define UART_SW 0x8000 // Software, only for iflow/oflow
+
+typedef uint16_t uart_modem_state_t;
+
+extern ErlDrvTermData am_dtr;
+extern ErlDrvTermData am_rts;
+extern ErlDrvTermData am_cts;
+extern ErlDrvTermData am_cd;
+extern ErlDrvTermData am_ri;
+extern ErlDrvTermData am_dsr;
+extern ErlDrvTermData am_sw;
+
+extern ErlDrvTermData am_device;
+extern ErlDrvTermData am_baud;
+extern ErlDrvTermData am_ibaud;
+extern ErlDrvTermData am_obaud;
+extern ErlDrvTermData am_csize;
+extern ErlDrvTermData am_bufsz;
+extern ErlDrvTermData am_buftm;
+extern ErlDrvTermData am_stopb;
+extern ErlDrvTermData am_parity;
+extern ErlDrvTermData am_iflow;
+extern ErlDrvTermData am_oflow;
+extern ErlDrvTermData am_xoffchar;
+extern ErlDrvTermData am_xonchar;
+extern ErlDrvTermData am_eolchar;
+extern ErlDrvTermData am_active;
+extern ErlDrvTermData am_delay_send;
+extern ErlDrvTermData am_deliver;
+extern ErlDrvTermData am_mode;
+extern ErlDrvTermData am_header;
+extern ErlDrvTermData am_packet;
+extern ErlDrvTermData am_packet_size;
+extern ErlDrvTermData am_high_watermark;
+extern ErlDrvTermData am_low_watermark;
+extern ErlDrvTermData am_send_timeout;
+extern ErlDrvTermData am_send_timeout_close;
+extern ErlDrvTermData am_buffer;
+extern ErlDrvTermData am_exit_on_close;
+extern ErlDrvTermData am_debug;
+extern ErlDrvTermData am_ptypkt;
+
+extern ErlDrvTermData am_none;
+extern ErlDrvTermData am_odd;
+extern ErlDrvTermData am_even;
+extern ErlDrvTermData am_mark;
+extern ErlDrvTermData am_space;
+
+extern ErlDrvTermData am_true;
+extern ErlDrvTermData am_false;
+extern ErlDrvTermData am_once;
+
+extern ErlDrvTermData am_port;
+extern ErlDrvTermData am_term;
+
+extern ErlDrvTermData am_list;
+extern ErlDrvTermData am_binary;
+
+extern ErlDrvTermData am_size;
+extern ErlDrvTermData am_line;
+extern ErlDrvTermData am_basic_0710;
+extern ErlDrvTermData am_advanced_0710;
+extern ErlDrvTermData am_gsm_0710;
+
+extern ErlDrvTermData am_ok;
+extern ErlDrvTermData am_uart;
+extern ErlDrvTermData am_error;
+extern ErlDrvTermData am_uart_async;
+extern ErlDrvTermData am_uart_reply;
+extern ErlDrvTermData am_timeout;
+extern ErlDrvTermData am_closed;
+extern ErlDrvTermData am_uart_closed;
+extern ErlDrvTermData am_uart_error;
+extern ErlDrvTermData am_empty_out_q;
+
+
+#define ATOM(NAME) am_ ## NAME
+#define INIT_ATOM(NAME) am_ ## NAME = driver_mk_atom(#NAME)
+
+#define UART_MAX_DEVICE_NAME 256
+
+#define UART_MAX_PACKET_SIZE 0x01000000
+
+typedef struct {
+ char device_name[UART_MAX_DEVICE_NAME];
+ int high; // high watermark
+ int low; // low watermark
+ int send_timeout; // timeout to use in send
+ int send_timeout_close; // auto-close fd on send_timeout
+ int active; // PASSIVE|ACTIVE|ONCE
+ int delay_send; // just buffer data
+ int deliver; // TERM | PORT
+ int mode; // LIST | BINARY
+ unsigned int htype; // header type
+ unsigned int psize; // max packet size
+ int eolchar; // '\n' line delimiter
+ unsigned int hsz; // the list header size, -1 is large !!!
+ unsigned int bsize; // input buffer size (buffer)
+ int exitf; // exit on error
+ int ptypkt; // pty packet mode
+} uart_opt_t;
+
+
+typedef struct _uart_buf_t
+{
+ size_t sz; // allocate buffer size
+ uint8_t* base; // base buffer point
+ uint8_t* ptr; // current pos in buf
+ uint8_t* ptr_start; // packet start pos in buf
+} uart_buf_t;
+
+extern void uart_buf_init(uart_buf_t* bf);
+extern void uart_buf_finish(uart_buf_t* bf);
+extern void uart_buf_reset(uart_buf_t* bf);
+extern int uart_buf_expand(uart_buf_t* bf, size_t len);
+extern int uart_buf_alloc(uart_buf_t* bf, size_t sz);
+extern void uart_buf_restart(uart_buf_t* bf);
+extern int uart_buf_push(uart_buf_t* bf, char* buf, size_t len);
+extern int uart_buf_packet(uart_buf_t* bf, unsigned int htype,
+ unsigned max_plen, int eol, unsigned trunc_len);
+extern int uart_buf_remain(uart_buf_t* bf, int* len,
+ unsigned int htype, unsigned int psize, int eol);
+
+extern char* format_hex(uint8_t* ptr, int len, char* dst, int dst_len);
+
+typedef struct _uart_queue_t
+{
+ int offs; // offset in current message
+ dmessage_t* mesg; // current message
+ dmessage_t* front; // send from front
+ dmessage_t* rear; // add to rear
+} uart_queue_t;
+
+extern void uart_queue_init(uart_queue_t* q);
+extern void uart_queue_clear(uart_queue_t* q);
+extern void uart_queue_final(uart_queue_t* q);
+extern int set_packet_bytes(dmessage_t* mp, unsigned int htype);
+
+typedef struct _uart_ctx_t
+{
+#ifdef __WIN32__
+ HANDLE fh; // File handle
+ OVERLAPPED in; // Overlapped input
+ OVERLAPPED out; // Overlapped output
+ OVERLAPPED stat; // Overlapped status
+ DWORD statm; // Status result
+ BOOLEAN reading; // Overlapped read in progress
+ BOOLEAN writing; // Overlapped write in progress
+ char rbuf[1]; // Overlapped read into this buffer
+#else
+ int tty_fd; // fd connected to tty device
+ int fd; // master side when pty else same as tty_fd
+#endif
+ ErlDrvPort port;
+ ErlDrvTermData dport; // the port identifier as DriverTermData
+ uint32_t ref; // command reference
+ uint32_t flags; // uart UART_F_xxx
+ int error; // last known error code
+ uint32_t sflags; // flags for update state & opts
+ uart_com_state_t state; // communication params
+ uart_opt_t option;
+ int recv; // ==1 if in recv
+ int remain; // remaining chars to read (recv)
+
+ ErlDrvTermData caller; // recipient of sync reply
+
+ dthread_t* self; // io thread
+ dthread_t* other; // current calling thread
+
+ uart_queue_t oq; // Output queue
+ uart_buf_t ib; // Input buffer
+ ErlDrvNowData t0; // point at start of operations
+ ErlDrvNowData* tp; // if tmo then point to t0!
+ uint32_t tmo; // timeout value in (ms)
+} uart_ctx_t;
+
+extern void com_state_dump(FILE* f, uart_com_state_t* state);
+extern void com_state_init(uart_com_state_t* ptr);
+extern void com_state_copy(uart_com_state_t* dst,uart_com_state_t* src,
+ uint32_t sflags);
+
+extern void modem_state_dump(FILE* f, uart_modem_state_t state);
+extern int modem_state_dterm(dterm_t* p, uart_modem_state_t state);
+
+// uart_options (dterm?)
+extern void put_kv_uint(dterm_t* p,ErlDrvTermData key, uint32_t value);
+extern void put_kv_int(dterm_t* p,ErlDrvTermData key, int32_t value);
+extern void put_kv_atom(dterm_t* p,ErlDrvTermData key, ErlDrvTermData value);
+extern void put_kv_bool(dterm_t* p,ErlDrvTermData key, int value);
+extern void put_kv_string(dterm_t* p,ErlDrvTermData key, char* value);
+extern int uart_get_opts(dterm_t* p, uart_ctx_t* ctx, uint8_t* ptr, size_t len);
+extern int uart_parse_opts(char* buf, ErlDrvSizeT len,
+ uart_com_state_t* state, uart_opt_t* option,
+ uint32_t* sflags);
+
+extern int uart_reply_error(uart_ctx_t* ctx, int err);
+extern int uart_port_data(uart_ctx_t* ctx, const char* buf, int len);
+extern int uart_port_binary_data(uart_ctx_t* ctx, ErlDrvBinary* bin, int offs, int len);
+extern int uart_message(uart_ctx_t* ctx, const char* buf, int len);
+extern int uart_binary_message(uart_ctx_t* ctx, ErlDrvBinary* bin,
+ int offs, int len);
+extern int uart_closed_message(uart_ctx_t* ctx);
+extern int uart_error_message(uart_ctx_t* ctx, int err);
+
+extern int uart_reply_data(uart_ctx_t* ctx, char* buf, int len);
+extern int uart_reply_binary_data(uart_ctx_t* ctx, ErlDrvBinary* bin, int offs, int len);
+
+extern int uart_async_error_am(uart_ctx_t* ctx, ErlDrvTermData Port,
+ ErlDrvTermData recipient, ErlDrvTermData Reason);
+extern int uart_async_error(uart_ctx_t* ctx, ErlDrvTermData Port,
+ ErlDrvTermData recipient, int err);
+
+#endif
diff --git a/deps/uart/c_src/uart_ftdi.c b/deps/uart/c_src/uart_ftdi.c
new file mode 100644
index 0000000..4beb78f
--- /dev/null
+++ b/deps/uart/c_src/uart_ftdi.c
@@ -0,0 +1,34 @@
+
+
+#ifdef HAVE_FTDI
+#include "ftd2xx.h"
+#endif
+
+#ifdef HAVE_FTDI
+const char* ft_strerror(FT_STATUS status)
+{
+ switch(status) {
+ case FT_OK: return "ok";
+ case FT_INVALID_HANDLE: return "invalid handle";
+ case FT_DEVICE_NOT_FOUND: return "device not found";
+ case FT_DEVICE_NOT_OPENED: return "device not opened";
+ case FT_IO_ERROR: return "io error";
+ case FT_INSUFFICIENT_RESOURCES: return "insufficent resources";
+ case FT_INVALID_PARAMETER: return "invalid paramter";
+ case FT_INVALID_BAUD_RATE: return "invalid baud rate";
+ case FT_DEVICE_NOT_OPENED_FOR_ERASE: return "device not opened for erase";
+ case FT_DEVICE_NOT_OPENED_FOR_WRITE: return "device not opened for write";
+ case FT_FAILED_TO_WRITE_DEVICE: return "faile to write device";
+ case FT_EEPROM_READ_FAILED: return "eeprom read failed";
+ case FT_EEPROM_WRITE_FAILED: return "eeprom write failed";
+ case FT_EEPROM_ERASE_FAILED: return "eeprom erase failed";
+ case FT_EEPROM_NOT_PRESENT: return "eeprom not present";
+ case FT_EEPROM_NOT_PROGRAMMED: return "eeprom not programmed";
+ case FT_INVALID_ARGS: return "invalid arguments";
+ case FT_NOT_SUPPORTED: return "not supported";
+ case FT_OTHER_ERROR: return "other error";
+ default: return "unknown error (%d)";
+ }
+}
+#endif
+
diff --git a/deps/uart/c_src/uart_message.c b/deps/uart/c_src/uart_message.c
new file mode 100644
index 0000000..e7943ef
--- /dev/null
+++ b/deps/uart/c_src/uart_message.c
@@ -0,0 +1,352 @@
+//
+// Implements message formating and sending
+//
+
+#include <ctype.h>
+
+#include "uart_drv.h"
+
+static ErlDrvTermData error_atom(int err)
+{
+ char errstr[256];
+ char* s;
+ char* t;
+
+ for (s = erl_errno_id(err), t = errstr; *s; s++, t++)
+ *t = tolower(*s);
+ *t = '\0';
+ return driver_mk_atom(errstr);
+}
+
+/* send message:
+** {uart_async, Port, Ref, ok}
+*/
+int uart_async_ok(uart_ctx_t* ctx,ErlDrvTermData port,ErlDrvTermData recipient)
+{
+ dterm_t t;
+ dterm_mark_t m;
+ int r;
+
+ dterm_init(&t);
+ dterm_tuple_begin(&t, &m); {
+ dterm_atom(&t, am_uart_async);
+ dterm_port(&t, port);
+ dterm_uint(&t, ctx->ref);
+ dterm_atom(&t, am_ok);
+ }
+ dterm_tuple_end(&t, &m);
+
+ r = dthread_port_send_dterm(ctx->other, ctx->self, recipient, &t);
+ dterm_finish(&t);
+ return r;
+}
+//
+// send message:
+// {uart_async, port, Ref, {error,Reason}}
+//
+int uart_async_error_am(uart_ctx_t* ctx, ErlDrvTermData port,
+ ErlDrvTermData recipient, ErlDrvTermData Reason)
+{
+ dterm_t t;
+ dterm_mark_t m;
+ int r;
+
+ dterm_init(&t);
+ dterm_tuple_begin(&t, &m); {
+ dterm_mark_t m1;
+ dterm_atom(&t, am_uart_async);
+ dterm_port(&t, port);
+ dterm_uint(&t, ctx->ref);
+ dterm_tuple_begin(&t, &m1); {
+ dterm_atom(&t, am_error);
+ dterm_atom(&t, Reason);
+ }
+ dterm_tuple_end(&t, &m1);
+ }
+ dterm_tuple_end(&t, &m);
+
+ r = dthread_port_send_dterm(ctx->other, ctx->self, recipient, &t);
+ dterm_finish(&t);
+ return r;
+}
+
+int uart_async_error(uart_ctx_t* ctx, ErlDrvTermData port,
+ ErlDrvTermData recipient, int err)
+
+{
+ return uart_async_error_am(ctx, port, recipient, error_atom(err));
+}
+
+//
+// {Port,{data,Binary}} |
+// {Port,{data,[H1,..Hn|Binary]}} |
+// {Port,{data,[B1,..Bn]}}
+//
+int uart_port_data(uart_ctx_t* ctx, const char* buf, int len)
+{
+ unsigned int hsz = ctx->option.hsz;
+
+ DEBUGF("uart_port_data(%ld): len = %d", (long)ctx->port, len);
+
+ if ((ctx->option.mode == UART_MODE_LIST) || ((int)hsz > len))
+ return dthread_port_output2(ctx->other, ctx->self,
+ (char*)buf, len, NULL, 0);
+ else if (hsz > 0)
+ return dthread_port_output2(ctx->other, ctx->self,
+ (char*)buf, hsz, (char*)buf+hsz, len-hsz);
+ else
+ return driver_output(ctx->port, (char*)buf, len);
+}
+
+// construct the data list [H1...,Hsz | Data]
+static void put_data_list(uart_ctx_t* ctx, dterm_t* t, const char* buf, int len)
+{
+ unsigned int hsz = ctx->option.hsz;
+
+ if ((ctx->option.mode == UART_MODE_LIST) || ((int)hsz > len))
+ dterm_string(t, buf, len);
+ else {
+ int sz = len - hsz;
+ dterm_buf_binary(t, buf+hsz, sz);
+ if (hsz > 0)
+ dterm_string_cons(t, buf, hsz);
+ }
+}
+
+// construct the data list [H1...,Hsz | Data] from binary
+static void put_data_bin(uart_ctx_t* ctx, dterm_t* t, ErlDrvBinary* bin, int offs, int len)
+{
+ unsigned int hsz = ctx->option.hsz;
+ if ((ctx->option.mode == UART_MODE_LIST) || ((int)hsz > len))
+ dterm_string(t, bin->orig_bytes+offs, len);
+ else {
+ int sz = len - hsz;
+ dterm_binary(t, bin, offs+hsz, sz);
+ if (hsz > 0)
+ dterm_string_cons(t, bin->orig_bytes+offs, hsz);
+ }
+}
+
+//
+// Deliver port data from binary (for an active mode socket)
+//
+int uart_port_binary_data(uart_ctx_t* ctx, ErlDrvBinary* bin, int offs, int len)
+{
+ unsigned int hsz = ctx->option.hsz;
+
+ DEBUGF("uart_port_binary_data(%ld): offs=%d, len = %d",
+ (long)ctx->port, offs, len);
+
+ if ((ctx->option.mode == UART_MODE_LIST) || ((int)hsz > len))
+ return dthread_port_output2(ctx->other, ctx->self,
+ bin->orig_bytes+offs, len, NULL, 0);
+ else
+ return dthread_port_output_binary(ctx->other, ctx->self,
+ bin->orig_bytes+offs, hsz,
+ bin, offs+hsz, len-hsz);
+}
+
+//
+// {uart, S, [H1,...Hsz | Data]}
+//
+int uart_message(uart_ctx_t* ctx, const char* buf, int len)
+{
+ dterm_t t;
+ dterm_mark_t m;
+ int r;
+
+ DEBUGF("uart_message(%ld): len = %d", (long)ctx->port, len);
+
+ dterm_init(&t);
+ dterm_tuple_begin(&t, &m); {
+ dterm_atom(&t, am_uart);
+ dterm_port(&t, ctx->dport);
+ put_data_list(ctx, &t, buf, len);
+ }
+ dterm_tuple_end(&t, &m);
+
+ r=dthread_port_output_dterm(ctx->other, ctx->self, &t);
+ dterm_finish(&t);
+ return r;
+}
+
+//
+//
+// {uart, S, [H1,...Hsz | Data]}
+//
+int uart_binary_message(uart_ctx_t* ctx, ErlDrvBinary* bin, int offs, int len)
+{
+ dterm_t t;
+ dterm_mark_t m;
+ int r;
+
+ DEBUGF("uart_binary_message(%ld): len = %d", (long)ctx->port, len);
+
+ dterm_init(&t);
+ dterm_tuple_begin(&t, &m); {
+ dterm_atom(&t, am_uart);
+ dterm_port(&t, ctx->dport);
+ put_data_bin(ctx, &t, bin, offs, len);
+ }
+ dterm_tuple_end(&t, &m);
+ r=dthread_port_output_dterm(ctx->other, ctx->self, &t);
+ dterm_finish(&t);
+ return r;
+}
+
+//
+// {uart_closed, S}
+//
+int uart_closed_message(uart_ctx_t* ctx)
+{
+ DEBUGF("uart_closed_message(%ld):", (long)ctx->port);
+
+ if (!(ctx->flags & UART_F_CLOSE_SENT)) {
+ dterm_t t;
+ dterm_mark_t m;
+ int r;
+
+ ctx->flags |= UART_F_CLOSE_SENT;
+ dterm_init(&t);
+ dterm_tuple_begin(&t, &m); {
+ dterm_atom(&t, am_uart_closed);
+ dterm_port(&t, ctx->dport);
+ }
+ dterm_tuple_end(&t, &m);
+ r=dthread_port_output_dterm(ctx->other, ctx->self, &t);
+ dterm_finish(&t);
+ return r;
+ }
+ return 0;
+}
+
+//
+// {uart_error, S, Error}
+//
+int uart_error_message(uart_ctx_t* ctx, int err)
+{
+ ErlDrvTermData e = error_atom(err);
+ dterm_t t;
+ dterm_mark_t m;
+ int r;
+
+ DEBUGF("uart_error_message(%ld): %d", (long)ctx->port, err);
+
+ dterm_init(&t);
+ dterm_tuple_begin(&t, &m); {
+ dterm_atom(&t, am_uart_error);
+ dterm_port(&t, ctx->dport);
+ dterm_atom(&t, e);
+ }
+ dterm_tuple_end(&t, &m);
+ r=dthread_port_output_dterm(ctx->other, ctx->self, &t);
+ dterm_finish(&t);
+ return r;
+}
+
+//
+// {uart_async, U, Ref, {ok,[H1,...Hsz | Data]}}
+//
+int uart_async_data(uart_ctx_t* ctx, const char* buf, int len)
+{
+ dterm_t t;
+ dterm_mark_t m;
+ int r;
+
+ DEBUGF("uart_async_data(%ld): len = %d", (long)ctx->port, len);
+
+ dterm_init(&t);
+ dterm_tuple_begin(&t, &m); {
+ dterm_atom(&t, am_uart_async);
+ dterm_port(&t, ctx->dport);
+ dterm_uint(&t, ctx->ref);
+ put_data_list(ctx, &t, buf, len);
+ }
+ dterm_tuple_end(&t, &m);
+ r=dthread_port_output_dterm(ctx->other, ctx->self, &t);
+ dterm_finish(&t);
+ return r;
+}
+
+
+//
+// {uart_async, U, Ref, {ok,[H1,...Hsz | Data]}}
+//
+int uart_async_binary_data(uart_ctx_t* ctx,
+ ErlDrvBinary* bin, int offs, int len)
+{
+ dterm_t t;
+ dterm_mark_t m;
+ int r;
+
+ DEBUGF("uart_async_binary_data(%ld): len = %d", (long)ctx->port, len);
+
+ dterm_init(&t);
+ dterm_tuple_begin(&t, &m); {
+ dterm_atom(&t, am_uart_async);
+ dterm_port(&t, ctx->dport);
+ dterm_uint(&t, ctx->ref);
+ put_data_bin(ctx, &t, bin, offs, len);
+ }
+ dterm_tuple_end(&t, &m);
+ r=dthread_port_output_dterm(ctx->other, ctx->self, &t);
+ dterm_finish(&t);
+ return r;
+}
+
+int uart_reply_data(uart_ctx_t* ctx, char* buf, int len)
+{
+ int code;
+ const char* body = buf;
+ int bodylen = len;
+
+ if ((ctx->option.htype & UART_PB_TYPE_MASK) == UART_PB_N) {
+ unsigned n = (ctx->option.htype & UART_PB_BYTES_MASK) >> 8;
+ body += n;
+ bodylen -= n;
+ }
+ if (ctx->option.deliver == UART_DELIVER_PORT) {
+ code = uart_port_data(ctx, body, bodylen);
+ }
+ else {
+ if (ctx->option.active == UART_PASSIVE)
+ return uart_async_data(ctx, body, bodylen);
+ else
+ code = uart_message(ctx, body, bodylen);
+ }
+ if (code < 0)
+ return code;
+ if (ctx->option.active == UART_ONCE)
+ ctx->option.active = UART_PASSIVE;
+ return code;
+}
+
+int uart_reply_binary_data(uart_ctx_t* ctx, ErlDrvBinary* bin, int offs, int len)
+{
+ int code;
+ const char* buf = bin->orig_bytes + offs;
+ const char* body = buf;
+ int bodylen = len;
+
+ if ((ctx->option.htype & UART_PB_TYPE_MASK) == UART_PB_N) {
+ unsigned n = (ctx->option.htype & UART_PB_BYTES_MASK) >> 8;
+ body += n;
+ bodylen -= n;
+ }
+
+ offs = body - bin->orig_bytes; /* body offset now */
+
+ if (ctx->option.deliver == UART_DELIVER_PORT)
+ code = uart_port_binary_data(ctx, bin, offs, bodylen);
+ else {
+ if (ctx->option.active == UART_PASSIVE)
+ return uart_async_binary_data(ctx, bin, offs, bodylen);
+ else
+ code = uart_binary_message(ctx, bin, offs, bodylen);
+ }
+ if (code < 0)
+ return code;
+ if (ctx->option.active == UART_ONCE)
+ ctx->option.active = UART_PASSIVE;
+ return code;
+}
diff --git a/deps/uart/c_src/uart_modem_state.c b/deps/uart/c_src/uart_modem_state.c
new file mode 100644
index 0000000..efb84a2
--- /dev/null
+++ b/deps/uart/c_src/uart_modem_state.c
@@ -0,0 +1,36 @@
+//
+// Modem state utils
+//
+
+#include "uart_drv.h"
+
+// Build a list of modem state flags
+int modem_state_dterm(dterm_t* t, uart_modem_state_t state)
+{
+ dterm_mark_t m;
+
+ dterm_list_begin(t, &m);
+ if (state & UART_DTR) dterm_atom(t, am_dtr);
+ if (state & UART_RTS) dterm_atom(t, am_rts);
+ if (state & UART_CTS) dterm_atom(t, am_cts);
+ if (state & UART_CD) dterm_atom(t, am_cd);
+ if (state & UART_RI) dterm_atom(t, am_ri);
+ if (state & UART_DSR) dterm_atom(t, am_dsr);
+ // SW is not really in modem state but used with flow control
+ if (state & UART_SW) dterm_atom(t, am_sw);
+ dterm_list_end(t, &m);
+ return dterm_used_size(t);
+}
+
+void modem_state_dump(FILE* f, uart_modem_state_t state)
+{
+ fprintf(f, "modem state:");
+ if (state & UART_DTR) fprintf(f," DTR");
+ if (state & UART_RTS) fprintf(f," RTS");
+ if (state & UART_CTS) fprintf(f," CTS");
+ if (state & UART_CD) fprintf(f," CD");
+ if (state & UART_RI) fprintf(f," RI");
+ if (state & UART_DSR) fprintf(f," DSR");
+ if (state & UART_SW) fprintf(f," SW");
+ fprintf(f, "\r\n");
+}
diff --git a/deps/uart/c_src/uart_options.c b/deps/uart/c_src/uart_options.c
new file mode 100644
index 0000000..f047053
--- /dev/null
+++ b/deps/uart/c_src/uart_options.c
@@ -0,0 +1,273 @@
+//
+// Option processing
+//
+#include "uart_drv.h"
+
+// encode options [{opt,value}] into dterm
+int uart_get_opts(dterm_t* t, uart_ctx_t* ctx, uint8_t* ptr, size_t len)
+{
+ uint8_t* ptr_end = ptr + len;
+ dterm_mark_t m;
+
+ dterm_list_begin(t, &m);
+
+ while(ptr < ptr_end) {
+ uint8_t opt = *ptr++;
+ switch(opt) {
+ case UART_OPT_DEVICE:
+ dterm_kv_string(t,am_device,ctx->option.device_name);
+ break;
+ case UART_OPT_IBAUD:
+ dterm_kv_uint(t,am_ibaud,ctx->state.ibaud);
+ break;
+ case UART_OPT_OBAUD:
+ dterm_kv_uint(t,am_obaud,ctx->state.obaud);
+ break;
+ case UART_OPT_CSIZE:
+ dterm_kv_uint(t,am_csize, ctx->state.csize);
+ break;
+ case UART_OPT_BUFSZ:
+ dterm_kv_uint(t,am_bufsz, ctx->state.bufsz);
+ break;
+ case UART_OPT_BUFTM:
+ dterm_kv_uint(t,am_buftm, ctx->state.buftm);
+ break;
+ case UART_OPT_STOPB:
+ dterm_kv_uint(t,am_stopb, ctx->state.stopb);
+ break;
+ case UART_OPT_PARITY:
+ switch(ctx->state.parity) {
+ case UART_PARITY_NONE: dterm_kv_atom(t,am_parity, am_none); break;
+ case UART_PARITY_ODD: dterm_kv_atom(t,am_parity, am_odd); break;
+ case UART_PARITY_EVEN: dterm_kv_atom(t,am_parity, am_even); break;
+ case UART_PARITY_MARK: dterm_kv_atom(t,am_parity, am_mark); break;
+ case UART_PARITY_SPACE: dterm_kv_atom(t,am_parity, am_space); break;
+ default: dterm_kv_uint(t,am_parity,ctx->state.parity); break;
+ }
+ break;
+
+ case UART_OPT_IFLOW: {
+ dterm_mark_t m2;
+ dterm_tuple_begin(t, &m2); {
+ dterm_atom(t, am_iflow);
+ modem_state_dterm(t, ctx->state.iflow);
+ }
+ dterm_tuple_end(t, &m2);
+ break;
+ }
+
+ case UART_OPT_OFLOW: {
+ dterm_mark_t m2;
+ dterm_tuple_begin(t, &m2); {
+ dterm_atom(t, am_oflow);
+ modem_state_dterm(t, ctx->state.oflow);
+ }
+ dterm_tuple_end(t, &m2);
+ break;
+ }
+
+ case UART_OPT_XOFFCHAR:
+ dterm_kv_uint(t,am_xoffchar, ctx->state.xoffchar);
+ break;
+
+ case UART_OPT_XONCHAR:
+ dterm_kv_uint(t,am_xonchar, ctx->state.xonchar);
+ break;
+
+ case UART_OPT_EOLCHAR:
+ dterm_kv_uint(t,am_eolchar, ctx->option.eolchar);
+ break;
+
+ case UART_OPT_ACTIVE:
+ switch(ctx->option.active) {
+ case UART_PASSIVE: dterm_kv_atom(t,am_active, am_false); break;
+ case UART_ACTIVE: dterm_kv_atom(t,am_active, am_true); break;
+ case UART_ONCE: dterm_kv_atom(t,am_active, am_once); break;
+ default: dterm_kv_int(t,am_active, ctx->option.active); break;
+ }
+ break;
+
+ case UART_OPT_DELAY_SEND:
+ dterm_kv_bool(t,am_delay_send, ctx->option.delay_send);
+ break;
+
+ case UART_OPT_DELIVER:
+ switch (ctx->option.deliver) {
+ case UART_DELIVER_PORT: dterm_kv_atom(t,am_deliver, am_port); break;
+ case UART_DELIVER_TERM: dterm_kv_atom(t,am_deliver, am_term); break;
+ default: dterm_kv_int(t,am_deliver,ctx->option.deliver); break;
+ }
+ break;
+
+ case UART_OPT_MODE:
+ switch (ctx->option.mode) {
+ case UART_MODE_LIST: dterm_kv_atom(t,am_mode, am_list); break;
+ case UART_MODE_BINARY: dterm_kv_atom(t,am_mode, am_binary); break;
+ default: dterm_kv_int(t,am_mode,ctx->option.mode); break;
+ }
+ break;
+
+ case UART_OPT_HEADER:
+ dterm_kv_uint(t,am_header,ctx->option.hsz);
+ break;
+
+ case UART_OPT_PACKET:
+ switch(ctx->option.htype & UART_PB_TYPE_MASK) {
+ case UART_PB_RAW: {
+ uint16_t fsz;
+ if ((fsz=((ctx->option.htype & UART_PB_FIXED_MASK)>>16)) == 0) {
+ dterm_kv_uint(t,am_packet, 0);
+ }
+ else {
+ dterm_mark_t m2,m3;
+ dterm_tuple_begin(t, &m2); {
+ dterm_atom(t, am_packet);
+ dterm_tuple_begin(t, &m3); {
+ dterm_atom(t, am_size);
+ dterm_uint(t, fsz);
+ }
+ dterm_tuple_end(t, &m3);
+ }
+ dterm_tuple_end(t, &m2);
+ }
+ break;
+ }
+ case UART_PB_N: {
+ int pb = ((ctx->option.htype & UART_PB_BYTES_MASK)>>8);
+ if (ctx->option.htype & UART_PB_LITTLE_ENDIAN)
+ dterm_kv_int(t,am_packet, -pb);
+ else
+ dterm_kv_int(t,am_packet, pb);
+ break;
+ }
+ case UART_PB_LINE_LF:
+ dterm_kv_atom(t,am_packet, am_line);
+ break;
+ case UART_PB_BASIC_0710:
+ dterm_kv_atom(t,am_packet, am_basic_0710);
+ break;
+ case UART_PB_ADVANCED_0710:
+ dterm_kv_atom(t,am_packet, am_advanced_0710);
+ break;
+ case UART_PB_GSM_0710:
+ dterm_kv_atom(t,am_packet, am_gsm_0710);
+ break;
+ default:
+ dterm_kv_uint(t,am_packet, ctx->option.htype);
+ break;
+ }
+ break;
+
+ case UART_OPT_PSIZE:
+ dterm_kv_uint(t,am_packet_size, ctx->option.psize);
+ break;
+ case UART_OPT_HIGH:
+ dterm_kv_uint(t,am_high_watermark, ctx->option.high);
+ break;
+ case UART_OPT_LOW:
+ dterm_kv_uint(t,am_low_watermark, ctx->option.low);
+ break;
+ case UART_OPT_SENDTMO:
+ dterm_kv_uint(t,am_send_timeout, ctx->option.send_timeout);
+ break;
+ case UART_OPT_CLOSETMO:
+ dterm_kv_uint(t,am_send_timeout_close,
+ ctx->option.send_timeout_close);
+ break;
+ case UART_OPT_PTYPKT:
+ dterm_kv_bool(t,am_ptypkt, ctx->option.ptypkt);
+ break;
+ case UART_OPT_BUFFER:
+ dterm_kv_uint(t,am_buffer, ctx->option.bsize);
+ break;
+ case UART_OPT_EXITF:
+ dterm_kv_bool(t,am_exit_on_close, ctx->option.exitf);
+ break;
+ case UART_OPT_DEBUG:
+ dterm_kv_int(t,am_debug, dlog_debug_level);
+ break;
+ default:
+ break;
+ }
+ }
+ dterm_list_end(t, &m);
+ return dterm_used_size(t);
+}
+
+int uart_parse_opts(char* buf, ErlDrvSizeT len,
+ uart_com_state_t* state, uart_opt_t* option,
+ uint32_t* sflags)
+{
+ unsigned char* ptr = (unsigned char*) buf;
+ unsigned char* ptr_end = ptr + len;
+
+#define GET_UINT32(v) do { \
+ if ((ptr_end - ptr) < 4) return -1; \
+ v = (int) get_uint32(ptr); \
+ DEBUGF("option %s value=0x%08x", #v, v); \
+ ptr += 4; \
+ } while(0)
+
+ DEBUGF("set_opts: called");
+
+ // process updates
+ while(ptr < ptr_end) {
+ DEBUGF("set_opts: opt=%u", ptr[0]);
+ *sflags |= (1 << ptr[0]);
+
+ switch(*ptr++) {
+ case UART_OPT_DEVICE: {
+ int n;
+ if ((ptr_end - ptr) < 1) return -1;
+ n = get_uint8(ptr);
+ ptr++;
+ if ((ptr_end - ptr) < n) return -1;
+ memcpy(option->device_name, ptr, n);
+ option->device_name[n] = 0;
+ DEBUGF("set_opts: device_name = %s", option->device_name);
+ ptr += n;
+ break;
+ }
+ case UART_OPT_DEBUG: {
+ int level;
+ if ((ptr_end - ptr) < 4)
+ return -1;
+ level = (int32_t) get_uint32(ptr);
+ DEBUGF("option debug value=0x%08x", level);
+ dlog_set_debug(level);
+ ptr += 4;
+ break;
+ }
+ case UART_OPT_IBAUD: GET_UINT32(state->ibaud); break;
+ case UART_OPT_OBAUD: GET_UINT32(state->obaud); break;
+ case UART_OPT_CSIZE: GET_UINT32(state->csize); break;
+ case UART_OPT_BUFSZ: GET_UINT32(state->bufsz); break;
+ case UART_OPT_BUFTM: GET_UINT32(state->buftm); break;
+ case UART_OPT_STOPB: GET_UINT32(state->stopb); break;
+ case UART_OPT_PARITY: GET_UINT32(state->parity); break;
+ case UART_OPT_IFLOW: GET_UINT32(state->iflow); break;
+ case UART_OPT_OFLOW: GET_UINT32(state->oflow); break;
+ case UART_OPT_XOFFCHAR: GET_UINT32(state->xoffchar); break;
+ case UART_OPT_XONCHAR: GET_UINT32(state->xonchar); break;
+ case UART_OPT_EOLCHAR: GET_UINT32(option->eolchar); break;
+ case UART_OPT_ACTIVE: GET_UINT32(option->active); break;
+ case UART_OPT_DELAY_SEND: GET_UINT32(option->delay_send); break;
+ case UART_OPT_DELIVER: GET_UINT32(option->deliver); break;
+ case UART_OPT_MODE: GET_UINT32(option->mode); break;
+ case UART_OPT_HEADER: GET_UINT32(option->hsz); break;
+ case UART_OPT_PACKET: GET_UINT32(option->htype); break;
+ case UART_OPT_PSIZE: GET_UINT32(option->psize); break;
+ case UART_OPT_HIGH: GET_UINT32(option->high); break;
+ case UART_OPT_LOW: GET_UINT32(option->low); break;
+ case UART_OPT_SENDTMO: GET_UINT32(option->send_timeout); break;
+ case UART_OPT_CLOSETMO: GET_UINT32(option->send_timeout_close); break;
+ case UART_OPT_BUFFER: GET_UINT32(option->bsize); break;
+ case UART_OPT_EXITF: GET_UINT32(option->exitf); break;
+ case UART_OPT_PTYPKT: GET_UINT32(option->ptypkt); break;
+ default:
+ return -1;
+ }
+ }
+ return 0;
+#undef GET_UINT32
+}
diff --git a/deps/uart/c_src/uart_queue.c b/deps/uart/c_src/uart_queue.c
new file mode 100644
index 0000000..f7bb5c7
--- /dev/null
+++ b/deps/uart/c_src/uart_queue.c
@@ -0,0 +1,84 @@
+//
+// Output queue processing
+//
+#include "uart_drv.h"
+
+void uart_queue_init(uart_queue_t* q)
+{
+ memset(q, 0, sizeof(uart_queue_t));
+}
+
+void uart_queue_clear(uart_queue_t* q)
+{
+ dmessage_t* mp;
+
+ if ((mp = q->mesg) != NULL) {
+ dmessage_free(mp);
+ q->mesg = NULL;
+ }
+ q->offs = 0;
+ mp = q->front;
+ while(mp) {
+ dmessage_t* tmp = mp->next;
+ dmessage_free(mp);
+ mp = tmp;
+ }
+ q->front = NULL;
+ q->rear = NULL;
+}
+
+void uart_queue_final(uart_queue_t* q)
+{
+ uart_queue_clear(q);
+}
+
+//
+// Patch message with header bytes
+// 8 bytes are reserved before message on entry
+// mp->data + 8 == mp->buffer
+//
+int set_packet_bytes(dmessage_t* mp, unsigned int htype)
+{
+#define UINT8(x) ((uint8_t) (x))
+ if ((htype & UART_PB_TYPE_MASK) == UART_PB_N) {
+ int h_len;
+ uint64_t pl = (uint64_t) mp->used;
+ h_len = (htype & UART_PB_BYTES_MASK) >> 8;
+ // FIXME check that we have at least 8 byte reserved
+ if (htype & UART_PB_LITTLE_ENDIAN) {
+ uint8_t* ptr = (uint8_t*) mp->buffer-h_len;
+ switch(h_len) {
+ case 8: *ptr++ = UINT8(pl); pl >>= 8;
+ case 7: *ptr++ = UINT8(pl); pl >>= 8;
+ case 6: *ptr++ = UINT8(pl); pl >>= 8;
+ case 5: *ptr++ = UINT8(pl); pl >>= 8;
+ case 4: *ptr++ = UINT8(pl); pl >>= 8;
+ case 3: *ptr++ = UINT8(pl); pl >>= 8;
+ case 2: *ptr++ = UINT8(pl); pl >>= 8;
+ case 1: *ptr = UINT8(pl); break;
+ default: return -1;
+ }
+ mp->buffer -= h_len;
+ mp->used += h_len;
+ }
+ else {
+ uint8_t* ptr = (uint8_t*)mp->buffer;
+ switch(h_len) {
+ case 8: *--ptr = UINT8(pl); pl >>= 8;
+ case 7: *--ptr = UINT8(pl); pl >>= 8;
+ case 6: *--ptr = UINT8(pl); pl >>= 8;
+ case 5: *--ptr = UINT8(pl); pl >>= 8;
+ case 4: *--ptr = UINT8(pl); pl >>= 8;
+ case 3: *--ptr = UINT8(pl); pl >>= 8;
+ case 2: *--ptr = UINT8(pl); pl >>= 8;
+ case 1: *--ptr = UINT8(pl); break;
+ default: return -1;
+ }
+ mp->buffer = (char*)ptr;
+ mp->used += h_len;
+ }
+ }
+ return 0;
+#undef UINT8
+}
+
diff --git a/deps/uart/c_src/uart_unix.c b/deps/uart/c_src/uart_unix.c
new file mode 100644
index 0000000..9889c7f
--- /dev/null
+++ b/deps/uart/c_src/uart_unix.c
@@ -0,0 +1,1362 @@
+//
+// UART "linux"/"unix"/"posix" implementation
+//
+// Nice ref about serial stuff is:
+// http://www.easysw.com/~mike/serial/serial.html
+// http://www.unixwiz.net/techtips/termios-vmin-vtime.html
+//
+//
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <string.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <sys/ioctl.h>
+
+#include "uart_drv.h"
+
+#if defined(__APPLE__)
+#include <util.h>
+#define HAVE_PTY
+#else
+#include <pty.h>
+#define HAVE_PTY
+#endif
+
+static struct _rate {
+ int baud;
+ unsigned int speed;
+} rate_tab[] =
+{
+ {0, B0 },
+ {50, B50 },
+ {75, B75 },
+ {110, B110 },
+ {134, B134 },
+ {150, B150 },
+ {200, B200 },
+ {300, B300 },
+ {600, B600 },
+ {1200, B1200 },
+ {1800, B1800 },
+ {2400, B2400 },
+ {4800, B4800 },
+ {9600, B9600 },
+#ifdef B19200
+ {19200, B19200 },
+#elif defined(EXTA)
+ {19200, EXTA },
+#endif
+#ifdef B38400
+ {38400, B38400 },
+#elif defined(EXTB)
+ {38400, EXTB },
+#endif
+#ifdef B57600
+ {57600, B57600 },
+#endif
+#ifdef B76800
+ {76800, B76800 },
+#endif
+#ifdef B115200
+ {115200, B115200 },
+#endif
+#ifdef B153600
+ {153600, B153600 },
+#endif
+#ifdef B230400
+ {230400, B230400 },
+#endif
+#ifdef B307200
+ {307200, B307200 },
+#endif
+#ifdef B460800
+ {460800, B460800 },
+#endif
+#ifdef B500000
+ {500000, B500000 },
+#endif
+#ifdef B576000
+ {576000, B576000 },
+#endif
+#ifdef B921600
+ {921600, B921600 },
+#endif
+#ifdef B1000000
+ {1000000, B1000000 },
+#endif
+#ifdef B1152000
+ {1152000, B1152000 },
+#endif
+#ifdef B1500000
+ {1500000, B1500000 },
+#endif
+#ifdef B2000000
+ {2000000, B2000000 },
+#endif
+#ifdef B2500000
+ {2500000, B2500000 },
+#endif
+#ifdef B3000000
+ {3000000, B3000000 },
+#endif
+#ifdef B3500000
+ {3500000, B3500000 },
+#endif
+#ifdef B4000000
+ {4000000, B4000000 },
+#endif
+ { -1, B0 }
+};
+
+
+#ifdef DARWIN
+#define HAVE_C_ISPEED 1
+#define HAVE_C_OSPEED 1
+#endif
+
+// save last error & return it
+static int uart_errno(uart_ctx_t* ctx)
+{
+ int err = errno;
+ ctx->error = err;
+ return err;
+}
+
+static int from_speed(unsigned int speed)
+{
+#ifdef DIRECT_SPEED
+ return (int) speed;
+#else
+ int i = 0;
+ int baud;
+
+ while((rate_tab[i].baud != -1) && (rate_tab[i].speed != speed))
+ i++;
+ baud = rate_tab[i].baud;
+ return baud;
+#endif
+}
+
+static unsigned int to_speed(int baud)
+{
+#ifdef DIRECT_SPEED
+ return (unsigned int) baud;
+#else
+ int i = 0;
+ int speed = 0;
+ while((rate_tab[i].baud != -1) && (baud > rate_tab[i].baud))
+ i++;
+ if (rate_tab[i].baud == -1)
+ speed = rate_tab[i-1].speed;
+ else
+ speed = rate_tab[i].speed;
+ return speed;
+#endif
+}
+
+
+
+//
+// t1 - t0 - t1 MUST be greater equal to t0 (later in time)
+// or 0 is returned
+//
+unsigned long diff_time_ms(ErlDrvNowData* t1, ErlDrvNowData* t0)
+{
+ unsigned long dm,ds,du;
+ unsigned long d = 0;
+ unsigned long b;
+ unsigned long t;
+
+ if (t1->microsecs >= t0->microsecs) {
+ du = t1->microsecs - t0->microsecs;
+ b = 0;
+ }
+ else {
+ du = (1000000 + t1->microsecs) - t0->microsecs;
+ b = 1;
+ }
+
+ t = t0->secs+b;
+ if (t1->secs >= t) {
+ ds = t1->secs - t;
+ b = 0;
+ }
+ else {
+ ds = (t1->secs+1000000) - t;
+ b = 1;
+ }
+
+ t = t0->megasecs+b;
+ if (t1->megasecs >= t) {
+ dm = t1->megasecs - t;
+ b = 0;
+ }
+ else {
+ dm = (t1->megasecs+1000000) - t;
+ b = 1;
+ }
+ if (b == 1)
+ return 0;
+ d = du/1000;
+ if (ds)
+ d += ds*1000;
+ if (dm)
+ d += dm*1000000000;
+ return d;
+}
+
+void clear_timeout(uart_ctx_t* ctx)
+{
+ ctx->recv = 0;
+ ctx->tp = NULL;
+}
+
+void set_timeout(uart_ctx_t* ctx, uint32_t tmo)
+{
+ if (tmo == 0xFFFFFFFF)
+ return;
+ driver_get_now(&ctx->t0);
+ ctx->tp = &ctx->t0;
+ ctx->tmo = tmo;
+}
+
+int next_timeout(uart_ctx_t* ctx)
+{
+ ErlDrvNowData t1;
+ unsigned long td;
+ if (ctx->tp == NULL)
+ return -1;
+ driver_get_now(&t1);
+ td = diff_time_ms(&t1, ctx->tp);
+ if (td >= ctx->tmo)
+ return 0;
+ return ctx->tmo - td;
+}
+
+#if defined(__APPLE__)
+static int local_ptsname_r(int fd, char* buf, size_t maxlen)
+{
+ char devname[128];
+ struct stat sbuf;
+ size_t n;
+ if (ioctl(fd, TIOCPTYGNAME, devname) < 0) {
+ DEBUGF("TIOCPTYGNAME failed : %s", strerror(errno));
+ return -1;
+ }
+ if (stat(devname, &sbuf) < 0) {
+ DEBUGF("stat %s failed : %s", devname, strerror(errno));
+ return -1;
+ }
+ if ((n=strlen(devname)) >= maxlen) {
+ errno = ERANGE;
+ return -1;
+ }
+ memcpy(buf, devname, n);
+ buf[n] = '\0';
+ return 0;
+}
+#elif defined(__linux__)
+#define local_ptsname_r ptsname_r
+#elif defined(HAVE_PTY)
+static int local_ptsname_r(int fd, char* buf, size_t maxlen)
+{
+ char* ptr;
+ size_t n;
+
+ // FIXME lock!
+ if ((ptr = ptrname(fd)) == NULL) {
+ DEBUGF("ptsname failed : %s", strerror(errno));
+ return -1;
+ }
+ if ((n=strlen(ptr)) >= maxlen) {
+ errno = ERANGE;
+ return -1;
+ }
+ memcpy(buf, ptr, n);
+ buf[n] = '\0';
+ return 0;
+}
+#endif
+
+// pseudo terminal devices to try
+// On MacOS X: /dev/pty[p-w][0-9a-f]
+// On *BSD: /dev/pty[p-sP-S][0-9a-v]
+// On AIX: /dev/ptyp[0-9a-f]
+// On HP-UX: /dev/pty[p-r][0-9a-f]
+// On OSF/1: /dev/pty[p-q][0-9a-f]
+// On Solaris: /dev/pty[p-r][0-9a-f]
+#if defined(__APPLE__)
+int local_openpt(int oflag)
+{
+// (void) oflag;
+ char devname[32];
+ const char* a = "pqrstuvw";
+ const char* b = "0123456789abcdef";
+ char* prefix = "/dev/pty";
+ int i,j,fd;
+
+// return getpt();
+// return open("/dev/ptmx", oflag);
+// return posix_openpt(oflag);
+
+ for (i = 0; a[i]; i++) {
+ for (j = 0; b[j]; j++) {
+ sprintf(devname, "%s%c%c", prefix,a[i],b[j]);
+ if ((fd = open(devname, oflag)) >= 0) {
+ // fixme: check that the device is available
+ return fd;
+ }
+ }
+ }
+ errno = ENOENT;
+ return -1;
+}
+#elif defined(__linux__)
+int local_openpt(int oflag)
+{
+ return posix_openpt(oflag);
+}
+#elif defined(HAVE_PTY)
+int local_openpt(int oflag)
+{
+ return posix_openpt(oflag);
+}
+#endif
+
+
+static int open_device(uart_ctx_t* ctx, char* name, size_t max_namelen)
+{
+ int flags;
+ int tty_fd = -1;
+ int fd = -1;
+
+ if (strcmp(name, "//pty") == 0) {
+#ifdef HAVE_PTY
+ char slave_name[UART_MAX_DEVICE_NAME];
+
+ if ((fd = local_openpt(O_RDWR|O_NOCTTY)) < 0) {
+ DEBUGF("posix_openpt failed : %s", strerror(errno));
+ return -1;
+ }
+ if (grantpt(fd) < 0) {
+ DEBUGF("grantpt failed : %s", strerror(errno));
+ goto error;
+ }
+ if (unlockpt(fd) < 0) {
+ DEBUGF("unlockpt failed : %s", strerror(errno));
+ goto error;
+ }
+ if (local_ptsname_r(fd, slave_name, sizeof(slave_name)) < 0) {
+ DEBUGF("ptsname_r failed : %s", strerror(errno));
+ goto error;
+ }
+ if (strlen(slave_name) >= max_namelen) {
+ errno = ERANGE;
+ goto error;
+ }
+ strcpy(name, slave_name);
+ if ((flags = fcntl(fd, F_GETFL, 0)) < 0) {
+ DEBUGF("fcntl: F_GETFL failed : %s", strerror(errno));
+ goto error;
+ }
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
+ DEBUGF("fcntl: F_SETFL failed : %s", strerror(errno));
+ goto error;
+ }
+#else
+ errno = ENOTSUP;
+ return -1;
+#endif
+ }
+ if ((tty_fd = open(name, O_RDWR|O_NDELAY|O_NOCTTY)) < 0)
+ goto error;
+ // non-blocking!!!
+ if ((flags = fcntl(tty_fd, F_GETFL, 0)) < 0) {
+ DEBUGF("fcntl: F_GETFL tty_fd failed : %s", strerror(errno));
+ goto error;
+ }
+ if (fcntl(tty_fd, F_SETFL, flags | O_NONBLOCK) < 0) {
+ DEBUGF("fcntl: F_SETFL tty_fd failed : %s", strerror(errno));
+ goto error;
+ }
+ tcflush(tty_fd, TCOFLUSH);
+ tcflush(tty_fd, TCIFLUSH);
+ ctx->tty_fd = tty_fd;
+ ctx->fd = (fd>=0) ? fd : tty_fd;
+ DEBUGF("open_device: tty_fd=%d fd=%d", ctx->tty_fd, ctx->fd);
+ return tty_fd;
+error:
+ {
+ int save_errno = errno;
+ if (fd >= 0) close(fd);
+ if ((fd != tty_fd) && (tty_fd >= 0)) close(tty_fd);
+ errno = save_errno;
+ }
+ return -1;
+}
+
+static void close_device(uart_ctx_t* ctx)
+{
+ if (ctx->fd != ctx->tty_fd) {
+ if (ctx->fd >= 0) {
+ DEBUGF("close_device: master=%d", ctx->fd);
+ close(ctx->fd);
+ ctx->fd = -1;
+ }
+ }
+ if (ctx->tty_fd >= 0) {
+ DEBUGF("close_device: fd=%d", ctx->tty_fd);
+ close(ctx->tty_fd);
+ ctx->tty_fd = -1;
+ }
+}
+
+// FIXME: use on linux subscribe to modem bits
+// TIOCMIWAIT wait for modem bits to change
+// TCIOGICOUNT count number of changes
+//
+#define SET_BIT(fs, x) fs |= (x)
+#define CLR_BIT(fs, x) fs &= ~(x)
+
+#define UPD_BIT(fs, x, on) do { \
+ if ((on)) SET_BIT(fs,x); \
+ else CLR_BIT(fs,(x)); \
+ } while(0)
+
+
+static int get_com_state(int fd, uart_com_state_t* com)
+{
+ struct termios tio;
+
+ if (tcgetattr(fd, &tio) < 0)
+ return -1;
+
+ // input baud reate
+ com->ibaud = from_speed(cfgetispeed(&tio));
+ com->obaud = from_speed(cfgetospeed(&tio));
+
+ // parity
+ if (tio.c_cflag & PARENB) {
+ if (tio.c_iflag & PARMRK)
+ com->parity = UART_PARITY_MARK;
+ else if (tio.c_cflag & PARODD)
+ com->parity = UART_PARITY_ODD;
+ else
+ com->parity = UART_PARITY_EVEN;
+ }
+ else
+ com->parity = UART_PARITY_NONE;
+
+ // stop bits
+ if (tio.c_cflag & CSTOPB)
+ com->stopb = 2;
+ else
+ com->stopb = 1;
+
+ // csize
+ switch(tio.c_cflag & CSIZE) {
+ case CS5: com->csize = 5; break;
+ case CS6: com->csize = 6; break;
+ case CS7: com->csize = 7; break;
+ case CS8: com->csize = 8; break;
+ default: break;
+ }
+
+ // may be used for {packet, {size,N}} and also when
+ // in {packet,N} (N!=0) when waiting for a certain amount of data
+ com->bufsz = tio.c_cc[VMIN]; // min number of bytes buffered
+ com->buftm = tio.c_cc[VTIME]*100;
+ com->xonchar = tio.c_cc[VSTART];
+ com->xoffchar = tio.c_cc[VSTOP];
+
+ com->iflow = 0;
+ if (tio.c_iflag & IXOFF) com->iflow |= UART_SW;
+#if defined(CRTS_IFLOW)
+ if (tio.c_cflag & CRTS_IFLOW) com->iflow |= UART_RTS;
+#endif
+#if defined(CDTR_IFLOW)
+ if (tio.c_cflag & CDTR_IFLOW) com->iflow |= UART_DTR;
+#endif
+
+ com->oflow = 0;
+ if (tio.c_iflag & IXON) com->oflow |= UART_SW;
+#if defined(CCTS_OFLOW)
+ if (tio.c_cflag & CCTS_OFLOW) com->oflow |= UART_CTS;
+#endif
+#if defined(CDSR_OFLOW)
+ if (tio.c_cflag & CDSR_OFLOW) com->oflow |= UART_DSR;
+#endif
+#if defined(CCAR_OFLOW)
+ if (tio.c_cflag & CCAR_OFLOW) com->oflow |= UART_CD;
+#endif
+#if defined(CRTSCTS)
+ if ((tio.c_cflag & CRTSCTS) == CRTSCTS) {
+ com->oflow |= UART_CTS; //com->oflow |= UART_CD;
+ com->iflow |= UART_RTS;
+ }
+#endif
+ return 0;
+}
+
+static int set_com_state(int fd, uart_com_state_t* com)
+{
+ struct termios tio;
+
+ // read current state
+ if (tcgetattr(fd, &tio) < 0) {
+ DEBUGF("unable to read com state: %s", strerror(errno));
+ return -1;
+ }
+
+ // On Mac os X IOSSIOSPEED can be used to set "non-traditional baud rate"
+ // From "IOKit/serial/ioss.h"
+
+ cfsetispeed(&tio, to_speed(com->ibaud));
+ cfsetospeed(&tio, to_speed(com->obaud));
+
+ // update from state
+ switch(com->parity) {
+ case UART_PARITY_NONE:
+ tio.c_iflag &= ~PARMRK;
+ tio.c_cflag &= ~PARENB;
+ break;
+ case UART_PARITY_ODD: // odd
+ tio.c_iflag &= ~PARMRK;
+ tio.c_cflag |= PARODD;
+ tio.c_cflag |= PARENB;
+ break;
+ case UART_PARITY_EVEN: // even
+ tio.c_iflag &= ~PARMRK;
+ tio.c_cflag &= ~PARODD;
+ tio.c_cflag |= PARENB;
+ break;
+ case UART_PARITY_MARK: // mark (FIXME)
+ tio.c_iflag |= PARMRK;
+ tio.c_cflag |= PARENB;
+ break;
+ case UART_PARITY_SPACE: // space (FIXME)
+ tio.c_iflag &= ~PARMRK;
+ tio.c_cflag &= ~PARENB;
+ break;
+ default:
+ break;
+ }
+
+ if (com->stopb == 1)
+ tio.c_cflag &= ~CSTOPB;
+ else if (com->stopb == 2)
+ tio.c_cflag |= CSTOPB;
+
+ tio.c_cflag &= ~CSIZE;
+ switch(com->csize) {
+ case 5: tio.c_cflag |= CS5; break;
+ case 6: tio.c_cflag |= CS6; break;
+ case 7: tio.c_cflag |= CS7; break;
+ case 8: tio.c_cflag |= CS8; break;
+ default: break;
+ }
+ // Set the buffer number of bytes buffered before interrupt
+ if (com->bufsz > 255)
+ tio.c_cc[VMIN] = 255;
+ else
+ tio.c_cc[VMIN] = com->bufsz;
+ // Set the max time to buffer bytes
+ if (com->buftm > 25500) // 25500 ms = 25.5 sec
+ tio.c_cc[VTIME] = 255;
+ else
+ tio.c_cc[VTIME] = com->buftm / 100;
+ tio.c_cc[VSTART] = com->xonchar;
+ tio.c_cc[VSTOP] = com->xoffchar;
+
+ // input flow control
+ UPD_BIT(tio.c_iflag, IXOFF, (com->iflow & UART_SW));
+#if defined(CRTS_IFLOW)
+ UPD_BIT(tio.c_cflag, CRTS_IFLOW, (com->iflow & UART_RTS));
+#endif
+#if defined(CDTR_IFLOW)
+ UPD_BIT(tio.c_cflag, CDTR_IFLOW, (com->iflow & UART_DTR));
+#endif
+
+ // output flow control
+ UPD_BIT(tio.c_iflag, IXON, (com->oflow & UART_SW));
+#if defined(CCTS_OFLOW)
+ UPD_BIT(tio.c_cflag, CCTS_OFLOW, (com->oflow & UART_CTS));
+#endif
+#if defined(CDSR_OFLOW)
+ UPD_BIT(tio.c_cflag, CDSR_OFLOW, (com->oflow & UART_DSR));
+#endif
+#if defined(CCAR_OFLOW)
+ UPD_BIT(tio.c_cflag, CCAR_OFLOW, (com->oflow & UART_CD));
+#endif
+#if defined(CRTSCTS)
+ if ((com->iflow & UART_RTS) && (com->oflow & UART_CTS))
+ tio.c_cflag |= CRTSCTS;
+ else if (!(com->iflow & UART_RTS) && !(com->oflow & UART_CTS))
+ tio.c_cflag &= ~CRTSCTS;
+#endif
+ // ignore break condition
+ tio.c_iflag |= IGNBRK;
+
+ // local line + enable receiver
+ tio.c_cflag |= (CLOCAL | CREAD);
+
+ // raw input processing
+ tio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG | IEXTEN);
+ // no output processing
+ tio.c_oflag &= ~(OPOST);
+ // do NOT hangup-on-close, need? we keep one slave open
+ tio.c_cflag &= ~HUPCL;
+
+ tcflush(fd, TCIFLUSH);
+ return tcsetattr(fd, TCSANOW, &tio);
+}
+
+static int get_modem_state(int fd, uart_modem_state_t* state)
+{
+ int status, r, s;
+
+ if ((r = ioctl(fd, TIOCMGET, &status)) < 0)
+ return r;
+ s = 0;
+ if ((status & TIOCM_DTR) != 0) s |= UART_DTR;
+ if ((status & TIOCM_RTS) != 0) s |= UART_RTS;
+ if ((status & TIOCM_CTS) != 0) s |= UART_CTS;
+ if ((status & TIOCM_CD) != 0) s |= UART_CD;
+ if ((status & TIOCM_RNG) != 0) s |= UART_RI;
+ if ((status & TIOCM_DSR) != 0) s |= UART_DSR;
+ *state = s;
+ return 0;
+}
+
+static int set_modem_state(int fd, uart_modem_state_t state, int on)
+{
+ if (state) {
+ int status = 0;
+ if (state & UART_DTR) status |= TIOCM_DTR; // out
+ if (state & UART_RTS) status |= TIOCM_RTS; // out
+ if (state & UART_CTS) status |= TIOCM_CTS; // in
+ if (state & UART_CD) status |= TIOCM_CD; // in
+ if (state & UART_RI) status |= TIOCM_RI; // in|out?
+ if (state & UART_DSR) status |= TIOCM_DSR; // in
+ if (on)
+ return ioctl(fd, TIOCMBIS, &status);
+ else
+ return ioctl(fd, TIOCMBIC, &status);
+ }
+ return 0;
+}
+
+static int uart_final(uart_ctx_t* ctx)
+{
+ uart_buf_finish(&ctx->ib);
+ // uart_queue_clear(&ctx->oq);
+ return 0;
+}
+
+void uart_init(uart_ctx_t* ctx, dthread_t* self, dthread_t* other)
+{
+ memset(ctx, 0, sizeof(uart_ctx_t));
+ ctx->fd = -1;
+ ctx->tty_fd = -1;
+ ctx->option.bsize = UART_DEF_BUFFER;
+ ctx->option.high = UART_HIGH_WATERMARK;
+ ctx->option.low = UART_LOW_WATERMARK;
+ ctx->option.send_timeout = UART_INFINITY;
+ ctx->option.send_timeout_close = 0;
+
+ ctx->option.hsz = 0; // list header size
+ ctx->option.htype = UART_PB_RAW; // default packet type
+ ctx->option.psize = 0; // no size check
+ ctx->option.mode = UART_MODE_LIST; // list mode
+ ctx->option.deliver = UART_DELIVER_TERM; // standard term format
+ ctx->option.active = UART_PASSIVE; // start passive
+ ctx->option.exitf = 0;
+ ctx->option.eolchar = '\n';
+
+ uart_buf_init(&ctx->ib);
+ uart_queue_init(&ctx->oq);
+
+ com_state_init(&ctx->state);
+
+ ctx->port = self->port;
+ ctx->dport = self->dport;
+
+ ctx->self = self;
+ ctx->other = other;
+}
+
+static int apply_opts(uart_ctx_t* ctx,
+ uart_com_state_t* state, uart_opt_t* option,
+ int32_t sflags)
+{
+ int old_active;
+ unsigned int old_htype;
+
+ if ((sflags & (1 << UART_OPT_DEVICE)) &&
+ (strcmp(option->device_name, ctx->option.device_name) != 0)) {
+ close_device(ctx);
+ sflags &= ~(UART_OPT_COMM | (1 << UART_OPT_DEVICE));
+ if (open_device(ctx,option->device_name,sizeof(option->device_name))<0)
+ return -1;
+#ifdef DEBUG
+ // com_state_dump(stderr, state);
+#endif
+ if (set_com_state(ctx->tty_fd, state) < 0) {
+ DEBUGF("set_opts: uart_set_com_state failed");
+ return -1;
+ }
+ if (get_com_state(ctx->tty_fd, state) >= 0) {
+ DEBUGF("set_opts: com_state: after");
+#ifdef DEBUG
+ // com_state_dump(stderr, state);
+#endif
+ }
+ else {
+ DEBUGF("apply_opts: get_com_state failed");
+ }
+ }
+ else if (sflags & UART_OPT_COMM) {
+ if (ctx->tty_fd >= 0) {
+ // DEBUGF("set_opts: com_state before:");
+ // com_state_dump(stderr, state);
+ if (set_com_state(ctx->tty_fd, state) < 0)
+ return -1;
+ sflags &= ~(UART_OPT_COMM);
+ if (get_com_state(ctx->tty_fd, state) >= 0) {
+ // DEBUGF("set_opts: com_state: after");
+#ifdef DEBUG
+ // com_state_dump(stderr, state);
+#endif
+ }
+ }
+ }
+
+ if (sflags & (1 << UART_OPT_PTYPKT)) {
+ if ((ctx->fd != ctx->tty_fd) && (ctx->fd >= 0)) {
+ DEBUGF("set TIOCPKT = %d\n", option->ptypkt);
+ if (ioctl(ctx->fd, TIOCPKT, &option->ptypkt) < 0) {
+ DEBUGF("set_opts: ioctl TIOCPKT failed: %s", strerror(errno));
+ }
+ }
+ sflags &= ~(1 << UART_OPT_PTYPKT);
+ }
+
+ old_active = ctx->option.active;
+ old_htype = ctx->option.htype;
+
+ ctx->sflags = sflags;
+ ctx->state = *state;
+ ctx->option = *option;
+
+ if (ctx->tty_fd >= 0) {
+ if (ctx->option.active) {
+ if (!old_active || (ctx->option.htype != old_htype))
+ return 1;
+ ctx->remain = 0; // CHECK ME
+ return 0;
+ }
+ }
+ return 0;
+}
+
+/*
+** Deliver packet ready
+** if len == 0 then check start with a check for ready packet
+*/
+int uart_deliver(uart_ctx_t* ctx, int len)
+{
+ int count = 0;
+ int n;
+
+ DEBUGF("uart_deliver(%ld): s=%ld about to deliver %d bytes...",
+ (long)ctx->port, (long)ctx->fd, len);
+
+ /* Poll for ready packet */
+ if (len == 0) {
+ /* empty buffer or waiting for more input */
+ if ((ctx->ib.base == NULL) || (ctx->remain > 0))
+ return count;
+ n = uart_buf_remain(&ctx->ib, &len,
+ ctx->option.htype,
+ ctx->option.psize,
+ ctx->option.eolchar);
+ if (n != 0) {
+ if (n < 0) /* packet error */
+ return n;
+ if (len > 0) /* more data pending */
+ ctx->remain = len;
+ return count;
+ }
+ }
+
+ if (len > 0) {
+ int code;
+
+ code = uart_reply_data(ctx, (char*) ctx->ib.ptr_start, len);
+ clear_timeout(ctx);
+
+ /* XXX The buffer gets thrown away on error (code < 0) */
+ /* Windows needs workaround for this in uart_uart_event... */
+ if (code < 0)
+ return code;
+ ctx->ib.ptr_start += len;
+ if (ctx->ib.ptr_start == ctx->ib.ptr)
+ uart_buf_reset(&ctx->ib);
+ else
+ ctx->remain = 0;
+ }
+
+ count++;
+ len = 0;
+
+ if (!ctx->option.active) {
+ if (ctx->ib.base != NULL)
+ uart_buf_restart(&ctx->ib);
+ }
+ else if (ctx->ib.base != NULL) {
+ n = uart_buf_remain(&ctx->ib, &len,
+ ctx->option.htype,
+ ctx->option.psize,
+ ctx->option.eolchar);
+ if (n != 0) {
+ if (n < 0) /* packet error */
+ return n;
+ uart_buf_restart(&ctx->ib);
+ if (len > 0)
+ ctx->remain = len;
+ len = 0;
+ }
+ }
+ return count;
+}
+
+
+// The modem has closed, cleanup and send event
+int uart_recv_closed(uart_ctx_t* ctx)
+{
+ DEBUGF("uart_recv_closed(%ld)", (long) ctx->port);
+
+ clear_timeout(ctx);
+ uart_buf_reset(&ctx->ib);
+ close_device(ctx);
+
+ if (!ctx->option.active) {
+ uart_async_error_am(ctx, ctx->dport, ctx->caller, am_closed);
+ }
+ else {
+ uart_closed_message(ctx);
+ }
+ return -1;
+}
+
+//
+// We have a read error determine the action
+//
+int uart_recv_error(uart_ctx_t* ctx, int err)
+{
+ if (err != EAGAIN) {
+ clear_timeout(ctx);
+ uart_buf_reset(&ctx->ib);
+ close_device(ctx);
+
+ if (!ctx->option.active) {
+ uart_async_error(ctx, ctx->dport, ctx->caller, err);
+ }
+ else {
+ uart_error_message(ctx, err); // first error
+ uart_closed_message(ctx); /* then closed */
+ }
+ return -1;
+ }
+ return 0;
+}
+
+// process input data, buffer according to packet type
+// return 0: no packet/data delivered
+// return 1: packet delivered
+// return -1: error
+//
+int process_input(uart_ctx_t* ctx, dthread_t* self, int request_len)
+{
+ (void) self;
+ int n;
+ int len;
+ int nread;
+
+ if (ctx->ib.base == NULL) { /* allocte a read buffer */
+ size_t sz = ctx->option.bsize;
+ if (request_len > 0)
+ sz = request_len;
+ if (sz == 0)
+ sz = 1;
+ nread = uart_buf_alloc(&ctx->ib,sz);
+ ctx->remain = request_len;
+ if (nread < 0)
+ return uart_recv_error(ctx, ENOMEM);
+ }
+ else if (request_len > 0) { /* we have a data in buffer and a request */
+ n = ctx->ib.ptr - ctx->ib.ptr_start;
+ if (n >= request_len)
+ return uart_deliver(ctx, request_len);
+ else if (uart_buf_expand(&ctx->ib, request_len) < 0)
+ return uart_recv_error(ctx, ENOMEM);
+ else {
+ nread = request_len - n;
+ ctx->remain = nread;
+ }
+ }
+ else if ((nread=ctx->remain) == 0) { /* poll remain from buffer data */
+ nread = uart_buf_remain(&ctx->ib, &len,
+ ctx->option.htype,
+ ctx->option.psize,
+ ctx->option.eolchar);
+ if (nread < 0)
+ return uart_recv_error(ctx, EMSGSIZE);
+ else if (nread == 0)
+ return uart_deliver(ctx, len);
+ else if (len > 0)
+ ctx->remain = len;
+ }
+
+ DEBUGF("uart_recv(%ld): s=%ld about to read %d bytes...",
+ (long)ctx->port, (long)ctx->fd, nread);
+
+ n = read(ctx->fd, ctx->ib.ptr, nread);
+
+ if (n < 0) {
+ int err = uart_errno(ctx);
+ if (err == EAGAIN) {
+ DEBUGF(" => would block");
+ return 0;
+ }
+ else {
+ DEBUGF(" => error: %d", err);
+ return uart_recv_error(ctx, err);
+ }
+ }
+ else if (n == 0) {
+ DEBUGF(" => detected zero bytes %s", strerror(errno));
+ return uart_recv_closed(ctx);
+ }
+
+ DEBUGF(" => got %d bytes", n);
+ ctx->ib.ptr += n;
+ if (ctx->remain > 0) {
+ ctx->remain -= n;
+ if (ctx->remain == 0)
+ return uart_deliver(ctx, ctx->ib.ptr - ctx->ib.ptr_start);
+ }
+ else {
+ nread = uart_buf_remain(&ctx->ib, &len,
+ ctx->option.htype,
+ ctx->option.psize,
+ ctx->option.eolchar);
+ if (nread < 0)
+ return uart_recv_error(ctx, EMSGSIZE);
+ else if (nread == 0)
+ return uart_deliver(ctx, len);
+ else if (len > 0)
+ ctx->remain = len;
+ }
+ return 0;
+}
+
+
+// process output queue
+int process_output(uart_ctx_t* ctx, dthread_t* self)
+{
+ dmessage_t* mp;
+ int r = 0;
+
+ while ((mp = ctx->oq.mesg) != NULL) {
+ int n = mp->used - ctx->oq.offs;
+
+ if ((r = write(ctx->fd, mp->buffer+ctx->oq.offs, n)) < 0) {
+ if ((r < 0) && (errno == EAGAIN)) {
+ DEBUGF("uart_unix: process_output: EAGAIN");
+ return 0;
+ }
+ return -1;
+ }
+ else if (r < n) {
+ ctx->oq.offs += r;
+ return 0;
+ }
+ if (mp->from)
+ dthread_port_send_ok(mp->source, self, mp->from, mp->ref);
+ dmessage_free(mp);
+ ctx->oq.offs = 0;
+ if ((mp = ctx->oq.front) == NULL)
+ ctx->oq.mesg = NULL;
+ else {
+ if (!(ctx->oq.front = mp->next))
+ ctx->oq.rear = NULL;
+ ctx->oq.mesg = mp;
+ }
+ }
+ return r;
+}
+
+
+int enq_output(uart_ctx_t* ctx, dthread_t* self,
+ dmessage_t* mp, ErlDrvTermData from)
+{
+ dmessage_t* mr;
+
+ mp->next = NULL;
+ mp->from = from; // from = 0 => async
+
+ // set packet bytes header!
+ set_packet_bytes(mp, ctx->option.htype);
+
+ if ((ctx->oq.mesg == NULL) && (ctx->oq.front == NULL)) {
+ ctx->oq.offs = 0;
+ ctx->oq.mesg = mp;
+ return process_output(ctx, self);
+ }
+ else {
+ if ((mr = ctx->oq.rear) != NULL)
+ mr->next = mp;
+ else
+ ctx->oq.front = mp;
+ ctx->oq.rear = mp;
+ return 0;
+ }
+}
+
+
+// thread main!
+int uart_unix_main(void* arg)
+{
+ dthread_t* self = (dthread_t*) arg;
+ dthread_t* other = (dthread_t*) self->arg;
+ dmessage_t* mp = NULL;
+ dthread_poll_event_t ev, *evp;
+ size_t nev;
+ dterm_t term;
+ uart_ctx_t ctx;
+ ErlDrvTermData mp_from;
+ ErlDrvTermData mp_ref;
+ dthread_t* mp_source;
+ int tmo;
+ int r;
+
+ DEBUGF("uart_unix: thread started");
+
+ uart_init(&ctx, self, other);
+
+ dterm_init(&term);
+
+again_tmo:
+ tmo = next_timeout(&ctx);
+again:
+ nev = 0;
+ evp = NULL;
+ if (ctx.fd >= 0) {
+ ev.event = (ErlDrvEvent) ((long)ctx.fd);
+ ev.events = 0;
+ if ((ctx.option.active != UART_PASSIVE) || ctx.recv) {
+ ev.events |= ERL_DRV_READ;
+ if (ctx.option.ptypkt && (ctx.fd != ctx.tty_fd))
+ ev.events |= ERL_DRV_EXCEP;
+ }
+ if (ctx.oq.mesg)
+ ev.events |= ERL_DRV_WRITE;
+ if (ev.events) {
+ evp = &ev;
+ nev = 1;
+ }
+ DEBUGF("ctx.fd=%d, ev.events=%d", ctx.fd, ev.events);
+ }
+
+ DEBUGF("uart_unix_main: nev=%d, events=%x, timeout = %d",
+ nev, ev.events, tmo);
+ r = dthread_poll(self, evp, &nev, tmo);
+
+ if (r < 0) {
+ DEBUGF("uart_unix_main: dthread_poll failed=%d", r);
+ goto again_tmo;
+ }
+ else {
+ DEBUGF("uart_unix_main: nev=%d, r=%d", nev, r);
+
+ if (evp && (nev == 1)) {
+ if (evp->revents & ERL_DRV_WRITE)
+ process_output(&ctx, self);
+ if (evp->revents & (ERL_DRV_READ|ERL_DRV_EXCEP)) {
+ while((process_input(&ctx, self, 0) == 1) &&
+ (ctx.option.active != UART_PASSIVE))
+ ;
+ }
+ }
+ tmo = next_timeout(&ctx);
+ DEBUGF("uart_unix_main: timeout = %d", tmo);
+ if (ctx.recv) {
+ if (tmo == 0) {
+ uart_async_error_am(&ctx, ctx.dport, ctx.caller, am_timeout);
+ clear_timeout(&ctx);
+ ctx.remain = 0;
+ }
+ }
+ if (r == 0)
+ goto again;
+
+ // r>0 (number of messages)
+ DEBUGF("about to receive message r=%d", r);
+ if ((mp = dthread_recv(self, NULL)) == NULL) {
+ DEBUGF("uart_unix_main: message was NULL");
+ goto again;
+ }
+ mp_from = mp->from;
+ mp_ref = mp->ref;
+ mp_source = mp->source;
+
+ switch (mp->cmd) {
+ case DTHREAD_STOP:
+ DEBUGF("uart_unix_main: STOP");
+ close_device(&ctx);
+ uart_final(&ctx);
+ dmessage_free(mp);
+ DEBUGF("uart_unix_main: EXIT");
+ dthread_exit(0);
+ break;
+
+ case DTHREAD_OUTPUT: // async send!
+ DEBUGF("uart_unix_main: OUTPUT");
+ if (ctx.fd < 0) {
+ dmessage_free(mp);
+ goto again;
+ }
+ if (enq_output(&ctx, self, mp, 0) < 0) {
+ mp = NULL;
+ goto error;
+ }
+ goto again;
+
+ case UART_CMD_CONNECT: {
+ ErlDrvTermData owner;
+ if (mp->used != 0) goto badarg;
+ owner = driver_connected(self->port);
+ self->owner = owner;
+ other->owner = owner;
+ goto ok;
+ }
+
+ case UART_CMD_CLOSE:
+ DEBUGF("uart_unix_main: CLOSE");
+ close_device(&ctx);
+ goto ok;
+
+ case UART_CMD_SEND: // sync send
+ DEBUGF("uart_unix_main: SEND");
+ if (ctx.fd < 0) goto ebadf;
+ if (enq_output(&ctx, self, mp, mp_from) < 0) {
+ mp = NULL;
+ goto error;
+ }
+ goto again;
+
+ case UART_CMD_SENDCHAR: // sync send
+ DEBUGF("uart_unix_main: SENDCHAR");
+ if (ctx.fd < 0) goto ebadf;
+ if (enq_output(&ctx, self, mp, mp_from) < 0) {
+ mp = NULL;
+ goto error;
+ }
+ goto again;
+
+ case UART_CMD_RECV: { // <<Time:32, Length:32>> Time=0xffffffff=inf
+ uint32_t tm;
+ int len;
+ DEBUGF("uart_unix_main: RECV");
+ if (ctx.fd < 0) goto ebadf;
+ if (ctx.recv) goto ealready;
+ if (mp->used != 8) goto badarg;
+ if (ctx.option.active != UART_PASSIVE) goto badarg;
+ tm = get_uint32((uint8_t*) mp->buffer);
+ len = (int) get_uint32((uint8_t*) (mp->buffer+4));
+ if ((len < 0) || (len > UART_MAX_PACKET_SIZE)) goto badarg;
+ ctx.ref = mp_ref;
+ ctx.caller = mp_from;
+ set_timeout(&ctx, tm);
+ ctx.recv = 1;
+ DEBUGF("recv timeout %lu", tm);
+ process_input(&ctx, self, len);
+ dmessage_free(mp);
+ goto again_tmo;
+ }
+
+ case UART_CMD_UNRECV: { // argument is data to push back
+ uart_buf_push(&ctx.ib, mp->buffer, mp->used);
+ DEBUGF("unrecived %d bytes", ctx.ib.ptr - ctx.ib.ptr_start);
+ if (ctx.option.active != UART_PASSIVE) {
+ while((process_input(&ctx, self, 0) == 1) &&
+ (ctx.option.active != UART_PASSIVE))
+ ;
+ }
+ goto ok;
+ }
+
+ case UART_CMD_SETOPTS: {
+ uart_com_state_t state = ctx.state;
+ uart_opt_t option = ctx.option;
+ uint32_t sflags = ctx.sflags;
+
+ // parse & update options in state,option and sflag
+ if (uart_parse_opts(mp->buffer, mp->used,
+ &state, &option, &sflags) < 0)
+ goto badarg;
+
+ // apply the changed values
+ if ((r=apply_opts(&ctx, &state, &option, sflags)) < 0)
+ goto error;
+
+ if (r == 1) {
+ while((process_input(&ctx, self, 0) == 1) &&
+ (ctx.option.active != UART_PASSIVE))
+ ;
+ }
+ goto ok;
+ }
+
+ case UART_CMD_GETOPTS: {
+ dterm_mark_t m1;
+ dterm_mark_t m2;
+ // {Ref, {ok,List}} || {Ref, {error,Reason}}
+ dterm_tuple_begin(&term, &m1); {
+ dterm_uint(&term, mp_ref);
+ dterm_tuple_begin(&term, &m2); {
+ dterm_atom(&term, am_ok);
+ if (uart_get_opts(&term, &ctx,(uint8_t*)mp->buffer,mp->used) < 0) {
+ dterm_reset(&term);
+ goto badarg;
+ }
+ }
+ dterm_tuple_end(&term, &m2);
+ }
+ dterm_tuple_end(&term, &m1);
+ dthread_port_send_dterm(mp_source, self, mp_from, &term);
+ dterm_reset(&term);
+ dmessage_free(mp);
+ goto again;
+ }
+
+ case UART_CMD_GET_MODEM: {
+ dterm_mark_t m1;
+ dterm_mark_t m2;
+ uart_modem_state_t mstate;
+ if (ctx.tty_fd < 0) goto ebadf;
+ if (get_modem_state(ctx.tty_fd, &mstate) < 0) goto error;
+
+ dterm_tuple_begin(&term, &m1); {
+ dterm_uint(&term, mp_ref);
+ dterm_tuple_begin(&term, &m2); {
+ dterm_atom(&term, am_ok);
+ modem_state_dterm(&term, mstate);
+ }
+ dterm_tuple_end(&term, &m2);
+ }
+ dterm_tuple_end(&term, &m1);
+ dthread_port_send_dterm(mp_source, self, mp_from, &term);
+ dterm_reset(&term);
+ dmessage_free(mp);
+ goto again;
+ }
+
+ case UART_CMD_SET_MODEM: {
+ uart_modem_state_t mstate;
+ if (ctx.tty_fd < 0) goto ebadf;
+ if (mp->used != 4) goto badarg;
+ mstate = (uart_modem_state_t) get_uint32((uint8_t*) mp->buffer);
+ if (set_modem_state(ctx.tty_fd, mstate, 1) < 0) goto error;
+ goto ok;
+ }
+
+ case UART_CMD_CLR_MODEM: {
+ uart_modem_state_t mstate;
+ if (ctx.tty_fd < 0) goto ebadf;
+ if (mp->used != 4) goto badarg;
+ mstate = (uart_modem_state_t) get_uint32((uint8_t*) mp->buffer);
+ if (set_modem_state(ctx.tty_fd, mstate, 0) < 0) goto error;
+ goto ok;
+ }
+
+ case UART_CMD_HANGUP: {
+ struct termios tio;
+ int r;
+ if (ctx.tty_fd < 0) goto ebadf;
+ if (mp->used != 0) goto badarg;
+ if ((r = tcgetattr(ctx.tty_fd, &tio)) < 0) {
+ INFOF("tcgetattr: error=%s\n", strerror(errno));
+ goto badarg;
+ }
+ cfsetispeed(&tio, B0);
+ cfsetospeed(&tio, B0);
+ if ((r = tcsetattr(ctx.tty_fd, TCSANOW, &tio)) < 0) {
+ INFOF("tcsetattr: error=%s\n", strerror(errno));
+ goto badarg;
+ }
+ goto ok;
+ }
+
+ case UART_CMD_BREAK: {
+ int duration;
+ if (ctx.tty_fd < 0) goto ebadf;
+ if (mp->used != 4) goto badarg;
+ duration = (int) get_uint32((uint8_t*) mp->buffer);
+ if (tcsendbreak(ctx.tty_fd, duration) < 0)
+ goto error;
+ goto ok;
+ }
+
+ case UART_CMD_FLOW:
+ if (ctx.tty_fd < 0) goto ebadf;
+ if (mp->used != 1) goto badarg;
+ switch(mp->buffer[0]) {
+ case 0: r = tcflow(ctx.tty_fd, TCIOFF); break;
+ case 1: r = tcflow(ctx.tty_fd, TCION); break;
+ case 2: r = tcflow(ctx.tty_fd, TCOOFF); break;
+ case 3: r = tcflow(ctx.tty_fd, TCOON); break;
+ default: goto badarg; break;
+ }
+ if (r < 0)
+ goto error;
+ goto ok;
+
+ default:
+ goto badarg;
+ }
+ }
+
+ok:
+ dthread_port_send_ok(mp_source, self, mp_from, mp_ref);
+ if (mp) dmessage_free(mp);
+ goto again;
+
+ebadf:
+ errno = EBADF;
+ goto error;
+badarg:
+ errno = EINVAL;
+ goto error;
+ealready:
+ errno = EALREADY;
+ goto error;
+
+error:
+ dthread_port_send_error(mp_source, self, mp_from, mp_ref,
+ uart_errno(&ctx));
+ if (mp) dmessage_free(mp);
+ goto again;
+}
diff --git a/deps/uart/c_src/uart_win32.c b/deps/uart/c_src/uart_win32.c
new file mode 100644
index 0000000..7a07c3f
--- /dev/null
+++ b/deps/uart/c_src/uart_win32.c
@@ -0,0 +1,1194 @@
+//
+// UART "win32" implementation
+//
+// http://msdn.microsoft.com/en-us/library/ms810467.aspx
+//
+// http://support.microsoft.com/kb/115831
+// devicename = COM1..COM9
+// for COM10 and other (includes COM1..COM9)
+// devicename = \\.\COM10 == "\\\\.\\COM10"
+//
+// EscapeCommFunction: (and others?)
+// Apparently if a serial device is disconnected while you still have the
+// handle open, this can return at least two interesting error codes:
+// ERROR_ACCESS_DENIED and ERROR_BAD_COMMAND.
+//
+
+#include <stdio.h>
+#include "windows.h"
+
+#define EAGAIN ERROR_IO_PENDING
+#define EWOULDBLOCK ERROR_IO_PENDING
+#define ENOMEM ERROR_NOT_ENOUGH_MEMORY
+#define EINVAL ERROR_BAD_ARGUMENTS
+#define EBUSY ERROR_BUSY
+#define EOVERFLOW ERROR_TOO_MANY_CMDS
+#define EMSGSIZE ERROR_NO_DATA
+#define ENOTCONN ERROR_PIPE_NOT_CONNECTED
+#define EINTR ERROR_INVALID_AT_INTERRUPT_TIME //dummy
+#define EBADF ERROR_INVALID_HANDLE
+
+#include "uart_drv.h"
+#include "dthread.h"
+
+// debug output - preserve last error
+#ifdef DEBUG
+#define DEBUG_ERROR(args...) do { \
+ DWORD error = GetLastError(); \
+ dlib_emit_error(DLOG_DEBUG,__FILE__,__LINE__,args); \
+ SetLastError(error); \
+ } while(0)
+#else
+#define DEBUG_ERROR(args...)
+#endif
+
+#define DEFAULT_IN_QUEUE 2048
+#define DEFAULT_OUT_QUEUE 2048
+
+static struct _rate {
+ int baud;
+ DWORD speed;
+} rate_tab[] =
+{
+ {0, 0},
+ {110, CBR_110},
+ {300, CBR_300},
+ {600, CBR_600},
+ {1200, CBR_1200},
+ {2400, CBR_2400},
+ {4800, CBR_4800},
+ {9600, CBR_9600},
+ {14400, CBR_14400},
+ {19200, CBR_19200},
+ {38400, CBR_38400},
+ {57600, CBR_57600},
+ {115200, CBR_115200},
+ {128000, CBR_128000},
+ {256000, CBR_256000},
+ { -1, 0}
+};
+
+extern void _dosmaperr(DWORD);
+extern int errno;
+
+static int uart_errno(uart_ctx_t* ctx)
+{
+ int error = GetLastError();
+ _dosmaperr(error);
+ ctx->error = errno;
+ return errno;
+}
+
+static int from_speed(DWORD speed)
+{
+ int i = 0;
+ int baud;
+
+ while((rate_tab[i].baud != -1) && (rate_tab[i].speed != speed))
+ i++;
+ baud = rate_tab[i].baud;
+ return baud;
+}
+
+static DWORD to_speed(int baud)
+{
+ int i = 0;
+ int speed = 0;
+ while((rate_tab[i].baud != -1) && (baud > rate_tab[i].baud))
+ i++;
+ if (rate_tab[i].baud == -1)
+ speed = rate_tab[i-1].speed;
+ else
+ speed = rate_tab[i].speed;
+ return speed;
+}
+
+
+
+//
+// t1 - t0 - t1 MUST be greater equal to t0 (later in time)
+// or 0 is returned
+//
+unsigned long diff_time_ms(ErlDrvNowData* t1, ErlDrvNowData* t0)
+{
+ unsigned long dm,ds,du;
+ unsigned long d = 0;
+ unsigned long b;
+ unsigned long t;
+
+ if (t1->microsecs >= t0->microsecs) {
+ du = t1->microsecs - t0->microsecs;
+ b = 0;
+ }
+ else {
+ du = (1000000 + t1->microsecs) - t0->microsecs;
+ b = 1;
+ }
+
+ t = t0->secs+b;
+ if (t1->secs >= t) {
+ ds = t1->secs - t;
+ b = 0;
+ }
+ else {
+ ds = (t1->secs+1000000) - t;
+ b = 1;
+ }
+
+ t = t0->megasecs+b;
+ if (t1->megasecs >= t) {
+ dm = t1->megasecs - t;
+ b = 0;
+ }
+ else {
+ dm = (t1->megasecs+1000000) - t;
+ b = 1;
+ }
+ if (b == 1)
+ return 0;
+ d = du/1000;
+ if (ds)
+ d += ds*1000;
+ if (dm)
+ d += dm*1000000000;
+ return d;
+}
+
+void clear_timeout(uart_ctx_t* ctx)
+{
+ ctx->recv = 0;
+ ctx->tp = NULL;
+}
+
+void set_timeout(uart_ctx_t* ctx, uint32_t tmo)
+{
+ if (tmo == 0xFFFFFFFF)
+ return;
+ driver_get_now(&ctx->t0);
+ ctx->tp = &ctx->t0;
+ ctx->tmo = tmo;
+}
+
+int next_timeout(uart_ctx_t* ctx)
+{
+ ErlDrvNowData t1;
+ unsigned long td;
+ if (ctx->tp == NULL)
+ return -1;
+ driver_get_now(&t1);
+ td = diff_time_ms(&t1, ctx->tp);
+ if (td >= ctx->tmo)
+ return 0;
+ return ctx->tmo - td;
+}
+
+static int open_device(uart_ctx_t* ctx, char* name)
+{
+ ctx->fh = CreateFile(name,
+ GENERIC_READ | GENERIC_WRITE,
+ 0,
+ 0,
+ OPEN_EXISTING,
+ FILE_FLAG_OVERLAPPED,
+ 0);
+ if (ctx->fh == INVALID_HANDLE_VALUE) {
+ DEBUG_ERROR("CreateFile: invalid handle: error %d", GetLastError());
+ goto error;
+ }
+
+ if (!(ctx->in.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) {
+ DEBUG_ERROR("CreateEvent: 1: error %d", GetLastError());
+ goto error;
+ }
+
+ if (!(ctx->out.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) {
+ DEBUG_ERROR("CreateEvent: 1: error %d", GetLastError());
+ goto error;
+ }
+
+ if (!(ctx->stat.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) {
+ DEBUG_ERROR("CreateEvent: 1: error %d", GetLastError());
+ goto error;
+ }
+
+ // SetCommMask(wd->fh, EV_RXCHAR);
+ WaitCommEvent(ctx->fh, &ctx->statm, &ctx->stat);
+ return 0;
+
+error:
+ if (ctx->fh != INVALID_HANDLE_VALUE) CloseHandle(ctx->fh);
+ if (ctx->in.hEvent) CloseHandle(ctx->in.hEvent);
+ if (ctx->out.hEvent) CloseHandle(ctx->out.hEvent);
+ if (ctx->stat.hEvent) CloseHandle(ctx->stat.hEvent);
+ return -1;
+}
+
+static void close_device(uart_ctx_t* ctx)
+{
+ if (ctx->fh != INVALID_HANDLE_VALUE) {
+ DEBUGF("close_device: %d", ctx->fh);
+ CloseHandle(ctx->fh);
+ }
+ if (ctx->in.hEvent) {
+ if (ctx->reading) {
+ CancelIo(ctx->in.hEvent);
+ // waitfor it (ERROR_OPERATION_ABORTED)
+ }
+ CloseHandle(ctx->in.hEvent);
+ ctx->in.hEvent = NULL;
+ }
+ if (ctx->out.hEvent) {
+ if (ctx->writing) {
+ CancelIo(ctx->out.hEvent);
+ // waitfor it (ERROR_OPERATION_ABORTED)
+ }
+ CloseHandle(ctx->out.hEvent);
+ ctx->out.hEvent = NULL;
+ }
+ if (ctx->stat.hEvent) {
+ CloseHandle(ctx->stat.hEvent);
+ ctx->stat.hEvent = NULL;
+ }
+}
+
+// FIXME: use on linux subscribe to modem bits
+// TIOCMIWAIT wait for modem bits to change
+// TCIOGICOUNT count number of changes
+//
+#define SET_BIT(fs, x) fs |= (x)
+#define CLR_BIT(fs, x) fs &= ~(x)
+
+#define UPD_BIT(fs, x, on) do { \
+ if ((on)) SET_BIT(fs,x); \
+ else CLR_BIT(fs,(x)); \
+ } while(0)
+
+
+static int get_com_state(HANDLE fh, uart_com_state_t* com)
+{
+ DCB dcb;
+ COMMTIMEOUTS tmo;
+ COMMPROP prop;
+
+ memset(&dcb, 0, sizeof(dcb));
+
+ if (!GetCommState(fh, &dcb)) {
+ DEBUG_ERROR("GetCommState: error %d", GetLastError());
+ return -1;
+ }
+ // input baud reate
+ com->ibaud = from_speed(dcb.BaudRate);
+ com->obaud = com->ibaud;
+
+ // parity
+ if (!dcb.fParity)
+ com->parity = UART_PARITY_NONE;
+ else {
+ switch (dcb.Parity) {
+ case ODDPARITY: com->parity = UART_PARITY_ODD; break;
+ case EVENPARITY: com->parity = UART_PARITY_EVEN; break;
+ case MARKPARITY: com->parity = UART_PARITY_MARK; break;
+ case SPACEPARITY: com->parity = UART_PARITY_SPACE; break;
+ case NOPARITY: com->parity = UART_PARITY_NONE; break;
+ default: com->parity = UART_PARITY_NONE; break;
+ }
+ }
+ // stop bits
+ if (dcb.StopBits == ONESTOPBIT)
+ com->stopb = 1;
+ else if (dcb.StopBits == TWOSTOPBITS)
+ com->stopb = 2;
+ else if (dcb.StopBits == ONE5STOPBITS)
+ com->stopb = 3; // error?
+ else
+ com->stopb = 0;
+
+ com->csize = dcb.ByteSize;
+
+ com->xonchar = dcb.XonChar;
+ com->xoffchar = dcb.XoffChar;
+
+ com->iflow = 0;
+ if (dcb.fInX) com->iflow |= UART_SW;
+ if (dcb.fDtrControl == DTR_CONTROL_HANDSHAKE)
+ com->iflow |= UART_DTR;
+ if (dcb.fRtsControl == RTS_CONTROL_HANDSHAKE)
+ com->iflow |= UART_RTS;
+
+ com->oflow = 0;
+ if (dcb.fOutX) com->oflow |= UART_SW;
+ if (dcb.fOutxCtsFlow) com->oflow |= UART_CTS;
+ if (dcb.fOutxDsrFlow) com->oflow |= UART_DSR;
+
+ if (!GetCommTimeouts(fh, &tmo)) {
+ DEBUG_ERROR("GetCommTimeouts: error %d", GetLastError());
+ return -1;
+ }
+
+ if (!GetCommProperties(fh, &prop)) {
+ DEBUG_ERROR("GetCommProperties: error %d", GetLastError());
+ return -1;
+ }
+ // Hardware buffer arguments (MODERNIZE this!!!)
+ com->bufsz = prop.dwCurrentRxQueue;
+ // com->rxqueue = prop.dwCurrentTxQueue; ????
+ // com->txqueue = prop.dwCurrentTxQueue;
+ com->buftm = tmo.ReadIntervalTimeout;
+
+ return 0;
+}
+
+static int set_com_state(HANDLE fh, uart_com_state_t* com)
+{
+ DCB dcb;
+ COMMTIMEOUTS tmo;
+ int baud;
+
+ memset(&dcb, 0, sizeof(dcb));
+ if (!GetCommState(fh, &dcb)) {
+ DEBUG_ERROR("GetCommState: error %d", GetLastError());
+ return -1;
+ }
+
+ baud = (com->ibaud > com->obaud) ? com->ibaud : com->obaud;
+ dcb.BaudRate = to_speed(baud);
+
+ switch(com->parity) {
+ case UART_PARITY_NONE:
+ dcb.fParity = FALSE;
+ dcb.Parity = NOPARITY;
+ break;
+ case UART_PARITY_ODD:
+ dcb.fParity = TRUE;
+ dcb.Parity = ODDPARITY;
+ break;
+ case UART_PARITY_EVEN:
+ dcb.fParity = TRUE;
+ dcb.Parity = EVENPARITY;
+ break;
+ case UART_PARITY_MARK:
+ dcb.fParity = TRUE;
+ dcb.Parity = MARKPARITY;
+ break;
+ case UART_PARITY_SPACE:
+ dcb.fParity = TRUE;
+ dcb.Parity = SPACEPARITY;
+ break;
+ default:
+ dcb.fParity = FALSE;
+ dcb.Parity = NOPARITY;
+ break;
+ }
+
+ if (com->stopb == 1)
+ dcb.StopBits = ONESTOPBIT;
+ else if (com->stopb == 2)
+ dcb.StopBits = TWOSTOPBITS;
+ else if (com->stopb == 3)
+ dcb.StopBits = ONE5STOPBITS;
+
+ dcb.ByteSize = com->csize;
+
+ dcb.XonChar = com->xonchar;
+ dcb.XoffChar = com->xoffchar;
+
+ // FIXME better flow handling!
+ // {flow,[xoff|dtr|rts],[xoff|cts|dsr]} ?
+ //
+ dcb.fInX = (com->iflow & UART_SW) != 0;
+ dcb.fDtrControl = (com->iflow & UART_DTR) != 0;
+ dcb.fRtsControl = (com->iflow & UART_RTS) != 0;
+
+ dcb.fOutX = (com->oflow & UART_SW) != 0;
+ dcb.fOutxCtsFlow = (com->oflow & UART_CTS) != 0;
+ dcb.fOutxDsrFlow = (com->oflow & UART_DSR) != 0;
+
+ dcb.fNull = FALSE;
+
+ dcb.DCBlength = sizeof(DCB);
+ if (!SetCommState(fh, &dcb)) {
+ DEBUG_ERROR("SetCommState: error %d", GetLastError());
+ return -1;
+ }
+
+ // Setup harware buffer and timers
+ if (com->bufsz > 255)
+ SetupComm(fh, 255, DEFAULT_OUT_QUEUE);
+ else
+ SetupComm(fh, com->bufsz, DEFAULT_OUT_QUEUE);
+
+ tmo.ReadIntervalTimeout = com->buftm;
+ tmo.ReadTotalTimeoutMultiplier = 0;
+ tmo.ReadTotalTimeoutConstant = 0;
+ tmo.WriteTotalTimeoutMultiplier = 0;
+ tmo.WriteTotalTimeoutConstant = 0;
+ if (!SetCommTimeouts(fh, &tmo)) {
+ DEBUG_ERROR("GetCommTimeouts: error %d", GetLastError());
+ return -1;
+ }
+ return 0;
+}
+
+static int get_modem_state(HANDLE fh, uart_modem_state_t* state)
+{
+ DCB dcb;
+ DWORD status;
+ int s = 0;
+
+ if (!GetCommState(fh, &dcb)) {
+ DEBUG_ERROR("GetCommState: error %d", GetLastError());
+ return -1;
+ }
+ if (dcb.fDtrControl == DTR_CONTROL_ENABLE) s |= UART_DTR;
+ if (dcb.fRtsControl == RTS_CONTROL_ENABLE) s |= UART_RTS;
+ if (!GetCommModemStatus(fh, &status)) {
+ DEBUG_ERROR("GetModemStatus: error %d", GetLastError());
+ return -1;
+ }
+ if (status & MS_DSR_ON) s |= UART_DSR;
+ if (status & MS_CTS_ON) s |= UART_CTS;
+ if (status & MS_RING_ON) s |= UART_RI;
+ if (status & MS_RLSD_ON) s |= UART_CD;
+ *state = s;
+ return 0;
+}
+
+static int set_modem_state(HANDLE fh, uart_modem_state_t state, int on)
+{
+ if (state & UART_DTR) {
+ if (!EscapeCommFunction(fh, on ? SETDTR : CLRDTR)) {
+ DEBUG_ERROR("EscapeCommFunction: error %d", GetLastError());
+ return -1;
+ }
+ }
+ if (state & UART_RTS) {
+ if (!EscapeCommFunction(fh, on ? SETRTS : CLRRTS)) {
+ DEBUG_ERROR("EscapeCommFunction: error %d", GetLastError());
+ return -1;
+ }
+ }
+ // Other bits ? RING?
+ return 0;
+}
+
+static int uart_final(uart_ctx_t* ctx)
+{
+ uart_buf_finish(&ctx->ib);
+ return 0;
+}
+
+void uart_init(uart_ctx_t* ctx, dthread_t* self, dthread_t* other)
+{
+ memset(ctx, 0, sizeof(uart_ctx_t));
+ ctx->fh = INVALID_HANDLE_VALUE;
+ ctx->option.bsize = UART_DEF_BUFFER;
+ ctx->option.high = UART_HIGH_WATERMARK;
+ ctx->option.low = UART_LOW_WATERMARK;
+ ctx->option.send_timeout = UART_INFINITY;
+ ctx->option.send_timeout_close = 0;
+
+ ctx->option.hsz = 0; // list header size
+ ctx->option.htype = UART_PB_RAW; // default packet type
+ ctx->option.psize = 0; // no size check
+ ctx->option.mode = UART_MODE_LIST; // list mode
+ ctx->option.deliver = UART_DELIVER_TERM; // standard term format
+ ctx->option.active = UART_PASSIVE; // start passive
+ ctx->option.exitf = 0;
+
+ uart_buf_init(&ctx->ib);
+ uart_queue_init(&ctx->oq);
+
+ com_state_init(&ctx->state);
+
+ ctx->port = self->port;
+ ctx->dport = self->dport;
+
+ ctx->self = self;
+ ctx->other = other;
+}
+
+static int apply_opts(uart_ctx_t* ctx,
+ uart_com_state_t* state, uart_opt_t* option,
+ int32_t sflags)
+{
+ int old_active;
+ unsigned int old_htype;
+
+ if ((sflags & (1 << UART_OPT_DEVICE)) &&
+ (strcmp(option->device_name, ctx->option.device_name) != 0)) {
+
+ close_device(ctx);
+ sflags = 0;
+ if (open_device(ctx, option->device_name) < 0)
+ return -1;
+#ifdef DEBUG
+ // com_state_dump(stderr, state);
+#endif
+ if (set_com_state(ctx->fh, state) < 0) {
+ DEBUGF("set_opts: uart_set_com_state failed");
+ return -1;
+ }
+ if (get_com_state(ctx->fh, state) >= 0) {
+ DEBUGF("set_opts: com_state: after");
+#ifdef DEBUG
+ // com_state_dump(stderr, state);
+#endif
+ }
+ else {
+ DEBUGF("apply_opts: get_com_state failed");
+ }
+ }
+ else if (sflags & UART_OPT_COMM) {
+ if (ctx->fh != INVALID_HANDLE_VALUE) {
+ // DEBUGF("set_opts: com_state before:");
+ // com_state_dump(stderr, state);
+ if (set_com_state(ctx->fh, state) < 0)
+ return -1;
+ sflags = 0;
+ if (get_com_state(ctx->fh, state) >= 0) {
+ // DEBUGF("set_opts: com_state: after");
+#ifdef DEBUG
+ // com_state_dump(stderr, state);
+#endif
+ }
+ }
+ }
+
+ old_active = ctx->option.active;
+ old_htype = ctx->option.htype;
+
+ ctx->sflags = sflags;
+ ctx->state = *state;
+ ctx->option = *option;
+
+ if (ctx->fh != INVALID_HANDLE_VALUE) {
+ if (ctx->option.active) {
+ if (!old_active || (ctx->option.htype != old_htype))
+ return 1;
+ ctx->remain = 0; // CHECK ME
+ return 0;
+ }
+ }
+ return 0;
+}
+
+/*
+** Deliver packet ready
+** if len == 0 then check start with a check for ready packet
+*/
+int uart_deliver(uart_ctx_t* ctx, int len)
+{
+ int count = 0;
+ int n;
+
+ DEBUGF("uart_deliver(%ld): s=%ld about to deliver %d bytes...",
+ (long)ctx->port, (long)ctx->fh, len);
+
+ /* Poll for ready packet */
+ if (len == 0) {
+ /* empty buffer or waiting for more input */
+ if ((ctx->ib.base == NULL) || (ctx->remain > 0))
+ return count;
+ n = uart_buf_remain(&ctx->ib, &len, ctx->option.htype,
+ ctx->option.psize);
+ if (n != 0) {
+ if (n < 0) /* packet error */
+ return n;
+ if (len > 0) /* more data pending */
+ ctx->remain = len;
+ return count;
+ }
+ }
+
+ if (len > 0) {
+ int code;
+
+ code = uart_reply_data(ctx, (char*) ctx->ib.ptr_start, len);
+ clear_timeout(ctx);
+
+ /* XXX The buffer gets thrown away on error (code < 0) */
+ /* Windows needs workaround for this in uart_uart_event... */
+ if (code < 0)
+ return code;
+ ctx->ib.ptr_start += len;
+ if (ctx->ib.ptr_start == ctx->ib.ptr)
+ uart_buf_reset(&ctx->ib);
+ else
+ ctx->remain = 0;
+ }
+
+ count++;
+ len = 0;
+
+ if (!ctx->option.active) {
+ if (ctx->ib.base != NULL)
+ uart_buf_restart(&ctx->ib);
+ }
+ else if (ctx->ib.base != NULL) {
+ n = uart_buf_remain(&ctx->ib, &len,
+ ctx->option.htype, ctx->option.psize);
+ if (n != 0) {
+ if (n < 0) /* packet error */
+ return n;
+ uart_buf_restart(&ctx->ib);
+ if (len > 0)
+ ctx->remain = len;
+ len = 0;
+ }
+ }
+ return count;
+}
+
+
+// The modem has closed, cleanup and send event
+int uart_recv_closed(uart_ctx_t* ctx)
+{
+ DEBUGF("uart_recv_closed(%ld)", (long) ctx->port);
+
+ clear_timeout(ctx);
+ uart_buf_reset(&ctx->ib);
+ close_device(ctx);
+
+ if (!ctx->option.active) {
+ uart_async_error_am(ctx, ctx->dport, ctx->caller, am_closed);
+ }
+ else {
+ uart_closed_message(ctx);
+ }
+ return -1;
+}
+
+//
+// We have a read error determine the action
+//
+int uart_recv_error(uart_ctx_t* ctx, int err)
+{
+ if (err != EAGAIN) {
+ clear_timeout(ctx);
+ uart_buf_reset(&ctx->ib);
+ close_device(ctx);
+
+ if (!ctx->option.active) {
+ uart_async_error(ctx, ctx->dport, ctx->caller, err);
+ }
+ else {
+ uart_error_message(ctx, err); // first error
+ uart_closed_message(ctx); /* then closed */
+ }
+ return -1;
+ }
+ return 0;
+}
+
+// process input data, buffer according to packet type
+// return 0: no packet/data delivered
+// return 1: packet delivered
+// return -1: error
+//
+int process_input(uart_ctx_t* ctx, dthread_t* self, size_t request_len)
+{
+ (void) self;
+ DWORD n;
+ DWORD nread;
+
+ if (ctx->ib.base == NULL) { /* allocte a read buffer */
+ size_t sz = ctx->option.bsize;
+ if (request_len > 0)
+ sz = request_len;
+ if (sz == 0)
+ sz = 1;
+ if (uart_buf_alloc(&ctx->ib,sz) < 0)
+ return uart_recv_error(ctx, ENOMEM);
+ ctx->remain = request_len;
+ nread = sz;
+ }
+ else if (request_len > 0) { /* we have a data in buffer and a request */
+ n = ctx->ib.ptr - ctx->ib.ptr_start;
+ if (n >= request_len)
+ return uart_deliver(ctx, request_len);
+ else if (uart_buf_expand(&ctx->ib, request_len) < 0)
+ return uart_recv_error(ctx, ENOMEM);
+ else {
+ nread = request_len - n;
+ ctx->remain = nread;
+ }
+ }
+ else if ((nread=ctx->remain) == 0) { /* poll remain from buffer data */
+ int len, i;
+ i = uart_buf_remain(&ctx->ib, &len,
+ ctx->option.htype,
+ ctx->option.psize);
+ if (i < 0)
+ return uart_recv_error(ctx, EMSGSIZE);
+ else if (i == 0)
+ return uart_deliver(ctx, len);
+ else if (len > 0)
+ ctx->remain = len;
+ nread = i;
+ }
+
+ DEBUGF("uart_recv(%ld): s=%ld about to read %d bytes...",
+ (long)ctx->port, (long)ctx->fh, nread);
+
+ if (!ctx->reading) {
+ if (!ReadFile(ctx->fh, ctx->rbuf, 1, &n, &ctx->in)) {
+ if (GetLastError() != ERROR_IO_PENDING)
+ return uart_recv_error(ctx, uart_errno(ctx));
+ ctx->reading = 1;
+ DEBUGF(" => would block");
+ return 0;
+ }
+ }
+ else {
+ // check that arguments are the same as last call ?
+ if (!GetOverlappedResult(ctx->fh, &ctx->in, &n, FALSE)) {
+ if (GetLastError() == ERROR_IO_INCOMPLETE) { // still waiting
+ ctx->reading = 1;
+ DEBUGF(" => would block");
+ return 0;
+ }
+ ctx->reading = 0;
+ return uart_recv_error(ctx, uart_errno(ctx));
+ }
+ ctx->reading = 0;
+ }
+
+ if (n == 0) {
+ DEBUGF(" => detected close");
+ return uart_recv_closed(ctx);
+ }
+ if (n == 1)
+ *ctx->ib.ptr = ctx->rbuf[0];
+
+ DEBUGF(" => got %d bytes", n);
+ ctx->ib.ptr += n;
+ if (ctx->remain > 0) {
+ ctx->remain -= n;
+ if (ctx->remain == 0)
+ return uart_deliver(ctx, ctx->ib.ptr - ctx->ib.ptr_start);
+ }
+ else {
+ int i, len;
+ i = uart_buf_remain(&ctx->ib, &len,
+ ctx->option.htype,
+ ctx->option.psize);
+ if (i < 0)
+ return uart_recv_error(ctx, EMSGSIZE);
+ else if (i == 0)
+ return uart_deliver(ctx, len);
+ else if (len > 0)
+ ctx->remain = len;
+ }
+ return 0;
+}
+
+
+// process output queue
+int process_output(uart_ctx_t* ctx, dthread_t* self)
+{
+ dmessage_t* mp;
+
+ while ((mp = ctx->oq.mesg) != NULL) {
+ DWORD nbytes = mp->used - ctx->oq.offs;
+ DWORD nwritten;
+
+ if (!ctx->writing) {
+ if (!WriteFile(ctx->fh, mp->buffer+ctx->oq.offs,
+ nbytes, &nwritten, &ctx->out)) {
+ if (GetLastError() != ERROR_IO_PENDING)
+ // return uart_send_error(ctx, uart_errno(ctx));
+ return -1;
+ }
+ ctx->writing = 1;
+ return 0;
+ }
+ else {
+ if (!GetOverlappedResult(ctx->fh, &ctx->out, &nwritten, FALSE)) {
+ if (GetLastError() == ERROR_IO_INCOMPLETE) // still waiting
+ return 0;
+ // return uart_send_error(ctx, uart_errno(ctx));
+ ctx->writing = 0;
+ return -1;
+ }
+ ctx->writing = 0;
+ }
+
+ if (nwritten < nbytes)
+ ctx->oq.offs += nwritten;
+ else {
+ if (mp->from)
+ dthread_port_send_ok(mp->source, self, mp->from, mp->ref);
+ dmessage_free(mp);
+ ctx->oq.offs = 0;
+ if ((mp = ctx->oq.front) == NULL)
+ ctx->oq.mesg = NULL;
+ else {
+ if (!(ctx->oq.front = mp->next))
+ ctx->oq.rear = NULL;
+ ctx->oq.mesg = mp;
+ }
+ }
+ }
+ return 0;
+}
+
+
+int enq_output(uart_ctx_t* ctx, dthread_t* self,
+ dmessage_t* mp, ErlDrvTermData from)
+{
+ dmessage_t* mr;
+
+ mp->next = NULL;
+ mp->from = from; // from = 0 => async
+
+ // set packet bytes header!
+ set_packet_bytes(mp, ctx->option.htype);
+
+ if ((ctx->oq.mesg == NULL) && (ctx->oq.front == NULL)) {
+ ctx->oq.offs = 0;
+ ctx->oq.mesg = mp;
+ return process_output(ctx, self);
+ }
+ else {
+ if ((mr = ctx->oq.rear) != NULL)
+ mr->next = mp;
+ else
+ ctx->oq.front = mp;
+ ctx->oq.rear = mp;
+ return 0;
+ }
+}
+
+
+// thread main!
+int uart_win32_main(void* arg)
+{
+ dthread_t* self = (dthread_t*) arg;
+ dthread_t* other = (dthread_t*) self->arg;
+ dmessage_t* mp = NULL;
+ dthread_poll_event_t ev[3];
+ dthread_poll_event_t* evp;
+ size_t nev;
+ dterm_t term;
+ uart_ctx_t ctx;
+ ErlDrvTermData mp_from;
+ ErlDrvTermData mp_ref;
+ dthread_t* mp_source;
+ int tmo;
+ int r;
+
+ DEBUGF("uart_win32: thread started");
+
+ uart_init(&ctx, self, other);
+
+ dterm_init(&term);
+
+again_tmo:
+ tmo = next_timeout(&ctx);
+again:
+ nev = 0;
+
+ if (ctx.writing) {
+ ev[nev].event = (ErlDrvEvent) ctx.out.hEvent;
+ ev[nev].events = ERL_DRV_READ; // yepp, even for write
+ nev++;
+ }
+
+ while(!ctx.reading && (ctx.recv || (ctx.option.active != UART_PASSIVE)))
+ process_input(&ctx, self, 0);
+
+ if (ctx.reading) {
+ ev[nev].event = (ErlDrvEvent) ctx.in.hEvent;
+ ev[nev].events = ERL_DRV_READ;
+ nev++;
+ }
+
+ evp = nev ? &ev[0] : NULL;
+
+ DEBUGF("uart_win32_main: ctx.fh=%d, nev=%u, timeout = %d",
+ ctx.fh, nev, tmo);
+ r = dthread_poll(self, evp, &nev, tmo);
+
+ if (r < 0) {
+ DEBUGF("uart_win32_main: dthread_poll failed=%d", r);
+ goto again_tmo;
+ }
+ else {
+ DWORD i;
+ DEBUGF("uart_win32_main: nev=%u, r=%d", nev, r);
+ for (i = 0; i < nev; i++) {
+ if (ev[i].revents & ERL_DRV_READ) {
+ if (ev[i].event == (ErlDrvEvent) ctx.in.hEvent) {
+ while((process_input(&ctx, self, 0) == 1) &&
+ (ctx.option.active != UART_PASSIVE))
+ ;
+ }
+ else if (ev[i].event == (ErlDrvEvent) ctx.out.hEvent) {
+ process_output(&ctx, self);
+ }
+ }
+ }
+ tmo = next_timeout(&ctx);
+ DEBUGF("uart_win32_main: timeout = %d", tmo);
+ if (ctx.recv) {
+ if (tmo == 0) {
+ uart_async_error_am(&ctx, ctx.dport, ctx.caller, am_timeout);
+ clear_timeout(&ctx);
+ ctx.remain = 0;
+ }
+ }
+ if (r == 0)
+ goto again;
+
+ // r>0 (number of messages)
+ DEBUGF("about to receive message r=%d", r);
+ if ((mp = dthread_recv(self, NULL)) == NULL) {
+ DEBUGF("uart_win32_main: message was NULL");
+ goto again;
+ }
+ mp_from = mp->from;
+ mp_ref = mp->ref;
+ mp_source = mp->source;
+
+ switch (mp->cmd) {
+ case DTHREAD_STOP:
+ DEBUGF("uart_win32_main: STOP");
+ close_device(&ctx);
+ uart_final(&ctx);
+ dmessage_free(mp);
+ DEBUGF("uart_win32_main: EXIT");
+ dthread_exit(0);
+ break;
+
+ case DTHREAD_OUTPUT: // async send!
+ DEBUGF("uart_win32_main: OUTPUT");
+ if (ctx.fh == INVALID_HANDLE_VALUE) {
+ dmessage_free(mp);
+ goto again;
+ }
+ if (enq_output(&ctx, self, mp, 0) < 0) {
+ mp = NULL;
+ goto error;
+ }
+ goto again;
+
+ case UART_CMD_CONNECT: {
+ ErlDrvTermData owner;
+ if (mp->used != 0) goto badarg;
+ owner = driver_connected(self->port);
+ self->owner = owner;
+ other->owner = owner;
+ goto ok;
+ }
+
+ case UART_CMD_CLOSE:
+ DEBUGF("uart_win32_main: CLOSE");
+ close_device(&ctx);
+ goto ok;
+
+ case UART_CMD_SEND: // sync send
+ DEBUGF("uart_win32_main: SEND");
+ if (ctx.fh == INVALID_HANDLE_VALUE) goto ebadf;
+ if (enq_output(&ctx, self, mp, mp_from) < 0) {
+ mp = NULL;
+ goto error;
+ }
+ goto again;
+
+ case UART_CMD_SENDCHAR: // sync send
+ DEBUGF("uart_win32_main: SENDCHAR");
+ if (ctx.fh == INVALID_HANDLE_VALUE) goto ebadf;
+ if (enq_output(&ctx, self, mp, mp_from) < 0) {
+ mp = NULL;
+ goto error;
+ }
+ goto again;
+
+ case UART_CMD_RECV: { // <<Time:32, Length:32>> Time=0xffffffff=inf
+ uint32_t tm;
+ int len;
+ DEBUGF("uart_win32_main: RECV");
+ if (ctx.fh == INVALID_HANDLE_VALUE) goto ebadf;
+ if (ctx.recv) goto ealready;
+ if (mp->used != 8) goto badarg;
+ if (ctx.option.active != UART_PASSIVE) goto badarg;
+ tm = get_uint32((uint8_t*) mp->buffer);
+ len = (int) get_uint32((uint8_t*) (mp->buffer+4));
+ if ((len < 0) || (len > UART_MAX_PACKET_SIZE)) goto badarg;
+ ctx.ref = mp_ref;
+ ctx.caller = mp_from;
+ set_timeout(&ctx, tm);
+ ctx.recv = 1;
+ DEBUGF("recv timeout %lu", tm);
+ process_input(&ctx, self, len);
+ dmessage_free(mp);
+ goto again_tmo;
+ }
+
+ case UART_CMD_UNRECV: { // argument is data to push back
+ uart_buf_push(&ctx.ib, mp->buffer, mp->used);
+ DEBUGF("unrecived %d bytes", ctx.ib.ptr - ctx.ib.ptr_start);
+ if (ctx.option.active != UART_PASSIVE) {
+ while((process_input(&ctx, self, 0) == 1) &&
+ (ctx.option.active != UART_PASSIVE))
+ ;
+ }
+ goto ok;
+ }
+
+ case UART_CMD_SETOPTS: {
+ uart_com_state_t state = ctx.state;
+ uart_opt_t option = ctx.option;
+ uint32_t sflags = ctx.sflags;
+
+ // parse & update options in state,option and sflag
+ if (uart_parse_opts(mp->buffer, mp->used,
+ &state, &option, &sflags) < 0)
+ goto badarg;
+
+ // apply the changed values
+ if ((r=apply_opts(&ctx, &state, &option, sflags)) < 0) {
+ goto error;
+ }
+ goto ok;
+ }
+
+ case UART_CMD_GETOPTS: {
+ dterm_mark_t m1;
+ dterm_mark_t m2;
+ // {Ref, {ok,List}} || {Ref, {error,Reason}}
+ dterm_tuple_begin(&term, &m1); {
+ dterm_uint(&term, mp_ref);
+ dterm_tuple_begin(&term, &m2); {
+ dterm_atom(&term, am_ok);
+ if (uart_get_opts(&term, &ctx,(uint8_t*)mp->buffer,mp->used) < 0) {
+ dterm_reset(&term);
+ goto badarg;
+ }
+ }
+ dterm_tuple_end(&term, &m2);
+ }
+ dterm_tuple_end(&term, &m1);
+ dthread_port_send_dterm(mp_source, self, mp_from, &term);
+ dterm_reset(&term);
+ dmessage_free(mp);
+ goto again;
+ }
+
+ case UART_CMD_GET_MODEM: {
+ dterm_mark_t m1;
+ dterm_mark_t m2;
+ uart_modem_state_t mstate;
+ if (ctx.fh == INVALID_HANDLE_VALUE) goto ebadf;
+ if (get_modem_state(ctx.fh, &mstate) < 0) goto error;
+
+ dterm_tuple_begin(&term, &m1); {
+ dterm_uint(&term, mp_ref);
+ dterm_tuple_begin(&term, &m2); {
+ dterm_atom(&term, am_ok);
+ modem_state_dterm(&term, mstate);
+ }
+ dterm_tuple_end(&term, &m2);
+ }
+ dterm_tuple_end(&term, &m1);
+ dthread_port_send_dterm(mp_source, self, mp_from, &term);
+ dterm_reset(&term);
+ dmessage_free(mp);
+ goto again;
+ }
+
+ case UART_CMD_SET_MODEM: {
+ uart_modem_state_t mstate;
+ if (ctx.fh == INVALID_HANDLE_VALUE) goto ebadf;
+ if (mp->used != 4) goto badarg;
+ mstate = (uart_modem_state_t) get_uint32((uint8_t*) mp->buffer);
+ if (set_modem_state(ctx.fh, mstate, 1) < 0) goto error;
+ goto ok;
+ }
+
+ case UART_CMD_CLR_MODEM: {
+ uart_modem_state_t mstate;
+ if (ctx.fh == INVALID_HANDLE_VALUE) goto ebadf;
+ if (mp->used != 4) goto badarg;
+ mstate = (uart_modem_state_t) get_uint32((uint8_t*) mp->buffer);
+ if (set_modem_state(ctx.fh, mstate, 0) < 0) goto error;
+ goto ok;
+ }
+
+ case UART_CMD_HANGUP: {
+ if (ctx.fh == INVALID_HANDLE_VALUE) goto ebadf;
+ if (mp->used != 0) goto badarg;
+ // FIXME?
+ goto ok;
+ }
+
+ case UART_CMD_BREAK: {
+ int duration;
+ if (ctx.fh == INVALID_HANDLE_VALUE) goto ebadf;
+ if (mp->used != 4) goto badarg;
+ duration = (int) get_uint32((uint8_t*) mp->buffer);
+ if (!EscapeCommFunction(ctx.fh, SETBREAK)) {
+ DEBUG_ERROR("EscapeCommFunction: error %d", GetLastError());
+ goto error;
+ }
+ Sleep(duration);
+ if (!EscapeCommFunction(ctx.fh, CLRBREAK)) {
+ DEBUG_ERROR("EscapeCommFunction: error %d", GetLastError());
+ goto error;
+ }
+ goto ok;
+ }
+
+ case UART_CMD_FLOW:
+ if (ctx.fh == INVALID_HANDLE_VALUE) goto ebadf;
+ if (mp->used != 1) goto badarg;
+ switch(mp->buffer[0]) {
+ case 0:
+ if (!EscapeCommFunction(ctx.fh, SETXOFF)) {
+ DEBUG_ERROR("EscapeCommFunction: error %d", GetLastError());
+ goto error;
+ }
+ break;
+ case 1:
+ if (!EscapeCommFunction(ctx.fh, SETXON)) {
+ DEBUG_ERROR("EscapeCommFunction: error %d", GetLastError());
+ goto error;
+ }
+ break;
+ case 2:
+ // TransmitCommChar(ctx.fh, XOFF);
+ break;
+ case 3:
+ // TransmitCommChar(ctx.fh, XON);
+ break;
+ default:
+ goto badarg;
+ }
+ goto ok;
+
+ default:
+ goto badarg;
+ }
+ }
+
+ok:
+ dthread_port_send_ok(mp_source, self, mp_from, mp_ref);
+ if (mp) dmessage_free(mp);
+ goto again;
+
+ebadf:
+ errno = EBADF;
+ goto error;
+badarg:
+ errno = EINVAL;
+ goto error;
+ealready:
+ errno = EBUSY;
+ goto error;
+
+error:
+ dthread_port_send_error(mp_source, self, mp_from, mp_ref,
+ uart_errno(&ctx));
+ if (mp) dmessage_free(mp);
+ goto again;
+}
diff --git a/deps/uart/doc/README.md b/deps/uart/doc/README.md
new file mode 100644
index 0000000..3198cab
--- /dev/null
+++ b/deps/uart/doc/README.md
@@ -0,0 +1,12 @@
+
+
+# The uart application #
+
+
+## Modules ##
+
+
+<table width="100%" border="0" summary="list of modules">
+<tr><td><a href="uart_devices.md" class="module">uart_devices</a></td></tr>
+<tr><td><a href="uart_win32_devices.md" class="module">uart_win32_devices</a></td></tr></table>
+
diff --git a/deps/uart/doc/uart.md b/deps/uart/doc/uart.md
new file mode 100644
index 0000000..31faf85
--- /dev/null
+++ b/deps/uart/doc/uart.md
@@ -0,0 +1,407 @@
+
+
+#Module uart#
+* [Description](#description)
+* [Data Types](#types)
+* [Function Index](#index)
+* [Function Details](#functions)
+
+
+
+Cross platform tty interface.
+
+Copyright (c) (C) 2012, Tony Rogvall
+
+__Authors:__ Tony Rogvall ([`tony@rogvall.se`](mailto:tony@rogvall.se)).
+<a name="types"></a>
+
+##Data Types##
+
+
+
+
+###<a name="type-uart">uart()</a>##
+
+
+
+<pre>uart() = port()</pre>
+
+
+
+###<a name="type-uart_input_pins">uart_input_pins()</a>##
+
+
+
+<pre>uart_input_pins() = cts | cd | ri | dcr</pre>
+
+
+
+###<a name="type-uart_modem_pins">uart_modem_pins()</a>##
+
+
+
+<pre>uart_modem_pins() = <a href="#type-uart_input_pins">uart_input_pins()</a> | <a href="#type-uart_output_pins">uart_output_pins()</a></pre>
+
+
+
+###<a name="type-uart_option">uart_option()</a>##
+
+
+
+<pre>uart_option() = device | baud | ibaud | obaud | csize | bufsz | buftm | stopb | parity | iflow | oflow | xonchar | xoffchar | eolchar | active | delay_send | header | packet | packet_size | deliver | mode | buffer | exit_on_close</pre>
+
+
+
+###<a name="type-uart_output_pins">uart_output_pins()</a>##
+
+
+
+<pre>uart_output_pins() = dtr | rts</pre>
+<a name="index"></a>
+
+##Function Index##
+
+
+<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#async_recv-2">async_recv/2</a></td><td>
+Initiate an async receive operation.</td></tr><tr><td valign="top"><a href="#async_recv-3">async_recv/3</a></td><td>
+Initiate an async receive operation.</td></tr><tr><td valign="top"><a href="#async_send-2">async_send/2</a></td><td>
+Send asynchronous data.</td></tr><tr><td valign="top"><a href="#break-2">break/2</a></td><td>
+Send break for Duration number of milliseconds .</td></tr><tr><td valign="top"><a href="#clear_modem-2">clear_modem/2</a></td><td>
+Clear modem pins.</td></tr><tr><td valign="top"><a href="#close-1">close/1</a></td><td>Close a tty device.</td></tr><tr><td valign="top"><a href="#controlling_process-2">controlling_process/2</a></td><td>
+Connect uart to a new controlling process, that is the process that
+is the event data receiver.</td></tr><tr><td valign="top"><a href="#flow-2">flow/2</a></td><td>
+Manage input and output flow control.</td></tr><tr><td valign="top"><a href="#get_modem-1">get_modem/1</a></td><td>
+Get modem pins status.</td></tr><tr><td valign="top"><a href="#getopt-2">getopt/2</a></td><td>Get single option value.</td></tr><tr><td valign="top"><a href="#getopts-2">getopts/2</a></td><td>Get multiple option values.</td></tr><tr><td valign="top"><a href="#hangup-1">hangup/1</a></td><td>
+Hangup.</td></tr><tr><td valign="top"><a href="#i-0">i/0</a></td><td>List information about open uart ports.</td></tr><tr><td valign="top"><a href="#open-2">open/2</a></td><td>Opens a tty device.</td></tr><tr><td valign="top"><a href="#options-0">options/0</a></td><td>
+This function is for documentations purpose.</td></tr><tr><td valign="top"><a href="#recv-2">recv/2</a></td><td>
+Receive data from a device in passive mode, no timeout.</td></tr><tr><td valign="top"><a href="#recv-3">recv/3</a></td><td>
+Receive data from a device in passive mode.</td></tr><tr><td valign="top"><a href="#send-2">send/2</a></td><td>
+Send characters.</td></tr><tr><td valign="top"><a href="#send_char-2">send_char/2</a></td><td>
+Send a single character.</td></tr><tr><td valign="top"><a href="#set_modem-2">set_modem/2</a></td><td>
+Set modem pins.</td></tr><tr><td valign="top"><a href="#setopt-3">setopt/3</a></td><td>
+Set single option.</td></tr><tr><td valign="top"><a href="#setopts-2">setopts/2</a></td><td>
+Set multiple options.</td></tr><tr><td valign="top"><a href="#unrecv-2">unrecv/2</a></td><td>
+Push back data onto the receive buffer.</td></tr></table>
+
+
+<a name="functions"></a>
+
+##Function Details##
+
+<a name="async_recv-2"></a>
+
+###async_recv/2##
+
+
+<pre>async_recv(Uart::<a href="#type-uart">uart()</a>, Length::non_neg_integer()) -> {ok, integer()} | {error, term()}</pre>
+<br></br>
+
+
+
+Initiate an async receive operation.<a name="async_recv-3"></a>
+
+###async_recv/3##
+
+
+<pre>async_recv(Uart::<a href="#type-uart">uart()</a>, Length::non_neg_integer(), Timeout::timeout() | -1) -> {ok, integer()} | {error, term()}</pre>
+<br></br>
+
+
+
+Initiate an async receive operation.
+To initiate an async operation reading a certain length and with
+a timeout the async_recv can be useful.
+<pre> {ok,Ref} = uart:async_recv(Uart, 16, 1000),
+ receive
+ {Ref,{error,Reason}} -> {error,Reason}
+ {uart_async,Uart,Ref,{ok,Data}} -> {ok,Data};
+ {uart_async,Uart,Ref,{error,Reason}} -> {error,Reason};
+ {'EXIT',Uart,_Reason} -> {error,closed}
+ ...
+ end</pre>
+The above can also be achived by using active once and
+a fixed packet mode.
+<pre> uart:setopts(Uart, [{packet,{size,16}},{active,once}]),
+ receive
+ {uart,Uart,Data} -> {ok,Data};
+ {uart_error,Uart,enxio} -> {error,usb_device_pulled_out};
+ {uart_error,Uart,Err} -> {error,Err};
+ {uart_closed,Uart} -> {error,close}
+ after Timeout ->
+ {error,timeout}
+ end</pre>
+Packet size are however limited (to 16 bits), so any size
+above 64K must be handled with async_recv or split into
+chunks.<a name="async_send-2"></a>
+
+###async_send/2##
+
+
+<pre>async_send(Uart::<a href="#type-uart">uart()</a>, Data::iolist()) -> ok</pre>
+<br></br>
+
+
+
+Send asynchronous data.<a name="break-2"></a>
+
+###break/2##
+
+
+<pre>break(Uart::<a href="#type-uart">uart()</a>, Duration::non_neg_integer()) -> ok | {error, term()}</pre>
+<br></br>
+
+
+
+Send break for Duration number of milliseconds .<a name="clear_modem-2"></a>
+
+###clear_modem/2##
+
+
+<pre>clear_modem(Uart::<a href="#type-uart">uart()</a>, Flags::[<a href="#type-uart_modem_pins">uart_modem_pins()</a>]) -> ok | {error, term()}</pre>
+<br></br>
+
+
+
+Clear modem pins.<a name="close-1"></a>
+
+###close/1##
+
+
+<pre>close(Uart::<a href="#type-uart">uart()</a>) -> true</pre>
+<br></br>
+
+
+Close a tty device.<a name="controlling_process-2"></a>
+
+###controlling_process/2##
+
+
+<pre>controlling_process(Uart::<a href="#type-uart">uart()</a>, NewOwner::pid()) -> ok | {error, term()}</pre>
+<br></br>
+
+
+
+Connect uart to a new controlling process, that is the process that
+is the event data receiver. The caller must be the current owner of
+the uart.<a name="flow-2"></a>
+
+###flow/2##
+
+
+<pre>flow(Uart::<a href="#type-uart">uart()</a>, Mode::(input_off | input_on | output_off | output_on)) -> ok | {error, term()}</pre>
+<br></br>
+
+
+
+Manage input and output flow control<a name="get_modem-1"></a>
+
+###get_modem/1##
+
+
+<pre>get_modem(Uart::<a href="#type-uart">uart()</a>) -> {ok, [<a href="#type-uart_modem_pins">uart_modem_pins()</a>]} | {error, term()}</pre>
+<br></br>
+
+
+
+Get modem pins status.<a name="getopt-2"></a>
+
+###getopt/2##
+
+
+<pre>getopt(Uart::<a href="#type-uart">uart()</a>, Option::<a href="#type-uart_option">uart_option()</a>) -> {ok, term()} | {error, term()}</pre>
+<br></br>
+
+
+Get single option value.
+See [`options/0`](#options-0) for available options.<a name="getopts-2"></a>
+
+###getopts/2##
+
+
+<pre>getopts(Uart::<a href="#type-uart">uart()</a>, Option::[<a href="#type-uart_option">uart_option()</a>]) -> {ok, [{<a href="#type-uart_option">uart_option()</a>, term()}]} | {error, term()}</pre>
+<br></br>
+
+
+Get multiple option values.<a name="hangup-1"></a>
+
+###hangup/1##
+
+
+<pre>hangup(Uart::<a href="#type-uart">uart()</a>) -> ok | {error, term()}</pre>
+<br></br>
+
+
+
+Hangup<a name="i-0"></a>
+
+###i/0##
+
+
+`i() -> any()`
+
+List information about open uart ports<a name="open-2"></a>
+
+###open/2##
+
+
+<pre>open(DeviceName::iolist(), Options::[{<a href="#type-uart_option">uart_option()</a>, term()}]) -> {ok, <a href="#type-uart">uart()</a>} | {error, term()}</pre>
+<br></br>
+
+
+
+
+Opens a tty device.
+
+The device name `pty` is reserved for opening a pseudo terminal
+using the openpty call. The slave device is accessed through the
+device option. See [`options/0`](#options-0) for a description of
+available options.<a name="options-0"></a>
+
+###options/0##
+
+
+<pre>options() -&gt; atom()</pre>
+<br></br>
+
+
+
+
+
+This function is for documentations purpose.
+
+List of available options:
+
+* `{device, "pty" | string()}`
+
+* `{ibaud, baudrate()}`
+
+* `{obaud, baudrate()}`
+
+* `{baud, baudrate()}`
+
+* `{csize, 5|6|7|8}`
+
+* `{stopb, 1|2|3}`
+
+* `{parity,none|odd|even|mark|space}`
+
+* `{iflow, [sw|rts|dtr]}`
+
+* `{oflow, [sw|cts|dsr|cd]}`
+
+* `{xonchar, byte()}`
+
+* `{xoffchar, byte()}`
+
+* `{eolchar, byte()}`
+
+* `{active, true | false | once}`
+
+* `{delay_send, boolean()}`
+
+* `{header, size()}`
+
+* `{packet, packet_type()}`
+<br></br>
+
+`packet_type() ::= -8..-1|0|1..8,line,{size,0..65535}`
+
+* `{packet_size, integer()}`
+
+* `{deliver, port | term}`
+
+* `{mode, list | binary}`
+
+* `{buffer, integer()}`
+
+* `{exit_on_close, boolean()}`
+
+* `{bufsz, 0..255}` - Max low level uart buffer size
+
+* `{buftm, 0..25500}` - Inter character timeout
+
+<a name="recv-2"></a>
+
+###recv/2##
+
+
+<pre>recv(Uart::<a href="#type-uart">uart()</a>, Length::non_neg_integer()) -> {ok, iolist()} | {error, term()}</pre>
+<br></br>
+
+
+
+Receive data from a device in passive mode, no timeout.<a name="recv-3"></a>
+
+###recv/3##
+
+
+<pre>recv(Uart::<a href="#type-uart">uart()</a>, Length::non_neg_integer(), Timeout::timeout()) -> {ok, iolist()} | {error, term()}</pre>
+<br></br>
+
+
+
+Receive data from a device in passive mode.<a name="send-2"></a>
+
+###send/2##
+
+
+<pre>send(Uart::<a href="#type-uart">uart()</a>, Data::iolist()) -> ok | {error, term()}</pre>
+<br></br>
+
+
+
+Send characters<a name="send_char-2"></a>
+
+###send_char/2##
+
+
+<pre>send_char(Uart::<a href="#type-uart">uart()</a>, C::byte()) -> ok | {error, term()}</pre>
+<br></br>
+
+
+
+Send a single character<a name="set_modem-2"></a>
+
+###set_modem/2##
+
+
+<pre>set_modem(Uart::<a href="#type-uart">uart()</a>, Flags::[<a href="#type-uart_modem_pins">uart_modem_pins()</a>]) -> ok | {error, term()}</pre>
+<br></br>
+
+
+
+Set modem pins.<a name="setopt-3"></a>
+
+###setopt/3##
+
+
+<pre>setopt(Uart::<a href="#type-uart">uart()</a>, Option::<a href="#type-uart_option">uart_option()</a>, Value::term()) -> ok | {error, term()}</pre>
+<br></br>
+
+
+
+
+
+Set single option.
+
+See [`options/0`](#options-0) for available options.<a name="setopts-2"></a>
+
+###setopts/2##
+
+
+<pre>setopts(Uart::<a href="#type-uart">uart()</a>, Options::[{<a href="#type-uart_option">uart_option()</a>, term()}]) -> ok | {error, term()}</pre>
+<br></br>
+
+
+
+Set multiple options.<a name="unrecv-2"></a>
+
+###unrecv/2##
+
+
+<pre>unrecv(Uart::<a href="#type-uart">uart()</a>, Data::iolist()) -> ok | {error, term()}</pre>
+<br></br>
+
+
+
+Push back data onto the receive buffer. \ No newline at end of file
diff --git a/deps/uart/priv/uart.yang b/deps/uart/priv/uart.yang
new file mode 100644
index 0000000..2969068
--- /dev/null
+++ b/deps/uart/priv/uart.yang
@@ -0,0 +1,174 @@
+/*
+ * YANG description of a UART options
+ *
+ */
+module uart {
+ namespace "http://rogvall.se/ns/uart";
+ prefix uart;
+
+ organization
+ "Rogvall Invest AB";
+
+ contact
+ "Tony Rogvall <tony@rogvall.se>";
+
+ description
+ "UART device settings";
+
+ revision 2012-04-26 {
+ description
+ "Initial revision.";
+ }
+
+ grouping uart-config {
+ leaf device {
+ type string { length "1..255"; }
+ }
+ leaf baud {
+ type uint32;
+ default 9600;
+ }
+
+ leaf ibaud {
+ type uint32;
+ default 9600;
+ }
+
+ leaf obaud {
+ type uint32;
+ default 9600;
+ }
+
+ leaf csize {
+ type uint8 { range "5..8"; }
+ default 8;
+ }
+ leaf bufsz {
+ type uint8;
+ default 1;
+ }
+
+ leaf buftm {
+ type uint8;
+ default 0;
+ }
+
+ leaf stopb {
+ type uint8 { range "1..3"; }
+ default 1;
+ }
+
+ leaf parity {
+ type enumeration {
+ enum none;
+ enum odd;
+ enum even;
+ enum mark;
+ }
+ default none;
+ }
+
+ // iflow = { [sw] [ rts | dtr ] }
+ leaf iflow {
+ type bits {
+ bit dtr { position 1; }
+ bit rts { position 2; }
+ bit sw { position 15; }
+ }
+ default "";
+ }
+
+ // oflow = { [sw] | cts | dsr | dcd }
+ leaf oflow {
+ type bits {
+ bit cts { position 3; }
+ bit dcd { position 4; }
+ bit dsr { position 6; }
+ bit sw { position 15; }
+ }
+ default "";
+ }
+ leaf xonchar {
+ type uint8;
+ }
+ leaf xoffchar {
+ type uint8;
+ }
+ leaf eolhar {
+ type uint8;
+ default 10;
+ }
+ leaf active {
+ type enumeration {
+ enum false;
+ enum true;
+ enum once;
+ }
+ }
+ leaf delay_send {
+ type boolean;
+ }
+ leaf header {
+ type uint32;
+ }
+ leaf packet_size {
+ type uint32;
+ }
+ leaf deliver {
+ type enumeration {
+ enum port;
+ enum term;
+ }
+ }
+ leaf mode {
+ type enumeration {
+ enum list;
+ enum binary;
+ }
+ }
+
+ choice packet {
+
+ leaf size {
+ description "No packaging is done. buf if and argument > 0
+ is given, fixed size packeting is used.";
+ type uint16;
+ default 0;
+ }
+
+ container packet-bytes {
+ description "Prepend packet with a length field";
+ leaf length {
+ description "Size of the lenght field in bytes";
+ type uint8 {
+ range "1..8";
+ }
+ }
+ leaf endian {
+ description "Byte order of the length field";
+ type enumeration {
+ enum big;
+ enum litttle;
+ }
+ default big;
+ }
+ }
+
+ leaf line {
+ description "Line mode, a packet is a line terminated with newline";
+ type empty;
+ }
+ }
+
+ leaf buffer {
+ description "Maximum number of byte to buffer in case of packet=0";
+ type uint32;
+ }
+
+ leaf exit_on_close {
+ description "Close the uart in case of error or hangup";
+ type boolean;
+ default true;
+ }
+ }
+} \ No newline at end of file
diff --git a/deps/uart/rebar.config b/deps/uart/rebar.config
new file mode 100644
index 0000000..4cda483
--- /dev/null
+++ b/deps/uart/rebar.config
@@ -0,0 +1,44 @@
+%% -*- erlang -*-
+%% Config file for uart-application
+{deps, [ {dthread, ".*", {git, "git@github.com:tonyrog/dthread.git"}}]}.
+{erl_opts, [debug_info, fail_on_warning]}.
+{sub_dirs, ["src"]}.
+
+{port_env, [
+ {"CFLAGS", "$CFLAGS -DDEBUG_MEM -DDLOG_DEFAULT=DLOG_NONE -D_THREAD_SAFE -I $REBAR_DEPS_DIR"},
+ {"(linux)", "LDFLAGS", "$LDFLAGS $REBAR_DEPS_DIR/dthread/c_src/dlog.o $REBAR_DEPS_DIR/dthread/c_src/dlib.o $REBAR_DEPS_DIR/dthread/c_src/dterm.o $REBAR_DEPS_DIR/dthread/c_src/dthread.o -lutil"},
+ {"(freebsd|darwin)", "LDFLAGS", "$LDFLAGS $REBAR_DEPS_DIR/dthread/c_src/dlog.o $REBAR_DEPS_DIR/dthread/c_src/dlib.o $REBAR_DEPS_DIR/dthread/c_src/dterm.o $REBAR_DEPS_DIR/dthread/c_src/dthread.o"},
+ {"win32", "LDFLAGS", "$LDFLAGS $REBAR_DEPS_DIR/dthread/c_src/dlog.obj $REBAR_DEPS_DIR/dthread/c_src/dlib.obj $REBAR_DEPS_DIR/dthread/c_src/dterm.obj $REBAR_DEPS_DIR/dthread/c_src/dthread.obj"}
+ ]}.
+
+{port_specs, [
+ {"(linux|freebsd|darwin)","priv/uart_drv.so",
+ [ "c_src/uart_drv.c",
+ "c_src/uart_buf.c",
+ "c_src/uart_queue.c",
+ "c_src/uart_com_state.c",
+ "c_src/uart_modem_state.c",
+ "c_src/uart_options.c",
+ "c_src/uart_message.c",
+ "c_src/uart_unix.c"
+ ]},
+ {"win32","priv/uart_drv.so",
+ ["c_src/uart_drv.c",
+ "c_src/uart_buf.c",
+ "c_src/uart_queue.c",
+ "c_src/uart_com_state.c",
+ "c_src/uart_modem_state.c",
+ "c_src/uart_options.c",
+ "c_src/uart_message.c",
+ "c_src/uart_win32.c"
+ ]}
+ ]}.
+
+{edoc_opts, [{doclet, edown_doclet}]}.
+%% {top_level_readme,
+%% {"./README_NOT.md",
+%% "http://github.com/tonyrog/uart"}}]}.
+
+{ct_extra_params, " -spec test/uart.spec"}.
+%% If you have hardware you can run the complete test
+%%{ct_extra_params, " -spec test/uart_hw.spec"}.
diff --git a/deps/uart/src/uart.app.src b/deps/uart/src/uart.app.src
new file mode 100644
index 0000000..26f07eb
--- /dev/null
+++ b/deps/uart/src/uart.app.src
@@ -0,0 +1,8 @@
+{application, uart,
+ [{description, "UART device controller"},
+ {vsn, git},
+ {modules, [uart]},
+ {registered, []},
+ {env, []},
+ {applications,[kernel,stdlib]}
+ ]}.
diff --git a/deps/uart/src/uart.erl b/deps/uart/src/uart.erl
new file mode 100644
index 0000000..d6dd8aa
--- /dev/null
+++ b/deps/uart/src/uart.erl
@@ -0,0 +1,958 @@
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2012, Tony Rogvall
+%%% @doc
+%%% Cross platform tty interface.
+%%% @end
+%%% Created : 29 Jan 2012 by Tony Rogvall <tony@rogvall.se>
+
+-module(uart).
+
+-export([open/2, close/1]).
+-export([send/2, send_char/2]).
+-export([recv/2, recv/3]).
+-export([async_recv/2, async_recv/3, async_send/2]).
+-export([unrecv/2]).
+-export([break/2, hangup/1, flow/2]).
+-export([get_modem/1, set_modem/2, clear_modem/2]).
+-export([options/0, validate_opts/1, validate_opt/2]).
+-export([setopt/3, setopts/2]).
+-export([getopt/2, getopts/2]).
+-export([controlling_process/2]).
+-export([i/0]).
+
+%% testing
+-export([encode_opt/2]).
+
+-define(UART_CMD_OPEN, 1).
+-define(UART_CMD_HANGUP, 2).
+-define(UART_CMD_CLOSE, 4).
+-define(UART_CMD_FLOW, 5).
+-define(UART_CMD_BREAK, 7).
+-define(UART_CMD_SETOPTS, 8).
+-define(UART_CMD_GETOPTS, 9).
+-define(UART_CMD_SENDCHAR, 10).
+-define(UART_CMD_SEND, 11).
+-define(UART_CMD_GET_MODEM, 12).
+-define(UART_CMD_SET_MODEM, 13).
+-define(UART_CMD_CLR_MODEM, 14).
+-define(UART_CMD_UNRECV, 15).
+-define(UART_CMD_RECV, 16).
+-define(UART_CMD_CONNECT, 17).
+
+%% Option bits are also used as bit numbers, so do not exceed 32.
+-define(UART_OPT_DEVICE, 1).
+-define(UART_OPT_IBAUD, 2).
+-define(UART_OPT_OBAUD, 3).
+-define(UART_OPT_CSIZE, 4).
+-define(UART_OPT_BUFSZ, 5).
+-define(UART_OPT_BUFTM, 6).
+-define(UART_OPT_STOPB, 7).
+-define(UART_OPT_PARITY, 8).
+-define(UART_OPT_IFLOW, 9).
+-define(UART_OPT_OFLOW, 10).
+-define(UART_OPT_XOFFCHAR, 11).
+-define(UART_OPT_XONCHAR, 12).
+-define(UART_OPT_EOLCHAR, 13).
+%% -define(UART_OPT_14, 14).
+-define(UART_OPT_ACTIVE, 15).
+-define(UART_OPT_DELAY_SEND, 16).
+-define(UART_OPT_DELIVER, 17).
+-define(UART_OPT_MODE, 18).
+%% -define(UART_OPT_19, 19).
+-define(UART_OPT_HEADER, 20).
+-define(UART_OPT_PACKET, 21).
+-define(UART_OPT_PSIZE, 22).
+-define(UART_OPT_HIGH, 23).
+-define(UART_OPT_LOW, 24).
+-define(UART_OPT_SENDTMO, 25). %% send timeout
+-define(UART_OPT_CLOSETMO, 26). %% send close timeout
+-define(UART_OPT_BUFFER, 27).
+-define(UART_OPT_DEBUG, 28).
+-define(UART_OPT_EXITF, 29).
+-define(UART_OPT_PTYPKT, 30).
+%% -define(UART_OPT_31, 31).
+
+-define(UART_PB_LITTLE_ENDIAN, 16#00008000). %% UART_PB_<n>
+-define(UART_PB_BYTES_MASK, 16#00000F00). %% UART_PB_<n> 0..8 allowed
+-define(UART_PB_FIXED_MASK, 16#FFFF0000). %% UART_PB_RAW
+-define(UART_PB_TYPE_MASK, 16#000000FF). %% UART_PB_x
+
+-define(UART_PB_RAW, 0).
+-define(UART_PB_N, 1).
+-define(UART_PB_LINE_LF, 2).
+-define(UART_PB_BASIC_0710, 3).
+-define(UART_PB_ADVANCED_0710, 4).
+-define(UART_PB_GSM_0710, 5).
+
+-define(UART_PASSIVE, 0).
+-define(UART_ACTIVE, 1).
+-define(UART_ONCE, 2).
+
+-define(UART_PARITY_NONE, 0).
+-define(UART_PARITY_ODD, 1).
+-define(UART_PARITY_EVEN, 2).
+-define(UART_PARITY_MARK, 3).
+-define(UART_PARITY_SPACE, 4).
+
+-define(UART_DELIVER_PORT, 0).
+-define(UART_DELIVER_TERM, 1).
+
+-define(UART_MODE_LIST, 0).
+-define(UART_MODE_BINARY, 1).
+
+-define(UART_OK, 0).
+-define(UART_ERROR, 1).
+-define(UART_OPTIONS, 2).
+
+-define(UART_DTR, 16#0002).
+-define(UART_RTS, 16#0004).
+-define(UART_CTS, 16#0008).
+-define(UART_CD, 16#0010).
+-define(UART_RI, 16#0020).
+-define(UART_DSR, 16#0040).
+-define(UART_SW, 16#8000).
+
+-define(bool(X), if (X) -> 1; true -> 0 end).
+
+-define(is_uint8(X), (((X) band (bnot 16#ff)) =:= 0)).
+-define(is_uint16(X), (((X) band (bnot 16#ffff)) =:= 0)).
+-define(is_uint32(X), (((X) band (bnot 16#ffffffff)) =:= 0)).
+
+-define(is_uart(P), is_port((P))).
+
+
+-type uart() :: port().
+
+-type uart_option() ::
+ device | baud | ibaud | obaud | csize | bufsz |
+ buftm | stopb | parity | iflow | oflow | xonchar |
+ xoffchar | eolchar | active | delay_send |
+ header | packet | packet_size | deliver | mode |
+ buffer | exit_on_close | debug.
+
+%% Todo:
+%% high_watermark | low_watermark | send_timeout | send_timeout_close |
+
+-type uart_input_pins() :: cts | cd | ri | dcr.
+-type uart_output_pins() :: dtr | rts.
+-type uart_modem_pins() :: uart_input_pins() | uart_output_pins().
+
+%%--------------------------------------------------------------------
+%% @doc
+%% This function is for documentations purpose.
+%%
+%% List of available options:
+%% <ul>
+%% <li> `{device, "//pty" | string()}' </li>
+%% <li> `{ibaud, baudrate()}' </li>
+%% <li> `{obaud, baudrate()}' </li>
+%% <li> `{baud, baudrate()}' </li>
+%% <li> `{csize, 5|6|7|8}' </li>
+%% <li> `{stopb, 1|2|3}' </li>
+%% <li> `{parity,none|odd|even|mark|space}' </li>
+%% <li> `{iflow, [sw|rts|dtr]}' </li>
+%% <li> `{oflow, [sw|cts|dsr|cd]}' </li>
+%% <li> `{xonchar, byte()}' </li>
+%% <li> `{xoffchar, byte()}' </li>
+%% <li> `{eolchar, byte()}' </li>
+%% <li> `{active, true | false | once}' </li>
+%% <li> `{delay_send, boolean()}' </li>
+%% <li> `{header, size()}' </li>
+%% <li> `{packet, packet_type()}' <br/>
+%% ` packet_type() ::= -8..-1|0|1..8,line,{size,0..65535}' </li>
+%% <li> `{packet_size, integer()}' </li>
+%% <li> `{deliver, port | term}' </li>
+%% <li> `{mode, list | binary}' </li>
+%% <li> `{buffer, integer()}' </li>
+%% <li> `{exit_on_close, boolean()}' </li>
+%% <li> `{bufsz, 0..255}' - Max low level uart buffer size </li>
+%% <li> `{buftm, 0..25500}' - Inter character timeout </li>
+%% <li> `{debug, log_level()} - Set debug level</li>
+%% <li> `{ptypkt, boolean()} - Set pty packet mode</li>
+%% </ul>
+%% @end
+%%--------------------------------------------------------------------
+-spec options() -> atom().
+
+options() ->
+ [
+ device,
+ baud,
+ ibaud,
+ obaud,
+ csize,
+ bufsz,
+ buftm,
+ stopb,
+ parity,
+ iflow,
+ oflow,
+ xonchar,
+ xoffchar,
+ eolchar,
+ active,
+ delay_send,
+ header,
+ packet,
+ packet_size,
+ deliver,
+ mode,
+ buffer,
+ exit_on_close,
+ debug,
+ ptypkt
+ ].
+
+%%--------------------------------------------------------------------
+%% @doc Opens a tty device.
+%%
+%% The device name `//pty' is reserved for opening a pseudo terminal
+%% using the openpty call. The slave device is accessed through the
+%% device option. See {@link options/0} for a description of
+%% available options.
+%% @end
+%%--------------------------------------------------------------------
+-spec open(DeviceName::iolist(), Options::[{uart_option(),term()}]) ->
+ {ok,uart()} | {error,term()}.
+
+open(DeviceName, Opts) ->
+ Path = code:priv_dir(uart),
+ {Type,_} = os:type(),
+ Driver = "uart_drv",
+ case load_driver(Path, Driver) of
+ ok ->
+ Command =
+ case proplists:get_bool(ftdi, Opts) of
+ true -> Driver ++ " ftdi";
+ false -> Driver ++ " " ++ atom_to_list(Type)
+ end,
+ Opts1 = proplists:delete(ftdi, Opts),
+ Uart = erlang:open_port({spawn_driver, Command}, [binary]),
+ Opts2 = [{ibaud,9600},{device,DeviceName} | Opts1],
+ case setopts(Uart, Opts2) of
+ ok ->
+ {ok,Uart};
+ Error ->
+ close(Uart),
+ Error
+ end;
+
+ Err={error,Error} ->
+ io:format("Error: ~s\n", [erl_ddll:format_error_int(Error)]),
+ Err
+ end.
+
+%%--------------------------------------------------------------------
+%% @doc List information about open uart ports
+%% @end
+%%--------------------------------------------------------------------
+
+i() ->
+ Us = lists:filter(
+ fun(P) ->
+ {_,Name} = erlang:port_info(P, name),
+ lists:prefix("uart_drv ",Name)
+ end, erlang:ports()),
+ lists:foreach(
+ fun(U) ->
+ try fmt_(U) of
+ Fmt ->
+ io:put_chars([Fmt,"\n"])
+ catch
+ _ -> ignore
+ end
+ end, Us).
+
+fmt_(U) ->
+ {ok,Opts} = getopts(U, [device,baud,csize,parity,stopb]),
+ {_,Connected} = erlang:port_info(U, connected),
+ io_lib:format("~s ~w ~w~s~w connected to ~p",
+ [proplists:get_value(device,Opts),
+ proplists:get_value(baud,Opts),
+ proplists:get_value(csize,Opts),
+ case proplists:get_value(parity,Opts) of
+ none -> "N";
+ even -> "E";
+ odd -> "O";
+ mark -> "M"
+ end, proplists:get_value(stopb,Opts),
+ Connected]).
+
+%%--------------------------------------------------------------------
+%% @doc Close a tty device.
+%% @end
+%%--------------------------------------------------------------------
+
+-spec close(Uart::uart()) -> true.
+
+close(Uart) when ?is_uart(Uart) ->
+ (catch erlang:port_close(Uart)),
+ true.
+
+%%--------------------------------------------------------------------
+%% @doc Get single option value.
+%% See {@link options/0} for available options.
+%% @end
+%%--------------------------------------------------------------------
+-spec getopt(Uart::uart(), Option::uart_option()) ->
+ {ok,term()} | {error,term()}.
+
+getopt(Uart, baud) ->
+ getopt(Uart, ibaud);
+getopt(Uart, Opt) ->
+ case command(Uart, ?UART_CMD_GETOPTS, <<(encode_opt(Opt))>>) of
+ {ok,[{_,Value}]} -> {ok,Value};
+ Error -> Error
+ end.
+
+%%--------------------------------------------------------------------
+%% @doc Get multiple option values.
+%% @end
+%%--------------------------------------------------------------------
+
+-spec getopts(Uart::uart(), Option::[uart_option()]) ->
+ {ok,[{uart_option(),term()}]} | {error,term()}.
+
+getopts(Uart, Opts) when ?is_uart(Uart), is_list(Opts) ->
+ Opts1 = translate_getopts(Opts),
+ Data = << <<(encode_opt(Opt))>> || Opt <- Opts1 >>,
+ case command(Uart, ?UART_CMD_GETOPTS, Data) of
+ {ok, Values} ->
+ {ok, translate_getopts_reply(Opts,Values)};
+ Error ->
+ Error
+ end.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Set single option.
+%%
+%% See {@link options/0} for available options.
+%% @end
+%%--------------------------------------------------------------------
+-spec setopt(Uart::uart(), Option::uart_option(), Value::term()) ->
+ ok | {error,term()}.
+
+setopt(Uart, Opt, Value) ->
+ setopts(Uart, [{Opt,Value}]).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Set multiple options.
+%% @end
+%%--------------------------------------------------------------------
+-spec setopts(Uart::uart(), Options::[{uart_option(),term()}]) ->
+ ok | {error,term()}.
+
+setopts(Uart, Opts) when ?is_uart(Uart), is_list(Opts) ->
+ Opts1 = translate_set_opts(Opts),
+ Data = << <<(encode_opt(Opt,Value))/binary>> || {Opt,Value} <- Opts1 >>,
+ command(Uart, ?UART_CMD_SETOPTS, Data).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Send break for Duration number of milliseconds .
+%% @end
+%%--------------------------------------------------------------------
+-spec break(Uart::uart(), Duration::non_neg_integer()) ->
+ ok | {error,term()}.
+break(Uart,Duration) when ?is_uart(Uart),
+ is_integer(Duration), Duration > 0 ->
+ command(Uart, ?UART_CMD_BREAK, <<Duration:32>>).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Hangup
+%% @end
+%%--------------------------------------------------------------------
+-spec hangup(Uart::uart()) ->
+ ok | {error,term()}.
+hangup(Uart) when ?is_uart(Uart) ->
+ command(Uart, ?UART_CMD_HANGUP, []).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Manage input and output flow control
+%% @end
+%%--------------------------------------------------------------------
+-spec flow(Uart::uart(), Mode::(input_off|input_on|output_off|output_on)) ->
+ ok | {error,term()}.
+
+flow(Uart, input_off) when ?is_uart(Uart) ->
+ command(Uart, ?UART_CMD_FLOW, [0]);
+flow(Uart, input_on) when ?is_uart(Uart) ->
+ command(Uart, ?UART_CMD_FLOW, [1]);
+flow(Uart, output_off) when ?is_uart(Uart) ->
+ command(Uart, ?UART_CMD_FLOW, [2]);
+flow(Uart, output_on) when ?is_uart(Uart) ->
+ command(Uart, ?UART_CMD_FLOW, [3]).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Get modem pins status.
+%% @end
+%%--------------------------------------------------------------------
+-spec get_modem(Uart::uart()) ->
+ {ok, [uart_modem_pins()]} |
+ {error, term()}.
+
+get_modem(Uart) ->
+ command(Uart, ?UART_CMD_GET_MODEM, []).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Set modem pins.
+%% @end
+%%--------------------------------------------------------------------
+
+-spec set_modem(Uart::uart(), Flags::[uart_modem_pins()]) ->
+ ok | {error, term()}.
+
+set_modem(Uart, Fs) when is_list(Fs) ->
+ Flags = encode_flags(Fs),
+ command(Uart, ?UART_CMD_SET_MODEM, <<Flags:32>>).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Clear modem pins.
+%% @end
+%%--------------------------------------------------------------------
+
+-spec clear_modem(Uart::uart(), Flags::[uart_modem_pins()]) ->
+ ok | {error, term()}.
+clear_modem(Uart, Fs) when ?is_uart(Uart), is_list(Fs) ->
+ Flags = encode_flags(Fs),
+ command(Uart, ?UART_CMD_CLR_MODEM, <<Flags:32>>).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Send characters
+%% @end
+%%--------------------------------------------------------------------
+
+-spec send(Uart::uart(), Data::iolist()) ->
+ ok | {error, term()}.
+
+send(Port, [C]) when is_port(Port), ?is_uint8(C) ->
+ command(Port, ?UART_CMD_SENDCHAR, [C]);
+send(Port, <<C>>) when is_port(Port) ->
+ command(Port, ?UART_CMD_SENDCHAR, [C]);
+send(Port, Data) when is_port(Port),is_list(Data) ->
+ command(Port, ?UART_CMD_SEND, Data);
+send(Port, Data) when is_port(Port), is_binary(Data) ->
+ command(Port, ?UART_CMD_SEND, Data).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Send a single character
+%% @end
+%%--------------------------------------------------------------------
+-spec send_char(Uart::uart(), C::byte()) ->
+ ok | {error, term()}.
+
+send_char(Port, C) when ?is_uart(Port), ?is_uint8(C) ->
+ command(Port, ?UART_CMD_SENDCHAR, [C]).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Send asynchronous data.
+%% @end
+%%--------------------------------------------------------------------
+-spec async_send(Uart::uart(), Data::iolist()) -> ok.
+
+async_send(Port, Data) ->
+ true = erlang:port_command(Port, Data),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Push back data onto the receive buffer.
+%% @end
+%%--------------------------------------------------------------------
+-spec unrecv(Uart::uart(), Data::iolist()) ->
+ ok | {error,term()}.
+
+unrecv(Port, Data) when is_list(Data); is_binary(Data) ->
+ command(Port, ?UART_CMD_UNRECV, Data).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Receive data from a device in passive mode, no timeout.
+%% @end
+%%--------------------------------------------------------------------
+-spec recv(Uart::uart(), Length::non_neg_integer()) ->
+ {ok,iolist()} | {error, term()}.
+
+recv(Port, Length) ->
+ recv_(Port, Length, -1).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Receive data from a device in passive mode.
+%% @end
+%%--------------------------------------------------------------------
+-spec recv(Uart::uart(), Length::non_neg_integer(), Timeout::timeout()) ->
+ {ok,iolist()} | {error, term()}.
+
+recv(Uart, Length, infinity) ->
+ recv_(Uart, Length, -1);
+
+recv(Uart, Length, Timeout) when is_integer(Timeout) ->
+ recv_(Uart, Length, Timeout).
+
+recv_(Uart, Length, Timeout) when
+ ?is_uart(Uart),
+ is_integer(Length), Length >= 0 ->
+ case async_recv(Uart, Length, Timeout) of
+ {ok, Ref} ->
+ receive
+ {Ref, Result} ->
+ Result;
+ {uart_async, Uart, Ref, Data} when is_list(Data) ->
+ {ok,Data};
+ {uart_async, Uart, Ref, Data} when is_binary(Data) ->
+ {ok,Data};
+ {uart_async, Uart, Ref, Other} ->
+ Other;
+ {'EXIT', Uart, _Reason} ->
+ {error, closed}
+ end;
+ Error ->
+ Error
+ end.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Initiate an async receive operation.
+%% @end
+%%--------------------------------------------------------------------
+
+-spec async_recv(Uart::uart(), Length::non_neg_integer()) ->
+ {ok,integer()} | {error,term()}.
+
+async_recv(Uart, Length) ->
+ async_recv(Uart, Length, -1).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Initiate an async receive operation.
+%% To initiate an async operation reading a certain length and with
+%% a timeout the async_recv can be useful.
+%% ```{ok,Ref} = uart:async_recv(Uart, 16, 1000),
+%% receive
+%% {Ref,{error,Reason}} -> {error,Reason}
+%% {uart_async,Uart,Ref,{ok,Data}} -> {ok,Data};
+%% {uart_async,Uart,Ref,{error,Reason}} -> {error,Reason};
+%% {'EXIT',Uart,_Reason} -> {error,closed}
+%% ...
+%% end'''
+%% The above can also be achived by using active once and
+%% a fixed packet mode.
+%% ```uart:setopts(Uart, [{packet,{size,16}},{active,once}]),
+%% receive
+%% {uart,Uart,Data} -> {ok,Data};
+%% {uart_error,Uart,enxio} -> {error,usb_device_pulled_out};
+%% {uart_error,Uart,Err} -> {error,Err};
+%% {uart_closed,Uart} -> {error,close}
+%% after Timeout ->
+%% {error,timeout}
+%% end'''
+%% Packet size are however limited (to 16 bits), so any size
+%% above 64K must be handled with async_recv or split into
+%% chunks.
+%% @end
+%%--------------------------------------------------------------------
+
+-spec async_recv(Uart::uart(),
+ Length::non_neg_integer(),
+ Timeout::timeout() | -1) ->
+ {ok,integer()} | {error,term()}.
+
+async_recv(Uart, Length, Time) ->
+ command_(Uart, ?UART_CMD_RECV, [<<Time:32,Length:32>>]).
+
+%%
+%% set controlling process of uart to the caller
+%%
+-spec controlling_process(Uart::uart(), NewOwner::pid()) ->
+ ok | {error,term()}.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Connect uart to a new controlling process, that is the process that
+%% is the event data receiver. The caller must be the current owner of
+%% the uart.
+%% @end
+%%--------------------------------------------------------------------
+
+%% Set controlling process for uart.
+controlling_process(U, NewOwner) when ?is_uart(U), is_pid(NewOwner) ->
+ case erlang:port_info(U, connected) of
+ {connected, NewOwner} ->
+ ok;
+ {connected, Pid} when Pid =/= self() ->
+ {error, not_owner};
+ undefined ->
+ {error, einval};
+ _ ->
+ case getopt(U, active) of
+ {ok, A0} ->
+ setopt(U, active, false),
+ case sync_input(U, NewOwner, false) of
+ true -> %% uart already closed,
+ ok;
+ false ->
+ try erlang:port_connect(U, NewOwner) of
+ true ->
+ unlink(U), %% unlink from port
+ %% make thread reconnect to port
+ ok = command(U, ?UART_CMD_CONNECT, []),
+ setopt(U, active, A0),
+ ok
+ catch
+ error:Reason ->
+ {error, Reason}
+ end
+ end;
+ Error ->
+ Error
+ end
+ end.
+
+sync_input(U, Owner, Flag) ->
+ receive
+ {uart, U, Data} ->
+ Owner ! {uart, U, Data},
+ sync_input(U, Owner, Flag);
+ {uart_closed, U} ->
+ Owner ! {uart_closed, U},
+ sync_input(U, Owner, true);
+ {U, {data, Data}} ->
+ Owner ! {U, {data, Data}},
+ sync_input(U, Owner, Flag);
+ {uart_async, U, Ref, Data} ->
+ Owner ! {uart_async, U, Ref, Data},
+ sync_input(U, Owner, Flag)
+ after 0 ->
+ Flag
+ end.
+%%--------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------
+%% @private
+command(Uart, Cmd, Args) ->
+ case command_(Uart,Cmd,Args) of
+ {ok,Ref} ->
+ receive
+ {Ref, Result} ->
+ Result
+ end;
+ Error -> Error
+ end.
+
+command_(Uart, Cmd, Args) ->
+ case erlang:port_control(Uart, Cmd, Args) of
+ <<?UART_OK,Ref:32>> ->
+ {ok, Ref};
+ <<?UART_ERROR>> ->
+ {error, unknown};
+ <<?UART_ERROR,Reason/binary>> ->
+ {error, binary_to_atom(Reason,latin1)}
+ end.
+
+%% @private
+validate_opts([{K,V}|Kvs]) ->
+ case validate_opt(K,V) of
+ true -> validate_opts(Kvs);
+ false -> {error,{type_error,K,V}};
+ undefined -> {error,{unknown_opt,K}};
+ Error -> Error
+ end;
+validate_opts([]) ->
+ ok.
+
+%% @private
+validate_opt(device,Arg) -> is_string(Arg);
+validate_opt(ibaud,Arg) -> is_baudrate(Arg);
+validate_opt(obaud,Arg) -> is_baudrate(Arg);
+validate_opt(baud,Arg) -> is_baudrate(Arg);
+validate_opt(csize,Arg) -> lists:member(Arg,[5,6,7,8]);
+validate_opt(stopb,Arg) -> lists:member(Arg,[1,2,3]);
+validate_opt(parity,Arg) -> lists:member(Arg,[none,odd,even,mark,space]);
+validate_opt(iflow,Arg) -> (Arg -- [sw,rts,dtr]) =:= [];
+validate_opt(oflow,Arg) -> (Arg -- [sw,cts,dsr,cd]) =:= [];
+validate_opt(xonchar, Arg) -> is_uint8(Arg);
+validate_opt(xoffchar, Arg) -> is_uint8(Arg);
+validate_opt(eolchar, Arg) -> is_uint8(Arg);
+validate_opt(active, Arg) -> lists:member(Arg,[true,false,once]);
+validate_opt(delay_send,Arg) -> is_boolean(Arg);
+validate_opt(header, Arg) -> is_integer(Arg) andalso (Arg >= 0);
+validate_opt(packet, {size,Sz}) -> is_uint16(Sz);
+validate_opt(packet, line) -> true;
+validate_opt(packet, basic_0710) -> true;
+validate_opt(packet, advanced_0710) -> true;
+validate_opt(packet, gsm_0710) -> true;
+validate_opt(packet, Arg) -> lists:member(Arg,lists:seq(-8,8));
+validate_opt(packet_size, Arg) -> is_uint32(Arg);
+validate_opt(deliver, Arg) -> lists:member(Arg,[port,term]);
+validate_opt(mode,Arg) -> lists:member(Arg,[list,binary]);
+validate_opt(buffer,Arg) -> is_uint32(Arg);
+validate_opt(exit_on_close, Arg) -> is_boolean(Arg);
+validate_opt(ptypkt, Arg) -> is_boolean(Arg);
+validate_opt(bufsz, Arg) -> (Arg >= 0 andalso Arg =< 255);
+validate_opt(buftm, Arg) -> (Arg >= 0 andalso Arg =< 25500);
+validate_opt(debug, Arg) ->
+ lists:member(Arg,[debug,info,notice,warning,
+ error,critical,alert,emergency,none]);
+validate_opt(_,_Arg) -> undefined.
+
+is_baudrate(Rate) ->
+ %% only check some limit reset is implementation specific
+ is_integer(Rate) andalso (Rate > 0).
+
+is_string([C|Cs]) when ?is_uint8(C) ->
+ is_string(Cs);
+is_string([]) -> true;
+is_string(_) -> false.
+
+is_uint8(C) -> ?is_uint8(C).
+is_uint16(C) -> ?is_uint16(C).
+is_uint32(C) -> ?is_uint32(C).
+
+%% @private
+translate_set_opts([{baud,B}|Opts]) ->
+ [{ibaud,B},{obaud,B}|translate_set_opts(Opts)];
+translate_set_opts([Opt={device,Name}|Opts]) ->
+ case os:type() of
+ {win32,_} ->
+ %% Translate device name on windows to allow for device above COM9
+ %% COM10 must be written like \\.\COM10 => "\\\\.\\COM10
+ %% We apply this scheme for all com ports.
+ case Name of
+ "\\\\.\\"++_Name -> %% already fixed
+ [Opt|translate_set_opts(Opts)];
+ _ ->
+ [{device,"\\\\.\\"++Name}|
+ translate_set_opts(Opts)]
+ end;
+ _ ->
+ [Opt|translate_set_opts(Opts)]
+ end;
+translate_set_opts([Opt|Opts]) ->
+ [Opt|translate_set_opts(Opts)];
+translate_set_opts([]) ->
+ [].
+
+translate_getopts([baud|Opts]) ->
+ [ibaud|translate_getopts(Opts)];
+translate_getopts([Opt|Opts]) ->
+ [Opt|translate_getopts(Opts)];
+translate_getopts([]) ->
+ [].
+
+translate_getopts_reply([baud|Opts],[{ibaud,B}|Vs]) ->
+ [{baud,B}|translate_getopts_reply(Opts,Vs)];
+translate_getopts_reply([device|Opts],[V={device,Name}|Vs]) ->
+ case os:type() of
+ {win32,_} ->
+ case Name of
+ "\\\\.\\"++Name1 ->
+ [{device,Name1}|translate_getopts_reply(Opts,Vs)];
+ _ ->
+ [V|translate_getopts_reply(Opts,Vs)]
+ end;
+ _ ->
+ [V|translate_getopts_reply(Opts,Vs)]
+ end;
+translate_getopts_reply([_Opt|Opts],[V|Vs]) ->
+ [V|translate_getopts_reply(Opts,Vs)];
+translate_getopts_reply([],[]) ->
+ [].
+
+%% @private
+-spec encode_opt(Option::atom(),Value::term()) ->
+ binary().
+
+encode_opt(packet,0) ->
+ <<?UART_OPT_PACKET, ?UART_PB_RAW:32>>;
+encode_opt(packet,PB) when PB>0, PB=< 8 ->
+ <<?UART_OPT_PACKET, (?UART_PB_N bor (PB bsl 8)):32>>;
+encode_opt(packet,PB) when PB<0, PB >= -8 ->
+ <<?UART_OPT_PACKET, (?UART_PB_N bor ?UART_PB_LITTLE_ENDIAN bor
+ ((-PB) bsl 8)):32>>;
+encode_opt(packet,{size,N}) when is_integer(N), N > 0, N =< 16#ffff ->
+ <<?UART_OPT_PACKET, ((N bsl 16) + ?UART_PB_RAW):32>>;
+encode_opt(packet,raw) ->
+ <<?UART_OPT_PACKET, ?UART_PB_RAW:32>>;
+encode_opt(packet,line) ->
+ <<?UART_OPT_PACKET, ?UART_PB_LINE_LF:32>>;
+encode_opt(packet,basic_0710) ->
+ <<?UART_OPT_PACKET, ?UART_PB_BASIC_0710:32>>;
+encode_opt(packet,advanced_0710) ->
+ <<?UART_OPT_PACKET, ?UART_PB_ADVANCED_0710:32>>;
+encode_opt(packet,gsm_0710) ->
+ <<?UART_OPT_PACKET, ?UART_PB_GSM_0710:32>>;
+
+encode_opt(device,Name) when is_list(Name); is_binary(Name) ->
+ Bin = iolist_to_binary(Name),
+ Len = byte_size(Bin),
+ <<?UART_OPT_DEVICE,Len,Bin/binary>>;
+encode_opt(ibaud,X) when X >= 0, X =< 16#ffffffff ->
+ <<?UART_OPT_IBAUD,X:32>>;
+encode_opt(obaud,X) when X >= 0, X =< 16#ffffffff ->
+ <<?UART_OPT_OBAUD,X:32>>;
+encode_opt(csize,X) when X >= 5, X =< 8 ->
+ <<?UART_OPT_CSIZE,X:32>>;
+encode_opt(bufsz,X) when X >= 0, X =< 16#ffffffff ->
+ <<?UART_OPT_BUFSZ,X:32>>;
+encode_opt(buftm,X) when X >= 0,X =< 16#ffffffff ->
+ <<?UART_OPT_BUFTM,X:32>>;
+encode_opt(stopb,Value) when Value >= 1, Value =< 3 ->
+ <<?UART_OPT_STOPB,Value:32>>;
+encode_opt(parity,none) ->
+ <<?UART_OPT_PARITY,?UART_PARITY_NONE:32>>;
+encode_opt(parity,odd) ->
+ <<?UART_OPT_PARITY,?UART_PARITY_ODD:32>>;
+encode_opt(parity,even) ->
+ <<?UART_OPT_PARITY,?UART_PARITY_EVEN:32>>;
+encode_opt(parity,mark) ->
+ <<?UART_OPT_PARITY,?UART_PARITY_MARK:32>>;
+encode_opt(parity,space) ->
+ <<?UART_OPT_PARITY,?UART_PARITY_SPACE:32>>;
+encode_opt(parity,P) when P >= 0, P=< 4 ->
+ <<?UART_OPT_PARITY,P:32>>;
+encode_opt(oflow,Value) when is_list(Value) ->
+ <<?UART_OPT_OFLOW,(encode_flags(Value)):32>>;
+encode_opt(iflow,Value) when is_list(Value) ->
+ <<?UART_OPT_IFLOW,(encode_flags(Value)):32>>;
+encode_opt(xonchar,Value) when Value >= 0, Value =< 255 ->
+ <<?UART_OPT_XONCHAR,Value:32>>;
+encode_opt(xoffchar,Value) when Value >= 0, Value =< 255 ->
+ <<?UART_OPT_XOFFCHAR,Value:32>>;
+encode_opt(eolchar,Value) when Value >= 0, Value =< 255 ->
+ <<?UART_OPT_EOLCHAR,Value:32>>;
+encode_opt(active,true) -> <<?UART_OPT_ACTIVE,?UART_ACTIVE:32>>;
+encode_opt(active,false) -> <<?UART_OPT_ACTIVE,?UART_PASSIVE:32>>;
+encode_opt(active,once) -> <<?UART_OPT_ACTIVE,?UART_ONCE:32>>;
+
+encode_opt(delay_send,X) when is_boolean(X) ->
+ <<?UART_OPT_DELAY_SEND,?bool(X):32>>;
+encode_opt(header,X) when is_integer(X), X >= 0 ->
+ <<?UART_OPT_HEADER, X:32>>;
+encode_opt(packet_size,X) when is_integer(X), X >= 0, X =< 16#ffffffff ->
+ <<?UART_OPT_PSIZE, X:32>>;
+encode_opt(deliver,port) ->
+ <<?UART_OPT_DELIVER, ?UART_DELIVER_PORT:32>>;
+encode_opt(deliver,term) ->
+ <<?UART_OPT_DELIVER, ?UART_DELIVER_TERM:32>>;
+encode_opt(mode,list) ->
+ <<?UART_OPT_MODE, ?UART_MODE_LIST:32>>;
+encode_opt(mode,binary) ->
+ <<?UART_OPT_MODE, ?UART_MODE_BINARY:32>>;
+
+encode_opt(packet,0) ->
+ <<?UART_OPT_PACKET, ?UART_PB_RAW:32>>;
+encode_opt(packet,PB) when is_integer(PB), PB>0, PB=< 8 ->
+ <<?UART_OPT_PACKET, (?UART_PB_N bor (PB bsl 8)):32>>;
+encode_opt(packet,PB) when is_integer(PB), PB<0, PB >= -8 ->
+ <<?UART_OPT_PACKET, (?UART_PB_N bor ?UART_PB_LITTLE_ENDIAN bor
+ ((-PB) bsl 8)):32>>;
+encode_opt(packet,{size,N}) when is_integer(N), N > 0, N =< 16#ffff ->
+ <<?UART_OPT_PACKET, ((N bsl 16) + ?UART_PB_RAW):32>>;
+encode_opt(packet,raw) ->
+ <<?UART_OPT_PACKET, ?UART_PB_RAW:32>>;
+encode_opt(packet,line) ->
+ <<?UART_OPT_PACKET, ?UART_PB_LINE_LF:32>>;
+encode_opt(packet,basic_0710) ->
+ <<?UART_OPT_PACKET, ?UART_PB_BASIC_0710:32>>;
+encode_opt(packet,advanced_0710) ->
+ <<?UART_OPT_PACKET, ?UART_PB_ADVANCED_0710:32>>;
+encode_opt(packet,gsm_0710) ->
+ <<?UART_OPT_PACKET, ?UART_PB_GSM_0710:32>>;
+encode_opt(high_watermark,X) when is_integer(X), X >= 0, X =< 16#ffffffff ->
+ <<?UART_OPT_HIGH, X:32>>;
+encode_opt(low_watermark,X) when is_integer(X), X >= 0, X =< 16#ffffffff ->
+ <<?UART_OPT_LOW, X:32>>;
+encode_opt(send_timeout,X) when is_integer(X),X >= -1, X =< 16#7fffffff ->
+ <<?UART_OPT_SENDTMO, X:32>>;
+encode_opt(send_timeout_close,X) when is_integer(X),X >= -1, X =< 16#7fffffff ->
+ <<?UART_OPT_CLOSETMO, X:32>>;
+encode_opt(buffer, X) when is_integer(X), X >= 0, X =< 16#ffffffff ->
+ <<?UART_OPT_BUFFER, X:32>>;
+encode_opt(exit_on_close, X) when is_boolean(X) ->
+ <<?UART_OPT_EXITF,?bool(X):32>>;
+encode_opt(ptypkt, X) when is_boolean(X) ->
+ <<?UART_OPT_PTYPKT,?bool(X):32>>;
+encode_opt(debug, X) ->
+ case X of
+ debug -> <<?UART_OPT_DEBUG, 7:32>>;
+ info -> <<?UART_OPT_DEBUG, 6:32>>;
+ notice -> <<?UART_OPT_DEBUG, 5:32>>;
+ warning -> <<?UART_OPT_DEBUG, 4:32>>;
+ error -> <<?UART_OPT_DEBUG, 3:32>>;
+ critical -> <<?UART_OPT_DEBUG, 2:32>>;
+ alert -> <<?UART_OPT_DEBUG, 1:32>>;
+ emergency -> <<?UART_OPT_DEBUG, 0:32>>;
+ none -> <<?UART_OPT_DEBUG, -1:32>>
+ end.
+
+
+encode_opt(device) -> ?UART_OPT_DEVICE;
+encode_opt(ibaud) -> ?UART_OPT_IBAUD;
+encode_opt(obaud) -> ?UART_OPT_OBAUD;
+encode_opt(csize) -> ?UART_OPT_CSIZE;
+encode_opt(bufsz) -> ?UART_OPT_BUFSZ;
+encode_opt(buftm) -> ?UART_OPT_BUFTM;
+encode_opt(stopb) -> ?UART_OPT_STOPB;
+encode_opt(parity) -> ?UART_OPT_PARITY;
+encode_opt(iflow) -> ?UART_OPT_IFLOW;
+encode_opt(oflow) -> ?UART_OPT_OFLOW;
+encode_opt(xonchar) -> ?UART_OPT_XONCHAR;
+encode_opt(xoffchar) -> ?UART_OPT_XOFFCHAR;
+encode_opt(eolchar) -> ?UART_OPT_EOLCHAR;
+encode_opt(active) -> ?UART_OPT_ACTIVE;
+encode_opt(delay_send) -> ?UART_OPT_DELAY_SEND;
+encode_opt(header) -> ?UART_OPT_HEADER;
+encode_opt(packet) -> ?UART_OPT_PACKET;
+encode_opt(packet_size) -> ?UART_OPT_PSIZE;
+encode_opt(deliver) -> ?UART_OPT_DELIVER;
+encode_opt(mode) -> ?UART_OPT_MODE;
+encode_opt(high_watermark) -> ?UART_OPT_HIGH;
+encode_opt(low_watermark) -> ?UART_OPT_LOW;
+encode_opt(send_timeout) -> ?UART_OPT_SENDTMO;
+encode_opt(send_timeout_close) -> ?UART_OPT_CLOSETMO;
+encode_opt(buffer) -> ?UART_OPT_BUFFER;
+encode_opt(exit_on_close) -> ?UART_OPT_EXITF;
+encode_opt(debug) -> ?UART_OPT_DEBUG;
+encode_opt(ptypkt) -> ?UART_OPT_PTYPKT.
+
+encode_flags([F|Fs]) ->
+ encode_flag(F) + encode_flags(Fs);
+encode_flags([]) ->
+ 0.
+
+encode_flag(dtr) -> ?UART_DTR;
+encode_flag(rts) -> ?UART_RTS;
+encode_flag(cts) -> ?UART_CTS;
+encode_flag(cd) -> ?UART_CD;
+encode_flag(ri) -> ?UART_RI;
+encode_flag(dsr) -> ?UART_DSR;
+encode_flag(sw) -> ?UART_SW.
+
+%% can be replaced with dloader later
+load_driver(Path, Name) ->
+ Ext = filename:extension(Name),
+ Base = filename:basename(Name,Ext),
+ NameExt = case os:type() of
+ {unix,_} -> Base++".so";
+ {win32,_} -> Base++".dll"
+ end,
+ SysPath = filename:join(Path,erlang:system_info(system_architecture)),
+ case filelib:is_regular(filename:join(SysPath,NameExt)) of
+ true -> erl_ddll:load(SysPath, Name);
+ false ->
+ case filelib:is_regular(filename:join(Path,NameExt)) of
+ true -> erl_ddll:load(Path, Name);
+ false -> {error, enoent}
+ end
+ end.
diff --git a/deps/uart/src/uart_devices.erl b/deps/uart/src/uart_devices.erl
new file mode 100644
index 0000000..ddb2881
--- /dev/null
+++ b/deps/uart/src/uart_devices.erl
@@ -0,0 +1,575 @@
+%%%-------------------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2013, Tony Rogvall
+%%% @doc
+%%% Server that keep track on uart/tty devices in the system
+%%% @end
+%%% Created : 20 Feb 2013 by Tony Rogvall <tony@rogvall.se>
+%%%-------------------------------------------------------------------
+-module(uart_devices).
+
+-behaviour(gen_server).
+
+-compile(export_all).
+
+%% API
+-export([start_link/0]).
+-export([start/0, stop/0]).
+
+-import(lists, [map/2]).
+
+-include_lib("kernel/include/file.hrl").
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-define(SERVER, ?MODULE).
+-define(debug(F,A), ok). % io:format((F),(A))
+
+-record(uart_device,
+ {
+ name :: string(), %% device name
+ path :: string(), %% device dir path
+ id :: string(), %% internal node name (info)
+ owner :: pid(), %% owner pid
+ mon :: reference(), %% process monitor
+ avail %% true it device is (known to be) available
+ }).
+
+-record(state, {
+ dref :: undefined | reference(),
+ devices = [] :: [#uart_device{}],
+ sub = [] :: [{pid(),reference()}]
+ }).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+alloc(Name) ->
+ gen_server:call(?SERVER, {alloc,self(),Name}).
+
+release(Name) ->
+ gen_server:call(?SERVER, {release,self(),Name}).
+
+get_list() ->
+ gen_server:call(?SERVER, get_list).
+
+get_info(Name,Keys) ->
+ gen_server:call(?SERVER, {get_info,Name,Keys}).
+
+get_fullname(Name) ->
+ gen_server:call(?SERVER, {get_fullname,Name}).
+
+subscribe() ->
+ gen_server:call(?SERVER, {subscribe,self()}).
+
+unsubscribe(Ref) ->
+ gen_server:call(?SERVER, {unsubscribe,self(),Ref}).
+
+stop() ->
+ gen_server:call(?SERVER, stop).
+
+i() ->
+ case get_list() of
+ {ok,Names} ->
+ lists:foreach(
+ fun(Name) ->
+ case get_info(Name,[id,owner]) of
+ [{id,ID},{owner,Owner}] ->
+ I = if ID =:= Name -> "";
+ true -> ID
+ end,
+ O = if is_pid(Owner) ->
+ pid_to_list(Owner);
+ true ->
+ "none"
+ end,
+ io:format("~-25s ~-15s ~-35s\n",
+ [Name, O, I]);
+ [] ->
+ ok
+ end
+ end, Names);
+ Error ->
+ Error
+ end.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server
+%%
+%% @spec start_link() -> {ok, Pid} | ignore | {error, Error}
+%% @end
+%%--------------------------------------------------------------------
+start_link() ->
+ start_link([]).
+
+start_link(Subs) when is_list(Subs) ->
+ gen_server:start_link({local, ?SERVER}, ?MODULE, [Subs], []).
+
+start() ->
+ start([]).
+start(Subs) when is_list(Subs) ->
+ gen_server:start({local, ?SERVER}, ?MODULE, [Subs], []).
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Initializes the server
+%%
+%% @spec init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% @end
+%%--------------------------------------------------------------------
+init([Subs]) ->
+ DRef = watch_uart_devices(),
+ Ds = list_uart_devices(),
+ State0 = #state{ devices =[], dref=DRef },
+ State1 = update_all(Ds, State0),
+ State2 = subscribe_all_(Subs, State1),
+ {ok, State2}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling call messages
+%%
+%% @spec handle_call(Request, From, State) ->
+%% {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_call(get_list, _From, State) ->
+ {reply, {ok, [D#uart_device.name || D <- State#state.devices]}, State};
+handle_call({get_info,Name,Keys}, _From, State) ->
+ case lists:keyfind(Name, #uart_device.name,State#state.devices) of
+ false -> {reply, [], State};
+ D ->
+ L =
+ lists:foldr(
+ fun(id,Acc) -> [{id,D#uart_device.id}|Acc];
+ (path,Acc) -> [{path,D#uart_device.path}|Acc];
+ (owner,Acc) -> [{owner,D#uart_device.owner}|Acc];
+ (_,Acc) -> Acc
+ end, [], Keys),
+ {reply, L, State}
+ end;
+handle_call({get_fullname,Name}, _From, State) ->
+ case lists:keyfind(Name,#uart_device.name,State#state.devices) of
+ false ->
+ {reply, {error,enoent}, State};
+ _D=#uart_device {path = P, name = N} ->
+ Path = filename:join(P,N),
+ {reply, {ok,Path}, State}
+ end;
+handle_call({subscribe,Pid}, _From, State) ->
+ {Ref,State1} = subscribe_(Pid, State),
+ {reply,{ok,Ref},State1};
+handle_call({unsubscribe,_Pid,Ref}, _From, State) ->
+ {reply,ok,unsubscribe_(Ref,State)};
+handle_call({alloc,Pid,Name}, _From, State) ->
+ case lists:keytake(Name,#uart_device.name,State#state.devices) of
+ false ->
+ {reply, {error,enoent}, State};
+ {value,D,Ds} ->
+ if not D#uart_device.avail ->
+ {reply, {error,ebusy}, State};
+ true ->
+ Mon = erlang:monitor(process, Pid),
+ D1 = D#uart_device { avail = false,
+ owner = Pid,
+ mon = Mon },
+ ?debug("ALLOC: ~p\n", [D1]),
+ Ds1 = [D1 | Ds],
+ Path = filename:join(D#uart_device.path,D#uart_device.name),
+ {reply, {ok,Path}, State#state { devices = Ds1 }}
+ end
+ end;
+handle_call({release,Pid,Name}, _From, State) ->
+ case lists:keytake(Name,#uart_device.name,State#state.devices) of
+ false ->
+ {reply, {error,enoent}, State};
+ {value,D,Ds} ->
+ if D#uart_device.avail =:= true,
+ D#uart_device.owner =:= Pid ->
+ ?debug("RELEASE: ~p\n", [D]),
+ erlang:demonitor(D#uart_device.mon, [flush]),
+ D1 = D#uart_device { avail = true,
+ owner = undefined,
+ mon = undefined },
+ Ds1 = [D1 | Ds],
+ {reply, ok, State#state { devices = Ds1 }};
+ true ->
+ {reply, {error,eperm}, State}
+ end
+ end;
+handle_call(stop, _From, State) ->
+ {stop, normal, ok, State};
+handle_call(_Request, _From, State) ->
+ ?debug("Unknown call: ~p\n", [_Request]),
+ Reply = {error,bad_call},
+ {reply, Reply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling cast messages
+%%
+%% @spec handle_cast(Msg, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_cast(scan_serial, State) ->
+ Ds = list_uart_devices(),
+ State1 = update_all(Ds, State),
+ {noreply, State1};
+handle_cast({update_id,Name}, State) ->
+ IDs = list_serial_devices(),
+ ?debug("Lookup ~s in ~p\n", [Name, IDs]),
+ case lists:keyfind(Name, 1, IDs) of
+ false -> false; %% not a serial device
+ {_,ID} ->
+ case lists:keytake(Name,#uart_device.name,State#state.devices) of
+ false ->
+ {noreply, State};
+ {value,D,Ds} ->
+ D1 = D#uart_device { id = ID },
+ {noreply, State#state{ devices=[D1|Ds]}}
+ end
+ end;
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling all non call/cast messages
+%%
+%% @spec handle_info(Info, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_info(_F={fevent,Ref,[create],Path,Name}, State)
+ when Ref =:= State#state.dref ->
+ ?debug("fevent: ~p\n", [_F]),
+ case is_uart_device(Path,Name) of
+ true ->
+ Ds0 = State#state.devices,
+ case lists:keyfind(Name,#uart_device.name, Ds0) of
+ false ->
+ case lookup_device_id(Name) of
+ false ->
+ {noreply,State};
+ ID ->
+ D = #uart_device { name = Name, path = Path,
+ id = ID, avail=true },
+ State1 = add(D, State),
+ {noreply, State1}
+ end;
+ _D ->
+ {noreply, State}
+ end;
+ false ->
+ case os:type() of
+ {unix,linux} when Name =:= "serial" ->
+ %% postpone scanning for /dev/serial/by-ids
+ %% until the directory is create
+ cast_when_created("/dev/serial", "by-id",
+ scan_serial, 5000),
+ {noreply, State};
+ _ ->
+ {noreply, State}
+ end
+ end;
+handle_info(_F={fevent,Ref,[delete],_Path,Name}, State)
+ when Ref =:= State#state.dref ->
+ ?debug("fevent: ~p\n", [_F]),
+ State1 = removed_by_name(Name, State),
+ {noreply, State1};
+
+handle_info({'DOWN',Ref,process,Pid,_Reason}, State) ->
+ case lists:keytake(Ref, #uart_device.mon, State#state.devices) of
+ false ->
+ case lists:keytake(Ref,2,State#state.sub) of
+ {value,{Pid,Ref},Sub} ->
+ {noreply,State#state { sub = Sub }};
+ _ ->
+ {noreply,State}
+ end;
+ {value,D,Ds} ->
+ D1 = D#uart_device { avail = true,
+ owner = undefined,
+ mon = undefined },
+ ?debug("AUTO-RELEASED: ~p\n", [D1]),
+ Ds1 = [D1 | Ds],
+ {noreply, State#state { devices = Ds1 }}
+ end;
+
+handle_info(_Info, State) ->
+ ?debug("Info: ~p\n", [_Info]),
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any
+%% necessary cleaning up. When it returns, the gen_server terminates
+%% with Reason. The return value is ignored.
+%%
+%% @spec terminate(Reason, State) -> void()
+%% @end
+%%--------------------------------------------------------------------
+terminate(_Reason, State) ->
+ remove_all(State#state.devices, State),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Convert process state when code is changed
+%%
+%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% @end
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+
+list_uart_devices() ->
+ case os:type() of
+ {unix,darwin} ->
+ Path = "/dev",
+ {ok,Ds} = file:list_dir(Path),
+ Ds1 = lists:filter(fun(D) -> is_uart_device(Path,D) end, Ds),
+ map(fun(Name) ->
+ #uart_device { name=Name,
+ path=Path,
+ id=Name,
+ avail=true}
+ end, Ds1);
+ {unix,linux} ->
+ case filelib:is_dir("/dev/serial") of
+ false ->
+ [];
+ true ->
+ ?debug("/dev/serial exist\n",[]),
+ IDs = list_serial_devices(),
+ map(fun({Name,ID}) ->
+ #uart_device { name=Name,
+ path="/dev",
+ id = ID,
+ avail=true}
+ end, IDs)
+ end;
+ _ ->
+ []
+ end.
+
+%% list serial devices on linux - return [{Name,ID}]
+list_serial_devices() ->
+ Path = "/dev/serial/by-id",
+ case file:list_dir(Path) of
+ {ok,Fs} ->
+ ?debug("/dev/serial/by-ids = ~p\n",[Fs]),
+ lists:foldl(
+ fun(ID,Acc) ->
+ Link = filename:join(Path,ID),
+ case file:read_link(Link) of
+ {ok,Name} ->
+ Name1=filename:basename(Name),
+ ?debug("ID=~s, Name=~s\n", [ID,Name1]),
+ [{Name1,ID}|Acc];
+ _ ->
+ Acc
+ end
+ end, [], Fs);
+ _E={error,enoent} ->
+ ?debug("/dev/serial/by-ids = ~p\n",[_E]),
+ []
+ end.
+
+
+is_uart_device(Path,Name) -> %% fixme check fileinfo
+ case os:type() of
+ {unix,Type} ->
+ Prefix = if Type =:= darwin -> "tty.";
+ true -> "tty"
+ end,
+ case lists:prefix(Prefix, Name) of
+ true ->
+ %% mac os x names are /dev/tty.*
+ %% linux /dev/tty*
+ case file:read_file_info(filename:join(Path,Name)) of
+ {ok,I} when I#file_info.type =:= device ->
+ true;
+ {ok,_} -> false;
+ {error,_} -> false
+ end;
+ false -> %% reconsider this
+ false
+ end;
+ {win32,_} ->
+ %% win32 \\.\COM* | COM[0-9]
+ case Name of
+ "\\\\.\\COM"++_ -> true;
+ "COM"++[_] -> true;
+ _ -> false
+ end
+ end.
+
+watch_uart_devices() ->
+ {ok,DRef} = fnotify:watch("/dev"),
+ DRef.
+
+unwatch(undefined) ->
+ ok;
+unwatch(Ref) ->
+ fnotify:unwatch(Ref).
+
+subscribe_all_([Pid|Ps], State) ->
+ {_Ref,State1} = subscribe_(Pid, State),
+ subscribe_all_(Ps, State1);
+subscribe_all_([], State) ->
+ State.
+
+subscribe_(Pid, State) ->
+ Ref = erlang:monitor(process,Pid),
+ Sub = [{Pid,Ref} | State#state.sub],
+ {Ref,State#state { sub = Sub }}.
+
+unsubscribe_(Ref,State) ->
+ case lists:keytake(Ref,2,State#state.sub) of
+ {value,{_Pid,Ref},Subs} ->
+ erlang:demonitor(Ref, [flush]),
+ State#state { sub = Subs };
+ _ ->
+ State
+ end.
+
+lookup_device_id(Name) ->
+ case os:type() of
+ {unix,linux} ->
+ %% when a devices is created in /dev we "need"/want to
+ %% find that device under /dev/serial/by-id to be sure
+ %% the device is a serial device.
+ cast_after(1000, {update_id,Name}),
+ Name;
+ _ ->
+ Name
+ end.
+
+%%
+%% add or update all devices from device list Ds
+%%
+update_all(Ds, State) ->
+ lists:foldl(fun update/2, State, Ds).
+
+update(D, State) ->
+ Ds = State#state.devices,
+ case lists:keytake(D#uart_device.name, #uart_device.name, Ds) of
+ false ->
+ add(D,State);
+ {value,D1,Ds1} ->
+ %% info may need update, more?
+ D2 = D1#uart_device { id=D#uart_device.id },
+ ?debug("UPDATE: ~p\n", [D]),
+ State#state { devices = [D2|Ds1] }
+ end.
+
+
+%% device was added (USB pugged in)
+add(D,State) ->
+ ?debug("ADD: ~p\n", [D]),
+ lists:foreach(fun({Pid,Ref}) ->
+ Pid ! {uart_device,Ref,[added],D#uart_device.name}
+ end, State#state.sub),
+ Ds = State#state.devices,
+ State#state { devices = [D|Ds]}.
+
+
+%% device is removed (USB device unplugged)
+removed_by_name(Name, State) ->
+ case lists:keytake(Name, #uart_device.name, State#state.devices) of
+ false ->
+ State;
+ {value,D,Ds} ->
+ remove(D,Ds,State)
+ end.
+
+removed_by_id(ID, State) ->
+ case lists:keytake(ID, #uart_device.id, State#state.devices) of
+ false ->
+ State;
+ {value,D,Ds} ->
+ remove(D,Ds,State)
+ end.
+
+remove_all([D|Ds],State) ->
+ State1 = remove(D,Ds,State),
+ remove_all(Ds, State1);
+remove_all([],State) ->
+ State.
+
+remove(D,Ds,State) ->
+ ?debug("REMOVED: ~p\n", [D]),
+ if is_pid(D#uart_device.owner) ->
+ %% maybe flag this ?
+ D#uart_device.owner ! {uart_device,self(),[removed],
+ D#uart_device.name};
+ true ->
+ ok
+ end,
+ lists:foreach(fun({Pid,Ref}) ->
+ Pid ! {uart_device,Ref,[removed],D#uart_device.name}
+ end, State#state.sub),
+ if is_reference(D#uart_device.mon) ->
+ erlang:demonitor(D#uart_device.mon,[flush]),
+ ok;
+ true ->
+ ok
+ end,
+ State#state { devices = Ds }.
+
+cast_after(Timeout, Msg) ->
+ Server = self(),
+ spawn(
+ fun() ->
+ timer:sleep(Timeout),
+ gen_server:cast(Server, Msg)
+ end).
+
+cast_when_created(Path, Name, Msg, Timeout) ->
+ Server = self(),
+ spawn(
+ fun() ->
+ {ok,FRef} = fnotify:watch(Path, [create]),
+ receive
+ {fevent,FRef,[create],Path,Name} ->
+ ?debug("cast_when_created: cast(~p)\n", [Msg]),
+ fnotify:unwatch(FRef),
+ timer:sleep(1000), %% revert to sleep for a while ...
+ gen_server:cast(Server, Msg)
+ after Timeout ->
+ ?debug("cast_when_created: timeout\n", []),
+ fnotify:unwatch(FRef),
+ {error,timeout}
+ end
+ end).
diff --git a/deps/uart/src/uart_win32_devices.erl b/deps/uart/src/uart_win32_devices.erl
new file mode 100644
index 0000000..f8c442d
--- /dev/null
+++ b/deps/uart/src/uart_win32_devices.erl
@@ -0,0 +1,129 @@
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2013, Tony Rogvall
+%%% @doc
+%%% Enumerate devices on win32
+%%% @end
+%%% Created : 5 Apr 2013 by Tony Rogvall <tony@rogvall.se>
+
+-module(uart_win32_devices).
+
+-compile(export_all).
+
+%% HKLM\ENUM
+%% |-BIOS
+%% |-*PNP0501
+%% |-0D (or any other value, this is not important for us)
+%% · CLASS= "Ports"
+%% · PORTNAME= "COM1"
+%% · FRIENDLYNAME= "Communications Port (COM1)"
+%%
+
+%% Win2000/XP
+%% \\HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Enum
+%%
+%% WinNT4.0
+%% \\HKEY_LOCAL_MACHINE\\Hardware\\DEVICEMAP\\SERIALCOMM
+%%
+scan_enum_tree() ->
+ scan_enum_tree("\\hkey_local_machine\\System\\CurrentControlSet\\Enum").
+
+scan_enum_tree(Base) ->
+ {ok,W} = win32reg:open([read]),
+ ok = win32reg:change_key(W, Base),
+ {ok, Types} = win32reg:sub_keys(W),
+ %% FIXME: remove keys that can not lead to serial devices!
+ Types1 = Types -- ["DISPLAY", "IDE", "STORAGE", "LPTENUM", "SCSI", "USBSTOR", "USBPRINT" ],
+ scan_types(W, Base, Types1, []).
+
+scan_types(W, Base, [Key|Ks], Acc) ->
+ Base1 = Base ++ "\\" ++ Key,
+ ok = win32reg:change_key(W, Base1),
+ {ok, Devices} = win32reg:sub_keys(W),
+ Acc1 = scan_devices(W, Base1, Devices, Acc),
+ scan_types(W, Base, Ks, Acc1);
+scan_types(_W, _Base, [], Acc) ->
+ Acc.
+
+%% keys like: "VID_0403+PID_6001+FTFBXORBA"
+scan_devices(W, Base, [Key|Ks], Acc) ->
+ Base1 = Base ++ "\\" ++ Key,
+ ok = win32reg:change_key(W, Base1),
+ {ok, Instances} = win32reg:sub_keys(W),
+ Acc1 = scan_instances(W, Base1, Instances, Acc),
+ scan_devices(W, Base, Ks, Acc1);
+scan_devices(_W, _Base, [], Acc) ->
+ Acc.
+
+%% keys like: "0000"
+scan_instances(W, Base, [Key|Ks], Acc) ->
+ Base1 = Base ++ "\\" ++ Key,
+ ok = win32reg:change_key(W, Base1),
+ {ok,KVs} = win32reg:values(W),
+ case is_serial_port(KVs) of
+ true ->
+ DVs =
+ case win32reg:change_key(W, Base1++"\\"++"Device Parameters") of
+ ok ->
+ {ok,DVs0} = win32reg:values(W),
+ DVs0;
+ _ -> []
+ end,
+ case port_name(KVs,DVs) of
+ Name="COM"++_ ->
+ io:format("Serial: ~s\n", [Name]),
+ io:format("Serial Params\n"),
+ lists:foreach(
+ fun({K,V}) ->
+ io:format(" ~s : ~p\n", [K,V])
+ end, KVs),
+ io:format("Device params\n"),
+ lists:foreach(
+ fun({K,V}) ->
+ io:format(" ~s : ~p\n", [K,V])
+ end, DVs),
+ scan_instances(W, Base, Ks, [{Name,KVs,DVs}|Acc]);
+ _ ->
+ scan_instances(W, Base, Ks, Acc)
+ end;
+ false ->
+ scan_instances(W, Base, Ks, Acc)
+ end;
+scan_instances(_W, _Base, [], Acc) ->
+ Acc.
+
+is_serial_port(KVs) ->
+ strcaseeq(get_value("Class", KVs),"PORTS")
+ orelse
+ case get_value("ClassGUID", KVs) of
+ "{4D36E978-E325-11CE-BFC1-08002BE10318}" -> true;
+ _ -> false
+ end.
+
+port_name(KVs,DVs) ->
+ case get_value("PORTNAME", KVs) of
+ undefined ->
+ get_value("PORTNAME", DVs);
+ Name -> Name
+ end.
+
+get_value(K, KVs) ->
+ get_value(K, KVs, undefined).
+
+get_value(K, [{K,V}|_], _D) -> V;
+get_value(K, [{K0,V}|KVs], D) ->
+ case strcaseeq(K,K0) of
+ true -> V;
+ false -> get_value(K, KVs, D)
+ end;
+get_value(_K, [], D) -> D.
+
+strcaseeq([A|As], [B|Bs]) ->
+ A1 = string:to_upper(A),
+ B1 = string:to_upper(B),
+ if A1 =:= B1 -> strcaseeq(As,Bs);
+ true -> false
+ end;
+strcaseeq([], []) ->
+ true;
+strcaseeq(_, _) ->
+ false.
diff --git a/deps/uart/test/Makefile b/deps/uart/test/Makefile
new file mode 100644
index 0000000..ac067b1
--- /dev/null
+++ b/deps/uart/test/Makefile
@@ -0,0 +1,34 @@
+#
+# Make the uart test modules
+#
+
+MODULES = \
+ uart_SUITE
+
+
+EBIN = .
+ERLC = erlc
+
+ERLDIR := $(shell erl -noshell -eval "io:format([126,115,126,110],[code:root_dir()])" -s erlang halt)
+
+override ERLC_FLAGS = -Wall -I ../include
+
+debug: ERLC_FLAGS += +debug_info -Ddebug
+
+OBJS = $(MODULES:%=$(EBIN)/%.beam)
+
+all: $(OBJS)
+
+debug: all
+
+depend:
+ edep -MM -o ../ebin $(ERLC_FLAGS) $(MODULES:%=%.erl) > depend.mk
+
+clean:
+ rm -f $(OBJS)
+
+
+-include depend.mk
+
+./%.beam: %.erl
+ erlc -o ../ebin $(ERLC_FLAGS) $<
diff --git a/deps/uart/test/uart.cfg b/deps/uart/test/uart.cfg
new file mode 100644
index 0000000..15ac617
--- /dev/null
+++ b/deps/uart/test/uart.cfg
@@ -0,0 +1,12 @@
+%% -*- erlang -*-
+%% Configuration file for testing of uart
+%%
+
+%% Devices
+%% Must be adapted !!! Find devicename in System Profile
+{device, [{darwin, [{a, "/dev/tty.usbserial-FTF5DP2J"},
+ {b, "/dev/tty.usbserial-FTFBXORB"}]},
+ {linux, [{a, "/dev/ttyUSB0"},
+ {b, "/dev/ttyUSB1"}]},
+ {win32, [{a, "COM10"},
+ {b, "COM11"}]}]}.
diff --git a/deps/uart/test/uart.spec b/deps/uart/test/uart.spec
new file mode 100644
index 0000000..56e26bc
--- /dev/null
+++ b/deps/uart/test/uart.spec
@@ -0,0 +1,7 @@
+%% -*- erlang -*-
+%% Test specification for testing of uart without any dongles
+%%
+{alias, uart_test, "."}.
+{config, "uart.cfg"}.
+{suites, uart_test, uart_SUITE}.
+{skip_cases, uart_test, uart_SUITE, [a_to_b, modem, options], "No hardware"}. \ No newline at end of file
diff --git a/deps/uart/test/uart_SUITE.erl b/deps/uart/test/uart_SUITE.erl
new file mode 100644
index 0000000..5560c6b
--- /dev/null
+++ b/deps/uart/test/uart_SUITE.erl
@@ -0,0 +1,562 @@
+%%%---- BEGIN COPYRIGHT --------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ----------------------------------------------------------
+%%%-------------------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @author Marina Westman Lönne <malotte@malotte.net>
+%%% @copyright (C) 2012, Tony Rogvall <tony@rogvall.se>
+%%% @doc
+%%% Test suite for uart.
+%%% @end
+%%% Created : 2012 by Marina Westman Lönne <malotte@malotte.net>
+%%%-------------------------------------------------------------------
+-module(uart_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%--------------------------------------------------------------------
+%% COMMON TEST CALLBACK FUNCTIONS
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Returns list of tuples to set default properties
+%% for the suite.
+%% @spec suite() -> Info
+%% @end
+%%--------------------------------------------------------------------
+suite() ->
+ [{timetrap,{minutes,10}},
+ {require, device}].
+
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Returns the list of groups and test cases that
+%% are to be executed.
+%%
+%% @spec all() -> GroupsAndTestCases | {skip,Reason}
+%% @end
+%%--------------------------------------------------------------------
+all() ->
+ [options,
+ a_to_b,
+ pty,
+ modem,
+ encode_opts].
+%% break].
+
+
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Initialization before the whole suite
+%%
+%% @spec init_per_suite(Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% @end
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Cleanup after the whole suite
+%%
+%% @spec end_per_suite(Config) -> _
+%% @end
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ ok.
+
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Initialization before each test case
+%%
+%% @spec init_per_testcase(TestCase, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% @end
+%%--------------------------------------------------------------------
+init_per_testcase(TC, Config) ->
+ ct:pal("Init testcase: ~p", [TC]),
+ tc_init(TC, Config).
+
+tc_init(_Any, Config) ->
+ Config.
+
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Cleanup after each test case
+%%
+%% @spec end_per_testcase(TestCase, Config0) ->
+%% void() | {save_config,Config1} | {fail,Reason}
+%% @end
+%%--------------------------------------------------------------------
+end_per_testcase(TC, Config) ->
+ ct:pal("End testcase: ~p", [TC]),
+ tc_end(TC, Config).
+
+tc_end(_Any, _Config) ->
+ ok.
+
+
+%%--------------------------------------------------------------------
+%% TEST CASES
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Get options for a port.
+%% @end
+%%--------------------------------------------------------------------
+-spec options(Config::list(tuple())) -> ok.
+
+options(_Config) ->
+ {ok,A} = open(a),
+ {ok,Opts} = uart:getopts(A, uart:options()),
+ uart:close(A),
+ ct:pal("Options ~p",[Opts]),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Let a and b communicate.
+%% @end
+%%--------------------------------------------------------------------
+-spec a_to_b(Config::list(tuple())) -> ok.
+
+a_to_b(_Config) ->
+ {ok,A} = open(a),
+ {ok,B} = open(b),
+ run_ab(A,B),
+ uart:close(A),
+ uart:close(B),
+ ok.
+
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Let a communicate with a pseudo terminal.
+%% @end
+%%--------------------------------------------------------------------
+-spec pty(Config::list(tuple())) -> ok.
+
+pty(_Config) ->
+ {ok,A} = open(pty),
+ {ok,TTY} = uart:getopt(A, device),
+ {ok,B} = open(TTY),
+ run_ab(A,B),
+ uart:close(A),
+ uart:close(B),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @doc
+%%
+%% Test modem bits
+%%
+%% A B
+%% DTR => DSR,CD
+%% RTS => CTS
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec modem(Config::list(tuple())) -> ok.
+
+modem(_Config) ->
+ {ok,A} = open(a),
+ {ok,B} = open(b),
+ modem_test(A, B),
+ uart:close(A),
+ uart:close(B),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Test encoding of options.
+%% @end
+%%--------------------------------------------------------------------
+-spec encode_opts(Config::list(tuple())) -> ok.
+
+encode_opts(_Config) ->
+ Success =
+ [{device,"COM1:"},
+ {ibaud, 0}, {ibaud, 19200}, {ibaud, 115200},
+ {obaud, 0}, {obaud, 19200}, {obaud, 115200},
+ {csize, 5}, {csize, 6}, {csize, 7}, {csize,8},
+ {bufsz, 1}, {bufsz, 1024},
+ {buftm, 250},
+ {stopb, 1}, {stopb,2}, {stopb,3},
+ {parity,none},{parity,odd},{parity,even},{parity,mark},{parity,space},
+ {parity,0},{parity,1},{parity,2},{parity,3},{parity,4},
+ {oflow,[dtr]},{oflow,[sw]},
+ {iflow,[sw]}, {iflow,[cts]},
+ {xonchar,$\^S},{xonchar,0},
+ {xoffchar,$\^Q},{xoffchar,0},
+ {eolchar,$\n}, {eolchar,$;}, {eolchar,$:},
+ {active,true},{active,false},{active,once},
+ {delay_send,true},{delay_send,false},
+ {header,0},{header,10},
+ {packet,0},
+ {packet,1},{packet,2},{packet,3},{packet,4},
+ {packet,5},{packet,6},{packet,7},{packet,8},
+ {packet,-1},{packet,-2},{packet,-3},{packet,-4},
+ {packet,-5},{packet,-6},{packet,-7},{packet,-8},
+ {packet,{size,16}},{packet,{size,1}},{packet,{size,64}},
+ {packet,line},
+ {packet_size,1024},
+ {deliver,term},{deliver,port},
+ {mode,list},{mode,binary},
+ {high_watermark, 10}, {high_watermark, 255},
+ {low_watermark, 10}, {low_watermark, 255},
+ {send_timeout, -1}, {send_timeout, 10},
+ {send_timeout_close, -1}, {send_timeout_close, 0},
+ {send_timeout_close, 1000},
+ {buffer,0}, {buffer,1}, {buffer,1024*64},
+ {exit_on_close,true}, {exit_on_close,false}
+ ],
+ Fail =
+ [{device, 123},
+ {ibaud, -1}, {ibaud,x}, {ibaud,1000000000000},
+ {obaud, -1}, {obaud,x}, {obaud,1000000000000},
+ {csize,x}, {csize,10},
+ {bufsz, -1},
+ {buftm, 1000000000000},
+ {buftm, -1},
+ {stopb, 4}, {stopb, x},
+ {parity, 15}, {parity, x},
+ {oflow, 12},
+ {iflow, [cls]},
+ {xonchar, 256},{xonchar,x},
+ {xoffchar, 256},{xoffchar,x},
+ {eolchar, 256},{eolchar,x},
+ {active,x},{active,1},
+ {delay_send,5},
+ {packet, 9},
+ {packet, -9},
+ {packet, {size,z}},
+ {packet, {size,16#10000}},
+ {deliver, beer},{deliver,1},
+ {mode,sleep}, {mode,18},
+ {buffer,-1},
+ {exit_on_close,x}
+ ],
+
+ lists:foreach(
+ fun({Opt,Value}) ->
+ uart:encode_opt(Opt,Value)
+ end, Success),
+
+ lists:foreach(
+ fun({Opt,Value}) ->
+ try uart:encode_opt(Opt,Value) of
+ _X -> ct:fail({bad_encoding,{Opt,Value}})
+ catch
+ error:_ ->
+ ok
+ end
+ end, Fail).
+
+
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Dummy test case to have a test environment running.
+%% Stores Config in ets table.
+%% @end
+%%--------------------------------------------------------------------
+-spec break(Config::list(tuple())) -> ok.
+
+break(Config) ->
+ ets:new(config, [set, public, named_table]),
+ ets:insert(config, Config),
+ test_server:break("Break for test development\n" ++
+ "Get Config by ets:tab2list(config)"),
+ ok.
+
+
+%%--------------------------------------------------------------------
+%% Help functions
+%%--------------------------------------------------------------------
+
+tty(a) ->
+ case os:type() of
+ {unix,darwin} ->
+ %% "/dev/tty.usbserial-FTF5DP2J";
+ ct:get_config({device, darwin, a});
+ {unix,linux} ->
+ %% "/dev/ttyUSB0";
+ ct:get_config({device, linux, a});
+ {win32,_} ->
+ %% "COM10"
+ ct:get_config({device, win32, a})
+ end;
+tty(b) ->
+ case os:type() of
+ {unix,darwin} ->
+ %% "/dev/tty.usbserial-FTFBXORB";
+ ct:get_config({device, darwin, b});
+ {unix,linux} ->
+ %% "/dev/ttyUSB1";
+ ct:get_config({device, linux, b});
+ {win32,_} ->
+ %% "COM11"
+ ct:get_config({device, win32, b})
+ end;
+tty(pty) ->
+ "//pty";
+tty(Name) when is_list(Name) ->
+ Name.
+
+
+%%
+%% Test "all" combination of:
+%% {active,false|true|once} x {packet,-8...8} = 3*17 = 51
+%% {active,false|true|once} x {packet,{size,1|5}} = 3*2 = 6
+%% {active,false|true|once} x {packet,0|line} = 3*2 = 6
+%% x
+%% {mode,list|binary} = 2
+%%
+%% Sofar = 51*2+6*2+6*2 = 126
+%%
+%% TODO:
+%% {deliver, term|port}
+%% {header, 0,1,2,3,4,7,12,13}
+%%
+run_ab(A,B) ->
+ transfer_test(A,B,[{mode,list},{active,false}]),
+ transfer_test(A,B,[{mode,list},{active,true}]),
+ transfer_test(A,B,[{mode,list},{active,once}]),
+ transfer_test(A,B,[{mode,binary},{active,false}]),
+ transfer_test(A,B,[{mode,binary},{active,true}]),
+ transfer_test(A,B,[{mode,binary},{active,once}]),
+ ok.
+
+open(X) ->
+ uart:open(tty(X), []).
+
+transfer_test(A,B,Opts) ->
+ Data = "Hello World",
+ Len = length(Data),
+ transfer_raw(A,B,Opts,Data,Len,0),
+ transfer_raw(A,B,Opts,Data,Len,1),
+ transfer_raw(A,B,Opts,Data,Len,2),
+ transfer_raw(A,B,Opts,Data,Len,3),
+ transfer_raw(A,B,Opts,Data,Len,4),
+ transfer_raw(A,B,Opts,Data,Len,5),
+ transfer_raw(A,B,Opts,Data,Len,6),
+ transfer_raw(A,B,Opts,Data,Len,7),
+ transfer_raw(A,B,Opts,Data,Len,8),
+ transfer_raw(A,B,Opts,Data,Len,-1),
+ transfer_raw(A,B,Opts,Data,Len,-2),
+ transfer_raw(A,B,Opts,Data,Len,-3),
+ transfer_raw(A,B,Opts,Data,Len,-4),
+ transfer_raw(A,B,Opts,Data,Len,-5),
+ transfer_raw(A,B,Opts,Data,Len,-6),
+ transfer_raw(A,B,Opts,Data,Len,-7),
+ transfer_raw(A,B,Opts,Data,Len,-8),
+
+ transfer_packet(A,B,Opts,Data,Len,0),
+ transfer_packet(A,B,Opts,Data,Len,1),
+ transfer_packet(A,B,Opts,Data,Len,2),
+ transfer_packet(A,B,Opts,Data,Len,3),
+ transfer_packet(A,B,Opts,Data,Len,4),
+ transfer_packet(A,B,Opts,Data,Len,5),
+ transfer_packet(A,B,Opts,Data,Len,6),
+ transfer_packet(A,B,Opts,Data,Len,7),
+ transfer_packet(A,B,Opts,Data,Len,8),
+ transfer_packet(A,B,Opts,Data,Len,-1),
+ transfer_packet(A,B,Opts,Data,Len,-2),
+ transfer_packet(A,B,Opts,Data,Len,-3),
+ transfer_packet(A,B,Opts,Data,Len,-4),
+ transfer_packet(A,B,Opts,Data,Len,-5),
+ transfer_packet(A,B,Opts,Data,Len,-6),
+ transfer_packet(A,B,Opts,Data,Len,-7),
+ transfer_packet(A,B,Opts,Data,Len,-8),
+
+ transfer_line_packet(A,B,Opts,Data,Len),
+
+ transfer_fixed_packet(A,B,Opts,Data,Len),
+ ok.
+
+transfer_raw(A,B,MatchOpts,Data,Len,Sz) ->
+ %% Formatting of sender packet + raw reception
+ Hdr =
+ if Sz =:= 0 -> [];
+ Sz < 0 -> binary_to_list(<<Len:(-Sz*8)/little>>);
+ true -> binary_to_list(<<Len:(Sz*8)>>)
+ end,
+ Match = Hdr++Data,
+ transfer(A, [{packet,Sz}], Data,
+ B, MatchOpts++[{packet,0}], Match).
+
+transfer_packet(A,B,MatchOpts,Data,_Len,Sz) ->
+ %% Packet reception
+ transfer(A, [{packet,Sz}], Data,
+ B, MatchOpts++[{packet,Sz}], Data).
+
+
+transfer_line_packet(A,B,MatchOpts,Data,_Len) ->
+ %% Line packet
+ transfer(A, [{packet,line}], Data++"\n",
+ B, MatchOpts++[{packet,0}], Data++"\n"),
+ transfer(A, [{packet,line}], Data++"\n",
+ B, MatchOpts++[{packet,line}], Data++"\n").
+
+transfer_fixed_packet(A,B,MatchOpts,Data,Len) ->
+ %% Fixed size packet
+ transfer(A, [{packet,0}], Data,
+ B, MatchOpts++[{packet,{size,Len}}], Data),
+ transfer(A, [{packet,0}], "W",
+ B, MatchOpts++[{packet,{size,1}}], "W").
+
+
+
+transfer(A, AOpts, Data, B, BOpts, Match0) ->
+ Match = case proplists:get_value(mode, BOpts, list) of
+ list -> Match0;
+ binary -> list_to_binary(Match0)
+ end,
+ ct:pal("Transfer: A=~w, Data=~p, B=~w, Match=~999p\n",
+ [AOpts,Data,BOpts,Match]),
+ uart:setopts(A, AOpts),
+ uart:setopts(B, BOpts),
+ uart:send(A, Data),
+ Active = proplists:get_value(active,BOpts,false),
+ case lists:member({packet,0}, BOpts) of
+ true ->
+ recv_loop(B, Active, Match);
+ false ->
+ recv(B, Active, Match)
+ end.
+
+recv_loop(_B, _Active, []) ->
+ true;
+recv_loop(_B, _Active, <<>>) ->
+ true;
+recv_loop(B, true, Match) ->
+ receive
+ {uart, B, Data} ->
+ case match(Data, Match) of
+ {true,Match1} -> recv_loop(B, true, Match1);
+ true -> true;
+ false ->
+ ct:fail({bad_match,Data})
+ end
+ after 1000 ->
+ ct:fail(receive_timeout)
+ end;
+recv_loop(B, once, Match) ->
+ receive
+ {uart, B, Data} ->
+ case match(Data, Match) of
+ {true, Match1} ->
+ {ok,false} = uart:getopt(B, active),
+ uart:setopt(B, active, once),
+ recv_loop(B, once, Match1);
+ true -> true;
+ false ->
+ ct:fail({bad_match,Data})
+ end
+ after 1000 ->
+ ct:fail(receive_timeout)
+ end;
+recv_loop(B, false, Match) ->
+ case uart:recv(B, 0, 100) of
+ {ok, Data} ->
+ case match(Data, Match) of
+ {true,Match1} ->
+ recv_loop(B, false, Match1);
+ true -> true;
+ false ->
+ ct:fail({bad_match,Data})
+ end;
+ Error ->
+ ct:fail(Error)
+ end.
+
+recv(B, Active, Match) when Active =:= true; Active =:= once ->
+ receive
+ {uart, B, Match} ->
+ true;
+ {uart, B, Data} ->
+ ct:fail({bad_match,Data})
+ after 1000 ->
+ ct:fail(receive_timeout)
+ end;
+recv(B, false, Match) ->
+ case uart:recv(B, 0, 100) of
+ {ok, Match} ->
+ true;
+ {ok, Data} ->
+ ct:fail({bad_match,Data});
+ Error ->
+ ct:fail(Error)
+ end.
+
+match(Data, Match) when is_list(Data), is_list(Match) ->
+ case lists:prefix(Data, Match) of
+ true ->
+ case Match--Data of
+ [] -> true;
+ More -> {true, More}
+ end;
+ false ->
+ false
+ end;
+match(Data, Match) when is_binary(Data), is_binary(Match) ->
+ Sz = byte_size(Data),
+ case Match of
+ <<Data:Sz/binary, Rest/binary>> ->
+ if byte_size(Rest) =:= 0 ->
+ true;
+ true ->
+ {true, Rest}
+ end;
+ _ ->
+ false
+ end.
+
+modem_test(A, B) ->
+ %% depending on modem pins available on A and B
+ %% clear pins
+ match_pins(A, B, [], [dtr,rts], []),
+ match_pins(B, A, [], [dtr,rts], []),
+ %% test RTS: A => B
+ match_pins(A, B, [rts], [], [cts]),
+ %% test RTS: B => A
+ match_pins(B, A, [rts], [], [cts]),
+ %% test DTR: A => B
+ match_pins(A, B, [dtr], [], [cd,dsr]),
+ %% test DTR: B => A
+ match_pins(B, A, [dtr], [], [cd,dsr]),
+
+ ok.
+
+match_pins(A, B, SetA,ClrA, MatchB) ->
+ uart:set_modem(A,SetA),
+ uart:clear_modem(A,ClrA),
+ {ok,BPins} = uart:get_modem(B),
+ uart:clear_modem(A,SetA),
+ io:format("match_pins: SetA=~p,ClrA=~p,MatchB=~p,BPins=~p\n",
+ [SetA,ClrA,MatchB,BPins]),
+ true = (lists:sort(MatchB) =:= lists:sort(BPins -- [ri,dtr,rts])).
+
diff --git a/deps/uart/test/uart_hw.spec b/deps/uart/test/uart_hw.spec
new file mode 100644
index 0000000..2853c57
--- /dev/null
+++ b/deps/uart/test/uart_hw.spec
@@ -0,0 +1,6 @@
+%% -*- erlang -*-
+%% Test specification for testing of uart with dongles
+%%
+{alias, uart_test, "."}.
+{config, "uart.cfg"}.
+{suites, uart_test, uart_SUITE}.
diff --git a/deps/wse/LICENSE b/deps/wse/LICENSE
new file mode 100644
index 0000000..b9134e4
--- /dev/null
+++ b/deps/wse/LICENSE
@@ -0,0 +1,24 @@
+Copyright (C) 2007 - 2014, Rogvall Invest AB, <tony@rogvall.se>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Except as contained in this notice, the name(s) of the above copyright holders
+shall not be used in advertising or otherwise to promote the sale, use or other
+dealings in this Software without prior written authorization.
+
diff --git a/deps/wse/README.md b/deps/wse/README.md
new file mode 100644
index 0000000..a9443f3
--- /dev/null
+++ b/deps/wse/README.md
@@ -0,0 +1,88 @@
+wse - Websockets for Erlang
+===========================
+
+WSE is a simple, standalone, websocket server which execute
+request in the browser instead of in the web server. There is a bit
+of boot strapping that may need help from a web server. But a simple
+web page and a browser is all that it takes to get running.
+
+To get control of part of a web page (or all of it) use the following
+HTML snippet. Also make sure you have ej.js and wse.js in the same directory.
+
+ <html><head>
+ <title>wse demo page</title>
+ <script src='ej.js'></script>
+ <script src='wse.js'></script>
+ <script>
+ window.onload = function() {
+ if (Wse.open("ws://localhost:1234/websession"))
+ Wse.start('wse_demo', 'run', ["myid"]);
+ };
+ </script></head>
+ <body>
+ <div id="myid"></div>
+ </body></html>
+
+Next thing is to start erlang wse server (default to port 1234):
+
+ $ erl
+ > wse_server:start().
+
+Then have the erlang module wse_demo in the path somewhere:
+
+ -module(wse_demo).
+ -export([run/2]).
+
+ run(Ws, Where) ->
+ ElemNode = wse:createElement(Ws, "p"),
+ TextNode = wse:createTextNode(Ws, "Hello world"),
+ wse:appendChild(Ws, ElemNode, TextNode),
+ wse:appendChild(Ws, wse:id(Where), ElemNode).
+
+The browser will call wse_demo:run (via the websocket) with the web socket proxy process as a the first argument and the "myid" as the second argument. From thereon the web page can be manipulated at will.
+
+# register pages
+
+One nice trick to interact with pages from command line is to
+register the page it self
+
+ <html><head>
+ <title>Page A</title>
+ <script src='ej.js'></script>
+ <script src='wse.js'></script>
+ <script>
+ window.onload = function() {
+ if (Wse.open("ws://localhost:1234/websession")) {
+ Wse.register('page_a');
+ };
+ </script>
+ </head><body>
+ <p id="x">Hello A World</p>
+ </body></html>
+
+Now this page should be registered in erlang and can be tested. Start with
+changing the text in the paragraf on the page.
+
+ > {ok,X} = wse:getElementById(page_a, "x").
+ > {ok,Text} = wse:firstChild(page_a, X).
+ > wse:set(page_a, Text, "nodeValue", "Hej A").
+
+Or we could send some ehtml (erlang style html) defining a button to it
+
+ > wse:send(page_a, "x", {button,[{id,"y"}],["Press Me"]}).
+
+Or why not send a whole table to it
+
+ > wse:send(page_a, "x", {table,[],[{tr,[],[{td,[],["A"]},{td,[],["B"]}]},{tr,[],[{td,[],["C"]},{td,[],["D"]}]}]}).
+
+To have events sent to us when pressing a button, using send, we can do
+
+ > {ok,E} = wse:create_event(page_a).
+ > wse:send(page_a, "x", {button,[{id,"y"},{onclick,"Wse.notify("++integer_to_list(E)++",'click');"}],["Press Me"]}).
+
+Click on the button on page A a couple of times
+
+ > flush().
+ Shell got {notify,2,[],"click"}
+ Shell got {notify,2,[],"click"}
+ Shell got {notify,2,[],"click"}
diff --git a/deps/wse/priv/base64.js b/deps/wse/priv/base64.js
new file mode 100644
index 0000000..56c7efe
--- /dev/null
+++ b/deps/wse/priv/base64.js
@@ -0,0 +1,83 @@
+//---- BEGIN COPYRIGHT -------------------------------------------------------
+//
+// Copyright (C) 2007 - 2014, Rogvall Invest AB, <tony@rogvall.se>
+//
+// This software is licensed as described in the file COPYRIGHT, which
+// you should have received as part of this distribution. The terms
+// are also available at http://www.rogvall.se/docs/copyright.txt.
+//
+// You may opt to use, copy, modify, merge, publish, distribute and/or sell
+// copies of the Software, and permit persons to whom the Software is
+// furnished to do so, under the terms of the COPYRIGHT file.
+//
+// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+// KIND, either express or implied.
+//
+//---- END COPYRIGHT ---------------------------------------------------------
+//
+// Base64 encoder/decoder (on strings)
+//
+function Base64Class() {
+ this._keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+};
+
+// Base64 encoder
+Base64Class.prototype.encode = function (input) {
+ var output = "";
+ var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
+ var i = 0;
+
+ while (i < input.length) {
+ chr1 = input.charCodeAt(i++);
+ chr2 = input.charCodeAt(i++);
+ chr3 = input.charCodeAt(i++);
+
+ enc1 = chr1 >> 2;
+ enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+ enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+ enc4 = chr3 & 63;
+
+ if (isNaN(chr2)) {
+ enc3 = enc4 = 64;
+ } else if (isNaN(chr3)) {
+ enc4 = 64;
+ }
+ output = output +
+ this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
+ this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
+ }
+ return output;
+};
+
+// Base64 decoder
+Base64Class.prototype.decode = function (input) {
+ var output = "";
+ var chr1, chr2, chr3;
+ var enc1, enc2, enc3, enc4;
+ var i = 0;
+
+ input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
+
+ while (i < input.length) {
+ enc1 = this._keyStr.indexOf(input.charAt(i++));
+ enc2 = this._keyStr.indexOf(input.charAt(i++));
+ enc3 = this._keyStr.indexOf(input.charAt(i++));
+ enc4 = this._keyStr.indexOf(input.charAt(i++));
+
+ chr1 = (enc1 << 2) | (enc2 >> 4);
+ chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+ chr3 = ((enc3 & 3) << 6) | enc4;
+
+ output = output + String.fromCharCode(chr1);
+
+ if (enc3 != 64) {
+ output = output + String.fromCharCode(chr2);
+ }
+ if (enc4 != 64) {
+ output = output + String.fromCharCode(chr3);
+ }
+ }
+ return output;
+};
+
+var Base64 = new Base64Class();
diff --git a/deps/wse/priv/bd.jpg b/deps/wse/priv/bd.jpg
new file mode 100644
index 0000000..6e2c45d
--- /dev/null
+++ b/deps/wse/priv/bd.jpg
Binary files differ
diff --git a/deps/wse/priv/button_demo.html b/deps/wse/priv/button_demo.html
new file mode 100644
index 0000000..d397f83
--- /dev/null
+++ b/deps/wse/priv/button_demo.html
@@ -0,0 +1,22 @@
+<html>
+<head>
+<title>Button demo page</title>
+<script src='ej.js'></script>
+<script src='wse.js'></script>
+<script>
+
+window.onload = function() {
+ if (Wse.open("ws://"+(location.hostname||"localhost")+":1234/websession")) {
+ // Wse.enable_console_output(true);
+ Wse.start('wse_button_demo', 'run', ["myid"]);
+ }
+ else
+ alert("WebSockets not supported");
+};
+
+</script>
+</head>
+<body>
+<div id="myid"></div>
+</body>
+</html>
diff --git a/deps/wse/priv/document_demo.html b/deps/wse/priv/document_demo.html
new file mode 100644
index 0000000..87e3a49
--- /dev/null
+++ b/deps/wse/priv/document_demo.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+<title>Document demo page</title>
+<script src='ej.js'></script>
+<script src='wse.js'></script>
+<script>
+
+window.onload = function() {
+ if (Wse.open("ws://"+(location.hostname||"localhost")+":1234/websession")) {
+ Wse.start('wse_document_demo', 'run', ["i1"]);
+ }
+ else
+ alert("WebSockets not supported");
+};
+
+</script>
+</head>
+<body>
+<div id="i1" width=640 height=480></div>
+</body>
+</html>
diff --git a/deps/wse/priv/ej.js b/deps/wse/priv/ej.js
new file mode 100644
index 0000000..af3b1f2
--- /dev/null
+++ b/deps/wse/priv/ej.js
@@ -0,0 +1,672 @@
+//---- BEGIN COPYRIGHT -------------------------------------------------------
+//
+// Copyright (C) 2007 - 2014, Rogvall Invest AB, <tony@rogvall.se>
+//
+// This software is licensed as described in the file COPYRIGHT, which
+// you should have received as part of this distribution. The terms
+// are also available at http://www.rogvall.se/docs/copyright.txt.
+//
+// You may opt to use, copy, modify, merge, publish, distribute and/or sell
+// copies of the Software, and permit persons to whom the Software is
+// furnished to do so, under the terms of the COPYRIGHT file.
+//
+// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+// KIND, either express or implied.
+//
+//---- END COPYRIGHT ---------------------------------------------------------
+//
+// Erlang term encode/decode using TypeArray interface
+//
+
+function EiClass() {
+ this.MAGIC = 131;
+ this.SMALL_ATOM = 115;
+ this.ATOM = 100;
+ this.BINARY = 109;
+ this.SMALL_INTEGER = 97;
+ this.INTEGER = 98;
+ this.SMALL_BIG = 110;
+ this.LARGE_BIG = 111;
+ this.FLOAT = 99;
+ this.NEW_FLOAT = 70;
+ this.STRING = 107;
+ this.LIST = 108;
+ this.SMALL_TUPLE = 104;
+ this.LARGE_TUPLE = 105;
+ this.NIL = 106;
+ this.MAP = 116;
+
+ this.use_map = false; // use map when available
+ this.use_small_atom = true; // use small atom when available
+
+ if (this.use_small_atom) {
+ this.true_atom_size = 1+4;
+ this.false_atom_size = 1+5;
+ }
+ else {
+ this.true_atom_size = 2+4;
+ this.false_atom_size = 2+5;
+ }
+};
+
+//
+// [] can be encoded as:
+// <<MAGIC, NIL>>
+// <<MAGIC, STRING, 0:16>>
+// <<MAGIC, LIST, 0:32, NIL>>
+//
+function EiAtom(Obj) {
+ this.type = "Atom";
+ this.value = Obj;
+ this.toString = function () {
+ return Obj;
+ };
+};
+
+// Obj is typed array or string
+function EiBinary(Obj) {
+ this.type = "Binary";
+ this.value = Obj;
+ this.toString = function () {
+ return "<<\"" + Obj + "\">>";
+ };
+};
+
+function EiTuple(Arr) {
+ var i;
+ this.type = "Tuple";
+ this.length = Arr.length;
+ this.value = Arr;
+ this.toString = function () {
+ var i, s = "";
+ for (i = 0; i < this.value.length; i++) {
+ if (s !== "") {
+ s += ", ";
+ }
+ s += this.value[i].toString();
+ }
+ return "{" + s + "}";
+ };
+};
+
+// - INTERFACE -
+
+EiClass.prototype.isAtom = function (Obj) {
+ return (Obj.type === "Atom");
+};
+
+EiClass.prototype.eqAtom = function (Obj, name) {
+ return (Obj.type === "Atom") && (Obj.value === name);
+};
+
+EiClass.prototype.isArray = function(Obj) {
+ return (typeof(Obj) == "object") &&
+ (Obj.constructor.toString().indexOf("Array") !== -1);
+};
+
+EiClass.prototype.isTuple = function(Obj) {
+ return (typeof(Obj) == "object") && (Obj.type == "Tuple");
+};
+
+EiClass.prototype.isTupleSize = function(Obj,n) {
+ return (typeof(Obj) == "object") && (Obj.type == "Tuple") &&
+ (Obj.length == n);
+};
+
+EiClass.prototype.isBinary = function (Obj) {
+ return (Obj.type === "Binary");
+};
+
+EiClass.prototype.binary_size = function (Obj) {
+ if (Obj.type === "Binary") return Obj.value.byteLength;
+ return 0;
+};
+
+
+EiClass.prototype.atom = function (Obj) {
+ return new EiAtom(Obj);
+};
+
+EiClass.prototype.binary = function (Obj) {
+ return new EiBinary(Obj);
+};
+
+EiClass.prototype.tuple = function () {
+ return new EiTuple([].splice.call(arguments,0));
+};
+
+// byte_size_xxx
+// calculate the size need to encode the Term
+// (use byte_size_term to avoid extra magic byte)
+//
+EiClass.prototype.byte_size = function (Obj) {
+ return 1+this.byte_size_term(Obj);
+};
+
+EiClass.prototype.byte_size_term = function (Obj) {
+ var len, Size;
+ switch(typeof(Obj)) {
+ case 'string':
+ len = Obj.length;
+ if (len <= 65535)
+ Size = 2+len; // STRING encoded
+ else
+ Size = 4+2*len+1; // LIST encoded (with terminating nil)!
+ break;
+ case 'boolean':
+ Size = Obj ? this.true_atom_size : this.false_atom_size;
+ break;
+ case 'number':
+ if (Obj % 1 === 0) { // then integer
+ if ((Obj >= 0) && (Obj < 256))
+ Size = 1; // small_integer (uint8)
+ else if ((Obj >= -134217728) && (Obj <= 134217727))
+ Size = 4; // integer (int32)
+ else // bignum (32 bit only, can not really be used here
+ Size = 1+1+4; // size,sign,byte*4
+ }
+ else {
+ Size = 8; // NEW_FLOAT
+ }
+ break;
+ case 'object':
+ switch (Obj.type) {
+ case 'Atom':
+ Size = this.atom_size(Obj.value);
+ break;
+ case 'Binary':
+ Size = 4 + Obj.value.byteLength;
+ break;
+ case 'Tuple':
+ Size = this.byte_size_tuple(Obj);
+ break;
+ default:
+ if (Obj.constructor.toString().indexOf("Array") !== -1)
+ Size = this.byte_size_array(Obj);
+ else
+ Size = this.byte_size_associative_array(Obj);
+ break;
+ }
+ break;
+ default:
+ throw ("bad object: " + Obj);
+ }
+ return 1+Size;
+}
+
+EiClass.prototype.atom_size = function (Name) {
+ if ((Name.length < 256) && this.use_small_atom)
+ return 1+Name.length;
+ else
+ return 2+Name.length;
+}
+
+EiClass.prototype.byte_size_tuple = function (Obj) {
+ var i;
+ var Size = (Obj.length < 256) ? 1 : 4;
+ for (i = 0; i < Obj.length; i++)
+ Size += this.byte_size_term(Obj.value[i]);
+ return Size;
+}
+
+EiClass.prototype.byte_size_array = function (Obj) {
+ var len = Obj.length;
+ if (len == 0)
+ return 1; // nil only
+ else {
+ var i;
+ var Size = 4; // length-bytes
+ for (i = 0; i < len; i++)
+ Size += this.byte_size_term(Obj[i]);
+ Size +=1; // nil byte
+ return Size;
+ }
+}
+
+EiClass.prototype.byte_size_associative_array = function (Obj) {
+ var key, Size = 0, N = 0;
+
+ for (key in Obj) {
+ if (Obj.hasOwnProperty(key)) {
+ var len = key.length;
+ klen = (len < 256) ? 1+len : 2+len;
+ Size += 1+klen; // (small)atom
+ Size += this.byte_size_term(Obj[key]);
+ N++;
+ }
+ }
+ if (N == 0) {
+ if (!this.use_map)
+ Size = 4; // map size
+ else
+ Size = 0 // nil
+ }
+ else {
+ if (!this.use_map)
+ Size += N*2; // add N small tuples
+ Size += 4; // list or map size
+ }
+ return Size;
+}
+
+// encode_xxx
+// encode the term into Array Buffer
+
+EiClass.prototype.encode = function (Obj) {
+ var sz = this.byte_size(Obj);
+ var ab = new ArrayBuffer(sz);
+ var dv = new DataView(ab);
+ dv.setUint8(0, this.MAGIC);
+ this.encode_term(Obj, dv, 1);
+ return ab;
+};
+
+EiClass.prototype.encode_term = function (Obj,dv,pos) {
+ switch(typeof(Obj)) {
+ case 'string':
+ if (Obj.length <= 65535) {
+ dv.setUint8(pos++, this.STRING);
+ dv.setUint16(pos, Obj.length);
+ pos += 2;
+ pos = this.string_to_bytes(Obj,dv,pos,Obj.length);
+ }
+ else {
+ dv.setUint8(pos++, this.LIST);
+ dv.setUint32(pos, Obj.length);
+ pos += 4;
+ for (i = 0; i < Obj.length; i++) {
+ dv.setUint8(pos++, this.SMALL_INTEGER);
+ dv.setUint8(pos++, Obj.charCodeAt(i));
+ }
+ dv.setUint8(pos++, this.NIL);
+ }
+ break;
+
+ case 'boolean':
+ if (Obj)
+ pos = this.encode_atom_string("true",dv,pos);
+ else
+ pos = this.encode_atom_string("false",dv,pos);
+ break;
+
+ case 'number':
+ if ((Obj % 1 === 0)) { // integer
+ if ((Obj >= 0) && (Obj < 256)) {
+ dv.setUint8(pos, this.SMALL_INTEGER);
+ dv.setUint8(pos+1, Obj);
+ pos += 2;
+ }
+ else if ((Obj >= -134217728) && (Obj <= 134217727)) {
+ dv.setUint8(pos, this.INTEGER);
+ dv.setInt32(pos+1, Obj);
+ pos += 5;
+ }
+ else if (Obj < 0) {
+ dv.setUint8(pos, this.SMALL_BIG);
+ dv.setUint8(pos+1, 4);
+ dv.setUint8(pos+2, 0); // negative
+ // little endian encoded digits!
+ dv.setUint32(pos+3, -Obj, true);
+ pos += 7;
+ }
+ else {
+ dv.setUint8(pos, this.SMALL_BIG);
+ dv.setUint8(pos+1, 4);
+ dv.setUint8(pos+2, 1);
+ // little endian encoded digits!
+ dv.setUint32(pos+3, Obj, true);
+ pos += 7;
+ }
+ }
+ else {
+ dv.setUint8(pos, this.NEW_FLOAT);
+ dv.setFloat64(pos+1, Obj, false); // store float as big endian 64
+ pos += 9;
+ }
+ break;
+
+ case 'object':
+ switch(Obj.type) {
+ case 'Atom':
+ pos = this.encode_atom_string(Obj.value,dv,pos);
+ break;
+
+ case 'Binary': { // Obj.value MUST be an Uint8Array!
+ var i, len = Obj.value.byteLength;
+ dv.setUint8(pos, this.BINARY);
+ dv.setUint32(pos+1, len, false);
+ pos += 5;
+ for (i = 0; i < len; i++, pos++)
+ dv.setUint8(pos, Obj.value[i]);
+ break;
+ }
+
+ case 'Tuple':
+ pos = this.encode_tuple(Obj,dv,pos);
+ break;
+ default:
+ if (Obj.constructor.toString().indexOf("Array") !== -1)
+ pos = this.encode_array(Obj,dv,pos);
+ else
+ pos = this.encode_associative_array(Obj,dv,pos);
+ }
+ break;
+
+ default:
+ throw ("bad object: " + Obj);
+ }
+ return pos;
+};
+
+EiClass.prototype.encode_atom_string = function (Name,dv,pos) {
+ var len = Name.length;
+ var i;
+ if ((len < 256) && this.use_small_atom) {
+ dv.setUint8(pos, this.SMALL_ATOM);
+ dv.setUint8(pos+1, len);
+ pos += 2;
+ }
+ else if (len < 65536) {
+ dv.setUint8(pos, this.ATOM);
+ dv.setUint16(pos+1, len, false);
+ pos += 3;
+ }
+ else
+ throw("bad atom: too big");
+ return this.string_to_bytes(Name,dv,pos,len);
+}
+
+
+EiClass.prototype.encode_tuple = function (Obj,dv,pos) {
+ var i;
+ var N = Obj.length;
+ if (N < 256) {
+ dv.setUint8(pos, this.SMALL_TUPLE);
+ dv.setUint8(pos+1, N);
+ pos += 2;
+ } else {
+ dv.setUint8(pos, this.LARGE_TUPLE);
+ dv.setUint32(pos+1, N, false);
+ pos += 5;
+ }
+ for (i = 0; i < N; i++)
+ pos = this.encode_term(Obj.value[i],dv,pos);
+ return pos;
+};
+
+EiClass.prototype.encode_array = function (Obj,dv,pos) {
+ var N = Obj.length;
+ if (N > 0) {
+ dv.setUint8(pos, this.LIST);
+ dv.setUint32(pos+1, N, false);
+ pos += 5;
+ for (var i = 0; i < N; i++)
+ pos = this.encode_term(Obj[i],dv,pos);
+ }
+ dv.setUint8(pos,this.NIL);
+ return pos+1;
+};
+
+// count number of keys
+EiClass.prototype.assoc_num_keys = function (Obj) {
+ if (!Object.keys) {
+ var N = 0;
+ var key;
+ for (key in Obj) {
+ if (Obj.hasOwnProperty(key))
+ N++;
+ }
+ return N;
+ }
+ return Object.keys(Obj).length;
+};
+
+
+EiClass.prototype.encode_associative_array = function (Obj,dv,pos) {
+ var N = this.assoc_num_keys(Obj);
+ if (N > 0) {
+ var key;
+ dv.setUint8(pos, this.use_map ? this.MAP : this.LIST);
+ dv.setUint32(pos+1,N,false);
+ pos += 5
+ for (key in Obj) {
+ if (Obj.hasOwnProperty(key)) {
+ if (!this.use_map) {
+ dv.setUint8(pos++, this.SMALL_TUPLE);
+ dv.setUint8(pos++, 2);
+ }
+ pos = this.encode_atom_string(key,dv,pos);
+ pos = this.encode_term(Obj[key],dv,pos);
+ }
+ }
+ }
+ if (!this.use_map)
+ dv.setUint8(pos++,this.NIL);
+ else if (this.use_map && (N == 0)) { // special case for empty map
+ dv.setUint8(pos, this.MAP);
+ dv.setUint32(pos+1,0,false);
+ pos += 5;
+ }
+ return pos;
+};
+
+//
+// decode_size_xxx
+//
+// Calculate the size of an external encoded term
+// in the buffer
+//
+
+EiClass.prototype.decode_size = function (ab, pos) {
+ var dv = new DataView(ab);
+ if (dv.getUint8(pos) !== this.MAGIC) {
+ throw ("badmagic");
+ }
+ return 1+this.decode_size_term(dv, pos+1);
+};
+
+EiClass.prototype.decode_size_term = function (dv,pos) {
+ var Tag = dv.getUint8(pos++);
+ var L = 0;
+
+ switch (Tag) {
+ case this.NIL: break;
+ case this.SMALL_ATOM: L = 1+dv.getUint8(pos); break;
+ case this.ATOM: L = 2+dv.getUint16(pos,false); break;
+ case this.BINARY: L = 4+dv.getUint32(pos,false); break;
+ case this.SMALL_INTEGER: L = 1; break;
+ case this.INTEGER: L = 4; break;
+ case this.SMALL_BIG: L = 1+dv.getUint8(pos); break;
+ case this.LARGE_BIG: L = 4+dv.getUint32(pos,false); break;
+ case this.FLOAT: L = 31; break;
+ case this.NEW_FLOAT: L = 8; break;
+ case this.STRING: L = 2+dv.getUint16(pos,false); break;
+ case this.LIST:
+ L = this.decode_size_seq(dv,pos+4,dv.getUint32(pos,false)+1,4);
+ break;
+ case this.SMALL_TUPLE:
+ L = this.decode_size_seq(dv,pos+1,dv.getUint8(pos),1);
+ break;
+ case this.LARGE_TUPLE:
+ L = this.decode_size_seq(dv,pos+4,dv.getUint32(pos,false),4);
+ break;
+ case this.MAP:
+ L = this.decode_size_seq(dv,pos+4,2*dv.getUint32(pos,false),4);
+ break;
+ default: throw ("bad tag: " + Tag);
+ }
+ return L+1;
+};
+
+EiClass.prototype.decode_size_seq = function (dv,pos,len,Size) {
+ var i;
+ for (i = 0; i < len; i++) {
+ var k = this.decode_size_term(dv,pos);
+ pos += k;
+ Size += k;
+ }
+ return Size;
+}
+
+//
+// Decode the Array Buffer
+//
+
+EiClass.prototype.decode = function (ab, pos) {
+ var dv = new DataView(ab);
+ if (dv.getUint8(pos) !== this.MAGIC) {
+ throw ("badmagic");
+ }
+ return this.decode_term(dv, pos+1);
+};
+
+EiClass.prototype.decode_term = function (dv,pos) {
+ var R,Tag = dv.getUint8(pos++);
+
+ switch (Tag) {
+ case this.NIL:
+ R = []; break;
+ case this.SMALL_ATOM:
+ R = this.decode_atom_bytes(dv,pos+1,dv.getUint8(pos)); break;
+ case this.ATOM:
+ R = this.decode_atom_bytes(dv,pos+2,dv.getUint16(pos,false)); break;
+ case this.BINARY:
+ R = this.decode_binary(dv,pos); break;
+ case this.SMALL_INTEGER:
+ R = dv.getUint8(pos); break;
+ case this.INTEGER:
+ R = dv.getInt32(pos,false); break;
+ case this.SMALL_BIG:
+ R = this.decode_big_bytes(dv,pos+1,dv.getUint8(pos)); break;
+ case this.LARGE_BIG:
+ R = this.decode_big_bytes(dv,pos+4,dv.getUint32(pos,false)); break;
+ case this.FLOAT:
+ R = parseFloat(this.bytes_to_string(dv,pos,31)); break;
+ case this.NEW_FLOAT:
+ R = dv.getFloat64(pos, false); break;
+ case this.STRING:
+ R = this.bytes_to_string(dv,pos+2,dv.getUint16(pos,false)); break;
+ case this.LIST:
+ R = this.decode_list(dv,pos); break;
+ case this.SMALL_TUPLE:
+ R = this.decode_tuple(dv,pos+1,dv.getUint8(pos)); break;
+ case this.LARGE_TUPLE:
+ R = this.decode_tuple(dv,pos+4,dv.getUint32(pos,false)); break;
+ case this.MAP:
+ R = this.decode_map(dv,pos+4,dv.getUint32(pos,false)); break;
+ default:
+ throw ("badtag: " + Tag);
+ }
+ // console.debug("decode_term = " + R);
+ return R;
+};
+
+EiClass.prototype.decode_atom_bytes = function (dv,pos,len) {
+ var S = this.bytes_to_string(dv,pos,len);
+ if (S === "true")
+ return true;
+ else if (S === "false")
+ return false;
+ return new this.atom(S);
+};
+
+EiClass.prototype.decode_binary = function (dv,pos) {
+ var Size = dv.getUint32(pos, false);
+ var Bin = new Uint8Array(dv.buffer, pos+4, Size);
+ return this.binary(Bin);
+};
+
+EiClass.prototype.decode_big_bytes = function (dv,pos,len) {
+ var Sign = dv.getUint8(pos++);
+ if (len == 4) {
+ var Num = dv.getUint32(pos,false);
+ if (Sign) return -Num;
+ return Num;
+ }
+ throw ("bad number");
+};
+
+//
+// Special hack for argument lists ['array',64,65,66,67]
+// to separate this in the erlang term_to_binary from a string!
+// also make sure [array|T] is encoded as [array,array|T]
+//
+EiClass.prototype.decode_list = function (dv,pos) {
+ var len, i, Arr = [];
+
+ len = dv.getUint32(pos, false);
+ pos += 4;
+ // console.debug("decode_list len=" + len);
+ for (i = 0; i < len; i++) {
+ var Term = this.decode_term(dv,pos);
+ var k = this.decode_size_term(dv,pos);
+ //console.debug("term (tag="+dv.getUint8(pos)+"["+ Term+ "] size = "+k);
+ pos += k;
+ if (!((i==0) && this.eqAtom(Term, "array")))
+ Arr.push(Term);
+ }
+ // console.debug("decode_list end");
+ if (dv.getUint8(pos) != this.NIL) { // improper list not allowed
+ throw ("List does not end with NIL! [tag="+dv.getUint8(pos)+"]");
+ }
+ return Arr;
+};
+
+EiClass.prototype.decode_tuple = function (dv,pos,len) {
+ var i, Arr = [];
+ for (i = 0; i < len; i++) {
+ var Term = this.decode_term(dv,pos);
+ pos += this.decode_size_term(dv,pos);
+ Arr.push(Term);
+ }
+ return new EiTuple(Arr);
+};
+
+
+EiClass.prototype.decode_map = function (dv,pos,len) {
+ var key, value, i, Obj = new Object();
+ for (i = 0; i < len; i++) {
+ key = this.decode_term(dv,pos);
+ pos += this.decode_size_term(dv,pos);
+ value = this.decode_term(dv,pos);
+ pos += this.decode_size_term(dv,pos);
+ Obj[key.value] = value; // key must be EiAtom! fixme test!
+ }
+ return Obj;
+};
+
+
+// Convert an array of bytes into a string.
+EiClass.prototype.bytes_to_string = function (dv,pos,count) {
+ var i, s = "";
+ for (i = 0; i < count; i++,pos++)
+ s += String.fromCharCode(dv.getUint8(pos));
+ return s;
+};
+
+// Convert an array of bytes into a string.
+EiClass.prototype.string_to_bytes = function (Obj,dv,pos,count) {
+ var i;
+ for (i = 0; i < count; i++, pos++)
+ dv.setUint8(pos, Obj.charCodeAt(i));
+ return pos;
+};
+
+// Pretty Print a byte-string in Erlang binary form.
+EiClass.prototype.pp_bytes = function (Bin) {
+ var i, s = "";
+ for (i = 0; i < Bin.length; i++) {
+ if (s !== "") {
+ s += ",";
+ }
+ s += "" + Bin[i];
+ }
+ return "<<" + s + ">>";
+};
+
+// Pretty Print a JS object in Erlang term form.
+EiClass.prototype.pp = function (Obj) {
+ return Obj.toString();
+};
+
+var Ei = new EiClass();
diff --git a/deps/wse/priv/fish.html b/deps/wse/priv/fish.html
new file mode 100644
index 0000000..2ea0e0b
--- /dev/null
+++ b/deps/wse/priv/fish.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+<title>Animated fish demo</title>
+<script src='ej.js'></script>
+<script src='wse.js'></script>
+<script>
+
+window.onload = function() {
+ if (Wse.open("ws://"+(location.hostname||"localhost")+":1234/websession")) {
+ // Wse.enable_console_output(true);
+ Wse.start('wse_fish_demo', 'run', ["myid"]);
+ }
+}
+</script>
+</head>
+<body>
+<div id="myid"></div>
+</body>
+</html>
diff --git a/deps/wse/priv/fish/fish0001.png b/deps/wse/priv/fish/fish0001.png
new file mode 100644
index 0000000..42b403a
--- /dev/null
+++ b/deps/wse/priv/fish/fish0001.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0002.png b/deps/wse/priv/fish/fish0002.png
new file mode 100644
index 0000000..c526314
--- /dev/null
+++ b/deps/wse/priv/fish/fish0002.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0003.png b/deps/wse/priv/fish/fish0003.png
new file mode 100644
index 0000000..89c5dcd
--- /dev/null
+++ b/deps/wse/priv/fish/fish0003.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0004.png b/deps/wse/priv/fish/fish0004.png
new file mode 100644
index 0000000..01ffb4c
--- /dev/null
+++ b/deps/wse/priv/fish/fish0004.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0005.png b/deps/wse/priv/fish/fish0005.png
new file mode 100644
index 0000000..e8d5674
--- /dev/null
+++ b/deps/wse/priv/fish/fish0005.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0006.png b/deps/wse/priv/fish/fish0006.png
new file mode 100644
index 0000000..c811c6b
--- /dev/null
+++ b/deps/wse/priv/fish/fish0006.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0007.png b/deps/wse/priv/fish/fish0007.png
new file mode 100644
index 0000000..de0ee78
--- /dev/null
+++ b/deps/wse/priv/fish/fish0007.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0008.png b/deps/wse/priv/fish/fish0008.png
new file mode 100644
index 0000000..77794a8
--- /dev/null
+++ b/deps/wse/priv/fish/fish0008.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0009.png b/deps/wse/priv/fish/fish0009.png
new file mode 100644
index 0000000..7cbfc30
--- /dev/null
+++ b/deps/wse/priv/fish/fish0009.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0010.png b/deps/wse/priv/fish/fish0010.png
new file mode 100644
index 0000000..81db776
--- /dev/null
+++ b/deps/wse/priv/fish/fish0010.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0011.png b/deps/wse/priv/fish/fish0011.png
new file mode 100644
index 0000000..be3df4e
--- /dev/null
+++ b/deps/wse/priv/fish/fish0011.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0012.png b/deps/wse/priv/fish/fish0012.png
new file mode 100644
index 0000000..7dad219
--- /dev/null
+++ b/deps/wse/priv/fish/fish0012.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0013.png b/deps/wse/priv/fish/fish0013.png
new file mode 100644
index 0000000..b8b899c
--- /dev/null
+++ b/deps/wse/priv/fish/fish0013.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0014.png b/deps/wse/priv/fish/fish0014.png
new file mode 100644
index 0000000..30ca6db
--- /dev/null
+++ b/deps/wse/priv/fish/fish0014.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0015.png b/deps/wse/priv/fish/fish0015.png
new file mode 100644
index 0000000..ab1d4b6
--- /dev/null
+++ b/deps/wse/priv/fish/fish0015.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0016.png b/deps/wse/priv/fish/fish0016.png
new file mode 100644
index 0000000..67567b4
--- /dev/null
+++ b/deps/wse/priv/fish/fish0016.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0017.png b/deps/wse/priv/fish/fish0017.png
new file mode 100644
index 0000000..a615fd2
--- /dev/null
+++ b/deps/wse/priv/fish/fish0017.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0018.png b/deps/wse/priv/fish/fish0018.png
new file mode 100644
index 0000000..06940cc
--- /dev/null
+++ b/deps/wse/priv/fish/fish0018.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0019.png b/deps/wse/priv/fish/fish0019.png
new file mode 100644
index 0000000..c13a0c7
--- /dev/null
+++ b/deps/wse/priv/fish/fish0019.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0020.png b/deps/wse/priv/fish/fish0020.png
new file mode 100644
index 0000000..8610f32
--- /dev/null
+++ b/deps/wse/priv/fish/fish0020.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0021.png b/deps/wse/priv/fish/fish0021.png
new file mode 100644
index 0000000..c3893be
--- /dev/null
+++ b/deps/wse/priv/fish/fish0021.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0022.png b/deps/wse/priv/fish/fish0022.png
new file mode 100644
index 0000000..8c30ed4
--- /dev/null
+++ b/deps/wse/priv/fish/fish0022.png
Binary files differ
diff --git a/deps/wse/priv/fish/fish0023.png b/deps/wse/priv/fish/fish0023.png
new file mode 100644
index 0000000..725b3fd
--- /dev/null
+++ b/deps/wse/priv/fish/fish0023.png
Binary files differ
diff --git a/deps/wse/priv/frame1.html b/deps/wse/priv/frame1.html
new file mode 100644
index 0000000..56c09c1
--- /dev/null
+++ b/deps/wse/priv/frame1.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<title>Frame</title>
+<script src='ej.js'></script>
+<script src='wse.js'></script>
+<script>
+
+window.onload = function() {
+ if (Wse.open("ws://"+(location.hostname||"localhost")+":1234/websession")) {
+ Wse.id = 'w1';
+ Wse.start('wse_frame_demo', 'run', ["i1"]);
+ }
+}
+</script>
+</head>
+<body>
+<h2>Frame1</h2>
+<div id="i1" width=620 height=170></div>
+</body>
+</html>
diff --git a/deps/wse/priv/frame2.html b/deps/wse/priv/frame2.html
new file mode 100644
index 0000000..fdece19
--- /dev/null
+++ b/deps/wse/priv/frame2.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<title>Frame</title>
+<script src='ej.js'></script>
+<script src='wse.js'></script>
+<script>
+
+window.onload = function() {
+ if (Wse.open("ws://"+(location.hostname||"localhost")+":1235/websession")) {
+ Wse.id = 'w2';
+ Wse.start('wse_frame_demo', 'run', ["i2"]);
+ }
+}
+</script>
+</head>
+<body>
+<h2>Frame2</h2>
+<div id="i2" width=620 height=170></div>
+</body>
+</html>
diff --git a/deps/wse/priv/frame_demo.html b/deps/wse/priv/frame_demo.html
new file mode 100644
index 0000000..3519ba8
--- /dev/null
+++ b/deps/wse/priv/frame_demo.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+<title>Frame demo page</title>
+
+<script src='ej.js'></script>
+<script src='wse.js'></script>
+<script>
+<!-- port numbers used main=1233, frame1=1234, frame2=1235 -->
+window.onload = function() {
+ if (Wse.open("ws://"+(location.hostname||"localhost")+":1233/websession")) {
+ Wse.id = 'w0';
+ Wse.start('wse_frame_demo', 'run', ["i0"]);
+ }
+}
+</script>
+</head>
+
+<body>
+<iframe id="f1" src="frame1.html" width=640 height=180></iframe>
+<iframe id="f2" src="frame2.html" width=640 height=180></iframe>
+<div id="i0"></div>
+</body>
+</html>
diff --git a/deps/wse/priv/location.html b/deps/wse/priv/location.html
new file mode 100644
index 0000000..b8eb3df
--- /dev/null
+++ b/deps/wse/priv/location.html
@@ -0,0 +1,30 @@
+<html>
+<head>
+<title>Location demo page</title>
+<script src='ej.js'></script>
+<script src='wse.js'></script>
+<script>
+
+window.onload = function() {
+ if (Wse.open("ws://"+(location.hostname||"localhost")+":1234/websession")) {
+ Wse.start('wse_location_demo', 'run', ["location"]);
+ }
+ else
+ alert("WebSockets not supported");
+};
+
+</script>
+</head>
+<body style="background: #333">
+ <input name="webpage" placeholder="webpage" type="text" id="webpage"/>
+ <button onclick="goto()">Goto</button>
+<script>
+ function goto() {
+ var location = document.getElementById("webpage").value;
+ window.console.debug("Goto " +location);
+ Wse.call('wse_location_demo', 'goto', [location]);
+}
+</script>
+
+</body>
+</html>
diff --git a/deps/wse/priv/locationX.html b/deps/wse/priv/locationX.html
new file mode 100644
index 0000000..5fb4645
--- /dev/null
+++ b/deps/wse/priv/locationX.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Location X demo page</title>
+</head>
+<body style="background: #333">
+ <p> Location X </p>
+
+</body>
+</html>
diff --git a/deps/wse/priv/page_a.html b/deps/wse/priv/page_a.html
new file mode 100644
index 0000000..25fa08e
--- /dev/null
+++ b/deps/wse/priv/page_a.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<title>Page A</title>
+<script src='ej.js'></script>
+<script src='wse.js'></script>
+<script>
+
+window.onload = function() {
+ if (Wse.open("ws://"+(location.hostname||"localhost")+":1234/websession")) {
+ // Wse.enable_console_output(true);
+ Wse.register('page_a');
+ }
+};
+
+</script>
+</head>
+<body>
+<p id="x">Hello A world</p>
+</body>
+</html>
diff --git a/deps/wse/priv/page_b.html b/deps/wse/priv/page_b.html
new file mode 100644
index 0000000..589da7e
--- /dev/null
+++ b/deps/wse/priv/page_b.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<title>Page B</title>
+<script src='ej.js'></script>
+<script src='wse.js'></script>
+<script>
+
+window.onload = function() {
+ if (Wse.open("ws://"+(location.hostname||"localhost")+":1234/websession")) {
+ // Wse.enable_console_output(true);
+ Wse.register('page_b');
+ }
+};
+
+</script>
+</head>
+<body>
+<p id="x">Hello B World</p>
+</body>
+</html>
diff --git a/deps/wse/priv/raphael-min.js b/deps/wse/priv/raphael-min.js
new file mode 100644
index 0000000..404f8b2
--- /dev/null
+++ b/deps/wse/priv/raphael-min.js
@@ -0,0 +1,11 @@
+// ┌────────────────────────────────────────────────────────────────────┠\\
+// │ Raphaël 2.1.2 - JavaScript Vector Library │ \\
+// ├────────────────────────────────────────────────────────────────────┤ \\
+// │ Copyright © 2008-2012 Dmitry Baranovskiy (http://raphaeljs.com) │ \\
+// │ Copyright © 2008-2012 Sencha Labs (http://sencha.com) │ \\
+// ├────────────────────────────────────────────────────────────────────┤ \\
+// │ Licensed under the MIT (http://raphaeljs.com/license.html) license.│ \\
+// └────────────────────────────────────────────────────────────────────┘ \\
+!function(a){var b,c,d="0.4.2",e="hasOwnProperty",f=/[\.\/]/,g="*",h=function(){},i=function(a,b){return a-b},j={n:{}},k=function(a,d){a=String(a);var e,f=c,g=Array.prototype.slice.call(arguments,2),h=k.listeners(a),j=0,l=[],m={},n=[],o=b;b=a,c=0;for(var p=0,q=h.length;q>p;p++)"zIndex"in h[p]&&(l.push(h[p].zIndex),h[p].zIndex<0&&(m[h[p].zIndex]=h[p]));for(l.sort(i);l[j]<0;)if(e=m[l[j++]],n.push(e.apply(d,g)),c)return c=f,n;for(p=0;q>p;p++)if(e=h[p],"zIndex"in e)if(e.zIndex==l[j]){if(n.push(e.apply(d,g)),c)break;do if(j++,e=m[l[j]],e&&n.push(e.apply(d,g)),c)break;while(e)}else m[e.zIndex]=e;else if(n.push(e.apply(d,g)),c)break;return c=f,b=o,n.length?n:null};k._events=j,k.listeners=function(a){var b,c,d,e,h,i,k,l,m=a.split(f),n=j,o=[n],p=[];for(e=0,h=m.length;h>e;e++){for(l=[],i=0,k=o.length;k>i;i++)for(n=o[i].n,c=[n[m[e]],n[g]],d=2;d--;)b=c[d],b&&(l.push(b),p=p.concat(b.f||[]));o=l}return p},k.on=function(a,b){if(a=String(a),"function"!=typeof b)return function(){};for(var c=a.split(f),d=j,e=0,g=c.length;g>e;e++)d=d.n,d=d.hasOwnProperty(c[e])&&d[c[e]]||(d[c[e]]={n:{}});for(d.f=d.f||[],e=0,g=d.f.length;g>e;e++)if(d.f[e]==b)return h;return d.f.push(b),function(a){+a==+a&&(b.zIndex=+a)}},k.f=function(a){var b=[].slice.call(arguments,1);return function(){k.apply(null,[a,null].concat(b).concat([].slice.call(arguments,0)))}},k.stop=function(){c=1},k.nt=function(a){return a?new RegExp("(?:\\.|\\/|^)"+a+"(?:\\.|\\/|$)").test(b):b},k.nts=function(){return b.split(f)},k.off=k.unbind=function(a,b){if(!a)return k._events=j={n:{}},void 0;var c,d,h,i,l,m,n,o=a.split(f),p=[j];for(i=0,l=o.length;l>i;i++)for(m=0;m<p.length;m+=h.length-2){if(h=[m,1],c=p[m].n,o[i]!=g)c[o[i]]&&h.push(c[o[i]]);else for(d in c)c[e](d)&&h.push(c[d]);p.splice.apply(p,h)}for(i=0,l=p.length;l>i;i++)for(c=p[i];c.n;){if(b){if(c.f){for(m=0,n=c.f.length;n>m;m++)if(c.f[m]==b){c.f.splice(m,1);break}!c.f.length&&delete c.f}for(d in c.n)if(c.n[e](d)&&c.n[d].f){var q=c.n[d].f;for(m=0,n=q.length;n>m;m++)if(q[m]==b){q.splice(m,1);break}!q.length&&delete c.n[d].f}}else{delete c.f;for(d in c.n)c.n[e](d)&&c.n[d].f&&delete c.n[d].f}c=c.n}},k.once=function(a,b){var c=function(){return k.unbind(a,c),b.apply(this,arguments)};return k.on(a,c)},k.version=d,k.toString=function(){return"You are running Eve "+d},"undefined"!=typeof module&&module.exports?module.exports=k:"undefined"!=typeof define?define("eve",[],function(){return k}):a.eve=k}(this),function(a,b){"function"==typeof define&&define.amd?define(["eve"],function(c){return b(a,c)}):b(a,a.eve)}(this,function(a,b){function c(a){if(c.is(a,"function"))return u?a():b.on("raphael.DOMload",a);if(c.is(a,V))return c._engine.create[D](c,a.splice(0,3+c.is(a[0],T))).add(a);var d=Array.prototype.slice.call(arguments,0);if(c.is(d[d.length-1],"function")){var e=d.pop();return u?e.call(c._engine.create[D](c,d)):b.on("raphael.DOMload",function(){e.call(c._engine.create[D](c,d))})}return c._engine.create[D](c,arguments)}function d(a){if("function"==typeof a||Object(a)!==a)return a;var b=new a.constructor;for(var c in a)a[z](c)&&(b[c]=d(a[c]));return b}function e(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return a.push(a.splice(c,1)[0])}function f(a,b,c){function d(){var f=Array.prototype.slice.call(arguments,0),g=f.join("â€"),h=d.cache=d.cache||{},i=d.count=d.count||[];return h[z](g)?(e(i,g),c?c(h[g]):h[g]):(i.length>=1e3&&delete h[i.shift()],i.push(g),h[g]=a[D](b,f),c?c(h[g]):h[g])}return d}function g(){return this.hex}function h(a,b){for(var c=[],d=0,e=a.length;e-2*!b>d;d+=2){var f=[{x:+a[d-2],y:+a[d-1]},{x:+a[d],y:+a[d+1]},{x:+a[d+2],y:+a[d+3]},{x:+a[d+4],y:+a[d+5]}];b?d?e-4==d?f[3]={x:+a[0],y:+a[1]}:e-2==d&&(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[e-2],y:+a[e-1]}:e-4==d?f[3]=f[2]:d||(f[0]={x:+a[d],y:+a[d+1]}),c.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}return c}function i(a,b,c,d,e){var f=-3*b+9*c-9*d+3*e,g=a*f+6*b-12*c+6*d;return a*g-3*b+3*c}function j(a,b,c,d,e,f,g,h,j){null==j&&(j=1),j=j>1?1:0>j?0:j;for(var k=j/2,l=12,m=[-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],n=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],o=0,p=0;l>p;p++){var q=k*m[p]+k,r=i(q,a,c,e,g),s=i(q,b,d,f,h),t=r*r+s*s;o+=n[p]*N.sqrt(t)}return k*o}function k(a,b,c,d,e,f,g,h,i){if(!(0>i||j(a,b,c,d,e,f,g,h)<i)){var k,l=1,m=l/2,n=l-m,o=.01;for(k=j(a,b,c,d,e,f,g,h,n);Q(k-i)>o;)m/=2,n+=(i>k?1:-1)*m,k=j(a,b,c,d,e,f,g,h,n);return n}}function l(a,b,c,d,e,f,g,h){if(!(O(a,c)<P(e,g)||P(a,c)>O(e,g)||O(b,d)<P(f,h)||P(b,d)>O(f,h))){var i=(a*d-b*c)*(e-g)-(a-c)*(e*h-f*g),j=(a*d-b*c)*(f-h)-(b-d)*(e*h-f*g),k=(a-c)*(f-h)-(b-d)*(e-g);if(k){var l=i/k,m=j/k,n=+l.toFixed(2),o=+m.toFixed(2);if(!(n<+P(a,c).toFixed(2)||n>+O(a,c).toFixed(2)||n<+P(e,g).toFixed(2)||n>+O(e,g).toFixed(2)||o<+P(b,d).toFixed(2)||o>+O(b,d).toFixed(2)||o<+P(f,h).toFixed(2)||o>+O(f,h).toFixed(2)))return{x:l,y:m}}}}function m(a,b,d){var e=c.bezierBBox(a),f=c.bezierBBox(b);if(!c.isBBoxIntersect(e,f))return d?0:[];for(var g=j.apply(0,a),h=j.apply(0,b),i=O(~~(g/5),1),k=O(~~(h/5),1),m=[],n=[],o={},p=d?0:[],q=0;i+1>q;q++){var r=c.findDotsAtSegment.apply(c,a.concat(q/i));m.push({x:r.x,y:r.y,t:q/i})}for(q=0;k+1>q;q++)r=c.findDotsAtSegment.apply(c,b.concat(q/k)),n.push({x:r.x,y:r.y,t:q/k});for(q=0;i>q;q++)for(var s=0;k>s;s++){var t=m[q],u=m[q+1],v=n[s],w=n[s+1],x=Q(u.x-t.x)<.001?"y":"x",y=Q(w.x-v.x)<.001?"y":"x",z=l(t.x,t.y,u.x,u.y,v.x,v.y,w.x,w.y);if(z){if(o[z.x.toFixed(4)]==z.y.toFixed(4))continue;o[z.x.toFixed(4)]=z.y.toFixed(4);var A=t.t+Q((z[x]-t[x])/(u[x]-t[x]))*(u.t-t.t),B=v.t+Q((z[y]-v[y])/(w[y]-v[y]))*(w.t-v.t);A>=0&&1.001>=A&&B>=0&&1.001>=B&&(d?p++:p.push({x:z.x,y:z.y,t1:P(A,1),t2:P(B,1)}))}}return p}function n(a,b,d){a=c._path2curve(a),b=c._path2curve(b);for(var e,f,g,h,i,j,k,l,n,o,p=d?0:[],q=0,r=a.length;r>q;q++){var s=a[q];if("M"==s[0])e=i=s[1],f=j=s[2];else{"C"==s[0]?(n=[e,f].concat(s.slice(1)),e=n[6],f=n[7]):(n=[e,f,e,f,i,j,i,j],e=i,f=j);for(var t=0,u=b.length;u>t;t++){var v=b[t];if("M"==v[0])g=k=v[1],h=l=v[2];else{"C"==v[0]?(o=[g,h].concat(v.slice(1)),g=o[6],h=o[7]):(o=[g,h,g,h,k,l,k,l],g=k,h=l);var w=m(n,o,d);if(d)p+=w;else{for(var x=0,y=w.length;y>x;x++)w[x].segment1=q,w[x].segment2=t,w[x].bez1=n,w[x].bez2=o;p=p.concat(w)}}}}}return p}function o(a,b,c,d,e,f){null!=a?(this.a=+a,this.b=+b,this.c=+c,this.d=+d,this.e=+e,this.f=+f):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0)}function p(){return this.x+H+this.y+H+this.width+" × "+this.height}function q(a,b,c,d,e,f){function g(a){return((l*a+k)*a+j)*a}function h(a,b){var c=i(a,b);return((o*c+n)*c+m)*c}function i(a,b){var c,d,e,f,h,i;for(e=a,i=0;8>i;i++){if(f=g(e)-a,Q(f)<b)return e;if(h=(3*l*e+2*k)*e+j,Q(h)<1e-6)break;e-=f/h}if(c=0,d=1,e=a,c>e)return c;if(e>d)return d;for(;d>c;){if(f=g(e),Q(f-a)<b)return e;a>f?c=e:d=e,e=(d-c)/2+c}return e}var j=3*b,k=3*(d-b)-j,l=1-j-k,m=3*c,n=3*(e-c)-m,o=1-m-n;return h(a,1/(200*f))}function r(a,b){var c=[],d={};if(this.ms=b,this.times=1,a){for(var e in a)a[z](e)&&(d[_(e)]=a[e],c.push(_(e)));c.sort(lb)}this.anim=d,this.top=c[c.length-1],this.percents=c}function s(a,d,e,f,g,h){e=_(e);var i,j,k,l,m,n,p=a.ms,r={},s={},t={};if(f)for(v=0,x=ic.length;x>v;v++){var u=ic[v];if(u.el.id==d.id&&u.anim==a){u.percent!=e?(ic.splice(v,1),k=1):j=u,d.attr(u.totalOrigin);break}}else f=+s;for(var v=0,x=a.percents.length;x>v;v++){if(a.percents[v]==e||a.percents[v]>f*a.top){e=a.percents[v],m=a.percents[v-1]||0,p=p/a.top*(e-m),l=a.percents[v+1],i=a.anim[e];break}f&&d.attr(a.anim[a.percents[v]])}if(i){if(j)j.initstatus=f,j.start=new Date-j.ms*f;else{for(var y in i)if(i[z](y)&&(db[z](y)||d.paper.customAttributes[z](y)))switch(r[y]=d.attr(y),null==r[y]&&(r[y]=cb[y]),s[y]=i[y],db[y]){case T:t[y]=(s[y]-r[y])/p;break;case"colour":r[y]=c.getRGB(r[y]);var A=c.getRGB(s[y]);t[y]={r:(A.r-r[y].r)/p,g:(A.g-r[y].g)/p,b:(A.b-r[y].b)/p};break;case"path":var B=Kb(r[y],s[y]),C=B[1];for(r[y]=B[0],t[y]=[],v=0,x=r[y].length;x>v;v++){t[y][v]=[0];for(var D=1,F=r[y][v].length;F>D;D++)t[y][v][D]=(C[v][D]-r[y][v][D])/p}break;case"transform":var G=d._,H=Pb(G[y],s[y]);if(H)for(r[y]=H.from,s[y]=H.to,t[y]=[],t[y].real=!0,v=0,x=r[y].length;x>v;v++)for(t[y][v]=[r[y][v][0]],D=1,F=r[y][v].length;F>D;D++)t[y][v][D]=(s[y][v][D]-r[y][v][D])/p;else{var K=d.matrix||new o,L={_:{transform:G.transform},getBBox:function(){return d.getBBox(1)}};r[y]=[K.a,K.b,K.c,K.d,K.e,K.f],Nb(L,s[y]),s[y]=L._.transform,t[y]=[(L.matrix.a-K.a)/p,(L.matrix.b-K.b)/p,(L.matrix.c-K.c)/p,(L.matrix.d-K.d)/p,(L.matrix.e-K.e)/p,(L.matrix.f-K.f)/p]}break;case"csv":var M=I(i[y])[J](w),N=I(r[y])[J](w);if("clip-rect"==y)for(r[y]=N,t[y]=[],v=N.length;v--;)t[y][v]=(M[v]-r[y][v])/p;s[y]=M;break;default:for(M=[][E](i[y]),N=[][E](r[y]),t[y]=[],v=d.paper.customAttributes[y].length;v--;)t[y][v]=((M[v]||0)-(N[v]||0))/p}var O=i.easing,P=c.easing_formulas[O];if(!P)if(P=I(O).match(Z),P&&5==P.length){var Q=P;P=function(a){return q(a,+Q[1],+Q[2],+Q[3],+Q[4],p)}}else P=nb;if(n=i.start||a.start||+new Date,u={anim:a,percent:e,timestamp:n,start:n+(a.del||0),status:0,initstatus:f||0,stop:!1,ms:p,easing:P,from:r,diff:t,to:s,el:d,callback:i.callback,prev:m,next:l,repeat:h||a.times,origin:d.attr(),totalOrigin:g},ic.push(u),f&&!j&&!k&&(u.stop=!0,u.start=new Date-p*f,1==ic.length))return kc();k&&(u.start=new Date-u.ms*f),1==ic.length&&jc(kc)}b("raphael.anim.start."+d.id,d,a)}}function t(a){for(var b=0;b<ic.length;b++)ic[b].el.paper==a&&ic.splice(b--,1)}c.version="2.1.2",c.eve=b;var u,v,w=/[, ]+/,x={circle:1,rect:1,path:1,ellipse:1,text:1,image:1},y=/\{(\d+)\}/g,z="hasOwnProperty",A={doc:document,win:a},B={was:Object.prototype[z].call(A.win,"Raphael"),is:A.win.Raphael},C=function(){this.ca=this.customAttributes={}},D="apply",E="concat",F="ontouchstart"in A.win||A.win.DocumentTouch&&A.doc instanceof DocumentTouch,G="",H=" ",I=String,J="split",K="click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel"[J](H),L={mousedown:"touchstart",mousemove:"touchmove",mouseup:"touchend"},M=I.prototype.toLowerCase,N=Math,O=N.max,P=N.min,Q=N.abs,R=N.pow,S=N.PI,T="number",U="string",V="array",W=Object.prototype.toString,X=(c._ISURL=/^url\(['"]?([^\)]+?)['"]?\)$/i,/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i),Y={NaN:1,Infinity:1,"-Infinity":1},Z=/^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,$=N.round,_=parseFloat,ab=parseInt,bb=I.prototype.toUpperCase,cb=c._availableAttrs={"arrow-end":"none","arrow-start":"none",blur:0,"clip-rect":"0 0 1e9 1e9",cursor:"default",cx:0,cy:0,fill:"#fff","fill-opacity":1,font:'10px "Arial"',"font-family":'"Arial"',"font-size":"10","font-style":"normal","font-weight":400,gradient:0,height:0,href:"http://raphaeljs.com/","letter-spacing":0,opacity:1,path:"M0,0",r:0,rx:0,ry:0,src:"",stroke:"#000","stroke-dasharray":"","stroke-linecap":"butt","stroke-linejoin":"butt","stroke-miterlimit":0,"stroke-opacity":1,"stroke-width":1,target:"_blank","text-anchor":"middle",title:"Raphael",transform:"",width:0,x:0,y:0},db=c._availableAnimAttrs={blur:T,"clip-rect":"csv",cx:T,cy:T,fill:"colour","fill-opacity":T,"font-size":T,height:T,opacity:T,path:"path",r:T,rx:T,ry:T,stroke:"colour","stroke-opacity":T,"stroke-width":T,transform:"transform",width:T,x:T,y:T},eb=/[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/,fb={hs:1,rg:1},gb=/,?([achlmqrstvxz]),?/gi,hb=/([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/gi,ib=/([rstm])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/gi,jb=/(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/gi,kb=(c._radial_gradient=/^r(?:\(([^,]+?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*([^\)]+?)\))?/,{}),lb=function(a,b){return _(a)-_(b)},mb=function(){},nb=function(a){return a},ob=c._rectPath=function(a,b,c,d,e){return e?[["M",a+e,b],["l",c-2*e,0],["a",e,e,0,0,1,e,e],["l",0,d-2*e],["a",e,e,0,0,1,-e,e],["l",2*e-c,0],["a",e,e,0,0,1,-e,-e],["l",0,2*e-d],["a",e,e,0,0,1,e,-e],["z"]]:[["M",a,b],["l",c,0],["l",0,d],["l",-c,0],["z"]]},pb=function(a,b,c,d){return null==d&&(d=c),[["M",a,b],["m",0,-d],["a",c,d,0,1,1,0,2*d],["a",c,d,0,1,1,0,-2*d],["z"]]},qb=c._getPath={path:function(a){return a.attr("path")},circle:function(a){var b=a.attrs;return pb(b.cx,b.cy,b.r)},ellipse:function(a){var b=a.attrs;return pb(b.cx,b.cy,b.rx,b.ry)},rect:function(a){var b=a.attrs;return ob(b.x,b.y,b.width,b.height,b.r)},image:function(a){var b=a.attrs;return ob(b.x,b.y,b.width,b.height)},text:function(a){var b=a._getBBox();return ob(b.x,b.y,b.width,b.height)},set:function(a){var b=a._getBBox();return ob(b.x,b.y,b.width,b.height)}},rb=c.mapPath=function(a,b){if(!b)return a;var c,d,e,f,g,h,i;for(a=Kb(a),e=0,g=a.length;g>e;e++)for(i=a[e],f=1,h=i.length;h>f;f+=2)c=b.x(i[f],i[f+1]),d=b.y(i[f],i[f+1]),i[f]=c,i[f+1]=d;return a};if(c._g=A,c.type=A.win.SVGAngle||A.doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")?"SVG":"VML","VML"==c.type){var sb,tb=A.doc.createElement("div");if(tb.innerHTML='<v:shape adj="1"/>',sb=tb.firstChild,sb.style.behavior="url(#default#VML)",!sb||"object"!=typeof sb.adj)return c.type=G;tb=null}c.svg=!(c.vml="VML"==c.type),c._Paper=C,c.fn=v=C.prototype=c.prototype,c._id=0,c._oid=0,c.is=function(a,b){return b=M.call(b),"finite"==b?!Y[z](+a):"array"==b?a instanceof Array:"null"==b&&null===a||b==typeof a&&null!==a||"object"==b&&a===Object(a)||"array"==b&&Array.isArray&&Array.isArray(a)||W.call(a).slice(8,-1).toLowerCase()==b},c.angle=function(a,b,d,e,f,g){if(null==f){var h=a-d,i=b-e;return h||i?(180+180*N.atan2(-i,-h)/S+360)%360:0}return c.angle(a,b,f,g)-c.angle(d,e,f,g)},c.rad=function(a){return a%360*S/180},c.deg=function(a){return 180*a/S%360},c.snapTo=function(a,b,d){if(d=c.is(d,"finite")?d:10,c.is(a,V)){for(var e=a.length;e--;)if(Q(a[e]-b)<=d)return a[e]}else{a=+a;var f=b%a;if(d>f)return b-f;if(f>a-d)return b-f+a}return b},c.createUUID=function(a,b){return function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(a,b).toUpperCase()}}(/[xy]/g,function(a){var b=0|16*N.random(),c="x"==a?b:8|3&b;return c.toString(16)}),c.setWindow=function(a){b("raphael.setWindow",c,A.win,a),A.win=a,A.doc=A.win.document,c._engine.initWin&&c._engine.initWin(A.win)};var ub=function(a){if(c.vml){var b,d=/^\s+|\s+$/g;try{var e=new ActiveXObject("htmlfile");e.write("<body>"),e.close(),b=e.body}catch(g){b=createPopup().document.body}var h=b.createTextRange();ub=f(function(a){try{b.style.color=I(a).replace(d,G);var c=h.queryCommandValue("ForeColor");return c=(255&c)<<16|65280&c|(16711680&c)>>>16,"#"+("000000"+c.toString(16)).slice(-6)}catch(e){return"none"}})}else{var i=A.doc.createElement("i");i.title="Raphaël Colour Picker",i.style.display="none",A.doc.body.appendChild(i),ub=f(function(a){return i.style.color=a,A.doc.defaultView.getComputedStyle(i,G).getPropertyValue("color")})}return ub(a)},vb=function(){return"hsb("+[this.h,this.s,this.b]+")"},wb=function(){return"hsl("+[this.h,this.s,this.l]+")"},xb=function(){return this.hex},yb=function(a,b,d){if(null==b&&c.is(a,"object")&&"r"in a&&"g"in a&&"b"in a&&(d=a.b,b=a.g,a=a.r),null==b&&c.is(a,U)){var e=c.getRGB(a);a=e.r,b=e.g,d=e.b}return(a>1||b>1||d>1)&&(a/=255,b/=255,d/=255),[a,b,d]},zb=function(a,b,d,e){a*=255,b*=255,d*=255;var f={r:a,g:b,b:d,hex:c.rgb(a,b,d),toString:xb};return c.is(e,"finite")&&(f.opacity=e),f};c.color=function(a){var b;return c.is(a,"object")&&"h"in a&&"s"in a&&"b"in a?(b=c.hsb2rgb(a),a.r=b.r,a.g=b.g,a.b=b.b,a.hex=b.hex):c.is(a,"object")&&"h"in a&&"s"in a&&"l"in a?(b=c.hsl2rgb(a),a.r=b.r,a.g=b.g,a.b=b.b,a.hex=b.hex):(c.is(a,"string")&&(a=c.getRGB(a)),c.is(a,"object")&&"r"in a&&"g"in a&&"b"in a?(b=c.rgb2hsl(a),a.h=b.h,a.s=b.s,a.l=b.l,b=c.rgb2hsb(a),a.v=b.b):(a={hex:"none"},a.r=a.g=a.b=a.h=a.s=a.v=a.l=-1)),a.toString=xb,a},c.hsb2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,a=a.h,d=a.o),a*=360;var e,f,g,h,i;return a=a%360/60,i=c*b,h=i*(1-Q(a%2-1)),e=f=g=c-i,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a],zb(e,f,g,d)},c.hsl2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h),(a>1||b>1||c>1)&&(a/=360,b/=100,c/=100),a*=360;var e,f,g,h,i;return a=a%360/60,i=2*b*(.5>c?c:1-c),h=i*(1-Q(a%2-1)),e=f=g=c-i/2,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a],zb(e,f,g,d)},c.rgb2hsb=function(a,b,c){c=yb(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;return f=O(a,b,c),g=f-P(a,b,c),d=0==g?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=60*((d+360)%6)/360,e=0==g?0:g/f,{h:d,s:e,b:f,toString:vb}},c.rgb2hsl=function(a,b,c){c=yb(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;return g=O(a,b,c),h=P(a,b,c),i=g-h,d=0==i?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=60*((d+360)%6)/360,f=(g+h)/2,e=0==i?0:.5>f?i/(2*f):i/(2-2*f),{h:d,s:e,l:f,toString:wb}},c._path2string=function(){return this.join(",").replace(gb,"$1")},c._preload=function(a,b){var c=A.doc.createElement("img");c.style.cssText="position:absolute;left:-9999em;top:-9999em",c.onload=function(){b.call(this),this.onload=null,A.doc.body.removeChild(this)},c.onerror=function(){A.doc.body.removeChild(this)},A.doc.body.appendChild(c),c.src=a},c.getRGB=f(function(a){if(!a||(a=I(a)).indexOf("-")+1)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:g};if("none"==a)return{r:-1,g:-1,b:-1,hex:"none",toString:g};!(fb[z](a.toLowerCase().substring(0,2))||"#"==a.charAt())&&(a=ub(a));var b,d,e,f,h,i,j=a.match(X);return j?(j[2]&&(e=ab(j[2].substring(5),16),d=ab(j[2].substring(3,5),16),b=ab(j[2].substring(1,3),16)),j[3]&&(e=ab((h=j[3].charAt(3))+h,16),d=ab((h=j[3].charAt(2))+h,16),b=ab((h=j[3].charAt(1))+h,16)),j[4]&&(i=j[4][J](eb),b=_(i[0]),"%"==i[0].slice(-1)&&(b*=2.55),d=_(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),e=_(i[2]),"%"==i[2].slice(-1)&&(e*=2.55),"rgba"==j[1].toLowerCase().slice(0,4)&&(f=_(i[3])),i[3]&&"%"==i[3].slice(-1)&&(f/=100)),j[5]?(i=j[5][J](eb),b=_(i[0]),"%"==i[0].slice(-1)&&(b*=2.55),d=_(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),e=_(i[2]),"%"==i[2].slice(-1)&&(e*=2.55),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(b/=360),"hsba"==j[1].toLowerCase().slice(0,4)&&(f=_(i[3])),i[3]&&"%"==i[3].slice(-1)&&(f/=100),c.hsb2rgb(b,d,e,f)):j[6]?(i=j[6][J](eb),b=_(i[0]),"%"==i[0].slice(-1)&&(b*=2.55),d=_(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),e=_(i[2]),"%"==i[2].slice(-1)&&(e*=2.55),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(b/=360),"hsla"==j[1].toLowerCase().slice(0,4)&&(f=_(i[3])),i[3]&&"%"==i[3].slice(-1)&&(f/=100),c.hsl2rgb(b,d,e,f)):(j={r:b,g:d,b:e,toString:g},j.hex="#"+(16777216|e|d<<8|b<<16).toString(16).slice(1),c.is(f,"finite")&&(j.opacity=f),j)):{r:-1,g:-1,b:-1,hex:"none",error:1,toString:g}},c),c.hsb=f(function(a,b,d){return c.hsb2rgb(a,b,d).hex}),c.hsl=f(function(a,b,d){return c.hsl2rgb(a,b,d).hex}),c.rgb=f(function(a,b,c){return"#"+(16777216|c|b<<8|a<<16).toString(16).slice(1)}),c.getColor=function(a){var b=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||.75},c=this.hsb2rgb(b.h,b.s,b.b);return b.h+=.075,b.h>1&&(b.h=0,b.s-=.2,b.s<=0&&(this.getColor.start={h:0,s:1,b:b.b})),c.hex},c.getColor.reset=function(){delete this.start},c.parsePathString=function(a){if(!a)return null;var b=Ab(a);if(b.arr)return Cb(b.arr);var d={a:7,c:6,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,z:0},e=[];return c.is(a,V)&&c.is(a[0],V)&&(e=Cb(a)),e.length||I(a).replace(hb,function(a,b,c){var f=[],g=b.toLowerCase();if(c.replace(jb,function(a,b){b&&f.push(+b)}),"m"==g&&f.length>2&&(e.push([b][E](f.splice(0,2))),g="l",b="m"==b?"l":"L"),"r"==g)e.push([b][E](f));else for(;f.length>=d[g]&&(e.push([b][E](f.splice(0,d[g]))),d[g]););}),e.toString=c._path2string,b.arr=Cb(e),e},c.parseTransformString=f(function(a){if(!a)return null;var b=[];return c.is(a,V)&&c.is(a[0],V)&&(b=Cb(a)),b.length||I(a).replace(ib,function(a,c,d){var e=[];M.call(c),d.replace(jb,function(a,b){b&&e.push(+b)}),b.push([c][E](e))}),b.toString=c._path2string,b});var Ab=function(a){var b=Ab.ps=Ab.ps||{};return b[a]?b[a].sleep=100:b[a]={sleep:100},setTimeout(function(){for(var c in b)b[z](c)&&c!=a&&(b[c].sleep--,!b[c].sleep&&delete b[c])}),b[a]};c.findDotsAtSegment=function(a,b,c,d,e,f,g,h,i){var j=1-i,k=R(j,3),l=R(j,2),m=i*i,n=m*i,o=k*a+3*l*i*c+3*j*i*i*e+n*g,p=k*b+3*l*i*d+3*j*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,w=j*e+i*g,x=j*f+i*h,y=90-180*N.atan2(q-s,r-t)/S;return(q>s||t>r)&&(y+=180),{x:o,y:p,m:{x:q,y:r},n:{x:s,y:t},start:{x:u,y:v},end:{x:w,y:x},alpha:y}},c.bezierBBox=function(a,b,d,e,f,g,h,i){c.is(a,"array")||(a=[a,b,d,e,f,g,h,i]);var j=Jb.apply(null,a);return{x:j.min.x,y:j.min.y,x2:j.max.x,y2:j.max.y,width:j.max.x-j.min.x,height:j.max.y-j.min.y}},c.isPointInsideBBox=function(a,b,c){return b>=a.x&&b<=a.x2&&c>=a.y&&c<=a.y2},c.isBBoxIntersect=function(a,b){var d=c.isPointInsideBBox;return d(b,a.x,a.y)||d(b,a.x2,a.y)||d(b,a.x,a.y2)||d(b,a.x2,a.y2)||d(a,b.x,b.y)||d(a,b.x2,b.y)||d(a,b.x,b.y2)||d(a,b.x2,b.y2)||(a.x<b.x2&&a.x>b.x||b.x<a.x2&&b.x>a.x)&&(a.y<b.y2&&a.y>b.y||b.y<a.y2&&b.y>a.y)},c.pathIntersection=function(a,b){return n(a,b)},c.pathIntersectionNumber=function(a,b){return n(a,b,1)},c.isPointInsidePath=function(a,b,d){var e=c.pathBBox(a);return c.isPointInsideBBox(e,b,d)&&1==n(a,[["M",b,d],["H",e.x2+10]],1)%2},c._removedFactory=function(a){return function(){b("raphael.log",null,"Raphaël: you are calling to method “"+a+"†of removed object",a)}};var Bb=c.pathBBox=function(a){var b=Ab(a);if(b.bbox)return d(b.bbox);if(!a)return{x:0,y:0,width:0,height:0,x2:0,y2:0};a=Kb(a);for(var c,e=0,f=0,g=[],h=[],i=0,j=a.length;j>i;i++)if(c=a[i],"M"==c[0])e=c[1],f=c[2],g.push(e),h.push(f);else{var k=Jb(e,f,c[1],c[2],c[3],c[4],c[5],c[6]);g=g[E](k.min.x,k.max.x),h=h[E](k.min.y,k.max.y),e=c[5],f=c[6]}var l=P[D](0,g),m=P[D](0,h),n=O[D](0,g),o=O[D](0,h),p=n-l,q=o-m,r={x:l,y:m,x2:n,y2:o,width:p,height:q,cx:l+p/2,cy:m+q/2};return b.bbox=d(r),r},Cb=function(a){var b=d(a);return b.toString=c._path2string,b},Db=c._pathToRelative=function(a){var b=Ab(a);if(b.rel)return Cb(b.rel);c.is(a,V)&&c.is(a&&a[0],V)||(a=c.parsePathString(a));var d=[],e=0,f=0,g=0,h=0,i=0;"M"==a[0][0]&&(e=a[0][1],f=a[0][2],g=e,h=f,i++,d.push(["M",e,f]));for(var j=i,k=a.length;k>j;j++){var l=d[j]=[],m=a[j];if(m[0]!=M.call(m[0]))switch(l[0]=M.call(m[0]),l[0]){case"a":l[1]=m[1],l[2]=m[2],l[3]=m[3],l[4]=m[4],l[5]=m[5],l[6]=+(m[6]-e).toFixed(3),l[7]=+(m[7]-f).toFixed(3);break;case"v":l[1]=+(m[1]-f).toFixed(3);break;case"m":g=m[1],h=m[2];default:for(var n=1,o=m.length;o>n;n++)l[n]=+(m[n]-(n%2?e:f)).toFixed(3)}else{l=d[j]=[],"m"==m[0]&&(g=m[1]+e,h=m[2]+f);for(var p=0,q=m.length;q>p;p++)d[j][p]=m[p]}var r=d[j].length;switch(d[j][0]){case"z":e=g,f=h;break;case"h":e+=+d[j][r-1];break;case"v":f+=+d[j][r-1];break;default:e+=+d[j][r-2],f+=+d[j][r-1]}}return d.toString=c._path2string,b.rel=Cb(d),d},Eb=c._pathToAbsolute=function(a){var b=Ab(a);if(b.abs)return Cb(b.abs);if(c.is(a,V)&&c.is(a&&a[0],V)||(a=c.parsePathString(a)),!a||!a.length)return[["M",0,0]];var d=[],e=0,f=0,g=0,i=0,j=0;"M"==a[0][0]&&(e=+a[0][1],f=+a[0][2],g=e,i=f,j++,d[0]=["M",e,f]);for(var k,l,m=3==a.length&&"M"==a[0][0]&&"R"==a[1][0].toUpperCase()&&"Z"==a[2][0].toUpperCase(),n=j,o=a.length;o>n;n++){if(d.push(k=[]),l=a[n],l[0]!=bb.call(l[0]))switch(k[0]=bb.call(l[0]),k[0]){case"A":k[1]=l[1],k[2]=l[2],k[3]=l[3],k[4]=l[4],k[5]=l[5],k[6]=+(l[6]+e),k[7]=+(l[7]+f);break;case"V":k[1]=+l[1]+f;break;case"H":k[1]=+l[1]+e;break;case"R":for(var p=[e,f][E](l.slice(1)),q=2,r=p.length;r>q;q++)p[q]=+p[q]+e,p[++q]=+p[q]+f;d.pop(),d=d[E](h(p,m));break;case"M":g=+l[1]+e,i=+l[2]+f;default:for(q=1,r=l.length;r>q;q++)k[q]=+l[q]+(q%2?e:f)}else if("R"==l[0])p=[e,f][E](l.slice(1)),d.pop(),d=d[E](h(p,m)),k=["R"][E](l.slice(-2));else for(var s=0,t=l.length;t>s;s++)k[s]=l[s];switch(k[0]){case"Z":e=g,f=i;break;case"H":e=k[1];break;case"V":f=k[1];break;case"M":g=k[k.length-2],i=k[k.length-1];default:e=k[k.length-2],f=k[k.length-1]}}return d.toString=c._path2string,b.abs=Cb(d),d},Fb=function(a,b,c,d){return[a,b,c,d,c,d]},Gb=function(a,b,c,d,e,f){var g=1/3,h=2/3;return[g*a+h*c,g*b+h*d,g*e+h*c,g*f+h*d,e,f]},Hb=function(a,b,c,d,e,g,h,i,j,k){var l,m=120*S/180,n=S/180*(+e||0),o=[],p=f(function(a,b,c){var d=a*N.cos(c)-b*N.sin(c),e=a*N.sin(c)+b*N.cos(c);return{x:d,y:e}});if(k)y=k[0],z=k[1],w=k[2],x=k[3];else{l=p(a,b,-n),a=l.x,b=l.y,l=p(i,j,-n),i=l.x,j=l.y;var q=(N.cos(S/180*e),N.sin(S/180*e),(a-i)/2),r=(b-j)/2,s=q*q/(c*c)+r*r/(d*d);s>1&&(s=N.sqrt(s),c=s*c,d=s*d);var t=c*c,u=d*d,v=(g==h?-1:1)*N.sqrt(Q((t*u-t*r*r-u*q*q)/(t*r*r+u*q*q))),w=v*c*r/d+(a+i)/2,x=v*-d*q/c+(b+j)/2,y=N.asin(((b-x)/d).toFixed(9)),z=N.asin(((j-x)/d).toFixed(9));y=w>a?S-y:y,z=w>i?S-z:z,0>y&&(y=2*S+y),0>z&&(z=2*S+z),h&&y>z&&(y-=2*S),!h&&z>y&&(z-=2*S)}var A=z-y;if(Q(A)>m){var B=z,C=i,D=j;z=y+m*(h&&z>y?1:-1),i=w+c*N.cos(z),j=x+d*N.sin(z),o=Hb(i,j,c,d,e,0,h,C,D,[z,B,w,x])}A=z-y;var F=N.cos(y),G=N.sin(y),H=N.cos(z),I=N.sin(z),K=N.tan(A/4),L=4/3*c*K,M=4/3*d*K,O=[a,b],P=[a+L*G,b-M*F],R=[i+L*I,j-M*H],T=[i,j];if(P[0]=2*O[0]-P[0],P[1]=2*O[1]-P[1],k)return[P,R,T][E](o);o=[P,R,T][E](o).join()[J](",");for(var U=[],V=0,W=o.length;W>V;V++)U[V]=V%2?p(o[V-1],o[V],n).y:p(o[V],o[V+1],n).x;return U},Ib=function(a,b,c,d,e,f,g,h,i){var j=1-i;return{x:R(j,3)*a+3*R(j,2)*i*c+3*j*i*i*e+R(i,3)*g,y:R(j,3)*b+3*R(j,2)*i*d+3*j*i*i*f+R(i,3)*h}},Jb=f(function(a,b,c,d,e,f,g,h){var i,j=e-2*c+a-(g-2*e+c),k=2*(c-a)-2*(e-c),l=a-c,m=(-k+N.sqrt(k*k-4*j*l))/2/j,n=(-k-N.sqrt(k*k-4*j*l))/2/j,o=[b,h],p=[a,g];return Q(m)>"1e12"&&(m=.5),Q(n)>"1e12"&&(n=.5),m>0&&1>m&&(i=Ib(a,b,c,d,e,f,g,h,m),p.push(i.x),o.push(i.y)),n>0&&1>n&&(i=Ib(a,b,c,d,e,f,g,h,n),p.push(i.x),o.push(i.y)),j=f-2*d+b-(h-2*f+d),k=2*(d-b)-2*(f-d),l=b-d,m=(-k+N.sqrt(k*k-4*j*l))/2/j,n=(-k-N.sqrt(k*k-4*j*l))/2/j,Q(m)>"1e12"&&(m=.5),Q(n)>"1e12"&&(n=.5),m>0&&1>m&&(i=Ib(a,b,c,d,e,f,g,h,m),p.push(i.x),o.push(i.y)),n>0&&1>n&&(i=Ib(a,b,c,d,e,f,g,h,n),p.push(i.x),o.push(i.y)),{min:{x:P[D](0,p),y:P[D](0,o)},max:{x:O[D](0,p),y:O[D](0,o)}}}),Kb=c._path2curve=f(function(a,b){var c=!b&&Ab(a);if(!b&&c.curve)return Cb(c.curve);for(var d=Eb(a),e=b&&Eb(b),f={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},g={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},h=(function(a,b,c){var d,e;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];switch(!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null),a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"][E](Hb[D](0,[b.x,b.y][E](a.slice(1))));break;case"S":"C"==c||"S"==c?(d=2*b.x-b.bx,e=2*b.y-b.by):(d=b.x,e=b.y),a=["C",d,e][E](a.slice(1));break;case"T":"Q"==c||"T"==c?(b.qx=2*b.x-b.qx,b.qy=2*b.y-b.qy):(b.qx=b.x,b.qy=b.y),a=["C"][E](Gb(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"][E](Gb(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"][E](Fb(b.x,b.y,a[1],a[2]));break;case"H":a=["C"][E](Fb(b.x,b.y,a[1],b.y));break;case"V":a=["C"][E](Fb(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"][E](Fb(b.x,b.y,b.X,b.Y))}return a}),i=function(a,b){if(a[b].length>7){a[b].shift();for(var c=a[b];c.length;)a.splice(b++,0,["C"][E](c.splice(0,6)));a.splice(b,1),l=O(d.length,e&&e.length||0)}},j=function(a,b,c,f,g){a&&b&&"M"==a[g][0]&&"M"!=b[g][0]&&(b.splice(g,0,["M",f.x,f.y]),c.bx=0,c.by=0,c.x=a[g][1],c.y=a[g][2],l=O(d.length,e&&e.length||0))},k=0,l=O(d.length,e&&e.length||0);l>k;k++){d[k]=h(d[k],f),i(d,k),e&&(e[k]=h(e[k],g)),e&&i(e,k),j(d,e,f,g,k),j(e,d,g,f,k);var m=d[k],n=e&&e[k],o=m.length,p=e&&n.length;f.x=m[o-2],f.y=m[o-1],f.bx=_(m[o-4])||f.x,f.by=_(m[o-3])||f.y,g.bx=e&&(_(n[p-4])||g.x),g.by=e&&(_(n[p-3])||g.y),g.x=e&&n[p-2],g.y=e&&n[p-1]}return e||(c.curve=Cb(d)),e?[d,e]:d},null,Cb),Lb=(c._parseDots=f(function(a){for(var b=[],d=0,e=a.length;e>d;d++){var f={},g=a[d].match(/^([^:]*):?([\d\.]*)/);if(f.color=c.getRGB(g[1]),f.color.error)return null;f.color=f.color.hex,g[2]&&(f.offset=g[2]+"%"),b.push(f)}for(d=1,e=b.length-1;e>d;d++)if(!b[d].offset){for(var h=_(b[d-1].offset||0),i=0,j=d+1;e>j;j++)if(b[j].offset){i=b[j].offset;break}i||(i=100,j=e),i=_(i);for(var k=(i-h)/(j-d+1);j>d;d++)h+=k,b[d].offset=h+"%"}return b}),c._tear=function(a,b){a==b.top&&(b.top=a.prev),a==b.bottom&&(b.bottom=a.next),a.next&&(a.next.prev=a.prev),a.prev&&(a.prev.next=a.next)}),Mb=(c._tofront=function(a,b){b.top!==a&&(Lb(a,b),a.next=null,a.prev=b.top,b.top.next=a,b.top=a)},c._toback=function(a,b){b.bottom!==a&&(Lb(a,b),a.next=b.bottom,a.prev=null,b.bottom.prev=a,b.bottom=a)},c._insertafter=function(a,b,c){Lb(a,c),b==c.top&&(c.top=a),b.next&&(b.next.prev=a),a.next=b.next,a.prev=b,b.next=a},c._insertbefore=function(a,b,c){Lb(a,c),b==c.bottom&&(c.bottom=a),b.prev&&(b.prev.next=a),a.prev=b.prev,b.prev=a,a.next=b},c.toMatrix=function(a,b){var c=Bb(a),d={_:{transform:G},getBBox:function(){return c}};return Nb(d,b),d.matrix}),Nb=(c.transformPath=function(a,b){return rb(a,Mb(a,b))},c._extractTransform=function(a,b){if(null==b)return a._.transform;b=I(b).replace(/\.{3}|\u2026/g,a._.transform||G);var d=c.parseTransformString(b),e=0,f=0,g=0,h=1,i=1,j=a._,k=new o;if(j.transform=d||[],d)for(var l=0,m=d.length;m>l;l++){var n,p,q,r,s,t=d[l],u=t.length,v=I(t[0]).toLowerCase(),w=t[0]!=v,x=w?k.invert():0;"t"==v&&3==u?w?(n=x.x(0,0),p=x.y(0,0),q=x.x(t[1],t[2]),r=x.y(t[1],t[2]),k.translate(q-n,r-p)):k.translate(t[1],t[2]):"r"==v?2==u?(s=s||a.getBBox(1),k.rotate(t[1],s.x+s.width/2,s.y+s.height/2),e+=t[1]):4==u&&(w?(q=x.x(t[2],t[3]),r=x.y(t[2],t[3]),k.rotate(t[1],q,r)):k.rotate(t[1],t[2],t[3]),e+=t[1]):"s"==v?2==u||3==u?(s=s||a.getBBox(1),k.scale(t[1],t[u-1],s.x+s.width/2,s.y+s.height/2),h*=t[1],i*=t[u-1]):5==u&&(w?(q=x.x(t[3],t[4]),r=x.y(t[3],t[4]),k.scale(t[1],t[2],q,r)):k.scale(t[1],t[2],t[3],t[4]),h*=t[1],i*=t[2]):"m"==v&&7==u&&k.add(t[1],t[2],t[3],t[4],t[5],t[6]),j.dirtyT=1,a.matrix=k}a.matrix=k,j.sx=h,j.sy=i,j.deg=e,j.dx=f=k.e,j.dy=g=k.f,1==h&&1==i&&!e&&j.bbox?(j.bbox.x+=+f,j.bbox.y+=+g):j.dirtyT=1}),Ob=function(a){var b=a[0];switch(b.toLowerCase()){case"t":return[b,0,0];case"m":return[b,1,0,0,1,0,0];case"r":return 4==a.length?[b,0,a[2],a[3]]:[b,0];case"s":return 5==a.length?[b,1,1,a[3],a[4]]:3==a.length?[b,1,1]:[b,1]}},Pb=c._equaliseTransform=function(a,b){b=I(b).replace(/\.{3}|\u2026/g,a),a=c.parseTransformString(a)||[],b=c.parseTransformString(b)||[];for(var d,e,f,g,h=O(a.length,b.length),i=[],j=[],k=0;h>k;k++){if(f=a[k]||Ob(b[k]),g=b[k]||Ob(f),f[0]!=g[0]||"r"==f[0].toLowerCase()&&(f[2]!=g[2]||f[3]!=g[3])||"s"==f[0].toLowerCase()&&(f[3]!=g[3]||f[4]!=g[4]))return;for(i[k]=[],j[k]=[],d=0,e=O(f.length,g.length);e>d;d++)d in f&&(i[k][d]=f[d]),d in g&&(j[k][d]=g[d])
+}return{from:i,to:j}};c._getContainer=function(a,b,d,e){var f;return f=null!=e||c.is(a,"object")?a:A.doc.getElementById(a),null!=f?f.tagName?null==b?{container:f,width:f.style.pixelWidth||f.offsetWidth,height:f.style.pixelHeight||f.offsetHeight}:{container:f,width:b,height:d}:{container:1,x:a,y:b,width:d,height:e}:void 0},c.pathToRelative=Db,c._engine={},c.path2curve=Kb,c.matrix=function(a,b,c,d,e,f){return new o(a,b,c,d,e,f)},function(a){function b(a){return a[0]*a[0]+a[1]*a[1]}function d(a){var c=N.sqrt(b(a));a[0]&&(a[0]/=c),a[1]&&(a[1]/=c)}a.add=function(a,b,c,d,e,f){var g,h,i,j,k=[[],[],[]],l=[[this.a,this.c,this.e],[this.b,this.d,this.f],[0,0,1]],m=[[a,c,e],[b,d,f],[0,0,1]];for(a&&a instanceof o&&(m=[[a.a,a.c,a.e],[a.b,a.d,a.f],[0,0,1]]),g=0;3>g;g++)for(h=0;3>h;h++){for(j=0,i=0;3>i;i++)j+=l[g][i]*m[i][h];k[g][h]=j}this.a=k[0][0],this.b=k[1][0],this.c=k[0][1],this.d=k[1][1],this.e=k[0][2],this.f=k[1][2]},a.invert=function(){var a=this,b=a.a*a.d-a.b*a.c;return new o(a.d/b,-a.b/b,-a.c/b,a.a/b,(a.c*a.f-a.d*a.e)/b,(a.b*a.e-a.a*a.f)/b)},a.clone=function(){return new o(this.a,this.b,this.c,this.d,this.e,this.f)},a.translate=function(a,b){this.add(1,0,0,1,a,b)},a.scale=function(a,b,c,d){null==b&&(b=a),(c||d)&&this.add(1,0,0,1,c,d),this.add(a,0,0,b,0,0),(c||d)&&this.add(1,0,0,1,-c,-d)},a.rotate=function(a,b,d){a=c.rad(a),b=b||0,d=d||0;var e=+N.cos(a).toFixed(9),f=+N.sin(a).toFixed(9);this.add(e,f,-f,e,b,d),this.add(1,0,0,1,-b,-d)},a.x=function(a,b){return a*this.a+b*this.c+this.e},a.y=function(a,b){return a*this.b+b*this.d+this.f},a.get=function(a){return+this[I.fromCharCode(97+a)].toFixed(4)},a.toString=function(){return c.svg?"matrix("+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)].join()+")":[this.get(0),this.get(2),this.get(1),this.get(3),0,0].join()},a.toFilter=function(){return"progid:DXImageTransform.Microsoft.Matrix(M11="+this.get(0)+", M12="+this.get(2)+", M21="+this.get(1)+", M22="+this.get(3)+", Dx="+this.get(4)+", Dy="+this.get(5)+", sizingmethod='auto expand')"},a.offset=function(){return[this.e.toFixed(4),this.f.toFixed(4)]},a.split=function(){var a={};a.dx=this.e,a.dy=this.f;var e=[[this.a,this.c],[this.b,this.d]];a.scalex=N.sqrt(b(e[0])),d(e[0]),a.shear=e[0][0]*e[1][0]+e[0][1]*e[1][1],e[1]=[e[1][0]-e[0][0]*a.shear,e[1][1]-e[0][1]*a.shear],a.scaley=N.sqrt(b(e[1])),d(e[1]),a.shear/=a.scaley;var f=-e[0][1],g=e[1][1];return 0>g?(a.rotate=c.deg(N.acos(g)),0>f&&(a.rotate=360-a.rotate)):a.rotate=c.deg(N.asin(f)),a.isSimple=!(+a.shear.toFixed(9)||a.scalex.toFixed(9)!=a.scaley.toFixed(9)&&a.rotate),a.isSuperSimple=!+a.shear.toFixed(9)&&a.scalex.toFixed(9)==a.scaley.toFixed(9)&&!a.rotate,a.noRotation=!+a.shear.toFixed(9)&&!a.rotate,a},a.toTransformString=function(a){var b=a||this[J]();return b.isSimple?(b.scalex=+b.scalex.toFixed(4),b.scaley=+b.scaley.toFixed(4),b.rotate=+b.rotate.toFixed(4),(b.dx||b.dy?"t"+[b.dx,b.dy]:G)+(1!=b.scalex||1!=b.scaley?"s"+[b.scalex,b.scaley,0,0]:G)+(b.rotate?"r"+[b.rotate,0,0]:G)):"m"+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)]}}(o.prototype);var Qb=navigator.userAgent.match(/Version\/(.*?)\s/)||navigator.userAgent.match(/Chrome\/(\d+)/);v.safari="Apple Computer, Inc."==navigator.vendor&&(Qb&&Qb[1]<4||"iP"==navigator.platform.slice(0,2))||"Google Inc."==navigator.vendor&&Qb&&Qb[1]<8?function(){var a=this.rect(-99,-99,this.width+99,this.height+99).attr({stroke:"none"});setTimeout(function(){a.remove()})}:mb;for(var Rb=function(){this.returnValue=!1},Sb=function(){return this.originalEvent.preventDefault()},Tb=function(){this.cancelBubble=!0},Ub=function(){return this.originalEvent.stopPropagation()},Vb=function(a){var b=A.doc.documentElement.scrollTop||A.doc.body.scrollTop,c=A.doc.documentElement.scrollLeft||A.doc.body.scrollLeft;return{x:a.clientX+c,y:a.clientY+b}},Wb=function(){return A.doc.addEventListener?function(a,b,c,d){var e=function(a){var b=Vb(a);return c.call(d,a,b.x,b.y)};if(a.addEventListener(b,e,!1),F&&L[b]){var f=function(b){for(var e=Vb(b),f=b,g=0,h=b.targetTouches&&b.targetTouches.length;h>g;g++)if(b.targetTouches[g].target==a){b=b.targetTouches[g],b.originalEvent=f,b.preventDefault=Sb,b.stopPropagation=Ub;break}return c.call(d,b,e.x,e.y)};a.addEventListener(L[b],f,!1)}return function(){return a.removeEventListener(b,e,!1),F&&L[b]&&a.removeEventListener(L[b],e,!1),!0}}:A.doc.attachEvent?function(a,b,c,d){var e=function(a){a=a||A.win.event;var b=A.doc.documentElement.scrollTop||A.doc.body.scrollTop,e=A.doc.documentElement.scrollLeft||A.doc.body.scrollLeft,f=a.clientX+e,g=a.clientY+b;return a.preventDefault=a.preventDefault||Rb,a.stopPropagation=a.stopPropagation||Tb,c.call(d,a,f,g)};a.attachEvent("on"+b,e);var f=function(){return a.detachEvent("on"+b,e),!0};return f}:void 0}(),Xb=[],Yb=function(a){for(var c,d=a.clientX,e=a.clientY,f=A.doc.documentElement.scrollTop||A.doc.body.scrollTop,g=A.doc.documentElement.scrollLeft||A.doc.body.scrollLeft,h=Xb.length;h--;){if(c=Xb[h],F&&a.touches){for(var i,j=a.touches.length;j--;)if(i=a.touches[j],i.identifier==c.el._drag.id){d=i.clientX,e=i.clientY,(a.originalEvent?a.originalEvent:a).preventDefault();break}}else a.preventDefault();var k,l=c.el.node,m=l.nextSibling,n=l.parentNode,o=l.style.display;A.win.opera&&n.removeChild(l),l.style.display="none",k=c.el.paper.getElementByPoint(d,e),l.style.display=o,A.win.opera&&(m?n.insertBefore(l,m):n.appendChild(l)),k&&b("raphael.drag.over."+c.el.id,c.el,k),d+=g,e+=f,b("raphael.drag.move."+c.el.id,c.move_scope||c.el,d-c.el._drag.x,e-c.el._drag.y,d,e,a)}},Zb=function(a){c.unmousemove(Yb).unmouseup(Zb);for(var d,e=Xb.length;e--;)d=Xb[e],d.el._drag={},b("raphael.drag.end."+d.el.id,d.end_scope||d.start_scope||d.move_scope||d.el,a);Xb=[]},$b=c.el={},_b=K.length;_b--;)!function(a){c[a]=$b[a]=function(b,d){return c.is(b,"function")&&(this.events=this.events||[],this.events.push({name:a,f:b,unbind:Wb(this.shape||this.node||A.doc,a,b,d||this)})),this},c["un"+a]=$b["un"+a]=function(b){for(var d=this.events||[],e=d.length;e--;)d[e].name!=a||!c.is(b,"undefined")&&d[e].f!=b||(d[e].unbind(),d.splice(e,1),!d.length&&delete this.events);return this}}(K[_b]);$b.data=function(a,d){var e=kb[this.id]=kb[this.id]||{};if(0==arguments.length)return e;if(1==arguments.length){if(c.is(a,"object")){for(var f in a)a[z](f)&&this.data(f,a[f]);return this}return b("raphael.data.get."+this.id,this,e[a],a),e[a]}return e[a]=d,b("raphael.data.set."+this.id,this,d,a),this},$b.removeData=function(a){return null==a?kb[this.id]={}:kb[this.id]&&delete kb[this.id][a],this},$b.getData=function(){return d(kb[this.id]||{})},$b.hover=function(a,b,c,d){return this.mouseover(a,c).mouseout(b,d||c)},$b.unhover=function(a,b){return this.unmouseover(a).unmouseout(b)};var ac=[];$b.drag=function(a,d,e,f,g,h){function i(i){(i.originalEvent||i).preventDefault();var j=i.clientX,k=i.clientY,l=A.doc.documentElement.scrollTop||A.doc.body.scrollTop,m=A.doc.documentElement.scrollLeft||A.doc.body.scrollLeft;if(this._drag.id=i.identifier,F&&i.touches)for(var n,o=i.touches.length;o--;)if(n=i.touches[o],this._drag.id=n.identifier,n.identifier==this._drag.id){j=n.clientX,k=n.clientY;break}this._drag.x=j+m,this._drag.y=k+l,!Xb.length&&c.mousemove(Yb).mouseup(Zb),Xb.push({el:this,move_scope:f,start_scope:g,end_scope:h}),d&&b.on("raphael.drag.start."+this.id,d),a&&b.on("raphael.drag.move."+this.id,a),e&&b.on("raphael.drag.end."+this.id,e),b("raphael.drag.start."+this.id,g||f||this,i.clientX+m,i.clientY+l,i)}return this._drag={},ac.push({el:this,start:i}),this.mousedown(i),this},$b.onDragOver=function(a){a?b.on("raphael.drag.over."+this.id,a):b.unbind("raphael.drag.over."+this.id)},$b.undrag=function(){for(var a=ac.length;a--;)ac[a].el==this&&(this.unmousedown(ac[a].start),ac.splice(a,1),b.unbind("raphael.drag.*."+this.id));!ac.length&&c.unmousemove(Yb).unmouseup(Zb),Xb=[]},v.circle=function(a,b,d){var e=c._engine.circle(this,a||0,b||0,d||0);return this.__set__&&this.__set__.push(e),e},v.rect=function(a,b,d,e,f){var g=c._engine.rect(this,a||0,b||0,d||0,e||0,f||0);return this.__set__&&this.__set__.push(g),g},v.ellipse=function(a,b,d,e){var f=c._engine.ellipse(this,a||0,b||0,d||0,e||0);return this.__set__&&this.__set__.push(f),f},v.path=function(a){a&&!c.is(a,U)&&!c.is(a[0],V)&&(a+=G);var b=c._engine.path(c.format[D](c,arguments),this);return this.__set__&&this.__set__.push(b),b},v.image=function(a,b,d,e,f){var g=c._engine.image(this,a||"about:blank",b||0,d||0,e||0,f||0);return this.__set__&&this.__set__.push(g),g},v.text=function(a,b,d){var e=c._engine.text(this,a||0,b||0,I(d));return this.__set__&&this.__set__.push(e),e},v.set=function(a){!c.is(a,"array")&&(a=Array.prototype.splice.call(arguments,0,arguments.length));var b=new mc(a);return this.__set__&&this.__set__.push(b),b.paper=this,b.type="set",b},v.setStart=function(a){this.__set__=a||this.set()},v.setFinish=function(){var a=this.__set__;return delete this.__set__,a},v.setSize=function(a,b){return c._engine.setSize.call(this,a,b)},v.setViewBox=function(a,b,d,e,f){return c._engine.setViewBox.call(this,a,b,d,e,f)},v.top=v.bottom=null,v.raphael=c;var bc=function(a){var b=a.getBoundingClientRect(),c=a.ownerDocument,d=c.body,e=c.documentElement,f=e.clientTop||d.clientTop||0,g=e.clientLeft||d.clientLeft||0,h=b.top+(A.win.pageYOffset||e.scrollTop||d.scrollTop)-f,i=b.left+(A.win.pageXOffset||e.scrollLeft||d.scrollLeft)-g;return{y:h,x:i}};v.getElementByPoint=function(a,b){var c=this,d=c.canvas,e=A.doc.elementFromPoint(a,b);if(A.win.opera&&"svg"==e.tagName){var f=bc(d),g=d.createSVGRect();g.x=a-f.x,g.y=b-f.y,g.width=g.height=1;var h=d.getIntersectionList(g,null);h.length&&(e=h[h.length-1])}if(!e)return null;for(;e.parentNode&&e!=d.parentNode&&!e.raphael;)e=e.parentNode;return e==c.canvas.parentNode&&(e=d),e=e&&e.raphael?c.getById(e.raphaelid):null},v.getElementsByBBox=function(a){var b=this.set();return this.forEach(function(d){c.isBBoxIntersect(d.getBBox(),a)&&b.push(d)}),b},v.getById=function(a){for(var b=this.bottom;b;){if(b.id==a)return b;b=b.next}return null},v.forEach=function(a,b){for(var c=this.bottom;c;){if(a.call(b,c)===!1)return this;c=c.next}return this},v.getElementsByPoint=function(a,b){var c=this.set();return this.forEach(function(d){d.isPointInside(a,b)&&c.push(d)}),c},$b.isPointInside=function(a,b){var d=this.realPath=qb[this.type](this);return this.attr("transform")&&this.attr("transform").length&&(d=c.transformPath(d,this.attr("transform"))),c.isPointInsidePath(d,a,b)},$b.getBBox=function(a){if(this.removed)return{};var b=this._;return a?((b.dirty||!b.bboxwt)&&(this.realPath=qb[this.type](this),b.bboxwt=Bb(this.realPath),b.bboxwt.toString=p,b.dirty=0),b.bboxwt):((b.dirty||b.dirtyT||!b.bbox)&&((b.dirty||!this.realPath)&&(b.bboxwt=0,this.realPath=qb[this.type](this)),b.bbox=Bb(rb(this.realPath,this.matrix)),b.bbox.toString=p,b.dirty=b.dirtyT=0),b.bbox)},$b.clone=function(){if(this.removed)return null;var a=this.paper[this.type]().attr(this.attr());return this.__set__&&this.__set__.push(a),a},$b.glow=function(a){if("text"==this.type)return null;a=a||{};var b={width:(a.width||10)+(+this.attr("stroke-width")||1),fill:a.fill||!1,opacity:a.opacity||.5,offsetx:a.offsetx||0,offsety:a.offsety||0,color:a.color||"#000"},c=b.width/2,d=this.paper,e=d.set(),f=this.realPath||qb[this.type](this);f=this.matrix?rb(f,this.matrix):f;for(var g=1;c+1>g;g++)e.push(d.path(f).attr({stroke:b.color,fill:b.fill?b.color:"none","stroke-linejoin":"round","stroke-linecap":"round","stroke-width":+(b.width/c*g).toFixed(3),opacity:+(b.opacity/c).toFixed(3)}));return e.insertBefore(this).translate(b.offsetx,b.offsety)};var cc=function(a,b,d,e,f,g,h,i,l){return null==l?j(a,b,d,e,f,g,h,i):c.findDotsAtSegment(a,b,d,e,f,g,h,i,k(a,b,d,e,f,g,h,i,l))},dc=function(a,b){return function(d,e,f){d=Kb(d);for(var g,h,i,j,k,l="",m={},n=0,o=0,p=d.length;p>o;o++){if(i=d[o],"M"==i[0])g=+i[1],h=+i[2];else{if(j=cc(g,h,i[1],i[2],i[3],i[4],i[5],i[6]),n+j>e){if(b&&!m.start){if(k=cc(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),l+=["C"+k.start.x,k.start.y,k.m.x,k.m.y,k.x,k.y],f)return l;m.start=l,l=["M"+k.x,k.y+"C"+k.n.x,k.n.y,k.end.x,k.end.y,i[5],i[6]].join(),n+=j,g=+i[5],h=+i[6];continue}if(!a&&!b)return k=cc(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),{x:k.x,y:k.y,alpha:k.alpha}}n+=j,g=+i[5],h=+i[6]}l+=i.shift()+i}return m.end=l,k=a?n:b?m:c.findDotsAtSegment(g,h,i[0],i[1],i[2],i[3],i[4],i[5],1),k.alpha&&(k={x:k.x,y:k.y,alpha:k.alpha}),k}},ec=dc(1),fc=dc(),gc=dc(0,1);c.getTotalLength=ec,c.getPointAtLength=fc,c.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)return gc(a,b).end;var d=gc(a,c,1);return b?gc(d,b).end:d},$b.getTotalLength=function(){var a=this.getPath();if(a)return this.node.getTotalLength?this.node.getTotalLength():ec(a)},$b.getPointAtLength=function(a){var b=this.getPath();if(b)return fc(b,a)},$b.getPath=function(){var a,b=c._getPath[this.type];if("text"!=this.type&&"set"!=this.type)return b&&(a=b(this)),a},$b.getSubpath=function(a,b){var d=this.getPath();if(d)return c.getSubpath(d,a,b)};var hc=c.easing_formulas={linear:function(a){return a},"<":function(a){return R(a,1.7)},">":function(a){return R(a,.48)},"<>":function(a){var b=.48-a/1.04,c=N.sqrt(.1734+b*b),d=c-b,e=R(Q(d),1/3)*(0>d?-1:1),f=-c-b,g=R(Q(f),1/3)*(0>f?-1:1),h=e+g+.5;return 3*(1-h)*h*h+h*h*h},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a-=1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){return a==!!a?a:R(2,-10*a)*N.sin((a-.075)*2*S/.3)+1},bounce:function(a){var b,c=7.5625,d=2.75;return 1/d>a?b=c*a*a:2/d>a?(a-=1.5/d,b=c*a*a+.75):2.5/d>a?(a-=2.25/d,b=c*a*a+.9375):(a-=2.625/d,b=c*a*a+.984375),b}};hc.easeIn=hc["ease-in"]=hc["<"],hc.easeOut=hc["ease-out"]=hc[">"],hc.easeInOut=hc["ease-in-out"]=hc["<>"],hc["back-in"]=hc.backIn,hc["back-out"]=hc.backOut;var ic=[],jc=a.requestAnimationFrame||a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame||a.msRequestAnimationFrame||function(a){setTimeout(a,16)},kc=function(){for(var a=+new Date,d=0;d<ic.length;d++){var e=ic[d];if(!e.el.removed&&!e.paused){var f,g,h=a-e.start,i=e.ms,j=e.easing,k=e.from,l=e.diff,m=e.to,n=(e.t,e.el),o={},p={};if(e.initstatus?(h=(e.initstatus*e.anim.top-e.prev)/(e.percent-e.prev)*i,e.status=e.initstatus,delete e.initstatus,e.stop&&ic.splice(d--,1)):e.status=(e.prev+(e.percent-e.prev)*(h/i))/e.anim.top,!(0>h))if(i>h){var q=j(h/i);for(var r in k)if(k[z](r)){switch(db[r]){case T:f=+k[r]+q*i*l[r];break;case"colour":f="rgb("+[lc($(k[r].r+q*i*l[r].r)),lc($(k[r].g+q*i*l[r].g)),lc($(k[r].b+q*i*l[r].b))].join(",")+")";break;case"path":f=[];for(var t=0,u=k[r].length;u>t;t++){f[t]=[k[r][t][0]];for(var v=1,w=k[r][t].length;w>v;v++)f[t][v]=+k[r][t][v]+q*i*l[r][t][v];f[t]=f[t].join(H)}f=f.join(H);break;case"transform":if(l[r].real)for(f=[],t=0,u=k[r].length;u>t;t++)for(f[t]=[k[r][t][0]],v=1,w=k[r][t].length;w>v;v++)f[t][v]=k[r][t][v]+q*i*l[r][t][v];else{var x=function(a){return+k[r][a]+q*i*l[r][a]};f=[["m",x(0),x(1),x(2),x(3),x(4),x(5)]]}break;case"csv":if("clip-rect"==r)for(f=[],t=4;t--;)f[t]=+k[r][t]+q*i*l[r][t];break;default:var y=[][E](k[r]);for(f=[],t=n.paper.customAttributes[r].length;t--;)f[t]=+y[t]+q*i*l[r][t]}o[r]=f}n.attr(o),function(a,c,d){setTimeout(function(){b("raphael.anim.frame."+a,c,d)})}(n.id,n,e.anim)}else{if(function(a,d,e){setTimeout(function(){b("raphael.anim.frame."+d.id,d,e),b("raphael.anim.finish."+d.id,d,e),c.is(a,"function")&&a.call(d)})}(e.callback,n,e.anim),n.attr(m),ic.splice(d--,1),e.repeat>1&&!e.next){for(g in m)m[z](g)&&(p[g]=e.totalOrigin[g]);e.el.attr(p),s(e.anim,e.el,e.anim.percents[0],null,e.totalOrigin,e.repeat-1)}e.next&&!e.stop&&s(e.anim,e.el,e.next,null,e.totalOrigin,e.repeat)}}}c.svg&&n&&n.paper&&n.paper.safari(),ic.length&&jc(kc)},lc=function(a){return a>255?255:0>a?0:a};$b.animateWith=function(a,b,d,e,f,g){var h=this;if(h.removed)return g&&g.call(h),h;var i=d instanceof r?d:c.animation(d,e,f,g);s(i,h,i.percents[0],null,h.attr());for(var j=0,k=ic.length;k>j;j++)if(ic[j].anim==b&&ic[j].el==a){ic[k-1].start=ic[j].start;break}return h},$b.onAnimation=function(a){return a?b.on("raphael.anim.frame."+this.id,a):b.unbind("raphael.anim.frame."+this.id),this},r.prototype.delay=function(a){var b=new r(this.anim,this.ms);return b.times=this.times,b.del=+a||0,b},r.prototype.repeat=function(a){var b=new r(this.anim,this.ms);return b.del=this.del,b.times=N.floor(O(a,0))||1,b},c.animation=function(a,b,d,e){if(a instanceof r)return a;(c.is(d,"function")||!d)&&(e=e||d||null,d=null),a=Object(a),b=+b||0;var f,g,h={};for(g in a)a[z](g)&&_(g)!=g&&_(g)+"%"!=g&&(f=!0,h[g]=a[g]);return f?(d&&(h.easing=d),e&&(h.callback=e),new r({100:h},b)):new r(a,b)},$b.animate=function(a,b,d,e){var f=this;if(f.removed)return e&&e.call(f),f;var g=a instanceof r?a:c.animation(a,b,d,e);return s(g,f,g.percents[0],null,f.attr()),f},$b.setTime=function(a,b){return a&&null!=b&&this.status(a,P(b,a.ms)/a.ms),this},$b.status=function(a,b){var c,d,e=[],f=0;if(null!=b)return s(a,this,-1,P(b,1)),this;for(c=ic.length;c>f;f++)if(d=ic[f],d.el.id==this.id&&(!a||d.anim==a)){if(a)return d.status;e.push({anim:d.anim,status:d.status})}return a?0:e},$b.pause=function(a){for(var c=0;c<ic.length;c++)ic[c].el.id!=this.id||a&&ic[c].anim!=a||b("raphael.anim.pause."+this.id,this,ic[c].anim)!==!1&&(ic[c].paused=!0);return this},$b.resume=function(a){for(var c=0;c<ic.length;c++)if(ic[c].el.id==this.id&&(!a||ic[c].anim==a)){var d=ic[c];b("raphael.anim.resume."+this.id,this,d.anim)!==!1&&(delete d.paused,this.status(d.anim,d.status))}return this},$b.stop=function(a){for(var c=0;c<ic.length;c++)ic[c].el.id!=this.id||a&&ic[c].anim!=a||b("raphael.anim.stop."+this.id,this,ic[c].anim)!==!1&&ic.splice(c--,1);return this},b.on("raphael.remove",t),b.on("raphael.clear",t),$b.toString=function(){return"Raphaël’s object"};var mc=function(a){if(this.items=[],this.length=0,this.type="set",a)for(var b=0,c=a.length;c>b;b++)!a[b]||a[b].constructor!=$b.constructor&&a[b].constructor!=mc||(this[this.items.length]=this.items[this.items.length]=a[b],this.length++)},nc=mc.prototype;nc.push=function(){for(var a,b,c=0,d=arguments.length;d>c;c++)a=arguments[c],!a||a.constructor!=$b.constructor&&a.constructor!=mc||(b=this.items.length,this[b]=this.items[b]=a,this.length++);return this},nc.pop=function(){return this.length&&delete this[this.length--],this.items.pop()},nc.forEach=function(a,b){for(var c=0,d=this.items.length;d>c;c++)if(a.call(b,this.items[c],c)===!1)return this;return this};for(var oc in $b)$b[z](oc)&&(nc[oc]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a][D](c,b)})}}(oc));return nc.attr=function(a,b){if(a&&c.is(a,V)&&c.is(a[0],"object"))for(var d=0,e=a.length;e>d;d++)this.items[d].attr(a[d]);else for(var f=0,g=this.items.length;g>f;f++)this.items[f].attr(a,b);return this},nc.clear=function(){for(;this.length;)this.pop()},nc.splice=function(a,b){a=0>a?O(this.length+a,0):a,b=O(0,P(this.length-a,b));var c,d=[],e=[],f=[];for(c=2;c<arguments.length;c++)f.push(arguments[c]);for(c=0;b>c;c++)e.push(this[a+c]);for(;c<this.length-a;c++)d.push(this[a+c]);var g=f.length;for(c=0;c<g+d.length;c++)this.items[a+c]=this[a+c]=g>c?f[c]:d[c-g];for(c=this.items.length=this.length-=b-g;this[c];)delete this[c++];return new mc(e)},nc.exclude=function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]==a)return this.splice(b,1),!0},nc.animate=function(a,b,d,e){(c.is(d,"function")||!d)&&(e=d||null);var f,g,h=this.items.length,i=h,j=this;if(!h)return this;e&&(g=function(){!--h&&e.call(j)}),d=c.is(d,U)?d:g;var k=c.animation(a,b,d,g);for(f=this.items[--i].animate(k);i--;)this.items[i]&&!this.items[i].removed&&this.items[i].animateWith(f,k,k),this.items[i]&&!this.items[i].removed||h--;return this},nc.insertAfter=function(a){for(var b=this.items.length;b--;)this.items[b].insertAfter(a);return this},nc.getBBox=function(){for(var a=[],b=[],c=[],d=[],e=this.items.length;e--;)if(!this.items[e].removed){var f=this.items[e].getBBox();a.push(f.x),b.push(f.y),c.push(f.x+f.width),d.push(f.y+f.height)}return a=P[D](0,a),b=P[D](0,b),c=O[D](0,c),d=O[D](0,d),{x:a,y:b,x2:c,y2:d,width:c-a,height:d-b}},nc.clone=function(a){a=this.paper.set();for(var b=0,c=this.items.length;c>b;b++)a.push(this.items[b].clone());return a},nc.toString=function(){return"Raphaël‘s set"},nc.glow=function(a){var b=this.paper.set();return this.forEach(function(c){var d=c.glow(a);null!=d&&d.forEach(function(a){b.push(a)})}),b},nc.isPointInside=function(a,b){var c=!1;return this.forEach(function(d){return d.isPointInside(a,b)?(console.log("runned"),c=!0,!1):void 0}),c},c.registerFont=function(a){if(!a.face)return a;this.fonts=this.fonts||{};var b={w:a.w,face:{},glyphs:{}},c=a.face["font-family"];for(var d in a.face)a.face[z](d)&&(b.face[d]=a.face[d]);if(this.fonts[c]?this.fonts[c].push(b):this.fonts[c]=[b],!a.svg){b.face["units-per-em"]=ab(a.face["units-per-em"],10);for(var e in a.glyphs)if(a.glyphs[z](e)){var f=a.glyphs[e];if(b.glyphs[e]={w:f.w,k:{},d:f.d&&"M"+f.d.replace(/[mlcxtrv]/g,function(a){return{l:"L",c:"C",x:"z",t:"m",r:"l",v:"c"}[a]||"M"})+"z"},f.k)for(var g in f.k)f[z](g)&&(b.glyphs[e].k[g]=f.k[g])}}return a},v.getFont=function(a,b,d,e){if(e=e||"normal",d=d||"normal",b=+b||{normal:400,bold:700,lighter:300,bolder:800}[b]||400,c.fonts){var f=c.fonts[a];if(!f){var g=new RegExp("(^|\\s)"+a.replace(/[^\w\d\s+!~.:_-]/g,G)+"(\\s|$)","i");for(var h in c.fonts)if(c.fonts[z](h)&&g.test(h)){f=c.fonts[h];break}}var i;if(f)for(var j=0,k=f.length;k>j&&(i=f[j],i.face["font-weight"]!=b||i.face["font-style"]!=d&&i.face["font-style"]||i.face["font-stretch"]!=e);j++);return i}},v.print=function(a,b,d,e,f,g,h,i){g=g||"middle",h=O(P(h||0,1),-1),i=O(P(i||1,3),1);var j,k=I(d)[J](G),l=0,m=0,n=G;if(c.is(e,"string")&&(e=this.getFont(e)),e){j=(f||16)/e.face["units-per-em"];for(var o=e.face.bbox[J](w),p=+o[0],q=o[3]-o[1],r=0,s=+o[1]+("baseline"==g?q+ +e.face.descent:q/2),t=0,u=k.length;u>t;t++){if("\n"==k[t])l=0,x=0,m=0,r+=q*i;else{var v=m&&e.glyphs[k[t-1]]||{},x=e.glyphs[k[t]];l+=m?(v.w||e.w)+(v.k&&v.k[k[t]]||0)+e.w*h:0,m=1}x&&x.d&&(n+=c.transformPath(x.d,["t",l*j,r*j,"s",j,j,p,s,"t",(a-p)/j,(b-s)/j]))}}return this.path(n).attr({fill:"#000",stroke:"none"})},v.add=function(a){if(c.is(a,"array"))for(var b,d=this.set(),e=0,f=a.length;f>e;e++)b=a[e]||{},x[z](b.type)&&d.push(this[b.type]().attr(b));return d},c.format=function(a,b){var d=c.is(b,V)?[0][E](b):arguments;return a&&c.is(a,U)&&d.length-1&&(a=a.replace(y,function(a,b){return null==d[++b]?G:d[b]})),a||G},c.fullfill=function(){var a=/\{([^\}]+)\}/g,b=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,c=function(a,c,d){var e=d;return c.replace(b,function(a,b,c,d,f){b=b||d,e&&(b in e&&(e=e[b]),"function"==typeof e&&f&&(e=e()))}),e=(null==e||e==d?a:e)+""};return function(b,d){return String(b).replace(a,function(a,b){return c(a,b,d)})}}(),c.ninja=function(){return B.was?A.win.Raphael=B.is:delete Raphael,c},c.st=nc,function(a,b,d){function e(){/in/.test(a.readyState)?setTimeout(e,9):c.eve("raphael.DOMload")}null==a.readyState&&a.addEventListener&&(a.addEventListener(b,d=function(){a.removeEventListener(b,d,!1),a.readyState="complete"},!1),a.readyState="loading"),e()}(document,"DOMContentLoaded"),b.on("raphael.DOMload",function(){u=!0}),function(){if(c.svg){var a="hasOwnProperty",b=String,d=parseFloat,e=parseInt,f=Math,g=f.max,h=f.abs,i=f.pow,j=/[, ]+/,k=c.eve,l="",m=" ",n="http://www.w3.org/1999/xlink",o={block:"M5,0 0,2.5 5,5z",classic:"M5,0 0,2.5 5,5 3.5,3 3.5,2z",diamond:"M2.5,0 5,2.5 2.5,5 0,2.5z",open:"M6,1 1,3.5 6,6",oval:"M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z"},p={};c.toString=function(){return"Your browser supports SVG.\nYou are running Raphaël "+this.version};var q=function(d,e){if(e){"string"==typeof d&&(d=q(d));for(var f in e)e[a](f)&&("xlink:"==f.substring(0,6)?d.setAttributeNS(n,f.substring(6),b(e[f])):d.setAttribute(f,b(e[f])))}else d=c._g.doc.createElementNS("http://www.w3.org/2000/svg",d),d.style&&(d.style.webkitTapHighlightColor="rgba(0,0,0,0)");return d},r=function(a,e){var j="linear",k=a.id+e,m=.5,n=.5,o=a.node,p=a.paper,r=o.style,s=c._g.doc.getElementById(k);if(!s){if(e=b(e).replace(c._radial_gradient,function(a,b,c){if(j="radial",b&&c){m=d(b),n=d(c);var e=2*(n>.5)-1;i(m-.5,2)+i(n-.5,2)>.25&&(n=f.sqrt(.25-i(m-.5,2))*e+.5)&&.5!=n&&(n=n.toFixed(5)-1e-5*e)}return l}),e=e.split(/\s*\-\s*/),"linear"==j){var t=e.shift();if(t=-d(t),isNaN(t))return null;var u=[0,0,f.cos(c.rad(t)),f.sin(c.rad(t))],v=1/(g(h(u[2]),h(u[3]))||1);u[2]*=v,u[3]*=v,u[2]<0&&(u[0]=-u[2],u[2]=0),u[3]<0&&(u[1]=-u[3],u[3]=0)}var w=c._parseDots(e);if(!w)return null;if(k=k.replace(/[\(\)\s,\xb0#]/g,"_"),a.gradient&&k!=a.gradient.id&&(p.defs.removeChild(a.gradient),delete a.gradient),!a.gradient){s=q(j+"Gradient",{id:k}),a.gradient=s,q(s,"radial"==j?{fx:m,fy:n}:{x1:u[0],y1:u[1],x2:u[2],y2:u[3],gradientTransform:a.matrix.invert()}),p.defs.appendChild(s);for(var x=0,y=w.length;y>x;x++)s.appendChild(q("stop",{offset:w[x].offset?w[x].offset:x?"100%":"0%","stop-color":w[x].color||"#fff"}))}}return q(o,{fill:"url(#"+k+")",opacity:1,"fill-opacity":1}),r.fill=l,r.opacity=1,r.fillOpacity=1,1},s=function(a){var b=a.getBBox(1);q(a.pattern,{patternTransform:a.matrix.invert()+" translate("+b.x+","+b.y+")"})},t=function(d,e,f){if("path"==d.type){for(var g,h,i,j,k,m=b(e).toLowerCase().split("-"),n=d.paper,r=f?"end":"start",s=d.node,t=d.attrs,u=t["stroke-width"],v=m.length,w="classic",x=3,y=3,z=5;v--;)switch(m[v]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":w=m[v];break;case"wide":y=5;break;case"narrow":y=2;break;case"long":x=5;break;case"short":x=2}if("open"==w?(x+=2,y+=2,z+=2,i=1,j=f?4:1,k={fill:"none",stroke:t.stroke}):(j=i=x/2,k={fill:t.stroke,stroke:"none"}),d._.arrows?f?(d._.arrows.endPath&&p[d._.arrows.endPath]--,d._.arrows.endMarker&&p[d._.arrows.endMarker]--):(d._.arrows.startPath&&p[d._.arrows.startPath]--,d._.arrows.startMarker&&p[d._.arrows.startMarker]--):d._.arrows={},"none"!=w){var A="raphael-marker-"+w,B="raphael-marker-"+r+w+x+y;c._g.doc.getElementById(A)?p[A]++:(n.defs.appendChild(q(q("path"),{"stroke-linecap":"round",d:o[w],id:A})),p[A]=1);var C,D=c._g.doc.getElementById(B);D?(p[B]++,C=D.getElementsByTagName("use")[0]):(D=q(q("marker"),{id:B,markerHeight:y,markerWidth:x,orient:"auto",refX:j,refY:y/2}),C=q(q("use"),{"xlink:href":"#"+A,transform:(f?"rotate(180 "+x/2+" "+y/2+") ":l)+"scale("+x/z+","+y/z+")","stroke-width":(1/((x/z+y/z)/2)).toFixed(4)}),D.appendChild(C),n.defs.appendChild(D),p[B]=1),q(C,k);var E=i*("diamond"!=w&&"oval"!=w);f?(g=d._.arrows.startdx*u||0,h=c.getTotalLength(t.path)-E*u):(g=E*u,h=c.getTotalLength(t.path)-(d._.arrows.enddx*u||0)),k={},k["marker-"+r]="url(#"+B+")",(h||g)&&(k.d=c.getSubpath(t.path,g,h)),q(s,k),d._.arrows[r+"Path"]=A,d._.arrows[r+"Marker"]=B,d._.arrows[r+"dx"]=E,d._.arrows[r+"Type"]=w,d._.arrows[r+"String"]=e}else f?(g=d._.arrows.startdx*u||0,h=c.getTotalLength(t.path)-g):(g=0,h=c.getTotalLength(t.path)-(d._.arrows.enddx*u||0)),d._.arrows[r+"Path"]&&q(s,{d:c.getSubpath(t.path,g,h)}),delete d._.arrows[r+"Path"],delete d._.arrows[r+"Marker"],delete d._.arrows[r+"dx"],delete d._.arrows[r+"Type"],delete d._.arrows[r+"String"];for(k in p)if(p[a](k)&&!p[k]){var F=c._g.doc.getElementById(k);F&&F.parentNode.removeChild(F)}}},u={"":[0],none:[0],"-":[3,1],".":[1,1],"-.":[3,1,1,1],"-..":[3,1,1,1,1,1],". ":[1,3],"- ":[4,3],"--":[8,3],"- .":[4,3,1,3],"--.":[8,3,1,3],"--..":[8,3,1,3,1,3]},v=function(a,c,d){if(c=u[b(c).toLowerCase()]){for(var e=a.attrs["stroke-width"]||"1",f={round:e,square:e,butt:0}[a.attrs["stroke-linecap"]||d["stroke-linecap"]]||0,g=[],h=c.length;h--;)g[h]=c[h]*e+(h%2?1:-1)*f;q(a.node,{"stroke-dasharray":g.join(",")})}},w=function(d,f){var i=d.node,k=d.attrs,m=i.style.visibility;i.style.visibility="hidden";for(var o in f)if(f[a](o)){if(!c._availableAttrs[a](o))continue;var p=f[o];switch(k[o]=p,o){case"blur":d.blur(p);break;case"href":case"title":var u=q("title"),w=c._g.doc.createTextNode(p);u.appendChild(w),i.appendChild(u);break;case"target":var x=i.parentNode;if("a"!=x.tagName.toLowerCase()){var u=q("a");x.insertBefore(u,i),u.appendChild(i),x=u}"target"==o?x.setAttributeNS(n,"show","blank"==p?"new":p):x.setAttributeNS(n,o,p);break;case"cursor":i.style.cursor=p;break;case"transform":d.transform(p);break;case"arrow-start":t(d,p);break;case"arrow-end":t(d,p,1);break;case"clip-rect":var z=b(p).split(j);if(4==z.length){d.clip&&d.clip.parentNode.parentNode.removeChild(d.clip.parentNode);var A=q("clipPath"),B=q("rect");A.id=c.createUUID(),q(B,{x:z[0],y:z[1],width:z[2],height:z[3]}),A.appendChild(B),d.paper.defs.appendChild(A),q(i,{"clip-path":"url(#"+A.id+")"}),d.clip=B}if(!p){var C=i.getAttribute("clip-path");if(C){var D=c._g.doc.getElementById(C.replace(/(^url\(#|\)$)/g,l));D&&D.parentNode.removeChild(D),q(i,{"clip-path":l}),delete d.clip}}break;case"path":"path"==d.type&&(q(i,{d:p?k.path=c._pathToAbsolute(p):"M0,0"}),d._.dirty=1,d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1)));break;case"width":if(i.setAttribute(o,p),d._.dirty=1,!k.fx)break;o="x",p=k.x;case"x":k.fx&&(p=-k.x-(k.width||0));case"rx":if("rx"==o&&"rect"==d.type)break;case"cx":i.setAttribute(o,p),d.pattern&&s(d),d._.dirty=1;break;case"height":if(i.setAttribute(o,p),d._.dirty=1,!k.fy)break;o="y",p=k.y;case"y":k.fy&&(p=-k.y-(k.height||0));case"ry":if("ry"==o&&"rect"==d.type)break;case"cy":i.setAttribute(o,p),d.pattern&&s(d),d._.dirty=1;break;case"r":"rect"==d.type?q(i,{rx:p,ry:p}):i.setAttribute(o,p),d._.dirty=1;break;case"src":"image"==d.type&&i.setAttributeNS(n,"href",p);break;case"stroke-width":(1!=d._.sx||1!=d._.sy)&&(p/=g(h(d._.sx),h(d._.sy))||1),d.paper._vbSize&&(p*=d.paper._vbSize),i.setAttribute(o,p),k["stroke-dasharray"]&&v(d,k["stroke-dasharray"],f),d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"stroke-dasharray":v(d,p,f);break;case"fill":var E=b(p).match(c._ISURL);if(E){A=q("pattern");var F=q("image");A.id=c.createUUID(),q(A,{x:0,y:0,patternUnits:"userSpaceOnUse",height:1,width:1}),q(F,{x:0,y:0,"xlink:href":E[1]}),A.appendChild(F),function(a){c._preload(E[1],function(){var b=this.offsetWidth,c=this.offsetHeight;q(a,{width:b,height:c}),q(F,{width:b,height:c}),d.paper.safari()})}(A),d.paper.defs.appendChild(A),q(i,{fill:"url(#"+A.id+")"}),d.pattern=A,d.pattern&&s(d);break}var G=c.getRGB(p);if(G.error){if(("circle"==d.type||"ellipse"==d.type||"r"!=b(p).charAt())&&r(d,p)){if("opacity"in k||"fill-opacity"in k){var H=c._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l));if(H){var I=H.getElementsByTagName("stop");q(I[I.length-1],{"stop-opacity":("opacity"in k?k.opacity:1)*("fill-opacity"in k?k["fill-opacity"]:1)})}}k.gradient=p,k.fill="none";break}}else delete f.gradient,delete k.gradient,!c.is(k.opacity,"undefined")&&c.is(f.opacity,"undefined")&&q(i,{opacity:k.opacity}),!c.is(k["fill-opacity"],"undefined")&&c.is(f["fill-opacity"],"undefined")&&q(i,{"fill-opacity":k["fill-opacity"]});G[a]("opacity")&&q(i,{"fill-opacity":G.opacity>1?G.opacity/100:G.opacity});case"stroke":G=c.getRGB(p),i.setAttribute(o,G.hex),"stroke"==o&&G[a]("opacity")&&q(i,{"stroke-opacity":G.opacity>1?G.opacity/100:G.opacity}),"stroke"==o&&d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"gradient":("circle"==d.type||"ellipse"==d.type||"r"!=b(p).charAt())&&r(d,p);break;case"opacity":k.gradient&&!k[a]("stroke-opacity")&&q(i,{"stroke-opacity":p>1?p/100:p});case"fill-opacity":if(k.gradient){H=c._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l)),H&&(I=H.getElementsByTagName("stop"),q(I[I.length-1],{"stop-opacity":p}));break}default:"font-size"==o&&(p=e(p,10)+"px");var J=o.replace(/(\-.)/g,function(a){return a.substring(1).toUpperCase()});i.style[J]=p,d._.dirty=1,i.setAttribute(o,p)}}y(d,f),i.style.visibility=m},x=1.2,y=function(d,f){if("text"==d.type&&(f[a]("text")||f[a]("font")||f[a]("font-size")||f[a]("x")||f[a]("y"))){var g=d.attrs,h=d.node,i=h.firstChild?e(c._g.doc.defaultView.getComputedStyle(h.firstChild,l).getPropertyValue("font-size"),10):10;
+if(f[a]("text")){for(g.text=f.text;h.firstChild;)h.removeChild(h.firstChild);for(var j,k=b(f.text).split("\n"),m=[],n=0,o=k.length;o>n;n++)j=q("tspan"),n&&q(j,{dy:i*x,x:g.x}),j.appendChild(c._g.doc.createTextNode(k[n])),h.appendChild(j),m[n]=j}else for(m=h.getElementsByTagName("tspan"),n=0,o=m.length;o>n;n++)n?q(m[n],{dy:i*x,x:g.x}):q(m[0],{dy:0});q(h,{x:g.x,y:g.y}),d._.dirty=1;var p=d._getBBox(),r=g.y-(p.y+p.height/2);r&&c.is(r,"finite")&&q(m[0],{dy:r})}},z=function(a,b){this[0]=this.node=a,a.raphael=!0,this.id=c._oid++,a.raphaelid=this.id,this.matrix=c.matrix(),this.realPath=null,this.paper=b,this.attrs=this.attrs||{},this._={transform:[],sx:1,sy:1,deg:0,dx:0,dy:0,dirty:1},!b.bottom&&(b.bottom=this),this.prev=b.top,b.top&&(b.top.next=this),b.top=this,this.next=null},A=c.el;z.prototype=A,A.constructor=z,c._engine.path=function(a,b){var c=q("path");b.canvas&&b.canvas.appendChild(c);var d=new z(c,b);return d.type="path",w(d,{fill:"none",stroke:"#000",path:a}),d},A.rotate=function(a,c,e){if(this.removed)return this;if(a=b(a).split(j),a.length-1&&(c=d(a[1]),e=d(a[2])),a=d(a[0]),null==e&&(c=e),null==c||null==e){var f=this.getBBox(1);c=f.x+f.width/2,e=f.y+f.height/2}return this.transform(this._.transform.concat([["r",a,c,e]])),this},A.scale=function(a,c,e,f){if(this.removed)return this;if(a=b(a).split(j),a.length-1&&(c=d(a[1]),e=d(a[2]),f=d(a[3])),a=d(a[0]),null==c&&(c=a),null==f&&(e=f),null==e||null==f)var g=this.getBBox(1);return e=null==e?g.x+g.width/2:e,f=null==f?g.y+g.height/2:f,this.transform(this._.transform.concat([["s",a,c,e,f]])),this},A.translate=function(a,c){return this.removed?this:(a=b(a).split(j),a.length-1&&(c=d(a[1])),a=d(a[0])||0,c=+c||0,this.transform(this._.transform.concat([["t",a,c]])),this)},A.transform=function(b){var d=this._;if(null==b)return d.transform;if(c._extractTransform(this,b),this.clip&&q(this.clip,{transform:this.matrix.invert()}),this.pattern&&s(this),this.node&&q(this.node,{transform:this.matrix}),1!=d.sx||1!=d.sy){var e=this.attrs[a]("stroke-width")?this.attrs["stroke-width"]:1;this.attr({"stroke-width":e})}return this},A.hide=function(){return!this.removed&&this.paper.safari(this.node.style.display="none"),this},A.show=function(){return!this.removed&&this.paper.safari(this.node.style.display=""),this},A.remove=function(){if(!this.removed&&this.node.parentNode){var a=this.paper;a.__set__&&a.__set__.exclude(this),k.unbind("raphael.*.*."+this.id),this.gradient&&a.defs.removeChild(this.gradient),c._tear(this,a),"a"==this.node.parentNode.tagName.toLowerCase()?this.node.parentNode.parentNode.removeChild(this.node.parentNode):this.node.parentNode.removeChild(this.node);for(var b in this)this[b]="function"==typeof this[b]?c._removedFactory(b):null;this.removed=!0}},A._getBBox=function(){if("none"==this.node.style.display){this.show();var a=!0}var b={};try{b=this.node.getBBox()}catch(c){}finally{b=b||{}}return a&&this.hide(),b},A.attr=function(b,d){if(this.removed)return this;if(null==b){var e={};for(var f in this.attrs)this.attrs[a](f)&&(e[f]=this.attrs[f]);return e.gradient&&"none"==e.fill&&(e.fill=e.gradient)&&delete e.gradient,e.transform=this._.transform,e}if(null==d&&c.is(b,"string")){if("fill"==b&&"none"==this.attrs.fill&&this.attrs.gradient)return this.attrs.gradient;if("transform"==b)return this._.transform;for(var g=b.split(j),h={},i=0,l=g.length;l>i;i++)b=g[i],h[b]=b in this.attrs?this.attrs[b]:c.is(this.paper.customAttributes[b],"function")?this.paper.customAttributes[b].def:c._availableAttrs[b];return l-1?h:h[g[0]]}if(null==d&&c.is(b,"array")){for(h={},i=0,l=b.length;l>i;i++)h[b[i]]=this.attr(b[i]);return h}if(null!=d){var m={};m[b]=d}else null!=b&&c.is(b,"object")&&(m=b);for(var n in m)k("raphael.attr."+n+"."+this.id,this,m[n]);for(n in this.paper.customAttributes)if(this.paper.customAttributes[a](n)&&m[a](n)&&c.is(this.paper.customAttributes[n],"function")){var o=this.paper.customAttributes[n].apply(this,[].concat(m[n]));this.attrs[n]=m[n];for(var p in o)o[a](p)&&(m[p]=o[p])}return w(this,m),this},A.toFront=function(){if(this.removed)return this;"a"==this.node.parentNode.tagName.toLowerCase()?this.node.parentNode.parentNode.appendChild(this.node.parentNode):this.node.parentNode.appendChild(this.node);var a=this.paper;return a.top!=this&&c._tofront(this,a),this},A.toBack=function(){if(this.removed)return this;var a=this.node.parentNode;return"a"==a.tagName.toLowerCase()?a.parentNode.insertBefore(this.node.parentNode,this.node.parentNode.parentNode.firstChild):a.firstChild!=this.node&&a.insertBefore(this.node,this.node.parentNode.firstChild),c._toback(this,this.paper),this.paper,this},A.insertAfter=function(a){if(this.removed)return this;var b=a.node||a[a.length-1].node;return b.nextSibling?b.parentNode.insertBefore(this.node,b.nextSibling):b.parentNode.appendChild(this.node),c._insertafter(this,a,this.paper),this},A.insertBefore=function(a){if(this.removed)return this;var b=a.node||a[0].node;return b.parentNode.insertBefore(this.node,b),c._insertbefore(this,a,this.paper),this},A.blur=function(a){var b=this;if(0!==+a){var d=q("filter"),e=q("feGaussianBlur");b.attrs.blur=a,d.id=c.createUUID(),q(e,{stdDeviation:+a||1.5}),d.appendChild(e),b.paper.defs.appendChild(d),b._blur=d,q(b.node,{filter:"url(#"+d.id+")"})}else b._blur&&(b._blur.parentNode.removeChild(b._blur),delete b._blur,delete b.attrs.blur),b.node.removeAttribute("filter");return b},c._engine.circle=function(a,b,c,d){var e=q("circle");a.canvas&&a.canvas.appendChild(e);var f=new z(e,a);return f.attrs={cx:b,cy:c,r:d,fill:"none",stroke:"#000"},f.type="circle",q(e,f.attrs),f},c._engine.rect=function(a,b,c,d,e,f){var g=q("rect");a.canvas&&a.canvas.appendChild(g);var h=new z(g,a);return h.attrs={x:b,y:c,width:d,height:e,r:f||0,rx:f||0,ry:f||0,fill:"none",stroke:"#000"},h.type="rect",q(g,h.attrs),h},c._engine.ellipse=function(a,b,c,d,e){var f=q("ellipse");a.canvas&&a.canvas.appendChild(f);var g=new z(f,a);return g.attrs={cx:b,cy:c,rx:d,ry:e,fill:"none",stroke:"#000"},g.type="ellipse",q(f,g.attrs),g},c._engine.image=function(a,b,c,d,e,f){var g=q("image");q(g,{x:c,y:d,width:e,height:f,preserveAspectRatio:"none"}),g.setAttributeNS(n,"href",b),a.canvas&&a.canvas.appendChild(g);var h=new z(g,a);return h.attrs={x:c,y:d,width:e,height:f,src:b},h.type="image",h},c._engine.text=function(a,b,d,e){var f=q("text");a.canvas&&a.canvas.appendChild(f);var g=new z(f,a);return g.attrs={x:b,y:d,"text-anchor":"middle",text:e,font:c._availableAttrs.font,stroke:"none",fill:"#000"},g.type="text",w(g,g.attrs),g},c._engine.setSize=function(a,b){return this.width=a||this.width,this.height=b||this.height,this.canvas.setAttribute("width",this.width),this.canvas.setAttribute("height",this.height),this._viewBox&&this.setViewBox.apply(this,this._viewBox),this},c._engine.create=function(){var a=c._getContainer.apply(0,arguments),b=a&&a.container,d=a.x,e=a.y,f=a.width,g=a.height;if(!b)throw new Error("SVG container not found.");var h,i=q("svg"),j="overflow:hidden;";return d=d||0,e=e||0,f=f||512,g=g||342,q(i,{height:g,version:1.1,width:f,xmlns:"http://www.w3.org/2000/svg"}),1==b?(i.style.cssText=j+"position:absolute;left:"+d+"px;top:"+e+"px",c._g.doc.body.appendChild(i),h=1):(i.style.cssText=j+"position:relative",b.firstChild?b.insertBefore(i,b.firstChild):b.appendChild(i)),b=new c._Paper,b.width=f,b.height=g,b.canvas=i,b.clear(),b._left=b._top=0,h&&(b.renderfix=function(){}),b.renderfix(),b},c._engine.setViewBox=function(a,b,c,d,e){k("raphael.setViewBox",this,this._viewBox,[a,b,c,d,e]);var f,h,i=g(c/this.width,d/this.height),j=this.top,l=e?"meet":"xMinYMin";for(null==a?(this._vbSize&&(i=1),delete this._vbSize,f="0 0 "+this.width+m+this.height):(this._vbSize=i,f=a+m+b+m+c+m+d),q(this.canvas,{viewBox:f,preserveAspectRatio:l});i&&j;)h="stroke-width"in j.attrs?j.attrs["stroke-width"]:1,j.attr({"stroke-width":h}),j._.dirty=1,j._.dirtyT=1,j=j.prev;return this._viewBox=[a,b,c,d,!!e],this},c.prototype.renderfix=function(){var a,b=this.canvas,c=b.style;try{a=b.getScreenCTM()||b.createSVGMatrix()}catch(d){a=b.createSVGMatrix()}var e=-a.e%1,f=-a.f%1;(e||f)&&(e&&(this._left=(this._left+e)%1,c.left=this._left+"px"),f&&(this._top=(this._top+f)%1,c.top=this._top+"px"))},c.prototype.clear=function(){c.eve("raphael.clear",this);for(var a=this.canvas;a.firstChild;)a.removeChild(a.firstChild);this.bottom=this.top=null,(this.desc=q("desc")).appendChild(c._g.doc.createTextNode("Created with Raphaël "+c.version)),a.appendChild(this.desc),a.appendChild(this.defs=q("defs"))},c.prototype.remove=function(){k("raphael.remove",this),this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas);for(var a in this)this[a]="function"==typeof this[a]?c._removedFactory(a):null};var B=c.st;for(var C in A)A[a](C)&&!B[a](C)&&(B[C]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(C))}}(),function(){if(c.vml){var a="hasOwnProperty",b=String,d=parseFloat,e=Math,f=e.round,g=e.max,h=e.min,i=e.abs,j="fill",k=/[, ]+/,l=c.eve,m=" progid:DXImageTransform.Microsoft",n=" ",o="",p={M:"m",L:"l",C:"c",Z:"x",m:"t",l:"r",c:"v",z:"x"},q=/([clmz]),?([^clmz]*)/gi,r=/ progid:\S+Blur\([^\)]+\)/g,s=/-?[^,\s-]+/g,t="position:absolute;left:0;top:0;width:1px;height:1px",u=21600,v={path:1,rect:1,image:1},w={circle:1,ellipse:1},x=function(a){var d=/[ahqstv]/gi,e=c._pathToAbsolute;if(b(a).match(d)&&(e=c._path2curve),d=/[clmz]/g,e==c._pathToAbsolute&&!b(a).match(d)){var g=b(a).replace(q,function(a,b,c){var d=[],e="m"==b.toLowerCase(),g=p[b];return c.replace(s,function(a){e&&2==d.length&&(g+=d+p["m"==b?"l":"L"],d=[]),d.push(f(a*u))}),g+d});return g}var h,i,j=e(a);g=[];for(var k=0,l=j.length;l>k;k++){h=j[k],i=j[k][0].toLowerCase(),"z"==i&&(i="x");for(var m=1,r=h.length;r>m;m++)i+=f(h[m]*u)+(m!=r-1?",":o);g.push(i)}return g.join(n)},y=function(a,b,d){var e=c.matrix();return e.rotate(-a,.5,.5),{dx:e.x(b,d),dy:e.y(b,d)}},z=function(a,b,c,d,e,f){var g=a._,h=a.matrix,k=g.fillpos,l=a.node,m=l.style,o=1,p="",q=u/b,r=u/c;if(m.visibility="hidden",b&&c){if(l.coordsize=i(q)+n+i(r),m.rotation=f*(0>b*c?-1:1),f){var s=y(f,d,e);d=s.dx,e=s.dy}if(0>b&&(p+="x"),0>c&&(p+=" y")&&(o=-1),m.flip=p,l.coordorigin=d*-q+n+e*-r,k||g.fillsize){var t=l.getElementsByTagName(j);t=t&&t[0],l.removeChild(t),k&&(s=y(f,h.x(k[0],k[1]),h.y(k[0],k[1])),t.position=s.dx*o+n+s.dy*o),g.fillsize&&(t.size=g.fillsize[0]*i(b)+n+g.fillsize[1]*i(c)),l.appendChild(t)}m.visibility="visible"}};c.toString=function(){return"Your browser doesn’t support SVG. Falling down to VML.\nYou are running Raphaël "+this.version};var A=function(a,c,d){for(var e=b(c).toLowerCase().split("-"),f=d?"end":"start",g=e.length,h="classic",i="medium",j="medium";g--;)switch(e[g]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":h=e[g];break;case"wide":case"narrow":j=e[g];break;case"long":case"short":i=e[g]}var k=a.node.getElementsByTagName("stroke")[0];k[f+"arrow"]=h,k[f+"arrowlength"]=i,k[f+"arrowwidth"]=j},B=function(e,i){e.attrs=e.attrs||{};var l=e.node,m=e.attrs,p=l.style,q=v[e.type]&&(i.x!=m.x||i.y!=m.y||i.width!=m.width||i.height!=m.height||i.cx!=m.cx||i.cy!=m.cy||i.rx!=m.rx||i.ry!=m.ry||i.r!=m.r),r=w[e.type]&&(m.cx!=i.cx||m.cy!=i.cy||m.r!=i.r||m.rx!=i.rx||m.ry!=i.ry),s=e;for(var t in i)i[a](t)&&(m[t]=i[t]);if(q&&(m.path=c._getPath[e.type](e),e._.dirty=1),i.href&&(l.href=i.href),i.title&&(l.title=i.title),i.target&&(l.target=i.target),i.cursor&&(p.cursor=i.cursor),"blur"in i&&e.blur(i.blur),(i.path&&"path"==e.type||q)&&(l.path=x(~b(m.path).toLowerCase().indexOf("r")?c._pathToAbsolute(m.path):m.path),"image"==e.type&&(e._.fillpos=[m.x,m.y],e._.fillsize=[m.width,m.height],z(e,1,1,0,0,0))),"transform"in i&&e.transform(i.transform),r){var y=+m.cx,B=+m.cy,D=+m.rx||+m.r||0,E=+m.ry||+m.r||0;l.path=c.format("ar{0},{1},{2},{3},{4},{1},{4},{1}x",f((y-D)*u),f((B-E)*u),f((y+D)*u),f((B+E)*u),f(y*u)),e._.dirty=1}if("clip-rect"in i){var G=b(i["clip-rect"]).split(k);if(4==G.length){G[2]=+G[2]+ +G[0],G[3]=+G[3]+ +G[1];var H=l.clipRect||c._g.doc.createElement("div"),I=H.style;I.clip=c.format("rect({1}px {2}px {3}px {0}px)",G),l.clipRect||(I.position="absolute",I.top=0,I.left=0,I.width=e.paper.width+"px",I.height=e.paper.height+"px",l.parentNode.insertBefore(H,l),H.appendChild(l),l.clipRect=H)}i["clip-rect"]||l.clipRect&&(l.clipRect.style.clip="auto")}if(e.textpath){var J=e.textpath.style;i.font&&(J.font=i.font),i["font-family"]&&(J.fontFamily='"'+i["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g,o)+'"'),i["font-size"]&&(J.fontSize=i["font-size"]),i["font-weight"]&&(J.fontWeight=i["font-weight"]),i["font-style"]&&(J.fontStyle=i["font-style"])}if("arrow-start"in i&&A(s,i["arrow-start"]),"arrow-end"in i&&A(s,i["arrow-end"],1),null!=i.opacity||null!=i["stroke-width"]||null!=i.fill||null!=i.src||null!=i.stroke||null!=i["stroke-width"]||null!=i["stroke-opacity"]||null!=i["fill-opacity"]||null!=i["stroke-dasharray"]||null!=i["stroke-miterlimit"]||null!=i["stroke-linejoin"]||null!=i["stroke-linecap"]){var K=l.getElementsByTagName(j),L=!1;if(K=K&&K[0],!K&&(L=K=F(j)),"image"==e.type&&i.src&&(K.src=i.src),i.fill&&(K.on=!0),(null==K.on||"none"==i.fill||null===i.fill)&&(K.on=!1),K.on&&i.fill){var M=b(i.fill).match(c._ISURL);if(M){K.parentNode==l&&l.removeChild(K),K.rotate=!0,K.src=M[1],K.type="tile";var N=e.getBBox(1);K.position=N.x+n+N.y,e._.fillpos=[N.x,N.y],c._preload(M[1],function(){e._.fillsize=[this.offsetWidth,this.offsetHeight]})}else K.color=c.getRGB(i.fill).hex,K.src=o,K.type="solid",c.getRGB(i.fill).error&&(s.type in{circle:1,ellipse:1}||"r"!=b(i.fill).charAt())&&C(s,i.fill,K)&&(m.fill="none",m.gradient=i.fill,K.rotate=!1)}if("fill-opacity"in i||"opacity"in i){var O=((+m["fill-opacity"]+1||2)-1)*((+m.opacity+1||2)-1)*((+c.getRGB(i.fill).o+1||2)-1);O=h(g(O,0),1),K.opacity=O,K.src&&(K.color="none")}l.appendChild(K);var P=l.getElementsByTagName("stroke")&&l.getElementsByTagName("stroke")[0],Q=!1;!P&&(Q=P=F("stroke")),(i.stroke&&"none"!=i.stroke||i["stroke-width"]||null!=i["stroke-opacity"]||i["stroke-dasharray"]||i["stroke-miterlimit"]||i["stroke-linejoin"]||i["stroke-linecap"])&&(P.on=!0),("none"==i.stroke||null===i.stroke||null==P.on||0==i.stroke||0==i["stroke-width"])&&(P.on=!1);var R=c.getRGB(i.stroke);P.on&&i.stroke&&(P.color=R.hex),O=((+m["stroke-opacity"]+1||2)-1)*((+m.opacity+1||2)-1)*((+R.o+1||2)-1);var S=.75*(d(i["stroke-width"])||1);if(O=h(g(O,0),1),null==i["stroke-width"]&&(S=m["stroke-width"]),i["stroke-width"]&&(P.weight=S),S&&1>S&&(O*=S)&&(P.weight=1),P.opacity=O,i["stroke-linejoin"]&&(P.joinstyle=i["stroke-linejoin"]||"miter"),P.miterlimit=i["stroke-miterlimit"]||8,i["stroke-linecap"]&&(P.endcap="butt"==i["stroke-linecap"]?"flat":"square"==i["stroke-linecap"]?"square":"round"),i["stroke-dasharray"]){var T={"-":"shortdash",".":"shortdot","-.":"shortdashdot","-..":"shortdashdotdot",". ":"dot","- ":"dash","--":"longdash","- .":"dashdot","--.":"longdashdot","--..":"longdashdotdot"};P.dashstyle=T[a](i["stroke-dasharray"])?T[i["stroke-dasharray"]]:o}Q&&l.appendChild(P)}if("text"==s.type){s.paper.canvas.style.display=o;var U=s.paper.span,V=100,W=m.font&&m.font.match(/\d+(?:\.\d*)?(?=px)/);p=U.style,m.font&&(p.font=m.font),m["font-family"]&&(p.fontFamily=m["font-family"]),m["font-weight"]&&(p.fontWeight=m["font-weight"]),m["font-style"]&&(p.fontStyle=m["font-style"]),W=d(m["font-size"]||W&&W[0])||10,p.fontSize=W*V+"px",s.textpath.string&&(U.innerHTML=b(s.textpath.string).replace(/</g,"&#60;").replace(/&/g,"&#38;").replace(/\n/g,"<br>"));var X=U.getBoundingClientRect();s.W=m.w=(X.right-X.left)/V,s.H=m.h=(X.bottom-X.top)/V,s.X=m.x,s.Y=m.y+s.H/2,("x"in i||"y"in i)&&(s.path.v=c.format("m{0},{1}l{2},{1}",f(m.x*u),f(m.y*u),f(m.x*u)+1));for(var Y=["x","y","text","font","font-family","font-weight","font-style","font-size"],Z=0,$=Y.length;$>Z;Z++)if(Y[Z]in i){s._.dirty=1;break}switch(m["text-anchor"]){case"start":s.textpath.style["v-text-align"]="left",s.bbx=s.W/2;break;case"end":s.textpath.style["v-text-align"]="right",s.bbx=-s.W/2;break;default:s.textpath.style["v-text-align"]="center",s.bbx=0}s.textpath.style["v-text-kern"]=!0}},C=function(a,f,g){a.attrs=a.attrs||{};var h=(a.attrs,Math.pow),i="linear",j=".5 .5";if(a.attrs.gradient=f,f=b(f).replace(c._radial_gradient,function(a,b,c){return i="radial",b&&c&&(b=d(b),c=d(c),h(b-.5,2)+h(c-.5,2)>.25&&(c=e.sqrt(.25-h(b-.5,2))*(2*(c>.5)-1)+.5),j=b+n+c),o}),f=f.split(/\s*\-\s*/),"linear"==i){var k=f.shift();if(k=-d(k),isNaN(k))return null}var l=c._parseDots(f);if(!l)return null;if(a=a.shape||a.node,l.length){a.removeChild(g),g.on=!0,g.method="none",g.color=l[0].color,g.color2=l[l.length-1].color;for(var m=[],p=0,q=l.length;q>p;p++)l[p].offset&&m.push(l[p].offset+n+l[p].color);g.colors=m.length?m.join():"0% "+g.color,"radial"==i?(g.type="gradientTitle",g.focus="100%",g.focussize="0 0",g.focusposition=j,g.angle=0):(g.type="gradient",g.angle=(270-k)%360),a.appendChild(g)}return 1},D=function(a,b){this[0]=this.node=a,a.raphael=!0,this.id=c._oid++,a.raphaelid=this.id,this.X=0,this.Y=0,this.attrs={},this.paper=b,this.matrix=c.matrix(),this._={transform:[],sx:1,sy:1,dx:0,dy:0,deg:0,dirty:1,dirtyT:1},!b.bottom&&(b.bottom=this),this.prev=b.top,b.top&&(b.top.next=this),b.top=this,this.next=null},E=c.el;D.prototype=E,E.constructor=D,E.transform=function(a){if(null==a)return this._.transform;var d,e=this.paper._viewBoxShift,f=e?"s"+[e.scale,e.scale]+"-1-1t"+[e.dx,e.dy]:o;e&&(d=a=b(a).replace(/\.{3}|\u2026/g,this._.transform||o)),c._extractTransform(this,f+a);var g,h=this.matrix.clone(),i=this.skew,j=this.node,k=~b(this.attrs.fill).indexOf("-"),l=!b(this.attrs.fill).indexOf("url(");if(h.translate(1,1),l||k||"image"==this.type)if(i.matrix="1 0 0 1",i.offset="0 0",g=h.split(),k&&g.noRotation||!g.isSimple){j.style.filter=h.toFilter();var m=this.getBBox(),p=this.getBBox(1),q=m.x-p.x,r=m.y-p.y;j.coordorigin=q*-u+n+r*-u,z(this,1,1,q,r,0)}else j.style.filter=o,z(this,g.scalex,g.scaley,g.dx,g.dy,g.rotate);else j.style.filter=o,i.matrix=b(h),i.offset=h.offset();return d&&(this._.transform=d),this},E.rotate=function(a,c,e){if(this.removed)return this;if(null!=a){if(a=b(a).split(k),a.length-1&&(c=d(a[1]),e=d(a[2])),a=d(a[0]),null==e&&(c=e),null==c||null==e){var f=this.getBBox(1);c=f.x+f.width/2,e=f.y+f.height/2}return this._.dirtyT=1,this.transform(this._.transform.concat([["r",a,c,e]])),this}},E.translate=function(a,c){return this.removed?this:(a=b(a).split(k),a.length-1&&(c=d(a[1])),a=d(a[0])||0,c=+c||0,this._.bbox&&(this._.bbox.x+=a,this._.bbox.y+=c),this.transform(this._.transform.concat([["t",a,c]])),this)},E.scale=function(a,c,e,f){if(this.removed)return this;if(a=b(a).split(k),a.length-1&&(c=d(a[1]),e=d(a[2]),f=d(a[3]),isNaN(e)&&(e=null),isNaN(f)&&(f=null)),a=d(a[0]),null==c&&(c=a),null==f&&(e=f),null==e||null==f)var g=this.getBBox(1);return e=null==e?g.x+g.width/2:e,f=null==f?g.y+g.height/2:f,this.transform(this._.transform.concat([["s",a,c,e,f]])),this._.dirtyT=1,this},E.hide=function(){return!this.removed&&(this.node.style.display="none"),this},E.show=function(){return!this.removed&&(this.node.style.display=o),this},E._getBBox=function(){return this.removed?{}:{x:this.X+(this.bbx||0)-this.W/2,y:this.Y-this.H,width:this.W,height:this.H}},E.remove=function(){if(!this.removed&&this.node.parentNode){this.paper.__set__&&this.paper.__set__.exclude(this),c.eve.unbind("raphael.*.*."+this.id),c._tear(this,this.paper),this.node.parentNode.removeChild(this.node),this.shape&&this.shape.parentNode.removeChild(this.shape);for(var a in this)this[a]="function"==typeof this[a]?c._removedFactory(a):null;this.removed=!0}},E.attr=function(b,d){if(this.removed)return this;if(null==b){var e={};for(var f in this.attrs)this.attrs[a](f)&&(e[f]=this.attrs[f]);return e.gradient&&"none"==e.fill&&(e.fill=e.gradient)&&delete e.gradient,e.transform=this._.transform,e}if(null==d&&c.is(b,"string")){if(b==j&&"none"==this.attrs.fill&&this.attrs.gradient)return this.attrs.gradient;for(var g=b.split(k),h={},i=0,m=g.length;m>i;i++)b=g[i],h[b]=b in this.attrs?this.attrs[b]:c.is(this.paper.customAttributes[b],"function")?this.paper.customAttributes[b].def:c._availableAttrs[b];return m-1?h:h[g[0]]}if(this.attrs&&null==d&&c.is(b,"array")){for(h={},i=0,m=b.length;m>i;i++)h[b[i]]=this.attr(b[i]);return h}var n;null!=d&&(n={},n[b]=d),null==d&&c.is(b,"object")&&(n=b);for(var o in n)l("raphael.attr."+o+"."+this.id,this,n[o]);if(n){for(o in this.paper.customAttributes)if(this.paper.customAttributes[a](o)&&n[a](o)&&c.is(this.paper.customAttributes[o],"function")){var p=this.paper.customAttributes[o].apply(this,[].concat(n[o]));this.attrs[o]=n[o];for(var q in p)p[a](q)&&(n[q]=p[q])}n.text&&"text"==this.type&&(this.textpath.string=n.text),B(this,n)}return this},E.toFront=function(){return!this.removed&&this.node.parentNode.appendChild(this.node),this.paper&&this.paper.top!=this&&c._tofront(this,this.paper),this},E.toBack=function(){return this.removed?this:(this.node.parentNode.firstChild!=this.node&&(this.node.parentNode.insertBefore(this.node,this.node.parentNode.firstChild),c._toback(this,this.paper)),this)},E.insertAfter=function(a){return this.removed?this:(a.constructor==c.st.constructor&&(a=a[a.length-1]),a.node.nextSibling?a.node.parentNode.insertBefore(this.node,a.node.nextSibling):a.node.parentNode.appendChild(this.node),c._insertafter(this,a,this.paper),this)},E.insertBefore=function(a){return this.removed?this:(a.constructor==c.st.constructor&&(a=a[0]),a.node.parentNode.insertBefore(this.node,a.node),c._insertbefore(this,a,this.paper),this)},E.blur=function(a){var b=this.node.runtimeStyle,d=b.filter;return d=d.replace(r,o),0!==+a?(this.attrs.blur=a,b.filter=d+n+m+".Blur(pixelradius="+(+a||1.5)+")",b.margin=c.format("-{0}px 0 0 -{0}px",f(+a||1.5))):(b.filter=d,b.margin=0,delete this.attrs.blur),this},c._engine.path=function(a,b){var c=F("shape");c.style.cssText=t,c.coordsize=u+n+u,c.coordorigin=b.coordorigin;var d=new D(c,b),e={fill:"none",stroke:"#000"};a&&(e.path=a),d.type="path",d.path=[],d.Path=o,B(d,e),b.canvas.appendChild(c);var f=F("skew");return f.on=!0,c.appendChild(f),d.skew=f,d.transform(o),d},c._engine.rect=function(a,b,d,e,f,g){var h=c._rectPath(b,d,e,f,g),i=a.path(h),j=i.attrs;return i.X=j.x=b,i.Y=j.y=d,i.W=j.width=e,i.H=j.height=f,j.r=g,j.path=h,i.type="rect",i},c._engine.ellipse=function(a,b,c,d,e){var f=a.path();return f.attrs,f.X=b-d,f.Y=c-e,f.W=2*d,f.H=2*e,f.type="ellipse",B(f,{cx:b,cy:c,rx:d,ry:e}),f},c._engine.circle=function(a,b,c,d){var e=a.path();return e.attrs,e.X=b-d,e.Y=c-d,e.W=e.H=2*d,e.type="circle",B(e,{cx:b,cy:c,r:d}),e},c._engine.image=function(a,b,d,e,f,g){var h=c._rectPath(d,e,f,g),i=a.path(h).attr({stroke:"none"}),k=i.attrs,l=i.node,m=l.getElementsByTagName(j)[0];return k.src=b,i.X=k.x=d,i.Y=k.y=e,i.W=k.width=f,i.H=k.height=g,k.path=h,i.type="image",m.parentNode==l&&l.removeChild(m),m.rotate=!0,m.src=b,m.type="tile",i._.fillpos=[d,e],i._.fillsize=[f,g],l.appendChild(m),z(i,1,1,0,0,0),i},c._engine.text=function(a,d,e,g){var h=F("shape"),i=F("path"),j=F("textpath");d=d||0,e=e||0,g=g||"",i.v=c.format("m{0},{1}l{2},{1}",f(d*u),f(e*u),f(d*u)+1),i.textpathok=!0,j.string=b(g),j.on=!0,h.style.cssText=t,h.coordsize=u+n+u,h.coordorigin="0 0";var k=new D(h,a),l={fill:"#000",stroke:"none",font:c._availableAttrs.font,text:g};k.shape=h,k.path=i,k.textpath=j,k.type="text",k.attrs.text=b(g),k.attrs.x=d,k.attrs.y=e,k.attrs.w=1,k.attrs.h=1,B(k,l),h.appendChild(j),h.appendChild(i),a.canvas.appendChild(h);var m=F("skew");return m.on=!0,h.appendChild(m),k.skew=m,k.transform(o),k},c._engine.setSize=function(a,b){var d=this.canvas.style;return this.width=a,this.height=b,a==+a&&(a+="px"),b==+b&&(b+="px"),d.width=a,d.height=b,d.clip="rect(0 "+a+" "+b+" 0)",this._viewBox&&c._engine.setViewBox.apply(this,this._viewBox),this},c._engine.setViewBox=function(a,b,d,e,f){c.eve("raphael.setViewBox",this,this._viewBox,[a,b,d,e,f]);var h,i,j=this.width,k=this.height,l=1/g(d/j,e/k);return f&&(h=k/e,i=j/d,j>d*h&&(a-=(j-d*h)/2/h),k>e*i&&(b-=(k-e*i)/2/i)),this._viewBox=[a,b,d,e,!!f],this._viewBoxShift={dx:-a,dy:-b,scale:l},this.forEach(function(a){a.transform("...")}),this};var F;c._engine.initWin=function(a){var b=a.document;b.createStyleSheet().addRule(".rvml","behavior:url(#default#VML)");try{!b.namespaces.rvml&&b.namespaces.add("rvml","urn:schemas-microsoft-com:vml"),F=function(a){return b.createElement("<rvml:"+a+' class="rvml">')}}catch(c){F=function(a){return b.createElement("<"+a+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}},c._engine.initWin(c._g.win),c._engine.create=function(){var a=c._getContainer.apply(0,arguments),b=a.container,d=a.height,e=a.width,f=a.x,g=a.y;if(!b)throw new Error("VML container not found.");var h=new c._Paper,i=h.canvas=c._g.doc.createElement("div"),j=i.style;return f=f||0,g=g||0,e=e||512,d=d||342,h.width=e,h.height=d,e==+e&&(e+="px"),d==+d&&(d+="px"),h.coordsize=1e3*u+n+1e3*u,h.coordorigin="0 0",h.span=c._g.doc.createElement("span"),h.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;",i.appendChild(h.span),j.cssText=c.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",e,d),1==b?(c._g.doc.body.appendChild(i),j.left=f+"px",j.top=g+"px",j.position="absolute"):b.firstChild?b.insertBefore(i,b.firstChild):b.appendChild(i),h.renderfix=function(){},h},c.prototype.clear=function(){c.eve("raphael.clear",this),this.canvas.innerHTML=o,this.span=c._g.doc.createElement("span"),this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;",this.canvas.appendChild(this.span),this.bottom=this.top=null},c.prototype.remove=function(){c.eve("raphael.remove",this),this.canvas.parentNode.removeChild(this.canvas);for(var a in this)this[a]="function"==typeof this[a]?c._removedFactory(a):null;return!0};var G=c.st;for(var H in E)E[a](H)&&!G[a](H)&&(G[H]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(H))}}(),B.was?A.win.Raphael=c:Raphael=c,c}); \ No newline at end of file
diff --git a/deps/wse/priv/raphael.html b/deps/wse/priv/raphael.html
new file mode 100644
index 0000000..ee18237
--- /dev/null
+++ b/deps/wse/priv/raphael.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+<title>Raphael demo page</title>
+<script src='ej.js'></script>
+<script src='wse.js'></script>
+<script>
+
+window.onload = function() {
+ if (Wse.open("ws://"+(location.hostname||"localhost")+":1234/websession")) {
+ Wse.start('wse_raphael_demo', 'run', ["i1"]);
+ }
+ else
+ alert("WebSockets not supported");
+};
+
+</script>
+</head>
+<body style="background: #333">
+<div id="i1" width=640 height=480></div>
+</body>
+</html>
diff --git a/deps/wse/priv/table_demo.html b/deps/wse/priv/table_demo.html
new file mode 100644
index 0000000..c0e93f6
--- /dev/null
+++ b/deps/wse/priv/table_demo.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+<title>Table demo page</title>
+<script src='ej.js'></script>
+<script src='wse.js'></script>
+<script>
+
+window.onload = function() {
+ if (Wse.open("ws://"+(location.hostname||"localhost")+":1234/websession")) {
+ Wse.start('wse_table_demo', 'run', ["t1"]);
+ }
+ else
+ alert("WebSockets not supported");
+};
+
+</script>
+</head>
+<body>
+<div id="t1" width=640 height=480></div>
+</body>
+</html>
diff --git a/deps/wse/priv/utf8.js b/deps/wse/priv/utf8.js
new file mode 100644
index 0000000..db7b033
--- /dev/null
+++ b/deps/wse/priv/utf8.js
@@ -0,0 +1,73 @@
+//---- BEGIN COPYRIGHT -------------------------------------------------------
+//
+// Copyright (C) 2007 - 2014, Rogvall Invest AB, <tony@rogvall.se>
+//
+// This software is licensed as described in the file COPYRIGHT, which
+// you should have received as part of this distribution. The terms
+// are also available at http://www.rogvall.se/docs/copyright.txt.
+//
+// You may opt to use, copy, modify, merge, publish, distribute and/or sell
+// copies of the Software, and permit persons to whom the Software is
+// furnished to do so, under the terms of the COPYRIGHT file.
+//
+// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+// KIND, either express or implied.
+//
+//---- END COPYRIGHT ---------------------------------------------------------
+//
+// UTF8 encoder/decoder
+//
+function UTF8Class() {
+};
+
+// method for UTF-8 encoding
+UTF8Class.prototype.encode = function (string) {
+ string = string.replace(/\r\n/g,"\n");
+ var utftext = "";
+
+ for (var n = 0; n < string.length; n++) {
+ var c = string.charCodeAt(n);
+ if (c < 128) {
+ utftext += String.fromCharCode(c);
+ }
+ else if((c > 127) && (c < 2048)) {
+ utftext += String.fromCharCode((c >> 6) | 192);
+ utftext += String.fromCharCode((c & 63) | 128);
+ }
+ else {
+ utftext += String.fromCharCode((c >> 12) | 224);
+ utftext += String.fromCharCode(((c >> 6) & 63) | 128);
+ utftext += String.fromCharCode((c & 63) | 128);
+ }
+ }
+ return utftext;
+};
+
+// method for UTF-8 decoding
+UTF8Class.prototype.decode = function(utftext) {
+ var string = "";
+ var i = 0;
+ var c = c1 = c2 = 0;
+
+ while (i < utftext.length) {
+ c = utftext.charCodeAt(i);
+ if (c < 128) {
+ string += String.fromCharCode(c);
+ i++;
+ }
+ else if((c > 191) && (c < 224)) {
+ c2 = utftext.charCodeAt(i+1);
+ string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
+ i += 2;
+ }
+ else {
+ c2 = utftext.charCodeAt(i+1);
+ c3 = utftext.charCodeAt(i+2);
+ string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
+ i += 3;
+ }
+ }
+ return string;
+};
+
+var UTF8 = new UTF8Class();
diff --git a/deps/wse/priv/wse.js b/deps/wse/priv/wse.js
new file mode 100644
index 0000000..602cde2
--- /dev/null
+++ b/deps/wse/priv/wse.js
@@ -0,0 +1,725 @@
+//---- BEGIN COPYRIGHT -------------------------------------------------------
+//
+// Copyright (C) 2007 - 2014, Rogvall Invest AB, <tony@rogvall.se>
+//
+// This software is licensed as described in the file COPYRIGHT, which
+// you should have received as part of this distribution. The terms
+// are also available at http://www.rogvall.se/docs/copyright.txt.
+//
+// You may opt to use, copy, modify, merge, publish, distribute and/or sell
+// copies of the Software, and permit persons to whom the Software is
+// furnished to do so, under the terms of the COPYRIGHT file.
+//
+// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+// KIND, either express or implied.
+//
+//---- END COPYRIGHT ---------------------------------------------------------
+//
+// WebSocket/Erlang interface
+//
+//
+
+// operations:
+// {rsync,IRef,Request} -> {reply,IRef,Reply}
+// {nsync,IRef,Request} -> {noreply,IRef}
+// {async,IRef,Request} -> void
+// {dsync,IRef,Request} -> void ... {reply,IRef,Reply}
+//
+// request:
+// {new, Class, Arguments}
+// -> {object,ID}
+//
+// {newf, ParamNames, Body}
+// -> {function,ID}
+//
+// {delete, ID}
+// -> ok | {error,Reason}
+//
+// {call, Object, method, This, Arguments}
+// -> {ok,Value} | {error,Reason}
+//
+// {call, Function, This, Arguments}
+// -> {ok,Value} | {error,Reason}
+//
+// {get, Object, AttrIndex}
+// -> {ok,Value} | {error,Reason}
+// {set, Object, AttrIndex, Value}
+// -> {ok,Value} | {error,Reason}
+//
+// Reverse operations:
+// {start, Mod, Fun, Args}
+// {call, Mod, Fun, Args} -> Value
+// {cast, Mod, Fun, Args}
+// {register, Name}
+// {unregister}
+//
+// {notify, ID, Data}
+// {info, String}
+//
+// Values:
+// number => integer|float
+// string => list of chars
+// atom => string
+// boolean => true|false
+// array => list of values
+// function => {function, ID}
+// object => {object,ID}
+//
+(function() {
+ id_counter = 1;
+ Object.defineProperty(Object.prototype, "__uniqueId", {
+ writable: true
+ });
+ Object.defineProperty(Object.prototype, "uniqueId", {
+ get: function() {
+ if (this.__uniqueId == undefined)
+ this.__uniqueId = id_counter++;
+ return this.__uniqueId;
+ }
+ });
+}());
+
+var wse_console_debug = true;
+//
+// window.onerror = function(message, url, line)
+// console.low("window error was invoked with message = " +
+// message + ", url = " + url + ", line = " + line);
+// }
+//
+
+function WseClass(enable_console) {
+ this.win = window;
+ this.ws = undefined;
+ this.state = "closed";
+
+ this.objects = {}; // object of objects
+
+ this.iref = 1;
+ this.requests = new Array();
+ this.reply_fun = {};
+ this.reply_obj = {};
+ this.reply_ref = {};
+
+ this.OkTag = Ei.atom("ok");
+ this.ErrorTag = Ei.atom("error");
+ this.ExceptionTag = Ei.atom("exception");
+ this.ObjectTag = Ei.atom("object");
+ this.FunctionTag = Ei.atom("function");
+ this.ReplyTag = Ei.atom("reply");
+ this.NoReplyTag = Ei.atom("noreply");
+
+ this.dummyConsole = {
+ assert : function(){},
+ log : function(){},
+ warn : function(){},
+ error : function(){},
+ debug : function(){},
+ dir : function(){},
+ info : function(){}
+ };
+ if (enable_console && (window.console !== undefined))
+ this.console = window.console;
+ else
+ this.console = this.dummyConsole;
+};
+
+WseClass.prototype.enable_console_output = function(enable) {
+ if (enable && (window.console !== undefined))
+ this.console = window.console;
+ else
+ this.console = this.dummyConsole;
+}
+
+WseClass.prototype.getWse = function(id) {
+ var i;
+ for (i = 0; i < this.win.frames.length; i++) {
+ if (('Wse' in this.win.frames[i]) &&
+ (this.win.frames[i].Wse.id == id))
+ return this.win.frames[i].Wse;
+ }
+ return null;
+}
+
+// Fixme: check binaryType for the wanted encoding ?!
+WseClass.prototype.encode = function(Obj) {
+// return Base64.encode(Ei.encode(Obj));
+ return Ei.encode(Obj);
+};
+
+// Fixme: handle base64 string & Blob and ArrayBuffer!
+WseClass.prototype.decode = function(Data) {
+// return Ei.decode(Base64.decode(Data));
+ return Ei.decode(Data, 0);
+};
+
+// Decide an Erlang term that represent a json object into a
+// native json object i.e {struct,[{a,1},{b,2}]} => { a:1, b:2 }
+WseClass.prototype.decode_js = function(Data) {
+ switch(typeof(Data)) {
+ case "string": return Data;
+ case "number": return Data;
+ case "object":
+ if (Ei.eqAtom(Data, "true")) return true;
+ else if (Ei.eqAtom(Data, "false")) return false;
+ else if (Ei.eqAtom(Data, "null")) return null;
+ else if (Ei.isTupleSize(Data,2)) {
+ var elem = Data.value;
+ if (Ei.eqAtom(elem[0], "array") && Ei.isArray(elem[1])) {
+ var iArr = elem[1];
+ var arr = new Array();
+ var len, i;
+ for (i = 0; i < iArr.length; i++)
+ arr[i] = this.decode_js(iArr[i]);
+ return arr;
+ }
+ else if (Ei.eqAtom(elem[0],"struct") && Ei.isArray(elem[1])) {
+ var iArr = elem[1];
+ var obj = new Object();
+ var len, i;
+ for (i = 0; i < iArr.length; i++) {
+ if (Ei.isTupleSize(iArr[i],2)) {
+ var pair = iArr[i].value;
+ if (typeof(pair[0]) == "string")
+ obj[pair[0]] = this.decode_js(pair[1]);
+ }
+ }
+ return obj;
+ }
+ else {
+ this.console.debug("unable to decode pair " + Data);
+ return null;
+ }
+ }
+ else if (Ei.isArray(Data)) {
+ var arr = new Array();
+ var len, i;
+ for (i = 0; i < Data.length; i++)
+ arr[i] = this.decode_js(Data[i]);
+ return arr
+ }
+ else {
+ this.console.debug("unable to decode object " + Data);
+ return null;
+ }
+ break;
+ default:
+ this.console.debug("unable to decode data " + Data);
+ return null;
+ }
+};
+
+WseClass.prototype.open = function(url) {
+ var wse = this; // save WebSocket closure
+
+ if ("WebSocket" in window) {
+ this.state = "connecting";
+ this.ws = new WebSocket(url);
+ this.ws.binaryType = "arraybuffer";
+
+
+ this.ws.onopen = function() {
+ var info = Ei.tuple(Ei.atom("info"),"connected");
+ wse.state = "open";
+ this.send(wse.encode(info));
+ for (ref in wse.requests) {
+ var cmd = wse.requests[ref];
+ if (cmd !== undefined)
+ this.send(wse.encode(cmd));
+ }
+ // overwrite the old request array with a new and let
+ // the old array be garbage collected. The new array is
+ // not used right now, so we could set it to null, but keep
+ // it for future ideas.
+ wse.requests = new Array();
+ };
+
+ this.ws.onmessage = function(evt) {
+ var Request = wse.decode(evt.data);
+ var val = wse.dispatch(Request);
+ if (val != undefined)
+ this.send(wse.encode(val));
+ };
+
+ this.ws.onclose = function() {
+ wse.state = "closed";
+ wse.ws = undefined;
+ };
+ return true;
+ }
+ return false;
+};
+
+//
+// Remove all children (DOM util)
+//
+WseClass.prototype.remove_children = function(Cell) {
+ if (Cell.hasChildNodes()) {
+ while(Cell.childNodes.length >= 1)
+ Cell.removeChild(Cell.firstChild);
+ }
+}
+
+WseClass.prototype.lookup_object = function(index) {
+ var obj = this.objects[index];
+ this.console.debug("lookup object "+index+" = "+obj);
+ return obj;
+}
+
+WseClass.prototype.insert_object = function(index,obj) {
+ this.console.debug("insert object "+index+" = "+obj);
+ this.objects[index] = obj;
+}
+
+WseClass.prototype.delete_object = function(index) {
+ this.console.debug("deleting object "+index);
+ delete this.objects[index];
+}
+
+//
+// Decode javascript object into BERT rpc values
+//
+WseClass.prototype.encode_value = function(Obj) {
+ switch(typeof(Obj)) {
+ case "number": return Obj;
+ case "string": return Obj;
+ case "boolean": return Obj ? Ei.atom("true") : Ei.atom("false");
+ case "object":
+ // {object, window} - the current window object
+ // {object, document} - the current document object
+ // {object, id} - DOM object with id field
+ // {object, num} - Stored in objects object!
+ if (Obj == window.self)
+ return Ei.tuple(this.ObjectTag,Ei.atom("window"));
+ else if (Obj == window.document)
+ return Ei.tuple(this.ObjectTag,Ei.atom("document"));
+ else if (Obj == screen)
+ return Ei.tuple(this.ObjectTag,Ei.atom("screen"));
+ else if (Obj == navigator)
+ return Ei.tuple(this.ObjectTag,Ei.atom("navigator"));
+ else if (('id' in Obj) && Obj.id) {
+ if (Obj == document.getElementById(Obj.id))
+ return Ei.tuple(this.ObjectTag,Obj.id);
+ }
+ this.insert_object(Obj.uniqueId, Obj);
+ return Ei.tuple(this.ObjectTag,Obj.uniqueId);
+ case "function":
+ this.insert_object(Obj.uniqueId, Obj);
+ return Ei.tuple(this.FunctionTag,Obj.uniqueId);
+ case "undefined":
+ return Ei.atom("undefined");
+ }
+};
+
+//
+// Decode BERT rpc values into javascript objects
+// {object, window} => window.self
+// {object, document} => window.document
+// {object, screen} => screen
+// {object, navigator} => navigator
+// {object, id} => window.document.getElelementById(id)
+// {object, num} => objects[num]
+// {function,num} => objects[num]
+// [H1,H2...Hn] => Array
+//
+
+WseClass.prototype.decode_value = function(Obj) {
+ switch(typeof(Obj)) {
+ case "number": return Obj;
+ case "string": return Obj;
+ case "boolean": return Obj;
+ case "object":
+ if (Ei.isAtom(Obj))
+ return Obj.value;
+ else if (Ei.isTuple(Obj)) {
+ var elem = Obj.value;
+ if ((elem.length==2) && Ei.eqAtom(elem[0],"object")) {
+ if (Ei.eqAtom(elem[1], "window"))
+ return window.self;
+ else if (Ei.eqAtom(elem[1],"document"))
+ return window.document;
+ else if (Ei.eqAtom(elem[1], "screen"))
+ return screen;
+ else if (Ei.eqAtom(elem[1], "navigator"))
+ return navigator;
+ else if (typeof(elem[1]) == "number")
+ return this.lookup_object(elem[1]);
+ else if (typeof(elem[1]) == "string")
+ return window.document.getElementById(elem[1]);
+ }
+ // this is a garbage collected version {object,num,res-bin}
+ if ((elem.length==3) && Ei.eqAtom(elem[0],"object")) {
+ if ((typeof(elem[1]) == "number") && Ei.isBinary(elem[2])) {
+ return this.lookup_object(elem[1]);
+ }
+ return undefined;
+ }
+ if ((elem.length==2) && Ei.eqAtom(elem[0],"function")) {
+ if (typeof(elem[1]) == "number")
+ return this.lookup_object(elem[1]);
+ return undefined;
+ }
+ // this is a garbage collected version {function,num,res-bin}
+ if ((elem.length==3) && Ei.eqAtom(elem[0],"function")) {
+ if ((typeof(elem[1]) == "number") && Ei.isBinary(elem[2])) {
+ return this.lookup_object(elem[1]);
+ }
+ return undefined;
+ }
+ }
+ else if (Ei.isArray(Obj)) {
+ var i;
+ var arr = new Array();
+ for (i = 0; i < Obj.length; i++)
+ arr[i] = this.decode_value(Obj[i]);
+ return arr;
+ }
+ return undefined;
+ default:
+ this.console.debug("unhandled object "+ Obj);
+ return Obj;
+ }
+};
+//
+// Decode ehtml to DOM
+// Elem = {Tag,Attributes,Children}
+// | {Tag,Attributes}
+// Tag is EiAtom
+// Attributes is Array of Tuple(Atom,Value) or Atom
+// optional Children is Array of Elem
+// Return top level DOM element
+//
+WseClass.prototype.decode_ehtml = function (Obj) {
+ var element = undefined;
+
+ if (typeof(Obj) == "string") {
+ element = document.createTextNode(Obj);
+ }
+ else if (typeof(Obj) == "number") {
+ element = document.createTextNode(Obj.toString());
+ }
+ else if (Ei.isArray(Obj)) {
+ element = document.createDocumentFragment();
+ for (i = 0; i < Obj.length; i++) {
+ var child = this.decode_ehtml(Obj[i]);
+ if (child != undefined)
+ element.appendChild(child);
+ }
+ }
+ else if (Ei.isTuple(Obj)) {
+ var argv = Obj.value;
+
+ if ((argv.length == 0) || !Ei.isAtom(argv[0]))
+ return undefined;
+
+ element = document.createElement(argv[0].value);
+
+ if ((argv.length > 1) && Ei.isArray(argv[1])) {
+ var attrs = argv[1];
+ var i;
+ for (i = 0; i < attrs.length; i++) {
+ if (Ei.isTuple(attrs[i])) {
+ var key = this.decode_value(attrs[i].value[0]);
+ var value;
+ if (attrs[0].length > 1)
+ value = this.decode_value(attrs[i].value[1]);
+ else
+ value = true;
+ element.setAttribute(key, value);
+ }
+ }
+ if ((argv.length > 2) && Ei.isArray(argv[2])) {
+ var children = argv[2];
+ for (i = 0; i < children.length; i++) {
+ var child = this.decode_ehtml(children[i]);
+ if (child != undefined)
+ element.appendChild(child);
+ }
+ }
+ }
+ }
+ return element;
+};
+
+//
+// Dispatch remote operations
+//
+WseClass.prototype.dispatch = function (Request) {
+ var iref, aref;
+ var value, rvalue;
+ var r, t;
+ var is_dsync = false;
+
+ if (Ei.isTupleSize(Request, 3)) {
+ var argv = Request.value;
+ aref = argv[1];
+ if (Ei.eqAtom(argv[0], "rsync"))
+ iref = aref;
+ else if (Ei.eqAtom(argv[0], "nsync"))
+ iref = -aref;
+ else if (Ei.eqAtom(argv[0], "async"))
+ iref = 0;
+ else if (Ei.eqAtom(argv[0], "dsync")) {
+ is_dsync = true;
+ iref = 0;
+ }
+ else if (Ei.eqAtom(argv[0], "reply")) {
+ var fn,obj,ref;
+ iref = aref;
+ value = argv[2];
+ fn = this.reply_fun[iref];
+ obj = this.reply_obj[iref];
+ ref = this.reply_ref[iref];
+ this.console.debug("got reply "+iref+","+value+" fn="+fn+" obj="+obj+" ref="+ref);
+ if (fn != undefined) {
+ delete this.reply_fun[iref];
+ delete this.reply_obj[iref];
+ delete this.reply_ref[iref];
+ try {
+ fn(obj,ref,value);
+ }
+ catch (err) {
+ this.console.info("reply function crashed "+err.message);
+ }
+ }
+ return undefined;
+ }
+ else
+ return undefined; // signal protocol error
+ r = argv[2];
+ }
+ else
+ return undefined;
+
+ if (Ei.isTuple(r)) {
+ var argv = r.value;
+ if ((argv.length == 3) && Ei.eqAtom(argv[0],"send")) {
+ var Cell = document.getElementById(argv[1]);
+
+ try {
+ if (typeof(argv[2]) == "string") {
+ Cell.innerHTML = Ei.pp(argv[2]);
+ }
+ else if (Ei.isTuple(argv[2]) || Ei.isArray(argv[2])) {
+ var elem = this.decode_ehtml(argv[2]);
+ this.remove_children(Cell);
+ Cell.appendChild(elem);
+ }
+ value = this.OkTag;
+ }
+ catch(err) {
+ value = Ei.tuple(this.ExceptionTag, err.message);
+ }
+ }
+ else if ((argv.length == 3) && Ei.eqAtom(argv[0],"new")) {
+ var obj = new Object();
+ var fn = window[this.decode_value(argv[1])];
+
+ try {
+ fn.apply(obj, this.decode_value(argv[2]));
+ obj.__proto__ = fn.prototype;
+ rvalue = this.encode_value(obj);
+ value = rvalue;
+ }
+ catch(err) {
+ value = Ei.tuple(this.ExceptionTag, err.message);
+ }
+ }
+ else if ((argv.length == 3) && Ei.eqAtom(argv[0],"newf")) {
+ this.console.debug("new Function("+argv[1]+","+argv[2]+")");
+ try {
+ var fn = new Function(argv[1],argv[2]);
+ this.console.debug("function = "+fn);
+ rvalue = this.encode_value(fn);
+ value = rvalue;
+ }
+ catch(err) {
+ value = Ei.tuple(this.ExceptionTag, err.message);
+ }
+ }
+ else if ((argv.length == 4) && Ei.eqAtom(argv[0],"call")) {
+ var fn = this.decode_value(argv[1]);
+ var objb = this.decode_value(argv[2]);
+ var args = this.decode_value(argv[3]);
+ var val;
+
+ try {
+ val = window[fn].apply(objb, args);
+ rvalue = this.encode_value(val);
+ this.console.debug("call/3=" + Ei.pp(argv[1]) + "," + Ei.pp(argv[2]) + "," + Ei.pp(argv[3]));
+ if (is_dsync && (fn === "call") && (val % 1 === 0)) {
+ // val is a reference in this case
+ this.console.debug("set reply_obj["+val+"] = "+objb);
+ objb.reply_obj[val] = this; // patch object
+ objb.reply_ref[val] = aref; // original ref
+ }
+ value = Ei.tuple(this.OkTag, rvalue);
+ }
+ catch(err) {
+ value = Ei.tuple(this.ErrorTag, err.message);
+ }
+ }
+ else if ((argv.length == 5) && Ei.eqAtom(argv[0],"call")) {
+ var obja = this.decode_value(argv[1]);
+ var meth = this.decode_value(argv[2]);
+ var objb = this.decode_value(argv[3]);
+ var args = this.decode_value(argv[4]);
+ var val;
+
+ try {
+ val = (obja[meth]).apply(objb, args);
+ rvalue = this.encode_value(val);
+ this.console.debug("call/4=" + Ei.pp(argv[1]) + "," + Ei.pp(argv[2]) + "," + Ei.pp(argv[3]) + "," + Ei.pp(argv[4]));
+ if (is_dsync && (meth === "call") && (val % 1 === 0)) {
+ // val is a reference in this case
+ this.console.debug("set obja.reply_obj["+val+"] = "+this);
+ obja.reply_obj[val] = this; // patch object
+ obja.reply_ref[val] = aref; // original ref
+ }
+ value = Ei.tuple(this.OkTag, rvalue);
+ }
+ catch(err) {
+ value = Ei.tuple(this.ErrorTag, err.message);
+ }
+ }
+ else if ((argv.length == 3) && Ei.eqAtom(argv[0],"get")) {
+ var obj = this.decode_value(argv[1]);
+ var attr = this.decode_value(argv[2]);
+ try {
+ rvalue = obj[attr]; // both array and object attribute!
+ this.console.debug(argv[1]+".get: "+attr+"="+rvalue);
+ value = Ei.tuple(this.OkTag, this.encode_value(rvalue));
+ }
+ catch (err) {
+ value = Ei.tuple(this.ErrorTag, err.message);
+ }
+ }
+ else if ((argv.length == 4) && Ei.eqAtom(argv[0],"set")) {
+ var obj = this.decode_value(argv[1]);
+ var attr = this.decode_value(argv[2]);
+ rvalue = this.decode_value(argv[3]);
+ this.console.debug(argv[1]+".set: "+attr+"="+argv[3]+"("+rvalue+")");
+ try {
+ obj[attr] = rvalue; // both array and object attribute!
+ value = this.OkTag;
+ }
+ catch (err) {
+ value = Ei.tuple(this.ErrorTag, err.message);
+ }
+ }
+ else if ((argv.length === 2) && Ei.eqAtom(argv[0],"delete")) {
+ // argv[1] must be the uniqID integer
+ this.delete_object(argv[1]);
+ rvalue = null;
+ value = this.OkTag;
+ }
+ }
+ if (iref == 0) {
+ // this.console.debug("ival=0");
+ return undefined;
+ }
+ else if (iref > 0) {
+ if (value == undefined) {
+ value = Ei.tuple(this.ErrorTag, Ei.atom("badarg"));
+ }
+ t = Ei.tuple(this.ReplyTag,iref,value);
+ }
+ else {
+ t = Ei.tuple(this.NoReplyTag,-iref);
+ }
+ // this.console.debug("t = " + Ei.pp(t));
+ return t;
+};
+
+WseClass.prototype.send_request = function (ref, request)
+{
+ if (this.state === "open") {
+ this.ws.send(this.encode(request));
+ }
+ else {
+ // safe until channel is open
+ this.requests[ref] = request;
+ }
+}
+
+//
+// Start remote controller "program"
+//
+WseClass.prototype.start = function (mod,fun,args) {
+ var ref = this.iref++;
+ var cmd = Ei.tuple(Ei.atom("start"),Ei.atom(mod),Ei.atom(fun),args);
+
+ this.send_request(ref, cmd);
+ return ref;
+};
+
+//
+// Call remote function mod:fun(Args)
+// execute onreply when reply is returned
+//
+WseClass.prototype.call = function (mod,fun,args,onreply) {
+ var ref = this.iref++;
+ var cmd = Ei.tuple(Ei.atom("call"),ref,Ei.atom(mod),Ei.atom(fun),args);
+
+ this.console.debug("call mod="+mod+", fun="+fun+", args="+args);
+ this.reply_fun[ref] = onreply;
+ this.reply_obj[ref] = this;
+ this.reply_ref[ref] = ref;
+ this.console.debug("set reply_fun["+ref+"] = "+onreply);
+ this.send_request(ref, cmd);
+ return ref;
+};
+
+// Used for handle return relay
+WseClass.prototype.reply = function (iref,value) {
+ var reply = Ei.tuple(this.ReplyTag,iref,value);
+ this.console.debug("sending reply "+reply+"id="+this.id);
+ if (this.state == "open") {
+ this.ws.send(this.encode(reply));
+ return true;
+ }
+ return false;
+};
+//
+// Execute remote function mod:fun(Args)
+//
+WseClass.prototype.cast = function (mod,fun,args) {
+ var ref = this.iref++;
+ var cmd = Ei.tuple(Ei.atom("cast"),ref,Ei.atom(mod),Ei.atom(fun),args);
+ this.console.debug("cast mod="+mod+", fun="+fun+", args="+args);
+ this.send_request(ref, cmd);
+ return true;
+};
+
+//
+// Send notification
+//
+WseClass.prototype.notify = function (ref,data) {
+ var cmd = Ei.tuple(Ei.atom("notify"),ref,data);
+ this.console.debug("notify "+ ref + ", data="+data);
+ this.send_request(ref, cmd);
+ return true;
+};
+
+//
+// Register a websocket (on the erlang node side)
+// could nearly call erlang:register through the call
+// but only nearly
+//
+WseClass.prototype.register = function (name) {
+ var ref = this.iref++;
+ var cmd = Ei.tuple(Ei.atom("register"),Ei.atom(name));
+ this.console.debug("register "+ name);
+ this.send_request(ref, cmd);
+ return true;
+}
+
+// Unregister a websocket (on the erlang node side)
+WseClass.prototype.unregister = function () {
+ var ref = this.iref++;
+ var cmd = Ei.tuple(Ei.atom("unregister"));
+ this.console.debug("unregister");
+ this.send_request(ref, cmd);
+ return true;
+}
+
+var Wse = new WseClass(wse_console_debug);
diff --git a/deps/wse/priv/wse_demo.html b/deps/wse/priv/wse_demo.html
new file mode 100644
index 0000000..610d03f
--- /dev/null
+++ b/deps/wse/priv/wse_demo.html
@@ -0,0 +1,13 @@
+<html><head>
+<title>wse demo page</title>
+<script src='ej.js'></script>
+<script src='wse.js'></script>
+<script>
+ window.onload = function() {
+ if (Wse.open("ws://"+(location.hostname||"localhost")+":1234/websession"))
+ Wse.start('wse_demo', 'run', ["myid"]);
+ };
+</script></head>
+<body>
+ <div id="myid"></div>
+</body></html>
diff --git a/deps/wse/rebar.config b/deps/wse/rebar.config
new file mode 100644
index 0000000..2df9f72
--- /dev/null
+++ b/deps/wse/rebar.config
@@ -0,0 +1,2 @@
+{deps, [ {resource, ".*", {git, "git@github.com:tonyrog/resource.git"}}]}.
+{edoc_opts, [{doclet, edown_doclet}]}.
diff --git a/deps/wse/src/raphael.erl b/deps/wse/src/raphael.erl
new file mode 100644
index 0000000..fb7a1fb
--- /dev/null
+++ b/deps/wse/src/raphael.erl
@@ -0,0 +1,106 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2014, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% File : raphael.erl
+%%% Author : Tony Rogvall <tony@rogvall.se>
+%%% Description : Raphael interface towards browser
+%%% Created : 21 Dec 2009 by Tony Rogvall <tony@rogvall.se>
+
+-module(raphael).
+
+-compile(export_all).
+
+%% New returns a reference to the canvase object
+new(Ws, Array) ->
+ wse:call(Ws, wse:window(), "Raphael", [Array]).
+
+new(Ws, Object, W, H) ->
+ wse:call(Ws, wse:window(), "Raphael", [Object,W,H]).
+
+new(Ws, X, Y, W, H) ->
+ wse:call(Ws, wse:window(), "Raphael", [X,Y,W,H]).
+
+setSize(Ws,Paper,W,H) ->
+ wse:call(Ws, Paper, setSize, [W, H]).
+
+close(Ws,Paper) ->
+ wse:call(Ws, Paper, clone, []).
+
+%% get attribute value
+attr(Ws,Object,Name) ->
+ wse:call(Ws,Object,attr,[Name]).
+
+%% set attribute value
+attr(Ws,Object,Name,Value) ->
+ wse:call(Ws,Object,attr,[Name,Value]).
+
+
+circle(Ws,Paper, X, Y, R) ->
+ wse:call(Ws, Paper, circle, [X, Y, R]).
+
+rect(Ws,Paper, X, Y, W, H) ->
+ wse:call(Ws, Paper, rect, [X, Y, W, H]).
+
+rect(Ws,Paper, X, Y, W, H, R) ->
+ wse:call(Ws, Paper, rect, [X, Y, W, H, R]).
+
+ellipse(Ws,Paper,X,Y,Rx,Ry) ->
+ wse:call(Ws,Paper,ellipse,[X,Y,Rx,Ry]).
+
+image(Ws,Paper,Src,X,Y,Width,Height) ->
+ wse:call(Ws,Paper,image,[Src,X,Y,Width,Height]).
+
+set(Ws,Paper) ->
+ wse:call(Ws,Paper,set,[]).
+
+push(Ws,Set,Objects) when is_list(Objects) ->
+ wse:call(Ws,Set,push,Objects);
+push(Ws, Set, Object) ->
+ wse:call(Ws,Set,push,[Object]).
+
+%% FIXME: utf8!
+text(Ws,Paper,X,Y,Text) ->
+ wse:call(Ws,Paper,text,[X,Y,Text]).
+
+%% return a font object
+getFont(Ws,Paper,Family) ->
+ wse:call(Ws,Paper,getFont,[Family]).
+
+print(Ws,Paper,X,Y,Text,Font,FontSize) ->
+ wse:call(Ws,Paper,print,[X,Y,Text,Font,FontSize]).
+
+path(Ws,Paper,SVG) ->
+ wse:call(Ws,Paper,path,[SVG]).
+
+path(Ws,Paper) ->
+ wse:call(Ws,Paper,path,[]).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/deps/wse/src/wse.app.src b/deps/wse/src/wse.app.src
new file mode 100644
index 0000000..cba9091
--- /dev/null
+++ b/deps/wse/src/wse.app.src
@@ -0,0 +1,7 @@
+{application, wse,
+ [{description, "Erlang web socket server"},
+ {vsn, git},
+ {registered, []},
+ {env, []},
+ {applications,[kernel,stdlib]}
+ ]}.
diff --git a/deps/wse/src/wse.erl b/deps/wse/src/wse.erl
new file mode 100644
index 0000000..21e2ef0
--- /dev/null
+++ b/deps/wse/src/wse.erl
@@ -0,0 +1,453 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2014, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% File : wse.erl
+%%% Author : Tony Rogvall <tony@rogvall.se>
+%%% Description : WebSocket Erlang interface
+%%% Created : 21 Dec 2009 by Tony Rogvall <tony@rogvall.se>
+
+-module(wse).
+
+-export([call/4, call/5]).
+-export([rcall/4, rcall/5]).
+-export([get/3, set/4]).
+-export([cast/4, cast/5]).
+-export([send/3]).
+-export([new/3]).
+-export([delete/2]).
+-export([close/1, close/2]).
+%% standard objects
+-export([document/0, window/0, screen/0, navigator/0]).
+%% object wrapper when passing string based id
+-export([id/1]).
+-export([array/1]).
+-export([create_event/1, create_event/3]).
+-export([wait_event/2]).
+-export([createElement/2]).
+-export([createTextNode/2]).
+-export([getElementsByTagName/2]).
+-export([getElementById/2]).
+-export([appendChild/3]).
+-export([load_image/2, load/2]).
+%% header items
+-export([header/1]).
+-export([header/2]).
+-export([session_header/0]).
+-export([session_header/1]).
+
+-compile(export_all).
+
+-type void() :: ok.
+-type wse_object() :: {object, atom()|string()|integer()}.
+-type wse_event() :: integer().
+-type wse() :: pid().
+-type url() :: string().
+
+-type dom_id() :: atom()|string().
+-type html_tag() :: atom().
+-type attr_name() :: atom().
+-type attr_value() :: atom()|string()|integer().
+-type html_attr() :: {attr_name(),attr_value()}.
+-type ehtml() :: atom() | string() | integer() |
+ {Tag::html_tag(),[html_attr()]} |
+ {Tag::html_tag(),[html_attr()],[ehtml()]}.
+
+id(ID) when is_atom(ID) ->
+ {object, ID};
+id(ID) when is_list(ID) ->
+ {object, ID}.
+
+-spec document() -> wse_object().
+document() -> id(document).
+
+-spec window() -> wse_object().
+window() -> id(window).
+
+-spec screen() -> wse_object().
+screen() -> id(screen).
+
+-spec navigator() -> wse_object().
+navigator() -> id(navigator).
+
+%% @doc
+%% Send (e)HTML to a DOM element sowmehere in the document.
+%% @end
+-spec send(Ws::wse(), Tag::dom_id(), Data::ehtml()) -> void().
+
+send(Ws, Tag, Data) ->
+ nsync(Ws, {send,Tag,Data}).
+
+%% @doc
+%% Call a Javascript method
+%% @end
+-spec call(Ws::wse(), Object::wse_object(), Method::atom(), Args::[term()]) ->
+ {ok, Value::term()}.
+call(Ws, Object, Method, Args) ->
+ call(Ws, Object, Method, Object, Args).
+
+%% @doc
+%% Call a Javascript method setting this
+%% @end
+-spec call(Ws::wse(), Object::wse_object(),
+ This::wse_object(), Method::atom(), Args::[term()]) ->
+ {ok, Value::term()}.
+call(Ws, Object, Method, This, Args) when is_list(Args) ->
+ rsync(Ws, {call, Object, Method, This, array(Args)}).
+
+%% @doc
+%% Remote call
+%% @end
+
+-spec rcall(Ws::wse(), Object::wse_object(), Method::atom(), Args::[term()]) ->
+ {ok, Value::term()}.
+rcall(Ws, Object, Method, Args) ->
+ rcall(Ws, Object, Method, Object, Args).
+
+-spec rcall(Ws::wse(), Object::wse_object(),
+ This::wse_object(), Method::atom(), Args::[term()]) ->
+ {ok, Value::term()}.
+rcall(Ws, Object, Method, This, Args) when is_list(Args) ->
+ dsync(Ws, {call, Object, Method, This, array(Args)}).
+
+%% @doc
+%% Get attribute or value at index
+%% @end
+
+-spec get(Ws::wse(), Object::wse_object(),
+ Attribute::integer()|atom()|string()|list())
+ -> {ok,Value::term()} | {error,Reason::term()}.
+
+get(Ws, Object, [LeafAttribute]) when is_list(LeafAttribute) ->
+ get(Ws, Object, LeafAttribute);
+get(Ws, Object, [Attribute | SubAttributes]) when is_list(Attribute) ->
+ {ok, AttributeObject} = get(Ws, Object, Attribute),
+ get(Ws, AttributeObject, SubAttributes);
+get(Ws, Object, Attribute) ->
+ rsync(Ws, {get, Object, Attribute}).
+
+%% @doc
+%% Set attibute or array at index
+%% @end
+-spec set(Ws::wse(), Object::wse_object(),
+ Attribute::integer()|atom()|string()|list(),
+ Value::term()) -> ok | {error,Reason::term()}.
+
+set(Ws, Object, [LeafAttribute], Value) when is_list(LeafAttribute) ->
+ rsync(Ws, {set, Object, LeafAttribute, Value});
+set(Ws, Object, [Attribute | SubAttributes], Value) when is_list(Attribute) ->
+ {ok, AttributeObject} = get(Ws, Object, Attribute),
+ set(Ws, AttributeObject, SubAttributes, Value);
+set(Ws, Object, Attribute, Value) ->
+ rsync(Ws, {set, Object, Attribute, Value}).
+
+%% @doc
+%% Cast like call, but with no return value
+%% @end
+-spec cast(Ws::wse(), Object::wse_object(), Method::atom(), Args::[term()]) ->
+ ok.
+cast(Ws, Object, Method, Args) ->
+ cast(Ws, Object, Method, Object, Args).
+
+-spec cast(Ws::wse(), Object::wse_object(), Method::atom(),
+ Thus::wse_object(), Args::[term()]) ->
+ ok.
+cast(Ws, Object, Method, This, Args) when is_list(Args) ->
+ async(Ws, {call, Object, Method, This, array(Args)}).
+
+%% @doc
+%% Create a new Java script object
+%% @end
+new(Ws, Class, Args) when is_list(Args) ->
+ rsync(Ws, {new,Class,array(Args)}).
+
+%% @doc
+%% Create a new function.
+%% <pre>
+%% newf(Ws, "e,f", "'e.pageX'").
+%% </pre>
+%% @end
+-spec newf(Wse::wse(), Agrs::string(), Body::string()) ->
+ wse_object().
+
+newf(Ws, Args, Body) when is_list(Args), is_list(Body) ->
+ rsync(Ws, {newf,Args,Body}).
+
+%% @doc
+%% Release an object from the encoding object array.
+%% use with care
+%% @end
+
+-spec delete(Ws::wse(), Object::wse_object()) ->
+ void().
+
+delete(Ws, {object,ID,_Ref}) ->
+ rsync(Ws, {delete,ID});
+delete(_Ws, _) ->
+ ok.
+
+close(Ws) ->
+ close(Ws, normal).
+
+close(Ws,Reason) ->
+ Ref = make_ref(),
+ Ws ! {close,[Ref|self()],Reason},
+ receive
+ {reply, Ref, Reply} ->
+ Reply
+ end.
+
+%% @doc
+%% Force list to be encoded as a list (instead of a string)
+%% when using term_to_binary, this is handled in the wse decoder
+%% @end
+
+-spec array([term()]) -> list().
+
+array(Elements) when is_list(Elements) ->
+ ['array'|Elements].
+
+%% @doc
+%% Create event listener that can be used to wait
+%% for events from java script.
+%% Same as create_event(Ws, all, []).
+%% @end
+
+-spec create_event(Ws::wse()) ->
+ {ok, wse_event()}.
+create_event(Ws) ->
+ create_event(Ws,all,[]).
+
+%% @doc
+%% Create event listener with two flavours
+%% @end
+-spec create_event(Ws::wse(), How::once|all, Data::term()) ->
+ {ok, wse_event()}.
+
+create_event(Ws,How,Data) ->
+ Ref = make_ref(),
+ Ws ! {create_event,[Ref|self()],How,Data},
+ receive
+ {reply, Ref, Reply} ->
+ Reply
+ end.
+
+%% @doc
+%% Wait for an event sent from java script.
+%% Reply will contain both the local data installed with the
+%% event it self, and the data sent by java script, as a pair.
+%% @end
+-spec wait_event(ID::wse_event(), Timeout::timeout()) ->
+ {ok,{Local::term(), Remote::term()}}.
+
+wait_event(ID,Timeout) ->
+ receive
+ {notify,ID,Local,Remote} ->
+ {ok,{Local,Remote}}
+ after Timeout ->
+ {error,timeout}
+ end.
+
+%% Short cuts to DOM access
+
+%% @doc
+%% Short cut for
+%% <pre>
+%% {ok,E} = call(Ws, document(), createElement, [Name]), E
+%% </pre>
+%% @end
+-spec createElement(Ws::wse(), Name::string()) -> wse_object().
+createElement(Ws, Name) ->
+ %% io:format("createElement: ~p\n", [Name]),
+ {ok,E} = call(Ws, document(), createElement, [Name]),
+ E.
+
+%% @doc
+%% Short cut for
+%% <pre>
+%% {ok,E} = call(Ws, document(), createTextNode, [Text]), E
+%% </pre>
+%% @end
+-spec createTextNode(Ws::wse(), Text::string()) -> wse_object().
+createTextNode(Ws, Text) ->
+ {ok,E} = call(Ws, document(), createTextNode, [Text]),
+ E.
+
+%% @doc
+%% Short cut for
+%% <pre>
+%% {ok,_} = call(Ws, Element, appendChild, [Child]), ok
+%% </pre>
+%% @end
+-spec appendChild(Ws::wse(), Element::wse_object(), Child::wse_object()) ->
+ ok.
+appendChild(Ws, Element, Child) ->
+ {ok,_} = call(Ws, Element, appendChild, [Child]),
+ ok.
+
+%% @doc
+%% Retrive an array of DOM objects by tag name
+%% Short cut for
+%% <ptr>
+%% call(Ws, document(), getElementsByTagName, [Name])
+%% </ptr>
+%% @end
+%%
+-spec getElementsByTagName(Ws::wse(), Name::string()) ->
+ {ok,Array::wse_object()} |
+ {error,Reason::string()}.
+getElementsByTagName(Ws, Name) ->
+ call(Ws, document(), getElementsByTagName, [Name]).
+
+
+%% @doc
+%% Retrive a DOM object by its id
+%% Short cut for
+%% <ptr>
+%% call(Ws, document(), getElementById, [ID]).
+%% </ptr>
+%% @end
+%%
+-spec getElementById(Ws::wse(), ID::string()) ->
+ {ok,Elem::wse_object()} |
+ {error,Reason::string()}.
+getElementById(Ws, ID) ->
+ call(Ws, document(), getElementById, [ID]).
+
+
+%% @doc
+%% Get first child
+%% @end
+firstChild(Ws, Object) ->
+ get(Ws, Object, firstChild).
+
+%% @doc
+%% Get last child
+%% @end
+lastChild(Ws, Object) ->
+ get(Ws, Object, lastChild).
+
+%% @doc
+%% Get next sibling
+%% @end
+nextSibling(Ws, Object) ->
+ get(Ws, Object, nextSibling).
+
+%% @doc
+%% Load an image into the document and return
+%% image the object.
+%% @end
+-spec load_image(Ws::wse(), Src::url()) ->
+ {ok,Image::wse_object()}.
+
+load_image(Ws, Src) ->
+ Image = createElement(Ws, "img"),
+ set(Ws, Image, "src", Src),
+ {ok,Style} = wse:get(Ws, Image, "style"),
+ set(Ws, Style, "display", "none"),
+ %% set(Ws, Image, "type", "image/jpeg");
+ {ok,Array} = getElementsByTagName(Ws, "head"),
+ {ok,Elem} = get(Ws, Array, 0),
+ appendChild(Ws, Elem, Image),
+ %% wait for image to load?
+ {ok,Image}.
+
+%% @doc
+%% Load a java script library, and wait for it to load.
+%% @end
+-spec load(Ws::wse(), Library::url()) -> ok.
+
+%% Short cut to dynamically load java script library
+load(Ws, Library) ->
+ Script1 = createElement(Ws, "script"),
+ set(Ws, Script1, "type", "text/javascript"),
+ set(Ws, Script1, "src", Library),
+
+ {ok,ID} = create_event(Ws),
+ Script2 = createElement(Ws, "script"),
+ Text=createTextNode(Ws, "Wse.notify("++integer_to_list(ID)++",'loaded');"),
+ appendChild(Ws, Script2, Text),
+
+ %% Append script's in head element
+ {ok,Head} = call(Ws, document(), getElementsByTagName, ["head"]),
+ {ok,Elem} = get(Ws, Head, 0),
+ appendChild(Ws, Elem, Script1),
+ appendChild(Ws, Elem, Script2),
+ _Result = wait_event(ID, 5000),
+ %% io:format("wait event = ~p\n", [_Result]),
+ ok.
+
+header(Ws) when is_pid(Ws) ->
+ Ref = make_ref(),
+ Ws ! {header,[Ref|self()]},
+ receive
+ {reply, Ref, Reply} ->
+ Reply
+ after 5000 ->
+ {error,timeout}
+ end.
+
+header(Ws, ItemName) when is_pid(Ws) ->
+ Ref = make_ref(),
+ Ws ! {header,ItemName, [Ref|self()]},
+ receive
+ {reply, Ref, Reply} ->
+ Reply
+ after 5000 ->
+ {error,timeout}
+ end.
+
+session_header() ->
+ %% Header stored in process dictionary
+ get(header).
+
+session_header(ItemName) ->
+ case get(header) of
+ undefined ->
+ %% or crash??
+ {error, no_header};
+ Header ->
+ case lists:keyfind(ItemName, 1, Header) of
+ {ItemName, ItemValue} ->
+ {ok, ItemValue};
+ false ->
+ {error, unknown_header_item}
+ end
+ end.
+
+%% Sync and Async primitives
+rsync(Ws, Command) ->
+ Ref = make_ref(),
+ Ws ! {rsync,[Ref|self()],Command},
+ receive
+ {reply, Ref, Reply} ->
+ Reply
+ end.
+
+dsync(Ws, Command) ->
+ Ref = make_ref(),
+ Ws ! {dsync,[Ref|self()],Command},
+ receive
+ {reply, Ref, Reply} ->
+ Reply
+ end.
+
+nsync(Ws, Command) ->
+ Ref = make_ref(),
+ Ws ! {nsync,[Ref|self()],Command}.
+
+async(Ws, Command) ->
+ Ref = make_ref(),
+ Ws ! {async,[Ref|self()],Command}.
diff --git a/deps/wse/src/wse_button_demo.erl b/deps/wse/src/wse_button_demo.erl
new file mode 100644
index 0000000..bfcde03
--- /dev/null
+++ b/deps/wse/src/wse_button_demo.erl
@@ -0,0 +1,50 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2014, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2014, Tony Rogvall
+%%% @doc
+%%% Demo a button with notification callback
+%%% @end
+%%% Created : 16 Jun 2014 by Tony Rogvall <tony@rogvall.se>
+
+-module(wse_button_demo).
+
+-export([run/2]).
+
+%% Button demo
+run(Ws, Where) ->
+ io:format("button_demo: called\n"),
+ Button = wse:createElement(Ws, "button"),
+ io:format("button=~w\n", [Button]),
+ Text = wse:createTextNode(Ws, "Press me"),
+ io:format("text=~w\n", [Text]),
+ wse:appendChild(Ws, Button, Text),
+ wse:appendChild(Ws, wse:id(Where), Button),
+ {ok,ID} = wse:create_event(Ws),
+ io:format("ID=~w\n", [ID]),
+ Func = wse:newf(Ws, "",
+ "{ Wse.notify("++integer_to_list(ID)++",'click'); }"),
+ wse:set(Ws,Button,"onclick",Func),
+ run_loop(Ws, Text, ID).
+
+run_loop(Ws, Text, ID) ->
+ receive
+ {notify,ID,_Local,Data} ->
+ io:format("~p\n", [Data]),
+ wse:set(Ws, Text, "nodeValue", "Again"),
+ run_loop(Ws, Text, ID)
+ end.
diff --git a/deps/wse/src/wse_demo.erl b/deps/wse/src/wse_demo.erl
new file mode 100644
index 0000000..3f0c40b
--- /dev/null
+++ b/deps/wse/src/wse_demo.erl
@@ -0,0 +1,24 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2014, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+-module(wse_demo).
+-export([run/2]).
+
+run(Ws, Where) ->
+ ElemNode = wse:createElement(Ws, "p"),
+ TextNode = wse:createTextNode(Ws, "Hello world"),
+ wse:appendChild(Ws, ElemNode, TextNode),
+ wse:appendChild(Ws, wse:id(Where), ElemNode).
diff --git a/deps/wse/src/wse_document_demo.erl b/deps/wse/src/wse_document_demo.erl
new file mode 100644
index 0000000..8b6aae6
--- /dev/null
+++ b/deps/wse/src/wse_document_demo.erl
@@ -0,0 +1,71 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2014, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2014, Tony Rogvall
+%%% @doc
+%%% This demo is called directly from web page
+%%% @end
+%%% Created : 24 Apr 2014 by Tony Rogvall <tony@rogvall.se>
+
+-module(wse_document_demo).
+-export([run/2]).
+
+-compile(export_all).
+
+run(Ws, Where) ->
+ io:format("document_demo: called\n"),
+
+ screen_info(Ws, wse:id(Where),
+ [availHeight, availWidth,colorDepth,
+ height, pixelDepth, width ]),
+
+ window_info(Ws, wse:id(Where),
+ [closed, innerHeight,innerWidth,length,name,
+ outerHeight,outerWidth]),
+
+ navigator_info(Ws, wse:id(Where),
+ [appCodeName,appName,appVersion,cookieEnabled,
+ language,onLine,platform,product,userAgent]),
+
+ document_info(Ws, wse:id(Where),
+ [cookie,docType,inputEncoding,lastModified,
+ referrer,domain,baseURI,title,'URL']),
+ %% wse:close(Ws, done),
+ ok.
+
+screen_info(Ws, Parent, As=[A|_]) when is_atom(A) ->
+ [ info(Ws, wse:screen(), "screen", What, Parent) || What <- As ].
+
+window_info(Ws, Parent, As=[A|_]) when is_atom(A) ->
+ [ info(Ws, wse:window(), "window", What, Parent) || What <- As ].
+
+navigator_info(Ws, Parent, As=[A|_]) when is_atom(A) ->
+ [ info(Ws, wse:navigator(), "navigator", What, Parent) || What <- As ].
+
+document_info(Ws, Parent, As=[A|_]) when is_atom(A) ->
+ [ info(Ws, wse:document(), "document", What, Parent) || What <- As ].
+
+info(Ws, Object, ObjName, What, Parent) ->
+ {ok,Value} = wse:get(Ws, Object, What),
+ Text = ObjName++"."++to_text(What)++"="++to_text(Value),
+ ElemNode = wse:createElement(Ws, "p"),
+ TextNode = wse:createTextNode(Ws, Text),
+ wse:appendChild(Ws, ElemNode, TextNode),
+ wse:appendChild(Ws, Parent, ElemNode).
+
+to_text(X) ->
+ lists:flatten(io_lib:format("~p", [X])).
diff --git a/deps/wse/src/wse_fish_demo.erl b/deps/wse/src/wse_fish_demo.erl
new file mode 100644
index 0000000..e486b6e
--- /dev/null
+++ b/deps/wse/src/wse_fish_demo.erl
@@ -0,0 +1,54 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2014, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2014, Tony Rogvall
+%%% @doc
+%%% Show how to do remote animation with png
+%%% Fishes from: http://www.squaregoldfish.co.uk
+%%% @end
+%%% Created : 24 Apr 2014 by Tony Rogvall <tony@rogvall.se>
+
+-module(wse_fish_demo).
+-export([run/2]).
+
+-compile(export_all).
+
+run(Ws, Where) ->
+ io:format("fish_demo: called\n"),
+ %% "preload" fish images
+ Fishes =
+ [ begin
+ FNo = tl(integer_to_list(10000+I)),
+ File = "fish/fish"++FNo++".png",
+ {ok,Image} = wse:load_image(Ws, File),
+ %% io:format("loaded: ~s\n", [File]),
+ Image
+ end || I <- lists:seq(1,23)],
+ Image = wse:createElement(Ws, "img"),
+ Parent = wse:id(Where),
+ wse:appendChild(Ws, Parent, Image),
+ update_loop(Ws, Image, Fishes, Fishes).
+
+update_loop(Ws, Image, [F|Fs], Fishes) ->
+ {ok,Src} = wse:get(Ws, F, "src"),
+ %% io:format("Src=~p\n", [Src]),
+ wse:set(Ws, Image, "src", Src),
+ timer:sleep(100),
+ update_loop(Ws, Image, Fs, Fishes);
+update_loop(Ws, Image, [], Fishes) ->
+ update_loop(Ws, Image, Fishes, Fishes).
+
diff --git a/deps/wse/src/wse_frame_demo.erl b/deps/wse/src/wse_frame_demo.erl
new file mode 100644
index 0000000..279b5b3
--- /dev/null
+++ b/deps/wse/src/wse_frame_demo.erl
@@ -0,0 +1,137 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2014, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2014, Tony Rogvall
+%%% @doc
+%%% This demo is called directly from web page
+%%% @end
+%%% Created : 24 Apr 2014 by Tony Rogvall <tony@rogvall.se>
+
+-module(wse_frame_demo).
+-export([run/2]).
+
+-compile(export_all).
+
+run(Ws, Where) ->
+ io:format("frame_demo where=~s: called\n", [Where]),
+
+ case Where of
+ "i0" ->
+ window_info(Ws, wse:id(Where), [frames]),
+ {ok,Frames} = wse:get(Ws, wse:window(), frames),
+ io:format("Frames object=~p\n", [Frames]),
+ {ok,Length} = wse:get(Ws, Frames, length),
+ io:format("#Frames=~p\n", [Length]),
+ lists:foreach(
+ fun(I) ->
+ %% Access frames and ping the wse in each
+ {ok,F} = wse:get(Ws, Frames, I),
+ io:format("Frame ~w: ~p\n", [I,F]),
+ {ok,D} = wse:get(Ws, F, document),
+ io:format("Frame Document: ~p\n", [D]),
+ {ok,Wse} = wse:get(Ws, F, 'Wse'),
+ io:format("Frame Wse: ~p\n", [Wse]),
+ {ok,ID} = wse:get(Ws, Wse, id),
+ io:format("Frame Wse: Wse.id=~p\n", [ID]),
+ %% wse:cast(Ws, Wse, notify, [120+I, hello_world]),
+ ok
+ end, lists:seq(0, Length-1)),
+ ok;
+ _ ->
+ window_info(Ws, wse:id(Where), [name,innerHeight,innerWidth]),
+
+ %% sleep short time ?
+ timer:sleep(500),
+ Target = case Where of
+ "i1" -> "w2";
+ "i2" -> "w1"
+ end,
+ io:format("target = ~p\n", [Target]),
+ {ok,Wse} = get_wse_frame(Ws, Target),
+ %% try send message across to other frames wse endpoint
+ case Wse of
+ undefined ->
+ ok;
+ _ ->
+ io:format("~s: get info of other system ~p\n",
+ [Where,Target]),
+ Func = wse:newf(Ws, "wse,iref,value",
+ "{ console.debug('frame_demo.reply_fun'); wse.reply(iref,value); }"),
+ Call = [lists,reverse,[[1,2,3]],Func],
+ io:format("rcall ~p\n", [Call]),
+ Ret=wse:rcall(Ws, Wse, call, Call),
+ io:format("rcall return = ~p\n", [Ret]),
+ %% wse:cast(Ws, Wse, notify, [111, [inter_frame,Where]]),
+ ok
+ end,
+ %% wse:close(Ws, done),
+ ok
+ end.
+
+get_wse_frame(Ws, Wid) ->
+ {ok,P} = wse:get(Ws, wse:window(), parent), %% p = window.parent,
+ io:format("window.parent = ~p\n", [P]),
+ {ok,Wse} = wse:get(Ws, P, 'Wse'),
+ io:format("window.parent.Wse = ~p\n", [Wse]),
+ {ok,WseID} = wse:get(Ws, Wse, id),
+ io:format("window.parent.Wse.id = ~p\n", [WseID]),
+ {ok,Wse1} = wse:call(Ws, Wse, getWse, [Wid]),
+ io:format("Wse1 = ~p\n", [Wse1]),
+ {ok,Wse1}.
+
+
+all_info(Ws, Where) ->
+ screen_info(Ws, wse:id(Where),
+ [availHeight, availWidth,colorDepth,
+ height, pixelDepth, width ]),
+ window_info(Ws, wse:id(Where),
+ [closed, innerHeight,innerWidth,length,name,
+ outerHeight,outerWidth]),
+ navigator_info(Ws, wse:id(Where),
+ [appCodeName,appName,appVersion,cookieEnabled,
+ language,onLine,platform,product,userAgent]),
+ document_info(Ws, wse:id(Where),
+ [cookie,docType,inputEncoding,lastModified,
+ referrer,domain,baseURI,title,'URL']),
+ ok.
+
+screen_info(Ws, Parent, As=[A|_]) when is_atom(A) ->
+ [ info(Ws, wse:screen(), "screen", What, Parent) || What <- As ].
+
+window_info(Ws, Parent, As=[A|_]) when is_atom(A) ->
+ [ info(Ws, wse:window(), "window", What, Parent) || What <- As ].
+
+navigator_info(Ws, Parent, As=[A|_]) when is_atom(A) ->
+ [ info(Ws, wse:navigator(), "navigator", What, Parent) || What <- As ].
+
+document_info(Ws, Parent, As=[A|_]) when is_atom(A) ->
+ [ info(Ws, wse:document(), "document", What, Parent) || What <- As ].
+
+info(Ws, Object, ObjName, What, Parent) ->
+ {ok,Value} = wse:get(Ws, Object, What),
+ Text = ObjName++"."++to_text(What)++"="++to_text(Value),
+ ElemNode = wse:createElement(Ws, "p"),
+ TextNode = wse:createTextNode(Ws, Text),
+ wse:appendChild(Ws, ElemNode, TextNode),
+ wse:appendChild(Ws, Parent, ElemNode).
+
+to_text(X) ->
+ lists:flatten(io_lib:format("~p", [X])).
+
+
+
+
diff --git a/deps/wse/src/wse_location_demo.erl b/deps/wse/src/wse_location_demo.erl
new file mode 100644
index 0000000..65d3c56
--- /dev/null
+++ b/deps/wse/src/wse_location_demo.erl
@@ -0,0 +1,46 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2014, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2014, Tony Rogvall
+%%% @doc
+%%% This demo is called directly from web page
+%%% @end
+%%% Created : 24 Apr 2014 by Tony Rogvall <tony@rogvall.se>
+
+-module(wse_location_demo).
+-export([run/2,
+ goto/1]).
+
+-compile(export_all).
+
+run(Ws,_Id) ->
+ io:format("location_demo: called\n"),
+ register(wse_location_demo, self()),
+ listen_loop(Ws).
+
+goto(Location) ->
+ wse_location_demo ! {goto, Location}.
+
+listen_loop(Ws) ->
+ receive
+ {goto, Location} ->
+ io:format("goto: ~p~n", [Location]),
+ ok = wse:set(Ws, wse:window(), ["location", "href"], Location);
+ {'DOWN',_Mon,process,_Pid,Reason} ->
+ io:format("process crashed: ~p~n", [Reason])
+ end,
+ listen_loop(Ws).
diff --git a/deps/wse/src/wse_raphael_demo.erl b/deps/wse/src/wse_raphael_demo.erl
new file mode 100644
index 0000000..a73b322
--- /dev/null
+++ b/deps/wse/src/wse_raphael_demo.erl
@@ -0,0 +1,73 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2014, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2014, Tony Rogvall
+%%% @doc
+%%% This demo is called directly from web page
+%%% @end
+%%% Created : 9 Feb 2014 by Tony Rogvall <tony@rogvall.se>
+
+-module(wse_raphael_demo).
+
+-export([run/2]).
+
+%%
+%% Small demo
+%%
+run(Ws, Where) ->
+ io:format("raphael_demo: called\n"),
+ ok = wse:load(Ws, "raphael-min.js"),
+ {ok,Paper} = raphael:new(Ws, Where, 640, 540),
+
+ %% must create the image element here!
+ {ok,Image} = wse:load_image(Ws, "bd.jpg"),
+ {ok,Src} = wse:get(Ws, Image, "src"),
+ {ok,_I1} = raphael:image(Ws, Paper, Src, 140, 140, 320, 240),
+ {ok,I2} = raphael:image(Ws, Paper, Src, 140, 380, 320, 240),
+ raphael:attr(Ws, I2, "transform", "s1-1"),
+ raphael:attr(Ws, I2, "opacity", "0.5"),
+ {ok,R6} = raphael:rect(Ws, Paper, 0, 380, 600, 160),
+ raphael:attr(Ws, R6, "fill", "90-#333-#333"),
+ raphael:attr(Ws, R6, "stroke", "none"),
+ raphael:attr(Ws, R6, "opacity", "0.5"),
+
+
+ {ok,C1} = raphael:circle(Ws, Paper, 10, 10, 10),
+ raphael:attr(Ws, C1, "fill", "#FF0000"),
+ {ok,C2} = raphael:circle(Ws, Paper, 40, 10, 10),
+ raphael:attr(Ws, C2, "fill", "#00FF00"),
+ {ok,C3} = raphael:circle(Ws, Paper, 70, 10, 10),
+ raphael:attr(Ws, C3, "fill", "#0000FF"),
+ {ok,C4} = raphael:circle(Ws, Paper, 100, 10, 10),
+ raphael:attr(Ws, C4, "fill", "90-#fff-#000"),
+
+ {ok,R1} = raphael:rect(Ws, Paper, 10, 30, 10, 10),
+ raphael:attr(Ws, R1, "fill", "#FF0000"),
+ {ok,R2} = raphael:rect(Ws, Paper, 40, 30, 10, 10),
+ raphael:attr(Ws, R2, "fill", "#00FF00"),
+
+ {ok,R3} = raphael:rect(Ws, Paper, 70, 30, 10, 10),
+ raphael:attr(Ws, R3, "fill", "#0000FF"),
+ {ok,R4} = raphael:rect(Ws, Paper, 100, 30, 10, 10),
+ raphael:attr(Ws, R4, "fill", "90-#fff-#000"),
+
+
+ ok.
+
+
+
+
diff --git a/deps/wse/src/wse_server.erl b/deps/wse/src/wse_server.erl
new file mode 100644
index 0000000..1ea4c58
--- /dev/null
+++ b/deps/wse/src/wse_server.erl
@@ -0,0 +1,696 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2014, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2014, Tony Rogvall
+%%% @doc
+%%% Web socket server (RFC 6455)
+%%% @end
+%%% Created : 9 Feb 2014 by Tony Rogvall <tony@rogvall.se>
+
+-module(wse_server).
+
+-export([start/0, start/1, start/2, stop/1]).
+-export([ws_loop/3]).
+
+-compile(export_all).
+
+-define(WS_UUID, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").
+-define(WS_OP_TEXT, 1).
+-define(WS_OP_BINARY, 2).
+-define(WS_OP_CLOSE, 8).
+-define(WS_OP_PING, 9).
+-define(WS_OP_PONG, 10).
+
+-define(WSE_DEFAULT_PORT, 1234).
+
+-record(event,
+ {
+ iref, %% global integer reference
+ from, %% [owner local reference | event owner pid]
+ how=once, %% once | all | none
+ data %% local data for events
+ }).
+
+-record(ws_header,
+ {
+ host, %% 'Host'
+ upgrade, %% 'Upgrade'
+ connection, %% 'Connection'
+ key, %% "Sec-WebSocket-Key"
+ protocol, %% "Sec-WebSocket-Protocol"
+ origin, %%
+ version, %% "Sec-WebSocket-Version"
+ cookie, %% 'Cookie'
+ hs = []
+ }).
+
+-record(s,
+ {
+ iref = 1,
+ closing=false, %% false|client|server
+ pingInterval = 10000, %% ping every T ms
+ pongTimeout = 5000, %% wait max T ns for pong
+ ping_ref, %% current ping reference
+ pong_tmr, %% current pong timeout reference
+ ping_data, %% current ping data
+ proto, %% from handshake "bert"?
+ type, %% ?WS_OP_TEXT|?WS_OP_BINARY
+ fs = [], %% fragments
+ wait = [], %% #event
+ header, %% ws_header
+ gc_table %% ets table of objects
+ }).
+
+
+-define(log(F,W,As),
+ io:format("~s:~w: " ++ (W)++" "++(F)++"\n", [?MODULE, ?LINE | (As)])).
+%%-define(debug(F,As), ?log(F,"debug",As)).
+-define(debug(F,A), ok).
+-define(info(F,As), ?log(F,"info", As)).
+-define(warn(F,As), ?log(F,"warn", As)).
+-define(error(F,As), ?log(F,"error", As)).
+
+%% start()
+%% This should be in another module for clarity
+%% but is included here to make the example self-contained
+
+start() -> start_([]).
+
+start(Port) when is_integer(Port) ->
+ start_([{port,Port}]);
+start([AtomPort]) when is_atom(AtomPort) ->
+ start(list_to_integer(atom_to_list(AtomPort))).
+
+start(Port,Opts) when is_integer(Port) -> start_([{port,Port}|Opts]).
+
+start_(Opts) -> spawn(fun() -> init(Opts) end).
+
+stop(RegName) when is_atom(RegName) ->
+ RegName ! stop.
+
+
+init(Opts) ->
+ Port = proplists:get_value(port, Opts, ?WSE_DEFAULT_PORT),
+
+ case proplists:get_value(name, Opts) of
+ undefined -> ok;
+ Name -> register(Name, self())
+ end,
+ Addr = proplists:get_value(ifaddr, Opts, any),
+ {ok, Listen} = gen_tcp:listen(Port,
+ [{packet,http},{reuseaddr,true},
+ {ifaddr, Addr},
+ {mode, binary}, {active, once}]),
+ process_flag(trap_exit, true),
+ listen_loop(Listen,Opts).
+
+listen_loop(Listen,Opts) ->
+ ?debug("Listen loop ~p\n", [Listen]),
+ Parent = self(),
+ Pid = spawn_link(fun() -> accept(Parent, Listen, Opts) end),
+ ?MODULE:accept_loop(Listen,Opts,Pid).
+
+accept_loop(Listen,Opts,Pid) ->
+ ?debug("Accept loop ~p\n", [Listen]),
+ receive
+ {Pid,ok} ->
+ ?MODULE:listen_loop(Listen,Opts);
+ {Pid,Error} ->
+ ?warn("process ~p error: ~p\n", [Pid, Error]),
+ ?MODULE:listen_loop(Listen,Opts);
+ {'EXIT',Pid,Reason} ->
+ ?warn("process ~p crashed: ~p\n", [Pid, Reason]),
+ ?MODULE:listen_loop(Listen,Opts);
+ {'EXIT',OtherPid,Reason} ->
+ ?warn("other process ~p crashed: ~p\n", [OtherPid, Reason]),
+ ?MODULE:accept_loop(Listen, Opts, Pid);
+ stop ->
+ gen_tcp:close(Listen),
+ exit(stopped)
+ end.
+
+accept(Parent, Listen, Opts) ->
+ ?debug("Accept ~p\n", [Listen]),
+ case gen_tcp:accept(Listen) of
+ {ok, Socket} ->
+ ?debug("Connected to ~p\n", [inet:peername(Socket)]),
+ Parent ! {self(), ok},
+ process_flag(trap_exit, true),
+ put(parent, Parent),
+ ?MODULE:ws_handshake(Socket,Opts);
+ Error ->
+ Parent ! {self(), Error}
+ end.
+%%
+%% Simple BERT
+%%
+bert_encode(Term) ->
+ term_to_binary(Term).
+
+bert_decode(Bin) ->
+ binary_to_term(Bin).
+
+%%
+ws_encode(Term,?WS_OP_BINARY) ->
+ bert_encode(Term);
+ws_encode(Term,?WS_OP_TEXT) ->
+ base64:encode(bert_encode(Term)).
+
+ws_decode(Data,?WS_OP_BINARY) -> {mesg,bert_decode(Data)};
+ws_decode(Data,?WS_OP_TEXT) -> {mesg,bert_decode(base64:decode(Data))};
+ws_decode(Data, ?WS_OP_PING) -> {ping, Data};
+ws_decode(Data, ?WS_OP_PONG) -> {pong,Data};
+ws_decode(Data, ?WS_OP_CLOSE) -> {close,Data}.
+
+
+
+ws_handshake(Socket,Opts) ->
+ receive
+ {http, Socket, _Req={http_request,'GET',Uri,_Version}} ->
+ ?debug("got ws request ~p", [_Req]),
+ ws_handshake(Socket, Uri, Opts);
+ {http, _Socket, Req={http_request, _, _, _}} ->
+ ?warn("reject ws request ~p", [Req]),
+ %% send error reply!
+ ws_error({error, bad_request});
+ Any ->
+ ?warn("reject ws data ~p", [Any]),
+ ws_error({error, no_data})
+ end.
+
+ws_handshake(Socket, _Uri, Opts) ->
+ inet:setopts(Socket, [{active, once}]),
+ case ws_recv_headers(Socket, #ws_header{}, 1000) of
+ Err ={error,_} ->
+ ws_error(Err);
+ F when is_list(F#ws_header.key) ->
+ ?debug("got request data: uri=~p, header=~p", [_Uri, F]),
+ %% fixme: check base64! (just crash now)
+ %% ?debug("Random = ~w", [base64:decode(F#ws_header.key)]),
+ Accept1 = [F#ws_header.key, ?WS_UUID],
+ Accept2 = crypto:hash(sha, Accept1),
+ Accept = base64:encode(Accept2),
+ WsAccept = ["Sec-Websocket-Accept:",Accept,"\r\n"],
+ ?debug("Accept = ~w", [Accept]),
+ WsProto = if is_list(F#ws_header.protocol) ->
+ ["Sec-Websocket-Protocol:",
+ hd(string:tokens(F#ws_header.protocol, ",")),
+ "\n\n"];
+ true -> []
+ end,
+ Handshake =
+ [
+ "HTTP/1.1 101 Switching Protocols\r\n",
+ "Upgrade: websocket\r\n",
+ "Connection: Upgrade\r\n",
+ WsAccept,
+ WsProto,
+ "\r\n"],
+ gen_tcp:send(Socket, Handshake),
+ ?debug("ws_server: sent: ~p", [Handshake]),
+ inet:setopts(Socket, [{packet, 0},{active,once}]),
+ PingInterval = proplists:get_value(pingInterval,Opts,10000),
+ PongTimeout = proplists:get_value(pongTimeout,Opts,5000),
+ Type = case proplists:get_value(type,Opts,binary) of
+ binary -> ?WS_OP_BINARY;
+ text -> ?WS_OP_TEXT
+ end,
+ %% Store header in process dictionary for direct access
+ put(header, F#ws_header.hs),
+ S0 = #s {proto=WsProto,
+ type=Type,
+ pingInterval=PingInterval,
+ pongTimeout=PongTimeout,
+ header = F,
+ gc_table = ets:new(gc_table, [])
+ },
+ S1 = start_ping_timer(S0),
+ ws_loop(<<>>, Socket, S1);
+ true ->
+ ws_error({error, missing_key})
+ end.
+
+ws_error(Error) ->
+ ?error("~w", [Error]),
+ Error.
+
+ws_recv_headers(S, F, Timeout) ->
+ receive
+ {http, S, http_eoh} ->
+ F;
+ {http, S, {http_header, _, K, _, V}} ->
+ inet:setopts(S, [{active, once}]),
+ %% Save all in hs
+ F1 = F#ws_header { hs = [{K,V}|F#ws_header.hs]},
+ case K of
+ 'Host' ->
+ ws_recv_headers(S, F1#ws_header { host = V}, Timeout);
+ 'Upgrade' ->
+ ws_recv_headers(S, F1#ws_header { upgrade = V}, Timeout);
+ 'Connection' ->
+ ws_recv_headers(S, F1#ws_header { connection = V}, Timeout);
+ "Sec-Websocket-Key" ->
+ ws_recv_headers(S, F1#ws_header { key = V}, Timeout);
+ "Sec-Websocket-Protocol" ->
+ ws_recv_headers(S, F1#ws_header { protocol = V}, Timeout);
+ "Sec-Websocket-Version" ->
+ ws_recv_headers(S, F1#ws_header { version = V}, Timeout);
+ 'Cookie' ->
+ ws_recv_headers(S, F1#ws_header { cookie = V}, Timeout);
+ _ ->
+ ws_recv_headers(S, F1, Timeout)
+ end
+ after Timeout ->
+ {error, timeout}
+ end.
+
+%%
+%% Reply on event
+%% if reply returns true then the event should stay
+%% otherwise the event should be deleted
+%%
+reply(E, Reply) ->
+ if E#event.how == none ->
+ false;
+ true ->
+ [Ref|Pid] = E#event.from,
+ Pid ! {reply,Ref,Reply},
+ E#event.how == all
+ end.
+
+
+next_ref(Ref) ->
+ Ref1 = (Ref+1) band 16#ffffffff,
+ if Ref1 == 0 ->
+ 1;
+ true ->
+ Ref1
+ end.
+
+ws_loop(Buf, Socket, S) ->
+ receive
+ %% WebSocket stuff
+ {tcp, Socket, Data} ->
+ %% ?debug("tcp ~w: ~p", [Socket, Data]),
+ ws_data(Buf, Data, Socket, S);
+
+ {tcp_closed, Socket} ->
+ ?debug("tcp_closed ~w", [Socket]),
+ %% reply to all remaining callers
+ lists:foreach(fun(E) -> reply(E, {error,closed}) end, S#s.wait),
+ exit(closed);
+
+ {'EXIT',Pid,Reason} ->
+ case get(parent) of
+ Pid ->
+ ?debug("exit from parent ~w reason=~p\n", [Pid, Reason]),
+ exit(Reason);
+ _ ->
+ ?debug("exit from ~w reason=~p\n", [Pid, Reason]),
+ ws_loop(Buf, Socket, S)
+ end;
+
+ {collect,ID} ->
+ case ets:update_counter(S#s.gc_table, ID, -1) of
+ 0 ->
+ ?debug("garbage collect: delete ~w\n", [ID]),
+ IRef = S#s.iref,
+ Data = ws_encode({async,IRef,{delete,ID}},S#s.type),
+ gen_tcp:send(Socket, ws_make_server_frame(Data,S#s.type)),
+ S1 = S#s { iref=next_ref(IRef) },
+ ets:delete(S#s.gc_table, ID),
+ ws_loop(Buf, Socket, S1);
+ _I ->
+ ?debug("garbage collect: ~w ref=~w\n", [ID,_I]),
+ ws_loop(Buf, Socket, S)
+ end;
+
+ Message ->
+ ?debug("handle_local: ~p", [Message]),
+ case handle_local(Message, Socket, S) of
+ {noreply,S1} ->
+ ws_loop(Buf, Socket, S1);
+ {stop,normal} ->
+ ok;
+ {stop,Reason} ->
+ exit(Reason)
+ end
+ end.
+
+ws_data(Buf, Data, Socket, S) ->
+ case <<Buf/binary, Data/binary>> of
+ %% masked data
+ <<Fin:1,_Rsv:3,Op:4,1:1,126:7,L:16,M:4/binary,Frag:L/binary,Buf1/binary>> ->
+ %% ?debug("unmask fragment: mask=~p, frag=~p", [M, Frag]),
+ Frag1 = ws_mask(M, Frag),
+ S1 = ws_fragment(Socket, Fin, Op, Frag1, S),
+ ws_data(Buf1, <<>>, Socket, S1);
+ <<Fin:1,_Rsv:3,Op:4,1:1,127:7,L:64,M:4/binary,Frag:L/binary,Buf1/binary>> ->
+ %% ?debug("unmask fragment: mask=~p, frag=~p", [M, Frag]),
+ Frag1 = ws_mask(M, Frag),
+ S1 = ws_fragment(Socket,Fin, Op, Frag1, S),
+ ws_data(Buf1, <<>>, Socket, S1);
+ <<Fin:1,_Rsv:3,Op:4,1:1,L:7,M:4/binary,Frag:L/binary,Buf1/binary>> ->
+ %% ?debug("unmask fragment: mask=~p, frag=~p", [M, Frag]),
+ Frag1 = ws_mask(M, Frag),
+ S1 = ws_fragment(Socket,Fin, Op, Frag1, S),
+ ws_data(Buf1, <<>>, Socket, S1);
+ %% non masked data
+ <<Fin:1,_Rsv:3,Op:4,0:1,126:7,L:16,Frag:L/binary,Buf1/binary>> ->
+ S1 = ws_fragment(Socket,Fin, Op, Frag, S),
+ ws_data(Buf1, <<>>, Socket, S1);
+ <<Fin:1,_Rsv:3,Op:4,0:1,127:7,L:64,Frag:L/binary,Buf1/binary>> ->
+ S1 = ws_fragment(Socket,Fin, Op, Frag, S),
+ ws_data(Buf1, <<>>, Socket, S1);
+ <<Fin:1,_Rsv:3,Op:4,0:1,L:7,Frag:L/binary,Buf1/binary>> ->
+ S1 = ws_fragment(Socket,Fin, Op, Frag, S),
+ ws_data(Buf1, <<>>, Socket, S1);
+ Buf1 -> %% handle to large messages and mal formed
+ inet:setopts(Socket, [{active, once}]),
+ ?MODULE:ws_loop(Buf1, Socket, S)
+ end.
+
+ws_mask(<<M:32>>, Frag) ->
+ Frag1 = << <<(X bxor M):32>> || <<X:32>> <= Frag >>,
+ Sz = byte_size(Frag),
+ case Sz band 3 of
+ 0 -> Frag1;
+ SzA ->
+ Sz0 = Sz-SzA,
+ SzB = 4-SzA,
+ <<_:Sz0/unit:8, Xa:SzA/unit:8>> = Frag,
+ <<X:32>> = <<Xa:SzA/unit:8,0:SzB/unit:8>>,
+ <<Yi:SzA/unit:8,_:SzB/unit:8>> = <<(X bxor M):32>>,
+ <<Frag1/binary,Yi:SzA/unit:8>>
+ end.
+
+ws_fragment(Socket,1, Op, Frag, S) ->
+ Payload = iolist_to_binary(lists:reverse([Frag|S#s.fs])),
+ %% ?debug("op=~w, unmasked payload = ~p", [ws_opcode(Op),Payload]),
+ Message = ws_decode(Payload,Op),
+ ?debug("handle_remote: ~p", [Message]),
+ handle_remote(Message, Socket, S#s { fs=[] });
+ws_fragment(_Socket, 0, _Op, Frag, S) ->
+ %% ?debug("collect fragment: Op=~w, Frag=~p", [_Op,Frag]),
+ S#s { fs = [Frag|S#s.fs ]}.
+
+
+ws_opcode(0) -> continuation;
+ws_opcode(?WS_OP_TEXT) -> text;
+ws_opcode(?WS_OP_BINARY) -> binary;
+ws_opcode(?WS_OP_CLOSE) -> close;
+ws_opcode(?WS_OP_PING) -> ping;
+ws_opcode(?WS_OP_PONG) -> pong;
+ws_opcode(Op) -> Op.
+
+ws_make_server_frame(Payload0,Type) ->
+ Fin = 1,
+ ws_make_frame(Fin,Type,<<>>, Payload0).
+
+ws_make_client_frame(Payload0,Type) ->
+ Fin = 1,
+ M = crypto:rand_bytes(4),
+ Payload = ws_mask(M, Payload0),
+ ws_make_frame(Fin,Type,M,Payload).
+
+
+ws_make_frame(Fin, Op, Mask, Data) ->
+ L = byte_size(Data),
+ M = if Mask =:= <<>> -> 0; true -> 1 end,
+ %% ?debug("payload size = ~w, mask=~w\n", [L,M]),
+ if L < 126 ->
+ <<Fin:1,0:3,Op:4,M:1,L:7,Mask/binary,Data/binary>>;
+ L < 65536 ->
+ <<Fin:1,0:3,Op:4,M:1,126:7,L:16,Mask/binary,Data/binary>>;
+ true ->
+ <<Fin:1,0:3,Op:4,M:1,127:7,L:64,Mask/binary,Data/binary>>
+ end.
+
+
+handle_local({rsync,From,Request},Socket,S0) ->
+ IRef = S0#s.iref,
+ Data = ws_encode({rsync,IRef,Request},S0#s.type),
+ gen_tcp:send(Socket, ws_make_server_frame(Data,S0#s.type)),
+ Event = #event{iref=IRef,from=From},
+ Wait1 = [Event|S0#s.wait],
+ {noreply,S0#s { iref=next_ref(IRef), wait=Wait1 }};
+handle_local({nsync,From,Request},Socket,S0) ->
+ IRef = S0#s.iref,
+ Data = ws_encode({nsync,IRef,Request},S0#s.type),
+ gen_tcp:send(Socket, ws_make_server_frame(Data,S0#s.type)),
+ Event = #event{iref=IRef,from=From,how=none},
+ Wait1 = [Event|S0#s.wait],
+ {noreply,S0#s { iref=next_ref(IRef), wait=Wait1 }};
+handle_local({async,_From,Request},Socket,S0) ->
+ IRef = S0#s.iref,
+ Data = ws_encode({async,IRef,Request},S0#s.type),
+ gen_tcp:send(Socket, ws_make_server_frame(Data,S0#s.type)),
+ {noreply,S0#s { iref=next_ref(IRef) }};
+handle_local({dsync,From,Request},Socket,S0) ->
+ IRef = S0#s.iref,
+ Data = ws_encode({dsync,IRef,Request},S0#s.type),
+ gen_tcp:send(Socket, ws_make_server_frame(Data,S0#s.type)),
+ Event = #event{iref=IRef,from=From},
+ Wait1 = [Event|S0#s.wait],
+ {noreply,S0#s { iref=next_ref(IRef), wait=Wait1 }};
+handle_local({close,From,Reason},Socket,S0) ->
+ reply(#event { from=From} , ok),
+ lists:foreach(fun(E) -> reply(E, {error, closed}) end, S0#s.wait),
+ CloseData = if is_atom(Reason) -> atom_to_binary(Reason, latin1);
+ true -> <<"unknown">>
+ end,
+ gen_tcp:send(Socket, ws_make_server_frame(CloseData,?WS_OP_CLOSE)),
+ {noreply,S0#s { closing=server }};
+
+handle_local({create_event,From,How,Data},_Socket,S0) ->
+ IRef = S0#s.iref,
+ Event = #event { iref=IRef, from=From, how=How, data=Data},
+ Wait1 = [Event|S0#s.wait],
+ reply(Event, {ok, IRef}),
+ {noreply,S0#s { iref=next_ref(IRef), wait=Wait1 }};
+
+handle_local({header, From},_Socket,S0=#s{header = Header}) ->
+ ?debug("header: all\n", []),
+ reply(#event {from=From}, {ok, Header#ws_header.hs}),
+ {noreply,S0};
+
+handle_local({header, ItemName, From},_Socket,S0=#s{header = Header}) ->
+ ?debug("header: ~p\n", [ItemName]),
+ case lists:keyfind(ItemName, 1, Header#ws_header.hs) of
+ {ItemName, ItemValue} ->
+ reply(#event {from=From}, {ok, ItemValue});
+ false ->
+ reply(#event {from=From}, {error, unknown_header_item})
+ end,
+ {noreply,S0};
+
+handle_local({timeout,Ref,ping},Socket,S0) when S0#s.ping_ref =:= Ref ->
+ %% ping the browser!
+ PingData = crypto:rand_bytes(4),
+ %% ?debug("sending ping ~p\n", [PingData]),
+ Frame = ws_make_server_frame(<<PingData/binary>>,?WS_OP_PING),
+ gen_tcp:send(Socket, Frame),
+ S1 = start_pong_timer(S0#s { ping_data=PingData, ping_ref=undefined }),
+ {noreply, S1};
+
+handle_local({timeout,Ref,pong},_Socket,S0) when S0#s.pong_tmr =:= Ref ->
+ ?debug("timeout waiting for pong ~p, stopping\n", [S0#s.ping_data]),
+ {stop, not_responding};
+
+handle_local(Other,_Socket,S0) ->
+ ?warn("handle_local: got ~p~n",[Other]),
+ {noreply,S0}.
+
+%%
+%% Handle remote operations and replies
+%%
+handle_remote({ping,Data}, Socket, S0) ->
+ %% ?debug("got ping ~p, sending pong ~p", [Data]),
+ gen_tcp:send(Socket, ws_make_server_frame(Data,?WS_OP_PONG)),
+ S0;
+handle_remote({pong,Data}, _Socket, S0) ->
+ if Data =:= S0#s.ping_data ->
+ %% ?debug("got pong reply: ~p", [Data]),
+ S1 = stop_pong_timer(S0),
+ start_ping_timer(S1);
+ true ->
+ ?debug("got heartbeat pong: ~p", [Data]),
+ S0
+ end;
+handle_remote({close,Data}, Socket, S0) ->
+ if S0#s.closing =:= server ->
+ ?debug("got close ~p, both sides closed", [Data]),
+ gen_tcp:close(Socket),
+ exit(Data);
+ S0#s.closing =:= false ->
+ ?debug("got close ~p, client closing", [Data]),
+ gen_tcp:send(Socket, ws_make_server_frame(Data,?WS_OP_CLOSE)),
+ S0#s { closing = client }
+ end;
+handle_remote({mesg, Mesg0}, Socket, S0) ->
+ Mesg = install_resource_objects(Mesg0, S0#s.gc_table),
+ handle_mesg(Mesg, Socket, S0).
+
+
+handle_mesg({reply,IRef,Reply}, _Socket, S0) ->
+ case lists:keytake(IRef, #event.iref, S0#s.wait) of
+ false ->
+ ?debug("got reply ~w = ~w (ignored)\n", [IRef,Reply]),
+ S0;
+ {value,Event,Wait1} ->
+ ?debug("got reply ~w = ~w (~w)\n", [IRef,Reply,Event]),
+ reply(Event, Reply),
+ S0#s { wait=Wait1}
+ end;
+handle_mesg({noreply,IRef},_Socket,S0) ->
+ case lists:keytake(IRef, #event.iref, S0#s.wait) of
+ false ->
+ S0;
+ {value,_Event,Wait1} ->
+ S0#s { wait=Wait1}
+ end;
+handle_mesg({notify,IRef,RemoteData},_Socket,S0) ->
+ ?info("notify: ~w ~p\n", [IRef,RemoteData]),
+ case lists:keytake(IRef,#event.iref, S0#s.wait) of
+ false ->
+ S0;
+ {value,E,Wait1} ->
+ [_Ref|Pid] = E#event.from,
+ Pid ! {notify,IRef,E#event.data,RemoteData},
+ if E#event.how == all ->
+ S0;
+ true ->
+ S0#s { wait=Wait1}
+ end
+ end;
+handle_mesg({info,_Data},_Socket,S0) ->
+ ?debug("info: ~p\n", [_Data]),
+ S0;
+handle_mesg({start,M,F,As},_Socket,S0) ->
+ _Pid = spawn_link(M,F,[self()|As]),
+ ?debug("wse process ~w:~w/~w, started, pid=~p\n", [M,F,length(As),_Pid]),
+ S0;
+handle_mesg({register,Name},_Socket,S0) ->
+ ?debug("register ~p\n", [Name]),
+ try register(Name,self()) of
+ true -> S0
+ catch
+ error:Reason ->
+ ?info("register of ~p failed : ~s\n", [Name, Reason]),
+ S0
+ end;
+handle_mesg({unregister}, _Socket, S0) ->
+ case process_info(self(), registered_name) of
+ [] -> S0;
+ {registered_name,Name} ->
+ ?debug("unregister ~p\n", [Name]),
+ catch (unregister(Name)),
+ S0
+ end;
+handle_mesg({call,IRef,M,F,As},Socket,S0) ->
+ %% maybe direct this to gen_server call on spawned processes?
+ try apply(M,F,As) of
+ Value ->
+ Data = ws_encode({reply,IRef,{ok,Value}},S0#s.type),
+ gen_tcp:send(Socket, ws_make_server_frame(Data,S0#s.type)),
+ S0
+ catch
+ error:Reason ->
+ Data = ws_encode({reply,IRef,{error,Reason}},S0#s.type),
+ gen_tcp:send(Socket, ws_make_server_frame(Data,S0#s.type)),
+ S0
+ end;
+handle_mesg({cast,_IRef,M,F,As},_Socket,S0) ->
+ %% maybe direct this to gen_server cast on spawned processes?
+ catch (apply(M,F,As)),
+ S0;
+handle_mesg(_Other, _Socket, S0) ->
+ ?debug("unknown mesg ~p\n", [_Other]),
+ S0.
+
+%%
+%% Transform a message sent from java script so that
+%% {object,N} => {objec,N,resource()}
+%% {function,N} => {function,N,resource()}
+%%
+install_resource_objects(Message, GcTable) ->
+ install_(Message, GcTable).
+
+install_(X, _GcTable) when is_number(X) -> X;
+install_(X, _GcTable) when is_atom(X) -> X;
+install_(X, _GcTable) when is_binary(X) -> X;
+install_(X, GcTable) when is_list(X) ->
+ try erlang:io_list_size(X) of
+ _ -> X
+ catch
+ error:_ -> install_list_(X, [], GcTable)
+ end;
+install_({object,ID}, GcTable) when is_integer(ID) ->
+ ets:insert_new(GcTable, {ID,0}),
+ ets:update_counter(GcTable,ID,1),
+ %% _RID is not relly needed so we use ID instead! (still unique)
+ {resource,_RID,Ref} = resource:notify_when_destroyed(self(),
+ {collect,ID}),
+ {object,ID,Ref};
+install_(X={object,_}, _GcTable) -> X;
+
+install_(X, GcTable) when is_tuple(X) ->
+ install_tuple_(size(X), X, [], GcTable).
+
+install_tuple_(0, _X, Acc, _GcTable) ->
+ list_to_tuple(Acc);
+install_tuple_(I, X, Acc, GcTable) ->
+ Y = install_(element(I,X), GcTable),
+ install_tuple_(I-1, X, [Y|Acc], GcTable).
+
+install_list_([], Acc, _GcTable) ->
+ lists:reverse(Acc);
+install_list_([H|T], Acc, GcTable) ->
+ Y = install_(H, GcTable),
+ install_list_(T, [Y|Acc], GcTable).
+
+
+start_ping_timer(S0) ->
+ if is_integer(S0#s.pingInterval),S0#s.pingInterval>0 ->
+ Ref = erlang:start_timer(S0#s.pingInterval, self(), ping),
+ S0#s { ping_ref = Ref, ping_data = undefined };
+ true ->
+ S0
+ end.
+
+start_pong_timer(S0) ->
+ if is_integer(S0#s.pongTimeout),S0#s.pongTimeout>0 ->
+ Ref = erlang:start_timer(S0#s.pongTimeout, self(), pong),
+ S0#s { pong_tmr = Ref };
+ true ->
+ S0
+ end.
+
+stop_pong_timer(S0) ->
+ Tmr = S0#s.pong_tmr,
+ if is_reference(Tmr) ->
+ erlang:cancel_timer(Tmr),
+ receive
+ {timeout,Tmr,pong} ->
+ ok
+ after 0 ->
+ ok
+ end,
+ S0#s { pong_tmr = undefined };
+ true ->
+ S0
+ end.
diff --git a/deps/wse/src/wse_table_demo.erl b/deps/wse/src/wse_table_demo.erl
new file mode 100644
index 0000000..b1c547f
--- /dev/null
+++ b/deps/wse/src/wse_table_demo.erl
@@ -0,0 +1,109 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2014, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2014, Tony Rogvall
+%%% @doc
+%%% This demo is called directly from web page
+%%% @end
+%%% Created : 24 Apr 2014 by Tony Rogvall <tony@rogvall.se>
+
+-module(wse_table_demo).
+-export([run/2]).
+
+-compile(export_all).
+
+table_data() ->
+ [ ["1111", on, 1],
+ ["1112", off, 2],
+ ["1113", off, 3],
+ ["1114", off, 4],
+ ["1115", off, 5]
+ ].
+
+run(Ws, Where) ->
+ io:format("table_demo: called\n"),
+
+ Parent = wse:id(Where),
+ {Table,TableElems} = make_table(Ws, table_data()),
+ wse:set(Ws, Table, "border", "2"),
+ wse:appendChild(Ws, Parent, Table),
+ update_loop(Ws, TableElems, 1).
+
+update_loop(Ws, Rs, I) ->
+ update_rows(Ws, Rs, 0, I),
+ timer:sleep(1000),
+ update_loop(Ws, Rs, I+1).
+
+update_rows(Ws, [{Tr,Tds}|Rs], Ri, I) ->
+ {ok,Style} = wse:get(Ws, Tr, "style"),
+ case Ri rem 2 of
+ 0 -> wse:set(Ws, Style, "backgroundColor", "rgb(0,255,0)");
+ 1 -> wse:set(Ws, Style, "backgroundColor", "rgb(255,0,0)")
+ end,
+ I1 = update_row(Ws, Tds, I),
+ update_rows(Ws, Rs, Ri+1, I1);
+update_rows(_Ws, [], _Ri, _I) ->
+ ok.
+
+update_row(Ws, [{_Td,Txt}|Tds], I) ->
+ wse:set(Ws, Txt, "nodeValue", integer_to_list(I)),
+ update_row(Ws, Tds, I+1);
+update_row(_Ws, [], I) ->
+ I.
+
+
+%%
+%% return:
+%% {Table,[{Tr1,[Td11,Td12,..Td1m1]},
+%% {Tr2,[Td21,Td22,..Td2m1]}
+%% {Trn,[Tdn1,Tn22,..Tdnmn]}]}
+%%
+make_table(Ws, TableData) ->
+ %% create a table from table data
+ Table = wse:createElement(Ws, "table"),
+ TBody = wse:createElement(Ws, "tbody"),
+ TableElems =
+ [ begin
+ Tr = wse:createElement(Ws, "tr"),
+ R = {Tr, [begin Td = wse:createElement(Ws, "td"),
+ Text = lists:flatten(io_lib:format("~p", [Col])),
+ TextNode = wse:createTextNode(Ws, Text),
+ wse:appendChild(Ws, Td, TextNode),
+ wse:appendChild(Ws, Tr, Td),
+ {Td,TextNode}
+ end || Col <- Row ]},
+ wse:appendChild(Ws, TBody, Tr),
+ R
+ end || Row <- TableData ],
+ wse:appendChild(Ws, Table, TBody),
+ {Table, TableElems}.
+
+
+info(Ws, Object, ObjName, What, Parent) ->
+ {ok,Value} = wse:get(Ws, Object, What),
+ Text = ObjName++"."++to_text(What)++"="++to_text(Value),
+ ElemNode = wse:createElement(Ws, "p"),
+ TextNode = wse:createTextNode(Ws, Text),
+ wse:appendChild(Ws, ElemNode, TextNode),
+ wse:appendChild(Ws, Parent, ElemNode).
+
+to_text(X) ->
+ lists:flatten(io_lib:format("~p", [X])).
+
+
+
+
diff --git a/packaging/README.md b/packaging/README.md
index 72f14e1..b7c7cd0 100644
--- a/packaging/README.md
+++ b/packaging/README.md
@@ -93,6 +93,43 @@ installed on a Tizen box. The RPM can be found at:
~/GBS-ROOT/local/repos/tizen/i586/RPMS/rvi-0.3.2-1.i686.rpm
+# UPDATING REBAR DEPENDENCIES
+All erlang dependencies, residing in ```deps```, are checked in with rvi_core in order
+to avoid having GBS and OBS to access the network to resolve them.
+If a new or updated module is to be included in deps, the following procedure
+can be used:
+
+1. <b>Update rebar.config</b><br>
+Edit rebar.config to include the correct version of the modules you need.
+
+2. <b>Clear deps</b><br>
+Delete the old deps with:
+
+ ```rm -rf deps/*```
+
+3. <b>Retrieve new deps</b><br>
+Use rebar to retrieve all modules listed in rebar.config.
+
+ ```rebar get-deps```
+
+3. <b>Clean out git info from deps</b><br>
+The checked out modules in deps are all their own repos. Clear the
+repo information.
+
+ ```rm -rf deps/*/.git
+ find deps -name .gitignore | xargs rm```
+
+4. <b>Add all new files in deps to the rvi_core repo</b><br>
+If any new files are added to deps by ```rebar get-deps``` above, they
+need to be added to the repo:
+
+ ```git add deps/*```
+
+5. <b>Commit rvi_core</b><br>
+Commit the updated deps directory to the repo:
+
+ ```git commit -a -m "Updated deps/gsm to version x"```
+