purge history since June 2017
This commit is contained in:
commit
389f19e82b
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
.gitdist
|
||||||
|
wombat
|
||||||
|
Makefile
|
||||||
|
sync
|
||||||
|
stage0/*
|
||||||
|
!stage0/README
|
||||||
|
stage1
|
||||||
|
stage2
|
||||||
|
finstr
|
||||||
|
go.sum
|
||||||
|
.wombat
|
41
README.md
Normal file
41
README.md
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
wombat
|
||||||
|
======
|
||||||
|
|
||||||
|
wombat is a static site generator. It compiles
|
||||||
|
html pages from html fragments and markdown files according to a template.
|
||||||
|
|
||||||
|
The fragments are assumed to be in 'stage0', results will appear in 'stage2'. Pages need a description header:
|
||||||
|
|
||||||
|
```
|
||||||
|
---
|
||||||
|
title: Foobar
|
||||||
|
description: This describes foobar
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
Blog posts (located in stage0/blog/) have a similar header with values for title, date and categories.
|
||||||
|
|
||||||
|
Normal pages are created by hand, new blog posts via "wombat post".
|
||||||
|
|
||||||
|
|
||||||
|
build
|
||||||
|
=====
|
||||||
|
|
||||||
|
You'll need go1.11+ with module support. Check out the repository and run 'go build wombat.go'.
|
||||||
|
|
||||||
|
quick start
|
||||||
|
===========
|
||||||
|
|
||||||
|
Put the executable somewhere in your path. Create a folder for your website, then...
|
||||||
|
|
||||||
|
```
|
||||||
|
> cd YOUR_FOLDER
|
||||||
|
> wombat init
|
||||||
|
Initializing YOUR_FOLDER
|
||||||
|
> wombat gen
|
||||||
|
Serving stage2 at http://127.0.0.1:8000 ...
|
||||||
|
Use Ctrl+C to exit
|
||||||
|
(This is not a production web server, don't get any funny ideas)
|
||||||
|
```
|
||||||
|
|
||||||
|
... and visit http://127.0.0.1:8000 in your browser.
|
314
blog/blog.go
Normal file
314
blog/blog.go
Normal file
|
@ -0,0 +1,314 @@
|
||||||
|
package blog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"git.gutmet.org/goutil.git"
|
||||||
|
. "git.gutmet.org/wombat.git/templatestructures"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const postsPerPage int = 5
|
||||||
|
|
||||||
|
var blogname string
|
||||||
|
var folder string
|
||||||
|
var style string
|
||||||
|
var templateString string
|
||||||
|
var t *template.Template
|
||||||
|
var categories map[string][]post
|
||||||
|
|
||||||
|
type metadata struct {
|
||||||
|
Title string
|
||||||
|
Date time.Time
|
||||||
|
Categories []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type post struct {
|
||||||
|
Content string
|
||||||
|
Meta metadata
|
||||||
|
Filename string
|
||||||
|
Link string
|
||||||
|
}
|
||||||
|
|
||||||
|
type categoryListing struct {
|
||||||
|
Name string
|
||||||
|
ASCIIName string
|
||||||
|
Posts []post
|
||||||
|
}
|
||||||
|
|
||||||
|
func linkToPost(filename string) string {
|
||||||
|
filename = filepath.Base(filename)
|
||||||
|
split := strings.Split(filename, "-")
|
||||||
|
if len(split) < 4 {
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
year := split[0]
|
||||||
|
month := split[1]
|
||||||
|
day := split[2]
|
||||||
|
name := ConvASCII(strings.Join(split[3:], "-"))
|
||||||
|
return fmt.Sprintf("/%s/%s/%s/%s/%s", folder, year, month, day, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func linkToCategory(name string) string {
|
||||||
|
return fmt.Sprintf("/%s/categories.html#%s", folder, ConvASCII(strings.ToLower(name)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func addToCategory(category string, post post) {
|
||||||
|
c := strings.ToLower(category)
|
||||||
|
if len(c) > 0 {
|
||||||
|
categories[c] = append(categories[c], post)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func keys(m map[string][]post) []string {
|
||||||
|
keys := make([]string, 0)
|
||||||
|
for k := range m {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func emitPostPage(post post) string {
|
||||||
|
filename := strings.TrimPrefix(strings.TrimPrefix(post.Filename, "stage1/"), "stage1\\")
|
||||||
|
link := post.Link
|
||||||
|
os.Remove(filepath.Join("stage2", filename))
|
||||||
|
base := path.Base(link)
|
||||||
|
folders := strings.TrimSuffix("stage2"+link, base)
|
||||||
|
os.MkdirAll(folders, 0755)
|
||||||
|
page := Page{}
|
||||||
|
page.Blogname = blogname
|
||||||
|
page.Title = post.Meta.Title
|
||||||
|
page.Style = style
|
||||||
|
page.Content = post.Content
|
||||||
|
page.Time = post.Meta.Date.Format(TimeFormat)
|
||||||
|
page.Blogpost = true
|
||||||
|
for _, categoryName := range post.Meta.Categories {
|
||||||
|
page.Categories = append(page.Categories, Category{categoryName, linkToCategory(categoryName)})
|
||||||
|
addToCategory(categoryName, post)
|
||||||
|
}
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
t.Execute(buf, page)
|
||||||
|
goutil.WriteFile(filepath.Join("stage2", link), buf.String())
|
||||||
|
return link
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexFilename(i int) string {
|
||||||
|
return filepath.Join("stage2", folder, fmt.Sprintf("%d.html", i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexPageExists(i int, total int) bool {
|
||||||
|
max := int(math.Ceil(float64(total)/float64(postsPerPage))) - 1
|
||||||
|
return (i >= 0) && (i <= max)
|
||||||
|
}
|
||||||
|
|
||||||
|
type byDateDesc []post
|
||||||
|
|
||||||
|
func (p byDateDesc) Len() int { return len(p) }
|
||||||
|
func (p byDateDesc) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||||
|
func (p byDateDesc) Less(i, j int) bool { return p[i].Meta.Date.After(p[j].Meta.Date) }
|
||||||
|
|
||||||
|
func emitIndexPage(i int, posts []post, total int) {
|
||||||
|
file := indexFilename(i)
|
||||||
|
postCollection := make([]Blogpost, 0)
|
||||||
|
sort.Sort(byDateDesc(posts))
|
||||||
|
for _, post := range posts {
|
||||||
|
link := emitPostPage(post)
|
||||||
|
p := Blogpost{}
|
||||||
|
p.Title = post.Meta.Title
|
||||||
|
p.Link = link
|
||||||
|
p.Content = post.Content
|
||||||
|
postCollection = append(postCollection, p)
|
||||||
|
}
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
page := Page{}
|
||||||
|
page.Title = blogname
|
||||||
|
page.Style = style
|
||||||
|
page.Blogdir = folder
|
||||||
|
page.Blogpost = false
|
||||||
|
page.PostCollection = postCollection
|
||||||
|
if indexPageExists(i-1, total) {
|
||||||
|
page.Previous = strconv.Itoa(i-1) + ".html"
|
||||||
|
}
|
||||||
|
if indexPageExists(i+1, total) {
|
||||||
|
page.Later = strconv.Itoa(i+1) + ".html"
|
||||||
|
}
|
||||||
|
t.Execute(buf, page)
|
||||||
|
goutil.WriteFile(file, buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMetadata(file string) (metadata, error) {
|
||||||
|
metaAll, err := goutil.ReadFile(goutil.TrimExt(file) + ".desc")
|
||||||
|
ret := metadata{}
|
||||||
|
if err == nil {
|
||||||
|
lines := strings.Split(metaAll, "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.HasPrefix(line, "title:") {
|
||||||
|
ret.Title = strings.Trim(strings.TrimPrefix(line, "title:"), "\" ")
|
||||||
|
} else if strings.HasPrefix(line, "date:") {
|
||||||
|
date, err2 := time.Parse(TimeFormat, strings.Trim(strings.TrimPrefix(line, "date:"), "\" "))
|
||||||
|
if err2 == nil {
|
||||||
|
ret.Date = date
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Unrecognized date format in %s", file)
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(line, "categories:") {
|
||||||
|
categories := strings.Trim(strings.TrimPrefix(line, "categories:"), "\"[] ")
|
||||||
|
splitcategories := strings.Split(categories, ",")
|
||||||
|
for _, category := range splitcategories {
|
||||||
|
c := strings.TrimSpace(category)
|
||||||
|
if len(c) > 0 {
|
||||||
|
ret.Categories = append(ret.Categories, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
} else {
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPost(file string) (post, error) {
|
||||||
|
tmp, err := goutil.ReadFile(file)
|
||||||
|
if err == nil {
|
||||||
|
meta, err2 := getMetadata(file)
|
||||||
|
if err2 == nil {
|
||||||
|
p := post{}
|
||||||
|
p.Content = tmp
|
||||||
|
p.Meta = meta
|
||||||
|
p.Filename = file
|
||||||
|
filename := strings.TrimPrefix(file, "stage1")
|
||||||
|
p.Link = linkToPost(filename)
|
||||||
|
return p, nil
|
||||||
|
} else {
|
||||||
|
return post{}, err2
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return post{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeCategories() {
|
||||||
|
ct := template.Must(template.New("Categories").Parse(categoryTemplate))
|
||||||
|
page := Page{}
|
||||||
|
page.Title = blogname + ": Categories"
|
||||||
|
page.Style = style
|
||||||
|
keySet := keys(categories)
|
||||||
|
sort.Strings(keySet)
|
||||||
|
page.Content += "<dl style=\"padding-bottom:150em\">"
|
||||||
|
for _, k := range keySet {
|
||||||
|
listing := categoryListing{}
|
||||||
|
listing.Name = k
|
||||||
|
listing.ASCIIName = ConvASCII(k)
|
||||||
|
sort.Sort(byDate(categories[k]))
|
||||||
|
listing.Posts = categories[k]
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
ct.Execute(buf, listing)
|
||||||
|
page.Content += buf.String()
|
||||||
|
}
|
||||||
|
page.Content += "</dl>"
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
t.Execute(buf, page)
|
||||||
|
goutil.WriteFile(filepath.Join("stage2", folder, "categories.html"), buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTimeline(posts []post) {
|
||||||
|
sort.Sort(byDateDesc(posts))
|
||||||
|
page := Page{}
|
||||||
|
page.Title = blogname + ": Timeline"
|
||||||
|
page.Style = style
|
||||||
|
page.Content += "<dl>"
|
||||||
|
year := -1
|
||||||
|
month := -1
|
||||||
|
for _, p := range posts {
|
||||||
|
if year != p.Meta.Date.Year() {
|
||||||
|
year = p.Meta.Date.Year()
|
||||||
|
page.Content += "</dl>\n\n" + "<h3>" + strconv.Itoa(year) + "</h3>\n<dl>\n"
|
||||||
|
}
|
||||||
|
if month != int(p.Meta.Date.Month()) {
|
||||||
|
month = int(p.Meta.Date.Month())
|
||||||
|
page.Content += fmt.Sprintf("<dt>%02d</dt>", month)
|
||||||
|
}
|
||||||
|
page.Content += fmt.Sprintf("<dd><a href=\"%s\">%s</a></dd>\n", p.Link, p.Meta.Title)
|
||||||
|
}
|
||||||
|
page.Content += "</dl>"
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
t.Execute(buf, page)
|
||||||
|
goutil.WriteFile(filepath.Join("stage2", folder, "timeline.html"), buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
type byDate []post
|
||||||
|
|
||||||
|
func (p byDate) Len() int { return len(p) }
|
||||||
|
func (p byDate) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||||
|
func (p byDate) Less(i, j int) bool { return p[i].Meta.Date.Before(p[j].Meta.Date) }
|
||||||
|
|
||||||
|
func getPosts() []post {
|
||||||
|
files := goutil.ListFilesExt(filepath.Join("stage1", folder), ".html")
|
||||||
|
posts := make([]post, 0)
|
||||||
|
for _, file := range files {
|
||||||
|
tmp, err := getPost(file)
|
||||||
|
if err == nil {
|
||||||
|
posts = append(posts, tmp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return posts
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeBlog() {
|
||||||
|
posts := getPosts()
|
||||||
|
sort.Sort(byDate(posts))
|
||||||
|
total := len(posts)
|
||||||
|
s := make([]post, 0)
|
||||||
|
n := 0
|
||||||
|
for i, tmp := range posts {
|
||||||
|
if (i / postsPerPage) != n {
|
||||||
|
emitIndexPage(n, s, total)
|
||||||
|
n = i / postsPerPage
|
||||||
|
s = make([]post, 0)
|
||||||
|
}
|
||||||
|
s = append(s, tmp)
|
||||||
|
}
|
||||||
|
if len(s) != 0 {
|
||||||
|
emitIndexPage(n, s, total)
|
||||||
|
}
|
||||||
|
os.Link(indexFilename(n), filepath.Join("stage2", folder, "index.html"))
|
||||||
|
makeCategories()
|
||||||
|
makeTimeline(posts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Generate(dir string) {
|
||||||
|
folder = dir
|
||||||
|
blogname, _ = goutil.ReadFile(filepath.Join("stage0", folder, ".name"))
|
||||||
|
if blogname == "" {
|
||||||
|
blogname = "blog"
|
||||||
|
}
|
||||||
|
blogname = strings.TrimSpace(blogname)
|
||||||
|
categories = make(map[string][]post)
|
||||||
|
var err error
|
||||||
|
var err2 error
|
||||||
|
style, err = goutil.ReadFile("style.css")
|
||||||
|
templateString, err2 = goutil.ReadFile("template")
|
||||||
|
t = template.Must(template.New("page").Parse(templateString))
|
||||||
|
if err == nil && err2 == nil {
|
||||||
|
makeBlog()
|
||||||
|
} else {
|
||||||
|
fmt.Println(err)
|
||||||
|
fmt.Println(err2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const categoryTemplate string = `
|
||||||
|
<dt><a name="{{.ASCIIName}}" class="categoryListing">{{.Name}}</a></dt>
|
||||||
|
{{range $index, $post := .Posts}}<dd><a href="{{$post.Link}}">{{$post.Meta.Title}}</a></dd>
|
||||||
|
{{end}}
|
||||||
|
`
|
25
blog/blogutil.go
Normal file
25
blog/blogutil.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package blog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TimeFormat string = "2006-01-02 15:04"
|
||||||
|
|
||||||
|
func isASCII(r rune) bool {
|
||||||
|
return r <= 127
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvASCII(s string) string {
|
||||||
|
out := []rune(s)
|
||||||
|
for i, c := range out {
|
||||||
|
if (isASCII(c) && (unicode.IsLetter(c) || unicode.IsDigit(c))) || c == '.' {
|
||||||
|
// keep unchanged
|
||||||
|
} else if unicode.IsSpace(c) || unicode.IsPunct(c) {
|
||||||
|
out[i] = '-'
|
||||||
|
} else {
|
||||||
|
out[i] = 'x'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(out)
|
||||||
|
}
|
35
converter/converter.go
Normal file
35
converter/converter.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package converter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.gutmet.org/goutil.git"
|
||||||
|
"github.com/russross/blackfriday/v2"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func convert(f string) {
|
||||||
|
outfile := f[0:len(f)-len(".md")] + ".html"
|
||||||
|
input, err := goutil.ReadFile(f)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
}
|
||||||
|
output := string(blackfriday.Run([]byte(input)))
|
||||||
|
goutil.WriteFile(outfile, output)
|
||||||
|
err = os.Remove(f)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertAll(files []string) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(files))
|
||||||
|
for _, f := range files {
|
||||||
|
go func(file string) {
|
||||||
|
defer wg.Done()
|
||||||
|
convert(file)
|
||||||
|
}(f)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
47
gallery/gallery.go
Normal file
47
gallery/gallery.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package gallery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"git.gutmet.org/finstr.git"
|
||||||
|
"git.gutmet.org/goutil.git"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Prefix string = "photos"
|
||||||
|
|
||||||
|
type Flags struct {
|
||||||
|
single string
|
||||||
|
clean bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func Generate(f Flags) error {
|
||||||
|
var err error
|
||||||
|
galleries := []string{Prefix + f.single}
|
||||||
|
if f.single == "" {
|
||||||
|
galleries, err = goutil.DirsWithPrefix(".", Prefix)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, g := range galleries {
|
||||||
|
fmt.Println("gallery: ", g)
|
||||||
|
args := finstr.Flags{InDir: g, OutDir: filepath.Join("stage0", g), Suffix: ".md", Clean: f.clean}
|
||||||
|
err = finstr.Generate(args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Command() (goutil.CommandFlagsInit, goutil.CommandFunc) {
|
||||||
|
f := Flags{}
|
||||||
|
flagsInit := func(s *flag.FlagSet) {
|
||||||
|
s.StringVar(&f.single, "single", "", "only generate single gallery instead of all (e.g. -single \"Holidays\" for folder 'photosHolidays')")
|
||||||
|
s.BoolVar(&f.clean, "clean", false, "remove old files in galleries before generating (use with caution)")
|
||||||
|
}
|
||||||
|
return flagsInit, func([]string) error {
|
||||||
|
return Generate(f)
|
||||||
|
}
|
||||||
|
}
|
21
gallery/new.go
Normal file
21
gallery/new.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package gallery
|
||||||
|
|
||||||
|
import (
|
||||||
|
finstr "git.gutmet.org/finstr.git/initer"
|
||||||
|
"git.gutmet.org/goutil.git"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(name string) error {
|
||||||
|
fl := finstr.IniterFlags{}
|
||||||
|
fl.Dir = "photos" + name
|
||||||
|
fl.Markdown = true
|
||||||
|
return finstr.Init(fl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInteractive() error {
|
||||||
|
name, err := goutil.AskFor("Name (one word)")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return New(name)
|
||||||
|
}
|
83
generate/generate.go
Normal file
83
generate/generate.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package generate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"git.gutmet.org/goutil.git"
|
||||||
|
"git.gutmet.org/simpleserver.git"
|
||||||
|
"git.gutmet.org/wombat.git/blog"
|
||||||
|
"git.gutmet.org/wombat.git/converter"
|
||||||
|
"git.gutmet.org/wombat.git/gallery"
|
||||||
|
"git.gutmet.org/wombat.git/initer"
|
||||||
|
"git.gutmet.org/wombat.git/site"
|
||||||
|
"git.gutmet.org/wombat.git/splitter"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func removeAll(path string) {
|
||||||
|
err := os.RemoveAll(path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func clean() {
|
||||||
|
removeAll("stage1")
|
||||||
|
removeAll("stage2")
|
||||||
|
}
|
||||||
|
|
||||||
|
func stage1() {
|
||||||
|
goutil.Copy("stage0", "stage1")
|
||||||
|
mdfiles := goutil.RecListFilesExt("stage1", ".md")
|
||||||
|
htmlfrags := goutil.RecListFilesExt("stage1", ".html")
|
||||||
|
splitter.SplitAll(append(mdfiles, htmlfrags...))
|
||||||
|
converter.ConvertAll(mdfiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
func blogs() []string {
|
||||||
|
dirs, err := goutil.DirsWithPrefix("stage0", "blog")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
}
|
||||||
|
return dirs
|
||||||
|
}
|
||||||
|
|
||||||
|
func stage2() {
|
||||||
|
goutil.Copy("stage1", "stage2")
|
||||||
|
site.Generate()
|
||||||
|
for _, blogdir := range blogs() {
|
||||||
|
blog.Generate(blogdir)
|
||||||
|
}
|
||||||
|
descfiles := goutil.RecListFilesExt("stage2", ".desc")
|
||||||
|
for _, f := range descfiles {
|
||||||
|
err := os.Remove(f)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os.Remove("stage2/README")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Generate(f GenerateFlags) error {
|
||||||
|
initer.InitializedOrDie()
|
||||||
|
clean()
|
||||||
|
gallery.Generate(gallery.Flags{})
|
||||||
|
stage1()
|
||||||
|
stage2()
|
||||||
|
if !f.Noserve {
|
||||||
|
simpleserver.Serve("stage2")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenerateFlags struct {
|
||||||
|
Noserve bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func Command() (goutil.CommandFlagsInit, goutil.CommandFunc) {
|
||||||
|
f := GenerateFlags{}
|
||||||
|
flagsInit := func(s *flag.FlagSet) {
|
||||||
|
s.BoolVar(&f.Noserve, "noserve", false, "don't start webserver after generating")
|
||||||
|
}
|
||||||
|
return flagsInit, func([]string) error { return Generate(f) }
|
||||||
|
}
|
9
go.mod
Normal file
9
go.mod
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
module git.gutmet.org/wombat.git
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.gutmet.org/finstr.git v0.0.0-20181223153419-09f9b6e6e555
|
||||||
|
git.gutmet.org/goutil.git v0.0.0-20181223145120-16cb002a8dab
|
||||||
|
git.gutmet.org/simpleserver.git v0.0.0-20181209151100-b298451c1f40
|
||||||
|
github.com/russross/blackfriday/v2 v2.0.1
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect
|
||||||
|
)
|
8
initer/index.go
Normal file
8
initer/index.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package initer
|
||||||
|
|
||||||
|
var defaultIndex string = `---
|
||||||
|
title: Your website
|
||||||
|
---
|
||||||
|
|
||||||
|
Some content
|
||||||
|
`
|
112
initer/initer.go
Normal file
112
initer/initer.go
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
package initer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"git.gutmet.org/goutil.git"
|
||||||
|
"git.gutmet.org/wombat.git/gallery"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
initflag = ".wombat"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Style int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Minimal Style = 0
|
||||||
|
BSStarter Style = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
func templateAndCSS(style Style) (string, string) {
|
||||||
|
switch style {
|
||||||
|
case BSStarter:
|
||||||
|
return bsStarterTemplate, bsStarterStyle
|
||||||
|
case Minimal:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return defaultTemplate, defaultStyle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var checkpaths []string = []string{initflag, "stage0", "template", "style.css"}
|
||||||
|
|
||||||
|
func dumpInitFiles(style Style) error {
|
||||||
|
template, css := templateAndCSS(style)
|
||||||
|
err := goutil.WriteFile("template", template)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = goutil.WriteFile("style.css", css)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = goutil.WriteFile(filepath.Join("stage0", "index.md"), defaultIndex)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return gallery.New("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isInitialized() bool {
|
||||||
|
for _, path := range checkpaths {
|
||||||
|
if !goutil.PathExists(path) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func initialize(style Style) error {
|
||||||
|
var err error
|
||||||
|
if !isInitialized() {
|
||||||
|
dir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("Initializing " + dir)
|
||||||
|
err = goutil.WriteFile(initflag, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.Mkdir("stage0", 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = dumpInitFiles(style)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitializedOrDie() {
|
||||||
|
if !isInitialized() {
|
||||||
|
dir, err := os.Getwd()
|
||||||
|
if err == nil {
|
||||||
|
err = errors.New(dir + " is not an initialized wombat directory")
|
||||||
|
}
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type initializeFlags struct {
|
||||||
|
style string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Command() (goutil.CommandFlagsInit, goutil.CommandFunc) {
|
||||||
|
f := initializeFlags{}
|
||||||
|
flagsInit := func(s *flag.FlagSet) {
|
||||||
|
s.StringVar(&f.style, "style", "minimal", "\"minimal\" or \"evil\" (Bootstrap)")
|
||||||
|
}
|
||||||
|
return flagsInit, func([]string) error {
|
||||||
|
if f.style == "evil" {
|
||||||
|
fmt.Println("You should feel bad, but anyway...")
|
||||||
|
return initialize(BSStarter)
|
||||||
|
} else {
|
||||||
|
return initialize(Minimal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
initer/styles.go
Normal file
62
initer/styles.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package initer
|
||||||
|
|
||||||
|
var defaultStyle string = `body {
|
||||||
|
font-family: Open Sans,Clear Sans,Verdana,Helvetica,Arial;
|
||||||
|
font-size : 12pt;
|
||||||
|
max-width: 850px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
background-color : black;
|
||||||
|
color: #E8E8E8;
|
||||||
|
text-align:justify;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color : #0D0;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color : red;
|
||||||
|
}
|
||||||
|
a.linktopost {
|
||||||
|
color: #E8E8E8;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a.categoryListing {
|
||||||
|
color: #E8E8E8;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
div.blogpost {
|
||||||
|
padding-bottom: 50px;
|
||||||
|
}
|
||||||
|
div.blogpost img {
|
||||||
|
max-width: 50%;
|
||||||
|
}
|
||||||
|
div.navigation {
|
||||||
|
padding-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
video {
|
||||||
|
max-width: 50%;
|
||||||
|
}
|
||||||
|
div.gallery {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var bsStarterStyle string = `body {
|
||||||
|
padding-top: 5rem;
|
||||||
|
}
|
||||||
|
.starter-template {
|
||||||
|
padding: 3rem 1.5rem;
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
`
|
193
initer/templates.go
Normal file
193
initer/templates.go
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
package initer
|
||||||
|
|
||||||
|
var defaultTemplate string = `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><title>{{.Title}}</title>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
|
||||||
|
<!-- Tell "smart" phones that they are as wide as they are wide... -->
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
|
||||||
|
<style>
|
||||||
|
{{.Style}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>{{if .Blogpost}}{{.Blogname}}: {{end}}{{.Title}}</h1>
|
||||||
|
<div class="navigation">
|
||||||
|
<a href="/">home</a>
|
||||||
|
<a href="/blog">blog</a>
|
||||||
|
<a href="/photos/pages/">photos</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{if .Subpages}}
|
||||||
|
<dl>
|
||||||
|
{{range $index, $subpage := .Subpages}}
|
||||||
|
<dt><a href="/{{$subpage.Path}}">[{{$subpage.Title}}]</a></dt><dd>{{$subpage.Description}}</dd>
|
||||||
|
{{end}}
|
||||||
|
</dl>
|
||||||
|
{{end}}
|
||||||
|
{{if .Description}}<h3>{{.Description}}</h3>{{end}}
|
||||||
|
|
||||||
|
{{if .PostCollection }}
|
||||||
|
{{range $index, $post := .PostCollection}}
|
||||||
|
<div class="blogpost">
|
||||||
|
<a href ="{{$post.Link}}" class="linktopost">
|
||||||
|
<h1>
|
||||||
|
{{$post.Title}}
|
||||||
|
</h1>
|
||||||
|
</a>
|
||||||
|
{{$post.Content}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
{{.Content}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{if .Categories}}
|
||||||
|
<div class="categories">
|
||||||
|
Posted in
|
||||||
|
{{range $index, $category := .Categories}}
|
||||||
|
<a href="{{$category.Path}}">{{$category.Name}}</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .Time}}
|
||||||
|
<div class="time">
|
||||||
|
{{.Time}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if or .Previous .Later}}
|
||||||
|
<div class="blognavigation">
|
||||||
|
{{if .Previous}}
|
||||||
|
<a href="{{.Previous}}"><h2><--Previous</h2></a>
|
||||||
|
{{end}}
|
||||||
|
{{if .Later}}
|
||||||
|
<a href="{{.Later}}"><h2>Later--></h2></a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .PostCollection}}
|
||||||
|
<a href="/{{.Blogdir}}/categories.html"><h2>Categories</h2></a>
|
||||||
|
<a href="/{{.Blogdir}}/timeline.html"><h2>Timeline</h2></a>
|
||||||
|
{{end}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// external JavaScript and CSS - definitely not recommended
|
||||||
|
var bsStarterTemplate string = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<!-- Tell "smart" phones that they are as wide as they are wide... -->
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
|
||||||
|
<title>{{.Title}}</title>
|
||||||
|
<style>
|
||||||
|
{{.Style}}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- Bootstrap core CSS -->
|
||||||
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
|
||||||
|
<a class="navbar-brand" href="/">Home</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
|
||||||
|
<ul class="navbar-nav mr-auto">
|
||||||
|
<li class="nav-item active">
|
||||||
|
<a class="nav-link" href="/blog">Blog</a>
|
||||||
|
<a class="nav-link" href="/photos/pages/">Photos</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main role="main" class="container">
|
||||||
|
<div class="starter-template">
|
||||||
|
<h1>{{if .Blogpost}}{{.Blogname}}: {{end}}{{.Title}}</h1>
|
||||||
|
{{if .Subpages}}
|
||||||
|
<dl>
|
||||||
|
{{range $index, $subpage := .Subpages}}
|
||||||
|
<dt><a href="/{{$subpage.Path}}">[{{$subpage.Title}}]</a></dt><dd>{{$subpage.Description}}</dd>
|
||||||
|
{{end}}
|
||||||
|
</dl>
|
||||||
|
{{end}}
|
||||||
|
{{if .Description}}<h3>{{.Description}}</h3>{{end}}
|
||||||
|
|
||||||
|
{{if .PostCollection }}
|
||||||
|
{{range $index, $post := .PostCollection}}
|
||||||
|
<div class="blogpost">
|
||||||
|
<a href ="{{$post.Link}}" class="linktopost">
|
||||||
|
<h1>
|
||||||
|
{{$post.Title}}
|
||||||
|
</h1>
|
||||||
|
</a>
|
||||||
|
{{$post.Content}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
{{.Content}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{if .Categories}}
|
||||||
|
<div class="categories">
|
||||||
|
Posted in
|
||||||
|
{{range $index, $category := .Categories}}
|
||||||
|
<a href="{{$category.Path}}">{{$category.Name}}</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .Time}}
|
||||||
|
<div class="time">
|
||||||
|
{{.Time}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if or .Previous .Later}}
|
||||||
|
<div class="blognavigation">
|
||||||
|
{{if .Previous}}
|
||||||
|
<a href="{{.Previous}}"><h2><--Previous</h2></a>
|
||||||
|
{{end}}
|
||||||
|
{{if .Later}}
|
||||||
|
<a href="{{.Later}}"><h2>Later--></h2></a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .PostCollection}}
|
||||||
|
<a href="/{{.Blogdir}}/categories.html"><h2>Categories</h2></a>
|
||||||
|
<a href="/{{.Blogdir}}/timeline.html"><h2>Timeline</h2></a>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</main><!-- /.container -->
|
||||||
|
|
||||||
|
<!-- Optional JavaScript -->
|
||||||
|
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
109
post/post.go
Normal file
109
post/post.go
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
package post
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"git.gutmet.org/goutil.git"
|
||||||
|
"git.gutmet.org/wombat.git/blog"
|
||||||
|
. "git.gutmet.org/wombat.git/generate"
|
||||||
|
"git.gutmet.org/wombat.git/initer"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const dateformat string = "2006-01-02"
|
||||||
|
const stub string = `---
|
||||||
|
title: "{{.Title}}"
|
||||||
|
date: {{.Date}}
|
||||||
|
categories:
|
||||||
|
---
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
type post struct {
|
||||||
|
Title string
|
||||||
|
Date string
|
||||||
|
}
|
||||||
|
|
||||||
|
func filename(dirsuffix string, title string, now time.Time) string {
|
||||||
|
date := now.Format(dateformat)
|
||||||
|
asciiTitle := blog.ConvASCII(title)
|
||||||
|
return filepath.Join("stage0", "blog"+dirsuffix, fmt.Sprintf("%s-%s.md", date, asciiTitle))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureDirectory(file string) {
|
||||||
|
dir := filepath.Dir(file)
|
||||||
|
err := os.MkdirAll(dir, 0755)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePostFile(dirsuffix string, title string, content string) string {
|
||||||
|
now := time.Now()
|
||||||
|
file := filename(dirsuffix, title, now)
|
||||||
|
ensureDirectory(file)
|
||||||
|
post := post{}
|
||||||
|
post.Title = title
|
||||||
|
post.Date = now.Format(blog.TimeFormat)
|
||||||
|
t := template.Must(template.New("Poststub").Parse(stub))
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
t.Execute(buf, post)
|
||||||
|
err := goutil.WriteFile(file, buf.String()+content)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
func postNew(fl postFlags) error {
|
||||||
|
var err error
|
||||||
|
initer.InitializedOrDie()
|
||||||
|
if fl.title == "" {
|
||||||
|
fl.title, err = goutil.AskFor("Title")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var content string
|
||||||
|
if fl.category != "" && fl.title != "" {
|
||||||
|
tmpcontent, _ := ioutil.ReadAll(os.Stdin)
|
||||||
|
content = string(tmpcontent)
|
||||||
|
}
|
||||||
|
f := makePostFile(fl.blog, fl.title, content)
|
||||||
|
fmt.Println(f)
|
||||||
|
if content == "" {
|
||||||
|
wait := true
|
||||||
|
err = goutil.OpenInEditor(f, wait)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
fmt.Println("trying default application...")
|
||||||
|
err = goutil.OpenInDefaultApp(f, wait)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
noserve := (content != "")
|
||||||
|
return Generate(GenerateFlags{Noserve: noserve})
|
||||||
|
}
|
||||||
|
|
||||||
|
type postFlags struct {
|
||||||
|
blog string
|
||||||
|
title string
|
||||||
|
category string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Command() (goutil.CommandFlagsInit, goutil.CommandFunc) {
|
||||||
|
f := postFlags{}
|
||||||
|
flagsInit := func(s *flag.FlagSet) {
|
||||||
|
s.StringVar(&f.blog, "blog", "", "which blog folder to post to, e.g. -blog=foo for 'blogfoo' (default \"blog\")")
|
||||||
|
s.StringVar(&f.title, "title", "", "if title and category are set: read content from stdin")
|
||||||
|
s.StringVar(&f.category, "category", "", "if title and category are set: read content from stdin")
|
||||||
|
}
|
||||||
|
return flagsInit, func([]string) error { return postNew(f) }
|
||||||
|
}
|
154
site/site.go
Normal file
154
site/site.go
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
package site
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"git.gutmet.org/goutil.git"
|
||||||
|
. "git.gutmet.org/wombat.git/templatestructures"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
type metadata struct {
|
||||||
|
explicit explicitMetadata
|
||||||
|
subpages []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type explicitMetadata struct {
|
||||||
|
title string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metadata) getSubpages() []Subpage {
|
||||||
|
if len(m.subpages) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ret := make([]Subpage, 0)
|
||||||
|
for _, path := range m.subpages {
|
||||||
|
emd := getExplicitMetadata(path)
|
||||||
|
if len(emd.title) > 0 && len(emd.description) > 0 {
|
||||||
|
subpage := Subpage{emd.title, emd.description, path}
|
||||||
|
ret = append(ret, subpage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func metapath(htmlfile string) string {
|
||||||
|
if !strings.HasPrefix(htmlfile, "stage1") {
|
||||||
|
htmlfile = filepath.Join("stage1", htmlfile)
|
||||||
|
}
|
||||||
|
return goutil.TrimExt(htmlfile) + ".desc"
|
||||||
|
}
|
||||||
|
|
||||||
|
func getExplicitMetadata(htmlfile string) explicitMetadata {
|
||||||
|
metapath := metapath(htmlfile)
|
||||||
|
ret := explicitMetadata{}
|
||||||
|
f, err := os.Open(metapath)
|
||||||
|
if err == nil {
|
||||||
|
defer f.Close()
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
text := scanner.Text()
|
||||||
|
if strings.HasPrefix(text, "title:") {
|
||||||
|
ret.title = strings.Trim(strings.TrimPrefix(text, "title:"), "\" ")
|
||||||
|
} else if strings.HasPrefix(text, "description:") {
|
||||||
|
ret.description = strings.TrimSpace(strings.TrimPrefix(text, "description:"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHTML(file os.FileInfo) bool {
|
||||||
|
return isHTMLFile(file.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHTMLFile(path string) bool {
|
||||||
|
return strings.HasSuffix(path, ".html")
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasDescription(path string) bool {
|
||||||
|
metapath := metapath(path)
|
||||||
|
if _, err := os.Stat(metapath); os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSubpages(filename string) []string {
|
||||||
|
ret := []string{}
|
||||||
|
dirpath := goutil.TrimExt(filename)
|
||||||
|
if files, err := ioutil.ReadDir(dirpath); err == nil {
|
||||||
|
for _, file := range files {
|
||||||
|
if isHTML(file) {
|
||||||
|
fullpath := strings.TrimPrefix(strings.TrimPrefix(dirpath, "stage1/"), "stage1\\")
|
||||||
|
ret = append(ret, filepath.Join(fullpath, file.Name()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMetadata(path string) metadata {
|
||||||
|
ret := metadata{}
|
||||||
|
ret.explicit = getExplicitMetadata(path)
|
||||||
|
ret.subpages = getSubpages(path)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func completePage(path string, t *template.Template, style string) {
|
||||||
|
md := getMetadata(path)
|
||||||
|
emd := md.explicit
|
||||||
|
content, _ := goutil.ReadFile(path)
|
||||||
|
page := Page{}
|
||||||
|
page.Title = emd.title
|
||||||
|
page.Style = style
|
||||||
|
page.Subpages = md.getSubpages()
|
||||||
|
page.Description = emd.description
|
||||||
|
page.Content = content
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
err := t.Execute(buf, page)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
destination := strings.Replace(path, "stage1", "stage2", 1)
|
||||||
|
goutil.WriteFile(destination, buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func ignore(path string) bool {
|
||||||
|
var ignorePaths = []string{filepath.Join("stage1", "blog") + string(os.PathSeparator)}
|
||||||
|
for _, prefix := range ignorePaths {
|
||||||
|
if strings.HasPrefix(path, prefix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func traverse(t *template.Template, style string, path string, info os.FileInfo, err error) error {
|
||||||
|
if !ignore(path) && isHTMLFile(path) && hasDescription(path) {
|
||||||
|
completePage(path, t, style)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Generate() {
|
||||||
|
style, err := goutil.ReadFile("style.css")
|
||||||
|
templateString, err2 := goutil.ReadFile("template")
|
||||||
|
t := template.Must(template.New("page").Parse(templateString))
|
||||||
|
traverseFunc := func(path string, info os.FileInfo, err error) error { return traverse(t, style, path, info, err) }
|
||||||
|
if err == nil && err2 == nil {
|
||||||
|
filepath.Walk("stage1", traverseFunc)
|
||||||
|
} else {
|
||||||
|
fmt.Println(err)
|
||||||
|
fmt.Println(err2)
|
||||||
|
}
|
||||||
|
}
|
77
splitter/splitter.go
Normal file
77
splitter/splitter.go
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
package splitter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"git.gutmet.org/goutil.git"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeMetadata(filename string, metadata string) {
|
||||||
|
metapath := goutil.TrimExt(filename) + ".desc"
|
||||||
|
goutil.WriteFile(metapath, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readData(scanner *bufio.Scanner) string {
|
||||||
|
var data bytes.Buffer
|
||||||
|
for scanner.Scan() {
|
||||||
|
data.WriteString(scanner.Text())
|
||||||
|
data.WriteString("\n")
|
||||||
|
}
|
||||||
|
return data.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func readMetadata(scanner *bufio.Scanner) string {
|
||||||
|
var metadata bytes.Buffer
|
||||||
|
firstLine := true
|
||||||
|
for scanner.Scan() {
|
||||||
|
text := scanner.Text()
|
||||||
|
if firstLine {
|
||||||
|
if !strings.HasPrefix(text, "---") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
firstLine = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(text, "---") {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
metadata.WriteString(text)
|
||||||
|
metadata.WriteString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return metadata.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func split(filename string) {
|
||||||
|
var metadata string
|
||||||
|
var data string
|
||||||
|
f, err := os.Open(filename)
|
||||||
|
if err == nil {
|
||||||
|
defer f.Close()
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
metadata = readMetadata(scanner)
|
||||||
|
data = readData(scanner)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
}
|
||||||
|
if len(strings.TrimSpace(metadata)) > 0 {
|
||||||
|
writeMetadata(filename, metadata)
|
||||||
|
goutil.WriteFile(filename, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SplitAll(files []string) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(files))
|
||||||
|
for _, f := range files {
|
||||||
|
go func(file string) {
|
||||||
|
defer wg.Done()
|
||||||
|
split(file)
|
||||||
|
}(f)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
34
templatestructures/templatestructures.go
Normal file
34
templatestructures/templatestructures.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package templatestructures
|
||||||
|
|
||||||
|
type Page struct {
|
||||||
|
Title string
|
||||||
|
Style string
|
||||||
|
Subpages []Subpage
|
||||||
|
Description string
|
||||||
|
Content string
|
||||||
|
Blogname string
|
||||||
|
Blogdir string
|
||||||
|
Blogpost bool
|
||||||
|
PostCollection []Blogpost
|
||||||
|
Categories []Category
|
||||||
|
Previous string
|
||||||
|
Later string
|
||||||
|
Time string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Subpage struct {
|
||||||
|
Title string
|
||||||
|
Description string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Blogpost struct {
|
||||||
|
Title string
|
||||||
|
Link string
|
||||||
|
Content string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Category struct {
|
||||||
|
Name string
|
||||||
|
Path string
|
||||||
|
}
|
33
wombat.go
Normal file
33
wombat.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.gutmet.org/goutil.git"
|
||||||
|
"git.gutmet.org/simpleserver.git"
|
||||||
|
"git.gutmet.org/wombat.git/gallery"
|
||||||
|
"git.gutmet.org/wombat.git/generate"
|
||||||
|
"git.gutmet.org/wombat.git/initer"
|
||||||
|
"git.gutmet.org/wombat.git/post"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func serve(args []string) error {
|
||||||
|
simpleserver.Serve("stage2")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
commands := []goutil.Command{
|
||||||
|
goutil.NewCommandWithFlags("init", initer.Command, "initialize wombat directory"),
|
||||||
|
goutil.NewCommandWithFlags("gen", generate.Command, "generate the website"),
|
||||||
|
goutil.NewCommandWithFlags("post", post.Command, "create a new blogpost"),
|
||||||
|
goutil.NewCommand("serve", serve, "just execute web server"),
|
||||||
|
goutil.NewCommandWithFlags("genpics", gallery.Command, "generate galleries"),
|
||||||
|
goutil.NewCommand("newgallery", func([]string) error { return gallery.NewInteractive() }, "init new gallery (interactive)"),
|
||||||
|
}
|
||||||
|
err := goutil.Execute(commands)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user