epitools 0.5.134 → 0.5.136

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2aefbc7936ad578ce3f2ae8806e67be1815f4e2a55ddf43a915bcdaa0ca9f68a
4
- data.tar.gz: 6d09789e434d7cda9b66e25ecf1ebf9db61d6236f1342b83743330e42d9ab09f
3
+ metadata.gz: 4c080c53832ae049dd0ac7abedc41844d515b759fd081f4541855359a21eb75a
4
+ data.tar.gz: f6871df3e1da9c2cdb315ab49a5c76cea1eb6dfc8f776d6a3f44b3e6d59844fd
5
5
  SHA512:
6
- metadata.gz: 4c98a36281937b7dd6e8f6154af595f5153d51803da0587eafc1a8dec26c96adac899e73c9ef49157b8fadcdf01fb845b60095d9ceb9722b5107c0eeaf6f8550
7
- data.tar.gz: ccf09c13ef7df31e8d6994ba1c5550dc606fd61ac909c14f00c53788160a69cb53e7c425156a373732f4fbc7e021bde12c27e850be0ca1cf4735b86d9cf01285
6
+ metadata.gz: d93c4452eb2e0537265eb95497bf6a0ca9d9022f2ab099972a64904465eccabbe0d3f1901ff2b711940e4603bb6e7afd7c56f7b9b93eea4d9d7762f6453ed3ee
7
+ data.tar.gz: 75a2d4e10cf13071f4a2cab7239f6a2db7c3b0bb66a68ce603c75bbe5051c15d742308d9f370cc9fb045e6db2492942f8cd37a65db40ba235655e1ea4d429ac8
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ task :release => :build do
9
9
  end
10
10
 
11
11
  task :install => :build do
12
- system "gem install --local epitools-#{gem_version}.gem"
12
+ system "gem install --user epitools-#{gem_version}.gem"
13
13
  end
14
14
 
15
15
  task :pry do
@@ -17,13 +17,11 @@ task :pry do
17
17
  end
18
18
 
19
19
  task :spec do
