lib/numerals/format/symbols.rb in numerals-0.1.0 vs lib/numerals/format/symbols.rb in numerals-0.2.0
- old
+ new
@@ -9,162 +9,10 @@
#
# * repeating : (boolean) support repeating decimals?
#
class Format::Symbols < FormattingAspect
- class Digits < FormattingAspect
-
- 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)
-
- def initialize(*args)
- @digits = DEFAULT_DIGITS
- @downcase_digits = @digits.map(&:downcase)
- @max_base = @digits.size
- @case_sensitive = false
- @uppercase = false
- @lowercase = false
- set! *args
- end
-
- include ModalSupport::StateEquivalent
-
- set do |*args|
- options = extract_options(*args)
- options.each do |option, value|
- send :"#{option}=", value
- end
- end
-
- attr_reader :digits_string, :max_base, :case_sensitive, :uppercase, :lowercase
- attr_writer :case_sensitive
-
- def digits(options = {})
- base = options[:base] || @max_base
- if base >= @max_base
- @digits
- else
- @digits[0, base]
- end
- end
-
- def digits=(digits)
- if digits.is_a?(String)
- @digits = digits.each_char.to_a
- else
- @digits = digits
- end
- @max_base = @digits.size
- @lowercase = @digits.all? { |d| d.downcase == d }
- @uppercase = @digits.all? { |d| d.upcase == d }
- @downcase_digits = @digits.map(&:downcase)
- if @digits.uniq.size != @max_base
- raise "Inconsistent digits"
- end
- end
-
- def uppercase=(v)
- @uppercase = v
- self.digits = @digits.map(&:upcase) if v
- end
-
- def lowercase=(v)
- @lowercase = v
- self.digits = @digits.map(&:downcase) if v
- end
-
- def case_sensitive?
- case_sensitive
- end
-
- def is_digit?(digit_symbol, options={})
- base = options[:base] || @max_base
- raise "Invalid base" if base > @max_base
- v = digit_value(digit_symbol)
- v && v < base
- end
-
- def digit_value(digit)
- if @case_sensitive
- @digits.index(digit)
- else
- @downcase_digits.index(digit.downcase)
- end
- end
-
- def digit_symbol(v, options={})
- base = options[:base] || @max_base
- raise "Invalid base" if base > @max_base
- v >= 0 && v < base ? @digits[v] : nil
- end
-
- # Convert sequence of digits to its text representation.
- # The nil value can be used in the digits sequence to
- # represent the group separator.
- def digits_text(digit_values, options={})
- insignificant_digits = options[:insignificant_digits] || 0
- num_digits = digit_values.reduce(0) { |num, digit|
- digit.nil? ? num : num + 1
- }
- num_digits -= insignificant_digits
- digit_values.map { |d|
- if d.nil?
- options[:separator]
- else
- num_digits -= 1
- if num_digits >= 0
- digit_symbol(d, options)
- else
- options[:insignificant_symbol]
- end
- end
- }.join
- end
-
- def parameters
- params = {}
- params[:digits] = @digits
- params[:case_sensitive] = @case_sensitive
- params[:uppercase] = @uppercase
- params[:lowercase] = @lowercase
- params
- end
-
- def to_s
- # TODO: show only non-defaults
- "Digits[#{parameters.inspect.unwrap('{}')}]"
- end
-
- def inspect
- "Format::Symbols::#{self}"
- end
-
- def dup
- Digits[parameters]
- end
-
- private
-
- def extract_options(*args)
- options = {}
- args = args.first if args.size == 1 && args.first.kind_of?(Array)
- args.each do |arg|
- case arg
- when Hash
- options.merge! arg
- when String, Array
- options[:digits] = arg
- when Format::Symbols::Digits
- options.merge! arg.parameters
- else
- raise "Invalid Symbols::Digits definition"
- end
- end
- options
- end
-
- end
-
DEFAULTS = {
nan: 'NaN',
infinity: 'Infinity',
plus: '+',
minus: '-',
@@ -173,22 +21,23 @@
group_separator: ',',
zero: nil,
repeat_begin: '<',
repeat_end: '>',
repeat_suffix: '...',
- #repeat_detect: false,
show_plus: false,
show_exponent_plus: false,
uppercase: false,
lowercase: false,
show_zero: true,
show_point: false,
repeat_delimited: false,
repeat_count: 3,
grouping: [],
insignificant_digit: nil,
- repeating: true
+ repeating: true,
+ base_prefix: nil,
+ base_suffix: nil
}
def initialize(*args)
DEFAULTS.each do |param, value|
instance_variable_set "@#{param}", value
@@ -197,33 +46,31 @@
# @digits is a mutable Object, so we don't want
# to set it from DEFAULTS (which would share the
# default Digits among all Symbols)
@digits = Format::Symbols::Digits[]
- # TODO: justification/padding
- # width, adjust_mode (left, right, internal), fill_symbol
+ # same with @padding
+ @padding = Format::Symbols::Padding[]
- # TODO: base_suffixes, base_preffixes, show_base
-
set! *args
end
- # TODO: transmit uppercase/lowercase to digits
-
attr_reader :digits, :nan, :infinity, :plus, :minus, :exponent, :point,
- :group_separator, :zero, :insignificant_digit
- attr_reader :repeat_begin, :repeat_end, :repeat_suffix, :repeat_delimited
- attr_reader :show_plus, :show_exponent_plus, :uppercase, :lowercase,
- :show_zero, :show_point
- attr_reader :grouping, :repeat_count, :repeating
+ :group_separator, :zero, :insignificant_digit, :padding,
+ :repeat_begin, :repeat_end, :repeat_suffix, :repeat_delimited,
+ :show_plus, :show_exponent_plus, :uppercase, :lowercase,
+ :show_zero, :show_point,
+ :grouping, :repeat_count, :repeating,
+ :base_prefix, :base_suffix
- attr_writer :uppercase, :lowercase, :nan, :infinity, :plus,
+ attr_writer :digits, :uppercase, :lowercase, :nan, :infinity, :plus,
:minus, :exponent, :point, :group_separator, :zero,
:repeat_begin, :repeat_end, :repeat_suffix,
:show_plus, :show_exponent_plus, :show_zero, :show_point,
:repeat_delimited, :repeat_count, :grouping,
- :insignificant_digit, :repeating
+ :insignificant_digit, :repeating,
+ :base_prefix, :base_suffix
include ModalSupport::StateEquivalent
def positive_infinity
txt = ""
@@ -249,43 +96,84 @@
def grouping?
!@grouping.empty? && @group_separator && !@group_separator.empty?
end
+ def padded?
+ @padding.padded?
+ end
+
+ def fill
+ fill = @padding.fill
+ if fill.is_a?(Integer)
+ @digits.digit_symbol(fill)
+ else
+ fill
+ end
+ end
+
set do |*args|
options = extract_options(*args)
options.each do |option, value|
if option == :digits
@digits.set! value
+ elsif option == :padding
+ @padding.set! value
else
send :"#{option}=", value
end
end
apply_case!
end
- attr_writer :digits, :nan, :infinity,
- :plus, :minus, :exponent, :point, :group_separator, :zero,
- :repeat_begin, :repeat_end, :repeat_suffix, :show_plus,
- :show_exponent_plus, :uppercase, :show_zero, :show_point,
- :grouping, :repeat_count
-
aspect :repeat do |*args|
- # TODO accept hash :begin, :end, :suffix, ...
+ args.each do |arg|
+ case arg
+ when true, false
+ @repeating = arg
+ when Integer
+ @repeat_count = arg
+ when :delimited
+ @repeat_delimited = true
+ when :suffixed
+ @repeat_delimited = false
+ when Hash
+ arg.each do |key, value|
+ case key
+ when :delimiters
+ @repeat_begin, @repeat_end = Array(value)
+ when :begin
+ @repeat_begin = value
+ when :end
+ @repeat_end = value
+ when :suffix
+ @repeat_suffix = value
+ when :delimited
+ @repeat_delimited = value
+ when :count
+ @repeat_count = value
+ else
+ send "#{key}=", value
+ end
+ end
+ end
+ end
end
aspect :grouping do |*args|
args.each do |arg|
case arg
when Symbol
if arg == :thousands
- @groups = [3]
+ @grouping = [3]
end
when String
@group_separator = arg
when Array
- @groups = groups
+ @grouping = arg
+ when false
+ @grouping = []
end
end
end
def case_sensitive
@@ -349,10 +237,18 @@
aspect :minus do |minus|
@minus = minus
end
+ aspect :padding do |*args|
+ @padding.set! *args
+ end
+
+ aspect :leading_zeros do |width|
+ @padding.leading_zeros = width
+ end
+
def parameters(abbreviated=false)
params = {}
DEFAULTS.each do |param, default|
value = instance_variable_get("@#{param}")
if !abbreviated || value != default
@@ -360,10 +256,13 @@
end
end
if !abbreviated || @digits != Format::Symbols::Digits[]
params[:digits] = @digits
end
+ if !abbreviated || @padding != Format::Symbols::Padding[]
+ params[:padding] = @padding
+ end
params
end
def to_s
"Digits[#{parameters(true).inspect.unwrap('{}')}]"
@@ -445,11 +344,13 @@
options = args.pop if args.last.is_a?(Hash)
options ||= {}
symbols = args
digits = symbols.delete(:digits)
grouped_digits = symbols.delete(:grouped_digits)
- symbols = symbols.map { |s| send(s.to_sym) }
+ symbols = symbols.map { |s|
+ s.is_a?(Symbol) ? send(s) : s
+ }
if grouped_digits
symbols += [group_separator, insignificant_digit]
elsif digits
symbols += [insignificant_digit]
end
@@ -477,10 +378,27 @@
@digits.digit_value(digit)
end
}.compact
end
+ # Returns left, internal and right padding for a number
+ # of given size (number of characters)
+ def paddings(number_size)
+ left_padding = internal_padding = right_padding = ''
+ if padded?
+ left_padding_size, internal_padding_size, right_padding_size = padding.padding_sizes(number_size)
+ right_padding_size = right_padding_size/fill.size
+ right_padding = fill*right_padding_size
+ d = right_padding_size - right_padding.size
+ left_padding_size = (left_padding_size + d)/fill.size
+ left_padding = fill*left_padding_size
+ internal_padding_size = internal_padding_size/fill.size
+ internal_padding = fill*internal_padding_size
+ end
+ [left_padding, internal_padding, right_padding ]
+ end
+
private
def regexp_char(c, options = {})
c_upcase = c.upcase
c_downcase = c.downcase
@@ -498,14 +416,26 @@
def regexp_group(symbols, options = {})
capture = !options[:no_capture]
symbols = Array(symbols).compact.select { |s| !s.empty? }
.map{ |d| regexp_symbol(d, options) }.join('|')
if capture
- "(#{symbols})"
+ symbols = "(#{symbols})"
else
- "(?:#{symbols})"
+ if symbols != ''
+ symbols = "(?:#{symbols})"
+ if options[:optional]
+ if options[:multiple]
+ symbols = "#{symbols}*"
+ else
+ symbols = "#{symbols}?"
+ end
+ elsif options[:multiple]
+ symbols = "#{symbols}+"
+ end
+ end
end
+ symbols
end
def extract_options(*args)
options = {}
args = args.first if args.size == 1 && args.first.kind_of?(Array)
@@ -538,11 +468,12 @@
@group_separator = @group_separator.upcase
@zero = @zero.upcase if @zero
@repeat_begin = @repeat_begin.upcase
@repeat_end = @repeat_end.upcase
@repeat_suffix = @repeat_suffix.upcase
- @digits = @digits[uppercase: true]
+ @digits.set! uppercase: true
+ @padding.fill = @padding.fill.upcase if @padding.fill.is_a?(String)
elsif @lowercase
@nan = @nan.downcase
@infinity = @infinity.downcase
@plus = @plus.downcase
@exponent = @exponent.downcase
@@ -550,16 +481,20 @@
@group_separator = @group_separator.downcase
@zero = @zero.downcase if @zero
@repeat_begin = @repeat_begin.downcase
@repeat_end = @repeat_end.downcase
@repeat_suffix = @repeat_suffix.downcase
- @digits = @digits[lowercase: true]
+ @digits.set! lowercase: true
+ @padding.fill = @padding.fill.downcase if @padding.filll.is_a?(String)
end
end
def cased(symbol)
@uppercase ? symbol.upcase : @lowercase ? symbol.downcase : symbol
end
end
end
+
+require 'numerals/format/symbols/digits'
+require 'numerals/format/symbols/padding'