configure_backup/conkeror/modules/index-webjump.js

314 lines
11 KiB
JavaScript

/**
* (C) Copyright 2009 David Kettler
*
* Use, modification, and distribution are subject to the terms specified in the
* COPYING file.
*
* Construct a webjump (with completer) to visit URLs referenced from
* an index page. An xpath expression is used to extract the indexed
* URLs. A specialized form is also provided for gitweb summary
* pages.
**/
require("webjump.js");
/* Objects with completion data for index webjumps. */
index_webjumps = {};
define_variable("index_webjumps_directory", null,
"A directory (instance of nsILocalFile) for storing the " +
"index files corresponding to index webjumps; the index " +
"data can be downloaded from the index URL using " +
"webjump-get-index. " +
"If the index file is available for an index webjump then " +
"the webjump will provide completions for the indexed URLs.");
define_variable("index_xpath_webjump_tidy_command",
"tidy -asxhtml -wrap 0 -numeric --clean yes" +
" -modify -quiet --show-warnings no",
"A command to run on the downloaded index. The xulrunner " +
"parser is quite fussy and specifically requires xhtml (or " +
"other xml). Running something like html tidy can avoid " +
"parser problems.");
function index_webjump (key, url, file) {
this.key = key;
this.url = url;
this.file = this.canonicalize_file(file);
if (this.require_completions && ! this.file)
throw interactive_error("Index file not defined for " + this.key);
}
index_webjump.prototype = {
constructor: index_webjump,
toString: function () "#<index_webjump>",
mime_type: null,
xpath_expr: null,
make_completion: null,
require_completions: false,
completions: null,
file_time: 0,
tidy_command: null,
/* Extract full completion list from index file. */
extract_completions: function () {
/* Parse the index file. */
var stream = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
stream.init(this.file, MODE_RDONLY, 0644, false);
var parser = Cc["@mozilla.org/xmlextras/domparser;1"]
.createInstance(Ci.nsIDOMParser);
// todo: catch parser errors
var doc = parser.parseFromStream(stream, null,
this.file.fileSize, this.mime_type);
/* Extract the completion items. */
var cmpl = [], node;
var res = doc.evaluate(
this.xpath_expr, doc, xpath_lookup_namespace,
Ci.nsIDOMXPathResult.UNORDERED_NODE_ITERATOR_TYPE, null);
while ((node = res.iterateNext()))
cmpl.push(this.make_completion(node));
cmpl.sort(function(a, b) {
if (a[1] < b[1]) return -1;
if (a[1] > b[1]) return 1;
if (a[0] < b[0]) return -1;
if (a[0] > b[0]) return 1;
return 0;
});
this.completions = cmpl;
},
/* The guts of the completer. */
internal_completer: function (input, pos, conservative) {
if (pos == 0 && conservative)
yield co_return(undefined);
let require = this.require_completions;
/* Update full completion list if necessary. */
if (require && ! this.file.exists())
throw interactive_error("Index file missing for " + this.key);
if (this.file.exists() &&
this.file.lastModifiedTime > this.file_time)
{
this.file_time = this.file.lastModifiedTime;
this.extract_completions();
}
if (require && !(this.completions && this.completions.length))
throw interactive_error("No completions for " + this.key);
if (! this.completions)
yield co_return(null);
/* Match completions against input. */
let words = trim_whitespace(input.toLowerCase()).split(/\s+/);
let data = this.completions.filter(function (x) {
for (var i = 0; i < words.length; ++i) {
if (x[0].toLowerCase().indexOf(words[i]) == -1 &&
x[1].toLowerCase().indexOf(words[i]) == -1)
{
return false;
}
}
return true;
});
let c = { count: data.length,
get_string: function (i) data[i][0],
get_description: function (i) data[i][1],
get_input_state: function (i) [data[i][0]],
get_match_required: function() require
};
yield co_return(c);
},
/* A completer suitable for supplying to define_webjump. */
make_completer: function () {
if (! this.file)
return null;
let jmp = this;
return function (input, pos, conservative) {
return jmp.internal_completer(input, pos, conservative);
};
},
/* Fetch and save the index for later use with completion.
* (buffer is used only to associate with the download) */
get_index: function (buffer) {
if (! this.file)
throw interactive_error("Index file not defined for " + this.key);
var info = save_uri(load_spec(this.url), this.file,
$buffer = buffer, $use_cache = false,
$temp_file = true);
// Note: it would be better to run this before the temp file
// is renamed; that requires support in save_uri.
if (this.tidy_command)
info.set_shell_command(this.tidy_command, index_webjumps_directory);
},
/* Try to make a suitable file object when the supplied file is a
* string or null. */
canonicalize_file: function (file) {
if (typeof file == 'string')
file = make_file(file);
if (! file && index_webjumps_directory) {
file = Cc["@mozilla.org/file/local;1"]
.createInstance(Ci.nsILocalFile);
file.initWithFile(index_webjumps_directory);
file.appendRelativePath(this.key + ".index");
}
return file;
}
};
function index_webjump_xhtml (key, url, file, xpath_expr) {
index_webjump.call(this, key, url, file);
this.xpath_expr = xpath_expr;
}
index_webjump_xhtml.prototype = {
constructor: index_webjump_xhtml,
__proto__: index_webjump.prototype,
toString: function () "#<index_webjump_xhtml>",
require_completions: true,
mime_type: "application/xhtml+xml",
get tidy_command () index_xpath_webjump_tidy_command,
make_completion: function (node) {
return [makeURLAbsolute(this.url, node.href), node.text];
},
make_handler: function () {
let jmp = this;
return function (term) {
if (!(jmp.completions && jmp.completions.length))
throw interactive_error("Completions required for " + this.key);
return term;
};
}
};
function index_webjump_gitweb (key, url, file) {
index_webjump.call(this, key, url, file);
}
index_webjump_gitweb.prototype = {
constructor: index_webjump_gitweb,
__proto__: index_webjump.prototype,
toString: function () "#<index_webjump_gitweb>",
mime_type: "text/xml",
xpath_expr: '//outline[@type="rss"]',
make_completion: function (node) {
var name = node.getAttribute("text");
return [name.replace(/\.git$/, ""), ""];
}
};
interactive("webjump-get-index",
"Fetch and save the index URL corresponding to an index " +
"webjump. It will then be available to the completer.",
function (I) {
var completions = [];
for (let i in index_webjumps)
completions.push(i);
completions.sort();
var key = yield I.minibuffer.read(
$prompt = "Fetch index for index webjump:",
$history = "webjump",
$completer = all_word_completer(
$completions = completions),
$match_required = true);
var jmp = index_webjumps[key];
if (jmp)
jmp.get_index(I.buffer);
});
/**
* Construct a webjump to visit URLs referenced from an index page.
*
* The index page must be able to be parsed as xhtml. The anchor
* nodes indexed are those that match the given xpath_expr. Don't
* forget to use xhtml: prefixes on the xpath steps.
*
* If an alternative is not specified then it is set to the index page.
*
* A completer is provided that uses the index page. A local file for
* the index must be specified either with $index_file or via
* index_webjumps_directory. The index must be manually downloaded;
* eg. using webjump-get-index. Each time the completer is used it
* will check if the file has been updated and reload if necessary.
* This kind of webjump is not useful without the completions.
*/
define_keywords("$alternative", "$index_file", "$description");
function define_xpath_webjump (key, index_url, xpath_expr) {
keywords(arguments);
let alternative = arguments.$alternative || index_url;
var jmp = new index_webjump_xhtml(key, index_url, arguments.$index_file,
xpath_expr);
index_webjumps[key] = jmp;
define_webjump(key, jmp.make_handler(),
$completer = jmp.make_completer(),
$alternative = alternative,
$description = arguments.$description);
}
/**
* Modify the xpath for an index webjump and show the resulting
* completions. Useful for figuring out an appropriate xpath. Either
* run using mozrepl or eval in the browser with the dump parameter
* set.
*/
function index_webjump_try_xpath (key, xpath_expr, dump) {
jmp = index_webjumps[key];
if (xpath_expr)
jmp.xpath_expr = xpath_expr;
jmp.extract_completions();
if (dump)
dumpln(dump_obj(jmp.completions,
"Completions for index webjump " + key));
return jmp.completions;
}
/**
* Construct a webjump to visit repository summary pages at a gitweb
* server.
*
* If a repository name is supplied as $default then the alternative
* url is set to that repository at the gitweb site. If an
* alternative is not specified by either $default or $alternative
* then it is set to the repository list page of the gitweb site.
*
* A completer is provided that uses the list of repositories from the
* OPML data on the gitweb server. The completer is setup in the same
* way as for define_xpath_webjump, but the webjump will work without
* the completions.
*/
define_keywords("$default", "$alternative", "$opml_file", "$description");
function define_gitweb_summary_webjump (key, base_url) {
keywords(arguments);
let alternative = arguments.$alternative;
let gitweb_url = base_url + "/gitweb.cgi";
let summary_url = gitweb_url + "?p=%s.git;a=summary";
let opml_url = gitweb_url + "?a=opml";
if (arguments.$default)
alternative = summary_url.replace("%s", arguments.$default);
if (! alternative)
alternative = gitweb_url;
var jmp = new index_webjump_gitweb(key, opml_url, arguments.$opml_file);
index_webjumps[key] = jmp;
define_webjump(key, summary_url,
$completer = jmp.make_completer(),
$alternative = alternative,
$description = arguments.$description);
}
provide("index-webjump");