rutty 2.2.0 → 2.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|