rudy 0.4.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +54 -30
- data/README.rdoc +100 -12
- data/Rakefile +103 -8
- data/Rudyfile +119 -0
- data/bin/ird +175 -0
- data/bin/rudy +259 -156
- data/bin/rudy-ec2 +228 -95
- data/bin/rudy-s3 +76 -0
- data/bin/rudy-sdb +67 -0
- data/lib/annoy.rb +270 -0
- data/lib/console.rb +30 -9
- data/lib/escape.rb +305 -0
- data/lib/rudy.rb +151 -182
- data/lib/rudy/aws.rb +56 -49
- data/lib/rudy/aws/ec2.rb +47 -292
- data/lib/rudy/aws/ec2/address.rb +157 -0
- data/lib/rudy/aws/ec2/group.rb +301 -0
- data/lib/rudy/aws/ec2/image.rb +168 -0
- data/lib/rudy/aws/ec2/instance.rb +434 -0
- data/lib/rudy/aws/ec2/keypair.rb +104 -0
- data/lib/rudy/aws/ec2/snapshot.rb +98 -0
- data/lib/rudy/aws/ec2/volume.rb +230 -0
- data/lib/rudy/aws/ec2/zone.rb +77 -0
- data/lib/rudy/aws/s3.rb +54 -0
- data/lib/rudy/aws/sdb.rb +298 -0
- data/lib/rudy/aws/sdb/error.rb +46 -0
- data/lib/rudy/{metadata/backup.rb → backup.rb} +26 -51
- data/lib/rudy/cli.rb +157 -0
- data/lib/rudy/cli/aws/ec2/addresses.rb +105 -0
- data/lib/rudy/cli/aws/ec2/candy.rb +208 -0
- data/lib/rudy/cli/aws/ec2/groups.rb +121 -0
- data/lib/rudy/cli/aws/ec2/images.rb +196 -0
- data/lib/rudy/cli/aws/ec2/instances.rb +194 -0
- data/lib/rudy/cli/aws/ec2/keypairs.rb +53 -0
- data/lib/rudy/cli/aws/ec2/snapshots.rb +49 -0
- data/lib/rudy/cli/aws/ec2/volumes.rb +104 -0
- data/lib/rudy/cli/aws/ec2/zones.rb +22 -0
- data/lib/rudy/cli/aws/s3/buckets.rb +50 -0
- data/lib/rudy/cli/aws/s3/store.rb +22 -0
- data/lib/rudy/cli/aws/sdb/domains.rb +41 -0
- data/lib/rudy/cli/candy.rb +8 -0
- data/lib/rudy/{command → cli}/config.rb +34 -24
- data/lib/rudy/cli/disks.rb +35 -0
- data/lib/rudy/cli/machines.rb +94 -0
- data/lib/rudy/cli/routines.rb +57 -0
- data/lib/rudy/config.rb +77 -72
- data/lib/rudy/config/objects.rb +29 -0
- data/lib/rudy/disks.rb +248 -0
- data/lib/rudy/global.rb +121 -0
- data/lib/rudy/huxtable.rb +340 -0
- data/lib/rudy/machines.rb +245 -0
- data/lib/rudy/metadata.rb +123 -13
- data/lib/rudy/routines.rb +47 -0
- data/lib/rudy/routines/helpers/diskhelper.rb +101 -0
- data/lib/rudy/routines/helpers/scripthelper.rb +91 -0
- data/lib/rudy/routines/release.rb +34 -0
- data/lib/rudy/routines/shutdown.rb +57 -0
- data/lib/rudy/routines/startup.rb +58 -0
- data/lib/rudy/scm/svn.rb +1 -1
- data/lib/rudy/utils.rb +322 -4
- data/lib/storable.rb +26 -17
- data/lib/sysinfo.rb +274 -0
- data/lib/tryouts.rb +6 -13
- data/rudy.gemspec +128 -42
- data/support/randomize-root-password +45 -0
- data/support/rudy-ec2-startup +9 -9
- data/support/update-ec2-ami-tools +20 -0
- data/test/05_config/00_setup_test.rb +20 -0
- data/test/05_config/30_machines_test.rb +69 -0
- data/test/20_sdb/00_setup_test.rb +16 -0
- data/test/20_sdb/10_domains_test.rb +115 -0
- data/test/25_ec2/00_setup_test.rb +29 -0
- data/test/25_ec2/10_keypairs_test.rb +41 -0
- data/test/25_ec2/20_groups_test.rb +131 -0
- data/test/25_ec2/30_addresses_test.rb +38 -0
- data/test/25_ec2/40_volumes_test.rb +49 -0
- data/test/25_ec2/50_snapshots_test.rb +74 -0
- data/test/26_ec2_instances/00_setup_test.rb +28 -0
- data/test/26_ec2_instances/10_instances_test.rb +83 -0
- data/test/26_ec2_instances/50_images_test.rb +13 -0
- data/test/30_sdb_metadata/00_setup_test.rb +21 -0
- data/test/30_sdb_metadata/10_disks_test.rb +109 -0
- data/test/30_sdb_metadata/20_backups_test.rb +102 -0
- data/test/coverage.txt +51 -0
- data/test/helper.rb +36 -0
- data/vendor/highline-1.5.1/CHANGELOG +222 -0
- data/vendor/highline-1.5.1/INSTALL +35 -0
- data/vendor/highline-1.5.1/LICENSE +7 -0
- data/vendor/highline-1.5.1/README +63 -0
- data/vendor/highline-1.5.1/Rakefile +82 -0
- data/vendor/highline-1.5.1/TODO +6 -0
- data/vendor/highline-1.5.1/examples/ansi_colors.rb +38 -0
- data/vendor/highline-1.5.1/examples/asking_for_arrays.rb +18 -0
- data/vendor/highline-1.5.1/examples/basic_usage.rb +75 -0
- data/vendor/highline-1.5.1/examples/color_scheme.rb +32 -0
- data/vendor/highline-1.5.1/examples/limit.rb +12 -0
- data/vendor/highline-1.5.1/examples/menus.rb +65 -0
- data/vendor/highline-1.5.1/examples/overwrite.rb +19 -0
- data/vendor/highline-1.5.1/examples/page_and_wrap.rb +322 -0
- data/vendor/highline-1.5.1/examples/password.rb +7 -0
- data/vendor/highline-1.5.1/examples/trapping_eof.rb +22 -0
- data/vendor/highline-1.5.1/examples/using_readline.rb +17 -0
- data/vendor/highline-1.5.1/lib/highline.rb +758 -0
- data/vendor/highline-1.5.1/lib/highline/color_scheme.rb +120 -0
- data/vendor/highline-1.5.1/lib/highline/compatibility.rb +17 -0
- data/vendor/highline-1.5.1/lib/highline/import.rb +43 -0
- data/vendor/highline-1.5.1/lib/highline/menu.rb +395 -0
- data/vendor/highline-1.5.1/lib/highline/question.rb +463 -0
- data/vendor/highline-1.5.1/lib/highline/system_extensions.rb +193 -0
- data/vendor/highline-1.5.1/setup.rb +1360 -0
- data/vendor/highline-1.5.1/test/tc_color_scheme.rb +56 -0
- data/vendor/highline-1.5.1/test/tc_highline.rb +823 -0
- data/vendor/highline-1.5.1/test/tc_import.rb +54 -0
- data/vendor/highline-1.5.1/test/tc_menu.rb +429 -0
- data/vendor/highline-1.5.1/test/ts_all.rb +15 -0
- metadata +141 -38
- data/lib/aws_sdb.rb +0 -3
- data/lib/aws_sdb/error.rb +0 -42
- data/lib/aws_sdb/service.rb +0 -215
- data/lib/rudy/aws/simpledb.rb +0 -53
- data/lib/rudy/command/addresses.rb +0 -46
- data/lib/rudy/command/backups.rb +0 -175
- data/lib/rudy/command/base.rb +0 -841
- data/lib/rudy/command/deploy.rb +0 -12
- data/lib/rudy/command/disks.rb +0 -213
- data/lib/rudy/command/environment.rb +0 -73
- data/lib/rudy/command/groups.rb +0 -61
- data/lib/rudy/command/images.rb +0 -91
- data/lib/rudy/command/instances.rb +0 -85
- data/lib/rudy/command/machines.rb +0 -161
- data/lib/rudy/command/metadata.rb +0 -41
- data/lib/rudy/command/release.rb +0 -174
- data/lib/rudy/command/volumes.rb +0 -66
- data/lib/rudy/metadata/disk.rb +0 -138
- data/tryouts/console_tryout.rb +0 -91
data/lib/annoy.rb
ADDED
@@ -0,0 +1,270 @@
|
|
1
|
+
#---
|
2
|
+
# TODO: Use Matrix to give a more accurate annoyance factor
|
3
|
+
# TODO: Add trivia questions
|
4
|
+
#+++
|
5
|
+
|
6
|
+
require 'timeout'
|
7
|
+
require 'sysinfo'
|
8
|
+
require 'highline'
|
9
|
+
|
10
|
+
# = Annoy
|
11
|
+
#
|
12
|
+
# Like your annoying friend that asks you questions all the time.
|
13
|
+
#
|
14
|
+
class Annoy
|
15
|
+
|
16
|
+
attr_accessor :factor
|
17
|
+
attr_accessor :flavor
|
18
|
+
attr_accessor :answer
|
19
|
+
attr_accessor :writer
|
20
|
+
attr_accessor :period
|
21
|
+
attr_accessor :system
|
22
|
+
|
23
|
+
@@operators = {
|
24
|
+
:low => %w(+ -),
|
25
|
+
:medium => %w(* -),
|
26
|
+
:high => %w(& * -),
|
27
|
+
:insane => %w(** << | & *)
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
@@strlen = {
|
31
|
+
:low => 2,
|
32
|
+
:medium => 3,
|
33
|
+
:high => 4,
|
34
|
+
:insane => 32
|
35
|
+
}.freeze
|
36
|
+
|
37
|
+
@@randsize = {
|
38
|
+
:low => 10,
|
39
|
+
:medium => 14,
|
40
|
+
:high => 50,
|
41
|
+
:insane => 1000
|
42
|
+
}.freeze
|
43
|
+
|
44
|
+
@@period = 60.freeze # max seconds to wait
|
45
|
+
@@flavors = [:numeric, :string].freeze
|
46
|
+
@@skip = false # skip questions
|
47
|
+
|
48
|
+
# Calling this method tells Annoy to not prompt for
|
49
|
+
# a response. All questions will return true.
|
50
|
+
def Annoy.enable_skip; @@skip = true; end
|
51
|
+
# Tells annoy to prompt for a response.
|
52
|
+
def Annoy.disable_skip; @@skip = false; end
|
53
|
+
# Returns true of Annoy is in skip mode
|
54
|
+
def Annoy.skip?; @@skip; end
|
55
|
+
|
56
|
+
# * +factor+ annoyance factor, one of :low (default), :medium, :high, :insane
|
57
|
+
# * +flavor+ annoyance flavor, one of :rand (default), :numeric, string
|
58
|
+
# * +writer+ an IO object to write to. Default: STDERR
|
59
|
+
# * +period+ the amount of time to wait in seconds. Default: 60
|
60
|
+
def initialize(opts={:factor=>:medium, :flavor=>:rand, :writer=>STDOUT, :period=>nil})
|
61
|
+
@factor = opts[:factor]
|
62
|
+
@flavor = Annoy.get_flavor(opts[:flavor])
|
63
|
+
@writer = opts[:writer]
|
64
|
+
@period = opts[:period] || @@period
|
65
|
+
unless Annoy.respond_to?("#{@flavor}_question")
|
66
|
+
raise "Hey, hey, hey. I don't know that flavor! (#{@flavor})"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Generates and returns a question. The correct response is available
|
71
|
+
# as +@answer+.
|
72
|
+
def question
|
73
|
+
q, @answer =Annoy.question(@factor, @flavor)
|
74
|
+
q
|
75
|
+
end
|
76
|
+
|
77
|
+
# A wrapper for string_question and numberic_question
|
78
|
+
def Annoy.question(factor=:medium, flavor=:rand)
|
79
|
+
raise "Come on, you ruined the flavor!" unless flavor
|
80
|
+
Annoy.send("#{flavor}_question", factor)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Generates a random string
|
84
|
+
def Annoy.string_question(factor=:medium)
|
85
|
+
# Strings don't need to be evaluated so the answer is the
|
86
|
+
# same as the question.
|
87
|
+
str = strand @@strlen[factor]
|
88
|
+
[str,str]
|
89
|
+
end
|
90
|
+
|
91
|
+
# * Generates a rudimentary numeric equation in the form: (Integer OPERATOR Integer).
|
92
|
+
# * Returns [equation, answer]
|
93
|
+
def Annoy.numeric_question(factor=:medium)
|
94
|
+
equation = answer = 0
|
95
|
+
while answer < 10
|
96
|
+
vals = [rand(@@randsize[factor])+1,
|
97
|
+
@@operators[factor][ rand(@@operators[factor].size) ],
|
98
|
+
rand(@@randsize[factor])+1 ]
|
99
|
+
equation = "(%d %s %d)" % vals
|
100
|
+
answer = eval(equation)
|
101
|
+
end
|
102
|
+
[equation, answer]
|
103
|
+
end
|
104
|
+
|
105
|
+
# Prints a question to +writer+ and waits for a response on STDIN.
|
106
|
+
# It checks whether STDIN is connected a tty so it doesn't block on gets
|
107
|
+
# when there's no human around to annoy. It will return <b>TRUE</b> when
|
108
|
+
# STDIN is NOT connected to a tty (when STDIN.tty? returns false).
|
109
|
+
# * +msg+ The message to print. Default: "Please confirm."
|
110
|
+
# Returns true when the answer is correct, otherwise false.
|
111
|
+
def Annoy.challenge?(msg="Please confirm.", factor=:medium, flavor=:rand, writer=STDOUT, period=nil)
|
112
|
+
return true unless STDIN.tty? # Humans only!
|
113
|
+
return true if Annoy.skip?
|
114
|
+
begin
|
115
|
+
success = Timeout::timeout(period || @@period) do
|
116
|
+
flavor = Annoy.get_flavor(flavor)
|
117
|
+
question, answer = Annoy.question(factor, flavor)
|
118
|
+
msg = "#{msg} To continue, #{Annoy.verb(flavor)} #{question}: "
|
119
|
+
#writer.print msg
|
120
|
+
#if ![:medium, :high, :insane].member?(factor) && flavor == :numeric
|
121
|
+
#writer.print "(#{answer}) "
|
122
|
+
#writer.flush
|
123
|
+
#end
|
124
|
+
#response = Annoy.get_response(writer)
|
125
|
+
|
126
|
+
trap("SIGINT") { raise Annoy::GiveUp }
|
127
|
+
|
128
|
+
highline = HighLine.new
|
129
|
+
response = highline.ask(msg) { |q|
|
130
|
+
q.echo = '*' # Don't display response
|
131
|
+
q.overwrite = true # Erase the question afterwards
|
132
|
+
q.whitespace = :strip # Remove whitespace from the response
|
133
|
+
q.answer_type = Integer if flavor == :numeric
|
134
|
+
}
|
135
|
+
|
136
|
+
ret = (response == answer)
|
137
|
+
writer.puts "Incorrect" unless ret
|
138
|
+
ret
|
139
|
+
end
|
140
|
+
rescue Annoy::GiveUp => ex
|
141
|
+
writer.puts $/, "Giving up!"
|
142
|
+
false
|
143
|
+
rescue Timeout::Error => ex
|
144
|
+
writer.puts $/, "Times up!"
|
145
|
+
false
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Runs a challenge with the message, "Are you sure?"
|
150
|
+
# See: Annoy.challenge?
|
151
|
+
def Annoy.are_you_sure?(factor=:medium, flavor=:rand, writer=STDOUT)
|
152
|
+
Annoy.challenge?("Are you sure?", factor, flavor, writer)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Runs a challenge with the message, "Proceed?"
|
156
|
+
# See: Annoy.challenge?
|
157
|
+
def Annoy.proceed?(factor=:medium, flavor=:rand, writer=STDOUT)
|
158
|
+
Annoy.challenge?("Proceed?", factor, flavor, writer)
|
159
|
+
end
|
160
|
+
|
161
|
+
# See: Annoy.challenge?
|
162
|
+
# Uses the value of @flavor, @factor, and @writer
|
163
|
+
def challenge?(msg="Please confirm.")
|
164
|
+
Annoy.challenge?(msg, @factor, @flavor, @writer)
|
165
|
+
end
|
166
|
+
|
167
|
+
# See: Annoy.pose_question
|
168
|
+
# Uses the value of @writer
|
169
|
+
def pose_question(msg, regexp)
|
170
|
+
Annoy.pose_question(msg, regexp, @writer)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Prints a question to writer and waits for a response on STDIN.
|
174
|
+
# It checks whether STDIN is connected a tty so it doesn't block on gets.
|
175
|
+
# when there's no human around to annoy. It will return <b>TRUE</b> when
|
176
|
+
# STDIN is NOT connected to a tty.
|
177
|
+
# * +msg+ The question to pose to the user
|
178
|
+
# * +regexp+ The regular expression to match the answer.
|
179
|
+
def Annoy.pose_question(msg, regexp, writer=STDOUT, period=nil)
|
180
|
+
return true unless STDIN.tty? # Only ask a question if there's a human
|
181
|
+
return true if Annoy.skip?
|
182
|
+
begin
|
183
|
+
success = Timeout::timeout(period || @@period) do
|
184
|
+
regexp &&= Regexp.new regexp
|
185
|
+
writer.print msg
|
186
|
+
writer.flush if writer.respond_to?(:flush)
|
187
|
+
response = Annoy.get_response
|
188
|
+
regexp.match(response)
|
189
|
+
end
|
190
|
+
rescue Timeout::Error => ex
|
191
|
+
writer.puts $/, "Times up!"
|
192
|
+
false
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
|
197
|
+
private
|
198
|
+
def Annoy.get_response(writer=STDOUT)
|
199
|
+
return true unless STDIN.tty? # Humans only
|
200
|
+
return true if Annoy.skip?
|
201
|
+
# TODO: Count the number of keystrokes to prevent copy/paste.
|
202
|
+
# We can probably use Highline.
|
203
|
+
# We likely need to be more specific but this will do for now.
|
204
|
+
#if ::SystemInfo.new.os == :unix
|
205
|
+
# begin
|
206
|
+
# response = []
|
207
|
+
# char = nil
|
208
|
+
# system("stty raw -echo") # Raw mode, no echo
|
209
|
+
# while char != "\r" || response.size > 5
|
210
|
+
# char = STDIN.getc.chr
|
211
|
+
# writer.print char
|
212
|
+
# writer.flush
|
213
|
+
# response << char
|
214
|
+
# end
|
215
|
+
# writer.print "\n\r"
|
216
|
+
# response = response.join('')
|
217
|
+
# rescue => ex
|
218
|
+
# ensure
|
219
|
+
# system("stty -raw echo") # Reset terminal mode
|
220
|
+
# end
|
221
|
+
#else
|
222
|
+
response = (STDIN.gets || "")
|
223
|
+
#end
|
224
|
+
response.chomp.gsub(/["']/, '')
|
225
|
+
end
|
226
|
+
# Returns a verb appropriate to the flavor.
|
227
|
+
# * :numeric => resolve
|
228
|
+
# * :string => type
|
229
|
+
def Annoy.verb(flavor)
|
230
|
+
case flavor
|
231
|
+
when :numeric then "resolve"
|
232
|
+
when :string then "type"
|
233
|
+
else
|
234
|
+
nil
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
#
|
239
|
+
# Generates a string of random alphanumeric characters.
|
240
|
+
# * +len+ is the length, an Integer. Default: 8
|
241
|
+
# * +safe+ in safe-mode, ambiguous characters are removed (default: true):
|
242
|
+
# i l o 1 0
|
243
|
+
def Annoy.strand( len=8, safe=true )
|
244
|
+
chars = ("a".."z").to_a + ("0".."9").to_a
|
245
|
+
chars.delete_if { |v| %w(i l o 1 0).member?(v) } if safe
|
246
|
+
str = ""
|
247
|
+
1.upto(len) { |i| str << chars[rand(chars.size-1)] }
|
248
|
+
str
|
249
|
+
end
|
250
|
+
|
251
|
+
# * +f+ a prospective flavor name
|
252
|
+
def Annoy.get_flavor(f)
|
253
|
+
f.to_sym == :rand ? flavor_rand : f.to_sym
|
254
|
+
end
|
255
|
+
|
256
|
+
# Return a random flavor
|
257
|
+
def Annoy.flavor_rand
|
258
|
+
@@flavors[rand(@@flavors.size)]
|
259
|
+
end
|
260
|
+
|
261
|
+
|
262
|
+
end
|
263
|
+
|
264
|
+
# = Annoy::GiveUp
|
265
|
+
#
|
266
|
+
# This is what happens when you don't answer Annoy's questions.
|
267
|
+
class Annoy::GiveUp < RuntimeError
|
268
|
+
end
|
269
|
+
|
270
|
+
|
data/lib/console.rb
CHANGED
@@ -4,8 +4,15 @@
|
|
4
4
|
# See: man terminfo
|
5
5
|
#+++
|
6
6
|
|
7
|
-
|
7
|
+
#
|
8
|
+
#
|
9
|
+
#
|
8
10
|
class String
|
11
|
+
@@print_with_attributes = true
|
12
|
+
def String.disable_colour; @@print_with_attributes = false; end
|
13
|
+
def String.disable_color; @@print_with_attributes = false; end
|
14
|
+
def String.enable_colour; @@print_with_attributes = true; end
|
15
|
+
def String.enable_color; @@print_with_attributes = true; end
|
9
16
|
|
10
17
|
# +col+, +bgcol+, and +attribute+ are symbols corresponding
|
11
18
|
# to Console::COLOURS, Console::BGCOLOURS, and Console::ATTRIBUTES.
|
@@ -14,6 +21,7 @@ class String
|
|
14
21
|
# "MONKEY_JUNK".colour(:blue, :white, :blink) # => "\e[34;47;5mMONKEY_JUNK\e[39;49;0m"
|
15
22
|
#
|
16
23
|
def colour(col, bgcol = nil, attribute = nil)
|
24
|
+
return self unless @@print_with_attributes
|
17
25
|
Console.style(col, bgcol, attribute) +
|
18
26
|
self +
|
19
27
|
Console.style(:default, :default, :default)
|
@@ -22,6 +30,7 @@ class String
|
|
22
30
|
|
23
31
|
# See colour
|
24
32
|
def bgcolour(bgcol = :default)
|
33
|
+
return self unless @@print_with_attributes
|
25
34
|
Console.style(nil, bgcol, nil) +
|
26
35
|
self +
|
27
36
|
Console.style(nil, :default, nil)
|
@@ -30,11 +39,16 @@ class String
|
|
30
39
|
|
31
40
|
# See colour
|
32
41
|
def att(a = :default)
|
42
|
+
return self unless @@print_with_attributes
|
33
43
|
Console.style(nil, nil, a) +
|
34
44
|
self +
|
35
45
|
Console.style(nil, nil, :default)
|
36
46
|
end
|
37
47
|
|
48
|
+
# Shortcut for att(:bright)
|
49
|
+
def bright
|
50
|
+
att(:bright)
|
51
|
+
end
|
38
52
|
|
39
53
|
# Print the string at +x+ +y+. When +minus+ is any true value
|
40
54
|
# the length of the string is subtracted from the value of x
|
@@ -46,7 +60,8 @@ class String
|
|
46
60
|
Console.print_at(self, args)
|
47
61
|
end
|
48
62
|
|
49
|
-
# Returns the string with escape
|
63
|
+
# Returns the string with ANSI escape codes removed.
|
64
|
+
#
|
50
65
|
# NOTE: The non-printable attributes count towards the string size.
|
51
66
|
# You can use this method to get the "visible" size:
|
52
67
|
#
|
@@ -54,11 +69,13 @@ class String
|
|
54
69
|
# "\e[34;47;5mMONKEY_JUNK\e[39;49;0m".size # => 31
|
55
70
|
#
|
56
71
|
def noatt
|
57
|
-
gsub(/\e\[[
|
72
|
+
gsub(/\e\[?[0-9;]*[mc]?/, '')
|
58
73
|
end
|
74
|
+
alias :noansi :noatt
|
75
|
+
|
59
76
|
end
|
60
77
|
|
61
|
-
class Object
|
78
|
+
class Object #:nodoc:all
|
62
79
|
|
63
80
|
# Executes tput +capnam+ with +args+. Returns true if tcap gives
|
64
81
|
# 0 exit status and false otherwise.
|
@@ -82,7 +99,7 @@ end
|
|
82
99
|
|
83
100
|
|
84
101
|
|
85
|
-
module Console
|
102
|
+
module Console #:nodoc:all
|
86
103
|
extend self
|
87
104
|
require 'timeout'
|
88
105
|
require 'thread'
|
@@ -127,6 +144,10 @@ module Console
|
|
127
144
|
:random => 40 + rand(10).to_i
|
128
145
|
}.freeze unless defined? BGCOLOURS
|
129
146
|
|
147
|
+
def valid_colour?(colour)
|
148
|
+
COLOURS.has_key? colour
|
149
|
+
end
|
150
|
+
alias :valid_color? :valid_colour?
|
130
151
|
|
131
152
|
def print_left(str, props={})
|
132
153
|
props[:x] ||= 0
|
@@ -159,8 +180,8 @@ module Console
|
|
159
180
|
end
|
160
181
|
def print_at(str, props={})
|
161
182
|
print_at_lamb = lambda {
|
162
|
-
|
163
|
-
|
183
|
+
props[:x] ||= 0
|
184
|
+
props[:y] ||= 0
|
164
185
|
props[:minus] = false unless props.has_key?(:minus)
|
165
186
|
props[:x] = props[:x]-str.noatt.size if props[:x] && props[:minus] # Subtract the str length from the position
|
166
187
|
Cursor.save
|
@@ -196,7 +217,7 @@ module Console
|
|
196
217
|
end
|
197
218
|
end
|
198
219
|
|
199
|
-
module Cursor
|
220
|
+
module Cursor #:nodoc:all
|
200
221
|
extend self
|
201
222
|
|
202
223
|
# Returns [x,y] for the current cursor position.
|
@@ -291,7 +312,7 @@ module Cursor
|
|
291
312
|
|
292
313
|
end
|
293
314
|
|
294
|
-
class Window
|
315
|
+
class Window #:nodoc:all
|
295
316
|
attr_accessor :row, :col, :width, :height, :text, :fg, :bg
|
296
317
|
attr_reader :threads
|
297
318
|
|
data/lib/escape.rb
ADDED
@@ -0,0 +1,305 @@
|
|
1
|
+
# escape.rb - escape/unescape library for several formats
|
2
|
+
#
|
3
|
+
# Copyright (C) 2006,2007 Tanaka Akira <akr@fsij.org>
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
9
|
+
# list of conditions and the following disclaimer.
|
10
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
# 3. The name of the author may not be used to endorse or promote products
|
14
|
+
# derived from this software without specific prior written permission.
|
15
|
+
#
|
16
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
17
|
+
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
18
|
+
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
19
|
+
# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
20
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
21
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
22
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
23
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
24
|
+
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
25
|
+
# OF SUCH DAMAGE.
|
26
|
+
|
27
|
+
# Escape module provides several escape functions.
|
28
|
+
# * URI
|
29
|
+
# * HTML
|
30
|
+
# * shell command
|
31
|
+
module Escape #:nodoc:all
|
32
|
+
module_function
|
33
|
+
|
34
|
+
class StringWrapper
|
35
|
+
class << self
|
36
|
+
alias new_no_dup new
|
37
|
+
def new(str)
|
38
|
+
new_no_dup(str.dup)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(str)
|
43
|
+
@str = str
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
@str.dup
|
48
|
+
end
|
49
|
+
|
50
|
+
def inspect
|
51
|
+
"\#<#{self.class}: #{@str}>"
|
52
|
+
end
|
53
|
+
|
54
|
+
def ==(other)
|
55
|
+
other.class == self.class && @str == other.instance_variable_get(:@str)
|
56
|
+
end
|
57
|
+
alias eql? ==
|
58
|
+
|
59
|
+
def hash
|
60
|
+
@str.hash
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class ShellEscaped < StringWrapper
|
65
|
+
end
|
66
|
+
|
67
|
+
# Escape.shell_command composes
|
68
|
+
# a sequence of words to
|
69
|
+
# a single shell command line.
|
70
|
+
# All shell meta characters are quoted and
|
71
|
+
# the words are concatenated with interleaving space.
|
72
|
+
# It returns an instance of ShellEscaped.
|
73
|
+
#
|
74
|
+
# Escape.shell_command(["ls", "/"]) #=> #<Escape::ShellEscaped: ls />
|
75
|
+
# Escape.shell_command(["echo", "*"]) #=> #<Escape::ShellEscaped: echo '*'>
|
76
|
+
#
|
77
|
+
# Note that system(*command) and
|
78
|
+
# system(Escape.shell_command(command)) is roughly same.
|
79
|
+
# There are two exception as follows.
|
80
|
+
# * The first is that the later may invokes /bin/sh.
|
81
|
+
# * The second is an interpretation of an array with only one element:
|
82
|
+
# the element is parsed by the shell with the former but
|
83
|
+
# it is recognized as single word with the later.
|
84
|
+
# For example, system(*["echo foo"]) invokes echo command with an argument "foo".
|
85
|
+
# But system(Escape.shell_command(["echo foo"])) invokes "echo foo" command without arguments (and it probably fails).
|
86
|
+
def shell_command(*command)
|
87
|
+
command = [command].flatten.compact # Delano
|
88
|
+
s = command.map {|word| shell_single_word(word) }.join(' ')
|
89
|
+
ShellEscaped.new_no_dup(s)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Escape.shell_single_word quotes shell meta characters.
|
93
|
+
# It returns an instance of ShellEscaped.
|
94
|
+
#
|
95
|
+
# The result string is always single shell word, even if
|
96
|
+
# the argument is "".
|
97
|
+
# Escape.shell_single_word("") returns #<Escape::ShellEscaped: ''>.
|
98
|
+
#
|
99
|
+
# Escape.shell_single_word("") #=> #<Escape::ShellEscaped: ''>
|
100
|
+
# Escape.shell_single_word("foo") #=> #<Escape::ShellEscaped: foo>
|
101
|
+
# Escape.shell_single_word("*") #=> #<Escape::ShellEscaped: '*'>
|
102
|
+
def shell_single_word(str)
|
103
|
+
return unless str
|
104
|
+
str &&= str.to_s # Delano fix
|
105
|
+
if str.empty?
|
106
|
+
ShellEscaped.new_no_dup("''")
|
107
|
+
elsif %r{\A[0-9A-Za-z+,./:=@_-]+\z} =~ str
|
108
|
+
ShellEscaped.new(str)
|
109
|
+
else
|
110
|
+
result = ''
|
111
|
+
str.scan(/('+)|[^']+/) {
|
112
|
+
if $1
|
113
|
+
result << %q{\'} * $1.length
|
114
|
+
else
|
115
|
+
result << "'#{$&}'"
|
116
|
+
end
|
117
|
+
}
|
118
|
+
ShellEscaped.new_no_dup(result)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class PercentEncoded < StringWrapper
|
123
|
+
end
|
124
|
+
|
125
|
+
# Escape.uri_segment escapes URI segment using percent-encoding.
|
126
|
+
# It returns an instance of PercentEncoded.
|
127
|
+
#
|
128
|
+
# Escape.uri_segment("a/b") #=> #<Escape::PercentEncoded: a%2Fb>
|
129
|
+
#
|
130
|
+
# The segment is "/"-splitted element after authority before query in URI, as follows.
|
131
|
+
#
|
132
|
+
# scheme://authority/segment1/segment2/.../segmentN?query#fragment
|
133
|
+
#
|
134
|
+
# See RFC 3986 for details of URI.
|
135
|
+
def uri_segment(str)
|
136
|
+
# pchar - pct-encoded = unreserved / sub-delims / ":" / "@"
|
137
|
+
# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
138
|
+
# sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
|
139
|
+
s = str.gsub(%r{[^A-Za-z0-9\-._~!$&'()*+,;=:@]}n) {
|
140
|
+
'%' + $&.unpack("H2")[0].upcase
|
141
|
+
}
|
142
|
+
PercentEncoded.new_no_dup(s)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Escape.uri_path escapes URI path using percent-encoding.
|
146
|
+
# The given path should be a sequence of (non-escaped) segments separated by "/".
|
147
|
+
# The segments cannot contains "/".
|
148
|
+
# It returns an instance of PercentEncoded.
|
149
|
+
#
|
150
|
+
# Escape.uri_path("a/b/c") #=> #<Escape::PercentEncoded: a/b/c>
|
151
|
+
# Escape.uri_path("a?b/c?d/e?f") #=> #<Escape::PercentEncoded: a%3Fb/c%3Fd/e%3Ff>
|
152
|
+
#
|
153
|
+
# The path is the part after authority before query in URI, as follows.
|
154
|
+
#
|
155
|
+
# scheme://authority/path#fragment
|
156
|
+
#
|
157
|
+
# See RFC 3986 for details of URI.
|
158
|
+
#
|
159
|
+
# Note that this function is not appropriate to convert OS path to URI.
|
160
|
+
def uri_path(str)
|
161
|
+
s = str.gsub(%r{[^/]+}n) { uri_segment($&) }
|
162
|
+
PercentEncoded.new_no_dup(s)
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
def html_form_fast(pairs, sep='&')
|
167
|
+
s = pairs.map {|k, v|
|
168
|
+
# query-chars - pct-encoded - x-www-form-urlencoded-delimiters =
|
169
|
+
# unreserved / "!" / "$" / "'" / "(" / ")" / "*" / "," / ":" / "@" / "/" / "?"
|
170
|
+
# query-char - pct-encoded = unreserved / sub-delims / ":" / "@" / "/" / "?"
|
171
|
+
# query-char = pchar / "/" / "?" = unreserved / pct-encoded / sub-delims / ":" / "@" / "/" / "?"
|
172
|
+
# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
173
|
+
# sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
|
174
|
+
# x-www-form-urlencoded-delimiters = "&" / "+" / ";" / "="
|
175
|
+
k = k.gsub(%r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n) {
|
176
|
+
'%' + $&.unpack("H2")[0].upcase
|
177
|
+
}
|
178
|
+
v = v.gsub(%r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n) {
|
179
|
+
'%' + $&.unpack("H2")[0].upcase
|
180
|
+
}
|
181
|
+
"#{k}=#{v}"
|
182
|
+
}.join(sep)
|
183
|
+
PercentEncoded.new_no_dup(s)
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
# Escape.html_form composes HTML form key-value pairs as a x-www-form-urlencoded encoded string.
|
188
|
+
# It returns an instance of PercentEncoded.
|
189
|
+
#
|
190
|
+
# Escape.html_form takes an array of pair of strings or
|
191
|
+
# an hash from string to string.
|
192
|
+
#
|
193
|
+
# Escape.html_form([["a","b"], ["c","d"]]) #=> #<Escape::PercentEncoded: a=b&c=d>
|
194
|
+
# Escape.html_form({"a"=>"b", "c"=>"d"}) #=> #<Escape::PercentEncoded: a=b&c=d>
|
195
|
+
#
|
196
|
+
# In the array form, it is possible to use same key more than once.
|
197
|
+
# (It is required for a HTML form which contains
|
198
|
+
# checkboxes and select element with multiple attribute.)
|
199
|
+
#
|
200
|
+
# Escape.html_form([["k","1"], ["k","2"]]) #=> #<Escape::PercentEncoded: k=1&k=2>
|
201
|
+
#
|
202
|
+
# If the strings contains characters which must be escaped in x-www-form-urlencoded,
|
203
|
+
# they are escaped using %-encoding.
|
204
|
+
#
|
205
|
+
# Escape.html_form([["k=","&;="]]) #=> #<Escape::PercentEncoded: k%3D=%26%3B%3D>
|
206
|
+
#
|
207
|
+
# The separator can be specified by the optional second argument.
|
208
|
+
#
|
209
|
+
# Escape.html_form([["a","b"], ["c","d"]], ";") #=> #<Escape::PercentEncoded: a=b;c=d>
|
210
|
+
#
|
211
|
+
# See HTML 4.01 for details.
|
212
|
+
def html_form(pairs, sep='&')
|
213
|
+
r = ''
|
214
|
+
first = true
|
215
|
+
pairs.each {|k, v|
|
216
|
+
# query-chars - pct-encoded - x-www-form-urlencoded-delimiters =
|
217
|
+
# unreserved / "!" / "$" / "'" / "(" / ")" / "*" / "," / ":" / "@" / "/" / "?"
|
218
|
+
# query-char - pct-encoded = unreserved / sub-delims / ":" / "@" / "/" / "?"
|
219
|
+
# query-char = pchar / "/" / "?" = unreserved / pct-encoded / sub-delims / ":" / "@" / "/" / "?"
|
220
|
+
# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
221
|
+
# sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
|
222
|
+
# x-www-form-urlencoded-delimiters = "&" / "+" / ";" / "="
|
223
|
+
r << sep if !first
|
224
|
+
first = false
|
225
|
+
k.each_byte {|byte|
|
226
|
+
ch = byte.chr
|
227
|
+
if %r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n =~ ch
|
228
|
+
r << "%" << ch.unpack("H2")[0].upcase
|
229
|
+
else
|
230
|
+
r << ch
|
231
|
+
end
|
232
|
+
}
|
233
|
+
r << '='
|
234
|
+
v.each_byte {|byte|
|
235
|
+
ch = byte.chr
|
236
|
+
if %r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n =~ ch
|
237
|
+
r << "%" << ch.unpack("H2")[0].upcase
|
238
|
+
else
|
239
|
+
r << ch
|
240
|
+
end
|
241
|
+
}
|
242
|
+
}
|
243
|
+
PercentEncoded.new_no_dup(r)
|
244
|
+
end
|
245
|
+
|
246
|
+
class HTMLEscaped < StringWrapper
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
HTML_TEXT_ESCAPE_HASH = {
|
251
|
+
'&' => '&',
|
252
|
+
'<' => '<',
|
253
|
+
'>' => '>',
|
254
|
+
}
|
255
|
+
|
256
|
+
|
257
|
+
# Escape.html_text escapes a string appropriate for HTML text using character references.
|
258
|
+
# It returns an instance of HTMLEscaped.
|
259
|
+
#
|
260
|
+
# It escapes 3 characters:
|
261
|
+
# * '&' to '&'
|
262
|
+
# * '<' to '<'
|
263
|
+
# * '>' to '>'
|
264
|
+
#
|
265
|
+
# Escape.html_text("abc") #=> #<Escape::HTMLEscaped: abc>
|
266
|
+
# Escape.html_text("a & b < c > d") #=> #<Escape::HTMLEscaped: a & b < c > d>
|
267
|
+
#
|
268
|
+
# This function is not appropriate for escaping HTML element attribute
|
269
|
+
# because quotes are not escaped.
|
270
|
+
def html_text(str)
|
271
|
+
s = str.gsub(/[&<>]/) {|ch| HTML_TEXT_ESCAPE_HASH[ch] }
|
272
|
+
HTMLEscaped.new_no_dup(s)
|
273
|
+
end
|
274
|
+
|
275
|
+
|
276
|
+
HTML_ATTR_ESCAPE_HASH = {
|
277
|
+
'&' => '&',
|
278
|
+
'<' => '<',
|
279
|
+
'>' => '>',
|
280
|
+
'"' => '"',
|
281
|
+
}
|
282
|
+
|
283
|
+
|
284
|
+
class HTMLAttrValue < StringWrapper
|
285
|
+
end
|
286
|
+
|
287
|
+
# Escape.html_attr_value encodes a string as a double-quoted HTML attribute using character references.
|
288
|
+
# It returns an instance of HTMLAttrValue.
|
289
|
+
#
|
290
|
+
# Escape.html_attr_value("abc") #=> #<Escape::HTMLAttrValue: "abc">
|
291
|
+
# Escape.html_attr_value("a&b") #=> #<Escape::HTMLAttrValue: "a&b">
|
292
|
+
# Escape.html_attr_value("ab&<>\"c") #=> #<Escape::HTMLAttrValue: "ab&<>"c">
|
293
|
+
# Escape.html_attr_value("a'c") #=> #<Escape::HTMLAttrValue: "a'c">
|
294
|
+
#
|
295
|
+
# It escapes 4 characters:
|
296
|
+
# * '&' to '&'
|
297
|
+
# * '<' to '<'
|
298
|
+
# * '>' to '>'
|
299
|
+
# * '"' to '"'
|
300
|
+
#
|
301
|
+
def html_attr_value(str)
|
302
|
+
s = '"' + str.gsub(/[&<>"]/) {|ch| HTML_ATTR_ESCAPE_HASH[ch] } + '"'
|
303
|
+
HTMLAttrValue.new_no_dup(s)
|
304
|
+
end
|
305
|
+
end
|