diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..da32794 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +# Top-most EditorConfig file +root = true + +# Common settings for all files. I don't specify indent type here, because +# some EditorConfig implementations (notably Atom) will cause default settings +# and behaviors (like tab type auto-detection) to be overridden. +[*] +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true + +[*.go] +indent_style = tab +indent_size = 4 + +[*.md] +indent_style = space +indent_size = 2 + +[*.yml] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore index e69de29..a5d8f72 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,2 @@ +bin/ +dist/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..08a9940 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +SHELL := /bin/bash + +VERSION=$(shell grep -e 'Version' blog.go | head -n 1 | cut -d '"' -f 2) +BUILD=$(shell git describe --always) +CURDIR=$(shell curdir) + +# Inject the build version (commit hash) into the executable. +LDFLAGS := -ldflags "-X main.Build=$(BUILD)" + +# `make setup` to set up a new environment, pull dependencies, etc. +.PHONY: setup +setup: clean + go get -u ./... + +# `make build` to build the binary. +.PHONY: build +build: + gofmt -w . + go build $(LDFLAGS) -i -o bin/blog main.go + +# `make run` to run it in debug mode. +.PHONY: run +run: + ./go-reload main.go -debug + +# `make test` to run unit tests. +.PHONY: test +test: + go test ./... + +# `make clean` cleans everything up. +.PHONY: clean +clean: + rm -rf bin dist diff --git a/core/app.go b/core/app.go new file mode 100644 index 0000000..321710f --- /dev/null +++ b/core/app.go @@ -0,0 +1,47 @@ +package core + +import ( + "net/http" + + "github.com/gorilla/mux" + "github.com/urfave/negroni" +) + +// Blog is the root application object that maintains the app configuration +// and helper objects. +type Blog struct { + // DocumentRoot is the core static files root; UserRoot masks over it. + DocumentRoot string + UserRoot string + + // Web app objects. + n *negroni.Negroni // Negroni middleware manager + r *mux.Router // Router +} + +// New initializes the Blog application. +func New(documentRoot, userRoot string) *Blog { + blog := &Blog{ + DocumentRoot: documentRoot, + UserRoot: userRoot, + } + r := mux.NewRouter() + blog.r = r + r.HandleFunc("/", blog.PageHandler) + r.NotFoundHandler = http.HandlerFunc(blog.PageHandler) + + n := negroni.New( + negroni.NewRecovery(), + negroni.NewLogger(), + ) + blog.n = n + n.UseHandler(r) + + return blog +} + +// ListenAndServe begins listening on the given bind address. +func (b *Blog) ListenAndServe(address string) { + log.Info("Listening on %s", address) + http.ListenAndServe(address, b.n) +} diff --git a/core/log.go b/core/log.go new file mode 100644 index 0000000..8189ada --- /dev/null +++ b/core/log.go @@ -0,0 +1,13 @@ +package core + +import "github.com/kirsle/golog" + +var log *golog.Logger + +func init() { + log = golog.GetLogger("blog") + log.Configure(&golog.Config{ + Colors: golog.ExtendedColor, + Theme: golog.DarkTheme, + }) +} diff --git a/core/pages.go b/core/pages.go new file mode 100644 index 0000000..ffd1b27 --- /dev/null +++ b/core/pages.go @@ -0,0 +1,56 @@ +package core + +import ( + "net/http" + "os" + "path/filepath" + "strings" +) + +// PageHandler is the catch-all route handler, for serving static web pages. +func (b *Blog) PageHandler(w http.ResponseWriter, r *http.Request) { + path := r.URL.Path + + // Remove trailing slashes by redirecting them away. + if len(path) > 1 && path[len(path)-1] == '/' { + Redirect(w, strings.TrimRight(path, "/")) + return + } + + // Search for a file that matches their URL. + log.Debug("Resolving filepath for URI: %s", path) + for _, root := range []string{b.DocumentRoot, b.UserRoot} { + relPath := filepath.Join(root, path) + absPath, err := filepath.Abs(relPath) + if err != nil { + log.Error("%v", err) + } + + log.Debug("Expected filepath: %s", absPath) + + // Found an exact hit? + if stat, err := os.Stat(absPath); !os.IsNotExist(err) && !stat.IsDir() { + log.Debug("Exact filepath found: %s", absPath) + http.ServeFile(w, r, absPath) + return + } + + // Try some supported suffixes. + suffixes := []string{ + ".html", + "/index.html", + ".md", + "/index.md", + } + for _, suffix := range suffixes { + if stat, err := os.Stat(absPath + suffix); !os.IsNotExist(err) && !stat.IsDir() { + log.Debug("Filepath found via suffix %s: %s", suffix, absPath+suffix) + http.ServeFile(w, r, absPath+suffix) + return + } + } + } + + // No file, must be a 404. + http.NotFound(w, r) +} diff --git a/core/responses.go b/core/responses.go new file mode 100644 index 0000000..cec892c --- /dev/null +++ b/core/responses.go @@ -0,0 +1,9 @@ +package core + +import "net/http" + +// Redirect sends an HTTP redirect response. +func Redirect(w http.ResponseWriter, location string) { + w.Header().Set("Location", location) + w.WriteHeader(http.StatusFound) +} diff --git a/go-reload b/go-reload new file mode 100755 index 0000000..b158682 --- /dev/null +++ b/go-reload @@ -0,0 +1,95 @@ +#!/bin/bash +# Credit from: https://github.com/alexedwards/go-reload/tree/aabe19d0a9935d1238763a4a35e71787854cd5f5 + +#################################################### +# @kirsle's custom changes from upstream are below # +#################################################### + +function die() { + echo >&2 $1 + exit 1 +} + +# Before we crash and burn, make sure necessary programs are installed! +command -v inotifywait || die "I need the inotifywait command (apt install inotify-tools)" + +################################ +# end @kirsle's custom changes # +################################ + +function monitor() { + if [ "$2" = "true" ]; then + # Watch all files in the specified directory + # Call the restart function when they are saved + inotifywait -q -m -r -e close_write -e moved_to $1 | + while read line; do + restart + done + else + # Watch all *.go files in the specified directory + # Call the restart function when they are saved + inotifywait -q -m -r -e close_write -e moved_to --exclude '[^g][^o]$' $1 | + while read line; do + restart + done + fi +} + +# Terminate and rerun the main Go program +function restart { + if [ "$(pidof $PROCESS_NAME)" ]; then + killall -q -w -9 $PROCESS_NAME + fi + echo ">> Reloading..." + eval "go run $ARGS &" +} + +# Make sure all background processes get terminated +function close { + killall -q -w -9 inotifywait + exit 0 +} + +trap close INT +echo "== Go-reload" + +WATCH_ALL=false +while getopts ":a" opt; do + case $opt in + a) + WATCH_ALL=true + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + exit 0 + ;; + esac +done + +shift "$((OPTIND - 1))" + +FILE_NAME=$(basename $1) +PROCESS_NAME=${FILE_NAME%%.*} + +ARGS=$@ + +# Start the main Go program +echo ">> Watching directories, CTRL+C to stop" +eval "go run $ARGS &" + +# Monitor all /src directories on the GOPATH +OIFS="$IFS" +IFS=':' +for path in $GOPATH +do + monitor $path/src $WATCH_ALL & +done +IFS="$OIFS" + +# If the current working directory isn't on the GOPATH, monitor it too +if [[ $PWD != "$GOPATH/"* ]] +then + monitor $PWD $WATCH_ALL +fi + +wait diff --git a/main.go b/main.go new file mode 100644 index 0000000..ba33a78 --- /dev/null +++ b/main.go @@ -0,0 +1,38 @@ +// Package blog is a web application which lets you host your own web blog, +// photo albums, wiki, etc. +// +// It is currently under early development and is not yet stable. +package main + +import ( + "flag" + + "github.com/kirsle/blog/core" +) + +// Build-time config constants. +var ( + Version = "0.0.1" + Build = "live" + DocumentRoot = "root" +) + +// Command line args. +var ( + fDebug bool + fAddress string +) + +func init() { + flag.BoolVar(&fDebug, "debug", false, "Debug mode") + flag.BoolVar(&fDebug, "d", false, "Debug mode (alias)") + flag.StringVar(&fAddress, "address", ":8000", "Bind address") + flag.StringVar(&fAddress, "a", ":8000", "Bind address (alias)") +} + +func main() { + flag.Parse() + + app := core.New(DocumentRoot, "") + app.ListenAndServe(fAddress) +} diff --git a/root/index.html b/root/index.html new file mode 100644 index 0000000..85fc7b1 --- /dev/null +++ b/root/index.html @@ -0,0 +1,11 @@ + + + + Blog + + + +

Index

+ + + diff --git a/root/test.html b/root/test.html new file mode 100644 index 0000000..4c4eb47 --- /dev/null +++ b/root/test.html @@ -0,0 +1,11 @@ + + + + Blog + + + +

Test

+ + +