solutious-rudy 0.4.0
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/CHANGES.txt +75 -0
- data/LICENSE.txt +19 -0
- data/README.rdoc +36 -0
- data/Rakefile +68 -0
- data/bin/rudy +175 -0
- data/bin/rudy-ec2 +108 -0
- data/lib/aws_sdb.rb +3 -0
- data/lib/aws_sdb/error.rb +42 -0
- data/lib/aws_sdb/service.rb +215 -0
- data/lib/console.rb +385 -0
- data/lib/rudy.rb +210 -0
- data/lib/rudy/aws.rb +68 -0
- data/lib/rudy/aws/ec2.rb +304 -0
- data/lib/rudy/aws/s3.rb +3 -0
- data/lib/rudy/aws/simpledb.rb +53 -0
- data/lib/rudy/command/addresses.rb +46 -0
- data/lib/rudy/command/backups.rb +175 -0
- data/lib/rudy/command/base.rb +839 -0
- data/lib/rudy/command/config.rb +77 -0
- data/lib/rudy/command/deploy.rb +12 -0
- data/lib/rudy/command/disks.rb +213 -0
- data/lib/rudy/command/environment.rb +74 -0
- data/lib/rudy/command/groups.rb +61 -0
- data/lib/rudy/command/images.rb +99 -0
- data/lib/rudy/command/instances.rb +85 -0
- data/lib/rudy/command/machines.rb +170 -0
- data/lib/rudy/command/metadata.rb +41 -0
- data/lib/rudy/command/release.rb +174 -0
- data/lib/rudy/command/volumes.rb +66 -0
- data/lib/rudy/config.rb +93 -0
- data/lib/rudy/metadata.rb +26 -0
- data/lib/rudy/metadata/backup.rb +160 -0
- data/lib/rudy/metadata/disk.rb +138 -0
- data/lib/rudy/scm/svn.rb +68 -0
- data/lib/rudy/utils.rb +64 -0
- data/lib/storable.rb +280 -0
- data/lib/tryouts.rb +40 -0
- data/rudy.gemspec +76 -0
- data/support/mailtest +40 -0
- data/support/rudy-ec2-startup +200 -0
- data/tryouts/console_tryout.rb +91 -0
- metadata +188 -0
data/lib/aws_sdb.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module AwsSdb
|
2
|
+
|
3
|
+
class Error < RuntimeError ; end
|
4
|
+
|
5
|
+
class RequestError < Error
|
6
|
+
attr_reader :request_id
|
7
|
+
|
8
|
+
def initialize(message, request_id=nil)
|
9
|
+
super(message)
|
10
|
+
@request_id = request_id
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class InvalidDomainNameError < RequestError ; end
|
15
|
+
class InvalidParameterValueError < RequestError ; end
|
16
|
+
class InvalidNextTokenError < RequestError ; end
|
17
|
+
class InvalidNumberPredicatesError < RequestError ; end
|
18
|
+
class InvalidNumberValueTestsError < RequestError ; end
|
19
|
+
class InvalidQueryExpressionError < RequestError ; end
|
20
|
+
class MissingParameterError < RequestError ; end
|
21
|
+
class NoSuchDomainError < RequestError ; end
|
22
|
+
class NumberDomainsExceededError < RequestError ; end
|
23
|
+
class NumberDomainAttributesExceededError < RequestError ; end
|
24
|
+
class NumberDomainBytesExceededError < RequestError ; end
|
25
|
+
class NumberItemAttributesExceededError < RequestError ; end
|
26
|
+
class RequestTimeoutError < RequestError ; end
|
27
|
+
|
28
|
+
class FeatureDeprecatedError < RequestError ; end
|
29
|
+
|
30
|
+
class ConnectionError < Error
|
31
|
+
attr_reader :response
|
32
|
+
|
33
|
+
def initialize(response)
|
34
|
+
super(
|
35
|
+
"#{response.code} \
|
36
|
+
#{response.message if response.respond_to?(:message)}"
|
37
|
+
)
|
38
|
+
@response = response
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'time'
|
3
|
+
require 'cgi'
|
4
|
+
require 'uri'
|
5
|
+
require 'net/http'
|
6
|
+
require 'base64'
|
7
|
+
require 'openssl'
|
8
|
+
require 'rexml/document'
|
9
|
+
require 'rexml/xpath'
|
10
|
+
|
11
|
+
module AwsSdb
|
12
|
+
|
13
|
+
class Service
|
14
|
+
def initialize(options={})
|
15
|
+
@access_key_id = options[:access_key_id] || ENV['AMAZON_ACCESS_KEY_ID']
|
16
|
+
@secret_access_key = options[:secret_access_key] || ENV['AMAZON_SECRET_ACCESS_KEY']
|
17
|
+
@base_url = options[:url] || 'http://sdb.amazonaws.com'
|
18
|
+
@logger = options[:logger] || Logger.new("aws_sdb.log")
|
19
|
+
end
|
20
|
+
|
21
|
+
def list_domains(max = nil, token = nil)
|
22
|
+
params = { 'Action' => 'ListDomains' }
|
23
|
+
params['NextToken'] =
|
24
|
+
token unless token.nil? || token.empty?
|
25
|
+
params['MaxNumberOfDomains'] =
|
26
|
+
max.to_s unless max.nil? || max.to_i == 0
|
27
|
+
doc = call(:get, params)
|
28
|
+
results = []
|
29
|
+
REXML::XPath.each(doc, '//DomainName/text()') do |domain|
|
30
|
+
results << domain.to_s
|
31
|
+
end
|
32
|
+
return results, REXML::XPath.first(doc, '//NextToken/text()').to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_domain(domain)
|
36
|
+
call(:post, { 'Action' => 'CreateDomain', 'DomainName'=> domain.to_s })
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete_domain(domain)
|
41
|
+
call(
|
42
|
+
:delete,
|
43
|
+
{ 'Action' => 'DeleteDomain', 'DomainName' => domain.to_s }
|
44
|
+
)
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
# <QueryWithAttributesResult><Item><Name>in-c2ffrw</Name><Attribute><Name>code</Name><Value>in-c2ffrw</Value></Attribute><Attribute><Name>date_created</Name><Value>2008-10-31</Value></Attribute></Item><Item>
|
48
|
+
def query_with_attributes(domain, query, max = nil, token = nil)
|
49
|
+
params = {
|
50
|
+
'Action' => 'QueryWithAttributes',
|
51
|
+
'QueryExpression' => query,
|
52
|
+
'DomainName' => domain.to_s
|
53
|
+
}
|
54
|
+
params['NextToken'] =
|
55
|
+
token unless token.nil? || token.empty?
|
56
|
+
params['MaxNumberOfItems'] =
|
57
|
+
max.to_s unless max.nil? || max.to_i == 0
|
58
|
+
|
59
|
+
doc = call(:get, params)
|
60
|
+
results = []
|
61
|
+
REXML::XPath.each(doc, "//Item") do |item|
|
62
|
+
name = REXML::XPath.first(item, './Name/text()').to_s
|
63
|
+
|
64
|
+
|
65
|
+
attributes = {'Name' => name}
|
66
|
+
REXML::XPath.each(item, "./Attribute") do |attr|
|
67
|
+
key = REXML::XPath.first(attr, './Name/text()').to_s
|
68
|
+
value = REXML::XPath.first(attr, './Value/text()').to_s
|
69
|
+
( attributes[key] ||= [] ) << value
|
70
|
+
end
|
71
|
+
results << attributes
|
72
|
+
end
|
73
|
+
return results, REXML::XPath.first(doc, '//NextToken/text()').to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
# <QueryResult><ItemName>in-c2ffrw</ItemName><ItemName>in-72yagt</ItemName><ItemName>in-52j8gj</ItemName>
|
77
|
+
def query(domain, query, max = nil, token = nil)
|
78
|
+
params = {
|
79
|
+
'Action' => 'Query',
|
80
|
+
'QueryExpression' => query,
|
81
|
+
'DomainName' => domain.to_s
|
82
|
+
}
|
83
|
+
params['NextToken'] =
|
84
|
+
token unless token.nil? || token.empty?
|
85
|
+
params['MaxNumberOfItems'] =
|
86
|
+
max.to_s unless max.nil? || max.to_i == 0
|
87
|
+
|
88
|
+
|
89
|
+
doc = call(:get, params)
|
90
|
+
results = []
|
91
|
+
REXML::XPath.each(doc, '//ItemName/text()') do |item|
|
92
|
+
results << item.to_s
|
93
|
+
end
|
94
|
+
return results, REXML::XPath.first(doc, '//NextToken/text()').to_s
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
def put_attributes(domain, item, attributes, replace = true)
|
99
|
+
params = {
|
100
|
+
'Action' => 'PutAttributes',
|
101
|
+
'DomainName' => domain.to_s,
|
102
|
+
'ItemName' => item.to_s
|
103
|
+
}
|
104
|
+
count = 0
|
105
|
+
attributes.each do | key, values |
|
106
|
+
([]<<values).flatten.each do |value|
|
107
|
+
params["Attribute.#{count}.Name"] = key.to_s
|
108
|
+
params["Attribute.#{count}.Value"] = value.to_s
|
109
|
+
params["Attribute.#{count}.Replace"] = replace
|
110
|
+
count += 1
|
111
|
+
end
|
112
|
+
end
|
113
|
+
call(:put, params)
|
114
|
+
nil
|
115
|
+
end
|
116
|
+
|
117
|
+
def get_attributes(domain, item)
|
118
|
+
doc = call(
|
119
|
+
:get,
|
120
|
+
{
|
121
|
+
'Action' => 'GetAttributes',
|
122
|
+
'DomainName' => domain.to_s,
|
123
|
+
'ItemName' => item.to_s
|
124
|
+
}
|
125
|
+
)
|
126
|
+
attributes = {}
|
127
|
+
REXML::XPath.each(doc, "//Attribute") do |attr|
|
128
|
+
key = REXML::XPath.first(attr, './Name/text()').to_s
|
129
|
+
value = REXML::XPath.first(attr, './Value/text()').to_s
|
130
|
+
( attributes[key] ||= [] ) << value
|
131
|
+
end
|
132
|
+
attributes
|
133
|
+
end
|
134
|
+
|
135
|
+
def delete_attributes(domain, item)
|
136
|
+
call(
|
137
|
+
:delete,
|
138
|
+
{
|
139
|
+
'Action' => 'DeleteAttributes',
|
140
|
+
'DomainName' => domain.to_s,
|
141
|
+
'ItemName' => item.to_s
|
142
|
+
}
|
143
|
+
)
|
144
|
+
nil
|
145
|
+
end
|
146
|
+
|
147
|
+
def select(select, token = nil)
|
148
|
+
params = {
|
149
|
+
'Action' => 'Select',
|
150
|
+
'SelectExpression' => select,
|
151
|
+
}
|
152
|
+
params['NextToken'] =
|
153
|
+
token unless token.nil? || token.empty?
|
154
|
+
|
155
|
+
doc = call(:get, params)
|
156
|
+
results = []
|
157
|
+
REXML::XPath.each(doc, "//Item") do |item|
|
158
|
+
name = REXML::XPath.first(item, './Name/text()').to_s
|
159
|
+
|
160
|
+
attributes = {'Name' => name}
|
161
|
+
REXML::XPath.each(item, "./Attribute") do |attr|
|
162
|
+
key = REXML::XPath.first(attr, './Name/text()').to_s
|
163
|
+
value = REXML::XPath.first(attr, './Value/text()').to_s
|
164
|
+
( attributes[key] ||= [] ) << value
|
165
|
+
end
|
166
|
+
results << attributes
|
167
|
+
end
|
168
|
+
return results, REXML::XPath.first(doc, '//NextToken/text()').to_s
|
169
|
+
end
|
170
|
+
|
171
|
+
protected
|
172
|
+
|
173
|
+
def call(method, params)
|
174
|
+
params.merge!( {
|
175
|
+
'Version' => '2007-11-07',
|
176
|
+
'SignatureVersion' => '1',
|
177
|
+
'AWSAccessKeyId' => @access_key_id,
|
178
|
+
'Timestamp' => Time.now.gmtime.iso8601
|
179
|
+
}
|
180
|
+
)
|
181
|
+
data = ''
|
182
|
+
query = []
|
183
|
+
params.keys.sort_by { |k| k.upcase }.each do |key|
|
184
|
+
data << "#{key}#{params[key].to_s}"
|
185
|
+
query << "#{key}=#{CGI::escape(params[key].to_s)}"
|
186
|
+
end
|
187
|
+
digest = OpenSSL::Digest::Digest.new('sha1')
|
188
|
+
hmac = OpenSSL::HMAC.digest(digest, @secret_access_key, data)
|
189
|
+
signature = Base64.encode64(hmac).strip
|
190
|
+
query << "Signature=#{CGI::escape(signature)}"
|
191
|
+
query = query.join('&')
|
192
|
+
url = "#{@base_url}?#{query}"
|
193
|
+
uri = URI.parse(url)
|
194
|
+
@logger.debug("#{url}") if @logger
|
195
|
+
response =
|
196
|
+
Net::HTTP.new(uri.host, uri.port).send_request(method, uri.request_uri)
|
197
|
+
@logger.debug("#{response.code}\n#{response.body}") if @logger
|
198
|
+
raise(ConnectionError.new(response)) unless (200..400).include?(
|
199
|
+
response.code.to_i
|
200
|
+
)
|
201
|
+
doc = REXML::Document.new(response.body)
|
202
|
+
error = doc.get_elements('*/Errors/Error')[0]
|
203
|
+
raise(
|
204
|
+
Module.class_eval(
|
205
|
+
"AwsSdb::#{error.get_elements('Code')[0].text}Error"
|
206
|
+
).new(
|
207
|
+
error.get_elements('Message')[0].text,
|
208
|
+
doc.get_elements('*/RequestID')[0].text
|
209
|
+
)
|
210
|
+
) unless error.nil?
|
211
|
+
doc
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
data/lib/console.rb
ADDED
@@ -0,0 +1,385 @@
|
|
1
|
+
#---
|
2
|
+
# Adapted from: http://github.com/oneup/ruby-console/tree/tput
|
3
|
+
# See: http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
4
|
+
# See: man terminfo
|
5
|
+
#+++
|
6
|
+
|
7
|
+
|
8
|
+
class String
|
9
|
+
|
10
|
+
# +col+, +bgcol+, and +attribute+ are symbols corresponding
|
11
|
+
# to Console::COLOURS, Console::BGCOLOURS, and Console::ATTRIBUTES.
|
12
|
+
# Returns the string in the format attributes + string + defaults.
|
13
|
+
#
|
14
|
+
# "MONKEY_JUNK".colour(:blue, :white, :blink) # => "\e[34;47;5mMONKEY_JUNK\e[39;49;0m"
|
15
|
+
#
|
16
|
+
def colour(col, bgcol = nil, attribute = nil)
|
17
|
+
Console.style(col, bgcol, attribute) +
|
18
|
+
self +
|
19
|
+
Console.style(:default, :default, :default)
|
20
|
+
end
|
21
|
+
alias :color :colour
|
22
|
+
|
23
|
+
# See colour
|
24
|
+
def bgcolour(bgcol = :default)
|
25
|
+
Console.style(nil, bgcol, nil) +
|
26
|
+
self +
|
27
|
+
Console.style(nil, :default, nil)
|
28
|
+
end
|
29
|
+
alias :bgcolor :bgcolour
|
30
|
+
|
31
|
+
# See colour
|
32
|
+
def att(a = :default)
|
33
|
+
Console.style(nil, nil, a) +
|
34
|
+
self +
|
35
|
+
Console.style(nil, nil, :default)
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
# Print the string at +x+ +y+. When +minus+ is any true value
|
40
|
+
# the length of the string is subtracted from the value of x
|
41
|
+
# before printing.
|
42
|
+
def print_at(x=nil, y=nil, minus=false)
|
43
|
+
args = {:minus=>minus}
|
44
|
+
args[:x] &&= x
|
45
|
+
args[:y] &&= y
|
46
|
+
Console.print_at(self, args)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns the string with escape code attributes removed.
|
50
|
+
# NOTE: The non-printable attributes count towards the string size.
|
51
|
+
# You can use this method to get the "visible" size:
|
52
|
+
#
|
53
|
+
# "\e[34;47;5mMONKEY_JUNK\e[39;49;0m".noatt.size # => 11
|
54
|
+
# "\e[34;47;5mMONKEY_JUNK\e[39;49;0m".size # => 31
|
55
|
+
#
|
56
|
+
def noatt
|
57
|
+
gsub(/\e\[[\d;]*m/, '')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Object
|
62
|
+
|
63
|
+
# Executes tput +capnam+ with +args+. Returns true if tcap gives
|
64
|
+
# 0 exit status and false otherwise.
|
65
|
+
#
|
66
|
+
# tput :cup, 1, 4
|
67
|
+
# $ tput cup 1 4
|
68
|
+
#
|
69
|
+
def tput(capnam, *args)
|
70
|
+
system("tput #{capnam} #{args.flatten.join(' ')}")
|
71
|
+
end
|
72
|
+
|
73
|
+
# Executes tput +capnam+ with +args+. Returns the output of tput.
|
74
|
+
#
|
75
|
+
# tput_val :cols # => 16
|
76
|
+
# $ tput cols # => 16
|
77
|
+
#
|
78
|
+
def tput_val(capnam, *args)
|
79
|
+
`tput #{capnam} #{args.flatten.join(' ')}`.chomp
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
module Console
|
86
|
+
extend self
|
87
|
+
require 'timeout'
|
88
|
+
require 'thread'
|
89
|
+
|
90
|
+
# ANSI escape sequence numbers for text attributes
|
91
|
+
ATTRIBUTES = {
|
92
|
+
:normal => 0,
|
93
|
+
:bright => 1,
|
94
|
+
:dim => 2,
|
95
|
+
:underline => 4,
|
96
|
+
:blink => 5,
|
97
|
+
:reverse => 7,
|
98
|
+
:hidden => 8,
|
99
|
+
:default => 0,
|
100
|
+
}.freeze unless defined? ATTRIBUTES
|
101
|
+
|
102
|
+
# ANSI escape sequence numbers for text colours
|
103
|
+
COLOURS = {
|
104
|
+
:black => 30,
|
105
|
+
:red => 31,
|
106
|
+
:green => 32,
|
107
|
+
:yellow => 33,
|
108
|
+
:blue => 34,
|
109
|
+
:magenta => 35,
|
110
|
+
:cyan => 36,
|
111
|
+
:white => 37,
|
112
|
+
:default => 39,
|
113
|
+
:random => 30 + rand(10).to_i
|
114
|
+
}.freeze unless defined? COLOURS
|
115
|
+
|
116
|
+
# ANSI escape sequence numbers for background colours
|
117
|
+
BGCOLOURS = {
|
118
|
+
:black => 40,
|
119
|
+
:red => 41,
|
120
|
+
:green => 42,
|
121
|
+
:yellow => 43,
|
122
|
+
:blue => 44,
|
123
|
+
:magenta => 45,
|
124
|
+
:cyan => 46,
|
125
|
+
:white => 47,
|
126
|
+
:default => 49,
|
127
|
+
:random => 40 + rand(10).to_i
|
128
|
+
}.freeze unless defined? BGCOLOURS
|
129
|
+
|
130
|
+
|
131
|
+
def print_left(str, props={})
|
132
|
+
props[:x] ||= 0
|
133
|
+
props[:y] ||= Cursor.y
|
134
|
+
# print_at("x:#{props[:x]} y:#{props[:y]}", {:x => 0, :y => 10})
|
135
|
+
print_at(str, props)
|
136
|
+
end
|
137
|
+
def print_right(str, props={})
|
138
|
+
props[:x] ||= width
|
139
|
+
props[:y] ||= Cursor.y
|
140
|
+
props[:minus] = true unless props.has_key?(:minus)
|
141
|
+
print_at(str, props)
|
142
|
+
end
|
143
|
+
def print_spaced(*args)
|
144
|
+
props = (args.last.is_a? Hash) ? args.pop : {}
|
145
|
+
props[:y] = Cursor.y
|
146
|
+
chunk_width = (width / args.flatten.size).to_i
|
147
|
+
chunk_at = 0
|
148
|
+
args.each do |chunk|
|
149
|
+
props[:x] = chunk_at
|
150
|
+
print_at(chunk.to_s[0, chunk_width], props)
|
151
|
+
chunk_at += chunk_width
|
152
|
+
end
|
153
|
+
puts
|
154
|
+
end
|
155
|
+
def print_center(str, props={})
|
156
|
+
props[:x] = ((width - str.noatt.length) / 2).to_i-1
|
157
|
+
props[:y] ||= height
|
158
|
+
print_at(str, props)
|
159
|
+
end
|
160
|
+
def print_at(str, props={})
|
161
|
+
print_at_lamb = lambda {
|
162
|
+
#props[:x] ||= 0
|
163
|
+
#props[:y] ||= 0
|
164
|
+
props[:minus] = false unless props.has_key?(:minus)
|
165
|
+
props[:x] = props[:x]-str.noatt.size if props[:x] && props[:minus] # Subtract the str length from the position
|
166
|
+
Cursor.save
|
167
|
+
Cursor.move = 0
|
168
|
+
print str
|
169
|
+
Cursor.restore
|
170
|
+
}
|
171
|
+
RUBY_VERSION =~ /1.9/ ? Thread.exclusive(&print_at_lamb) : print_at_lamb.call
|
172
|
+
end
|
173
|
+
|
174
|
+
def self.style(col, bgcol=nil, att=nil)
|
175
|
+
valdor = []
|
176
|
+
valdor << COLOURS[col] if COLOURS.has_key?(col)
|
177
|
+
valdor << BGCOLOURS[bgcol] if BGCOLOURS.has_key?(bgcol)
|
178
|
+
valdor << ATTRIBUTES[att] if ATTRIBUTES.has_key?(att)
|
179
|
+
"\e[#{valdor.join(";")}m" # => \e[8;34;42m
|
180
|
+
end
|
181
|
+
|
182
|
+
def self.clear
|
183
|
+
tput :clear
|
184
|
+
end
|
185
|
+
|
186
|
+
def reset
|
187
|
+
tput :reset
|
188
|
+
end
|
189
|
+
|
190
|
+
def width
|
191
|
+
tput_val(:cols).to_i
|
192
|
+
end
|
193
|
+
|
194
|
+
def height
|
195
|
+
tput_val(:lines).to_i
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
module Cursor
|
200
|
+
extend self
|
201
|
+
|
202
|
+
# Returns [x,y] for the current cursor position.
|
203
|
+
def position
|
204
|
+
yx = [0,0]
|
205
|
+
|
206
|
+
position_lamb = lambda {
|
207
|
+
begin
|
208
|
+
# NOTE: Can we get cursor position from tput?
|
209
|
+
termsettings = `stty -g`
|
210
|
+
|
211
|
+
# DEBUGGING: The following code works in Ruby 1.9 but not 1.8.
|
212
|
+
|
213
|
+
system("stty raw -echo")
|
214
|
+
print "\e[6n" # Forces output of: \e[49;1R (\e is not printable)
|
215
|
+
c = ''
|
216
|
+
(pos ||= '') << c while (c = STDIN.getc) != 'R'# NOTE: There must be a better way!
|
217
|
+
yx = pos.scan(/(\d+);(\d+)/).flatten
|
218
|
+
yx[0] = yx[0].to_i - 1 # It returns 1 for the first column, but we want 0
|
219
|
+
yx[1] = yx[1].to_i - 1
|
220
|
+
ensure
|
221
|
+
system("stty #{termsettings}") # Get out of raw mode
|
222
|
+
end
|
223
|
+
}
|
224
|
+
|
225
|
+
RUBY_VERSION =~ /1.9/ ? Thread.exclusive(&position_lamb) : position_lamb.call
|
226
|
+
yx.reverse
|
227
|
+
end
|
228
|
+
|
229
|
+
def x; position[0]; end
|
230
|
+
def y; position[1]; end
|
231
|
+
|
232
|
+
def move=(*args)
|
233
|
+
x,y = *args.flatten
|
234
|
+
tput(:cup, y, x) # "tput cup" takes y before x
|
235
|
+
end
|
236
|
+
|
237
|
+
def up(n=1)
|
238
|
+
tput :cuu, n
|
239
|
+
end
|
240
|
+
|
241
|
+
def down(n=1)
|
242
|
+
tput :cud, n
|
243
|
+
end
|
244
|
+
|
245
|
+
def right(x=1)
|
246
|
+
tput :cuf, x
|
247
|
+
end
|
248
|
+
|
249
|
+
def left(x=1)
|
250
|
+
tput :cub, x
|
251
|
+
end
|
252
|
+
|
253
|
+
def line(n=1)
|
254
|
+
tput :il, n
|
255
|
+
end
|
256
|
+
|
257
|
+
def save
|
258
|
+
tput :sc
|
259
|
+
end
|
260
|
+
|
261
|
+
def restore
|
262
|
+
tput :rc
|
263
|
+
end
|
264
|
+
|
265
|
+
def clear_line
|
266
|
+
tput :el
|
267
|
+
end
|
268
|
+
|
269
|
+
# TODO: replace methods with this kinda thing
|
270
|
+
#@@capnames = {
|
271
|
+
# :restore => [:rc],
|
272
|
+
# :save => [:sc],
|
273
|
+
# :clear_line => [:el],
|
274
|
+
# :line => [:il, 1, 1],
|
275
|
+
#
|
276
|
+
# :up => [:cuu, 1, 1],
|
277
|
+
# :down => [:cud, 1, 1],
|
278
|
+
# :right => [:cuf, 1, 1],
|
279
|
+
# :left => [:cub, 1, 1],
|
280
|
+
#
|
281
|
+
# :move => [:cup, 2, 0, 0]
|
282
|
+
#}
|
283
|
+
#
|
284
|
+
#@@capnames.each_pair do |meth, cap|
|
285
|
+
# module_eval <<-RUBY
|
286
|
+
# def #{meth}(*args)
|
287
|
+
# tput '#{cap[0]}'
|
288
|
+
# end
|
289
|
+
# RUBY
|
290
|
+
#end
|
291
|
+
|
292
|
+
end
|
293
|
+
|
294
|
+
class Window
|
295
|
+
attr_accessor :row, :col, :width, :height, :text, :fg, :bg
|
296
|
+
attr_reader :threads
|
297
|
+
|
298
|
+
def initialize(*args)
|
299
|
+
@row = 1
|
300
|
+
@col = 1
|
301
|
+
@width = 10
|
302
|
+
@height = 5
|
303
|
+
@text = ""
|
304
|
+
@fg = :default
|
305
|
+
@bg = :default
|
306
|
+
@threads = []
|
307
|
+
end
|
308
|
+
|
309
|
+
def position=(x,y=nil)
|
310
|
+
@x = x
|
311
|
+
@y = y if y
|
312
|
+
end
|
313
|
+
|
314
|
+
def position
|
315
|
+
[@row, @col]
|
316
|
+
end
|
317
|
+
|
318
|
+
def self.bar(len, unit='=')
|
319
|
+
unit*len
|
320
|
+
end
|
321
|
+
|
322
|
+
|
323
|
+
# Execute the given block every +n+ seconds in a separate thread.
|
324
|
+
# The lower limit for +n+ is 1 second.
|
325
|
+
# Returns a Thread object.
|
326
|
+
def every_n_seconds(n)
|
327
|
+
#n = 1 if n < 1
|
328
|
+
thread = Thread.new do
|
329
|
+
|
330
|
+
begin
|
331
|
+
while true
|
332
|
+
before = Time.now
|
333
|
+
yield
|
334
|
+
interval = n - (Time.now - before)
|
335
|
+
sleep(interval) if interval > 0
|
336
|
+
end
|
337
|
+
rescue Interrupt
|
338
|
+
break
|
339
|
+
ensure
|
340
|
+
thread
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
# Print text to the screen via +type+ every +refresh+ seconds.
|
346
|
+
# Print the return value of the block to the screen using the
|
347
|
+
# print_+type+ method. +refresh+ is number of seconds to wait
|
348
|
+
# +props+ is the hash sent to print_+type+.
|
349
|
+
# Returns a Thread object.
|
350
|
+
#
|
351
|
+
# # Print the time in the upper right corner every second
|
352
|
+
# thread1 = Console.static(:right, 1, {:y => 0}) do
|
353
|
+
# Time.now.utc.strftime("%Y-%m-%d %H:%M:%S").colour(:blue, :white, :underline)
|
354
|
+
# end
|
355
|
+
#
|
356
|
+
def static(type, refresh=2, props={}, &b)
|
357
|
+
meth = "print_#{type}"
|
358
|
+
raise "#{meth} is not supported" unless Console.respond_to?(meth)
|
359
|
+
|
360
|
+
refresh ||= 0
|
361
|
+
refreh = refresh.to_s.to_i
|
362
|
+
|
363
|
+
thread = every_n_seconds(refresh) do
|
364
|
+
Console.send(meth, b.call, props.clone)
|
365
|
+
end
|
366
|
+
|
367
|
+
@threads << thread
|
368
|
+
|
369
|
+
thread
|
370
|
+
end
|
371
|
+
|
372
|
+
def join_threads
|
373
|
+
begin
|
374
|
+
@threads.each do |t|
|
375
|
+
t.join
|
376
|
+
end
|
377
|
+
rescue Interrupt
|
378
|
+
ensure
|
379
|
+
@threads.each do |t|
|
380
|
+
t.kill
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
end
|