riel 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/README +0 -0
  2. data/lib/riel/ansicolor.rb +93 -0
  3. data/lib/riel/array.rb +20 -0
  4. data/lib/riel/command.rb +30 -0
  5. data/lib/riel/date.rb +16 -0
  6. data/lib/riel/dir.rb +90 -0
  7. data/lib/riel/enumerable.rb +66 -0
  8. data/lib/riel/env.rb +49 -0
  9. data/lib/riel/file.rb +212 -0
  10. data/lib/riel/filetype.rb +189 -0
  11. data/lib/riel/hash.rb +12 -0
  12. data/lib/riel/io.rb +20 -0
  13. data/lib/riel/log.rb +548 -0
  14. data/lib/riel/matchdata.rb +13 -0
  15. data/lib/riel/optproc.rb +369 -0
  16. data/lib/riel/pathname.rb +16 -0
  17. data/lib/riel/rcfile.rb +35 -0
  18. data/lib/riel/regexp.rb +152 -0
  19. data/lib/riel/setdiff.rb +53 -0
  20. data/lib/riel/size_converter.rb +62 -0
  21. data/lib/riel/string.rb +81 -0
  22. data/lib/riel/tempfile.rb +28 -0
  23. data/lib/riel/text.rb +408 -0
  24. data/lib/riel/timer.rb +52 -0
  25. data/lib/riel.rb +13 -0
  26. data/test/riel/array_test.rb +22 -0
  27. data/test/riel/command_test.rb +28 -0
  28. data/test/riel/date_test.rb +17 -0
  29. data/test/riel/dir_test.rb +98 -0
  30. data/test/riel/enumerable_test.rb +27 -0
  31. data/test/riel/env_test.rb +52 -0
  32. data/test/riel/file_test.rb +242 -0
  33. data/test/riel/filetype_test.rb +32 -0
  34. data/test/riel/hash_test.rb +12 -0
  35. data/test/riel/io_test.rb +22 -0
  36. data/test/riel/log_test.rb +184 -0
  37. data/test/riel/matchdata_test.rb +15 -0
  38. data/test/riel/optproc_test.rb +233 -0
  39. data/test/riel/pathname_test.rb +36 -0
  40. data/test/riel/rcfile_test.rb +44 -0
  41. data/test/riel/regexp_test.rb +24 -0
  42. data/test/riel/setdiff_test.rb +26 -0
  43. data/test/riel/size_converter_test.rb +64 -0
  44. data/test/riel/string_test.rb +58 -0
  45. data/test/riel/tempfile_test.rb +16 -0
  46. data/test/riel/text_test.rb +102 -0
  47. data/test/riel/timer_test.rb +43 -0
  48. metadata +134 -0
