codex/main.go
2025-02-07 22:31:22 +01:00

172 lines
No EOL
4.9 KiB
Go

package main
import (
"html/template"
"log"
"net/http"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
)
const (
FILES_DIR = "files"
TEMPLATES_DIR = "templates"
TIME_FORMAT = "Jan 2 2006 15:04:05"
)
const (
POST_ICON = "/static/post.gif"
FILE_ICON = "/static/file.gif"
)
var (
headerPattern = regexp.MustCompile(`(?m)^# (.+)$`)
subHeaderPattern = regexp.MustCompile(`(?m)^## (.+)$`)
newlinePattern = regexp.MustCompile(`(?m)^([^#<].+)`)
boldPattern = regexp.MustCompile(`\*\*(.*?)\*\*|__(.*?)__`)
italicPattern = regexp.MustCompile(`\*(.*?)\*|_(.*?)_`)
inlineCodePattern = regexp.MustCompile("`([^`]+)`")
codeBlockPattern = regexp.MustCompile("(?s)```(.*?)```")
blockquotePattern = regexp.MustCompile(`(?m)^> (.+)$`)
ulPattern = regexp.MustCompile(`(?m)^(?:-|\*) (.+)$`)
olPattern = regexp.MustCompile(`(?m)^\d+\. (.+)$`)
linkPattern = regexp.MustCompile(`\[(.*?)\]\((.*?)\)`)
)
type FileEntry struct {
Name string
Timestamp string
ParentPath string
Icon string
}
type Page struct {
Title string
Content template.HTML
Files []FileEntry
Folders []string
BasePath string
ParentPath string
TimeStamp string
}
func listEntries(basePath string) ([]FileEntry, []string) {
entries, err := os.ReadDir(filepath.Join(FILES_DIR, strings.TrimPrefix(basePath, "/")))
if err != nil {
log.Fatal(err)
}
var files []FileEntry
var folders []string
for _, entry := range entries {
if entry.IsDir() {
folders = append(folders, entry.Name())
} else if strings.HasSuffix(entry.Name(), ".md") {
info, _ := entry.Info()
files = append(files, FileEntry{Name: entry.Name(), Timestamp: info.ModTime().Format(TIME_FORMAT), ParentPath: basePath, Icon: POST_ICON})
}else
{
info, _ := entry.Info()
files = append(files, FileEntry{Name: entry.Name(), Timestamp: info.ModTime().Format(TIME_FORMAT), ParentPath: basePath, Icon: FILE_ICON})
}
}
sort.Slice(files, func(i, j int) bool { return files[i].Name < files[j].Name })
sort.Strings(folders)
return files, folders
}
func readMarkdown(filename string) string {
content, err := os.ReadFile(filepath.Join(FILES_DIR, filename))
if err != nil {
log.Fatal(err)
}
return string(content)
}
func renderMarkdown(content string) template.HTML {
content = headerPattern.ReplaceAllString(content, "<h1>$1</h1>")
content = subHeaderPattern.ReplaceAllString(content, "<h2>$1</h2>")
content = boldPattern.ReplaceAllString(content, "<b>$1$2</b>")
content = italicPattern.ReplaceAllString(content, "<i>$1$2</i>")
content = inlineCodePattern.ReplaceAllString(content, "<code>$1</code>")
content = codeBlockPattern.ReplaceAllString(content, "<pre><code>$1</code></pre>")
content = blockquotePattern.ReplaceAllString(content, "<blockquote>$1</blockquote>")
content = ulPattern.ReplaceAllString(content, "<li>$1</li>")
content = olPattern.ReplaceAllString(content, "<li>$1</li>")
content = linkPattern.ReplaceAllString(content, `<a href="$2">$1</a>`)
content = regexp.MustCompile(`(<li>.+?</li>)`).ReplaceAllString(content, "<ul>$1</ul>")
content = newlinePattern.ReplaceAllString(content, "<p>$1</p>")
return template.HTML(content)
}
func loadTemplate(name string) *template.Template {
tmplPath := filepath.Join(TEMPLATES_DIR, name)
tmpl, err := template.ParseFiles(tmplPath)
if err != nil {
log.Fatal(err)
}
return tmpl
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
basePath := strings.TrimPrefix(r.URL.Path, "/")
files, folders := listEntries(r.URL.Path)
page := Page{Files: files, Folders: folders, BasePath: basePath}
tmpl := loadTemplate("index.html")
tmpl.Execute(w, page)
}
func postHandler(w http.ResponseWriter, r *http.Request) {
filename := strings.TrimPrefix(r.URL.Path, "/")
parentPath := filepath.ToSlash(filepath.Dir(filename) + "/")
if parentPath == "." {
parentPath = ""
}
info, err := os.Stat(filepath.Join(FILES_DIR, filename))
if err != nil {
http.NotFound(w, r)
return
}
timeStamp := info.ModTime().Format(TIME_FORMAT)
content := readMarkdown(filename)
page := Page{Title: filename, Content: renderMarkdown(content), ParentPath: parentPath, TimeStamp: timeStamp}
tmpl := loadTemplate("post.html")
tmpl.Execute(w, page)
println("handling request " + r.URL.Path + " with parentdir " + parentPath)
}
func main() {
if _, err := os.Stat(FILES_DIR); os.IsNotExist(err) {
os.Mkdir(FILES_DIR, os.ModePerm)
}
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
path := strings.TrimPrefix(r.URL.Path, "/")
if strings.HasSuffix(path, ".md") {
postHandler(w, r)
} else if strings.Contains(path, ".") {
http.ServeFile(w, r, filepath.Join(FILES_DIR, path))
} else {
indexHandler(w, r)
}
})
http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "static/post.gif")
})
log.Println("Serving on :8080")
http.ListenAndServe(":8080", nil)
}