Skip to content

Instantly share code, notes, and snippets.

@michael-nischt
Last active December 31, 2017 02:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save michael-nischt/9749575 to your computer and use it in GitHub Desktop.
Save michael-nischt/9749575 to your computer and use it in GitHub Desktop.
MD-WIKI server and daemon
#!/bin/sh
### BEGIN INIT INFO
# Provides: md-wikid
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop:
# Short-Description: Markdown Wiki Service
### END INIT INFO
FILES="/home/wiki/Dropbox/py"
CSS="/_/md.css"
DAEMON=/usr/local/bin/md-wiki
DAEMON_OPTS="-path=$FILES -css=$CSS"
WIKI_USER=wiki
start() {
echo "Starting md-wiki..."
start-stop-daemon -b -o -c $WIKI_USER -S -u $WIKI_USER -x $DAEMON -- $DAEMON_OPTS
}
stop() {
echo "Stopping md-wiki..."
start-stop-daemon -o -c $WIKI_USER -K -u $WIKI_USER -x $DAEMON
}
status() {
dbpid=`pgrep -u $WIKI_USER md-wiki`
if [ -z $dbpid ] ; then
echo "md-wikid: not running."
else
echo "md-wikid: running (pid $dbpid)"
fi
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart|reload|force-reload)
stop
start
;;
status)
status
;;
*)
echo "Usage: /etc/init.d/md-wiki {start|stop|reload|force-reload|restart|status}"
exit 1
esac
exit 0
package main
import (
"flag"
"fmt"
"html/template"
"io/ioutil"
"log"
"net/http"
"os"
"os/user"
"path/filepath"
"strconv"
"strings"
auth "github.com/abbot/go-http-auth"
"github.com/microcosm-cc/bluemonday"
blackfriday "gopkg.in/russross/blackfriday.v2"
)
const index = "index"
var serverPath = ""
var cssPath = ""
var mdTmpl *template.Template
var fileServer http.Handler
func navigation(url string) string {
path := strings.Split(url, "/")
nav := ""
link := "/"
for i := range path {
path[i] = strings.ToLower(path[i])
if path[i] == index {
continue
}
if i == 0 {
nav += "<a href=\"" + link + "\">" + strings.Title("home") + "</a>"
} else {
link += path[i]
exists := false
if _, err := os.Stat(serverPath + link + "/index.md"); err == nil {
exists = exists || true
}
if _, err := os.Stat(serverPath + link + ".md"); err == nil {
exists = exists || true
}
if i+1 < len(path) {
link += "/"
}
if exists {
nav += " &gt; <a href=\"" + link + "\">" + strings.Title(path[i]) + "</a>"
} else {
nav += " &gt; " + strings.Title(path[i])
}
}
}
return nav
}
func splitContent(s string) (header []string, body string) {
const delim = "---"
lines := strings.Split(s, "\n")
if len(lines) > 0 && strings.TrimSpace(lines[0]) == delim {
for i, n := 1, len(lines); i < n; i++ {
if l := lines[i]; strings.TrimSpace(l) == delim {
header = lines[1:i]
body = strings.Join(lines[i+1:], "\n")
return
}
}
}
return nil, s
}
type page struct {
Title string
Style string
Nav template.HTML
Content template.HTML
}
func (h *page) Parse(url, s string) error {
s = strings.Replace(s, "\r\n", "\n", -1) // windows to unix line endings
header, body := splitContent(s)
b := []byte(body)
b = blackfriday.Run(b) // md -> html
b = bluemonday.UGCPolicy().SanitizeBytes(b) // sanitize html
nav := navigation(url)
h.Nav = template.HTML(nav)
h.Content = template.HTML(b)
return h.parseHeader(header)
}
func (h *page) parseHeader(lines []string) error {
noNav := lines == nil
for i := range lines {
l := strings.TrimSpace(lines[i])
n := len(l)
if n == 0 {
continue // allow empty lines
}
split := strings.Index(l, ":")
if split <= 0 || split >= n {
return fmt.Errorf("Illegal header property: '%v'", l)
}
name, value := l[:split], l[split+1:]
value = strings.TrimSpace(value)
switch strings.ToLower(name) {
case "title":
h.Title = value
case "nonavigation":
fallthrough
case "nonav":
v, err := strconv.ParseBool(value)
if err != nil {
return err
}
noNav = v
case "style":
h.Style = value
}
}
if noNav {
h.Nav = ""
}
return nil
}
func markDownHandler(w http.ResponseWriter, r *http.Request) {
params := page{Style: cssPath}
urlPath := r.URL.Path
if n := len(urlPath); n > 0 && urlPath[n-1] == '/' {
urlPath += index
}
filePath := serverPath + urlPath + ".md"
file, err := ioutil.ReadFile(filePath)
if err != nil {
fileServer.ServeHTTP(w, r)
return
}
params.Parse(urlPath, string(file))
if err := mdTmpl.Execute(w, params); err != nil {
log.Printf("ERROR executig http template %v", err)
}
}
func main() {
address := ""
port := 8000
serverPath, _ = os.Getwd()
flag.StringVar(&serverPath, "path", serverPath, "Server root folder")
flag.IntVar(&port, "port", port, "Server port")
flag.StringVar(&address, "address", address, "Server address")
flag.StringVar(&cssPath, "css", cssPath, "CSS Style sheet address")
flag.Parse()
// expand home directory path
if usr, err := user.Current(); err == nil && serverPath[:2] == "~/" {
serverPath = filepath.Join(usr.HomeDir, serverPath[2:])
}
fileServer = http.FileServer(http.Dir(serverPath))
var tmplSrc = `
<!DOCTYPE html>
<html>
<head>
<title>{{.Title}}</title>
<link rel="stylesheet" type="text/css" href="{{.Style}}">
</head>
<body>
{{if .Title}}
<h1 id="title">{{.Title}}</h1>
{{end}}
{{if .Nav }}
<div id="navigation">
{{.Nav}}
</div>
{{end}}
{{if .Content }}
<div id="content">
{{.Content}}
{{end}}
</div>
</body>
</html>
`
if tmpl, err := template.New("markdown-site").Parse(tmplSrc); err != nil {
log.Fatal("templateParse: ", err)
} else {
mdTmpl = tmpl
}
pwdFile := serverPath + "/.htpasswd"
if _, err := os.Stat(pwdFile); err == nil {
authenticator := auth.NewBasicAuthenticator("wiki.monoid.com", auth.HtpasswdFileProvider(pwdFile))
http.HandleFunc("/", authenticator.Wrap(func(w http.ResponseWriter, r *auth.AuthenticatedRequest) {
markDownHandler(w, &r.Request)
}))
} else {
http.HandleFunc("/", markDownHandler)
}
if err := http.ListenAndServe(address+":"+strconv.Itoa(port), nil); err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment