aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/try_gleam.gleam390
1 files changed, 390 insertions, 0 deletions
diff --git a/src/try_gleam.gleam b/src/try_gleam.gleam
new file mode 100644
index 0000000..f34f4a7
--- /dev/null
+++ b/src/try_gleam.gleam
@@ -0,0 +1,390 @@
+import gleam/io
+import gleam/list
+import htmb.{h, text}
+import gleam/string_builder
+import gleam/option.{type Option, None, Some}
+import gleam/pair
+import gleam/string
+import gleam/result
+import simplifile
+import snag
+
+const static = "static"
+
+const public = "public"
+
+const public_precompiled = "public/precompiled"
+
+const prelude = "../compiler-core/templates/prelude.mjs"
+
+const stdlib_compiled = "build/dev/javascript/gleam_stdlib/gleam"
+
+const stdlib_sources = "build/packages/gleam_stdlib/src/gleam"
+
+const stdlib_external = "build/packages/gleam_stdlib/src"
+
+const compiler_wasm = "../compiler-wasm/pkg"
+
+const lessons_src = "lessons/src"
+
+const hello_joe = "import gleam/io
+
+pub fn main() {
+ io.println(\"Hello, Joe!\")
+}
+"
+
+// Don't include deprecated stdlib modules
+const skipped_stdlib_modules = [
+ "bit_string.gleam", "bit_builder.gleam", "map.gleam",
+]
+
+pub fn main() {
+ let result = {
+ use _ <- result.try(reset_output())
+ use _ <- result.try(make_prelude_available())
+ use _ <- result.try(make_stdlib_available())
+ use _ <- result.try(copy_wasm_compiler())
+ use p <- result.try(load_pages())
+ use _ <- result.try(write_pages(p))
+ Ok(Nil)
+ }
+
+ case result {
+ Ok(_) -> Nil
+ Error(snag) -> {
+ io.println(snag.pretty_print(snag))
+ panic
+ }
+ }
+}
+
+type Page {
+ Page(
+ name: String,
+ text: String,
+ code: String,
+ path: String,
+ previous: Option(String),
+ next: Option(String),
+ )
+}
+
+fn load_pages() -> snag.Result(List(Page)) {
+ use lessons <- result.try(
+ simplifile.read_directory(lessons_src)
+ |> file_error("Failed to read lessons directory"),
+ )
+
+ let lessons =
+ lessons
+ |> list.sort(by: string.compare)
+ |> list.index_map(pair.new)
+
+ use pages <- result.try(list.try_map(lessons, fn(pair) {
+ let #(index, lesson) = pair
+ let path = lessons_src <> "/" <> lesson
+ let name =
+ lesson
+ |> string.split("_")
+ |> list.drop(1)
+ |> string.join("-")
+
+ use code <- result.try(
+ simplifile.read(path <> "/code.gleam")
+ |> file_error("Failed to read code.gleam"),
+ )
+
+ use text <- result.try(
+ simplifile.read(path <> "/text.html")
+ |> file_error("Failed to read text.html"),
+ )
+
+ let path = case index {
+ 0 -> "/"
+ _ -> "/" <> name
+ }
+
+ Ok(
+ Page(
+ name: name,
+ text: text,
+ code: code,
+ path: path,
+ previous: None,
+ next: None,
+ ),
+ )
+ }))
+
+ Ok(add_previous_next(pages, [], None))
+}
+
+fn write_pages(pages: List(Page)) -> snag.Result(Nil) {
+ use _ <- result.try(list.try_each(pages, write_page))
+
+ let render = fn(h) { string_builder.to_string(htmb.render(h)) }
+ let html =
+ string.concat([
+ render(h("h2", [], [text("Table of contents")])),
+ render(h("ul", [], list.map(pages, fn(page) {
+ h("li", [], [
+ h("a", [#("href", page.path)], [
+ page.name
+ |> string.replace("-", " ")
+ |> string.capitalise
+ |> text,
+ ]),
+ ])
+ }))),
+ ])
+
+ let page =
+ Page(
+ name: "Index",
+ text: html,
+ code: hello_joe,
+ path: "/index",
+ previous: None,
+ next: None,
+ )
+ write_page(page)
+}
+
+fn write_page(page: Page) -> snag.Result(Nil) {
+ let path = public <> page.path
+ use _ <- result.try(
+ simplifile.create_directory_all(path)
+ |> file_error("Failed to make " <> path),
+ )
+
+ let path = path <> "/index.html"
+ simplifile.write(to: path, contents: page_html(page))
+ |> file_error("Failed to write page " <> path)
+}
+
+fn add_previous_next(
+ rest: List(Page),
+ acc: List(Page),
+ previous: Option(String),
+) -> List(Page) {
+ case rest {
+ [] -> list.reverse(acc)
+ [page, next, ..rest] -> {
+ let page = Page(..page, previous: previous, next: Some(next.path))
+ add_previous_next([next, ..rest], [page, ..acc], Some(page.path))
+ }
+ [page, ..rest] -> {
+ let page = Page(..page, previous: previous, next: None)
+ add_previous_next(rest, [page, ..acc], Some(page.path))
+ }
+ }
+}
+
+fn copy_wasm_compiler() -> snag.Result(Nil) {
+ use <- require(
+ simplifile.is_directory(compiler_wasm),
+ "compiler-wasm/pkg must have been compiled",
+ )
+
+ simplifile.copy_directory(compiler_wasm, public <> "/compiler")
+ |> file_error("Failed to copy compiler-wasm")
+}
+
+fn make_prelude_available() -> snag.Result(Nil) {
+ use _ <- result.try(
+ simplifile.create_directory_all(public_precompiled)
+ |> file_error("Failed to make " <> public_precompiled),
+ )
+
+ simplifile.copy_file(prelude, public_precompiled <> "/gleam.mjs")
+ |> file_error("Failed to copy prelude.mjs")
+}
+
+fn make_stdlib_available() -> snag.Result(Nil) {
+ use files <- result.try(
+ simplifile.read_directory(stdlib_sources)
+ |> file_error("Failed to read stdlib directory"),
+ )
+
+ let modules =
+ files
+ |> list.filter(fn(file) { string.ends_with(file, ".gleam") })
+ |> list.filter(fn(file) { !list.contains(skipped_stdlib_modules, file) })
+ |> list.map(string.replace(_, ".gleam", ""))
+
+ use _ <- result.try(
+ generate_stdlib_bundle(modules)
+ |> snag.context("Failed to generate stdlib.js bundle"),
+ )
+
+ use _ <- result.try(
+ copy_compiled_stdlib(modules)
+ |> snag.context("Failed to copy precompiled stdlib modules"),
+ )
+
+ use _ <- result.try(
+ copy_stdlib_externals()
+ |> snag.context("Failed to copy stdlib external files"),
+ )
+
+ Ok(Nil)
+}
+
+fn copy_stdlib_externals() -> snag.Result(Nil) {
+ use files <- result.try(
+ simplifile.read_directory(stdlib_external)
+ |> file_error("Failed to read stdlib external directory"),
+ )
+ let files = list.filter(files, string.ends_with(_, ".mjs"))
+
+ list.try_each(files, fn(file) {
+ let from = stdlib_external <> "/" <> file
+ let to = public_precompiled <> "/" <> file
+ simplifile.copy_file(from, to)
+ |> file_error("Failed to copy stdlib external file " <> from)
+ })
+}
+
+fn copy_compiled_stdlib(modules: List(String)) -> snag.Result(Nil) {
+ use <- require(
+ simplifile.is_directory(stdlib_compiled),
+ "Project must have been compiled for JavaScript",
+ )
+
+ let dest = public_precompiled <> "/gleam"
+ use _ <- result.try(
+ simplifile.create_directory_all(dest)
+ |> file_error("Failed to make " <> dest),
+ )
+
+ use _ <- result.try(list.try_each(modules, fn(name) {
+ let from = stdlib_compiled <> "/" <> name <> ".mjs"
+ let to = dest <> "/" <> name <> ".mjs"
+ simplifile.copy_file(from, to)
+ |> file_error("Failed to copy stdlib module " <> from)
+ }))
+
+ Ok(Nil)
+}
+
+fn generate_stdlib_bundle(modules: List(String)) -> snag.Result(Nil) {
+ use entries <- result.try(list.try_map(modules, fn(name) {
+ let path = stdlib_sources <> "/" <> name <> ".gleam"
+ use code <- result.try(
+ simplifile.read(path)
+ |> file_error("Failed to read stdlib module " <> path),
+ )
+ let name = string.replace(name, ".gleam", "")
+ let code =
+ code
+ |> string.replace("\\", "\\\\")
+ |> string.replace("`", "\\`")
+ |> string.split("\n")
+ |> list.filter(fn(line) { !string.starts_with(string.trim(line), "//") })
+ |> list.filter(fn(line) { !string.starts_with(line, "@external(erlang") })
+ |> list.filter(fn(line) { line != "" })
+ |> string.join("\n")
+
+ Ok(" \"gleam/" <> name <> "\": `" <> code <> "`")
+ }))
+
+ entries
+ |> string.join(",\n")
+ |> string.append("export default {\n", _)
+ |> string.append("\n}\n")
+ |> simplifile.write(public <> "/stdlib.js", _)
+ |> file_error("Failed to write stdlib.js")
+}
+
+fn reset_output() -> snag.Result(Nil) {
+ use files <- result.try(
+ simplifile.read_directory(public)
+ |> file_error("Failed to read public directory"),
+ )
+
+ use _ <- result.try(
+ files
+ |> list.map(string.append(public <> "/", _))
+ |> simplifile.delete_all
+ |> file_error("Failed to delete public directory"),
+ )
+
+ simplifile.copy_directory(static, public)
+ |> file_error("Failed to copy static directory")
+}
+
+fn require(
+ that condition: Bool,
+ because reason: String,
+ then next: fn() -> snag.Result(t),
+) -> snag.Result(t) {
+ case condition {
+ True -> next()
+ False -> Error(snag.new(reason))
+ }
+}
+
+fn file_error(
+ result: Result(t, simplifile.FileError),
+ context: String,
+) -> snag.Result(t) {
+ case result {
+ Ok(value) -> Ok(value)
+ Error(error) ->
+ snag.error("File error: " <> string.inspect(error))
+ |> snag.context(context)
+ }
+}
+
+fn page_html(page: Page) -> String {
+ let navlink = fn(name, link) {
+ case link {
+ None -> h("span", [], [text(name)])
+ Some(path) -> h("a", [#("href", path)], [text(name)])
+ }
+ }
+
+ h("html", [#("lang", "en-gb")], [
+ h("head", [], [
+ h("meta", [#("charset", "utf-8")], []),
+ h(
+ "meta",
+ [
+ #("name", "viewport"),
+ #("content", "width=device-width, initial-scale=1"),
+ ],
+ [],
+ ),
+ h("title", [], [text("Try Gleam")]),
+ h("link", [#("rel", "stylesheet"), #("href", "/style.css")], []),
+ ]),
+ h("body", [], [
+ h("nav", [#("class", "navbar")], [
+ h("a", [#("href", "/")], [text("Try Gleam")]),
+ ]),
+ h("article", [#("class", "playground")], [
+ h("section", [#("id", "text")], [
+ htmb.dangerous_unescaped_fragment(
+ string_builder.from_string(page.text),
+ ),
+ h("nav", [#("class", "prev-next")], [
+ navlink("Back", page.previous),
+ text(" — "),
+ h("a", [#("href", "/index")], [text("Index")]),
+ text(" — "),
+ navlink("Next", page.next),
+ ]),
+ ]),
+ h("section", [#("id", "editor")], [
+ h("div", [#("id", "editor-target")], []),
+ ]),
+ h("aside", [#("id", "output")], []),
+ ]),
+ h("script", [#("type", "gleam"), #("id", "code")], [text(page.code)]),
+ h("script", [#("type", "module"), #("src", "/index.js")], []),
+ ]),
+ ])
+ |> htmb.render_page("html")
+ |> string_builder.to_string
+}