Use yaml for config, build filename and slug using configurable templates

master
Jan-Lukas Else 2020-03-26 15:45:46 +01:00
parent 442395a83c
commit cbf53ccb23
4 changed files with 162 additions and 63 deletions

155
config.go
View File

@ -2,8 +2,9 @@ package main
import (
"errors"
"github.com/caarlos0/env/v6"
"gopkg.in/yaml.v2"
"log"
"os"
"strings"
)
@ -17,6 +18,8 @@ var (
SelectedCdn Cdn
SelectedNotificationServices NotificationServices
SelectedImageCompression ImageCompression
DefaultLanguage string
Languages map[string]Language
)
type SyndicationTarget struct {
@ -24,41 +27,109 @@ type SyndicationTarget struct {
Name string `json:"name"`
}
type config struct {
BlogUrl string `env:"BLOG_URL,required"`
BaseUrl string `env:"BASE_URL,required"`
MediaUrl string `env:"MEDIA_URL"`
GitFilepath string `env:"GIT_FILEPATH" envDefault:"/tmp/micropubrepo"`
GitUrl string `env:"GIT_URL"`
GitUsername string `env:"GIT_USERNAME"`
GitPassword string `env:"GIT_PASSWORD"`
GitAuthorName string `env:"GIT_AUTHOR_NAME" envDefault:"hugo-micropub"`
GitAuthorEmail string `env:"GIT_AUTHOR_EMAIL" envDefault:"hugo-micropub@example.com"`
BunnyCdnKey string `env:"BUNNY_CDN_KEY"`
BunnyCdnStorageKey string `env:"BUNNY_CDN_STORAGE_KEY"`
BunnyCdnStorageName string `env:"BUNNY_CDN_STORAGE_NAME"`
TelegramUserId int `env:"TELEGRAM_USER_ID"`
TelegramBotToken string `env:"TELEGRAM_BOT_TOKEN"`
IgnoredWebmentionUrls []string `env:"WEBMENTION_IGNORED" envSeparator:","`
SyndicationTargets []string `env:"SYNDICATION" envSeparator:","`
TinifyKey string `env:"TINIFY_KEY"`
type YamlConfig struct {
BlogUrl string `yaml:"blogUrl"`
BaseUrl string `yaml:"baseUrl"`
MediaUrl string `yaml:"mediaUrl"`
DefaultLanguage string `yaml:"defaultLang"`
Languages map[string]Language `yaml:"languages"`
Git GitConfig `yaml:"git"`
BunnyCdn BunnyCdnConfig `yaml:"bunnyCdn"`
Telegram TelegramConfig `yaml:"telegram"`
Tinify TinifyConfig `yaml:"tinify"`
IgnoredWebmentionUrls []string `yaml:"ignoreWebmention"`
SyndicationTargets []string `yaml:"syndication"`
}
type BunnyCdnConfig struct {
Key string `yaml:"key"`
StorageKey string `yaml:"storageKey"`
StorageName string `yaml:"storageName"`
}
type TelegramConfig struct {
UserId int `yaml:"userId"`
BotToken string `yaml:"botToken"`
}
type TinifyConfig struct {
Key string `yaml:"key"`
}
type GitConfig struct {
Filepath string `yaml:"filepath"`
Url string `yaml:"url"`
Username string `yaml:"username"`
Password string `yaml:"password"`
AuthorName string `yaml:"authorName"`
AuthorEmail string `yaml:"authorEmail"`
}
type Language struct {
BlogUrl string `yaml:"blogUrl"`
ContentDir string `yaml:"contentDir"`
DefaultSection string `yaml:"defaultSection"`
Sections map[string]Section `yaml:"sections"`
}
type Section struct {
FilenameTemplate string `yaml:"file"`
LocationTemplate string `yaml:"location"`
}
func initConfig() (err error) {
cfg := config{}
if err := env.Parse(&cfg); err != nil {
return errors.New("failed to parse config, probably not all required env vars set")
configFileName, configSet := os.LookupEnv("CONFIG")
if !configSet {
configFileName = "config.yml"
}
configFile, err := os.Open(configFileName)
if err != nil {
return errors.New("failed to open config file")
}
cfg := YamlConfig{}
err = yaml.NewDecoder(configFile).Decode(&cfg)
if err != nil {
return errors.New("failed to parse yaml")
}
// Blog URL (required)
if len(cfg.BlogUrl) < 1 {
return errors.New("blogUrl not configured")
}
if !strings.HasSuffix(cfg.BlogUrl, "/") {
return errors.New("missing trailing slash in BLOG_URL")
return errors.New("missing trailing slash in configured blogUrl")
}
BlogUrl = cfg.BlogUrl
// Media endpoint
// Media endpoint (required)
if len(cfg.BaseUrl) < 1 {
return errors.New("baseUrl not configured")
}
if len(cfg.MediaUrl) < 1 {
return errors.New("mediaUrl not configured")
}
if !strings.HasSuffix(cfg.BaseUrl, "/") {
return errors.New("missing trailing slash in BASE_URL")
return errors.New("missing trailing slash in configured baseUrl")
}
MediaEndpointUrl = cfg.BaseUrl + "media"
// Languages (required)
if len(cfg.DefaultLanguage) < 1 {
return errors.New("no default language configured")
}
DefaultLanguage = cfg.DefaultLanguage
if len(cfg.Languages) > 0 {
for _, lang := range cfg.Languages {
if len(lang.ContentDir) < 1 || len(lang.DefaultSection) < 1 || len(lang.Sections) < 1 {
return errors.New("language not completely configured")
}
for _, section := range lang.Sections {
if len(section.FilenameTemplate) < 1 || len(section.LocationTemplate) < 1 {
return errors.New("section not completely configured")
}
}
}
Languages = cfg.Languages
} else {
return errors.New("no languages configured")
}
// Ignored Webmention URLs (optional)
IgnoredWebmentionUrls = cfg.IgnoredWebmentionUrls
// Syndication Targets (optional)
@ -73,14 +144,14 @@ func initConfig() (err error) {
// Find selected storage
SelectedStorage = func() Storage {
// Git
if len(cfg.GitFilepath) > 0 && len(cfg.GitUrl) > 0 && len(cfg.GitUsername) > 0 && len(cfg.GitPassword) > 0 && len(cfg.GitAuthorName) > 0 && len(cfg.GitAuthorEmail) > 0 {
if len(cfg.Git.Filepath) > 0 && len(cfg.Git.Url) > 0 && len(cfg.Git.Username) > 0 && len(cfg.Git.Password) > 0 && len(cfg.Git.AuthorName) > 0 && len(cfg.Git.AuthorEmail) > 0 {
return &Git{
filepath: cfg.GitFilepath,
url: cfg.GitUrl,
username: cfg.GitUsername,
password: cfg.GitPassword,
name: cfg.GitAuthorName,
email: cfg.GitAuthorEmail,
filepath: cfg.Git.Filepath,
url: cfg.Git.Url,
username: cfg.Git.Username,
password: cfg.Git.Password,
name: cfg.Git.AuthorName,
email: cfg.Git.AuthorEmail,
}
}
return nil
@ -92,10 +163,10 @@ func initConfig() (err error) {
SelectedMediaStorage = func() MediaStorage {
// BunnyCDN
// MEDIA_URL needs trailing slash too
if len(cfg.BunnyCdnStorageKey) > 0 && len(cfg.BunnyCdnStorageName) > 0 && len(cfg.MediaUrl) > 0 && strings.HasSuffix(cfg.MediaUrl, "/") {
if len(cfg.BunnyCdn.StorageKey) > 0 && len(cfg.BunnyCdn.StorageName) > 0 && len(cfg.MediaUrl) > 0 && strings.HasSuffix(cfg.MediaUrl, "/") {
return &BunnyCdnStorage{
key: cfg.BunnyCdnStorageKey,
storageZoneName: cfg.BunnyCdnStorageName,
key: cfg.BunnyCdn.StorageKey,
storageZoneName: cfg.BunnyCdn.StorageName,
baseLocation: cfg.MediaUrl,
}
}
@ -107,8 +178,8 @@ func initConfig() (err error) {
// Find selected CDN (optional)
SelectedCdn = func() Cdn {
// BunnyCDN
if len(cfg.BunnyCdnKey) > 0 {
return &BunnyCdn{key: cfg.BunnyCdnKey}
if len(cfg.BunnyCdn.Key) > 0 {
return &BunnyCdn{key: cfg.BunnyCdn.Key}
}
return nil
}()
@ -119,10 +190,10 @@ func initConfig() (err error) {
SelectedNotificationServices = func() NotificationServices {
var notificationServices []NotificationService = nil
// Telegram
if cfg.TelegramUserId > 0 && len(cfg.TelegramBotToken) > 0 {
if cfg.Telegram.UserId > 0 && len(cfg.Telegram.BotToken) > 0 {
notificationServices = append(notificationServices, &Telegram{
userId: cfg.TelegramUserId,
botToken: cfg.TelegramBotToken,
userId: cfg.Telegram.UserId,
botToken: cfg.Telegram.BotToken,
})
}
return notificationServices
@ -133,9 +204,9 @@ func initConfig() (err error) {
// Find configured image compression service (optional)
SelectedImageCompression = func() ImageCompression {
// Tinify
if len(cfg.TinifyKey) > 0 {
if len(cfg.Tinify.Key) > 0 {
return &Tinify{
key: cfg.TinifyKey,
key: cfg.Tinify.Key,
}
}
return nil

View File

@ -9,6 +9,7 @@ import (
"math/rand"
"net/http"
"strings"
"text/template"
"time"
)
@ -258,26 +259,57 @@ func computeExtraSettings(entry *Entry) error {
random := generateRandomString(now, 5)
entry.slug = fmt.Sprintf("%v-%02d-%02d-%v", now.Year(), int(now.Month()), now.Day(), random)
}
// Compute filename and location
contentFolder := "content"
localizedBlogUrl := BlogUrl
if len(entry.language) > 0 && entry.language != "en" {
// Append language to content folder: "content-de" for language "de"
contentFolder += "-" + entry.language
// Append language to BlogUrl
localizedBlogUrl += entry.language + "/"
// Set language
if len(entry.language) == 0 {
entry.language = DefaultLanguage
}
if len(entry.section) < 1 {
entry.section = "micro"
// Compute filename and location
lang := Languages[entry.language]
contentFolder := lang.ContentDir
localizedBlogUrl := BlogUrl
if len(lang.BlogUrl) != 0 {
localizedBlogUrl = lang.BlogUrl
}
if len(entry.section) == 0 {
entry.section = lang.DefaultSection
}
entry.section = strings.ToLower(entry.section)
if entry.section == "thoughts" || entry.section == "links" || entry.section == "micro" {
entry.filename = fmt.Sprintf("%v/%v/%02d/%02d/%v.md", contentFolder, entry.section, now.Year(), int(now.Month()), entry.slug)
entry.location = fmt.Sprintf("%v%v/%02d/%02d/%v/", localizedBlogUrl, entry.section, now.Year(), int(now.Month()), entry.slug)
} else {
entry.filename = fmt.Sprintf("%v/%v/%v.md", contentFolder, entry.section, entry.slug)
entry.location = fmt.Sprintf("%v%v/%v/", localizedBlogUrl, entry.section, entry.slug)
section := lang.Sections[entry.section]
pathVars := struct {
LocalContentFolder string
LocalBlogUrl string
Year int
Month int
Slug string
Section string
}{
LocalContentFolder: contentFolder,
LocalBlogUrl: localizedBlogUrl,
Year: now.Year(),
Month: int(now.Month()),
Slug: entry.slug,
Section: entry.section,
}
filenameTmpl, err := template.New("filename").Parse(section.FilenameTemplate)
if err != nil {
return errors.New("failed to parse filename template")
}
filename := new(bytes.Buffer)
err = filenameTmpl.Execute(filename, pathVars)
if err != nil {
return errors.New("failed to execute filename template")
}
entry.filename = filename.String()
locationTmpl, err := template.New("location").Parse(section.LocationTemplate)
if err != nil {
return errors.New("failed to parse location template")
}
location := new(bytes.Buffer)
err = locationTmpl.Execute(location, pathVars)
if err != nil {
return errors.New("failed to execute location template")
}
entry.location = location.String()
return nil
}

1
go.mod
View File

@ -4,7 +4,6 @@ go 1.14
require (
codeberg.org/jlelse/tinify v0.0.0-20200123222407-7fc9c21822b0
github.com/caarlos0/env/v6 v6.2.1
github.com/go-git/go-git/v5 v5.0.0
gopkg.in/yaml.v2 v2.2.8
willnorris.com/go/webmention v0.0.0-20200126231626-5a55fff6bf71

5
go.sum
View File

@ -8,8 +8,6 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/caarlos0/env/v6 v6.2.1 h1:/bFpX1dg4TNioJjg7mrQaSrBoQvRfLUHNfXivdFbbEo=
github.com/caarlos0/env/v6 v6.2.1/go.mod h1:3LpmfcAYCG6gCiSgDLaFR5Km1FRpPwFvBbRcjHar6Sw=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -51,9 +49,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM=
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=