Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/kbrw/clojure-erlastic
Micro lib making use of erlang JInterface lib to decode and encode Binary Erlang Term and simple erlang port interface with core.async channel. So you can communicate with erlang coroutine with clojure abstraction
https://github.com/kbrw/clojure-erlastic
clojure erlang-ports
Last synced: 3 months ago
JSON representation
Micro lib making use of erlang JInterface lib to decode and encode Binary Erlang Term and simple erlang port interface with core.async channel. So you can communicate with erlang coroutine with clojure abstraction
- Host: GitHub
- URL: https://github.com/kbrw/clojure-erlastic
- Owner: kbrw
- License: mit
- Created: 2014-06-13T10:42:05.000Z (over 10 years ago)
- Default Branch: master
- Last Pushed: 2017-10-21T10:16:53.000Z (over 7 years ago)
- Last Synced: 2024-09-14T04:24:09.950Z (5 months ago)
- Topics: clojure, erlang-ports
- Language: Clojure
- Size: 107 KB
- Stars: 34
- Watchers: 5
- Forks: 6
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
clojure-erlastic
================![Clojars Project](http://clojars.org/clojure-erlastic/latest-version.svg)
Micro lib making use of erlang JInterface lib to decode and encode Binary
Erlang Term and simple erlang port interface with core.async channel. So you
can communicate with erlang coroutine with clojure abstractionDesigned to be used (but not necessarily) with
[https://github.com/awetzel/exos](https://github.com/awetzel/exos).Last version of JInterface (from erlang 17.0) is taken from google scalaris
maven repo.## Usage
`port-connection` creates two channels that you can use to
communicate respectively in and out with the calling erlang port.
The objects you put or receive throught these channels are encoded
and decoded into erlang binary term following these rules :- erlang atom is clojure keyword
- erlang list is clojure list
- erlang tuple is clojure vector
- erlang binary is clojure bytes[]
- erlang integer is clojure long
- erlang float is clojure double
- erlang map is clojure map (thanks to erlang 17.0)
- clojure set is erlang listConversion of nil and string are configurable : every functions
`port-connection`, `decode`, `encode`, `run-server` can take an optional
`config` argument : a map defining 3 configs `:convention`, `:str-detect`, `:str-autodetect-len`.- if `(= :convention :elixir)` then :
- clojure nil is erlang `nil` atom, so elixir `nil`
- clojure string is encoded into erlang utf8 binary
- erlang binaries are decoded into clojure string :
- always if `(= :str-detect :all)`
- never if `(= :str-detect :none)`
- if the "str-autodetect-len" first bytes are printable when `(= :str-detect :auto)`
- if `(= :convention :erlang)` then :
- clojure nil is erlang `undefined`
- clojure string is encoded into erlang integer list
- erlang lists are decoded into clojure string :
- always if `(= :str-detect :all)`
- never if `(= :str-detect :none)`
- if the "str-autodetect-len" first elems are printable when `(= :str-detect :auto)`- default config is Elixir convention with no str detection.
For instance, here is a simple echo server :
```clojure
(let [[in out] (clojure-erlastic.core/port-connection)]
(! out ( mkdir calculator; cd calculator> vim project.clj
```clojure
(defproject calculator "0.0.1"
:dependencies [[clojure-erlastic "0.1.4"]
[org.clojure/core.match "0.2.1"]])
```> lein uberjar
Then create your clojure server as a simple script
> vim calculator.clj
```clojure
(require '[clojure.core.async :as async :refer [! ! out num) (recur num)))))))
```Finally launch the clojure server as a port, do not forget the `:binary` and `{:packet,4}` options, mandatory, then convert sent and received terms with `:erlang.binary_to_term` and `:erlang.term_to_binary`.
> vim calculator.exs
```elixir
defmodule CljPort do
def start, do:
Port.open({:spawn,'java -cp target/calculator-0.0.1-standalone.jar clojure.main calculator.clj'},[:binary, packet: 4])
def psend(port,data), do:
send(port,{self,{:command,:erlang.term_to_binary(data)}})
def preceive(port), do:
receive(do: ({^port,{:data,b}}->:erlang.binary_to_term(b)))
end
port = CljPort.start
CljPort.psend(port, {:add,3})
CljPort.psend(port, {:rem,2})
CljPort.psend(port, {:add,5})
CljPort.psend(port, :get)
6 = CljPort.preceive(port)
```> elixir calculator.exs
## OTP integration ##
If you want to integrate your clojure server in your OTP application, use the
`priv` directory which is copied 'as is'.```bash
mix new myapp ; cd myapp
mkdir -p priv/calculator
vim priv/calculator/project.clj # define dependencies
vim priv/calculator/calculator.clj # write your server
cd priv/calculator ; lein uberjar ; cd ../../ # build the jar
```Then use `"#{:code.priv_dir(:myapp)}/calculator"` to find correct path in your app.
To easily use your clojure server, link the opened port in a GenServer, to
ensure that if java crash, then the genserver crash and can be restarted by its
supervisor.> vim lib/calculator.ex
```elixir
defmodule Calculator do
use GenServer
def start_link, do: GenServer.start_link(__MODULE__, nil, name: __MODULE__)
def init(nil) do
Process.flag(:trap_exit, true)
cd = "#{:code.priv_dir(:myapp)}/calculator"
cmd = "java -cp 'target/*' clojure.main calculator.clj"
{:ok,Port.open({:spawn,'#{cmd}'},[:binary, packet: 4, cd: cd])}
end
def handle_info({:EXIT,port,_},port), do: exit(:port_terminated)def handle_cast(term,port) do
send(port,{self,{:command,:erlang.term_to_binary(term)}})
{:noreply,port}
enddef handle_call(term,_,port) do
send(port,{self,{:command,:erlang.term_to_binary(term)}})
result = receive do {^port,{:data,b}}->:erlang.binary_to_term(b) end
{:reply,result,port}
end
end
```Then create the OTP application and its root supervisor launching `Calculator`.
> vim mix.exs
```elixir
def application do
[mod: { Myapp, [] },
applications: []]
end
```> vim lib/myapp.ex
```elixir
defmodule Myapp do
use Application
def start(_type, _args), do: Myapp.Sup.start_linkdefmodule Sup do
use Supervisor
def start_link, do: :supervisor.start_link(__MODULE__,nil)
def init(nil), do:
supervise([worker(Calculator,[])], strategy: :one_for_one)
end
end
```Then you can launch and test your application in the shell :
```
iex -S mix
iex(1)> GenServer.call Calculator,:get
0
iex(2)> GenServer.cast Calculator,{:add, 3}
:ok
iex(3)> GenServer.cast Calculator,{:add, 3}
:ok
iex(4)> GenServer.cast Calculator,{:add, 3}
:ok
iex(5)> GenServer.cast Calculator,{:add, 3}
:ok
iex(6)> GenServer.call Calculator,:get
12
```
## Handle exitThe channels are closed when the launching erlang application dies, so you just
have to test if `(!