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 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