epitools 0.1.11 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.11
1
+ 0.2.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{epitools}
8
- s.version = "0.1.11"
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-25}
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/rash_spec.rb",
58
- "spec/ratio_spec.rb",
59
- "spec/metaclass_spec.rb",
60
- "spec/basetypes_spec.rb",
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/permutations_spec.rb",
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
@@ -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
@@ -7,10 +7,10 @@ describe Object do
7
7
  defined?(Enum).should_not == nil
8
8
  end
9
9
 
10
- it "enums" do
11
- generator = enum { |y| y.yield 1 }
12
- generator.next.should == 1
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
@@ -0,0 +1,14 @@
1
+ require 'epitools/browser'
2
+
3
+ describe Browser do
4
+
5
+ before :all do
6
+ @browser = Browser.new
7
+ end
8
+
9
+ it "googles" do
10
+ page = @browser.get("http://google.com")
11
+ page.body["Feeling Lucky"].should_not be_empty
12
+ end
13
+
14
+ end
@@ -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: 13
4
+ hash: 23
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 1
9
- - 11
10
- version: 0.1.11
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-25 00:00:00 -04:00
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/ratio_spec.rb
114
- - spec/metaclass_spec.rb
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/permutations_spec.rb
119
- - spec/spec_helper.rb
128
+ - spec/metaclass_spec.rb