terminus 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +61 -0
- data/bin/terminus +33 -0
- data/lib/public/js.packages.js +1 -0
- data/lib/public/style.css +44 -0
- data/lib/public/terminus.js +15 -0
- data/lib/terminus.rb +23 -0
- data/lib/terminus/application.rb +24 -0
- data/lib/terminus/server.rb +48 -0
- data/lib/views/bookmarklet.erb +40 -0
- data/lib/views/index.erb +29 -0
- metadata +133 -0
data/README.rdoc
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
= Terminus
|
2
|
+
|
3
|
+
* http://github.com/jcoglan/terminus
|
4
|
+
|
5
|
+
Terminus is a distributed JavaScript terminal. It lets you connect
|
6
|
+
web browsers you have open to a command-line console and run code
|
7
|
+
on all connected browsers at once. It's currently a toy, but it
|
8
|
+
might turn into a useful debugging and testing tool. We'll see.
|
9
|
+
|
10
|
+
|
11
|
+
== Installation
|
12
|
+
|
13
|
+
sudo gem install terminus
|
14
|
+
|
15
|
+
|
16
|
+
== Usage
|
17
|
+
|
18
|
+
In your terminal, run the +terminus+ command. You can optionally
|
19
|
+
specify a port for it to open up like this:
|
20
|
+
|
21
|
+
terminus --port 7004
|
22
|
+
|
23
|
+
You should see something like this:
|
24
|
+
|
25
|
+
Terminus running at http://0.0.0.0:7004
|
26
|
+
>>
|
27
|
+
|
28
|
+
This is the Terminus prompt - it's where you type in JavaScript
|
29
|
+
commands. Before you do that, though, visit http://localhost:7004
|
30
|
+
in your browser. From there you can grab a bookmarklet that lets
|
31
|
+
you connect any web page to the Terminus server. When you click
|
32
|
+
the bookmarklet, the current page will open a connection with
|
33
|
+
Terminus so that any commands you type into the shell are executed
|
34
|
+
on that page.
|
35
|
+
|
36
|
+
|
37
|
+
== License
|
38
|
+
|
39
|
+
(The MIT License)
|
40
|
+
|
41
|
+
Copyright (c) 2010 James Coglan
|
42
|
+
|
43
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
44
|
+
a copy of this software and associated documentation files (the
|
45
|
+
'Software'), to deal in the Software without restriction, including
|
46
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
47
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
48
|
+
permit persons to whom the Software is furnished to do so, subject to
|
49
|
+
the following conditions:
|
50
|
+
|
51
|
+
The above copyright notice and this permission notice shall be
|
52
|
+
included in all copies or substantial portions of the Software.
|
53
|
+
|
54
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
55
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
56
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
57
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
58
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
59
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
60
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
61
|
+
|
data/bin/terminus
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'readline'
|
5
|
+
require 'oyster'
|
6
|
+
require File.dirname(__FILE__) + '/../lib/terminus'
|
7
|
+
|
8
|
+
spec = Oyster.spec do
|
9
|
+
name "terminus -- controll web browsers from the command line"
|
10
|
+
synopsis "terminus [--port PORT]"
|
11
|
+
|
12
|
+
integer :port, :default => 7004
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
options = spec.parse
|
17
|
+
app = Terminus.create(options)
|
18
|
+
|
19
|
+
app.run!
|
20
|
+
puts "Terminus running at http://0.0.0.0:#{options[:port]}"
|
21
|
+
puts "Press CTRL-C to exit"
|
22
|
+
|
23
|
+
trap("INT") { app.stop! ; exit }
|
24
|
+
|
25
|
+
loop {
|
26
|
+
script, result = Readline.readline('>> '), nil
|
27
|
+
Readline::HISTORY.push(script)
|
28
|
+
app.execute(script) { |r| result = r }
|
29
|
+
}
|
30
|
+
|
31
|
+
rescue Oyster::HelpRendered
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
this.JS=this.JS||{};JS.Package=function(a){var b=JS.Package.OrderedSet;JS.Package._3(this);this._0=a;this._1=new b();this._4=new b();this._9=new b();this._2={};this._5={}};(function(f){var l=f.OrderedSet=function(a,b){this._a=this.list=[];this._3={};if(!a)return;for(var c=0,d=a.length;c<d;c++)this.push(b?b(a[c]):a[c])};l.prototype.push=function(a){var b=(a.id!==undefined)?a.id:a,c=this._3;if(c.hasOwnProperty(b))return;c[b]=this._a.length;this._a.push(a)};f._b=this;if((this.document||{}).getElementsByTagName){var m=document.getElementsByTagName('script')[0];f._i=(m.readyState!==undefined)}var h=f.prototype;h.addDependency=function(a){this._4.push(a)};h.addSoftDependency=function(a){this._9.push(a)};h.addName=function(a){this._1.push(a);f.getFromCache(a).pkg=this};h.onload=function(a){this._c=a};h.on=function(a,b,c){if(this._5[a])return b.call(c);var d=this._2[a]=this._2[a]||[];d.push([b,c])};h.fire=function(a){if(this._5[a])return false;this._5[a]=true;var b=this._2[a];if(!b)return true;delete this._2[a];for(var c=0,d=b.length;c<d;c++)b[c][0].call(b[c][1]);return true};h.isLoaded=function(a){if(!a&&this._6!==undefined)return this._6;var b=this._1.list,c=b.length,d,e;while(c--){d=b[c];e=f.getObject(d);if(e!==undefined)continue;if(a)throw new Error('Expected package at '+this._0+' to define '+d);else return this._6=false}return this._6=true};h.load=function(){if(!this.fire('request'))return;var c=this._4.list.concat(this._9.list),d='load',e={};e[d]=this._4.list;f.when(e,function(){f.when({complete:c,load:[this]},function(){this.fire('complete')},this);var a=this,b=function(){if(a._c)a._c();a.isLoaded(true);a.fire('load')};if(this.isLoaded()){this.fire('download');return this.fire('load')}if(this._0===undefined)throw new Error('No load path found for '+this._1.list[0]);typeof this._0==='function'?this._0(b):f.Loader.loadFile(this._0,b);this.fire('download')},this)};h.toString=function(){return'Package:'+this._1.list.join(',')};f.when=function(b,c,d){var e=[],g,j,i;for(g in b){if(!b.hasOwnProperty(g))continue;j=new f.OrderedSet(b[g],function(a){return f.getByName(a)});i=j.list.length;while(i--)e.push([g,j.list[i]])}var k=i=e.length;if(k===0)return c&&c.call(d);while(i--){e[i][1].on(e[i][0],function(){k-=1;if(k===0&&c)c.call(d)});e[i][1].load()}};f._d=1;f._e={};f._f={};f._3=function(a){a.id=this._d;this._d+=1};f.getByPath=function(a){var b=a.toString();return this._e[b]=this._e[b]||new this(a)};f.getByName=function(a){if(typeof a!=='string')return a;var b=this.getFromCache(a);if(b.pkg)return b.pkg;var c=new this();c.addName(a);return c};f.getFromCache=function(a){return this._f[a]=this._f[a]||{}};f.getObject=function(a){var b=this.getFromCache(a);if(b.obj!==undefined)return b.obj;var c=this._b,d=a.split('.'),e;while(e=d.shift())c=c&&c[e];return this.getFromCache(a).obj=c}})(JS.Package);JS.Package.DomLoader={usable:function(){return!!JS.Package.getObject('window.document.getElementsByTagName')},__FILE__:function(){var a=document.getElementsByTagName('script');return a[a.length-1].src},loadFile:function(c,d){var e=this,g=document.createElement('script');g.type='text/javascript';g.src=c;g.onload=g.onreadystatechange=function(){var a=g.readyState,b=g.status;if(!a||a==='loaded'||a==='complete'||(a===4&&b===200)){d();g.onload=g.onreadystatechange=e._g;g=null}};if(window.console&&console.info)console.info('Loading '+c);document.getElementsByTagName('head')[0].appendChild(g)},_g:function(){}};JS.Package.ServerLoader={usable:function(){return typeof JS.Package.getObject('load')==='function'&&typeof JS.Package.getObject('version')==='function'},setup:function(){var b=this;load=(function(a){return function(){b._h=arguments[0];return a.apply(JS.Package._b,arguments)}})(load)},__FILE__:function(){return this._h},loadFile:function(a,b){load(a);b()}};(function(){var a=[JS.Package.DomLoader,JS.Package.ServerLoader],b=a.length,c,d;for(c=0;c<b;c++){d=a[c];if(d.usable()){JS.Package.Loader=d;if(d.setup)d.setup();break}}})();JS.Package.DSL={__FILE__:function(){return JS.Package.Loader.__FILE__()},pkg:function(a,b){var c=b?JS.Package.getByPath(b):JS.Package.getByName(a);c.addName(a);return new JS.Package.Description(c)},file:function(a){var b=JS.Package.getByPath(a);return new JS.Package.Description(b)},load:function(a,b){JS.Package.Loader.loadFile(a,b)}};JS.Package.Description=function(a){this._7=a};(function(e){e._8=function(a,b){var c=b.length,a=this._7[a],d;for(d=0;d<c;d++)a.call(this._7,b[d]);return this};e.provides=function(){return this._8('addName',arguments)};e.requires=function(){return this._8('addDependency',arguments)};e.uses=function(){return this._8('addSoftDependency',arguments)};e.setup=function(a){this._7.onload(a);return this}})(JS.Package.Description.prototype);JS.Package.DSL.loader=JS.Package.DSL.file;JS.Packages=function(a){a.call(JS.Package.DSL)};JS.require=function(){var a=[],b=0;while(typeof arguments[b]==='string'){a.push(arguments[b]);b+=1}JS.Package.when({complete:a},arguments[b],arguments[b+1])};require=JS.require;
|
@@ -0,0 +1,44 @@
|
|
1
|
+
body {
|
2
|
+
background: #222;
|
3
|
+
margin: 0;
|
4
|
+
padding: 40px;
|
5
|
+
}
|
6
|
+
|
7
|
+
.container {
|
8
|
+
background: #fff;
|
9
|
+
color: #444;
|
10
|
+
margin: 0 auto;
|
11
|
+
padding: 40px;
|
12
|
+
width: 400px;
|
13
|
+
font: 16px/1.5 Georgia, Times New Roman, serif;
|
14
|
+
}
|
15
|
+
|
16
|
+
.footer {
|
17
|
+
margin: 0 auto;
|
18
|
+
padding: 40px;
|
19
|
+
width: 400px;
|
20
|
+
color: #fff;
|
21
|
+
font: 13px Helvetica, Arial, sans-serif;
|
22
|
+
}
|
23
|
+
|
24
|
+
h1 {
|
25
|
+
font: bold 64px Helvetica, Arial, sans-serif;
|
26
|
+
letter-spacing: -0.03em;
|
27
|
+
margin: 0 0 32px;
|
28
|
+
color: #f00846;
|
29
|
+
}
|
30
|
+
|
31
|
+
a {
|
32
|
+
color: #f00846;
|
33
|
+
text-decoration: none;
|
34
|
+
}
|
35
|
+
|
36
|
+
a:hover {
|
37
|
+
background: #f00846;
|
38
|
+
color: #fff;
|
39
|
+
}
|
40
|
+
|
41
|
+
p {
|
42
|
+
margin: 0;
|
43
|
+
}
|
44
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
Terminus = {
|
2
|
+
connect: function(endpoint) {
|
3
|
+
if (this._client) return;
|
4
|
+
|
5
|
+
this._client = new Faye.Client(endpoint);
|
6
|
+
this._client.subscribe('/terminus/commands', function(message) {
|
7
|
+
this.execute(message.command);
|
8
|
+
}, this);
|
9
|
+
},
|
10
|
+
|
11
|
+
execute: function(command) {
|
12
|
+
eval(command);
|
13
|
+
}
|
14
|
+
};
|
15
|
+
|
data/lib/terminus.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rack'
|
3
|
+
require 'thin'
|
4
|
+
require 'eventmachine'
|
5
|
+
require 'faye'
|
6
|
+
require 'sinatra'
|
7
|
+
require 'packr'
|
8
|
+
|
9
|
+
%w[application server].each do |file|
|
10
|
+
require File.join(File.dirname(__FILE__), 'terminus', file)
|
11
|
+
end
|
12
|
+
|
13
|
+
Thin::Logging.silent = true
|
14
|
+
|
15
|
+
module Terminus
|
16
|
+
VERSION = '0.1.0'
|
17
|
+
FAYE_MOUNT = '/messaging'
|
18
|
+
|
19
|
+
def self.create(options = {})
|
20
|
+
Server.new(options)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Terminus
|
2
|
+
class Application < Sinatra::Base
|
3
|
+
|
4
|
+
ROOT = File.expand_path(File.dirname(__FILE__) + '/../')
|
5
|
+
|
6
|
+
set :static, true
|
7
|
+
set :public, ROOT + '/public'
|
8
|
+
set :views, ROOT + '/views'
|
9
|
+
|
10
|
+
helpers do
|
11
|
+
def host
|
12
|
+
"http://#{ env['HTTP_HOST'] }"
|
13
|
+
end
|
14
|
+
|
15
|
+
def bookmarklet
|
16
|
+
Packr.pack(erb(:bookmarklet), :shrink_vars => true)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
get('/') { erb :index }
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Terminus
|
2
|
+
class Server
|
3
|
+
|
4
|
+
def initialize(options = {})
|
5
|
+
@options = options
|
6
|
+
end
|
7
|
+
|
8
|
+
def execute(script, &callback)
|
9
|
+
messenger.publish('/terminus/commands', :command => script)
|
10
|
+
end
|
11
|
+
|
12
|
+
def running?
|
13
|
+
not @server.nil?
|
14
|
+
end
|
15
|
+
|
16
|
+
def run!
|
17
|
+
return if running?
|
18
|
+
handler = Rack::Handler.get('thin')
|
19
|
+
ensure_reactor_running!
|
20
|
+
handler.run(app, :Port => @options[:port]) { |s| @server = s }
|
21
|
+
end
|
22
|
+
|
23
|
+
def stop!
|
24
|
+
return unless running?
|
25
|
+
@server.stop!
|
26
|
+
@server = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def app
|
32
|
+
return @app if defined?(@app)
|
33
|
+
frontend = Application.new
|
34
|
+
@app = Faye::RackAdapter.new(frontend, :mount => FAYE_MOUNT, :timeout => 15)
|
35
|
+
end
|
36
|
+
|
37
|
+
def messenger
|
38
|
+
app.get_client
|
39
|
+
end
|
40
|
+
|
41
|
+
def ensure_reactor_running!
|
42
|
+
Thread.new { EM.run unless EM.reactor_running? }
|
43
|
+
while not EM.reactor_running?; end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
(function() {
|
2
|
+
var faye = '<%= ::Terminus::FAYE_MOUNT %>',
|
3
|
+
host = '<%= host %>';
|
4
|
+
|
5
|
+
var withPackageManager = function(callback) {
|
6
|
+
if (window.JS && JS.Packages) return callback();
|
7
|
+
|
8
|
+
var script = document.createElement('script'),
|
9
|
+
head = document.getElementsByTagName('head')[0];
|
10
|
+
|
11
|
+
script.type = 'text/javascript';
|
12
|
+
script.src = host + '/js.packages.js';
|
13
|
+
|
14
|
+
script.onload = script.onreadystatechange = function() {
|
15
|
+
var state = script.readyState;
|
16
|
+
if (!state || state === 'loaded' || state === 'complete') {
|
17
|
+
script.onload = script.onreadystatechange = null;
|
18
|
+
head.removeChild(script);
|
19
|
+
callback();
|
20
|
+
}
|
21
|
+
};
|
22
|
+
head.appendChild(script);
|
23
|
+
};
|
24
|
+
|
25
|
+
withPackageManager(function() {
|
26
|
+
JS.Packages(function() {
|
27
|
+
this.file(host + faye + '/client.js')
|
28
|
+
.provides('Faye', 'Faye.Client');
|
29
|
+
|
30
|
+
this.file(host + '/terminus.js')
|
31
|
+
.requires('Faye.Client')
|
32
|
+
.provides('Terminus');
|
33
|
+
});
|
34
|
+
|
35
|
+
require('Terminus', function() {
|
36
|
+
Terminus.connect(host + faye);
|
37
|
+
});
|
38
|
+
});
|
39
|
+
})();
|
40
|
+
|
data/lib/views/index.erb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
|
3
|
+
<html>
|
4
|
+
<head>
|
5
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
6
|
+
<link rel="stylesheet" type="text/css" href="/style.css">
|
7
|
+
<title>Terminus</title>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<div class="container">
|
12
|
+
<h1><a href="javascript:<%= bookmarklet %>">Terminus</a>.</h1>
|
13
|
+
|
14
|
+
<p>Terminus is a distributed JavaScript console. You can find out
|
15
|
+
more at <a href="http://github.com/jcoglan/terminus">its GitHub page</a>.
|
16
|
+
To connect the console to your browser, you’ll need this
|
17
|
+
bookmarklet. Just drag the word ‘Terminus’ up to your
|
18
|
+
bookmark toolbar and click it to connect any page to your console.</p>
|
19
|
+
</div>
|
20
|
+
|
21
|
+
<div class="footer">
|
22
|
+
<p>Copyright © 2010
|
23
|
+
<a href="http://jcoglan.com">James Coglan</a>.
|
24
|
+
Released under the MIT license.</p>
|
25
|
+
</div>
|
26
|
+
|
27
|
+
</body>
|
28
|
+
</html>
|
29
|
+
|
metadata
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: terminus
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- James Coglan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-04-05 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rack
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "1.0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: thin
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "1.2"
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: eventmachine
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0.12"
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: faye
|
47
|
+
type: :runtime
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.3.2
|
54
|
+
version:
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sinatra
|
57
|
+
type: :runtime
|
58
|
+
version_requirement:
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "1.0"
|
64
|
+
version:
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: packr
|
67
|
+
type: :runtime
|
68
|
+
version_requirement:
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "3.1"
|
74
|
+
version:
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: oyster
|
77
|
+
type: :runtime
|
78
|
+
version_requirement:
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: "0.9"
|
84
|
+
version:
|
85
|
+
description:
|
86
|
+
email: jcoglan@googlemail.com
|
87
|
+
executables:
|
88
|
+
- terminus
|
89
|
+
extensions: []
|
90
|
+
|
91
|
+
extra_rdoc_files:
|
92
|
+
- README.rdoc
|
93
|
+
files:
|
94
|
+
- README.rdoc
|
95
|
+
- lib/terminus.rb
|
96
|
+
- lib/public/style.css
|
97
|
+
- lib/public/terminus.js
|
98
|
+
- lib/public/js.packages.js
|
99
|
+
- lib/views/bookmarklet.erb
|
100
|
+
- lib/views/index.erb
|
101
|
+
- lib/terminus/server.rb
|
102
|
+
- lib/terminus/application.rb
|
103
|
+
has_rdoc: true
|
104
|
+
homepage: http://github.com/jcoglan/terminus
|
105
|
+
licenses: []
|
106
|
+
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options:
|
109
|
+
- --main
|
110
|
+
- README.rdoc
|
111
|
+
require_paths:
|
112
|
+
- lib
|
113
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: "0"
|
118
|
+
version:
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: "0"
|
124
|
+
version:
|
125
|
+
requirements: []
|
126
|
+
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 1.3.5
|
129
|
+
signing_key:
|
130
|
+
specification_version: 3
|
131
|
+
summary: Control multiple web browsers from the command line
|
132
|
+
test_files: []
|
133
|
+
|