The fastest, period.

Signed-off-by: Izuru Yakumo <yakumo.izuru@chaotic.ninja>
このコミットが含まれているのは:
Izuru Yakumo 2023-04-08 23:39:19 -03:00
コミット ce6cb44096
31個のファイルの変更93行の追加450行の削除

2
.gitignore vendored
ファイルの表示

@ -2,6 +2,6 @@
*.bak
**.pub
/zs
/aya
/dist
/test.md

ファイルの表示

@ -1,25 +0,0 @@
---
builds:
-
id: zs
binary: zs
main: .
flags: -tags "static_build"
ldflags: -w -X main.Version={{.Version}} -X main.Commit={{.Commit}}
env:
- CGO_ENABLED=0
goos:
- darwin
- linux
goarch:
- amd64
- arm64
signs:
- artifacts: checksum
release:
gitea:
owner: prologic
name: zs
draft: true
gitea_urls:
api: https://git.mills.io/api/v1/

ファイルの表示

@ -1,6 +1,7 @@
The MIT License (MIT)
Copyright (c) 2014 zserge
Copyright (c) 2023 Izuru Yakumo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

ファイルの表示

@ -1,13 +1,17 @@
destdir ?=
goflags ?= -v -ldflags "-w -X `go list`.Version=$(version) -X `go list`.Commit=$(commit)" -tags "static_build"
prefix ?= /usr/local
version ?= `git rev-list --count HEAD || echo "$version"`
commit ?= `git rev-parse --short HEAD || echo "$commit"`
build:
go build -v
go build ${goflags} ./cmd/aya
clean:
rm -f zs
rm -f aya
install:
install -m0755 zs ${destdir}${prefix}/bin/zs
install -m0644 zs.1 ${destdir}${prefix}/share/man/man1/zs.1
install -Dm0755 aya ${destdir}${prefix}/bin/aya
install -Dm0644 aya.1 ${destdir}${prefix}/share/man/man1/aya.1
uninstall:
rm -f ${prefix}/bin/zs
rm -f ${prefix}/share/man/man1/zs.1
rm -f ${prefix}/bin/aya
rm -f ${prefix}/share/man/man1/aya.1

ファイルの表示

