Compare commits
10 commits
Author | SHA1 | Date | |
---|---|---|---|
da9589ec57 | |||
d31fc4b1c4 | |||
c797c0e85d | |||
18800e1cd8 | |||
ea286868b7 | |||
1c05dd4a46 | |||
2d3480e94d | |||
e368126761 | |||
79ade5e8a3 | |||
c0b98d7a99 |
17 changed files with 66 additions and 340 deletions
16
.build.yml
Normal file
16
.build.yml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
image: alpine/latest
|
||||||
|
packages:
|
||||||
|
- git
|
||||||
|
- openssh
|
||||||
|
secrets:
|
||||||
|
- 0639564d-6995-4e2e-844b-2f8feb0b7fb1
|
||||||
|
environment:
|
||||||
|
repo: zona
|
||||||
|
github: git@github.com:ficcdaf/zona.git
|
||||||
|
tasks:
|
||||||
|
- mirror: |
|
||||||
|
ssh-keyscan github.com >> ~/.ssh/known_hosts
|
||||||
|
cd "$repo"
|
||||||
|
git remote add github "$github"
|
||||||
|
git push --mirror github
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -24,3 +24,4 @@ bin/
|
||||||
go.sum
|
go.sum
|
||||||
|
|
||||||
# test/
|
# test/
|
||||||
|
foobar/
|
||||||
|
|
100
README.md
100
README.md
|
@ -1,67 +1,65 @@
|
||||||
# Zona
|
# Zona
|
||||||
|
|
||||||
Zona is a small tool for building a static blog website. It allows users to write pages and blog posts in Markdown and automatically build them into a static, lightweight blog.
|
**IMPORTANT:** Zona is currently migrating to a Python implementation. The new
|
||||||
|
repository is called [zona-py](https://git.sr.ht/~ficd/zona-py). **It will
|
||||||
|
replace this repository once the migration is complete.**
|
||||||
|
|
||||||
> **Warning:** Implementing v1 functionality is still WIP. There will be binaries available to download from the Releases tab when Zona is ready to be used. Until then, you are welcome to download the source code, but I can't promise anything will work!
|
[Zona](https://sr.ht/~ficd/zona/) is a tool for building a static website,
|
||||||
|
optimized for lightweight blogs following minimalist design principles. The
|
||||||
|
project is hosted on [sourcehut](https://sr.ht/~ficd/zona/) and mirrored on
|
||||||
|
[GitHub](https://github.com/ficcdaf/zona). I am not accepting GitHub issues,
|
||||||
|
please make your submission to the
|
||||||
|
[issue tracker](https://todo.sr.ht/~ficd/zona) or send an email to the public
|
||||||
|
mailing list at
|
||||||
|
[~ficd/zona-devel@lists.sr.ht](mailto:~ficd/zona-devel@lists.sr.ht)
|
||||||
|
|
||||||
## Table of Contents
|
<!-- prettier-ignore-start -->
|
||||||
|
|
||||||
- [Features](#v1-features)
|
> Zona is currently in development. The `main` branch of this repository does
|
||||||
- [Installation](#installation)
|
> not yet contain the software. The `dev-stable` branch contains the code used
|
||||||
- [Roadmap](#roadmap)
|
> to generate [ficd.ca](https://ficd.ca) -- although the program is undocumented
|
||||||
|
> and missing features, so please proceed at your own risk. The `dev` branch
|
||||||
|
> contains the latest development updates and is not guaranteed to be functional
|
||||||
|
> (or even compile) at any given commit. Kindly note that the commit history
|
||||||
|
> will be cleaned up before the program is merged into the `main` branch.
|
||||||
|
|
||||||
## v1 Features
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
- Write your pages in Markdown.
|
## Design Goals
|
||||||
- Build a lightweight website with zero JavaScript.
|
|
||||||
- Simple CLI build interface.
|
|
||||||
- HTML layout optimized for screen readers and Neocities.
|
|
||||||
|
|
||||||
## Getting Started
|
Zona is intended to be easy-to-use. A user should be able to build a reasonably
|
||||||
|
complex website or blog with only a directory of Markdown content and a single
|
||||||
|
command, without needing to write any HTML or configuration. However, users
|
||||||
|
should optionally have access to sensible and flexible configuration options,
|
||||||
|
including writing HTML. The output of Zona should also be lightweight, retaining
|
||||||
|
the smallest file sizes possible. These characteristics make Zona well-suited
|
||||||
|
for both beginners and power users that wish to host a website on a service like
|
||||||
|
Neocities or GitHub Pages.
|
||||||
|
|
||||||
### Dependencies
|
## Features Implemented
|
||||||
|
|
||||||
- `go 1.23.2`
|
- Write pages purely in Markdown.
|
||||||
|
- Single-command build process.
|
||||||
|
- Lightweight output.
|
||||||
|
- Sensible default template and stylesheet.
|
||||||
|
- Configuration file.
|
||||||
|
- Internal links preserved.
|
||||||
|
- Custom image element parsing & formatting.
|
||||||
|
- Site header and footer defined in Markdown.
|
||||||
|
- YAML frontmatter support.
|
||||||
|
|
||||||
```Bash
|
## Planned Features
|
||||||
# On Arch Linux
|
|
||||||
sudo pacman -S go
|
|
||||||
|
|
||||||
# On Ubuntu/Debian
|
- Automatically treat contents of `posts/` directory as blog posts.
|
||||||
sudo apt install go
|
- Automatically generated `Archive`, `Recent Posts`, and `Image Gallery`
|
||||||
```
|
elements.
|
||||||
|
- Support for custom stylesheets, favicons, and page templates.
|
||||||
### Installation
|
- Image optimization and dithering.
|
||||||
|
- Custom markdown tags that expand to user-defined templates.
|
||||||
First, download the repository and open it:
|
- Live preview server.
|
||||||
|
- Robust tests.
|
||||||
```Bash
|
|
||||||
git clone https://github.com/ficcdaf/zona.git && cd zona
|
|
||||||
```
|
|
||||||
|
|
||||||
On Linux:
|
|
||||||
|
|
||||||
```Bash
|
|
||||||
# run the provided build script
|
|
||||||
./build.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
On other platforms:
|
|
||||||
|
|
||||||
```Bash
|
|
||||||
go build -o bin/zona cmd/zona
|
|
||||||
```
|
|
||||||
|
|
||||||
The resulting binary can be found at `bin/zona`.
|
|
||||||
|
|
||||||
## Roadmap
|
|
||||||
|
|
||||||
- [ ] Zona configuration file to define build options.
|
|
||||||
- [ ] Image optimization & dithering options.
|
|
||||||
- [ ] AUR package after first release
|
|
||||||
- [ ] Automatic RSS/Atom feed generation.
|
|
||||||
|
|
||||||
## Inspirations
|
## Inspirations
|
||||||
|
|
||||||
- [Zoner](https://git.sr.ht/~ryantrawick/zoner)
|
- [Zoner](https://git.sr.ht/~ryantrawick/zoner)
|
||||||
- Zonelets
|
- [Zonelets](https://zonelets.net/)
|
||||||
|
|
4
build.sh
4
build.sh
|
@ -1,4 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
go build -o bin/zona ./cmd/zona
|
|
||||||
ln -sf bin/zona ./zona
|
|
|
@ -1,53 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/ficcdaf/zona/internal/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// validateFile checks whether a given path
|
|
||||||
// is a valid file && matches an expected extension
|
|
||||||
func validateFile(path, ext string) bool {
|
|
||||||
return (util.CheckExtension(path, ext) == nil) && (util.PathIsValid(path, true))
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
mdPath := flag.String("file", "", "Path to the markdown file.")
|
|
||||||
flag.Parse()
|
|
||||||
if *mdPath == "" {
|
|
||||||
// no flag provided, check for positional argument instead
|
|
||||||
n := flag.NArg()
|
|
||||||
var e error
|
|
||||||
switch n {
|
|
||||||
case 1:
|
|
||||||
// we read the positional arg
|
|
||||||
arg := flag.Arg(0)
|
|
||||||
// mdPath wants a pointer so we get arg's address
|
|
||||||
mdPath = &arg
|
|
||||||
case 0:
|
|
||||||
// in case of no flag and no arg, we fail
|
|
||||||
e = errors.New("Required argument missing!")
|
|
||||||
default:
|
|
||||||
// more args than expected is also fail
|
|
||||||
e = errors.New("Unexpected arguments!")
|
|
||||||
}
|
|
||||||
if e != nil {
|
|
||||||
fmt.Printf("Error: %s\n", e.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
// if !validateFile(*mdPath, ".md") {
|
|
||||||
// fmt.Println("File validation failed!")
|
|
||||||
// os.Exit(1)
|
|
||||||
// }
|
|
||||||
// convert.ConvertFile(*mdPath, "test/test.html")
|
|
||||||
err := util.Traverse(*mdPath, "foobar")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error: %s\n", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
5
go.mod
5
go.mod
|
@ -1,5 +0,0 @@
|
||||||
module github.com/ficcdaf/zona
|
|
||||||
|
|
||||||
go 1.23.2
|
|
||||||
|
|
||||||
require github.com/gomarkdown/markdown v0.0.0-20240930133441-72d49d9543d8 // indirect
|
|
|
@ -1,76 +0,0 @@
|
||||||
package convert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/gomarkdown/markdown"
|
|
||||||
"github.com/gomarkdown/markdown/html"
|
|
||||||
"github.com/gomarkdown/markdown/parser"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This function takes a Markdown document and returns an HTML document.
|
|
||||||
func MdToHTML(md []byte) ([]byte, error) {
|
|
||||||
// create parser with extensions
|
|
||||||
extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
|
|
||||||
p := parser.NewWithExtensions(extensions)
|
|
||||||
doc := p.Parse(md)
|
|
||||||
|
|
||||||
// build HTML renderer
|
|
||||||
htmlFlags := html.CommonFlags | html.HrefTargetBlank
|
|
||||||
opts := html.RendererOptions{Flags: htmlFlags}
|
|
||||||
renderer := html.NewRenderer(opts)
|
|
||||||
|
|
||||||
return markdown.Render(doc, renderer), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteFile writes a given byte array to the given path.
|
|
||||||
func WriteFile(b []byte, p string) error {
|
|
||||||
f, err := os.Create(p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = f.Write(b)
|
|
||||||
defer f.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFile reads a byte array from a given path.
|
|
||||||
func ReadFile(p string) ([]byte, error) {
|
|
||||||
f, err := os.Open(p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var result []byte
|
|
||||||
buf := make([]byte, 1024)
|
|
||||||
for {
|
|
||||||
n, err := f.Read(buf)
|
|
||||||
// check for a non EOF error
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// n==0 when there are no chunks left to read
|
|
||||||
if n == 0 {
|
|
||||||
defer f.Close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
result = append(result, buf[:n]...)
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConvertFile(in string, out string) error {
|
|
||||||
md, err := ReadFile(in)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
html, err := MdToHTML(md)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = WriteFile(html, out)
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
package tree
|
|
||||||
|
|
||||||
// Node is a struct containing nodes of a file tree.
|
|
||||||
type Node struct {
|
|
||||||
// can be nil
|
|
||||||
Parent *Node
|
|
||||||
Name string
|
|
||||||
// Empty value mean directory
|
|
||||||
Ext string
|
|
||||||
// cannot be nil; may have len 0
|
|
||||||
Children []*Node
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNode constructs and returns a Node instance.
|
|
||||||
// parent and children are optional and can be nil,
|
|
||||||
// in which case Parent will be stored as nil,
|
|
||||||
// but Children will be initialized as []*Node of len 0.
|
|
||||||
// If ext == "", the Node is a directory.
|
|
||||||
func NewNode(name string, ext string, parent *Node, children []*Node) *Node {
|
|
||||||
var c []*Node
|
|
||||||
if children == nil {
|
|
||||||
c = make([]*Node, 0)
|
|
||||||
} else {
|
|
||||||
c = children
|
|
||||||
}
|
|
||||||
n := &Node{
|
|
||||||
Name: name,
|
|
||||||
Ext: ext,
|
|
||||||
Parent: parent,
|
|
||||||
Children: c,
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Node) IsRoot() bool {
|
|
||||||
return n.Parent == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Node) IsTail() bool {
|
|
||||||
return len(n.Children) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Node) IsDir() bool {
|
|
||||||
return n.Ext == ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Implement recursive depth-first traversal to process a tree
|
|
||||||
|
|
||||||
func Traverse(root *Node) {
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
// Package util provides general utilities.
|
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CheckExtension checks if the file located at path (string)
|
|
||||||
// matches the provided extension type
|
|
||||||
func CheckExtension(path, ext string) error {
|
|
||||||
if filepath.Ext(path) == ext {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return errors.New("Invalid extension.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathIsValid checks if a path is valid.
|
|
||||||
// If requireFile is set, directories are not considered valid.
|
|
||||||
func PathIsValid(path string, requireFile bool) bool {
|
|
||||||
s, err := os.Stat(path)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return false
|
|
||||||
} else if requireFile {
|
|
||||||
// fmt.Printf("Directory status: %s\n", strconv.FormatBool(s.IsDir()))
|
|
||||||
return !s.IsDir()
|
|
||||||
}
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRoot(path string) string {
|
|
||||||
for {
|
|
||||||
parent := filepath.Dir(path)
|
|
||||||
if parent == "." {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
path = parent
|
|
||||||
}
|
|
||||||
fmt.Println("getRoot: ", path)
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
func replaceRoot(inPath, outRoot string) string {
|
|
||||||
relPath := strings.TrimPrefix(inPath, getRoot(inPath))
|
|
||||||
outPath := filepath.Join(outRoot, relPath)
|
|
||||||
return outPath
|
|
||||||
}
|
|
||||||
|
|
||||||
func createFileWithParents(path string) error {
|
|
||||||
dir := filepath.Dir(path)
|
|
||||||
// Check if the parent directory already exists
|
|
||||||
// before trying to create it
|
|
||||||
if _, dirErr := os.Stat(dir); os.IsNotExist(dirErr) {
|
|
||||||
// create directories
|
|
||||||
err := os.MkdirAll(dir, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// TODO: write the file here
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func processFile(inPath string, entry fs.DirEntry, err error, outRoot string) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !entry.IsDir() {
|
|
||||||
ext := filepath.Ext(inPath)
|
|
||||||
fmt.Println("Root: ", replaceRoot(inPath, outRoot))
|
|
||||||
switch ext {
|
|
||||||
case ".md":
|
|
||||||
fmt.Println("Processing markdown...")
|
|
||||||
default:
|
|
||||||
// All other file types, we copy!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Printf("Visited: %s\n", inPath)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Traverse(root string, outRoot string) error {
|
|
||||||
// err := filepath.WalkDir(root, func(path string, entry fs.DirEntry, err error) error {
|
|
||||||
walkFunc := func(path string, entry fs.DirEntry, err error) error {
|
|
||||||
return processFile(path, entry, err, outRoot)
|
|
||||||
}
|
|
||||||
err := filepath.WalkDir(root, walkFunc)
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
# My amazing markdown file!
|
|
||||||
|
|
||||||
I can _even_ do **this**!
|
|
|
@ -1,3 +0,0 @@
|
||||||
<h1 id="my-amazing-markdown-file">My amazing markdown file!</h1>
|
|
||||||
|
|
||||||
<p>I can <em>even</em> do <strong>this</strong>!</p>
|
|
1
zona
1
zona
|
@ -1 +0,0 @@
|
||||||
bin/zona
|
|
Loading…
Add table
Add a link
Reference in a new issue