package tldr import ( "errors" "fmt" "git.sophuwu.com/manhttpd/CFG" "git.sophuwu.com/manhttpd/embeds" "git.sophuwu.com/manhttpd/logs" "git.sophuwu.com/manhttpd/neterr" "net/http" "os" "strings" "time" "os/exec" "path/filepath" ) func GitDir() string { return filepath.Join(CFG.TldrDir, "tldr.git") } func getGitList(path string, mp *map[string]string) error { cmd := exec.Command("/bin/git", "--git-dir", GitDir(), "--no-color", "show", "main:"+path) cmd.Dir = CFG.TldrDir b, err := cmd.Output() if err != nil { return err } var k, v string for _, v = range strings.Split(string(b), "\n") { if strings.HasSuffix(v, ".md") { v = strings.TrimSpace(v) k = strings.TrimSuffix(v, ".md") v = filepath.Join(path, v) (*mp)[k] = v } } return nil } var shspt = `#!/bin/bash cd '{{ .TldrDir }}' if [[ -d '{{ .GitDir }}' ]]; then rm -rf '{{ .GitDir }}' fi set -e git clone --filter=blob:none --no-checkout '{{ .TldrGitSrc }}' '{{ .GitDir }}' cd '{{ .GitDir }}' git sparse-checkout init git sparse-checkout set pages/linux/*.md pages/common/*.md git checkout main mkdir -p '{{ .TldrDir }}/pages' find "{{ .GitDir }}/pages/common" -type f -name "*.md" -exec cp "{}" '{{ .TldrDir }}/pages/' \; find "{{ .GitDir }}/pages/linux" -type f -name "*.md" -exec cp "{}" '{{ .TldrDir }}/pages/' \; ` var TldrPagesMap = make(map[string]string) var PageDir string func updateTldrPages() error { sh := strings.ReplaceAll(shspt, "{{ .TldrDir }}", CFG.TldrDir) sh = strings.ReplaceAll(sh, "{{ .GitDir }}", GitDir()) sh = strings.ReplaceAll(sh, "{{ .TldrGitSrc }}", CFG.TldrGitSrc) upcmd := filepath.Join(CFG.TldrDir, "update.sh") _ = os.Remove(upcmd) err := os.WriteFile(upcmd, []byte(sh), 0755) if err != nil { return fmt.Errorf("failed to write update script: %w", err) } cmd := exec.Command(upcmd) cmd.Env = os.Environ() cmd.Dir = CFG.TldrDir _, err = cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to run update script: %w", err) } return nil } func Open() { if !CFG.TldrPages { return } PageDir = filepath.Join(CFG.TldrDir, "pages") update := false st, err := os.Stat(PageDir) if err != nil && os.IsNotExist(err) { err = os.MkdirAll(PageDir, 0755) logs.CheckFatal("unable to create tldr pages directory", err) update = true } else if time.Now().After(st.ModTime().AddDate(0, 0, 14)) { update = true } if update { logs.Log("Updating tldr pages...") logs.CheckFatal("unable to update tldr pages", err) logs.Log("Tldr pages updated successfully.") } TldrPagesMap = make(map[string]string) var de []os.DirEntry de, err = os.ReadDir(PageDir) logs.CheckFatal("unable to read tldr pages directory", err) var name string for _, d := range de { if d.IsDir() { continue } name = strings.TrimSuffix(d.Name(), ".md") TldrPagesMap[name] = filepath.Join(PageDir, d.Name()) } } type TldrPage struct { Name string Path string Content string } func (p *TldrPage) findPath() error { if p.Name == "" { return errors.New("tldr page name cannot be empty") } var ok bool p.Path, ok = TldrPagesMap[p.Name] if !ok { return errors.New("tldr page not found: " + p.Name) } return nil } func (p *TldrPage) open() error { err := p.findPath() if err != nil { return err } // cmd := exec.Command("/bin/git", "--git-dir", GitDir(), "--no-color", "show", "main:"+p.Path) // cmd.Dir = CFG.TldrDir // b, err := cmd.Output() b, err := os.ReadFile(p.Path) p.Content = string(b) return err } /* tldr page format: # tldr page name > tldr page description - command 1 `command 1 usage` - command 2 `command 2 usage` Specification: lines beginning with `#` are titles lines beginning with `>` are descriptions inline urls, inside <> tags may contain inline code lines beginning with `-` list elements may contain inline code `codeblocks` on same level */ func inlineLink(s string) string { i := strings.Index(s, "<") if i < 0 { return s } j := strings.Index(s[i:], ">") if j < 0 { return s } j += i return s[:i] + `` + s[i+1:j] + `` + inlineLink(s[j+1:]) } func inlineCode(s string) string { i := strings.Index(s, "`") if i < 0 { return s } j := strings.Index(s[i+1:], "`") if j < 0 { return s } j += i + 1 return s[:i] + `` + s[i+1:j] + `` + inlineCode(s[j+1:]) } func inline(s string) string { s = inlineLink(s) s = inlineCode(s) return s } func htmlEscape(s string) string { s = strings.ReplaceAll(s, "&", "&") s = strings.ReplaceAll(s, "<", "<") s = strings.ReplaceAll(s, ">", ">") s = strings.ReplaceAll(s, `"`, """) s = strings.ReplaceAll(s, "'", "'") return s } func (p *TldrPage) HTML() (string, error) { s := "" lines := strings.Split(p.Content, "\n") for _, line := range lines { line = strings.TrimSpace(line) if len(line) == 0 { continue } i := strings.Index(line, " ") if strings.HasPrefix(line, "#") { if i < 0 { return "", errors.New("invalid tldr page format: missing space after title") } s += `

` + htmlEscape(line[i+1:]) + "

\n" } else if strings.HasPrefix(line, ">") { if i < 0 { return "", errors.New("invalid tldr page format: missing space after description") } s += `

` + inline(htmlEscape(line[i+1:])) + "

\n" } else if strings.HasPrefix(line, "-") { if i < 0 { return "", errors.New("invalid tldr page format: missing space after list item") } s += `

` + inline(htmlEscape(line[i+1:])) + "

\n" } else if strings.HasPrefix(line, "`") && strings.HasSuffix(line, "`") { s += `
` + htmlEscape(line[1:len(line)-1]) + "
\n" } else { s += `

` + inline(htmlEscape(line)) + "

\n" } } if len(s) == 0 { return "", errors.New("invalid tldr page format: no content found") } s = `
` + s + "
\n" return s, nil } func OpenTldrPage(name string) (*TldrPage, neterr.NetErr) { if name == "" { return nil, neterr.Err400 } page := &TldrPage{Name: name} err := page.open() if err != nil { return nil, neterr.Err404 } return page, nil } func Http(w http.ResponseWriter, r *http.Request, q string) bool { if filepath.Ext(q) != ".tldr" { return false } name := strings.TrimSuffix(q, ".tldr") page, nerr := OpenTldrPage(name) if embeds.ChkWriteError(w, r, nerr, q) { return true } html, err := page.HTML() if err != nil { embeds.WriteError(w, r, neterr.Err500, q) return true } embeds.WriteHtml(w, r, "TLDR: "+name, html, q, q) return true }