rutty 2.2.0 → 2.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ --private
2
+ --default-return void
3
+ --exclude tag_query*
data/Gemfile CHANGED
@@ -5,6 +5,7 @@ gem "terminal-table", '~> 1.4.2'
5
5
  gem "net-ssh", '~> 2.0.23'
6
6
  gem "net-scp", '~> 1.0.4'
7
7
  gem "builder", '~> 2.1.2'
8
+ gem "treetop", '~> 1.4.8'
8
9
 
9
10
  group :development do
10
11
  gem "jeweler", '~> 1.4.0'
@@ -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
@@ -79,7 +79,6 @@ TODO
79
79
  ----
80
80
 
81
81
  * Cleanup dsh action code
82
- * Expand the tag searching semantics to allow for AND and/or OR queries
83
82
  * Implement delete_node command
84
83
 
85
84
  Copyright
data/Rakefile CHANGED
@@ -23,6 +23,7 @@ begin
23
23
  gem.add_dependency "net-ssh", ">= 2.0.23"
24
24
  gem.add_dependency "net-scp", ">= 1.0.4"
25
25
  gem.add_dependency "builder", ">= 2.1.2"
26
+ gem.add_dependency "treetop", ">= 1.4.8"
26
27
  end
27
28
  Jeweler::GemcutterTasks.new
28
29
  rescue LoadError
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.2.0
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,...]', Array, 'Comma-separated list of tags to run the command on')
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
  }
@@ -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
@@ -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 [Array<String>] :tags (nil) The tags to filter by
34
- # @return [Array] An Array containing the {Rutty::Node} objects after filtering
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[:all]
37
-
38
- ary = Array.new self
39
-
40
- ary.reject! { |n| n.keypath != opts[:keypath] } unless opts[:keypath].nil?
41
- ary.reject! { |n| n.user != opts[:user] } unless opts[:user].nil?
42
- ary.reject! { |n| n.port != opts[:port] } unless opts[:port].nil?
43
- ary.reject! { |n| (n.tags & opts[:tags]).empty? } unless opts[:tags].nil?
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
+
@@ -8,8 +8,8 @@ module Rutty
8
8
  # @see http://semver.org
9
9
  module Version
10
10
  MAJOR = 2
11
- MINOR = 2
12
- PATCH = 0
11
+ MINOR = 3
12
+ PATCH = 1
13
13
  BUILD = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{rutty}
8
- s.version = "2.2.0"
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-13}
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
 
@@ -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
- @nodes = Rutty::Nodes.new [@node1, @node2]
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(:tags => %w(localhost))
53
- assert_equal [@node1, @node2], @nodes.filter(:port => 22)
54
- assert_equal [@node2], @nodes.filter(:user => 'google')
55
- assert_equal [@node2], @nodes.filter(:keypath => '/home/user/.ssh/id_rsa')
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: 7
4
+ hash: 1
5
5
  prerelease: false
6
6
  segments:
7
7
  - 2
8
- - 2
9
- - 0
10
- version: 2.2.0
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-13 00:00:00 -05:00
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