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