epitools 0.1.11 → 0.2.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/VERSION +1 -1
- data/epitools.gemspec +17 -8
- data/lib/epitools/browser.rb +213 -0
- data/lib/epitools/browser/browser_cache.rb +164 -0
- data/lib/epitools/browser/mechanize_progressbar.rb +53 -0
- data/lib/epitools/progressbar.rb +269 -0
- data/lib/epitools/sys.rb +398 -0
- data/spec/basetypes_spec.rb +4 -4
- data/spec/browser_spec.rb +14 -0
- data/spec/sys_spec.rb +64 -0
- metadata +19 -10
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.2.0
|
data/epitools.gemspec
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
Gem::Specification.new do |s|
|
|
7
7
|
s.name = %q{epitools}
|
|
8
|
-
s.version = "0.
|
|
8
|
+
s.version = "0.2.0"
|
|
9
9
|
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
11
11
|
s.authors = ["epitron"]
|
|
12
|
-
s.date = %q{2010-09-
|
|
12
|
+
s.date = %q{2010-09-28}
|
|
13
13
|
s.description = %q{Miscellaneous utility libraries to make my life easier.}
|
|
14
14
|
s.email = %q{chris@ill-logic.com}
|
|
15
15
|
s.extra_rdoc_files = [
|
|
@@ -26,6 +26,9 @@ Gem::Specification.new do |s|
|
|
|
26
26
|
"epitools.gemspec",
|
|
27
27
|
"lib/epitools.rb",
|
|
28
28
|
"lib/epitools/basetypes.rb",
|
|
29
|
+
"lib/epitools/browser.rb",
|
|
30
|
+
"lib/epitools/browser/browser_cache.rb",
|
|
31
|
+
"lib/epitools/browser/mechanize_progressbar.rb",
|
|
29
32
|
"lib/epitools/hexdump.rb",
|
|
30
33
|
"lib/epitools/http.rb",
|
|
31
34
|
"lib/epitools/lcs.rb",
|
|
@@ -33,12 +36,15 @@ Gem::Specification.new do |s|
|
|
|
33
36
|
"lib/epitools/niceprint.rb",
|
|
34
37
|
"lib/epitools/permutations.rb",
|
|
35
38
|
"lib/epitools/pretty_backtrace.rb",
|
|
39
|
+
"lib/epitools/progressbar.rb",
|
|
36
40
|
"lib/epitools/rails.rb",
|
|
37
41
|
"lib/epitools/rash.rb",
|
|
38
42
|
"lib/epitools/ratio.rb",
|
|
39
43
|
"lib/epitools/string_to_proc.rb",
|
|
44
|
+
"lib/epitools/sys.rb",
|
|
40
45
|
"lib/epitools/zopen.rb",
|
|
41
46
|
"spec/basetypes_spec.rb",
|
|
47
|
+
"spec/browser_spec.rb",
|
|
42
48
|
"spec/lcs_spec.rb",
|
|
43
49
|
"spec/metaclass_spec.rb",
|
|
44
50
|
"spec/permutations_spec.rb",
|
|
@@ -46,6 +52,7 @@ Gem::Specification.new do |s|
|
|
|
46
52
|
"spec/ratio_spec.rb",
|
|
47
53
|
"spec/spec.opts",
|
|
48
54
|
"spec/spec_helper.rb",
|
|
55
|
+
"spec/sys_spec.rb",
|
|
49
56
|
"spec/zopen_spec.rb"
|
|
50
57
|
]
|
|
51
58
|
s.homepage = %q{http://github.com/epitron/epitools}
|
|
@@ -54,14 +61,16 @@ Gem::Specification.new do |s|
|
|
|
54
61
|
s.rubygems_version = %q{1.3.7}
|
|
55
62
|
s.summary = %q{NOT UTILS... METILS!}
|
|
56
63
|
s.test_files = [
|
|
57
|
-
"spec/
|
|
58
|
-
"spec/
|
|
59
|
-
"spec/
|
|
60
|
-
"spec/
|
|
64
|
+
"spec/sys_spec.rb",
|
|
65
|
+
"spec/permutations_spec.rb",
|
|
66
|
+
"spec/rash_spec.rb",
|
|
67
|
+
"spec/spec_helper.rb",
|
|
68
|
+
"spec/browser_spec.rb",
|
|
61
69
|
"spec/lcs_spec.rb",
|
|
70
|
+
"spec/basetypes_spec.rb",
|
|
71
|
+
"spec/ratio_spec.rb",
|
|
62
72
|
"spec/zopen_spec.rb",
|
|
63
|
-
"spec/
|
|
64
|
-
"spec/spec_helper.rb"
|
|
73
|
+
"spec/metaclass_spec.rb"
|
|
65
74
|
]
|
|
66
75
|
|
|
67
76
|
if s.respond_to? :specification_version then
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
require 'mechanize'
|
|
2
|
+
require 'uri'
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
require 'epitools/browser/mechanize_progressbar'
|
|
6
|
+
|
|
7
|
+
# TODO: Make socksify optional (eg: if proxy is specified)
|
|
8
|
+
#require 'socksify'
|
|
9
|
+
|
|
10
|
+
# TODO: Put options here.
|
|
11
|
+
=begin
|
|
12
|
+
class BrowserOptions < OpenStruct
|
|
13
|
+
|
|
14
|
+
DEFAULTS = {
|
|
15
|
+
:delay => 1,
|
|
16
|
+
:delay_jitter => 0.2,
|
|
17
|
+
:use_cache => true,
|
|
18
|
+
:use_logs => false,
|
|
19
|
+
:cookie_file => "cookies.txt"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def initialize(extra_opts)
|
|
23
|
+
|
|
24
|
+
@opts = DEFAULTS.dup
|
|
25
|
+
|
|
26
|
+
for key, val in opts
|
|
27
|
+
if key.in? DEFAULTS
|
|
28
|
+
@opts[key] = val
|
|
29
|
+
else
|
|
30
|
+
raise "Unknown option: #{key}"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
=end
|
|
37
|
+
|
|
38
|
+
#
|
|
39
|
+
# A mechanize class that emulates a web-browser, with cache and everything.
|
|
40
|
+
# Progress bars are enabled by default.
|
|
41
|
+
#
|
|
42
|
+
class Browser
|
|
43
|
+
|
|
44
|
+
attr_accessor :agent, :cache, :use_cache, :delay, :delay_jitter
|
|
45
|
+
|
|
46
|
+
#
|
|
47
|
+
# Default options:
|
|
48
|
+
# :delay => 1, # Sleep 1 second between gets
|
|
49
|
+
# :delay_jitter => 0.2, # Random deviation from delay
|
|
50
|
+
# :use_cache => true, # Cache all gets
|
|
51
|
+
# :use_logs => false, # Don't log the detailed transfer info
|
|
52
|
+
# :cookie_file => "cookies.txt" # Save cookies to file
|
|
53
|
+
#
|
|
54
|
+
def initialize(options={})
|
|
55
|
+
@last_get = Time.at(0)
|
|
56
|
+
@delay = options[:delay] || 1
|
|
57
|
+
@delay_jitter = options[:delay_jitter] || 0.2
|
|
58
|
+
@use_cache = options[:cache] || true
|
|
59
|
+
@use_logs = options[:logs] || false
|
|
60
|
+
@cookie_file = options[:cookiefile] || "cookies.txt"
|
|
61
|
+
|
|
62
|
+
# TODO: @progress, @user_agent, @logfile, @cache_file (default location: ~/.epitools?)
|
|
63
|
+
|
|
64
|
+
if options[:proxy]
|
|
65
|
+
host, port = options[:proxy].split(':')
|
|
66
|
+
TCPSocket::socks_server = host
|
|
67
|
+
TCPSocket::socks_port = port.to_i
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
init_agent!
|
|
71
|
+
init_cache!
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def init_agent!
|
|
76
|
+
@agent = Mechanize.new do |a|
|
|
77
|
+
# ["Mechanize", "Mac Mozilla", "Linux Mozilla", "Windows IE 6", "iPhone", "Linux Konqueror", "Windows IE 7", "Mac FireFox", "Mac Safari", "Windows Mozilla"]
|
|
78
|
+
a.max_history = 10
|
|
79
|
+
a.user_agent_alias = "Windows IE 7"
|
|
80
|
+
a.log = Logger.new "mechanize.log" if @use_logs
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
load_cookies!
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def delay(override_delay=nil, override_jitter=nil)
|
|
88
|
+
elapsed = Time.now - @last_get
|
|
89
|
+
jitter = rand * (override_jitter || @delay_jitter)
|
|
90
|
+
amount = ( (override_delay || @delay) + jitter ) - elapsed
|
|
91
|
+
|
|
92
|
+
if amount > 0
|
|
93
|
+
puts " |_ sleeping for %0.3f seconds..." % amount
|
|
94
|
+
sleep amount
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def init_cache!
|
|
100
|
+
# TODO: Rescue "couldn't load" exception and disable caching
|
|
101
|
+
require 'epitools/browser/browser_cache'
|
|
102
|
+
@cache = CacheDB.new(agent) if @use_cache
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def relative?(url)
|
|
107
|
+
not url =~ %r{^https?://}
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def cache_put(page, url)
|
|
112
|
+
if page.is_a? Mechanize::Page and page.content_type =~ %r{^text/}
|
|
113
|
+
puts " |_ writing to cache"
|
|
114
|
+
cache.put(page, url, :overwrite=>true)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
#
|
|
119
|
+
# Retrieve an URL, and return a Mechanize::Page instance (which acts a
|
|
120
|
+
# bit like a Nokogiri::HTML::Document instance.)
|
|
121
|
+
#
|
|
122
|
+
# Options:
|
|
123
|
+
# :use_cache => true/false | read/write
|
|
124
|
+
# :read_cache => true/false | check cache before getting page
|
|
125
|
+
# :write_cache => true/false | write gotten pages to cache
|
|
126
|
+
#
|
|
127
|
+
def get(url, options={})
|
|
128
|
+
|
|
129
|
+
# TODO: Have a base-URL option
|
|
130
|
+
|
|
131
|
+
#if relative?(url)
|
|
132
|
+
# url = URI.join("http://base-url/", url).to_s
|
|
133
|
+
#end
|
|
134
|
+
|
|
135
|
+
# Determine the cache setting
|
|
136
|
+
options[:use_cache] ||= @use_cache
|
|
137
|
+
|
|
138
|
+
if options[:use_cache] == false
|
|
139
|
+
options[:read_cache] = false
|
|
140
|
+
options[:write_cache] = false
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
options[:read_cache] = true if options[:read_cache].nil?
|
|
144
|
+
options[:write_cache] = true if options[:write_cache].nil?
|
|
145
|
+
|
|
146
|
+
read_cache = options[:read_cache] && cache.include?(url)
|
|
147
|
+
write_cache = options[:write_cache]
|
|
148
|
+
|
|
149
|
+
puts
|
|
150
|
+
puts "[ #{url.inspect} (read_cache=#{options[:read_cache]}, write_cache=#{options[:write_cache]}) ]"
|
|
151
|
+
|
|
152
|
+
delay unless read_cache
|
|
153
|
+
|
|
154
|
+
begin
|
|
155
|
+
|
|
156
|
+
if read_cache
|
|
157
|
+
page = cache.get(url)
|
|
158
|
+
if page.nil?
|
|
159
|
+
puts " |_ CACHE FAIL! Re-getting page."
|
|
160
|
+
page = get(url, false)
|
|
161
|
+
end
|
|
162
|
+
puts " |_ cached (#{page.content_type})"
|
|
163
|
+
else
|
|
164
|
+
page = agent.get url
|
|
165
|
+
@last_get = Time.now
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
cache_put(page, url) if write_cache and not read_cache
|
|
169
|
+
|
|
170
|
+
puts
|
|
171
|
+
|
|
172
|
+
rescue Net::HTTPBadResponse, Errno::ECONNRESET, SocketError, Timeout::Error, SOCKSError => e
|
|
173
|
+
puts " |_ ERROR: #{e.inspect} -- retrying"
|
|
174
|
+
delay(5)
|
|
175
|
+
retry
|
|
176
|
+
=begin
|
|
177
|
+
rescue Mechanize::ResponseCodeError => e
|
|
178
|
+
|
|
179
|
+
case e.response_code
|
|
180
|
+
when "401" #=> Net::HTTPUnauthorized
|
|
181
|
+
p e
|
|
182
|
+
login!
|
|
183
|
+
page = get(url)
|
|
184
|
+
puts
|
|
185
|
+
when "404"
|
|
186
|
+
p e
|
|
187
|
+
raise e
|
|
188
|
+
when "503"
|
|
189
|
+
puts " |_ ERROR: #{e.inspect} -- retrying"
|
|
190
|
+
delay(5)
|
|
191
|
+
retry
|
|
192
|
+
else
|
|
193
|
+
raise e
|
|
194
|
+
end
|
|
195
|
+
=end
|
|
196
|
+
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
page
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
private
|
|
203
|
+
|
|
204
|
+
def load_cookies!
|
|
205
|
+
agent.cookie_jar.load @cookie_file if File.exists? @cookie_file
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def save_cookies!
|
|
209
|
+
agent.cookie_jar.save_as @cookie_file
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
end
|
|
213
|
+
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#require 'mechanize'
|
|
2
|
+
require 'sqlite3'
|
|
3
|
+
|
|
4
|
+
class CacheDB
|
|
5
|
+
|
|
6
|
+
include Enumerable
|
|
7
|
+
|
|
8
|
+
attr_reader :db, :agent
|
|
9
|
+
|
|
10
|
+
def initialize(agent, filename="browsercache.db")
|
|
11
|
+
@agent = agent
|
|
12
|
+
@filename = filename
|
|
13
|
+
|
|
14
|
+
@db = SQLite3::Database.new(filename)
|
|
15
|
+
@db.busy_timeout(50)
|
|
16
|
+
|
|
17
|
+
create_tables
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def inspect
|
|
21
|
+
"#<CacheDB filename=#{@filename.inspect}, count=#{count}, size=#{File.size @filename} bytes>"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def count
|
|
25
|
+
db.execute("SELECT COUNT(1) FROM cache").first.first.to_i
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
alias_method :size, :count
|
|
29
|
+
|
|
30
|
+
def put(page, original_url=nil, options={})
|
|
31
|
+
raise "Invalid page" unless [:body, :content_type, :uri].all?{|m| page.respond_to? m }
|
|
32
|
+
|
|
33
|
+
url = page.uri.to_s
|
|
34
|
+
|
|
35
|
+
if url != original_url
|
|
36
|
+
# redirect original_url to url
|
|
37
|
+
expire(original_url) if options[:overwrite]
|
|
38
|
+
db.execute(
|
|
39
|
+
"INSERT INTO cache VALUES ( ?, ?, ?, ? )",
|
|
40
|
+
original_url,
|
|
41
|
+
page.content_type,
|
|
42
|
+
nil,
|
|
43
|
+
url
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
compressed_body = Zlib::Deflate.deflate(page.body)
|
|
48
|
+
expire(url) if options[:overwrite]
|
|
49
|
+
db.execute(
|
|
50
|
+
"INSERT INTO cache VALUES ( ?, ?, ?, ? )",
|
|
51
|
+
url,
|
|
52
|
+
page.content_type,
|
|
53
|
+
SQLite3::Blob.new( compressed_body ),
|
|
54
|
+
nil
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
true
|
|
58
|
+
|
|
59
|
+
rescue SQLite3::SQLException => e
|
|
60
|
+
p [:exception, e]
|
|
61
|
+
false
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def row_to_page(row)
|
|
65
|
+
url, content_type, compressed_body, redirect = row
|
|
66
|
+
|
|
67
|
+
if redirect
|
|
68
|
+
get(redirect)
|
|
69
|
+
else
|
|
70
|
+
body = Zlib::Inflate.inflate(compressed_body)
|
|
71
|
+
|
|
72
|
+
WWW::Mechanize::Page.new(
|
|
73
|
+
URI.parse(url),
|
|
74
|
+
{'content-type'=>content_type},
|
|
75
|
+
body,
|
|
76
|
+
nil,
|
|
77
|
+
agent
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def pages_via_sql(*args, &block)
|
|
83
|
+
if block_given?
|
|
84
|
+
db.execute(*args) do |row|
|
|
85
|
+
yield row_to_page(row)
|
|
86
|
+
end
|
|
87
|
+
else
|
|
88
|
+
db.execute(*args).map{|row| row_to_page(row) }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def grep(pattern, &block)
|
|
93
|
+
pages_via_sql("SELECT * FROM cache WHERE url like '%#{pattern}%'", &block)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def get(url)
|
|
97
|
+
pages = pages_via_sql("SELECT * FROM cache WHERE url = ?", url.to_s)
|
|
98
|
+
|
|
99
|
+
if pages.any?
|
|
100
|
+
pages.first
|
|
101
|
+
else
|
|
102
|
+
nil
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def includes?(url)
|
|
107
|
+
db.execute("SELECT url FROM cache WHERE url = ?", url.to_s).any?
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
alias_method :include?, :includes?
|
|
111
|
+
|
|
112
|
+
def urls(pattern=nil)
|
|
113
|
+
if pattern
|
|
114
|
+
rows = db.execute("SELECT url FROM cache WHERE url LIKE '%#{pattern}%'")
|
|
115
|
+
else
|
|
116
|
+
rows = db.execute('SELECT url FROM cache')
|
|
117
|
+
end
|
|
118
|
+
rows.map{|row| row.first}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def clear(pattern=nil)
|
|
122
|
+
if pattern
|
|
123
|
+
db.execute("DELETE FROM cache WHERE url LIKE '%#{pattern}%'")
|
|
124
|
+
else
|
|
125
|
+
db.execute("DELETE FROM cache")
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def each(&block)
|
|
130
|
+
pages_via_sql("SELECT * FROM cache", &block)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def each_url
|
|
134
|
+
db.execute("SELECT url FROM cache") do |row|
|
|
135
|
+
yield row.first
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def expire(url)
|
|
140
|
+
db.execute("DELETE FROM cache WHERE url = ?", url)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def recreate_tables
|
|
144
|
+
drop_tables rescue nil
|
|
145
|
+
create_tables
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
private
|
|
149
|
+
|
|
150
|
+
def list_tables
|
|
151
|
+
db.execute("SELECT name FROM SQLITE_MASTER WHERE type='table'")
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def create_tables
|
|
155
|
+
db.execute("CREATE TABLE IF NOT EXISTS cache ( url varchar(2048), content_type varchar(255), body blob, redirect varchar(2048) )")
|
|
156
|
+
db.execute("CREATE UNIQUE INDEX IF NOT EXISTS url_index ON cache ( url )")
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def drop_tables
|
|
160
|
+
db.execute("DROP TABLE cache")
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require 'epitools/progressbar'
|
|
2
|
+
|
|
3
|
+
class Mechanize
|
|
4
|
+
class Chain
|
|
5
|
+
class ResponseReader
|
|
6
|
+
include Mechanize::Handler
|
|
7
|
+
|
|
8
|
+
def initialize(response)
|
|
9
|
+
@response = response
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def handle(ctx, params)
|
|
13
|
+
params[:response] = @response
|
|
14
|
+
body = StringIO.new
|
|
15
|
+
total = 0
|
|
16
|
+
|
|
17
|
+
if @response.respond_to? :content_type
|
|
18
|
+
pbar = ProgressBar.new(" |_ #{@response.content_type}", @response.content_length)
|
|
19
|
+
else
|
|
20
|
+
pbar = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
@response.read_body { |part|
|
|
24
|
+
total += part.length
|
|
25
|
+
body.write(part)
|
|
26
|
+
|
|
27
|
+
pbar.set(total) if pbar
|
|
28
|
+
Mechanize.log.debug("Read #{total} bytes") if Mechanize.log
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
pbar.finish if pbar
|
|
32
|
+
|
|
33
|
+
body.rewind
|
|
34
|
+
|
|
35
|
+
res_klass = Net::HTTPResponse::CODE_TO_OBJ[@response.code.to_s]
|
|
36
|
+
raise ResponseCodeError.new(@response) unless res_klass
|
|
37
|
+
|
|
38
|
+
# Net::HTTP ignores EOFError if Content-length is given, so we emulate it here.
|
|
39
|
+
unless res_klass <= Net::HTTPRedirection
|
|
40
|
+
raise EOFError if (!params[:request].is_a?(Net::HTTP::Head)) && @response.content_length() && @response.content_length() != total
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
@response.each_header { |k,v|
|
|
44
|
+
Mechanize.log.debug("response-header: #{ k } => #{ v }")
|
|
45
|
+
} if Mechanize.log
|
|
46
|
+
|
|
47
|
+
params[:response_body] = body
|
|
48
|
+
params[:res_klass] = res_klass
|
|
49
|
+
super
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Ruby/ProgressBar - a text progress bar library
|
|
3
|
+
#
|
|
4
|
+
# Copyright (C) 2001-2005 Satoru Takabayashi <satoru@namazu.org>
|
|
5
|
+
# Copyright (C) 2010 Chris Gahan <chris@ill-logic.com>
|
|
6
|
+
# All rights reserved.
|
|
7
|
+
# This is free software with ABSOLUTELY NO WARRANTY.
|
|
8
|
+
#
|
|
9
|
+
# You can redistribute it and/or modify it under the terms
|
|
10
|
+
# of Ruby's license.
|
|
11
|
+
#
|
|
12
|
+
|
|
13
|
+
class ProgressBar
|
|
14
|
+
VERSION = "0.10"
|
|
15
|
+
|
|
16
|
+
def initialize (title, total=nil, out = STDERR)
|
|
17
|
+
@title = title
|
|
18
|
+
@total = total
|
|
19
|
+
@out = out
|
|
20
|
+
@terminal_width = 80
|
|
21
|
+
@bar_mark = "."
|
|
22
|
+
@current = 0
|
|
23
|
+
@previous = 0
|
|
24
|
+
@finished_p = false
|
|
25
|
+
@start_time = Time.now
|
|
26
|
+
@previous_time = @start_time
|
|
27
|
+
@title_width = 18
|
|
28
|
+
#@format = "%-#{@title_width}s %3d%% %s %s"
|
|
29
|
+
if @total
|
|
30
|
+
#@format_arguments = [:title, :percentage, :bar, :stat_for_file_transfer]
|
|
31
|
+
@format_arguments = [:title, :percentage, :stat_for_file_transfer]
|
|
32
|
+
else
|
|
33
|
+
@format_arguments = [:title, :stat_for_file_transfer]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
clear
|
|
37
|
+
show
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
attr_reader :title
|
|
41
|
+
attr_reader :current
|
|
42
|
+
attr_reader :total
|
|
43
|
+
attr_accessor :start_time
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
def fmt_bar
|
|
47
|
+
bar_width = do_percentage * @terminal_width / 100
|
|
48
|
+
sprintf("|%s%s|",
|
|
49
|
+
@bar_mark * bar_width,
|
|
50
|
+
" " * (@terminal_width - bar_width))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def fmt_percentage
|
|
54
|
+
"%3d%%" % do_percentage
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def fmt_stat
|
|
58
|
+
if @finished_p then elapsed else eta end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def fmt_stat_for_file_transfer
|
|
62
|
+
if !@total or @finished_p then
|
|
63
|
+
sprintf("%s %s %s", bytes, transfer_rate, elapsed)
|
|
64
|
+
else
|
|
65
|
+
sprintf("%s %s %s", bytes, transfer_rate, eta)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def fmt_title
|
|
70
|
+
title = (@title[0,(@title_width - 1)] + ":")
|
|
71
|
+
@total ? "%-#{@title_width}s" % title : title
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def convert_bytes(bytes)
|
|
75
|
+
if bytes < 1024
|
|
76
|
+
sprintf("%6dB", bytes)
|
|
77
|
+
elsif bytes < 1024 * 1000 # 1000kb
|
|
78
|
+
sprintf("%5.1fKB", bytes.to_f / 1024)
|
|
79
|
+
elsif bytes < 1024 * 1024 * 1000 # 1000mb
|
|
80
|
+
sprintf("%5.1fMB", bytes.to_f / 1024 / 1024)
|
|
81
|
+
else
|
|
82
|
+
sprintf("%5.1fGB", bytes.to_f / 1024 / 1024 / 1024)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def transfer_rate
|
|
87
|
+
bytes_per_second = @current.to_f / (Time.now - @start_time)
|
|
88
|
+
sprintf("%s/s", convert_bytes(bytes_per_second))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def bytes
|
|
92
|
+
convert_bytes(@current)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def format_time(t)
|
|
96
|
+
t = t.to_i
|
|
97
|
+
sec = t % 60
|
|
98
|
+
min = (t / 60) % 60
|
|
99
|
+
hour = t / 3600
|
|
100
|
+
sprintf("%02d:%02d:%02d", hour, min, sec);
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# ETA stands for Estimated Time of Arrival.
|
|
104
|
+
def eta
|
|
105
|
+
if @current == 0
|
|
106
|
+
"ETA: --:--:--"
|
|
107
|
+
else
|
|
108
|
+
elapsed = Time.now - @start_time
|
|
109
|
+
eta = elapsed * @total / @current - elapsed;
|
|
110
|
+
sprintf("ETA: %s", format_time(eta))
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def elapsed
|
|
115
|
+
elapsed = Time.now - @start_time
|
|
116
|
+
sprintf("Time: %s", format_time(elapsed))
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def eol
|
|
120
|
+
if @finished_p then "\n" else "\r" end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def do_percentage
|
|
124
|
+
if @total.zero?
|
|
125
|
+
100
|
|
126
|
+
else
|
|
127
|
+
@current * 100 / @total
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def get_width
|
|
132
|
+
# FIXME: I don't know how portable it is.
|
|
133
|
+
default_width = 80
|
|
134
|
+
begin
|
|
135
|
+
tiocgwinsz = 0x5413
|
|
136
|
+
data = [0, 0, 0, 0].pack("SSSS")
|
|
137
|
+
if @out.ioctl(tiocgwinsz, data) >= 0 then
|
|
138
|
+
rows, cols, xpixels, ypixels = data.unpack("SSSS")
|
|
139
|
+
if cols >= 0 then cols else default_width end
|
|
140
|
+
else
|
|
141
|
+
default_width
|
|
142
|
+
end
|
|
143
|
+
rescue Exception
|
|
144
|
+
default_width
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def show
|
|
149
|
+
arguments = @format_arguments.map {|method|
|
|
150
|
+
send("fmt_#{method}")
|
|
151
|
+
}
|
|
152
|
+
#line = sprintf(@format, *arguments)
|
|
153
|
+
line = arguments.join(" ")
|
|
154
|
+
|
|
155
|
+
# width = get_width
|
|
156
|
+
# if line.length == width - 1
|
|
157
|
+
# @out.print(line + eol)
|
|
158
|
+
# @out.flush
|
|
159
|
+
# elsif line.length >= width
|
|
160
|
+
# @terminal_width = [@terminal_width - (line.length - width + 1), 0].max
|
|
161
|
+
# if @terminal_width == 0 then @out.print(line + eol) else show end
|
|
162
|
+
# else # line.length < width - 1
|
|
163
|
+
# @terminal_width += width - line.length + 1
|
|
164
|
+
# show
|
|
165
|
+
# end
|
|
166
|
+
@out.print(line + eol)
|
|
167
|
+
@out.flush
|
|
168
|
+
|
|
169
|
+
@previous_time = Time.now
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def show_if_needed
|
|
174
|
+
|
|
175
|
+
if @total
|
|
176
|
+
|
|
177
|
+
if @total.zero?
|
|
178
|
+
cur_percentage = 100
|
|
179
|
+
prev_percentage = 0
|
|
180
|
+
else
|
|
181
|
+
cur_percentage = (@current * 100 / @total).to_i
|
|
182
|
+
prev_percentage = (@previous * 100 / @total).to_i
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Use "!=" instead of ">" to support negative changes
|
|
186
|
+
if cur_percentage != prev_percentage ||
|
|
187
|
+
Time.now - @previous_time >= 1 || @finished_p
|
|
188
|
+
show
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
else
|
|
192
|
+
|
|
193
|
+
if Time.now - @previous_time >= 1 || @finished_p
|
|
194
|
+
show
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
public
|
|
202
|
+
def clear
|
|
203
|
+
@out.print "\r"
|
|
204
|
+
@out.print(" " * (get_width - 1))
|
|
205
|
+
@out.print "\r"
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def finish
|
|
209
|
+
@current = @total if @total
|
|
210
|
+
@finished_p = true
|
|
211
|
+
show
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def finished?
|
|
215
|
+
@finished_p
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def file_transfer_mode
|
|
219
|
+
@format_arguments = [:title, :percentage, :bar, :stat_for_file_transfer]
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def format=(format)
|
|
223
|
+
@format = format
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def format_arguments=(arguments)
|
|
227
|
+
@format_arguments = arguments
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def halt
|
|
231
|
+
@finished_p = true
|
|
232
|
+
show
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def inc(step = 1)
|
|
236
|
+
@current += step
|
|
237
|
+
@current = @total if @total and @current > @total
|
|
238
|
+
show_if_needed
|
|
239
|
+
@previous = @current
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def set(count)
|
|
243
|
+
if @total and (count < 0 || count > @total)
|
|
244
|
+
raise "invalid count: #{count} (total: #{@total})"
|
|
245
|
+
end
|
|
246
|
+
@current = count
|
|
247
|
+
show_if_needed
|
|
248
|
+
@previous = @current
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def inspect
|
|
252
|
+
"#<ProgressBar:#{@current}/#{@total}>"
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
class ReversedProgressBar < ProgressBar
|
|
257
|
+
def do_percentage
|
|
258
|
+
100 - super
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
if $0 == __FILE__
|
|
263
|
+
pb = ProgressBar.new("no_total")
|
|
264
|
+
5.times do
|
|
265
|
+
pb.inc 4000
|
|
266
|
+
sleep 1
|
|
267
|
+
end
|
|
268
|
+
pb.finish
|
|
269
|
+
end
|
data/lib/epitools/sys.rb
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Cross-platform operating system functions.
|
|
3
|
+
# Includes: process listing, platform detection, etc.
|
|
4
|
+
#
|
|
5
|
+
module Sys
|
|
6
|
+
|
|
7
|
+
#-----------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
PS_FIELD_TABLE = [
|
|
10
|
+
[:pid, :to_i],
|
|
11
|
+
[:pcpu, :to_f],
|
|
12
|
+
[:pmem, :to_f],
|
|
13
|
+
[:stat, :to_s],
|
|
14
|
+
[:rss, :to_i],
|
|
15
|
+
[:vsz, :to_i],
|
|
16
|
+
[:user, :to_s],
|
|
17
|
+
[:majflt, :to_i],
|
|
18
|
+
[:minflt, :to_i],
|
|
19
|
+
[:command,:to_s],
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
PS_FIELDS = PS_FIELD_TABLE.map { |name, func| name }
|
|
23
|
+
PS_FIELD_TRANSFORMS = Hash[ *PS_FIELD_TABLE.flatten ]
|
|
24
|
+
|
|
25
|
+
class ProcessNotFound < Exception; end
|
|
26
|
+
|
|
27
|
+
#
|
|
28
|
+
# Contains all the information that PS can report about a process for
|
|
29
|
+
# the current platform.
|
|
30
|
+
#
|
|
31
|
+
# The following attribute accessor methods are available:
|
|
32
|
+
#
|
|
33
|
+
# pid (integer)
|
|
34
|
+
# pcpu (float)
|
|
35
|
+
# pmem (float)
|
|
36
|
+
# stat (string)
|
|
37
|
+
# rss (integer)
|
|
38
|
+
# vsz (integer)
|
|
39
|
+
# user (string)
|
|
40
|
+
# majflt (integer)
|
|
41
|
+
# minflt (integer)
|
|
42
|
+
# command (string)
|
|
43
|
+
# state (array of symbols; see DARWIN_STATES or LINUX_STATES)
|
|
44
|
+
#
|
|
45
|
+
class ProcessInfo < Struct.new(*PS_FIELDS+[:state])
|
|
46
|
+
|
|
47
|
+
DARWIN_STATES = {
|
|
48
|
+
"R"=>:running,
|
|
49
|
+
"S"=>:sleeping,
|
|
50
|
+
"I"=>:idle,
|
|
51
|
+
"T"=>:stopped,
|
|
52
|
+
"U"=>:wait,
|
|
53
|
+
"Z"=>:zombie,
|
|
54
|
+
"W"=>:swapped,
|
|
55
|
+
|
|
56
|
+
"s"=>:session_leader,
|
|
57
|
+
"X"=>:debugging,
|
|
58
|
+
"E"=>:exiting,
|
|
59
|
+
"<"=>:high_priority,
|
|
60
|
+
"N"=>:low_priority,
|
|
61
|
+
"+"=>:foreground,
|
|
62
|
+
"L"=>:locked_pages,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
LINUX_STATES = {
|
|
66
|
+
"R"=>:running,
|
|
67
|
+
"S"=>:sleeping,
|
|
68
|
+
"T"=>:stopped,
|
|
69
|
+
"D"=>:wait,
|
|
70
|
+
"Z"=>:zombie,
|
|
71
|
+
"W"=>:swapped,
|
|
72
|
+
"X"=>:dead,
|
|
73
|
+
|
|
74
|
+
"s"=>:session_leader,
|
|
75
|
+
"<"=>:high_priority,
|
|
76
|
+
"N"=>:low_priority,
|
|
77
|
+
"+"=>:foreground,
|
|
78
|
+
"L"=>:locked_pages,
|
|
79
|
+
"l"=>:multithreaded,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
def initialize(*args)
|
|
83
|
+
@dead = false
|
|
84
|
+
args << stat_to_state(args[PS_FIELDS.index(:stat)])
|
|
85
|
+
super(*args)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
#
|
|
89
|
+
# Convert all the process information to a hash.
|
|
90
|
+
#
|
|
91
|
+
def to_hash
|
|
92
|
+
Hash[ *members.zip(values).flatten(1) ]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
#
|
|
96
|
+
# Send the TERM signal to this process.
|
|
97
|
+
#
|
|
98
|
+
def kill!(signal="TERM")
|
|
99
|
+
puts "Killing #{pid} (#{signal})"
|
|
100
|
+
Process.kill(signal, pid)
|
|
101
|
+
# TODO: handle exception Errno::ESRCH (no such process)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
#
|
|
105
|
+
# Has this process been killed?
|
|
106
|
+
#
|
|
107
|
+
def dead?
|
|
108
|
+
@dead ||= Sys.pid(pid).empty?
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
#
|
|
112
|
+
# Refresh this process' statistics.
|
|
113
|
+
#
|
|
114
|
+
def refresh
|
|
115
|
+
processes = Sys.ps(pid)
|
|
116
|
+
|
|
117
|
+
if processes.empty?
|
|
118
|
+
@dead = true
|
|
119
|
+
raise ProcessNotFound
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
updated_process = processes.first
|
|
123
|
+
members.each { |member| self[member] = updated_process[member] }
|
|
124
|
+
self
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def stat_to_state(str)
|
|
130
|
+
states = case Sys.os
|
|
131
|
+
when "Linux" then LINUX_STATES
|
|
132
|
+
when "Darwin" then DARWIN_STATES
|
|
133
|
+
else raise "Unsupported platform: #{Sys.os}"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
str.scan(/./).map { |char| states[char] }.compact
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
#-----------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
#
|
|
144
|
+
# List all (or specified) processes, and return ProcessInfo objects.
|
|
145
|
+
# (Takes an optional list of pids as arguments.)
|
|
146
|
+
#
|
|
147
|
+
def self.ps(*pids)
|
|
148
|
+
options = PS_FIELDS.join(',')
|
|
149
|
+
|
|
150
|
+
if pids.any?
|
|
151
|
+
command = "ps -p #{pids.join(',')} -o #{options}"
|
|
152
|
+
else
|
|
153
|
+
command = "ps ax -o #{options}"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
lines = `#{command}`.to_a
|
|
157
|
+
|
|
158
|
+
lines[1..-1].map do |line|
|
|
159
|
+
fields = line.split
|
|
160
|
+
if fields.size > PS_FIELDS.size
|
|
161
|
+
fields = fields[0..PS_FIELDS.size-2] + [fields[PS_FIELDS.size-1..-1].join(" ")]
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
fields = PS_FIELDS.zip(fields).map { |name, value| value.send(PS_FIELD_TRANSFORMS[name]) }
|
|
165
|
+
|
|
166
|
+
ProcessInfo.new(*fields)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
#-----------------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
#
|
|
173
|
+
# Return the current operating system: Darwin, Linux, or Windows.
|
|
174
|
+
#
|
|
175
|
+
def self.os
|
|
176
|
+
return @os if @os
|
|
177
|
+
|
|
178
|
+
require 'rbconfig'
|
|
179
|
+
host_os = Config::CONFIG['host_os']
|
|
180
|
+
case host_os
|
|
181
|
+
when /darwin/
|
|
182
|
+
@os = "Darwin"
|
|
183
|
+
when /linux/
|
|
184
|
+
@os = "Linux"
|
|
185
|
+
when /mingw|mswin/
|
|
186
|
+
@os = 'Windows'
|
|
187
|
+
else
|
|
188
|
+
raise "Unknown OS: #{host_os.inspect}"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
@os
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
#
|
|
195
|
+
# Is this Linux?
|
|
196
|
+
#
|
|
197
|
+
def self.linux?
|
|
198
|
+
os == "Linux"
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
#
|
|
202
|
+
# Is this Windows?
|
|
203
|
+
#
|
|
204
|
+
def self.windows?
|
|
205
|
+
os == "Windows"
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
#
|
|
209
|
+
# Is this Darwin?
|
|
210
|
+
#
|
|
211
|
+
def self.darwin?
|
|
212
|
+
os == "Darwin"
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
#
|
|
216
|
+
# Is this a Mac? (aka. Darwin?)
|
|
217
|
+
#
|
|
218
|
+
def self.mac?; darwin?; end
|
|
219
|
+
|
|
220
|
+
#-----------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
#
|
|
223
|
+
# Trap signals!
|
|
224
|
+
#
|
|
225
|
+
# usage: trap("EXIT", "HUP", "ETC", :ignore=>["VTALRM"]) { |signal| puts "Got #{signal}!" }
|
|
226
|
+
# (Execute Signal.list to see what's available.)
|
|
227
|
+
#
|
|
228
|
+
# No paramters defaults to all signals except VTALRM, CHLD, CLD, and EXIT.
|
|
229
|
+
#
|
|
230
|
+
def self.trap(*args, &block)
|
|
231
|
+
options = if args.last.is_a?(Hash) then args.pop else Hash.new end
|
|
232
|
+
args = [args].flatten
|
|
233
|
+
signals = if args.any? then args else Signal.list.keys end
|
|
234
|
+
|
|
235
|
+
ignore = %w[ VTALRM CHLD CLD EXIT ] unless ignore = options[:ignore]
|
|
236
|
+
ignore = [ignore] unless ignore.is_a? Array
|
|
237
|
+
|
|
238
|
+
signals = signals - ignore
|
|
239
|
+
|
|
240
|
+
signals.each do |signal|
|
|
241
|
+
p [:sig, signal]
|
|
242
|
+
Signal.trap(signal) { yield signal }
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
#-----------------------------------------------------------------------------
|
|
247
|
+
|
|
248
|
+
#
|
|
249
|
+
# A metaprogramming helper that allows you to write platform-specific methods
|
|
250
|
+
# which the user can call with one name. Here's how to use it:
|
|
251
|
+
#
|
|
252
|
+
# Define these methods:
|
|
253
|
+
# reboot_linux, reboot_darwin, reboot_windows
|
|
254
|
+
#
|
|
255
|
+
# Call the magic method:
|
|
256
|
+
# cross_platform_method(:reboot)
|
|
257
|
+
#
|
|
258
|
+
# Now the user can execute "reboot" on any platform!
|
|
259
|
+
#
|
|
260
|
+
# (Note: If you didn't create a method for a specific platform, then you'll get
|
|
261
|
+
# NoMethodError exception when the "reboot" method is called on that platform.)
|
|
262
|
+
#
|
|
263
|
+
def self.cross_platform_method(name)
|
|
264
|
+
platform_method_name = "#{name}_#{os.downcase}"
|
|
265
|
+
metaclass.instance_eval do
|
|
266
|
+
define_method(name) do |*args|
|
|
267
|
+
begin
|
|
268
|
+
self.send(platform_method_name, *args)
|
|
269
|
+
rescue NoMethodError
|
|
270
|
+
raise NotImplementedError.new("#{name} is not yet supported on this platform.")
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
#-----------------------------------------------------------------------------
|
|
277
|
+
|
|
278
|
+
cross_platform_method :interfaces
|
|
279
|
+
|
|
280
|
+
#
|
|
281
|
+
# Darwin: Return a hash of (device, IP address) pairs.
|
|
282
|
+
#
|
|
283
|
+
# eg: {"en0"=>"192.168.1.101"}
|
|
284
|
+
#
|
|
285
|
+
def self.interfaces_darwin
|
|
286
|
+
sections = `ifconfig`.split(/^(?=[^\t])/)
|
|
287
|
+
sections_with_relevant_ip = sections.select {|i| i =~ /inet/ }
|
|
288
|
+
|
|
289
|
+
device_ips = {}
|
|
290
|
+
sections_with_relevant_ip.each do |section|
|
|
291
|
+
device = section[/[^:]+/]
|
|
292
|
+
ip = section[/inet ([^ ]+)/, 1]
|
|
293
|
+
device_ips[device] = ip
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
device_ips
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
#
|
|
300
|
+
# Linux: Return a hash of (device, IP address) pairs.
|
|
301
|
+
#
|
|
302
|
+
# eg: {"eth0"=>"192.168.1.101"}
|
|
303
|
+
#
|
|
304
|
+
def self.interfaces_linux
|
|
305
|
+
sections = `ifconfig`.split(/^(?=Link encap:Ethernet)/)
|
|
306
|
+
sections_with_relevant_ip = sections.select {|i| i =~ /inet/ }
|
|
307
|
+
|
|
308
|
+
device_ips = {}
|
|
309
|
+
sections_with_relevant_ip.each do |section|
|
|
310
|
+
device = section[/([\w\d]+)\s+Link encap:Ethernet/, 1]
|
|
311
|
+
ip = section[/inet addr:([^\s]+)/, 1]
|
|
312
|
+
device_ips[device] = ip
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
device_ips
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
#-----------------------------------------------------------------------------
|
|
319
|
+
|
|
320
|
+
cross_platform_method :browser_open
|
|
321
|
+
|
|
322
|
+
#
|
|
323
|
+
# Linux: Open an URL in the default browser (using "gnome-open").
|
|
324
|
+
#
|
|
325
|
+
def browser_open_linux(url)
|
|
326
|
+
system("gnome-open", url)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
#
|
|
330
|
+
# Darwin: Open the webpage in a new chrome tab.
|
|
331
|
+
#
|
|
332
|
+
def browser_open_darwin(url)
|
|
333
|
+
system("open", "-a", "chrome", url)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
#-----------------------------------------------------------------------------
|
|
337
|
+
|
|
338
|
+
cross_platform_method :memstat
|
|
339
|
+
|
|
340
|
+
def self.memstat_linux
|
|
341
|
+
#$ free
|
|
342
|
+
# total used free shared buffers cached
|
|
343
|
+
#Mem: 4124380 3388548 735832 0 561888 968004
|
|
344
|
+
#-/+ buffers/cache: 1858656 2265724
|
|
345
|
+
#Swap: 2104504 166724 1937780
|
|
346
|
+
|
|
347
|
+
#$ vmstat
|
|
348
|
+
raise "Not implemented"
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def self.memstat_darwin
|
|
352
|
+
#$ vm_stat
|
|
353
|
+
#Mach Virtual Memory Statistics: (page size of 4096 bytes)
|
|
354
|
+
#Pages free: 198367.
|
|
355
|
+
#Pages active: 109319.
|
|
356
|
+
#Pages inactive: 61946.
|
|
357
|
+
#Pages speculative: 18674.
|
|
358
|
+
#Pages wired down: 70207.
|
|
359
|
+
#"Translation faults": 158788687.
|
|
360
|
+
#Pages copy-on-write: 17206973.
|
|
361
|
+
#Pages zero filled: 54584525.
|
|
362
|
+
#Pages reactivated: 8768.
|
|
363
|
+
#Pageins: 176076.
|
|
364
|
+
#Pageouts: 3757.
|
|
365
|
+
#Object cache: 16 hits of 255782 lookups (0% hit rate)
|
|
366
|
+
|
|
367
|
+
#$ iostat
|
|
368
|
+
raise "Not implemented"
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def self.temperatures
|
|
372
|
+
#/Applications/Utilities/TemperatureMonitor.app/Contents/MacOS/tempmonitor -a -l
|
|
373
|
+
#CPU Core 1: 28 C
|
|
374
|
+
#CPU Core 2: 28 C
|
|
375
|
+
#SMART Disk Hitachi HTS543216L9SA02 (090831FBE200VCGH3D5F): 40 C
|
|
376
|
+
#SMC CPU A DIODE: 41 C
|
|
377
|
+
#SMC CPU A HEAT SINK: 42 C
|
|
378
|
+
#SMC DRIVE BAY 1: 41 C
|
|
379
|
+
#SMC NORTHBRIDGE POS 1: 46 C
|
|
380
|
+
#SMC WLAN CARD: 45 C
|
|
381
|
+
raise "Not implemented"
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
if $0 == __FILE__
|
|
387
|
+
require 'pp'
|
|
388
|
+
procs = Sys.ps
|
|
389
|
+
p [:processes, procs.size]
|
|
390
|
+
# some = procs[0..3]
|
|
391
|
+
# some.each{|ps| pp ps}
|
|
392
|
+
# some.first.kill!
|
|
393
|
+
# pp some.first.to_hash
|
|
394
|
+
# p [:total_cpu, procs.map{|ps| ps.pcpu}.sum]
|
|
395
|
+
# p [:total_mem, procs.map{|ps| ps.pmem}.sum]
|
|
396
|
+
|
|
397
|
+
pp Sys.interfaces
|
|
398
|
+
end
|
data/spec/basetypes_spec.rb
CHANGED
|
@@ -7,10 +7,10 @@ describe Object do
|
|
|
7
7
|
defined?(Enum).should_not == nil
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
it "enums" do
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
end
|
|
10
|
+
#it "enums" do
|
|
11
|
+
# generator = enum { |y| y.yield 1 }
|
|
12
|
+
# generator.next.should == 1
|
|
13
|
+
#end
|
|
14
14
|
|
|
15
15
|
it "in?" do
|
|
16
16
|
5.in?([1,2,3,4,5,6]).should == true
|
data/spec/sys_spec.rb
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
require 'epitools/sys'
|
|
2
|
+
|
|
3
|
+
describe Sys::ProcessInfo do
|
|
4
|
+
|
|
5
|
+
specify "checks OS" do
|
|
6
|
+
proc { Sys.os }.should_not raise_error
|
|
7
|
+
proc { Sys.linux? }.should_not raise_error
|
|
8
|
+
proc { Sys.mac? }.should_not raise_error
|
|
9
|
+
proc { Sys.darwin? }.should_not raise_error
|
|
10
|
+
|
|
11
|
+
%w[Linux Windows Darwin].include?(Sys.os).should == true
|
|
12
|
+
|
|
13
|
+
( (Sys.linux? and not Sys.mac?) or (Sys.mac? and not Sys.linux?) ).should == true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
specify "list all processes" do
|
|
18
|
+
# procs = Sys.ps
|
|
19
|
+
#
|
|
20
|
+
# procs.first.state.is_a?(Array).should == true
|
|
21
|
+
#
|
|
22
|
+
# pids = procs.map{ |process| process.pid }
|
|
23
|
+
#
|
|
24
|
+
# p2s = Hash[ *Sys.ps(*pids).map { |process| [process.pid, process] }.flatten ]
|
|
25
|
+
# matches = 0
|
|
26
|
+
# procs.each do |p1|
|
|
27
|
+
# if p2 = p2s[p1.pid]
|
|
28
|
+
# matches += 1
|
|
29
|
+
# p1.command.should == p2.command
|
|
30
|
+
# end
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# matches.should > 1
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
specify "refresh processes" do
|
|
38
|
+
|
|
39
|
+
# STDOUT.sync = true
|
|
40
|
+
#
|
|
41
|
+
# procs = Sys.ps
|
|
42
|
+
# procs.shuffle!
|
|
43
|
+
# procs.each do |process|
|
|
44
|
+
# proc do
|
|
45
|
+
# begin
|
|
46
|
+
# process.refresh
|
|
47
|
+
# print "."
|
|
48
|
+
# rescue Sys::ProcessNotFound
|
|
49
|
+
# end
|
|
50
|
+
# end.should_not raise_error
|
|
51
|
+
# end
|
|
52
|
+
#
|
|
53
|
+
# puts
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
specify "cross-platform method" do
|
|
59
|
+
Sys.interfaces.should_not be_nil
|
|
60
|
+
Sys.cross_platform_method(:cross_platform_test)
|
|
61
|
+
proc{ Sys.cross_platform_test }.should raise_error
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: epitools
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
hash:
|
|
4
|
+
hash: 23
|
|
5
5
|
prerelease: false
|
|
6
6
|
segments:
|
|
7
7
|
- 0
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
version: 0.
|
|
8
|
+
- 2
|
|
9
|
+
- 0
|
|
10
|
+
version: 0.2.0
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- epitron
|
|
@@ -15,7 +15,7 @@ autorequire:
|
|
|
15
15
|
bindir: bin
|
|
16
16
|
cert_chain: []
|
|
17
17
|
|
|
18
|
-
date: 2010-09-
|
|
18
|
+
date: 2010-09-28 00:00:00 -04:00
|
|
19
19
|
default_executable:
|
|
20
20
|
dependencies:
|
|
21
21
|
- !ruby/object:Gem::Dependency
|
|
@@ -53,6 +53,9 @@ files:
|
|
|
53
53
|
- epitools.gemspec
|
|
54
54
|
- lib/epitools.rb
|
|
55
55
|
- lib/epitools/basetypes.rb
|
|
56
|
+
- lib/epitools/browser.rb
|
|
57
|
+
- lib/epitools/browser/browser_cache.rb
|
|
58
|
+
- lib/epitools/browser/mechanize_progressbar.rb
|
|
56
59
|
- lib/epitools/hexdump.rb
|
|
57
60
|
- lib/epitools/http.rb
|
|
58
61
|
- lib/epitools/lcs.rb
|
|
@@ -60,12 +63,15 @@ files:
|
|
|
60
63
|
- lib/epitools/niceprint.rb
|
|
61
64
|
- lib/epitools/permutations.rb
|
|
62
65
|
- lib/epitools/pretty_backtrace.rb
|
|
66
|
+
- lib/epitools/progressbar.rb
|
|
63
67
|
- lib/epitools/rails.rb
|
|
64
68
|
- lib/epitools/rash.rb
|
|
65
69
|
- lib/epitools/ratio.rb
|
|
66
70
|
- lib/epitools/string_to_proc.rb
|
|
71
|
+
- lib/epitools/sys.rb
|
|
67
72
|
- lib/epitools/zopen.rb
|
|
68
73
|
- spec/basetypes_spec.rb
|
|
74
|
+
- spec/browser_spec.rb
|
|
69
75
|
- spec/lcs_spec.rb
|
|
70
76
|
- spec/metaclass_spec.rb
|
|
71
77
|
- spec/permutations_spec.rb
|
|
@@ -73,6 +79,7 @@ files:
|
|
|
73
79
|
- spec/ratio_spec.rb
|
|
74
80
|
- spec/spec.opts
|
|
75
81
|
- spec/spec_helper.rb
|
|
82
|
+
- spec/sys_spec.rb
|
|
76
83
|
- spec/zopen_spec.rb
|
|
77
84
|
has_rdoc: true
|
|
78
85
|
homepage: http://github.com/epitron/epitools
|
|
@@ -109,11 +116,13 @@ signing_key:
|
|
|
109
116
|
specification_version: 3
|
|
110
117
|
summary: NOT UTILS... METILS!
|
|
111
118
|
test_files:
|
|
119
|
+
- spec/sys_spec.rb
|
|
120
|
+
- spec/permutations_spec.rb
|
|
112
121
|
- spec/rash_spec.rb
|
|
113
|
-
- spec/
|
|
114
|
-
- spec/
|
|
115
|
-
- spec/basetypes_spec.rb
|
|
122
|
+
- spec/spec_helper.rb
|
|
123
|
+
- spec/browser_spec.rb
|
|
116
124
|
- spec/lcs_spec.rb
|
|
125
|
+
- spec/basetypes_spec.rb
|
|
126
|
+
- spec/ratio_spec.rb
|
|
117
127
|
- spec/zopen_spec.rb
|
|
118
|
-
- spec/
|
|
119
|
-
- spec/spec_helper.rb
|
|
128
|
+
- spec/metaclass_spec.rb
|