Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION := v0.2.0-alpha
VERSION := v1.0.0-alpha
BINARY_NAME := mend

run:
Expand Down
49 changes: 23 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,10 @@ Mend is a simple HTML template processor designed to, but not limited to be used

The produced HTML is always consistently formatted and sorted.

> [!IMPORTANT]
> Mend writes into **stdout** instead of a file. This is **not** a limitation, it's an [important advantage](https://github.com/bbfh-dev/mend/wiki#taking-advantage-of-stdout).

<!-- vim-markdown-toc GFM -->

* [📥 Installation](#-installation)
* [⚙️ Usage](#-usage)
* [📌 Developer notes](#-developer-notes)

<!-- vim-markdown-toc -->

Expand All @@ -22,35 +18,36 @@ The produced HTML is always consistently formatted and sorted.
Download the [latest release](https://github.com/bbfh-dev/mend/releases/latest) or install via the command line with:

```bash
go install github.com/bbfh-dev/mend
go install github.com/bbfh-dev/mend@latest
```

# ⚙️ Usage

Run `mend --help` to display usage information.
Check out the [Wiki](https://github.com/bbfh-dev/mend/wiki) for detailed documentation.

Run `mend --help` to display CLI usage information.

```bash
Usage:
mend <options> [html files...]
mend v1.0.0-alpha

HTML template processor designed to, but not limited to be used to generate static websites

Commands:
Usage:
mend [options] <html files...>

Options:
--help, -h Print help and exit
--version, -V Print version and exit
--input, -i <value> Set global input parameters
--indent <value> Set amount of spaces to indent with. Gets ignored if --tabs is used
--tabs, -t Use tabs instead of spaces
--decomment Strips away any comments
--help
# Print this help message
--version
# Print the program version
--tabs, -t
# Use tabs for indentation
--indent <int> (default: 4)
# The amount of spaces to be used for indentation (overwriten by --tabs)
--strip-comments
# Strips away HTML comments from the output
--input <string>
# Provide input to the provided files in the following format: 'attr1=value1,attr2.a.b.c=value2,...'
--output <string>
# (Optional) output path. Use '.' to substitute the same filename (e.g. './out/.' -> './out/input.html')
```

# 📌 Developer notes

These are some important development notes, informing about parts of the project that need to be polished out.

> **Expressions are very clunky.**
>
> 1. In code they require every node to implement its own processing while referencing a global function that handles them. Basically, it's just a big bowl of spaghetti. There's gotta be a better way of doing them.
> 1. The way expressions are parsed is very primitive, it could cause unexpected errors/behavior when using bad syntax.
>
> — [@bbfh-dev](https://github.com/bbfh-dev/)
74 changes: 45 additions & 29 deletions cli/cli.go
Original file line number Diff line number Diff line change
@@ -1,62 +1,78 @@
package cli

import (
"errors"
"os"
"path/filepath"
"strings"

"github.com/bbfh-dev/mend/mend"
"github.com/bbfh-dev/mend/mend/settings"
"github.com/bbfh-dev/mend/lang"
"github.com/bbfh-dev/mend/lang/context"
"github.com/bbfh-dev/mend/lang/printer"
)

var Options struct {
Input string `alt:"i" desc:"Set global input parameters"`
Indent int `desc:"Set amount of spaces to indent with. Gets ignored if --tabs is used"`
Tabs bool `alt:"t" desc:"Use tabs for indentation"`
StripComments bool `desc:"Strips away any comments"`
Indent int `default:"4" desc:"The amount of spaces to be used for indentation (overwriten by --tabs)"`
StripComments bool `desc:"Strips away HTML comments from the output"`
Input string `desc:"Provide input to the provided files in the following format: 'attr1=value1,attr2.a.b.c=value2,...'"`
Output string `desc:"(Optional) output path. Use '.' to substitute the same filename (e.g. './out/.' -> './out/input.html')"`
}

func Main(args []string) error {
if len(args) == 0 {
return nil
printer.StripComments = Options.StripComments
if Options.Tabs {
printer.IndentString = "\t"
} else {
printer.IndentString = strings.Repeat(" ", Options.Indent)
}

settings.KeepComments = !Options.StripComments
if Options.Tabs {
settings.IndentWith = "\t"
} else if Options.Indent != 0 {
settings.IndentWith = strings.Repeat(" ", Options.Indent)
context.GlobalContext = context.New()
if len(Options.Input) != 0 {
for prop := range strings.SplitSeq(Options.Input, ",") {
pair := strings.SplitN(prop, "=", 2)
if len(pair) != 2 {
return errors.New("input format must be: 'attr1=value1,attr2.a.b.c=value2,...'")
}
key, value := pair[0], pair[1]
context.GlobalContext.Set(strings.Split(key, "."), value)
}
}

var err error
for _, filename := range args {
filename, err = filepath.Abs(filename)
if err != nil {
return err
}
if _, err := os.Stat(filename); os.IsNotExist(err) {
return err
}
dir := filepath.Dir(filename)
base := filepath.Base(filename)
filename, _ := filepath.Abs(filename)
context.GlobalContext.Set([]string{"@file"}, filename)

file, err := os.OpenFile(filename, os.O_RDONLY, os.ModePerm)
if err != nil {
return err
}
defer file.Close()

params := "{}"
if Options.Input != "" {
params = Options.Input
template := lang.New(0, context.GlobalContext, dir, base)
if err := template.Build(file); err != nil {
return err
}
settings.GlobalParams = params

template := mend.NewTemplate(filename, params)
err = template.Parse(file)
if err != nil {
return err
out := os.Stdout
if Options.Output != "" {
if strings.HasSuffix(Options.Output, ".") {
Options.Output = strings.TrimSuffix(Options.Output, ".") + base
}

if err := os.MkdirAll(filepath.Dir(Options.Output), os.ModePerm); err != nil {
return err
}

out, err = os.OpenFile(Options.Output, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm)
if err != nil {
return err
}
}

template.Root.Render(os.Stdout, 0)
template.Root().Render(out, -1)
}

return nil
Expand Down
4 changes: 4 additions & 0 deletions example/components/button.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<a class="w-button [[ this.type ]]">
<mend:include :src="./icons/[[ this.icon ]].svg" />
[[ this.label ]]
</a>
1 change: 1 addition & 0 deletions example/components/icon.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<mend:include :src="./icons/[[ this.name ]].svg" class="type-icon" />
3 changes: 3 additions & 0 deletions example/components/icons/notification-unread.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions example/components/icons/popout.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions example/components/icons/search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions example/components/icons/smithed.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions example/components/icons/user.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
94 changes: 94 additions & 0 deletions example/components/root.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<!DOCTYPE html>
<html lang="en">

<head>
<!-- some comment -->
<meta charset="UTF-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="user-scalable=yes, initial-scale=1.0, width=device-width" />
<meta name="author" content="github.com/bbfh-dev/mend" />
<meta name="description" content="An example website" />

<title>[[ this.title ]] — Smithed™</title>

<link rel="stylesheet" href="/static/styles.min.css?checksum=[[ root.checksum ]]" />
<script src="/static/main.js"></script>
<script src="/static/page_[[ root.@file.filename().trim_extension() ]].js"></script>
</head>

<body class="theme-default">
<header>
<pkg:unmarked-link href="/" style="grid-area: logotype;">
<pkg:icon :name="smithed" :size="32" />
<h2 class="a-hide-wide">Smithed</h2>
</pkg:unmarked-link>

<pkg:unmarked-link :label="Browse" href="/browse" style="grid-area: browse;" />

<pkg:unmarked-link :label="Articles" href="" style="grid-area: articles;" />

<pkg:unmarked-link href="" style="grid-area: weld;">
Weld
<pkg:icon :name="popout" />
</pkg:unmarked-link>

<mend:if :true="[[ this.searchbar ]]">
<div class="w-input is-flex">
<pkg:icon :name="search" />
<input id="searchbar" type="text" placeholder="Search..." />
</div>
</mend:if>

<mend:if :false="[[ this.searchbar ]]">
<div class="is-flex" style="grid-area: searchbar;"></div>
</mend:if>

<pkg:unmarked-link href="" style="grid-area: weld;">
Inbox
<pkg:icon :name="notification-unread" class="%s a-hide-content a-show-tablet" />
</pkg:unmarked-link>

<pkg:button :type="accent" :label="Log in" :icon="user" style="grid-area: profile;" />
</header>
<!-- ... -->
<main>
<mend:slot />
</main>
<!-- ... -->
<footer class="--centered">
<div>
<section>
<h2>
ABOUT
</h2>
<pkg:unmarked-link :label="About Smithed" href="" />
<pkg:unmarked-link :label="How to Contribute" href="" />
<pkg:unmarked-link :label="Donate" href="" />
</section>
<section>
<h2>
COMMUNITY
</h2>
<pkg:unmarked-link :label="Discord" href="" />
<pkg:unmarked-link :label="Guidelines" href="" />
<pkg:unmarked-link :label="Wiki" href="" />
</section>
<section>
<h2>
LEGAL
</h2>
<pkg:unmarked-link :label="Terms of Use" href="" />
<pkg:unmarked-link :label="Privacy Policy" href="" />
<pkg:unmarked-link :label="Cookies" href="" />
</section>
</div>
<section>
<b>Copyright © 2018-2025 Smithed</b>
<p>
<em>Not an official Minecraft product. Not approved by or associated with Mojang Studios</em>
</p>
</section>
</footer>
</body>

</html>
5 changes: 5 additions & 0 deletions example/components/unmarked-link.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<a class="w-unmarked-link on-highlight">
<mend:slot>
[[ this.label? ]]
</mend:slot>
</a>
4 changes: 0 additions & 4 deletions example/header.html

This file was deleted.

4 changes: 0 additions & 4 deletions example/icon.svg

This file was deleted.

35 changes: 31 additions & 4 deletions example/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
<pkg:root>
<main>
[[ .title || "Hello World" ]]
</main>
{{ define "index" }}
<pkg:root :title="Home" :searchbar="true">
<pkg:heading class="%s is-center width-content">
<h1>Welcome!</h1>
<p class="is-center">
Smithed is an open-source platform for exploring, sharing <br />
and supercharging minecraft data & resource packs.
</p>
</pkg:heading>

<pkg:grid :cols="2" class="%s width-content">
<section class="o-landing-carousell">
<div id="js-carousell-1" data-i="1">
{{ range .Cards }}
{{ template "pack_card" .Trending }}
{{ end }}
</div>
</section>
<section class="o-landing-carousell">
<div id="js-carousell-2" data-i="1">
{{ range .Cards }}
{{ template "pack_card" .Newest }}
{{ end }}
</div>
</section>
</pkg:grid>

<pkg:separator class="%s width-content">
What is Smithed exactly?
</pkg:separator>
</pkg:root>
{{ end }}
3 changes: 3 additions & 0 deletions example/layout/grid.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<section class="l-grid-[[ this.cols ]]">
<mend:slot />
</section>
3 changes: 3 additions & 0 deletions example/layout/heading.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<section class="w-heading">
<mend:slot />
</section>
5 changes: 5 additions & 0 deletions example/layout/separator.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<section class="w-separator">
<hr class="type-soft" />
<mend:slot />
<hr class="type-soft" />
</section>
Loading