Skip to content

Instantly share code, notes, and snippets.

@a-h

a-h/main.go Secret

Created March 3, 2021 20:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save a-h/194b9b1bc9414d15c38aa1599c4ae17e to your computer and use it in GitHub Desktop.
Save a-h/194b9b1bc9414d15c38aa1599c4ae17e to your computer and use it in GitHub Desktop.
Gemini generator
package main
import (
"encoding/json"
"fmt"
"io/fs"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"sort"
"strings"
"time"
)
const indexHeader = `# Index Header
`
const indexFooter = ``
const pageHeader = `# Page Header
=> gemini://your_address home
`
func pageFooter(root string, p Post, all Posts) string {
var sb = new(strings.Builder)
sb.WriteString("\n\n")
index := indexOf(p, all)
sb.WriteString(fmt.Sprintf("# More\n\n"))
if index > 0 {
sb.WriteString(fmt.Sprintf("## Next\n\n"))
sb.WriteString(fmt.Sprintf("=> %s %s\n\n", all[index-1].Metadata.URL, all[index-1].Metadata.Title))
}
if index < len(all)-1 {
sb.WriteString(fmt.Sprintf("## Previous\n\n"))
sb.WriteString(fmt.Sprintf("=> %s %s\n\n", all[index+1].Metadata.URL, all[index+1].Metadata.Title))
}
sb.WriteString(fmt.Sprintf("## Home\n\n"))
sb.WriteString("=> gemini://your_address home\n")
return sb.String()
}
func indexOf(p Post, all Posts) int {
for i := 0; i < len(all); i++ {
if all[i].FileName == p.FileName {
return i
}
}
return -1
}
func main() {
source := "posts"
target := "dist"
// Clear out the target.
exitOnError("failed to delete dist directory", exec.Command("rm", "-rf", target).Run())
exitOnError("failed to copy to dist directory", exec.Command("cp", "-r", source, target).Run())
exitOnError("failed to remove .DS_Store files", exec.Command("find", target, "-name", ".DS_Store", "-delete").Run())
// Get the posts from the dist directory, because we're going to use that as the working directory.
posts, err := getPosts(target)
exitOnError("failed to get posts", err)
// Write out the index.
var index = new(strings.Builder)
index.WriteString(indexHeader)
var year int
for _, p := range posts {
if p.Metadata.Title == "" {
fmt.Printf("WARN: no metadata for %q\n", p.FileName)
continue
}
if p.Metadata.Date.Year() != year {
year = p.Metadata.Date.Year()
index.WriteString(fmt.Sprintf("## %d\n\n", year))
}
index.WriteString(fmt.Sprintf("### %s\n\n", p.Metadata.Title))
index.WriteString(fmt.Sprintf("=> %s\n\n", p.Metadata.URL))
// Rewrite all the gemini files to add a header and footer.
header := pageHeader
footer := pageFooter(target, p, posts)
exitOnError("failed to add header and footer", addHeaderAndFooter(p.FileName, header, footer))
}
index.WriteString(indexFooter)
exitOnError("failed to write index", os.WriteFile(path.Join(target, "./index.gmi"), []byte(index.String()), os.ModePerm))
}
func addHeaderAndFooter(to, header, footer string) error {
existing, err := ioutil.ReadFile(to)
if err != nil {
return err
}
f, err := os.Create(to)
if err != nil {
return err
}
_, err = f.WriteString(header)
if err != nil {
return err
}
_, err = f.Write([]byte(strings.TrimSpace(string(existing))))
if err != nil {
return err
}
_, err = f.WriteString(footer)
if err != nil {
return err
}
return nil
}
func exitOnError(msg string, err error) {
if err != nil {
fmt.Println(msg+":", err)
os.Exit(1)
}
}
type Posts []Post
type Post struct {
FileName string
Metadata Metadata
}
type Metadata struct {
Date time.Time `json:"date"`
Title string `json:"title"`
URL string `json:"url"`
Tags []string `json:"tags"`
}
func getPosts(src string) (posts Posts, err error) {
var geminiFiles []string
filepath.Walk(src, func(currentPath string, info fs.FileInfo, err error) error {
if path.Dir(currentPath) == src {
// Skip the root of the src directory, the posts are in the subdirectories.
return nil
}
if strings.HasSuffix(currentPath, ".gmi") {
geminiFiles = append(geminiFiles, currentPath)
}
return nil
})
if err != nil {
err = fmt.Errorf("failed to walk directory: %w", err)
return
}
posts = make([]Post, len(geminiFiles))
for i, gf := range geminiFiles {
posts[i].FileName = gf
posts[i].Metadata, err = loadMetadata(path.Join(path.Dir(gf), "metadata.json"))
if err != nil {
err = fmt.Errorf("failed to load metadata %q: %w", gf, err)
return
}
if posts[i].Metadata.URL != trimRootAndFileName(src, posts[i].FileName) {
fmt.Printf("WARN: mismatched URL and filename: %q -> %q\n", posts[i].Metadata.URL, trimRootAndFileName(src, posts[i].FileName))
}
err = nil
}
sort.Slice(posts, func(i, j int) bool {
return posts[i].Metadata.Date.After(posts[j].Metadata.Date)
})
return
}
func trimRootAndFileName(prefix, fn string) string {
dir, _ := path.Split(fn)
return strings.TrimPrefix(strings.TrimSuffix(dir, "/"), prefix)
}
func loadMetadata(fileName string) (m Metadata, err error) {
f, err := os.Open(fileName)
if err != nil {
return
}
dec := json.NewDecoder(f)
err = dec.Decode(&m)
if err == nil {
m.URL = strings.TrimSuffix(m.URL, "/")
}
return
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment