aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gleam.toml2
-rw-r--r--manifest.toml5
-rw-r--r--src/http.ffi.mjs85
-rw-r--r--src/http_ffi.erl13
-rw-r--r--src/lustre/try.gleam99
5 files changed, 149 insertions, 55 deletions
diff --git a/gleam.toml b/gleam.toml
index 0c4250d..f288a24 100644
--- a/gleam.toml
+++ b/gleam.toml
@@ -17,3 +17,5 @@ internal_modules = [
[dependencies]
gleam_stdlib = "~> 0.34"
gleam_community_ansi = "~> 1.3"
+glint = "~> 0.14"
+argv = "~> 1.0"
diff --git a/manifest.toml b/manifest.toml
index ef7a2b2..0c1900d 100644
--- a/manifest.toml
+++ b/manifest.toml
@@ -2,11 +2,16 @@
# You typically do not need to edit this file
packages = [
+ { name = "argv", version = "1.0.1", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "A6E9009E50BBE863EB37D963E4315398D41A3D87D0075480FC244125808F964A" },
{ name = "gleam_community_ansi", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "AB7C3CCC894653637E02DC455D5890C8CF3064E83E78CFE61145A4C458D02DE6" },
{ name = "gleam_community_colour", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "A49A5E3AE8B637A5ACBA80ECB9B1AFE89FD3D5351FF6410A42B84F666D40D7D5" },
{ name = "gleam_stdlib", version = "0.34.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1FB8454D2991E9B4C0C804544D8A9AD0F6184725E20D63C3155F0AEB4230B016" },
+ { name = "glint", version = "0.14.0", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_stdlib", "gleam_community_colour", "snag"], otp_app = "glint", source = "hex", outer_checksum = "21AB16D5A50D4EF34DF935915FDBEE06B2DAEDEE3FCC8584C6E635A866566B38" },
+ { name = "snag", version = "0.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "54D32E16E33655346AA3E66CBA7E191DE0A8793D2C05284E3EFB90AD2CE92BCC" },
]
[requirements]
+argv = { version = "~> 1.0" }
gleam_community_ansi = { version = "~> 1.3" }
gleam_stdlib = { version = "~> 0.34" }
+glint = { version = "~> 0.14" }
diff --git a/src/http.ffi.mjs b/src/http.ffi.mjs
index d1989bf..aad5e3d 100644
--- a/src/http.ffi.mjs
+++ b/src/http.ffi.mjs
@@ -9,29 +9,14 @@ const root = Path.join(cwd, "build/dev/javascript");
const toml = readFileSync(Path.join(cwd, "gleam.toml"), "utf-8");
const name = toml.match(/name *= *"(.+)"/)[1];
-const html = `<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Lustre preview server</title>
-
- <script type="module">
- import { main } from "./${name}/${name}.mjs"
-
- document.addEventListener("DOMContentLoaded", () => {
- main();
- });
- </script>
-</head>
-<body>
- <div data-lustre-app></div>
-</body>
-</html>`;
+let html;
const server = Http.createServer((req, res) => {
- res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, private');
- res.setHeader('Pragma', 'no-cache');
+ res.setHeader(
+ "Cache-Control",
+ "no-store, no-cache, must-revalidate, private"
+ );
+ res.setHeader("Pragma", "no-cache");
switch (true) {
case req.url === "/": {
@@ -88,17 +73,51 @@ const server = Http.createServer((req, res) => {
}
});
-export const serve = (host, port, on_start, on_port_taken) => {
- let tries = 1;
- server.on("error", (error) => {
- if (error.code === "EADDRINUSE") {
- let is_first_try = tries === 1;
- if (is_first_try) {
- on_port_taken(port);
+export const serve = (
+ { host, port, include_styles },
+ on_start,
+ on_port_taken
+) => {
+ let is_first_try = true;
+
+ html = `<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Lustre preview server</title>
+
+ ${
+ include_styles
+ ? `<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/lustre-labs/ui/priv/styles.css">`
+ : ""
+ }
+
+ <script type="module">
+ import { main } from "./${name}/${name}.mjs"
+
+ document.addEventListener("DOMContentLoaded", () => {
+ main();
+ });
+ </script>
+</head>
+<body>
+ <div data-lustre-app></div>
+</body>
+</html>`;
+
+ server
+ .on("error", (error) => {
+ if (error.code === "EADDRINUSE") {
+ if (is_first_try) {
+ on_port_taken(port);
+ is_first_try = false;
+ }
+
+ server.listen(++port, host);
}
- tries++;
- port++;
- server.listen(port, host);
- }
- }).listen(port, host, () => { on_start(port) });
+ })
+ .listen(port, host, () => {
+ on_start(port);
+ });
};
diff --git a/src/http_ffi.erl b/src/http_ffi.erl
index e9de7e7..355fabd 100644
--- a/src/http_ffi.erl
+++ b/src/http_ffi.erl
@@ -1,7 +1,7 @@
-module(http_ffi).
--export([serve/4, response_default_headers/0]).
+-export([serve/3, response_default_headers/0]).
-serve(Host, Port, OnStart, OnPortTaken) ->
+serve({options, Host, Port, IncludeStyles}, OnStart, OnPortTaken) ->
{ok, Pattern} = re:compile("name *= *\"(?<Name>.+)\""),
{ok, Toml} = file:read_file("gleam.toml"),
{match, [Name]} = re:run(Toml, Pattern, [{capture, all_names, binary}]),
@@ -13,8 +13,13 @@ serve(Host, Port, OnStart, OnPortTaken) ->
"<head>\n"
" <meta charset=\"UTF-8\">\n"
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
- " <title>Lustre preview server</title>\n"
- "\n"
+ " <title>Lustre preview server</title>\n",
+ case IncludeStyles of
+ true ->
+ <<" <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/gh/lustre-labs/ui/priv/styles.css\">\n">>;
+ false ->
+ <<"">>
+ end/binary,
" <script type=\"module\">\n"
" import { main } from './",
Name/binary,
diff --git a/src/lustre/try.gleam b/src/lustre/try.gleam
index 83d6e5a..3feef35 100644
--- a/src/lustre/try.gleam
+++ b/src/lustre/try.gleam
@@ -1,32 +1,95 @@
+// IMPORTS ---------------------------------------------------------------------
+
+import argv
+import gleam_community/ansi
import gleam/int
import gleam/io
-import gleam_community/ansi
+import glint.{CommandInput}
+import glint/flag
+
+// TYPES -----------------------------------------------------------------------
+
+type Options {
+ /// It's important to remember that for Erlang, Gleam records have their field
+ /// names erased and they degenerate to tuples. This means that the order of
+ /// the fields is important!
+ Options(host: String, port: Int, include_styles: Bool)
+}
+
+// MAIN ------------------------------------------------------------------------
pub fn main() {
- let host = "localhost"
- let port = 1234
-
- let on_start = fn(actual_port) {
- let address = "http://" <> host <> ":" <> int.to_string(actual_port)
- io.println("✨ Server has been started at " <> ansi.bold(address))
- }
-
- let on_port_taken = fn(taken_port) {
- io.println(
- "🚨 Port "
- <> ansi.bold(int.to_string(taken_port))
- <> " already in use, using next available port",
+ let args = argv.load().arguments
+ let program =
+ glint.new()
+ // There's an open issue on the glint repo to have the generated help text
+ // include the `gleam run -m ` prefix. If/until that's addressed, we can kind
+ // of hack it by telling glint the name of the program is the full command.
+ //
+ // See: https://github.com/TanklesXL/glint/issues/23
+ //
+ |> glint.with_name("gleam run -m lustre/try")
+ |> glint.with_pretty_help(glint.default_pretty_help())
+ |> glint.add(
+ at: [],
+ do: glint.command(fn(input) {
+ let CommandInput(_, flags) = input
+ let assert Ok(port) = flag.get_int(flags, "port")
+ let assert Ok(host) = flag.get_string(flags, "host")
+ let assert Ok(include_styles) = flag.get_bool(flags, "include-styles")
+ let options = Options(host, port, include_styles)
+
+ serve(options, on_start(host, _), on_port_taken)
+ })
+ |> glint.flag("host", host_flag())
+ |> glint.flag("port", port_flag())
+ |> glint.flag("include-styles", include_styles_flag()),
)
- }
- serve(host, port, on_start, on_port_taken)
+ glint.run(program, args)
+}
+
+// GLINT FLAGS -----------------------------------------------------------------
+
+fn host_flag() {
+ flag.string()
+ |> flag.default("localhost")
+ |> flag.description("The host to run the server on")
}
+fn port_flag() {
+ flag.int()
+ |> flag.default(1234)
+ |> flag.description("The port to run the server on")
+}
+
+fn include_styles_flag() {
+ flag.bool()
+ |> flag.default(False)
+ |> flag.description("Include lustre_ui's default stylesheet in your app.")
+}
+
+// UTILS -----------------------------------------------------------------------
+
+fn on_start(host: String, port: Int) -> Nil {
+ let address = "http://" <> host <> ":" <> int.to_string(port)
+ io.println("✨ Server has been started at " <> ansi.bold(address))
+}
+
+fn on_port_taken(port) -> Nil {
+ io.println(
+ "🚨 Port "
+ <> ansi.bold(int.to_string(port))
+ <> " already in use, using next available port",
+ )
+}
+
+// EXTERNALS -------------------------------------------------------------------
+
@external(erlang, "http_ffi", "serve")
@external(javascript, "../http.ffi.mjs", "serve")
fn serve(
- host: String,
- port: Int,
+ options: Options,
on_start: fn(Int) -> Nil,
on_port_taken: fn(Int) -> Nil,
) -> Nil