hugoext/main.go

312 lines
7.2 KiB
Go
Raw Normal View History

2021-07-10 16:27:58 +03:00
package main
import (
"bytes"
2021-07-10 16:27:58 +03:00
"flag"
"fmt"
"log"
"os"
"os/exec"
2021-07-10 16:27:58 +03:00
"path"
"path/filepath"
2021-07-10 17:44:25 +03:00
"strings"
2021-07-10 16:27:58 +03:00
"time"
"github.com/gohugoio/hugo/config"
2021-07-10 20:33:55 +03:00
"github.com/n0x1m/hugoext/hugo"
2021-07-10 16:27:58 +03:00
"github.com/spf13/afero"
)
const (
2021-07-10 20:29:02 +03:00
defaultExt = "md"
defaultProcessor = ""
2021-07-10 16:27:58 +03:00
defaultSource = "content"
defaultDestination = "public"
defaultConfigPath = "config.toml"
defaultUglyURLs = false
defaultPermalinkFormat = "/:year/:month/:title/"
)
func main() {
2021-07-11 05:46:19 +03:00
var ext, pipe, source, destination, cfgPath string
2021-07-10 20:18:52 +03:00
var uglyURLs, noSectionList, withDrafts bool
2021-07-10 16:27:58 +03:00
flag.StringVar(&ext, "ext", defaultExt, "ext to look for templates in ./layout")
2021-07-11 05:46:19 +03:00
flag.StringVar(&pipe, "pipe", defaultProcessor, "pipe markdown to this program for content processing")
2021-07-10 16:27:58 +03:00
flag.StringVar(&source, "source", defaultSource, "source directory")
flag.StringVar(&destination, "destination", defaultDestination, "output directory")
2021-07-10 20:18:52 +03:00
flag.StringVar(&cfgPath, "config", defaultConfigPath, "hugo config path")
flag.BoolVar(&uglyURLs, "ugly-urls", false, "use directories with index or .ext files")
flag.BoolVar(&noSectionList, "no-section-list", false, "disable auto append of section content lists")
flag.BoolVar(&withDrafts, "enable-withDrafts", false, "include withDrafts in processing and output")
2021-07-10 16:27:58 +03:00
flag.Parse()
2021-07-11 05:46:19 +03:00
fmt.Printf("converting markdown to %v with %v\n", ext, pipe)
2021-07-10 20:33:55 +03:00
2021-07-10 16:27:58 +03:00
osfs := afero.NewOsFs()
cfg, err := config.FromFile(osfs, "config.toml")
if err != nil {
log.Fatal("config from file", err)
}
permalinks := cfg.GetStringMapString("permalinks")
2021-07-10 19:32:21 +03:00
if permalinks == nil {
log.Println("no permalinks from config loaded, using default: ", defaultPermalinkFormat)
}
2021-07-10 16:27:58 +03:00
2021-07-10 19:32:21 +03:00
linkpattern := func(subdir string) string {
2021-07-10 16:27:58 +03:00
format, ok := permalinks[subdir]
if ok {
return format
}
return defaultPermalinkFormat
}
2021-07-10 17:44:25 +03:00
// iterate through file tree source
2021-07-10 17:44:25 +03:00
files := make(chan File)
go collectFiles(source, files)
// for each file, get destination path, switch file extension, remove underscore for index
var tree FileTree
2021-07-10 17:44:25 +03:00
for file := range files {
2021-07-10 19:32:21 +03:00
pattern := linkpattern(file.Parent)
2021-07-10 17:44:25 +03:00
err := destinationPath(&file, pattern)
if err != nil {
fmt.Println(err, file)
continue
}
//fmt.Printf("%s -> %s (%d)\n", file.Source, file.Destination, len(file.Body))
2021-07-10 20:18:52 +03:00
if file.Draft && !withDrafts {
fmt.Printf("skipping draft %s (%dbytes)\n", file.Source, len(file.Body))
continue
}
tree.Files = append(tree.Files, file)
}
// call proc and pipe content through it, catch output of proc
for i, file := range tree.Files {
// fmt.Printf("%s -> %s (%d)\n", file.Source, file.Destination, len(file.Body))
2021-07-11 05:46:19 +03:00
extpipe := exec.Command(pipe)
buf := bytes.NewReader(file.Body)
extpipe.Stdin = buf
var procout bytes.Buffer
extpipe.Stdout = &procout
extpipe.Start()
extpipe.Wait()
// write to source
tree.Files[i].NewBody = procout.Bytes()
2021-07-10 19:32:21 +03:00
fmt.Printf("processed %s (%dbytes)\n", file.Source, len(tree.Files[i].Body))
}
newpath := filepath.Join(".", "public")
err = os.MkdirAll(newpath, os.ModePerm)
if err != nil {
log.Fatal(err)
}
// write to destination
for _, file := range tree.Files {
2021-07-10 19:32:21 +03:00
//fmt.Printf("%s -> %s (%d)\n", file.Source, file.Destination, len(file.Body))
outfile := "index." + ext
2021-07-11 05:46:19 +03:00
outdir := file.Destination
if uglyURLs && file.Destination != "index" {
// make the last element in destination the file
outfile = filepath.Base(file.Destination) + "." + ext
// set the parent directory of that file to be the dir to create
outdir = filepath.Dir(file.Destination)
}
if file.Destination == "index" {
outdir = "."
}
// ensure directory exists
newpath := filepath.Join(destination, outdir)
fmt.Printf("mkdir %s\n", newpath)
if err = os.MkdirAll(newpath, os.ModePerm); err != nil {
log.Fatalf("cannot directory file %s, error: %v", newpath, err)
}
2021-07-11 05:46:19 +03:00
// create file based on directory and filename
fullpath := filepath.Join(newpath, outfile)
newfile, err := os.Create(fullpath)
if err != nil {
2021-07-11 05:46:19 +03:00
log.Fatalf("cannot create file %s, error: %v", fullpath, err)
}
2021-07-10 19:32:21 +03:00
2021-07-11 05:46:19 +03:00
n, err := newfile.Write(file.NewBody)
if err != nil {
log.Fatalf("cannot write file %s, error: %v", fullpath, err)
}
newfile.Close()
2021-07-10 19:32:21 +03:00
fmt.Printf("written %s (%dbytes)\n", fullpath, n)
2021-07-10 17:44:25 +03:00
}
2021-07-10 20:18:52 +03:00
// TODO
//
// append section listings
// => link title line
// date - summary block
2021-07-10 19:32:21 +03:00
// TODO
2021-07-11 05:46:19 +03:00
// in watch mode, compare timestamps of files before replacement, keep index?
2021-07-10 19:32:21 +03:00
// check/replace links
2021-07-10 17:44:25 +03:00
// write rss?
// write listings from template?
2021-07-10 16:27:58 +03:00
}
2021-07-10 17:44:25 +03:00
type FileTree struct {
Files []File
2021-07-10 17:44:25 +03:00
}
type File struct {
Root string
Source string
Destination string
Parent string
Name string
Extension string
2021-07-10 20:18:52 +03:00
Draft bool
Body []byte
NewBody []byte
2021-07-10 17:44:25 +03:00
}
2021-07-10 20:33:55 +03:00
func parse(fullpath string) ([]byte, *hugo.Content, error) {
2021-07-10 16:27:58 +03:00
file, err := os.Open(fullpath)
if err != nil {
2021-07-10 17:44:25 +03:00
return nil, nil, err
2021-07-10 16:27:58 +03:00
}
defer file.Close()
2021-07-10 20:33:55 +03:00
page, err := hugo.ReadFrom(file)
2021-07-10 16:27:58 +03:00
if err != nil {
2021-07-10 17:44:25 +03:00
return nil, nil, err
2021-07-10 16:27:58 +03:00
}
meta, err := page.Metadata()
if err != nil {
2021-07-10 17:44:25 +03:00
return nil, nil, err
2021-07-10 16:27:58 +03:00
}
c := NewContentFromMeta(meta)
body := page.FrontMatter()
body = append(body, '\n')
body = append(body, page.Content()...)
return body, c, nil
2021-07-10 17:44:25 +03:00
}
2021-07-10 16:27:58 +03:00
2021-07-10 17:44:25 +03:00
func destinationPath(file *File, pattern string) error {
body, c, err := parse(file.Source)
if err != nil {
return err
}
c.Filepath = file.Name
file.Body = body
2021-07-10 20:18:52 +03:00
file.Draft = c.Draft
2021-07-10 17:44:25 +03:00
if file.Parent != "." {
2021-07-10 20:33:55 +03:00
link, err := hugo.PathPattern(pattern).Expand(c)
2021-07-10 17:44:25 +03:00
if err != nil {
return err
}
file.Destination = link
} else {
file.Destination = strings.TrimLeft(file.Name, "_")
}
return nil
2021-07-10 16:27:58 +03:00
}
2021-07-10 17:44:25 +03:00
func collectFiles(fullpath string, filechan chan File) error {
defer close(filechan)
2021-07-10 16:27:58 +03:00
return filepath.Walk(fullpath,
func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
rel, err := filepath.Rel(fullpath, p)
if err != nil {
return err
}
filename := info.Name()
ext := path.Ext(filename)
name := filename[0 : len(filename)-len(ext)]
parent := filepath.Dir(rel)
2021-07-10 17:44:25 +03:00
filechan <- File{
Root: fullpath,
Source: p,
Name: name,
Extension: ext,
Parent: parent,
2021-07-10 16:27:58 +03:00
}
return nil
})
}
2021-07-10 20:33:55 +03:00
func NewContentFromMeta(meta map[string]interface{}) *hugo.Content {
return &hugo.Content{
2021-07-10 19:32:21 +03:00
Title: stringFromInterface(meta["title"]),
Slug: stringFromInterface(meta["slug"]),
Summary: stringFromInterface(meta["summary"]),
Categories: stringArrayFromInterface(meta["categories"]),
Tags: stringArrayFromInterface(meta["tags"]),
Date: dateFromInterface(meta["date"]),
2021-07-10 20:18:52 +03:00
Draft: boolFromInterface(meta["draft"]),
2021-07-10 16:27:58 +03:00
}
}
2021-07-10 19:32:21 +03:00
func stringFromInterface(input interface{}) string {
2021-07-10 20:18:52 +03:00
str, _ := input.(string)
return str
}
func boolFromInterface(input interface{}) bool {
v, _ := input.(bool)
return v
2021-07-10 16:27:58 +03:00
}
2021-07-10 19:32:21 +03:00
func dateFromInterface(input interface{}) time.Time {
2021-07-10 16:27:58 +03:00
str, ok := input.(string)
if !ok {
return time.Now()
}
t, err := time.Parse(time.RFC3339, str)
if err != nil {
// try just date, or give up
t, err := time.Parse("2006-01-02", str)
if err != nil {
return time.Now()
}
return t
}
return t
}
2021-07-10 19:32:21 +03:00
func stringArrayFromInterface(input interface{}) []string {
strarr, ok := input.([]interface{})
2021-07-10 16:27:58 +03:00
if ok {
2021-07-10 19:32:21 +03:00
var out []string
for _, str := range strarr {
out = append(out, stringFromInterface(str))
}
return out
2021-07-10 16:27:58 +03:00
}
return nil
}