Skip to content

Commit

Permalink
Init for opensource
Browse files Browse the repository at this point in the history
  • Loading branch information
mustafaturan committed Jul 23, 2017
0 parents commit 08a9552
Show file tree
Hide file tree
Showing 16 changed files with 544 additions and 0 deletions.
22 changes: 22 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# The directory Mix will write compiled artifacts to.
/_build

# If you run "mix test --cover", coverage assets end up here.
/cover

# The directory Mix downloads your dependencies sources to.
/deps

# Where 3rd-party dependencies like ExDoc output generated docs.
/doc

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

.DS_Store
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
language: elixir
elixir:
- 1.4.5
80 changes: 80 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# EventBus

[![Build Status](https://travis-ci.org/mustafaturan/event_bus.svg?branch=master)](https://travis-ci.org/mustafaturan/event_bus)

Simple event bus implementation.

## Installation

The package can be installed by adding `event_bus` to your list of dependencies in `mix.exs`:

```elixir
def deps do
[{:event_bus, "~> 0.1.0"}]
end
```

## Usage

Module docs can be found at [https://hexdocs.pm/event_bus](https://hexdocs.pm/event_bus).

Subscribe to the 'event bus'
```elixir
EventBus.subscribe(MyEventListener)
```

Unsubscribe from the 'event bus'
```elixir
EventBus.unsubscribe(MyEventListener)
```

List subscribers
```elixir
EventBus.subscribers()
```

Notify all subscribers with any type of data
```elixir
EventBus.notify(:hello_received, %{message: "Hello"})
EventBus.notify(:bye_received, [user_id: 1, goal: "exit"])
```

### Sample Listener Implementation

```elixir
defmodule MyEventListener do
...

def process({:hello_received, _event_data} = event) do
GenServer.cast(__MODULE__, event)
end
def process({:bye_received, event_data}) do
# do sth
:ok
end
def process({event_type, event_data}) do
# this one matches all events
:ok
end

...
end
```

## Todo

- [ ] Move event data to ETS and pass key instead of data to listeners

## Contributing

### Issues, Bugs, Documentation, Enhancements

1. Fork the project

2. Make your improvements and write your tests(make sure you covered all the cases).

3. Make a pull request.

## License

MIT
30 changes: 30 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config

# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# 3rd-party users, it should be done in your "mix.exs" file.

# You can configure for your application as:
#
# config :event_bus, key: :value
#
# And access this configuration in your application as:
#
# Application.get_env(:event_bus, :key)
#
# Or configure a 3rd-party app:
#
# config :logger, level: :info
#

# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"
8 changes: 8 additions & 0 deletions coveralls.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"skip_files": [
"test/support"
],
"custom_stop_words": [
"defdelegate"
]
}
58 changes: 58 additions & 0 deletions lib/event_bus.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
defmodule EventBus do
@moduledoc """
Simple event bus implementation.
"""

alias EventBus.EventManager
alias EventBus.SubscriptionManager

@doc """
Send event to all listeners.
## Examples
EventBus.notify({:webhook_received, %{"message" => "Hi all!"}})
:ok
"""
@spec notify({:atom, any()}) :: :ok
def notify({_event_type, _event_data} = event) do
EventManager.notify(subscribers(), event)
end

@doc """
Subscribe to the bus.
## Examples
EventBus.subscribe(MyEventListener)
:ok
"""
@spec subscribe(any()) :: :ok
defdelegate subscribe(listener), to: SubscriptionManager, as: :subscribe

@doc """
Unsubscribe from the bus.
## Examples
EventBus.unsubscribe(MyEventListener)
:ok
"""
@spec unsubscribe(any()) :: :ok
defdelegate unsubscribe(listener), to: SubscriptionManager, as: :unsubscribe

@doc """
List the subscribers to the bus.
## Examples
EventBus.subscribers()
[MyEventListener]
"""
@spec subscribers() :: list(any())
defdelegate subscribers, to: SubscriptionManager, as: :subscribers
end
19 changes: 19 additions & 0 deletions lib/event_bus/application.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
defmodule EventBus.Application do
@moduledoc false

use Application
alias EventBus.EventManager
alias EventBus.SubscriptionManager

def start(_type, _args) do
import Supervisor.Spec, warn: false

children = [
worker(SubscriptionManager, [], id: make_ref(), restart: :permanent),
worker(EventManager, [], id: make_ref(), restart: :permanent)
]

opts = [strategy: :one_for_one, name: EventBus.Supervisor]
Supervisor.start_link(children, opts)
end
end
38 changes: 38 additions & 0 deletions lib/event_bus/event_manager.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
defmodule EventBus.EventManager do
@moduledoc """
Event Manager
"""

require Logger
use GenServer

@logging_level :info

@doc false
def start_link do
GenServer.start_link(__MODULE__, nil, name: __MODULE__)
end

@doc false
def notify(listeners, {_event_type, _event_data} = event) do
GenServer.cast(__MODULE__, {:notify, listeners, event})
end

@doc false
def handle_cast({:notify, listeners, event}, state) do
Enum.each(listeners, fn listener ->
notify_listener(listener, event)
end)
{:noreply, state}
end

defp notify_listener(listener, event) do
try do
listener.process(event)
rescue
err ->
Logger.log(@logging_level,
fn -> "#{listener}.process/1 raised an error!\n#{inspect(err)}" end)
end
end
end
52 changes: 52 additions & 0 deletions lib/event_bus/subscription_manager.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
defmodule EventBus.SubscriptionManager do
@moduledoc """
Subscription Manager
"""

use GenServer

@doc false
def start_link do
GenServer.start_link(__MODULE__, nil, name: __MODULE__)
end

def init(_) do
{:ok, []}
end

@doc false
def subscribe(listener) do
GenServer.cast(__MODULE__, {:subscribe, listener})
end

@doc false
def unsubscribe(listener) do
GenServer.cast(__MODULE__, {:unsubscribe, listener})
end

@doc false
def subscribers do
GenServer.call(__MODULE__, {:subscribers})
end

@doc false
def handle_cast({:subscribe, listener}, state) do
state =
if Enum.any?(state, fn old_listener -> old_listener == listener end) do
state
else
[listener | state]
end
{:noreply, state}
end

@doc false
def handle_cast({:unsubscribe, listener}, state) do
{:noreply, List.delete(state, listener)}
end

@doc false
def handle_call({:subscribers}, _from, state) do
{:reply, state, state}
end
end
63 changes: 63 additions & 0 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
defmodule EventBus.Mixfile do
use Mix.Project

def project do
[app: :event_bus,
version: "0.1.0",
elixir: "~> 1.4",
elixirc_paths: elixirc_paths(Mix.env),
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
description: description(),
package: package(),
deps: deps(),
dialyzer: [plt_add_deps: :transitive],
test_coverage: [tool: ExCoveralls],
docs: [extras: ["README.md"]]]
end

# Configuration for the OTP application
#
# Type "mix help compile.app" for more information
def application do
# Specify extra applications you'll use from Erlang/Elixir
[extra_applications: [:logger],
mod: {EventBus.Application, []}]
end

defp elixirc_paths(:test),
do: ["lib", "test/support"]
defp elixirc_paths(_),
do: ["lib"]

# Dependencies can be Hex packages:
#
# {:my_dep, "~> 0.3.0"}
#
# Or git/path repositories:
#
# {:my_dep, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
#
# Type "mix help deps" for more examples and options
defp deps do
[
{:dialyxir, "~> 0.5", only: [:dev], runtime: false},
{:excoveralls, "~> 0.7", only: [:test]},
{:ex_doc, "~> 0.16.2", only: [:dev]}
]
end

defp description do
"""
Simple event bus
"""
end

defp package do
[name: :event_bus,
files: ["lib", "mix.exs", "README.md"],
maintainers: ["Mustafa Turan"],
licenses: ["MIT"],
links: %{"GitHub" => "https://github.com/mustafaturan/event_bus"}]
end
end
13 changes: 13 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
%{"certifi": {:hex, :certifi, "1.2.1", "c3904f192bd5284e5b13f20db3ceac9626e14eeacfbb492e19583cf0e37b22be", [:rebar3], [], "hexpm"},
"dialyxir": {:hex, :dialyxir, "0.5.0", "5bc543f9c28ecd51b99cc1a685a3c2a1a93216990347f259406a910cf048d1d7", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.2.2", "f718159d6b65068e8daeef709ccddae5f7fdc770707d82e7d126f584cd925b74", [:mix], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.16.2", "3b3e210ebcd85a7c76b4e73f85c5640c011d2a0b2f06dcdf5acdb2ae904e5084", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
"excoveralls": {:hex, :excoveralls, "0.7.1", "3dd659db19c290692b5e2c4a2365ae6d4488091a1ba58f62dcbdaa0c03da5491", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
"hackney": {:hex, :hackney, "1.8.6", "21a725db3569b3fb11a6af17d5c5f654052ce9624219f1317e8639183de4a423", [:rebar3], [{:certifi, "1.2.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.0.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "5.0.2", "ac203208ada855d95dc591a764b6e87259cb0e2a364218f215ad662daa8cd6b4", [:rebar3], [{:unicode_util_compat, "0.2.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"jsx": {:hex, :jsx, "2.8.2", "7acc7d785b5abe8a6e9adbde926a24e481f29956dd8b4df49e3e4e7bcc92a018", [:mix, :rebar3], [], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.2.0", "dbbccf6781821b1c0701845eaf966c9b6d83d7c3bfc65ca2b78b88b8678bfa35", [:rebar3], [], "hexpm"}}
Loading

0 comments on commit 08a9552

Please sign in to comment.