keywords.js: restyle to eliminate strict mode warnings
[conkeror.git] / modules / index-webjump.js
blobc8c59a502dff4dbfa4f3c9ac5a9dd135231cc38b
1 /**
2  * (C) Copyright 2009 David Kettler
3  * (C) Copyright 2012 John J. Foerch
4  *
5  * Use, modification, and distribution are subject to the terms specified in the
6  * COPYING file.
7  *
8  * Construct a webjump (with completer) to visit URLs referenced from
9  * an index page.  An xpath expression is used to extract the indexed
10  * URLs.  A specialized form is also provided for gitweb summary
11  * pages.
12 **/
14 require("webjump.js");
16 define_variable("index_webjumps_directory", null,
17     "A directory (instance of nsILocalFile) for storing the " +
18     "index files corresponding to index webjumps; the index " +
19     "data can be downloaded from the index URL using " +
20     "webjump-get-index.  " +
21     "If the index file is available for an index webjump then " +
22     "the webjump will provide completions for the indexed URLs.");
24 define_variable("index_xpath_webjump_tidy_command",
25                 "tidy -asxhtml -wrap 0 -numeric --clean yes" +
26                 " -modify -quiet --show-warnings no",
27     "A command to run on the downloaded index.  The xulrunner " +
28     "parser is quite fussy and specifically requires xhtml (or " +
29     "other xml).  Running something like html tidy can avoid " +
30     "parser problems.");
32 /**
33  * Try to make a suitable file object when the supplied file is a string
34  * or null.
35  */
36 function index_webjump_canonicalize_file (file, webjump_name) {
37     if (typeof file == "string")
38         return make_file(file);
39     if (! file && index_webjumps_directory) {
40         file = index_webjumps_directory.clone()
41             .QueryInterface(Ci.nsILocalFile);
42         file.appendRelativePath(webjump_name + ".index");
43     }
44     return file;
48 function index_webjump_completions (completer, data, descriptions) {
49     completions.call(this, completer, data);
50     this.descriptions = descriptions;
52 index_webjump_completions.prototype = {
53     constructor: index_webjump_completions,
54     __proto__: completions.prototype,
55     toString: function () "#<index_webjump_completions>",
56     descriptions: null,
57     get_description: function (i) this.descriptions[i]
61 function index_webjump_completer (webjump) {
62     completer.call(this);
63     this.webjump = webjump;
65 index_webjump_completer.prototype = {
66     constructor: index_webjump_completer,
67     __proto__: completer.prototype,
68     toString: function () "#<index_webjump_completer>",
69     complete: function (input, pos) {
70         var require = this.webjump.require_match;
72         /* Update full completion list if necessary. */
73         if (require && ! this.webjump.file.exists())
74             throw interactive_error("Index file missing for " + this.webjump.name);
75         if (this.webjump.file.exists() &&
76             this.webjump.file.lastModifiedTime > this.webjump.file_time)
77         {
78             this.webjump.file_time = this.webjump.file.lastModifiedTime;
79             this.webjump.extract_completions();
80         }
81         if (require && !(this.webjump.completions && this.webjump.completions.length))
82             throw interactive_error("No completions for " + this.webjump.name);
83         if (! this.webjump.completions)
84             yield co_return(null);
86         /* Match completions against input. */
87         var words = trim_whitespace(input.toLowerCase()).split(/\s+/);
88         var data = this.webjump.completions.filter(function (x) {
89             for (var i = 0; i < words.length; ++i) {
90                 if (x[0].toLowerCase().indexOf(words[i]) == -1 &&
91                     x[1].toLowerCase().indexOf(words[i]) == -1)
92                 {
93                     return false;
94                 }
95             }
96             return true;
97         });
98         var descriptions = data.map(second);
99         data = data.map(first);
100         yield co_return(new index_webjump_completions(this, data, descriptions));
101     }
105 function index_webjump (name, url, file) {
106     keywords(arguments);
107     this.url = url;
108     this.file = index_webjump_canonicalize_file(file, name);
109     webjump.call(this, name, this.make_handler(),
110                  $completer = this.make_completer(),
111                  forward_keywords(arguments));
112     if (this.require_match && ! this.file)
113         throw interactive_error("Index file not defined for " + this.name);
115 index_webjump.prototype = {
116     constructor: index_webjump,
117     __proto__: webjump.prototype,
118     toString: function () "#<index_webjump>",
119     mime_type: null,
120     xpath_expr: null,
121     make_completion: null,
122     completions: null,
123     file_time: 0,
124     tidy_command: null,
125     make_handler: function () {
126         throw new Error("Subclasses of index_webjump must implement make_handler.");
127     },
129     /* Extract full completion list from index file. */
130     extract_completions: function () {
131         /* Parse the index file. */
132         var stream = Cc["@mozilla.org/network/file-input-stream;1"]
133             .createInstance(Ci.nsIFileInputStream);
134         stream.init(this.file, MODE_RDONLY, 0644, false);
135         var parser = Cc["@mozilla.org/xmlextras/domparser;1"]
136             .createInstance(Ci.nsIDOMParser);
137         // todo: catch parser errors
138         var doc = parser.parseFromStream(stream, null,
139                                          this.file.fileSize, this.mime_type);
141         /* Extract the completion items. */
142         var cmpl = [], node;
143         var res = doc.evaluate(
144             this.xpath_expr, doc, xpath_lookup_namespace,
145             Ci.nsIDOMXPathResult.UNORDERED_NODE_ITERATOR_TYPE, null);
146         while ((node = res.iterateNext()))
147             cmpl.push(this.make_completion(node));
149         cmpl.sort(function(a, b) {
150             if (a[1] < b[1])  return -1;
151             if (a[1] > b[1])  return 1;
152             if (a[0] < b[0])  return -1;
153             if (a[0] > b[0])  return 1;
154             return 0;
155         });
157         this.completions = cmpl;
158     },
160     /* A completer suitable for supplying to define_webjump. */
161     make_completer: function () {
162         if (! this.file)
163             return null;
164         return new index_webjump_completer(this);
165     },
167     /* Fetch and save the index for later use with completion.
168      * (buffer is used only to associate with the download) */
169     get_index: function (buffer) {
170         if (! this.file)
171             throw interactive_error("Index file not defined for " + this.name);
173         var info = save_uri(load_spec(this.url), this.file,
174                             $buffer = buffer, $use_cache = false,
175                             $temp_file = true);
177         // Note: it would be better to run this before the temp file
178         // is renamed; that requires support in save_uri.
179         if (this.tidy_command)
180             info.set_shell_command(this.tidy_command, index_webjumps_directory);
181     }
185 function index_webjump_xhtml (name, url, file, xpath_expr) {
186     keywords(arguments);
187     index_webjump.call(this, name, url, file,
188                        $require_match = true,
189                        forward_keywords(arguments));
190     this.xpath_expr = xpath_expr;
192 index_webjump_xhtml.prototype = {
193     constructor: index_webjump_xhtml,
194     __proto__: index_webjump.prototype,
195     toString: function () "#<index_webjump_xhtml>",
196     mime_type: "application/xhtml+xml",
197     get tidy_command () index_xpath_webjump_tidy_command,
199     make_completion: function (node) {
200         return [makeURLAbsolute(this.url, node.href), node.text];
201     },
203     make_handler: function () {
204         let jmp = this;
205         return function (term) {
206             if (!(jmp.completions && jmp.completions.length))
207                 throw interactive_error("Completions required for " + this.name);
208             return term;
209         };
210     }
214 define_keywords("$default");
215 function index_webjump_gitweb (name, base_url, file) {
216     keywords(arguments);
217     var alternative = arguments.$alternative;
218     var gitweb_url = base_url + "/gitweb.cgi";
219     this.summary_url = gitweb_url + "?p=%s.git;a=summary";
220     var opml_url = gitweb_url + "?a=opml";
221     if (arguments.$default)
222         alternative = this.summary_url.replace("%s", arguments.$default);
223     if (! alternative)
224         alternative = gitweb_url;
225     index_webjump.call(this, name, opml_url, file,
226                        $alternative = alternative,
227                        forward_keywords(arguments));
229 index_webjump_gitweb.prototype = {
230     constructor: index_webjump_gitweb,
231     __proto__: index_webjump.prototype,
232     toString: function () "#<index_webjump_gitweb>",
233     summary_url: null,
234     mime_type: "text/xml",
235     xpath_expr: '//outline[@type="rss"]',
236     make_completion: function (node) {
237         var name = node.getAttribute("text");
238         return [name.replace(/\.git$/, ""), ""];
239     },
240     make_handler: function () {
241         return this.summary_url;
242     }
246 interactive("webjump-get-index",
247     "Fetch and save the index URL corresponding to an index " +
248     "webjump.  It will then be available to the completer.",
249     function (I) {
250         var completions = [];
251         for (let [name, w] in Iterator(webjumps)) {
252             if (w instanceof index_webjump)
253                 completions.push(name);
254         }
255         completions.sort();
256         var name = yield I.minibuffer.read(
257             $prompt = "Fetch index for index webjump:",
258             $history = "index-webjump",
259             $completer = new all_word_completer(
260                 $completions = completions),
261             $require_match = true);
262         var jmp = webjumps[name];
263         if (jmp)
264             jmp.get_index(I.buffer);
265     });
268  * Construct a webjump to visit URLs referenced from an index page.
270  * The index page must be able to be parsed as xhtml.  The anchor
271  * nodes indexed are those that match the given xpath_expr.  Don't
272  * forget to use xhtml: prefixes on the xpath steps.
274  * If an alternative is not specified then it is set to the index page.
276  * A completer is provided that uses the index page.  A local file for
277  * the index must be specified either with $index_file or via
278  * index_webjumps_directory.  The index must be manually downloaded;
279  * eg. using webjump-get-index.  Each time the completer is used it
280  * will check if the file has been updated and reload if necessary.
281  * This kind of webjump is not useful without the completions.
282  */
283 define_keywords("$index_file");
284 function define_xpath_webjump (name, index_url, xpath_expr) {
285     keywords(arguments);
286     var alternative = arguments.$alternative || index_url;
287     var w = new index_webjump_xhtml(name, index_url,
288                                     arguments.$index_file,
289                                     xpath_expr,
290                                     $alternative = alternative,
291                                     forward_keywords(arguments));
292     webjumps[w.name] = w;
296  * Modify the xpath for an index webjump and show the resulting
297  * completions.  Useful for figuring out an appropriate xpath.  Either
298  * run using mozrepl or eval in the browser with the dump parameter
299  * set.
300  */
301 function index_webjump_try_xpath (name, xpath_expr, dump) {
302     var jmp = webjumps[name];
303     if (!(jmp instanceof index_webjump))
304         throw new Error(name + " is not an index_webjump");
305     if (xpath_expr)
306         jmp.xpath_expr = xpath_expr;
307     jmp.extract_completions();
308     if (dump)
309         dumpln(dump_obj(jmp.completions,
310                         "Completions for index webjump " + name));
311     return jmp.completions;
316  * Construct a webjump to visit repository summary pages at a gitweb
317  * server.
319  * If a repository name is supplied as $default then the alternative
320  * url is set to that repository at the gitweb site.  If an
321  * alternative is not specified by either $default or $alternative
322  * then it is set to the repository list page of the gitweb site.
324  * A completer is provided that uses the list of repositories from the
325  * OPML data on the gitweb server.  The completer is setup in the same
326  * way as for define_xpath_webjump, but the webjump will work without
327  * the completions.
328  */
329 define_keywords("$opml_file");
330 function define_gitweb_summary_webjump (name, base_url) {
331     keywords(arguments);
332     var w = new index_webjump_gitweb(name, base_url, arguments.$opml_file,
333                                      forward_keywords(arguments));
334     webjumps[w.name] = w;
337 provide("index-webjump");