var PLUGIN_INFO = {NAME} Manage Vimperator Plugins Vimpeatorプラグインの管理 teramako 0.4 2.0pre 2.0pre http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/pluginManager.js || var PLUGIN_INFO = ... ||< とE4X形式でXMLを記述してください 各要素は下記参照 === 要素 === name: プラグイン名 description: 簡易説明 属性langに"ja"などと言語を指定するとFirefoxのlocaleに合わせたものになります。 author: 製作者名 属性mailにe-mail、homepageにURLを付けるとリンクされます license: ライセンスについて 属性documentにURLを付けるとリンクされます version: プラグインのバージョン maxVersion: プラグインが使用できるVimperatorの最大バージョン minVersion: プラグインが使用できるVimperatorの最小バージョン updateURL: プラグインの最新リソースURL detail: ここにコマンドやマップ、プラグインの説明 CDATAセクションにwiki的に記述可能 == Wiki書式 == 見出し: - == heading1 == で第一見出し(h1) - === heading2 === で第二見出し(h2) - ==== heading3 ==== で第三見出し(h3) リスト: - "- "を先頭につけると箇条書きリスト(ul)になります。 - 改行が可能 >|| - 改行 可能 ||< の場合 - 改行 可能 となります。 - ネスト可能 - "+ "を先頭につけると番号付きリスト(ol)になります。 仕様は箇条書きリストと同じです。 定義リスト: - 末尾が":"で終わる行は定義リスト(dl,dt)になります。 - 次行をネストして始めるとdd要素になります。 - これもネスト可能です。 整形式テキスト: >|| と ||< で囲むと整形式テキスト(pre)になります。 コードなどを書きたい場合に使用できるでしょう。 インライン: - mailtoとhttp、httpsスキームのURLはリンクになります == ToDo == - 更新通知 - スタイルの追加(これはすべき?) ]]> ; liberator.plugins.pluginManager = (function(){ function id(value) value; var lang = window.navigator.language; var tags = { // {{{ name: function(info) fromUTF8Octets(info.toString()), author: function(info){ var name = fromUTF8Octets(info.toString()); var xml = <>{name}; if (info.@mail.toString() != '') xml += <> <'} highlight="URL">{info.@mail}>; if (info.@homepage.toString() != '') xml += <> ({makeLink(info.@homepage.toString())}); return xml; }, description: function(info) makeLink(fromUTF8Octets(info.toString())), license: function(info){ var xml = <>{fromUTF8Octets(info.toString())}; if (info.@document.toString() != '') xml += <> {makeLink(info.@document.toString())}; return xml; }, version: id, maxVersion: id, minVersion: id, updateURL: function(info) makeLink(info.toString(), true), detail: function(info){ if (info.* && info.*[0] && info.*[0].nodeKind() == 'element') return info.*; var text = fromUTF8Octets(info.*.toString()); var parser = new WikiParser(text); var xml = parser.parse(); return xml; } }; // }}} function chooseByLang(elems){ if (!elems) return null; function get(lang){ var i = elems.length(); while (i-->0){ if (elems[i].@lang.toString() == lang) return elems[i]; } } return get(lang) || get(lang.split('-', 2).shift()) || get('') || get('en-US') || get('en') || elems[0] || elems; } for (let it in Iterator(tags)){ let [name, value] = it; tags[name] = function(info){ if (!info[name]) return null; return value.call(tags, chooseByLang(info[name])); }; } function makeLink(str, withLink){ var href = withLink ? '$&' : '#'; return XMLList(str.replace(/(?:https?:\/\/|mailto:)\S+/g, '$&')); } function fromUTF8Octets(octets){ return decodeURIComponent(octets.replace(/[%\x80-\xFF]/g, function(c){ return '%' + c.charCodeAt(0).toString(16); })); } // -------------------------------------------------------- // Plugin // -----------------------------------------------------{{{ var plugins = []; function getPlugins(reload){ if (plugins.length > 0 && !reload){ return plugins; } plugins = []; var contexts = liberator.plugins.contexts; for (let path in contexts){ let context = contexts[path]; plugins.push(new Plugin(path, context)); } return plugins; } function Plugin() { this.initialize.apply(this, arguments); } Plugin.prototype = { // {{{ initialize: function(path, context){ this.path = path; this.name = context.NAME; this.info = context.PLUGIN_INFO || <>; this.getItems(); }, getItems: function(){ if (this.items) return this.items; this.items = {}; for (let tag in tags){ if (tag == "detail") continue; let xml = this.info[tag]; let value = tags[tag](this.info); if (value && value.toString().length > 0) this.items[tag] = value; } return this.items; }, getDetail: function(){ if (this.detail) return this.detail; else if (!this.info || !this.info.detail) return null; return this.detail = tags['detail'](this.info); }, itemFormatter: function(showDetail){ let data = [ ["path", this.path] ]; let items = this.getItems(); for (let name in items){ data.push([name, items[name]]); } if (showDetail && this.getDetail()) data.push(["detail", this.getDetail()]); return template.table(this.name, data); }, checkVersion: function(){ return this.updatePlugin(true); }, updatePlugin: function(checkOnly){ //{{{ var [localResource, serverResource, store] = this.getResourceInfo(); var localDate = Date.parse(localResource['Last-Modified']) || 0; var serverDate = Date.parse(serverResource.headers['Last-Modified']) || 0; var data = { 'Local Version': this.info.version || 'unknown', 'Local Last-Modified': localResource['Last-Modified'] || 'unkonwn', 'Local Path': this.path || 'unknown', 'Server Latest Version': serverResource.version || 'unknown', 'Server Last-Modified': serverResource.headers['Last-Modified'] || 'unknown', 'Update URL': this.info.updateURL || '-' }; if (checkOnly) return template.table(this.name, data); if (!this.info.version || !serverResource.version){ data.Information = 'unknown version.'; } else if (this.info.version == serverResource.version && localResource['Last-Modified'] == serverResource.headers['Last-Modified']){ data.Information = 'up to date.'; } else if (this.compVersion(this.info.version, serverResource.version) > 0 || localDate > serverDate){ data.information = 'local version is newest.'; } else { data.Information = this.overwritePlugin(serverResource); localResource = {}; // cleanup pref. localResource['Last-Modified'] = serverResource.headers['Last-Modified']; store.set(this.name, localResource); store.save(); } return template.table(this.name, data); }, // }}} getResourceInfo: function(){ var store = storage.newMap('plugins-pluginManager', true); var url = this.info.updateURL; var localResource = store.get(this.name) || {}; var serverResource = { version: '', source: '', headers: {} }; if (url && /^(http|ftp):\/\//.test(url)){ let xhr = util.httpGet(url); let version = ''; let source = xhr.responseText || ''; let headers = {}; try { xhr.getAllResponseHeaders().split(/\r?\n/).forEach(function(h){ var pair = h.split(': '); if (pair && pair.length > 1) { headers[pair.shift()] = pair.join(''); } }); } catch(e){} let m = /\bPLUGIN_INFO[ \t\r\n]*=[ \t\r\n]*]*)?>([\s\S]+?)<\/VimperatorPlugin[ \t\r\n]*>/(source); if (m){ m = m[1].replace(/(?:))*\]\]|--(?:[^-]|-(?!-))*--)>)+/g, ''); m = /^[\w\W]*?]*)?>([^<]+)<\/version[ \t\r\n]*>/(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.getFile(this.path); if (!source) return 'source is null.'; try { io.writeFile(file, source); } catch (e){ liberaotr.log('Could not write to ' + file.path + ': ' + e.message); return 'E190: Cannot open ' + filename.quote() + ' 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 // -----------------------------------------------------{{{ function WikiParser(text){ this.mode = ''; this.lines = text.split(/\r\n|[\r\n]/); this.preCount = 0; this.pendingMode = ''; this.xmlstack = new HTMLStack(); } WikiParser.prototype = { // {{{ inlineParse: function(str){ function replacer(str) ({ '<': '<', '>': '>', '&': '&' })[str] || ''+str+''; return XMLList(str.replace(/>|<|&|(?:https?:\/\/|mailto:)\S+/g, replacer)); }, wikiReg: { // {{{ hn: /^(={2,4})\s*(.*?)\s*\1$/, dt: /^(.*)\s*:$/, ul: /^-\s+(.*)$/, ol: /^\+\s+(.*)$/, preStart: /^>\|\|$/, preEnd: /^\|\|<$/ }, // }}} blockParse: function(line, prevMode){ // {{{ if (prevMode == 'pre'){ if (this.wikiReg.preEnd.test(line)){ if (this.preCount > 0){ this.preCount--; return <>{line}; } else { this.mode = ''; return <>; } return <>{line}; } else if (this.wikiReg.preStart.test(line)){ this.preCount++; } return <>{line}; } else if (this.wikiReg.preStart.test(line)){ this.mode = 'pre'; this.pendingMode = prevMode; return
;
        } else if (this.wikiReg.hn.test(line)){
            let hn = RegExp.$1.length - 1;
            this.mode = '';
            return {this.inlineParse(RegExp.$2)};
        } else if (this.wikiReg.ul.test(line)){
            this.mode = 'ul';
            return 
  • {this.inlineParse(RegExp.$1)}
; } else if (this.wikiReg.ol.test(line)){ this.mode = 'ol'; return
  1. {this.inlineParse(RegExp.$1)}
; } else if (this.wikiReg.dt.test(line)){ this.mode = 'dl'; return
{this.inlineParse(RegExp.$1)}
; } else if (prevMode == 'dl'){ return <>{this.inlineParse(line)}; } this.mode = ''; return <>{this.inlineParse(line)}; }, // }}} parse: function(){ // {{{ var ite = Iterator(this.lines); var num, line, indent; var currentIndent = 0, indentList = [0], nest=0; var prevMode = ''; var stack = []; var nest; var isNest = false; var bufXML; //try { for ([num, line] in ite){ [, indent, line] = line.match(/^(\s*)(.*?)\s*$/); currentIndent = indent.length; let prevIndent = indentList[indentList.length -1]; bufXML = this.blockParse(line, prevMode); if (prevMode == 'pre'){ if (this.mode){ this.xmlstack.appendLastChild(indent.substr(prevIndent) + line + '\n'); } else { this.xmlstack.reorg(-2); this.mode = this.pendingMode; indentList.pop(); if (indentList.length == 0) indentList = [0]; } prevMode = this.mode; continue; } if (!line){ //this.xmlstack.append(<>{'\n'}); continue; } if (currentIndent > prevIndent){ if (this.mode){ if (prevMode == 'dl'){ this.xmlstack.appendChild(
); } this.xmlstack.push(bufXML); indentList.push(currentIndent); } else { if (prevMode && this.xmlstack.length > 0){ this.xmlstack.appendLastChild(bufXML); } else { this.xmlstack.append(bufXML); } this.mode = prevMode; } } else if (currentIndent < prevIndent){ for (let i in indentList){ if (currentIndent == indentList[i] || currentIndent < indentList[i+1]){ nest = i; break; } } indentList.splice(nest); indentList.push(currentIndent); this.xmlstack.reorg(nest); this.xmlstack.append(bufXML); } else { this.xmlstack.append(bufXML); } prevMode = this.mode; } //} catch (e){ alert(num + ':'+ e); } this.xmlstack.reorg(); return this.xmlstack.last; } // }}} }; // }}} // 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){ var buf = this.stack[this.length-1]; buf[buf.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; } }; // }}} // }}} // -------------------------------------------------------- // 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 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], ], 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); // }}} // -------------------------------------------------------- // 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; }, list: function(names, verbose){ let xml = <> this.getPlugins(names).forEach(function(plugin){ xml += plugin.itemFormatter(verbose); }); return xml; } }; return public; // }}} })(); // vim: sw=4 ts=4 et fdm=marker: