import std/os import std/posix import std/strutils import std/unicode import bindings/libregexp import js/regex import types/opt import utils/twtstr proc lre_check_stack_overflow(opaque: pointer; alloca_size: csize_t): cint {.exportc.} = return 0 proc parseSection(query: string): tuple[page, section: string] = var section = "" if query.len > 0 and query[^1] == ')': for i in countdown(query.high, 0): if query[i] == '(': section = query.substr(i + 1, query.high - 1) break if section != "": return (query.substr(0, query.high - 2 - section.len), section) return (query, "") func processBackspace(line: string): string = var s = "" var i = 0 var thiscs = 0 .. -1 var bspace = false var inU = false var inB = false var pendingInU = false var pendingInB = false template flushChar = if pendingInU != inU: s &= (if inU: "" else: "") inU = pendingInU if pendingInB != inB: s &= (if inB: "" else: "") inB = pendingInB if thiscs.len > 0: let cs = case line[thiscs.a] of '&': "&" of '<': "<" of '>': ">" else: line.substr(thiscs.a, thiscs.b) s &= cs thiscs = i ..< i + n pendingInU = false pendingInB = false while i < line.len: # this is the same "sometimes works" algorithm as in ansi2html if line[i] == '\b' and thiscs.len > 0: bspace = true inc i continue let n = line.runeLenAt(i) if thiscs.len == 0: thiscs = i ..< i + n i += n continue if bspace and thiscs.len > 0: if line[i] == '_' and not pendingInU and line[thiscs.a] != '_': pendingInU = true elif line[thiscs.a] == '_' and not pendingInU and line[i] != '_': # underscore comes first; set thiscs to the current charseq thiscs = i ..< i + n pendingInU = true elif line[i] == '_' and line[thiscs.a] == '_': if pendingInB: pendingInU = true else: pendingInB = true elif not pendingInB: pendingInB = true bspace = false else: flushChar i += n let n = 0 flushChar pendingInU = false pendingInB = false flushChar return s proc isCommand(paths: seq[string]; s: string): bool = for p in paths: if fileExists(p & s): return true false iterator myCaptures(captures: var seq[RegexCapture]; target: int; offset: var int): RegexCapture = for cap in captures.mitems: if cap.i == target: cap.s += offset cap.e += offset yield cap proc processManpage(file: File) = template re(s: static string): Regex = let r = s.compileRegex({LRE_FLAG_GLOBAL, LRE_FLAG_UTF16}) if r.isNone: stdout.write(s & ": " & r.error) return r.get # regexes partially from w3mman2html let linkRe = re"(https?|ftp)://[\w/~.-]+" let mailRe = re"(mailto:|)(\w[\w.-]*@[\w-]+\.[\w.-]*)" let fileRe = re"(file:)?[/~][\w/~.-]+[\w/]" let includeRe = re"#include(|\s)*<([\w./-]+)" let manRe = re"()*(\w[\w.-]*)()*(\([0-9nlx]\w*\))" var paths: seq[string] = @[] for p in getEnv("PATH").split(':'): var i = p.high while i > 0 and p[i] == '/': dec i paths.add(p.substr(0, i) & "/") var line: string var wasBlank = false while file.readLine(line): if line == "": if wasBlank: continue wasBlank = true else: wasBlank = false var line = line.processBackspace() if (var res = linkRe.exec(line); res.success): var offset = 0 for cap in res.captures.myCaptures(0, offset): let s = line[cap.s.." & s & "" line[cap.s.." & s & "" line[cap.s.." & s & "" else: "" & s & "" line[cap.s.." & s & "" line[cap.s.." & man & "" line[manCap.s..&1" let file = popen(cstring(cmd), "r") if file == nil: stdout.write("Cha-Control: ConnectionError 1 failed to run " & cmd) return var line0: string if not file.readLine(line0) or file.endOfFile(): discard file.pclose() if line0.startsWith("man: "): line0 = line0.after(' ') stdout.write("Cha-Control: ConnectionError 4 " & line0) return var manword = keyword if section != "": manword &= '(' & section & ')' stdout.write("""Content-Type: text/html man """ & manword & """
""" & line0 & "\n")
  file.processManpage()
  discard file.pclose()

proc doLocal(man, keyword: string) =
  let cmd = "GROFF_NO_SGR=1 MAN_KEEP_FORMATTING=1 " &
    man & " -l " & keyword & " 2>/dev/null"
  let file = popen(cstring(cmd), "r")
  if file == nil:
    stdout.write("Cha-Control: ConnectionError 1 failed to run " & cmd)
    return
  stdout.write("""Content-Type: text/html

man -l """ & keyword & """

man -l """ & keyword & """

""")
  file.processManpage()
  discard file.pclose()

proc doKeyword(man, keyword, section: string) =
  let sectionOpt = if section == "": "" else: " -s " & section
  let cmd = man & sectionOpt & " -k " & keyword & " 2>/dev/null"
  let file = popen(cstring(cmd), "r")
  if file == nil:
    stdout.write("Cha-Control: ConnectionError 1 failed to run " & cmd)
    return
  stdout.write("Content-Type: text/html\n\n")
  stdout.write("man" & sectionOpt & " -k " & keyword & "\n")
  stdout.write("

man" & sectionOpt & " -k " & keyword & "

\n") stdout.write("
    ") var line: string template die = stdout.write("Error parsing line! " & line) return while file.readLine(line): if line.len == 0: stdout.write("\n") continue # collect titles var titles: seq[string] = @[] var i = 0 while true: let title = line.until({'(', ','}, i) i += title.len titles.add(title) if i >= line.len or line[i] == '(': break i = line.skipBlanks(i + 1) # collect section if line[i] != '(': die let sectionText = line.substr(i, line.find(')', i)) i += sectionText.len # create line var section = sectionText.until(',') # for multiple sections, take first if section[^1] != ')': section &= ')' var s = "
  • " for i, title in titles: let title = title.htmlEscape() s &= "" & title & "" if i < titles.high: s &= ", " s &= sectionText s &= line.substr(i) s &= "\n" stdout.write(s) proc main() = var man = getEnv("MANCHA_MAN") if man == "": man = "/usr/bin/man" if not fileExists(man): man = "/bin/man" if not fileExists(man): man = "/usr/local/bin/man" if not fileExists(man): man = "/usr/bin/env man" doAssert getAppFilename() != man # don't accidentally fork bomb ourselves let query = percentDecode(getEnv("QUERY_STRING")) if query.startsWith("man:"): let (keyword, section) = parseSection(query.after(':')) doMan(man, keyword, section) elif query.startsWith("man-k:"): let (keyword, section) = parseSection(query.after(':')) doKeyword(man, keyword, section) elif query.startsWith("man-l:"): doLocal(man, query.after(':')) else: stdout.write("Cha-Control: ConnectionError 1 invalid invocation") main()