Skip to content

Commit 859cff7

Browse files
committed
First release
0 parents  commit 859cff7

File tree

20 files changed

+989
-0
lines changed

20 files changed

+989
-0
lines changed

.formatter.exs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Used by "mix format"
2+
[
3+
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
4+
]

.github/workflows/main.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
pull_request:
6+
branches:
7+
- main
8+
9+
jobs:
10+
mix_test:
11+
name: mix test (OTP ${{matrix.pair.otp}} | Elixir ${{matrix.pair.elixir}})
12+
13+
strategy:
14+
matrix:
15+
include:
16+
- pair:
17+
elixir: "1.14"
18+
otp: 25
19+
- pair:
20+
elixir: "1.18"
21+
otp: 27
22+
lint: lint
23+
24+
runs-on: ubuntu-20.04
25+
26+
steps:
27+
- uses: actions/checkout@v4
28+
29+
- name: Install Erlang and Elixir
30+
uses: erlef/setup-beam@v1
31+
with:
32+
otp-version: ${{matrix.pair.otp}}
33+
elixir-version: ${{matrix.pair.elixir}}
34+
35+
- name: Restore deps and _build cache
36+
uses: actions/cache@v4
37+
with:
38+
path: |
39+
deps
40+
_build
41+
key: deps-${{ runner.os }}-${{ matrix.pair.otp }}-${{ matrix.pair.elixir }}-${{ hashFiles('**/mix.lock') }}
42+
restore-keys: |
43+
deps-${{ runner.os }}-${{ matrix.pair.otp }}-${{ matrix.pair.elixir }}
44+
45+
- run: mix deps.get
46+
47+
- run: mix format --check-formatted
48+
if: ${{ matrix.lint }}
49+
50+
- run: mix deps.unlock --check-unused
51+
if: ${{ matrix.lint }}
52+
53+
- run: mix deps.compile
54+
55+
- run: mix compile --warnings-as-errors
56+
if: ${{ matrix.lint }}
57+
58+
- run: mix test
59+
60+
- run: mix test --warnings-as-errors
61+
if: ${{ matrix.lint }}

.gitignore

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# The directory Mix will write compiled artifacts to.
2+
/_build/
3+
4+
# If you run "mix test --cover", coverage assets end up here.
5+
/cover/
6+
7+
# The directory Mix downloads your dependencies sources to.
8+
/deps/
9+
10+
# Where third-party dependencies like ExDoc output generated docs.
11+
/doc/
12+
13+
# If the VM crashes, it generates a dump, let's ignore it too.
14+
erl_crash.dump
15+
16+
# Also ignore archive artifacts (built via "mix archive.build").
17+
*.ez
18+
19+
# Ignore package tarball (built via "mix hex.build").
20+
serve-*.tar
21+
22+
# Temporary files, for example, from tests.
23+
/tmp/
24+
25+
.history
26+
27+
/serve
28+
29+
.ignore/

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Changelog
2+
3+
## v0.1.0 (2025-02-20)
4+
5+
* First release

