Initial
commit
019992c4c2
|
|
@ -0,0 +1,50 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"local/args"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Root string
|
||||||
|
Port string
|
||||||
|
Head string
|
||||||
|
Foot string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Refresh() {
|
||||||
|
if strings.Contains(fmt.Sprint(os.Args), "-test") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
as := args.NewArgSet()
|
||||||
|
as.Append(args.STRING, "root", "root dir path", "./public")
|
||||||
|
as.Append(args.STRING, "port", "port to listen on", "39909")
|
||||||
|
as.Append(args.STRING, "wrap", "file with http header/footer", "./wrapper.html")
|
||||||
|
if err := as.Parse(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wrap := as.Get("wrap").String()
|
||||||
|
b, err := ioutil.ReadFile(wrap)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
bs := bytes.Split(b, []byte("{{{}}}"))
|
||||||
|
if len(bs) != 2 {
|
||||||
|
panic(len(bs))
|
||||||
|
}
|
||||||
|
|
||||||
|
Root = as.Get("root").String()
|
||||||
|
Port = ":" + strings.TrimPrefix(as.Get("port").String(), ":")
|
||||||
|
Head = string(bs[0])
|
||||||
|
Foot = string(bs[1])
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gomarkdown/markdown"
|
||||||
|
"github.com/miekg/mmark"
|
||||||
|
"gopkg.in/russross/blackfriday.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
GOMARKDOWN = "gomarkdown"
|
||||||
|
RUSSROSS = "russross"
|
||||||
|
MIEKG = "miekg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func envOrDefault(key, def string) string {
|
||||||
|
if v := os.Getenv(key); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
port := flag.String("p", envOrDefault("PORT", ":41913"), "port to run on")
|
||||||
|
root := flag.String("root", envOrDefault("ROOT", "."), "root dir to serve")
|
||||||
|
render := flag.String("render", envOrDefault("RENDER", GOMARKDOWN), "renderer to use")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if !strings.HasPrefix(*port, ":") {
|
||||||
|
*port = ":" + *port
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
log.Printf("Serving %q on %q", *root, *port)
|
||||||
|
if err := http.ListenAndServe(*port, handler(*render, *root)); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// catch stop
|
||||||
|
stop := make(chan os.Signal)
|
||||||
|
signal.Notify(stop, os.Interrupt)
|
||||||
|
<-stop
|
||||||
|
}
|
||||||
|
|
||||||
|
func handler(code, root string) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
path := path.Join(root, path.Clean(r.URL.Path))
|
||||||
|
content, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintln(w, `<html>`)
|
||||||
|
fmt.Fprintln(w, `<head>`)
|
||||||
|
fmt.Fprintln(w, `
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kognise/water.css@latest/dist/dark.min.css">
|
||||||
|
`)
|
||||||
|
fmt.Fprintln(w, `</head>`)
|
||||||
|
fmt.Fprintln(w, `<body>`)
|
||||||
|
w.Write(render(code, content))
|
||||||
|
fmt.Fprintln(w, `</body>`)
|
||||||
|
fmt.Fprintln(w, `</html>`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func render(code string, content []byte) []byte {
|
||||||
|
switch code {
|
||||||
|
case GOMARKDOWN:
|
||||||
|
return markdown.ToHTML(content, nil, nil)
|
||||||
|
case RUSSROSS:
|
||||||
|
return blackfriday.Run(content) //, blackfriday.WithExtensions(blackfriday.Extensions(blackfriday.TOC)))
|
||||||
|
case MIEKG:
|
||||||
|
return mmark.Parse(content, mmark.HtmlRenderer(0, ``, ``), mmark.EXTENSION_TABLES|int(blackfriday.TOC)).Bytes()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isDir(path string) bool {
|
||||||
|
stat, err := os.Stat(path)
|
||||||
|
return err == nil && stat.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
func notesDir(path string, w http.ResponseWriter, r *http.Request) {
|
||||||
|
dirs, files := lsDir(path)
|
||||||
|
log.Println(dirs)
|
||||||
|
log.Println(files)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lsDir(p string) ([]Path, []Path) {
|
||||||
|
dirs := newDirs()
|
||||||
|
files := newFiles()
|
||||||
|
|
||||||
|
found, _ := ioutil.ReadDir(p)
|
||||||
|
for _, f := range found {
|
||||||
|
dirs.Push(p, f)
|
||||||
|
files.Push(p, f)
|
||||||
|
}
|
||||||
|
return *dirs, *files
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsDir(t *testing.T) {
|
||||||
|
if ok := isDir("/tmp"); !ok {
|
||||||
|
t.Fatal(ok)
|
||||||
|
}
|
||||||
|
if ok := isDir("/dev/null"); ok {
|
||||||
|
t.Fatal(ok)
|
||||||
|
}
|
||||||
|
if ok := isDir("/proc/not/real/tho"); ok {
|
||||||
|
t.Fatal(ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLsDir(t *testing.T) {
|
||||||
|
dirs, files := lsDir("/usr/local")
|
||||||
|
if len(dirs) == 0 {
|
||||||
|
t.Fatal(len(dirs))
|
||||||
|
}
|
||||||
|
if len(files) == 0 {
|
||||||
|
t.Fatal(len(files))
|
||||||
|
}
|
||||||
|
t.Log(dirs)
|
||||||
|
t.Log(files)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Dirs []Path
|
||||||
|
|
||||||
|
func newDirs() *Dirs {
|
||||||
|
d := Dirs([]Path{})
|
||||||
|
return &d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dirs) Push(p string, f os.FileInfo) {
|
||||||
|
if f.IsDir() {
|
||||||
|
*d = append(*d, Path{
|
||||||
|
dir: p,
|
||||||
|
base: f.Name(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
package server
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isFile(path string) bool {
|
||||||
|
stat, err := os.Stat(path)
|
||||||
|
return err == nil && !stat.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
func notesFile(path string, w http.ResponseWriter, r *http.Request) {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsFile(t *testing.T) {
|
||||||
|
if ok := isFile("/tmp"); ok {
|
||||||
|
t.Fatal(ok)
|
||||||
|
}
|
||||||
|
if ok := isFile("/dev/null"); !ok {
|
||||||
|
t.Fatal(ok)
|
||||||
|
}
|
||||||
|
if ok := isFile("/proc/not/real/tho"); ok {
|
||||||
|
t.Fatal(ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Files []Path
|
||||||
|
|
||||||
|
func newFiles() *Files {
|
||||||
|
d := Files([]Path{})
|
||||||
|
return &d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Files) Push(p string, f os.FileInfo) {
|
||||||
|
if !f.IsDir() {
|
||||||
|
*d = append(*d, Path{
|
||||||
|
dir: p,
|
||||||
|
base: f.Name(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
package server
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"local/notes-server/config"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) notes(w http.ResponseWriter, r *http.Request) {
|
||||||
|
path := resolvePath(r.URL.Path)
|
||||||
|
if isDir(path) {
|
||||||
|
head(w, r)
|
||||||
|
notesDir(path, w, r)
|
||||||
|
foot(w, r)
|
||||||
|
} else if isFile(path) {
|
||||||
|
head(w, r)
|
||||||
|
notesFile(path, w, r)
|
||||||
|
foot(w, r)
|
||||||
|
} else {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolvePath(p string) string {
|
||||||
|
p = strings.TrimPrefix(p, "/notes/")
|
||||||
|
p = path.Join(config.Root, p)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"local/notes-server/config"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResolvePath(t *testing.T) {
|
||||||
|
config.Root = "ROOT"
|
||||||
|
if p := resolvePath("/notes/a/b/c"); p != "ROOT/a/b/c" {
|
||||||
|
t.Fatal(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import "path"
|
||||||
|
|
||||||
|
type Path struct {
|
||||||
|
dir string
|
||||||
|
base string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Path) String() string {
|
||||||
|
return path.Join(p.dir, p.base)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
package server
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"local/router"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) Routes() error {
|
||||||
|
wildcard := router.Wildcard
|
||||||
|
endpoints := []struct {
|
||||||
|
path string
|
||||||
|
handler http.HandlerFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
path: fmt.Sprintf("notes/%s%s", wildcard, wildcard),
|
||||||
|
handler: s.notes,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
if err := s.Add(endpoint.path, endpoint.handler); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServerRoutes(t *testing.T) {
|
||||||
|
s := New()
|
||||||
|
err := s.Routes()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerNotes(t *testing.T) {
|
||||||
|
s := New()
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := &http.Request{
|
||||||
|
URL: &url.URL{
|
||||||
|
Path: "/",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s.notes(w, r)
|
||||||
|
t.Logf("serve %s: %s", r.URL.Path, w.Body.Bytes())
|
||||||
|
|
||||||
|
w.Body.Reset()
|
||||||
|
r.URL.Path = "/serve/"
|
||||||
|
s.notes(w, r)
|
||||||
|
t.Logf("serve %s: %s", r.URL.Path, w.Body.Bytes())
|
||||||
|
|
||||||
|
w.Body.Reset()
|
||||||
|
r.URL.Path = "/serve/test"
|
||||||
|
s.notes(w, r)
|
||||||
|
t.Logf("serve %s: %s", r.URL.Path, w.Body.Bytes())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"local/notes-server/config"
|
||||||
|
"local/router"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
*router.Router
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *Server {
|
||||||
|
return &Server{
|
||||||
|
Router: router.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func head(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(config.Head + "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func foot(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(config.Foot + "\n"))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"local/notes-server/config"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHead(t *testing.T) {
|
||||||
|
config.Head = "A"
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
head(w, nil)
|
||||||
|
if s := strings.TrimSpace(string(w.Body.Bytes())); s != config.Head {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFoot(t *testing.T) {
|
||||||
|
config.Foot = "A"
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
foot(w, nil)
|
||||||
|
if s := strings.TrimSpace(string(w.Body.Bytes())); s != config.Foot {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue