Refactor a bunch of stuff adding zs serve, CI/CD workflows, fixing docs and license (#10)

Fixes #5
Fixes #8
Fixes #9

Co-authored-by: James Mills <1290234+prologic@users.noreply.github.com>
Reviewed-on: https://git.mills.io/prologic/zs/pulls/10
This commit is contained in:
James Mills 2023-03-12 04:13:53 +00:00
parent 0f4623afae
commit 339de0457c
12 changed files with 660 additions and 144 deletions

9
.dockerfiles/entrypoint.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/sh
[ -n "${PUID}" ] && usermod -u "${PUID}" zs
[ -n "${PGID}" ] && groupmod -g "${PGID}" zs
printf "Configuring zs...\n"
printf "Switching UID=%s and GID=%s\n" "${PUID}" "${PGID}"
exec su-exec zs:zs "$@"

81
.drone.yml Normal file
View File

@ -0,0 +1,81 @@
---
kind: pipeline
type: exec
name: 🚀 CI
platform:
os: linux
arch: amd64
steps:
- name: 🛠️ Build
commands:
- make build
- name: 🧪 Test
commands:
- make test
trigger:
branch:
- main
event:
- tag
- push
- pull_request
---
kind: pipeline
name: 🐳 Docker
steps:
- name: 📦 Image
image: plugins/kaniko
settings:
repo: prologic/saltyim
tags: latest
build_args:
- VERSION=latest
- COMMIT=${DRONE_COMMIT_SHA:0:8}
username:
from_secret: dockerhub_username
password:
from_secret: dockerhub_password
when:
branch:
- main
event:
- push
depends_on:
- 🚀 CI
trigger:
branch:
- main
event:
- push
---
kind: pipeline
name: 🥳 Done
steps:
- name: 🔔 Notify
image: plugins/webhook
settings:
urls:
- https://msgbus.mills.io/ci.mills.io
depends_on:
- 🚀 CI
- 🐳 Docker
trigger:
branch:
- main
event:
- tag
- push
- pull_request

2
.gitignore vendored
View File

@ -4,4 +4,4 @@
/zs /zs
/dist /dist
/test.md **/.DS_Store

60
Dockerfile Normal file
View File

@ -0,0 +1,60 @@
# Build
FROM golang:alpine AS build
RUN apk add --no-cache -U build-base git
RUN mkdir -p /src
WORKDIR /src
# Copy Makefile
COPY Makefile ./
# Install deps
RUN make deps
# Copy go.mod and go.sum and install and cache dependencies
COPY go.mod .
COPY go.sum .
# Download dependencies
RUN go mod download
# Copy sources
COPY *.go ./
# Version/Commit (there there is no .git in Docker build context)
# NOTE: This is fairly low down in the Dockerfile instructions so
# we don't break the Docker build cache just be changing
# unrelated files that actually haven't changed but caused the
# COMMIT value to change.
ARG VERSION="0.0.0"
ARG COMMIT="HEAD"
ARG BUILD=""
# Build cli binary
RUN make cli VERSION=$VERSION COMMIT=$COMMIT BUILD=$BUILD
# Runtime
FROM alpine:latest
RUN apk --no-cache -U add su-exec shadow
ENV PUID=1000
ENV PGID=1000
RUN addgroup -g "${PGID}" zs && \
adduser -D -H -G zs -h /var/empty -u "${PUID}" zs && \
mkdir -p /data && chown -R zs:zs /data
VOLUME /data
WORKDIR /
COPY --from=build /src/zs /usr/local/bin/zs
COPY .dockerfiles/entrypoint.sh /init
ENTRYPOINT ["/init"]
CMD ["zs""]

36
LICENSE
View File

@ -1,22 +1,22 @@
The MIT License (MIT) Copyright (C) 2021-present James Mills
Copyright (c) 2014 zserge zs is covered by the MIT license::
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person
of this software and associated documentation files (the "Software"), to deal obtaining a copy of this software and associated documentation
in the Software without restriction, including without limitation the rights files (the "Software"), to deal in the Software without restriction,
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell including without limitation the rights to use, copy, modify, merge,
copies of the Software, and to permit persons to whom the Software is publish, distribute, sublicense, and/or sell copies of the Software,
furnished to do so, subject to the following conditions: and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included
copies or substantial portions of the Software. in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

22
LICENSE.old Normal file
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2014 zserge
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

103
Makefile
View File

@ -1,13 +1,92 @@
destdir ?= -include environ.inc
prefix ?= /usr/local .PHONY: help deps dev build install image release test clean
build: export CGO_ENABLED=0
go build -v VERSION=$(shell git describe --abbrev=0 --tags 2>/dev/null || echo "$VERSION")
clean: COMMIT=$(shell git rev-parse --short HEAD || echo "$COMMIT")
rm -f zs BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
install: BUILD=$(shell git show -s --pretty=format:%cI)
install -m0755 zs ${destdir}${prefix}/bin/zs GOCMD=go
install -m0644 zs.1 ${destdir}${prefix}/share/man/man1/zs.1
uninstall: DESTDIR=/usr/local/bin
rm -f ${prefix}/bin/zs
rm -f ${prefix}/share/man/man1/zs.1 ifeq ($(LOCAL), 1)
IMAGE := r.mills.io/prologic/zs
TAG := dev
else
IMAGE := prologic/zs
TAG := latest
endif
all: help
help: ## Show this help message
@echo "zs - Zen Static site generator"
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[$$()% a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
preflight: ## Run preflight checks to ensure you have the right build tools
@./preflight.sh
deps: ## Install any required dependencies
dev : DEBUG=1
dev : build ## Build debug version of zs (cli)
@./zs -v
cli: ## Build the zs command-line tool
ifeq ($(DEBUG), 1)
@echo "Building in debug mode..."
@$(GOCMD) build -tags "netgo static_build" -installsuffix netgo \
-ldflags "\
-X $(shell go list).Version=$(VERSION) \
-X $(shell go list).Commit=$(COMMIT) \
-X $(shell go list).Build=$(BUILD)" \
.
else
@$(GOCMD) build -tags "netgo static_build" -installsuffix netgo \
-ldflags "-w \
-X $(shell go list).Version=$(VERSION) \
-X $(shell go list).Commit=$(COMMIT) \
-X $(shell go list).Build=$(BUILD)" \
.
endif
build: cli ## Build the cli
install: build ## Install zs (cli) to $DESTDIR
@install -D -m 755 zs $(DESTDIR)/zs
ifeq ($(PUBLISH), 1)
image: generate ## Build the Docker image
@docker buildx build \
--build-arg VERSION="$(VERSION)" \
--build-arg COMMIT="$(COMMIT)" \
--build-arg BUILD="$(BUILD)" \
--platform linux/amd64,linux/arm64 --push -t $(IMAGE):$(TAG) .
else
image: generate
@docker build \
--build-arg VERSION="$(VERSION)" \
--build-arg COMMIT="$(COMMIT)" \
--build-arg BUILD="$(BUILD)" \
-t $(IMAGE):$(TAG) .
endif
release: generate ## Release a new version to Gitea
@./tools/release.sh
fmt: ## Format sources files
@$(GOCMD) fmt ./...
test: ## Run test suite
@CGO_ENABLED=1 $(GOCMD) test -v -cover -race ./...
coverage: ## Get test coverage report
@CGO_ENABLED=1 $(GOCMD) test -v -cover -race -cover -coverprofile=coverage.out ./...
@$(GOCMD) tool cover -html=coverage.out
clean: ## Remove untracked files
@git clean -f -d -x
clean-all: ## Remove untracked and Git ignored files
@git clean -f -d -X

View File

@ -1,10 +1,8 @@
# zs # zs - Zen Static site generator
zs is an extremely minimal static site generator written in Go. zs is an extremely minimal static site generator written in Go.
It's inspired by `zas` generator, but is even more minimal. [![Build Status](https://ci.mills.io/api/badges/prologic/zs/status.svg)](https://ci.mills.io/prologic/zs)
The name stands for 'zen static' as well as it's my initials.
## Features ## Features
@ -17,9 +15,19 @@ The name stands for 'zen static' as well as it's my initials.
## Installation ## Installation
Download the binaries from Github or build it manually: Download the binaries from [go.mills.io/prologic/zs](https://git.mills.io/prologic/zs):
$ go get git.mills.io/prologic/zs ```console
go get go.mills.io/zs@latest
```
Or build from source manaully:
```console
git clone https://git.mills.io/prologic/zs
cd zs
make install
```
## Ideology ## Ideology
@ -29,13 +37,16 @@ of your blog/site.
Keep all service files (extensions, layout pages, deployment scripts etc) Keep all service files (extensions, layout pages, deployment scripts etc)
in the `.zs` subdirectory. in the `.zs` subdirectory.
Define variables in the header of the content files using [YAML]: Define variables in the header of the content files using [YAML front matter](https://assemble.io/docs/YAML-front-matter.html):
title: My web site ```markdown
keywords: best website, hello, world ---
--- title: My web site
keywords: best website, hello, world
---
Markdown text goes after a header *separator* Markdown text goes after a header *separator*
```
Use placeholders for variables and plugins in your markdown or html Use placeholders for variables and plugins in your markdown or html
files, e.g. `{{ title }}` or `{{ command arg1 arg2 }}. files, e.g. `{{ title }}` or `{{ command arg1 arg2 }}.
@ -48,16 +59,16 @@ 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 `$ZS_TITLE` and so on. There are some special variables:
* `$ZS` - a path to the `zs` executable - `$ZS` - a path to the `zs` executable
* `$ZS_OUTDIR` - a path to the directory with generated files - `$ZS_OUTDIR` - a path to the directory with generated files
* `$ZS_FILE` - a path to the currently processed markdown file - `$ZS_FILE` - a path to the currently processed markdown file
* `$ZS_URL` - a URL for the currently generated page - `$ZS_URL` - a URL for the currently generated page
## Example of RSS generation ## Example of RSS generation
Extensions can be written in any language you know (Bash, Python, Lua, JavaScript, Go, even Assembler). Here's an example of how to scan all markdown blog posts and create RSS items: Extensions can be written in any language you know (Bash, Python, Lua, JavaScript, Go, even Assembler). Here's an example of how to scan all markdown blog posts and create RSS items:
``` bash ```bash
for f in ./blog/*.md ; do for f in ./blog/*.md ; do
d=$($ZS var $f date) d=$($ZS var $f date)
if [ ! -z $d ] ; then if [ ! -z $d ] ; then
@ -81,26 +92,29 @@ done | sort -r -n | cut -d' ' -f2-
There are two special plugin names that are executed every time the build 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 happens - `prehook` and `posthook`. You can define some global actions here like
content generation, or additional commands, like LESS to CSS conversion: content generation, or additional commands, like to minify CSS or Javascript files.
# .zs/post ```bash
#!/bin/sh
#!/bin/sh set -e
lessc < $ZS_OUTDIR/styles.less > $ZS_OUTDIR/styles.css
rm -f $ZS_OUTDIR/styles.css minify -o "$ZS_OUTDIR/css/fa.min.css" "$ZS_OUTDIR/css/fa.css"
minify -o "$ZS_OUTDIR/css/site.min.css" "$ZS_OUTDIR/css/site.css"
rm -rf "$ZS_OUTDIR/css/fa.css"
rm -rf "$ZS_OUTDIR/css/screen.css"
```
## Command line usage ## Command line usage
`zs build` re-builds your site. - `zs build` re-builds your site.
- `zs build <file>` re-builds one file and prints resulting content to stdout.
`zs build <file>` re-builds one file and prints resulting content to stdout. - `zs watch` rebuilds your site every time you modify any file.
- `zs var <filename> [var1 var2...]` prints a list of variables defined in the
`zs watch` rebuilds your site every time you modify any file.
`zs 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 header of a given markdown file, or the values of certain variables (even if
it's an empty string). it's an empty string).
## License ## License
The software is distributed under the MIT license. `zs` is licensed under the terms of the [MIT License](/LICENSE) and was orignally forked from [zserge/zs](https://github.com/zserge/zs) also licensed under the terms of the [MIT License](/LICENSE.old).

12
go.mod
View File

@ -1,8 +1,18 @@
module git.mills.io/prologic/zs module go.mills.io/zs
go 1.17 go 1.17
require ( require (
github.com/russross/blackfriday/v2 v2.1.0 github.com/russross/blackfriday/v2 v2.1.0
go.mills.io/static v0.0.0-20230312034046-6dff09caed3b
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
) )
require (
github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/julienschmidt/httprouter v1.3.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/unrolled/logger v0.0.0-20201216141554-31a3694fe979 // indirect
golang.org/x/sys v0.0.0-20221010170243-090e33056c14 // indirect
)

27
go.sum
View File

@ -1,6 +1,33 @@
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/unrolled/logger v0.0.0-20201216141554-31a3694fe979 h1:47+K4wN0S8L3fUwgZtPEBIfNqtAE3tUvBfvHVZJAXfg=
github.com/unrolled/logger v0.0.0-20201216141554-31a3694fe979/go.mod h1:X5DBNY1yIVkuLwJP3BXlCoQCa5mGg7hPJPIA0Blwc44=
go.mills.io/static v0.0.0-20230312034046-6dff09caed3b h1:9mSSHQJztO83b4939B31Z8bCOlvQUei6bRhnJq8eRC0=
go.mills.io/static v0.0.0-20230312034046-6dff09caed3b/go.mod h1:TmaEDwM+IgrCRyMxtVWtmSdoxLP3N6ehBa7AiOZj2Mk=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14 h1:k5II8e6QD8mITdi+okbbmR/cIyEbeXLBhy5Ha4nevyc=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

121
main.go
View File

@ -1,29 +1,53 @@
// Package main is a command-lint tool `zs` called Zen Static for generating static websites
package main package main
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"os/exec" "os/exec"
"os/signal"
"path/filepath" "path/filepath"
"strings" "strings"
"syscall"
"text/template" "text/template"
"time" "time"
"github.com/russross/blackfriday/v2" "github.com/russross/blackfriday/v2"
"go.mills.io/static"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
const ( const (
// ZSDIR is the default directory for storing layouts and extensions
ZSDIR = ".zs" ZSDIR = ".zs"
// PUBDIR is the default directory for publishing final built content
PUBDIR = ".pub" PUBDIR = ".pub"
) )
// Vars holds a map of global variables
type Vars map[string]string type Vars map[string]string
// NewTicker is a function that wraps a time.Ticker and ticks immediately instead of waiting for the first interval
func NewTicker(d time.Duration) *time.Ticker {
ticker := time.NewTicker(d)
oc := ticker.C
nc := make(chan time.Time, 1)
go func() {
nc <- time.Now()
for tm := range oc {
nc <- tm
}
}()
ticker.C = nc
return ticker
}
// renameExt renames extension (if any) from oldext to newext // renameExt renames extension (if any) from oldext to newext
// If oldext is an empty string - extension is extracted automatically. // If oldext is an empty string - extension is extracted automatically.
// If path has no extension - new extension is appended // If path has no extension - new extension is appended
@ -113,46 +137,50 @@ func getVars(path string, globals Vars) (Vars, string, error) {
} }
delim := "\n---\n" delim := "\n---\n"
if sep := strings.Index(s, delim); sep == -1 { sep := strings.Index(s, delim)
if sep == -1 {
return v, s, nil return v, s, nil
} else { }
header := s[:sep] header := s[:sep]
body := s[sep+len(delim):] body := s[sep+len(delim):]
vars := Vars{} vars := Vars{}
if err := yaml.Unmarshal([]byte(header), &vars); err != nil { if err := yaml.Unmarshal([]byte(header), &vars); err != nil {
fmt.Println("ERROR: failed to parse header", err) fmt.Println("WARN: failed to parse header", err)
return nil, "", err return v, s, nil
} else { }
// Override default values + globals with the ones defines in the file // Override default values + globals with the ones defines in the file
for key, value := range vars { for key, value := range vars {
v[key] = value v[key] = value
} }
}
if strings.HasPrefix(v["url"], "./") { if strings.HasPrefix(v["url"], "./") {
v["url"] = v["url"][2:] v["url"] = v["url"][2:]
} }
return v, body, nil return v, body, nil
}
} }
// Render expanding zs plugins and variables // Render expanding zs plugins and variables
func render(s string, vars Vars) (string, error) { func render(s string, vars Vars) (string, error) {
delim_open := "{{" openingDelimiter := "{{"
delim_close := "}}" closingDelimiter := "}}"
out := &bytes.Buffer{} out := &bytes.Buffer{}
for { for {
if from := strings.Index(s, delim_open); from == -1 { from := strings.Index(s, openingDelimiter)
if from == -1 {
out.WriteString(s) out.WriteString(s)
return out.String(), nil return out.String(), nil
} else { }
if to := strings.Index(s, delim_close); to == -1 {
to := strings.Index(s, closingDelimiter)
if to == -1 {
return "", fmt.Errorf("Close delim not found") return "", fmt.Errorf("Close delim not found")
} else { }
out.WriteString(s[:from]) out.WriteString(s[:from])
cmd := s[from+len(delim_open) : to] cmd := s[from+len(openingDelimiter) : to]
s = s[to+len(delim_close):] s = s[to+len(closingDelimiter):]
m := strings.Fields(cmd) m := strings.Fields(cmd)
if len(m) == 1 { if len(m) == 1 {
if v, ok := vars[m[0]]; ok { if v, ok := vars[m[0]]; ok {
@ -166,8 +194,6 @@ func render(s string, vars Vars) (string, error) {
fmt.Println(err) fmt.Println(err)
} }
} }
}
}
} }
@ -228,13 +254,13 @@ func buildRaw(path string, w io.Writer) error {
} }
defer in.Close() defer in.Close()
if w == nil { if w == nil {
if out, err := os.Create(filepath.Join(PUBDIR, path)); err != nil { out, err := os.Create(filepath.Join(PUBDIR, path))
if err != nil {
return err return err
} else { }
defer out.Close() defer out.Close()
w = out w = out
} }
}
_, err = io.Copy(w, in) _, err = io.Copy(w, in)
return err return err
} }
@ -250,12 +276,19 @@ func build(path string, w io.Writer, vars Vars) error {
} }
} }
func buildAll(watch bool) { func buildAll(ctx context.Context, watch bool) {
ticker := NewTicker(time.Second)
defer ticker.Stop()
lastModified := time.Unix(0, 0) lastModified := time.Unix(0, 0)
modified := false modified := false
vars := globals() vars := globals()
for { for {
select {
case <-ctx.Done():
return
case <-ticker.C:
os.Mkdir(PUBDIR, 0755) os.Mkdir(PUBDIR, 0755)
filepath.Walk(".", func(path string, info os.FileInfo, err error) error { filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
// ignore hidden files and directories // ignore hidden files and directories
@ -288,11 +321,33 @@ func buildAll(watch bool) {
modified = false modified = false
} }
if !watch { if !watch {
break return
} }
lastModified = time.Now() lastModified = time.Now()
time.Sleep(1 * time.Second)
} }
}
}
// serve runs a static web server and builds and continuously watches for changes to rebuild
func serve(ctx context.Context, bind string) error {
os.Mkdir(PUBDIR, 0755)
svr, err := static.NewServer(
static.WithBind(bind),
static.WithDir(true),
static.WithRoot(PUBDIR),
static.WithSPA(true),
)
if err != nil {
return err
}
go svr.Run(ctx)
go buildAll(ctx, true)
<-ctx.Done()
return nil
} }
func init() { func init() {
@ -305,15 +360,21 @@ func init() {
func main() { func main() {
if len(os.Args) == 1 { if len(os.Args) == 1 {
fmt.Println(os.Args[0], "<command> [args]") fmt.Printf("%s <command> [args]\n", filepath.Base(os.Args[0]))
os.Exit(1)
return return
} }
cmd := os.Args[1] cmd := os.Args[1]
args := os.Args[2:] args := os.Args[2:]
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
switch cmd { switch cmd {
case "build": case "build":
if len(args) == 0 { if len(args) == 0 {
buildAll(false) buildAll(ctx, false)
} else if len(args) == 1 { } else if len(args) == 1 {
if err := build(args[0], os.Stdout, globals()); err != nil { if err := build(args[0], os.Stdout, globals()); err != nil {
fmt.Println("ERROR: " + err.Error()) fmt.Println("ERROR: " + err.Error())
@ -322,7 +383,15 @@ func main() {
fmt.Println("ERROR: too many arguments") fmt.Println("ERROR: too many arguments")
} }
case "watch": case "watch":
buildAll(true) buildAll(ctx, true)
case "serve":
bind := ":8000"
if len(args) > 1 {
bind = args[0]
}
if err := serve(ctx, bind); err != nil {
fmt.Println("ERROR: " + err.Error())
}
case "var": case "var":
if len(args) == 0 { if len(args) == 0 {
fmt.Println("var: filename expected") fmt.Println("var: filename expected")

145
preflight.sh Executable file
View File

@ -0,0 +1,145 @@
#!/usr/bin/env sh
set -e
color() {
fg="$1"
bg="${2}"
ft="${3:-0}"
printf "\33[%s;%s;%s" "$ft" "$fg" "$bg"
}
color_reset() {
printf "\033[0m"
}
ok() {
if [ -t 1 ]; then
printf "%s[ OK ]%s\n" "$(color 37 42m 1)" "$(color_reset)"
else
printf "%s\n" "[ OK ]"
fi
}
err() {
if [ -t 1 ]; then
printf "%s[ ERR ]%s\n" "$(color 37 41m 1)" "$(color_reset)"
else
printf "%s\n" "[ ERR ]"
fi
}
run() {
retval=0
logfile="$(mktemp -t "run-XXXXXX")"
if "$@" 2> "$logfile"; then
ok
else
retval=$?
err
cat "$logfile" || true
fi
rm -rf "$logfile"
return $retval
}
progress() {
printf "%-40s" "$(printf "%s ... " "$1")"
}
log() {
printf "%s\n" "$1"
}
log2() {
printf "%s\n" "$1" 1>&2
}
error() {
log "ERROR: ${1}"
}
fail() {
log "FATAL: ${1}"
exit 1
}
check_goversion() {
progress "Checking Go version"
if ! command -v go > /dev/null 2>&1; then
log2 "Cannot find the Go compiler"
return 1
fi
gover="$(go version | grep -o -E 'go[0-9]+\.[0-9]+(\.[0-9]+)?')"
if ! go version | grep -E 'go1\.1[6789](\.[0-9]+)?' > /dev/null; then
log2 "Go 1.16+ is required, found ${gover}"
return 1
fi
return 0
}
check_path() {
progress "Checking \$PATH"
gobin="$(eval "$(go env | grep GOBIN)")"
gopath="$(eval "$(go env | grep GOPATH)")"
if [ -n "$gobin" ] && ! echo "$PATH" | grep "$gobin" > /dev/null; then
log2 "\$GOBIN '$gobin' is not in your \$PATH"
return 1
fi
if [ -n "$gopath" ] && ! echo "$PATH" | grep "$gopath/bin" > /dev/null; then
log2 "\$GOPATH/bin '$gopath/bin' is not in your \$PATH"
return 1
fi
if ! echo "$PATH" | grep "$HOME/go/bin" > /dev/null; then
log2 "\$HOME/go/bin is not in your \$PATH"
return 1
fi
return 0
}
check_deps() {
progress "Checking deps"
if ! command -v minify > /dev/null 2>&1; then
log2 "minify not found, Try running: make deps"
return 1
fi
if ! minify --help 2>&1 | grep '\-b, \-\-bundle' > /dev/null; then
log2 "wrong version of minify found, Try running: make deps"
return 1
fi
if ! command -v goi18n > /dev/null 2>&1; then
log2 "goi18n not found, Try running: make deps"
return 1
fi
return 0
}
steps="check_goversion check_path check_deps"
_main() {
for step in $steps; do
if ! run "$step"; then
fail "🙁 preflight failed"
fi
done
log "🥳 All Done! Ready to build, run: make build"
}
if [ -n "$0" ] && [ x"$0" != x"-bash" ]; then
_main "$@"
fi