diff --git a/README.md b/README.md index ab7688d..72f40de 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,15 @@ # mdgopher -Gopher + Markdown. Kinda defeats the purpose but hey whatever. \ No newline at end of file +Gopher + Markdown. Kinda defeats the purpose but hey whatever. + +## mdbrowse +A terrible lua-curses and luasocket based markdown browser. + +Gopher menu support is experimental but markdown support should work fairly well. + +### keybinds +- j and k for scrolling +- f to follow a link (marked by a [number]) +- o to open a URL +- s for settings (terminal size and etc.) +- q to quit diff --git a/mdbrowse.lua b/mdbrowse.lua new file mode 100644 index 0000000..ee92ad2 --- /dev/null +++ b/mdbrowse.lua @@ -0,0 +1,245 @@ +local curses = require "curses" +local md = require "mdparse" +local socket = require "socket" +local surl = require "socket.url" + +local sx,sy,pc = 80, 25, "@" + +local function pgopher(gm) + local s = "" + for line in gm:gmatch("[^\n]+") do + local t = line:sub(1,1) + if t == "i" then + if line:find("\t") then + line=line:sub(1,line:find("\t")) + end + s=s..line:sub(2).."\n" + elseif t == "0" or t == "1" then + line=line:gsub("(.)(.+)\t/?([%w%./]+)\t([%w%.]+)\t(%d+)","%1 [%2](gopher://%4:%5/%3")..")" -- we gsub now + line=line:gsub("\n","") + s=s..line.."\n" + else + s=s..line.."\n" + end + end + return s +end + +local function geturl(url) + local ds = {} + ds.scheme = "gopher" + ds.port = "70" + ds.path = "" + local c = "" + local ourl = url + tURL = surl.parse(url,ds) + if not tURL.host then tURL.host = tURL.path tURL.path = "" end + local dn, po, pt = tURL.host, tURL.port, tURL.path + print(dn,po,pt) + if dn and po then + local s=socket.tcp() + s:connect(dn,po) + s:send(pt.."\n") + c=s:receive("*a") + s:close() + local w,tpg = pcall(pgopher,c) + if w then + print(tpg) + c=tpg + end + else + c="Unable to load URL "..ourl + end +--[[-- + else + local f=io.open(url,"rb") + if not f then + return "\r\nPage could not be loaded." + end + c=f:read("*a") + print(c) + f:close() + end +--]]-- + return c +end + +local function settings() + while true do + local scr = curses.initscr () + + curses.cbreak () + curses.echo (false) -- not noecho ! + curses.nl (false) -- not nonl ! + local selection = 1 + while true do + local menu = {"Screen width: "..tostring(sx),"Screen height: "..tostring(sy),"Back"} + scr:clear() + scr:refresh() + for k,v in ipairs(menu) do + if k == selection then + io.write(" * ") + end + print("\t"..v.."\r") + end + local ch = scr:getch() + if ch == 66 then + selection = selection + 1 + elseif ch == 65 then + selection = selection - 1 + elseif ch == 13 then break + end + if selection > #menu then + selection = #menu + elseif selection < 1 then + selection = 1 + end + end + if selection == 1 then + scr:clear() + scr:refresh() + scr:addstr("New screen width: ") + curses.echo (true) + scr:refresh() + sx = tonumber(scr:getstr()) or 80 + curses.echo (false) + elseif selection == 2 then + scr:clear() + scr:refresh() + scr:addstr("New screen height: ") + curses.echo (true) + scr:refresh() + sy = tonumber(scr:getstr()) or 80 + curses.echo (false) + else + scr:close() + break + end + end +end + +local function main () + local scr = curses.initscr () + local mode = 1 + local p = false + local sc = "#" + local history = {} + + curses.cbreak () + curses.echo(false) -- not noecho ! + curses.nl(false) -- not nonl ! + + cy = 1 + local ts = [[ +**SKS Markdown Gopher Client** + +See the [SKS gopher hole](gopher://shadowkat.net) for more info. + +Use *f* to follow a link, *o* to open a URL directly, *j* and *k* to scroll, and *s* for settings. + +Scrolling test: +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z]] + local url = "homepage" + local lt = {} + while true do + scr:clear() + scr:refresh() + local ds = "" + local dt = {} + lt = md.parse(ts).l + for k,v in ipairs(md.parse(ts)) do +--[[-- + if v.bold then -- note to self: fix bold + v.content = "\27[n1"..v.content.."\27[n0" + end +--]]-- + if v.addrid then + ds=ds.."["..tostring(v.addrid).."]"..v.content + else + ds=ds..v.content + end + end + for line in ds:gmatch("[^\n]*") do + while line:len() > 0 do + dt[#dt+1] = line:sub(1,sx) + line = line:sub(sx+1) + end + end + if sy < #dt then + ms = sy + else + ms = #dt + end + for i = cy, cy+ms do + io.write((dt[i] or "").."\r\n") + end + print("\r\n["..url.." "..tostring(cy).."/"..tostring(#dt).." "..tostring(ts:len()).."]\r") + ch = scr:getch() or 0 + if ch < 256 then + if string.char(ch) == "q" then + break + elseif string.char(ch) == "s" then + settings() + elseif string.char(ch) == "o" then + scr:addstr("\rURL: ") + curses.echo(true) + scr:refresh() + local nurl = scr:getstr() + local w,nts=pcall(geturl,nurl) + --print(w,nts) + if w then ts = nts url=nurl end + local nurl = scr:getstr() + elseif string.char(ch) == "f" then + scr:addstr("\rLink: ") + curses.echo(true) + scr:refresh() + local nurl = lt[tonumber(scr:getstr())] + local w,nts=pcall(geturl,nurl) + if w then ts = nts url=nurl end + scr:refresh() + elseif string.char(ch) == "j" then + if cy < #dt then + cy = cy + 1 + end + elseif string.char(ch) == "k" then + if cy > 1 then + cy = cy - 1 + end + end + end + end +end + +local function err (err) + curses.endwin () + print "Caught an error:" + print (debug.traceback (err, 2)) + os.exit (2) +end + +xpcall (main, err) diff --git a/mdparse.lua b/mdparse.lua new file mode 100644 index 0000000..e6f8299 --- /dev/null +++ b/mdparse.lua @@ -0,0 +1,50 @@ +local md = {} +function md.parse(md) + local it = {} + it.l = {} + it[#it+1] = {["content"]="",["bold"]=false,["italic"]=false} + local lc,llc = "","" + local function newpart() + it[#it+1] = {["content"]="",["bold"]=it[#it].bold,["italic"]=it[#it].italic} + end + newpart() + local lm = false + for c in md:gmatch(".") do + if c == "*" then + if lc == "*" then + it[#it].italic = false + it[#it].italic = it[#it-1].italic + it[#it].bold = not it[#it].bold + else + newpart() + it[#it].italic = not it[#it].italic + end + elseif c == "[" then + newpart() + elseif c == "(" and lc == "]" then + lm = true + it[#it].content = it[#it].content:sub(1,-2) + it[#it].address = "" + elseif c == ")" and lm then + lm = false + it.l[#it.l+1] = it[#it].address + it[#it].addrid = #it.l + newpart() + else + if not lm then + it[#it].content = it[#it].content .. c + else + it[#it].address = it[#it].address .. c + end + end + llc = lc + lc = c + end + for k,v in pairs(it) do + if v.content then + v.content = v.content:gsub("\n","\r\n") + end + end + return it +end +return md