├── LICENCE ├── README ├── README.md ├── deps ├── DOM_util.js ├── es5-shim.min.js ├── lists.js └── util.js ├── hinting.js ├── ports ├── aws.js ├── fake.js ├── http.js ├── nhttpd.js ├── rvs.js └── ssh.js ├── prototype.html └── verbs ├── aws ├── csv ├── deps ├── 3box_util.js ├── PanPG_util.js ├── csv_util.js ├── less.js ├── lists.js ├── mustache.js └── util.js ├── fake ├── http ├── js ├── json ├── misc ├── mustache ├── nhttpd ├── panpg ├── posix ├── rvs ├── ssh └── template /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 inimino@inimino.org 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ActiveShell is a shell for the Web. 2 | 3 | It can be used like a Unix shell to manage files on remote systems accessible by SSH, like an REPL for exploratory programming, and can access any networked service for which a port has been implemented. 4 | 5 | http://inimino.org/~inimino/blog/shell_for_the_web 6 | http://inimino.org/~inimino/blog/activeshell_aiclass 7 | 8 | 9 | Principles 10 | ---------- 11 | 12 | Ad-hoc reuse is beautiful. 13 | 14 | Automate automation. 15 | 16 | 17 | Installation 18 | ------------ 19 | 20 | The shell UI is in prototype.html. 21 | However, the shell creates a session and for that session to persist there must be a server that handles PUT requests. 22 | Installation on Node.js using nhttpd and revstore is the easiest approach: 23 | 24 | http://inimino.org/~inimino/blog/activeshell_installation -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ActiveShell is a shell for the Web. 2 | 3 | It can be used like a Unix shell to manage files on remote systems accessible by SSH, like an REPL for exploratory programming, and can access any networked service for which a port has been implemented. 4 | 5 | http://inimino.org/~inimino/blog/shell_for_the_web 6 | http://inimino.org/~inimino/blog/activeshell_aiclass 7 | 8 | 9 | Principles 10 | ---------- 11 | 12 | Ad-hoc reuse is beautiful. 13 | 14 | Automate automation. 15 | 16 | 17 | Installation 18 | ------------ 19 | 20 | The shell UI is in prototype.html. 21 | However, the shell creates a session and for that session to persist there must be a server that handles PUT requests. 22 | Installation on Node.js using nhttpd and revstore is the easiest approach: 23 | 24 | http://inimino.org/~inimino/blog/activeshell_installation -------------------------------------------------------------------------------- /deps/DOM_util.js: -------------------------------------------------------------------------------- 1 | function cEl(tn){return document.createElement(tn)} 2 | function byId(id){return document.getElementById(id)} 3 | function cTN(s){return document.createTextNode(s)} 4 | function byClass(cls){return Array.prototype.slice.call(document.body.getElementsByClassName(cls))} 5 | function removeEl(el){if(el&&el.parentNode)el.parentNode.removeChild(el)} 6 | function replaceEl(old,_new){old.parentNode.replaceChild(_new,old)} 7 | //function removeEl(el){el.parentNode.removeChild(el)} 8 | function emptyEl(el){var el_p,el_sib 9 | el_p=el.parentNode;el_sib=el.nextSibling 10 | el_p.removeChild(el) // DOM reflows can make clearing an element slow in older browsers so we remove from the DOM first 11 | while(el.firstChild)el.removeChild(el.firstChild) 12 | el_p.insertBefore(el,el_sib)} 13 | 14 | function handleTextChange(el,f,ms){var timeout 15 | el.onclick=el.onmouseup=el.onkeyup=handler 16 | function handler(){ 17 | if(timeout){clearTimeout(timeout);timeout=null} 18 | timeout=setTimeout(function(){f(el.value)},ms)}} 19 | 20 | function xhr(method,uri,body,callback){var headers,p 21 | // instead of a method string, the first argument can be an object with method and headers properties 22 | if(typeof method==='object'){headers=method.headers;method=method.method} 23 | var x=new XMLHttpRequest() 24 | x.onreadystatechange=function(){if (x.readyState==4){callback(x)}} 25 | x.open(method,uri,true) 26 | if(headers) for(p in headers){x.setRequestHeader(p,headers[p])} 27 | x.send(body)} 28 | 29 | function GET_all(uris,cb){var pending,results 30 | pending=uris.length 31 | results=[] 32 | if(!pending){cb(results)} 33 | uris.forEach(function(uri,i,a){xhr('GET',uri,null,function(x){ 34 | results[i]=x 35 | if(!--pending)cb(results)})})} 36 | 37 | function boundingRect(el){ 38 | if(el.getBoundingClientRect)return el.getBoundingClientRect() // newer browsers have this 39 | throw new Error('boundingRect unimplemented') 40 | return {/*...*/}} 41 | 42 | function addClass(el,_class){var x // Safari does not like "class" as an identifier 43 | x=el.className 44 | if(x.match(RegExp('\\b'+_class+'\\b')))return 45 | if(x=='')return el.className=_class 46 | el.className=x+' '+_class} 47 | 48 | function removeClass(el,_class){var x,y 49 | x=el.className 50 | if(x==_class)return el.className='' 51 | y=x.replace(RegExp('\\b'+_class+'\\b'),'') 52 | if(y!=x)el.className=y} 53 | 54 | function parseQueryString(s){ 55 | if(s[0]=='?')s=s.slice(1) 56 | return s.split('&').map(function(s){var pos 57 | if((pos=s.indexOf('='))<0)return {value:decodeURIComponent(s)} 58 | return {key:decodeURIComponent(s.slice(0,pos)),value:decodeURIComponent(s.slice(pos+1))}})} 59 | 60 | // XXX: these functions don't belong in this file 61 | 62 | // unlike POSIX, this dirname() always returns a result that ends with slash 63 | function dirname(path){var components 64 | components=path.split('/') 65 | if(components[components.length-1]=="") components.pop() 66 | return components.slice(0,-1).join('/')+'/'} 67 | 68 | // path segments, trailing slashes preserved, empty segments dropped 69 | // '/foo/bar/' -> ['/','foo/','bar/'] 70 | // '../foo//bar' -> ['../','foo/','bar'] 71 | function path_segments(path){var ret=[] 72 | path.split('/').forEach(function(s,i,a){if(s||!i)ret.push(s+(i>>0;if("[object Function]"!=m(a))throw new TypeError; 3 | for(;c>>0,e=Array(c);if("[object Function]"!=m(a))throw new TypeError;for(var g=0;g>>0,e=[];if("[object Function]"!=m(a))throw new TypeError;for(var g=0;g>>0;if("[object Function]"!=m(a))throw new TypeError;for(var e=0;e>>0;if("[object Function]"!=m(a))throw new TypeError;for(var e=0;e>>0;if("[object Function]"!=m(a))throw new TypeError; 5 | if(!b&&1==arguments.length)throw new TypeError;var c=0,e;if(2<=arguments.length)e=arguments[1];else{do{if(c in d){e=d[c++];break}if(++c>=b)throw new TypeError;}while(1)}for(;c>>0;if("[object Function]"!=m(a))throw new TypeError;if(!b&&1==arguments.length)throw new TypeError;var c,b=b-1;if(2<=arguments.length)c=arguments[1];else{do{if(b in d){c=d[b--]; 6 | break}if(0>--b)throw new TypeError;}while(1)}do b in this&&(c=a.call(void 0,c,d[b],b,d));while(b--);return c};if(!Array.prototype.indexOf)Array.prototype.indexOf=function(a){var d=i(this),b=d.length>>>0;if(!b)return-1;var c=0;1>>0;if(!b)return-1;var c=b-1;1c?"-":9999=c?-4:-6);for(d=a.length;d--;)b=a[d],10>b&&(a[d]="0"+b);return c+"-"+a.slice(0,2).join("-")+"T"+a.slice(2).join(":")+"."+("000"+this.getUTCMilliseconds()).slice(-3)+"Z"};if(!Date.now)Date.now=function(){return(new Date).getTime()};if(!Date.prototype.toJSON)Date.prototype.toJSON=function(){if("function"!=typeof this.toISOString)throw new TypeError;return this.toISOString()}; 14 | if(!Date.parse||864E13!==Date.parse("+275760-09-13T00:00:00.000Z"))Date=function(a){var d=function g(b,d,c,f,h,i,j){var k=arguments.length;return this instanceof a?(k=1==k&&""+b===b?new a(g.parse(b)):7<=k?new a(b,d,c,f,h,i,j):6<=k?new a(b,d,c,f,h,i):5<=k?new a(b,d,c,f,h):4<=k?new a(b,d,c,f):3<=k?new a(b,d,c):2<=k?new a(b,d):1<=k?new a(b):new a,k.constructor=g,k):a.apply(this,arguments)},b=RegExp("^(\\d{4}|[+-]\\d{6})(?:-(\\d{2})(?:-(\\d{2})(?:T(\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.(\\d{3}))?)?(?:Z|(?:([-+])(\\d{2}):(\\d{2})))?)?)?)?$"), 15 | c;for(c in a)d[c]=a[c];d.now=a.now;d.UTC=a.UTC;d.prototype=a.prototype;d.prototype.constructor=d;d.parse=function(d){var c=b.exec(d);if(c){c.shift();for(var f=1;7>f;f++)c[f]=+(c[f]||(3>f?1:0)),1==f&&c[f]--;var h=+c.pop(),i=+c.pop(),j=c.pop(),f=0;if(j){if(23=h?(c[0]=h+400,a.UTC.apply(this,c)+f-126227808E5):a.UTC.apply(this,c)+f}return a.parse.apply(this,arguments)};return d}(Date);j="\t\n\u000b\u000c\r \u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\ufeff"; 16 | if(!String.prototype.trim||j.trim()){j="["+j+"]";var A=RegExp("^"+j+j+"*"),B=RegExp(j+j+"*$");String.prototype.trim=function(){return(""+this).replace(A,"").replace(B,"")}}var v=function(a){a=+a;a!==a?a=0:0!==a&&a!==1/0&&a!==-(1/0)&&(a=(0-1)refs.push(parents[i]) 64 | if((i=refs.lastIndexOf(x))>-1)return '#'+i+'#' 65 | if(x.constructor==Array||Array.isArray&&Array.isArray(x)){ 66 | parents.push(x) 67 | for(i=0,l=x.length;i-1){ 71 | if(a.length) a.push('\n'+ss) 72 | a.push(sub)} 73 | else{ 74 | if(cols-sub.length < 0){a.push('\n'+ss);cols=opts.cols-ss.length} 75 | a.push(sub) 76 | cols-=sub.length}} 77 | parents.pop() 78 | return ((i=refs.lastIndexOf(x))>-1?'#'+i+'=':'') 79 | + '['+a.join('')+']'} 80 | parents.push(x) 81 | for(p in x) if(Object.prototype.hasOwnProperty.call(x,p)){ 82 | if(opts.hide.indexOf(p)>-1)sub=p+':' 83 | else sub=p+':'+go(x[p],(',:'+p).replace(/./g,' ')+ss) 84 | if(sub.indexOf('\n')>-1){ 85 | if(opts.reorder)defer.push(sub) 86 | else{ 87 | if(a.length)sub=','+sub 88 | if(a.length) a.push('\n'+ss) 89 | a.push(sub)}} 90 | else{ 91 | if(a.length)sub=','+sub 92 | if(cols-sub.length<0 && a.length){a.push('\n'+ss);cols=opts.cols-ss.length} 93 | a.push(sub) 94 | cols-=sub.length}} 95 | defer.sort(function(a,b){var la=lines(a),lb=lines(b); return la==lb?0:la>lb?1:-1}) 96 | for(i=0,l=defer.length;i-1?'#'+i+'=':'') 101 | + '{'+a.join('')+'}'}} 102 | 103 | function pp_quote(s,opts){opts=opts||{} 104 | if(opts.string_escape) s=s.replace(/\\/g,'\\\\').replace(/\n/g,'\\n') 105 | if(opts.string_limit && s.length>opts.string_limit)s=s.slice(0,opts.string_limit)+'…' 106 | if(s.indexOf("'")==-1)return "'"+s+"'" 107 | return '"'+s.replace(/"/g,'\\"')+'"'} 108 | 109 | /* 110 | 111 | // version for IRC bot 112 | // returns output on a single line, and adds E4X support 113 | 114 | function pp(o,depth){return pp_r(o,depth==undefined?8:depth)} 115 | 116 | function pp_r(o,d){var a=[],p 117 | if(!d)return '...' 118 | if(o===undefined)return 'undefined' 119 | if(o===null)return 'null' 120 | switch(typeof o){ 121 | case 'boolean':return o.toString() 122 | case 'string':return '"'+o.replace(/\n/g,'\\n').replace(/"/g,'\\"')+'"' 123 | case 'number':return o.toString() 124 | case 'xml':return o.toXMLString()} 125 | if(o instanceof RegExp)return '/'+o.source+'/' 126 | if(typeof o=='function')return o.toString().replace(/\s+/g,' ').replace(/(.{32}).+/,'$1…') 127 | if(o.constructor==Array){ 128 | o.forEach(function(e,i){ 129 | a.push(pp_r(o[i],d-1))}) 130 | return '['+a.join(',')+']'} 131 | if(o.constructor==Date){ 132 | return o.toString()} 133 | for(p in o) if(Object.prototype.hasOwnProperty.call(o,p)) 134 | a.push(p+':'+pp_r(o[p],d-1)) 135 | return '{'+a.join(',')+'}'} 136 | 137 | /* 138 | */ 139 | 140 | function quote_string_single(s){ 141 | return "'" 142 | + s.replace(/\\/g,'\\\\').replace(/\n/g,'\\n').replace(/'/g,"\\'") 143 | + "'"} 144 | 145 | function quote_string_double(s){ 146 | return '"' 147 | + s.replace(/\\/g,'\\\\').replace(/\n/g,'\\n').replace(/"/g,'\\"') 148 | + '"'} 149 | 150 | (function(){ 151 | function create(){ 152 | function log(o){var x,s='' 153 | if(arguments.length>1){o=[].concat(Array.prototype.slice.call(arguments))} 154 | if(log.timing){x=+new Date;s=x-(log.time||x)+'ms\n';log.time=x} 155 | log.log.push(s+(typeof o=='string'?o:pp(o)));return o} 156 | log.log=[] 157 | log.get=function(n){return '\n\n'+log.log.slice(n?-n:0).join('\n\n')} 158 | log.count=function(){return log.log.length} 159 | log.clear=function(){log.log=[]} 160 | log.limit=function(n){if(log.log.length>n)log.log=log.log.slice(-n)} 161 | return log} 162 | log=create() 163 | log.create=create})() 164 | 165 | var log 166 | 167 | function deepEq(x,y){var p 168 | if(x===y)return true 169 | if(typeof x!='object' 170 | || typeof y!='object')return false 171 | for(p in x)if(!deepEq(x[p],y[p]))return false 172 | for(p in y)if(!(p in x))return false 173 | return true} 174 | 175 | function extend(a,b){ 176 | for(var p in b)if(Object.prototype.hasOwnProperty.call(b,p))a[p]=b[p] 177 | return arguments.length==2?a:extend.apply(null,[a].concat(Array.prototype.slice.call(arguments,2)))} 178 | 179 | function event_interface(){var handlers 180 | handlers={} 181 | return {on:on 182 | ,event:event 183 | } 184 | function on(e,cb){(handlers[e]=handlers[e]||[]).push(cb)} 185 | function event(e,a1,a2){var args=[].slice.call(arguments,1);(handlers[e]||[]).forEach(function(cb){cb.apply(null,args)})}} 186 | -------------------------------------------------------------------------------- /hinting.js: -------------------------------------------------------------------------------- 1 | // hint :: partial command → hints object 2 | 3 | // partial command: 4 | // - verb (zero, one, or more) 5 | // - args (zero or more) 6 | // - ports (zero or more) 7 | // Each verb, arg, or port, is parsed as a token in the command, and the parser may know that the token is incomplete (e.g. unclosed quoted string) 8 | // The UI may also pass cursor position and editing history. 9 | 10 | // hints object: 11 | // - error conditions 12 | // - command effect hints 13 | // - completion suggestions 14 | // - deletion / occlusion hints 15 | 16 | function hint(partial,context,cb){var errors,effects,completions,deletions,verb 17 | errors=['fake error condition'] 18 | effects=[] 19 | completions=[]//{type:"arg",token:0,completion:"abc"}] 20 | deletions=[{type:"verb",token:0}] 21 | if(partial.verbs.length) verb=context.verbs[partial.verbs[partial.verbs.length-1]] 22 | if(verb){ 23 | if(!verb.desc)alert('verb without description') 24 | effects.push(verb.desc) 25 | if((partial.ports||[]).length<(verb.ports||[]).length){ 26 | completions.push({type:"port"})} 27 | completions.push(verb.args.join('; ')) 28 | } 29 | cb({errors:errors 30 | ,effects:effects 31 | ,completions:completions 32 | ,deletions:deletions})} -------------------------------------------------------------------------------- /ports/aws.js: -------------------------------------------------------------------------------- 1 | /* AWS port 2 | * all messages are proxied by the awsrpc module which must be installed and configured on the server 3 | */ 4 | 5 | window.PortAWS={} 6 | 7 | ;(function(exports){ 8 | 9 | exports.init=init 10 | 11 | function init(persistence,port_obj,args){var o,server_has_credentials,aws_access_key_id,aws_secret_access_key 12 | o=port_obj 13 | identify() 14 | server_has_credentials=false 15 | o.awsrpc_path=args[0] 16 | o.set_access_id=set_access_id 17 | o.set_secret_access_key=set_secret_access_key 18 | o.list_verified_email_addresses=list_verified_email_addresses 19 | o.request=request 20 | function set_access_id(id){ 21 | aws_access_key_id=id 22 | identify()} 23 | function set_secret_access_key(key){ 24 | aws_secret_access_key=key 25 | identify()} 26 | function list_verified_email_addresses(out){ 27 | request('ListVerifiedEmailAddresses',{},out)} 28 | 29 | function request(action,query,cb){var body,req 30 | //authenticate(after_auth) 31 | after_auth() 32 | function after_auth(){ 33 | req={method:'request' 34 | ,access_key_id:aws_access_key_id 35 | ,secret_access_key:aws_secret_access_key 36 | ,args:[action,query] 37 | } 38 | awsrpc_send(req,cb)}} 39 | 40 | function awsrpc_send(req,cb){ 41 | body=JSON.stringify(req) 42 | xhr('POST',o.awsrpc_path,body,function(x){cb(x.responseText)})}//XXX TODO: handle errors 43 | 44 | function authenticate(cb){ 45 | if(server_has_credentials)return setTimeout(cb,0) 46 | if(!aws_access_key_id||!aws_secret_access_key){alert('access key id or secret key not set');return} 47 | awsrpc_send({method:'setCredentials',args:[aws_access_key_id,aws_secret_access_key]},function(resp){if(resp!="Accepted")return alert('Error submitting authentication credentials to server');server_has_credentials=true;cb()}) 48 | } 49 | 50 | function identify(){ 51 | o.event('identify','AWS: ' 52 | +(aws_access_key_id 53 | ?aws_secret_access_key 54 | ?'access key id '+aws_access_key_id 55 | :aws_access_key_id+' (no secret access key set (use setsecretaccesskey))' 56 | :'no access id (use setaccesskeyid)'))} 57 | 58 | } 59 | 60 | })(PortAWS); 61 | -------------------------------------------------------------------------------- /ports/fake.js: -------------------------------------------------------------------------------- 1 | /* Fake filesystem port 2 | */ 3 | 4 | window.PortFake={} 5 | 6 | ;(function(exports){ 7 | 8 | exports.init=init 9 | 10 | function init(persistence,o){var obj 11 | obj={insert:insert 12 | ,update:update 13 | ,populate:populate 14 | ,serialize:serialize 15 | ,setprop:setprop 16 | ,remove:remove 17 | ,persist:persist 18 | } 19 | extend(o,obj) 20 | obj=o 21 | persistence.read('listing',function(err,data){obj.data=data||{};update()}) 22 | return o 23 | function insert(a,b){ 24 | obj.data[a]=b} 25 | function populate(str){var o 26 | o=JSON.parse(str) 27 | obj.data=o} 28 | function serialize(){ 29 | return JSON.stringify(obj.data)} 30 | function setprop(path,prop,val){ 31 | obj.data[path][prop]=val} 32 | function remove(path){var x 33 | x=obj.data[path] 34 | delete obj.data[path] 35 | return x} 36 | function update(){ 37 | obj.event('identify',JSON.stringify(obj.data))} 38 | function persist(){ 39 | persistence.write('listing',obj.data)} 40 | } 41 | 42 | })(PortFake); 43 | -------------------------------------------------------------------------------- /ports/http.js: -------------------------------------------------------------------------------- 1 | /* HTTP port for same-origin and CORS requests (pure XHR without a server proxy) 2 | */ 3 | 4 | window.PortHTTP={} 5 | 6 | ;(function(exports){ 7 | 8 | exports.init=init 9 | 10 | function init(persistence,obj){ 11 | extend(obj, 12 | {request:request 13 | }) 14 | 15 | setTimeout(identify,0) 16 | 17 | function request(method,uri,body,cb){ 18 | xhr(method,uri,body,cb)} 19 | 20 | function identify(){ 21 | obj.event('identify','HTTP')} 22 | 23 | } 24 | 25 | })(PortHTTP); -------------------------------------------------------------------------------- /ports/nhttpd.js: -------------------------------------------------------------------------------- 1 | // nhttpd configuration port 2 | 3 | window.PortNHTTPD={} 4 | 5 | ;(function(exports){ 6 | 7 | exports.init=function init(persistence,o,args){var nhttpd_conf_path,dispatch_path,read,write 8 | 9 | read=persistence.read 10 | write=persistence.write 11 | nhttpd_conf_path=args[0] 12 | o.devmode=devmode 13 | o.dispatch_ls=dispatch_ls 14 | o.dispatch_cd=dispatch_cd 15 | o.dispatch_insert_before=dispatch_insert_before 16 | o.dispatch_update=dispatch_update 17 | if(!o)throw new Error('missing path argument') 18 | identify() 19 | read('dispatch_path',function(e,s){ 20 | dispatch_path=e?'':s 21 | identify()}) 22 | 23 | function devmode(devmode_path){ 24 | } 25 | 26 | function dispatch_ls(out){ 27 | xhr('GET',current_path(),null,function(x){out(x.responseText)}) 28 | } 29 | 30 | function dispatch_cd(path,out){ 31 | dispatch_path=path[0]=='/'?path.slice(1):dispatch_path+path // XXX horrid 32 | write('dispatch_path',dispatch_path) 33 | identify() 34 | out(dispatch_path)} 35 | 36 | function dispatch_insert_before(matchtype,matchtok,value,ref,out){ 37 | req={before:ref 38 | ,value:[matchtype,matchtok,value]} 39 | conf_dispatch_POST(req,out)} 40 | 41 | function dispatch_update(placement,ref,value,out){ 42 | req={value:value} 43 | if(ref===undefined)ref=true 44 | req[placement]=ref 45 | conf_dispatch_POST(req,out)} 46 | 47 | function conf_dispatch_POST(obj,out){var uri 48 | uri=current_path() 49 | xhr({method:'POST',headers:{"Content-Type":"application/json"}},uri,JSON.stringify(obj),function(x){ 50 | out(x.status+'\n'+x.responseText)})} 51 | 52 | function current_path(){return nhttpd_conf_path+"dispatch/"+dispatch_path} 53 | 54 | function identify(){ 55 | o.event('identify','nhttpd: '+current_path())} 56 | 57 | } 58 | 59 | })(PortNHTTPD); -------------------------------------------------------------------------------- /ports/rvs.js: -------------------------------------------------------------------------------- 1 | /* Revstore port 2 | */ 3 | 4 | window.PortRevstore={} 5 | 6 | ;(function(exports){"use strict" 7 | 8 | exports.init=init 9 | 10 | function init(persistence,o,args){var revstore_root 11 | o.history=history 12 | o.cd=cd 13 | o.cat=cat 14 | o.ls=ls 15 | o.mv=mv 16 | o.ln=ln 17 | o.rm=rm 18 | o.put=put 19 | revstore_root=o.revstore_root=args[0] 20 | o.PWD=args[0] 21 | o.update=update 22 | o.persist=persist 23 | o.resolve=resolve_relative 24 | o.parse_history=parse_history 25 | persistence.read("path",function(e,x){o.PWD=x||revstore_root;update()}) 26 | function update(){ 27 | o.event('identify','rvs: '+o.PWD)} 28 | function persist(){ 29 | persistence.write("path",o.PWD)} 30 | function history(path,out){ 31 | xhr('GET',resolve(o.PWD,path||'')+'?history',null,function(x){var a,i,l,ts,ret 32 | a=JSON.parse(x.responseText) 33 | ts=0 34 | ret=[] 35 | for(i=0,l=a.length;i 2 | Shell prototype 3 | 70 | 71 | 72 | 73 |

  74 | 
  75 | 
  76 | 
    77 | 78 | 84 | 86 |
      87 |
        88 |
          89 |
            90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /verbs/aws: -------------------------------------------------------------------------------- 1 | {"setaccesskeyid": 2 | {"desc":"set [id] as the Access Key ID to use on the port [aws]" 3 | ,"ports":["aws:AWSPort"] 4 | ,"args":["id:String"] 5 | ,"outs":[] 6 | ,"f":"function(cmd){cmd.ports[0].set_access_id(cmd.args[0])}" 7 | } 8 | ,"setsecretaccesskey": 9 | {"desc":"set the Secret Access Key for use on the port [aws] (via a prompt)" 10 | ,"ports":["aws:AWSPort"] 11 | ,"args":[] 12 | ,"outs":[] 13 | ,"f":"function(cmd){var pass=prompt('Please enter your AWS Secret Access Key.');if(pass){cmd.ports[0].set_secret_access_key(pass);cmd.out('set from prompt')}else{cmd.out('canceled')}}" 14 | } 15 | ,"listverifiedemailaddresses": 16 | {"desc":"Ask Amazon SES for the list of verified addresses on the account" 17 | ,"ports":["aws:AWSPort"] 18 | ,"args":[] 19 | ,"outs":["resp:String"] 20 | ,"f":"function(cmd){cmd.ports[0].list_verified_email_addresses(cmd.out)}" 21 | } 22 | ,"verifyemailaddress": 23 | {"desc":"verify a new address, causing a verification email to be sent" 24 | ,"ports":["aws:AWSPort"] 25 | ,"args":["address:String"] 26 | ,"outs":[] 27 | ,"f":"function(cmd){cmd.ports[0].request('VerifyEmailAddress',{EmailAddress:cmd.args[0]},cmd.out)}" 28 | } 29 | ,"deleteverifiedemailaddress": 30 | {"desc":"delete a verified email address" 31 | ,"ports":["aws:AWSPort"] 32 | ,"args":["address:String"] 33 | ,"outs":[] 34 | ,"f":"function(cmd){cmd.ports[0].request('DeleteVerifiedEmailAddress',{EmailAddress:cmd.args[0]},cmd.out)}" 35 | } 36 | ,"sendemail": 37 | {"desc":"send an email message through AWS SES" 38 | ,"ports":["aws:AWSPort"] 39 | ,"args":["message:Object"] 40 | ,"outs":["messageID:String"] 41 | ,"f":"function(cmd){cmd.ports[0].request('SendEmail',cmd.args[0],cmd.out)}" 42 | } 43 | } -------------------------------------------------------------------------------- /verbs/csv: -------------------------------------------------------------------------------- 1 | {"_metadata_": 2 | {"includes": 3 | {"$panpg_util.js$":"deps/PanPG_util.js" 4 | ,"$csv_util.js$":"deps/csv_util.js" 5 | } 6 | } 7 | ,"parsecsv": 8 | {"desc":"Parse CSV file into an array of arrays" 9 | ,"args":["csv:String"] 10 | ,"outs":["data:[[String]]"] 11 | ,"f":"function parsecsv(cmd){var PanPG_util;$panpg_util.js$;$csv_util.js$;cmd.out(parse_csv(cmd.args[0]))}" 12 | } 13 | } -------------------------------------------------------------------------------- /verbs/deps/3box_util.js: -------------------------------------------------------------------------------- 1 | /* copied from f_confParser.js */ 2 | function parse3boxConf(s){var 3 | ls=s.split('\n'),a=[],b=[],c=[],d=[],e=[],ws 4 | ls.forEach(function(l){ 5 | ws=l.split(/ +/) 6 | switch(ws.shift()){ 7 | case 'Files:': a=a.concat(ws); break 8 | case 'Feedbacks:': b=b.concat(ws); break 9 | case 'Output:': c.push(ws); break 10 | case 'HTML:': d.push(ws); break 11 | case 'TAL:': e.push(ws); break}}) 12 | return {files:a,feedbacks:b,outputs:c,html:d,tal:e}} 13 | -------------------------------------------------------------------------------- /verbs/deps/PanPG_util.js: -------------------------------------------------------------------------------- 1 | /* Utility functions on PanPG parse trees. 2 | * PanPG version 0.0.10 3 | * built on Thu, 18 Aug 2011 18:51:30 GMT 4 | * http://boshi.inimino.org/3box/PanPG/about.html 5 | * MIT Licensed 6 | */ 7 | 8 | ;(function(exports){ 9 | 10 | // (event array (can be partial), [name array], [input string], [state]) → [ascii-art tree, state] 11 | // -or- 12 | // (complete event array, [name array], [input string]) → ascii-art 13 | // if the event array doesn't describe a complete, finished tree, or if the state value argument is provided, then the ascii-art and the state value will be returned as an array 14 | // this is for examining partial tree fragments as they are generated by a streaming parser 15 | 16 | function showTree(res,opts,state){var names,str,a,i,l,indent,name,x,y,out=[],output_positions=[],node,out_pos,state_was_passed 17 | if(!res[0])return showError(res) 18 | res=res[1] 19 | names=res.names 20 | a=res.tree 21 | str=res.input 22 | opts=opts||{} 23 | opts.elide=opts.elide||['anonymous'] 24 | opts.drop=opts.drop||[] 25 | state_was_passed=!!state 26 | state=state||{stack:[],indent:'',pos:0,drop_depth:0} 27 | for(i=0,l=a.length;i0){ 29 | if(names){ 30 | name=names[x] 31 | if(!name) return err('no such rule index in name array: '+x)} 32 | else name=''+x 33 | output_positions[state.stack.length]=out.length 34 | node={index:x,name:name,start:state.pos} 35 | if(opts.drop.indexOf(name)>-1)state.drop_depth++ 36 | out.push(show(state,node)) 37 | state.indent+=' ' 38 | state.stack.push(node)} 39 | else if(x==-1){ 40 | i++ 41 | if(i==l){i--;return} 42 | node={name:'anonymous',start:state.pos,end:state.pos+a[i]} 43 | state.pos=node.end 44 | out.push(show(state,node)) 45 | } 46 | else if(x==-2){ 47 | i++ 48 | if(i==l)return err('incomplete close event, expected length at position '+i+' but found end of input array') 49 | y=state.stack.pop() 50 | state.pos=y.end=y.start+a[i] 51 | out_pos=output_positions[state.stack.length] 52 | state.indent=state.indent.slice(0,-1) 53 | if(out_pos!=undefined){ 54 | out[out_pos]=show(state,y)} 55 | if(opts.drop.indexOf(y.name)>-1)state.drop_depth--} 56 | else return err('invalid event '+x+' at position '+i)} 57 | if(state_was_passed || state.stack.length) return [out.join(''),state] 58 | else return out.join('') 59 | function err(s){return ['showTree: '+s]} 60 | function show(state,node){var text='',main,indent,l 61 | if(opts.elide.indexOf(node.name)>-1)return '' 62 | if(state.drop_depth)return '' 63 | if(node.end!=undefined && str){ 64 | text=show_str(str.slice(node.start,node.end))} 65 | main=state.indent+node.name+' '+node.start+'-'+(node.end==undefined?'?':node.end) 66 | l=main.length 67 | indent=Array(32*Math.ceil((l+2)/32)-l).join(' ') 68 | return main+indent+text+'\n'} 69 | function show_str(s){ 70 | return '»'+s.replace(/\n/g,'\\n').replace(/\r/g,'\\r').replace(/(.{16}).{8,}/,"$1…")+'«'}} 71 | 72 | // inspired by: http://gist.github.com/312863 73 | function showError(res){var line_number,col,lines,line,start,end,prefix,suffix,arrow,pos,msg,str 74 | pos=res[1];msg=res[2];str=res[3] 75 | msg=msg||'Parse error' 76 | if(str==undefined)return msg+' at position '+pos 77 | prefix=str.slice(0,pos) 78 | suffix=str.slice(pos) 79 | line_number=prefix.split('\n').length 80 | start=prefix.lastIndexOf('\n')+1 81 | end=suffix.indexOf('\n') 82 | if(end==-1) end=str.length 83 | else end=prefix.length+end 84 | line=str.slice(start,end) 85 | line=line.replace(/\t/g,' ') 86 | col=pos-start 87 | arrow=Array(col).join('-')+'^' 88 | return msg+' at line '+line_number+' column '+col+'\n'+line+'\n'+arrow} 89 | 90 | function showResult(r,opts){ 91 | if(r[0])return showTree(r,opts) 92 | return showError(r)} 93 | 94 | function treeWalker(dict,result){var p,any,anon,other,fail,except,index,cb=[],stack=[],frame,pos=0,i,l,x,retval,names,events,begin=[],match,target,msg 95 | fail=dict.fail 96 | except=dict.exception 97 | if(!result[0]){ 98 | msg='parse failed: '+result[1]+' '+(result[2]||'') 99 | if(fail)return fail(result)||msg 100 | return err(msg)} 101 | result=result[1] 102 | names=result.names 103 | events=result.tree 104 | for(p in dict) if(dict.hasOwnProperty(p)){ 105 | if(p=='any'){any=dict[p];throw new Error('unimplemented, use `other` instead')} 106 | if(p=='anonymous'||p=='anon'){anon=dict[p];continue} 107 | if(p=='other'){other=dict[p];continue} 108 | if(p=='fail'){fail=dict[p];continue} 109 | if(p=='exception'){except=dict[p];continue} 110 | if(p=='warn'){continue} 111 | target=cb 112 | if(match=/(.*) start/.exec(p)){p=m[1];target=begin} 113 | index=names.indexOf(p) 114 | if(index==-1)return err('rule not found in rule names: '+p) 115 | target[index]=dict[p]} 116 | frame={cn:[]} 117 | for(i=0,l=events.length;i0){ // named rule start 119 | stack.push(frame) 120 | frame={index:x,start:pos} 121 | if(begin[x]){ 122 | try{retval=begin[x](pos)} 123 | // here we call err() but continue iff `except` returns true 124 | catch(e){if(!err('exception in '+names[x]+' start:'+e))return}} 125 | if(cb[x]||any||other) frame.cn=[]} 126 | else if(x==-1){ // anonymous node 127 | i++ 128 | if(i==l)return err('incomplete anonymous node') 129 | if(anon)anon(m(pos,pos+events[i])) 130 | pos+=events[i]} 131 | else if(x==-2){ // node close 132 | i++ 133 | if(i==l)return err('incomplete rule close') 134 | pos=frame.start+events[i] 135 | x=frame.index 136 | match=m(frame.start,pos) 137 | try{ 138 | if(cb[x]) retval=cb[x](match,frame.cn) 139 | else if(other)retval=cb[x](match,frame.cn,names[x])} 140 | catch(e){return err('exception in '+names[x]+': '+e+' (on node at char '+match.start+'-'+match.end+')')} 141 | frame=stack.pop() // the parent node 142 | if(cb[x] && retval!==undefined) 143 | if(frame.cn)frame.cn.push(retval) 144 | else warn('ignored return value of '+names[x]+' in '+names[frame.index])} 145 | else return err('invalid event stream (saw '+x+' at position '+i+')')} 146 | if(frame.cn)return frame.cn[0] 147 | function m(s,e){ 148 | return {start:s 149 | ,end:e 150 | ,text:function(){return result.input.slice(s,e)}}} 151 | function err(s){ 152 | if(except)return except(s) 153 | throw new Error('treeWalker: '+s)} 154 | function warn(s){ 155 | if(dict.warn)dict.warn(s)}} 156 | 157 | exports.showTree=showTree 158 | exports.treeWalker=treeWalker 159 | 160 | })(typeof exports=='object'?exports:PanPG_util={}); 161 | -------------------------------------------------------------------------------- /verbs/deps/csv_util.js: -------------------------------------------------------------------------------- 1 | /** 3box_verb:tocsv 2 | desc:"Create a CSV string from an array of arrays" 3 | args:["records:[[String]]"] 4 | f:to_csv 5 | 6 | 3box_verb:parsecsv 7 | desc:"Parse a csv file into an array of arrays" 8 | args:["csv:String"] 9 | f:parse_csv 10 | 11 | N.B. nothing interprets these comments (just an idea) 12 | */ 13 | function to_csv(records){ 14 | return records.map(function(fields){ 15 | return fields.map(function(field){var quote=false 16 | field=field.replace(/[",\r\n]/g,function(m){quote=true;return m[0]=='"'?'""':m[0]}) 17 | return quote?'"'+field+'"':field 18 | }).join(',')+'\r\n'}).join('')} 19 | 20 | /* 21 | 22 | CSV ← EmptyLine* (EOF / Record (LB+ Record)* EmptyLine*) 23 | 24 | EmptyLine ← LB 25 | LB ← CR LF? / LF 26 | CR ← [U+000D] 27 | LF ← [U+000A] 28 | 29 | Record ← (Field/NullField &",") ("," (Field/NullField))* 30 | 31 | EOF ← ![^] 32 | 33 | Comma ← "," 34 | 35 | Field ← DQuote ( TextData / Comma / CR / LF / DDQuote )* DQuote 36 | / TextData 37 | 38 | NullField ← "" 39 | 40 | DQuote ← ["] 41 | 42 | DDQuote ← DQuote DQuote 43 | 44 | TextData ← [^ U+000A U+000D " , ]+ 45 | 46 | Parser below generated from grammar above by http://boshi.inimino.org/3box/PanPG/build/demo.html 47 | 48 | */ 49 | 50 | CSV.names=['','CSV','EmptyLine','LB','CR','LF','Record','EOF','Comma','Field','NullField','DQuote','DDQuote','TextData','_'] 51 | function CSV(out){var eof=false,s='',l=0,S=57344,T,M,F,D,R,tbl=[],x,pos=0,offset=0,buf=[],bufs=[],states=[],posns=[],c,equiv,ds,dp,failed=0,emp=0,emps=[]; 52 | equiv=rle_dec([10,0,1,1,2,0,1,2,20,0,1,3,9,0,1,4,55251,0,2048,5,8192,0]) 53 | function rle_dec(a){var r=[],i,l,n,x,ll;for(i=0,l=a.length;i=s.length){ds=pr;dp=i-1;return}ds=0;dp=undefined;if(st==l_ss){pos=i;return true}return false}}} 61 | if(typeof out=='string'){s=out;out=[];x=CSV(function(m,x,y){if(m=='fail')out=[false,x,y,s];if(m=='tree segment')out=out.concat(x)});x('chunk',s);x('eof');return out[0]===false?out:[true,{names:CSV.names,tree:out,input:s}]} 62 | return function(m,x){if(failed){out('fail',pos,'parse already failed');return} 63 | switch(m){ 64 | case 'chunk':s+=x;l=s.length;while(tbl.length83||S<80)) 70 | t_block:{ 71 | if(S&4/*pushpos*/)posns.push(pos) 72 | if(S&2/*t_bufferout*/){bufs.push(buf);buf=[]} 73 | if(S&8/*t_emitstate*/){emps.push(emp);emp=pos;buf.push(S>>>12)} 74 | if(S&1/*cache*/&&(x=tbl[pos-offset][S])!=undefined){if(x){R=true;pos=x[0];buf=x[1];if(emp>>12]){R=D[S>>>12](ds||0,dp||pos);if(R==undefined){if(eof){ds=dp=undefined;R=false}else{out('ready');return}}} 78 | else{states.push(S);S=T[S>>>12]} 79 | if(S==80){R=true;S=states.pop()}} 80 | while(R!=undefined){ 81 | if(S==57344){(R?emit:fail)();return}if(R){ 82 | if(S&1/*cache*/){tbl[posns[posns.length-1]][S]=[pos,buf,emp];buf=buf.slice()} 83 | if(S&8/*t_emitstate*/){if(pos!=emp&&emp!=posns[posns.length-1]){buf.push(-1,pos-emp)}emp=emps.pop();if(emp!=posns[posns.length-1]){buf=[-1,posns[posns.length-1]-emp].concat(buf)}} 84 | if(S&16/*m_emitstate*/)buf.push(S>>>12) 85 | if(S&32/*m_emitclose*/)buf.push(-2) 86 | if(S&128/*m_emitlength*/)buf.push(pos-posns[posns.length-1]) 87 | if(S&8/*t_emitstate*/){emp=pos} 88 | if(S&256/*m_resetpos*/)pos=posns[posns.length-1] 89 | if(S&4/*pushpos*/)posns.pop() 90 | if(S&512/*m_tossbuf*/)buf=bufs.pop() 91 | if(S&1024/*m_emitbuf*/){buf=bufs.pop().concat(buf);} 92 | if(!bufs.length&&buf.length>64)emit() 93 | S=M[S>>>12]} 94 | else{ 95 | if(S&1/*cache*/)tbl[posns[posns.length-1]][S]=false 96 | if(S&4/*pushpos*/)pos=posns.pop() 97 | if(S&2048/*f_tossbuf*/)buf=bufs.pop() 98 | if(S&8/*t_emitstate*/){emp=emps.pop()} 99 | if(emp>pos){emp=pos} 100 | S=F[S>>>12]} 101 | if(S==78){R=true;S=states.pop()}else if(S==79){R=false;S=states.pop()}else R=undefined;}}} 102 | function emit(){var x=bufs.length?bufs[0]:buf;if(x.length){out('tree segment',x);if(bufs.length)bufs[0]=[];else buf=[]}} 103 | function fail(s){out('fail',pos,s);failed=1}} 104 | 105 | 106 | function parse_csv(s){var cbs 107 | cbs= 108 | {CSV: function(m,cn){return cn} 109 | ,Record: function(m,cn){return cn} 110 | ,Field: function(m,cn){return cn.join('')} 111 | ,NullField: function(m,cn){return ''} 112 | ,TextData:function(m,cn){return m.text()} 113 | ,Comma:function(m,cn){return ','} 114 | ,DDQuote:function(m,cn){return '"'} 115 | } 116 | return PanPG_util.treeWalker(cbs,CSV(s))} 117 | -------------------------------------------------------------------------------- /verbs/deps/lists.js: -------------------------------------------------------------------------------- 1 | function foldl1(f,a){var x,i,l 2 | x=a[0] 3 | for(i=1,l=a.length;i": ">", 88 | '"': '"', 89 | "'": ''' 90 | }; 91 | 92 | function escapeHTML(string) { 93 | return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) { 94 | return escapeMap[s] || s; 95 | }); 96 | } 97 | 98 | /** 99 | * Adds the `template`, `line`, and `file` properties to the given error 100 | * object and alters the message to provide more useful debugging information. 101 | */ 102 | function debug(e, template, line, file) { 103 | file = file || "