-# Make a Site Today (Mast) Web Engine
\ No newline at end of file
+# Make a Site Today (Mast) Web Engine
+
+The Mast engine builds on Go's templating with reusable parts in the style of
+the Web Components standard. It aims to be usable for anything from static HTML
+websites to complex applications with a mix of server and client-side behavior.
+
+## Install
+
+```sh
+go get git.earlybird.gay/mast-engine@latest
+```
+
+## Usage
+
+Currently, the `examples` folder contains a site going through concepts by
+example. You can walk through these examples yourself by installing them as
+an executable:
+
+```sh
+go install git.earlybird.gay/mast-engine/cmd/run-mast-examples@latest
+run-mast-examples
+```
+
+## License
+
+> This section is not legally binding, please read the license text for
+> specifics!
+
+The Mast Web Engine is licensed under the LGPL (GNU Lesser General Public
+License). This license affects how you can run, modify, and distribute
+mast-engine. In short:
+
+- If you modify mast-engine, you have to publish your modifications under the
+ GPL or LGPL.
+- If you use mast-engine without modifying it, you're in the clear.
+- Building a website using mast-engine does not count as modifying mast-engine.
+++ /dev/null
-// Copyright (C) 2024 early (LGPL)
-package main
-
-import (
- "html/template"
- "net/http"
-
- "git.earlybird.gay/mast-engine/page"
- "git.earlybird.gay/mast-engine/part"
- "git.earlybird.gay/mast-engine/render"
-)
-
-func mkList(args ...string) template.HTML {
- out := template.HTML("<ul>")
- for _, arg := range args {
- out += template.HTML("<li>" + arg + "</li>")
- }
- out += template.HTML("</ul>")
- return out
-}
-
-var testPage = page.New("test-page", "example-page.html",
- page.Includes(testPart),
- page.OnLoad(func(data render.Data) error {
- data.Set("message", "hello from test-page!")
- return nil
- }),
- page.Pretty(" "),
-)
-var testPart = part.New("test-part", "example-part.html",
- part.Funcs(template.FuncMap{
- "mkList": mkList,
- }),
- part.OnLoad(func(data render.Data) error {
- data.Set("message", "hello from test-part!")
- return nil
- }),
-)
-
-func main() {
- http.ListenAndServe("0.0.0.0:3000", testPage)
-}
--- /dev/null
+// Copyright (C) 2024 early (LGPL)
+package ex01
+
+import (
+ "git.earlybird.gay/mast-engine/cmd/run-mast-examples/parts"
+ "git.earlybird.gay/mast-engine/page"
+)
+
+var Page = page.New("ex01", "page.html", page.Includes(parts.ExampleNav))
--- /dev/null
+<!DOCTYPE html>
+<html>
+ <header>
+ <title>Example 1</title>
+ </header>
+ <body>
+ <h1>Basic Page</h1>
+ <p>This is a static page. Hooray.</p>
+
+ <!-- Ignore me!! I'll be explained in a second! -->
+ <example-nav>
+ <a href="ex02" slot="next">Parts</a>
+ </example-nav>
+ </body>
+</html>
\ No newline at end of file
--- /dev/null
+// Copyright (C) 2024 early (LGPL)
+package ex02
+
+import (
+ "git.earlybird.gay/mast-engine/cmd/run-mast-examples/parts"
+ "git.earlybird.gay/mast-engine/page"
+ "git.earlybird.gay/mast-engine/part"
+)
+
+var section = part.New("my-section", "section.html")
+
+// Use *.Includes to include a part as a dependency.
+var Page = page.New("ex02", "page.html", page.Includes(section, parts.ExampleNav))
--- /dev/null
+<!DOCTYPE html>
+<html>
+ <header>
+ <title>Example 2</title>
+ </header>
+ <body>
+ <h1>Page with Parts</h1>
+ <p>This page uses "parts" to create sections on the page. Parts are
+ HTML blocks that you can re-use.
+ </p>
+ </body>
+ <my-section>
+ <h2>Section</h2>
+ <p>This is inner HTML for a my-section "part". It replaces the slot tag
+ in the part.
+ </p>
+ </my-section>
+
+ <example-nav>
+ <a href="ex01" slot="previous">Pages</a>
+ <a href="ex03" slot="next">Slots</a>
+ </example-nav>
+</html>
\ No newline at end of file
--- /dev/null
+<template>
+ <section>
+ <!-- Slots are where the inner HTML of the part goes when rendered. -->
+ <slot></slot>
+ </section>
+</template>
\ No newline at end of file
--- /dev/null
+// Copyright (C) 2024 early (LGPL)
+package ex03
+
+import (
+ "git.earlybird.gay/mast-engine/cmd/run-mast-examples/parts"
+ "git.earlybird.gay/mast-engine/page"
+ "git.earlybird.gay/mast-engine/part"
+)
+
+var section = part.New("my-section", "section.html")
+
+// Use *.Includes to include a part as a dependency.
+var Page = page.New("ex03", "page.html", page.Includes(section, parts.ExampleNav))
--- /dev/null
+<!DOCTYPE html>
+<html>
+ <header>
+ <title>Example 3</title>
+ </header>
+ <body>
+ <h1>Page with Parts</h1>
+ <p>This page uses "parts" to create sections on the page. Parts are
+ HTML blocks that you can re-use.
+ </p>
+ </body>
+ <!-- Use named slots to choose where HTML goes in the part. -->
+ <my-section>
+ <h2 slot="header">Section with Named Slots</h2>
+ <p slot="content">Use the slot tag to fill in named slots.</p>
+ <p slot="content">You can do this with multiple HTML elements!</p>
+ </my-section>
+ <!-- Slots left empty have default HTML; see section.html for details! -->
+ <my-section/>
+ <my-section>
+ <h2 slot="header">Section with a Subsection</h2>
+ <p slot="content">You can also include parts inside parts.</p>
+ <my-section slot="content">
+ <h3 slot="header">Subsection</h3>
+ <p slot="content">This my-section is a subsection!</p>
+ </my-section>
+ </my-section>
+
+ <example-nav>
+ <a href="ex02" slot="previous">Parts</a>
+ <a href="ex04" slot="next">Go Templates</a>
+ </example-nav>
+</html>
\ No newline at end of file
--- /dev/null
+<template>
+ <section>
+ <!-- Slots are where the inner HTML of the part goes when rendered. -->
+ <slot name="header">
+ <h2>Section</h2>
+ </slot>
+ <slot name="content">
+ <p>When no HTML is provided for a slot, it uses the HTML provided
+ in the template.
+ </p>
+ </slot>
+ </section>
+</template>
\ No newline at end of file
--- /dev/null
+// Copyright (C) 2024 early (LGPL)
+package ex04
+
+import (
+ "text/template"
+
+ "git.earlybird.gay/mast-engine/cmd/run-mast-examples/parts"
+ "git.earlybird.gay/mast-engine/page"
+ "git.earlybird.gay/mast-engine/render"
+)
+
+var Page = page.New("ex04", "page.html",
+ page.OnLoad(func(data render.Data) error {
+ data.Set("message", "Hello, Hastur!")
+ return nil
+ }),
+ page.Funcs(template.FuncMap{
+ "reverse": func(s string) string {
+ out := make([]byte, len(s))
+ for i := range (len(s) + 1) / 2 {
+ j := len(s) - i - 1
+ out[i] = s[j]
+ out[j] = s[i]
+ }
+ return string(out)
+ },
+ }),
+ page.Includes(parts.ExampleNav),
+)
--- /dev/null
+<!DOCTYPE html>
+<html>
+ <header>
+ <title>Example 4</title>
+ </header>
+ <body>
+ <h1>Template Data</h1>
+ <p>Let's step back to talk about template data. Go provides templates
+ using "handlebars" notation to create server-side behavior on your
+ pages. For example, on this page, {{ `{{ .message }}` }} is
+ "{{ .message }}".
+ </p>
+ <p>You can also add functions, if the ones provided do not do everything
+ you need. On this page, {{ `{{ reverse "hello" }}`}} is
+ "{{ reverse "hello" }}".
+ </p>
+ <pre><code>var index = page.New("index", "index.html",
+ page.OnLoad(func(data render.Data) error {
+ data.Set("message", "Hello, Hastur!")
+ return nil
+ }),
+ page.Funcs(template.FuncMap{
+ "reverse": func(s string) string {
+ out := make([]byte, len(s))
+ for i := range (len(s) + 1) / 2 {
+ j := len(s) - i - 1
+ out[i] = s[j]
+ out[j] = s[i]
+ }
+ return string(out)
+ },
+ }),
+)</code></pre>
+
+ <example-nav>
+ <a href="ex03" slot="previous">Slots</a>
+ </example-nav>
+ </body>
+</html>
\ No newline at end of file
--- /dev/null
+// Copyright (C) 2024 early (LGPL)
+package ex06
+
+import (
+ "errors"
+ "strings"
+
+ "git.earlybird.gay/mast-engine/cmd/run-mast-examples/parts"
+ "git.earlybird.gay/mast-engine/page"
+ "git.earlybird.gay/mast-engine/part"
+ "git.earlybird.gay/mast-engine/render"
+)
+
+type exampleStruct struct {
+ Data string
+}
+
+var Page = page.New("ex01", "page.html",
+ page.OnLoad(func(data render.Data) error {
+ // This sets .some.really.nested.Data to "hello!".
+ // Bit of a silly example, but it shows the point.
+ data.Set("some", map[string]any{
+ "really": map[string]any{
+ "nested": exampleStruct{
+ Data: "hello, nested data!",
+ },
+ },
+ })
+ return nil
+ }),
+ page.Includes(parts.ExampleNav, messagePrinter),
+)
+var messagePrinter = part.New("message-printer", "message-printer.html",
+ part.OnLoad(func(data render.Data) error {
+ message, ok := data.Get("message").(string)
+ if !ok {
+ return errors.New("no message set")
+ }
+ data.Set("transformedMessage", strings.ToUpper(message))
+ return nil
+ }),
+)
--- /dev/null
+<template>
+ <p>{{ .message }}</p>
+ <p>{{ .transformedMessage }}</p>
+</template>
\ No newline at end of file
--- /dev/null
+<!DOCTYPE html>
+<html>
+ <header>
+ <title>Example 6</title>
+ </header>
+ <body>
+ <h1>Passing Data to Parts</h1>
+ <p>Sometimes, you may want to provide more data to a part than just the
+ HTML to include. To do this, write attributes on the part that start
+ with a colon ":".
+ </p>
+ <p>You can even do this with template fields by starting with a ".".
+ Unfortunately, full template pipelines aren't supported this way
+ right now. You can only pass a string or a value contained in the
+ parent data.
+ </p>
+
+ <message-printer :message="hello"></message-printer>
+ <message-printer :message=".some.really.nested.Data"></message-printer>
+
+ <example-nav>
+ <a href="ex02" slot="next">Parts</a>
+ </example-nav>
+ </body>
+</html>
\ No newline at end of file
--- /dev/null
+<!DOCTYPE html>
+<html>
+ <header>
+ <title>Hastur Engine Examples</title>
+ </header>
+ <body>
+ <h1>Hastur Engine Examples</h1>
+ <p>This is a set of examples for usage of the Hastur Web Engine!</p>
+ <h2>Links</h2>
+ <ol>
+ <li><a href="/ex01">Pages</a></li>
+ <li><a href="/ex02">Parts</a></li>
+ <li><a href="/ex03">Slots</a></li>
+ <li><a href="/ex04">Go Templates</a></li>
+ <li>N/A</li>
+ <li><a href="/ex06">Passing Data to Parts</a></li>
+ </ol>
+ </body>
+</html>
\ No newline at end of file
--- /dev/null
+// Copyright (C) 2024 early (LGPL)
+package main
+
+import (
+ "net/http"
+
+ "git.earlybird.gay/mast-engine/cmd/run-mast-examples/ex01-pages"
+ "git.earlybird.gay/mast-engine/cmd/run-mast-examples/ex02-parts"
+ "git.earlybird.gay/mast-engine/cmd/run-mast-examples/ex03-slots"
+ "git.earlybird.gay/mast-engine/cmd/run-mast-examples/ex04-templates"
+ "git.earlybird.gay/mast-engine/cmd/run-mast-examples/ex06-data"
+ "git.earlybird.gay/mast-engine/page"
+)
+
+var index = page.New("index", "index.html")
+
+func main() {
+ mux := new(http.ServeMux)
+ mux.Handle("GET /{$}", index)
+ mux.Handle("GET /ex01", ex01.Page)
+ mux.Handle("GET /ex02", ex02.Page)
+ mux.Handle("GET /ex03", ex03.Page)
+ mux.Handle("GET /ex04", ex04.Page)
+ mux.Handle("GET /ex06", ex06.Page)
+
+ http.ListenAndServe("0.0.0.0:3000", mux)
+}
--- /dev/null
+<template>
+ <slot name="style">
+ <style>
+ example-nav nav {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ border-radius: 5px;
+ background-color: lightgray;
+ width: fit-content;
+ }
+ example-nav > nav > span {
+ padding: 10px 2rem;
+ display: flex;
+ gap: .4rem;
+ justify-content: center;
+ }
+ </style>
+ </slot>
+ <nav>
+ <span>Previous: <slot name="previous">None</slot></span>
+ <span>Next: <slot name="next">None</slot></span>
+ <span style="grid-column: span 2;"><a href="/">Back to Overview</a></span>
+ </nav>
+</template>
\ No newline at end of file
--- /dev/null
+// Copyright (C) 2024 early (LGPL)
+package parts
+
+import "git.earlybird.gay/mast-engine/part"
+
+var ExampleNav = part.New("example-nav", "example-nav.html")
+++ /dev/null
-// Copyright (C) 2024 early (LGPL)
-package ex01
-
-import (
- "git.earlybird.gay/mast-engine/examples/parts"
- "git.earlybird.gay/mast-engine/page"
-)
-
-var Page = page.New("ex01", "page.html", page.Includes(parts.ExampleNav))
+++ /dev/null
-<!DOCTYPE html>
-<html>
- <header>
- <title>Example 1</title>
- </header>
- <body>
- <h1>Basic Page</h1>
- <p>This is a static page. Hooray.</p>
-
- <!-- Ignore me!! I'll be explained in a second! -->
- <example-nav>
- <a href="ex02" slot="next">Parts</a>
- </example-nav>
- </body>
-</html>
\ No newline at end of file
+++ /dev/null
-// Copyright (C) 2024 early (LGPL)
-package ex02
-
-import (
- "git.earlybird.gay/mast-engine/examples/parts"
- "git.earlybird.gay/mast-engine/page"
- "git.earlybird.gay/mast-engine/part"
-)
-
-var section = part.New("my-section", "section.html")
-
-// Use *.Includes to include a part as a dependency.
-var Page = page.New("ex02", "page.html", page.Includes(section, parts.ExampleNav))
+++ /dev/null
-<!DOCTYPE html>
-<html>
- <header>
- <title>Example 2</title>
- </header>
- <body>
- <h1>Page with Parts</h1>
- <p>This page uses "parts" to create sections on the page. Parts are
- HTML blocks that you can re-use.
- </p>
- </body>
- <my-section>
- <h2>Section</h2>
- <p>This is inner HTML for a my-section "part". It replaces the slot tag
- in the part.
- </p>
- </my-section>
-
- <example-nav>
- <a href="ex01" slot="previous">Pages</a>
- <a href="ex03" slot="next">Slots</a>
- </example-nav>
-</html>
\ No newline at end of file
+++ /dev/null
-<template>
- <section>
- <!-- Slots are where the inner HTML of the part goes when rendered. -->
- <slot></slot>
- </section>
-</template>
\ No newline at end of file
+++ /dev/null
-// Copyright (C) 2024 early (LGPL)
-package ex03
-
-import (
- "git.earlybird.gay/mast-engine/examples/parts"
- "git.earlybird.gay/mast-engine/page"
- "git.earlybird.gay/mast-engine/part"
-)
-
-var section = part.New("my-section", "section.html")
-
-// Use *.Includes to include a part as a dependency.
-var Page = page.New("ex03", "page.html", page.Includes(section, parts.ExampleNav))
+++ /dev/null
-<!DOCTYPE html>
-<html>
- <header>
- <title>Example 3</title>
- </header>
- <body>
- <h1>Page with Parts</h1>
- <p>This page uses "parts" to create sections on the page. Parts are
- HTML blocks that you can re-use.
- </p>
- </body>
- <!-- Use named slots to choose where HTML goes in the part. -->
- <my-section>
- <h2 slot="header">Section with Named Slots</h2>
- <p slot="content">Use the slot tag to fill in named slots.</p>
- <p slot="content">You can do this with multiple HTML elements!</p>
- </my-section>
- <!-- Slots left empty have default HTML; see section.html for details! -->
- <my-section/>
- <my-section>
- <h2 slot="header">Section with a Subsection</h2>
- <p slot="content">You can also include parts inside parts.</p>
- <my-section slot="content">
- <h3 slot="header">Subsection</h3>
- <p slot="content">This my-section is a subsection!</p>
- </my-section>
- </my-section>
-
- <example-nav>
- <a href="ex02" slot="previous">Parts</a>
- <a href="ex04" slot="next">Go Templates</a>
- </example-nav>
-</html>
\ No newline at end of file
+++ /dev/null
-<template>
- <section>
- <!-- Slots are where the inner HTML of the part goes when rendered. -->
- <slot name="header">
- <h2>Section</h2>
- </slot>
- <slot name="content">
- <p>When no HTML is provided for a slot, it uses the HTML provided
- in the template.
- </p>
- </slot>
- </section>
-</template>
\ No newline at end of file
+++ /dev/null
-// Copyright (C) 2024 early (LGPL)
-package ex04
-
-import (
- "text/template"
-
- "git.earlybird.gay/mast-engine/examples/parts"
- "git.earlybird.gay/mast-engine/page"
- "git.earlybird.gay/mast-engine/render"
-)
-
-var Page = page.New("ex04", "page.html",
- page.OnLoad(func(data render.Data) error {
- data.Set("message", "Hello, Hastur!")
- return nil
- }),
- page.Funcs(template.FuncMap{
- "reverse": func(s string) string {
- out := make([]byte, len(s))
- for i := range (len(s) + 1) / 2 {
- j := len(s) - i - 1
- out[i] = s[j]
- out[j] = s[i]
- }
- return string(out)
- },
- }),
- page.Includes(parts.ExampleNav),
-)
+++ /dev/null
-<!DOCTYPE html>
-<html>
- <header>
- <title>Example 4</title>
- </header>
- <body>
- <h1>Template Data</h1>
- <p>Let's step back to talk about template data. Go provides templates
- using "handlebars" notation to create server-side behavior on your
- pages. For example, on this page, {{ `{{ .message }}` }} is
- "{{ .message }}".
- </p>
- <p>You can also add functions, if the ones provided do not do everything
- you need. On this page, {{ `{{ reverse "hello" }}`}} is
- "{{ reverse "hello" }}".
- </p>
- <pre><code>var index = page.New("index", "index.html",
- page.OnLoad(func(data render.Data) error {
- data.Set("message", "Hello, Hastur!")
- return nil
- }),
- page.Funcs(template.FuncMap{
- "reverse": func(s string) string {
- out := make([]byte, len(s))
- for i := range (len(s) + 1) / 2 {
- j := len(s) - i - 1
- out[i] = s[j]
- out[j] = s[i]
- }
- return string(out)
- },
- }),
-)</code></pre>
-
- <example-nav>
- <a href="ex03" slot="previous">Slots</a>
- </example-nav>
- </body>
-</html>
\ No newline at end of file
+++ /dev/null
-// Copyright (C) 2024 early (LGPL)
-package ex06
-
-import (
- "errors"
- "strings"
-
- "git.earlybird.gay/mast-engine/examples/parts"
- "git.earlybird.gay/mast-engine/page"
- "git.earlybird.gay/mast-engine/part"
- "git.earlybird.gay/mast-engine/render"
-)
-
-type exampleStruct struct {
- Data string
-}
-
-var Page = page.New("ex01", "page.html",
- page.OnLoad(func(data render.Data) error {
- // This sets .some.really.nested.Data to "hello!".
- // Bit of a silly example, but it shows the point.
- data.Set("some", map[string]any{
- "really": map[string]any{
- "nested": exampleStruct{
- Data: "hello, nested data!",
- },
- },
- })
- return nil
- }),
- page.Includes(parts.ExampleNav, messagePrinter),
-)
-var messagePrinter = part.New("message-printer", "message-printer.html",
- part.OnLoad(func(data render.Data) error {
- message, ok := data.Get("message").(string)
- if !ok {
- return errors.New("no message set")
- }
- data.Set("transformedMessage", strings.ToUpper(message))
- return nil
- }),
-)
+++ /dev/null
-<template>
- <p>{{ .message }}</p>
- <p>{{ .transformedMessage }}</p>
-</template>
\ No newline at end of file
+++ /dev/null
-<!DOCTYPE html>
-<html>
- <header>
- <title>Example 6</title>
- </header>
- <body>
- <h1>Passing Data to Parts</h1>
- <p>Sometimes, you may want to provide more data to a part than just the
- HTML to include. To do this, write attributes on the part that start
- with a colon ":".
- </p>
- <p>You can even do this with template fields by starting with a ".".
- Unfortunately, full template pipelines aren't supported this way
- right now. You can only pass a string or a value contained in the
- parent data.
- </p>
-
- <message-printer :message="hello"></message-printer>
- <message-printer :message=".some.really.nested.Data"></message-printer>
-
- <example-nav>
- <a href="ex02" slot="next">Parts</a>
- </example-nav>
- </body>
-</html>
\ No newline at end of file
+++ /dev/null
-module git.earlybird.gay/mast-engine/examples
-
-go 1.22.4
-
-require git.earlybird.gay/mast-engine v0.0.0
-
-require golang.org/x/net v0.27.0 // indirect
-
-replace git.earlybird.gay/mast-engine => ..
+++ /dev/null
-golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
-golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
+++ /dev/null
-<!DOCTYPE html>
-<html>
- <header>
- <title>Hastur Engine Examples</title>
- </header>
- <body>
- <h1>Hastur Engine Examples</h1>
- <p>This is a set of examples for usage of the Hastur Web Engine!</p>
- <h2>Links</h2>
- <ol>
- <li><a href="/ex01">Pages</a></li>
- <li><a href="/ex02">Parts</a></li>
- <li><a href="/ex03">Slots</a></li>
- <li><a href="/ex04">Go Templates</a></li>
- <li>N/A</li>
- <li><a href="/ex06">Passing Data to Parts</a></li>
- </ol>
- </body>
-</html>
\ No newline at end of file
+++ /dev/null
-// Copyright (C) 2024 early (LGPL)
-package main
-
-import (
- "net/http"
-
- "git.earlybird.gay/mast-engine/examples/ex01-pages"
- "git.earlybird.gay/mast-engine/examples/ex02-parts"
- "git.earlybird.gay/mast-engine/examples/ex03-slots"
- "git.earlybird.gay/mast-engine/examples/ex04-templates"
- "git.earlybird.gay/mast-engine/examples/ex06-data"
- "git.earlybird.gay/mast-engine/page"
-)
-
-var index = page.New("index", "index.html")
-
-func main() {
- mux := new(http.ServeMux)
- mux.Handle("GET /{$}", index)
- mux.Handle("GET /ex01", ex01.Page)
- mux.Handle("GET /ex02", ex02.Page)
- mux.Handle("GET /ex03", ex03.Page)
- mux.Handle("GET /ex04", ex04.Page)
- mux.Handle("GET /ex06", ex06.Page)
-
- http.ListenAndServe("0.0.0.0:3000", mux)
-}
+++ /dev/null
-<template>
- <slot name="style">
- <style>
- example-nav nav {
- display: grid;
- grid-template-columns: 1fr 1fr;
- border-radius: 5px;
- background-color: lightgray;
- width: fit-content;
- }
- example-nav > nav > span {
- padding: 10px 2rem;
- display: flex;
- gap: .4rem;
- justify-content: center;
- }
- </style>
- </slot>
- <nav>
- <span>Previous: <slot name="previous">None</slot></span>
- <span>Next: <slot name="next">None</slot></span>
- <span style="grid-column: span 2;"><a href="/">Back to Overview</a></span>
- </nav>
-</template>
\ No newline at end of file
+++ /dev/null
-// Copyright (C) 2024 early (LGPL)
-package parts
-
-import "git.earlybird.gay/mast-engine/part"
-
-var ExampleNav = part.New("example-nav", "example-nav.html")