├── .gitignore ├── README.md ├── config └── config.exs ├── lib ├── mix │ └── tasks │ │ └── server.ex └── spirit.ex ├── mix.exs └── test ├── spirit_test.exs └── test_helper.exs /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /deps 3 | erl_crash.dump 4 | *.ez 5 | mix.lock 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Spirit 2 | ====== 3 | 4 | n. a microframework for web development. 5 | 6 | ## Description 7 | 8 | We are big fans of [cuba] for `ruby` so we wanted to contribute to `elixir` 9 | community with a similar microframework. 10 | 11 | The intention of this project is to learn how `elixir` works and create a 12 | framework for our upcoming projects. 13 | 14 | We know there are many frameworks like [phoenix], [clint], [sugar] and others 15 | which we will be watching to learn and contribute but we still want to build 16 | a new one. It will teach us a lot! 17 | 18 | ## Installation 19 | 20 | Add `:spirit` to deps 21 | 22 | ```elixir 23 | defp deps do 24 | [ 25 | { :spirit, "~> 0.0.1" } 26 | ] 27 | end 28 | ``` 29 | 30 | And run `mix do deps.get, deps.compile` 31 | 32 | ## Usage 33 | 34 | Here's a simple application: 35 | 36 | ```elixir 37 | # cat lib/sample_app.ex 38 | 39 | defmodule SampleApp do 40 | use Spirit 41 | 42 | get "/hello" do 43 | send_resp(conn, 200, "

Hello World!

") 44 | end 45 | 46 | match _ do 47 | send_resp(conn, 404, "Not found :/") 48 | end 49 | end 50 | ``` 51 | 52 | And the config file 53 | 54 | ```elixir 55 | # cat config/config.exs 56 | 57 | use Mix.Config 58 | 59 | config :spirit, app: SampleApp 60 | ``` 61 | 62 | To run it, just do `mix server` and start browsing your application. 63 | 64 | > Check [spirit-example] to see the full example and step-by-step guide. 65 | 66 | ## Composition 67 | 68 | You can compose as many Spirit applications as you want using `forward`. 69 | This is a recommended practice when you have nested routes or want to group 70 | routes based on a criterion. 71 | 72 | ```elixir 73 | defmodule Users do 74 | use Spirit 75 | 76 | get "/" do 77 | send_resp(conn, 200, "Users index") 78 | end 79 | 80 | get "/:id" do 81 | # Show the User with `id` 82 | end 83 | 84 | post "/" do 85 | # Create a new user 86 | end 87 | 88 | match _ do 89 | send_resp(conn, 404, "Not found") 90 | end 91 | end 92 | 93 | defmodule MainApp do 94 | use Spirit 95 | 96 | get "/hi/:name" do 97 | send_resp(conn, 200, "

hello #{name}!

") 98 | end 99 | 100 | forward "/users", to: Users 101 | 102 | get "/hello/*_rest" do 103 | send_resp(conn, 200, "matches all routes starting with /hello") 104 | end 105 | 106 | match _ do 107 | send_resp(conn, 404, "Not found") 108 | end 109 | end 110 | ``` 111 | 112 | 113 | [cuba]: https://github.com/soveran/cuba 114 | [clint]: https://github.com/lpil/clint 115 | [sugar]: http://sugar-framework.github.io 116 | [phoenix]: http://phoenixframework.org 117 | [spirit-example]: https://github.com/citrusbyte/spirit-example 118 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # Sample configuration 4 | # 5 | # config :spirit, 6 | # app: MySpiritRouter 7 | # 8 | 9 | -------------------------------------------------------------------------------- /lib/mix/tasks/server.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Server do 2 | use Mix.Task 3 | 4 | def run(_args) do 5 | { :ok, app } = :application.get_env(:spirit, :app) 6 | 7 | app.start 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/spirit.ex: -------------------------------------------------------------------------------- 1 | defmodule Spirit do 2 | 3 | defmacro __using__(_) do 4 | quote do 5 | use Plug.Router 6 | use Plug.ErrorHandler 7 | 8 | plug :fetch_query_params 9 | plug Plug.Parsers, parsers: [:urlencoded, :json, :multipart], 10 | pass: ["*/*"], 11 | json_decoder: Poison 12 | 13 | plug :match 14 | plug :dispatch 15 | 16 | def start do 17 | {:ok, _} = Plug.Adapters.Cowboy.http(__MODULE__, [], port: port) 18 | 19 | IO.puts "Running on http://localhost:#{port}" 20 | 21 | no_halt 22 | end 23 | 24 | @doc """ 25 | Redirects the response. 26 | ## Arguments 27 | * `conn` - `Plug.Conn` 28 | * `location` - `String` 29 | * `opts` - `Keyword` 30 | 31 | ## Returns 32 | `Plug.Conn` 33 | """ 34 | @spec redirect(Plug.Conn.t, binary, Keyword.t) :: Plug.Conn.t 35 | def redirect(conn, location, opts \\ []) 36 | 37 | def redirect(%Plug.Conn{state: :sent} = conn, _, _) do 38 | conn 39 | end 40 | 41 | def redirect(conn, location, opts \\ []) do 42 | opts = [status: 302] |> Keyword.merge(opts) 43 | 44 | conn 45 | |> put_resp_header("Location", location) 46 | |> send_resp(opts[:status], "") 47 | end 48 | 49 | defp iex_running? do 50 | Code.ensure_loaded?(IEx) && IEx.started? 51 | end 52 | 53 | defp no_halt do 54 | unless iex_running? do 55 | :timer.sleep :infinity 56 | end 57 | end 58 | 59 | defp port do 60 | System.get_env("PORT") || 4000 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Spirit.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :spirit, 7 | version: "0.0.1", 8 | elixir: "~> 1.0.4", 9 | description: "Elixir microframework for web development.", 10 | deps: deps, 11 | package: package 12 | ] 13 | end 14 | 15 | # Configuration for the OTP application 16 | # 17 | # Type `mix help compile.app` for more information 18 | def application do 19 | [applications: [:cowboy, :plug]] 20 | end 21 | 22 | # Dependencies can be hex.pm packages: 23 | # 24 | # {:mydep, "~> 0.3.0"} 25 | # 26 | # Or git/path repositories: 27 | # 28 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1"} 29 | # 30 | # Type `mix help deps` for more examples and options 31 | defp deps do 32 | [ 33 | {:cowboy, "~> 1.0"}, 34 | {:plug, "~> 0.13"}, 35 | {:poison, "~> 1.4"} 36 | ] 37 | end 38 | 39 | defp package do 40 | [ 41 | contributors: ["Emiliano Mancuso"], 42 | licenses: ["MIT"], 43 | links: %{"Github" => "https://github.com/emancu/spirit"}, 44 | files: ~w(mix.exs README.md lib test) 45 | ] 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/spirit_test.exs: -------------------------------------------------------------------------------- 1 | defmodule SpiritTest do 2 | use ExUnit.Case 3 | 4 | test "the truth" do 5 | assert 1 + 1 == 2 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------