diff --git a/README.md b/README.md index f4b0bf5..2656c42 100644 --- a/README.md +++ b/README.md @@ -1,147 +1,219 @@ Pagine logo -# Pagine -Template-driven generator for building websites of any scale. +# Pagine v2 -Latest version: v1.0.0 +Pagine is an high-performance website constructor that makes full use of multicore hardware. -- Template. -- Separation of **content**, **template** and **page form**. +Build jobs can be completed very fast. -### Planned features +## Features -- Job pipeline for reducing redundant content generation. -- Directories as collections of web pages. +- Parallel hierarchy processing and unit execution. Everything is executed in parallel from beginning to end. +- Hierarchical metadata propagation which makes metadata management easy. +- Manage templates and assets via Git. Every template can be distributed and used without modification. +- In-template builtin functions +- Interact with Pagine in templates. +- Update on file change while running as HTTP server. + +Supported rich text formats: + +- Markdown with MathJax/LaTeX support +- Asciidoc ## Install +### Binaries + +Find the executable that matches your OS and architecture in [releases](https://github.com/webpagine/pagine/releases). + +### Build from source + ```shell $ go install github.com/webpagine/pagine/cmd/pagine -$ pagine --gen ``` -Serve as HTTP server and automatically generate when files change: +## Usage + +Usage of pagine: +- `-public` string + - Location of public directory. (default `/tmp/$(basename $PWD).public`) +- `-root` string + - Site root. (default `$PWD`) +- `-serve` string + - Specify the port to listen and serve as HTTP. + + +### Generate + +```shell +$ cd ~/web/my_site +$ pagine +Generation complete. +``` + +### Run as HTTP server ```shell -$ pagine --serve --listen :8080 --public /tmp/public +$ cd ~/web/my_site +$ pagine --serve :12450 ``` > [!NOTE] > Incremental generation is not implemented yet.
-> Set the `--public` under `/tmp/` is recommended to reduce hard disk writes. +> Set the `--public` under `/tmp` is recommended to reduce hard disk writes. -## Get Started +## Structure -Example structure: -``` -. -├── pagine.toml -├── contents/ -│ └── my_first_post.md -├── data/ -│ ├── header_all.toml -│ └── header_specific.toml -├── posts/ -│ └── my_first_post.html.pagine -└── templates/ - ├── header.html - ├── footer.html - └── post.html -``` - -### Site +### Template -- Top level directory contents, such as website metadata. -- Global elements, such as page frame, navigation bar. +Template is a set of page frames (Go template file) and assets (e.g. SVGs, stylesheets and scripts). -For example: `/pagine.toml` +Manifest of one template looks like: ```toml -ignore = [ "/\\.*", "/*toml", "/contents/*", "/templates/*" ] -``` +[manifest] +canonical = "com.symboltics.pagine.genesis" # Canonical name +patterns = [ "/*html" ] # Matched files will be added as template file. -### Template +[[templates]] +name = "page" # Export as name `page` +export = "page.html" # Export `page.html` -Current implementation of template depends on Go `text/template` library. +[[templates]] +name = "post" # Export as name `post` +export = "post.html" # Export `post.html` +``` -For Go templates, refer to the [tutorial](https://gohugo.io/templates/introduction/) by Hugo team. +To the Go templates files syntax, see [text/template](https://pkg.go.dev/text/template). -For example: `/templates/post.html` +Example: `page.html` ```html - + - {{ .data.title }} + {{ .title }} + -

{{ .contents.my_first_post }}

-

{{ .data.time }}

