sophuwu.site > manhttpd   
              189
            
             package main

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

//go:embed index.html
var index string

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

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

var CFG struct {
	Hostname string
	Port     string
	Mandoc   string
	Addr     string
}

func GetCFG() {
	CFG.Hostname = os.Getenv("HOSTNAME")
	index = strings.ReplaceAll(index, "{{ hostname }}", CFG.Hostname)
	b, e := exec.Command("which", "mandoc").Output()
	if e != nil || len(b) == 0 {
		CFG.Mandoc = os.Getenv("MANDOCPATH")
		if CFG.Mandoc == "" {
			log.Fatal("Fatal: no mandoc `apt-get install mandoc`")
		}
	} else {
		CFG.Mandoc = strings.TrimSpace(string(b))
	}

	CFG.Port = os.Getenv("ListenPort")
	if CFG.Port == "" {
		CFG.Port = "8082"
	}
	CFG.Addr = os.Getenv("ListenAddr")
	if CFG.Addr == "" {
		CFG.Addr = "0.0.0.0"
	}
}

func CssHandle(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)
}

func main() {
	GetCFG()
	http.HandleFunc("/style.css", CssHandle)
	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.Addr+":"+CFG.Port, nil)
}

func WriteHtml(w http.ResponseWriter, r *http.Request, title, html string) {
	out := strings.ReplaceAll(index, "{{ host }}", r.Host)
	out = strings.ReplaceAll(out, "{{ title }}", title)
	out = strings.ReplaceAll(out, "{{ content }}", html)
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.WriteHeader(http.StatusOK)
	fmt.Fprint(w, out)
}

var LinkRemover = regexp.MustCompile(`(<a [^>]*>)|(</a>)`).ReplaceAllString
var HTMLManName = regexp.MustCompile(`(?:<b>)?([a-zA-Z0-9_.:\-]+)(?:</b>)?\(([0-9][0-9a-z]*)\)`)

type ManPage struct {
	Name    string
	Section string
	Desc    string
	Path    string
}

func (m *ManPage) Where() error {
	var arg = []string{"-w", m.Name}
	if m.Section != "" {
		arg = []string{"-w", "-s" + m.Section, m.Name}
	}
	b, err := exec.Command("man", arg...).Output()
	m.Path = strings.TrimSpace(string(b))
	return err
}
func (m *ManPage) Html() string {
	if m.Where() != nil {
		return fmt.Sprintf("<p>404: Unable to locate page %s</p>", m.Name)
	}
	b, err := exec.Command(CFG.Mandoc, "-Thtml", "-O", "fragment", m.Path).Output()
	if err != nil {
		return fmt.Sprintf("<p>500: server error loading %s</p>", m.Name)
	}
	html := LinkRemover(string(b), "")
	html = HTMLManName.ReplaceAllStringFunc(html, func(s string) string {
		m := HTMLManName.FindStringSubmatch(s)
		return fmt.Sprintf(`<a href="?%s.%s">%s(%s)</a>`, m[1], m[2], m[1], m[2])
	})
	return html
}

var ManDotName = regexp.MustCompile(`^([a-zA-Z0-9_\-]+)(?:\.([0-9a-z]+))?$`)

func NewManPage(s string) (m ManPage) {
	name := ManDotName.FindStringSubmatch(s)
	if len(name) >= 2 {
		m.Name = name[1]
	}
	if len(name) >= 3 {
		m.Section = name[2]
	}
	return
}

var RxWords = regexp.MustCompile(`("[^"]+")|([^ ]+)`).FindAllString
var RxWhatIs = regexp.MustCompile(`([a-zA-Z0-9_\-]+) [(]([0-9a-z]+)[)][\- ]+(.*)`).FindAllStringSubmatch

func searchHandler(w http.ResponseWriter, r *http.Request) {
	_ = r.ParseForm()
	q := r.Form.Get("q")
	if q == "" {
		http.Redirect(w, r, r.URL.Path, http.StatusFound)
	}
	if func() bool {
		m := NewManPage(q)
		return m.Where() == nil
	}() {
		http.Redirect(w, r, "?"+q, http.StatusFound)
		return
	}
	var args = RxWords("-lw "+q, -1)
	for i := range args {
		args[i] = strings.TrimSpace(args[i])
		args[i] = strings.TrimPrefix(args[i], `"`)
		args[i] = strings.TrimSuffix(args[i], `"`)
	}
	cmd := exec.Command("whatis", args...)
	b, e := cmd.Output()
	if len(b) < 1 || e != nil {
		WriteHtml(w, r, "Search", fmt.Sprintf("<p>404: no resualts matching %s</p>", q))
		return
	}
	var output string
	for _, line := range RxWhatIs(string(b), -1) { // strings.Split(string(b), "\n") {
		if len(line) == 4 {
			output += fmt.Sprintf(`<p><a href="?%s.%s">%s (%s)</a> - %s</p>%c`, line[1], line[2], line[1], line[2], line[3], 10)
		}
	}
	WriteHtml(w, r, "Search", output)
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
	path := filepath.Base(r.URL.Path)
	path = strings.TrimSuffix(path, "/")
	if path == "style.css" {
		CssHandle(w, r)
		return
	}

	if r.Method == "POST" {
		searchHandler(w, r)
		return
	}

	r.ParseForm()
	name := r.URL.RawQuery
	if name != "" {
		man := NewManPage(name)
		WriteHtml(w, r, man.Name, man.Html())
		return
	}
	WriteHtml(w, r, "Index", "")
	return
}