diff options
-rw-r--r-- | gleam.toml | 2 | ||||
-rw-r--r-- | manifest.toml | 5 | ||||
-rw-r--r-- | src/http.ffi.mjs | 85 | ||||
-rw-r--r-- | src/http_ffi.erl | 13 | ||||
-rw-r--r-- | src/lustre/try.gleam | 99 |
5 files changed, 149 insertions, 55 deletions
@@ -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 |