Last active
December 31, 2017 02:35
-
-
Save michael-nischt/9749575 to your computer and use it in GitHub Desktop.
MD-WIKI server and daemon
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 += " > <a href=\"" + link + "\">" + strings.Title(path[i]) + "</a>" | |
} else { | |
nav += " > " + 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