LICENSE.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# MIT License
2+
3+
Copyright (c) 2025 Piotr Baj
4+
5+
Permission is hereby granted, free of charge, to any person obtaining
6+
a copy of this software and associated documentation files (the
7+
"Software"), to deal in the Software without restriction, including
8+
without limitation the rights to use, copy, modify, merge, publish,
9+
distribute, sublicense, and/or sell copies of the Software, and to
10+
permit persons to whom the Software is furnished to do so, subject to
11+
the following conditions:
12+
13+
The above copyright notice and this permission notice shall be
14+
included in all copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Serve
2+
3+
**Serve** is escript to quickly start an HTTP server, similar to quick-start solutions available in other languages:
4+
5+
```shell
6+
$ php -S localhost:8080
7+
$ ruby -run -e httpd . -p 8080
8+
$ python3 -m http.server 8080
9+
```
10+
11+
With **Serve**, you can quickly start an HTTP server:
12+
13+
```shell
14+
$ serve /var/www/html
15+
[info] Serving /var/www/html with Cowboy 2.13.0 at http://127.0.0.1:4444
16+
```
17+
18+
## Installation
19+
20+
To install from Hex, run:
21+
22+
$ mix escript.install hex serve
23+
24+
To build and install it locally run:
25+
26+
$ mix install
27+
28+
it's a alias for:
29+
30+
$ mix do deps.get, escript.build, escript.install
31+
32+
> Note: For convenience, consider adding `~/.mix/escripts` directory to your `$PATH` environment variable.
33+
> If you are using [asdf](https://github.com/asdf-vm/asdf) Whenever you install a new escript with `mix escript.install` you need to `asdf reshim elixir` in order to create shims for it.
34+
35+
If you want to uninstall Serve simply run:
36+
37+
$ mix escript.uninstall serve
38+
39+
## Options
40+
41+
### Comand line options
42+
43+
You can pass options directly to `serve`
44+
45+
- `-p`, `--port` - Specify the port to listen on (default: `4444`).
46+
- `-o`, `--open` - Open the browser automatically.
47+
- `-n`, `--no-index` - Disable directory listing.
48+
- `-v`, `--version` - Shows Serve version.
49+
- `-h`, `--help` - Shows Serve usage information.
50+
51+
```shell
52+
$ serve --port 3000
53+
```
54+
55+
### Environment variables
56+
57+
Serve also supports environment variables for all options. Simply use env with `MIX_SERVE_` prefix.
58+
59+
```shell
60+
$ MIX_SERVE_PORT=3000 serve
61+
```
62+
63+
You can also add exports to yours profile file, for example:
64+
```
65+
export MIX_SERVE_PORT=8000
66+
export MIX_SERVE_OPEN=true
67+
export MIX_SERVE_NO_INDEX=true
68+
```
69+
70+
## Arguments
71+
72+
Serve accepts one argument: the directory to be served. If no directory is specified, the current working directory is used.
73+
74+
```shell
75+
$ pwd
76+
/var/www/html/my_website
77+
$ serve
78+
[info] Serving /var/www/html/my_website with Cowboy 2.13.0 at http://localhost:4444
79+
```
80+
81+
```shell
82+
$ mix docs
83+
Generating docs...
84+
View "html" docs at "doc/index.html"
85+
View "epub" docs at "doc/serve.epub"
86+
$ serve s doc
87+
[info] Serving /home/quex/workspace/serve/doc with Cowboy 2.13.0 at http://localhost:4444
88+
```
89+
90+
## Security
91+
92+
Although Serve uses the robust Cowboy HTTP server, it is not intended to be a production-ready web server. It only supports the HTTP protocol and listens on the loopback interface.

lib/serve.ex

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
defmodule Serve do
2+
@moduledoc false
3+
4+
@doc false
5+
def port do
6+
Application.get_env(:serve, :port, 4444)
7+
end
8+
9+
@doc false
10+
def static_path do
11+
Application.get_env(:serve, :path, File.cwd!())
12+
end
13+
14+
@doc false
15+
def backend, do: :cowboy
16+
17+
@doc false
18+
def index? do
19+
not Application.get_env(:serve, :no_index, false)
20+
end
21+
22+
@doc false
23+
def open? do
24+
Application.get_env(:serve, :open, false)
25+
end
26+
end

lib/serve/cli.ex

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
defmodule Serve.CLI do
2+
@moduledoc """
3+
Starts HTTP Server in DIR (the current directory by default).
4+
5+
Usage: `serve [OPTION] [DIR]`
6+
7+
Available options:
8+
-p, --port specify the port to listen on (default: `4444`).
9+
-o, --open open the browser automatically.
10+
-n, --no-index disable directory listing.
11+
-v, --version output version information and exit
12+
-h, --help display this help and exit
13+
14+
Examples:
15+
serve
16+
serve path/to/web
17+
serve -op 3000
18+
"""
19+
def main(["--version"]), do: version()
20+
def main(["-v"]), do: version()
21+
22+
def main(["--help"]), do: IO.puts(help())
23+
def main(["-h"]), do: IO.puts(help())
24+
25+
def main(command_line_args) do
26+
valid_opts = [port: :integer, no_index: :boolean, open: :boolean]
27+
aliases = [p: :port, n: :no_index, o: :open]
28+
29+
try do
30+
{opts, working_dir} =
31+
case OptionParser.parse_head!(command_line_args, strict: valid_opts, aliases: aliases) do
32+
{opts, []} ->
33+
{opts, File.cwd!()}
34+
35+
{opts, [working_dir]} ->
36+
{opts, Path.expand(working_dir)}
37+
end
38+
39+
{:ok, _supervisor} = runtime_config(opts, working_dir) |> start()
40+
Process.sleep(:infinity)
41+
rescue
42+
e in OptionParser.ParseError ->
43+
raise """
44+
Invalid arguments: #{e.message}
45+
46+
#{help()}
47+
"""
48+
exit({:shutdown, 1})
49+
end
50+
end
51+
52+
defp start(opts) do
53+
Application.put_all_env(serve: opts)
54+
55+
opts = [strategy: :one_for_one, name: Serve.ServerSupervisor]
56+
Supervisor.start_link([Serve.Server], opts)
57+
end
58+
59+
defp version, do: IO.puts("Serve v#{Application.spec(:serve, :vsn)}")
60+
61+
defp runtime_config(opts, working_dir) do
62+
verify_path(working_dir)
63+
64+
[
65+
path: working_dir,
66+
port: runtime_option(opts, :port, :integer, 4444),
67+
no_index: runtime_option(opts, :no_index, :boolean, false),
68+
open: runtime_option(opts, :open, :boolean, false)
69+
]
70+
end
71+
72+
defp verify_path(working_dir) do
73+
unless File.exists?(working_dir) do
74+
raise """
75+
Invalid path. The path '#{working_dir}' does not exist.
76+
"""
77+
end
78+
end
79+
80+
defp runtime_option(opts, key, :boolean, default) do
81+
case System.get_env(env_var(key)) do
82+
"true" -> true
83+
"false" -> false
84+
_ -> Keyword.get(opts, key, default)
85+
end
86+
end
87+
88+
defp runtime_option(opts, key, :integer, default) do
89+
case System.get_env(env_var(key)) do
90+
nil -> Keyword.get(opts, key, default)
91+
value -> parse_integer(value, default)
92+
end
93+
end
94+
95+
defp env_var(key) do
96+
"MIX_SERVE_#{String.upcase(Atom.to_string(key))}"
97+
end
98+
99+
defp parse_integer(maybe_int, default) do
100+
case Integer.parse(maybe_int) do
101+
{int, _} -> int
102+
_ -> default
103+
end
104+
end
105+
106+
defp help() do
107+
"""
108+
Usage: serve [OPTION] [DIR]
109+
Starts HTTP Server in DIR (the current directory by default).
110+
111+
Available options:
112+
-p, --port specify the port to listen on (default: `4444`).
113+
-o, --open open the browser automatically.
114+
-n, --no-index disable directory listing.
115+
-v, --version output version information and exit
116+
-h, --help display this help and exit
117+
118+
Examples:
119+
serve
120+
serve path/to/web
121+
serve -op 3000
122+
"""
123+
end
124+
end

0 commit comments

Comments
 (0)