@ -1,10 +1,8 @@
# zs
# aya
zs is an extremely minimal static site generator written in Go.
aya is an extremely minimal static site generator written in Go.
It's inspired by `zas` generator, but is even more minimal.
The name stands for 'zen static' as well as it's my initials.
This crow tengu stands for 'the fastest one in Gensokyo' and yes this is also a Touhou Project reference.
## Features
@ -17,9 +15,9 @@ The name stands for 'zen static' as well as it's my initials.
## Installation
Download the binaries from Github or build it manually:
Build it manually assuming you have Go installed:
$ go get git.mills.io/prologic/zs
$ go install marisa.chaotic.ninja/aya@latest
## Ideology
@ -27,7 +25,7 @@ Keep your texts in markdown, or HTML format right in the main directory
of your blog/site.
Keep all service files (extensions, layout pages, deployment scripts etc)
in the `.zs` subdirectory.
in the `.aya` subdirectory.
Define variables in the header of the content files using [YAML]:
@ -40,18 +38,18 @@ Define variables in the header of the content files using [YAML]:
Use placeholders for variables and plugins in your markdown or html
files, e.g. `{{ title }}` or `{{ command arg1 arg2 }}.
Write extensions in any language you like and put them into the `.zs`
Write extensions in any language you like and put them into the `.aya`
subdiretory.
Everything the extensions prints to stdout becomes the value of the
placeholder.
Every variable from the content header will be passed via environment variables like `title` becomes `$ZS_TITLE` and so on. There are some special variables:
Every variable from the content header will be passed via environment variables like `title` becomes `$AYA_TITLE` and so on. There are some special variables:
* `$ZS` - a path to the `zs` executable
* `$ZS_OUTDIR` - a path to the directory with generated files
* `$ZS_FILE` - a path to the currently processed markdown file
* `$ZS_URL` - a URL for the currently generated page
* `$AYA` - a path to the `aya` executable
* `$AYA_OUTDIR` - a path to the directory with generated files
* `$AYA_FILE` - a path to the currently processed markdown file
* `$AYA_URL` - a URL for the currently generated page
## Example of RSS generation
@ -59,19 +57,19 @@ Extensions can be written in any language you know (Bash, Python, Lua, JavaScrip
``` bash
for f in ./blog/*.md ; do
d=$($ZS var $f date)
d=$($AYA var $f date)
if [ ! -z $d ] ; then
timestamp=`date --date "$d" +%s`
url=`$ZS var $f url`
title=`$ZS var $f title | tr A-Z a-z`
descr=`$ZS var $f description`
url=`$AYA var $f url`
title=`$AYA var $f title | tr A-Z a-z`
descr=`$AYA var $f description`
echo $timestamp \
"<item>" \
"<title>$title</title>" \
"<link>http://zserge.com/$url</link>" \
"<link>http://ayaerge.com/$url</link>" \
"<description>$descr</description>" \
"<pubDate>$(date --date @$timestamp -R)</pubDate>" \
"<guid>http://zserge.com/$url</guid>" \
"<guid>http://ayaerge.com/$url</guid>" \
"</item>"
fi
done | sort -r -n | cut -d' ' -f2-
@ -83,21 +81,21 @@ There are two special plugin names that are executed every time the build
happens - `prehook` and `posthook`. You can define some global actions here like
content generation, or additional commands, like LESS to CSS conversion:
# .zs/post
# .aya/post
#!/bin/sh
lessc < $ZS_OUTDIR/styles.less > $ZS_OUTDIR/styles.css
rm -f $ZS_OUTDIR/styles.css
lessc < $AYA_OUTDIR/styles.less > $AYA_OUTDIR/styles.css
rm -f $AYA_OUTDIR/styles.css
## Command line usage
`zs build` re-builds your site.
`aya build` re-builds your site.
`zs build <file>` re-builds one file and prints resulting content to stdout.
`aya build <file>` re-builds one file and prints resulting content to stdout.
`zs watch` rebuilds your site every time you modify any file.
`aya watch` rebuilds your site every time you modify any file.
`zs var <filename> [var1 var2...]` prints a list of variables defined in the
`aya var <filename> [var1 var2...]` prints a list of variables defined in the
header of a given markdown file, or the values of certain variables (even if
it's an empty string).

37
aya.1 ノーマルファイル
ファイルの表示

@ -0,0 +1,37 @@
.Dd $Mdocdate$
.Dt AYA 1
.Os
.Sh NAME
.Nm aya
.Nd A really fast static site generator
.Sh DESCRIPTION
Does it need one?
.Sh FEATURES
.Bl -tag -width 11n -compact
.It Zero configuration (no configuration file needed)
.It Cross-platform
.It Highly extensible
.It Works well for blogs and generic static websites (landing pages etc)
.It Easy to learn
.It Fast (of course)
.El
.Sh USAGE
.Ss (Re-)build your site.
.Nm
.Cm build
.Ss (Re-)build one file and prints resulting content to standard output.
.Nm
.Cm build
.Ar <file>
.Ss (Re-)build your site every time you modify any file.
.Nm
.Cm watch
.Ss Print a list of variables defined in the header of a given markdown file.
.Nm
.Cm var
.Ar <file>
.Ar <var1> <var2> ...
.Sh AUTHORS
.Nm
is maintained by Izuru Yakumo
.Aq Lk https://pub.chaotic.ninja/~yakumo_izuru/

ファイルの表示

@ -1,72 +0,0 @@
package main
import (
"crypto/md5"
"encoding/hex"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
)
const TESTDIR = ".test"
func TestBuild(t *testing.T) {
files, _ := ioutil.ReadDir("testdata")
for _, f := range files {
if f.IsDir() {
testBuild(filepath.Join("testdata", f.Name()), t)
}
}
}
func testBuild(path string, t *testing.T) {
wd, _ := os.Getwd()
os.Chdir(path)
args := os.Args[:]
os.Args = []string{"zs", "build"}
t.Log("--- BUILD", path)
main()
compare(PUBDIR, TESTDIR, t)
os.Chdir(wd)
os.Args = args
}
func compare(pub, test string, t *testing.T) {
a := md5dir(pub)
b := md5dir(test)
for k, v := range a {
if s, ok := b[k]; !ok {
t.Error("Unexpected file:", k, v)
} else if s != v {
t.Error("Different file:", k, v, s)
} else {
t.Log("Matching file", k, v)
}
}
for k, v := range b {
if _, ok := a[k]; !ok {
t.Error("Missing file:", k, v)
}
}
}
func md5dir(path string) map[string]string {
files := map[string]string{}
filepath.Walk(path, func(s string, info os.FileInfo, err error) error {
if err == nil && !info.IsDir() {
if f, err := os.Open(s); err == nil {
defer f.Close()
hash := md5.New()
io.Copy(hash, f)
files[strings.TrimPrefix(s, path)] = hex.EncodeToString(hash.Sum(nil))
}
}
return nil
})
return files
}

ファイルの表示

@ -15,10 +15,11 @@ import (
"github.com/russross/blackfriday/v2"
"gopkg.in/yaml.v2"
"marisa.chaotic.ninja/aya"
)
const (
ZSDIR = ".zs"
AYADIR = ".aya"
PUBDIR = ".pub"
)
@ -38,12 +39,12 @@ func renameExt(path, oldext, newext string) string {
}
// globals returns list of global OS environment variables that start
// with ZS_ prefix as Vars, so the values can be used inside templates
// with AYA_ prefix as Vars, so the values can be used inside templates
func globals() Vars {
vars := Vars{}
for _, e := range os.Environ() {
pair := strings.Split(e, "=")
if strings.HasPrefix(pair[0], "ZS_") {
if strings.HasPrefix(pair[0], "AYA_") {
vars[strings.ToLower(pair[0][3:])] = pair[1]
}
}
@ -51,21 +52,21 @@ func globals() Vars {
}
// run executes a command or a script. Vars define the command environment,
// each zs var is converted into OS environemnt variable with ZS_ prefix
// prepended. Additional variable $ZS contains path to the zs binary. Command
// stderr is printed to zs stderr, command output is returned as a string.
// each aya var is converted into OS environemnt variable with AYA_ prefix
// prepended. Additional variable $AYA contains path to the aya binary. Command
// stderr is printed to aya stderr, command output is returned as a string.
func run(vars Vars, cmd string, args ...string) (string, error) {
// First check if partial exists (.html)
if b, err := ioutil.ReadFile(filepath.Join(ZSDIR, cmd+".html")); err == nil {
if b, err := ioutil.ReadFile(filepath.Join(AYADIR, cmd+".html")); err == nil {
return string(b), nil
}
var errbuf, outbuf bytes.Buffer
c := exec.Command(cmd, args...)
env := []string{"ZS=" + os.Args[0], "ZS_OUTDIR=" + PUBDIR}
env := []string{"AYA=" + os.Args[0], "AYA_OUTDIR=" + PUBDIR}
env = append(env, os.Environ()...)
for k, v := range vars {
env = append(env, "ZS_"+strings.ToUpper(k)+"="+v)
env = append(env, "AYA_"+strings.ToUpper(k)+"="+v)
}
c.Env = env
c.Stdout = &outbuf
@ -136,7 +137,7 @@ func getVars(path string, globals Vars) (Vars, string, error) {
}
}
// Render expanding zs plugins and variables
// Render expanding aya plugins and variables
func render(s string, vars Vars) (string, error) {
delim_open := "{{"
delim_close := "}}"
@ -193,7 +194,7 @@ func buildMarkdown(path string, w io.Writer, vars Vars) error {
defer out.Close()
w = out
}
return buildHTML(filepath.Join(ZSDIR, v["layout"]), w, v)
return buildHTML(filepath.Join(AYADIR, v["layout"]), w, v)
}
// Renders text file expanding all variable macros inside it
@ -296,9 +297,9 @@ func buildAll(watch bool) {
}
func init() {
// prepend .zs to $PATH, so plugins will be found before OS commands
// prepend .aya to $PATH, so plugins will be found before OS commands
p := os.Getenv("PATH")
p = ZSDIR + ":" + p
p = AYADIR + ":" + p
os.Setenv("PATH", p)
}
@ -342,6 +343,9 @@ func main() {
}
fmt.Println(strings.TrimSpace(s))
}
case "version":
fmt.Printf("%v\n", aya.Version)
os.Exit(0)
default:
if s, err := run(globals(), cmd, args...); err != nil {
fmt.Println(err)

2
go.mod
ファイルの表示

@ -1,4 +1,4 @@
module git.mills.io/prologic/zs
module marisa.chaotic.ninja/aya
go 1.17

ファイルの表示

@ -1,108 +0,0 @@
package main
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestRenameExt(t *testing.T) {
if s := renameExt("foo.amber", ".amber", ".html"); s != "foo.html" {
t.Error(s)
}
if s := renameExt("foo.amber", "", ".html"); s != "foo.html" {
t.Error(s)
}
if s := renameExt("foo.amber", ".md", ".html"); s != "foo.amber" {
t.Error(s)
}
if s := renameExt("foo", ".amber", ".html"); s != "foo" {
t.Error(s)
}
if s := renameExt("foo", "", ".html"); s != "foo.html" {
t.Error(s)
}
}
func TestRun(t *testing.T) {
// external command
if s, err := run(Vars{}, "echo", "hello"); err != nil || s != "hello\n" {
t.Error(s, err)
}
// passing variables to plugins
if s, err := run(Vars{"foo": "bar"}, "sh", "-c", "echo $ZS_FOO"); err != nil || s != "bar\n" {
t.Error(s, err)
}
// custom plugin overriding external command
os.Mkdir(ZSDIR, 0755)
script := `#!/bin/sh
echo foo
`
ioutil.WriteFile(filepath.Join(ZSDIR, "echo"), []byte(script), 0755)
if s, err := run(Vars{}, "echo", "hello"); err != nil || s != "foo\n" {
t.Error(s, err)
}
os.Remove(filepath.Join(ZSDIR, "echo"))
os.Remove(ZSDIR)
}
func TestVars(t *testing.T) {
tests := map[string]Vars{
`
foo: bar
title: Hello, world!
---
Some content in markdown
`: {
"foo": "bar",
"title": "Hello, world!",
"url": "test.html",
"file": "test.md",
"output": filepath.Join(PUBDIR, "test.html"),
"__content": "Some content in markdown\n",
},
`
url: "example.com/foo.html"
---
Hello
`: {
"url": "example.com/foo.html",
"__content": "Hello\n",
},
}
for script, vars := range tests {
ioutil.WriteFile("test.md", []byte(script), 0644)
if v, s, err := getVars("test.md", Vars{"baz": "123"}); err != nil {
t.Error(err)
} else if s != vars["__content"] {
t.Error(s, vars["__content"])
} else {
for key, value := range vars {
if key != "__content" && v[key] != value {
t.Error(key, v[key], value)
}
}
}
}
}
func TestRender(t *testing.T) {
vars := map[string]string{"foo": "bar"}
if s, _ := render("foo bar", vars); s != "foo bar" {
t.Error(s)
}
if s, _ := render("a {{printf short}} text", vars); s != "a short text" {
t.Error(s)
}
if s, _ := render("{{printf Hello}} x{{foo}}z", vars); s != "Hello xbarz" {
t.Error(s)
}
// Test error case
if _, err := render("a {{greet text ", vars); err == nil {
t.Error("error expected")
}
}

ファイルの表示

@ -1,10 +0,0 @@
<html>
<head>
<title>About myself</title>
<link href="styles.css" rel="stylesheet" type="text/css" />
</head>
<body><h1>About myself</h1>
<p>Hi all. This is a brief description of who I am.</p>
</body>
</html>

ファイルの表示

@ -1,17 +0,0 @@
<html>
<head>
<title>My blog</title>
<link href="styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
<p>Here goes list of posts</p>
<ul>
<li>
<a href="/posts/hello.html">First post</a>
</li>
<li>
<a href="/posts/update.html">Second post</a>
</li>
</ul>
</body>
</html>

ファイルの表示

@ -1,10 +0,0 @@
<html>
<head>
<title>First post</title>
<link href="styles.css" rel="stylesheet" type="text/css" />
</head>
<body><h1>First post</h1>
<p>This is my first post</p>
</body>
</html>

ファイルの表示

@ -1,10 +0,0 @@
<html>
<head>
<title>Second post</title>
<link href="styles.css" rel="stylesheet" type="text/css" />
</head>
<body><h1>Second post</h1>
<p>This is my second post</p>
</body>
</html>

ファイルの表示

@ -1 +0,0 @@
html{margin:0;padding:0;box-sizing:border-box;}body{font-size:16pt;}

ファイルの表示

@ -1,6 +0,0 @@
html
head
title #{title}
link[href="styles.css"][rel="stylesheet"][type="text/css"]
body
#{unescaped(content)}

ファイルの表示

@ -1,7 +0,0 @@
title: About myself
date: 28-08-2015
---
# {{title}}
Hi all. This is a brief description of who I am.

ファイルの表示

@ -1,12 +0,0 @@
html
head
title My blog
link[href="styles.css"][rel="stylesheet"][type="text/css"]
body
p Here goes list of posts
ul
li
a[href="/posts/hello.html"] First post
li
a[href="/posts/update.html"] Second post

ファイルの表示

@ -1,8 +0,0 @@
title: First post
date: 28-08-2015
---
# {{title}}
This is my first post

ファイルの表示

@ -1,8 +0,0 @@
title: Second post
date: 29-08-2015
---
# {{title}}
This is my second post

ファイルの表示

@ -1,7 +0,0 @@
html
margin: 0
padding: 0
box-sizing: border-box
body
font-size: 16pt

ファイルの表示

ファイルの表示

@ -1,5 +0,0 @@
<html>
<body>
<h1>Hello</h1>
</body>
</html>

ファイルの表示

@ -1,5 +0,0 @@
<html>
<body>
<h1>{{ printf Hello }}</h1>
</body>
</html>

ファイルの表示

@ -1,5 +0,0 @@
<html>
<body>
<p>Hello world</p>
</body>
</html>

ファイルの表示

@ -1 +0,0 @@
body{font:100% Helvetica, sans-serif;color:blue;}

ファイルの表示

@ -1,3 +0,0 @@
html
body
p Hello world

ファイルの表示

@ -1,6 +0,0 @@
$base-font: Helvetica, sans-serif
$main-color: blue
body
font: 100% $base-font
color: $main-color

ファイルの表示

@ -1,25 +0,0 @@
#!/bin/sh
# Get the highest tag number
VERSION="$(git describe --abbrev=0 --tags)"
VERSION=${VERSION:-'0.0.0'}
# Get number parts
MAJOR="${VERSION%%.*}"; VERSION="${VERSION#*.}"
MINOR="${VERSION%%.*}"; VERSION="${VERSION#*.}"
PATCH="${VERSION%%.*}"; VERSION="${VERSION#*.}"
# Increase version
PATCH=$((PATCH+1))
TAG="${1}"
if [ "${TAG}" = "" ]; then
TAG="${MAJOR}.${MINOR}.${PATCH}"
fi
echo "Releasing ${TAG} ..."
git tag -a -s -m "Release ${TAG}" "${TAG}"
git push --tags
goreleaser release --rm-dist

ファイルの表示

@ -1,4 +1,4 @@
package main
package aya
import (
"fmt"

50
zs.1
ファイルの表示

@ -1,50 +0,0 @@
.Dd January 7, 2022
.Dt ZS 1
.Os
.Sh NAME
.Nm zs
.Nd Absolutely minimal static site generator written in Go.
.Sh DESCRIPTION
.Nm
is an extremely minimal static site generator written in Go.
.Pp
It's inspired by
.Em zas
generator, but is even more minimal.
.Pp
The name stands for 'zen static'
.Sh FEATURES
.Li Zero configuration (no configuration file needed)
.Pp
.Li Cross-platform
.Pp
.Li Highly extensible
.Pp
.Li Works well for blogs and generic static websites (landing pages etc)
.Pp
.Li Easy to learn
.Pp
.Li Fast
.Sh USAGE
.Ss (Re-)build your site.
.Nm
.Cm build
.Ss (Re-)build one file and prints resulting content to standard output.
.Nm
.Cm build
.Ar <file>
.Ss (Re-)build your site every time you modify any file.
.Nm
.Cm watch
.Ss Print a list of variables defined in the header of a given markdown file.
.Nm
.Cm var
.Ar <file>
.Ar <var1> <var2> ...
.Sh AUTHORS
.Nm
is maintained by James Mills
.Aq Lk https://prologic.shortcircuit.net.au/
.Pp
This manual page was written by Nova
.Aq Lk https://tilde.cafe/~novaburst