]*)?>([^<]+)<\/version[ \t\r\n]*>/.exec(m);
if (m){
version = m[1];
}
}
serverResource = {version: version, source: source, headers: headers};
}
if (!localResource['Last-Modified']){
localResource['Last-Modified'] = serverResource.headers['Last-Modified'];
store.set(this.name, localResource);
}
return [localResource, serverResource, store];
},
overwritePlugin: function(serverResource){
/*
if (!plugin[0] || plugin[0][0] != 'path')
return 'plugin localpath was not found.';
var localpath = plugin[0][1];
*/
var source = serverResource.source;
var file = io.File(this.path);
if (!source)
return 'source is null.';
try {
file.write(source);
} catch (e){
liberator.log('Could not write to ' + file.path + ': ' + e.message);
return 'E190: Cannot open ' + JSON.stringify(file.path) + ' for writing';
}
try {
io.source(this.path);
} catch (e){
return e.message;
}
return 'update complete.';
},
compVersion: function(a, b){
const comparator = Cc["@mozilla.org/xpcom/version-comparator;1"].getService(Ci.nsIVersionComparator);
return comparator.compare(a, b);
}
}; // }}}
// }}}
// --------------------------------------------------------
// WikiParser
// -----------------------------------------------------{{{
var WikiParser = (function () {
function State (lines, result, indents) {
if (!(this instanceof arguments.callee))
return new arguments.callee(lines, result, indents);
this.lines = lines;
this.result = result || ``;
this.indents = indents || [];
}
State.prototype = {
get end () !(this.lines.length && let (i = this.indents[this.indents.length - 1])
/^\s*$/.test(this.head) || !i || i.test(this.head)),
get realEnd () !this.lines.length,
get head () this.lines[0],
set head (value) this.lines[0] = value,
get clone () State(cloneArray(this.lines), this.result, cloneArray(this.indents)),
get next () {
let result = this.clone;
result.lines = this.lines.slice(1);
return result;
},
indent: function (indent, more) {
let result = this.clone;
let re = RegExp('^' + indent.replace(/\S/g, ' ') + (more ? '\\s+' : '') + '(.*)$');
result.indents.push(re);
return result;
},
indentBack: function () {
let result = this.clone;
result.indents.pop();
return result;
},
wrap: function (name) {
let result = this.clone;
result.result = <{name}>{this.result}{name}>;
return result;
},
set: function (v) {
let result = this.clone;
result.result = v instanceof State ? v.result : v;
return result;
},
};
function Error (name, state) {
if (!(this instanceof arguments.callee))
return new arguments.callee(name, state);
this.name = name;
this.state = state;
}
function ok (v)
v instanceof State;
function cloneArray (ary)
Array.concat(ary);
function xmlJoin (xs, init) {
let result = init || ``;
for (let i = 0, l = xs.length; i < l; i++)
result += xs[i];
return result;
}
function strip (s)
s.replace(/^\s+|\s+$/g, '');
function trimAll (lines) {
let min = null;
lines.forEach(function (line) {
let s = line.match(/^\s*/).toString();
if (min) {
if (min.indexOf(s) == 0)
min = s;
} else {
min = s;
}
});
if (min) {
let spre = RegExp('^' + min);
liberator.log(min.length)
return lines.map(function (line) line.replace(spre, ''));
}
return lines;
}
// FIXME
function link (s) {
let m;
let result = ``;
while (s && (m = s.match(/(?:https?:\/\/|mailto:)\S+/))) {
result += `{RegExp.leftContext || ''}{m[0]}`;
s = RegExp.rightContext;
}
if (s)
result += `{s}`;
return result;
}
function stripAndLink (s)
link(strip(s));
function isEmptyLine (s)
/^\s*$/.test(s);
////////////////////////////////////////////////////////////////////////////////
let C = {
// [Parser a] -> Parser b
or: function or () {
let as = [];
for (let i = 0, l = arguments.length; i < l; i++)
as.push(arguments[i]);
return function (st) {
for each (let a in as) {
let r = a(st);
if (ok(r))
return r;
}
return Error('or-end', st);
};
},
// Parser a -> Parser a
many: function many (p) {
return function (st) {
let result = [];
let cnt = 0;
while (!st.end) {
let r = p(st);
if (ok(r))
result.push(r.result);
else
break;
st = r;
if (cnt++ > 100) { liberator.log('force break: many-while'); break; }
}
if (ok(st))
return st.set(result);
return Error('many', st);
}
},
// Parser a -> Parser a
many1: function many1 (p) {
return function (st) {
let result = [];
let cnt = 0;
while (!st.end) {
let r = p(st);
if (ok(r))
result.push(r.result);
else
break;
st = r;
if (cnt++ > 100) { liberator.log('force break: many1-while'); break; }
}
if (result.length) {
return st.set(result);
}
return Error('many1', st);
};
},
// Parser a -> Parser a
indent: function indent (p) {
return function (st) {
if (st.end)
return Error('EOL', 'st');
return p(st);
};
}
};
////////////////////////////////////////////////////////////////////////////////
let P = (function () {
// Int -> Parser XML
function hn (n) {
let re = RegExp('^\\s*=={' + n + '}\\s+(.*)\\s*=={' + n + '}\\s*$');
return function (st) {
let m = st.head.match(re);
if (m) {
let hn = 'h' + n;
return st.next.set(<{hn} style={'font-size:'+(0.75+1/n)+'em'}>{stripAndLink(m[1])}{hn}>)
}
return Error('not head1', st);
};
}
// String -> Parser XML
function list (name) {
return function (st) {
let lis = C.many1(self[name + 'i'])(st);
if (ok(lis)) {
return lis.set(xmlJoin(lis.result)).wrap(name);
}
return Error(name, st);
};
}
// String -> Parser XML
function listItem (c) {
let re = RegExp('^(\\s*\\' + c + ' )(.*)$');
return function li (st) {
let m = st.head.match(re);
if (m) {
let h = m[2];
let next = C.many(self.wikiLine)(st.next.indent(m[1]));
return next.indentBack().set(xmlJoin([`{h}
`].concat(next.result))).wrap('li');
}
return Error(c, st);
};
}
let self = {
// St -> St
debug: function debug (st) {
//liberator.log({ head: st.head, indent: st.indents[st.indents.length - 1] });
return Error('debug', st);
},
emptyLine: function emptyLine (st) {
if (/^\s*$/.test(st.head)) {
return st.next.set(``);
}
return Error('spaces', st);
},
// St -> St XML
plain: function plain (st) {
let text = st.head;
return st.next.set(`{stripAndLink(text)}
`);
},
// St -> St XML
h1: hn(1),
h2: hn(2),
h3: hn(3),
h4: hn(4),
// St -> St XML
pre: function pre (st) {
let m = st.head.match(/^(\s*)>\|\|\s*$/);
if (m) {
let result = [];
let cnt = 0;
while (!st.realEnd) {
st = st.next;
if (/^(\s*)\|\|<\s*$/.test(st.head)){
st = st.next;
break;
}
result.push(st.head);
if (cnt++ > 100) { liberator.log('force break: pre-while'); break; }
}
return st.set({trimAll(result).join('\n')}
);
}
return Error('pre', st);
},
// St -> St XML
ul: list('ul'),
// St -> St XML
uli: listItem('-'),
// St -> St XML
ol: list('ol'),
// St -> St XML
oli: listItem('+'),
// St -> St XML
dl: function dl (st) {
let r = C.many1(self.dtdd)(st);
if (ok(r)) {
let body = xmlJoin(r.result);
return r.set(body).wrap('dl');
}
return Error('dl', st);
},
// St -> St XML
dtdd: function dtdd (st) {
let r = self.dt(st);
if (ok(r)) {
let [indent, dt] = r.result;
let dd = C.many(self.wikiLine)(r.indent(indent, true));
return dd.indentBack().set(dt + {xmlJoin(dd.result)});
}
return r;
},
// St -> St (lv, XML)
dt: function dt (st) {
let m = st.head.match(/^(\s*)(.+):\s*$/);
if (m) {
return st.next.set([m[1], {m[2]}]);
}
return Error('not dt', st);
},
};
// St -> St XML
with (self) {
self.wikiLine = C.or(debug, emptyLine, h1, h2, h3, h4, dl, ol, ul, pre, plain);
// St -> St [XML]
self.wikiLines = C.many(wikiLine);
self.wiki = function (st) {
let r = wikiLines(st);
if (ok(r)) {
let xs = r.result;
return r.set(xmlJoin(xs)).wrap('div');
}
return Error('wiki', st);
}
}
for (let [name, p] in Iterator(self)) {
self[name] = C.indent(p);
}
return self;
})();
return liberator.plugins.PMWikiParser = {
parsers: P,
combs: C,
classes: {
State: State,
},
parse: function (src) {
let r = P.wiki(State(src.split(/\r\n|[\r\n]/)));
if (ok(r))
return r.result;
else
liberator.echoerr(r.name);
}
};
})();
// End WikiParser }}}
// --------------------------------------------------------
// HTML Stack
// -----------------------------------------------------{{{
function HTMLStack(){
this.stack = [];
}
HTMLStack.prototype = { // {{{
get length() this.stack.length,
get last() this.stack[this.length-1],
get lastLocalName() this.last[this.last.length()-1].localName(),
get inlineElements() 'a abbr acronym b basefont bdo big br button cite code dfn em font i iframe img inout kbd label map object q s samp script select small span strike strong sub sup textarea tt u var'.split(' '),
isInline: function(xml)
xml.length() > 1 || xml.nodeKind() == 'text' || this.inlineElements.indexOf(xml.localName()) >= 0,
push: function(xml) this.stack.push(xml),
append: function(xml){
if (this.length == 0){
this.push(xml);
return xml;
}
var buf = this.last[this.last.length()-1];
if (buf.nodeKind() == 'text'){
this.last[this.last.length()-1] += this.isInline(xml) ? `
{xml}` : xml;
} else if (this.isInline(xml)){
this.stack[this.length-1] += xml;
} else if (buf.localName() == xml.localName()){
buf.* += xml.*;
} else {
this.stack[this.length-1] += xml;
}
return this.last;
},
appendChild: function(xml){
if (this.length == 0){
this.push(xml);
return xml;
}
var buf = this.stack[this.length-1];
if (buf[buf.length()-1].localName() == xml.localName()){
if (this.isInline(xml.*[0]))
buf[buf.length()-1].* +=
+ xml.*;
else
buf[buf.length()-1].* += xml.*;
} else
this.stack[this.length-1] += xml;
return this.last;
},
appendLastChild: function(xml){
var buf = this.last[this.last.length()-1].*;
if (buf.length() > 0 && buf[buf.length()-1].nodeKind() == 'element'){
let tmp = buf[buf.length()-1].*;
if (tmp[tmp.length()-1].nodeKind() == 'element'){
buf[buf.length()-1].* += xml;
} else {
buf[buf.length()-1].* += `
{xml}`;
}
} else {
this.last[this.last.length()-1].* += xml;
}
return this.last;
},
reorg: function(from){
if (this.length == 0) return;
if (!from) from = 0;
var xmllist = this.stack.splice(from);
var xml;
if (xmllist.length > 1){
xml = xmllist.reduceRight(function(p, c){
let buf = c[c.length()-1].*;
if (buf.length() > 0){
if (buf[buf.length()-1].nodeKind() == 'text'){
c += p;
} else {
buf[buf.length()-1].* += p;
}
} else {
c += p;
}
return c;
});
} else if (xmllist.length > 0){
xml = xmllist[0];
}
this.push(xml);
return this.last;
}
}; // }}}
// }}}
// --------------------------------------------------------
// CODEREPOS_PLUGINS
// -----------------------------------------------------{{{
var CODEREPOS = (function(){
const indexURL = 'http://vimperator.kurinton.net/plugins/info.xml';
var public = {
plugins: [],
init: function(){
this.plugins = [];
util.httpGet(indexURL, function(xhr){
let xml = new XMLList(xhr.responseText);
let plugins = xml.plugin;
for (let i=0, length = plugins.length(); i < length; i++){
public.plugins.push(new CodeReposPlugin(plugins[i]));
}
});
}
};
function CodeReposPlugin(xml){
this.name = tags.name(xml);
this.URL = tags.updateURL(xml);
this.description = tags.description(xml);
this.version = tags.version(xml);
}
public.init();
return public;
})();
// }}}
// --------------------------------------------------------
// Vimperator Command
// -----------------------------------------------------{{{
commands.addUserCommand(['plugin[help]'], 'list Vimperator plugins',
function(args){
var xml;
if (args["-check"])
xml = liberator.plugins.pluginManager.checkVersion(args);
else if (args["-update"])
xml = liberator.plugins.pluginManager.update(args);
else if (args["-source"]) {
if (args.length < 1)
return liberator.echoerr('Argument(plugin name) required');
return liberator.plugins.pluginManager.source(args);
} else
xml = liberator.plugins.pluginManager.list(args, args["-verbose"]);
liberator.echo(xml, true);
}, {
argCount: '*',
options: [
[['-verbose', '-v'], commands.OPTION_NOARG],
[['-check', '-c'], commands.OPTION_NOARG],
[['-update', '-u'], commands.OPTION_NOARG],
[['-source', '-s'], commands.OPTION_NOARG],
],
completer: function(context){
context.title = ['PluginName', '[Version]Description'];
context.completions = getPlugins().map(function(plugin) [
plugin.name,
'[' + (plugin.items.version || 'unknown') + ']' +
(plugin.items.description || '-')
]).filter(function(row)
row[0].toLowerCase().indexOf(context.filter.toLowerCase()) >= 0);
}
}, true);
commands.addUserCommand(['pluginmanager', 'pm'], 'Manage Vimperator plugins',
function(args){
if (args.length < 1)
liberator.echoerr('Not enough arguments. Sub-command required.');
var xml;
var sub = args[0];
var subArgs = args.slice(1);
function s2b (s, d) (!/^(\d+|false)$/i.test(s)|parseInt(s)|!!d*2)&1<= 0);
} else if (args[0] == "install"){
context.anchored = false;
context.title = ["PluginName", "Name: Description"];
context.completions = CODEREPOS.plugins.filter(function($_){
return !getPlugins().some(function(installed){
return installed.items.updateURL ? installed.items.updateURL == $_.URL : false;
});
}).map(function(plugin) [plugin.URL, plugin.name + ": " + plugin.description]);
}
}
}
}, true); // }}}
// --------------------------------------------------------
// Public Member (liberator.plugins.pluginManger)
// -----------------------------------------------------{{{
var public = {
getPlugins: function(names, forceReload){
let plugins = getPlugins(forceReload);
if (!names || names.length == 0)
return plugins;
return plugins.filter(function(plugin) names.indexOf(plugin.name) >= 0);
},
checkVersion: function(names){
let xml = ``;
this.getPlugins(names).forEach(function(plugin){
xml += plugin.checkVersion();
});
return xml;
},
update: function(names){
let xml = ``;
this.getPlugins(names).forEach(function(plugin){
xml += plugin.updatePlugin();
});
return xml;
},
source: function(names){
// XXX 一度に開くようにするべき? (ref: editor.js L849)
this.getPlugins(names).forEach(function(plugin){
editor.editFileExternally(plugin.path);
});
return;
},
list: function(names, verbose){
let xml = ``
this.getPlugins(names).forEach(function(plugin){
xml += plugin.itemFormatter(verbose);
});
return xml;
},
install: function(urls){
function makeURL(s){
let url = Cc["@mozilla.org/network/standard-url;1"].createInstance(Ci.nsIURL);
url.spec = s;
return url;
}
function fixURL(url){
const cr = RegExp('^http://coderepos\\.org/share/browser/lang/javascript/vimperator-plugins/trunk/[^/]+\\.js$');
const pi = RegExp('^http://vimperator\\.kurinton\\.net/plugins/');
const npi = /\/(all|index)\.html/;
const js = /\.js$/i;
function xe(xpath){
let ss = util.evaluateXPath(xpath);
return (ss.snapshotLength > 0) && ss.snapshotItem(0).href;
}
if (cr.test(url)) {
return url.replace(/(?=coderepos\.org\/)/, 'svn.').replace(/browser\//, '');
}
if (pi.test(url) && !npi.test(url)) {
return xe('//a[@id="file-link"]');
}
if (js.test(url)) {
return url;
}
throw 'Current URL is not a pluginFile';
}
function download(url){
let wbp = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"].createInstance(Ci.nsIWebBrowserPersist);
let progressListener = {
onStateChange: function (_webProgress, _request, _stateFlags, _status) {
if (_stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
if (file.exists()) {
io.source(file.path);
liberator.echo('Executed: ' + file.path)
} else {
return liberator.echoerr('Download error!');
}
}
},
onProgressChange: function () undefined,
onLocationChange: function () undefined,
onStatusChange: function () undefined,
onSecurityChange: function () undefined,
};
let filename = url.match(/[^\/]+$/).toString();
let file = io.getRuntimeDirectories('plugin')[0];
file.append(filename);
if (file.exists())
return liberator.echoerr('Already exists: ' + file.path);
let fileuri = makeFileURI(file);
wbp.progressListener = progressListener;
wbp.persistFlags |= wbp.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
wbp.saveURI(makeURL(url), null, null, null, null, fileuri);
}
var url = urls.length ? urls[0] : buffer.URL;
var sourceURL = fixURL(url);
if (sourceURL == url) {
liberator.log(url);
download(url);
} else {
liberator.open(sourceURL);
liberator.echoerr('Please check the source code of plugin, and retry ":pluginmanager install"');
}
}
};
return public;
// }}}
})();
// vim: sw=4 ts=4 et fdm=marker: