rutty 2.1.1 → 2.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +62 -7
- data/VERSION +1 -1
- data/lib/rutty.rb +31 -0
- data/lib/rutty/actions.rb +36 -0
- data/lib/rutty/config.rb +53 -1
- data/lib/rutty/consts.rb +10 -0
- data/lib/rutty/errors.rb +10 -0
- data/lib/rutty/helpers.rb +16 -1
- data/lib/rutty/node.rb +32 -2
- data/lib/rutty/nodes.rb +27 -0
- data/lib/rutty/version.rb +8 -1
- data/rutty.gemspec +1 -1
- metadata +3 -3
data/README.md
CHANGED
@@ -1,23 +1,79 @@
|
|
1
1
|
RuTTY
|
2
2
|
=====
|
3
3
|
|
4
|
-
RuTTY is a DSH implementation in Ruby.
|
4
|
+
RuTTY is a DSH implementation in Ruby. You can use it to execute shell commands on multiple remote
|
5
|
+
servers simultaneously using a tagging system to target just the servers you want.
|
6
|
+
|
7
|
+
Also supports SCP uploads to multiple remote servers using the same tagging system.
|
5
8
|
|
6
9
|
Requirements
|
7
10
|
------------
|
8
11
|
|
9
12
|
* Ruby >= 1.8.7 (Not tested on 1.9.x)
|
10
13
|
* Rubygems >= 1.3.7
|
14
|
+
|
15
|
+
###Development Requirements###
|
16
|
+
|
11
17
|
* Bundler >= 1.0.0
|
12
18
|
|
13
19
|
Installation
|
14
20
|
------------
|
15
21
|
|
16
|
-
$ sudo gem install
|
17
|
-
$
|
18
|
-
$
|
19
|
-
|
20
|
-
|
22
|
+
$ sudo gem install rutty
|
23
|
+
$ rutty init
|
24
|
+
$ rutty help
|
25
|
+
|
26
|
+
Usage
|
27
|
+
-----
|
28
|
+
|
29
|
+
###Init###
|
30
|
+
|
31
|
+
You must first initialize the RuTTY configuration and data directory with the `rutty init` command. This
|
32
|
+
command takes an optional argument to specify the directory to install into. If omitted, it will install
|
33
|
+
into `~/.rutty/`. Note that if you install into a directory other than the default, you will have to supply
|
34
|
+
the config to all the other commands with the `-c` option.
|
35
|
+
|
36
|
+
$ rutty init
|
37
|
+
create /Users/jlindsey/.rutty
|
38
|
+
create /Users/jlindsey/.rutty/defaults.yaml
|
39
|
+
create /Users/jlindsey/.rutty/nodes.yaml
|
40
|
+
|
41
|
+
|
42
|
+
###Adding Nodes###
|
43
|
+
|
44
|
+
After initialization, you must add nodes to the RuTTY config. This is done with the `rutty add_node` command.
|
45
|
+
Invoking `rutty help add_node` will give you a list of all the options to pass into it. Any options you don't pass
|
46
|
+
will be filled in from the defaults at `$RUTTY_HOME/defaults.yaml`.
|
47
|
+
|
48
|
+
$ rutty add_node example.com -u root -k /Users/jlindsey/.ssh/id_rsa --tags example,test
|
49
|
+
|
50
|
+
The above will add a node to the RuTTY config that looks like this (in YAML):
|
51
|
+
|
52
|
+
---
|
53
|
+
host: example.com
|
54
|
+
user: root
|
55
|
+
keypath: /Users/jlindsey/.ssh/id_rsa
|
56
|
+
tags:
|
57
|
+
- example
|
58
|
+
- test
|
59
|
+
port: 22
|
60
|
+
|
61
|
+
Note that the `port: 22` line was filled in from the defaults because it was not specified.
|
62
|
+
|
63
|
+
###Running Commands###
|
64
|
+
|
65
|
+
Now that we have a node, we can run commands on it. The default RuTTY command is the `dsh` action, so it
|
66
|
+
can be omitted. That is to say, the following two commands are identical:
|
67
|
+
|
68
|
+
$ rutty dsh -a uptime
|
69
|
+
$ rutty -a uptime
|
70
|
+
|
71
|
+
The `dsh` action can accept either a list of tags passed via `--tags` or the `-a` flag, which will run the command
|
72
|
+
on all defined nodes regardless of tags.
|
73
|
+
|
74
|
+
Note that any command that has any whitespace in it must be enclosed in quotes.
|
75
|
+
|
76
|
+
$ rutty -a "free -m"
|
21
77
|
|
22
78
|
TODO
|
23
79
|
----
|
@@ -28,7 +84,6 @@ TODO
|
|
28
84
|
* Implement delete_node command
|
29
85
|
* Make better printouts
|
30
86
|
* Documentation
|
31
|
-
* Tests
|
32
87
|
|
33
88
|
Copyright
|
34
89
|
---------
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.1.
|
1
|
+
2.1.2
|
data/lib/rutty.rb
CHANGED
@@ -7,7 +7,19 @@ require 'rutty/helpers'
|
|
7
7
|
require 'rutty/node'
|
8
8
|
require 'rutty/nodes'
|
9
9
|
|
10
|
+
##
|
11
|
+
# The RuTTY top-level module. Everything in the RuTTY gem is contained in this module.
|
12
|
+
#
|
13
|
+
# @author Josh Lindsey
|
14
|
+
# @since 2.0.0
|
10
15
|
module Rutty
|
16
|
+
|
17
|
+
##
|
18
|
+
# The Rutty::Runner class includes mixins from the other modules. All end-user interaction
|
19
|
+
# should be done through this class.
|
20
|
+
#
|
21
|
+
# @author Josh Lindsey
|
22
|
+
# @since 2.0.0
|
11
23
|
class Runner
|
12
24
|
attr_writer :config_dir
|
13
25
|
|
@@ -15,18 +27,37 @@ module Rutty
|
|
15
27
|
include Rutty::Helpers
|
16
28
|
include Rutty::Actions
|
17
29
|
|
30
|
+
##
|
31
|
+
# Initialize a new {Rutty::Runner} instance
|
32
|
+
#
|
33
|
+
# @param config_dir [String] Optional parameter specifying the directory RuTTY has been init'd into
|
18
34
|
def initialize config_dir = nil
|
19
35
|
self.config_dir = config_dir
|
20
36
|
end
|
21
37
|
|
38
|
+
##
|
39
|
+
# Lazy-load the {Rutty::Config} object for this instance, based on the config_dir param
|
40
|
+
# passed to {#initialize}.
|
41
|
+
#
|
42
|
+
# @return [Rutty::Config]
|
22
43
|
def config
|
23
44
|
@config ||= Rutty::Config.load_config self.config_dir
|
24
45
|
end
|
25
46
|
|
47
|
+
##
|
48
|
+
# Lazy-load the {Rutty::Nodes} object containing the user-defined nodes' connection info.
|
49
|
+
# Loads from the config_dir param passed to {#initialize}
|
50
|
+
#
|
51
|
+
# @return [Rutty::Nodes]
|
26
52
|
def nodes
|
27
53
|
@nodes ||= Rutty::Nodes.load_config self.config_dir
|
28
54
|
end
|
29
55
|
|
56
|
+
##
|
57
|
+
# If @config_dir is nil, returns {Rutty::Consts::CONF_DIR}. Otherwise return @config_dir.
|
58
|
+
#
|
59
|
+
# @see Rutty::Consts::CONF_DIR
|
60
|
+
# @return [String] The user-specified config directory, falling back to the default on nil
|
30
61
|
def config_dir
|
31
62
|
(@config_dir.nil? && Rutty::Consts::CONF_DIR) || @config_dir
|
32
63
|
end
|
data/lib/rutty/actions.rb
CHANGED
@@ -1,7 +1,20 @@
|
|
1
1
|
require 'rutty/consts'
|
2
2
|
|
3
3
|
module Rutty
|
4
|
+
|
5
|
+
##
|
6
|
+
# The primary mixin module containing the code executed by the rutty bin's actions.
|
7
|
+
#
|
8
|
+
# @author Josh Lindsey
|
9
|
+
# @since 2.0.0
|
4
10
|
module Actions
|
11
|
+
##
|
12
|
+
# Initialize the Rutty config file structure in the specified directory, or
|
13
|
+
# report that the files already exist there.
|
14
|
+
#
|
15
|
+
# @see Rutty::Runner#config_dir
|
16
|
+
# @param [String] dir The directory to install into. {Rutty::Runner#config_dir} ensures
|
17
|
+
# that this falls back to {Rutty::Consts::CONF_DIR} if not passed in by the user.
|
5
18
|
def init dir
|
6
19
|
general_file = File.join(dir, Rutty::Consts::GENERAL_CONF_FILE)
|
7
20
|
nodes_file = File.join(dir, Rutty::Consts::NODES_CONF_FILE)
|
@@ -40,6 +53,12 @@ module Rutty
|
|
40
53
|
end
|
41
54
|
end
|
42
55
|
|
56
|
+
##
|
57
|
+
# Add a new user-defined node to the datastore file.
|
58
|
+
#
|
59
|
+
# @see http://visionmedia.github.com/commander/
|
60
|
+
# @param [Array] args ARGV passed by the bin
|
61
|
+
# @param [Object] options The parsed options object as passed by the bin
|
43
62
|
def add_node args, options
|
44
63
|
raise Rutty::BadUsage.new "Must supply a hostname or IP address.
|
45
64
|
See `rutty help add_node' for usage" if args.empty?
|
@@ -54,12 +73,23 @@ module Rutty
|
|
54
73
|
self.nodes.write_config self.config_dir
|
55
74
|
end
|
56
75
|
|
76
|
+
##
|
77
|
+
# List all the user-defined nodes currently stored in the datastore file.
|
78
|
+
#
|
79
|
+
# @see (see #add_node)
|
80
|
+
# @param (see #add_node)
|
57
81
|
def list_nodes args, options
|
58
82
|
require 'pp'
|
59
83
|
|
60
84
|
pp self.nodes.filter(options)
|
61
85
|
end
|
62
86
|
|
87
|
+
##
|
88
|
+
# Cycle through all the user-defined nodes, filtered by the options, connect to them
|
89
|
+
# and run the specified command on them.
|
90
|
+
#
|
91
|
+
# @see (see #add_node)
|
92
|
+
# @param (see #add_node)
|
63
93
|
def dsh args, options
|
64
94
|
# TODO: Clean this up, it's pretty hard to read and follow
|
65
95
|
|
@@ -138,6 +168,12 @@ module Rutty
|
|
138
168
|
pp @returns
|
139
169
|
end
|
140
170
|
|
171
|
+
##
|
172
|
+
# Cycle through all the user-defined nodes, filtered by the options, connect to them
|
173
|
+
# and upload the specified file(s).
|
174
|
+
#
|
175
|
+
# @see (see #add_node)
|
176
|
+
# @param (see #add_node)
|
141
177
|
def scp args, options
|
142
178
|
check_installed!
|
143
179
|
raise Rutty::BadUsage.new "Must supply a local path and a remote path" unless args.length == 2
|
data/lib/rutty/config.rb
CHANGED
@@ -1,7 +1,29 @@
|
|
1
|
-
# http://mjijackson.com/2010/02/flexible-ruby-config-objects
|
2
1
|
module Rutty
|
2
|
+
|
3
|
+
##
|
4
|
+
# Flexible config class able to use both hash accessors and object accessors. Nested
|
5
|
+
# attributes are also instances of this class, allowing for object (dot) style accessor
|
6
|
+
# chaining.
|
7
|
+
#
|
8
|
+
# @see http://mjijackson.com/2010/02/flexible-ruby-config-objects
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# config = Config.new :foo => "bar", :test => "baz", :widget => { :another => "string" }
|
12
|
+
#
|
13
|
+
# config.foo # => "bar"
|
14
|
+
# config.widget.another # => "string"
|
15
|
+
#
|
16
|
+
# @author Michael Jackson
|
17
|
+
# @author Josh Lindsey
|
18
|
+
# @since 2.0.0
|
3
19
|
class Config
|
4
20
|
class << self
|
21
|
+
##
|
22
|
+
# Loads the default config data from the yaml file in the specified config dir.
|
23
|
+
#
|
24
|
+
# @see Rutty::Consts::GENERAL_CONF_FILE
|
25
|
+
# @param [String] dir The directory to look in for the file specified by {Rutty::Consts::GENERAL_CONF_FILE}
|
26
|
+
# @return [Rutty::Config] The populated {Config} instance
|
5
27
|
def load_config dir
|
6
28
|
require 'yaml'
|
7
29
|
|
@@ -10,21 +32,41 @@ module Rutty
|
|
10
32
|
end
|
11
33
|
end
|
12
34
|
|
35
|
+
##
|
36
|
+
# Returns a new {Config} populated with the specified data hash.
|
37
|
+
#
|
38
|
+
# @see #update!
|
39
|
+
# @param (see #update!)
|
13
40
|
def initialize(data={})
|
14
41
|
@data = {}
|
15
42
|
update!(data)
|
16
43
|
end
|
17
44
|
|
45
|
+
##
|
46
|
+
# Updates this instance's @data attribute with the specified data hash.
|
47
|
+
#
|
48
|
+
# @param [Hash] data Data hash to iterate over
|
49
|
+
# @see #[]=
|
18
50
|
def update!(data)
|
19
51
|
data.each do |key, value|
|
20
52
|
self[key] = value
|
21
53
|
end
|
22
54
|
end
|
23
55
|
|
56
|
+
##
|
57
|
+
# Retrieve the specified key via Hash accessor syntax.
|
58
|
+
#
|
59
|
+
# @param [Symbol, String] key The key to lookup
|
60
|
+
# @return [Object] The object corresponding to the key
|
24
61
|
def [](key)
|
25
62
|
@data[key.to_sym]
|
26
63
|
end
|
27
64
|
|
65
|
+
##
|
66
|
+
# Set the specified key as the specified value via Hash accessor syntax.
|
67
|
+
#
|
68
|
+
# @param [Symbol, String] key The key to set
|
69
|
+
# @param [Object] value The value to set into the key
|
28
70
|
def []=(key, value)
|
29
71
|
if value.class == Hash
|
30
72
|
@data[key.to_sym] = Config.new(value)
|
@@ -33,10 +75,20 @@ module Rutty
|
|
33
75
|
end
|
34
76
|
end
|
35
77
|
|
78
|
+
##
|
79
|
+
# Simply returns this instance's @data attribute, which is internally stored as a hash.
|
80
|
+
#
|
81
|
+
# @return [Hash] The @data attribute
|
36
82
|
def to_hash
|
37
83
|
@data
|
38
84
|
end
|
39
85
|
|
86
|
+
##
|
87
|
+
# Allows for object accessor (dot) syntax to access the stored data.
|
88
|
+
# If the missing method ends with an equals, calls {#[]=}, otherwise calls {#[]}
|
89
|
+
#
|
90
|
+
# @param [Symbol] sym The method symbol
|
91
|
+
# @param [*Array] args The splatted array of method arguments
|
40
92
|
def method_missing(sym, *args)
|
41
93
|
if sym.to_s =~ /(.+)=$/
|
42
94
|
self[$1] = args.first
|
data/lib/rutty/consts.rb
CHANGED
@@ -1,10 +1,20 @@
|
|
1
1
|
module Rutty
|
2
|
+
##
|
3
|
+
# Simple container module for constants.
|
4
|
+
#
|
5
|
+
# @author Josh Lindsey
|
6
|
+
# @since 2.0.0
|
2
7
|
module Consts
|
8
|
+
## Name of the general (defaults) config file
|
3
9
|
GENERAL_CONF_FILE = 'defaults.yaml'
|
10
|
+
## Name of the datastore file for user-defined nodes
|
4
11
|
NODES_CONF_FILE = 'nodes.yaml'
|
5
12
|
|
13
|
+
## Default configuaration storage directory
|
6
14
|
CONF_DIR = File.join(ENV['HOME'], '.rutty')
|
15
|
+
## Default general (defaults) config file location
|
7
16
|
GENERAL_CONF = File.join(CONF_DIR, GENERAL_CONF_FILE)
|
17
|
+
## Default nodes datastore file location
|
8
18
|
NODES_CONF = File.join(CONF_DIR, NODES_CONF_FILE)
|
9
19
|
end
|
10
20
|
end
|
data/lib/rutty/errors.rb
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
module Rutty
|
2
|
+
##
|
3
|
+
# Raised by {Rutty::Helpers#check_installed!} if the check fails.
|
4
|
+
#
|
5
|
+
# @author Josh Lindsey
|
6
|
+
# @since 2.0.0
|
2
7
|
class NotInstalledError < StandardError; end
|
3
8
|
|
9
|
+
##
|
10
|
+
# Raised by various {Rutty::Actions} methods on invalid options, etc.
|
11
|
+
#
|
12
|
+
# @author Josh Lindsey
|
13
|
+
# @since 2.0.0
|
4
14
|
class BadUsage < StandardError; end
|
5
15
|
end
|
data/lib/rutty/helpers.rb
CHANGED
@@ -1,9 +1,19 @@
|
|
1
1
|
require 'rutty/errors'
|
2
|
-
require 'rutty/consts'
|
3
2
|
require 'rutty/version'
|
4
3
|
|
5
4
|
module Rutty
|
5
|
+
|
6
|
+
##
|
7
|
+
# Simple mixin module for miscellaneous methods that don't fit in elsewhere.
|
8
|
+
#
|
9
|
+
# @author Josh Lindsey
|
10
|
+
# @since 2.0.0
|
6
11
|
module Helpers
|
12
|
+
##
|
13
|
+
# Check to ensure the config dir exists. Method expects this module to be included in a
|
14
|
+
# class or module where self.config_dir is meaningful, such as {Rutty::Runner}.
|
15
|
+
#
|
16
|
+
# @raise [Rutty::NotInstalledError] If file cannot be found
|
7
17
|
def check_installed!
|
8
18
|
unless File.exists? self.config_dir
|
9
19
|
raise Rutty::NotInstalledError.new %Q(Can't find conf directory at #{self.config_dir}.
|
@@ -11,6 +21,11 @@ module Rutty
|
|
11
21
|
end
|
12
22
|
end
|
13
23
|
|
24
|
+
##
|
25
|
+
# Returns the version string contained in {Rutty::Version::STRING}. Used by the rutty bin.
|
26
|
+
#
|
27
|
+
# @see (see Rutty::Version)
|
28
|
+
# @return [String] The version string
|
14
29
|
def self.get_version
|
15
30
|
Rutty::Version::STRING
|
16
31
|
end
|
data/lib/rutty/node.rb
CHANGED
@@ -1,21 +1,51 @@
|
|
1
1
|
require 'rutty/config'
|
2
|
-
require 'rutty/consts'
|
3
2
|
|
4
3
|
module Rutty
|
5
|
-
|
4
|
+
|
5
|
+
##
|
6
|
+
# A wrapper class representing an individual node. Normally contained by {Rutty::Nodes}.
|
7
|
+
#
|
8
|
+
# @see Rutty::Config
|
9
|
+
# @author Josh Lindsey
|
10
|
+
# @since 2.0.0
|
11
|
+
class Node < Config
|
12
|
+
##
|
13
|
+
# Initialize a new {Rutty::Node} instance by merging the user-provided data with
|
14
|
+
# the configured defaults.
|
15
|
+
#
|
16
|
+
# @see Rutty::Config#initialize
|
17
|
+
# @param [Hash] data The data provided by the user
|
18
|
+
# @param [Hash] defaults The defaults data provided by the {Rutty::Config} class
|
6
19
|
def initialize data, defaults = {}
|
7
20
|
merged_data = defaults.merge data
|
8
21
|
super merged_data
|
9
22
|
end
|
10
23
|
|
24
|
+
##
|
25
|
+
# Whether this object's tags array includes the specified tag.
|
26
|
+
#
|
27
|
+
# @param [String] tag The tag string to check for
|
11
28
|
def has_tag? tag
|
12
29
|
self.tags.include? tag
|
13
30
|
end
|
14
31
|
|
32
|
+
##
|
33
|
+
# Relation of this {Rutty::Node} to another {Rutty::Node}. Used for sorting.
|
34
|
+
# Compares the host property of the nodes, as it's the only property guaranteed to be
|
35
|
+
# set and unique.
|
36
|
+
#
|
37
|
+
# @see http://ruby-doc.org/core/classes/String.html#M000763
|
38
|
+
# @param [Rutty::Node] other The other {Rutty::Node} to compare to
|
39
|
+
# @return [Integer] Relation of self to other: -1 for less-than, 0 for equal-to, 1 for greater-than
|
15
40
|
def <=> other
|
16
41
|
self.host <=> other.host
|
17
42
|
end
|
18
43
|
|
44
|
+
##
|
45
|
+
# Checks for object eqality. Checks each property of this {Node} against the other.
|
46
|
+
#
|
47
|
+
# @param (see Rutty::Node#<=>)
|
48
|
+
# @return [Boolean]
|
19
49
|
def == other
|
20
50
|
self.host == other.host and
|
21
51
|
self.port == other.port and
|
data/lib/rutty/nodes.rb
CHANGED
@@ -2,14 +2,36 @@ require 'rutty/node'
|
|
2
2
|
require 'rutty/consts'
|
3
3
|
|
4
4
|
module Rutty
|
5
|
+
|
6
|
+
##
|
7
|
+
# Simple container class for {Rutty::Node} instances. Contains methods to load node data from file,
|
8
|
+
# write data back to the file, and filter nodes based on user-supplied criteria.
|
9
|
+
#
|
10
|
+
# @author Josh Lindsey
|
11
|
+
# @since 2.1.0
|
5
12
|
class Nodes < Array
|
6
13
|
class << self
|
14
|
+
##
|
15
|
+
# Loads the users's node data from the yaml file contained in the specified dir.
|
16
|
+
#
|
17
|
+
# @param [String] dir The directory to look in for the filename specified by {Rutty::Consts::NODES_CONF_FILE}
|
18
|
+
# @return [Rutty::Nodes] The filled instance of {Rutty::Node} objects
|
19
|
+
# @see Rutty::Consts::NODES_CONF_FILE
|
7
20
|
def load_config dir
|
8
21
|
require 'yaml'
|
9
22
|
Rutty::Nodes.new YAML.load(File.open(File.join(dir, Rutty::Consts::NODES_CONF_FILE)).read)
|
10
23
|
end
|
11
24
|
end
|
12
25
|
|
26
|
+
##
|
27
|
+
# Filters out the {Rutty::Node} objects contained in this instance based on user-defined criteria.
|
28
|
+
#
|
29
|
+
# @param [Hash, #[]] opts The filter criteria
|
30
|
+
# @option opts [String] :keypath (nil) The path to the private key
|
31
|
+
# @option opts [String] :user (nil) The user to login as
|
32
|
+
# @option opts [Integer] :port (nil) The port to connect to
|
33
|
+
# @option opts [Array<String>] :tags (nil) The tags to filter by
|
34
|
+
# @return [Array] An Array containing the {Rutty::Node} objects after filtering
|
13
35
|
def filter opts = {}
|
14
36
|
return self if opts[:all]
|
15
37
|
|
@@ -23,6 +45,11 @@ module Rutty
|
|
23
45
|
ary
|
24
46
|
end
|
25
47
|
|
48
|
+
##
|
49
|
+
# Writes the updated nodes config back into the nodes.yaml file in the specified dir.
|
50
|
+
#
|
51
|
+
# @param (see Rutty::Nodes.load_config)
|
52
|
+
# @see (see Rutty::Nodes.load_config)
|
26
53
|
def write_config dir
|
27
54
|
File.open(File.join(dir, Rutty::Consts::NODES_CONF_FILE), 'w') do |f|
|
28
55
|
YAML.dump(self, f)
|
data/lib/rutty/version.rb
CHANGED
@@ -1,8 +1,15 @@
|
|
1
1
|
module Rutty
|
2
|
+
|
3
|
+
##
|
4
|
+
# The current version of the gem, expressed in Ruby code so it can
|
5
|
+
# be accessed programmatically.
|
6
|
+
#
|
7
|
+
# @author Josh Lindsey
|
8
|
+
# @see http://semver.org
|
2
9
|
module Version
|
3
10
|
MAJOR = 2
|
4
11
|
MINOR = 1
|
5
|
-
PATCH =
|
12
|
+
PATCH = 2
|
6
13
|
BUILD = nil
|
7
14
|
|
8
15
|
STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
|
data/rutty.gemspec
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rutty
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 15
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 2
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 2.1.
|
9
|
+
- 2
|
10
|
+
version: 2.1.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Josh Lindsey
|