Skip to content

Commit

Permalink
update for gleam 1.6
Browse files Browse the repository at this point in the history
  • Loading branch information
aslilac committed Nov 24, 2024
1 parent dad9063 commit a31e059
Show file tree
Hide file tree
Showing 17 changed files with 278 additions and 251 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Changelog

## v1.1.0

- **Breaking change:** Nakai now uses `gleam/string_tree` from gleam_stdlib rather than `gleam/string_builder`.

## v1.0.0

- Nakai is now stable!
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
## Development

Parts of Nakai rely on code generation so that we don't have to write and update hundreds of identical functions by hand. The Gleam compiler does not provide a way for us to generate this code automatically when running things like `gleam build`, or `gleam test`. Instead, you should run `make`, `make test`, etc.

While Nakai itself is pure Gleam, and has no dependencies, the benchmarks require having Elixir installed, and building it requires that you have Rebar3 installed.
25 changes: 2 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
![Nakai](https://cdn.mckayla.cloud/-/2d8051c1ce2f4fbd91eaf07df5661e25/Nakai-Banner.svg)

[![Documentation](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/nakai/)

## Getting started

```sh
Expand All @@ -13,32 +11,13 @@ import nakai
import nakai/html.{type Node}
import nakai/attr.{type Attr}
const header_style = "
color: #331f26;
font-family: 'Neuton', serif;
font-size: 128px;
font-weight: 400;
"
pub fn header(attrs: List(Attr), text: String) -> Node {
let attrs = [attr.style(header_style), ..attrs]
let attrs = [attr.class("text-xl weight-400"), ..attrs]
html.h1_text(attrs, text)
}
pub fn app() -> String {
html.div([],
[
html.Head([html.title("Hello!")]),
header([], "Hello, from Nakai!")
]
)
header([], "Hello, from Nakai!")
|> nakai.to_string()
}
```

## Development

While Nakai itself is pure Gleam, and has no dependencies, the benchmarks require having [Elixir] installed, and building it requires that you have [Rebar3] installed.

[elixir]: https://elixir-lang.org/
[rebar3]: https://rebar3.org/
79 changes: 49 additions & 30 deletions codegen/html_prelude.gleam
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
import nakai/attr.{type Attr}

pub type Node {
/// Can be used anywhere in the document, and will set the doctype of the document
/// being rendered. Usually not necessary, as documents have a default of `<!DOCTYPE html>`.
/// Can be used anywhere in the document, and will set the doctype of the
/// document being rendered. Usually not necessary, as documents have a
/// default of `<!DOCTYPE html>`.
///
/// ## Example
/// ```gleam
/// html.Doctype("html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\"")
/// ```
Doctype(content: String)
/// Used for setting attributes on the root `<html>` element of the document. Children
/// will be rendered in-place, equivalent to using `html.Fragment(children)`.
/// Used for setting attributes on the root `<html>` element of the document.
/// Children will be rendered in-place, equivalent to using
/// `html.Fragment(children)`.
///
/// ## Example
/// ```gleam
/// html.Html([attr.lang("en-US")], [
/// ...
/// ])
/// ```
Html(attrs: List(Attr), children: List(Node))
/// Used for placing content in the `<head>` of the document. Useful for elements like
/// `<meta>`, `<title>`, `<link>`, etc.
/// Used for placing content in the `<head>` of the document. Useful for
/// elements like `<meta>`, `<title>`, `<link>`, etc.
///
/// ## Example
/// ```gleam
/// html.Fragment([
Expand All @@ -31,18 +36,21 @@ pub type Node {
/// ])
/// ```
Head(children: List(Node))
/// Used for setting attributes on the `<body>` element of the document. Children
/// will be rendered in-place, equivalent to using `html.Fragment(children)`.
/// Used for setting attributes on the `<body>` element of the document.
/// Children will be rendered in-place, equivalent to using
/// `html.Fragment(children)`.
///
/// ## Example
/// ```gleam
/// html.Body([attr.class("dark-mode")], [
/// ...
/// ])
/// ```
Body(attrs: List(Attr), children: List(Node))
/// An "transparent" container that will render it's children, but does not add anything
/// itself to the document. If you've ever used `React.Fragment` or `<>` and `</>` in
/// JSX/React, this is that.
/// An "transparent" container that will render it's children, but does not
/// add anything itself to the document. If you've ever used `React.Fragment`
/// or `<>` and `</>` in JSX/React, this is that.
///
/// ## Example
/// ```gleam
/// html.ul([], [
Expand All @@ -60,32 +68,38 @@ pub type Node {
/// // </ul>
/// ```
Fragment(children: List(Node))
/// An HTML element. You shouldn't need to reach for this very often, but it can be a
/// handy escape hatch if there isn't a shorthand function for the element type you need.
/// An HTML element. You shouldn't need to reach for this very often, but it
/// can be a handy escape hatch if there isn't a shorthand function for the
/// element type you need.
///
/// ## Example
/// ```gleam
/// // bad example, pls use `html.div`
/// html.Element("div", [], [html.Text("hello, lucy!")])
/// ```
Element(tag: String, attrs: List(Attr), children: List(Node))
/// An HTML element, but that does not have any children, and should be self closing.
/// Similarly to `Element`, you shouldn't really need this, except as an escape hatch
/// if there isn't a shorthand function for the element type you need.
/// An HTML element, but that does not have any children, and should be self
/// closing. Similarly to `Element`, you shouldn't really need this, except as
/// an escape hatch if there isn't a shorthand function for the element type
/// you need.
///
/// ## Example
/// ```gleam
/// // bad example, pls use `html.link`
/// html.LeafElement("link", [attr.rel("stylesheet"), attr.href(...)])
/// ```
LeafElement(tag: String, attrs: List(Attr))
/// An HTML comment, which will be included in the document.
///
/// ## Example
/// ```gleam
/// html.Comment("You've uncovered my secrets!")
/// // <!-- You've uncovered my secrets! -->
/// ```
Comment(content: String)
/// Some plain text to include in the document. The provided text will be escaped, to
/// make it safe to include in the document.
/// Some plain text to include in the document. The provided text will be
/// escaped, to make it safe to include in the document.
///
/// ## Example
/// ```gleam
/// html.Text("hello, lucy!")
Expand All @@ -97,9 +111,10 @@ pub type Node {
/// // <div>&lt;script&gt;alert('pwned');&lt;/script&gt;</div>
/// ```
Text(content: String)
/// The dangerous cousin of `Text`. This will render the provided text as-is, without
/// any santization. Good for things like including some HTML you just generated from
/// a Markdown file. Bad for things like `$_GET['search']`.
/// The dangerous cousin of `Text`. This will render the provided text as-is,
/// without any santization. Good for things like including some HTML you just
/// generated from a Markdown file. Bad for things like `$_GET['search']`.
///
/// ## Example
/// ```gleam
/// html.Text("hello, lucy!")
Expand All @@ -112,14 +127,17 @@ pub type Node {
/// // Oh no, we just got got! D:
/// ```
UnsafeInlineHtml(content: String)
/// Add some JavaScript to your page! When using the document renderer, scripts will always be
/// inserted at the end of the page, regardless of where in the document the `Script` node is, so
/// that your content loads first. If you're using the inline renderer, the script will just be
/// Add some JavaScript to your page! When using the document renderer,
/// scripts will always be inserted at the end of the page, regardless of
/// where in the document the `Script` node is, so that your content loads
/// first. If you're using the inline renderer, the script will just be
/// placed as is.
///
/// Nakai does not do any validation of the script content! If it contains a `</script>`, weird
/// things will happen. It's also, naturally, a script, with full access to everything, just like
/// any other script, so **do not use any untrusted input**.
/// Nakai does not do any validation of the script content! If it contains a
/// `</script>`, weird things will happen. It's also, naturally, a script,
/// with full access to everything, just like any other script, so **do not
/// use any untrusted input**.
///
/// ## Example
/// ```gleam
/// html.Script([], "alert('hello, lucy!')")
Expand All @@ -128,8 +146,8 @@ pub type Node {
/// html.Script([attr.type_("module"), attr.src(...)], "")
/// ```
Script(attrs: List(Attr), content: String)
/// Renders absolutely nothing. For when you may or may not have something to render,
/// and need a way to say "I've got nothing."
/// Renders absolutely nothing. For when you may or may not have something to
/// render, and need a way to say "I've got nothing."
/// ## Example
/// ```gleam
/// html.div([], [
Expand All @@ -141,7 +159,8 @@ pub type Node {
Nothing
}

/// The HTML [`<title>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title) element
/// The HTML [`<title>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title).
/// element
pub fn title(text: String) -> Node {
Element("title", [], [Text(text)])
}
14 changes: 7 additions & 7 deletions gleam.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name = "nakai"
version = "1.0.0"
version = "1.1.0"
licences = ["MIT"]
description = "HTML generation for Gleam, on the server or anywhere else"
repository = { type = "github", user = "nakaixo", repo = "nakai" }
repository = { type = "github", user = "aslilac", repo = "nakai" }
links = [
{ title = "Website", href = "https://nakaixo.github.io" },
{ title = "Sponsor", href = "https://github.com/sponsors/aslilac" },
Expand All @@ -12,11 +12,11 @@ internal_modules = ["nakai/internal/*"]
gleam = ">= 1.0.0"

[dependencies]
gleam_stdlib = ">= 0.36.0 and < 2.0.0"
gleam_stdlib = ">= 0.43.0 and < 2.0.0"

[dev-dependencies]
envoy = "~> 1.0"
gleam_json = "~> 0.7"
gleeunit = "~> 1.0"
glychee = "~> 0.3"
simplifile = "~> 1.0"
gleam_json = "~> 2.1"
gleeunit = "~> 1.2"
glychee = "~> 1.1"
simplifile = "~> 2.2"
28 changes: 13 additions & 15 deletions manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,22 @@
# You typically do not need to edit this file

packages = [
{ name = "benchee", version = "1.3.0", build_tools = ["mix"], requirements = ["deep_merge", "statistex", "table"], otp_app = "benchee", source = "hex", outer_checksum = "34F4294068C11B2BD2EBF2C59AAC9C7DA26FFA0068AFDF3419F1B176E16C5F81" },
{ name = "benchee", version = "1.3.1", build_tools = ["mix"], requirements = ["deep_merge", "statistex", "table"], otp_app = "benchee", source = "hex", outer_checksum = "76224C58EA1D0391C8309A8ECBFE27D71062878F59BD41A390266BF4AC1CC56D" },
{ name = "deep_merge", version = "1.0.0", build_tools = ["mix"], requirements = [], otp_app = "deep_merge", source = "hex", outer_checksum = "CE708E5F094B9CD4E8F2BE4F00D2F4250C4095BE93F8CD6D018C753894885430" },
{ name = "envoy", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "CFAACCCFC47654F7E8B75E614746ED924C65BD08B1DE21101548AC314A8B6A41" },
{ name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" },
{ name = "gleam_json", version = "0.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "CB405BD93A8828BCD870463DE29375E7B2D252D9D124C109E5B618AAC00B86FC" },
{ name = "gleam_stdlib", version = "0.38.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "663CF11861179AF415A625307447775C09404E752FF99A24E2057C835319F1BE" },
{ name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" },
{ name = "glychee", version = "0.3.0", build_tools = ["gleam"], requirements = ["benchee"], otp_app = "glychee", source = "hex", outer_checksum = "CB925EA2078B75ABAC0B363F6E12E0BD80197100863DAD97E96998BBBB25CAC8" },
{ name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" },
{ name = "envoy", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "95FD059345AA982E89A0B6E2A3BF1CF43E17A7048DCD85B5B65D3B9E4E39D359" },
{ name = "filepath", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "67A6D15FB39EEB69DD31F8C145BB5A421790581BD6AA14B33D64D5A55DBD6587" },
{ name = "gleam_json", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "0A57FB5666E695FD2BEE74C0428A98B0FC11A395D2C7B4CDF5E22C5DD32C74C6" },
{ name = "gleam_stdlib", version = "0.43.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "69EF22E78FDCA9097CBE7DF91C05B2A8B5436826D9F66680D879182C0860A747" },
{ name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" },
{ name = "glychee", version = "1.1.2", build_tools = ["gleam"], requirements = ["benchee"], otp_app = "glychee", source = "hex", outer_checksum = "41784216C213F223095BB3FC3EDDB60CC537835B2340A868EA3931193F7F3824" },
{ name = "simplifile", version = "2.2.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0DFABEF7DC7A9E2FF4BB27B108034E60C81BEBFCB7AB816B9E7E18ED4503ACD8" },
{ name = "statistex", version = "1.0.0", build_tools = ["mix"], requirements = [], otp_app = "statistex", source = "hex", outer_checksum = "FF9D8BEE7035028AB4742FF52FC80A2AA35CECE833CF5319009B52F1B5A86C27" },
{ name = "table", version = "0.1.2", build_tools = ["mix"], requirements = [], otp_app = "table", source = "hex", outer_checksum = "7E99BC7EFEF806315C7E65640724BF165C3061CDC5D854060F74468367065029" },
{ name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" },
]

[requirements]
envoy = { version = "~> 1.0" }
gleam_json = { version = "~> 0.7" }
gleam_stdlib = { version = ">= 0.36.0 and < 2.0.0" }
gleeunit = { version = "~> 1.0" }
glychee = { version = "~> 0.3" }
simplifile = { version = "~> 1.0" }
gleam_json = { version = "~> 2.1" }
gleam_stdlib = { version = ">= 0.43.0 and < 2.0.0" }
gleeunit = { version = "~> 1.2" }
glychee = { version = "~> 1.1" }
simplifile = { version = "~> 2.2" }
53 changes: 27 additions & 26 deletions src/nakai.gleam
Original file line number Diff line number Diff line change
@@ -1,40 +1,41 @@
//// Nakai has several built-in "builders" that can be used.
////
//// - A `document` builder (the recommend one) that renders a full HTML document, does a
//// little magic to dedepulicate `<head>` elements, and some other things that generally fit
//// the theme of "rendering a full, valid, HTML document". It will generate a document which
//// is configured to use UTF-8 encoding, as that is the encoding for all `String`s in Gleam.
//// - A `document` builder (the recommend one) that renders a full HTML
//// document, does anlittle magic to dedepulicate `<head>` elements, and some
//// other things that generally fit the theme of "rendering a full, valid,
//// HTML document". It will generate a document which is configured to use
//// UTF-8 encoding, as that is the encoding for all `String`s in Gleam.
////
//// - An `inline` builder that should mostly be used for snippets, and partial bits of
//// HTML that will be inlined into a full document; hence the name. It renders things
//// much more literally. For example, if you tell it to give you a `<head>` element inside a
//// `<p>`, it will. As a benefit for the trade off, it can be much faster for certain use
//// cases.
//// - An `inline` builder that should mostly be used for snippets, and partial
//// bits of HTML that will be inlined into a full document; hence the name.
//// It renders things much more literally. For example, if you tell it to
//// give you a `<head>` element inside a `<p>`, it will. As a benefit for the
//// trade off, it can be much faster for certain use cases.

import gleam/string_builder.{type StringBuilder}
import gleam/string_tree.{type StringTree}
import nakai/html.{type Node}
import nakai/internal/render

/// Renders a full HTML document from the given tree, into a `StringBuilder`.
/// Renders a full HTML document from the given tree, into a `StringTree`.
///
/// Since `String`s in Gleam are always UTF-8 encoded, Nakai will automatically set
/// `<meta charset="utf-8" />`, as it's the only option that makes sense, and will prevent
/// incorrect rendering of glyphs if you don't set it yourself.
/// Since `String`s in Gleam are always UTF-8 encoded, Nakai will automatically
/// set `<meta charset="utf-8" />`, as it's the only option that makes sense,
/// and will prevent incorrect rendering of glyphs if you don't set it yourself.
///
/// ## Examples
/// ```gleam
/// html.div_text([], "hello, lucy!")
/// |> nakai.to_string_builder()
/// |> nakai.to_string_tree()
/// ```
pub fn to_string_builder(tree: Node) -> StringBuilder {
pub fn to_string_tree(tree: Node) -> StringTree {
render.render_document(tree)
}

/// Renders a full HTML document from the given tree, into a `String`.
///
/// Since `String`s in Gleam are always UTF-8 encoded, Nakai will automatically set
/// `<meta charset="utf-8" />`, as it's the only option that makes sense, and will prevent
/// incorrect rendering of glyphs if you don't set it yourself.
/// Since `String`s in Gleam are always UTF-8 encoded, Nakai will automatically
/// set `<meta charset="utf-8" />`, as it's the only option that makes sense,
/// and will prevent incorrect rendering of glyphs if you don't set it yourself.
///
/// ## Examples
/// ```gleam
Expand All @@ -43,25 +44,25 @@ pub fn to_string_builder(tree: Node) -> StringBuilder {
/// ```
pub fn to_string(tree: Node) -> String {
render.render_document(tree)
|> string_builder.to_string()
|> string_tree.to_string()
}

/// Renders only the provided HTML, exactly as provided (disables `<head>`
/// deduplication, etc.), into a `StringBuilder`. Useful for generating snippets
/// deduplication, etc.), into a `StringTree`. Useful for generating snippets
/// instead of whole pages.
///
/// ## Examples
/// ```gleam
/// html.div_text([], "hello, lucy!")
/// |> nakai.to_inline_string_builder()
/// |> nakai.to_inline_string_tree()
/// ```
pub fn to_inline_string_builder(tree: Node) -> StringBuilder {
pub fn to_inline_string_tree(tree: Node) -> StringTree {
render.render_inline(tree)
}

/// Renders only the provided HTML, exactly as provided (disables `<head>`
/// deduplication, etc.), into a `String`. Useful for generating snippets instead
/// of whole pages.
/// deduplication, etc.), into a `String`. Useful for generating snippets
/// instead of whole pages.
///
/// ## Examples
/// ```gleam
Expand All @@ -70,5 +71,5 @@ pub fn to_inline_string_builder(tree: Node) -> StringBuilder {
/// ```
pub fn to_inline_string(tree: Node) -> String {
render.render_inline(tree)
|> string_builder.to_string()
|> string_tree.to_string()
}
Loading

0 comments on commit a31e059

Please sign in to comment.