20
- cmd = %w[rspec -fd -c spec]
20
+ cmd = %w[rspec --format documentation --force-color --pattern spec/*_spec.rb]
21
+ cmd.unshift "rescue" if system *%w[which rescue]
21
22
 
22
- if system *%w[which rescue]
23
- system *(["rescue"]+cmd)
24
- else
25
- system *cmd
26
- end
23
+ p cmd
24
+ system *cmd
27
25
  end
28
26
 
29
27
  task :default => :spec
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.134
1
+ 0.5.136
@@ -69,7 +69,7 @@ autoload :Path, 'epitools/path'
69
69
  autoload :Ezdb, 'epitools/ezdb'
70
70
  autoload :Browser, 'epitools/browser'
71
71
  autoload :Rash, 'epitools/rash'
72
- autoload :Ratio, 'epitools/ratio'
72
+ autoload :Fraction, 'epitools/fraction'
73
73
  autoload :ProgressBar, 'epitools/progressbar'
74
74
  autoload :Trie, 'epitools/trie'
75
75
  autoload :MimeMagic, 'epitools/mimemagic'
@@ -37,7 +37,7 @@ class Browser
37
37
  end
38
38
 
39
39
 
40
- def put(page, original_url=nil, options={})
40
+ def put(page, original_url=nil, **options)
41
41
  dmsg [:put, original_url]
42
42
 
43
43
  raise "Invalid page" unless valid_page?(page)
@@ -36,7 +36,7 @@ class Browser
36
36
  # :use_logs => false, # Don't log the detailed transfer info
37
37
  # :cookie_file => "cookies.txt" # Save cookies to file
38
38
  #
39
- def initialize(options={})
39
+ def initialize(**options)
40
40
  @last_get = Time.at(0)
41
41
  @delay = options[:delay] || 1
42
42
  @delay_jitter = options[:delay_jitter] || 0.2
@@ -85,7 +85,7 @@ class Browser
85
85
  end
86
86
 
87
87
  def load_cookies!
88
- if File.exists? @cookie_file
88
+ if File.exist? @cookie_file
89
89
  agent.cookie_jar.load @cookie_file
90
90
  true
91
91
  else
@@ -126,7 +126,7 @@ class Browser
126
126
  # Options:
127
127
  # :cached => true/false | check cache before getting page
128
128
  #
129
- def get(url, options={})
129
+ def get(url, **options)
130
130
 
131
131
  # TODO: Have a base-URL option
132
132
 
@@ -151,7 +151,7 @@ class Browser
151
151
  if use_cache and page = cache.get(url)
152
152
  puts " |_ cached (#{page.content_type})"
153
153
  else
154
- page = agent.get(url)
154
+ page = agent.get(url, [], options[:referer])
155
155
  @last_get = Time.now
156
156
  cache_put(page, url) if use_cache
157
157
  end
@@ -56,6 +56,13 @@ rescue Errno::EPIPE, Interrupt
56
56
  end
57
57
 
58
58
 
59
+ #
60
+ # Colorized puts (see: `String#colorize`)
61
+ #
62
+ def cputs(*args)
63
+ puts args.join("\n").colorize
64
+ end
65
+
59
66
  #
60
67
  # Execute a `system()` command using SQL-style escaped arguments.
61
68
  #
@@ -179,11 +186,11 @@ end
179
186
  def geoip(addr, city_data='/usr/share/GeoIP/GeoIPCity.dat', country_data='/usr/share/GeoIP/GeoIP.dat')
180
187
  (
181
188
  $geoip ||= begin
182
- if city_data and File.exists? city_data
189
+ if city_data and File.exist? city_data
183
190
  geo = GeoIP.new city_data
184
191
  proc { |addr| geo.city(addr) }
185
192
 
186
- elsif country_data and File.exists? country_data
193
+ elsif country_data and File.exist? country_data
187
194
  geo = GeoIP.new country_data
188
195
  proc { |addr| geo.country(addr) }
189
196
 
@@ -202,7 +209,7 @@ def which(*bins)
202
209
  bins.flatten.each do |bin|
203
210
  ENV["PATH"].split(":").each do |dir|
204
211
  full_path = File.join(dir, bin)
205
- return full_path if File.exists? full_path
212
+ return full_path if File.exist? full_path
206
213
  end
207
214
  end
208
215
  nil
@@ -234,7 +241,7 @@ def curl(url)
234
241
  curl_open(url).read
235
242
  end
236
243
 
237
- def curl_open(url, headers={})
244
+ def curl_open(url, **headers)
238
245
  # headers["User-Agent"] ||= "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/85 Version/11.1.1 Safari/605.1.15"
239
246
  cmd = ["curl", "-LSs"]
240
247
  headers.each { |k,v| cmd += ["-H", "#{k}: #{v}"] }
@@ -266,7 +266,7 @@ class Array
266
266
  # 5.0...9.0 => 5
267
267
  # }
268
268
  #
269
- def histogram(n_buckets=10, options={})
269
+ def histogram(n_buckets=10, **options)
270
270
 
271
271
  use_ranges = options[:ranges] || options[:hash]
272
272
 
@@ -283,6 +283,16 @@ class Integer
283
283
  end
284
284
  alias_method :bits, :to_bits
285
285
 
286
+ #
287
+ # Convert this number to a Ruby-style string of platform-endian binary digits (eg: "0b011010101").
288
+ # Note: The `min_length` argument tells how long the string should be (padding the missing digits with 0's); defaults to 8.
289
+ #
290
+ def binary(min_length=8)
291
+ width = (log(2).ceil / min_length.to_f).ceil * min_length
292
+ "0b%0.#{width}b" % self
293
+ end
294
+ alias_method :bin, :binary
295
+
286
296
  #
287
297
  # Cached constants for encoding numbers into bases up to 64
288
298
  #
@@ -443,20 +453,40 @@ end
443
453
  class Float
444
454
 
445
455
  #
446
- # Convert the float to a rounded percentage string (eg: "42%").
456
+ # Convert the float to a rounded percentage string (eg: "42.5%").
447
457
  # Its argument lets you specify how many decimals to display
448
458
  #
449
459
  # eg:
450
- # > 0.32786243.percent # => "33%"
460
+ # > 0.32786243.percent # => "33.8%"
451
461
  # > 0.32786243.percent(2) # => "32.79%"
452
462
  #
453
- def percent(decimals=0)
463
+ def percent(decimals=1)
454
464
  "%0.#{decimals}f%%" % (self * 100)
455
465
  end
456
466
 
457
467
  end
458
468
 
459
469
 
470
+ class Rational
471
+
472
+ #
473
+ # Convert the float to a rounded percentage string (eg: "42.5%")
474
+ # (see: Float#percent)
475
+ #
476
+ def percent(decimals=1)
477
+ to_f.percent(decimals)
478
+ end
479
+
480
+ #
481
+ # Alternate initializer (`Rational[1,2]`)
482
+ #
483
+ def self.[](*args)
484
+ Rational(*args)
485
+ end
486
+
487
+ end
488
+
489
+
460
490
  class Prime
461
491
 
462
492
  #
@@ -485,3 +515,4 @@ end
485
515
  def primes
486
516
  Prime.instance
487
517
  end
518
+
@@ -11,6 +11,9 @@ class Object
11
11
  Hash[ vars.zip(vals) ]
12
12
  end
13
13
 
14
+ def write_to(file)
15
+ open(file, "wb") { |f| f.write self.to_s }
16
+ end
14
17
 
15
18
  #
16
19
  # Gives you a copy of the object with its attributes changed to whatever was
@@ -36,7 +39,7 @@ class Object
36
39
  #
37
40
  # Good for chaining lots of operations together in a REPL.
38
41
  #
39
- def with(options={})
42
+ def with(**options)
40
43
  if block_given?
41
44
  yield self
42
45
  else
@@ -14,6 +14,13 @@ class String
14
14
  #
15
15
  STOP_WORDS = %w[a cannot into our thus about co is ours to above could it ourselves together across down its out too after during itself over toward afterwards each last own towards again eg latter per under against either latterly perhaps until all else least rather up almost elsewhere less same upon alone enough ltd seem us along etc many seemed very already even may seeming via also ever me seems was although every meanwhile several we always everyone might she well among everything more should were amongst everywhere moreover since what an except most so whatever and few mostly some when another first much somehow whence any for must someone whenever anyhow former my something where anyone formerly myself sometime whereafter anything from namely sometimes whereas anywhere further neither somewhere whereby are had never still wherein around has nevertheless such whereupon as have next than wherever at he no that whether be hence nobody the whither became her none their which because here noone them while become hereafter nor themselves who becomes hereby not then whoever becoming herein nothing thence whole been hereupon now there whom before hers nowhere thereafter whose beforehand herself of thereby why behind him off therefore will being himself often therein with below his on thereupon within beside how once these without besides however one they would between i only this yet beyond ie onto those you both if or though your but in other through yours by inc others throughout yourself can indeed otherwise thru yourselves]
16
16
 
17
+ #
18
+ # Are there any non-whitespace characters in the string?
19
+ #
20
+ def any?
21
+ not blank?
22
+ end
23
+
17
24
  #
18
25
  # Convert \r\n to \n
19
26
  #
@@ -28,6 +35,14 @@ class String
28
35
  Shellwords.escape(self)
29
36
  end
30
37
 
38
+ #
39
+ # Do what a browser would do when you type something into the address bar
40
+ #
41
+ def urlescape
42
+ @@uri_parser ||= URI::RFC2396_Parser.new
43
+ @@uri_parser.escape(self)
44
+ end
45
+
31
46
  #
32
47
  # Remove redundant whitespaces (not including newlines).
33
48
  #
@@ -115,7 +130,7 @@ class String
115
130
  alias_method :chomp_lines, :each_chomped
116
131
 
117
132
 
118
- def split_at(boundary, options={})
133
+ def split_at(boundary, **options)
119
134
  include_boundary = options[:include_boundary] || false
120
135
 
121
136
  boundary = Regexp.new(Regexp.escape(boundary)) if boundary.is_a?(String)
@@ -269,20 +284,39 @@ class String
269
284
  tr('n-za-mN-ZA-M', 'a-zA-Z')
270
285
  end
271
286
 
287
+ #
288
+ # Cache an `URI::RFC2396_Parser` instance, because it's slowwww to initialize
289
+ #
290
+ def _rfc2396_parser
291
+ @@rfc2396_parser ||= URI::RFC2396_Parser.new
292
+ end
293
+
272
294
  #
273
295
  # Convert non-URI characters into %XXes.
274
296
  #
275
297
  def urlencode
276
- URI.escape(self)
298
+ #URI.escape(self)
299
+ _rfc2396_parser.escape(self)
277
300
  end
278
301
 
279
302
  #
280
303
  # Convert an URI's %XXes into regular characters.
281
304
  #
282
305
  def urldecode
283
- URI.unescape(self)
306
+ _rfc2396_parser.unescape(self)
284
307
  end
285
308
 
309
+ #
310
+ #
311
+ #
312
+ def to_bigdecimal
313
+ BigDecimal
314
+ BigDecimal(self)
315
+ end
316
+ alias_method :to_d, :to_bigdecimal
317
+ alias_method :to_dec, :to_bigdecimal
318
+ alias_method :to_decimal, :to_bigdecimal
319
+
286
320
  #
287
321
  # URI.parse the string and return an URI object
288
322
  #
@@ -495,6 +529,68 @@ class String
495
529
  nums_and_units.map { |num, units| num.send(units) }.sum
496
530
  end
497
531
 
532
+
533
+ #
534
+ # Translate numbers with units (like 25k, 150GB, 15%, 5 hours) into their expanded numeric value
535
+ #
536
+ def parse_units
537
+ # extract the unit suffix
538
+ if self =~ /(\d[\d_]*(?:\.\d+)?)\s*([a-zA-Z]+\b|%(?= \s|$))/
539
+ units = $2.downcase
540
+ num = $1 #.to_f
541
+ num = num["."] ? num.to_f : num.to_i
542
+
543
+ case units
544
+ when "%"
545
+ # 0.01
546
+ num / 100.0
547
+ when "k"
548
+ # 10**3
549
+ num.thousand
550
+ when "m", "mm"
551
+ # 10**6
552
+ num.million
553
+ when "b", "bn"
554
+ # 10**9
555
+ num.billion
556
+ when "gib", "gb", "g"
557
+ num * 2**30
558
+ when "mib", "mb"
559
+ num * 2**20
560
+ when "kib", "kb"
561
+ num * 2**10
562
+ when "t", "tb"
563
+ # 10**12
564
+ num.trillion
565
+ when "q"
566
+ # 10**15
567
+ num.quadrillion
568
+ when "Q"
569
+ # 10**18
570
+ num.quintillion
571
+ when "min"
572
+ # 1.minute
573
+ num.minutes
574
+ when "hours", "h", "hr", "hrs"
575
+ # 1.hour
576
+ num.hours
577
+ when "d", "days", "dy"
578
+ num.days
579
+ else
580
+ raise "Invalid units: #{units.inspect}, in: #{self.inspect}"
581
+ end
582
+ else
583
+ raise "Couldn't find any units to parse! (expecting: '<a number><some letters>')"
584
+ end
585
+ end
586
+
587
+ alias_method :from_units, :parse_units
588
+ alias_method :from_human, :parse_units
589
+ alias_method :from_size, :parse_units
590
+ alias_method :from_percent, :parse_units
591
+ alias_method :from_time, :parse_units
592
+
593
+
498
594
  #
499
595
  # Print a hexdump of the string to STDOUT (coloured, if the terminal supports it)
500
596
  #
@@ -0,0 +1,131 @@
1
+ #
2
+ # A fraction! (Like a Rational, but ... uh ... in pure Ruby!)
3
+ #
4
+ # Can be compared, added, multiplied, simplified, "percent"ed, "to_f"ed, and printed/inspected.
5
+ #
6
+ class Fraction
7
+
8
+ attr_accessor :first, :last
9
+
10
+ alias_method :top, :first
11
+ alias_method :bottom, :last
12
+
13
+ alias_method :numerator, :first
14
+ alias_method :denominator, :last
15
+
16
+ #
17
+ # `first` is the top part of the fraction, `last` is the bottom (eg: `first/last`)
18
+ #
19
+ def initialize(first, last=1)
20
+ @first = first
21
+ @last = last
22
+ end
23
+
24
+ def self.[](*args)
25
+ new(*args)
26
+ end
27
+
28
+ include Comparable
29
+
30
+ def <=>(other)
31
+ to_f <=> other.to_f
32
+ end
33
+
34
+ #
35
+ # Returns a string representation: "a/b"
36
+ #
37
+ def to_s
38
+ "#{@first}/#{@last}"
39
+ end
40
+ alias_method :fraction, :to_s
41
+
42
+ #
43
+ # Returns the fraction as a float. (eg: Fraction[1,2].to_f == 0.5)
44
+ #
45
+ def to_f
46
+ if @last == 0
47
+ raise ZeroDivisionError
48
+ else
49
+ @first.to_f / @last
50
+ end
51
+ end
52
+
53
+ #
54
+ # Returns a string representing the number in percent
55
+ #
56
+ def percent
57
+ "%0.1f%%" % (to_f * 100)
58
+ end
59
+ alias_method :to_percent, :percent
60
+
61
+ #
62
+ # "#<Fraction: 1/2>"
63
+ #
64
+ def inspect
65
+ "#<Fraction: #{to_s}>"
66
+ end
67
+
68
+ #
69
+ # Adds together the tops and bottoms of the fractions.
70
+ #
71
+ # Example: For the fractions `a/c` and `b/d`, returns `a+b/c+d`
72
+ #
73
+ def +(r)
74
+ case r
75
+ when Integer
76
+ self + Fraction[r]
77
+ when Fraction
78
+ Fraction[ r.last*first + r.first*last, r.last*last ]
79
+ else
80
+ raise TypeError.new("Sorry, I can't add a Fraction and a #{r.class}. :(")
81
+ end
82
+ end
83
+
84
+ #
85
+ # Multiply the fractions
86
+ #
87
+ def *(v)
88
+ case v
89
+ when Integer
90
+ Fraction[ v*first, v*last ]
91
+ when Fraction
92
+ Fraction[ v.first*first, v.last*last ]
93
+ else
94
+ raise TypeError.new("I don't know how to multiply a Fraction and a #{v.class}. Sorry. :(")
95
+ end
96
+ end
97
+
98
+ def simplify
99
+ require 'prime'
100
+
101
+ # factor the numerator and denominator into hashes of { factor => exponent } pairs
102
+ n_fact, d_fact = [numerator, denominator].map { |n| Prime.prime_division(n).to_h }
103
+
104
+ # cancel out common factors by subtracting exponents
105
+ d_fact.each do |v, d_exp|
106
+ if n_exp = n_fact[v]
107
+ if n_exp < d_exp
108
+ d_fact[v] = d_exp - n_exp
109
+ n_fact[v] = 0
110
+ else
111
+ n_fact[v] = n_exp - d_exp # <= if n_exp == d_exp, this is 0, which covers the 3rd case
112
+ d_fact[v] = 0
113
+ end
114
+ end
115
+ end
116
+
117
+ # multiply the simplified factors back into full numbers
118
+ simp_n, simp_d = [n_fact, d_fact].map { |h| h.map{ |n, exp| n ** exp }.reduce(:*) }
119
+
120
+ Fraction[simp_n, simp_d]
121
+ end
122
+
123
+ end
124
+
125
+ #####################################################################################
126
+ #
127
+ # Fraction(a,b) is a wrapper for Fraction[a,b]
128
+ #
129
+ def Fraction(*args)
130
+ Fraction.new(*args)
131
+ end
@@ -26,7 +26,7 @@ module Hex
26
26
  :default => 7
27
27
  )
28
28
 
29
- def self.dump(data, options={})
29
+ def self.dump(data, **options)
30
30
  base_offset = options[:base_offset] || 0
31
31
  color = options[:color].nil? ? true : options[:color]
32
32
  highlight = options[:highlight]
@@ -253,7 +253,7 @@ module Kernel
253
253
  #
254
254
  # Global AwesomePrint method (which triggers the loading of AwesomePrint the first time it's called)
255
255
  #
256
- def ap(object, options={})
256
+ def ap(object, **options)
257
257
  AwesomePrint
258
258
  Kernel.ap(object, options)
259
259
  end
@@ -275,13 +275,12 @@ end
275
275
 
276
276
  ####################################################################################
277
277
  #
278
- # Path("/some/path") is an alias for Path["/some/path"]
278
+ # Path("/some/path") is a wrapper for Path["/some/path"]
279
279
  #
280
280
  def Path(arg)
281
281
  Path[arg]
282
282
  end
283
283
 
284
-
285
284
  ####################################################################
286
285
  require 'epitools/autoloads'
287
286
  ####################################################################
data/lib/epitools/path.rb CHANGED
@@ -125,16 +125,17 @@ class Path
125
125
  # Initializers
126
126
  ###############################################################################
127
127
 
128
+ alias_class_method :old_new, :new
128
129
  def self.new(*args)
129
130
  if args.first =~ URI_RE and self != Path::URI
130
131
  Path::URI.new(args.first)
131
132
  else
132
- super(*args)
133
+ old_new(*args)
133
134
  end
134
135
  end
135
136
 
136
- def initialize(newpath, hints={})
137
- send("path=", newpath, hints)
137
+ def initialize(newpath, **hints)
138
+ send("path=", newpath, **hints)
138
139
 
139
140
  # if hints[:unlink_when_garbage_collected]
140
141
  # backup_path = path.dup
@@ -155,8 +156,8 @@ class Path
155
156
  Shellwords.escape(str)
156
157
  end
157
158
 
158
- def self.glob(str, hints={})
159
- Dir[str].map { |entry| new(entry, hints) }
159
+ def self.glob(str, **hints)
160
+ Dir[str].map { |entry| new(entry, **hints) }
160
161
  end
161
162
 
162
163
  def self.[](path)
@@ -194,7 +195,7 @@ class Path
194
195
  #
195
196
  # Note: The `hints` parameter contains options so `path=` doesn't have to touch the filesytem as much.
196
197
  #
197
- def path=(newpath, hints={})
198
+ def path=(newpath, **hints)
198
199
  if hints[:type] or File.exist? newpath
199
200
  if hints[:type] == :dir or File.directory? newpath
200
201
  self.dir = newpath
@@ -816,7 +817,7 @@ class Path
816
817
  # zopen("otherfile.gz", "w") #=> #<Zlib::GzipWriter:0x7fe30448>>
817
818
  # zopen("test.txt.gz") { |f| f.read } # read the contents of the .gz file, then close the file handle automatically.
818
819
  #
819
- def zopen(mode="rb", &block)
820
+ def zopen(mode="rb", **opts, &block)
820
821
  # if ext == "gz"
821
822
  # io = open(mode)
822
823
  # case mode
@@ -829,7 +830,7 @@ class Path
829
830
  # raise "Unknown mode: #{mode.inspect}. zopen only supports 'r' and 'w'."
830
831
  # end
831
832
  # elsif bin = COMPRESSORS[ext]
832
- if bin = COMPRESSORS[ext]
833
+ if bin = (opts[:format] || COMPRESSORS[ext])
833
834
  if which(bin)
834
835
  case mode
835
836
  when "w", "wb"
@@ -868,18 +869,25 @@ class Path
868
869
 
869
870
  end
870
871
 
872
+ def self.zopen(filename, mode, &block)
873
+ Path.new(filename).zopen(mode, &block)
874
+ end
875
+
871
876
  ###############################################################################
872
877
  # Parsing files
873
878
  ###############################################################################
874
879
 
875
880
  #
876
881
  # Parse the file based on the file extension.
877
- # (Handles json, html, yaml, xml, csv, marshal, and bson.)
882
+ # (Handles json, html, yaml, xml, csv, tsv, marshal, and bson.)
883
+ #
884
+ # The "format" option lets you specify the file format (eg: `Path["something.conf"].parse(format: "yaml")`)
885
+ # You can also pass CSV parsing options (eg: `Path["thing.csv"].parse(col_sep: "\t")`)
878
886
  #
879
- def parse(io=self.io, forced_ext=nil, opts={})
880
- case (forced_ext or ext.downcase)
887
+ def parse(io=self.io, **opts)
888
+ case (opts[:format] || ext.downcase)
881
889
  when 'gz', 'bz2', 'xz'
882
- parse(zopen, exts[-2])
890
+ parse(zopen, format: exts[-2])
883
891
  when 'json'
884
892
  read_json(io)
885
893
  when 'html', 'htm'
@@ -889,7 +897,10 @@ class Path
889
897
  when 'xml', 'rdf', 'rss'
890
898
  read_xml(io)
891
899
  when 'csv'
892
- read_csv(io, opts)
900
+ read_csv(io, **opts)
901
+ when 'tsv'
902
+ opts[:col_sep] ||= "\t"
903
+ read_csv(io, **opts)
893
904
  when 'marshal'
894
905
  read_marshal(io)
895
906
  when 'bson'
@@ -920,7 +931,8 @@ class Path
920
931
 
921
932
 
922
933
  def read_html(io=self.io)
923
- Nokogiri::HTML(io)
934
+ #Nokogiri::HTML(io)
935
+ Oga.parse_html(io)
924
936
  end
925
937
  alias_method :from_html, :read_html
926
938
 
@@ -938,14 +950,15 @@ class Path
938
950
 
939
951
 
940
952
  # Parse the file as CSV
941
- def read_csv(io=self.io, opts={})
953
+ def read_csv(io=self.io, **opts)
942
954
  CSV.new(io.read, **opts).each
943
955
  end
944
956
  alias_method :from_csv, :read_csv
945
957
 
946
958
  # Parse the file as XML
947
959
  def read_xml(io=self.io)
948
- Nokogiri::XML(io)
960
+ # Nokogiri::XML(io)
961
+ Oga.parse_xml(io)
949
962
  end
950
963
 
951
964
  # Parse the file as a Ruby Marshal dump
@@ -1024,6 +1037,7 @@ class Path
1024
1037
  dest
1025
1038
  end
1026
1039
  alias_method :ren, :rename
1040
+ alias_method :rename_to, :rename
1027
1041
 
1028
1042
  #
1029
1043
  # Works the same as "rename", but the destination can be on another disk.
@@ -1502,7 +1516,8 @@ class Path
1502
1516
  # TODO: Remove the tempfile when the Path object is garbage collected or freed.
1503
1517
  #
1504
1518
  def self.tmpfile(prefix="tmp")
1505
- path = Path.new(Tempfile.new(prefix).path, unlink_when_garbage_collected: true)
1519
+ # path = Path.new(Tempfile.new(prefix).path, unlink_when_garbage_collected: true)
1520
+ path = Path.new(Tempfile.new(prefix).path)
1506
1521
  yield path if block_given?
1507
1522
  path
1508
1523
  end
@@ -1633,7 +1648,7 @@ class Path::URI < Path
1633
1648
  # TODO: only include certain methods from Path (delegate style)
1634
1649
  # (eg: remove commands that write)
1635
1650
 
1636
- def initialize(uri, hints={})
1651
+ def initialize(uri, **hints)
1637
1652
  @uri = ::URI.parse(uri)
1638
1653
  self.path = @uri.path
1639
1654
  end
@@ -67,7 +67,7 @@ end
67
67
 
68
68
 
69
69
 
70
- def color_backtrace_2(lines, options={})
70
+ def color_backtrace_2(lines, **options)
71
71
 
72
72
  groups = lines.reverse.split_at { |line,nextline| line.path != nextline.path }
73
73
 
data/lib/epitools/rash.rb CHANGED
@@ -11,7 +11,7 @@ class Rash
11
11
 
12
12
  attr_accessor :optimize_every
13
13
 
14
- def initialize(initial={})
14
+ def initialize(initial)
15
15
  @hash = {}
16
16
  @regexes = []
17
17
  @ranges = []
data/lib/epitools/term.rb CHANGED
@@ -29,26 +29,62 @@ module Term
29
29
  # Return the [width,height] of the terminal.
30
30
  #
31
31
  def size
32
- $stdout.winsize.reverse
32
+ $stdout.winsize.reverse rescue [80,25]
33
33
  end
34
34
 
35
35
  def width; size[0]; end
36
36
  def height; size[1]; end
37
- def goto(x,y); @x, @y = x, y; end
38
- def pos; [@x, @y]; end
37
+ # def goto(x,y); @x, @y = x, y; end
38
+ # def pos; [@x, @y]; end
39
39
 
40
- def clear
41
- print "\e[H\e[J"
42
- end
43
40
 
41
+ ##################################################################################
42
+ ### ANSI Stuff (see: ttps://en.wikipedia.org/wiki/ANSI_escape_code)
43
+ ##################################################################################
44
+
45
+ ##################################################################################
46
+ ## <n>K = Clear (part of) the line
47
+ ##################################################################################
48
+
49
+ # 2 = clear entire line
44
50
  def clear_line
45
51
  print "\e[2K"
46
52
  end
47
53
 
54
+ # 0 = clear to end of line
48
55
  def clear_eol
49
56
  print "\e[0K"
50
57
  end
51
58
 
59
+ ##################################################################################
60
+ ## <n>J = Clear (part of) the screen.
61
+ ##################################################################################
62
+
63
+ def clear
64
+ # If n is 2, clear entire screen (and moves cursor to upper left on DOS ANSI.SYS).
65
+ print "\e[2J\e[H"
66
+ end
67
+
68
+ def clear_all_above
69
+ # If n is 1, clear from cursor to beginning of the screen.
70
+ print "\e[1J"
71
+ end
72
+
73
+ def clear_all_below
74
+ # If n is 0 (or missing), clear from cursor to end of screen.
75
+ print "\e[0J"
76
+ end
77
+
78
+ def clear_scrollback_buffer!
79
+ # If n is 3, clear entire screen and delete all lines saved in the scrollback buffer (this feature was added for xterm and is supported by other terminal applications).
80
+ print "\e[3J"
81
+ end
82
+
83
+
84
+ ##################################################################################
85
+ ## <n>;<m>H = Move!
86
+ ##################################################################################
87
+
52
88
  def move_to(row: 1, col: 1)
53
89
  print "\e[#{row};#{col}H"
54
90
  end
@@ -82,11 +118,15 @@ module Term
82
118
  @back = back if back
83
119
  end
84
120
 
85
- def puts(s)
86
- # some curses shit
87
- end
88
-
121
+ #
122
+ # curses-style scrollable terminal window
123
+ #
89
124
  class Window
125
+
126
+ # work in progress. probably requires an event loop and a higher order container for having multiple windows and a text-input and stuff.
127
+
128
+ attr_accessor :wrap
129
+
90
130
  def initialize
91
131
  end
92
132
 
@@ -94,21 +134,39 @@ module Term
94
134
  end
95
135
  end
96
136
 
137
+
97
138
  class Table
98
139
 
99
140
  # TODO:
100
141
  #
101
142
  # * make Table's configuration eaiser to remember by putting the formatting parameters in initialize
102
143
  # eg: Table.new(elements, :sort=>:vertical).to_s
144
+ # * strip ansi
145
+ # * wrap contents
146
+ # * rounded corners
147
+ # * [far future] dynamic sortable filterable toggleable table
103
148
  #
104
149
 
105
150
  attr_accessor :border, :columns, :padding, :strip_color, :indent, :width, :height
106
151
 
107
- def self.[](data)
108
- self.new(data)
152
+ def self.print(thing, **opts)
153
+ raise "Can't tablize a #{thing.class}" unless thing.class < Enumerable
154
+ puts new(thing, **opts).display
155
+ end
156
+
157
+ def self.hprint(thing)
158
+ puts new(thing).in_rows
159
+ end
160
+
161
+ def self.vprint(thing)
162
+ puts new(thing).in_columns
163
+ end
164
+
165
+ def self.[](data, **opts)
166
+ new(data, **opts)
109
167
  end
110
168
 
111
- def initialize(data, options={})
169
+ def initialize(data, **options)
112
170
  @data = data.map(&:to_s)
113
171
  @strip_color = options[:ansi] || options[:colorized] || options[:colored] || options[:strip_color] || options[:strip_ansi]
114
172
 
@@ -185,7 +243,7 @@ module Term
185
243
  end
186
244
  alias_method :by_rows, :in_rows
187
245
 
188
- def display #(opts={})
246
+ def display #(**opts)
189
247
  case @direction
190
248
  when :horizontal
191
249
  puts in_rows
@@ -198,7 +256,7 @@ module Term
198
256
  by_rows
199
257
  end
200
258
 
201
- def render(rows, options={})
259
+ def render(rows, **options)
202
260
  num_cols = rows.first.size
203
261
  result = []
204
262
 
@@ -404,6 +404,15 @@ describe String do
404
404
  ]
405
405
  end
406
406
 
407
+ it "parses units" do
408
+ "50%".parse_units.should == 0.5
409
+ "1b".parse_units.should == 1.billion
410
+ "1gb".parse_units.should == 2**30
411
+
412
+ lambda { "whee".parse_units }.should raise_error
413
+ lambda { "57 butts".parse_units }.should raise_error
414
+ end
415
+
407
416
  end
408
417
 
409
418
 
@@ -491,15 +500,30 @@ describe Float do
491
500
  end
492
501
  end
493
502
 
494
- it "percent" do
503
+ it "percents" do
495
504
  f = 0.716237
496
- f.percent.should == "72%"
505
+ f.percent.should == "71.6%"
506
+ f.percent(0).should == "72%"
507
+ f.percent(1).should == "71.6%"
497
508
  f.percent(2).should == "71.62%"
498
509
  f.percent(3).should == "71.624%"
499
510
  end
500
511
 
501
512
  end
502
513
 
514
+ describe Rational do
515
+
516
+ it "makes em" do
517
+ lambda { Rational[1,2] }.should_not raise_error
518
+ end
519
+
520
+ it "percents" do
521
+ Rational(1,2).percent.should == "50.0%"
522
+ Rational(1,3).percent(3).should == "33.333%"
523
+ end
524
+
525
+ end
526
+
503
527
  describe Number do
504
528
 
505
529
  it "number?" do
@@ -0,0 +1,53 @@
1
+ require 'epitools/fraction'
2
+
3
+ describe Fraction do
4
+
5
+ # before :each do
6
+ # @a = Fraction[1,1]
7
+ # @b = Fraction[1,2]
8
+ # end
9
+
10
+ it "adds normally" do
11
+ ( Fraction[1,1] + Fraction[1,2] ).should == Fraction[3,2]
12
+ end
13
+
14
+ it "doesn't let you add weird stuff together" do
15
+ -> { Fraction[1,2] + :splunge }.should raise_error(TypeError)
16
+ end
17
+
18
+ it "timeses normally" do
19
+ ( Fraction[1,1] * 2 ).should == Fraction[2,2]
20
+ ( Fraction[1,2] * 2 ).should == Fraction[2,4]
21
+ ( Fraction[1,1] * Fraction[1,2] ).should == Fraction[1,2]
22
+ ( Fraction[5,3] * Fraction[1,2] ).should == Fraction[5*1, 2*3]
23
+ ( Fraction[5,3] * Fraction[1,2] ).should == Fraction[5, 6]
24
+ ( Fraction[1,2] * Fraction[1,2] ).should == Fraction[1, 4]
25
+ end
26
+
27
+ it "doesn't let you times it with weird stuff" do
28
+ -> { Fraction[1,2] * ([:ayeeee]*100) }.should raise_error(TypeError)
29
+ end
30
+
31
+ it "floats" do
32
+ Fraction[1,1].to_f.should == 1.0
33
+ Fraction[1,2].to_f.should == 0.5
34
+
35
+ -> { Fraction[1,0].to_f }.should raise_error(ZeroDivisionError)
36
+ end
37
+
38
+ it "percents" do
39
+ Fraction[1,1].percent.should == "100.0%"
40
+ Fraction[1,2].percent.should == "50.0%"
41
+ end
42
+
43
+ it "simplifies" do
44
+ Fraction[2,4].simplify.should == Fraction[1,2]
45
+ Fraction[4,2].simplify.should == Fraction[2,1]
46
+ end
47
+
48
+ it "has a function-style wrapper! (for paren fans)" do
49
+ Fraction(1,2).should == Fraction[1,2]
50
+ Fraction(1,2).should == Fraction.new(1,2)
51
+ end
52
+
53
+ end
@@ -7,17 +7,14 @@ describe Numeric do
7
7
  10 => "ten",
8
8
  3_123 => "three thousand, one-hundred and twenty-three",
9
9
  123_124 => "one-hundred and twenty-three thousand, one-hundred and twenty-four",
10
-
11
10
  8_128_937_981_273_987_129_837_174_612_897_638_613 => "eight undecillion, one-hundred and twenty-eight decillion, nine-hundred and thirty-seven nonillion, nine-hundred and eighty-one octillion, two-hundred and seventy-three septillion, nine-hundred and eighty-seven sextillion, one-hundred and twenty-nine quintillion, eight-hundred and thirty-seven quadrillion, one-hundred and seventy-four trillion, six-hundred and twelve billion, eight-hundred and ninety-seven million, six-hundred and thirty-eight thousand, six-hundred and thirteen",
12
-
13
- 3_486_597_230_495_871_304_981_320_498_123_498_263_984_739_841_834_091_823_094_812_039_481_231_623_987_461_293_874_698_123_649_817_236 => "three duotrigintillion, four-hundred and eighty-six untrigintillion, five-hundred and ninety-seven trigintillion, two-hundred and thirty novemvigintillion, four-hundred and ninety-five octovigintillion, eight-hundred and seventy-one septenvigintillion, three-hundred and four sexvigintillion, nine-hundred and eighty-one quinvigintillion, three-hundred and twenty quattuorvigintillion, four-hundred and ninety-eight trevigintillion, one-hundred and twenty-three duovigintillion, four-hundred and ninety-eight unvigintillion, two-hundred and sixty-three vigintillion, nine-hundred and eighty-four novemdecillion, seven-hundred and thirty-nine octodecillion, eight-hundred and fourty-one septendecillion, eight-hundred and thirty-four sexdecillion, ninety-one quindecillion, eight-hundred and twenty-three quattuordecillion, ninety-four tredecillion, eight-hundred and twelve duodecillion, thirty-nine undecillion, four-hundred and eighty-one decillion, two-hundred and thirty-one nonillion, six-hundred and twenty-three octillion, nine-hundred and eighty-seven septillion, four-hundred and sixty-one sextillion, two-hundred and ninety-three quintillion, eight-hundred and seventy-four quadrillion, six-hundred and ninety-eight trillion, one-hundred and twenty-three billion, six-hundred and fourty-nine million, eight-hundred and seventeen thousand, two-hundred and thirty-six",
14
-
11
+ 3_486_597_230_495_871_304_981_320_498_123_498_263_984_739_841_834_091_823_094_812_039_481_231_623_987_461_293_874_698_123_649_817_236 => "three duotrigintillion, four-hundred and eighty-six untrigintillion, five-hundred and ninety-seven trigintillion, two-hundred and thirty novemvigintillion, four-hundred and ninety-five octovigintillion, eight-hundred and seventy-one septenvigintillion, three-hundred and four sexvigintillion, nine-hundred and eighty-one quinvigintillion, three-hundred and twenty quattuorvigintillion, four-hundred and ninety-eight trevigintillion, one-hundred and twenty-three duovigintillion, four-hundred and ninety-eight unvigintillion, two-hundred and sixty-three vigintillion, nine-hundred and eighty-four novemdecillion, seven-hundred and thirty-nine octodecillion, eight-hundred and forty-one septendecillion, eight-hundred and thirty-four sexdecillion, ninety-one quindecillion, eight-hundred and twenty-three quattuordecillion, ninety-four tredecillion, eight-hundred and twelve duodecillion, thirty-nine undecillion, four-hundred and eighty-one decillion, two-hundred and thirty-one nonillion, six-hundred and twenty-three octillion, nine-hundred and eighty-seven septillion, four-hundred and sixty-one sextillion, two-hundred and ninety-three quintillion, eight-hundred and seventy-four quadrillion, six-hundred and ninety-eight trillion, one-hundred and twenty-three billion, six-hundred and forty-nine million, eight-hundred and seventeen thousand, two-hundred and thirty-six",
15
12
  1763241823498172490817349807213409238409123409128340981234781236487126348791263847961238794612839468917236489712364987162398746129834698172364987123 => "more than a googol! (148 digits)"
16
13
  }.each do |num, result|
17
14
  num.to_words.should == result
18
15
  end
19
16
 
20
- lambda{ 1.523.million.billion.to_words }.should_not raise_error
17
+ lambda { 1.523.million.billion.to_words }.should_not raise_error
21
18
  end
22
19
 
23
20
  it "has .thousand, .million, etc." do
data/spec/path_spec.rb CHANGED
@@ -147,7 +147,9 @@ describe Path do
147
147
  data = "<h1>The best webpage in the universe.</h1>"
148
148
  html = Path.tmpfile
149
149
  html.write data
150
- html.read_html.at("h1").to_s.should == data
150
+
151
+ # html.read_html.at("h1").to_s.should == data
152
+ html.read_html.at_css("h1").to_s.should == data
151
153
 
152
154
  ensure
153
155
  yaml.rm
@@ -243,7 +245,7 @@ describe Path do
243
245
 
244
246
  old_name = path.to_s
245
247
 
246
- dest = path.rename(:ext=>".dat")
248
+ dest = path.rename(path.with(ext: ".dat"))
247
249
 
248
250
  dest.to_s.should == old_name+".dat"
249
251
  path.to_s.should == old_name
@@ -253,10 +255,10 @@ describe Path do
253
255
  path.exists?.should == false
254
256
 
255
257
  path.touch
256
- lambda { path.rename(:ext=>".dat") }.should raise_error(RuntimeError)
258
+ lambda { path.rename(path.with(ext: ".dat")) }.should raise_error(RuntimeError)
257
259
 
258
260
  dest.rm
259
- path.rename!(:ext=>".dat")
261
+ path.rename!(path.with(ext: ".dat"))
260
262
  path.to_s.should_not == old_name
261
263
  path.exists?.should == true
262
264
 
@@ -413,6 +415,14 @@ describe Path do
413
415
 
414
416
  # system("gzip -c < #{tmpjson} > #{tmpjson}.gz")
415
417
 
418
+ # class method version
419
+ Path.zopen("#{tmpjson}.gz", "w") { |io| io.write(JSON.dump(hash)) }
420
+ tmpgzip = Path["#{tmpjson}.gz"]
421
+ tmpgzip.exists?.should == true
422
+ tmpgzip.size.should be > 0
423
+ tmpgzip&.rm
424
+
425
+ # instance method version
416
426
  tmpgzip = Path["#{tmpjson}.gz"]
417
427
  tmpgzip.zopen("w") { |io| io.write(JSON.dump(hash)) }
418
428
  tmpgzip.exists?.should == true
data/spec/sys_spec.rb CHANGED
@@ -30,6 +30,11 @@ describe Sys::ProcessInfo do
30
30
  if p2 = p2s[p1.pid]
31
31
  matches += 1
32
32
  p1.command.should == p2.command
33
+
34
+ # FIXME: this behaves weirdly with kernel processes, eg:
35
+ # expected: "[kworker/u8:1-phy5]"
36
+ # got: "[kworker/u8:1-events_unbound]" (using ==)
37
+
33
38
  end
34
39
  end
35
40
 
data/spec/zopen_spec.rb CHANGED
@@ -1,49 +1,49 @@
1
- require 'epitools/zopen'
2
- require 'tempfile'
1
+ # require 'epitools/zopen'
2
+ # require 'tempfile'
3
3
 
4
- describe "zopen()" do
4
+ # describe "zopen()" do
5
5
 
6
- before :all do
7
- @data = ("x"*100+"\n") * 1000
8
- @tmp = Tempfile.new("zopen_spec")
6
+ # before :all do
7
+ # @data = ("x"*100+"\n") * 1000
8
+ # @tmp = Tempfile.new("zopen_spec")
9
9
 
10
- @plainfile = @tmp.path
11
- @gzfile = "#{@tmp.path}.gz"
12
- end
10
+ # @plainfile = @tmp.path
11
+ # @gzfile = "#{@tmp.path}.gz"
12
+ # end
13
13
 
14
- after :all do
15
- File.unlink @plainfile
16
- File.unlink @gzfile
17
- end
14
+ # after :all do
15
+ # File.unlink @plainfile
16
+ # File.unlink @gzfile
17
+ # end
18
18
 
19
- it "writes/reads a gz" do
20
- f = zopen(@gzfile, "w")
21
- f.write(@data).should == @data.size
22
- f.close
19
+ # it "writes/reads a gz" do
20
+ # f = zopen(@gzfile, "w")
21
+ # f.write(@data).should == @data.size
22
+ # f.close
23
23
 
24
- f = zopen(@gzfile, "r")
25
- f.read.should == @data
26
- f.close
27
- end
24
+ # f = zopen(@gzfile, "r")
25
+ # f.read.should == @data
26
+ # f.close
27
+ # end
28
28
 
29
- it "writes/reads non-gz files" do
30
- zopen(@plainfile, "w") {|f| f.write(@data) }
29
+ # it "writes/reads non-gz files" do
30
+ # zopen(@plainfile, "w") {|f| f.write(@data) }
31
31
 
32
- # readstyle
33
- File.read(@plainfile).should == zopen(@plainfile).read
32
+ # # readstyle
33
+ # File.read(@plainfile).should == zopen(@plainfile).read
34
34
 
35
- # blockstyle
36
- open(@plainfile){|f| f.read }.should == zopen(@plainfile){|f| f.read }
37
- end
35
+ # # blockstyle
36
+ # open(@plainfile){|f| f.read }.should == zopen(@plainfile){|f| f.read }
37
+ # end
38
38
 
39
- it "is enumerable" do
40
- zopen(@gzfile) do |f|
41
- f.respond_to?(:each).should == true
42
- f.respond_to?(:map).should == true
43
- f.respond_to?(:inject).should == true
39
+ # it "is enumerable" do
40
+ # zopen(@gzfile) do |f|
41
+ # f.respond_to?(:each).should == true
42
+ # f.respond_to?(:map).should == true
43
+ # f.respond_to?(:inject).should == true
44
44
 
45
- f.all?{|line| line =~ /^x+$/ }
46
- end
47
- end
45
+ # f.all?{|line| line =~ /^x+$/ }
46
+ # end
47
+ # end
48
48
 
49
- end
49
+ # end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: epitools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.134
4
+ version: 0.5.136
5
5
  platform: ruby
6
6
  authors:
7
7
  - epitron
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-31 00:00:00.000000000 Z
11
+ date: 2023-03-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -69,6 +69,7 @@ files:
69
69
  - lib/epitools/core_ext/truthiness.rb
70
70
  - lib/epitools/core_ext/uri.rb
71
71
  - lib/epitools/daemonize.rb
72
+ - lib/epitools/fraction.rb
72
73
  - lib/epitools/gem_ext/oga.rb
73
74
  - lib/epitools/hexdump.rb
74
75
  - lib/epitools/iter.rb
@@ -86,7 +87,6 @@ files:
86
87
  - lib/epitools/progressbar.rb
87
88
  - lib/epitools/rails.rb
88
89
  - lib/epitools/rash.rb
89
- - lib/epitools/ratio.rb
90
90
  - lib/epitools/semantic_version.rb
91
91
  - lib/epitools/slop.rb
92
92
  - lib/epitools/slop/LICENSE
@@ -105,14 +105,16 @@ files:
105
105
  - lib/epitools/wm.rb
106
106
  - spec/.rspec
107
107
  - spec/autoreq_spec.rb
108
- - spec/browser_spec.rb
109
108
  - spec/btree_spec.rb
110
109
  - spec/clitools_spec.rb
111
110
  - spec/colored_spec.rb
112
111
  - spec/core_ext_spec.rb
112
+ - spec/fraction_spec.rb
113
113
  - spec/histogram_spec.rb
114
114
  - spec/iter_spec.rb
115
115
  - spec/lcs_spec.rb
116
+ - spec/manual/browser_spec.rb
117
+ - spec/manual/wm_spec.rb
116
118
  - spec/numwords_spec.rb
117
119
  - spec/path_spec.rb
118
120
  - spec/permutations_spec.rb
@@ -122,7 +124,6 @@ files:
122
124
  - spec/sys_spec.rb
123
125
  - spec/term_spec.rb
124
126
  - spec/typed_struct_spec.rb
125
- - spec/wm_spec.rb
126
127
  - spec/zopen_spec.rb
127
128
  homepage: http://github.com/epitron/epitools
128
129
  licenses:
@@ -143,7 +144,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
143
144
  - !ruby/object:Gem::Version
144
145
  version: '0'
145
146
  requirements: []
146
- rubygems_version: 3.2.21
147
+ rubygems_version: 3.3.25
147
148
  signing_key:
148
149
  specification_version: 3
149
150
  summary: Not utils... METILS!
@@ -1,78 +0,0 @@
1
- #
2
- # The ratio between two numbers (eg: 2:1, 3:4)
3
- #
4
- # Can be compared, added, "percent"ed, "to_f"ed, and displayed.
5
- #
6
- class Ratio
7
-
8
- include Comparable
9
-
10
- def <=>(other)
11
- to_f <=> other.to_f
12
- end
13
-
14
- attr_accessor :first, :last
15
-
16
- def self.[](*args)
17
- new(*args)
18
- end
19
-
20
- #
21
- # `first` is the top part of the ratio, `last` is the bottom (eg: `first/last`)
22
- #
23
- def initialize(first, last=1)
24
- @first = first
25
- @last = last
26
- end
27
-
28
- #
29
- # Returns a string representation: "a/b"
30
- #
31
- def to_s
32
- "#{@first}/#{@last}"
33
- end
34
- alias_method :ratio, :to_s
35
-
36
- #
37
- # Returns the ratio as a float. (eg: Ratio[1,2].to_f == 0.5)
38
- #
39
- def to_f
40
- if @last == 0
41
- 0.0
42
- else
43
- @first.to_f / @last
44
- end
45
- end
46
-
47
- #
48
- # Returns a string representing the number in percent
49
- #
50
- def percent
51
- "%0.1f%%" % (to_f * 100)
52
- end
53
- alias_method :to_percent, :percent
54
-
55
- #
56
- # "#<Ratio: 1/2>"
57
- #
58
- def inspect
59
- "#<Ratio: #{to_s}>"
60
- end
61
-
62
- #
63
- # Adds together the tops and bottoms of the ratios.
64
- #
65
- # Example: For the ratios `a/c` and `b/d`, returns `a+b/c+d`
66
- #
67
- def +(other)
68
- Ratio.new( first+other.first, last+other.last)
69
- end
70
-
71
- end
72
-
73
- #
74
- # Function-style wrapper
75
- #
76
- def Ratio(*args)
77
- Ratio.new(*args)
78
- end
File without changes
File without changes