README
++ {{.Readme}} +
diff --git a/go.mod b/go.mod index c1104b3..0b80eac 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module forge.arimelody.space/ari/indir go 1.24.3 + +require github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..52d2bfd --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab h1:VYNivV7P8IRHUam2swVUNkhIdp0LRRFKe4hXNnoZKTc= +github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= diff --git a/main.go b/main.go index a00ecd0..778ec1b 100755 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + _ "embed" "fmt" "html/template" "io/fs" @@ -8,11 +9,15 @@ import ( "net/http" "os" "path" + "path/filepath" "slices" "strconv" "strings" "time" - _ "embed" + + "github.com/gomarkdown/markdown" + "github.com/gomarkdown/markdown/html" + "github.com/gomarkdown/markdown/parser" ) //go:embed templates/dir.html @@ -20,25 +25,25 @@ var dirTemplateSrc string type ( Directory struct { - Name string - Root bool - Files []*File + Name string + Root bool + Files []*File + Readme template.HTML } File struct { - Name string - URI string - IsDir bool - Size string + Name string + URI string + IsDir bool + Size string ModifiedDate string } ) -//go:embed templates/dir.html -var dirHTML string - func main() { - if len(os.Args) < 2 { printHelp() } + if len(os.Args) < 2 { + printHelp() + } host := "127.0.0.1" port := 8080 @@ -47,7 +52,9 @@ func main() { filesDir := "" i := 1 for { - if i >= len(os.Args) { break } + if i >= len(os.Args) { + break + } switch os.Args[i] { case "-h": fallthrough @@ -55,7 +62,7 @@ func main() { printHelp() case "--host": - if i + 1 >= len(os.Args) { + if i+1 >= len(os.Args) { fmt.Fprintf(os.Stderr, "fatal: --host argument cannot be empty\n") os.Exit(1) } @@ -63,7 +70,7 @@ func main() { host = os.Args[i] case "--port": - if i + 1 >= len(os.Args) { + if i+1 >= len(os.Args) { fmt.Fprintf(os.Stderr, "fatal: --port argument cannot be empty\n") os.Exit(1) } @@ -76,14 +83,18 @@ func main() { } case "--root": - if i + 1 >= len(os.Args) { + if i+1 >= len(os.Args) { fmt.Fprintf(os.Stderr, "fatal: --root argument cannot be empty\n") os.Exit(1) } i++ root = os.Args[i] - if !strings.HasPrefix(root, "/") { root = "/" + root } - if !strings.HasSuffix(root, "/") { root += "/" } + if !strings.HasPrefix(root, "/") { + root = "/" + root + } + if !strings.HasSuffix(root, "/") { + root += "/" + } default: if len(filesDir) > 0 { @@ -111,7 +122,9 @@ func main() { } fmt.Printf("Now hosting \"%s\" at http://%s:%d", filesDir, host, port) - if root != "/" { fmt.Printf("%s", root) } + if root != "/" { + fmt.Printf("%s", root) + } fmt.Println(".") http.ListenAndServe(fmt.Sprintf("%s:%d", host, port), HTTPLog(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -121,8 +134,8 @@ func main() { } isRoot := r.URL.Path == root - filepath := path.Join(filesDir, strings.TrimPrefix(r.URL.Path, root)) - info, err := os.Stat(filepath) + fpath := path.Join(filesDir, strings.TrimPrefix(r.URL.Path, root)) + info, err := os.Stat(fpath) if err != nil { http.NotFound(w, r) return @@ -135,7 +148,7 @@ func main() { return } - file, err := os.Open(filepath) + file, err := os.Open(fpath) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return @@ -144,7 +157,7 @@ func main() { defer func() { err := file.Close() if err != nil { - fmt.Fprintf(os.Stderr, "failed to close file %s: %v\n", filepath, err) + fmt.Fprintf(os.Stderr, "failed to close file %s: %v\n", fpath, err) } }() @@ -159,30 +172,54 @@ func main() { _, err = file.WriteTo(w) if err != nil { - fmt.Fprintf(os.Stderr, "failed to send file %s: %v\n", filepath, err) + fmt.Fprintf(os.Stderr, "failed to send file %s: %v\n", fpath, err) } return } if !strings.HasSuffix(r.URL.Path, "/") { - http.Redirect(w, r, r.URL.Path + "/", http.StatusFound) + http.Redirect(w, r, r.URL.Path+"/", http.StatusFound) return } - data := Directory{ - Root: isRoot, - Name: r.URL.Path, - Files: []*File{}, + // embeded readme + var readmeHTML template.HTML + entries, err := os.ReadDir(fpath) + if err == nil { + for _, entry := range entries { + if strings.EqualFold(entry.Name(), "readme.md") { + src, err := os.ReadFile(filepath.Join(fpath, entry.Name())) + if err == nil { + mdFlags := html.CommonFlags | html.HrefTargetBlank + mdRenderer := html.NewRenderer(html.RendererOptions{Flags: mdFlags}) + mdParser := parser.NewWithExtensions(parser.CommonExtensions | parser.AutoHeadingIDs) + md := mdParser.Parse(src) + readmeHTML = template.HTML(markdown.Render(md, mdRenderer)) + } + break + } + } } - fsDir := os.DirFS(filepath) + data := Directory{ + Root: isRoot, + Name: r.URL.Path, + Files: []*File{}, + Readme: readmeHTML, + } + + fsDir := os.DirFS(fpath) directories, err := fs.ReadDir(fsDir, ".") for _, dir := range directories { name := dir.Name() - if slices.Contains(ignoredFiles, name) { continue } + if slices.Contains(ignoredFiles, name) { + continue + } info, err := dir.Info() - if err != nil { continue } + if err != nil { + continue + } var uri string if isRoot { @@ -213,10 +250,10 @@ func main() { dateStr := info.ModTime().Format("02-Jan-2006 15:04") data.Files = append(data.Files, &File{ - Name: name, - URI: uri, - IsDir: info.IsDir(), - Size: sizeStr, + Name: name, + URI: uri, + IsDir: info.IsDir(), + Size: sizeStr, ModifiedDate: dateStr, }) } @@ -232,52 +269,60 @@ func main() { } type LoggingResponseWriter struct { - http.ResponseWriter - Status int + http.ResponseWriter + Status int } -var COL_Reset = "\033[0m" -var COL_Red = "\033[31m" -var COL_Green = "\033[32m" +var COL_Reset = "\033[0m" +var COL_Red = "\033[31m" +var COL_Green = "\033[32m" var COL_Yellow = "\033[33m" -var COL_Blue = "\033[34m" +var COL_Blue = "\033[34m" var COL_Purple = "\033[35m" -var COL_Cyan = "\033[36m" -var COL_Gray = "\033[37m" -var COL_White = "\033[97m" +var COL_Cyan = "\033[36m" +var COL_Gray = "\033[37m" +var COL_White = "\033[97m" func HTTPLog(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - start := time.Now() + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() - lrw := LoggingResponseWriter{w, http.StatusOK} + lrw := LoggingResponseWriter{w, http.StatusOK} - next.ServeHTTP(&lrw, r) + next.ServeHTTP(&lrw, r) - after := time.Now() - difference := (after.Nanosecond() - start.Nanosecond()) / 1_000_000 - elapsed := "<1" - if difference >= 1 { - elapsed = strconv.Itoa(difference) - } + after := time.Now() + difference := (after.Nanosecond() - start.Nanosecond()) / 1_000_000 + elapsed := "<1" + if difference >= 1 { + elapsed = strconv.Itoa(difference) + } - statusColour := COL_Reset + statusColour := COL_Reset - if lrw.Status - 600 <= 0 { statusColour = COL_Red } - if lrw.Status - 500 <= 0 { statusColour = COL_Yellow } - if lrw.Status - 400 <= 0 { statusColour = COL_White } - if lrw.Status - 300 <= 0 { statusColour = COL_Green } + if lrw.Status-600 <= 0 { + statusColour = COL_Red + } + if lrw.Status-500 <= 0 { + statusColour = COL_Yellow + } + if lrw.Status-400 <= 0 { + statusColour = COL_White + } + if lrw.Status-300 <= 0 { + statusColour = COL_Green + } - fmt.Printf("[%s] %s %s - %s%d%s (%sms) (%s)\n", - after.Format(time.UnixDate), - r.Method, - r.URL.Path, - statusColour, - lrw.Status, - COL_Reset, - elapsed, - r.Header["User-Agent"][0]) - }) + fmt.Printf("[%s] %s %s - %s%d%s (%sms) (%s)\n", + after.Format(time.UnixDate), + r.Method, + r.URL.Path, + statusColour, + lrw.Status, + COL_Reset, + elapsed, + r.Header["User-Agent"][0]) + }) } func printHelp() { diff --git a/templates/dir.html b/templates/dir.html index 562cc95..6d20006 100644 --- a/templates/dir.html +++ b/templates/dir.html @@ -1,82 +1,157 @@ - +
- - + +| Name | @@ -89,8 +164,7 @@ footer {— | — |
|---|---|---|
| {{.Name}} | {{if .IsDir}}—{{else}}{{.Size}}{{end}} | @@ -98,10 +172,24 @@ footer {