numerals 0.1.0 → 0.2.0

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
  SHA1:
3
- metadata.gz: 0e7931e88c73e54a2d95c47f45d222562dc03a02
4
- data.tar.gz: 0179555b287f177e458d854708929838c9619bcc
3
+ metadata.gz: 0e87d9faabf0392f12d84d026d85d480aa67d32c
4
+ data.tar.gz: 67b89570a8c97ae4abe727c730bdc77099ca70d0
5
5
  SHA512:
6
- metadata.gz: fbdfea7324a61b2d25888c0553acefac0911e97ed9ddd5e9217064694f1f66fefea0ba968941cead3634e0b9c5a4bfa55e12cfcb16896c42407f057724446580
7
- data.tar.gz: ea99d01ee36c91692c926d64f022e08274f89b0a3f4f4eb96f0a4dcaaac1c4340c1c6484d9a1b29f235ae370a2fce903624b1e5656f3297ee1026728cf4d6b87
6
+ metadata.gz: 3c3b97ef87dcda6dec5dce9a8d8449da8b8da3c5faab0664ab4563dfb32f4770b9295271454c6e9c2efbec2be3b72843cf12c6f2d48395e36c7474b8d6be886c
7
+ data.tar.gz: 6ff01ddb162578b80b30596adfd38be3fb992509b8ff7aa32018b98815dae4e2449770313728647e9b8af0110e6a0cd4078edc1f4d01754a91a3205ceb7cd93a
data/README.md CHANGED
@@ -161,8 +161,10 @@ Done:
161
161
  * Handling of 'unsignificant' digits: show them either as special
162
162
  symbol, as zeros or omit them (comfigured in Symbols)
163
163
 
164
- Pending:
165
-
166
164
  * Padding aspect of formatting on output
167
165
 
168
166
  * Show base indicators on output
167
+
168
+ Pending:
169
+
170
+ * HTML & Latex Input/Output
@@ -60,6 +60,14 @@ module Numerals
60
60
  @rounding.base
61
61
  end
62
62
 
63
+ def padding
64
+ @symbols.padding
65
+ end
66
+
67
+ def padded?
68
+ padding.padded?
69
+ end
70
+
63
71
  # Presentation base for the significand
64
72
  def significand_base
65
73
  base**@mode.base_scale
@@ -94,6 +102,7 @@ module Numerals
94
102
  @rounding.set! places: options[:places] if options[:places]
95
103
  @symbols.set! repeating: options[:repeating] if options.has_key?(:repeating)
96
104
  @symbols.set! case_sensitive: options[:case_sensitive] if options.has_key?(:case_sensitive)
105
+ @symbols.set! padding: options[:padding] if options[:padding]
97
106
  end
98
107
 
99
108
  def parameters
@@ -130,6 +139,10 @@ module Numerals
130
139
  set! rounding: args
131
140
  end
132
141
 
142
+ aspect :padding do |*args|
143
+ set! padding: args
144
+ end
145
+
133
146
  aspect :base do |base|
134
147
  set! base: base
135
148
  end
@@ -200,6 +213,10 @@ module Numerals
200
213
  @symbols.set_minus!(minus)
201
214
  end
202
215
 
216
+ aspect :leading_zeros do |width|
217
+ @symbols.set_leading_zeros! width
218
+ end
219
+
203
220
  private
204
221
 
205
222
  def extract_options(*args)
@@ -217,6 +234,8 @@ module Numerals
217
234
  options[:symbols] = arg
218
235
  when Symbols::Digits
219
236
  options[:digits] = arg
237
+ when Symbols::Padding
238
+ options[:padding] = arg
220
239
  when Format
221
240
  options.merge! arg.parameters
222
241
  when :exact_input
@@ -230,7 +249,7 @@ module Numerals
230
249
  sci_int_digits: 1
231
250
  },
232
251
  symbols: {
233
- exponent: 'p'
252
+ exponent: 'p', base_prefix: '0x'
234
253
  }
235
254
  )
236
255
  when :gen, :general, :sci, :scientific, :fix; :fixed
@@ -12,20 +12,31 @@ module Numerals
12
12
  # <span class=”numerals-num”>1.23<span class="numerals-rep">456</span> &times;10<span class="numerals-sup">9</span></span>
13
13
  # .numerals-rep { text-decoration: overline; }
14
14
  # .numerals-sup { vertical-align: super; }
15
+ # TODO: padding
15
16
  if text_parts.special?
16
17
  output << escape(text_parts.special)
17
18
  else
18
19
  output << escape(text_parts.sign)
20
+ if format.symbols.base_prefix
21
+ output << format.symbols.base_prefix
22
+ end
19
23
  output << escape(text_parts.integer) # or decide here if empty integer part is show as 0?
20
- unless !text_parts.fractional? &&
21
- !text_parts.repeat? &&
22
- !format.symbols.show_point
24
+ if text_parts.show_point?(format)
23
25
  output << escape(format.symbols.point)
24
26
  end
25
27
  output << escape(text_parts.fractional)
26
28
  if text_parts.repeat
