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.
@@ -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