treequel 1.0.1 → 1.0.4
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/ChangeLog +176 -14
- data/LICENSE +1 -1
- data/Rakefile +61 -45
- data/Rakefile.local +20 -0
- data/bin/treequel +502 -269
- data/examples/ldap-rack-auth.rb +2 -0
- data/lib/treequel.rb +221 -18
- data/lib/treequel/branch.rb +410 -201
- data/lib/treequel/branchcollection.rb +25 -13
- data/lib/treequel/branchset.rb +42 -40
- data/lib/treequel/constants.rb +233 -3
- data/lib/treequel/control.rb +95 -0
- data/lib/treequel/controls/contentsync.rb +138 -0
- data/lib/treequel/controls/pagedresults.rb +162 -0
- data/lib/treequel/controls/sortedresults.rb +216 -0
- data/lib/treequel/directory.rb +212 -65
- data/lib/treequel/exceptions.rb +11 -12
- data/lib/treequel/filter.rb +1 -12
- data/lib/treequel/mixins.rb +83 -47
- data/lib/treequel/monkeypatches.rb +29 -0
- data/lib/treequel/schema.rb +23 -19
- data/lib/treequel/schema/attributetype.rb +33 -3
- data/lib/treequel/schema/ldapsyntax.rb +0 -11
- data/lib/treequel/schema/matchingrule.rb +0 -11
- data/lib/treequel/schema/matchingruleuse.rb +0 -11
- data/lib/treequel/schema/objectclass.rb +36 -10
- data/lib/treequel/schema/table.rb +159 -0
- data/lib/treequel/sequel_integration.rb +7 -7
- data/lib/treequel/utils.rb +4 -66
- data/rake/documentation.rb +89 -0
- data/rake/helpers.rb +375 -307
- data/rake/hg.rb +16 -2
- data/rake/manual.rb +11 -6
- data/rake/packaging.rb +20 -35
- data/rake/publishing.rb +22 -62
- data/spec/lib/constants.rb +20 -0
- data/spec/lib/control_behavior.rb +44 -0
- data/spec/lib/matchers.rb +51 -0
- data/spec/treequel/branch_spec.rb +88 -29
- data/spec/treequel/branchcollection_spec.rb +24 -1
- data/spec/treequel/branchset_spec.rb +123 -51
- data/spec/treequel/control_spec.rb +48 -0
- data/spec/treequel/controls/contentsync_spec.rb +38 -0
- data/spec/treequel/controls/pagedresults_spec.rb +138 -0
- data/spec/treequel/controls/sortedresults_spec.rb +171 -0
- data/spec/treequel/directory_spec.rb +186 -16
- data/spec/treequel/mixins_spec.rb +42 -3
- data/spec/treequel/schema/attributetype_spec.rb +22 -20
- data/spec/treequel/schema/objectclass_spec.rb +67 -46
- data/spec/treequel/schema/table_spec.rb +134 -0
- data/spec/treequel_spec.rb +277 -15
- metadata +89 -108
- data/bin/treequel.orig +0 -963
- data/examples/ldap-monitor.rb +0 -143
- data/examples/ldap-monitor/public/css/master.css +0 -328
- data/examples/ldap-monitor/public/images/card_small.png +0 -0
- data/examples/ldap-monitor/public/images/chain_small.png +0 -0
- data/examples/ldap-monitor/public/images/globe_small.png +0 -0
- data/examples/ldap-monitor/public/images/globe_small_green.png +0 -0
- data/examples/ldap-monitor/public/images/plug.png +0 -0
- data/examples/ldap-monitor/public/images/shadows/large-30-down.png +0 -0
- data/examples/ldap-monitor/public/images/tick.png +0 -0
- data/examples/ldap-monitor/public/images/tick_circle.png +0 -0
- data/examples/ldap-monitor/public/images/treequel-favicon.png +0 -0
- data/examples/ldap-monitor/views/backends.erb +0 -41
- data/examples/ldap-monitor/views/connections.erb +0 -74
- data/examples/ldap-monitor/views/databases.erb +0 -39
- data/examples/ldap-monitor/views/dump_subsystem.erb +0 -14
- data/examples/ldap-monitor/views/index.erb +0 -14
- data/examples/ldap-monitor/views/layout.erb +0 -35
- data/examples/ldap-monitor/views/listeners.erb +0 -30
- data/rake/rdoc.rb +0 -30
- data/rake/win32.rb +0 -190
data/rake/helpers.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
|
2
3
|
#####################################################################
|
3
4
|
### G L O B A L H E L P E R F U N C T I O N S
|
4
5
|
#####################################################################
|
@@ -6,7 +7,6 @@
|
|
6
7
|
|
7
8
|
require 'pathname'
|
8
9
|
require 'uri'
|
9
|
-
require 'open3'
|
10
10
|
|
11
11
|
begin
|
12
12
|
require 'readline'
|
@@ -19,416 +19,484 @@ rescue LoadError
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
|
-
#
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
'black' => 30, 'on_black' => 40,
|
36
|
-
'red' => 31, 'on_red' => 41,
|
37
|
-
'green' => 32, 'on_green' => 42,
|
38
|
-
'yellow' => 33, 'on_yellow' => 43,
|
39
|
-
'blue' => 34, 'on_blue' => 44,
|
40
|
-
'magenta' => 35, 'on_magenta' => 45,
|
41
|
-
'cyan' => 36, 'on_cyan' => 46,
|
42
|
-
'white' => 37, 'on_white' => 47
|
43
|
-
}
|
44
|
-
|
45
|
-
|
46
|
-
MULTILINE_PROMPT = <<-'EOF'
|
47
|
-
Enter one or more values for '%s'.
|
48
|
-
A blank line finishes input.
|
49
|
-
EOF
|
50
|
-
|
51
|
-
|
52
|
-
CLEAR_TO_EOL = "\e[K"
|
53
|
-
CLEAR_CURRENT_LINE = "\e[2K"
|
54
|
-
|
55
|
-
|
56
|
-
### Output a logging message
|
57
|
-
def log( *msg )
|
58
|
-
output = colorize( msg.flatten.join(' '), 'cyan' )
|
59
|
-
$stderr.puts( output )
|
60
|
-
end
|
22
|
+
module RakefileHelpers
|
23
|
+
# Set some ANSI escape code constants (Shamelessly stolen from Perl's
|
24
|
+
# Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
|
25
|
+
ANSI_ATTRIBUTES = {
|
26
|
+
'clear' => 0,
|
27
|
+
'reset' => 0,
|
28
|
+
'bold' => 1,
|
29
|
+
'dark' => 2,
|
30
|
+
'underline' => 4,
|
31
|
+
'underscore' => 4,
|
32
|
+
'blink' => 5,
|
33
|
+
'reverse' => 7,
|
34
|
+
'concealed' => 8,
|
61
35
|
|
36
|
+
'black' => 30, 'on_black' => 40,
|
37
|
+
'red' => 31, 'on_red' => 41,
|
38
|
+
'green' => 32, 'on_green' => 42,
|
39
|
+
'yellow' => 33, 'on_yellow' => 43,
|
40
|
+
'blue' => 34, 'on_blue' => 44,
|
41
|
+
'magenta' => 35, 'on_magenta' => 45,
|
42
|
+
'cyan' => 36, 'on_cyan' => 46,
|
43
|
+
'white' => 37, 'on_white' => 47
|
44
|
+
}
|
62
45
|
|
63
|
-
### Output a logging message if tracing is on
|
64
|
-
def trace( *msg )
|
65
|
-
return unless $trace
|
66
|
-
output = colorize( msg.flatten.join(' '), 'yellow' )
|
67
|
-
$stderr.puts( output )
|
68
|
-
end
|
69
46
|
|
47
|
+
MULTILINE_PROMPT = <<-'EOF'
|
48
|
+
Enter one or more values for '%s'.
|
49
|
+
A blank line finishes input.
|
50
|
+
EOF
|
70
51
|
|
71
|
-
### Return the specified args as a string, quoting any that have a space.
|
72
|
-
def quotelist( *args )
|
73
|
-
return args.flatten.collect {|part| part =~ /\s/ ? part.inspect : part}
|
74
|
-
end
|
75
52
|
|
53
|
+
CLEAR_TO_EOL = "\e[K"
|
54
|
+
CLEAR_CURRENT_LINE = "\e[2K"
|
55
|
+
|
56
|
+
|
57
|
+
TAR_OPTS = { :compression => :gzip }
|
58
|
+
|
59
|
+
|
60
|
+
###############
|
61
|
+
module_function
|
62
|
+
###############
|
63
|
+
|
64
|
+
### Output a logging message
|
65
|
+
def log( *msg )
|
66
|
+
output = colorize( msg.flatten.join(' '), 'cyan' )
|
67
|
+
$stderr.puts( output )
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
### Output a logging message if tracing is on
|
72
|
+
def trace( *msg )
|
73
|
+
return unless $trace
|
74
|
+
output = colorize( msg.flatten.join(' '), 'yellow' )
|
75
|
+
$stderr.puts( output )
|
76
|
+
end
|
76
77
|
|
77
|
-
### Run the specified command +cmd+ with system(), failing if the execution
|
78
|
-
### fails.
|
79
|
-
def run( *cmd )
|
80
|
-
cmd.flatten!
|
81
78
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
trace( cmd )
|
79
|
+
### Return the specified args as a string, quoting any that have a space.
|
80
|
+
def quotelist( *args )
|
81
|
+
return args.flatten.collect {|part| part =~ /\s/ ? part.inspect : part}
|
86
82
|
end
|
87
83
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
84
|
+
|
85
|
+
### Run the specified command +cmd+ with system(), failing if the execution
|
86
|
+
### fails.
|
87
|
+
def run( *cmd )
|
88
|
+
cmd.flatten!
|
89
|
+
|
90
|
+
if cmd.length > 1
|
91
|
+
trace( quotelist(*cmd) )
|
92
|
+
else
|
93
|
+
trace( cmd )
|
94
|
+
end
|
95
|
+
|
96
|
+
if $dryrun
|
97
|
+
$stderr.puts "(dry run mode)"
|
98
|
+
else
|
99
|
+
system( *cmd )
|
100
|
+
unless $?.success?
|
101
|
+
fail "Command failed: [%s]" % [cmd.join(' ')]
|
102
|
+
end
|
94
103
|
end
|
95
104
|
end
|
96
|
-
end
|
97
105
|
|
98
106
|
|
99
|
-
### Run the given +cmd+ with the specified +args+ without interpolation by the shell and
|
100
|
-
### return anything written to its STDOUT.
|
101
|
-
def read_command_output( cmd, *args )
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
end
|
107
|
+
### Run the given +cmd+ with the specified +args+ without interpolation by the shell and
|
108
|
+
### return anything written to its STDOUT.
|
109
|
+
def read_command_output( cmd, *args )
|
110
|
+
trace "Reading output from: %s" % [ cmd, quotelist(cmd, *args) ]
|
111
|
+
output = IO.read( '|-' ) or exec cmd, *args
|
112
|
+
return output
|
113
|
+
end
|
106
114
|
|
107
115
|
|
108
|
-
### Run a subordinate Rake process with the same options and the specified +targets+.
|
109
|
-
def rake( *targets )
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
end
|
116
|
+
### Run a subordinate Rake process with the same options and the specified +targets+.
|
117
|
+
def rake( *targets )
|
118
|
+
opts = ARGV.select {|arg| arg[0,1] == '-' }
|
119
|
+
args = opts + targets.map {|t| t.to_s }
|
120
|
+
run 'rake', '-N', *args
|
121
|
+
end
|
114
122
|
|
115
123
|
|
116
|
-
### Open a pipe to a process running the given +cmd+ and call the given block with it.
|
117
|
-
def pipeto( *cmd )
|
118
|
-
|
124
|
+
### Open a pipe to a process running the given +cmd+ and call the given block with it.
|
125
|
+
def pipeto( *cmd )
|
126
|
+
$DEBUG = true
|
119
127
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
128
|
+
cmd.flatten!
|
129
|
+
log( "Opening a pipe to: ", cmd.collect {|part| part =~ /\s/ ? part.inspect : part} )
|
130
|
+
if $dryrun
|
131
|
+
$stderr.puts "(dry run mode)"
|
132
|
+
else
|
133
|
+
open( '|-', 'w+' ) do |io|
|
126
134
|
|
127
|
-
|
128
|
-
|
129
|
-
|
135
|
+
# Parent
|
136
|
+
if io
|
137
|
+
yield( io )
|
130
138
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
139
|
+
# Child
|
140
|
+
else
|
141
|
+
exec( *cmd )
|
142
|
+
fail "Command failed: [%s]" % [cmd.join(' ')]
|
143
|
+
end
|
135
144
|
end
|
136
145
|
end
|
137
146
|
end
|
138
|
-
end
|
139
147
|
|
140
148
|
|
141
|
-
### Download the file at +sourceuri+ via HTTP and write it to +targetfile+.
|
142
|
-
def download( sourceuri, targetfile=nil )
|
143
|
-
|
144
|
-
|
145
|
-
|
149
|
+
### Download the file at +sourceuri+ via HTTP and write it to +targetfile+.
|
150
|
+
def download( sourceuri, targetfile=nil )
|
151
|
+
oldsync = $stdout.sync
|
152
|
+
$stdout.sync = true
|
153
|
+
require 'open-uri'
|
146
154
|
|
147
|
-
|
155
|
+
targetpath = Pathname.new( targetfile )
|
148
156
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
157
|
+
log "Downloading %s to %s" % [sourceuri, targetfile]
|
158
|
+
trace " connecting..."
|
159
|
+
ifh = open( sourceuri ) do |ifh|
|
160
|
+
trace " connected..."
|
161
|
+
targetpath.open( File::WRONLY|File::TRUNC|File::CREAT, 0644 ) do |ofh|
|
162
|
+
log "Downloading..."
|
163
|
+
buf = ''
|
156
164
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
165
|
+
while ifh.read( 16384, buf )
|
166
|
+
until buf.empty?
|
167
|
+
bytes = ofh.write( buf )
|
168
|
+
buf.slice!( 0, bytes )
|
169
|
+
end
|
161
170
|
end
|
171
|
+
|
172
|
+
log "Done."
|
162
173
|
end
|
163
174
|
|
164
|
-
log "Done."
|
165
175
|
end
|
166
176
|
|
177
|
+
return targetpath
|
178
|
+
ensure
|
179
|
+
$stdout.sync = oldsync
|
167
180
|
end
|
168
181
|
|
169
|
-
return targetpath
|
170
|
-
ensure
|
171
|
-
$stdout.sync = oldsync
|
172
|
-
end
|
173
182
|
|
183
|
+
### Return the fully-qualified path to the specified +program+ in the PATH.
|
184
|
+
def which( program )
|
185
|
+
return nil unless ENV['PATH']
|
186
|
+
ENV['PATH'].split(/:/).
|
187
|
+
collect {|dir| Pathname.new(dir) + program }.
|
188
|
+
find {|path| path.exist? && path.executable? }
|
189
|
+
end
|
174
190
|
|
175
|
-
### Return the fully-qualified path to the specified +program+ in the PATH.
|
176
|
-
def which( program )
|
177
|
-
ENV['PATH'].split(/:/).
|
178
|
-
collect {|dir| Pathname.new(dir) + program }.
|
179
|
-
find {|path| path.exist? && path.executable? }
|
180
|
-
end
|
181
191
|
|
192
|
+
### Create a string that contains the ANSI codes specified and return it
|
193
|
+
def ansi_code( *attributes )
|
194
|
+
attributes.flatten!
|
195
|
+
attributes.collect! {|at| at.to_s }
|
196
|
+
# $stderr.puts "Returning ansicode for TERM = %p: %p" %
|
197
|
+
# [ ENV['TERM'], attributes ]
|
198
|
+
return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
|
199
|
+
attributes = ANSI_ATTRIBUTES.values_at( *attributes ).compact.join(';')
|
182
200
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
|
190
|
-
attributes = ANSI_ATTRIBUTES.values_at( *attributes ).compact.join(';')
|
191
|
-
|
192
|
-
# $stderr.puts " attr is: %p" % [attributes]
|
193
|
-
if attributes.empty?
|
194
|
-
return ''
|
195
|
-
else
|
196
|
-
return "\e[%sm" % attributes
|
201
|
+
# $stderr.puts " attr is: %p" % [attributes]
|
202
|
+
if attributes.empty?
|
203
|
+
return ''
|
204
|
+
else
|
205
|
+
return "\e[%sm" % attributes
|
206
|
+
end
|
197
207
|
end
|
198
|
-
end
|
199
208
|
|
200
209
|
|
201
|
-
### Colorize the given +string+ with the specified +attributes+ and return it, handling
|
202
|
-
### line-endings, color reset, etc.
|
203
|
-
def colorize( *args )
|
204
|
-
|
210
|
+
### Colorize the given +string+ with the specified +attributes+ and return it, handling
|
211
|
+
### line-endings, color reset, etc.
|
212
|
+
def colorize( *args )
|
213
|
+
string = ''
|
214
|
+
|
215
|
+
if block_given?
|
216
|
+
string = yield
|
217
|
+
else
|
218
|
+
string = args.shift
|
219
|
+
end
|
220
|
+
|
221
|
+
ending = string[/(\s)$/] || ''
|
222
|
+
string = string.rstrip
|
205
223
|
|
206
|
-
|
207
|
-
string = yield
|
208
|
-
else
|
209
|
-
string = args.shift
|
224
|
+
return ansi_code( args.flatten ) + string + ansi_code( 'reset' ) + ending
|
210
225
|
end
|
211
226
|
|
212
|
-
ending = string[/(\s)$/] || ''
|
213
|
-
string = string.rstrip
|
214
227
|
|
215
|
-
|
216
|
-
|
228
|
+
### Output the specified <tt>msg</tt> as an ANSI-colored error message
|
229
|
+
### (white on red).
|
230
|
+
def error_message( msg, details='' )
|
231
|
+
$stderr.puts colorize( 'bold', 'white', 'on_red' ) { msg } + details
|
232
|
+
end
|
233
|
+
alias :error :error_message
|
217
234
|
|
218
235
|
|
219
|
-
###
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
end
|
224
|
-
alias :error :error_message
|
236
|
+
### Highlight and embed a prompt control character in the given +string+ and return it.
|
237
|
+
def make_prompt_string( string )
|
238
|
+
return CLEAR_CURRENT_LINE + colorize( 'bold', 'green' ) { string + ' ' }
|
239
|
+
end
|
225
240
|
|
226
241
|
|
227
|
-
###
|
228
|
-
|
229
|
-
|
230
|
-
|
242
|
+
### Output the specified <tt>prompt_string</tt> as a prompt (in green) and
|
243
|
+
### return the user's input with leading and trailing spaces removed. If a
|
244
|
+
### test is provided, the prompt will repeat until the test returns true.
|
245
|
+
### An optional failure message can also be passed in.
|
246
|
+
def prompt( prompt_string, failure_msg="Try again." ) # :yields: response
|
247
|
+
prompt_string.chomp!
|
248
|
+
prompt_string << ":" unless /\W$/.match( prompt_string )
|
249
|
+
response = nil
|
250
|
+
|
251
|
+
begin
|
252
|
+
prompt = make_prompt_string( prompt_string )
|
253
|
+
response = readline( prompt ) || ''
|
254
|
+
response.strip!
|
255
|
+
if block_given? && ! yield( response )
|
256
|
+
error_message( failure_msg + "\n\n" )
|
257
|
+
response = nil
|
258
|
+
end
|
259
|
+
end while response.nil?
|
231
260
|
|
261
|
+
return response
|
262
|
+
end
|
232
263
|
|
233
|
-
### Output the specified <tt>prompt_string</tt> as a prompt (in green) and
|
234
|
-
### return the user's input with leading and trailing spaces removed. If a
|
235
|
-
### test is provided, the prompt will repeat until the test returns true.
|
236
|
-
### An optional failure message can also be passed in.
|
237
|
-
def prompt( prompt_string, failure_msg="Try again." ) # :yields: response
|
238
|
-
prompt_string.chomp!
|
239
|
-
prompt_string << ":" unless /\W$/.match( prompt_string )
|
240
|
-
response = nil
|
241
|
-
|
242
|
-
begin
|
243
|
-
prompt = make_prompt_string( prompt_string )
|
244
|
-
response = readline( prompt ) || ''
|
245
|
-
response.strip!
|
246
|
-
if block_given? && ! yield( response )
|
247
|
-
error_message( failure_msg + "\n\n" )
|
248
|
-
response = nil
|
249
|
-
end
|
250
|
-
end while response.nil?
|
251
264
|
|
252
|
-
|
253
|
-
|
265
|
+
### Prompt the user with the given <tt>prompt_string</tt> via #prompt,
|
266
|
+
### substituting the given <tt>default</tt> if the user doesn't input
|
267
|
+
### anything. If a test is provided, the prompt will repeat until the test
|
268
|
+
### returns true. An optional failure message can also be passed in.
|
269
|
+
def prompt_with_default( prompt_string, default, failure_msg="Try again." )
|
270
|
+
response = nil
|
254
271
|
|
272
|
+
begin
|
273
|
+
default ||= '~'
|
274
|
+
response = prompt( "%s [%s]" % [ prompt_string, default ] )
|
275
|
+
response = default.to_s if !response.nil? && response.empty?
|
255
276
|
|
256
|
-
|
257
|
-
### substituting the given <tt>default</tt> if the user doesn't input
|
258
|
-
### anything. If a test is provided, the prompt will repeat until the test
|
259
|
-
### returns true. An optional failure message can also be passed in.
|
260
|
-
def prompt_with_default( prompt_string, default, failure_msg="Try again." )
|
261
|
-
response = nil
|
277
|
+
trace "Validating response %p" % [ response ]
|
262
278
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
279
|
+
# the block is a validator. We need to make sure that the user didn't
|
280
|
+
# enter '~', because if they did, it's nil and we should move on. If
|
281
|
+
# they didn't, then call the block.
|
282
|
+
if block_given? && response != '~' && ! yield( response )
|
283
|
+
error_message( failure_msg + "\n\n" )
|
284
|
+
response = nil
|
285
|
+
end
|
286
|
+
end while response.nil?
|
267
287
|
|
268
|
-
|
288
|
+
return nil if response == '~'
|
289
|
+
return response
|
290
|
+
end
|
269
291
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
292
|
+
|
293
|
+
### Prompt for an array of values
|
294
|
+
def prompt_for_multiple_values( label, default=nil )
|
295
|
+
$stderr.puts( MULTILINE_PROMPT % [label] )
|
296
|
+
if default
|
297
|
+
$stderr.puts "Enter a single blank line to keep the default:\n %p" % [ default ]
|
276
298
|
end
|
277
|
-
end while response.nil?
|
278
299
|
|
279
|
-
|
280
|
-
|
281
|
-
end
|
300
|
+
results = []
|
301
|
+
result = nil
|
282
302
|
|
303
|
+
begin
|
304
|
+
result = readline( make_prompt_string("> ") )
|
305
|
+
if result.nil? || result.empty?
|
306
|
+
results << default if default && results.empty?
|
307
|
+
else
|
308
|
+
results << result
|
309
|
+
end
|
310
|
+
end until result.nil? || result.empty?
|
283
311
|
|
284
|
-
|
285
|
-
def prompt_for_multiple_values( label, default=nil )
|
286
|
-
$stderr.puts( MULTILINE_PROMPT % [label] )
|
287
|
-
if default
|
288
|
-
$stderr.puts "Enter a single blank line to keep the default:\n %p" % [ default ]
|
312
|
+
return results.flatten
|
289
313
|
end
|
290
314
|
|
291
|
-
results = []
|
292
|
-
result = nil
|
293
315
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
results << default if default && results.empty?
|
298
|
-
else
|
299
|
-
results << result
|
300
|
-
end
|
301
|
-
end until result.nil? || result.empty?
|
316
|
+
### Turn echo and masking of input on/off.
|
317
|
+
def noecho( masked=false )
|
318
|
+
require 'termios'
|
302
319
|
|
303
|
-
|
304
|
-
|
320
|
+
rval = nil
|
321
|
+
term = Termios.getattr( $stdin )
|
305
322
|
|
323
|
+
begin
|
324
|
+
newt = term.dup
|
325
|
+
newt.c_lflag &= ~Termios::ECHO
|
326
|
+
newt.c_lflag &= ~Termios::ICANON if masked
|
306
327
|
|
307
|
-
|
308
|
-
def noecho( masked=false )
|
309
|
-
require 'termios'
|
328
|
+
Termios.tcsetattr( $stdin, Termios::TCSANOW, newt )
|
310
329
|
|
311
|
-
|
312
|
-
|
330
|
+
rval = yield
|
331
|
+
ensure
|
332
|
+
Termios.tcsetattr( $stdin, Termios::TCSANOW, term )
|
333
|
+
end
|
313
334
|
|
314
|
-
|
315
|
-
|
316
|
-
newt.c_lflag &= ~Termios::ECHO
|
317
|
-
newt.c_lflag &= ~Termios::ICANON if masked
|
335
|
+
return rval
|
336
|
+
end
|
318
337
|
|
319
|
-
Termios.tcsetattr( $stdin, Termios::TCSANOW, newt )
|
320
338
|
|
321
|
-
|
322
|
-
|
323
|
-
|
339
|
+
### Prompt the user for her password, turning off echo if the 'termios' module is
|
340
|
+
### available.
|
341
|
+
def prompt_for_password( prompt="Password: " )
|
342
|
+
return noecho( true ) do
|
343
|
+
$stderr.print( prompt )
|
344
|
+
($stdin.gets || '').chomp
|
345
|
+
end
|
324
346
|
end
|
325
347
|
|
326
|
-
return rval
|
327
|
-
end
|
328
348
|
|
349
|
+
### Display a description of a potentially-dangerous task, and prompt
|
350
|
+
### for confirmation. If the user answers with anything that begins
|
351
|
+
### with 'y', yield to the block. If +abort_on_decline+ is +true+,
|
352
|
+
### any non-'y' answer will fail with an error message.
|
353
|
+
def ask_for_confirmation( description, abort_on_decline=true )
|
354
|
+
puts description
|
355
|
+
|
356
|
+
answer = prompt_with_default( "Continue?", 'n' ) do |input|
|
357
|
+
input =~ /^[yn]/i
|
358
|
+
end
|
359
|
+
|
360
|
+
if answer =~ /^y/i
|
361
|
+
return yield
|
362
|
+
elsif abort_on_decline
|
363
|
+
error "Aborted."
|
364
|
+
fail
|
365
|
+
end
|
329
366
|
|
330
|
-
|
331
|
-
### available.
|
332
|
-
def prompt_for_password( prompt="Password: " )
|
333
|
-
return noecho( true ) do
|
334
|
-
$stderr.print( prompt )
|
335
|
-
($stdin.gets || '').chomp
|
367
|
+
return false
|
336
368
|
end
|
337
|
-
|
369
|
+
alias :prompt_for_confirmation :ask_for_confirmation
|
338
370
|
|
339
371
|
|
340
|
-
###
|
341
|
-
###
|
342
|
-
###
|
343
|
-
|
344
|
-
|
345
|
-
puts description
|
372
|
+
### Search line-by-line in the specified +file+ for the given +regexp+, returning the
|
373
|
+
### first match, or nil if no match was found. If the +regexp+ has any capture groups,
|
374
|
+
### those will be returned in an Array, else the whole matching line is returned.
|
375
|
+
def find_pattern_in_file( regexp, file )
|
376
|
+
rval = nil
|
346
377
|
|
347
|
-
|
348
|
-
|
349
|
-
|
378
|
+
File.open( file, 'r' ).each do |line|
|
379
|
+
if (( match = regexp.match(line) ))
|
380
|
+
rval = match.captures.empty? ? match[0] : match.captures
|
381
|
+
break
|
382
|
+
end
|
383
|
+
end
|
350
384
|
|
351
|
-
|
352
|
-
return yield
|
353
|
-
elsif abort_on_decline
|
354
|
-
error "Aborted."
|
355
|
-
fail
|
385
|
+
return rval
|
356
386
|
end
|
357
387
|
|
358
|
-
return false
|
359
|
-
end
|
360
|
-
alias :prompt_for_confirmation :ask_for_confirmation
|
361
388
|
|
389
|
+
### Search line-by-line in the output of the specified +cmd+ for the given +regexp+,
|
390
|
+
### returning the first match, or nil if no match was found. If the +regexp+ has any
|
391
|
+
### capture groups, those will be returned in an Array, else the whole matching line
|
392
|
+
### is returned.
|
393
|
+
def find_pattern_in_pipe( regexp, *cmd )
|
394
|
+
require 'open3'
|
395
|
+
output = []
|
362
396
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
def find_pattern_in_file( regexp, file )
|
367
|
-
rval = nil
|
397
|
+
log( cmd.collect {|part| part =~ /\s/ ? part.inspect : part} )
|
398
|
+
Open3.popen3( *cmd ) do |stdin, stdout, stderr|
|
399
|
+
stdin.close
|
368
400
|
|
369
|
-
|
370
|
-
|
371
|
-
rval = match.captures.empty? ? match[0] : match.captures
|
372
|
-
break
|
401
|
+
output << stdout.gets until stdout.eof?
|
402
|
+
output << stderr.gets until stderr.eof?
|
373
403
|
end
|
374
|
-
end
|
375
404
|
|
376
|
-
|
377
|
-
|
405
|
+
result = output.find { |line| regexp.match(line) }
|
406
|
+
return $1 || result
|
407
|
+
end
|
378
408
|
|
379
409
|
|
380
|
-
###
|
381
|
-
###
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
410
|
+
### Invoke the user's editor on the given +filename+ and return the exit code
|
411
|
+
### from doing so.
|
412
|
+
def edit( filename )
|
413
|
+
editor = ENV['EDITOR'] || ENV['VISUAL'] || DEFAULT_EDITOR
|
414
|
+
system editor, filename
|
415
|
+
unless $?.success? || editor =~ /vim/i
|
416
|
+
fail "Editor exited uncleanly."
|
417
|
+
end
|
418
|
+
end
|
386
419
|
|
387
|
-
log( cmd.collect {|part| part =~ /\s/ ? part.inspect : part} )
|
388
|
-
Open3.popen3( *cmd ) do |stdin, stdout, stderr|
|
389
|
-
stdin.close
|
390
420
|
|
391
|
-
|
392
|
-
|
421
|
+
### Extract all the non Rake-target arguments from ARGV and return them.
|
422
|
+
def get_target_args
|
423
|
+
args = ARGV.reject {|arg| arg =~ /^-/ || Rake::Task.task_defined?(arg) }
|
424
|
+
return args
|
393
425
|
end
|
394
426
|
|
395
|
-
result = output.find { |line| regexp.match(line) }
|
396
|
-
return $1 || result
|
397
|
-
end
|
398
427
|
|
428
|
+
### Log a subdirectory change, execute a block, and exit the subdirectory
|
429
|
+
def in_subdirectory( subdir )
|
430
|
+
block = Proc.new
|
399
431
|
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
editor = ENV['EDITOR'] || ENV['VISUAL'] || DEFAULT_EDITOR
|
404
|
-
system editor, filename
|
405
|
-
unless $?.success? || editor =~ /vim/i
|
406
|
-
fail "Editor exited uncleanly."
|
432
|
+
log "Entering #{subdir}"
|
433
|
+
Dir.chdir( subdir, &block )
|
434
|
+
log "Leaving #{subdir}"
|
407
435
|
end
|
408
|
-
end
|
409
436
|
|
410
437
|
|
411
|
-
###
|
412
|
-
def
|
413
|
-
|
414
|
-
|
415
|
-
end
|
438
|
+
### Make an easily-comparable version vector out of +ver+ and return it.
|
439
|
+
def vvec( ver )
|
440
|
+
return ver.split('.').collect {|char| char.to_i }.pack('N*')
|
441
|
+
end
|
416
442
|
|
417
443
|
|
418
|
-
###
|
419
|
-
|
420
|
-
|
444
|
+
### Archive::Tar::Reader#extract (as of 0.9.0) is broken w.r.t.
|
445
|
+
### permissions, so we have to do this ourselves.
|
446
|
+
def untar( tarfile, targetdir )
|
447
|
+
require 'archive/tar'
|
448
|
+
targetdir = Pathname( targetdir )
|
449
|
+
raise "No such directory: #{targetdir}" unless targetdir.directory?
|
421
450
|
|
422
|
-
|
423
|
-
Dir.chdir( subdir, &block )
|
424
|
-
log "Leaving #{subdir}"
|
425
|
-
end
|
451
|
+
reader = Archive::Tar::Reader.new( tarfile.to_s, TAR_OPTS )
|
426
452
|
|
453
|
+
mkdir_p( targetdir )
|
454
|
+
reader.each( true ) do |header, body|
|
455
|
+
path = targetdir + header[:path]
|
456
|
+
# trace "Header is: %p" % [ header ]
|
427
457
|
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
458
|
+
case header[:type]
|
459
|
+
when :file
|
460
|
+
trace " #{path}"
|
461
|
+
path.open( File::WRONLY|File::EXCL|File::CREAT|File::TRUNC, header[:mode] ) do |fio|
|
462
|
+
bytesize = header[:size]
|
463
|
+
fio.write( body[0,bytesize] )
|
464
|
+
end
|
465
|
+
|
466
|
+
when :directory
|
467
|
+
trace " #{path}"
|
468
|
+
path.mkpath
|
469
|
+
|
470
|
+
when :link
|
471
|
+
linktarget = targetdir + header[:dest]
|
472
|
+
trace " #{path} => #{linktarget}"
|
473
|
+
path.make_link( linktarget.to_s )
|
474
|
+
|
475
|
+
when :symlink
|
476
|
+
linktarget = targetdir + header[:dest]
|
477
|
+
trace " #{path} -> #{linktarget}"
|
478
|
+
path.make_symlink( linktarget )
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
end
|
483
|
+
|
484
|
+
|
485
|
+
### Extract the contents of the specified +zipfile+ into the given +targetdir+.
|
486
|
+
def unzip( zipfile, targetdir, *files )
|
487
|
+
require 'zip/zip'
|
488
|
+
targetdir = Pathname( targetdir )
|
489
|
+
raise "No such directory: #{targetdir}" unless targetdir.directory?
|
490
|
+
|
491
|
+
Zip::ZipFile.foreach( zipfile ) do |entry|
|
492
|
+
# trace " entry is: %p" % [ entry ]
|
493
|
+
next unless files.empty? || files.include?( entry.name )
|
494
|
+
target_path = targetdir + entry.name
|
495
|
+
# trace " would extract to: %s" % [ target_path ]
|
496
|
+
entry.extract( target_path ) { true }
|
497
|
+
end
|
498
|
+
end
|
432
499
|
|
500
|
+
end # module Rakefile::Helpers
|
433
501
|
|
434
502
|
|