inital testing and ideas in readme

This commit is contained in:
dre 2021-07-10 21:27:58 +08:00
commit 970dca3f58
3 changed files with 267 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
public
config.toml
hugoext

78
README.md Normal file
View file

@ -0,0 +1,78 @@
# hugoext
Utility to parse a hugo config file and create the same file structure for content through an
arbitrary output pipe extension.
Hugo parses primarily markdown files and go templates. The initial motivation for this utility was
to enable the same tools to publish a gemlog version of the same blog to make it accessible through
the Gemini protocol.
**NOTE**: not many features, this is minimal and only has one use case for now.
Features
- reads hugo `.toml` file for section output formats
- supports an arbitrary document processor, any program that supports UNIX pipes
When the selected extension is blank, markdown files will be copied unmodified.
## Example Use
Using the [md2gmi](https://github.com/n0x1m/md2gmi) command line utility to convert markdown to
gemtext. Executed from the hugo directory:
```
hugoext -ext gmi -pipe md2gmi
```
It abides the hugo section config in `[permalinks]` but only uses the content subdirectory to
determine the section. An example section config in hugo looks like this:
```
[permalink]
posts = "/posts/:year/:month:day/:filename"
snippets = "/snippets/:filename"
page = ":filename"
```
### Installation
```
go install github.com/n0x1m/hugoext
```
To use the gemini file server and markdown to gemtext converter in the examples below, also install
these:
```
go install github.com/n0x1m/md2gmi
go install github.com/n0x1m/gmifs
```
### Development
To test the extension in a similar fashion to the hugo workflow, use a server to host the static
files. Here an example for a Gemlog using [gmifs](https://github.com/n0x1m/gmifs) in a makefile:
```makefile
serve:
hugoext -ext gmi -pipe md2gmi -serve="gmifs -autoindex"
```
hugoext pipes the input through the `md2gmi` extension and spawns `gmifs` to serve the local gemini
directory with auto indexing enabled.
### Production
I have a makefile target in my hugo directory to build and publish html and gemtext content:
```makefile
build:
hugo --minify
hugoext -ext gmi -pipe md2gmi
publish: build
rsync -a -P --delete ./public/ dre@nox.im/var/www/htdocs/nox.im/
```
The output directory for both hugo and hugoext is `./public`. It's ok to mix the two into the same
file tree as each directory will contain an `index.html` and an `index.gmi` file.

186
main.go Normal file
View file

@ -0,0 +1,186 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"path"
"path/filepath"
"time"
"github.com/gohugoio/hugo/config"
"github.com/spf13/afero"
)
const (
defaultExt = "gmi"
defaultProcessor = "md2gmi"
defaultSource = "content"
defaultDestination = "public"
defaultConfigPath = "config.toml"
defaultUglyURLs = false
defaultPermalinkFormat = "/:year/:month/:title/"
)
func main() {
var ext, processor, source, destination, cfgpath string
var uglyurls bool
flag.StringVar(&ext, "ext", defaultExt, "ext to look for templates in ./layout")
flag.StringVar(&processor, "proc", defaultProcessor, "processor to pipe markdown content through")
flag.StringVar(&source, "source", defaultSource, "source directory")
flag.StringVar(&destination, "destination", defaultDestination, "output directory")
flag.StringVar(&cfgpath, "config", defaultConfigPath, "hugo config path")
flag.BoolVar(&uglyurls, "ugly-urls", defaultUglyURLs, "use directories with index or .ext files")
flag.Parse()
osfs := afero.NewOsFs()
cfg, err := config.FromFile(osfs, "config.toml")
if err != nil {
log.Fatal("config from file", err)
}
permalinks := cfg.GetStringMapString("permalinks")
fmt.Println("permalinks", permalinks)
fpath := "content/posts/first-post.md"
file, err := os.Open(fpath)
if err != nil {
log.Fatal("open", err)
}
page, err := ReadFrom(file)
if err != nil {
log.Fatal("read page", err)
}
fmt.Println(string(page.FrontMatter()))
fmt.Println(string(page.Content()))
meta, err := page.Metadata()
if err != nil {
log.Fatal("read meta", err)
}
c := NewContentFromMeta(meta)
fmt.Println(c)
link, err := pathPattern(permalinks["posts"]).Expand(c)
if err != nil {
log.Fatal("permalink expand", err)
}
fmt.Println(link)
// test
linkcfg := func(subdir string) string {
format, ok := permalinks[subdir]
if ok {
return format
}
return defaultPermalinkFormat
}
listDirectory(source, linkcfg)
}
func parse(fullpath string) (*Content, error) {
file, err := os.Open(fullpath)
if err != nil {
return nil, err
}
page, err := ReadFrom(file)
if err != nil {
return nil, err
}
meta, err := page.Metadata()
if err != nil {
return nil, err
}
c := NewContentFromMeta(meta)
fmt.Println(c)
return c, nil
}
func listDirectory(fullpath string, linkcfg func(string) string) error {
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)
fmt.Println(p, info.Name(), parent, rel, ext)
c, err := parse(p)
if err != nil {
return err
}
c.Filepath = name
if parent != "." {
pattern := linkcfg(parent)
link, err := pathPattern(pattern).Expand(c)
if err != nil {
return err
}
c.Permalink = link
} else {
c.Permalink = filename
}
fmt.Println(c)
return nil
})
}
func NewContentFromMeta(meta map[string]interface{}) *Content {
return &Content{
Title: istring(meta["title"]),
Slug: istring(meta["slug"]),
Categories: istringArr(meta["categories"]),
Date: idate(meta["date"]),
}
}
func istring(input interface{}) string {
str, ok := input.(string)
if ok {
return str
}
return ""
}
func idate(input interface{}) time.Time {
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
}
func istringArr(input interface{}) []string {
str, ok := input.([]string)
if ok {
return str
}
return nil
}