├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── LICENSE
├── README.md
├── build.zig
├── build.zig.zon
├── build
└── camera.zig
├── flake.lock
├── flake.nix
├── frontmatter.ziggy-schema
├── src
├── AnsiRenderer.zig
├── Build.zig
├── Git.zig
├── PathTable.zig
├── StringTable.zig
├── Template.zig
├── Variant.zig
├── channel.zig
├── cli
│ ├── debug.zig
│ ├── init.zig
│ ├── init
│ │ ├── assets
│ │ │ ├── Temml-Local.css
│ │ │ ├── Temml.woff2
│ │ │ ├── highlight.css
│ │ │ ├── render-mathtex.js
│ │ │ ├── style.css
│ │ │ ├── temml.min.js
│ │ │ └── under-construction.gif
│ │ ├── content
│ │ │ ├── about.smd
│ │ │ ├── blog
│ │ │ │ ├── first-post
│ │ │ │ │ ├── fanzine.jpg
│ │ │ │ │ └── index.smd
│ │ │ │ ├── index.smd
│ │ │ │ └── second-post.smd
│ │ │ ├── devlog
│ │ │ │ ├── 1989.smd
│ │ │ │ ├── 1990.smd
│ │ │ │ └── index.smd
│ │ │ └── index.smd
│ │ ├── layouts
│ │ │ ├── blog.shtml
│ │ │ ├── blog.xml
│ │ │ ├── devlog-archive.shtml
│ │ │ ├── devlog.shtml
│ │ │ ├── devlog.xml
│ │ │ ├── index.shtml
│ │ │ ├── page.shtml
│ │ │ ├── post.shtml
│ │ │ └── templates
│ │ │ │ └── base.shtml
│ │ └── zine.ziggy
│ ├── release.zig
│ ├── serve.zig
│ └── serve
│ │ ├── 404.html
│ │ ├── error.html
│ │ ├── outside.html
│ │ ├── watcher
│ │ ├── LinuxWatcher.zig
│ │ ├── MacosWatcher.zig
│ │ └── WindowsWatcher.zig
│ │ ├── websocket.zig
│ │ └── zinereload.js
├── context.zig
├── context
│ ├── Array.zig
│ ├── Asset.zig
│ ├── Bool.zig
│ ├── Build.zig
│ ├── DateTime.zig
│ ├── Float.zig
│ ├── Git.zig
│ ├── Int.zig
│ ├── Iterator.zig
│ ├── Map.zig
│ ├── Optional.zig
│ ├── Page.zig
│ ├── Site.zig
│ ├── Slice.zig
│ ├── String.zig
│ ├── Template.zig
│ ├── Value.zig
│ ├── doctypes.zig
│ ├── markdown.zig
│ └── utils.zig
├── docgen.zig
├── fatal.zig
├── fuzz
│ └── scripty.zig
├── highlight.zig
├── main.zig
├── render.zig
├── render
│ └── html.zig
├── root.zig
├── table.zig
├── worker.zig
└── wuffs.zig
└── tests
├── content-scanning
├── collisions
│ ├── assets
│ │ └── .keep
│ ├── content
│ │ ├── index.smd
│ │ ├── nested
│ │ │ └── path
│ │ │ │ └── page.smd
│ │ ├── page.smd
│ │ └── page
│ │ │ └── index.smd
│ ├── layouts
│ │ ├── index.shtml
│ │ └── page.shtml
│ ├── snapshot.txt
│ └── zine.ziggy
├── frontmatter
│ ├── assets
│ │ └── .keep
│ ├── content
│ │ ├── index.smd
│ │ ├── validation-errors.smd
│ │ └── wrong-syntax.smd
│ ├── layouts
│ │ └── .keep
│ ├── snapshot.txt
│ └── zine.ziggy
├── page-analysis
│ ├── assets
│ │ ├── .keep
│ │ ├── code.zig
│ │ └── skater.webp
│ ├── content
│ │ ├── code.smd
│ │ ├── index.smd
│ │ ├── other.smd
│ │ ├── parse.smd
│ │ └── skater.webp
│ ├── layouts
│ │ ├── archive-entry.shtml
│ │ ├── archive.shtml
│ │ ├── index.shtml
│ │ └── sections.shtml
│ ├── snapshot.txt
│ └── zine.ziggy
├── simple
│ ├── assets
│ │ └── .keep
│ ├── content
│ │ ├── archive
│ │ │ ├── 2024
│ │ │ │ ├── first.smd
│ │ │ │ └── index.smd
│ │ │ ├── 2025
│ │ │ │ ├── index.smd
│ │ │ │ └── second.smd
│ │ │ └── index.smd
│ │ ├── index.smd
│ │ └── sections.smd
│ ├── layouts
│ │ ├── archive-entry.shtml
│ │ ├── archive.shtml
│ │ ├── index.shtml
│ │ └── sections.shtml
│ ├── snapshot.txt
│ └── zine.ziggy
└── templates
│ ├── assets
│ └── .keep
│ ├── content
│ ├── another.smd
│ ├── badextend.smd
│ ├── badhtml.smd
│ ├── badshtml.smd
│ ├── index.smd
│ └── page.smd
│ ├── layouts
│ ├── .keep
│ ├── badextend.shtml
│ ├── badhtml.shtml
│ ├── badshtml.shtml
│ ├── index.shtml
│ ├── oops.html
│ ├── page.shtml
│ └── templates
│ │ ├── base.shtml
│ │ └── withmenu.shtml
│ ├── snapshot.txt
│ └── zine.ziggy
├── drafts
└── simple
│ ├── assets
│ └── .keep
│ ├── content
│ ├── archive
│ │ ├── 2024
│ │ │ ├── first.smd
│ │ │ └── index.smd
│ │ ├── 2025
│ │ │ ├── index.smd
│ │ │ └── second.smd
│ │ └── index.smd
│ ├── index.smd
│ └── sections.smd
│ ├── layouts
│ ├── archive-entry.shtml
│ ├── archive.shtml
│ ├── index.shtml
│ └── sections.shtml
│ ├── snapshot.txt
│ ├── snapshot
│ ├── archive
│ │ ├── 2024
│ │ │ ├── first
│ │ │ │ └── index.html
│ │ │ └── index.html
│ │ ├── 2025
│ │ │ ├── index.html
│ │ │ └── second
│ │ │ │ └── index.html
│ │ └── index.html
│ ├── index.html
│ └── sections
│ │ └── index.html
│ └── zine.ziggy
├── rendering
├── multi
│ ├── content
│ │ ├── de-DE
│ │ │ ├── about.smd
│ │ │ ├── contact-us.smd
│ │ │ └── index.smd
│ │ ├── en-US
│ │ │ ├── about.smd
│ │ │ ├── contact-us.smd
│ │ │ └── index.smd
│ │ └── it-IT
│ │ │ ├── contattaci.smd
│ │ │ └── index.smd
│ ├── i18n
│ │ ├── de-DE.ziggy
│ │ ├── en-US.ziggy
│ │ └── it-IT.ziggy
│ ├── layouts
│ │ ├── blog.shtml
│ │ ├── blog.xml
│ │ ├── devlog-archive.shtml
│ │ ├── devlog.shtml
│ │ ├── devlog.xml
│ │ ├── index.shtml
│ │ ├── page.shtml
│ │ ├── post.shtml
│ │ └── templates
│ │ │ └── base.shtml
│ ├── snapshot.txt
│ ├── snapshot
│ │ ├── about
│ │ │ └── index.html
│ │ ├── contact-us
│ │ │ └── index.html
│ │ ├── de-DE
│ │ │ ├── about
│ │ │ │ └── index.html
│ │ │ ├── contact-us
│ │ │ │ └── index.html
│ │ │ └── index.html
│ │ ├── index.html
│ │ └── it-IT
│ │ │ ├── contattaci
│ │ │ └── index.html
│ │ │ └── index.html
│ └── zine.ziggy
└── simple
│ ├── assets
│ └── .keep
│ ├── content
│ ├── archive
│ │ ├── 2024
│ │ │ ├── first.smd
│ │ │ └── index.smd
│ │ ├── 2025
│ │ │ ├── index.smd
│ │ │ └── second.smd
│ │ └── index.smd
│ ├── index.smd
│ ├── nested
│ │ └── aliases.smd
│ ├── sections.smd
│ └── syntax.smd
│ ├── layouts
│ ├── archive-entry.shtml
│ ├── archive.shtml
│ ├── index.shtml
│ └── sections.shtml
│ ├── snapshot.txt
│ ├── snapshot
│ ├── alias_absolute.html
│ ├── aliases
│ │ └── path
│ │ │ └── with
│ │ │ └── leading
│ │ │ └── slash.html
│ ├── archive
│ │ ├── 2024
│ │ │ ├── first
│ │ │ │ └── index.html
│ │ │ └── index.html
│ │ ├── 2025
│ │ │ ├── index.html
│ │ │ └── second
│ │ │ │ └── index.html
│ │ └── index.html
│ ├── index.html
│ ├── nested
│ │ └── aliases
│ │ │ ├── alias_relative.html
│ │ │ ├── aliases
│ │ │ └── path
│ │ │ │ └── without
│ │ │ │ └── leading
│ │ │ │ └── slash.html
│ │ │ └── index.html
│ ├── sections
│ │ └── index.html
│ └── syntax
│ │ └── index.html
│ └── zine.ziggy
└── simple
└── snapshot
└── syntax
└── index.html
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | pull_request:
4 | push:
5 | branches:
6 | - main
7 | concurrency:
8 | # Cancels pending runs when a PR gets updated.
9 | group: ${{ github.head_ref || github.run_id }}-${{ github.actor }}
10 | cancel-in-progress: true
11 | permissions:
12 | # Sets permission policy for `GITHUB_TOKEN`
13 | contents: read
14 | jobs:
15 | tests:
16 | strategy:
17 | fail-fast: false
18 | matrix:
19 | os: [ubuntu-latest, windows-latest, macos-latest]
20 |
21 | runs-on: ${{ matrix.os }}
22 | steps:
23 | - name: No autocrlf
24 | run: git config --global core.autocrlf false
25 |
26 | - uses: actions/checkout@v4
27 | with:
28 | fetch-depth: 0 # Change if you need git info
29 |
30 | - name: Setup Zig
31 | uses: mlugg/setup-zig@v1
32 | with:
33 | version: 0.14.0
34 |
35 | - name: Build
36 | run: zig build test
37 |
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | public
2 | .zine-cache
3 | .zig-cache
4 | zig-cache
5 | zig-out
6 | scratch
7 | result
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Loris Cro
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Zine
2 | Fast, Scalable, Flexible Static Site Generator (SSG)
3 | Zine is pronounced like in fanzine.
4 |
5 | ## Development Status
6 | Zine is still a young project, not yet at feature parity with more popular
7 | alternatives (e.g. Hugo), but it's perfectly able to handle a personal website
8 | with a blog.
9 |
10 | ## Getting Started
11 | Go to https://zine-ssg.io to get started.
12 |
13 | ## GitHub Actions
14 | If you plan to build your website with GitHub Actions, take a look at [kristoff-it/setup-zine](https://github.com/marketplace/actions/setup-zine), the official
15 | GitHub Action to get access to Zine in your runner.
16 |
17 | We also have more complete guides at https://zine-ssg.io/docs/.
18 |
--------------------------------------------------------------------------------
/build.zig.zon:
--------------------------------------------------------------------------------
1 | .{
2 | .name = .zine,
3 | .version = "0.9.0",
4 | .fingerprint = 0xa466bcb520a7eea2,
5 | .minimum_zig_version = "0.14.0",
6 | .dependencies = .{
7 | .ziggy = .{
8 | .url = "git+https://github.com/kristoff-it/ziggy#8a29017169f43dc2c3526817e98142eb9a335087",
9 | .hash = "ziggy-0.1.0-kTg8vwkbBgAOHreabwZtDDtNDi3U_RAiOMvuRDTJiy0I",
10 | },
11 | .afl_kit = .{
12 | .url = "git+https://github.com/kristoff-it/zig-afl-kit#f003bfe714f2964c90939fdc940d5993190a66ec",
13 | .hash = "1220f2d8402bb7bbc4786b9c0aad73910929ea209cbd3b063842371d68abfed33c1e",
14 | .lazy = true,
15 | },
16 | .lsp_kit = .{
17 | .url = "git+https://github.com/kristoff-it/zig-lsp-kit#87ff3d537a0c852442e180137d9557711963802c",
18 | .hash = "lsp_kit-0.1.0-hAAxO9S9AADv_5D0iplASFtNCFXAPk54M0u-3jj2MRFk",
19 | },
20 | .scripty = .{
21 | .url = "git+https://github.com/kristoff-it/scripty#57056571abcc6fe69fcb171c10b0c9e5962f53b0",
22 | .hash = "scripty-0.1.0-LKK5O9jDAADwZbkwkzYcmtTD3xIStr1SNYWL0kcGf8sk",
23 | },
24 | .tracy = .{
25 | .url = "git+https://github.com/kristoff-it/tracy#67d2d89e351048c76fc6d161e0ac09d8a831dc60",
26 | .hash = "tracy-0.0.0-4Xw-1pwwAABTfMgoDP1unCbZDZhJEfict7XCBGF6IdIn",
27 | },
28 | .mime = .{
29 | .url = "git+https://github.com/andrewrk/mime.git#0b676643886b1e2f19cf11b4e15b028768708342",
30 | .hash = "12209083b0c43d0f68a26a48a7b26ad9f93b22c9cff710c78ddfebb47b89cfb9c7a4",
31 | },
32 | .zeit = .{
33 | .url = "git+https://github.com/rockorager/zeit#52b100caa223d5cb1ff0d34f1b677f26e0ce8b84",
34 | .hash = "1220e97357cc39f4f9f053c763f3ec1623e0c7f3999f185746f2bd9bf9b5c5551392",
35 | },
36 | .flow_syntax = .{
37 | .url = "git+https://github.com/neurocyte/flow-syntax#d231728c92cb3c5a7139cb0d75a321a119b8e777",
38 | .hash = "flow_syntax-0.1.0-X8jOoUX-AADI9WdOuVSYK9yjyBOTFj4UicSapF7QQssd",
39 | },
40 | .wuffs = .{
41 | .url = "git+https://github.com/allyourcodebase/wuffs#818c8ad6607dd5c1ee571362fdb9813b744ee548",
42 | .hash = "1220e4ee09c4fa2d90a9cc7f34f14e04be55a779c84d486696fa9f9ab98ade35409d",
43 | },
44 | .frameworks = .{
45 | .url = "git+https://github.com/hexops/xcode-frameworks.git#8a1cfb373587ea4c9bb1468b7c986462d8d4e10e",
46 | .hash = "N-V-__8AALShqgXkvqYU6f__FrA22SMWmi2TXCJjNTO1m8XJ",
47 | .lazy = true,
48 | },
49 | .superhtml = .{
50 | .url = "git+https://github.com/kristoff-it/superhtml#16887e9fa3122c36a3d4942470e33c1c282fe859",
51 | .hash = "superhtml-0.4.0-Y7MdPKeXDQD6PoBdAJL8JlNYHk8kH0rcIbRjEfmKTj5r",
52 | },
53 | .supermd = .{
54 | .url = "git+https://github.com/kristoff-it/supermd#48500784d7706eaba2d5e1a35332353aca3fc04e",
55 | .hash = "supermd-0.1.0-3Mco3MySWADIsgRWDOxCc1wMJckAQa7yVW2cFzFrL4Fn",
56 | },
57 | },
58 | .paths = .{"."},
59 | }
60 |
--------------------------------------------------------------------------------
/build/camera.zig:
--------------------------------------------------------------------------------
1 | //! Runs a program that might or might not fail and appends to stdout what
2 | //! the actual exit code was, always returning a successful exit code under
3 | //! normal conditions (regardless of the child's exit code).
4 | //!
5 | //! This is useful for snapshot tests where some of which are meant to be
6 | //! successes, while others are meant to be failures.
7 | const std = @import("std");
8 |
9 | pub fn main() !void {
10 | const gpa = std.heap.smp_allocator;
11 | const args = try std.process.argsAlloc(gpa);
12 |
13 | var cmd = std.process.Child.init(args[1..], gpa);
14 | const term = try cmd.spawnAndWait();
15 |
16 | switch (term) {
17 | .Exited => |code| {
18 | const fmt = "\n\n ----- EXIT CODE: {} -----\n";
19 | std.debug.print(fmt, .{code});
20 | // try std.io.getStdOut().writer().print(fmt, .{code});
21 | },
22 | else => std.debug.panic("child process crashed: {}\n", .{term}),
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-utils": {
4 | "inputs": {
5 | "systems": "systems"
6 | },
7 | "locked": {
8 | "lastModified": 1731533236,
9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
10 | "owner": "numtide",
11 | "repo": "flake-utils",
12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
13 | "type": "github"
14 | },
15 | "original": {
16 | "owner": "numtide",
17 | "repo": "flake-utils",
18 | "type": "github"
19 | }
20 | },
21 | "nixpkgs": {
22 | "locked": {
23 | "lastModified": 1744932701,
24 | "narHash": "sha256-fusHbZCyv126cyArUwwKrLdCkgVAIaa/fQJYFlCEqiU=",
25 | "owner": "NixOS",
26 | "repo": "nixpkgs",
27 | "rev": "b024ced1aac25639f8ca8fdfc2f8c4fbd66c48ef",
28 | "type": "github"
29 | },
30 | "original": {
31 | "owner": "NixOS",
32 | "ref": "nixos-unstable",
33 | "repo": "nixpkgs",
34 | "type": "github"
35 | }
36 | },
37 | "root": {
38 | "inputs": {
39 | "nixpkgs": "nixpkgs",
40 | "zig2nix": "zig2nix"
41 | }
42 | },
43 | "systems": {
44 | "locked": {
45 | "lastModified": 1681028828,
46 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
47 | "owner": "nix-systems",
48 | "repo": "default",
49 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
50 | "type": "github"
51 | },
52 | "original": {
53 | "owner": "nix-systems",
54 | "repo": "default",
55 | "type": "github"
56 | }
57 | },
58 | "zig2nix": {
59 | "inputs": {
60 | "flake-utils": "flake-utils",
61 | "nixpkgs": [
62 | "nixpkgs"
63 | ]
64 | },
65 | "locked": {
66 | "lastModified": 1745026949,
67 | "narHash": "sha256-P+6DKKaZniG5xJIzKpZ7Kt5qdn3RCuFDDo2Atr0y5NU=",
68 | "owner": "Cloudef",
69 | "repo": "zig2nix",
70 | "rev": "d8730240de15020f8022b23d7d6d2fbb53cdae6d",
71 | "type": "github"
72 | },
73 | "original": {
74 | "owner": "Cloudef",
75 | "repo": "zig2nix",
76 | "type": "github"
77 | }
78 | }
79 | },
80 | "root": "root",
81 | "version": 7
82 | }
83 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | inputs = {
3 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
4 | zig2nix = {
5 | url = "github:Cloudef/zig2nix";
6 | inputs.nixpkgs.follows = "nixpkgs";
7 | };
8 | };
9 | outputs =
10 | {
11 | self,
12 | nixpkgs,
13 | zig2nix,
14 | ...
15 | }:
16 | let
17 | inherit (nixpkgs) lib;
18 | forAllSystems =
19 | body: lib.genAttrs lib.systems.flakeExposed (system: body nixpkgs.legacyPackages.${system});
20 | in
21 | {
22 | packages = forAllSystems (
23 | pkgs:
24 | let
25 | env = zig2nix.outputs.zig-env.${pkgs.system} {
26 | nixpkgs = nixpkgs;
27 | zig = pkgs.zig;
28 | };
29 | in
30 | {
31 | zine = env.package {
32 | src = lib.cleanSource ./.;
33 | nativeBuildInputs = [ ];
34 | buildInputs = [ ];
35 | zigPreferMusl = false;
36 | };
37 | default = self.packages.${pkgs.system}.zine;
38 | }
39 | );
40 | formatter = forAllSystems (pkgs: pkgs.nixfmt-rfc-style);
41 | };
42 | }
43 |
--------------------------------------------------------------------------------
/frontmatter.ziggy-schema:
--------------------------------------------------------------------------------
1 | root = Frontmatter
2 |
3 | ///A RFC 3339 date string, eg "2024-10-24T00:00:00".
4 | @date = bytes,
5 |
6 | struct Frontmatter {
7 | ///The title of this page.
8 | title: ?bytes,
9 | ///A short description that the section page has
10 | ///access to.
11 | description: ?bytes,
12 | ///The main author of this page.
13 | author: ?bytes,
14 | date: ?@date,
15 | tags: ?[bytes],
16 | ///Alternative paths where this content will also be
17 | ///made available.
18 | aliases: ?[bytes],
19 | ///When set to true this file will be ignored when
20 | ///bulding the website.
21 | draft: ?bool,
22 | ///Path to a layout file inside of the configured
23 | ///layouts directory.
24 | layout: bytes,
25 | ///Alternative versions of this page, created by
26 | ///rendering the content using a different layout.
27 | ///Useful for creating RSS feeds, for example.
28 | alternatives: ?[Alternative],
29 | ///Ignore other markdown files in this directory and
30 | ///any sub-directory. Can only be meaningfully set to
31 | ///true for 'index.smd' pages.
32 | skip_subdirs: ?bool,
33 | ///User-defined properties that you can then reference
34 | ///in templates.
35 | custom: ?map[any],
36 | }
37 |
38 | struct Alternative {
39 | ///Path to a layout file inside of the configured
40 | ///layouts directory.
41 | layout: bytes,
42 | ///Output path, relative to the current directory.
43 | ///Use an absolute path to refer to the website's root
44 | ///directory.
45 | output: bytes,
46 | ///Useful when generating ``
47 | ///elements.
48 | title: ?bytes,
49 | ///Useful when generating ``
50 | ///elements.
51 | type: ?bytes,
52 | }
53 |
--------------------------------------------------------------------------------
/src/AnsiRenderer.zig:
--------------------------------------------------------------------------------
1 | const AnsiRenderer = @This();
2 | const std = @import("std");
3 |
4 | state: State = .normal,
5 | current_style: Style = .{},
6 | g0_charset: Charset = .ascii,
7 |
8 | const State = union(enum) {
9 | normal,
10 | escape,
11 | csi: ?u32,
12 | gzd4,
13 | };
14 |
15 | const Style = struct {
16 | bold: bool = false,
17 | dim: bool = false,
18 | foreground: ?Color = null,
19 |
20 | const Color = enum {
21 | black,
22 | red,
23 | green,
24 | yellow,
25 | blue,
26 | magenta,
27 | cyan,
28 | white,
29 | };
30 |
31 | fn print(style: Style, out: anytype, open: bool) !void {
32 | comptime var fields: [std.meta.fields(Style).len]std.builtin.Type.StructField = undefined;
33 | @memcpy(&fields, std.meta.fields(Style));
34 | comptime std.mem.reverse(std.builtin.Type.StructField, &fields);
35 | const FieldEnum = std.meta.FieldEnum(Style);
36 | inline for (fields) |field| {
37 | const value = @field(style, field.name);
38 | const tag: ?std.meta.Tuple(&.{ []const u8, ?[]const u8 }) = switch (@field(FieldEnum, field.name)) {
39 | .bold => if (value) .{ "b", null } else null,
40 | .dim => if (value) .{ "span", "style=\"filter: brightness(75%)\"" } else null,
41 | .foreground => if (value) |color| .{ "span", switch (color) {
42 | inline else => |c| "style=\"color: " ++ @tagName(c) ++ "\"",
43 | } } else null,
44 | };
45 |
46 | if (tag) |t| {
47 | if (open) {
48 | if (t[1]) |attrs| {
49 | try out.print("<{s} {s}>", .{ t[0], attrs });
50 | } else {
51 | try out.print("<{s}>", .{t[0]});
52 | }
53 | } else {
54 | try out.print("{s}>", .{t[0]});
55 | }
56 | }
57 | }
58 | }
59 |
60 | fn printOpen(style: Style, out: anytype) !void {
61 | try style.print(out, true);
62 | }
63 |
64 | fn printClose(style: Style, out: anytype) !void {
65 | try style.print(out, false);
66 | }
67 | };
68 |
69 | const Charset = enum {
70 | ascii,
71 | vt100_line_drawing,
72 | };
73 |
74 | pub fn renderSlice(allocator: std.mem.Allocator, src: []const u8) ![]const u8 {
75 | var fbs = std.io.fixedBufferStream(src);
76 |
77 | var out = std.ArrayList(u8).init(allocator);
78 | defer out.deinit();
79 |
80 | var renderer: AnsiRenderer = .{};
81 | try renderer.render(fbs.reader(), out.writer());
82 |
83 | return try out.toOwnedSlice();
84 | }
85 |
86 | fn render(renderer: *AnsiRenderer, reader: anytype, writer: anytype) !void {
87 | try renderer.current_style.printOpen(writer);
88 |
89 | while (true) {
90 | const char = reader.readByte() catch break;
91 |
92 | switch (renderer.state) {
93 | .normal => switch (char) {
94 | '\x1b' => renderer.state = .escape,
95 | else => switch (renderer.g0_charset) {
96 | .ascii => {
97 | _ = try writer.write(switch (char) {
98 | '<' => "<",
99 | '>' => ">",
100 | else => &.{char},
101 | });
102 | },
103 | .vt100_line_drawing => {
104 | _ = try writer.write(switch (char) {
105 | 'j' => "┘",
106 | 'k' => "┐",
107 | 'l' => "┌",
108 | 'm' => "└",
109 | 'n' => "┼",
110 | 'q' => "─",
111 | 't' => "├",
112 | 'u' => "┤",
113 | 'v' => "┴",
114 | 'w' => "┬",
115 | 'x' => "│",
116 | else => "�",
117 | });
118 | },
119 | },
120 | },
121 | .escape => switch (char) {
122 | '[' => renderer.state = .{ .csi = null },
123 | '(' => renderer.state = .gzd4,
124 | else => renderer.state = .normal,
125 | },
126 | .csi => |payload| switch (char) {
127 | '0'...'9' => {
128 | if (payload == null) {
129 | renderer.state.csi = char - '0';
130 | } else {
131 | renderer.state.csi.? *= 10;
132 | renderer.state.csi.? += char - '0';
133 | }
134 | },
135 | 'm' => {
136 | const n = payload orelse 0;
137 |
138 | try renderer.current_style.printClose(writer);
139 |
140 | switch (n) {
141 | 0 => renderer.current_style = .{},
142 | 1 => renderer.current_style.bold = true,
143 | 2 => renderer.current_style.dim = true,
144 | 30...37 => renderer.current_style.foreground = @enumFromInt(n - 30),
145 | else => {},
146 | }
147 |
148 | try renderer.current_style.printOpen(writer);
149 |
150 | renderer.state = .normal;
151 | },
152 | else => renderer.state = .normal,
153 | },
154 | .gzd4 => {
155 | switch (char) {
156 | 'B' => renderer.g0_charset = .ascii,
157 | '0' => renderer.g0_charset = .vt100_line_drawing,
158 | else => {},
159 | }
160 | renderer.state = .normal;
161 | },
162 | }
163 | }
164 |
165 | try renderer.current_style.printClose(writer);
166 | }
167 |
--------------------------------------------------------------------------------
/src/StringTable.zig:
--------------------------------------------------------------------------------
1 | const StringTable = @This();
2 |
3 | const std = @import("std");
4 | const mem = std.mem;
5 | const assert = std.debug.assert;
6 | const Allocator = std.mem.Allocator;
7 |
8 | string_bytes: std.ArrayListUnmanaged(u8),
9 | string_map: String.Map,
10 |
11 | pub const empty: StringTable = .{
12 | .string_bytes = .empty,
13 | .string_map = .empty,
14 | };
15 |
16 | pub fn deinit(st: *const StringTable, gpa: Allocator) void {
17 | var sb = st.string_bytes;
18 | sb.deinit(gpa);
19 |
20 | var sm = st.string_map;
21 | sm.deinit(gpa);
22 | }
23 |
24 | pub fn get(st: *const StringTable, bytes: []const u8) ?String {
25 | return st.string_map.getKeyAdapted(
26 | @as([]const u8, bytes),
27 | @as(String.MapIndexAdapter, .{ .bytes = st.string_bytes.items }),
28 | );
29 | }
30 |
31 | pub fn intern(
32 | st: *StringTable,
33 | gpa: Allocator,
34 | bytes: []const u8,
35 | ) !String {
36 | const gop = try st.string_map.getOrPutContextAdapted(
37 | gpa,
38 | @as([]const u8, bytes),
39 | @as(String.MapIndexAdapter, .{ .bytes = st.string_bytes.items }),
40 | @as(String.MapContext, .{ .bytes = st.string_bytes.items }),
41 | );
42 | if (gop.found_existing) return gop.key_ptr.*;
43 |
44 | try st.string_bytes.ensureUnusedCapacity(gpa, bytes.len + 1);
45 | const new_off: String = @enumFromInt(st.string_bytes.items.len);
46 |
47 | st.string_bytes.appendSliceAssumeCapacity(bytes);
48 | st.string_bytes.appendAssumeCapacity(0);
49 |
50 | gop.key_ptr.* = new_off;
51 |
52 | return new_off;
53 | }
54 |
55 | pub fn ArrayHashMap(T: type) type {
56 | return std.AutoArrayHashMapUnmanaged(String, T);
57 | }
58 |
59 | pub fn HashMap(T: type) type {
60 | return std.AutoHashMapUnmanaged(String, T);
61 | }
62 |
63 | pub const String = enum(u32) {
64 | _,
65 |
66 | const Map = std.HashMapUnmanaged(
67 | String,
68 | void,
69 | MapContext,
70 | std.hash_map.default_max_load_percentage,
71 | );
72 |
73 | const MapContext = struct {
74 | bytes: []const u8,
75 |
76 | pub fn eql(_: @This(), a: String, b: String) bool {
77 | return a == b;
78 | }
79 |
80 | pub fn hash(ctx: @This(), key: String) u64 {
81 | return std.hash_map.hashString(mem.sliceTo(ctx.bytes[@intFromEnum(key)..], 0));
82 | }
83 | };
84 |
85 | const MapIndexAdapter = struct {
86 | bytes: []const u8,
87 |
88 | pub fn eql(ctx: @This(), a: []const u8, b: String) bool {
89 | return mem.eql(u8, a, mem.sliceTo(ctx.bytes[@intFromEnum(b)..], 0));
90 | }
91 |
92 | pub fn hash(_: @This(), adapted_key: []const u8) u64 {
93 | assert(mem.indexOfScalar(u8, adapted_key, 0) == null);
94 | return std.hash_map.hashString(adapted_key);
95 | }
96 | };
97 |
98 | pub fn slice(index: String, st: *const StringTable) [:0]const u8 {
99 | const start_slice = st.string_bytes.items[@intFromEnum(index)..];
100 | return start_slice[0..mem.indexOfScalar(u8, start_slice, 0).? :0];
101 | }
102 | };
103 |
104 | test StringTable {
105 | const gpa = std.testing.allocator;
106 |
107 | var string_table: StringTable = .empty;
108 | defer string_table.deinit(gpa);
109 |
110 | const banana = try string_table.intern(gpa, "banana");
111 | const apple = try string_table.intern(gpa, "apple");
112 | const melon = try string_table.intern(gpa, "melon");
113 |
114 | try std.testing.expectEqual(banana, string_table.get("banana").?);
115 | try std.testing.expectEqual(apple, string_table.get("apple").?);
116 | try std.testing.expectEqual(melon, string_table.get("melon").?);
117 |
118 | try std.testing.expectEqual(banana, try string_table.intern(gpa, "banana"));
119 | try std.testing.expectEqual(apple, try string_table.intern(gpa, "apple"));
120 | try std.testing.expectEqual(melon, try string_table.intern(gpa, "melon"));
121 |
122 | try std.testing.expect(banana != apple);
123 | try std.testing.expect(apple != melon);
124 | try std.testing.expect(melon != banana);
125 |
126 | try std.testing.expectEqual(null, string_table.get("strawberry"));
127 | try std.testing.expectEqual(null, string_table.get("coconut"));
128 | try std.testing.expectEqual(null, string_table.get("lemon"));
129 | }
130 |
131 | test HashMap {
132 | const gpa = std.testing.allocator;
133 |
134 | const Color = enum { yellow, red, orange };
135 |
136 | inline for (&.{ ArrayHashMap, HashMap }) |Map| {
137 | var fruit_color: Map(Color) = .empty;
138 | defer fruit_color.deinit(gpa);
139 |
140 | var string_table: StringTable = .empty;
141 | defer string_table.deinit(gpa);
142 |
143 | const banana = try string_table.intern(gpa, "banana");
144 | const apple = try string_table.intern(gpa, "apple");
145 | const melon = try string_table.intern(gpa, "melon");
146 |
147 | try fruit_color.put(gpa, banana, .yellow);
148 | try fruit_color.put(gpa, apple, .red);
149 | try fruit_color.put(gpa, melon, .orange);
150 |
151 | try std.testing.expectEqual(fruit_color.get(banana).?, .yellow);
152 | try std.testing.expectEqual(fruit_color.get(apple).?, .red);
153 | try std.testing.expectEqual(fruit_color.get(melon).?, .orange);
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/Template.zig:
--------------------------------------------------------------------------------
1 | const Template = @This();
2 |
3 | const std = @import("std");
4 | const superhtml = @import("superhtml");
5 | const tracy = @import("tracy");
6 | const root = @import("root.zig");
7 | const fatal = @import("fatal.zig");
8 | const worker = @import("worker.zig");
9 | const Build = @import("Build.zig");
10 | const StringTable = @import("StringTable.zig");
11 | const String = StringTable.String;
12 | const PathTable = @import("PathTable.zig");
13 | const Path = PathTable.Path;
14 | const PathName = PathTable.PathName;
15 | const Allocator = std.mem.Allocator;
16 | const assert = std.debug.assert;
17 |
18 | src: []const u8 = undefined,
19 | html_ast: superhtml.html.Ast = undefined,
20 | // Only present if html_ast.errors.len == 0
21 | ast: superhtml.Ast = undefined,
22 | missing_parent: bool = false,
23 | layout: bool,
24 |
25 | pub fn deinit(t: *const Template, gpa: Allocator) void {
26 | gpa.free(t.src);
27 | t.html_ast.deinit(gpa);
28 | t.ast.deinit(gpa);
29 | }
30 |
31 | pub fn parse(
32 | t: *Template,
33 | gpa: Allocator,
34 | arena: Allocator,
35 | build: *const Build,
36 | pn: PathName,
37 | ) void {
38 | const zone = tracy.trace(@src());
39 | defer zone.end();
40 |
41 | errdefer |err| switch (err) {
42 | error.OutOfMemory => fatal.oom(),
43 | };
44 |
45 | const path = try std.fmt.allocPrint(arena, "{/}", .{
46 | pn.fmt(&build.st, &build.pt, null),
47 | });
48 |
49 | const max = std.math.maxInt(u32);
50 | const src = build.layouts_dir.readFileAlloc(
51 | gpa,
52 | path,
53 | max,
54 | ) catch |err| fatal.file(path, err);
55 |
56 | t.src = src;
57 |
58 | t.html_ast = try .init(
59 | gpa,
60 | src,
61 | if (std.mem.endsWith(u8, path, ".xml")) .xml else .superhtml,
62 | );
63 | if (t.html_ast.errors.len > 0) return;
64 |
65 | t.ast = try .init(gpa, t.html_ast, src);
66 |
67 | if (t.ast.errors.len == 0 and t.ast.extends_idx != 0) {
68 | const parent_name = t.ast.nodes[t.ast.extends_idx].templateValue().span.slice(src);
69 | const parent_path = try root.join(arena, &.{ "templates", parent_name }, '/');
70 | const parent_pn = PathName.get(&build.st, &build.pt, parent_path) orelse {
71 | t.missing_parent = true;
72 | return;
73 | };
74 | if (!build.templates.contains(parent_pn)) {
75 | t.missing_parent = true;
76 | return;
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/channel.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 |
3 | pub fn Channel(comptime T: type) type {
4 | return struct {
5 | lock: std.Thread.Mutex = .{},
6 | fifo: Fifo,
7 | writeable: std.Thread.Condition = .{},
8 | readable: std.Thread.Condition = .{},
9 |
10 | const Fifo = std.fifo.LinearFifo(T, .Slice);
11 | const Self = @This();
12 |
13 | pub fn init(buffer: []T) Self {
14 | return Self{ .fifo = Fifo.init(buffer) };
15 | }
16 |
17 | pub fn put(self: *Self, item: T) void {
18 | self.lock.lock();
19 | defer {
20 | self.lock.unlock();
21 | self.readable.signal();
22 | }
23 |
24 | while (true) return self.fifo.writeItem(item) catch {
25 | self.writeable.wait(&self.lock);
26 | continue;
27 | };
28 | }
29 |
30 | pub fn tryPut(self: *Self, item: T) !void {
31 | self.lock.lock();
32 | defer self.lock.unlock();
33 |
34 | try self.fifo.writeItem(item);
35 |
36 | // only signal on success
37 | self.readable.signal();
38 | }
39 |
40 | pub fn get(self: *Self) T {
41 | self.lock.lock();
42 | defer {
43 | self.lock.unlock();
44 | self.writeable.signal();
45 | }
46 |
47 | while (true) return self.fifo.readItem() orelse {
48 | self.readable.wait(&self.lock);
49 | continue;
50 | };
51 | }
52 |
53 | pub fn getOrNull(self: *Self) ?T {
54 | self.lock.lock();
55 | defer self.lock.unlock();
56 |
57 | if (self.fifo.readItem()) |item| return item;
58 |
59 | // signal on empty queue
60 | self.writeable.signal();
61 | }
62 | };
63 | }
64 |
--------------------------------------------------------------------------------
/src/cli/init.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const builtin = @import("builtin");
3 | const tracy = @import("tracy");
4 | const fatal = @import("../fatal.zig");
5 | const Allocator = std.mem.Allocator;
6 |
7 | const log = std.log.scoped(.init);
8 |
9 | pub fn init(gpa: Allocator, args: []const []const u8) bool {
10 | _ = gpa;
11 |
12 | const cmd: Command = .parse(args);
13 | if (cmd.multilingual) @panic("TODO: multilingual init");
14 |
15 | const File = struct { path: []const u8, src: []const u8 };
16 | const files = [_]File{
17 | .{
18 | .path = "zine.ziggy",
19 | .src = @embedFile("init/zine.ziggy"),
20 | },
21 | .{
22 | .path = "content/index.smd",
23 | .src = @embedFile("init/content/index.smd"),
24 | },
25 | .{
26 | .path = "content/about.smd",
27 | .src = @embedFile("init/content/about.smd"),
28 | },
29 | .{
30 | .path = "content/blog/index.smd",
31 | .src = @embedFile("init/content/blog/index.smd"),
32 | },
33 | .{
34 | .path = "content/blog/first-post/index.smd",
35 | .src = @embedFile("init/content/blog/first-post/index.smd"),
36 | },
37 | .{
38 | .path = "content/blog/first-post/fanzine.jpg",
39 | .src = @embedFile("init/content/blog/first-post/fanzine.jpg"),
40 | },
41 | .{
42 | .path = "content/blog/second-post.smd",
43 | .src = @embedFile("init/content/blog/second-post.smd"),
44 | },
45 | .{
46 | .path = "content/devlog/index.smd",
47 | .src = @embedFile("init/content/devlog/index.smd"),
48 | },
49 | .{
50 | .path = "content/devlog/1990.smd",
51 | .src = @embedFile("init/content/devlog/1990.smd"),
52 | },
53 | .{
54 | .path = "content/devlog/1989.smd",
55 | .src = @embedFile("init/content/devlog/1989.smd"),
56 | },
57 | .{
58 | .path = "layouts/index.shtml",
59 | .src = @embedFile("init/layouts/index.shtml"),
60 | },
61 | .{
62 | .path = "layouts/page.shtml",
63 | .src = @embedFile("init/layouts/page.shtml"),
64 | },
65 | .{
66 | .path = "layouts/post.shtml",
67 | .src = @embedFile("init/layouts/post.shtml"),
68 | },
69 | .{
70 | .path = "layouts/blog.shtml",
71 | .src = @embedFile("init/layouts/blog.shtml"),
72 | },
73 | .{
74 | .path = "layouts/blog.xml",
75 | .src = @embedFile("init/layouts/blog.xml"),
76 | },
77 | .{
78 | .path = "layouts/devlog.shtml",
79 | .src = @embedFile("init/layouts/devlog.shtml"),
80 | },
81 | .{
82 | .path = "layouts/devlog.xml",
83 | .src = @embedFile("init/layouts/devlog.xml"),
84 | },
85 | .{
86 | .path = "layouts/devlog-archive.shtml",
87 | .src = @embedFile("init/layouts/devlog-archive.shtml"),
88 | },
89 | .{
90 | .path = "layouts/templates/base.shtml",
91 | .src = @embedFile("init/layouts/templates/base.shtml"),
92 | },
93 | .{
94 | .path = "assets/style.css",
95 | .src = @embedFile("init/assets/style.css"),
96 | },
97 | .{
98 | .path = "assets/highlight.css",
99 | .src = @embedFile("init/assets/highlight.css"),
100 | },
101 | .{
102 | .path = "assets/under-construction.gif",
103 | .src = @embedFile("init/assets/under-construction.gif"),
104 | },
105 |
106 | .{
107 | .path = "assets/render-mathtex.js",
108 | .src = @embedFile("init/assets/render-mathtex.js"),
109 | },
110 | .{
111 | .path = "assets/Temml-Local.css",
112 | .src = @embedFile("init/assets/Temml-Local.css"),
113 | },
114 | .{
115 | .path = "assets/Temml.woff2",
116 | .src = @embedFile("init/assets/Temml.woff2"),
117 | },
118 | .{
119 | .path = "assets/temml.min.js",
120 | .src = @embedFile("init/assets/temml.min.js"),
121 | },
122 | };
123 |
124 | for (files) |file| {
125 | const dirname = std.fs.path.dirnamePosix(file.path);
126 | const basename = std.fs.path.basenamePosix(file.path);
127 |
128 | const base_dir = if (dirname) |dn|
129 | std.fs.cwd().makeOpenPath(dn, .{}) catch |err| fatal.dir(dn, err)
130 | else
131 | std.fs.cwd();
132 |
133 | const f = base_dir.createFile(basename, .{
134 | .exclusive = true,
135 | }) catch |err| switch (err) {
136 | else => fatal.file(basename, err),
137 | error.PathAlreadyExists => {
138 | std.debug.print(
139 | "WARNING: '{s}' already exists, skipping.\n",
140 | .{file.path},
141 | );
142 | continue;
143 | },
144 | };
145 | std.debug.print("Created: {s}\n", .{file.path});
146 | f.writeAll(file.src) catch |err| fatal.file(file.path, err);
147 | }
148 |
149 | std.debug.print(
150 | \\
151 | \\Run `zine` to run the Zine development server.
152 | \\Run `zine release` to build your website in 'public/'.
153 | \\Run `zine help` for more commands and options.
154 | \\
155 | \\Read https://zine-ssg.io/docs/ to learn more about Zine.
156 | \\
157 | , .{});
158 |
159 | return false;
160 | }
161 |
162 | const Command = struct {
163 | multilingual: bool,
164 | fn parse(args: []const []const u8) Command {
165 | var multilingual: ?bool = null;
166 | for (args) |a| {
167 | if (std.mem.eql(u8, a, "--multilingual")) {
168 | multilingual = true;
169 | }
170 |
171 | if (std.mem.eql(u8, a, "-h") or std.mem.eql(u8, a, "--help")) {
172 | fatal.msg(
173 | \\Usage: zine init [OPTIONS]
174 | \\
175 | \\Command specific options:
176 | \\ --multilingual Setup a sample multilingual website
177 | \\
178 | \\General Options:
179 | \\ --help, -h Print command specific usage
180 | \\
181 | \\
182 | , .{});
183 | }
184 | }
185 |
186 | return .{ .multilingual = multilingual orelse false };
187 | }
188 | };
189 |
--------------------------------------------------------------------------------
/src/cli/init/assets/Temml.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/zine/aa43d44c2d271cb309549e8d7eab0a7fe6bb06f2/src/cli/init/assets/Temml.woff2
--------------------------------------------------------------------------------
/src/cli/init/assets/highlight.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --light-yellow: #e5c07b;
3 | --dark-yellow: #d19a66;
4 | --blue: #61afef;
5 | --cyan: #56b6c2;
6 | --light-red: #e06c75;
7 | --dark-red: #be5046;
8 | --comment-gray: #5c6370;
9 | --magenta: #c678dd;
10 | }
11 |
12 | pre {
13 | border-top: 1px solid white;
14 | border-bottom: 1px solid white;
15 | padding: 10px 5px;
16 | }
17 |
18 | code.ziggy {
19 | color: var(--cyan);
20 | }
21 |
22 | code.ziggy .keyword,
23 | code.ziggy .type {
24 | color: var(--light-yellow);
25 | }
26 |
27 | code.ziggy .string {
28 | color: var(--dark-yellow);
29 | }
30 |
31 | code.ziggy .numeric.constant {
32 | color: var(--magenta);
33 | }
34 |
35 | code.ziggy .function {
36 | color: var(--blue);
37 | }
--------------------------------------------------------------------------------
/src/cli/init/assets/render-mathtex.js:
--------------------------------------------------------------------------------
1 | let eqns = document.querySelectorAll("script[type='math/tex']");
2 | for (let i=eqns.length-1; i>=0; i--) {
3 | let eqn = eqns[i];
4 | let src = eqn.text;
5 | let d = eqn.closest('p') == null;
6 | eqn.outerHTML = temml.renderToString(src, { displayMode: d });
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/src/cli/init/assets/style.css:
--------------------------------------------------------------------------------
1 | h1,
2 | h2,
3 | h3,
4 | h4,
5 | h5 {
6 | color: #ddd;
7 | font-family: "Verdana", sans-serif;
8 | }
9 |
10 | b,
11 | strong {
12 | color: #fff;
13 | }
14 |
15 | a {
16 | color: #eee;
17 | }
18 |
19 | html {
20 | color: #ccc;
21 | font-family: "Georgia", serif;
22 | font-size: 1.2em;
23 | display: flex;
24 | flex-direction: row;
25 | justify-content: center;
26 | background-color: #111;
27 | }
28 |
29 | body {
30 | width: 800px;
31 | padding: 15px;
32 | display: flex;
33 | flex-direction: column;
34 | }
35 |
36 | .site-title {
37 | font-size: 2.5em;
38 | margin-bottom: 10px;
39 | /* text-align: center; */
40 | }
41 |
42 | nav {
43 | display: flex;
44 | flex-direction: row;
45 | justify-content: left;
46 | font-size: 1.2em;
47 | margin-bottom: 20px;
48 | }
49 |
50 | .block {
51 | border: 1px dotted white;
52 | padding: 5px 15px;
53 | margin: 0 10px;
54 | text-align: center;
55 | display: flex;
56 | flex-direction: column;
57 | justify-content: center;
58 | }
59 |
60 | .block h1 {
61 | font-size: 1em;
62 | text-align: center;
63 | margin-bottom: 0;
64 | }
65 |
66 | .small {
67 | font-size: 0.8em;
68 | }
69 |
70 | .wave {
71 | background: #111;
72 | color: #fff;
73 | text-shadow: 1px 1px 10px #fff, 1px 1px 10px #ccc;
74 | }
75 |
76 | footer {
77 | margin-top: 30px;
78 | display: flex;
79 | flex-direction: column;
80 | align-items: center;
81 | }
82 |
83 | footer hr {
84 | width: 100%;
85 | }
--------------------------------------------------------------------------------
/src/cli/init/assets/under-construction.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/zine/aa43d44c2d271cb309549e8d7eab0a7fe6bb06f2/src/cli/init/assets/under-construction.gif
--------------------------------------------------------------------------------
/src/cli/init/content/about.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "About",
3 | .date = @date("1990-01-01T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | ---
8 |
9 | ## About Zine
10 | Zine is an MIT-licensed project created by [Loris Cro](https://kristoff.it) and
11 | other contributors listed on the [official repository](https://github.com/kristoff-it/zine/contributors).
12 |
13 | Zine is inspired by [Hugo](https://gohugo.io) but features an entirely custom set
14 | of authoring languages:
15 |
16 | - [Scripty](https://zine-ssg.io/docs/scripty/) is the small expression
17 | language that both SuperHTML and SuperMD share to express templating logic.
18 |
19 | - [SuperHTML](https://zine-ssg.io/docs/superhtml/) is the HTML templating
20 | language used by Zine. Unlike most `{{curly braced}}` templating languages,
21 | SuperHTML uses valid HTML syntax to express the templating logic, adding only
22 | minor extensions to normal HTML.
23 | Thanks to this approach, it offers instant
24 | syntax checking and autoformatting via [a CLI tool](https://github.com/kristoff-it/superhtml) as well as Language Server support ([VScode Extension](https://marketplace.visualstudio.com/items?itemName=LorisCro.super)).
25 |
26 | ># [NOTE]($block)
27 | >The correct file extension for SuperHTML templates is `.shtml`.
28 |
29 | - [SuperMD](https://zine-ssg.io/docs/supermd/) is a superset of Markdown
30 | that, instead of relying on inline HTML, offers new constructs for expressing
31 | content embeds without pulling into your content needless layouting concerns.
32 | A CLI tool and language server for SuperMD is in the works.
33 |
34 | ># [NOTE]($block)
35 | >The correct file extension for SuperMD pages is `.smd`.
36 |
37 | ## Zine is alpha software
38 |
39 | Zine is not yet complete. The main functionality is present and you will be able
40 | to build even moderately complex static websites without issue.
41 |
42 | That said using Zine today does imply participating in the development process
43 | to some degree, which usually means inquiring about the development status of
44 | a feature you need, or reporting a bug.
45 |
46 |
47 | Here are some quicklinks related to Zine:
48 |
49 | - Official Website: https://zine-ssg.io/
50 | - Source Code: https://github.com/kristoff-it/zine/
51 | - Discord: https://discord.com/invite/B73sGxF
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/cli/init/content/blog/first-post/fanzine.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/zine/aa43d44c2d271cb309549e8d7eab0a7fe6bb06f2/src/cli/init/content/blog/first-post/fanzine.jpg
--------------------------------------------------------------------------------
/src/cli/init/content/blog/first-post/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "First Post: What's A Zine?",
3 | .date = @date("1990-01-01T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "post.shtml",
6 | .draft = false,
7 | ---
8 |
9 | This is a sample first post for your blog.
10 |
11 | This post is defined in `content/blog/first-post/` and contains the following
12 | files:
13 |
14 | - `index.smd`
15 | - `fanzine.jpg`
16 |
17 | Another interesting thing about this post is that it uses the `layouts/post.shtml`
18 | template which adds at the bottom page navigation within the blog section.
19 |
20 | Enough about Zine, let's talk about zines.
21 |
22 | ## Fanzines
23 |
24 | A zine (short for *fanzine*, from "fan" + "magazine") is a non-professional
25 | publication created by people that want to express themselves in paper form,
26 | usually in relation to a cultural phenomenon of some kind.
27 |
28 | [An example of zine from 1976.](<$image.asset("fanzine.jpg").alt("A photo of a black and white flyer. The main title reads 'A night of pure energy' and the rest of the page contains a mix of pictures of guitarists interleaved with other text snippets, some seemingly taken from a professional publication of some kind, and some handwritten to announce concert night dates, presumably for the musicians in the picture.")>)
29 |
30 | ## Zines in the digital era
31 |
32 | In the digital age, some of the cultural impetus behind zines got redirected to
33 | personal blogs and similar digital, non-professional publications. The 90's and
34 | early 00's are famous for their whacky websites full of clip art, wordart text
35 | and "under construction" animated gifs.
36 |
37 | This initial organic exploration didn't last for too long as the rise of social
38 | media diverted a lot of self-expression energy towards walled gardens, a change
39 | that was also fueled by the much tighter and intense feedback loop that those
40 | platform enable.
41 |
42 | In the 90's the stongest dopamine hit you could get was adding a visit counter
43 | to your altavista website and watch it go up, slowly, mostly because of your own
44 | accesses. In modern times views are almost meaningless and interactions such as
45 | *likes*, *retweets* and *comments* provide much stronger positive feedback.
46 |
47 | An unfortunate side effect of this new cultural wave centered around social media
48 | is that not only you end up gifting your content to platform owners, but you also
49 | participate in a system where the *language* of the social media site shapes your
50 | thoughts and experience in specific, and often user-hostile, ways.
51 |
52 | Art, just like liquids, takes the shape of the container you put it in. A mobile
53 | game that lives off of in app purchases will never be truly great because of the
54 | tension between making the game entertaining enough to keep players engaged, and
55 | the need to make it boring enough so that they will want to buy upgrades to make
56 | the game more fun.
57 |
58 | Similarly, self-expression on Twitter is encouraged to take the shape of short,
59 | hyperbolic hot takes that forgo nuance in order to create catchy quips that can
60 | be used for hasty decision making.
61 |
62 | Likewise, [corpowave]($text.attrs('wave')) never goes out of style on LinkedIn.
63 | Spend enough time in there and you will become a character from Severance.
64 |
65 | ## We're not in 1990 anymore
66 |
67 | Despite all the issues with social media, there is no point in thinking of the
68 | 90's as a better time. It was not. And despite the winks at the past, Zine is
69 | not a tool for indulging in nostalgia.
70 |
71 | **The goal is to make art**: the act of inducing a change in others through
72 | our self-expression.
73 |
74 | You could argue that the 90's excelled at self-expression, but in doing so you
75 | would also have to accept that social media is infinitely more effective at
76 | inducing change in others (albeit at the expense of freedom of expression).
77 |
78 | Once you realize that, the path forward is clear:
79 |
80 | 1. Own your content.
81 | 2. Create new social systems that optimize for creating art over engagement.
82 |
83 | Owning your content means that you will be unaffected by enshittification of
84 | platforms that would otherwise keep your data hostage. It also is the single
85 | most effective thing you can do as an individual to take power away from
86 | platforms, all while protecting your own immediate interests.
87 |
88 | Creation of new social systems is a *slightly more hairy* problem than self
89 | hosting a static website, but it's something that can be done. Over the years
90 | we've had plenty of social outlets that have allowed people to socialize through
91 | their homemade games, music, drawings, fanfics, etc; and chances are that we
92 | have yet many more of these outlets ahead of us to create.
93 |
94 | Zine gives you a small puzzle piece to help you inch closer toward a better
95 | future, partially by providing you with a new iteration over tried and true
96 | patterns (e.g. by facilitating content creation by separating content from
97 | layouting concerns as much as possible), and also by being a bit experimental
98 | with the concept of a devlog, something that you wouldn't normally expect to
99 | find on a static website.
100 |
101 | Lastly, Zine makes sure your content (both blog and devlog, but also any
102 | other content format you might come up with yourself) is available via RSS
103 | syndication. RSS feeds are far from a winning technology in the fight against
104 | the ebb and *enshitty*flow of social media, but they are another small puzzle
105 | piece that costs nothing to maintain and that might turn out to be critical once
106 | enough other preconditions are met.
107 |
108 | With that in mind, **go make art with your words**.
109 |
110 | -- Loris
111 |
--------------------------------------------------------------------------------
/src/cli/init/content/blog/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Blog",
3 | .date = @date("1990-01-01T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "blog.shtml",
6 | .alternatives = [{
7 | .name = "rss",
8 | .layout = "blog.xml",
9 | .output = "index.xml",
10 | }],
11 | .draft = false,
12 | ---
13 |
14 | This page defines the blog section and lists all posts in it.
15 |
16 | A "site section" in Zine is a group of pages that form a logical subtree of the
17 | website. It's related to directory structure, but it's not an entirely 1:1 mapping.
18 |
19 | What defines a site section in Zine is the presence of `index.smd` files. You
20 | can learn more [in the official Zine docs](https://zine-ssg.io/docs/).
21 |
22 | Take also a look at `layouts/blog.shtml` to get an idea of how to render a page
23 | list in a SuperHTML template.
24 |
25 | The blog section also has an [RSS feed]($link.alternative('rss')).
26 |
27 | In Zine, RSS feeds are considered "alternative" versions of an existing page. In
28 | concrete defines the blog section and that lists all pages in it, is rendered in
29 | two versions: HTML for human readers, and XML for RSS readers.
30 |
31 | This is the SuperMD frontmatter code that defines the RSS feed:
32 |
33 | ```ziggy
34 | .alternatives = [{
35 | .name = "rss",
36 | .layout = "rss.xml",
37 | .output = "index.xml",
38 | }],
39 | ```
40 | [(btw syntax highlighting is done statically in Zine, no need for javascript libraries, unless you want to)]($text.attrs('small'))
41 |
--------------------------------------------------------------------------------
/src/cli/init/content/blog/second-post.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Second Post",
3 | .date = @date("1990-01-02T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "post.shtml",
6 | .draft = false,
7 | ---
8 |
9 | This second post is mainly here to show you that you can also create single file
10 | posts for convenience. The first post contains more interesting content.
11 |
12 | Don't forget to read [the official SuperMD
13 | docs](https://zine-ssg.io/docs/supermd/) to know how to *style* your content.
14 |
15 |
16 | Btw this sample website also includes the JS/CSS dependencies required to render
17 | math:
18 |
19 | ```=mathtex
20 | \begin{aligned}
21 | f(t) &= \int_{-\infty}^\infty F(\omega) \cdot (-1)^{2 \omega t} \mathrm{d}\omega \\
22 | F(\omega) &= \int_{-\infty}^\infty f(t) \div (-1)^{2 \omega t} \mathrm{d}t \\
23 | \end{aligned}
24 | ```
25 |
26 | This: [`(-1)^x = \cos(\pi x) + i\sin(\pi x)`]($mathtex) is an inline equation
27 | instead!
28 |
29 |
--------------------------------------------------------------------------------
/src/cli/init/content/devlog/1989.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Devlog - 1989",
3 | .date = @date("1989-01-01T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "devlog.shtml",
6 | .draft = false,
7 | ---
8 | []($section.id('about'))
9 | ## About this Devlog
10 | This is a non-exhaustive, curated list of changes meant to help users quickly see what has improved since they last checked.
11 |
12 | You can [subscribe the latest devlog via RSS]($link.page('devlog').alternative('rss')).
13 |
14 | This page lists entries for the year 1989, for past or future entries consult the
15 | [devlog archive](/devlog/).
16 |
17 | ## [Hello 1989]($section.id("1989-01-01T00:00:00"))
18 | This is a sample entry.
19 |
--------------------------------------------------------------------------------
/src/cli/init/content/devlog/1990.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Devlog - 1990",
3 | .date = @date("1990-01-01T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "devlog.shtml",
6 | .draft = false,
7 | ---
8 | []($section.id('about'))
9 | ## About this Devlog
10 |
11 | This is where you should describe to your users what your devlog is about.
12 | Refer to the corresponding section in the home page to learn more about devlogs.
13 |
14 | For example this is how the Zine devlog describes itself:
15 |
16 | > This is a non-exhaustive, curated list of changes meant to help users quickly see what has improved since they last checked.
17 | >
18 | > You can [subscribe to this page via RSS]($link.page('devlog').alternative('rss')).
19 | >
20 | > This page lists entries for the current year, for past entries consult the
21 | [devlog archive](/devlog/).
22 |
23 | Feel free to tweak it to your specific use case or replace it entirely as you
24 | see fit.
25 |
26 | Regardless of what you decide, you might want to make sure you preserve the
27 | links to the RSS feed and to the devlog archive from the copy above.
28 |
29 |
30 | ## [Third Entry]($section.id("1990-01-03T00:00:00"))
31 | This is the third entry.
32 |
33 | ## [Second Entry]($section.id("1990-01-02T00:00:00"))
34 | This is the second entry.
35 |
36 | ## [Hello Zine]($section.id("1990-01-01T00:00:00"))
37 | This is the first entry in this year's devlog created with
38 | [Zine](https://zine-ssg.io)!
39 |
--------------------------------------------------------------------------------
/src/cli/init/content/devlog/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Devlog Archive",
3 | .date = @date("1990-01-01T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "devlog-archive.shtml",
6 | .alternatives = [{
7 | .name = "rss",
8 | .layout = "devlog.xml",
9 | .output = "index.xml",
10 | }],
11 | .draft = false,
12 | ---
13 |
14 | This page lists all the devlog years that are present on this site.
15 | Note how the top navigation menu doesn't link to this page, but instead links
16 | to the latest devlog year directly.
17 |
18 | The reason for adopting this structure is to make sure that links that exist in
19 | the wild to entries to our devlog don't become invalidated when a new year comes
20 | around and we rotate the devlog feed.
21 |
22 | Devlog rotation ensures that you don't have a single page that grows
23 | indefinitely, eventually compromising your editing expreience and worsening your
24 | user's browsing experience. You can use any arbitrary policy (even deleting old
25 | entries if you are fine with having a more ephemeral devlog), but cutting them
26 | by year is a good default option.
27 |
28 | When rotating a devlog all you have to do is create a new page with a newer
29 | date set in the frontmatter `date` field, and update the page description to let
30 | people know that this page is not the current year's devlog anymore.
31 |
32 | The latest devlog year is also available [via RSS feed]($link.alternative('rss')).
33 |
--------------------------------------------------------------------------------
/src/cli/init/content/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Homepage",
3 | .date = @date("1990-01-01T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | ---
8 |
9 | This sample website showcases:
10 |
11 | - A couple simple pages
12 | - `content/index.smd`
13 | - `content/about.smd`
14 | - A blog
15 | - `content/blog/index.smd`
16 | - `content/blog/first-post/index.smd`
17 | - `content/blog/second-post.smd`
18 | - A devlog
19 | - `content/devlog/index.smd`
20 | - `content/devlog/1990.smd`
21 | - `content/devlog/1989.smd`
22 |
23 |
24 |
25 | ## About Devlogs
26 |
27 | While a blog has each entry be a separate page, a devlog is one single page with
28 | a list of smaller entries in it, making it a form of microblogging. The relative
29 | RSS feed will generate a separate item per devlog entry, creating a
30 | "twitter-like" feed.
31 |
32 | This can be a useful pattern for thoughts that are too small for a full blog post
33 | so consider giving it a try!
34 |
35 | The name "devlog" comes from the fact that this kind of microblogging feed works
36 | well when you have an open source project and you want to give small updates to
37 | your users, but it might work equally well for other domains, depending on your
38 | interests and audience. Maybe a "foodlog", a "catlog" or a "treklog" could also
39 | work well.
40 |
41 | The devlog section contains more information about how devlogs can be implemented
42 | in Zine.
43 |
44 | Some examples of devlogs in the wild:
45 | - https://zine-ssg.io/log/
46 | - https://ziglang.org/devlog/
47 |
48 |
49 | ## Next steps
50 |
51 | Make sure to read the [official Zine docs](https://zine-ssg.io/docs/)
52 | and then start editing this website!
53 |
54 | Start by putting the correct information in `zine.ziggy` and then start editing
55 | the existing pages. Before deleting existing copy consider giving it a brief
56 | look as it will show you some SuperMD specific syntax.
57 |
58 | HTML markup in Zine is defined via SuperHTML templates:
59 | - `layouts/index.shtml`
60 | - `layouts/page.shtml`
61 | - `layouts/post.shtml`
62 | - `layouts/blog.shtml`
63 | - `layouts/blog.xml`
64 | - `layouts/devlog.shtml`
65 | - `layouts/devlog.xml`
66 | - `layouts/devlog-archive.shtml`
67 | - `layouts/templates/base.shtml`
68 |
69 | **If you're running the Zine development server (by running `zine`), then all
70 | changes you make will be picked up immediately, causing the website to rebuild
71 | and the page in your browser to refresh**.
72 |
73 | You can learn more about SuperMD and SuperHTML in [/about/](/about/).
74 |
75 | Lastly, this sample website also includes the following asset files:
76 |
77 | - `assets/style.css`
78 | - `assets/hightlight.css`
79 | - `assets/under-construction.gif`
80 | - `assets/katex-tag.js`
81 | - `assets/katex0.16.21.css`
82 | - `assets/katex0.16.21.js`
83 | - `content/blog/first-post/fanzine.jpg`
84 |
85 | The first few asset files are **site assets**, while the last is a **page asset** that belongs to the "first post" page.
86 |
87 | The Zine docs contain more information about [dealing with assets](https://zine-ssg.io/docs/assets/).
88 |
89 |
--------------------------------------------------------------------------------
/src/cli/init/layouts/blog.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 |
15 |
16 |
25 |
--------------------------------------------------------------------------------
/src/cli/init/layouts/blog.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Zine -- https://zine-ssg.io
7 | en-US
8 |
9 |
10 | -
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/cli/init/layouts/devlog-archive.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 |
15 |
16 |
17 |
Past years
18 |
24 |
25 |
--------------------------------------------------------------------------------
/src/cli/init/layouts/devlog.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
34 |
35 |
36 |
37 |
38 |
45 |
--------------------------------------------------------------------------------
/src/cli/init/layouts/devlog.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Zine -- https://zine-ssg.io
7 | en-US
8 |
9 |
10 |
11 | -
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/cli/init/layouts/index.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/cli/init/layouts/page.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/cli/init/layouts/post.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
22 |
23 |
24 |
25 |
26 |
40 |
--------------------------------------------------------------------------------
/src/cli/init/layouts/templates/base.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
29 |
30 |
35 |
36 |
--------------------------------------------------------------------------------
/src/cli/init/zine.ziggy:
--------------------------------------------------------------------------------
1 | Site {
2 | .title = "Welcome to Zine!",
3 | .host_url = "https://example.com",
4 | .content_dir_path = "content",
5 | .layouts_dir_path = "layouts",
6 | .assets_dir_path = "assets",
7 | .static_assets = [
8 | "Temml.woff2",
9 | ],
10 | }
11 |
--------------------------------------------------------------------------------
/src/cli/release.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const builtin = @import("builtin");
3 | const tracy = @import("tracy");
4 | const fatal = @import("../fatal.zig");
5 | const root = @import("../root.zig");
6 | const worker = @import("../worker.zig");
7 | const Allocator = std.mem.Allocator;
8 | const BuildAsset = root.BuildAsset;
9 |
10 | pub fn release(gpa: Allocator, args: []const []const u8) bool {
11 | errdefer |err| switch (err) {
12 | error.OutOfMemory => fatal.oom(),
13 | };
14 |
15 | const cmd: Command = try .parse(gpa, args);
16 | const cfg, const base_dir_path = root.Config.load(gpa);
17 |
18 | worker.start();
19 | defer if (builtin.mode == .Debug) worker.stopWaitAndDeinit();
20 |
21 | const build = root.run(gpa, &cfg, .{
22 | .base_dir_path = base_dir_path,
23 | .build_assets = &cmd.build_assets,
24 | .drafts = cmd.drafts,
25 | .mode = .{
26 | .disk = .{
27 | .output_dir_path = cmd.output_dir_path,
28 | },
29 | },
30 | });
31 |
32 | defer if (builtin.mode == .Debug) build.deinit(gpa);
33 |
34 | if (tracy.enable) {
35 | tracy.frameMarkNamed("waiting for tracy");
36 | var progress_tracy = root.progress.start("Tracy", 0);
37 | std.Thread.sleep(100 * std.time.ns_per_ms);
38 | progress_tracy.end();
39 | }
40 |
41 | if (build.any_prerendering_error or
42 | build.any_rendering_error.load(.acquire))
43 | {
44 | return true;
45 | }
46 |
47 | return false;
48 | }
49 |
50 | pub const Command = struct {
51 | output_dir_path: ?[]const u8,
52 | build_assets: std.StringArrayHashMapUnmanaged(BuildAsset),
53 | drafts: bool,
54 |
55 | pub fn deinit(co: *const Command, gpa: Allocator) void {
56 | var ba = co.build_assets;
57 | ba.deinit(gpa);
58 | }
59 |
60 | pub fn parse(gpa: Allocator, args: []const []const u8) !Command {
61 | var output_dir_path: ?[]const u8 = null;
62 | var build_assets: std.StringArrayHashMapUnmanaged(BuildAsset) = .empty;
63 | var drafts = false;
64 |
65 | const eql = std.mem.eql;
66 | const startsWith = std.mem.startsWith;
67 | var idx: usize = 0;
68 | while (idx < args.len) : (idx += 1) {
69 | const arg = args[idx];
70 | if (eql(u8, arg, "-h") or eql(u8, arg, "--help")) {
71 | fatal.msg(help_message, .{});
72 | } else if (eql(u8, arg, "-i") or eql(u8, arg, "--install")) {
73 | idx += 1;
74 | if (idx >= args.len) fatal.msg(
75 | "error: missing argument to '{s}'",
76 | .{arg},
77 | );
78 | output_dir_path = args[idx];
79 | } else if (startsWith(u8, arg, "--install=")) {
80 | output_dir_path = arg["--install=".len..];
81 | } else if (startsWith(u8, arg, "--build-asset=")) {
82 | const name = arg["--build-asset=".len..];
83 |
84 | idx += 1;
85 | if (idx >= args.len) fatal.msg(
86 | "error: missing build asset sub-argument for '{s}'",
87 | .{name},
88 | );
89 |
90 | const input_path = args[idx];
91 |
92 | idx += 1;
93 | var output_path: ?[]const u8 = null;
94 | var output_always = false;
95 | if (idx < args.len) {
96 | const next = args[idx];
97 | if (startsWith(u8, next, "--output=")) {
98 | output_path = next["--output=".len..];
99 | } else if (startsWith(u8, next, "--output-always=")) {
100 | output_always = true;
101 | output_path = next["--output-always=".len..];
102 | } else {
103 | idx -= 1;
104 | }
105 | }
106 |
107 | const gop = try build_assets.getOrPut(gpa, name);
108 | if (gop.found_existing) fatal.msg(
109 | "error: duplicate build asset name '{s}'",
110 | .{name},
111 | );
112 |
113 | gop.value_ptr.* = .{
114 | .input_path = input_path,
115 | .output_path = output_path,
116 | .output_always = output_always,
117 | .rc = .{ .raw = @intFromBool(output_always) },
118 | };
119 | } else if (eql(u8, arg, "--drafts")) {
120 | drafts = true;
121 | } else {
122 | fatal.msg("error: unexpected cli argument '{s}'\n", .{arg});
123 | }
124 | }
125 |
126 | return .{
127 | .output_dir_path = output_dir_path,
128 | .build_assets = build_assets,
129 | .drafts = drafts,
130 | };
131 | }
132 | };
133 |
134 | const help_message =
135 | \\Usage: zine release [OPTIONS]
136 | \\
137 | \\Command specific options:
138 | \\ --install DIR Directory where to install the website (default 'public/')
139 | // \\ --build-assets FILE Path to a file containing a list of build assets
140 | \\ --help, -h Show this help menu
141 | \\
142 | \\
143 | ;
144 |
--------------------------------------------------------------------------------
/src/cli/serve/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 404 not found
8 | {s}
9 |
10 |
--------------------------------------------------------------------------------
/src/cli/serve/error.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | This page contains errors!
8 | {s}
9 | This page should show the exact errors in an overlay.
10 | If that doesn't work for some reason, the terminal will contain all the info you need.
11 |
12 |
--------------------------------------------------------------------------------
/src/cli/serve/outside.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Error
8 | Outside of the url path prefix!
9 | This website defines a url_path_prefix set to '{0s}' and you are outside of it.
10 | Click here to navigate to the url path prefix.
11 |
12 |
--------------------------------------------------------------------------------
/src/cli/serve/watcher/MacosWatcher.zig:
--------------------------------------------------------------------------------
1 | const MacosWatcher = @This();
2 |
3 | const std = @import("std");
4 | const fatal = @import("../../../fatal.zig");
5 | const Debouncer = @import("../../serve.zig").Debouncer;
6 |
7 | const c = @cImport({
8 | @cInclude("CoreServices/CoreServices.h");
9 | });
10 |
11 | const log = std.log.scoped(.watcher);
12 |
13 | gpa: std.mem.Allocator,
14 | debouncer: *Debouncer,
15 | dir_paths: []const []const u8,
16 |
17 | pub fn init(
18 | gpa: std.mem.Allocator,
19 | debouncer: *Debouncer,
20 | dir_paths: []const []const u8,
21 | ) MacosWatcher {
22 | return .{
23 | .gpa = gpa,
24 | .debouncer = debouncer,
25 | .dir_paths = dir_paths,
26 | };
27 | }
28 |
29 | pub fn start(watcher: *MacosWatcher) !void {
30 | const t = try std.Thread.spawn(.{}, MacosWatcher.listen, .{watcher});
31 | t.detach();
32 | }
33 |
34 | pub fn listen(watcher: *MacosWatcher) void {
35 | errdefer |err| switch (err) {
36 | error.OutOfMemory => fatal.oom(),
37 | };
38 |
39 | const macos_paths = try watcher.gpa.alloc(
40 | c.CFStringRef,
41 | watcher.dir_paths.len,
42 | );
43 | defer watcher.gpa.free(macos_paths);
44 |
45 | for (watcher.dir_paths, macos_paths) |str, *ref| {
46 | ref.* = c.CFStringCreateWithCString(
47 | null,
48 | str.ptr,
49 | c.kCFStringEncodingUTF8,
50 | );
51 | }
52 |
53 | const paths_to_watch: c.CFArrayRef = c.CFArrayCreate(
54 | null,
55 | @ptrCast(macos_paths.ptr),
56 | @intCast(macos_paths.len),
57 | null,
58 | );
59 |
60 | var stream_context: c.FSEventStreamContext = .{ .info = watcher };
61 | const stream: c.FSEventStreamRef = c.FSEventStreamCreate(
62 | null,
63 | &macosCallback,
64 | &stream_context,
65 | paths_to_watch,
66 | c.kFSEventStreamEventIdSinceNow,
67 | 0.05,
68 | c.kFSEventStreamCreateFlagFileEvents,
69 | );
70 |
71 | c.FSEventStreamScheduleWithRunLoop(
72 | stream,
73 | c.CFRunLoopGetCurrent(),
74 | c.kCFRunLoopDefaultMode,
75 | );
76 |
77 | if (c.FSEventStreamStart(stream) == 0) {
78 | fatal.msg("error: macos watcher FSEventStreamStart failed", .{});
79 | }
80 |
81 | c.CFRunLoopRun();
82 |
83 | c.FSEventStreamStop(stream);
84 | c.FSEventStreamInvalidate(stream);
85 | c.FSEventStreamRelease(stream);
86 |
87 | c.CFRelease(paths_to_watch);
88 | }
89 |
90 | pub fn macosCallback(
91 | streamRef: c.ConstFSEventStreamRef,
92 | clientCallBackInfo: ?*anyopaque,
93 | numEvents: usize,
94 | eventPaths: ?*anyopaque,
95 | eventFlags: ?[*]const c.FSEventStreamEventFlags,
96 | eventIds: ?[*]const c.FSEventStreamEventId,
97 | ) callconv(.C) void {
98 | _ = eventIds;
99 | _ = eventFlags;
100 | _ = streamRef;
101 | const watcher: *MacosWatcher = @alignCast(@ptrCast(clientCallBackInfo));
102 |
103 | const paths: [*][*:0]u8 = @alignCast(@ptrCast(eventPaths));
104 | for (paths[0..numEvents]) |p| {
105 | const path = std.mem.span(p);
106 | log.debug("Changed: {s}\n", .{path});
107 |
108 | // const basename = std.fs.path.basename(path);
109 | // var base_path = path[0 .. path.len - basename.len];
110 | // if (std.mem.endsWith(u8, base_path, "/"))
111 | // base_path = base_path[0 .. base_path.len - 1];
112 | watcher.debouncer.newEvent();
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/cli/serve/zinereload.js:
--------------------------------------------------------------------------------
1 | function log() {
2 | console.log("[Zine Reloader]", ...arguments);
3 | }
4 |
5 | function zineConnect() {
6 | let socket = new WebSocket("ws://" + window.location.host + "/__zine/ws");
7 |
8 | socket.addEventListener("open", (event) => {
9 | log("connected");
10 | });
11 |
12 | // Listen for messages
13 | socket.addEventListener("message", (event) => {
14 | const msg = JSON.parse(event.data);
15 |
16 | if (msg.command == "reload_all") {
17 | location.reload();
18 | }
19 |
20 | if (msg.command == "reload") {
21 | log("reload", msg.path);
22 |
23 | if (msg.path.endsWith(".html")) {
24 | let path = window.location.pathname;
25 | if (path.endsWith('/')) path = path + 'index.html';
26 |
27 | if (path == msg.path) {
28 | location.reload();
29 | }
30 | } else if (msg.path.endsWith(".css")) {
31 | const links = document.querySelectorAll("link");
32 | for (let i = 0; i < links.length; i++) {
33 | const link = links[i];
34 | if (link._zine_temp) continue;
35 |
36 | let url = new URL(link.href);
37 | if (url.pathname == msg.path) {
38 | const now = Date.now();
39 | url.search = now;
40 | let copy = link.cloneNode(false);
41 | copy.href = url;
42 | link._zine_temp = true;
43 | link.parentElement.appendChild(copy);
44 | setTimeout(function(){
45 | link.remove();
46 | }, 200);
47 |
48 | break;
49 | }
50 | }
51 | } else if (msg.path.match(/\.(jpe?g|png|gif|svg|webp)$/i)) {
52 | for (let i = 0; i < document.images.length; i++) {
53 | const img = document.images[i];
54 | let url = new URL(img.src);
55 | if (url.pathname == msg.path) {
56 | const now = Date.now();
57 | url.search = now;
58 | img.src = url;
59 | }
60 | }
61 |
62 | for (let i = 0; i < document.styleSheets.length; i++) {
63 | const style = document.styleSheets[i];
64 | log("TODO: implement image reload in stylesheets");
65 | }
66 |
67 | const inlines = this.document.querySelectorAll("[style*=background]");
68 | for (let i = 0; i < inlines.length; i++) {
69 | const style = inlines[i];
70 | log("TODO: implement image reload in inline stylesheets");
71 | }
72 | }
73 | } else if (msg.command == "build"){
74 | const id = "__zine_build_box";
75 | if(msg.err != "") {
76 | let box = document.getElementById(id);
77 | if (box == null) {
78 | box = document.createElement("pre");
79 | box.style = "position: absolute; top: 0; left: 0; width: 100vw; height: 100vh;color: white; background-color: black;z-index:100; overflow-y: scroll; margin: 0; padding: 5px; font-family: monospace;";
80 | box.id = id;
81 | document.body.appendChild(box);
82 | box.innerHTML = "ZINE BUILD ERROR
" ;
83 | }
84 |
85 | box.innerHTML += "\n\n" + msg.err.replace(/ {
97 | log("close", event);
98 | setTimeout(zineConnect, 3000);
99 | });
100 |
101 | socket.addEventListener("error", (event) => {
102 | log("error", event);
103 | });
104 | return socket;
105 | }
106 |
107 | {
108 | const socket = zineConnect();
109 |
110 | // Keep sending messages to circumvent an issue related to windows
111 | // networking, see https://github.com/ziglang/zig/issues/14233
112 | function zinewin() {
113 | if (socket.readyState === WebSocket.OPEN) {
114 | socket.send("https://github.com/ziglang/zig/issues/14233");
115 | }
116 | }
117 | setInterval(zinewin, 100);
118 | }
119 |
--------------------------------------------------------------------------------
/src/context/Bool.zig:
--------------------------------------------------------------------------------
1 | const Bool = @This();
2 |
3 | const std = @import("std");
4 | const utils = @import("utils.zig");
5 | const context = @import("../context.zig");
6 | const Signature = @import("doctypes.zig").Signature;
7 | const Allocator = std.mem.Allocator;
8 | const Value = context.Value;
9 | const String = context.String;
10 |
11 | value: bool,
12 |
13 | pub fn init(b: bool) Value {
14 | return .{ .bool = .{ .value = b } };
15 | }
16 |
17 | fn not(b: Bool) Value {
18 | return .{ .bool = .{ .value = !b.value } };
19 | }
20 |
21 | pub const True = Bool.init(true);
22 | pub const False = Bool.init(false);
23 |
24 | pub const PassByRef = false;
25 | pub const docs_description = "A boolean value";
26 | pub const Builtins = struct {
27 | pub const then = struct {
28 | pub const signature: Signature = .{
29 | .params = &.{ .String, .{ .Opt = .String } },
30 | .ret = .String,
31 | };
32 | pub const docs_description =
33 | \\If the boolean is `true`, returns the first argument.
34 | \\Otherwise, returns the second argument.
35 | \\
36 | \\The second argument defaults to an empty string.
37 | \\
38 | ;
39 | pub const examples =
40 | \\$page.draft.then("DRAFT!")
41 | ;
42 | pub fn call(
43 | b: Bool,
44 | _: Allocator,
45 | _: *const context.Template,
46 | args: []const Value,
47 | ) !Value {
48 | if (args.len < 1 or args.len > 2) return .{
49 | .err = "expected 1 or 2 string arguments",
50 | };
51 |
52 | if (b.value) {
53 | return args[0];
54 | } else {
55 | if (args.len < 2) return String.init("");
56 | return args[1];
57 | }
58 | }
59 | };
60 | pub const not = struct {
61 | pub const signature: Signature = .{ .ret = .Bool };
62 | pub const docs_description =
63 | \\Negates a boolean value.
64 | \\
65 | ;
66 | pub const examples =
67 | \\$page.draft.not()
68 | ;
69 | pub fn call(
70 | b: Bool,
71 | _: Allocator,
72 | _: *const context.Template,
73 | args: []const Value,
74 | ) !Value {
75 | if (args.len != 0) return .{ .err = "expected 0 arguments" };
76 | return b.not();
77 | }
78 | };
79 | pub const @"and" = struct {
80 | pub const signature: Signature = .{
81 | .params = &.{ .Bool, .{ .Many = .Bool } },
82 | .ret = .Bool,
83 | };
84 |
85 | pub const docs_description =
86 | \\Computes logical `and` between the receiver value and any other
87 | \\value passed as argument.
88 | ;
89 | pub const examples =
90 | \\$page.draft.and($site.tags.len().eq(10))
91 | ;
92 | pub fn call(
93 | b: Bool,
94 | _: Allocator,
95 | _: *const context.Template,
96 | args: []const Value,
97 | ) !Value {
98 | if (args.len == 0) return .{ .err = "expected 1 or more boolean argument(s)" };
99 | for (args) |a| switch (a) {
100 | .bool => {},
101 | else => return .{ .err = "wrong argument type" },
102 | };
103 | if (!b.value) return False;
104 | for (args) |a| if (!a.bool.value) return False;
105 |
106 | return True;
107 | }
108 | };
109 | pub const @"or" = struct {
110 | pub const signature: Signature = .{
111 | .params = &.{ .Bool, .{ .Many = .Bool } },
112 | .ret = .Bool,
113 | };
114 | pub const docs_description =
115 | \\Computes logical `or` between the receiver value and any other value passed as argument.
116 | \\
117 | ;
118 | pub const examples =
119 | \\$page.draft.or($site.tags.len().eq(0))
120 | ;
121 | pub fn call(
122 | b: Bool,
123 | _: Allocator,
124 | _: *const context.Template,
125 | args: []const Value,
126 | ) !Value {
127 | if (args.len == 0) return .{ .err = "'or' wants at least one argument" };
128 | for (args) |a| switch (a) {
129 | .bool => {},
130 | else => return .{ .err = "wrong argument type" },
131 | };
132 | if (b.value) return True;
133 | for (args) |a| if (a.bool.value) return True;
134 |
135 | return False;
136 | }
137 | };
138 | };
139 |
--------------------------------------------------------------------------------
/src/context/Build.zig:
--------------------------------------------------------------------------------
1 | const Build = @This();
2 |
3 | const std = @import("std");
4 | const scripty = @import("scripty");
5 | const utils = @import("utils.zig");
6 | const context = @import("../context.zig");
7 | const Signature = @import("doctypes.zig").Signature;
8 | const Allocator = std.mem.Allocator;
9 | const Value = context.Value;
10 | const Optional = context.Optional;
11 | const uninitialized = utils.uninitialized;
12 |
13 | pub const dot = scripty.defaultDot(Build, Value, false);
14 | pub const PassByRef = true;
15 |
16 | generated: context.DateTime,
17 | _git_data_path: []const u8,
18 | _git: context.Git,
19 |
20 | pub fn init(
21 | git_data_path: []const u8,
22 | git: context.Git,
23 | ) Build {
24 | return .{
25 | .generated = context.DateTime.initNow(),
26 | ._git_data_path = git_data_path,
27 | ._git = git,
28 | };
29 | }
30 |
31 | pub const docs_description =
32 | \\Gives you access to build-time assets and other build related info.
33 | \\When inside of a git repository it also gives git-related metadata.
34 | ;
35 |
36 | pub const Fields = struct {
37 | pub const generated =
38 | \\Returns the current date when the build is taking place.
39 | \\
40 | \\># [Note]($block.attrs('note'))
41 | \\>Using this function will not add a dependency on the current time
42 | \\>for the page, hence the name `generated`.
43 | \\>
44 | \\>To get the best results, use in conjunction with caching as otherwise
45 | \\>the page will be regenerated anew every single time.
46 | ;
47 | };
48 |
49 | pub const Builtins = struct {
50 | pub const asset = struct {
51 | pub const signature: Signature = .{
52 | .params = &.{.String},
53 | .ret = .Asset,
54 | };
55 | pub const docs_description =
56 | \\Retuns a build-time asset (i.e. an asset generated through your 'build.zig' file) by name.
57 | ;
58 | pub const examples =
59 | \\
60 | ;
61 | pub fn call(
62 | _: *const Build,
63 | gpa: Allocator,
64 | ctx: *const context.Template,
65 | args: []const Value,
66 | ) !Value {
67 | const bad_arg: Value = .{
68 | .err = "expected 1 string argument",
69 | };
70 | if (args.len != 1) return bad_arg;
71 |
72 | const ref = switch (args[0]) {
73 | .string => |s| s.value,
74 | else => return bad_arg,
75 | };
76 |
77 | const ok = ctx._meta.build.build_assets.contains(ref);
78 | if (!ok) return Value.errFmt(gpa, "unknown build asset '{s}'", .{
79 | ref,
80 | });
81 |
82 | return .{
83 | .asset = .{
84 | ._meta = .{
85 | .ref = ref,
86 | .kind = .build,
87 | .url = undefined,
88 | },
89 | },
90 | };
91 | }
92 | };
93 |
94 | pub const git = struct {
95 | pub const signature: Signature = .{ .ret = .Git };
96 | pub const docs_description =
97 | \\Returns git-related metadata if you are inside a git repository.
98 | \\If you are not or the parsing failes, it will return an error.
99 | \\Packed object are not supported, commit anything to get the metadata.
100 | ;
101 | pub const examples =
102 | \\
103 | ;
104 | pub fn call(
105 | build: *const Build,
106 | _: Allocator,
107 | _: *const context.Template,
108 | args: []const Value,
109 | ) Value {
110 | const bad_arg: Value = .{
111 | .err = "expected 0 arguments",
112 | };
113 | if (args.len != 0) return bad_arg;
114 |
115 | return if (build._git._in_repo) .{
116 | .git = build._git,
117 | } else .{
118 | .err = "Not in a git repository",
119 | };
120 | }
121 | };
122 |
123 | pub const @"git?" = struct {
124 | pub const signature: Signature = .{ .ret = .Git };
125 | pub const docs_description =
126 | \\Returns git-related metadata if you are inside a git repository.
127 | \\If you are not or the parsing failes, it will return null.
128 | \\Packed object are not supported, commit anything to get the metadata.
129 | ;
130 | pub const examples =
131 | \\...
132 | ;
133 | pub fn call(
134 | build: *const Build,
135 | gpa: Allocator,
136 | _: *const context.Template,
137 | args: []const Value,
138 | ) !Value {
139 | const bad_arg: Value = .{
140 | .err = "expected 0 arguments",
141 | };
142 | if (args.len != 0) return bad_arg;
143 |
144 | return if (build._git._in_repo)
145 | Optional.init(gpa, build._git)
146 | else
147 | Optional.Null;
148 | }
149 | };
150 | };
151 |
--------------------------------------------------------------------------------
/src/context/Float.zig:
--------------------------------------------------------------------------------
1 | const Float = @This();
2 |
3 | f: f64,
4 |
5 | pub const PassByRef = false;
6 | pub const docs_description = "A 64bit float value.";
7 | pub const Builtins = struct {};
8 |
--------------------------------------------------------------------------------
/src/context/Git.zig:
--------------------------------------------------------------------------------
1 | const Git = @This();
2 |
3 | const std = @import("std");
4 | const builtin = @import("builtin");
5 | const scripty = @import("scripty");
6 | const context = @import("../context.zig");
7 | const Signature = @import("doctypes.zig").Signature;
8 | const Allocator = std.mem.Allocator;
9 | const DateTime = context.DateTime;
10 | const String = context.String;
11 | const Optional = context.Optional;
12 | const Bool = context.Bool;
13 | const Value = context.Value;
14 |
15 | pub const dot = scripty.defaultDot(Git, Value, false);
16 |
17 | _in_repo: bool = false,
18 |
19 | commit_hash: []const u8 = undefined,
20 | commit_date: DateTime = undefined,
21 | commit_message: []const u8 = undefined,
22 | author_name: []const u8 = undefined,
23 | author_email: []const u8 = undefined,
24 |
25 | _tag: ?[]const u8 = null,
26 | _branch: ?[]const u8 = null,
27 |
28 | pub const docs_description =
29 | \\Information about the current git repository.
30 | ;
31 |
32 | pub const Fields = struct {
33 | pub const commit_hash =
34 | \\The current commit hash.
35 | ;
36 | pub const commit_date =
37 | \\The date of the current commit.
38 | ;
39 | pub const commit_message =
40 | \\The commit message of the current commit.
41 | ;
42 | pub const author_name =
43 | \\The name of the author of the current commit.
44 | ;
45 | pub const author_email =
46 | \\The email of the author of the current commit.
47 | ;
48 | };
49 |
50 | pub const Builtins = struct {
51 | pub const tag = struct {
52 | pub const signature: Signature = .{ .ret = .String };
53 | pub const docs_description =
54 | \\Returns the tag of the current commit.
55 | \\If the current commit does not have a tag, an error is returned.
56 | ;
57 | pub const examples =
58 | \\
59 | \\
60 | ;
61 | pub fn call(
62 | git: Git,
63 | gpa: Allocator,
64 | _: *const context.Template,
65 | args: []const Value,
66 | ) !Value {
67 | const bad_arg: Value = .{
68 | .err = "expected 0 arguments",
69 | };
70 | if (args.len != 0) return bad_arg;
71 |
72 | return if (git._tag) |_tag| Value.from(gpa, _tag) else .{ .err = "No tag for this commit" };
73 | }
74 | };
75 |
76 | pub const @"tag?" = struct {
77 | pub const signature: Signature = .{ .ret = .String };
78 | pub const docs_description =
79 | \\Returns the tag of the current commit.
80 | \\If the current commit does not have a tag, null is returned.
81 | ;
82 | pub const examples =
83 | \\
84 | \\
85 | ;
86 | pub fn call(
87 | git: Git,
88 | gpa: Allocator,
89 | _: *const context.Template,
90 | args: []const Value,
91 | ) !Value {
92 | const bad_arg: Value = .{
93 | .err = "expected 0 arguments",
94 | };
95 | if (args.len != 0) return bad_arg;
96 |
97 | return if (git._tag) |_tag| Optional.init(gpa, _tag) else Optional.Null;
98 | }
99 | };
100 |
101 | pub const branch = struct {
102 | pub const signature: Signature = .{ .ret = .String };
103 | pub const docs_description =
104 | \\Returns the branch of the current commit.
105 | \\If the current commit does not have a branch, an error is returned.
106 | ;
107 | pub const examples =
108 | \\
109 | \\
110 | ;
111 | pub fn call(
112 | git: Git,
113 | gpa: Allocator,
114 | _: *const context.Template,
115 | args: []const Value,
116 | ) !Value {
117 | const bad_arg: Value = .{
118 | .err = "expected 0 arguments",
119 | };
120 | if (args.len != 0) return bad_arg;
121 |
122 | return if (git._branch) |_branch| Value.from(gpa, _branch) else .{ .err = "No branch for this commit" };
123 | }
124 | };
125 |
126 | pub const @"branch?" = struct {
127 | pub const signature: Signature = .{ .ret = .String };
128 | pub const docs_description =
129 | \\Returns the branch of the current commit.
130 | \\If the current commit does not have a branch, null is returned.
131 | ;
132 | pub const examples =
133 | \\
134 | \\
135 | ;
136 | pub fn call(
137 | git: Git,
138 | gpa: Allocator,
139 | _: *const context.Template,
140 | args: []const Value,
141 | ) !Value {
142 | const bad_arg: Value = .{
143 | .err = "expected 0 arguments",
144 | };
145 | if (args.len != 0) return bad_arg;
146 |
147 | return if (git._branch) |_branch| Optional.init(gpa, _branch) else Optional.Null;
148 | }
149 | };
150 | };
151 |
--------------------------------------------------------------------------------
/src/context/Int.zig:
--------------------------------------------------------------------------------
1 | const Int = @This();
2 |
3 | const std = @import("std");
4 | const utils = @import("utils.zig");
5 | const context = @import("../context.zig");
6 | const Signature = @import("doctypes.zig").Signature;
7 | const Allocator = std.mem.Allocator;
8 | const Value = context.Value;
9 | const Bool = context.Bool;
10 | const String = context.String;
11 |
12 | value: i64,
13 |
14 | pub fn init(i: i64) Value {
15 | return .{ .int = .{ .value = i } };
16 | }
17 |
18 | pub const PassByRef = false;
19 | pub const docs_description = "A signed 64-bit integer.";
20 | pub const Builtins = struct {
21 | pub const eq = struct {
22 | pub const signature: Signature = .{
23 | .params = &.{.Int},
24 | .ret = .Bool,
25 | };
26 | pub const docs_description =
27 | \\Tests if two integers have the same value.
28 | \\
29 | ;
30 | pub const examples =
31 | \\$page.wordCount().eq(200)
32 | ;
33 | pub fn call(
34 | int: Int,
35 | _: Allocator,
36 | _: *const context.Template,
37 | args: []const Value,
38 | ) !Value {
39 | const argument_error: Value = .{ .err = "'plus' wants one int argument" };
40 | if (args.len != 1) return argument_error;
41 |
42 | switch (args[0]) {
43 | .int => |rhs| return Bool.init(int.value == rhs.value),
44 | else => return argument_error,
45 | }
46 | }
47 | };
48 | pub const gt = struct {
49 | pub const signature: Signature = .{
50 | .params = &.{.Int},
51 | .ret = .Bool,
52 | };
53 | pub const docs_description =
54 | \\Returns true if lhs is greater than rhs (the argument).
55 | \\
56 | ;
57 | pub const examples =
58 | \\$page.wordCount().gt(200)
59 | ;
60 | pub fn call(
61 | int: Int,
62 | _: Allocator,
63 | _: *const context.Template,
64 | args: []const Value,
65 | ) !Value {
66 | const argument_error: Value = .{ .err = "'gt' wants one int argument" };
67 | if (args.len != 1) return argument_error;
68 |
69 | switch (args[0]) {
70 | .int => |rhs| return Bool.init(int.value > rhs.value),
71 | else => return argument_error,
72 | }
73 | }
74 | };
75 |
76 | pub const plus = struct {
77 | pub const signature: Signature = .{
78 | .params = &.{.Int},
79 | .ret = .Int,
80 | };
81 | pub const docs_description =
82 | \\Sums two integers.
83 | \\
84 | ;
85 | pub const examples =
86 | \\$page.wordCount().plus(10)
87 | ;
88 | pub fn call(
89 | int: Int,
90 | _: Allocator,
91 | _: *const context.Template,
92 | args: []const Value,
93 | ) !Value {
94 | const argument_error: Value = .{ .err = "expected 1 int argument" };
95 | if (args.len != 1) return argument_error;
96 |
97 | switch (args[0]) {
98 | .int => |add| return Int.init(int.value +| add.value),
99 | .float => @panic("TODO: int with float argument"),
100 | else => return argument_error,
101 | }
102 | }
103 | };
104 | pub const div = struct {
105 | pub const signature: Signature = .{
106 | .params = &.{.Int},
107 | .ret = .Int,
108 | };
109 | pub const docs_description =
110 | \\Divides the receiver by the argument.
111 | \\
112 | ;
113 | pub const examples =
114 | \\$page.wordCount().div(10)
115 | ;
116 | pub fn call(
117 | int: Int,
118 | _: Allocator,
119 | _: *const context.Template,
120 | args: []const Value,
121 | ) !Value {
122 | const argument_error: Value = .{ .err = "'div' wants one (int|float) argument" };
123 | if (args.len != 1) return argument_error;
124 |
125 | switch (args[0]) {
126 | .int => |den| {
127 | const res = std.math.divTrunc(i64, int.value, den.value) catch |err| {
128 | return .{ .err = @errorName(err) };
129 | };
130 |
131 | return Int.init(res);
132 | },
133 | .float => @panic("TODO: div with float argument"),
134 | else => return argument_error,
135 | }
136 | }
137 | };
138 |
139 | pub const byteSize = struct {
140 | pub const signature: Signature = .{ .ret = .String };
141 | pub const docs_description =
142 | \\Turns a raw number of bytes into a human readable string that
143 | \\appropriately uses Kilo, Mega, Giga, etc.
144 | \\
145 | ;
146 | pub const examples =
147 | \\$page.asset('photo.jpg').size().byteSize()
148 | ;
149 | pub fn call(
150 | int: Int,
151 | gpa: Allocator,
152 | _: *const context.Template,
153 | args: []const Value,
154 | ) !Value {
155 | if (args.len != 0) return .{ .err = "expected 0 arguments" };
156 |
157 | const size: usize = if (int.value > 0) @intCast(int.value) else return Value.errFmt(
158 | gpa,
159 | "cannot represent {} (a negative value) as a size",
160 | .{int.value},
161 | );
162 |
163 | return String.init(try std.fmt.allocPrint(gpa, "{:.0}", .{
164 | std.fmt.fmtIntSizeBin(size),
165 | }));
166 | }
167 | };
168 |
169 | pub const str = struct {
170 | pub const signature: Signature = .{ .ret = .String };
171 | pub const docs_description =
172 | \\Converts the number into a string, so that can be used for
173 | \\functions that require a string argument.
174 | ;
175 |
176 | pub const examples =
177 | \\$i18n.get!("current_page").fmt($loop.idx.str())
178 | ;
179 |
180 | pub fn call(
181 | int: Int,
182 | gpa: Allocator,
183 | _: *const context.Template,
184 | args: []const Value,
185 | ) !Value {
186 | if (args.len != 0) return .{ .err = "expected 0 arguments" };
187 | return String.init(try std.fmt.allocPrint(gpa, "{}", .{int.value}));
188 | }
189 | };
190 | };
191 |
--------------------------------------------------------------------------------
/src/context/Iterator.zig:
--------------------------------------------------------------------------------
1 | const Iterator = @This();
2 |
3 | const std = @import("std");
4 | const ziggy = @import("ziggy");
5 | const superhtml = @import("superhtml");
6 | const scripty = @import("scripty");
7 | const context = @import("../context.zig");
8 | const doctypes = @import("doctypes.zig");
9 | const Signature = doctypes.Signature;
10 | const Allocator = std.mem.Allocator;
11 | const Value = context.Value;
12 | const Template = context.Template;
13 | const Site = context.Site;
14 | const Page = context.Page;
15 | const Map = context.Map;
16 | const Array = context.Array;
17 |
18 | it: Value = undefined,
19 | idx: usize = 0,
20 | first: bool = undefined,
21 | last: bool = undefined,
22 | len: usize,
23 |
24 | _superhtml_context: superhtml.utils.IteratorContext(Value, Template) = .{},
25 | _impl: Impl,
26 |
27 | pub const Impl = union(enum) {
28 | value_it: SliceIterator(Value),
29 |
30 | pub fn len(impl: Impl) usize {
31 | switch (impl) {
32 | inline else => |v| return v.len(),
33 | }
34 | }
35 | };
36 |
37 | pub fn init(gpa: Allocator, impl: Impl) !*Iterator {
38 | const res = try gpa.create(Iterator);
39 | res.* = .{ ._impl = impl, .len = impl.len() };
40 | return res;
41 | }
42 |
43 | pub fn deinit(iter: *const Iterator, gpa: Allocator) void {
44 | gpa.destroy(iter);
45 | }
46 |
47 | pub fn next(iter: *Iterator, gpa: Allocator) !bool {
48 | switch (iter._impl) {
49 | inline else => |*v| {
50 | const item = try v.next(gpa);
51 | iter.it = try Value.from(gpa, item orelse return false);
52 | iter.idx += 1;
53 | iter.first = iter.idx == 1;
54 | iter.last = iter.idx == iter.len;
55 | return true;
56 | },
57 | }
58 | }
59 |
60 | pub fn fromArray(gpa: Allocator, arr: Array) !*Iterator {
61 | return init(gpa, .{
62 | .value_it = .{ .items = arr._items },
63 | });
64 | }
65 |
66 | pub const dot = scripty.defaultDot(Iterator, Value, false);
67 | pub const docs_description = "An iterator.";
68 | pub const Fields = struct {
69 | pub const it =
70 | \\The current iteration variable.
71 | ;
72 | pub const idx =
73 | \\The current iteration index.
74 | ;
75 | pub const len =
76 | \\The length of the sequence being iterated.
77 | ;
78 | pub const first =
79 | \\True on the first iteration loop.
80 | ;
81 | pub const last =
82 | \\True on the last iteration loop.
83 | ;
84 | };
85 | pub const Builtins = struct {
86 | pub const up = struct {
87 | pub const signature: Signature = .{ .ret = .Iterator };
88 | pub const docs_description =
89 | \\In nested loops, accesses the upper `$loop`
90 | \\
91 | ;
92 | pub const examples =
93 | \\$loop.up().it
94 | ;
95 | pub fn call(
96 | it: *Iterator,
97 | _: Allocator,
98 | _: *const context.Template,
99 | args: []const Value,
100 | ) !Value {
101 | const bad_arg: Value = .{ .err = "expected 0 arguments" };
102 | if (args.len != 0) return bad_arg;
103 | return it._superhtml_context.up();
104 | }
105 | };
106 | };
107 |
108 | fn SliceIterator(comptime Element: type) type {
109 | return struct {
110 | idx: usize = 0,
111 | items: []const Element,
112 |
113 | pub fn len(self: @This()) usize {
114 | return self.items.len;
115 | }
116 |
117 | pub fn next(self: *@This(), gpa: Allocator) !?Element {
118 | _ = gpa;
119 | if (self.idx == self.items.len) return null;
120 | defer self.idx += 1;
121 | return self.items[self.idx];
122 | }
123 | };
124 | }
125 |
--------------------------------------------------------------------------------
/src/context/Optional.zig:
--------------------------------------------------------------------------------
1 | const Optional = @This();
2 |
3 | const std = @import("std");
4 | const context = @import("../context.zig");
5 | const Allocator = std.mem.Allocator;
6 | const Value = context.Value;
7 |
8 | value: Value,
9 |
10 | pub const Null: Value = .{ .optional = null };
11 | pub fn init(gpa: Allocator, v: anytype) !Value {
12 | const box = try gpa.create(Optional);
13 | box.value = try Value.from(gpa, v);
14 | return .{ .optional = box };
15 | }
16 |
17 | // pub fn dot(opt: Optional, gpa: Allocator, path: []const u8) !Value {
18 | // _ = opt;
19 | // _ = gpa;
20 | // _ = path;
21 | // return .{ .err = "todo" };
22 | // }
23 | pub const PassByRef = false;
24 | pub const docs_description = "An optional value, to be used in conjunction with `if` attributes.";
25 | pub const Builtins = struct {};
26 |
--------------------------------------------------------------------------------
/src/context/Slice.zig:
--------------------------------------------------------------------------------
1 | const Slice = @This();
2 |
3 | const std = @import("std");
4 | const context = @import("../context.zig");
5 | const Allocator = std.mem.Allocator;
6 | const Value = context.Value;
7 |
8 | value: []const Value,
9 |
10 | pub fn dot(s: Slice, gpa: Allocator, path: []const u8) !Value {
11 | _ = s;
12 | _ = gpa;
13 | _ = path;
14 | return .{ .err = "todo" };
15 | }
16 | pub const docs_description = "TODO";
17 | pub const Builtins = struct {};
18 |
--------------------------------------------------------------------------------
/src/context/Template.zig:
--------------------------------------------------------------------------------
1 | const Template = @This();
2 |
3 | const std = @import("std");
4 | const superhtml = @import("superhtml");
5 | const scripty = @import("scripty");
6 | const ziggy = @import("ziggy");
7 | const ZineBuild = @import("../Build.zig");
8 | const context = @import("../context.zig");
9 | const Value = context.Value;
10 | const Site = context.Site;
11 | const Page = context.Page;
12 | const Build = context.Build;
13 | const Map = context.Map;
14 | const Iterator = context.Iterator;
15 | const Optional = context.Optional;
16 | const Ctx = superhtml.utils.Ctx;
17 |
18 | site: *const Site,
19 | page: *const Page,
20 | build: Build,
21 | i18n: Map.ZiggyMap,
22 |
23 | _meta: struct {
24 | build: *const ZineBuild,
25 | // Indexed by language code, empty when building a simple site
26 | // Get by key when you have a language code, get by idx when you
27 | // have a variant_id.
28 | sites: *const std.StringArrayHashMapUnmanaged(Site),
29 | },
30 |
31 | // Globals specific to SuperHTML
32 | ctx: Ctx(Value) = .{},
33 | loop: ?*Iterator = null,
34 | @"if": ?*const Optional = null,
35 |
36 | pub fn printLinkPrefix(
37 | ctx: *const Template,
38 | w: anytype,
39 | other_variant_id: u32,
40 | /// When set to true the full host url will be always printed
41 | /// otherwise it will only be added in multilingual websites when
42 | /// linking to content across variants that have different host url
43 | /// overrides.
44 | force_host_url: bool,
45 | ) error{OutOfMemory}!void {
46 | const other_site = ctx._meta.sites.entries.items(.value)[other_variant_id];
47 | switch (other_site._meta.kind) {
48 | .simple => |url_path_prefix| {
49 | if (force_host_url) try w.print("{s}", .{
50 | ctx._meta.build.cfg.Site.host_url,
51 | });
52 | if (url_path_prefix.len > 0) {
53 | try w.print("/{s}/", .{url_path_prefix});
54 | } else {
55 | try w.writeAll("/");
56 | }
57 | },
58 | .multi => |loc| {
59 | const our_variant_id = ctx.page._scan.variant_id;
60 | if (other_variant_id != our_variant_id) {
61 | const sites = ctx._meta.sites.entries.items(.value);
62 | const our_host_url = sites[our_variant_id].host_url;
63 | const other_host_url = sites[other_variant_id].host_url;
64 | if (force_host_url or our_host_url.ptr != other_host_url.ptr) {
65 | try w.print("{s}", .{other_host_url});
66 | }
67 | }
68 | try w.writeAll("/");
69 | const path_prefix = loc.output_prefix_override orelse loc.code;
70 | if (path_prefix.len > 0) try w.print("{s}/", .{path_prefix});
71 | },
72 | }
73 | }
74 |
75 | pub const dot = scripty.defaultDot(Template, Value, false);
76 | pub const docs_description = "";
77 | pub const Fields = struct {
78 | pub const site =
79 | \\The current website. In a multilingual website,
80 | \\each locale will have its own separate instance of $site
81 | ;
82 |
83 | pub const page =
84 | \\The page being currently rendered.
85 | ;
86 |
87 | pub const i18n =
88 | \\In a multilingual website it contains the translations
89 | \\defined in the corresponding i18n file.
90 | \\
91 | \\See the i18n docs for more info.
92 | ;
93 |
94 | pub const build =
95 | \\Gives you access to build-time assets (i.e. assets built
96 | \\ via the Zig build system) alongside other information
97 | \\relative to the current build.
98 | ;
99 |
100 | pub const ctx =
101 | \\A key-value mapping that contains data defined in ``
102 | \\nodes.
103 | ;
104 |
105 | pub const loop =
106 | \\The current iterator, only available within elements
107 | \\that have a `loop` attribute.
108 | ;
109 |
110 | pub const @"if" =
111 | \\The current branching variable, only available within elements
112 | \\that have an `if` attribute used to unwrap an optional value.
113 | ;
114 | };
115 | pub const Builtins = struct {};
116 |
--------------------------------------------------------------------------------
/src/context/Value.zig:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/zine/aa43d44c2d271cb309549e8d7eab0a7fe6bb06f2/src/context/Value.zig
--------------------------------------------------------------------------------
/src/context/doctypes.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const ziggy = @import("ziggy");
3 | const superhtml = @import("superhtml");
4 | const context = @import("../context.zig");
5 | const Value = context.Value;
6 |
7 | pub const Signature = struct {
8 | params: []const ScriptyParam = &.{},
9 | ret: ScriptyParam,
10 |
11 | pub fn format(
12 | s: Signature,
13 | comptime fmt: []const u8,
14 | options: std.fmt.FormatOptions,
15 | out_stream: anytype,
16 | ) !void {
17 | _ = fmt;
18 | _ = options;
19 | try out_stream.writeAll("(");
20 | for (s.params, 0..) |p, idx| {
21 | try out_stream.writeAll(p.link(true));
22 | if (idx < s.params.len - 1) {
23 | try out_stream.writeAll(", ");
24 | }
25 | }
26 | try out_stream.writeAll(") -> ");
27 | try out_stream.writeAll(s.ret.link(false));
28 | }
29 | };
30 |
31 | pub const ScriptyParam = union(enum) {
32 | Site,
33 | Page,
34 | Build,
35 | Git,
36 | Asset,
37 | Alternative,
38 | ContentSection,
39 | Footnote,
40 | Iterator,
41 | Array,
42 | String,
43 | Int,
44 | Float,
45 | Bool,
46 | Date,
47 | Ctx,
48 | KV,
49 | any,
50 | err,
51 | Map: Base,
52 | Opt: Base,
53 | Many: Base,
54 |
55 | pub const Base = union(enum) {
56 | Site,
57 | Page,
58 | Alternative,
59 | ContentSection,
60 | Footnote,
61 | Iterator,
62 | String,
63 | Int,
64 | Bool,
65 | Date,
66 | KV,
67 | any,
68 | Many: Base2,
69 |
70 | pub const Base2 = enum {
71 | Footnote,
72 | };
73 | };
74 |
75 | pub fn fromType(t: type) ScriptyParam {
76 | return switch (t) {
77 | context.Template => .any,
78 | ?context.Value => .any,
79 | context.Page, *const context.Page => .Page,
80 | context.Site, *const context.Site => .Site,
81 | context.Build => .Build,
82 | context.Git => .Git,
83 | superhtml.utils.Ctx(context.Value) => .Ctx,
84 | context.Page.Alternative => .Alternative,
85 | context.Page.ContentSection => .ContentSection,
86 | context.Page.Footnote => .Footnote,
87 | context.Asset => .Asset,
88 | // context.Slice => .any,
89 | context.Optional, ?*const context.Optional => .{ .Opt = .any },
90 | context.String => .String,
91 | context.Bool => .Bool,
92 | context.Int => .Int,
93 | context.Float => .Float,
94 | context.DateTime => .Date,
95 | context.Map, context.Map.ZiggyMap => .{ .Map = .any },
96 | context.Map.KV => .KV,
97 | context.Array => .Array,
98 | context.Iterator => .Iterator,
99 | ?*context.Iterator => .{ .Opt = .Iterator },
100 | []const context.Page.Alternative => .{ .Many = .Alternative },
101 | []const context.Page.Footnote => .{ .Many = .Footnote },
102 | ?[]const context.Page.Footnote => .{ .Opt = .{ .Many = .Footnote } },
103 | []const u8 => .String,
104 | ?[]const u8 => .{ .Opt = .String },
105 | []const []const u8 => .{ .Many = .String },
106 | bool => .Bool,
107 | usize => .Int,
108 | ziggy.dynamic.Value => .any,
109 | context.Value => .any,
110 | else => @compileError("TODO: add support for " ++ @typeName(t)),
111 | };
112 | }
113 |
114 | pub fn string(
115 | p: ScriptyParam,
116 | comptime is_fn_param: bool,
117 | ) []const u8 {
118 | switch (p) {
119 | inline .Many => |m| switch (m) {
120 | inline else => {
121 | const dots = if (is_fn_param) "..." else "";
122 | return "[" ++ @tagName(m) ++ dots ++ "]";
123 | },
124 | },
125 | .Opt => |o| switch (o) {
126 | .Many => |om| switch (om) {
127 | inline else => |omm| return "?[" ++ @tagName(omm) ++ "]",
128 | },
129 | inline else => return "?" ++ @tagName(o),
130 | },
131 | inline else => return @tagName(p),
132 | }
133 | }
134 | pub fn link(
135 | p: ScriptyParam,
136 | comptime is_fn_param: bool,
137 | ) []const u8 {
138 | switch (p) {
139 | inline .Many => |m| switch (m) {
140 | inline else => {
141 | const dots = if (is_fn_param) "..." else "";
142 | return std.fmt.comptimePrint(
143 | \\[[{0s}]($link.ref("{0s}")){1s}]{2s}
144 | , .{
145 | @tagName(m), dots, if (is_fn_param or m == .any) "" else
146 | \\ *(see also [[any]]($link.ref("Array")))*
147 | });
148 | },
149 | },
150 | inline .Opt => |o| switch (o) {
151 | inline .Many => |om| switch (om) {
152 | inline else => |omm| return comptime std.fmt.comptimePrint(
153 | \\?[[{0s}]($link.ref("{0s}"))]
154 | , .{@tagName(omm)}),
155 | },
156 | inline else => return comptime std.fmt.comptimePrint(
157 | \\?[{0s}]($link.ref("{0s}"))
158 | , .{@tagName(o)}),
159 | },
160 | inline else => |_, t| return comptime std.fmt.comptimePrint(
161 | \\[{0s}]($link.ref("{0s}"))
162 | , .{@tagName(t)}),
163 | }
164 | }
165 |
166 | pub fn id(p: ScriptyParam) []const u8 {
167 | switch (p) {
168 | .Opt, .Many => |o| switch (o) {
169 | inline else => return @tagName(o),
170 | },
171 | inline else => return @tagName(p),
172 | }
173 | }
174 | };
175 |
--------------------------------------------------------------------------------
/src/context/markdown.zig:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/zine/aa43d44c2d271cb309549e8d7eab0a7fe6bb06f2/src/context/markdown.zig
--------------------------------------------------------------------------------
/src/context/utils.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const ziggy = @import("ziggy");
3 | const super = @import("superhtml");
4 | const Allocator = std.mem.Allocator;
5 | const Asset = @import("Asset.zig");
6 | const Value = @import("../context.zig").Value;
7 |
8 | pub const log = std.log.scoped(.builtin);
9 |
--------------------------------------------------------------------------------
/src/fatal.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const main = @import("main.zig");
3 | const builtin = @import("builtin");
4 |
5 | pub fn msg(comptime fmt: []const u8, args: anytype) noreturn {
6 | std.debug.print(fmt, args);
7 | if (builtin.mode == .Debug) std.debug.panic("\n\n(Zine debug stack trace)\n", .{});
8 | std.process.exit(1);
9 | }
10 |
11 | pub fn oom() noreturn {
12 | msg("oom\n", .{});
13 | }
14 |
15 | pub fn dir(path: []const u8, err: anyerror) noreturn {
16 | msg("error accessing dir '{s}': {s}\n", .{
17 | path, @errorName(err),
18 | });
19 | }
20 |
21 | pub fn file(path: []const u8, err: anyerror) noreturn {
22 | msg("error accessing file '{s}': {s}\n", .{
23 | path, @errorName(err),
24 | });
25 | }
26 |
27 | pub fn help() noreturn {
28 | std.debug.print(
29 | \\Usage: zine [COMMAND] [OPTIONS]
30 | \\
31 | \\Commands:
32 | \\ (no command) Start the development web server
33 | \\ init Initialize a Zine site in the current directory
34 | \\ release Create a release of a Zine site
35 | \\ help Show this menu and exit
36 | \\ version Print the Zine version and exit
37 | \\
38 | \\General Options:
39 | \\ --drafts Enable draft pages
40 | \\ --help, -h Print command specific usage and extra options
41 | \\
42 | \\Development web server options:
43 | \\ --host HOST Listening host (default 'localhost')
44 | \\ --port PORT Listening port (default 1990)
45 | \\ --debounce Rebuild delay after a file change (default 25)
46 | \\
47 | \\
48 | , .{});
49 | std.process.exit(1);
50 | }
51 |
--------------------------------------------------------------------------------
/src/fuzz/scripty.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const zine = @import("zine");
3 |
4 | export fn zig_fuzz_init() void {}
5 |
6 | export fn zig_fuzz_test(buf: [*]u8, len: isize) void {}
7 |
--------------------------------------------------------------------------------
/src/highlight.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const options = @import("options");
3 | const syntax = @import("syntax");
4 | const treez = @import("treez");
5 | const tracy = @import("tracy");
6 | const HtmlSafe = @import("superhtml").HtmlSafe;
7 |
8 | const log = std.log.scoped(.highlight);
9 |
10 | pub const DotsToUnderscores = struct {
11 | bytes: []const u8,
12 |
13 | pub fn format(
14 | self: DotsToUnderscores,
15 | comptime fmt: []const u8,
16 | _: std.fmt.FormatOptions,
17 | out_stream: anytype,
18 | ) !void {
19 | _ = fmt;
20 | for (self.bytes) |b| {
21 | switch (b) {
22 | '.' => try out_stream.writeAll("_"),
23 | else => try out_stream.writeByte(b),
24 | }
25 | }
26 | }
27 | };
28 |
29 | var query_cache: syntax.QueryCache = .{
30 | .allocator = @import("main.zig").gpa,
31 | .mutex = std.Thread.Mutex{},
32 | };
33 |
34 | const ClassSet = struct {
35 | classes: std.StringHashMap(void),
36 |
37 | const Self = @This();
38 |
39 | pub fn init(allocator: std.mem.Allocator) ClassSet {
40 | return .{
41 | .classes = std.StringHashMap(void).init(allocator),
42 | };
43 | }
44 |
45 | pub fn deinit(self: *Self) void {
46 | self.classes.deinit();
47 | }
48 |
49 | pub fn addClass(self: *Self, class: []const u8) !void {
50 | try self.classes.put(class, {});
51 | }
52 |
53 | pub fn removeClass(self: *Self, class: []const u8) void {
54 | _ = self.classes.remove(class);
55 | }
56 |
57 | pub fn getClasses(self: Self, result: *std.ArrayList([]const u8)) !void {
58 | result.clearRetainingCapacity();
59 | var it = self.classes.keyIterator();
60 | while (it.next()) |key| try result.append(key.*);
61 | }
62 | };
63 |
64 | const ClassChange = struct {
65 | position: usize,
66 | is_add: bool,
67 | class: []const u8,
68 |
69 | pub fn lessThan(_: void, a: ClassChange, b: ClassChange) bool {
70 | return a.position < b.position;
71 | }
72 | };
73 |
74 | fn printSpan(
75 | writer: anytype,
76 | code: []const u8,
77 | start: usize,
78 | end: usize,
79 | classes: []const []const u8,
80 | arena: std.mem.Allocator,
81 | ) !void {
82 | if (classes.len == 0) {
83 | try writer.print("{s}", .{HtmlSafe{ .bytes = code[start..end] }});
84 | return;
85 | }
86 |
87 | var class_str = std.ArrayList(u8).init(arena);
88 | defer class_str.deinit();
89 |
90 | for (classes, 0..) |class, i| {
91 | if (i > 0) try class_str.append(' ');
92 | try class_str.appendSlice(class);
93 | }
94 |
95 | try writer.print(
96 | \\{s}
97 | , .{
98 | DotsToUnderscores{ .bytes = class_str.items },
99 | HtmlSafe{ .bytes = code[start..end] },
100 | });
101 | }
102 |
103 | pub fn highlightCode(
104 | arena: std.mem.Allocator,
105 | lang_name: []const u8,
106 | code: []const u8,
107 | writer: anytype,
108 | ) !void {
109 | const zone = tracy.traceNamed(@src(), "highlightCode");
110 | defer zone.end();
111 | tracy.messageCopy(lang_name);
112 |
113 | if (!options.enable_treesitter) {
114 | try writer.print("{s}", .{HtmlSafe{ .bytes = code }});
115 | return;
116 | }
117 |
118 | const lang = blk: {
119 | const query_zone = tracy.traceNamed(@src(), "syntax");
120 | defer query_zone.end();
121 |
122 | break :blk syntax.create_file_type(
123 | arena,
124 | lang_name,
125 | &query_cache,
126 | ) catch {
127 | const syntax_fallback_zone = tracy.traceNamed(@src(), "syntax fallback");
128 | defer syntax_fallback_zone.end();
129 | const fake_filename = try std.fmt.allocPrint(arena, "file.{s}", .{lang_name});
130 | break :blk try syntax.create_guess_file_type(arena, "", fake_filename, &query_cache);
131 | };
132 | };
133 |
134 | {
135 | const refresh_zone = tracy.traceNamed(@src(), "refresh");
136 | defer refresh_zone.end();
137 | try lang.refresh_full(code);
138 | }
139 | // we don't want to free any resource from the query cache
140 | // defer lang.destroy();
141 |
142 | const tree = lang.tree orelse return;
143 | const cursor = try treez.Query.Cursor.create();
144 | defer cursor.destroy();
145 |
146 | {
147 | const query_zone = tracy.traceNamed(@src(), "exec query");
148 | defer query_zone.end();
149 | cursor.execute(lang.query, tree.getRootNode());
150 | }
151 |
152 | const match_zone = tracy.traceNamed(@src(), "render");
153 | defer match_zone.end();
154 |
155 | cursor.execute(lang.query, tree.getRootNode());
156 |
157 | var changes = std.ArrayList(ClassChange).init(arena);
158 |
159 | while (cursor.nextMatch()) |match| {
160 | for (match.captures()) |capture| {
161 | const range = capture.node.getRange();
162 | const capture_name = lang.query.getCaptureNameForId(capture.id);
163 |
164 | try changes.append(ClassChange{
165 | .position = range.start_byte,
166 | .is_add = true,
167 | .class = capture_name,
168 | });
169 |
170 | try changes.append(ClassChange{
171 | .position = range.end_byte,
172 | .is_add = false,
173 | .class = capture_name,
174 | });
175 | }
176 | }
177 |
178 | std.sort.insertion(ClassChange, changes.items, {}, ClassChange.lessThan);
179 |
180 | var current_classes = ClassSet.init(arena);
181 | defer current_classes.deinit();
182 |
183 | var class_list = std.ArrayList([]const u8).init(arena);
184 | defer class_list.deinit();
185 |
186 | var current_pos: usize = 0;
187 |
188 | for (changes.items) |change| {
189 | if (change.position > current_pos) {
190 | try current_classes.getClasses(&class_list);
191 | try printSpan(writer, code, current_pos, change.position, class_list.items, arena);
192 | current_pos = change.position;
193 | }
194 |
195 | if (change.is_add) {
196 | try current_classes.addClass(change.class);
197 | continue;
198 | }
199 |
200 | current_classes.removeClass(change.class);
201 | }
202 |
203 | if (current_pos < code.len) {
204 | try current_classes.getClasses(&class_list);
205 | try printSpan(writer, code, current_pos, code.len, class_list.items, arena);
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/src/main.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const builtin = @import("builtin");
3 | const options = @import("options");
4 | const tracy = @import("tracy");
5 | const fatal = @import("fatal.zig");
6 | const worker = @import("worker.zig");
7 | const root = @import("root.zig");
8 | const Allocator = std.mem.Allocator;
9 |
10 | const log = std.log.scoped(.main);
11 |
12 | pub const std_options: std.Options = .{
13 | .log_level = .err,
14 | .log_scope_levels = options.log_scope_levels,
15 | };
16 |
17 | const Command = enum {
18 | init,
19 | release,
20 | debug,
21 | help,
22 | @"-h",
23 | @"--help",
24 | version,
25 | @"-v",
26 | @"--version",
27 | // Because other ssgs have them:
28 | serve,
29 | server,
30 | dev,
31 | develop,
32 | };
33 |
34 | var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
35 | pub const gpa = if (builtin.single_threaded)
36 | debug_allocator.allocator()
37 | else
38 | std.heap.smp_allocator;
39 |
40 | pub fn main() u8 {
41 | errdefer |err| switch (err) {
42 | error.OutOfMemory, error.Overflow => fatal.oom(),
43 | };
44 |
45 | root.progress = std.Progress.start(.{ .draw_buffer = &root.progress_buf });
46 | defer root.progress.end();
47 |
48 | if (builtin.mode == .Debug) {
49 | std.debug.print(
50 | \\*-----------------------------------------------*
51 | \\| WARNING: THIS IS A DEBUG BUILD OF ZINE |
52 | \\|-----------------------------------------------|
53 | \\| Debug builds enable expensive sanity checks |
54 | \\| that reduce performance. |
55 | \\| |
56 | \\| To create a release build, run: |
57 | \\| |
58 | \\| zig build --release=fast |
59 | \\| |
60 | \\| If you're investigating a bug in Zine, then a |
61 | \\| debug build might turn confusing behavior |
62 | \\| into a crash. |
63 | \\| |
64 | \\| To disable all forms of concurrency, you can |
65 | \\| add the following flag to your build command: |
66 | \\| |
67 | \\| -Dsingle-threaded |
68 | \\| |
69 | \\*-----------------------------------------------*
70 | \\
71 | \\
72 | , .{});
73 | }
74 | if (tracy.enable) {
75 | std.debug.print(
76 | \\*-----------------------------------------------*
77 | \\| WARNING: TRACING ENABLED |
78 | \\|-----------------------------------------------|
79 | \\| Tracing introduces a significant performance |
80 | \\| overhead. |
81 | \\| |
82 | \\| If you're not interested in tracing Zine, |
83 | \\| remove `-Dtracy` when building again. |
84 | \\*-----------------------------------------------*
85 | \\
86 | \\
87 | , .{});
88 | }
89 |
90 | if (options.tsan) {
91 | std.debug.print(
92 | \\*-----------------------------------------------*
93 | \\| WARNING: TSAN ENABLED |
94 | \\|-----------------------------------------------|
95 | \\| Thread sanitizer introduces a significant |
96 | \\| performance overhead. |
97 | \\| |
98 | \\| If you're not interested in debugging |
99 | \\| concurrency bugs in Zine, remove `-Dtsan` |
100 | \\| when building again. |
101 | \\*-----------------------------------------------*
102 | \\
103 | \\
104 | , .{});
105 | }
106 |
107 | const args = try std.process.argsAlloc(gpa);
108 | defer std.process.argsFree(gpa, args);
109 |
110 | const cmd = blk: {
111 | if (args.len >= 2) {
112 | if (std.meta.stringToEnum(Command, args[1])) |cmd| {
113 | break :blk cmd;
114 | }
115 | }
116 |
117 | @import("cli/serve.zig").serve(gpa, args[1..]);
118 | };
119 |
120 | const any_error = switch (cmd) {
121 | .init => @import("cli/init.zig").init(gpa, args[2..]),
122 | .release => @import("cli/release.zig").release(gpa, args[2..]),
123 | .debug => @import("cli/debug.zig").debug(gpa, args[2..]),
124 | .help, .@"-h", .@"--help" => fatal.help(),
125 | .version, .@"-v", .@"--version" => printVersion(),
126 | .serve, .server, .dev, .develop => {
127 | std.debug.print(
128 | "error: run zine without any subcommand to start the development web server\n\n",
129 | .{},
130 | );
131 | fatal.help();
132 | },
133 | };
134 |
135 | return @intFromBool(any_error);
136 | }
137 |
138 | fn printVersion() noreturn {
139 | std.debug.print("{s}\n", .{options.version});
140 | std.process.exit(0);
141 | }
142 |
--------------------------------------------------------------------------------
/src/render.zig:
--------------------------------------------------------------------------------
1 | pub const html = @import("render/html.zig").html;
2 | pub const htmlToc = @import("render/html.zig").htmlToc;
3 | pub const htmlTocDetails = @import("render/html.zig").htmlTocDetails;
4 |
--------------------------------------------------------------------------------
/tests/content-scanning/collisions/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/zine/aa43d44c2d271cb309549e8d7eab0a7fe6bb06f2/tests/content-scanning/collisions/assets/.keep
--------------------------------------------------------------------------------
/tests/content-scanning/collisions/content/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Homepage",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .aliases = [
7 | "index.html",
8 | "foo/bar/baz.html",
9 | "foo/bar.html",
10 | "README.html",
11 | "another_index.html",
12 | ],
13 | .alternatives = [{
14 | .name = "readme",
15 | .output = "nested/path/page/README.html",
16 | .layout = "",
17 | }],
18 | .draft = false,
19 | ---
20 | Your **SuperMD** content goes here.
21 |
--------------------------------------------------------------------------------
/tests/content-scanning/collisions/content/nested/path/page.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "foo",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "page.shtml",
6 | .aliases = [
7 | "/another_index.html",
8 | // None of these aliases should collide because they're all relative:
9 | "foo/bar/baz.html",
10 | "foo/bar.html",
11 | ],
12 | .alternatives = [{
13 | .name = "readme",
14 | .output = "README.html",
15 | .layout = "",
16 | }],
17 | .draft = false,
18 | ---
19 |
20 |
--------------------------------------------------------------------------------
/tests/content-scanning/collisions/content/page.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Sections",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "page.shtml",
6 | .aliases = [
7 | "/foo/bar/baz.html",
8 | "/foo/bar.html",
9 | "/README.html",
10 | "/another_page.html",
11 | "/nested/path/page/index.html"
12 | ],
13 | .alternatives = [{
14 | .name = "readme",
15 | .output = "README.html",
16 | .layout = "",
17 | }],
18 | .draft = false,
19 | ---
20 |
21 |
--------------------------------------------------------------------------------
/tests/content-scanning/collisions/content/page/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Page",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "page.shtml",
6 | .draft = false,
7 | ---
8 | Your **SuperMD** content goes here.
9 |
--------------------------------------------------------------------------------
/tests/content-scanning/collisions/layouts/index.shtml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/zine/aa43d44c2d271cb309549e8d7eab0a7fe6bb06f2/tests/content-scanning/collisions/layouts/index.shtml
--------------------------------------------------------------------------------
/tests/content-scanning/collisions/layouts/page.shtml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/zine/aa43d44c2d271cb309549e8d7eab0a7fe6bb06f2/tests/content-scanning/collisions/layouts/page.shtml
--------------------------------------------------------------------------------
/tests/content-scanning/collisions/snapshot.txt:
--------------------------------------------------------------------------------
1 | *-----------------------------------------------*
2 | | WARNING: THIS IS A DEBUG BUILD OF ZINE |
3 | |-----------------------------------------------|
4 | | Debug builds enable expensive sanity checks |
5 | | that reduce performance. |
6 | | |
7 | | To create a release build, run: |
8 | | |
9 | | zig build --release=fast |
10 | | |
11 | | If you're investigating a bug in Zine, then a |
12 | | debug build might turn confusing behavior |
13 | | into a crash. |
14 | | |
15 | | To disable all forms of concurrency, you can |
16 | | add the following flag to your build command: |
17 | | |
18 | | -Dsingle-threaded |
19 | | |
20 | *-----------------------------------------------*
21 |
22 | content/index.smd:16:15: error: invalid layout in alternatives
23 | | .layout = "",
24 | | ^^
25 |
26 | content/page.smd:16:13: error: invalid layout in alternatives
27 | | .layout = "",
28 | | ^^
29 |
30 | content/nested/path/page.smd:15:15: error: invalid layout in alternatives
31 | | .layout = "",
32 | | ^^
33 |
34 | page/index.html: error: output url collision detected
35 | between page.smd (main output)
36 | and page/index.smd (main output)
37 |
38 | index.html: error: output url collision detected
39 | between index.smd (main output)
40 | and index.smd (page alias)
41 |
42 | foo/bar/baz.html: error: output url collision detected
43 | between index.smd (page alias)
44 | and page.smd (page alias)
45 |
46 | foo/bar.html: error: output url collision detected
47 | between index.smd (page alias)
48 | and page.smd (page alias)
49 |
50 | README.html: error: output url collision detected
51 | between index.smd (page alias)
52 | and page.smd (page alias)
53 |
54 | nested/path/page/index.html: error: output url collision detected
55 | between nested/path/page.smd (main output)
56 | and page.smd (page alias)
57 |
58 | another_index.html: error: output url collision detected
59 | between index.smd (page alias)
60 | and nested/path/page.smd (page alias)
61 |
62 | nested/path/page/README.html: error: output url collision detected
63 | between index.smd (page alternative 'readme')
64 | and nested/path/page.smd (page alternative 'readme')
65 |
66 | ----------------------------
67 | -- VARIANT --
68 | ----------------------------
69 | .id = 0,
70 | .content_dir_path = content
71 |
72 | ------- SECTION -------
73 | .index = 1,
74 | .section_path = content/,
75 | .pages = [
76 | content/page.smd
77 | content/page/index.smd
78 | content/nested/path/page.smd
79 | ],
80 |
81 |
82 | ------- SECTION -------
83 | .index = 2,
84 | .section_path = content/page/,
85 | .pages = [
86 | ],
87 |
88 |
89 |
90 | ----- EXIT CODE: 1 -----
91 |
--------------------------------------------------------------------------------
/tests/content-scanning/collisions/zine.ziggy:
--------------------------------------------------------------------------------
1 | Site {
2 | .title = "Sample Site",
3 | .host_url = "https://example.com",
4 | .content_dir_path = "content",
5 | .layouts_dir_path = "layouts",
6 | .assets_dir_path = "content",
7 | }
--------------------------------------------------------------------------------
/tests/content-scanning/frontmatter/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/zine/aa43d44c2d271cb309549e8d7eab0a7fe6bb06f2/tests/content-scanning/frontmatter/assets/.keep
--------------------------------------------------------------------------------
/tests/content-scanning/frontmatter/content/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Homepage",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "",
6 | .aliases = [
7 | "good.html",
8 | "/also/good.html",
9 | ],
10 | .alternatives = [{
11 | .name = "good",
12 | .output = "also/good/but/not/a/collision.html",
13 | .layout = "foo.html",
14 | },{
15 | .name = "also good",
16 | .output = "good/url/though.html",
17 | .layout = "foo.html",
18 | },{
19 | .name = "good name and good url",
20 | .layout = "foo.html",
21 | .output = "foobar.html",
22 | }],
23 | .draft = false,
24 | ---
25 | Your **SuperMD** content goes here.
26 |
--------------------------------------------------------------------------------
/tests/content-scanning/frontmatter/content/validation-errors.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Homepage",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "",
6 | .aliases = [
7 | "good.html",
8 | "/also/good.html",
9 | "bad path 💩",
10 | ],
11 | .alternatives = [{
12 | .name = "good",
13 | .output = "also/good/but/not/a/collision.html",
14 | .layout = "foo.html",
15 | },{
16 | .name = "",
17 | .output = "good/url/though.html",
18 | .layout = "foo.html",
19 | },{
20 | .name = "good name but bad url",
21 | .output = "",
22 | .layout = "foo.html",
23 | }],
24 | .draft = false,
25 | ---
26 | Your **SuperMD** content goes here.
27 |
--------------------------------------------------------------------------------
/tests/content-scanning/frontmatter/content/wrong-syntax.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Homepage",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "",
6 | .aliases = [
7 | "good.html",
8 | "/also/good.html",
9 | "bad path 💩",
10 | ],
11 | .alternatives = [{
12 | .name = "good",
13 | .output = "also/good/but/not/a/collision.html",
14 | .layout = "foo.html",
15 | },{
16 | .name = "",
17 | .output = "good/url/though.html"
18 | .layout = "foo.html",
19 | },{
20 | .name = "good name but bad url",
21 | .output = "",
22 | .layout = "foo.html",
23 | }],
24 | .draft = false,
25 | ---
26 | Your **SuperMD** content goes here.
27 |
--------------------------------------------------------------------------------
/tests/content-scanning/frontmatter/layouts/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/zine/aa43d44c2d271cb309549e8d7eab0a7fe6bb06f2/tests/content-scanning/frontmatter/layouts/.keep
--------------------------------------------------------------------------------
/tests/content-scanning/frontmatter/snapshot.txt:
--------------------------------------------------------------------------------
1 | *-----------------------------------------------*
2 | | WARNING: THIS IS A DEBUG BUILD OF ZINE |
3 | |-----------------------------------------------|
4 | | Debug builds enable expensive sanity checks |
5 | | that reduce performance. |
6 | | |
7 | | To create a release build, run: |
8 | | |
9 | | zig build --release=fast |
10 | | |
11 | | If you're investigating a bug in Zine, then a |
12 | | debug build might turn confusing behavior |
13 | | into a crash. |
14 | | |
15 | | To disable all forms of concurrency, you can |
16 | | add the following flag to your build command: |
17 | | |
18 | | -Dsingle-threaded |
19 | | |
20 | *-----------------------------------------------*
21 |
22 | content/wrong-syntax.smd:18:5:
23 | .layout = "foo.html",
24 | ^
25 | unexpected '.', expected: ',' or '}'
26 |
27 |
28 | content/index.smd:5:11: error: missing layout file
29 | | .layout = "",
30 | | ^^
31 |
32 | content/validation-errors.smd:5:11: error: missing layout file
33 | | .layout = "",
34 | | ^^
35 |
36 | content/validation-errors.smd:9:4: error: invalid value in 'aliases'
37 | | "bad path 💩",
38 | | ^^^^^^^^^^^^^^^
39 |
40 | content/validation-errors.smd:16:13: error: invalid name in alternatives
41 | | .name = "",
42 | | ^^
43 |
44 | content/validation-errors.smd:21:15: error: invalid path in alternatives
45 | | .output = "",
46 | | ^^
47 |
48 | ----------------------------
49 | -- VARIANT --
50 | ----------------------------
51 | .id = 0,
52 | .content_dir_path = content
53 |
54 | ------- SECTION -------
55 | .index = 1,
56 | .section_path = content/,
57 | .pages = [
58 | content/validation-errors.smd
59 | content/wrong-syntax.smd
60 | ],
61 |
62 |
63 |
64 | ----- EXIT CODE: 1 -----
65 |
--------------------------------------------------------------------------------
/tests/content-scanning/frontmatter/zine.ziggy:
--------------------------------------------------------------------------------
1 | Site {
2 | .title = "Sample Site",
3 | .host_url = "https://example.com",
4 | .content_dir_path = "content",
5 | .layouts_dir_path = "layouts",
6 | .assets_dir_path = "content",
7 | }
--------------------------------------------------------------------------------
/tests/content-scanning/page-analysis/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/zine/aa43d44c2d271cb309549e8d7eab0a7fe6bb06f2/tests/content-scanning/page-analysis/assets/.keep
--------------------------------------------------------------------------------
/tests/content-scanning/page-analysis/assets/code.zig:
--------------------------------------------------------------------------------
1 | const foo = 42;
2 |
--------------------------------------------------------------------------------
/tests/content-scanning/page-analysis/assets/skater.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/zine/aa43d44c2d271cb309549e8d7eab0a7fe6bb06f2/tests/content-scanning/page-analysis/assets/skater.webp
--------------------------------------------------------------------------------
/tests/content-scanning/page-analysis/content/code.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Code",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | ---
8 |
9 | ```
10 | correct!
11 | ```
12 |
13 | ```=html
14 | correct!
15 | ```
16 |
17 | ```=html
18 | wrong!
19 | ```
20 |
21 | ```rust
22 | // Correct!
23 | ```
24 |
25 | ```zig
26 | // correct
27 | ```
28 |
--------------------------------------------------------------------------------
/tests/content-scanning/page-analysis/content/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Homepage",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .alternatives = [{
7 | .name = "correct-alt",
8 | .output = "arst.html",
9 | .layout = "index.shtml",
10 | }],
11 | .draft = false,
12 | ---
13 |
14 | # [Ok Ref]($heading.id('okref')) // correct
15 |
16 | # Wrong asset
17 | []($image.siteAsset('doesntexist.jpg')) //wrong
18 |
19 | # Self page, correct alternative
20 | []($link.alternative('correct-alt')) //correct
21 |
22 | # Self page, wrong alternative
23 | []($link.alternative('doesntexist-alternative')) //wrong
24 |
25 | # Self page, correct ref
26 | []($link.ref('okref')) //correct
27 |
28 | # Correct page
29 | []($link.page('other')) //correct
30 |
31 | # Correct page
32 | []($link.sub('other')) //correct
33 |
34 | # Wrong page
35 | []($link.page('doesntexist')) //wrong
36 |
37 | # Correct page, wrong alternative
38 | []($link.page('other').alternative('doesntexist')) // wrong
39 |
40 | # Markdown syntax
41 |  //wrong
42 |  //wrong
43 |  // wrong
44 |  // correct
45 |  // correct
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/tests/content-scanning/page-analysis/content/other.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Sections",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "sections.shtml",
6 | .draft = false,
7 | ---
8 |
9 | # [H1]($section.id('h1'))
10 | Lorem Ipsum 1
11 |
12 | ## [H2]($section.id('h2'))
13 | Lorem Ipsum 2
14 |
15 | ### [H3]($section.id('h3'))
16 | Lorem Ipsum 3
17 |
18 |
19 | ```zig++ wrong
20 | ```
21 |
22 | [](<$code.siteAsset('code.zig').language('zig++')>) //wrong
23 |
--------------------------------------------------------------------------------
/tests/content-scanning/page-analysis/content/parse.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "parsing errors",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | ---
8 | # Inline html
9 | wrong
10 |
11 | wrong block-level html
12 |
13 | []($link.page(/other)) //wrong
14 | []($link.page('/other)) //wrong
15 |
16 | # Correct page but wrong path (no abs paths in scripty arguments)
17 | []($link.page('/other')) //wrong
18 |
19 | # Wrong page (the dot makes it wrong)
20 | []($link.page('./other')) //wrong
21 |
22 | # Bad paths
23 | []($link.page('foo//bar')) //wrong 1/9
24 |
25 | []($link.page('foo/./bar')) //wrong 2/9
26 |
27 | []($link.page('foo/../bar')) //wrong 3/9
28 |
29 | []($link.page('foo/.')) //wrong 4/9
30 |
31 | []($link.page('foo/..')) //wrong 5/9
32 |
33 | []($link.page('a//foo/./bar')) //wrong 6/9
34 |
35 | []($link.page('a//foo/../bar')) //wrong 7/9
36 |
37 | []($link.page('a//foo/.')) //wrong 8/9
38 |
39 | []($link.page('a//foo/..')) //wrong 9/9
40 |
41 |
--------------------------------------------------------------------------------
/tests/content-scanning/page-analysis/content/skater.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/zine/aa43d44c2d271cb309549e8d7eab0a7fe6bb06f2/tests/content-scanning/page-analysis/content/skater.webp
--------------------------------------------------------------------------------
/tests/content-scanning/page-analysis/layouts/archive-entry.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 | Prev:
21 |
22 |
23 |
24 |
25 |
26 |
30 |
31 |
32 |
33 |
34 | Next:
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/tests/content-scanning/page-analysis/layouts/archive.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/tests/content-scanning/page-analysis/layouts/index.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tests/content-scanning/page-analysis/layouts/sections.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
-- TABLE OF CONTENTS --
11 |
12 |
13 |
14 |
-- SECTION BEGIN --
15 |
16 |
-- SECTION END --
17 |
18 |
19 |
--------------------------------------------------------------------------------
/tests/content-scanning/page-analysis/snapshot.txt:
--------------------------------------------------------------------------------
1 | *-----------------------------------------------*
2 | | WARNING: THIS IS A DEBUG BUILD OF ZINE |
3 | |-----------------------------------------------|
4 | | Debug builds enable expensive sanity checks |
5 | | that reduce performance. |
6 | | |
7 | | To create a release build, run: |
8 | | |
9 | | zig build --release=fast |
10 | | |
11 | | If you're investigating a bug in Zine, then a |
12 | | debug build might turn confusing behavior |
13 | | into a crash. |
14 | | |
15 | | To disable all forms of concurrency, you can |
16 | | add the following flag to your build command: |
17 | | |
18 | | -Dsingle-threaded |
19 | | |
20 | *-----------------------------------------------*
21 |
22 | content/code.smd:18:3: [erroneous_end_tag]
23 | | wrong!
24 | | ^^^^
25 |
26 | content/parse.smd:10:1: [html_is_forbidden]
27 | | wrong block-level html
28 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
29 |
30 | content/parse.smd:12:1: [scripty] syntax error
31 | | []($link.page(/other)) //wrong
32 | | ^^^^^^^^^^^^^^^^^^^^^^
33 |
34 | content/parse.smd:13:1: [scripty] syntax error
35 | | []($link.page('/other)) //wrong
36 | | ^^^^^^^^^^^^^^^^^^^^^^^
37 |
38 | content/parse.smd:16:1: [scripty] path must be relative
39 | | []($link.page('/other')) //wrong
40 | | ^^^^^^^^^^^^^^^^^^^^^^^^
41 |
42 | content/parse.smd:19:1: [scripty] '.' and '..' are not allowed in paths
43 | | []($link.page('./other')) //wrong
44 | | ^^^^^^^^^^^^^^^^^^^^^^^^^
45 |
46 | content/parse.smd:22:1: [scripty] empty component in path
47 | | []($link.page('foo//bar')) //wrong 1/9
48 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^
49 |
50 | content/parse.smd:24:1: [scripty] '.' and '..' are not allowed in paths
51 | | []($link.page('foo/./bar')) //wrong 2/9
52 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
53 |
54 | content/parse.smd:26:1: [scripty] '.' and '..' are not allowed in paths
55 | | []($link.page('foo/../bar')) //wrong 3/9
56 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
57 |
58 | content/parse.smd:28:1: [scripty] '.' and '..' are not allowed in paths
59 | | []($link.page('foo/.')) //wrong 4/9
60 | | ^^^^^^^^^^^^^^^^^^^^^^^
61 |
62 | content/parse.smd:30:1: [scripty] '.' and '..' are not allowed in paths
63 | | []($link.page('foo/..')) //wrong 5/9
64 | | ^^^^^^^^^^^^^^^^^^^^^^^^
65 |
66 | content/parse.smd:32:1: [scripty] empty component in path
67 | | []($link.page('a//foo/./bar')) //wrong 6/9
68 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
69 |
70 | content/parse.smd:34:1: [scripty] empty component in path
71 | | []($link.page('a//foo/../bar')) //wrong 7/9
72 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
73 |
74 | content/parse.smd:36:1: [scripty] empty component in path
75 | | []($link.page('a//foo/.')) //wrong 8/9
76 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^
77 |
78 | content/parse.smd:38:1: [scripty] empty component in path
79 | | []($link.page('a//foo/..')) //wrong 9/9
80 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
81 |
82 | content/index.smd:18:1: error: missing site asset
83 | | []($image.siteAsset('doesntexist.jpg')) //wrong
84 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
85 |
86 | content/index.smd:24:1: error: unknown alternative
87 | | []($link.alternative('doesntexist-alternative')) //wrong
88 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
89 |
90 | content/index.smd:36:1: error: unknown page
91 | | []($link.page('doesntexist')) //wrong
92 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
93 |
94 | content/index.smd:39:1: error: unknown alternative
95 | | []($link.page('other').alternative('doesntexist')) // wrong
96 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
97 |
98 | content/index.smd:42:1: error: missing site asset
99 | |  //wrong
100 | | ^^^^^^^^^^^^^
101 |
102 | content/index.smd:43:1: error: missing site asset
103 | |  //wrong
104 | | ^^^^^^^^^^^^^^
105 |
106 | content/index.smd:44:1: error: missing site asset
107 | |  // wrong
108 | | ^^^^^^^^^^^^
109 |
110 | content/other.smd:20:1: error: unknown language code
111 | | ```zig++ wrong
112 | | ^^^^^^^^^^^^^^^
113 |
114 | content/other.smd:23:1: error: unknown language code
115 | | [](<$code.siteAsset('code.zig').language('zig++')>) //wrong
116 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
117 |
118 | ----------------------------
119 | -- VARIANT --
120 | ----------------------------
121 | .id = 0,
122 | .content_dir_path = content
123 |
124 | ------- SECTION -------
125 | .index = 1,
126 | .section_path = content/,
127 | .pages = [
128 | content/parse.smd
129 | content/other.smd
130 | content/code.smd
131 | ],
132 |
133 | skater.webp (0)
134 |
135 |
136 | ----- EXIT CODE: 1 -----
137 |
--------------------------------------------------------------------------------
/tests/content-scanning/page-analysis/zine.ziggy:
--------------------------------------------------------------------------------
1 | Site {
2 | .title = "Sample Site",
3 | .host_url = "https://example.com",
4 | .content_dir_path = "content",
5 | .layouts_dir_path = "layouts",
6 | .assets_dir_path = "assets",
7 | }
--------------------------------------------------------------------------------
/tests/content-scanning/simple/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/zine/aa43d44c2d271cb309549e8d7eab0a7fe6bb06f2/tests/content-scanning/simple/assets/.keep
--------------------------------------------------------------------------------
/tests/content-scanning/simple/content/archive/2024/first.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "First post (2024)",
3 | .date = @date("2024-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "archive-entry.shtml",
6 | .draft = false,
7 | ---
8 | Lorem ipsum
9 |
--------------------------------------------------------------------------------
/tests/content-scanning/simple/content/archive/2024/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "2024",
3 | .date = @date("2024-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | ---
8 |
9 | # 2024
10 |
11 | Lorem ipsum
12 |
--------------------------------------------------------------------------------
/tests/content-scanning/simple/content/archive/2025/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "2025",
3 | .date = @date("2025-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | ---
8 |
9 | # 2025
10 |
11 | Lorem ipsum
12 |
--------------------------------------------------------------------------------
/tests/content-scanning/simple/content/archive/2025/second.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Second post (2025)",
3 | .date = @date("2024-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "archive-entry.shtml",
6 | .draft = false,
7 | ---
8 | dolor something something
9 |
--------------------------------------------------------------------------------
/tests/content-scanning/simple/content/archive/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Archive",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "archive.shtml",
6 | .draft = false,
7 | ---
8 |
9 | Archive
10 |
--------------------------------------------------------------------------------
/tests/content-scanning/simple/content/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Homepage",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | ---
8 | Your **SuperMD** content goes here.
9 |
10 |
11 | # H1
12 | Lorem Ipsum 1
13 |
14 | ## H2
15 | Lorem Ipsum 2
16 |
17 | #### H3
18 | Lorem Ipsum 3
19 |
20 |
--------------------------------------------------------------------------------
/tests/content-scanning/simple/content/sections.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Sections",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "sections.shtml",
6 | .draft = false,
7 | ---
8 |
9 | # [H1]($section.id('h1'))
10 | Lorem Ipsum 1
11 |
12 | ## [H2]($section.id('h2'))
13 | Lorem Ipsum 2
14 |
15 | ### [H3]($section.id('h3'))
16 | Lorem Ipsum 3
17 |
18 |
--------------------------------------------------------------------------------
/tests/content-scanning/simple/layouts/archive-entry.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 | Prev:
21 |
22 |
23 |
24 |
25 |
26 |
30 |
31 |
32 |
33 |
34 | Next:
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/tests/content-scanning/simple/layouts/archive.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/tests/content-scanning/simple/layouts/index.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tests/content-scanning/simple/layouts/sections.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
-- TABLE OF CONTENTS --
11 |
12 |
13 |
14 |
-- SECTION BEGIN --
15 |
16 |
-- SECTION END --
17 |
18 |
19 |
--------------------------------------------------------------------------------
/tests/content-scanning/simple/snapshot.txt:
--------------------------------------------------------------------------------
1 | *-----------------------------------------------*
2 | | WARNING: THIS IS A DEBUG BUILD OF ZINE |
3 | |-----------------------------------------------|
4 | | Debug builds enable expensive sanity checks |
5 | | that reduce performance. |
6 | | |
7 | | To create a release build, run: |
8 | | |
9 | | zig build --release=fast |
10 | | |
11 | | If you're investigating a bug in Zine, then a |
12 | | debug build might turn confusing behavior |
13 | | into a crash. |
14 | | |
15 | | To disable all forms of concurrency, you can |
16 | | add the following flag to your build command: |
17 | | |
18 | | -Dsingle-threaded |
19 | | |
20 | *-----------------------------------------------*
21 |
22 | ----------------------------
23 | -- VARIANT --
24 | ----------------------------
25 | .id = 0,
26 | .content_dir_path = content
27 |
28 | ------- SECTION -------
29 | .index = 1,
30 | .section_path = content/,
31 | .pages = [
32 | content/sections.smd
33 | content/archive/index.smd
34 | ],
35 |
36 |
37 | ------- SECTION -------
38 | .index = 2,
39 | .section_path = content/archive/,
40 | .pages = [
41 | content/archive/2025/index.smd
42 | content/archive/2024/index.smd
43 | ],
44 |
45 |
46 | ------- SECTION -------
47 | .index = 3,
48 | .section_path = content/archive/2024/,
49 | .pages = [
50 | content/archive/2024/first.smd
51 | ],
52 |
53 |
54 | ------- SECTION -------
55 | .index = 4,
56 | .section_path = content/archive/2025/,
57 | .pages = [
58 | content/archive/2025/second.smd
59 | ],
60 |
61 |
62 |
63 | ----- EXIT CODE: 0 -----
64 |
--------------------------------------------------------------------------------
/tests/content-scanning/simple/zine.ziggy:
--------------------------------------------------------------------------------
1 | Site {
2 | .title = "Sample Site",
3 | .host_url = "https://example.com",
4 | .content_dir_path = "content",
5 | .layouts_dir_path = "layouts",
6 | .assets_dir_path = "content",
7 | }
--------------------------------------------------------------------------------
/tests/content-scanning/templates/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/zine/aa43d44c2d271cb309549e8d7eab0a7fe6bb06f2/tests/content-scanning/templates/assets/.keep
--------------------------------------------------------------------------------
/tests/content-scanning/templates/content/another.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Page",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "doesntexist-layout.shtml",
6 | .draft = false,
7 | ---
8 | Your **SuperMD** content goes here.
9 |
--------------------------------------------------------------------------------
/tests/content-scanning/templates/content/badextend.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Page",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "badextend.shtml",
6 | .draft = false,
7 | ---
8 | Your **SuperMD** content goes here.
9 |
--------------------------------------------------------------------------------
/tests/content-scanning/templates/content/badhtml.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Page",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "badhtml.shtml",
6 | .draft = false,
7 | ---
8 | Your **SuperMD** content goes here.
9 |
--------------------------------------------------------------------------------
/tests/content-scanning/templates/content/badshtml.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Page",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "badshtml.shtml",
6 | .draft = false,
7 | ---
8 | Your **SuperMD** content goes here.
9 |
--------------------------------------------------------------------------------
/tests/content-scanning/templates/content/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Homepage",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | ---
8 | Your **SuperMD** content goes here.
9 |
--------------------------------------------------------------------------------
/tests/content-scanning/templates/content/page.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Page",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "page.shtml",
6 | .draft = false,
7 | ---
8 | Your **SuperMD** content goes here.
9 |
--------------------------------------------------------------------------------
/tests/content-scanning/templates/layouts/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/zine/aa43d44c2d271cb309549e8d7eab0a7fe6bb06f2/tests/content-scanning/templates/layouts/.keep
--------------------------------------------------------------------------------
/tests/content-scanning/templates/layouts/badextend.shtml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/content-scanning/templates/layouts/badhtml.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/tests/content-scanning/templates/layouts/badshtml.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tests/content-scanning/templates/layouts/index.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hello World!
4 |
--------------------------------------------------------------------------------
/tests/content-scanning/templates/layouts/oops.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/zine/aa43d44c2d271cb309549e8d7eab0a7fe6bb06f2/tests/content-scanning/templates/layouts/oops.html
--------------------------------------------------------------------------------
/tests/content-scanning/templates/layouts/page.shtml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/zine/aa43d44c2d271cb309549e8d7eab0a7fe6bb06f2/tests/content-scanning/templates/layouts/page.shtml
--------------------------------------------------------------------------------
/tests/content-scanning/templates/layouts/templates/base.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/tests/content-scanning/templates/layouts/templates/withmenu.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/tests/content-scanning/templates/snapshot.txt:
--------------------------------------------------------------------------------
1 | *-----------------------------------------------*
2 | | WARNING: THIS IS A DEBUG BUILD OF ZINE |
3 | |-----------------------------------------------|
4 | | Debug builds enable expensive sanity checks |
5 | | that reduce performance. |
6 | | |
7 | | To create a release build, run: |
8 | | |
9 | | zig build --release=fast |
10 | | |
11 | | If you're investigating a bug in Zine, then a |
12 | | debug build might turn confusing behavior |
13 | | into a crash. |
14 | | |
15 | | To disable all forms of concurrency, you can |
16 | | add the following flag to your build command: |
17 | | |
18 | | -Dsingle-threaded |
19 | | |
20 | *-----------------------------------------------*
21 |
22 | WARNING: found plain HTML file layouts/oops.html, did you mean to give it a shtml extension?
23 | content/another.smd:5:11: error: missing layout file
24 | | .layout = "doesntexist-layout.shtml",
25 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^
26 |
27 | layouts/badextend.shtml: error: extending a template that doesn't exist
28 | template 'doesntexist-template.shtml' does not exist
29 | layouts/badhtml.shtml:1:4: erroneous_end_tag
30 | layouts/badshtml.shtml:0:1: extend_without_template_attr
31 | layouts/badshtml.shtml:2:3: super_parent_element_missing_id
32 | layouts/badshtml.shtml:8:3: unexpected_extend
33 | layouts/badshtml.shtml:10:1: top_level_super
34 | layouts/badshtml.shtml:6:4: super_under_branching
35 | ----------------------------
36 | -- VARIANT --
37 | ----------------------------
38 | .id = 0,
39 | .content_dir_path = content
40 |
41 | ------- SECTION -------
42 | .index = 1,
43 | .section_path = content/,
44 | .pages = [
45 | content/page.smd
46 | content/badshtml.smd
47 | content/badhtml.smd
48 | content/badextend.smd
49 | content/another.smd
50 | ],
51 |
52 |
53 |
54 | ----- EXIT CODE: 1 -----
55 |
--------------------------------------------------------------------------------
/tests/content-scanning/templates/zine.ziggy:
--------------------------------------------------------------------------------
1 | Site {
2 | .title = "Sample Site",
3 | .host_url = "https://example.com",
4 | .content_dir_path = "content",
5 | .layouts_dir_path = "layouts",
6 | .assets_dir_path = "content",
7 | }
--------------------------------------------------------------------------------
/tests/drafts/simple/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/zine/aa43d44c2d271cb309549e8d7eab0a7fe6bb06f2/tests/drafts/simple/assets/.keep
--------------------------------------------------------------------------------
/tests/drafts/simple/content/archive/2024/first.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "First post (2024)",
3 | .date = @date("2024-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "archive-entry.shtml",
6 | .draft = true,
7 | ---
8 | Lorem ipsum
9 |
--------------------------------------------------------------------------------
/tests/drafts/simple/content/archive/2024/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "2024",
3 | .date = @date("2024-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | ---
8 |
9 | # 2024
10 |
11 | Lorem ipsum
12 |
--------------------------------------------------------------------------------
/tests/drafts/simple/content/archive/2025/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "2025",
3 | .date = @date("2025-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | ---
8 |
9 | # 2025
10 |
11 | Lorem ipsum
12 |
--------------------------------------------------------------------------------
/tests/drafts/simple/content/archive/2025/second.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Second post (2025)",
3 | .date = @date("2024-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "archive-entry.shtml",
6 | .draft = false,
7 | ---
8 | dolor something something
9 |
--------------------------------------------------------------------------------
/tests/drafts/simple/content/archive/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Archive",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "archive.shtml",
6 | .draft = true,
7 | ---
8 |
9 | Archive
10 |
--------------------------------------------------------------------------------
/tests/drafts/simple/content/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Homepage",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | ---
8 | Your **SuperMD** content goes here.
9 |
10 |
11 | # H1
12 | Lorem Ipsum 1
13 |
14 | ## H2
15 | Lorem Ipsum 2
16 |
17 | #### H3
18 | Lorem Ipsum 3
19 |
20 |
--------------------------------------------------------------------------------
/tests/drafts/simple/content/sections.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Sections",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "sections.shtml",
6 | .draft = false,
7 | ---
8 |
9 | # [H1]($section.id('h1'))
10 | Lorem Ipsum 1
11 |
12 | ## [H2]($section.id('h2'))
13 | Lorem Ipsum 2
14 |
15 | ### [H3]($section.id('h3'))
16 | Lorem Ipsum 3
17 |
18 | This is a footnote[^1]
19 |
20 | [^1]: That should not break this page
21 |
--------------------------------------------------------------------------------
/tests/drafts/simple/layouts/archive-entry.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 | Prev:
21 |
22 |
23 |
24 |
25 |
26 |
30 |
31 |
32 |
33 |
34 | Next:
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/tests/drafts/simple/layouts/archive.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/tests/drafts/simple/layouts/index.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tests/drafts/simple/layouts/sections.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
-- TABLE OF CONTENTS --
11 |
12 |
13 |
14 |
-- SECTION BEGIN --
15 |
16 |
-- SECTION END --
17 |
18 |
19 |
--------------------------------------------------------------------------------
/tests/drafts/simple/snapshot.txt:
--------------------------------------------------------------------------------
1 | *-----------------------------------------------*
2 | | WARNING: THIS IS A DEBUG BUILD OF ZINE |
3 | |-----------------------------------------------|
4 | | Debug builds enable expensive sanity checks |
5 | | that reduce performance. |
6 | | |
7 | | To create a release build, run: |
8 | | |
9 | | zig build --release=fast |
10 | | |
11 | | If you're investigating a bug in Zine, then a |
12 | | debug build might turn confusing behavior |
13 | | into a crash. |
14 | | |
15 | | To disable all forms of concurrency, you can |
16 | | add the following flag to your build command: |
17 | | |
18 | | -Dsingle-threaded |
19 | | |
20 | *-----------------------------------------------*
21 |
22 |
--------------------------------------------------------------------------------
/tests/drafts/simple/snapshot/archive/2024/first/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Simple Test Website
7 |
8 |
9 | First post (2024)
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Next:
20 | Second post (2025)
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/tests/drafts/simple/snapshot/archive/2024/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Test Website
6 |
7 |
8 | 2024
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tests/drafts/simple/snapshot/archive/2025/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Test Website
6 |
7 |
8 | 2025
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tests/drafts/simple/snapshot/archive/2025/second/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Simple Test Website
7 |
8 |
9 | Second post (2025)
10 | dolor something something
11 |
12 |
13 |
14 |
15 | Prev:
16 | First post (2024)
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/tests/drafts/simple/snapshot/archive/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Test Website
6 |
7 |
8 | Archive
9 |
10 |
14 |
20 |
21 |
25 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/tests/drafts/simple/snapshot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Test Website
6 |
7 |
8 | Homepage
9 | Your SuperMD content goes here.
H1
Lorem Ipsum 1
H2
Lorem Ipsum 2
H3
Lorem Ipsum 3
10 |
11 |
--------------------------------------------------------------------------------
/tests/drafts/simple/snapshot/sections/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Test Website
6 |
7 |
8 | Sections
9 |
10 |
-- TABLE OF CONTENTS --
11 |
15 |
16 |
17 |
-- SECTION BEGIN --
18 |
19 |
-- SECTION END --
20 |
21 |
-- SECTION BEGIN --
22 |
23 |
-- SECTION END --
24 |
25 |
-- SECTION BEGIN --
26 |
Lorem Ipsum 3
This is a footnote
27 |
-- SECTION END --
28 |
29 |
30 |
--------------------------------------------------------------------------------
/tests/drafts/simple/zine.ziggy:
--------------------------------------------------------------------------------
1 | Site {
2 | .title = "Simple Test Website",
3 | .host_url = "https://example.com",
4 | .content_dir_path = "content",
5 | .layouts_dir_path = "layouts",
6 | .assets_dir_path = "content",
7 | }
--------------------------------------------------------------------------------
/tests/rendering/multi/content/de-DE/about.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "About",
3 | .date = @date("1990-01-01T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | ---
8 |
9 | ## About Page (en-US)
10 |
--------------------------------------------------------------------------------
/tests/rendering/multi/content/de-DE/contact-us.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Contact Us",
3 | .date = @date("1990-01-01T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .translation_key = "contact-us-page",
7 | .draft = false,
8 | ---
9 |
10 | ## Contact us (de-DE)
11 |
--------------------------------------------------------------------------------
/tests/rendering/multi/content/de-DE/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Homepage",
3 | .date = @date("1990-01-01T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | ---
8 |
9 | ## Home (en-US)
10 |
--------------------------------------------------------------------------------
/tests/rendering/multi/content/en-US/about.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "About",
3 | .date = @date("1990-01-01T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | ---
8 |
9 | ## About Page (en-US)
10 |
--------------------------------------------------------------------------------
/tests/rendering/multi/content/en-US/contact-us.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Contact us",
3 | .date = @date("1990-01-01T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .translation_key = "contact-us-page",
7 | .draft = false,
8 | ---
9 |
10 | ## Contact us (en-US)
11 |
--------------------------------------------------------------------------------
/tests/rendering/multi/content/en-US/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Homepage",
3 | .date = @date("1990-01-01T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | ---
8 |
9 | ## Home (en-US)
10 |
--------------------------------------------------------------------------------
/tests/rendering/multi/content/it-IT/contattaci.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Contattaci",
3 | .date = @date("1990-01-01T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .translation_key = "contact-us-page",
7 | .draft = false,
8 | ---
9 |
10 | ## Contattaci (it-IT)
11 |
--------------------------------------------------------------------------------
/tests/rendering/multi/content/it-IT/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Homepage",
3 | .date = @date("1990-01-01T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | ---
8 |
9 | ## Home (it-IT)
10 |
--------------------------------------------------------------------------------
/tests/rendering/multi/i18n/de-DE.ziggy:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/tests/rendering/multi/i18n/en-US.ziggy:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/tests/rendering/multi/i18n/it-IT.ziggy:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/tests/rendering/multi/layouts/blog.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 |
15 |
16 |
25 |
--------------------------------------------------------------------------------
/tests/rendering/multi/layouts/blog.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Zine -- https://zine-ssg.io
7 | en-US
8 |
9 |
10 | -
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/tests/rendering/multi/layouts/devlog-archive.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 |
15 |
16 |
17 |
Past years
18 |
24 |
25 |
--------------------------------------------------------------------------------
/tests/rendering/multi/layouts/devlog.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
34 |
35 |
36 |
37 |
38 |
45 |
--------------------------------------------------------------------------------
/tests/rendering/multi/layouts/devlog.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Zine -- https://zine-ssg.io
7 | en-US
8 |
9 |
10 |
11 | -
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/tests/rendering/multi/layouts/index.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/rendering/multi/layouts/page.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/rendering/multi/layouts/post.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
22 |
23 |
24 |
25 |
26 |
40 |
--------------------------------------------------------------------------------
/tests/rendering/multi/layouts/templates/base.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
15 |
16 |
19 |
20 |
--------------------------------------------------------------------------------
/tests/rendering/multi/snapshot.txt:
--------------------------------------------------------------------------------
1 | *-----------------------------------------------*
2 | | WARNING: THIS IS A DEBUG BUILD OF ZINE |
3 | |-----------------------------------------------|
4 | | Debug builds enable expensive sanity checks |
5 | | that reduce performance. |
6 | | |
7 | | To create a release build, run: |
8 | | |
9 | | zig build --release=fast |
10 | | |
11 | | If you're investigating a bug in Zine, then a |
12 | | debug build might turn confusing behavior |
13 | | into a crash. |
14 | | |
15 | | To disable all forms of concurrency, you can |
16 | | add the following flag to your build command: |
17 | | |
18 | | -Dsingle-threaded |
19 | | |
20 | *-----------------------------------------------*
21 |
22 |
--------------------------------------------------------------------------------
/tests/rendering/multi/snapshot/about/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Test
7 |
8 |
9 |
10 |
11 | Test
12 |
22 |
23 | About
24 | About Page (en-US)
25 |
26 |
31 |
32 |
--------------------------------------------------------------------------------
/tests/rendering/multi/snapshot/contact-us/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Test
7 |
8 |
9 |
10 |
11 | Test
12 |
22 |
23 | Contact us
24 | Contact us (en-US)
25 |
26 |
33 |
34 |
--------------------------------------------------------------------------------
/tests/rendering/multi/snapshot/de-DE/about/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Test
7 |
8 |
9 |
10 |
11 | Test
12 |
22 |
23 | About
24 | About Page (en-US)
25 |
26 |
31 |
32 |
--------------------------------------------------------------------------------
/tests/rendering/multi/snapshot/de-DE/contact-us/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Test
7 |
8 |
9 |
10 |
11 | Test
12 |
22 |
23 | Contact Us
24 | Contact us (de-DE)
25 |
26 |
33 |
34 |
--------------------------------------------------------------------------------
/tests/rendering/multi/snapshot/de-DE/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Test
7 |
8 |
9 |
10 |
11 | Test
12 |
22 |
23 | Homepage
24 | Home (en-US)
25 |
26 |
33 |
34 |
--------------------------------------------------------------------------------
/tests/rendering/multi/snapshot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Test
7 |
8 |
9 |
10 |
11 | Test
12 |
22 |
23 | Homepage
24 | Home (en-US)
25 |
26 |
33 |
34 |
--------------------------------------------------------------------------------
/tests/rendering/multi/snapshot/it-IT/contattaci/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Prova
7 |
8 |
9 |
10 |
11 | Prova
12 |
19 |
20 | Contattaci
21 | Contattaci (it-IT)
22 |
23 |
30 |
31 |
--------------------------------------------------------------------------------
/tests/rendering/multi/snapshot/it-IT/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Prova
7 |
8 |
9 |
10 |
11 | Prova
12 |
19 |
20 | Homepage
21 | Home (it-IT)
22 |
23 |
30 |
31 |
--------------------------------------------------------------------------------
/tests/rendering/multi/zine.ziggy:
--------------------------------------------------------------------------------
1 | Multilingual {
2 | .host_url = "https://example,.org",
3 | .i18n_dir_path = "i18n",
4 | .layouts_dir_path = "layouts",
5 | .assets_dir_path = "assets",
6 | .locales = [
7 | {
8 | .code = "en-US",
9 | .name = "English (original)",
10 | .site_title = "Test",
11 | .content_dir_path = "content/en-US",
12 | .output_prefix_override = "",
13 | },
14 | {
15 | .code = "it-IT",
16 | .name = "Italiano",
17 | .site_title = "Prova",
18 | .content_dir_path = "content/it-IT",
19 | },
20 | {
21 | .code = "de-DE",
22 | .name = "German",
23 | .site_title = "Test",
24 | .content_dir_path = "content/de-DE",
25 | // .host_url_override = "de.example.org",
26 |
27 | },
28 | ],
29 | }
30 |
--------------------------------------------------------------------------------
/tests/rendering/simple/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kristoff-it/zine/aa43d44c2d271cb309549e8d7eab0a7fe6bb06f2/tests/rendering/simple/assets/.keep
--------------------------------------------------------------------------------
/tests/rendering/simple/content/archive/2024/first.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "First post (2024)",
3 | .date = @date("2024-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "archive-entry.shtml",
6 | .draft = false,
7 | ---
8 | Lorem ipsum
9 |
--------------------------------------------------------------------------------
/tests/rendering/simple/content/archive/2024/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "2024",
3 | .date = @date("2024-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | ---
8 |
9 | # 2024
10 |
11 | Lorem ipsum
12 |
--------------------------------------------------------------------------------
/tests/rendering/simple/content/archive/2025/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "2025",
3 | .date = @date("2025-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | ---
8 |
9 | # 2025
10 |
11 | Lorem ipsum
12 |
--------------------------------------------------------------------------------
/tests/rendering/simple/content/archive/2025/second.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Second post (2025)",
3 | .date = @date("2024-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "archive-entry.shtml",
6 | .draft = false,
7 | ---
8 | dolor something something
9 |
--------------------------------------------------------------------------------
/tests/rendering/simple/content/archive/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Archive",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "archive.shtml",
6 | .draft = false,
7 | ---
8 |
9 | Archive
10 |
--------------------------------------------------------------------------------
/tests/rendering/simple/content/index.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Homepage",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | ---
8 | Your **SuperMD** content goes here.
9 |
10 |
11 | # H1
12 | Lorem Ipsum 1
13 |
14 | ## H2
15 | Lorem Ipsum 2
16 |
17 | #### H3
18 | Lorem Ipsum 3
19 |
20 |
--------------------------------------------------------------------------------
/tests/rendering/simple/content/nested/aliases.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Homepage",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | .aliases = [
8 | "alias_relative.html",
9 | "/alias_absolute.html",
10 | "/aliases/path/with/leading/slash.html",
11 | "aliases/path/without/leading/slash.html",
12 | ],
13 | ---
14 | Your **SuperMD** content goes here.
15 |
16 |
17 | # H1
18 | Lorem Ipsum 1
19 |
20 | ## H2
21 | Lorem Ipsum 2
22 |
23 | #### H3
24 | Lorem Ipsum 3
25 |
--------------------------------------------------------------------------------
/tests/rendering/simple/content/sections.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Sections",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "sections.shtml",
6 | .draft = false,
7 | ---
8 |
9 | # [H1]($section.id('h1'))
10 | Lorem Ipsum 1
11 |
12 | ## [H2]($section.id('h2'))
13 | Lorem Ipsum 2
14 |
15 | ### [H3]($section.id('h3'))
16 | Lorem Ipsum 3
17 |
18 | This is a footnote[^1]
19 |
20 | [^1]: That should not break this page
21 |
--------------------------------------------------------------------------------
/tests/rendering/simple/content/syntax.smd:
--------------------------------------------------------------------------------
1 | ---
2 | .title = "Sections",
3 | .date = @date("2020-07-06T00:00:00"),
4 | .author = "Sample Author",
5 | .layout = "index.shtml",
6 | .draft = false,
7 | ---
8 |
9 | # [Zig Code]($section.id('zig'))
10 | ```zig
11 | const Sample = struct {
12 | a: i32,
13 | b: i32,
14 | };
15 |
16 | pub extern "sampleLibrary" fn SampleFunction(
17 | arg1: i32,
18 | arg2: *Sample,
19 | ) callconv(WINAPI) BOOL;
20 |
21 | pub inline fn main() anyerror!void {
22 | var sample = Sample{ .a = 1, .b = 2 };
23 | SampleFunction(1, &sample);
24 | }
25 | ```
26 |
27 | # [Rust Code]($section.id('rust'))
28 | ```rust
29 | #[derive(Debug, Clone, PartialEq)]
30 | pub struct ComplexData<'a, T: 'a + Display> {
31 | #[serde(rename = "identifier")]
32 | id: &'a str,
33 | #[serde(skip_serializing_if = "Option::is_none")]
34 | metadata: Option>,
35 | #[serde(default)]
36 | references: Vec<&'a RefCell>,
37 | timestamp: SystemTime,
38 | data: Box + 'a>,
39 | }
40 |
41 | impl<'a, T: Display> ComplexData<'a, T> {
42 | pub fn new(id: &'a str) -> Result> {
43 | Ok(Self {
44 | id,
45 | metadata: None,
46 | references: Vec::new(),
47 | timestamp: SystemTime::now(),
48 | data: Box::new(std::iter::empty()),
49 | })
50 | }
51 | }
52 | ```
53 |
--------------------------------------------------------------------------------
/tests/rendering/simple/layouts/archive-entry.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 | Prev:
21 |
22 |
23 |
24 |
25 |
26 |
30 |
31 |
32 |
33 |
34 | Next:
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/tests/rendering/simple/layouts/archive.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/tests/rendering/simple/layouts/index.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tests/rendering/simple/layouts/sections.shtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
-- TABLE OF CONTENTS --
11 |
12 |
13 |
14 |
-- SECTION BEGIN --
15 |
16 |
-- SECTION END --
17 |
18 |
19 |
--------------------------------------------------------------------------------
/tests/rendering/simple/snapshot.txt:
--------------------------------------------------------------------------------
1 | *-----------------------------------------------*
2 | | WARNING: THIS IS A DEBUG BUILD OF ZINE |
3 | |-----------------------------------------------|
4 | | Debug builds enable expensive sanity checks |
5 | | that reduce performance. |
6 | | |
7 | | To create a release build, run: |
8 | | |
9 | | zig build --release=fast |
10 | | |
11 | | If you're investigating a bug in Zine, then a |
12 | | debug build might turn confusing behavior |
13 | | into a crash. |
14 | | |
15 | | To disable all forms of concurrency, you can |
16 | | add the following flag to your build command: |
17 | | |
18 | | -Dsingle-threaded |
19 | | |
20 | *-----------------------------------------------*
21 |
22 |
--------------------------------------------------------------------------------
/tests/rendering/simple/snapshot/alias_absolute.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Test Website
6 |
7 |
8 | Homepage
9 | Your SuperMD content goes here.
H1
Lorem Ipsum 1
H2
Lorem Ipsum 2
H3
Lorem Ipsum 3
10 |
11 |
--------------------------------------------------------------------------------
/tests/rendering/simple/snapshot/aliases/path/with/leading/slash.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Test Website
6 |
7 |
8 | Homepage
9 | Your SuperMD content goes here.
H1
Lorem Ipsum 1
H2
Lorem Ipsum 2
H3
Lorem Ipsum 3
10 |
11 |
--------------------------------------------------------------------------------
/tests/rendering/simple/snapshot/archive/2024/first/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Simple Test Website
7 |
8 |
9 | First post (2024)
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Next:
20 | Second post (2025)
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/tests/rendering/simple/snapshot/archive/2024/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Test Website
6 |
7 |
8 | 2024
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tests/rendering/simple/snapshot/archive/2025/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Test Website
6 |
7 |
8 | 2025
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tests/rendering/simple/snapshot/archive/2025/second/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Simple Test Website
7 |
8 |
9 | Second post (2025)
10 | dolor something something
11 |
12 |
13 |
14 |
15 | Prev:
16 | First post (2024)
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/tests/rendering/simple/snapshot/archive/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Test Website
6 |
7 |
8 | Archive
9 |
10 |
14 |
20 |
21 |
25 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/tests/rendering/simple/snapshot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Test Website
6 |
7 |
8 | Homepage
9 | Your SuperMD content goes here.
H1
Lorem Ipsum 1
H2
Lorem Ipsum 2
H3
Lorem Ipsum 3
10 |
11 |
--------------------------------------------------------------------------------
/tests/rendering/simple/snapshot/nested/aliases/alias_relative.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Test Website
6 |
7 |
8 | Homepage
9 | Your SuperMD content goes here.
H1
Lorem Ipsum 1
H2
Lorem Ipsum 2
H3
Lorem Ipsum 3
10 |
11 |
--------------------------------------------------------------------------------
/tests/rendering/simple/snapshot/nested/aliases/aliases/path/without/leading/slash.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Test Website
6 |
7 |
8 | Homepage
9 | Your SuperMD content goes here.
H1
Lorem Ipsum 1
H2
Lorem Ipsum 2
H3
Lorem Ipsum 3
10 |
11 |
--------------------------------------------------------------------------------
/tests/rendering/simple/snapshot/nested/aliases/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Test Website
6 |
7 |
8 | Homepage
9 | Your SuperMD content goes here.
H1
Lorem Ipsum 1
H2
Lorem Ipsum 2
H3
Lorem Ipsum 3
10 |
11 |
--------------------------------------------------------------------------------
/tests/rendering/simple/snapshot/sections/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple Test Website
6 |
7 |
8 | Sections
9 |
10 |
-- TABLE OF CONTENTS --
11 |
15 |
16 |
17 |
-- SECTION BEGIN --
18 |
19 |
-- SECTION END --
20 |
21 |
-- SECTION BEGIN --
22 |
23 |
-- SECTION END --
24 |
25 |
-- SECTION BEGIN --
26 |
Lorem Ipsum 3
This is a footnote
27 |
-- SECTION END --
28 |
29 |
30 |
--------------------------------------------------------------------------------
/tests/rendering/simple/zine.ziggy:
--------------------------------------------------------------------------------
1 | Site {
2 | .title = "Simple Test Website",
3 | .host_url = "https://example.com",
4 | .content_dir_path = "content",
5 | .layouts_dir_path = "layouts",
6 | .assets_dir_path = "content",
7 | }
--------------------------------------------------------------------------------