Skip to content

Commit

Permalink
[builtin] add arithmetic functions, replace attr to getAttr
Browse files Browse the repository at this point in the history
  • Loading branch information
jellyterra committed Jun 10, 2024
1 parent d713b27 commit 356ddc4
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 98 deletions.
252 changes: 162 additions & 90 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,147 +1,219 @@

<img src="https://github.com/jellyterra/artworks/raw/master/logo/pagine.svg" width="410.4" height="140" alt="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.<br/>
> 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
<html lang="{{ .data.lang }}">
<html>
<head>
<title>{{ .data.title }}</title>
<title>{{ .title }}</title>
<link rel="stylesheet" href="{{ (getAttr).templateBase }}/css/base.css" />
</head>
<body>
<p>{{ .contents.my_first_post }}</p>
<p>{{ .data.time }}</p>
{{ template "header.html" .header }}
<main>{{ render .content }}</main>
</body>
{{ template "footer.html" .footer }}
</html>
```

### 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.

Expand Down
4 changes: 4 additions & 0 deletions structure/builtin.go
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 1 addition & 1 deletion structure/hierarchy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
4 changes: 3 additions & 1 deletion structure/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 "" }
16 changes: 10 additions & 6 deletions structure/unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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,
}
)
Expand All @@ -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 {
Expand Down

0 comments on commit 356ddc4

Please sign in to comment.