How the sausage is made
4 minute read
I'm writing this in the evening, I didn't write in the morning because I slept in then went to the gallery. My mind's been racing a little bit, thinking about all the things I want to do. Standard sunday.
I will write more tomorrow about my general plans, I wrote out a list in my bullet journal I'd like to transcribe.
This evening, after hanging at the gallery, I rewrote the build script for this journal. I've take out the deployment scripts that push it to neocity, I think it's best if the program only focuses on transforming markdown to html and leaves the deployment up to somebody else.
I'm calling it (for now) gopub
gopub
I think the core mission statement for this site builder is:
Bury the dead1 where they're found2
- that is to say:
“That for which we find words is something already dead in our hearts. There is always a kind of contempt in the act of speaking.” ― Nietzsche
But really, I just want a site builder that treats the directory I'm in as the final product, and just does the html producing in the same/whichever directory it finds the markdown in. I know this is bad practice for a web app or a program, but I think for a journal or zine it's more true to the process. When you journal in a notebook it doesn't go through any other process to be a journal entry. I don't like to abstract more and more complexity in between the content and the form. I'd like to find a clearer way to express this idea, I don't think I have it completely resolved in my mind.
It takes two .html templates in the root directory, written as golang html templates
- home.html - a file thats is given a list of posts as data to render
- post.html - a file that is given a journal entry as data to render
All markdown files are rendered with the post.html template into an index.html in whichever directory they're found in, and then an index.html is created in the root directory using the home.html template.
the <body> tag for home.html looks like this
<body>
<main class="home">
<h1>Louis Journal</h1>
my <s>ass</s> heart is a pop-up-card, unfolding
<ol reversed >
{{range $p := .Posts}}<li><a href="{{$p.Date}}/">{{$p.Date}} - {{$p.Title}}</a></li> {{end}}
</ol>
</main>
</body>
and for the post.html
<body>
<main>
<a href="..">back</a>
<h1>{{.Title}}</h1>
<date>{{.Date}}</date>
{{.Content}}
</main>
</body>
You can see the style.css on this website.
At the moment I just call it as gopub
straight and it builds the site, but in time I'd like to style it more as a cli program, with different commands and maybe a boilerplate set up. It would also be good for it to support pages outside of dated entries.
Speaking of cli programs, I've been thinking forge.horse might be better off with some kind of repl or terminal to run the publishing and updating. Never short of ideas here are we?
At the end of this entry I'll put in the entire gopub program, it's only 130 lines, 3kb.
listening to
- Matt Lucas describing a problem with cucumbers on Episode 162 of Off Menu
"I dont like the sort of alarming, incongruous change of texture that happens when you're eating a cucumber and you get to the middle of the cucumber"
- Carrie and Lowell by Sufjan Stevens
Questions
- What could I be doing that's really hard?
- What will I tell myself inside to get the hard thing done?
gopub source code
package main
import (
"bytes"
"fmt"
"html/template"
"io/fs"
"log"
"os"
"path/filepath"
"github.com/yuin/goldmark"
meta "github.com/yuin/goldmark-meta"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer/html"
)
func main() {
// get our posts
ee, err := getPosts()
// terrible unhelpful error
if err != nil {
fmt.Printf("broken")
}
parseFiles(ee)
}
func getPosts() (posts []string, err error) {
// walk the directory looking for posts, add them to the posts var
err = filepath.Walk(".", func(path string, info fs.FileInfo, e error) error {
if e != nil {
return e
}
// if it's a .md file then add it to our posts
if filepath.Ext(info.Name()) == ".md" {
posts = append(posts, path)
return nil
}
return nil
})
return
}
type Post struct {
Title string
Date string
Content template.HTML
}
type Homepage struct {
Posts []Post
}
func parseFiles(paths []string) {
// go through the paths
// - load em up,
// - convert em to html,
// - spit em out where they lie
// set up our buffers
var buf bytes.Buffer
var htmlBuf bytes.Buffer
var posts []Post
// get our templates
postTemplate, _ := template.ParseFiles("post.html")
homeTemplate, _ := template.ParseFiles("home.html")
// add our markdown extensions
markdown := goldmark.New(
goldmark.WithExtensions(
meta.Meta, extension.Strikethrough,
),
goldmark.WithRendererOptions(
html.WithUnsafe(),
),
)
context := parser.NewContext()
// loop through paths
for _, path := range paths {
// current directory
dir := os.DirFS(".")
// file directory
fd := filepath.Dir(path)
// load file
source, _ := fs.ReadFile(dir, path)
// parse md to html
markdown.Convert(source, &buf, parser.WithContext(context))
// get title from yaml head
metaData := meta.Get(context)
title := fmt.Sprintf("%v", metaData["title"])
// for now, the date is the file name. Will add support for pages later
// regexp.Match(`^\d\d\d\d-\d\d-\d\d$`, []byte(dd)); m {
date := fd
// }
// create our post data to pass into the template
post := Post{
title,
date,
template.HTML((buf.Bytes())),
}
posts = append([]Post{post}, posts...)
// render template
postTemplate.Execute(&htmlBuf, post)
// save file
os.WriteFile(filepath.Dir(path)+"/index.html", htmlBuf.Bytes(), 0660)
// clear buffers
htmlBuf.Reset()
buf.Reset()
}
data := Homepage{posts}
err := homeTemplate.Execute(&htmlBuf, data)
if err != nil {
log.Fatal(err)
}
os.WriteFile("index.html", htmlBuf.Bytes(), 0660)
// messaging
fmt.Printf("\n✧・゚: *✧・゚:* done *:・゚✧*:・゚✧\n\n")
}
func fileNameWithoutExt(fileName string) string {
return fileName[:len(fileName)-len(filepath.Ext(fileName))]
}