numerals 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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