aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.golangci.yml74
-rw-r--r--cmd/server/app.go16
-rw-r--r--cmd/server/feed.go48
-rw-r--r--cmd/server/handlers.go17
-rw-r--r--cmd/server/main.go38
-rw-r--r--go.mod3
-rw-r--r--go.sum25
-rw-r--r--page/checkup.go8
-rw-r--r--page/index.go23
-rw-r--r--page/misc.go8
-rw-r--r--page/page.go59
-rw-r--r--page/pagelist.go12
-rw-r--r--page/render.go33
-rw-r--r--page/time.go14
-rw-r--r--rediscache/main.go118
15 files changed, 415 insertions, 81 deletions
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 0000000..319e4af
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,74 @@
+
+linters:
+ enable:
+ - deadcode
+ - dupl
+ - errcheck
+ - funlen
+ # - gochecknoglobals
+ - ineffassign
+ - structcheck
+ - typecheck
+ - varcheck
+ - asciicheck
+ - bodyclose
+ - depguard
+ - dogsled
+ - errorlint
+ - exhaustive
+ # - exhaustivestruct
+ - exportloopref
+ - gci
+ - gochecknoinits
+ - gocognit
+ - goconst
+ - gocritic
+ - gocyclo
+ - godot
+ - godox
+ - gofmt
+ - gofumpt
+ - goheader
+ # - goimports
+ - golint
+ - gomnd
+ - gomodguard
+ - goprintffuncname
+ - interfacer
+ - lll
+ - maligned
+ - misspell
+ - nakedret
+ - nestif
+ - nlreturn
+ - noctx
+ - nolintlint
+ - paralleltest
+ - prealloc
+ - rowserrcheck
+ - scopelint
+ - sqlclosecheck
+ - stylecheck
+ - testpackage
+ - tparallel
+ - unconvert
+ - unparam
+ - whitespace
+ - wrapcheck
+ - wsl
+ - gosec
+ - goerr113
+
+
+issues:
+ # List of regexps of issue texts to exclude, empty list by default.
+ # But independently from this option we use default exclude patterns,
+ # it can be disabled by `exclude-use-default: false`. To list all
+ # excluded by default patterns execute `golangci-lint run --help`
+ exclude:
+ - .*and that stutters.*
+
+ # It's a complex function--it's just one though
+ - Function 'Checkup' is too long
+ - Cognitive complexity .* of func ..\*Page..Checkup. is high
+ - 'if .info.IsDir.. .. strings.HasSuffix.info.Name.., Suffix.. is deeply nested .complexity: ... .nesti'
diff --git a/cmd/server/app.go b/cmd/server/app.go
index 743a389..290b44a 100644
--- a/cmd/server/app.go
+++ b/cmd/server/app.go
@@ -1,8 +1,10 @@
package main
import (
+ "fmt"
"os"
+ "github.com/gomodule/redigo/redis"
"gopkg.in/yaml.v3"
"riedstra.dev/mitch/go-website/page"
)
@@ -10,6 +12,8 @@ import (
var FeedPrefixDefault = ".feeds"
type App struct {
+ redisPool *redis.Pool
+
ReIndexPath string
StaticDirectory string
BaseTemplate string
@@ -21,7 +25,7 @@ type App struct {
Description string // aka, "subtitle"
Author Author
SiteURL string
- FeedId string
+ FeedId string //nolint
Updated page.PageTime
FeedPrefix string
}
@@ -29,28 +33,34 @@ type App struct {
func loadConf(fn string) (*App, error) {
fh, err := os.Open(fn)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("loading config: %w", err)
}
+
dec := yaml.NewDecoder(fh)
app := &App{}
+
err = dec.Decode(app)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("decoding yaml: %w", err)
}
if app.StaticDirectory == "" {
app.StaticDirectory = "static"
}
+
if app.FeedPrefix == "" {
app.FeedPrefix = FeedPrefixDefault
}
+
if app.BaseTemplate != "" {
page.BaseTemplate = app.BaseTemplate
}
+
if app.DocumentSplit != "" {
page.DocumentSplit = app.DocumentSplit
}
+
if app.Suffix != "" {
page.Suffix = app.Suffix
}
diff --git a/cmd/server/feed.go b/cmd/server/feed.go
index c478365..880eeb4 100644
--- a/cmd/server/feed.go
+++ b/cmd/server/feed.go
@@ -16,8 +16,8 @@ import (
)
type Author struct {
- Name string `xml:"name"` // Required
- Uri string `xml:"uri,omitempty"`
+ Name string `xml:"name"` // Required
+ Uri string `xml:"uri,omitempty"` //nolint:golint,stylecheck
Email string `xml:"email,omitempty"`
}
@@ -37,7 +37,7 @@ type Content struct {
type Entry struct {
// Spec requires this, autogenerated from Title and updated if otherwise
// left empty
- Id string `xml:"id"`
+ Id string `xml:"id"` //nolint:golint,stylecheck
Title string `xml:"title"` // Required
Updated *time.Time `xml:"updated"` // Required
@@ -54,12 +54,13 @@ func (i Entry) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if i.Title == "" {
errs = append(errs, "Title Cannot be empty")
}
+
if i.Updated == nil {
errs = append(errs, "Updated cannot be nil")
}
if len(errs) > 0 {
- return errors.New(strings.Join(errs, ","))
+ return errors.New(strings.Join(errs, ",")) //nolint:goerr113
}
if i.Id == "" {
@@ -71,6 +72,7 @@ func (i Entry) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return e.EncodeElement(i2, start)
}
+//nolint:stylecheck,golint
type Atom struct {
Ns string `xml:"xmlns,attr"`
Title string `xml:"title"` // Required
@@ -87,18 +89,21 @@ func (a Atom) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
a.Ns = "http://www.w3.org/2005/Atom"
errs := []string{}
+
if a.Id == "" {
errs = append(errs, "ID Cannot be empty")
}
+
if a.Author.Name == "" {
errs = append(errs, "Author Name cannot be empty")
}
+
if a.Updated == nil {
errs = append(errs, "Updated cannot be empty")
}
if len(errs) > 0 {
- return errors.New(strings.Join(errs, ","))
+ return errors.New(strings.Join(errs, ",")) //nolint:goerr113
}
start.Name = xml.Name{Local: "feed"}
@@ -114,12 +119,15 @@ func (a Atom) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
// Relevant query parameters are:
//
// "content" if unset, or set to false content is omitted from the feed
-// "limit=n" stop at "n" and return the feed
+// "limit=n" stop at "n" and return the feed.
//
-func (a *App) FeedHandler(w http.ResponseWriter, r *http.Request) {
+func (a *App) FeedHandler(w http.ResponseWriter, r *http.Request) { //nolint:funlen
vars := mux.Vars(r)
- var addContent bool
- var limit int
+
+ var (
+ addContent bool
+ limit int
+ )
if _, ok := r.URL.Query()["content"]; ok {
if r.URL.Query().Get("content") != "false" {
@@ -137,20 +145,24 @@ func (a *App) FeedHandler(w http.ResponseWriter, r *http.Request) {
tag, ok := vars["tag"]
if !ok {
http.Error(w, "Tag not found or supplied", http.StatusNotFound)
+
return
}
p := page.NewPage("index")
+
index, err := p.Index()
if err != nil {
log.Println(err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
+
return
}
pages, ok := index[tag]
if !ok {
http.Error(w, "Invalid tag", http.StatusNotFound)
+
return
}
@@ -158,6 +170,7 @@ func (a *App) FeedHandler(w http.ResponseWriter, r *http.Request) {
for _, p := range dateless {
log.Printf("Warning, page %s has no Date field. Skipping inclusion on feed", p)
}
+
pages.SortDate()
feed := &Atom{
@@ -180,22 +193,24 @@ func (a *App) FeedHandler(w http.ResponseWriter, r *http.Request) {
}
content := &bytes.Buffer{}
+
err := p.Render(content)
if err != nil {
log.Println(err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
+
return
}
entry := Entry{
Title: p.Title,
Updated: &p.Date.Time,
- Links: []Link{Link{Href: strings.Join([]string{a.SiteURL, p.Path()}, "/")}},
+ Links: []Link{{Href: strings.Join([]string{a.SiteURL, p.Path()}, "/")}},
}
if p.AuthorName != "" {
entry.Author = &Author{
- Name: p.AuthorName,
+ Name: p.AuthorName,
}
if p.AuthorEmail != "" {
entry.Author.Email = p.AuthorEmail
@@ -207,13 +222,18 @@ func (a *App) FeedHandler(w http.ResponseWriter, r *http.Request) {
}
entries = append(entries, entry)
-
}
feed.Entries = entries
w.Header().Add("Content-type", "application/xml")
- w.Write([]byte(xml.Header))
+
+ _, err = w.Write([]byte(xml.Header))
+ if err != nil {
+ log.Println("Writing xml: ", err)
+
+ return
+ }
enc := xml.NewEncoder(w)
enc.Indent("", " ")
@@ -224,6 +244,4 @@ func (a *App) FeedHandler(w http.ResponseWriter, r *http.Request) {
// Headers probably already sent, but we'll try anyway
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
-
- return
}
diff --git a/cmd/server/handlers.go b/cmd/server/handlers.go
index cb63774..869e5ba 100644
--- a/cmd/server/handlers.go
+++ b/cmd/server/handlers.go
@@ -7,14 +7,23 @@ import (
"github.com/gorilla/mux"
"riedstra.dev/mitch/go-website/page"
+ "riedstra.dev/mitch/go-website/rediscache"
)
func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
rtr := mux.NewRouter()
rtr.HandleFunc(a.ReIndexPath, a.RebuildIndexHandler)
rtr.PathPrefix("/static/").Handler(a.StaticHandler())
- rtr.PathPrefix(fmt.Sprintf("/%s/{tag}", a.FeedPrefix)).HandlerFunc(a.FeedHandler)
- rtr.PathPrefix("/").HandlerFunc(a.PageHandler)
+ rtr.PathPrefix(fmt.Sprintf("/%s/{tag}", a.FeedPrefix)).HandlerFunc(
+ a.FeedHandler)
+
+ if a.redisPool != nil {
+ rtr.PathPrefix("/").Handler(rediscache.Handle(
+ a.redisPool, http.HandlerFunc(a.PageHandler)))
+ } else {
+ rtr.PathPrefix("/").Handler(http.HandlerFunc(a.PageHandler))
+ }
+
rtr.ServeHTTP(w, r)
}
@@ -23,6 +32,7 @@ func (a *App) PageHandler(w http.ResponseWriter, r *http.Request) {
if u == "/" {
u = "/index"
}
+
u = filepath.Join(".", u)
page.RenderForPath(w, r, u)
@@ -33,6 +43,7 @@ func (a *App) RebuildIndexHandler(w http.ResponseWriter, r *http.Request) {
if u == "/" {
u = "/index"
}
+
u = filepath.Join(".", u)
p := page.NewPage("index")
@@ -44,7 +55,7 @@ func (a *App) RebuildIndexHandler(w http.ResponseWriter, r *http.Request) {
}
// StaticHandler simply returns a HTTP handler that looks at the current
-// directory and exposes `static` via HTTP `/static`
+// directory and exposes `static` via HTTP `/static`.
func (a *App) StaticHandler() http.Handler {
return http.StripPrefix("/static/", http.FileServer(http.Dir(a.StaticDirectory)))
}
diff --git a/cmd/server/main.go b/cmd/server/main.go
index 00aecbf..d76f938 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -3,12 +3,13 @@ package main
import (
"flag"
"fmt"
- "gopkg.in/yaml.v3"
"log"
"net/http"
"os"
"time"
+ "github.com/gomodule/redigo/redis"
+ "gopkg.in/yaml.v3"
"riedstra.dev/mitch/go-website/page"
)
@@ -19,17 +20,28 @@ func VersionPrint() {
os.Exit(0)
}
-func main() {
+func main() { //nolint:funlen
fl := flag.NewFlagSet("Website", flag.ExitOnError)
listen := fl.String("l", "0.0.0.0:8001", "Listening address")
directory := fl.String("d", ".", "Directory to serve.")
version := fl.Bool("v", false, "Print the version then exit")
confFn := fl.String("c", "conf.yml", "Location for the config file")
verbose := fl.Bool("V", false, "Be more verbose ( dump config, etc ) ")
- fl.StringVar(&page.TimeFormat, "T", page.TimeFormat, "Set the page time format, be careful with this")
+ fl.StringVar(&page.TimeFormat, "T", page.TimeFormat,
+ "Set the page time format, be careful with this")
+
defaultIndexPath := "/reIndex"
+
indexPath := fl.String("i", defaultIndexPath,
"Path in which, when called will rebuild the index and clear the cache")
+ redisAddr := fl.String("r", "127.0.0.1:6379",
+ "Redis server set to \"\" to disable")
+
+ pageTimeout := fl.Int("timeout", 15, "Seconds until page timeout for read and write")
+
+ fl.BoolVar(&page.CacheIndex, "cache-index", true,
+ "If set to false do not cache index")
+
_ = fl.Parse(os.Args[1:])
if *version {
@@ -43,6 +55,7 @@ func main() {
app, err := loadConf(*confFn)
if err != nil {
log.Println(err)
+
app = &App{}
}
@@ -50,6 +63,21 @@ func main() {
app.ReIndexPath = *indexPath
}
+ if *redisAddr != "" {
+ app.redisPool = &redis.Pool{
+ MaxIdle: 80, //nolint:gomnd
+ MaxActive: 12000, //nolint:gomnd
+ Dial: func() (redis.Conn, error) {
+ c, err := redis.Dial("tcp", *redisAddr)
+ if err != nil {
+ log.Println("Redis dial error: ", err)
+ }
+
+ return c, err //nolint
+ },
+ }
+ }
+
if *verbose {
b, _ := yaml.Marshal(app)
os.Stderr.Write(b)
@@ -58,8 +86,8 @@ func main() {
srv := &http.Server{
Handler: app,
Addr: *listen,
- WriteTimeout: 15 * time.Second,
- ReadTimeout: 15 * time.Second,
+ WriteTimeout: time.Duration(*pageTimeout) * time.Second,
+ ReadTimeout: time.Duration(*pageTimeout) * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
diff --git a/go.mod b/go.mod
index 0843585..444615b 100644
--- a/go.mod
+++ b/go.mod
@@ -3,10 +3,13 @@ module riedstra.dev/mitch/go-website
go 1.13
require (
+ github.com/gomodule/redigo v1.8.5 // indirect
github.com/gorilla/mux v1.8.0
github.com/kr/pretty v0.1.0 // indirect
github.com/russross/blackfriday v2.0.0+incompatible
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
+ github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
+ google.golang.org/appengine v1.6.7 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
)
diff --git a/go.sum b/go.sum
index 0b76651..9503c39 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,9 @@
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/gomodule/redigo v1.8.5 h1:nRAxCa+SVsyjSBrtZmG/cqb6VbTmuRzpg/PoTFlpumc=
+github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@@ -6,12 +12,31 @@ github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday v2.0.0+incompatible h1:cBXrhZNUf9C+La9/YpS+UHpUT8YD6Td9ZMSU9APFcsk=
github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+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/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
+github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/page/checkup.go b/page/checkup.go
index c4501c2..9f721b7 100644
--- a/page/checkup.go
+++ b/page/checkup.go
@@ -9,25 +9,25 @@ import (
)
// Checkup will return a map[string]PageList of all the pages broken down by
-// the status of their fields. For instance, whehter or not they have a date
+// the status of their fields. For instance, whehter or not they have a date.
func (p *Page) Checkup() (map[string]PageList, error) {
Logger.Println("Checking up on all files...")
out := make(map[string]PageList)
- filepath.Walk(filepath.Dir("."),
+ _ = filepath.Walk(filepath.Dir("."),
func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && strings.HasSuffix(info.Name(), Suffix) {
-
p2 := NewPage(strings.ReplaceAll(path, Suffix, ""))
err = p2.Read()
if err != nil {
Logger.Println("Error encountered: ", err)
+
return err
}
@@ -73,9 +73,7 @@ func (p *Page) Checkup() (map[string]PageList, error) {
} else {
out[key] = append(out[key], p2)
}
-
}
-
}
return nil
diff --git a/page/index.go b/page/index.go
index f9f2df5..425bf04 100644
--- a/page/index.go
+++ b/page/index.go
@@ -8,27 +8,32 @@ import (
"time"
)
-var index map[string]PageList
-var indexMu sync.RWMutex
+var (
+ index map[string]PageList
+ indexMu sync.RWMutex
+)
// RebuildIndex can be called in order to rebuild the entire website
-// index
+// index.
func (p *Page) RebuildIndex() error {
indexMu.Lock()
index = nil
indexMu.Unlock()
+
_, err := p.Index()
+
return err
}
-// Index returns a map of all pages in the current directory seperated into
+// Index returns a map of all pages in the current directory separated into
// their respective tags If a Page has multiple tags it will be listed under
// each.
-// Pages are located by their Suffix, default being ".md"
+// Pages are located by their Suffix, default being ".md".
func (p *Page) Index() (map[string]PageList, error) {
indexMu.RLock()
if index != nil && CacheIndex {
indexMu.RUnlock()
+
return index, nil
}
indexMu.RUnlock()
@@ -36,30 +41,29 @@ func (p *Page) Index() (map[string]PageList, error) {
out := make(map[string]PageList)
- filepath.Walk(filepath.Dir("."),
+ _ = filepath.Walk(filepath.Dir("."),
func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && strings.HasSuffix(info.Name(), Suffix) {
-
p2 := NewPage(strings.ReplaceAll(path, Suffix, ""))
err = p2.Read()
if err != nil {
Logger.Println("Error encountered: ", err)
+
return err
}
- for tag, _ := range p2.Tags {
+ for tag := range p2.Tags {
if _, ok := out[tag]; !ok {
out[tag] = []*Page{p2}
} else {
out[tag] = append(out[tag], p2)
}
}
-
}
return nil
@@ -72,6 +76,7 @@ func (p *Page) Index() (map[string]PageList, error) {
return out, nil
}
+// Time fetches the time.Time from the Date field.
func (p *Page) Time() time.Time {
return p.Date.Time
}
diff --git a/page/misc.go b/page/misc.go
index 0dbd059..e8bdfb7 100644
--- a/page/misc.go
+++ b/page/misc.go
@@ -6,6 +6,8 @@ import (
"gopkg.in/yaml.v3"
)
+// EncodeYaml is meant to be used in templating functions to encode
+// arbitrary information as a yaml string.
func (p Page) EncodeYaml(data interface{}) string {
if data == nil {
data = p
@@ -15,10 +17,13 @@ func (p Page) EncodeYaml(data interface{}) string {
if err != nil {
Logger.Println("Encountered error in EncodeYaml: ", err)
}
+
return string(b)
}
-func (p Page) EncodeJson(data interface{}) string {
+// EncodeJSON is meant to be used in templating functions to encode
+// arbitrary information as a JSON string.
+func (p Page) EncodeJSON(data interface{}) string {
if data == nil {
data = p
}
@@ -27,5 +32,6 @@ func (p Page) EncodeJson(data interface{}) string {
if err != nil {
Logger.Println("Encountered error in EncodeJson: ", err)
}
+
return string(b)
}
diff --git a/page/page.go b/page/page.go
index c926bed..fa4ce6c 100644
--- a/page/page.go
+++ b/page/page.go
@@ -1,4 +1,4 @@
-// page implements the website backed by a local filesystem.
+// Package page implements the website backed by a local filesystem.
//
// Reading the base template off the disk, then any markdown files which are
// split into two sections by the DocumentSplit global variable. The first
@@ -25,6 +25,7 @@ package page
import (
"bufio"
"bytes"
+ "errors"
"fmt"
"io"
"log"
@@ -56,27 +57,28 @@ type Page struct {
}
// Global is meant to be supplied by external users of this package to populate
-// globally accessable information across all of the templates accessiable via
-// .Global care must be taken when utilizing this functionality
+// globally accessible information across all of the templates accessiable via
+// .Global care must be taken when utilizing this functionality.
var Global interface{}
// CacheIndex determines whether or not the index will be cached in memory
-// or rebuilt on each call
+// or rebuilt on each call.
var CacheIndex = true
-// BaseTemplate can be adjusted to change the base template used in rendering
+// BaseTemplate can be adjusted to change the base template used in rendering.
var BaseTemplate = "inc/base.html"
-// Suffix is applied to all pages for reading off of the disk
+// Suffix is applied to all pages for reading off of the disk.
var Suffix = ".md"
-// DocumentSplit is used to split the .md files into yaml and markdown
+// DocumentSplit is used to split the .md files into yaml and markdown.
var DocumentSplit = "|---\n"
-// Default logger
-var Logger = log.New(os.Stderr, "", log.LstdFlags)
+// Logger is the default logger used throughout this package, feel
+// free to override.
+var Logger = log.New(os.Stderr, "PAGE: ", log.LstdFlags)
-// NewPage returns a page struct with the path populated
+// NewPage returns a page struct with the path populated.
func NewPage(pth string) *Page {
return &Page{path: filepath.FromSlash(filepath.Clean(pth))}
}
@@ -95,12 +97,12 @@ func (p Page) Path() string {
}
// Global is specifically for use inside of a page markdown file or
-// in a base template. This simply returns the package Global variable
+// in a base template. This simply returns the package Global variable.
func (p *Page) Global() interface{} {
return Global
}
-// Renders a page
+// Render a page.
func (p *Page) Render(wr io.Writer) error {
if err := p.Read(); err != nil {
return err
@@ -108,38 +110,46 @@ func (p *Page) Render(wr io.Writer) error {
t, err := template.ParseFiles(BaseTemplate)
if err != nil {
- return err
+ return fmt.Errorf("rendering: %w", err)
}
return t.Execute(wr, p)
}
-// Reads in the special markdown file format for the website off of the disk
+// Read in the special markdown file format for the website off of the disk.
func (p *Page) Read() error {
yamlBuf := bytes.NewBuffer(nil)
markdownBuf := bytes.NewBuffer(nil)
fh, err := os.Open(p.path + Suffix)
if err != nil {
- return err
+ return fmt.Errorf("opening markdown: %w", err)
}
- defer fh.Close()
+
+ defer func() {
+ err := fh.Close()
+ if err != nil {
+ Logger.Println(err)
+ }
+ }()
+
rdr := bufio.NewReader(fh)
// Read in the file and split between markdown and yaml buffers
yamlDone := false
- for {
+ for {
bytes, err := rdr.ReadBytes('\n')
- if err == io.EOF {
+ if errors.Is(err, io.EOF) {
break
} else if err != nil {
- return err
+ return fmt.Errorf("reading markdown: %w", err)
}
// Is this the line where we stop reading the yaml and start reading markdown?
if DocumentSplit == string(bytes) && !yamlDone {
yamlDone = true
+
continue
}
@@ -152,23 +162,28 @@ func (p *Page) Read() error {
err = yaml.Unmarshal(yamlBuf.Bytes(), p)
if err != nil {
- return err
+ return fmt.Errorf("reading yaml: %w", err)
}
p.markdown = markdownBuf.Bytes()
+
return nil
}
+// RenderBody renders and executes a template from the body of the
+// markdown file, then runs it through the markdown parser.
func (p *Page) RenderBody() (string, error) {
buf := &bytes.Buffer{}
+
t, err := template.New("Body").Parse(string(p.markdown))
if err != nil {
- return "", err
+ return "", fmt.Errorf("render body: %w", err)
}
err = t.Execute(buf, p)
+
if err != nil {
- return "", err
+ return "", fmt.Errorf("template execute; %w", err)
}
return string(blackfriday.Run(buf.Bytes())), nil
diff --git a/page/pagelist.go b/page/pagelist.go
index f2140f9..298acf9 100644
--- a/page/pagelist.go
+++ b/page/pagelist.go
@@ -5,15 +5,16 @@ import (
)
// PageList is a slice of pages, providing a couple of methods to sort
-// by the date, or date reversed
+// by the date, or date reversed.
type PageList []*Page
// RemoveDateless returns two PageLists, the first with valid dates,
// and the second without. This is useful if you need a PageList which
-// will run SortDate and SortDateReverse without issue
+// will run SortDate and SortDateReverse without issue.
func (p PageList) RemoveDateless() (PageList, PageList) {
with := PageList{}
without := PageList{}
+
for _, p := range p {
if p.Date != nil {
with = append(with, p)
@@ -21,19 +22,26 @@ func (p PageList) RemoveDateless() (PageList, PageList) {
without = append(without, p)
}
}
+
return with, without
}
+// SortDate returns the pagelist sorted by date, may panic if pages do
+// not all have dates.
func (p PageList) SortDate() PageList {
sort.Slice(p, func(i, j int) bool {
return p[i].Time().After(p[j].Time())
})
+
return p
}
+// SortDateReverse returns the pagelist sorted by date in reverse, may panic if
+// pages do not all have dates.
func (p PageList) SortDateReverse() PageList {
sort.Slice(p, func(i, j int) bool {
return p[i].Time().Before(p[j].Time())
})
+
return p
}
diff --git a/page/render.go b/page/render.go
index 07b1b88..6cbabf9 100644
--- a/page/render.go
+++ b/page/render.go
@@ -7,14 +7,14 @@ import (
)
// Render is a lower level option, allowing you to specify local
-// variables and the status code in which to return
+// variables and the status code in which to return.
func Render(w http.ResponseWriter, r *http.Request,
path string, vars map[string]interface{}, statusCode int) {
-
u := r.URL.Path
if u == "/" {
u = "/index"
}
+
u = filepath.Join(".", u)
// Sepcifically use the specified path for the page
@@ -32,8 +32,11 @@ func Render(w http.ResponseWriter, r *http.Request,
r.Method,
http.StatusNotFound,
u)
+
p = NewPage("404")
+
w.WriteHeader(http.StatusNotFound)
+
err := p.Render(w)
if err != nil {
Logger.Printf("%s %s path: %s while trying 404: %s",
@@ -43,19 +46,22 @@ func Render(w http.ResponseWriter, r *http.Request,
err)
http.Error(w, "Internal server error",
http.StatusInternalServerError)
+
return
}
- return
- } else {
- Logger.Printf("%s %s path: %s encountered: %s",
- r.RemoteAddr,
- r.Method,
- u,
- err)
- http.Error(w, "Internal server error",
- http.StatusInternalServerError)
+
return
}
+
+ Logger.Printf("%s %s path: %s encountered: %s",
+ r.RemoteAddr,
+ r.Method,
+ u,
+ err)
+ http.Error(w, "Internal server error",
+ http.StatusInternalServerError)
+
+ return
}
Logger.Printf("%s %s %d %s", r.RemoteAddr, r.Method, statusCode, u)
@@ -63,15 +69,14 @@ func Render(w http.ResponseWriter, r *http.Request,
// RenderWithVars allows you to specify a specific page and whether or not
// you wish to override vars. If left nil they will not be overridden.
-// Also see RenderForPath if you don't need to override them
+// Also see RenderForPath if you don't need to override them.
func RenderWithVars(w http.ResponseWriter, r *http.Request,
path string, vars map[string]interface{}) {
-
Render(w, r, path, vars, http.StatusOK)
}
// RenderForPath takes the path to a page and finish up the rendering
-// Allowing you to place logic on what page is rendered by your handlers
+// Allowing you to place logic on what page is rendered by your handlers.
func RenderForPath(w http.ResponseWriter, r *http.Request, path string) {
RenderWithVars(w, r, path, nil)
}
diff --git a/page/time.go b/page/time.go
index 0374490..cd419c8 100644
--- a/page/time.go
+++ b/page/time.go
@@ -1,10 +1,14 @@
package page
import (
- "gopkg.in/yaml.v3"
+ "fmt"
"time"
+
+ "gopkg.in/yaml.v3"
)
+// PageTime allows us to slip in a different time format for loading/unloading
+// the yaml from disk than the default.
type PageTime struct {
time.Time
}
@@ -13,16 +17,22 @@ type PageTime struct {
// from the yaml information.
var TimeFormat = "01.02.2006 15:04:05 MST"
+// UnmarshalYAML override the default and parse our time with the global time
+// format.
func (pt *PageTime) UnmarshalYAML(n *yaml.Node) error {
t, err := time.Parse(TimeFormat, n.Value)
if err != nil {
- return err
+ return fmt.Errorf("pagetime: %w", err)
}
+
pt.Time = t
+
return nil
}
+// MarshalYAML override the default with our own time format.
func (pt PageTime) MarshalYAML() (interface{}, error) {
s := pt.Time.Format(TimeFormat)
+
return s, nil
}
diff --git a/rediscache/main.go b/rediscache/main.go
new file mode 100644
index 0000000..e7564c8
--- /dev/null
+++ b/rediscache/main.go
@@ -0,0 +1,118 @@
+package rediscache
+
+import (
+ "bytes"
+ "log"
+ "net/http"
+ "os"
+
+ "github.com/gomodule/redigo/redis"
+ "github.com/vmihailenco/msgpack"
+)
+
+// Logger is the default logger used for this package, feel free to
+// override.
+var Logger = log.New(os.Stderr, "REDIS: ", log.LstdFlags)
+
+// redisHTTPResponseWriter is essentially a fake http.ResponseWriter that
+// is going to let us suck out information and chuck it into redis
+// implements the interface as defined in net/http.
+type redisHTTPResponseWriter struct {
+ Headers http.Header
+ StatusCode int
+ Data []byte
+
+ buf *bytes.Buffer
+}
+
+// Simply for satisfying the http.ResponseWriter interface.
+func (rw *redisHTTPResponseWriter) Header() http.Header {
+ if rw.Headers == nil {
+ rw.Headers = http.Header{}
+ }
+
+ return rw.Headers
+}
+
+// Writes to the internal buffer.
+func (rw *redisHTTPResponseWriter) Write(msg []byte) (int, error) {
+ if rw.buf == nil {
+ rw.buf = &bytes.Buffer{}
+ }
+
+ return rw.buf.Write(msg)
+}
+
+// Simply for satisfying the http.ResponseWriter interface.
+func (rw *redisHTTPResponseWriter) WriteHeader(code int) {
+ rw.StatusCode = code
+}
+
+// WriteData takes the internal buffer and writes out the entire
+// contents to the 'Data' field for storage in Redis.
+func (rw *redisHTTPResponseWriter) WriteData() {
+ rw.Data = rw.buf.Bytes()
+}
+
+// Simple function that will cache the response for given handler in redis
+// and instead of responding with the result from the handler it will
+// simply dump the contents of the redis key if it exists.
+func Handle(pool *redis.Pool, next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ client := pool.Get()
+ defer client.Close()
+
+ content:
+ data, err := client.Do("GET", r.URL.Path)
+ if err != nil {
+ // Assume something bad has happened with redis, we're
+ // just going to log this and then pass through the
+ // request as normal.
+ Logger.Println("ERROR: ", err)
+ next.ServeHTTP(w, r)
+
+ return
+ } else if data == nil {
+ rw := &redisHTTPResponseWriter{}
+ next.ServeHTTP(rw, r)
+
+ rw.WriteData()
+ b, err := msgpack.Marshal(rw)
+ if err != nil {
+ Logger.Println("ERROR: marshaling: ", err)
+
+ return
+ }
+ _, err = client.Do("SET", r.URL.Path, b)
+ if err != nil {
+ Logger.Println("ERROR: during set: ", err)
+
+ return
+ }
+
+ // We got the content, let's go back around again and dump
+ // it out from redis
+ goto content
+ }
+
+ rw := &redisHTTPResponseWriter{}
+
+ err = msgpack.Unmarshal(data.([]byte), rw)
+ if err != nil {
+ Logger.Println("ERROR: unmarshaling: ", err)
+
+ return
+ }
+
+ if rw.Headers != nil {
+ for k, v := range rw.Headers {
+ w.Header()[k] = v
+ }
+ }
+
+ if rw.StatusCode != 0 {
+ w.WriteHeader(rw.StatusCode)
+ }
+ _, _ = w.Write(rw.Data)
+ })
+}