2 * (C) Copyright 2009 David Kettler
3 * (C) Copyright 2012 John J. Foerch
5 * Use, modification, and distribution are subject to the terms specified in the
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
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 " +
33 * Try to make a suitable file object when the supplied file is a string
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");
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>",
57 get_description: function (i) this.descriptions[i]
61 function index_webjump_completer (webjump) {
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)
78 this.webjump.file_time = this.webjump.file.lastModifiedTime;
79 this.webjump.extract_completions();
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)
98 var descriptions = data.map(second);
99 data = data.map(first);
100 yield co_return(new index_webjump_completions(this, data, descriptions));
105 function index_webjump (name, url, file) {
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>",
121 make_completion: null,
125 make_handler: function () {
126 throw new Error("Subclasses of index_webjump must implement make_handler.");
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. */
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;
157 this.completions = cmpl;
160 /* A completer suitable for supplying to define_webjump. */
161 make_completer: function () {
164 return new index_webjump_completer(this);
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) {
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,
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);
185 function index_webjump_xhtml (name, url, file, xpath_expr) {
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];
203 make_handler: function () {
205 return function (term) {
206 if (!(jmp.completions && jmp.completions.length))
207 throw interactive_error("Completions required for " + this.name);
214 define_keywords("$default");
215 function index_webjump_gitweb (name, base_url, file) {
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);
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>",
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$/, ""), ""];
240 make_handler: function () {
241 return this.summary_url;
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.",
250 var completions = [];
251 for (let [name, w] in Iterator(webjumps)) {
252 if (w instanceof index_webjump)
253 completions.push(name);
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];
264 jmp.get_index(I.buffer);
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.
283 define_keywords("$index_file");
284 function define_xpath_webjump (name, index_url, xpath_expr) {
286 var alternative = arguments.$alternative || index_url;
287 var w = new index_webjump_xhtml(name, index_url,
288 arguments.$index_file,
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
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");
306 jmp.xpath_expr = xpath_expr;
307 jmp.extract_completions();
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
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
329 define_keywords("$opml_file");
330 function define_gitweb_summary_webjump (name, base_url) {
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");