sophuwu.site > manhttpd   
              164
            
             package main

import (
	"bytes"
	_ "embed"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/exec"
	"regexp"
	"strings"
)

//go:embed index.html
var index []byte

//go:embed dark_theme.css
var css []byte

//go:embed favicon.ico
var favicon []byte

var CFG struct {
	Hostname   string
	ListenAddr string
	ListenPort string
	MANPATH    []string
	Mandoc     string
}

func cmdout(s string) string {
	b, e := exec.Command("mandoc").Output()
	if e != nil {
		log.Fatal("Fatal: unable to get " + ss[0])
	}
	return strings.TrimSpace(string(b))
}

func init() {
	CFG.MANPATH = cmdout("manpath")
	CFG.Hostname, _ = os.Hostname()
	CFG.Mandoc = cmdout()
	CFG.ListenAddr = os.Getenv("ListenAddr")
	CFG.ListenPort = os.Getenv("ListenPort")
	if CFG.ListenPort == "" {
		CFG.ListenPort = "8082"
	}
}

func main() {

	http.HandleFunc("/style.css", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "text/css; charset=utf-8")
		w.Header().Set("Content-Length", fmt.Sprint(len(css)))
		w.WriteHeader(http.StatusOK)
		w.Write(css)
	})

	http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "image/x-icon")
		w.Header().Set("Content-Length", fmt.Sprint(len(favicon)))
		w.WriteHeader(http.StatusOK)
		w.Write(favicon)
	})
	http.HandleFunc("/", indexHandler)
	http.ListenAndServe(CFG.ListenAddr+":"+CFG.ListenPort, nil)

}

type ManPage struct {
	Section int
	Name    string
	Path    string
}

func (m *ManPage) html(w http.ResponseWriter, r *http.Request) error {
	if m.Path == "" {
		return fmt.Errorf("no path")
	}
	b, err := exec.Command(CFG.Mandoc, "-Thtml", m.Path).Output()
	if err != nil {
		return err
	}
	s := string(b)
	// HtmlHeader(&s, m.Name)
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.WriteHeader(http.StatusOK)
	fmt.Fprint(w, s)
	return nil
}

func (m *ManPage) FindPath() error {
	s := m.Name
	if m.Section > 0 {
		s += "." + fmt.Sprint(m.Section)
	}
	cmd := exec.Command("man", "-w", s)
	b, e := cmd.Output()
	if e != nil {
		return fmt.Errorf("page not found")
	}
	m.Path = strings.TrimSpace(string(b))
	return nil
}

var manRegexp = []*regexp.Regexp{regexp.MustCompile(`\.[1-9]$`), regexp.MustCompile(`( )?[(][1-9][)]$`)}

func (m *ManPage) ParseName(s string) (err error) {
	s = strings.TrimSpace(s)
	for i, rx := range manRegexp {
		if rx.MatchString(s) {
			m.Section = int((s[len(s)-i-1]) - '0')
			m.Name = strings.TrimSpace(s[:len(s)-i-2])
			return m.FindPath()
		}
	}
	m.Section = 0
	m.Name = s
	return m.FindPath()
}

func searchHandler(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	args := "-l\n-" + strings.Join(r.Form["arg"], "\n-")
	search := strings.ReplaceAll(r.Form["search"][0], "\r", "")
	args += "\n" + search
	cmd := exec.Command("apropos", strings.Split(args, "\n")...)
	cmd.Env = append(cmd.Env, os.Environ()...)
	// cmd.Env = append(cmd.Env, "MANPATH="+CFG.MANPATH)
	b, e := cmd.Output()
	if e != nil {
		http.Error(w, "no results", http.StatusNotFound)
		return
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.WriteHeader(http.StatusOK)
	fmt.Fprint(w, strings.ReplaceAll(string(b), "\n", "<br>"))
}
func indexHandler(w http.ResponseWriter, r *http.Request) {

	if r.URL.Path != "/" {
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	if r.Method == "POST" {
		searchHandler(w, r)
		return
	}
	if !strings.HasPrefix(r.URL.RawQuery, "man=") && r.URL.RawQuery != "" {
		r.URL.RawQuery = "man=" + r.URL.RawQuery
	}
	_ = r.ParseForm()
	q := r.Form.Get("man")
	var man ManPage
	if err := man.ParseName(q); err != nil {
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		w.WriteHeader(http.StatusOK)
		w.Write(bytes.ReplaceAll(index, []byte("{{ host }}"), []byte(r.Host)))
		return
	}
	fmt.Fprintf(w, "%v", man.html(w, r))
}