From cbf53ccb230167697a07fdde02580f7284f41e1f Mon Sep 17 00:00:00 2001 From: Jan-Lukas Else Date: Thu, 26 Mar 2020 15:45:46 +0100 Subject: [PATCH] Use yaml for config, build filename and slug using configurable templates --- config.go | 155 +++++++++++++++++++++++++++++++++++++++--------------- entry.go | 64 ++++++++++++++++------ go.mod | 1 - go.sum | 5 +- 4 files changed, 162 insertions(+), 63 deletions(-) diff --git a/config.go b/config.go index 7733610..d4d8b4d 100644 --- a/config.go +++ b/config.go @@ -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 diff --git a/entry.go b/entry.go index 5180e4b..f999dd1 100644 --- a/entry.go +++ b/entry.go @@ -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 } diff --git a/go.mod b/go.mod index 9c993ba..a131d79 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 49673e8..ee0960a 100644 --- a/go.sum +++ b/go.sum @@ -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=