27
29
  output << %(<span style="text-decoration: overline">#{escape(text_parts.repeat)}</span>)
28
30
  end
31
+ if format.symbols.base_suffix || format.base != 10
32
+ if format.symbols.base_prefix
33
+ output << format.symbols.base_suffix
34
+ else
35
+ # show base suffix as a subscript
36
+ subscript = format.symbols.base_suffix || base.to_s
37
+ º output << "<sub>#{subscript}</sub>"
38
+ end
39
+ end
29
40
  if text_parts.exponent_value != 0 || format.mode.mode == :scientific
30
41
  output << "&times;"
31
42
  output << escape(text_parts.exponent_base)
@@ -6,20 +6,31 @@ module Numerals
6
6
 
7
7
  def assemble(output, text_parts)
8
8
  # 1.23\overline{456}\times10^{9}
9
+ # TODO: padding
9
10
  if text_parts.special?
10
11
  output << text_parts.special
11
12
  else
12
13
  output << text_parts.sign
14
+ if format.symbols.base_prefix
15
+ output << format.symbols.base_prefix
16
+ end
13
17
  output << text_parts.integer # or decide here if empty integer part is shown as 0?
14
- unless !text_parts.fractional? &&
15
- !text_parts.repeat? &&
16
- !format.symbols.show_point
18
+ if text_parts.show_point?(format)
17
19
  output << format.symbols.point
18
20
  end
19
21
  output << text_parts.fractional
20
22
  if text_parts.repeat?
21
23
  output << "\\overline{#{text_parts.repeat}}"
22
24
  end
25
+ if format.symbols.base_suffix || format.base != 10
26
+ if format.symbols.base_prefix
27
+ output << format.symbols.base_suffix
28
+ else
29
+ # show base suffix as a subscript
30
+ subscript = format.symbols.base_suffix || base.to_s
31
+ output << "_{#{subscript}}"
32
+ end
33
+ end
23
34
  if text_parts.exponent_value != 0 || format.mode.mode == :scientific
24
35
  output << "\\times"
25
36
  output << text_parts.exponent_base
@@ -8,35 +8,12 @@ module Numerals
8
8
  if text_parts.special?
9
9
  output << text_parts.special
10
10
  else
11
- output << text_parts.sign
12
- output << text_parts.integer # or decide here if empty integer part is show as 0?
13
- unless !text_parts.fractional? &&
14
- !text_parts.repeat? &&
15
- !format.symbols.show_point
16
- output << format.symbols.point
17
- end
18
- output << text_parts.fractional
19
- if text_parts.repeat?
20
- if format.symbols.repeat_delimited
21
- output << format.symbols.repeat_begin
22
- output << text_parts.repeat
23
- output << format.symbols.repeat_end
24
- else
25
- n = RepeatDetector.min_repeat_count(
26
- text_parts.numeral.digits.digits_array,
27
- text_parts.numeral.repeat,
28
- format.symbols.repeat_count - 1
29
- )
30
- n.times do
31
- output << text_parts.repeat
32
- end
33
- output << format.symbols.repeat_suffix
34
- end
35
- end
36
- if text_parts.exponent_value != 0 || format.mode.mode == :scientific
37
- output << format.symbols.exponent
38
- output << text_parts.exponent
11
+ if format.symbols.padding.padded?
12
+ output_size = OutputSize.new
13
+ assemble_parts(output_size, text_parts)
14
+ left_padding, internal_padding, right_padding = format.symbols.paddings(output_size.size)
39
15
  end
16
+ assemble_parts(output, text_parts, left_padding, internal_padding, right_padding)
40
17
  end
41
18
  end
42
19
 
@@ -57,11 +34,13 @@ module Numerals
57
34
  valid = true
58
35
  base = format.significand_base
59
36
  # TODO: replace numbered groups by named variables ?<var>
60
- # TODO: ignore padding, admit base indicators
61
37
  regular = /
62
38
  \A
39
+ #{s.regexp(:fill, no_capture: true, optional: true, multiple: true)}
63
40
  #{s.regexp(:plus, :minus)}?
64
41
  \s*
42
+ #{s.regexp(:fill, no_capture: true, optional: true, multiple: true)}
43
+ #{s.regexp(:base_prefix, no_capture: true, optional: true)}
65
44
  (?:
66
45
  (?:(#{s.regexp(:grouped_digits, base: base, no_capture: true)}+)#{s.regexp(:point)}?)
67
46
  |
@@ -70,7 +49,9 @@ module Numerals
70
49
  (#{s.regexp(:digits, base: base, no_capture: true)}*)
71
50
  (?:#{s.regexp(:repeat_begin)}(#{s.regexp(:digits, base: base, no_capture: true)}+)#{s.regexp(:repeat_end)})?
72
51
  #{s.regexp(:repeat_suffix)}?
52
+ #{s.regexp(:base_suffix, no_capture: true, optional: true)}
73
53
  (?:#{s.regexp(:exponent)}#{s.regexp(:plus, :minus)}?(\d+))?
54
+ #{s.regexp(:fill, no_capture: true, optional: true, multiple: true)}
74
55
  \Z
75
56
  /x
76
57
  unless s.case_sensitive?
@@ -132,6 +113,57 @@ module Numerals
132
113
  text_parts
133
114
  end
134
115
 
116
+ private
117
+
118
+ class OutputSize
119
+ def initialize
120
+ @size = 0
121
+ end
122
+ def <<(text)
123
+ @size += text.size
124
+ end
125
+ attr_reader :size
126
+ end
127
+
128
+ def assemble_parts(output, text_parts, left_padding='', internal_padding='', right_padding='')
129
+ output << left_padding
130
+ output << text_parts.sign
131
+ if format.symbols.base_prefix
132
+ output << format.symbols.base_prefix
133
+ end
134
+ output << internal_padding
135
+ output << text_parts.integer # or decide here if empty integer part is show as 0?
136
+ if text_parts.show_point?(format)
137
+ output << format.symbols.point
138
+ end
139
+ output << text_parts.fractional
140
+ if text_parts.repeat?
141
+ if format.symbols.repeat_delimited
142
+ output << format.symbols.repeat_begin
143
+ output << text_parts.repeat
144
+ output << format.symbols.repeat_end
145
+ else
146
+ n = RepeatDetector.min_repeat_count(
147
+ text_parts.numeral.digits.digits_array,
148
+ text_parts.numeral.repeat,
149
+ format.symbols.repeat_count - 1
150
+ )
151
+ n.times do
152
+ output << text_parts.repeat
153
+ end
154
+ output << format.symbols.repeat_suffix
155
+ end
156
+ end
157
+ if format.symbols.base_suffix
158
+ output << format.symbols.base_suffix
159
+ end
160
+ if text_parts.exponent_value != 0 || format.mode.mode == :scientific
161
+ output << format.symbols.exponent
162
+ output << text_parts.exponent
163
+ end
164
+ output << right_padding
165
+ end
166
+
135
167
  end
136
168
 
137
169
  define_notation :text, TextNotation
@@ -153,8 +153,6 @@ module Numerals
153
153
  text_parts.exponent_base = num_parts.exponent_base.to_s(10) # use digits_definition ?
154
154
  text_parts.exponent_base_value = num_parts.exponent_base
155
155
  end
156
- # TODO: justification
157
- # TODO: base indicator for significand? significand_bas?
158
156
  text_parts
159
157
  end
160
158
 
@@ -11,158 +11,6 @@ module Numerals
11
11
  #
12
12
  class Format::Symbols < FormattingAspect
13
13
 
14
- class Digits < FormattingAspect
15
-
16
- DEFAULT_DIGITS = %w(0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z)
17
-
18
- def initialize(*args)
19
- @digits = DEFAULT_DIGITS
20
- @downcase_digits = @digits.map(&:downcase)
21
- @max_base = @digits.size
22
- @case_sensitive = false
23
- @uppercase = false
24
- @lowercase = false
25
- set! *args
26
- end
27
-
28
- include ModalSupport::StateEquivalent
29
-
30
- set do |*args|
31
- options = extract_options(*args)
32
- options.each do |option, value|
33
- send :"#{option}=", value
34
- end
35
- end
36
-
37
- attr_reader :digits_string, :max_base, :case_sensitive, :uppercase, :lowercase
38
- attr_writer :case_sensitive
39
-
40
- def digits(options = {})
41
- base = options[:base] || @max_base
42
- if base >= @max_base
43
- @digits
44
- else
45
- @digits[0, base]
46
- end
47
- end
48
-
49
- def digits=(digits)
50
- if digits.is_a?(String)
51
- @digits = digits.each_char.to_a
52
- else
53
- @digits = digits
54
- end
55
- @max_base = @digits.size
56
- @lowercase = @digits.all? { |d| d.downcase == d }
57
- @uppercase = @digits.all? { |d| d.upcase == d }
58
- @downcase_digits = @digits.map(&:downcase)
59
- if @digits.uniq.size != @max_base
60
- raise "Inconsistent digits"
61
- end
62
- end
63
-
64
- def uppercase=(v)
65
- @uppercase = v
66
- self.digits = @digits.map(&:upcase) if v
67
- end
68
-
69
- def lowercase=(v)
70
- @lowercase = v
71
- self.digits = @digits.map(&:downcase) if v
72
- end
73
-
74
- def case_sensitive?
75
- case_sensitive
76
- end
77
-
78
- def is_digit?(digit_symbol, options={})
79
- base = options[:base] || @max_base
80
- raise "Invalid base" if base > @max_base
81
- v = digit_value(digit_symbol)
82
- v && v < base
83
- end
84
-
85
- def digit_value(digit)
86
- if @case_sensitive
87
- @digits.index(digit)
88
- else
89
- @downcase_digits.index(digit.downcase)
90
- end
91
- end
92
-
93
- def digit_symbol(v, options={})
94
- base = options[:base] || @max_base
95
- raise "Invalid base" if base > @max_base
96
- v >= 0 && v < base ? @digits[v] : nil
97
- end
98
-
99
- # Convert sequence of digits to its text representation.
100
- # The nil value can be used in the digits sequence to
101
- # represent the group separator.
102
- def digits_text(digit_values, options={})
103
- insignificant_digits = options[:insignificant_digits] || 0
104
- num_digits = digit_values.reduce(0) { |num, digit|
105
- digit.nil? ? num : num + 1
106
- }
107
- num_digits -= insignificant_digits
108
- digit_values.map { |d|
109
- if d.nil?
110
- options[:separator]
111
- else
112
- num_digits -= 1
113
- if num_digits >= 0
114
- digit_symbol(d, options)
115
- else
116
- options[:insignificant_symbol]
117
- end
118
- end
119
- }.join
120
- end
121
-
122
- def parameters
123
- params = {}
124
- params[:digits] = @digits
125
- params[:case_sensitive] = @case_sensitive
126
- params[:uppercase] = @uppercase
127
- params[:lowercase] = @lowercase
128
- params
129
- end
130
-
131
- def to_s
132
- # TODO: show only non-defaults
133
- "Digits[#{parameters.inspect.unwrap('{}')}]"
134
- end
135
-
136
- def inspect
137
- "Format::Symbols::#{self}"
138
- end
139
-
140
- def dup
141
- Digits[parameters]
142
- end
143
-
144
- private
145
-
146
- def extract_options(*args)
147
- options = {}
148
- args = args.first if args.size == 1 && args.first.kind_of?(Array)
149
- args.each do |arg|
150
- case arg
151
- when Hash
152
- options.merge! arg
153
- when String, Array
154
- options[:digits] = arg
155
- when Format::Symbols::Digits
156
- options.merge! arg.parameters
157
- else
158
- raise "Invalid Symbols::Digits definition"
159
- end
160
- end
161
- options
162
- end
163
-
164
- end
165
-
166
14
  DEFAULTS = {
167
15
  nan: 'NaN',
168
16
  infinity: 'Infinity',
@@ -175,7 +23,6 @@ module Numerals
175
23
  repeat_begin: '<',
176
24
  repeat_end: '>',
177
25
  repeat_suffix: '...',
178
- #repeat_detect: false,
179
26
  show_plus: false,
180
27
  show_exponent_plus: false,
181
28
  uppercase: false,
@@ -186,7 +33,9 @@ module Numerals
186
33
  repeat_count: 3,
187
34
  grouping: [],
188
35
  insignificant_digit: nil,
189
- repeating: true
36
+ repeating: true,
37
+ base_prefix: nil,
38
+ base_suffix: nil
190
39
  }
191
40
 
192
41
  def initialize(*args)
@@ -199,29 +48,27 @@ module Numerals
199
48
  # default Digits among all Symbols)
200
49
  @digits = Format::Symbols::Digits[]
201
50
 
202
- # TODO: justification/padding
203
- # width, adjust_mode (left, right, internal), fill_symbol
204
-
205
- # TODO: base_suffixes, base_preffixes, show_base
51
+ # same with @padding
52
+ @padding = Format::Symbols::Padding[]
206
53
 
207
54
  set! *args
208
55
  end
209
56
 
210
- # TODO: transmit uppercase/lowercase to digits
211
-
212
57
  attr_reader :digits, :nan, :infinity, :plus, :minus, :exponent, :point,
213
- :group_separator, :zero, :insignificant_digit
214
- attr_reader :repeat_begin, :repeat_end, :repeat_suffix, :repeat_delimited
215
- attr_reader :show_plus, :show_exponent_plus, :uppercase, :lowercase,
216
- :show_zero, :show_point
217
- attr_reader :grouping, :repeat_count, :repeating
218
-
219
- attr_writer :uppercase, :lowercase, :nan, :infinity, :plus,
58
+ :group_separator, :zero, :insignificant_digit, :padding,
59
+ :repeat_begin, :repeat_end, :repeat_suffix, :repeat_delimited,
60
+ :show_plus, :show_exponent_plus, :uppercase, :lowercase,
61
+ :show_zero, :show_point,
62
+ :grouping, :repeat_count, :repeating,
63
+ :base_prefix, :base_suffix
64
+
65
+ attr_writer :digits, :uppercase, :lowercase, :nan, :infinity, :plus,
220
66
  :minus, :exponent, :point, :group_separator, :zero,
221
67
  :repeat_begin, :repeat_end, :repeat_suffix,
222
68
  :show_plus, :show_exponent_plus, :show_zero, :show_point,
223
69
  :repeat_delimited, :repeat_count, :grouping,
224
- :insignificant_digit, :repeating
70
+ :insignificant_digit, :repeating,
71
+ :base_prefix, :base_suffix
225
72
 
226
73
  include ModalSupport::StateEquivalent
227
74
 
@@ -251,11 +98,26 @@ module Numerals
251
98
  !@grouping.empty? && @group_separator && !@group_separator.empty?
252
99
  end
253
100
 
101
+ def padded?
102
+ @padding.padded?
103
+ end
104
+
105
+ def fill
106
+ fill = @padding.fill
107
+ if fill.is_a?(Integer)
108
+ @digits.digit_symbol(fill)
109
+ else
110
+ fill
111
+ end
112
+ end
113
+
254
114
  set do |*args|
255
115
  options = extract_options(*args)
256
116
  options.each do |option, value|
257
117
  if option == :digits
258
118
  @digits.set! value
119
+ elsif option == :padding
120
+ @padding.set! value
259
121
  else
260
122
  send :"#{option}=", value
261
123
  end
@@ -263,14 +125,38 @@ module Numerals
263
125
  apply_case!
264
126
  end
265
127
 
266
- attr_writer :digits, :nan, :infinity,
267
- :plus, :minus, :exponent, :point, :group_separator, :zero,
268
- :repeat_begin, :repeat_end, :repeat_suffix, :show_plus,
269
- :show_exponent_plus, :uppercase, :show_zero, :show_point,
270
- :grouping, :repeat_count
271
-
272
128
  aspect :repeat do |*args|
273
- # TODO accept hash :begin, :end, :suffix, ...
129
+ args.each do |arg|
130
+ case arg
131
+ when true, false
132
+ @repeating = arg
133
+ when Integer
134
+ @repeat_count = arg
135
+ when :delimited
136
+ @repeat_delimited = true
137
+ when :suffixed
138
+ @repeat_delimited = false
139
+ when Hash
140
+ arg.each do |key, value|
141
+ case key
142
+ when :delimiters
143
+ @repeat_begin, @repeat_end = Array(value)
144
+ when :begin
145
+ @repeat_begin = value
146
+ when :end
147
+ @repeat_end = value
148
+ when :suffix
149
+ @repeat_suffix = value
150
+ when :delimited
151
+ @repeat_delimited = value
152
+ when :count
153
+ @repeat_count = value
154
+ else
155
+ send "#{key}=", value
156
+ end
157
+ end
158
+ end
159
+ end
274
160
  end
275
161
 
276
162
  aspect :grouping do |*args|
@@ -278,12 +164,14 @@ module Numerals
278
164
  case arg
279
165
  when Symbol
280
166
  if arg == :thousands
281
- @groups = [3]
167
+ @grouping = [3]
282
168
  end
283
169
  when String
284
170
  @group_separator = arg
285
171
  when Array
286
- @groups = groups
172
+ @grouping = arg
173
+ when false
174
+ @grouping = []
287
175
  end
288
176
  end
289
177
  end
@@ -351,6 +239,14 @@ module Numerals
351
239
  @minus = minus
352
240
  end
353
241
 
242
+ aspect :padding do |*args|
243
+ @padding.set! *args
244
+ end
245
+
246
+ aspect :leading_zeros do |width|
247
+ @padding.leading_zeros = width
248
+ end
249
+
354
250
  def parameters(abbreviated=false)
355
251
  params = {}
356
252
  DEFAULTS.each do |param, default|
@@ -362,6 +258,9 @@ module Numerals
362
258
  if !abbreviated || @digits != Format::Symbols::Digits[]
363
259
  params[:digits] = @digits
364
260
  end
261
+ if !abbreviated || @padding != Format::Symbols::Padding[]
262
+ params[:padding] = @padding
263
+ end
365
264
  params
366
265
  end
367
266
 
@@ -447,7 +346,9 @@ module Numerals
447
346
  symbols = args
448
347
  digits = symbols.delete(:digits)
449
348
  grouped_digits = symbols.delete(:grouped_digits)
450
- symbols = symbols.map { |s| send(s.to_sym) }
349
+ symbols = symbols.map { |s|
350
+ s.is_a?(Symbol) ? send(s) : s
351
+ }
451
352
  if grouped_digits
452
353
  symbols += [group_separator, insignificant_digit]
453
354
  elsif digits
@@ -479,6 +380,23 @@ module Numerals
479
380
  }.compact
480
381
  end
481
382
 
383
+ # Returns left, internal and right padding for a number
384
+ # of given size (number of characters)
385
+ def paddings(number_size)
386
+ left_padding = internal_padding = right_padding = ''
387
+ if padded?
388
+ left_padding_size, internal_padding_size, right_padding_size = padding.padding_sizes(number_size)
389
+ right_padding_size = right_padding_size/fill.size
390
+ right_padding = fill*right_padding_size
391
+ d = right_padding_size - right_padding.size
392
+ left_padding_size = (left_padding_size + d)/fill.size
393
+ left_padding = fill*left_padding_size
394
+ internal_padding_size = internal_padding_size/fill.size
395
+ internal_padding = fill*internal_padding_size
396
+ end
397
+ [left_padding, internal_padding, right_padding ]
398
+ end
399
+
482
400
  private
483
401
 
484
402
  def regexp_char(c, options = {})
@@ -500,10 +418,22 @@ module Numerals
500
418
  symbols = Array(symbols).compact.select { |s| !s.empty? }
501
419
  .map{ |d| regexp_symbol(d, options) }.join('|')
502
420
  if capture
503
- "(#{symbols})"
421
+ symbols = "(#{symbols})"
504
422
  else
505
- "(?:#{symbols})"
423
+ if symbols != ''
424
+ symbols = "(?:#{symbols})"
425
+ if options[:optional]
426
+ if options[:multiple]
427
+ symbols = "#{symbols}*"
428
+ else
429
+ symbols = "#{symbols}?"
430
+ end
431
+ elsif options[:multiple]
432
+ symbols = "#{symbols}+"
433
+ end
434
+ end
506
435
  end
436
+ symbols
507
437
  end
508
438
 
509
439
  def extract_options(*args)
@@ -540,7 +470,8 @@ module Numerals
540
470
  @repeat_begin = @repeat_begin.upcase
541
471
  @repeat_end = @repeat_end.upcase
542
472
  @repeat_suffix = @repeat_suffix.upcase
543
- @digits = @digits[uppercase: true]
473
+ @digits.set! uppercase: true
474
+ @padding.fill = @padding.fill.upcase if @padding.fill.is_a?(String)
544
475
  elsif @lowercase
545
476
  @nan = @nan.downcase
546
477
  @infinity = @infinity.downcase
@@ -552,7 +483,8 @@ module Numerals
552
483
  @repeat_begin = @repeat_begin.downcase
553
484
  @repeat_end = @repeat_end.downcase
554
485
  @repeat_suffix = @repeat_suffix.downcase
555
- @digits = @digits[lowercase: true]
486
+ @digits.set! lowercase: true
487
+ @padding.fill = @padding.fill.downcase if @padding.filll.is_a?(String)
556
488
  end
557
489
  end
558
490
 
@@ -563,3 +495,6 @@ module Numerals
563
495
  end
564
496
 
565
497
  end
498
+
499
+ require 'numerals/format/symbols/digits'
500
+ require 'numerals/format/symbols/padding'
@@ -0,0 +1,173 @@
1
+ module Numerals
2
+
3
+ class Format::Symbols::Digits < FormattingAspect
4
+
5
+ DEFAULT_DIGITS = %w(0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z)
6
+
7
+ def initialize(*args)
8
+ @digits = DEFAULT_DIGITS
9
+ @downcase_digits = @digits.map(&:downcase)
10
+ @max_base = @digits.size
11
+ @case_sensitive = false
12
+ @uppercase = false
13
+ @lowercase = false
14
+ set! *args
15
+ end
16
+
17
+ include ModalSupport::StateEquivalent
18
+
19
+ set do |*args|
20
+ options = extract_options(*args)
21
+ options.each do |option, value|
22
+ send :"#{option}=", value
23
+ end
24
+ end
25
+
26
+ attr_reader :digits_string, :max_base, :case_sensitive, :uppercase, :lowercase
27
+ attr_writer :case_sensitive
28
+
29
+ def digits(options = {})
30
+ base = options[:base] || @max_base
31
+ if base >= @max_base
32
+ @digits
33
+ else
34
+ @digits[0, base]
35
+ end
36
+ end
37
+
38
+ def digits=(digits)
39
+ if digits.is_a?(String)
40
+ @digits = digits.each_char.to_a
41
+ else
42
+ @digits = digits
43
+ end
44
+ @max_base = @digits.size
45
+ @lowercase = @digits.all? { |d| d.downcase == d }
46
+ @uppercase = @digits.all? { |d| d.upcase == d }
47
+ @downcase_digits = @digits.map(&:downcase)
48
+ if @digits.uniq.size != @max_base
49
+ raise "Inconsistent digits"
50
+ end
51
+ end
52
+
53
+ def uppercase=(v)
54
+ @uppercase = v
55
+ self.digits = @digits.map(&:upcase) if v
56
+ end
57
+
58
+ def lowercase=(v)
59
+ @lowercase = v
60
+ self.digits = @digits.map(&:downcase) if v
61
+ end
62
+
63
+ def case_sensitive?
64
+ case_sensitive
65
+ end
66
+
67
+ def is_digit?(digit_symbol, options={})
68
+ base = options[:base] || @max_base
69
+ raise "Invalid base" if base > @max_base
70
+ v = digit_value(digit_symbol)
71
+ v && v < base
72
+ end
73
+
74
+ def digit_value(digit)
75
+ if @case_sensitive
76
+ @digits.index(digit)
77
+ else
78
+ @downcase_digits.index(digit.downcase)
79
+ end
80
+ end
81
+
82
+ def digit_symbol(v, options={})
83
+ base = options[:base] || @max_base
84
+ raise "Invalid base" if base > @max_base
85
+ v >= 0 && v < base ? @digits[v] : nil
86
+ end
87
+
88
+ # Convert sequence of digits to its text representation.
89
+ # The nil value can be used in the digits sequence to
90
+ # represent the group separator.
91
+ def digits_text(digit_values, options={})
92
+ insignificant_digits = options[:insignificant_digits] || 0
93
+ num_digits = digit_values.reduce(0) { |num, digit|
94
+ digit.nil? ? num : num + 1
95
+ }
96
+ num_digits -= insignificant_digits
97
+ digit_values.map { |d|
98
+ if d.nil?
99
+ options[:separator]
100
+ else
101
+ num_digits -= 1
102
+ if num_digits >= 0
103
+ digit_symbol(d, options)
104
+ else
105
+ options[:insignificant_symbol]
106
+ end
107
+ end
108
+ }.join
109
+ end
110
+
111
+ def parameters
112
+ params = {}
113
+ params[:digits] = @digits
114
+ params[:case_sensitive] = @case_sensitive
115
+ params[:uppercase] = @uppercase
116
+ params[:lowercase] = @lowercase
117
+ params
118
+ end
119
+
120
+ def to_s
121
+ # TODO: show only non-defaults
122
+ args = []
123
+ if @digits != DEFAULT_DIGITS
124
+ args << @digits.to_s
125
+ end
126
+ if @max_base != @digits.size
127
+ args << "max_base: #{@max_base}"
128
+ end
129
+ if @case_sensitive
130
+ args << "case_sensitive: #{case_sensitive.inspect}"
131
+ end
132
+ if @uppercase
133
+ args << "uppercase: #{uppercase.inspect}"
134
+ end
135
+ if @lowercase
136
+ args << "lowercase: #{lowercase.inspect}"
137
+ end
138
+ "Digits[#{args.join(', ')}]"
139
+ end
140
+
141
+ def inspect
142
+ "Format::Symbols::#{self}"
143
+ end
144
+
145
+ def dup
146
+ Format::Symbols::Digits[parameters]
147
+ end
148
+
149
+ private
150
+
151
+ def extract_options(*args)
152
+ options = {}
153
+ args = args.first if args.size == 1 && args.first.kind_of?(Array)
154
+ args.each do |arg|
155
+ case arg
156
+ when Hash
157
+ options.merge! arg
158
+ when String, Array
159
+ options[:digits] = arg
160
+ when Format::Symbols::Digits
161
+ options.merge! arg.parameters
162
+ when :uppercase, :downcase
163
+ send :"#{arg}=", true
164
+ else
165
+ raise "Invalid Symbols::Digits definition"
166
+ end
167
+ end
168
+ options
169
+ end
170
+
171
+ end
172
+
173
+ end
@@ -0,0 +1,112 @@
1
+ module Numerals
2
+
3
+ # Padding a number to a given width
4
+ class Format::Symbols::Padding < FormattingAspect
5
+
6
+ # Parameters:
7
+ #
8
+ # * :width field width (0 for no padding)
9
+ # * :fill filling symbol; nil for no padding;
10
+ # 0 to use the digit zero; otherwise should be a String
11
+ # * :adjust adjust mode: :left, :right, :integer, :center
12
+ #
13
+ def initialize(*args)
14
+ @width = 0
15
+ @fill = nil
16
+ @adjust = :right
17
+ set! *args
18
+ end
19
+
20
+ include ModalSupport::StateEquivalent
21
+
22
+ set do |*args|
23
+ options = extract_options(*args)
24
+ options.each do |option, value|
25
+ send :"#{option}=", value
26
+ end
27
+ end
28
+
29
+ attr_accessor :width, :fill, :adjust
30
+
31
+ def leading_zeros=(width)
32
+ @width = width
33
+ @fill = 0
34
+ @adjust = :internal
35
+ end
36
+
37
+ def padded?
38
+ @width > 0 && @fill && @fill != ''
39
+ end
40
+
41
+ def parameters
42
+ { width: width, fill: fill, adjust: adjust }
43
+ end
44
+
45
+ def to_s
46
+ params = []
47
+ if fill == 0 && adjust == :internal
48
+ params << "leading_zeros: #{width}"
49
+ else
50
+ if width != 0
51
+ params << "width: #{width}"
52
+ end
53
+ if fill
54
+ params << "fill: #{fill.inspect}"
55
+ end
56
+ if adjust != :right || !params.empty?
57
+ params << "adjust: #{adjust.inspect}"
58
+ end
59
+ end
60
+ "Padding[#{params.join(', ')}]"
61
+ end
62
+
63
+ def inspect
64
+ "Format::Symbols::#{to_s}"
65
+ end
66
+
67
+ # Returns size (characters of left, internal and right padding)
68
+ # for a number of given width (without padding)
69
+ def padding_sizes(number_size)
70
+ left_padding_size = internal_padding_size = right_padding_size = 0
71
+ padding_size = width - number_size
72
+ if padding_size > 0 && padded?
73
+ case adjust
74
+ when :left
75
+ left_padding_size = padding_size
76
+ when :right
77
+ right_padding_size = padding_size
78
+ when :internal
79
+ internal_padding_size = padding_size
80
+ when :center
81
+ left_padding_size = (padding_size + 1) / 2
82
+ right_padding_size = padding_size - left_padding_size
83
+ end
84
+ end
85
+ [left_padding_size, internal_padding_size, right_padding_size]
86
+ end
87
+
88
+ private
89
+
90
+ def extract_options(*args)
91
+ options = {}
92
+ args = args.first if args.size == 1 && args.first.kind_of?(Array)
93
+ args.each do |arg|
94
+ case arg
95
+ when Integer
96
+ options.merge! width: arg
97
+ when String
98
+ options.merge! fill: arg
99
+ when :left, :right, :internal, :center
100
+ options.merge! adjust: arg
101
+ when Hash
102
+ options.merge! arg
103
+ when Format::Symbols::Padding
104
+ options.merge! arg.parameters
105
+ end
106
+ end
107
+ options
108
+ end
109
+
110
+ end
111
+
112
+ end
@@ -32,4 +32,8 @@ class TextParts
32
32
  @detect_repeat
33
33
  end
34
34
 
35
+ def show_point?(format)
36
+ format.symbols.show_point || fractional? || repeat?
37
+ end
38
+
35
39
  end
@@ -1,3 +1,3 @@
1
1
  module Numerals
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -121,4 +121,51 @@ class TestFormat < Test::Unit::TestCase # < Minitest::Test
121
121
  assert_equal 'PLUS', f2.symbols.plus
122
122
  end
123
123
 
124
+ def test_repeat_aspect
125
+ s = Format::Symbols[]
126
+ assert_equal '<', s.repeat_begin
127
+ assert_equal '>', s.repeat_end
128
+ assert_equal '...', s.repeat_suffix
129
+ refute s.repeat_delimited
130
+ assert_equal 3, s.repeat_count
131
+ assert s.repeating
132
+
133
+ s.set_repeat! false
134
+ refute s.repeating
135
+
136
+ s.set_repeat! true, delimiters: '[', count: 2, suffix: '****'
137
+ assert_equal '[', s.repeat_begin
138
+ assert_nil s.repeat_end
139
+ assert_equal '****', s.repeat_suffix
140
+ refute s.repeat_delimited
141
+ assert_equal 2, s.repeat_count
142
+ assert s.repeating
143
+
144
+ s.set_repeat! true, delimiters: ['>', '<'], delimited: true
145
+ assert_equal '>', s.repeat_begin
146
+ assert_equal '<', s.repeat_end
147
+ assert s.repeat_delimited
148
+ end
149
+
150
+ def test_padding_aspect
151
+ f = Format[]
152
+ refute f.padded?
153
+ assert_equal :right, f.padding.adjust
154
+ f = f[padding:[10, ' ', :left]]
155
+ assert f.padded?
156
+ assert_equal :left, f.padding.adjust
157
+ assert_equal ' ', f.padding.fill
158
+ end
159
+
160
+ def tst_grouping
161
+ f = Format[]
162
+ refute f.grouping?
163
+ f.set_grouping! :thousands
164
+ assert f.grouping?
165
+ assert_equal [3], f.grouping
166
+ f.set_grouping! false
167
+ refute f.grouping?
168
+ assert_equal [], f.grouping
169
+ end
170
+
124
171
  end
@@ -223,4 +223,29 @@ class TestFormatInput < Test::Unit::TestCase # < Minitest::Test
223
223
 
224
224
  end
225
225
 
226
+ def test_padding
227
+ f = Format[padding: '*']
228
+ assert_equal 643454333.32, f.read("******643,454,333.32", type: Float)
229
+ assert_equal -643454333.32, f.read("*****-643,454,333.32", type: Float)
230
+ assert_equal -643454333.32, f.read("-*****643,454,333.32", type: Float)
231
+ assert_equal 643454333.32, f.read("+*****643,454,333.32", type: Float)
232
+ assert_equal 643454333.32, f.read("643,454,333.32******", type: Float)
233
+ assert_equal -643454333.32, f.read("-643,454,333.32*****", type: Float)
234
+ assert_equal 643454333.32, f.read("***643,454,333.32***", type: Float)
235
+ assert_equal 643454333.32, f.read("***643,454,333.32***", type: Float)
236
+ f.set_leading_zeros! 10
237
+ assert_equal 123, f.read("0000000123", type: Integer)
238
+ assert_equal -123, f.read("-000000123", type: Integer)
239
+ assert_equal 123.5, f.read("00000123.5", type: Float)
240
+ assert_equal -123.5, f.read("-00000123.5000", type: Float)
241
+ assert_equal 123.5, f.read("00000123.5000", type: Float)
242
+ assert_equal 100.5, f.read("00000100.5", type: Float)
243
+ assert_equal -100.5, f.read("-0000100.5", type: Float)
244
+ assert_equal Rational(1,3), f.read("000000.<3>", type: Rational)
245
+ assert_equal Rational(-1,3), f.read("-00000.<3>", type: Rational)
246
+ f.set_padding! '*'
247
+ assert_equal Flt::DecNum('0.667'), f.read("********0.667*******", type: Flt::DecNum)
248
+ assert_equal Flt::DecNum('-0.667'), f.read("*******-0.667*******", type: Flt::DecNum)
249
+ end
250
+
226
251
  end
@@ -229,7 +229,7 @@ class TestFormatOutput < Test::Unit::TestCase # < Minitest::Test
229
229
  def test_write_binnum_hex
230
230
  context = Flt::BinNum::IEEEDoubleContext
231
231
  x = Flt::BinNum('0.1', :fixed, context: context)
232
- assert_equal "1.999999999999Ap-4", Format[:hexbin].write(x)
232
+ assert_equal "0x1.999999999999Ap-4", Format[:hexbin].write(x)
233
233
  end
234
234
 
235
235
  def test_write_to_file
@@ -786,4 +786,63 @@ class TestFormatOutput < Test::Unit::TestCase # < Minitest::Test
786
786
  Format[:short, repeating: false].write(Rational(2469,200))
787
787
  end
788
788
  end
789
+
790
+ def test_padding
791
+ f = Format[padding: [:right, 20, fill: 0]]
792
+
793
+ f = Format[padding: [:left, 20, fill: '*']]
794
+ f = f[symbols: [group_separator: ',', grouping: [3]]]
795
+ assert_equal "******643,454,333.32", f.write(643454333.32)
796
+ assert_equal "*****-643,454,333.32", f.write(-643454333.32)
797
+ assert_equal "-*****643,454,333.32", f[padding: :internal].write(-643454333.32)
798
+ f = f[padding: :right]
799
+ assert_equal "643,454,333.32******", f.write(643454333.32)
800
+ assert_equal "-643,454,333.32*****", f.write(-643454333.32)
801
+ f = f[padding: :center]
802
+ assert_equal "***643,454,333.32***", f.write(643454333.32)
803
+ assert_equal "***-643,454,333.32**", f.write(-643454333.32)
804
+ f.set_leading_zeros! 10
805
+ assert_equal "0000000123", f.write(123)
806
+ assert_equal "-000000123", f.write(-123)
807
+ assert_equal "00000123.5", f.write(123.5)
808
+ assert_equal "-0000123.5", f.write(-123.5)
809
+ f.set_grouping! false
810
+ assert_equal "0123456789", f.write(123456789)
811
+ assert_equal "1234567891", f.write(1234567891)
812
+ assert_equal "12345678912", f.write(12345678912)
813
+ assert_equal "-123456789", f.write(-123456789)
814
+ assert_equal "-1234567891", f.write(-1234567891)
815
+ assert_equal "-12345678912", f.write(-12345678912)
816
+ f.set! symbols: [repeat_delimited: true]
817
+ assert_equal "000000.<3>", f.write(Rational(1,3))
818
+ assert_equal "-00000.<3>", f.write(Rational(-1,3))
819
+ assert_equal "000000.<6>", f.write(Rational(2,3))
820
+ assert_equal "-00000.<6>", f.write(Rational(-2,3))
821
+ f.set! rounding: [places: 3]
822
+ assert_equal "000000.333", f.write(Rational(1,3))
823
+ assert_equal "-00000.333", f.write(Rational(-1,3))
824
+ assert_equal "000000.667", f.write(Rational(2,3))
825
+ assert_equal "-00000.667", f.write(Rational(-2,3))
826
+ f.set! rounding: [places: 4]
827
+ assert_equal "00000.3333", f.write(Rational(1,3))
828
+ assert_equal "-0000.3333", f.write(Rational(-1,3))
829
+ assert_equal "00000.6667", f.write(Rational(2,3))
830
+ assert_equal "-0000.6667", f.write(Rational(-2,3))
831
+ f.set! padding: [:center, width: 20, fill: '*']
832
+ f.set! rounding: [places: 3]
833
+ assert_equal "********0.667*******", f.write(Rational(2,3))
834
+ assert_equal "*******-0.667*******", f.write(Rational(-2,3))
835
+ f.set! rounding: [places: 4]
836
+ assert_equal "*******0.5555*******", f.write(Flt::DecNum('0.5555'))
837
+ f.set! padding: 8
838
+ assert_equal "*0.5555*", f.write(Flt::DecNum('0.5555'))
839
+ f.set! padding: 7
840
+ assert_equal "*0.5555", f.write(Flt::DecNum('0.5555'))
841
+ f.set! padding: 6
842
+ assert_equal "0.5555", f.write(Flt::DecNum('0.5555'))
843
+ f.set! padding: 5
844
+ assert_equal "0.5555", f.write(Flt::DecNum('0.5555'))
845
+ f.set! padding: 4
846
+ assert_equal "0.5555", f.write(Flt::DecNum('0.5555'))
847
+ end
789
848
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: numerals
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Javier Goizueta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-31 00:00:00.000000000 Z
11
+ date: 2015-04-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: flt
@@ -99,8 +99,9 @@ files:
99
99
  - lib/numerals/format/notations/text.rb
100
100
  - lib/numerals/format/output.rb
101
101
  - lib/numerals/format/symbols.rb
102
+ - lib/numerals/format/symbols/digits.rb
103
+ - lib/numerals/format/symbols/padding.rb
102
104
  - lib/numerals/format/text_parts.rb
103
- - lib/numerals/formatting/options.rb
104
105
  - lib/numerals/formatting_aspect.rb
105
106
  - lib/numerals/numeral.rb
106
107
  - lib/numerals/repeat_detector.rb
@@ -1,84 +0,0 @@
1
-
2
- module Numerals
3
-
4
- # Repeating decimal configuration options
5
- class Options
6
- include ModalSupport::StateEquivalent
7
- include ModalSupport::BracketConstructor
8
-
9
- def initialize(options={})
10
- options = {
11
- # default options
12
- delim: ['<', '>'],
13
- suffix: '...',
14
- sep: '.',
15
- grouping: [',', []], # [...,[3]] for thousands separators
16
- special: ['NaN', 'Infinity'],
17
- digits: nil,
18
- signs: ['+', '-'],
19
- maximum_number_of_digits: Numeral.maximum_number_of_digits
20
- }.merge(options)
21
-
22
- set_delim *Array(options[:delim])
23
- set_suffix options[:suffix]
24
- set_sep options[:sep]
25
- set_grouping *Array(options[:grouping])
26
- set_special *Array(options[:special])
27
- set_digits *Array(options[:digits])
28
- set_signs *Array(options[:signs])
29
- @maximum_number_of_digits = options[:maximum_number_of_digits]
30
- end
31
-
32
- attr_accessor :begin_rep, :end_rep, :auto_rep, :dec_sep, :grp_sep, :grp, :maximum_number_of_digits
33
- attr_accessor :nan_txt, :inf_txt, :plus_sign, :minus_sign
34
-
35
- def set_delim(begin_d, end_d='')
36
- @begin_rep = begin_d
37
- @end_rep = end_d
38
- return self
39
- end
40
-
41
- def set_suffix(a)
42
- @auto_rep = a
43
- return self
44
- end
45
-
46
- def set_sep(d)
47
- @dec_sep = d
48
- return self
49
- end
50
-
51
- def set_grouping(sep, g=[])
52
- @grp_sep = sep
53
- @grp = g
54
- return self
55
- end
56
-
57
- def set_special(nan_txt, inf_txt)
58
- @nan_txt = nan_txt
59
- @inf_txt = inf_txt
60
- return self
61
- end
62
-
63
- def set_digits(*args)
64
- @digits_defined = !args.empty?
65
- @digits = DigitsDefinition[*args]
66
- self
67
- end
68
-
69
- def set_signs(plus, minus)
70
- @plus_sign = plus
71
- @minus_sign = minus
72
- end
73
-
74
- attr_accessor :digits
75
-
76
- def digits_defined?
77
- @digits_defined
78
- end
79
-
80
- end
81
-
82
- DEFAULT_OPTIONS = Options[]
83
-
84
- end