rutty 2.2.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.yardopts +3 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +4 -0
- data/README.md +0 -1
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/bin/rutty +1 -1
- data/lib/rutty/errors.rb +8 -0
- data/lib/rutty/nodes.rb +123 -12
- data/lib/rutty/proc_classes.rb +18 -0
- data/lib/rutty/treetop/syntax_nodes.rb +24 -0
- data/lib/rutty/treetop/tag_query.rb +382 -0
- data/lib/rutty/treetop/tag_query.treetop +38 -0
- data/lib/rutty/version.rb +2 -2
- data/rutty.gemspec +10 -2
- data/test/test_nodes.rb +49 -6
- metadata +26 -5
data/.yardopts
ADDED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -16,10 +16,13 @@ GEM
|
|
16
16
|
net-scp (1.0.4)
|
17
17
|
net-ssh (>= 1.99.1)
|
18
18
|
net-ssh (2.0.23)
|
19
|
+
polyglot (0.3.1)
|
19
20
|
rubyforge (2.0.4)
|
20
21
|
json_pure (>= 1.1.7)
|
21
22
|
terminal-table (1.4.2)
|
22
23
|
thoughtbot-shoulda (2.11.1)
|
24
|
+
treetop (1.4.8)
|
25
|
+
polyglot (>= 0.3.1)
|
23
26
|
xml-simple (1.0.12)
|
24
27
|
|
25
28
|
PLATFORMS
|
@@ -33,4 +36,5 @@ DEPENDENCIES
|
|
33
36
|
net-ssh (~> 2.0.23)
|
34
37
|
terminal-table (~> 1.4.2)
|
35
38
|
thoughtbot-shoulda
|
39
|
+
treetop (~> 1.4.8)
|
36
40
|
xml-simple (~> 1.0.12)
|
data/README.md
CHANGED
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.3.1
|
data/bin/rutty
CHANGED
@@ -16,7 +16,7 @@ add_filter_options = lambda { |c|
|
|
16
16
|
}
|
17
17
|
|
18
18
|
add_remote_options = lambda { |c|
|
19
|
-
c.option('-t', '--tags TAG1[,TAG2,...]',
|
19
|
+
c.option('-t', '--tags TAG1[,TAG2,...]', String, 'Comma-separated list of tags to run the command on')
|
20
20
|
c.option('-a', 'Run the command on ALL nodes')
|
21
21
|
c.option('-d', '--debug', 'Enable debug output')
|
22
22
|
}
|
data/lib/rutty/errors.rb
CHANGED
@@ -19,4 +19,12 @@ module Rutty
|
|
19
19
|
# @author Josh Lindsey
|
20
20
|
# @since 2.1.4
|
21
21
|
class InvalidOutputFormat < StandardError; end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Raised by {Rutty::Helpers#get_tag_query_filter} if the Treetop
|
25
|
+
# parser is unable to parse the tag query string.
|
26
|
+
#
|
27
|
+
# @author Josh Lindsey
|
28
|
+
# @since 2.3.0
|
29
|
+
class InvalidTagQueryString < StandardError; end
|
22
30
|
end
|
data/lib/rutty/nodes.rb
CHANGED
@@ -2,7 +2,7 @@ require 'rutty/node'
|
|
2
2
|
require 'rutty/consts'
|
3
3
|
|
4
4
|
module Rutty
|
5
|
-
|
5
|
+
|
6
6
|
##
|
7
7
|
# Simple container class for {Rutty::Node} instances. Contains methods to load node data from file,
|
8
8
|
# write data back to the file, and filter nodes based on user-supplied criteria.
|
@@ -22,7 +22,7 @@ module Rutty
|
|
22
22
|
Rutty::Nodes.new YAML.load(File.open(File.join(dir, Rutty::Consts::NODES_CONF_FILE)).read)
|
23
23
|
end
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
##
|
27
27
|
# Filters out the {Rutty::Node} objects contained in this instance based on user-defined criteria.
|
28
28
|
#
|
@@ -30,21 +30,38 @@ module Rutty
|
|
30
30
|
# @option opts [String] :keypath (nil) The path to the private key
|
31
31
|
# @option opts [String] :user (nil) The user to login as
|
32
32
|
# @option opts [Integer] :port (nil) The port to connect to
|
33
|
-
# @option opts [
|
34
|
-
# @return [
|
33
|
+
# @option opts [String] :tags (nil) The tags to filter by
|
34
|
+
# @return [Rutty::Nodes] A duplicate of this object, but containing the {Rutty::Node} objects after filtering.
|
35
35
|
def filter opts = {}
|
36
|
-
return self if opts
|
37
|
-
|
38
|
-
ary =
|
39
|
-
|
40
|
-
ary.
|
41
|
-
ary.
|
42
|
-
ary.
|
43
|
-
ary.
|
36
|
+
return self if opts.all
|
37
|
+
|
38
|
+
ary = self.dup
|
39
|
+
|
40
|
+
ary.delete_if { |n| n.keypath != opts.keypath } unless opts.keypath.nil?
|
41
|
+
ary.delete_if { |n| n.user != opts.user } unless opts.user.nil?
|
42
|
+
ary.delete_if { |n| n.port != opts.port } unless opts.port.nil?
|
43
|
+
ary.delete_if &get_tag_query_filter(opts.tags) unless opts.tags.nil?
|
44
44
|
|
45
45
|
ary
|
46
46
|
end
|
47
47
|
|
48
|
+
##
|
49
|
+
# Same as {#filter}, but destructive on this object instead of a duplicate.
|
50
|
+
#
|
51
|
+
# @param (see #filter)
|
52
|
+
# @option (see #filter)
|
53
|
+
# @return [Rutty::Nodes] This object with the requested nodes filtered out.
|
54
|
+
def filter! opts = {}
|
55
|
+
return self if opts.all
|
56
|
+
|
57
|
+
self.delete_if { |n| n.keypath != opts.keypath } unless opts.keypath.nil?
|
58
|
+
self.delete_if { |n| n.user != opts.user } unless opts.user.nil?
|
59
|
+
self.delete_if { |n| n.port != opts.port } unless opts.port.nil?
|
60
|
+
self.delete_if &get_tag_query_filter(opts.tags) unless opts.tags.nil?
|
61
|
+
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
48
65
|
##
|
49
66
|
# Writes the updated nodes config back into the nodes.yaml file in the specified dir.
|
50
67
|
#
|
@@ -55,5 +72,99 @@ module Rutty
|
|
55
72
|
YAML.dump(self, f)
|
56
73
|
end
|
57
74
|
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
##
|
79
|
+
# Builds and returns a <tt>Proc</tt> for {#filter}. The Proc
|
80
|
+
# should be passed a {Rutty::Node} object when called. The Proc calls
|
81
|
+
# {Rutty::Node#has_tag?} on the passed in object to determine tag inclusion.
|
82
|
+
#
|
83
|
+
# In other words, the Proc returned by this should be suitable for passing to
|
84
|
+
# Array#reject!, as that's what {#filter} calls.
|
85
|
+
#
|
86
|
+
# @param [String] query_str The string passed in by the user via the <tt>--tags</tt> option
|
87
|
+
# @return [Proc] The generated filter Proc
|
88
|
+
#
|
89
|
+
# @see #recursive_build_query_proc_string
|
90
|
+
# @see Rutty::Nodes#filter
|
91
|
+
# @since 2.3.0
|
92
|
+
def get_tag_query_filter query_str
|
93
|
+
require 'rutty/proc_classes'
|
94
|
+
|
95
|
+
filter = nil
|
96
|
+
|
97
|
+
if query_str =~ /^\w$/
|
98
|
+
# Single tag query, no need to fire up the parser
|
99
|
+
filter = Procs::SingleTagQuery.new { |n| n.has_tag? "#{$1}" }
|
100
|
+
elsif query_str =~ /^(?:\w,?)+$/
|
101
|
+
# Old-style comma-separated tag query. Still no
|
102
|
+
# need to fire up the parser, just additively compare.
|
103
|
+
tags = query_str.split(',')
|
104
|
+
filter = Procs::MultipleTagQuery.new { |n| (n.tags & tags).empty? }
|
105
|
+
else
|
106
|
+
# New sql-like tag query. Need the parser for this.
|
107
|
+
require 'treetop'
|
108
|
+
require 'rutty/treetop/syntax_nodes'
|
109
|
+
require 'rutty/treetop/tag_query'
|
110
|
+
|
111
|
+
parser = TagQueryGrammarParser.new
|
112
|
+
|
113
|
+
parsed_syntax_tree = parser.parse(query_str)
|
114
|
+
|
115
|
+
raise InvalidTagQueryString.new "error: Unable to parse tag query string" if parsed_syntax_tree.nil?
|
116
|
+
|
117
|
+
proc_str = recursive_build_query_proc_string parsed_syntax_tree.elements
|
118
|
+
proc_str = proc_str.rstrip << ') }'
|
119
|
+
|
120
|
+
filter = eval(proc_str)
|
121
|
+
end
|
122
|
+
|
123
|
+
filter
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
##
|
128
|
+
# Builds a string to be <tt>eval</tt>'d into a Proc by {#get_tag_query_filter}. Recursively
|
129
|
+
# walks a Treetop parsed syntax tree, building the string as it goes. The string is not actually
|
130
|
+
# evaluated into a Proc object in this method; that happens in {#get_tag_query_filter}.
|
131
|
+
#
|
132
|
+
# @param [Array<Treetop::Runtime::SyntaxNode>] elems A multidimensional array ultimately
|
133
|
+
# consisting of Treetop SyntaxNodes or children thereof.
|
134
|
+
# @param [String] str The string to build on. Passed around over recursions.
|
135
|
+
# @return [String] The completed string to evaluate into a Proc.
|
136
|
+
#
|
137
|
+
# @see #get_tag_query_filter
|
138
|
+
# @since 2.3.0
|
139
|
+
def recursive_build_query_proc_string elems, str = ''
|
140
|
+
return str if elems.nil? or elems.empty?
|
141
|
+
|
142
|
+
str = 'Procs::SqlLikeTagQuery.new { |n| !(' if str.empty?
|
143
|
+
|
144
|
+
elem = elems.shift
|
145
|
+
|
146
|
+
case elem.class.to_s
|
147
|
+
when 'TagQueryGrammar::Tag'
|
148
|
+
str << "n.has_tag?(#{elem.text_value}) "
|
149
|
+
when 'TagQueryGrammar::AndLiteral'
|
150
|
+
str << "&& "
|
151
|
+
when 'TagQueryGrammar::OrLiteral'
|
152
|
+
str << "|| "
|
153
|
+
when 'TagQueryGrammar::GroupStart'
|
154
|
+
str << '('
|
155
|
+
when 'TagQueryGrammar::GroupEnd'
|
156
|
+
str.rstrip!
|
157
|
+
str << ')'
|
158
|
+
when 'TagQueryGrammar::QueryGroup'
|
159
|
+
str = recursive_build_query_proc_string elem.elements, str
|
160
|
+
# For some reason, Treetop won't let me assign a class to the "query" rule,
|
161
|
+
# so it defaults to this class. This should be the only parsed rule that evaluates
|
162
|
+
# to this generic class.
|
163
|
+
when 'Treetop::Runtime::SyntaxNode'
|
164
|
+
str = recursive_build_query_proc_string elem.elements, str
|
165
|
+
end
|
166
|
+
|
167
|
+
recursive_build_query_proc_string elems, str
|
168
|
+
end
|
58
169
|
end
|
59
170
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Rutty
|
2
|
+
|
3
|
+
##
|
4
|
+
# The classes defined in here are used by {Rutty::Nodes#get_tag_query_filter} and
|
5
|
+
# {Rutty::Nodes#recursive_build_query_proc_string}. Mostly they exist to simplify unit
|
6
|
+
# tests, to ensure that the correct logic paths are reached in those methods for differing
|
7
|
+
# query strings.
|
8
|
+
#
|
9
|
+
# @author Josh Lindsey
|
10
|
+
# @since 2.3.0
|
11
|
+
module Procs
|
12
|
+
class SingleTagQuery < Proc; end
|
13
|
+
|
14
|
+
class MultipleTagQuery < Proc; end
|
15
|
+
|
16
|
+
class SqlLikeTagQuery < Proc; end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
##
|
2
|
+
# Extensions to the TagQueryGrammar module, generated by Treetop. The classes
|
3
|
+
# contained in this module are applied to the nodes of the parsed syntax tree
|
4
|
+
# for easier interpretation later.
|
5
|
+
#
|
6
|
+
# @see http://treetop.rubyforge.org/
|
7
|
+
#
|
8
|
+
# @author Josh Lindsey
|
9
|
+
# @since 2.3.0
|
10
|
+
module TagQueryGrammar
|
11
|
+
class Tag < Treetop::Runtime::SyntaxNode; end
|
12
|
+
|
13
|
+
class Whitespace < Treetop::Runtime::SyntaxNode; end
|
14
|
+
|
15
|
+
class QueryGroup < Treetop::Runtime::SyntaxNode; end
|
16
|
+
|
17
|
+
class GroupStart < Treetop::Runtime::SyntaxNode; end
|
18
|
+
|
19
|
+
class GroupEnd < Treetop::Runtime::SyntaxNode; end
|
20
|
+
|
21
|
+
class AndLiteral < Treetop::Runtime::SyntaxNode; end
|
22
|
+
|
23
|
+
class OrLiteral < Treetop::Runtime::SyntaxNode; end
|
24
|
+
end
|
@@ -0,0 +1,382 @@
|
|
1
|
+
# Autogenerated from a Treetop grammar. Edits may be lost.
|
2
|
+
|
3
|
+
|
4
|
+
module TagQueryGrammar
|
5
|
+
include Treetop::Runtime
|
6
|
+
|
7
|
+
def root
|
8
|
+
@root ||= :query
|
9
|
+
end
|
10
|
+
|
11
|
+
module Query0
|
12
|
+
def tag
|
13
|
+
elements[0]
|
14
|
+
end
|
15
|
+
|
16
|
+
def space1
|
17
|
+
elements[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def boolean
|
21
|
+
elements[2]
|
22
|
+
end
|
23
|
+
|
24
|
+
def space2
|
25
|
+
elements[3]
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
def _nt_query
|
31
|
+
start_index = index
|
32
|
+
if node_cache[:query].has_key?(index)
|
33
|
+
cached = node_cache[:query][index]
|
34
|
+
if cached
|
35
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
36
|
+
@index = cached.interval.end
|
37
|
+
end
|
38
|
+
return cached
|
39
|
+
end
|
40
|
+
|
41
|
+
i0 = index
|
42
|
+
i1, s1 = index, []
|
43
|
+
r2 = _nt_tag
|
44
|
+
s1 << r2
|
45
|
+
if r2
|
46
|
+
r3 = _nt_space
|
47
|
+
s1 << r3
|
48
|
+
if r3
|
49
|
+
r4 = _nt_boolean
|
50
|
+
s1 << r4
|
51
|
+
if r4
|
52
|
+
r5 = _nt_space
|
53
|
+
s1 << r5
|
54
|
+
if r5
|
55
|
+
i6 = index
|
56
|
+
r7 = _nt_query
|
57
|
+
if r7
|
58
|
+
r6 = r7
|
59
|
+
else
|
60
|
+
r8 = _nt_group
|
61
|
+
if r8
|
62
|
+
r6 = r8
|
63
|
+
else
|
64
|
+
@index = i6
|
65
|
+
r6 = nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
s1 << r6
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
if s1.last
|
74
|
+
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
|
75
|
+
r1.extend(Query0)
|
76
|
+
else
|
77
|
+
@index = i1
|
78
|
+
r1 = nil
|
79
|
+
end
|
80
|
+
if r1
|
81
|
+
r0 = r1
|
82
|
+
else
|
83
|
+
r9 = _nt_tag
|
84
|
+
if r9
|
85
|
+
r0 = r9
|
86
|
+
else
|
87
|
+
@index = i0
|
88
|
+
r0 = nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
node_cache[:query][start_index] = r0
|
93
|
+
|
94
|
+
r0
|
95
|
+
end
|
96
|
+
|
97
|
+
module Tag0
|
98
|
+
end
|
99
|
+
|
100
|
+
def _nt_tag
|
101
|
+
start_index = index
|
102
|
+
if node_cache[:tag].has_key?(index)
|
103
|
+
cached = node_cache[:tag][index]
|
104
|
+
if cached
|
105
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
106
|
+
@index = cached.interval.end
|
107
|
+
end
|
108
|
+
return cached
|
109
|
+
end
|
110
|
+
|
111
|
+
i0, s0 = index, []
|
112
|
+
if has_terminal?('\G["|\']', true, index)
|
113
|
+
r1 = true
|
114
|
+
@index += 1
|
115
|
+
else
|
116
|
+
r1 = nil
|
117
|
+
end
|
118
|
+
s0 << r1
|
119
|
+
if r1
|
120
|
+
s2, i2 = [], index
|
121
|
+
loop do
|
122
|
+
if has_terminal?('\G[\\w-]', true, index)
|
123
|
+
r3 = true
|
124
|
+
@index += 1
|
125
|
+
else
|
126
|
+
r3 = nil
|
127
|
+
end
|
128
|
+
if r3
|
129
|
+
s2 << r3
|
130
|
+
else
|
131
|
+
break
|
132
|
+
end
|
133
|
+
end
|
134
|
+
if s2.empty?
|
135
|
+
@index = i2
|
136
|
+
r2 = nil
|
137
|
+
else
|
138
|
+
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
|
139
|
+
end
|
140
|
+
s0 << r2
|
141
|
+
if r2
|
142
|
+
if has_terminal?('\G["|\']', true, index)
|
143
|
+
r4 = true
|
144
|
+
@index += 1
|
145
|
+
else
|
146
|
+
r4 = nil
|
147
|
+
end
|
148
|
+
s0 << r4
|
149
|
+
end
|
150
|
+
end
|
151
|
+
if s0.last
|
152
|
+
r0 = instantiate_node(Tag,input, i0...index, s0)
|
153
|
+
r0.extend(Tag0)
|
154
|
+
else
|
155
|
+
@index = i0
|
156
|
+
r0 = nil
|
157
|
+
end
|
158
|
+
|
159
|
+
node_cache[:tag][start_index] = r0
|
160
|
+
|
161
|
+
r0
|
162
|
+
end
|
163
|
+
|
164
|
+
module Group0
|
165
|
+
def group_start
|
166
|
+
elements[0]
|
167
|
+
end
|
168
|
+
|
169
|
+
def query
|
170
|
+
elements[1]
|
171
|
+
end
|
172
|
+
|
173
|
+
def group_end
|
174
|
+
elements[2]
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def _nt_group
|
179
|
+
start_index = index
|
180
|
+
if node_cache[:group].has_key?(index)
|
181
|
+
cached = node_cache[:group][index]
|
182
|
+
if cached
|
183
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
184
|
+
@index = cached.interval.end
|
185
|
+
end
|
186
|
+
return cached
|
187
|
+
end
|
188
|
+
|
189
|
+
i0, s0 = index, []
|
190
|
+
r1 = _nt_group_start
|
191
|
+
s0 << r1
|
192
|
+
if r1
|
193
|
+
r2 = _nt_query
|
194
|
+
s0 << r2
|
195
|
+
if r2
|
196
|
+
r3 = _nt_group_end
|
197
|
+
s0 << r3
|
198
|
+
end
|
199
|
+
end
|
200
|
+
if s0.last
|
201
|
+
r0 = instantiate_node(QueryGroup,input, i0...index, s0)
|
202
|
+
r0.extend(Group0)
|
203
|
+
else
|
204
|
+
@index = i0
|
205
|
+
r0 = nil
|
206
|
+
end
|
207
|
+
|
208
|
+
node_cache[:group][start_index] = r0
|
209
|
+
|
210
|
+
r0
|
211
|
+
end
|
212
|
+
|
213
|
+
def _nt_boolean
|
214
|
+
start_index = index
|
215
|
+
if node_cache[:boolean].has_key?(index)
|
216
|
+
cached = node_cache[:boolean][index]
|
217
|
+
if cached
|
218
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
219
|
+
@index = cached.interval.end
|
220
|
+
end
|
221
|
+
return cached
|
222
|
+
end
|
223
|
+
|
224
|
+
i0 = index
|
225
|
+
r1 = _nt_AND
|
226
|
+
if r1
|
227
|
+
r0 = r1
|
228
|
+
else
|
229
|
+
r2 = _nt_OR
|
230
|
+
if r2
|
231
|
+
r0 = r2
|
232
|
+
else
|
233
|
+
@index = i0
|
234
|
+
r0 = nil
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
node_cache[:boolean][start_index] = r0
|
239
|
+
|
240
|
+
r0
|
241
|
+
end
|
242
|
+
|
243
|
+
def _nt_space
|
244
|
+
start_index = index
|
245
|
+
if node_cache[:space].has_key?(index)
|
246
|
+
cached = node_cache[:space][index]
|
247
|
+
if cached
|
248
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
249
|
+
@index = cached.interval.end
|
250
|
+
end
|
251
|
+
return cached
|
252
|
+
end
|
253
|
+
|
254
|
+
s0, i0 = [], index
|
255
|
+
loop do
|
256
|
+
if has_terminal?('\G[\\s]', true, index)
|
257
|
+
r1 = true
|
258
|
+
@index += 1
|
259
|
+
else
|
260
|
+
r1 = nil
|
261
|
+
end
|
262
|
+
if r1
|
263
|
+
s0 << r1
|
264
|
+
else
|
265
|
+
break
|
266
|
+
end
|
267
|
+
end
|
268
|
+
if s0.empty?
|
269
|
+
@index = i0
|
270
|
+
r0 = nil
|
271
|
+
else
|
272
|
+
r0 = instantiate_node(Whitespace,input, i0...index, s0)
|
273
|
+
end
|
274
|
+
|
275
|
+
node_cache[:space][start_index] = r0
|
276
|
+
|
277
|
+
r0
|
278
|
+
end
|
279
|
+
|
280
|
+
def _nt_group_start
|
281
|
+
start_index = index
|
282
|
+
if node_cache[:group_start].has_key?(index)
|
283
|
+
cached = node_cache[:group_start][index]
|
284
|
+
if cached
|
285
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
286
|
+
@index = cached.interval.end
|
287
|
+
end
|
288
|
+
return cached
|
289
|
+
end
|
290
|
+
|
291
|
+
if has_terminal?('(', false, index)
|
292
|
+
r0 = instantiate_node(GroupStart,input, index...(index + 1))
|
293
|
+
@index += 1
|
294
|
+
else
|
295
|
+
terminal_parse_failure('(')
|
296
|
+
r0 = nil
|
297
|
+
end
|
298
|
+
|
299
|
+
node_cache[:group_start][start_index] = r0
|
300
|
+
|
301
|
+
r0
|
302
|
+
end
|
303
|
+
|
304
|
+
def _nt_group_end
|
305
|
+
start_index = index
|
306
|
+
if node_cache[:group_end].has_key?(index)
|
307
|
+
cached = node_cache[:group_end][index]
|
308
|
+
if cached
|
309
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
310
|
+
@index = cached.interval.end
|
311
|
+
end
|
312
|
+
return cached
|
313
|
+
end
|
314
|
+
|
315
|
+
if has_terminal?(')', false, index)
|
316
|
+
r0 = instantiate_node(GroupEnd,input, index...(index + 1))
|
317
|
+
@index += 1
|
318
|
+
else
|
319
|
+
terminal_parse_failure(')')
|
320
|
+
r0 = nil
|
321
|
+
end
|
322
|
+
|
323
|
+
node_cache[:group_end][start_index] = r0
|
324
|
+
|
325
|
+
r0
|
326
|
+
end
|
327
|
+
|
328
|
+
def _nt_AND
|
329
|
+
start_index = index
|
330
|
+
if node_cache[:AND].has_key?(index)
|
331
|
+
cached = node_cache[:AND][index]
|
332
|
+
if cached
|
333
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
334
|
+
@index = cached.interval.end
|
335
|
+
end
|
336
|
+
return cached
|
337
|
+
end
|
338
|
+
|
339
|
+
if has_terminal?('AND', false, index)
|
340
|
+
r0 = instantiate_node(AndLiteral,input, index...(index + 3))
|
341
|
+
@index += 3
|
342
|
+
else
|
343
|
+
terminal_parse_failure('AND')
|
344
|
+
r0 = nil
|
345
|
+
end
|
346
|
+
|
347
|
+
node_cache[:AND][start_index] = r0
|
348
|
+
|
349
|
+
r0
|
350
|
+
end
|
351
|
+
|
352
|
+
def _nt_OR
|
353
|
+
start_index = index
|
354
|
+
if node_cache[:OR].has_key?(index)
|
355
|
+
cached = node_cache[:OR][index]
|
356
|
+
if cached
|
357
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
358
|
+
@index = cached.interval.end
|
359
|
+
end
|
360
|
+
return cached
|
361
|
+
end
|
362
|
+
|
363
|
+
if has_terminal?('OR', false, index)
|
364
|
+
r0 = instantiate_node(OrLiteral,input, index...(index + 2))
|
365
|
+
@index += 2
|
366
|
+
else
|
367
|
+
terminal_parse_failure('OR')
|
368
|
+
r0 = nil
|
369
|
+
end
|
370
|
+
|
371
|
+
node_cache[:OR][start_index] = r0
|
372
|
+
|
373
|
+
r0
|
374
|
+
end
|
375
|
+
|
376
|
+
end
|
377
|
+
|
378
|
+
class TagQueryGrammarParser < Treetop::Runtime::CompiledParser
|
379
|
+
include TagQueryGrammar
|
380
|
+
end
|
381
|
+
|
382
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
grammar TagQueryGrammar
|
2
|
+
rule query
|
3
|
+
tag space boolean space (query / group) / tag
|
4
|
+
end
|
5
|
+
|
6
|
+
rule tag
|
7
|
+
["|'] [\w-]+ ["|'] <Tag>
|
8
|
+
end
|
9
|
+
|
10
|
+
rule group
|
11
|
+
group_start query group_end <QueryGroup>
|
12
|
+
end
|
13
|
+
|
14
|
+
rule boolean
|
15
|
+
AND / OR
|
16
|
+
end
|
17
|
+
|
18
|
+
rule space
|
19
|
+
[\s]+ <Whitespace>
|
20
|
+
end
|
21
|
+
|
22
|
+
rule group_start
|
23
|
+
'(' <GroupStart>
|
24
|
+
end
|
25
|
+
|
26
|
+
rule group_end
|
27
|
+
')' <GroupEnd>
|
28
|
+
end
|
29
|
+
|
30
|
+
rule AND
|
31
|
+
'AND' <AndLiteral>
|
32
|
+
end
|
33
|
+
|
34
|
+
rule OR
|
35
|
+
'OR' <OrLiteral>
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
data/lib/rutty/version.rb
CHANGED
data/rutty.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{rutty}
|
8
|
-
s.version = "2.
|
8
|
+
s.version = "2.3.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Josh Lindsey"]
|
12
|
-
s.date = %q{2010-11-
|
12
|
+
s.date = %q{2010-11-15}
|
13
13
|
s.default_executable = %q{rutty}
|
14
14
|
s.description = %q{
|
15
15
|
RuTTY is a DSH (distributed / dancer's shell) written in Ruby. It's used to run commands
|
@@ -24,6 +24,7 @@ Gem::Specification.new do |s|
|
|
24
24
|
]
|
25
25
|
s.files = [
|
26
26
|
".gitignore",
|
27
|
+
".yardopts",
|
27
28
|
"Gemfile",
|
28
29
|
"Gemfile.lock",
|
29
30
|
"LICENSE",
|
@@ -39,6 +40,10 @@ Gem::Specification.new do |s|
|
|
39
40
|
"lib/rutty/helpers.rb",
|
40
41
|
"lib/rutty/node.rb",
|
41
42
|
"lib/rutty/nodes.rb",
|
43
|
+
"lib/rutty/proc_classes.rb",
|
44
|
+
"lib/rutty/treetop/syntax_nodes.rb",
|
45
|
+
"lib/rutty/treetop/tag_query.rb",
|
46
|
+
"lib/rutty/treetop/tag_query.treetop",
|
42
47
|
"lib/rutty/version.rb",
|
43
48
|
"rutty.gemspec",
|
44
49
|
"test/helper.rb",
|
@@ -88,6 +93,7 @@ Gem::Specification.new do |s|
|
|
88
93
|
s.add_runtime_dependency(%q<net-ssh>, [">= 2.0.23"])
|
89
94
|
s.add_runtime_dependency(%q<net-scp>, [">= 1.0.4"])
|
90
95
|
s.add_runtime_dependency(%q<builder>, [">= 2.1.2"])
|
96
|
+
s.add_runtime_dependency(%q<treetop>, [">= 1.4.8"])
|
91
97
|
else
|
92
98
|
s.add_dependency(%q<bundler>, [">= 1.0.0"])
|
93
99
|
s.add_dependency(%q<jeweler>, [">= 1.4.0"])
|
@@ -98,6 +104,7 @@ Gem::Specification.new do |s|
|
|
98
104
|
s.add_dependency(%q<net-ssh>, [">= 2.0.23"])
|
99
105
|
s.add_dependency(%q<net-scp>, [">= 1.0.4"])
|
100
106
|
s.add_dependency(%q<builder>, [">= 2.1.2"])
|
107
|
+
s.add_dependency(%q<treetop>, [">= 1.4.8"])
|
101
108
|
end
|
102
109
|
else
|
103
110
|
s.add_dependency(%q<bundler>, [">= 1.0.0"])
|
@@ -109,6 +116,7 @@ Gem::Specification.new do |s|
|
|
109
116
|
s.add_dependency(%q<net-ssh>, [">= 2.0.23"])
|
110
117
|
s.add_dependency(%q<net-scp>, [">= 1.0.4"])
|
111
118
|
s.add_dependency(%q<builder>, [">= 2.1.2"])
|
119
|
+
s.add_dependency(%q<treetop>, [">= 1.4.8"])
|
112
120
|
end
|
113
121
|
end
|
114
122
|
|
data/test/test_nodes.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'helper'
|
2
|
+
require 'ostruct'
|
2
3
|
|
3
4
|
class TestNodes < Test::Unit::TestCase
|
4
5
|
context "The Rutty::Nodes class" do
|
@@ -33,13 +34,18 @@ class TestNodes < Test::Unit::TestCase
|
|
33
34
|
:port => 22,
|
34
35
|
:user => 'root',
|
35
36
|
:keypath => '/dev/null',
|
36
|
-
:tags => %w(localhost test)
|
37
|
+
:tags => %w(localhost fake test)
|
37
38
|
@node2 = Rutty::Node.new :host => 'google.com',
|
38
39
|
:port => 22,
|
39
40
|
:user => 'google',
|
40
41
|
:keypath => '/home/user/.ssh/id_rsa',
|
41
42
|
:tags => %w(google fake test)
|
42
|
-
@
|
43
|
+
@node3 = Rutty::Node.new :host => 'example.com',
|
44
|
+
:port => 22333,
|
45
|
+
:user => 'example',
|
46
|
+
:keypath => '/home/example/.ssh/id_rsa.pem',
|
47
|
+
:tags => %w(broken test example)
|
48
|
+
@nodes = Rutty::Nodes.new [@node1, @node2, @node3]
|
43
49
|
end
|
44
50
|
|
45
51
|
teardown { clean_test_config! }
|
@@ -49,10 +55,47 @@ class TestNodes < Test::Unit::TestCase
|
|
49
55
|
end
|
50
56
|
|
51
57
|
should "filter out nodes matching provided criteria" do
|
52
|
-
assert_equal [@node1], @nodes.filter(:
|
53
|
-
assert_equal [@
|
54
|
-
assert_equal [@node2], @nodes.filter(:
|
55
|
-
|
58
|
+
assert_equal [@node1, @node2], @nodes.filter(OpenStruct.new :port => 22)
|
59
|
+
assert_equal [@node2], @nodes.filter(OpenStruct.new :user => 'google')
|
60
|
+
assert_equal [@node2], @nodes.filter(OpenStruct.new :keypath => '/home/user/.ssh/id_rsa')
|
61
|
+
end
|
62
|
+
|
63
|
+
should "correctly filter on a single tag and return the correct node(s)" do
|
64
|
+
filtered = @nodes.filter(OpenStruct.new :tags => 'test')
|
65
|
+
assert_equal 3, filtered.length
|
66
|
+
assert_equal [@node1, @node2, @node3], filtered
|
67
|
+
|
68
|
+
filtered = @nodes.filter(OpenStruct.new :tags => 'fake')
|
69
|
+
assert_equal 2, filtered.length
|
70
|
+
assert_equal [@node1, @node2], filtered
|
71
|
+
end
|
72
|
+
|
73
|
+
should "correctly filter on a comma-separated list of tags and return the correct node(s)" do
|
74
|
+
filtered = @nodes.filter(OpenStruct.new :tags => 'broken,example')
|
75
|
+
assert_equal 1, filtered.length
|
76
|
+
assert_equal [@node3], filtered
|
77
|
+
|
78
|
+
filtered = @nodes.filter(OpenStruct.new :tags => 'test,fake')
|
79
|
+
assert_equal 3, filtered.length
|
80
|
+
assert_equal [@node1, @node2, @node3], filtered
|
81
|
+
|
82
|
+
filtered = @nodes.filter(OpenStruct.new :tags => 'localhost,example')
|
83
|
+
assert_equal 2, filtered.length
|
84
|
+
assert_equal [@node1, @node3], filtered
|
85
|
+
end
|
86
|
+
|
87
|
+
should "correctly filter on a SQL-like tag query and return the correct node(s)" do
|
88
|
+
filtered = @nodes.filter(OpenStruct.new :tags => "'test' AND 'localhost'")
|
89
|
+
assert_equal 1, filtered.length
|
90
|
+
assert_equal [@node1], filtered
|
91
|
+
|
92
|
+
filtered = @nodes.filter(OpenStruct.new :tags => "'example' OR 'localhost'")
|
93
|
+
assert_equal 2, filtered.length
|
94
|
+
assert_equal [@node1, @node3], filtered
|
95
|
+
|
96
|
+
filtered = @nodes.filter(OpenStruct.new :tags => "'localhost' AND 'test' OR ('google')")
|
97
|
+
assert_equal 2, filtered.length
|
98
|
+
assert_equal [@node1, @node2], filtered
|
56
99
|
end
|
57
100
|
|
58
101
|
should "correctly write out a node config" do
|
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: 1
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 2
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 2.
|
8
|
+
- 3
|
9
|
+
- 1
|
10
|
+
version: 2.3.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Josh Lindsey
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-11-
|
18
|
+
date: 2010-11-15 00:00:00 -05:00
|
19
19
|
default_executable: rutty
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -160,6 +160,22 @@ dependencies:
|
|
160
160
|
version: 2.1.2
|
161
161
|
type: :runtime
|
162
162
|
version_requirements: *id009
|
163
|
+
- !ruby/object:Gem::Dependency
|
164
|
+
name: treetop
|
165
|
+
prerelease: false
|
166
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
167
|
+
none: false
|
168
|
+
requirements:
|
169
|
+
- - ">="
|
170
|
+
- !ruby/object:Gem::Version
|
171
|
+
hash: 23
|
172
|
+
segments:
|
173
|
+
- 1
|
174
|
+
- 4
|
175
|
+
- 8
|
176
|
+
version: 1.4.8
|
177
|
+
type: :runtime
|
178
|
+
version_requirements: *id010
|
163
179
|
description: "\n RuTTY is a DSH (distributed / dancer's shell) written in Ruby. It's used to run commands \n on multiple remote servers at once, based on a tagging system. Also allows for multiple\n SCP-style uploads.\n "
|
164
180
|
email: josh@cloudspace.com
|
165
181
|
executables:
|
@@ -171,6 +187,7 @@ extra_rdoc_files:
|
|
171
187
|
- README.md
|
172
188
|
files:
|
173
189
|
- .gitignore
|
190
|
+
- .yardopts
|
174
191
|
- Gemfile
|
175
192
|
- Gemfile.lock
|
176
193
|
- LICENSE
|
@@ -186,6 +203,10 @@ files:
|
|
186
203
|
- lib/rutty/helpers.rb
|
187
204
|
- lib/rutty/node.rb
|
188
205
|
- lib/rutty/nodes.rb
|
206
|
+
- lib/rutty/proc_classes.rb
|
207
|
+
- lib/rutty/treetop/syntax_nodes.rb
|
208
|
+
- lib/rutty/treetop/tag_query.rb
|
209
|
+
- lib/rutty/treetop/tag_query.treetop
|
189
210
|
- lib/rutty/version.rb
|
190
211
|
- rutty.gemspec
|
191
212
|
- test/helper.rb
|