Compare commits

...
Sign in to create a new pull request.

10 commits
dev ... main

17 changed files with 66 additions and 340 deletions

16
.build.yml Normal file
View 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
View file

@ -24,3 +24,4 @@ bin/
go.sum go.sum
# test/ # test/
foobar/

100
README.md
View file

@ -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/)

View file

@ -1,4 +0,0 @@
#!/bin/bash
go build -o bin/zona ./cmd/zona
ln -sf bin/zona ./zona

View file

@ -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
View file

@ -1,5 +0,0 @@
module github.com/ficcdaf/zona
go 1.23.2
require github.com/gomarkdown/markdown v0.0.0-20240930133441-72d49d9543d8 // indirect

View file

@ -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
}

View file

@ -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) {
}

View file

@ -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
}

View file

View file

View file

View file

View file

View file

@ -1,3 +0,0 @@
# My amazing markdown file!
I can _even_ do **this**!

View file

@ -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
View file

@ -1 +0,0 @@
bin/zona