sophuwu.site > manhttpd
regex stuff
header on all pages
remove reduntant links from pages
sophuwu sophie@skisiel.com
Wed, 03 Apr 2024 04:26:13 +0200
commit

a22bff606498d08942feb35c5d74b5dd1a3ef911

parent

f6c1aeedc32977d3c731e134401ca87d50cbbc12

3 files changed, 102 insertions(+), 97 deletions(-)

jump to
M dark_theme.cssdark_theme.css

@@ -2,6 +2,31 @@ *{

font-family: Ubuntu, Helvetica, sans-serif; color: #c7c7c7; } +table.head, table.foot { + width: 100%; +} +td.head-rtitle, td.foot-os { + text-align: right; +} +td.head-vol { + text-align: center; +} +.Nd, .Bf, .Op { + display: inline; +} +.Pa, .Ad { + font-style: italic; +} +.Ms { + font-weight: bold; +} +.Bl-diag > dt { + font-weight: bold; +} +code.Nm, .Fl, .Cm, .Ic, code.In, .Fd, .Fn, .Cd { + font-weight: bold; + font-family: inherit; +} code, pre { font-family: Ubuntu Mono, Monospaced, serif!important; }

@@ -13,12 +38,12 @@ color: #f0f0f0;

} hr { color: #363636; - margin-top: 10px; + margin-top: 0; } .flexy { display: flex; flex-direction: row; - justify-content: flex-start; + justify-content: center; } body, html { background-color: #1f1e23;

@@ -46,22 +71,16 @@ background-color: #343434;

border: 1px solid #c7c7c7; padding: 4px; border-radius: 5px; + margin: 2px; } input[type="text"], textarea { height: inherit; width: 300px; - margin: 2px 10px; -} - -input[type="checkbox"] { - width: fit-content; - height: fit-content; } input[type="submit"] { background-color: #343434; font-weight: bold; cursor: pointer; - margin: 0; }
M index.htmlindex.html

@@ -1,34 +1,26 @@

<html> -<head> -<meta charset="utf-8"> -<title>Web Manpages</title> -<link rel="stylesheet" type="text/css" href="/style.css"> -</head> + <head> + <meta charset="utf-8"> + <title>{{ title }}</title> + <link rel="stylesheet" type="text/css" href="/style.css"> + <script> + + </script> + </head> <body> -<H1>Man Pages Index</H1> -<HR> -<H2>Open Page:</H2> -<p>Input name of page.</p> -<FORM method="get" action="/"> - <INPUT TYPE="text" NAME="man"> - <INPUT TYPE="submit" VALUE="Submit"> -</FORM> -<HR> -<H2>Search Pages:</H2> -<p>Interpret each line as a separate keyword.</p> -<FORM method="post" action="/"> <div class="flexy"> - <textarea rows="5" name="search"></textarea><br> - <div> - <LABEL><INPUT TYPE="checkbox" NAME="arg" VALUE="r"> <B>Regex</B> match all keywords with regex.</LABEL><BR> - <LABEL><INPUT TYPE="checkbox" NAME="arg" VALUE="w"> <B>Wildcard</B> match all keywords by wildcard.</LABEL><BR> - <LABEL><INPUT TYPE="checkbox" NAME="arg" VALUE="e"> <B>Exact</B> match keywords exactly.</LABEL><BR> - <LABEL><INPUT TYPE="checkbox" NAME="arg" VALUE="a"> <B>And</B> all keywords must match.</LABEL><BR> - <INPUT TYPE="submit" VALUE="Submit"> - </div> + <h3 style="margin: 2px;">Find Man Pages: </h3> + <FORM method="post" action="/"> + <INPUT TYPE="text" name="q" autocomplete="off"> + <INPUT TYPE="submit" name="t" VALUE="Open"> + <INPUT TYPE="submit" name="t" VALUE="Search"> + </FORM> </div> -</FORM> -<HR> -<a href="//{{ host }}">{{ host }}</a><br> -</body> + <HR> + {{ content }} + <HR> + <p> + Host: <a href="{{ host }}">{{ hostname }}</a> + </p> + </body> </html>
M main.gomain.go

@@ -1,7 +1,6 @@

package main import ( - "bytes" _ "embed" "fmt" "log"

@@ -13,7 +12,7 @@ "strings"

) //go:embed index.html -var index []byte +var index string //go:embed dark_theme.css var css []byte

@@ -22,27 +21,27 @@ //go:embed favicon.ico

var favicon []byte var CFG struct { - Hostname string - ListenAddr string - ListenPort string - Mandoc string + Hostname string + Addr string + Mandoc string } -func init() { +func GetCFG() { CFG.Hostname, _ = os.Hostname() + index = strings.ReplaceAll(index, "{{ hostname }}", CFG.Hostname) b, e := exec.Command("which", "mandoc").Output() if e != nil || len(b) == 0 { log.Fatal("Fatal: no mandoc") } CFG.Mandoc = strings.TrimSpace(string(b)) - CFG.ListenAddr = os.Getenv("ListenAddr") - CFG.ListenPort = os.Getenv("ListenPort") - if CFG.ListenPort == "" { - CFG.ListenPort = "8082" + CFG.Addr = os.Getenv("ListenPort") + if CFG.Addr == "" { + CFG.Addr = "8082" } } func main() { + GetCFG() http.HandleFunc("/style.css", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/css; charset=utf-8")

@@ -50,7 +49,6 @@ 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)))

@@ -58,60 +56,56 @@ w.WriteHeader(http.StatusOK)

w.Write(favicon) }) http.HandleFunc("/", indexHandler) - http.ListenAndServe(CFG.ListenAddr+":"+CFG.ListenPort, nil) + http.ListenAndServe(":"+CFG.Addr, 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-9a-z]+\)`) + type ManPage struct { - Section int Name string - Path string + Section string + Desc 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 +func (m *ManPage) Path() string { + arg := "-w" + if m.Section != "" { + arg += "s" + m.Section } - 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 + b, _ := exec.Command("man", arg, m.Name).Output() + return strings.TrimSpace(string(b)) } - -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") +func (m *ManPage) Html() string { + b, err := exec.Command(CFG.Mandoc, "-c", "-K", "utf-8", "-T", "html", "-O", "fragment", m.Path()).Output() + if err != nil { + return fmt.Sprintf("<p>404: Page %s not found.</p>", m.Name) } - m.Path = strings.TrimSpace(string(b)) - return nil + html := LinkRemover(string(b), "") + return html } -var manRegexp = []*regexp.Regexp{regexp.MustCompile(`\.[1-9]$`), regexp.MustCompile(`( )?[(][1-9][)]$`)} +var ManDotName = regexp.MustCompile(`^([a-zA-Z0-9_\-]+)(?:.([0-9a-z]+))?$`).FindStringSubmatch -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() - } +func NewManPage(s string) (m ManPage) { + name := ManDotName(s) + if len(name) >= 2 { + m.Name = name[1] } - m.Section = 0 - m.Name = s - return m.FindPath() + if len(name) >= 3 { + m.Section = name[2] + } + return } func searchHandler(w http.ResponseWriter, r *http.Request) {

@@ -133,7 +127,9 @@ }

func indexHandler(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { - http.Redirect(w, r, "/", http.StatusTemporaryRedirect) + // http.Redirect(w, r, "/", http.StatusFound) + man := NewManPage(r.URL.Path[1:]) + WriteHtml(w, r, man.Name, man.Html()) return }

@@ -145,13 +141,11 @@ 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))) + man.Name = r.Form.Get("man") + if man.Name == "" { + WriteHtml(w, r, "Index", "") return } - fmt.Fprintf(w, "%v", man.html(w, r)) + }