2 * (C) Copyright 2009 David Kettler
4 * Use, modification, and distribution are subject to the terms specified in the
7 * Construct a webjump (with completer) to visit URLs referenced from
8 * an index page. An xpath expression is used to extract the indexed
9 * URLs. A specialized form is also provided for gitweb summary
13 require("webjump.js");
15 /* Objects with completion data for index webjumps. */
18 define_variable("index_webjumps_directory", null,
19 "A directory (instance of nsILocalFile) for storing the " +
20 "index files corresponding to index webjumps; the index " +
21 "data can be downloaded from the index URL using " +
22 "webjump-get-index. " +
23 "If the index file is available for an index webjump then " +
24 "the webjump will provide completions for the indexed URLs.");
26 define_variable("index_xpath_webjump_tidy_command",
27 "tidy -asxhtml -wrap 0 -numeric --clean yes" +
28 " -modify -quiet --show-warnings no",
29 "A command to run on the downloaded index. The xulrunner " +
30 "parser is quite fussy and specifically requires xhtml (or " +
31 "other xml). Running something like html tidy can avoid " +
34 function index_webjump (key, url, file) {
37 this.file = this.canonicalize_file(file);
38 if (this.require_completions && ! this.file)
39 throw interactive_error("Index file not defined for " + this.key);
41 index_webjump.prototype = {
42 constructor: index_webjump,
43 toString: function () "#<index_webjump>",
46 make_completion: null,
47 require_completions: false,
52 /* Extract full completion list from index file. */
53 extract_completions: function () {
54 /* Parse the index file. */
55 var stream = Cc["@mozilla.org/network/file-input-stream;1"]
56 .createInstance(Ci.nsIFileInputStream);
57 stream.init(this.file, MODE_RDONLY, 0644, false);
58 var parser = Cc["@mozilla.org/xmlextras/domparser;1"]
59 .createInstance(Ci.nsIDOMParser);
60 // todo: catch parser errors
61 var doc = parser.parseFromStream(stream, null,
62 this.file.fileSize, this.mime_type);
64 /* Extract the completion items. */
65 var cmpl = [], node, res;
67 this.xpath_expr, doc, xpath_lookup_namespace,
68 Ci.nsIDOMXPathResult.UNORDERED_NODE_ITERATOR_TYPE, null);
69 while ((node = res.iterateNext()))
70 cmpl.push(this.make_completion(node));
72 cmpl.sort(function(a, b) {
73 if (a[1] < b[1]) return -1;
74 if (a[1] > b[1]) return 1;
75 if (a[0] < b[0]) return -1;
76 if (a[0] > b[0]) return 1;
80 this.completions = cmpl;
83 /* The guts of the completer. */
84 internal_completer: function (input, pos, conservative) {
85 if (pos == 0 && conservative)
86 yield co_return(undefined);
88 let require = this.require_completions;
90 /* Update full completion list if necessary. */
91 if (require && ! this.file.exists())
92 throw interactive_error("Index file missing for " + this.key);
93 if (this.file.exists() &&
94 this.file.lastModifiedTime > this.file_time)
96 this.file_time = this.file.lastModifiedTime;
97 this.extract_completions();
99 if (require && !(this.completions && this.completions.length))
100 throw interactive_error("No completions for " + this.key);
101 if (! this.completions)
102 yield co_return(null);
104 /* Match completions against input. */
105 let words = trim_whitespace(input.toLowerCase()).split(/\s+/);
106 let data = this.completions.filter(function (x) {
107 for (var i = 0; i < words.length; ++i) {
108 if (x[0].toLowerCase().indexOf(words[i]) == -1 &&
109 x[1].toLowerCase().indexOf(words[i]) == -1)
117 let c = { count: data.length,
118 get_string: function (i) data[i][0],
119 get_description: function (i) data[i][1],
120 get_input_state: function (i) [data[i][0]],
121 get_match_required: function() require
126 /* A completer suitable for supplying to define_webjump. */
127 make_completer: function () {
131 return function (input, pos, conservative) {
132 return jmp.internal_completer(input, pos, conservative);
136 /* Fetch and save the index for later use with completion.
137 * (buffer is used only to associate with the download) */
138 get_index: function (buffer) {
140 throw interactive_error("Index file not defined for " + this.key);
142 var info = save_uri(load_spec(this.url), this.file,
143 $buffer = buffer, $use_cache = false,
146 // Note: it would be better to run this before the temp file
147 // is renamed; that requires support in save_uri.
148 if (this.tidy_command)
149 info.set_shell_command(this.tidy_command, index_webjumps_directory);
152 /* Try to make a suitable file object when the supplied file is a
154 canonicalize_file: function (file) {
155 if (typeof file == 'string')
156 file = make_file(file);
157 if (! file && index_webjumps_directory) {
158 file = Cc["@mozilla.org/file/local;1"]
159 .createInstance(Ci.nsILocalFile);
160 file.initWithFile(index_webjumps_directory);
161 file.appendRelativePath(this.key + ".index");
168 function index_webjump_xhtml (key, url, file, xpath_expr) {
169 index_webjump.call(this, key, url, file);
170 this.xpath_expr = xpath_expr;
172 index_webjump_xhtml.prototype = {
173 constructor: index_webjump_xhtml,
174 __proto__: index_webjump.prototype,
175 toString: function () "#<index_webjump_xhtml>",
176 require_completions: true,
177 mime_type: "application/xhtml+xml",
178 tidy_command: index_xpath_webjump_tidy_command,
180 make_completion: function (node) {
181 return [makeURLAbsolute(this.url, node.href), node.text];
184 make_handler: function () {
186 return function (term) {
187 if (!(jmp.completions && jmp.completions.length))
188 throw interactive_error("Completions required for " + this.key);
195 function index_webjump_gitweb (key, url, file) {
196 index_webjump.call(this, key, url, file);
198 index_webjump_gitweb.prototype = {
199 constructor: index_webjump_gitweb,
200 __proto__: index_webjump.prototype,
201 toString: function () "#<index_webjump_gitweb>",
202 mime_type: "text/xml",
203 xpath_expr: '//outline[@type="rss"]',
204 make_completion: function (node) {
205 var name = node.getAttribute("text");
206 return [name.replace(/\.git$/, ""), ""];
211 interactive("webjump-get-index",
212 "Fetch and save the index URL corresponding to an index " +
213 "webjump. It will then be available to the completer.",
215 var completions = [];
216 for (let i in index_webjumps)
219 var key = yield I.minibuffer.read(
220 $prompt = "Fetch index for index webjump:",
221 $history = "webjump",
222 $completer = all_word_completer(
223 $completions = completions),
224 $match_required = true);
225 var jmp = index_webjumps[key];
227 jmp.get_index(I.buffer);
231 * Construct a webjump to visit URLs referenced from an index page.
233 * The index page must be able to be parsed as xhtml. The anchor
234 * nodes indexed are those that match the given xpath_expr. Don't
235 * forget to use xhtml: prefixes on the xpath steps.
237 * If an alternative is not specified then it is set to the index page.
239 * A completer is provided that uses the index page. A local file for
240 * the index must be specified either with $index_file or via
241 * index_webjumps_directory. The index must be manually downloaded;
242 * eg. using webjump-get-index. Each time the completer is used it
243 * will check if the file has been updated and reload if necessary.
244 * This kind of webjump is not useful without the completions.
246 define_keywords("$alternative", "$index_file", "$description");
247 function define_xpath_webjump (key, index_url, xpath_expr) {
249 let alternative = arguments.$alternative || index_url;
250 var jmp = new index_webjump_xhtml(key, index_url, arguments.$index_file,
252 index_webjumps[key] = jmp;
253 define_webjump(key, jmp.make_handler(),
254 $completer = jmp.make_completer(),
255 $alternative = alternative,
256 $description = arguments.$description);
260 * Modify the xpath for an index webjump and show the resulting
261 * completions. Useful for figuring out an appropriate xpath. Either
262 * run using mozrepl or eval in the browser with the dump parameter
265 function index_webjump_try_xpath (key, xpath_expr, dump) {
266 jmp = index_webjumps[key];
268 jmp.xpath_expr = xpath_expr;
269 jmp.extract_completions();
271 dumpln(dump_obj(jmp.completions,
272 "Completions for index webjump " + key));
273 return jmp.completions;
278 * Construct a webjump to visit repository summary pages at a gitweb
281 * If a repository name is supplied as $default then the alternative
282 * url is set to that repository at the gitweb site. If an
283 * alternative is not specified by either $default or $alternative
284 * then it is set to the repository list page of the gitweb site.
286 * A completer is provided that uses the list of repositories from the
287 * OPML data on the gitweb server. The completer is setup in the same
288 * way as for define_xpath_webjump, but the webjump will work without
291 define_keywords("$default", "$alternative", "$opml_file", "$description");
292 function define_gitweb_summary_webjump (key, base_url) {
294 let alternative = arguments.$alternative;
295 let gitweb_url = base_url + "/gitweb.cgi";
296 let summary_url = gitweb_url + "?p=%s.git;a=summary";
297 let opml_url = gitweb_url + "?a=opml";
299 if (arguments.$default)
300 alternative = summary_url.replace("%s", arguments.$default);
302 alternative = gitweb_url;
304 var jmp = new index_webjump_gitweb(key, opml_url, arguments.$opml_file);
305 index_webjumps[key] = jmp;
307 define_webjump(key, summary_url,
308 $completer = jmp.make_completer(),
309 $alternative = alternative,
310 $description = arguments.$description);
313 provide("index-webjump");