package main import ( "fmt" "html/template" "log" "net/http" "os" "path/filepath" "regexp" "sort" "strings" ) const ( PORT = 61594 FILES_DIR = "files" TEMPLATES_DIR = "templates" TIME_FORMAT = "Jan 2 2006 15:04:05" POST_ICON = "/static/post.gif" FILE_ICON = "/static/file.gif" ) var ( headerPattern = regexp.MustCompile(`(?m)^# (.+)$`) subHeaderPattern = regexp.MustCompile(`(?m)^## (.+)$`) thirdHeaderPattern = regexp.MustCompile(`(?m)^### (.+)$`) fourthHeaderPattern = regexp.MustCompile(`(?m)^#### (.+)$`) fifthHeaderPattern = regexp.MustCompile(`(?m)^##### (.+)$`) sixthHeaderPattern = 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 = codeBlockPattern.ReplaceAllString(content, "
$1")
content = inlineCodePattern.ReplaceAllString(content, "$1")
content = headerPattern.ReplaceAllString(content, "$1") content = linkPattern.ReplaceAllString(content, `$1`) // Handle lists properly //content = ulPattern.ReplaceAllString(content, "
$1
") 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) } 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 :", PORT) http.ListenAndServe(fmt.Sprintf(":%d", PORT), nil) }