fixed prior merge conflic causing double'd embed, added readme parser with full markdown, added readme render to HTML, renamed filepath to fpath to use FilePath.Join for case insensitivity, fixed ident in html src (inline css), added readme css with proper accents
This commit is contained in:
parent
cf03c723d0
commit
245d6e0fa0
4 changed files with 270 additions and 133 deletions
2
go.mod
2
go.mod
|
|
@ -1,3 +1,5 @@
|
||||||
module forge.arimelody.space/ari/indir
|
module forge.arimelody.space/ari/indir
|
||||||
|
|
||||||
go 1.24.3
|
go 1.24.3
|
||||||
|
|
||||||
|
require github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab
|
||||||
|
|
|
||||||
2
go.sum
Normal file
2
go.sum
Normal file
|
|
@ -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=
|
||||||
87
main.go
87
main.go
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
|
@ -8,11 +9,15 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
_ "embed"
|
|
||||||
|
"github.com/gomarkdown/markdown"
|
||||||
|
"github.com/gomarkdown/markdown/html"
|
||||||
|
"github.com/gomarkdown/markdown/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed templates/dir.html
|
//go:embed templates/dir.html
|
||||||
|
|
@ -23,6 +28,7 @@ type (
|
||||||
Name string
|
Name string
|
||||||
Root bool
|
Root bool
|
||||||
Files []*File
|
Files []*File
|
||||||
|
Readme template.HTML
|
||||||
}
|
}
|
||||||
|
|
||||||
File struct {
|
File struct {
|
||||||
|
|
@ -34,11 +40,10 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed templates/dir.html
|
|
||||||
var dirHTML string
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) < 2 { printHelp() }
|
if len(os.Args) < 2 {
|
||||||
|
printHelp()
|
||||||
|
}
|
||||||
|
|
||||||
host := "127.0.0.1"
|
host := "127.0.0.1"
|
||||||
port := 8080
|
port := 8080
|
||||||
|
|
@ -47,7 +52,9 @@ func main() {
|
||||||
filesDir := ""
|
filesDir := ""
|
||||||
i := 1
|
i := 1
|
||||||
for {
|
for {
|
||||||
if i >= len(os.Args) { break }
|
if i >= len(os.Args) {
|
||||||
|
break
|
||||||
|
}
|
||||||
switch os.Args[i] {
|
switch os.Args[i] {
|
||||||
case "-h":
|
case "-h":
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|
@ -82,8 +89,12 @@ func main() {
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
root = os.Args[i]
|
root = os.Args[i]
|
||||||
if !strings.HasPrefix(root, "/") { root = "/" + root }
|
if !strings.HasPrefix(root, "/") {
|
||||||
if !strings.HasSuffix(root, "/") { root += "/" }
|
root = "/" + root
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(root, "/") {
|
||||||
|
root += "/"
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if len(filesDir) > 0 {
|
if len(filesDir) > 0 {
|
||||||
|
|
@ -111,7 +122,9 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Now hosting \"%s\" at http://%s:%d", filesDir, host, port)
|
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(".")
|
fmt.Println(".")
|
||||||
|
|
||||||
http.ListenAndServe(fmt.Sprintf("%s:%d", host, port), HTTPLog(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
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
|
isRoot := r.URL.Path == root
|
||||||
|
|
||||||
filepath := path.Join(filesDir, strings.TrimPrefix(r.URL.Path, root))
|
fpath := path.Join(filesDir, strings.TrimPrefix(r.URL.Path, root))
|
||||||
info, err := os.Stat(filepath)
|
info, err := os.Stat(fpath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
|
|
@ -135,7 +148,7 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Open(filepath)
|
file, err := os.Open(fpath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|
@ -144,7 +157,7 @@ func main() {
|
||||||
defer func() {
|
defer func() {
|
||||||
err := file.Close()
|
err := file.Close()
|
||||||
if err != nil {
|
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,7 +172,7 @@ func main() {
|
||||||
|
|
||||||
_, err = file.WriteTo(w)
|
_, err = file.WriteTo(w)
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -169,20 +182,44 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data := Directory{
|
data := Directory{
|
||||||
Root: isRoot,
|
Root: isRoot,
|
||||||
Name: r.URL.Path,
|
Name: r.URL.Path,
|
||||||
Files: []*File{},
|
Files: []*File{},
|
||||||
|
Readme: readmeHTML,
|
||||||
}
|
}
|
||||||
|
|
||||||
fsDir := os.DirFS(filepath)
|
fsDir := os.DirFS(fpath)
|
||||||
directories, err := fs.ReadDir(fsDir, ".")
|
directories, err := fs.ReadDir(fsDir, ".")
|
||||||
for _, dir := range directories {
|
for _, dir := range directories {
|
||||||
name := dir.Name()
|
name := dir.Name()
|
||||||
if slices.Contains(ignoredFiles, name) { continue }
|
if slices.Contains(ignoredFiles, name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
info, err := dir.Info()
|
info, err := dir.Info()
|
||||||
if err != nil { continue }
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
var uri string
|
var uri string
|
||||||
if isRoot {
|
if isRoot {
|
||||||
|
|
@ -263,10 +300,18 @@ func HTTPLog(next http.Handler) http.Handler {
|
||||||
|
|
||||||
statusColour := COL_Reset
|
statusColour := COL_Reset
|
||||||
|
|
||||||
if lrw.Status - 600 <= 0 { statusColour = COL_Red }
|
if lrw.Status-600 <= 0 {
|
||||||
if lrw.Status - 500 <= 0 { statusColour = COL_Yellow }
|
statusColour = COL_Red
|
||||||
if lrw.Status - 400 <= 0 { statusColour = COL_White }
|
}
|
||||||
if lrw.Status - 300 <= 0 { statusColour = COL_Green }
|
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",
|
fmt.Printf("[%s] %s %s - %s%d%s (%sms) (%s)\n",
|
||||||
after.Format(time.UnixDate),
|
after.Format(time.UnixDate),
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Files in {{.Name}}</title>
|
<title>Files in {{.Name}}</title>
|
||||||
<style>
|
<style>
|
||||||
html {
|
html {
|
||||||
background: #101010;
|
background: #101010;
|
||||||
color: #f0f0f0;
|
color: #f0f0f0;
|
||||||
font-family: 'Monaspace Argon', monospace;
|
font-family: "Monaspace Argon", monospace;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ th {
|
||||||
td {
|
td {
|
||||||
width: 1%;
|
width: 1%;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
padding: .2em 0;
|
padding: 0.2em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
table a {
|
table a {
|
||||||
|
|
@ -53,6 +53,81 @@ a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.readme {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.readme h1,
|
||||||
|
.readme h2,
|
||||||
|
.readme h3,
|
||||||
|
.readme h4,
|
||||||
|
.readme h5,
|
||||||
|
.readme h6 {
|
||||||
|
color: #f0f0f0;
|
||||||
|
margin-top: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.readme p,
|
||||||
|
.readme li {
|
||||||
|
line-height: 1.7;
|
||||||
|
color: #d0d0d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.readme a {
|
||||||
|
color: #b7fd49;
|
||||||
|
}
|
||||||
|
|
||||||
|
.readme a:hover {
|
||||||
|
color: white;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.readme code {
|
||||||
|
background: #1e1e1e;
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 0.1em, 0.4em;
|
||||||
|
font-family: "Monaspace Argon", monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #b7fd79;
|
||||||
|
}
|
||||||
|
|
||||||
|
.readme pre {
|
||||||
|
background: #1e1e1e;
|
||||||
|
border: 3px solid #333;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 1em;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.readme pre code {
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.readme blockquote {
|
||||||
|
border-left: 3px solid #b7fd49;
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 1em;
|
||||||
|
color: #a0a0a0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.readme hr {
|
||||||
|
border-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.readme table {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.readme table td,
|
||||||
|
.readme table th {
|
||||||
|
border: 1px solid #333;
|
||||||
|
padding: 0.3em 0.6em;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
padding: 1em 0;
|
padding: 1em 0;
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +151,7 @@ footer {
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
<h1>Files in {{.Name}}</h1>
|
<h1>Files in {{.Name}}</h1>
|
||||||
<hr>
|
<hr />
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
|
|
@ -89,8 +164,7 @@ footer {
|
||||||
<td>—</td>
|
<td>—</td>
|
||||||
<td>—</td>
|
<td>—</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
{{end}} {{range .Files}}
|
||||||
{{range .Files}}
|
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{.URI}}">{{.Name}}</a></td>
|
<td><a href="{{.URI}}">{{.Name}}</a></td>
|
||||||
<td>{{if .IsDir}}—{{else}}{{.Size}}{{end}}</td>
|
<td>{{if .IsDir}}—{{else}}{{.Size}}{{end}}</td>
|
||||||
|
|
@ -98,10 +172,24 @@ footer {
|
||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
</table>
|
</table>
|
||||||
<hr>
|
<hr />
|
||||||
|
{{if .Readme}}
|
||||||
|
<article class="readme">
|
||||||
|
<h2>README</h2>
|
||||||
|
<hr />
|
||||||
|
{{.Readme}}
|
||||||
|
</article>
|
||||||
|
{{end}}
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
<footer>
|
||||||
<em>made with <span aria-label="love">♥</span> by ari, 2025 <a href="https://forge.arimelody.space/ari/indir" target="_blank">[source]</a></em>
|
<em
|
||||||
|
>made with <span aria-label="love">♥</span> by ari, 2025
|
||||||
|
<a
|
||||||
|
href="https://forge.arimelody.space/ari/indir"
|
||||||
|
target="_blank"
|
||||||
|
>[source]</a
|
||||||
|
></em
|
||||||
|
>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue