inital testing and ideas in readme
This commit is contained in:
commit
970dca3f58
3 changed files with 267 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
public
|
||||
config.toml
|
||||
hugoext
|
78
README.md
Normal file
78
README.md
Normal 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
186
main.go
Normal 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
|
||||
}
|
Loading…
Reference in a new issue