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="&nbsp;"')
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