+{{ template "header.html" .header }} +
{{ render .content }}
+{{ template "footer.html" .footer }} ``` -### Page +### Env + +"Environment" is the configuration of the details of the entire process. + +```toml +ignore = [ "/.git*" ] # Pattern matching. Matched files will not be **copied** to the public/destination. + +[use] +genesis = "/templates/genesis" +another = "/templates/something_else" # Load and set alias for the template. +``` + +Installing templates via Git submodule is recommended. Such as: + +```shell +$ git submodule add https://github.com/webpagine/genesis templates/genesis +``` + +### Level -Page is a set of attributions of single page. +Each "level" contains its metadata. And a set of units to be executed. -- Templates to be used. -- Data definitions to be used in template. -- Different contents to be used in template. +For directories, metadata sets are stored in `metadata.toml` in the form of map, and units are stored in `unit.toml` -For example: `/posts/my_first_post.html.pagine` +Each template has its alias that defined in `env` as the namespace. + +Levels can override fields propagated from parents. + +Example: `/metadata.toml` ```toml -# Templates to be used in this page. -[templates] -header = "/templates/header.html" -footer = "/templates/footer.html" - -# Main template (top-level) is required. -main = "/templates/post.html" - -# Include data definitions from extern TOMLs. -[include] -header = [ - "/data/header_all.toml", - "/data/header_specific.toml", -] - -# Contents to be parsed and generated to HTML. -[contents] -my_first_post = "/contents/my_first_post.md" - -# Define data for template "main". -[define.main] -lang = "en" -title = "My First Post" -time = 2024-05-01 - -# Define data for template "header". -[define.header] -logo = "/assets/img/logo.svg" +[genesis] +title = "Pagine" + +[genesis.head] +icon = "/favicon.ico" + +[[genesis.header.nav.items]] +name = "Documentation" +link = "/docs/" ``` +### Unit + +Example: `/unit.toml` +```toml +[[unit]] +template = "genesis:page" # Which template to use. +output = "/index.html" # Where to save the result. +define = { title = "Pagine" } # Unit-specified metadata. + +[[unit]] +template = "genesis:page" +output = "/404.html" +define = { title = "Page not found" } +``` + +## Builtin functions + +### Arithmetic + +| Func | Args | Result | +|-------|-----------|--------| +| `add` | a, b: Int | Int | +| `sub` | a, b: Int | Int | +| `mul` | a, b: Int | Int | +| `div` | a, b: Int | Int | +| `mod` | a,b : Int | Int | + +### Engine API + +| Func | Args | Description | +|-----------|-------------|--------------------------------------------------------------------------------------------------| +| `getAttr` | key: String | Get meta information in the form of map about units, hierarchy and templates provided by engine. | + +| Attribution | Description | +|----------------|-------------------------------------------------| +| `unitBase` | Unit's level's base dir path. | +| `templateBase` | It tells the template where it has been stored. | + +### Data processing + +| Func | Args | Result | Description | +|------------------|-------------------------------------------------|--------------------------------------------------|-----------------------------------------------------------------------------------| +| `divideSliceByN` | slice: []Any, n: Int | [][]Any | Divide a slice into *len(slice) / N* slices | +| `mapAsSlice` | map: map[String]Any, **key**, **value**: String | []map[String]{ **key**: String, **value**: Any } | Convert map to a slice of map that contains two keys named **key** and **value**. | + ### Content -For each supported rich text format, there is a parser and an HTML generator. Pagine detects format by file name suffix `.md`. +Path starts from where the unit is. -The latest implementation accepts: -- Markdown +| Func | Args | Description | +|------------------|--------------|-----------------------------------------| +| `embed` | path: String | Embed file raw content. | +| `render` | path: String | Invoke renderer by file extension name. | +| `renderAsciidoc` | path: String | Render and embed Asciidoc content. | +| `renderMarkdown` | path: String | Render and embed Markdown content. | -For example: `/contents/my_first_post.md` -```markdown -# My First Post +| Format | File Extension Name | +|----------|---------------------| +| Markdown | `md` | +| Asciidoc | `adoc` | -It is a post in Markdown. -``` +## Deploy -## Deploy manually +### Manually ```shell -$ pagine --gen --public ../public +$ pagine --public ../public ``` -## Deploy via CI/CD +Upload `public` to your server. -### GitHub Actions +### Deploy to GitHub Pages via GitHub Actions (recommended) GitHub Actions workflow configuration can be found in [Get Started](https://github.com/webpagine/get-started) repository. diff --git a/structure/builtin.go b/structure/builtin.go index 468cd21..3db33c6 100644 --- a/structure/builtin.go +++ b/structure/builtin.go @@ -1,6 +1,10 @@ package structure func add(aInt, bInt any) int { return aInt.(int) + bInt.(int) } +func sub(aInt, bInt any) int { return aInt.(int) - bInt.(int) } +func mul(aInt, bInt any) int { return aInt.(int) * bInt.(int) } +func div(aInt, bInt any) int { return aInt.(int) / bInt.(int) } +func mod(aInt, bInt any) int { return aInt.(int) % bInt.(int) } func divideSliceByN(s []any, nInt any) [][]any { n := nInt.(int) diff --git a/structure/hierarchy.go b/structure/hierarchy.go index d9e73f9..e99d3a5 100644 --- a/structure/hierarchy.go +++ b/structure/hierarchy.go @@ -89,7 +89,7 @@ func ExecuteLevels(env *Env, root, dest vfs.DirFS, inherit MetadataSet) (Level, case err == nil: // No error will cause interrupt below. - for _, unitItem := range unitManifest.Unit { + for _, unitItem := range unitManifest.Units { wg.Add(1) go func() { defer wg.Done() diff --git a/structure/template.go b/structure/template.go index 18ca595..f9b695a 100644 --- a/structure/template.go +++ b/structure/template.go @@ -71,11 +71,13 @@ func LoadTemplate(root vfs.DirFS) (*Template, error) { } var emptyFuncMap = map[string]any{ - "attr": _empty, + "getAttr": empty, "embed": _empty, "render": _empty, "renderMarkdown": _empty, "renderAsciidoc": _empty, } +func empty() any { return nil } + func _empty(_ any) any { return "" } diff --git a/structure/unit.go b/structure/unit.go index dcf7b1f..bce252f 100644 --- a/structure/unit.go +++ b/structure/unit.go @@ -16,7 +16,7 @@ type UnitReport struct { } type UnitManifest struct { - Unit []struct { + Units []struct { Template string `toml:"template"` Output string `toml:"output"` Define map[string]any `toml:"define"` @@ -44,7 +44,7 @@ func (u *Unit) Generate(env *Env, root, dest vfs.DirFS, data MetadataSet, define templateBase, _ = strings.CutPrefix(t.Root.Path, env.Root.Path) attr = map[string]any{ - "base": base, + "unitBase": base, "templateBase": templateBase, } ) @@ -59,13 +59,17 @@ func (u *Unit) Generate(env *Env, root, dest vfs.DirFS, data MetadataSet, define } funcMap := map[string]any{ - "add": add, + "add": add, + "sub": sub, + "mul": mul, + "div": div, + "mod": mod, + "divideSliceByN": divideSliceByN, "mapAsSlice": mapAsSlice, - "attr": func(keyStr any) any { - return attr[keyStr.(string)] - }, + "getAttr": func() any { return attr }, + "embed": func(pathStr any) any { b, err := root.ReadFile(pathStr.(string)) if err != nil {