package main import "fmt" import "sort" import "strings" import "os" import "net/http" import "html/template" import "path/filepath" import "path" import ( "github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown/html" "github.com/gomarkdown/markdown/parser" ) // new iter stuff import "maps" // maps.Keys() import "slices" // slices.Collect() // why in two different packages? func main() { if len(os.Args) <= 1 { fmt.Println("gituwa reposPath [staticPath] [port]") os.Exit(1) } repoPath := os.Args[1] staticPath := "static" if len(os.Args) > 2 { staticPath = os.Args[2] } addr := ":2137" if len(os.Args) > 3 { addr = os.Args[3] } repos, err := ScanForGitRepos(repoPath) if err != nil { fmt.Println("Could not load repos") os.Exit(1) } // index repo chooser repoNames := slices.Collect(maps.Keys(repos)) defaultSort(repoNames) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") data := struct { Title string Repos []string }{ Title: "repos", Repos: repoNames, } if err := index.Execute(w, data); err != nil { http.Error(w, "Server error", http.StatusInternalServerError) } }) // Interesting. Prebaking all the paths, yo! // Although I could just use r.PathValue("id") with "/{id}" path. http.HandleFunc("/{repoName}/{path...}", func(w http.ResponseWriter, r *http.Request) { repoName := r.PathValue("repoName") repo := repos[repoName] gitPath := r.PathValue("path") files, entry, err := fromPath(repo, gitPath) if err != nil { panic(err) } // file entry if entry != nil { if entry.Filetype != File { panic(fmt.Sprintf("not a tree entry?? (%d)", entry.Filetype)) } ext := entry.GetExtension() fmt.Println(ext) blob := get_blob(repo, entry) if ext == ".md" { if err := readmePage.Execute(w, struct { Title string Md template.HTML }{ Title: entry.Name, Md: template.HTML(mdToHTML(blob.Bytes)), }); err != nil { fmt.Println(err) http.Error(w, "Server error", http.StatusInternalServerError) } } else { _, err = w.Write(blob.Bytes) // automatically sets Content-Type if err != nil { panic(err) } } } else { // render dirs w.Header().Set("Content-Type", "text/html; charset=utf-8") if err != nil { fmt.Println(err) http.Error(w, "Server error", http.StatusInternalServerError) } isRoot := len(gitPath) == 0 var readme, realReadmeName string for _, entry := range files { if strings.ToLower(entry.Name) == "readme.md" { blob := get_blob(repo, &entry) realReadmeName = entry.Name readme = string(mdToHTML(blob.Bytes)) } } p := path.Join("/", repoName, gitPath) p += "/" fmt.Println(p) data := struct { Title string Files []Entry Base string Home string IsRoot bool Readme template.HTML RealReadmeName string }{ Title: repoName, Files: files, Base: p, Home: path.Join("/", repoName), IsRoot: isRoot, Readme: template.HTML(readme), RealReadmeName: realReadmeName, } if err := repoPage.Execute(w, data); err != nil { fmt.Println(err) http.Error(w, "Server error", http.StatusInternalServerError) } } }) http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(staticPath)))) fmt.Println("Listenin' on", addr) if err := http.ListenAndServe(addr, nil); err != nil { fmt.Println("Server error:", err) } } func ScanForGitRepos(repo_path string) (map[string]Repo, error) { libgit_init() entries, err := os.ReadDir(repo_path) if err != nil { return nil, err } repos := make(map[string]Repo) for _, e := range entries { if !e.IsDir() { continue } repo, err := repository_open(filepath.Join(repo_path, e.Name())) if err != nil { fmt.Println(e.Name(), ": ", err) continue } // remove .git suffix, cuz these names are only used for displaying // it looks nicer that way. name := e.Name() if path.Ext(name) == ".git" { name = trimExtension(name) } repos[name] = repo } return repos, nil } var index = template.Must(template.New("page").Parse(`