@@ -0,0 +1,369 @@
1
+ #!/usr/bin/ruby -w
2
+ # -*- ruby -*-
3
+
4
+ require 'riel/env'
5
+ require 'riel/log'
6
+ require 'riel/text'
7
+ require 'riel/enumerable'
8
+
9
+
10
+ module OptProc
11
+
12
+ class Option
13
+ include Loggable
14
+
15
+ attr_reader :md, :tags, :res
16
+
17
+ ARG_INTEGER = %r{^ ([\-\+]?\d+) $ }x
18
+ ARG_FLOAT = %r{^ ([\-\+]?\d* (?:\.\d+)?) $ }x
19
+ ARG_STRING = %r{^ [\"\']? (.*?) [\"\']? $ }x
20
+ ARG_BOOLEAN = %r{^ (yes|true|on|no|false|off) $ }ix
21
+
22
+ ARG_TYPES = Array.new
23
+ ARG_TYPES << [ :integer, ARG_INTEGER ]
24
+ ARG_TYPES << [ :float, ARG_FLOAT ]
25
+ ARG_TYPES << [ :string, ARG_STRING ]
26
+ ARG_TYPES << [ :boolean, ARG_BOOLEAN ]
27
+
28
+ def initialize(args = Hash.new, &blk)
29
+ @tags = args[:tags] || Array.new
30
+ @rc = args[:rc]
31
+ @rc = [ @rc ] if @rc.kind_of?(String)
32
+ @md = nil
33
+ @set = blk || args[:set]
34
+
35
+ @type = nil
36
+ @valuere = nil
37
+
38
+ @argtype = nil
39
+
40
+ @res = args[:res]
41
+ @res = [ @res ] if @res.kind_of?(Regexp)
42
+
43
+ if args[:arg]
44
+ # log { "args.class: #{args[:arg].class}" }
45
+ demargs = args[:arg].dup
46
+ while arg = demargs.shift
47
+ # log { "arg: #{arg}" }
48
+ case arg
49
+ when :required
50
+ @type = "required"
51
+ when :optional
52
+ @type = "optional"
53
+ when :none
54
+ @type = nil
55
+ when :regexp
56
+ @valuere = demargs.shift
57
+ else
58
+ if re = ARG_TYPES.assoc(arg)
59
+ # log { "re: #{re}" }
60
+ @valuere = re[1]
61
+ @argtype = arg
62
+ @type ||= "required"
63
+ else
64
+ # log { "no expression for arg #{arg}" }
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ # log { "valuere: #{@valuere}" }
71
+ # log { "type: #{@type}" }
72
+ end
73
+
74
+ def inspect
75
+ '[' + @tags.collect { |t| t.inspect }.join(" ") + ']'
76
+ end
77
+
78
+ def to_str
79
+ to_s
80
+ end
81
+
82
+ def to_s
83
+ @tags.join(" ")
84
+ end
85
+
86
+ def match_rc?(field)
87
+ @rc && @rc.include?(field)
88
+ end
89
+
90
+ def match_value(val)
91
+ # log { "valuere: #{@valuere.inspect}; val: #{val}" }
92
+ @md = @valuere && @valuere.match(val)
93
+ # log { "md: #{@md.inspect}" }
94
+ @md && @md[1]
95
+ end
96
+
97
+ def match_tag(tag)
98
+ stack { "@rc: #{@rc.inspect}; @tags: #{@tags.inspect}" }
99
+
100
+ if tm = @tags.detect do |t|
101
+ log { "t: #{t}; tag: #{tag}; idx: #{t.index(tag)}" }
102
+ t.index(tag) == 0 && tag.length <= t.length
103
+ end
104
+
105
+ log { "tm: #{tm}" }
106
+ if tag.length == tm.length
107
+ 1.0
108
+ else
109
+ len = tag.length.to_f * 0.01 # / tm.length
110
+ log { "len: #{len}" }
111
+ len
112
+ end
113
+ else
114
+ nil
115
+ end
116
+ end
117
+
118
+ def match(args, opt = args[0])
119
+ return nil unless %r{^-}.match(opt)
120
+
121
+ # log { "opt: #{opt.inspect}; args: #{args.inspect}" }
122
+ # log { "@rc: #{@rc.inspect}; @re: #{@re.inspect}; @tags: #{@tags.inspect}" }
123
+
124
+ tag, val = opt.split('=', 2)
125
+ tag ||= opt
126
+
127
+ # log { "opt: #{opt}; opt: #{opt.class}; tag: #{tag}; tags: #{@tags.inspect}" }
128
+ # log { "res: #{@res.inspect}" }
129
+
130
+ @md = nil
131
+
132
+ if @res && (@md = @res.collect { |re| re.match(opt) }.detect)
133
+ # log { "matched: #{@md}" }
134
+ 1.0
135
+ else
136
+ match_tag(tag)
137
+ end
138
+ end
139
+
140
+ def set_value(args, opt = args[0])
141
+ tag, val = opt.split('=', 2)
142
+ args.shift
143
+
144
+ # log { "opt : #{opt}" }
145
+ # log { "tag : #{tag}" }
146
+ # log { "tags: #{@tags.inspect}" }
147
+ # log { "val : #{val.inspect}" }
148
+ # log { "md : #{@md.inspect}" }
149
+
150
+ if @md
151
+ # log { "already have match data" }
152
+ elsif @type == "required"
153
+ if val
154
+ # already have value
155
+ # log { "already have value: #{val}" }
156
+ elsif args.size > 0
157
+ val = args.shift
158
+ # log { "got next value: #{val}" }
159
+ else
160
+ $stderr.puts "value expected"
161
+ end
162
+
163
+ if val
164
+ match_value(val)
165
+ end
166
+ elsif @type == "optional"
167
+ if val
168
+ # log { "already have value: #{val}" }
169
+ match_value(val)
170
+ elsif args.size > 0
171
+ if %r{^-}.match(args[0])
172
+ # log { "skipping next value; apparently option" }
173
+ elsif match_value(args[0])
174
+ # log { "value matches: #{val}" }
175
+ args.shift
176
+ else
177
+ # log { "value does not match" }
178
+ end
179
+ end
180
+ else
181
+ # log { "no type" }
182
+ end
183
+
184
+ value = value_from_match
185
+
186
+ set(value, opt, args)
187
+ end
188
+
189
+ def value_from_match
190
+ if @md
191
+ if @argtype.nil? || @argtype == :regexp
192
+ @md
193
+ else
194
+ convert_value(@md[1])
195
+ end
196
+ elsif @argtype == :boolean
197
+ true
198
+ end
199
+ end
200
+
201
+ def convert_value(val)
202
+ if val
203
+ case @argtype
204
+ when :string
205
+ val
206
+ when :integer
207
+ val.to_i
208
+ when :float
209
+ val.to_f
210
+ when :boolean
211
+ to_boolean(val)
212
+ when :regexp
213
+ val
214
+ when nil
215
+ val
216
+ else
217
+ log { "unknown argument type: #{@type.inspect}" }
218
+ end
219
+ elsif @argtype == :boolean
220
+ true
221
+ end
222
+ end
223
+
224
+ def to_boolean(val)
225
+ %w{ yes true on soitenly }.include?(val.downcase)
226
+ end
227
+
228
+ def set(val, opt = nil, args = nil)
229
+ # log { "argtype: #{@argtype}; md: #{@md.inspect}" }
230
+
231
+ setargs = [ val, opt, args ].select_with_index { |x, i| i < @set.arity }
232
+ # log "val: #{val}"
233
+ @set.call(*setargs)
234
+ end
235
+ end
236
+
237
+
238
+ class OptionSet
239
+ include Loggable
240
+
241
+ attr_reader :options
242
+
243
+ def initialize(data)
244
+ @options = Array.new
245
+ @shortopts = Array.new
246
+ @longopts = Array.new
247
+ @regexps = Hash.new
248
+
249
+ data.each do |optdata|
250
+ opt = OptProc::Option.new(optdata)
251
+ @options << opt
252
+
253
+ opt.tags.each do |tag|
254
+ ch = tag[0]
255
+ if ch == 45 # 45 = '-'
256
+ ch = tag[1]
257
+ assocopts = nil
258
+ if ch == tag
259
+ ch = tag[2]
260
+ assocopts = @longopts
261
+ else
262
+ assocopts = @shortopts
263
+ end
264
+
265
+ (assocopts[ch] ||= Array.new) << opt
266
+ end
267
+
268
+ if res = opt.res
269
+ res.each do |re|
270
+ (@regexps[re] ||= Array.new) << opt
271
+ end
272
+ end
273
+ end
274
+ end
275
+
276
+ if false
277
+ [ @longopts, @shortopts ].each do |list|
278
+ list.each_with_index do |v, idx|
279
+ log { "#{idx} => #{v.inspect}" }
280
+ end
281
+ end
282
+ [ @regexps ].each do |map|
283
+ map.each do |k, v|
284
+ log { "#{k} => #{v.inspect}" }
285
+ end
286
+ end
287
+ end
288
+
289
+ end
290
+
291
+ COMBINED_OPTS_RES = [
292
+ # -number non-num, then anything
293
+ Regexp.new('^ ( - \d+ ) ( \D+.* ) $ ', Regexp::EXTENDED),
294
+ # -letter anything
295
+ Regexp.new('^ ( - [a-z] ) ( .+ ) $ ', Regexp::EXTENDED)
296
+ ]
297
+
298
+ def process_option(args)
299
+ opt = args[0]
300
+
301
+ # log { "processing option #{opt}" }
302
+
303
+ if md = COMBINED_OPTS_RES.collect { |re| re.match(opt) }.detect
304
+ lhs = md[1]
305
+ rhs = "-" + md[2]
306
+
307
+ # log { "lhs, rhs: #{lhs.inspect}, #{rhs.inspect}" }
308
+
309
+ args[0, 1] = lhs, rhs
310
+
311
+ return process_option(args)
312
+ elsif opt[0] == 45
313
+ ch = opt[1]
314
+ assocopts = if ch == 45 # 45 = '-'
315
+ ch = opt[2]
316
+ @longopts[ch]
317
+ elsif ch.nil?
318
+ nil
319
+ else
320
+ @shortopts[ch]
321
+ end
322
+
323
+ # log { "opts: #{assocopts.inspect}" }
324
+ if assocopts && x = set_option(assocopts, args)
325
+ return x
326
+ end
327
+ end
328
+
329
+ if x = set_option(@options, args)
330
+ return x
331
+ elsif @bestmatch
332
+ # what's the best match here ...?
333
+ log { "bestmatch: #{@bestmatch}" }
334
+ log { "bestopts : #{@bestopts.inspect}" }
335
+ if @bestopts.size == 1
336
+ @bestopts[0].set_value(args)
337
+ return @bestopts[0]
338
+ else
339
+ optstr = @bestopts.collect { |x| '(' + x.tags.join(', ') + ')' }.join(', ')
340
+ $stderr.puts "ERROR: ambiguous match of '#{args[0]}'; matches options: #{optstr}"
341
+ exit 2
342
+ end
343
+ end
344
+
345
+ nil
346
+ end
347
+
348
+ def set_option(optlist, args)
349
+ @bestmatch = nil
350
+ @bestopts = Array.new
351
+
352
+ optlist.each do |option|
353
+ if mv = option.match(args)
354
+ if mv >= 1.0
355
+ # exact match:
356
+ option.set_value(args)
357
+ return option
358
+ elsif !@bestmatch || @bestmatch <= mv
359
+ @bestmatch = mv
360
+ @bestopts << option
361
+ end
362
+ end
363
+ end
364
+ nil
365
+ end
366
+
367
+ end
368
+
369
+ end
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/ruby -w
2
+ # -*- ruby -*-
3
+
4
+ require 'pathname'
5
+ require 'riel/string'
6
+
7
+
8
+ class Pathname
9
+
10
+ # a compliment to the +dirname+, +basename+, and +extname+ family, this returns
11
+ # the basename without the extension, e.g. "foo" from "/usr/share/lib/foo.bar".
12
+ def rootname
13
+ basename.to_s - extname.to_s
14
+ end
15
+
16
+ end
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/ruby -w
2
+ # -*- ruby -*-
3
+
4
+
5
+ # Represents a resource file, where '#' is used to comment to end of lines, and
6
+ # name/value pairs are separated by '=' or ':'.
7
+
8
+ class RCFile
9
+
10
+ attr_reader :settings
11
+
12
+ # Reads the RC file, if it exists, and if a block is passed, calls the block
13
+ # with each name/value pair, which are also accessible via
14
+ # <code>settings</code>.
15
+
16
+ def initialize(fname, &blk)
17
+ @settings = Array.new
18
+
19
+ if File.exists?(fname)
20
+ IO.readlines(fname).each do |line|
21
+ line.sub!(/\s*#.*/, "")
22
+ line.chomp!
23
+ name, value = line.split(/\s*[=:]\s*/)
24
+ if name && value
25
+ name.strip!
26
+ value.strip!
27
+ @settings << [ name, value ]
28
+ if blk
29
+ blk.call(name, value)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/ruby -w
2
+ # -*- ruby -*-
3
+
4
+ # Negates the given expression.
5
+ class NegatedRegexp < Regexp
6
+
7
+ def match(str)
8
+ !super
9
+ end
10
+
11
+ end
12
+
13
+ class Regexp
14
+
15
+ # shell expressions to Ruby regular expression sequences
16
+ SH2RE = Hash[
17
+ '*' => '.*',
18
+ '?' => '.',
19
+ # '[' => '\[',
20
+ # ']' => '\]',
21
+ '.' => '\.',
22
+ '$' => '\$',
23
+ '/' => '\/',
24
+ '(' => '\(',
25
+ ')' => '\)',
26
+ ]
27
+
28
+ # Returns a regular expression for the given Unix file system expression.
29
+
30
+ def self.unixre_to_string(pat)
31
+ pat.gsub(%r{(\\.)|(.)}) do
32
+ $1 || SH2RE[$2] || $2
33
+ end
34
+ end
35
+
36
+ WORD_START_RE = Regexp.new('^ # start of word
37
+ [\[\(]* # parentheses or captures, maybe
38
+ (?: \\\w | \\w) # literal \w, or what \w matches
39
+ ',
40
+ Regexp::EXTENDED)
41
+
42
+ WORD_END_RE = Regexp.new('(?: # one of the following:
43
+ \\\w # - \w for regexp
44
+ | #
45
+ \w # - a literal A-Z, a-z, 0-9, or _
46
+ | #
47
+ (?: # - one of the following:
48
+ \[[^\]]* # LB, with no RB until:
49
+ (?: # - either of:
50
+ \\w # - "\w"
51
+ | #
52
+ \w # - a literal A-Z, a-z, 0-9, or _
53
+ ) #
54
+ [^\]]*\] # - anything (except RB) to the next RB
55
+ ) #
56
+ ) #
57
+ (?: # optionally, one of the following:
58
+ \* # - "*"
59
+ | #
60
+ \+ # - "+"
61
+ | #
62
+ \? # - "?"
63
+ | #
64
+ \{\d*,\d*\} # - "{3,4}", "{,4}, "{,123}" (also matches the invalid {,})
65
+ )? #
66
+ $ # fin
67
+ ',
68
+ Regexp::EXTENDED)
69
+
70
+ # Handles negation, whole words, and ignore case (Ruby no longer supports
71
+ # Rexexp.new(/foo/i), as of 1.8).
72
+
73
+ def self.create(pat, args = Hash.new)
74
+ negated = args[:negated]
75
+ ignorecase = args[:ignorecase]
76
+ wholewords = args[:wholewords]
77
+ wholelines = args[:wholelines]
78
+ extended = args[:extended]
79
+ multiline = args[:multiline]
80
+
81
+ pattern = pat.dup
82
+
83
+ # we handle a ridiculous number of possibilities here:
84
+ # /foobar/ -- "foobar"
85
+ # /foo/bar/ -- "foo", then slash, then "bar"
86
+ # /foo\/bar/ -- same as above
87
+ # /foo/bar/i -- same as above, case insensitive
88
+ # /foo/bari -- "/foo/bari" exactly
89
+ # /foo/bar\/i -- "/foo/bar/i" exactly
90
+ # foo/bar/ -- "foo/bar/" exactly
91
+ # foo/bar/ -- "foo/bar/" exactly
92
+
93
+ if pattern.sub!(%r{ ^ !(?=/) }x, "")
94
+ negated = true
95
+ end
96
+
97
+ if pattern.sub!(%r{ ^ \/ (.*[^\\]) \/ ([mix]+) $ }x) { $1 }
98
+ modifiers = $2
99
+
100
+ multiline ||= modifiers.index('m')
101
+ ignorecase ||= modifiers.index('i')
102
+ extended ||= modifiers.index('x')
103
+ else
104
+ pattern.sub!(%r{ ^\/ (.*[^\\]) \/ $ }x) { $1 }
105
+ end
106
+
107
+ if wholewords
108
+ # sanity check:
109
+
110
+ errs = [
111
+ [ WORD_START_RE, "start" ],
112
+ [ WORD_END_RE, "end" ]
113
+ ].collect do |ary|
114
+ re, err = *ary
115
+ re.match(pattern) ? nil : err
116
+ end.compact
117
+
118
+ if errs.length > 0
119
+ Log.warn "pattern '#{pattern}' does not " + errs.join(" and ") + " on a word boundary"
120
+ end
121
+ pattern = '\b' + pattern + '\b'
122
+ elsif wholelines
123
+ pattern = '^' + pattern + '$' # ' for emacs
124
+ end
125
+
126
+ reclass = negated ? NegatedRegexp : Regexp
127
+
128
+ flags = [
129
+ [ ignorecase, Regexp::IGNORECASE ],
130
+ [ extended, Regexp::EXTENDED ],
131
+ [ multiline, Regexp::MULTILINE ]
132
+ ].inject(0) do |tot, ary|
133
+ val, flag = *ary
134
+ tot | (val ? flag : 0)
135
+ end
136
+
137
+ reclass.new(pattern, flags)
138
+ end
139
+
140
+ def self.matches_word_start?(pat)
141
+ WORD_START_RE.match(pat)
142
+ end
143
+
144
+ def self.matches_word_end?(pat)
145
+ WORD_END_RE.match(pat)
146
+ end
147
+
148
+ # applies Perl-style substitution (s/foo/bar/).
149
+ def self.perl_subst(pat)
150
+ end
151
+
152
+ end
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/ruby -w
2
+ # -*- ruby -*-
3
+
4
+ # Compares two enumerables, treating them as sets, showing whether they are
5
+ # identical, A contains B, B contains A, or A and B contain common elements.
6
+
7
+ class SetDiff
8
+ def SetDiff.new(a, b)
9
+ allitems = a | b
10
+
11
+ a_and_b = Array.new
12
+ a_not_in_b = Array.new
13
+ b_not_in_a = Array.new
14
+
15
+ allitems.each do |it|
16
+ if a.include?(it)
17
+ if b.include?(it)
18
+ a_and_b
19
+ else
20
+ a_not_in_b
21
+ end
22
+ else
23
+ b_not_in_a
24
+ end << it
25
+ end
26
+
27
+ super(a_and_b, a_not_in_b, b_not_in_a)
28
+ end
29
+
30
+ attr_reader :a_and_b, :a_not_in_b, :b_not_in_a
31
+
32
+ def initialize(a_and_b, a_not_in_b, b_not_in_a)
33
+ @a_and_b = a_and_b
34
+ @a_not_in_b = a_not_in_b
35
+ @b_not_in_a = b_not_in_a
36
+ end
37
+
38
+ def diff_type
39
+ @diff_type ||= if @a_and_b.empty?
40
+ :no_common
41
+ elsif @a_not_in_b.empty?
42
+ if @b_not_in_a.empty?
43
+ :identical
44
+ else
45
+ :b_contains_a
46
+ end
47
+ elsif @b_not_in_a.empty?
48
+ :a_contains_b
49
+ else
50
+ :common
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/ruby -w
2
+ # -*- ruby -*-
3
+
4
+ class SizeConverter
5
+ # http://www.gnu.org/software/coreutils/manual/html_node/Block-size.html
6
+
7
+ # don't round to closest -- just convert
8
+ def self.convert_to_kilobytes(size, decimal_places = 1)
9
+ ### SizeConverter._convert(Human::CONVERSIONS, 2, size, decimal_places)
10
+ end
11
+
12
+ class Human
13
+ CONVERSIONS = [
14
+ [ 12, "T" ],
15
+ [ 9, "G" ],
16
+ [ 6, "M" ],
17
+ [ 3, "K" ]
18
+ ]
19
+
20
+ # returns a string representation of the size. Note that K, G, M are
21
+ # gibibytes, etc., that is, powers of 10.
22
+
23
+ def self.convert(size, decimal_places = 1)
24
+ SizeConverter._convert(CONVERSIONS, 10, size, decimal_places)
25
+ end
26
+ end
27
+
28
+ class SI
29
+ # http://physics.nist.gov/cuu/Units/binary.html
30
+ CONVERSIONS = [
31
+ [ 40, "TiB" ],
32
+ [ 30, "GiB" ],
33
+ [ 20, "MiB" ],
34
+ [ 10, "KiB" ]
35
+ ]
36
+
37
+ # returns a string representation of the size. Note that K, G, M are
38
+ # gigabytes, etc.
39
+
40
+ def self.convert(size, decimal_places = 1)
41
+ SizeConverter._convert(CONVERSIONS, 2, size, decimal_places)
42
+ end
43
+ end
44
+
45
+ # legacy:
46
+
47
+ def self.convert(size, decimal_places = 1)
48
+ Human::convert(size, decimal_places)
49
+ end
50
+
51
+ def self._convert(conversions, base, size, decimal_places)
52
+ sizef = size.to_f
53
+ conversions.each do |conv|
54
+ sz = sizef / (base ** conv[0])
55
+ if sz >= 1.0
56
+ return sprintf("%.*f%s", decimal_places, sz, conv[1])
57
+ end
58
+ end
59
+
60
+ sprintf("%.*f", decimal_places, size)
61
+ end
62
+ end