package main
import (
"fmt"
"html/template"
"log"
"net/http"
"os"
"slices"
"regexp"
"database/sql"
_ "modernc.org/sqlite"
)
type Entry struct {
os string
name string
section int
path string
}
type Database struct {
DB *sql.DB
}
func (d *Database) GetOS(w http.ResponseWriter, r *http.Request) {
rows, err := d.DB.Query("SELECT DISTINCT os FROM manpages;")
if err != nil {
log.Println(err)
ErrorPage(w, 500)
return
}
defer rows.Close()
for rows.Next() {
var osName string
if err := rows.Scan(&osName); err != nil {
log.Println(err)
ErrorPage(w, 500)
return
}
fmt.Fprintf(w, "", osName, osName)
}
}
func (d *Database) Search(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
log.Println("Request for:", r.URL.String())
name := query["name"][0]
section := query["section"][0]
osName := query["os"][0]
log.Println(name)
rows, err := d.DB.Query("SELECT * FROM manpages WHERE name == ? AND section == ? AND os == ?;", name, section, osName)
if err != nil {
log.Println(err)
ErrorPage(w, 500)
return
}
defer rows.Close()
var entries []Entry
for rows.Next() {
var entry Entry
if err := rows.Scan(&entry.os, &entry.name, &entry.section, &entry.path); err != nil {
log.Println(err)
ErrorPage(w, 500)
return
}
entries = append(entries, entry)
}
if len(entries) > 0 {
fmt.Fprintf(w, "
")
for _, e := range entries {
data, err := os.ReadFile(e.path)
if err != nil {
log.Println(err)
ErrorPage(w, 500)
return
}
fmt.Fprintf(w, "%s", data)
}
fmt.Fprintf(w, "
")
} else {
fmt.Fprintf(w, "No results found for %s %s(%s)
", osName, name, section)
}
}
type AllowedFiles struct {
allowedFiles []string
}
func (a *AllowedFiles) GetFiles(w http.ResponseWriter, r *http.Request) {
log.Println("Request for:", r.URL.String())
url := fmt.Sprintf("static%s", r.URL.String())
htmlRegex, err := regexp.Compile("^static/.*\\.html$")
if err != nil {
ErrorPage(w, 500)
return
}
if url == "static/" {
url = "static/index.html"
}
if !slices.Contains(a.allowedFiles, url) {
ErrorPage(w, 404)
return
}
if htmlRegex.MatchString(url) {
t := template.Must(template.ParseFiles("templates/template.html"))
html, err := os.ReadFile(url)
if err != nil {
ErrorPage(w, 500)
return
}
t.Execute(w, template.HTML(html))
} else {
http.ServeFile(w, r, url)
}
}
func ErrorPage(w http.ResponseWriter, code int) {
log.Println(code, http.StatusText(code))
t := template.Must(template.ParseFiles("templates/error.html"))
t.Execute(w, struct {
Code int
Message string
}{
Code: code,
Message: http.StatusText(code),
})
}
func Usage() {
fmt.Fprintf(os.Stderr, "%s [--port port]\n", os.Args[0])
}
func main() {
port := ":8000"
for i := 1; i < len(os.Args); i++ {
switch os.Args[i] {
case "--port":
if i+1 >= len(os.Args) {
Usage()
} else {
port = ":" + os.Args[i+1]
i++
}
default:
log.Println("Unknown option: ", os.Args[i])
Usage()
}
}
db, err := sql.Open("sqlite", "man.db")
if err != nil {
log.Fatal(err)
}
if err := db.Ping(); err != nil {
log.Fatal(err)
}
dbWrapper := &Database{ DB: db }
allowed := &AllowedFiles {
allowedFiles: []string{
"static/htmx.min.js",
"static/index.html",
"static/css/main.css",
},
}
http.HandleFunc("/search", dbWrapper.Search)
http.HandleFunc("/os", dbWrapper.GetOS)
http.HandleFunc("/", allowed.GetFiles)
log.Fatal(http.ListenAndServe(port, nil))
err = db.Close()
if err != nil {
log.Fatal(err)
}
}