1-- Example hooks.lua file, put in ~/.elinks/ as hooks.lua. 2 3-- TODO: Bookmarks stuff should be completely moved to bm.lua. --pasky 4 5-- Take care about @SOMETHING@, we're processed with autoconf! 6 7---------------------------------------------------------------------- 8-- Compatibility routines for Lua 4 code. 9-- Taken from Lua's compat.lua. 10---------------------------------------------------------------------- 11 12_INPUT = io.stdin 13_OUTPUT = io.stdout 14 15function writeto (name) 16 if name == nil then 17 local f, err, cod = io.close(_OUTPUT) 18 _OUTPUT = io.stdout 19 return f, err, cod 20 else 21 local f, err, cod = io.open(name, "w") 22 _OUTPUT = f or _OUTPUT 23 return f, err, cod 24 end 25end 26 27function write (...) 28 local f = _OUTPUT 29 if type(arg[1]) == 'userdata' then 30 f = table.remove(arg, 1) 31 end 32 return f:write(unpack(arg)) 33end 34 35function read (...) 36 local f = _INPUT 37 if type(arg[1]) == 'userdata' then 38 f = table.remove(arg, 1) 39 end 40 return f:read(unpack(arg)) 41end 42 43function do_ (f, err) 44 if not f then _ALERT(err); return end 45 local a,b = pcall(f) 46 if not a then _ALERT(b); return nil 47 else return b or true 48 end 49end 50 51function dostring(s) return do_(loadstring(s)) end 52function dofile(s) return do_(loadfile(s)) end 53 54---------------------------------------------------------------------- 55-- Load configuration 56---------------------------------------------------------------------- 57 58function file_exists(filename) 59 local f = io.open(filename, "r") 60 if f then 61 io.close(f) 62 return 1 63 end 64 return nil 65end 66 67function dofile_if_fileexists(filename) 68 if file_exists(filename) then dofile(filename) end 69end 70 71dofile_if_fileexists ("@sysconfdir@/config.lua") 72home_dir = (os.getenv ("HOME") or "/home/"..os.getenv("USER")) or "." 73hooks_file = elinks_home.."/hooks.lua" -- for reload() 74dofile_if_fileexists (elinks_home.."/config.lua") 75 76---------------------------------------------------------------------- 77-- hooks 78---------------------------------------------------------------------- 79 80pre_format_html_hooks = {n=0} 81function pre_format_html_hook (url, html) 82 local changed = nil 83 84 table.foreachi(pre_format_html_hooks, 85 function (i, fn) 86 local new,stop = fn(url,html) 87 88 if new then html=new changed=1 end 89 return stop 90 end) 91 92 return changed and html 93end 94 95goto_url_hooks = {n=0} 96function goto_url_hook (url, current_url) 97 table.foreachi(goto_url_hooks, 98 function (i, fn) 99 local new,stop = fn(url, current_url) 100 101 url = new 102 103 return stop 104 end) 105 106 return url 107end 108 109follow_url_hooks = {n=0} 110function follow_url_hook (url) 111 table.foreachi(follow_url_hooks, 112 function (i, fn) 113 local new,stop = fn(url) 114 115 url = new 116 117 return stop 118 end) 119 120 return url 121end 122 123quit_hooks = {n=0} 124function quit_hook (url, html) 125 table.foreachi(quit_hooks, function (i, fn) return fn() end) 126end 127 128---------------------------------------------------------------------- 129-- case-insensitive string.gsub 130---------------------------------------------------------------------- 131 132-- Please note that this is not completely correct yet. 133-- It will not handle pattern classes like %a properly. 134-- FIXME: Handle pattern classes. 135 136function gisub (s, pat, repl, n) 137 pat = string.gsub (pat, '(%a)', 138 function (v) return '['..string.upper(v)..string.lower(v)..']' end) 139 if n then 140 return string.gsub (s, pat, repl, n) 141 else 142 return string.gsub (s, pat, repl) 143 end 144end 145 146 147---------------------------------------------------------------------- 148-- goto_url_hook 149---------------------------------------------------------------------- 150 151function match (prefix, url) 152 return string.sub (url, 1, string.len (prefix)) == prefix 153end 154 155function hx (c) 156 return string.char((c >= 10 and (c - 10) + string.byte ('A')) or c + string.byte ('0')) 157end 158 159function char2hex (c) 160 return '%'..hx (string.byte (c) / 16)..hx (math.mod(string.byte (c), 16)) 161end 162 163function escape (str) 164 return string.gsub (str, "(%W)", char2hex) 165end 166 167-- You can write smt like "gg" to goto URL dialog and it'll go to google.com. 168 169-- Note that this is obsoleted by the URI rewrite plugin. 170 171dumbprefixes = { 172 arc = "http://web.archive.org/web/*/%c", 173 b = "http://babelfish.altavista.com/babelfish/tr", 174 bz = "http://bugzilla.elinks.cz", 175 bug = "http://bugzilla.elinks.cz", 176 d = "http://www.dict.org", 177 g = "http://www.google.com/", 178 gg = "http://www.google.com/", 179 go = "http://www.google.com/", 180 fm = "http://www.freshmeat.net/", 181 sf = "http://www.sourceforge.net/", 182 dbug = "http://bugs.debian.org/", 183 dpkg = "http://packages.debian.org/", 184 -- Hm, is this Debian-centric? -- Miciah 185 lua = "file:///usr/share/doc/lua40-doc/manual/idx.html", 186 pycur = "http://www.python.org/doc/current/", 187 pydev = "http://www.python.org/dev/doc/devel/", 188 pyhelp = "http://starship.python.net/crew/theller/pyhelp.cgi", 189 pyvault = "http://www.vex.net/parnassus/", 190 e2 = "http://www.everything2.org/", 191 sd = "http://www.slashdot.org/", 192 vhtml = "http://validator.w3.org/check?uri=%c", 193 vcss = "http://jigsaw.w3.org/css-validator/validator?uri=%c", 194} 195 196function debian_package (url, t) 197 url = string.gsub(url, '(%w+):(%w+)', function (key, val) t[key] = val end) 198 199 return 'http://packages.debian.org/cgi-bin/search_contents.pl?word=' 200 ..escape(string.gsub(url, '%s*([^%s]+)%s*', '%1')) 201 ..'&searchmode='..(t.searchmode or 'searchfilesanddirs') 202 ..'&case='..(t.case or 'insensitive') 203 ..'&version='..(t.version or pipe_read('test -r /etc/debian_version && cut -d/ -f1 /etc/debian_version | tr -d \\\\n || echo stable') or 'stable') 204 ..'&arch='..(t.arch or pipe_read('test -x /usr/bin/dpkg-architecture && dpkg-architecture -qDEB_HOST_ARCH | tr -d \\\\n || echo i386')) 205end 206 207function debian_contents (url) 208 return debian_package (url, { searchmode = "filelist" }) 209end 210 211function debian_file (url) 212 return debian_package (url, { searchmode = "searchfilesanddirs" }) 213end 214 215function cvsweb (base, project, url) 216 local t = {n=0} 217 local file, old, new 218 local replacements 219 220 -- allow <file>:<revision>[-><revision>] 221 url,replacements = string.gsub(url, "^(.*):(.*)->(.*)$", "%1 %2 %3") 222 if replacements == 0 then url = string.gsub(url, "^(.*):(.*)$", "%1 %2") end 223 224 -- split into words 225 string.gsub(url, "([^%s]+)", function (w) table.insert(t, w) end) 226 file, old, new = t[1], t[2], t[3] 227 228 if t[4] then error('this smartprefix takes only one to three arguments') return nil end 229 if not file then error('no file given') return nil end 230 231 if new then return base..project.."/"..file..".diff?r1="..old.."&r2="..new.."&f=u" 232 elseif old then return base.."~checkout~/"..project.."/"..file..(old ~= "latest" and "?rev="..old or "") 233 else return base..project.."/"..file 234 end 235end 236 237function gmane (url) 238 local group, words 239 240 _,_,group,words = string.find(url, "([^%s]+)%s%s*(.*)$") 241 242 if not words then return nil end 243 244 return "http://search.gmane.org/search.php?query="..words.."&group="..group 245end 246 247-- You can write "gg:foo" or "gg foo" to goto URL dialog and it'll ask google 248-- for it automagically. 249 250-- Note that this is _mostly_ obsoleted by the URI rewrite plugin. (It can't do the 251-- metas, though.) 252 253function bugzilla (base_url, arguments) 254 if not arguments or arguments == '' then return base_url end 255 256 if string.find(arguments, '^[%d]+$') then 257 return base_url..'show_bug.cgi?id='..arguments 258 end 259 260 return base_url..'buglist.cgi?short_desc_type=allwordssubstr' 261 ..'&short_desc='..escape(arguments) 262end 263 264smartprefixes = { 265 arc = "http://web.archive.org/web/*/%s", 266 bug = function (url) return bugzilla('http://bugzilla.elinks.cz/', url) end, 267 cambridge = "http://dictionary.cambridge.org/results.asp?searchword=%s", 268 cliki = "http://www.cliki.net/admin/search?words=%s", 269 -- If you want to add a smartprefix for another project's CVSweb, 270 -- just create a lambda like this. Aren't high-level languages fun? 271 cvs = function (x) return cvsweb ("http://cvsweb.elinks.cz/cvsweb.cgi/", "elinks", x) end, 272 d = "http://www.dict.org/bin/Dict?Query=%s&Form=Dict1&Strategy=*&Database=*&submit=Submit+query", 273 debcontents = debian_contents, 274 debfile = debian_file, 275 dmoz = "http://search.dmoz.org/cgi-bin/search?search=%s", 276 foldoc = "http://wombat.doc.ic.ac.uk/foldoc/foldoc.cgi?%s", 277 g = "http://www.google.com/search?q=%s&btnG=Google+Search", 278 gd = "http://www.google.com/search?q=%s&cat=gwd/Top", 279 gg = "http://www.google.com/search?q=%s&btnG=Google+Search", 280 -- Whose idea was it to use 'gg' for websearches? -- Miciah 281 --gg = "http://groups.google.com/groups?q=%s", 282 gi = "http://images.google.com/images?q=%s", 283 gmane = gmane, 284 gn = "http://news.google.com/news?q=%s", 285 go = "http://www.google.com/search?q=%s&btnG=Google+Search", 286 gwho = "http://www.googlism.com/?ism=%s&name=1", 287 gwhat = "http://www.googlism.com/?ism=%s&name=2", 288 gwhere = "http://www.googlism.com/?ism=%s&name=3", 289 gwhen = "http://www.googlism.com/?ism=%s&name=4", 290 fm = "http://www.freshmeat.net/search/?q=%s", 291 savannah = "http://savannah.nongnu.org/search/?words=%s&type_of_search=soft&exact=1", 292 sf = "http://sourceforge.net/search/?q=%s", 293 sfp = "http://sourceforge.net/projects/%s", 294 sd = "http://www.slashdot.org/search.pl?query=%s", 295 sdc = "http://www.slashdot.org/search.pl?query=%s&op=comments", 296 sdu = "http://www.slashdot.org/search.pl?query=%s&op=users", 297 sdp = "http://www.slashdot.org/search.pl?query=%s&op=polls", 298 sdj = "http://www.slashdot.org/search.pl?query=%s&op=journals", 299 dbug = "http://bugs.debian.org/%s", 300 dpkg = "http://packages.debian.org/%s", 301 emacs = "http://www.emacswiki.org/cgi-bin/wiki.pl?search=%s", 302 lyrics = "http://music.lycos.com/lyrics/results.asp?QT=L&QW=%s", 303 lxr = "http://lxr.linux.no/ident?i=%s", 304 leo = "http://dict.leo.org/?search=%s", 305 onelook = "http://onelook.com/?w=%s&ls=a", 306 py = "http://starship.python.net/crew/theller/pyhelp.cgi?keyword=%s&version=current", 307 pydev = "http://starship.python.net/crew/theller/pyhelp.cgi?keyword=%s&version=devel", 308 pyvault = "http://py.vaults.ca/apyllo.py?find=%s", 309 e2 = "http://www.everything2.org/?node=%s", 310 encz = "http://www.slovnik.cz/bin/ecd?ecd_il=1&ecd_vcb=%s&ecd_trn=translate&ecd_trn_dir=0&ecd_lines=15&ecd_hptxt=0", 311 czen = "http://www.slovnik.cz/bin/ecd?ecd_il=1&ecd_vcb=%s&ecd_trn=translate&ecd_trn_dir=1&ecd_lines=15&ecd_hptxt=0", 312 dict = "http://dictionary.reference.com/search?q=%s", 313 thes = "http://thesaurus.reference.com/search?q=%s", 314 a = "http://acronymfinder.com/af-query.asp?String=exact&Acronym=%s", 315 imdb = "http://imdb.com/Find?%s", 316 mw = "http://www.m-w.com/cgi-bin/dictionary?book=Dictionary&va=%s", 317 mwt = "http://www.m-w.com/cgi-bin/thesaurus?book=Thesaurus&va=%s", 318 whatis = "http://uptime.netcraft.com/up/graph/?host=%s", 319 wiki = "http://www.wikipedia.org/w/wiki.phtml?search=%s", 320 wn = "http://www.cogsci.princeton.edu/cgi-bin/webwn1.7.1?stage=1&word=%s", 321 -- rfc by number 322 rfc = "http://www.rfc-editor.org/rfc/rfc%s.txt", 323 -- rfc search 324 rfcs = "http://www.rfc-editor.org/cgi-bin/rfcsearch.pl?searchwords=%s&format=http&abstract=abson&keywords=keyon&num=25", 325 cr = "http://www.rfc-editor.org/cgi-bin/rfcsearch.pl?searchwords=%s&format=http&abstract=abson&keywords=keyon&num=25", 326 -- Internet Draft search 327 rfcid = "http://www.rfc-editor.org/cgi-bin/idsearch.pl?searchwords=%s&format=http&abstract=abson&keywords=keyon&num=25", 328 urbandict = "http://www.urbandictionary.com/define.php?term=%s", 329 id = "http://www.rfc-editor.org/cgi-bin/idsearch.pl?searchwords=%s&format=http&abstract=abson&keywords=keyon&num=25", 330 draft = "http://www.rfc-editor.org/cgi-bin/idsearch.pl?searchwords=%s&format=http&abstract=abson&keywords=keyon&num=25", 331} 332 333-- Expand ~ to home directories. 334function expand_tilde (url, current_url) 335 if not match ("~", url) then return url,nil end 336 337 if string.sub(url, 2, 2) == "/" or string.len(url) == 1 then -- ~/foo 338 return home_dir..string.sub(url, 2),true 339 else -- ~foo/bar 340 return "/home/"..string.sub(url, 2),true 341 end 342end 343table.insert(goto_url_hooks, expand_tilde) 344 345 346-- Don't take localhost as directory name 347function expand_localhost (url) 348 if not match("localhost", url) then return url,nil end 349 350 return "http://"..url,nil 351end 352table.insert(goto_url_hooks, expand_localhost) 353 354 355function complete_uri_prefix (url, current_url) 356 if dumbprefixes[url] then 357 return string.gsub(dumbprefixes[url], "%%c", current_url or ""),true 358 end 359 360 if string.find(url,'%s') or string.find(url, ':') then 361 local _,_,nick,val = string.find(url, "^([^%s:]+)[:%s]%s*(.-)%s*$") 362 if nick and smartprefixes[nick] then 363 if type(smartprefixes[nick]) == 'function' then 364 return smartprefixes[nick](val),true 365 elseif type(smartprefixes[nick]) == 'string' then 366 return string.format(smartprefixes[nick], escape(val)),true 367 else 368 error('smartprefix "'..nick..'" has unsupported type "' 369 ..type(smartprefixes[nick])..'".') 370 return url,nil 371 end 372 end 373 end 374 375 -- Unmatched. 376 return url,nil 377end 378table.insert(goto_url_hooks, complete_uri_prefix) 379 380 381---------------------------------------------------------------------- 382-- pre_format_html_hook 383---------------------------------------------------------------------- 384 385-- Plain string.find (no metacharacters). 386function sstrfind (s, pattern) 387 return string.find (s, pattern, 1, 1) 388end 389 390-- Mangle ALT="" in IMG tags. 391function mangle_blank_alt (url, html) 392 local n 393 394 if not mangle_blank_alt then return nil,nil end 395 396 html, n = gisub (html, '(<img[^>]-) alt=""', '%1 alt=" "') 397 398 return ((n > 0) and html), nil 399end 400table.insert(pre_format_html_hooks, mangle_blank_alt) 401 402 403-- Fix unclosed INPUT tags. 404function mangle_unclosed_input_tags (url, html) 405 local n 406 407 html, n = gisub (html, '(<input[^>]-[^=]")<', '%1><') 408 409 return ((n > 0) and html), nil 410end 411table.insert(pre_format_html_hooks, mangle_unclosed_input_tags) 412 413 414-- Fix unclosed A tags. 415function mangle_unclosed_a_tags (url, html) 416 local n 417 418 html, n = gisub (html, '(<a[^>]-[^=]")<', '%1><') 419 420 return ((n > 0) and html), nil 421end 422table.insert(pre_format_html_hooks, mangle_unclosed_a_tags) 423 424 425function mangle_linuxtoday (url, html) 426 if not sstrfind (url, "linuxtoday.com") then return nil,nil end 427 428 if sstrfind (url, "news_story") then 429 html = string.gsub (html, '<TABLE CELLSPACING="0".-</TABLE>', '', 1) 430 html = string.gsub (html, '<TR BGCOLOR="#FFF.-</TR></TABLE>', '', 1) 431 else 432 html = string.gsub (html, 'WIDTH="120">\n<TR.+</TABLE></TD>', '>', 1) 433 end 434 html = string.gsub (html, '<A HREF="http://www.internet.com.-</A>', '') 435 html = string.gsub (html, "<IFRAME.-</IFRAME>", "") 436 -- emphasis in text is lost 437 html = string.gsub (html, 'text="#002244"', 'text="#001133"', 1) 438 439 return html,true 440end 441table.insert(pre_format_html_hooks, mangle_linuxtoday) 442 443 444function mangle_dictionary_dot_com (url, html) 445 local t = { t = "" } 446 local n 447 448 if not sstrfind (url, "dictionary.com/cgi-bin/dict.pl") then return nil,nil end 449 450 _,n = string.gsub (html, "resultItemStart %-%-%>(.-)%<%!%-%- resultItemEnd", 451 function (x) t.t = t.t.."<tr><td>"..x.."</td></tr>" end) 452 if n == 0 then 453 -- we've already mangled this page before 454 return nil,true 455 end 456 457 html = "<html><head><title>Dictionary.com lookup</title></head>".. 458 "<body><table border=0 cellpadding=5>"..t.t.."</table>".. 459 "</body></html>" 460 461 return html,true 462end 463table.insert(pre_format_html_hooks, mangle_dictionary_dot_com) 464 465 466function mangle_allmusic_dot_com (url, html) 467 if not sstrfind (url, "allmusic.com") then return nil,nil end 468 469 html = string.gsub(html, "javascript:z%('(.-)'%)", "/cg/amg.dll?p=amg&sql=%1") 470 471 return html,true 472end 473table.insert(pre_format_html_hooks, mangle_allmusic_dot_com) 474 475 476-- Handle gzip'd files within reasonable size. 477-- Note that this is not needed anymore since we have a support for this 478-- in core ELinks. I still keep it here for a reference (as an example), 479-- though. If you will add something similiar using pipe_read(), feel free 480-- to remove this. --pasky 481function decompress_html (url, html) 482 local tmp 483 484 if not string.find (url, "%.gz$") or string.len (html) >= 65536 then 485 return nil,nil 486 end 487 488 tmp = tmpname () 489 writeto (tmp) write (html) writeto () 490 html = pipe_read ("(gzip -dc "..tmp.." || cat "..tmp..") 2>/dev/null") 491 os.remove (tmp) 492 493 return html,nil 494end 495--table.insert(pre_format_html_hooks, decompress_html) 496 497---------------------------------------------------------------------- 498-- Miscellaneous functions, accessed with the Lua Console. 499---------------------------------------------------------------------- 500 501-- Reload this file (hooks.lua) from within Links. 502function reload () 503 dofile (hooks_file) 504end 505 506-- Helper function. 507function catto (output) 508 local doc = current_document_formatted (79) 509 if doc then writeto (output) write (doc) writeto () end 510end 511 512-- Email the current document, using Mutt (http://www.mutt.org). 513-- This only works when called from lua_console_hook, below. 514function mutt () 515 local tmp = tmpname () 516 writeto (tmp) write (current_document ()) writeto () 517 table.insert (tmp_files, tmp) 518 return "run", "mutt -a "..tmp 519end 520 521-- Table of expressions which are recognised by our lua_console_hook. 522console_hook_functions = { 523 reload = "reload ()", 524 mutt = mutt, 525} 526 527function lua_console_hook (expr) 528 local x = console_hook_functions[expr] 529 if type (x) == "function" then 530 return x () 531 else 532 return "eval", x or expr 533 end 534end 535 536 537---------------------------------------------------------------------- 538-- quit_hook 539---------------------------------------------------------------------- 540 541-- We need to delete the temporary files that we create. 542if not tmp_files then 543 tmp_files = {} 544end 545 546function delete_tmp_files () 547 if tmp_files and os.remove then 548 tmp_files.n = nil 549 for i,v in tmp_files do os.remove (v) end 550 end 551end 552table.insert(quit_hooks, delete_tmp_files) 553 554 555---------------------------------------------------------------------- 556-- Examples of keybinding 557---------------------------------------------------------------------- 558 559-- Bind Ctrl-H to a "Home" page. 560 561-- bind_key ("main", "Ctrl-H", 562-- function () return "goto_url", "http://www.google.com/" end) 563 564-- Bind Alt-p to print. 565 566-- bind_key ("main", "Alt-p", lpr) 567 568-- Bind Alt-m to toggle ALT="" mangling. 569 570 bind_key ("main", "Alt-m", 571 function () mangle_blank_alt = not mangle_blank_alt end) 572 573 574-- vim: shiftwidth=4 softtabstop=4 575