193
package main
import (
"bytes"
_ "embed"
"fmt"
"github.com/mholt/archiver/v4"
"log"
"net/http"
"os"
"os/exec"
"strings"
)
var m2h = "/home/sophuwu/Documents/project/manpages/build/mh.2"
//go:embed index.html
var index []byte
//go:embed font.css
var font []byte
//go:embed dark_theme.css
var css []byte
var CFG struct {
Hostname string
ListenAddr string
ListenPort string
MANPATH string
}
func cmdout(s string) string {
ss := strings.Split(s, " ")
b, e := exec.Command("/usr/bin/"+ss[0], ss[1:]...).Output()
if e != nil {
log.Fatal("Fatal: unable to get " + ss[0])
}
return strings.TrimSpace(string(b))
}
func init() {
CFG.MANPATH = cmdout("manpath -g")
CFG.Hostname = cmdout("hostname")
CFG.ListenAddr = os.Getenv("ListenAddr")
CFG.ListenPort = os.Getenv("ListenPort")
if CFG.ListenPort == "" {
CFG.ListenPort = "8080"
}
css = append(css, font...)
index = bytes.ReplaceAll(index, []byte("{{ hostname }}"), []byte(CFG.Hostname))
index = bytes.ReplaceAll(index, []byte("{{ port }}"), []byte(CFG.ListenPort))
}
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.Handle("/favicon.ico", http.NotFoundHandler())
http.HandleFunc("/", indexHandler)
http.ListenAndServe(CFG.ListenAddr+":"+CFG.ListenPort, nil)
}
type ManPage struct {
Section string
Name string
Path string
WhatIs string
}
func readCompressed(fh *os.File, buff *bytes.Buffer) error {
decompressor, err := archiver.Gz{}.OpenReader(fh)
if err != nil {
return err
}
defer decompressor.Close()
_, err = buff.ReadFrom(decompressor)
return err
}
func ReadFh(buff *bytes.Buffer, path string) error {
fh, err := os.OpenFile(path, os.O_RDONLY, 0)
if err != nil {
return err
}
defer fh.Close()
if strings.HasSuffix(path, ".gz") {
return readCompressed(fh, buff)
}
_, err = buff.ReadFrom(fh)
return err
}
func (m *ManPage) html(w http.ResponseWriter) error {
if m.Path == "" {
return fmt.Errorf("no path")
}
var buff bytes.Buffer
err := ReadFh(&buff, m.Path)
if err != nil {
return err
}
cmd := exec.Command(m2h, "-H", CFG.Hostname+":"+CFG.ListenPort, "-M", "/", "-")
cmd.Stdin = &buff
b, e := cmd.Output()
if e != nil {
return fmt.Errorf("page not found")
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write(b)
return nil
}
func (m *ManPage) FindPath() error {
s := m.Name
if m.Section != "" {
s = m.Section + "." + s
}
cmd := exec.Command("man", "-w", s)
cmd.Env = append(os.Environ(), "MANPATH="+CFG.MANPATH)
b, e := cmd.Output()
if e != nil {
return fmt.Errorf("page not found")
}
m.Path = strings.TrimSpace(string(b))
return nil
}
func (m *ManPage) FindHumanInput(s string) error {
s = strings.TrimSpace(s)
s = strings.ReplaceAll(s, "(", "")
s = strings.ReplaceAll(s, ")", "")
if strings.Contains(s, " ") {
arr := strings.SplitN(s, " ", 2)
m.Section, m.Name = arr[1], arr[0]
} else if strings.Contains(s, ".") {
arr := strings.SplitN(s, ".", 2)
m.Section, m.Name = arr[0], arr[1]
} else {
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 {
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write(b)
}
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.FindHumanInput(q); err != nil {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write(index)
return
}
fmt.Fprintf(w, "%v", man.html(w))
}