ruby-cldr 0.0.1
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.
- data/LICENSE +20 -0
- data/README.textile +104 -0
- data/Rakefile +18 -0
- data/TODO +68 -0
- data/VERSION +1 -0
- data/cldr.thor +5 -0
- data/lib/cldr/data/base.rb +66 -0
- data/lib/cldr/data/calendars/gregorian.rb +124 -0
- data/lib/cldr/data/calendars.rb +12 -0
- data/lib/cldr/data/currencies.rb +26 -0
- data/lib/cldr/data/delimiters.rb +21 -0
- data/lib/cldr/data/languages.rb +17 -0
- data/lib/cldr/data/numbers.rb +60 -0
- data/lib/cldr/data/plurals/cldr_grammar.treetop +50 -0
- data/lib/cldr/data/plurals/grammar.rb +536 -0
- data/lib/cldr/data/plurals/rules.rb +113 -0
- data/lib/cldr/data/plurals.rb +39 -0
- data/lib/cldr/data/territories.rb +17 -0
- data/lib/cldr/data/timezones.rb +25 -0
- data/lib/cldr/data/units.rb +25 -0
- data/lib/cldr/data.rb +34 -0
- data/lib/cldr/download.rb +20 -0
- data/lib/cldr/export/ruby.rb +16 -0
- data/lib/cldr/export/yaml.rb +39 -0
- data/lib/cldr/export.rb +69 -0
- data/lib/cldr/format/currency.rb +11 -0
- data/lib/cldr/format/date.rb +144 -0
- data/lib/cldr/format/datetime/base.rb +32 -0
- data/lib/cldr/format/datetime.rb +28 -0
- data/lib/cldr/format/decimal/base.rb +18 -0
- data/lib/cldr/format/decimal/fraction.rb +28 -0
- data/lib/cldr/format/decimal/integer.rb +46 -0
- data/lib/cldr/format/decimal/number.rb +44 -0
- data/lib/cldr/format/decimal.rb +32 -0
- data/lib/cldr/format/percent.rb +11 -0
- data/lib/cldr/format/time.rb +71 -0
- data/lib/cldr/format.rb +113 -0
- data/lib/cldr/thor.rb +33 -0
- data/lib/cldr.rb +7 -0
- data/lib/core_ext/hash/deep_merge.rb +7 -0
- data/lib/core_ext/hash/deep_stringify_keys.rb +11 -0
- data/lib/core_ext/hash/symbolize_keys.rb +10 -0
- data/lib/core_ext/string/camelize.rb +5 -0
- data/lib/core_ext/string/underscore.rb +9 -0
- data/test/all.rb +3 -0
- data/test/data/all.rb +3 -0
- data/test/data/calendars_test.rb +149 -0
- data/test/data/currencies_test.rb +47 -0
- data/test/data/delimiters_test.rb +31 -0
- data/test/data/languages_test.rb +67 -0
- data/test/data/numbers_test.rb +61 -0
- data/test/data/plurals_test.rb +132 -0
- data/test/data/territories_test.rb +51 -0
- data/test/data/timezones_test.rb +56 -0
- data/test/data/units_test.rb +36 -0
- data/test/export_test.rb +57 -0
- data/test/formats/all.rb +3 -0
- data/test/formats/datetime/all.rb +3 -0
- data/test/formats/datetime/date_test.rb +31 -0
- data/test/formats/datetime/datetime_test.rb +18 -0
- data/test/formats/datetime/day_test.rb +41 -0
- data/test/formats/datetime/hour_test.rb +79 -0
- data/test/formats/datetime/minute_test.rb +25 -0
- data/test/formats/datetime/month_test.rb +76 -0
- data/test/formats/datetime/period_test.rb +20 -0
- data/test/formats/datetime/quarter_test.rb +118 -0
- data/test/formats/datetime/second_test.rb +80 -0
- data/test/formats/datetime/time_test.rb +33 -0
- data/test/formats/datetime/timezone_test.rb +23 -0
- data/test/formats/datetime/year_test.rb +57 -0
- data/test/formats/decimal/fraction_test.rb +17 -0
- data/test/formats/decimal/integer_test.rb +67 -0
- data/test/formats/decimal/number_test.rb +77 -0
- data/test/formats/decimal_test.rb +19 -0
- data/test/formats/format_test.rb +66 -0
- data/test/formats/rails_compat/all.rb +3 -0
- data/test/formats/rails_compat/format_integer_test.rb +56 -0
- data/test/formats/rails_compat/format_number_test.rb +134 -0
- data/test/test_helper.rb +5 -0
- metadata +169 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
# I probably don't really get timezones.
|
2
|
+
|
3
|
+
class Cldr
|
4
|
+
module Data
|
5
|
+
class Timezones < Base
|
6
|
+
def initialize(locale)
|
7
|
+
super
|
8
|
+
self[:timezones] = timezones
|
9
|
+
end
|
10
|
+
|
11
|
+
def timezones
|
12
|
+
@timezones ||= select('dates/timeZoneNames/zone').inject({}) do |result, zone|
|
13
|
+
type = zone.attribute('type').value.to_sym
|
14
|
+
city = select(zone, 'exemplarCity').first
|
15
|
+
result[type] = {}
|
16
|
+
# see en.xml, Europe/London, does not have an exemplarCity element
|
17
|
+
# instead it has long and short daylight names which otherwise only
|
18
|
+
# have metazones. (??)
|
19
|
+
result[type][:city] = city.content if city
|
20
|
+
result
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Cldr
|
2
|
+
module Data
|
3
|
+
class Units < Base
|
4
|
+
def initialize(locale)
|
5
|
+
super
|
6
|
+
self[:units] = units
|
7
|
+
end
|
8
|
+
|
9
|
+
def units
|
10
|
+
select('units/unit').inject({}) do |result, node|
|
11
|
+
result[node.attribute('type').value.to_sym] = unit(node)
|
12
|
+
result
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def unit(node)
|
17
|
+
node.xpath('unitPattern').inject({}) do |result, node|
|
18
|
+
count = node.attribute('count') ? node.attribute('count').value.to_sym : :one
|
19
|
+
result[count] = node.content unless draft?(node)
|
20
|
+
result
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/cldr/data.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'core_ext/string/camelize'
|
2
|
+
|
3
|
+
class Cldr
|
4
|
+
module Data
|
5
|
+
autoload :Base, 'cldr/data/base'
|
6
|
+
autoload :Calendars, 'cldr/data/calendars'
|
7
|
+
autoload :Currencies, 'cldr/data/currencies'
|
8
|
+
autoload :Delimiters, 'cldr/data/delimiters'
|
9
|
+
autoload :Languages, 'cldr/data/languages'
|
10
|
+
autoload :Numbers, 'cldr/data/numbers'
|
11
|
+
autoload :Plurals, 'cldr/data/plurals'
|
12
|
+
autoload :Territories, 'cldr/data/territories'
|
13
|
+
autoload :Timezones, 'cldr/data/timezones'
|
14
|
+
autoload :Units, 'cldr/data/units'
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def dir
|
18
|
+
@dir ||= File.expand_path('./vendor/cldr/common')
|
19
|
+
end
|
20
|
+
|
21
|
+
def dir=(dir)
|
22
|
+
@dir = dir
|
23
|
+
end
|
24
|
+
|
25
|
+
def locales
|
26
|
+
Dir["#{dir}/main/*.xml"].map { |path| path =~ /([\w_-]+)\.xml/ && $1 }
|
27
|
+
end
|
28
|
+
|
29
|
+
def components
|
30
|
+
self.constants.sort - [:Base, :Export]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'net/http'
|
3
|
+
require 'uri'
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
class Cldr
|
7
|
+
class << self
|
8
|
+
def download(source = nil, target = nil)
|
9
|
+
source ||= 'http://unicode.org/Public/cldr/1.7.2/core.zip'
|
10
|
+
target ||= File.expand_path('./vendor/cldr')
|
11
|
+
|
12
|
+
source = URI.parse(source)
|
13
|
+
tempfile = Tempfile.new('cldr-core')
|
14
|
+
|
15
|
+
system("curl #{source} -o #{tempfile.path}")
|
16
|
+
FileUtils.mkdir_p(target)
|
17
|
+
system("unzip #{tempfile.path} -d #{target}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Cldr
|
2
|
+
module Export
|
3
|
+
class Ruby
|
4
|
+
def export(locale, component, options = {})
|
5
|
+
data = Export.data(component, locale, options)
|
6
|
+
data = data.to_ruby if data.respond_to?(:to_ruby)
|
7
|
+
unless data.empty?
|
8
|
+
path = Export.path(locale, component, 'rb')
|
9
|
+
Export.write(path, data)
|
10
|
+
yield(component, locale, path) if block_given?
|
11
|
+
data
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'ya2yaml'
|
2
|
+
|
3
|
+
class Cldr
|
4
|
+
module Export
|
5
|
+
class Yaml < Ya2YAML
|
6
|
+
def initialize
|
7
|
+
super(:syck_compatible => true)
|
8
|
+
end
|
9
|
+
|
10
|
+
def export(locale, component, options = {})
|
11
|
+
data = Export.data(component, locale, options)
|
12
|
+
unless data.empty?
|
13
|
+
data = { locale.to_s.gsub('_', '-') => data }.deep_stringify_keys
|
14
|
+
path = Export.path(locale, component, 'yml')
|
15
|
+
Export.write(path, yaml(data))
|
16
|
+
yield(component, locale, path) if block_given?
|
17
|
+
data
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def yaml(data)
|
22
|
+
emit(data, 1)[1..-1]
|
23
|
+
end
|
24
|
+
|
25
|
+
def emit(object, level = 1)
|
26
|
+
result = object.is_a?(Symbol) ? object.inspect : super
|
27
|
+
result.gsub(/(\s{1})(no):/i) { %(#{$1}"#{$2}":) } # FIXME fucking spaghetti code
|
28
|
+
end
|
29
|
+
|
30
|
+
def is_one_plain_line?(str)
|
31
|
+
# removed REX_BOOL, REX_INT
|
32
|
+
str !~ /^([\-\?:,\[\]\{\}\#&\*!\|>'"%@`\s]|---|\.\.\.)/ &&
|
33
|
+
str !~ /[:\#\s\[\]\{\},]/ &&
|
34
|
+
str !~ /#{REX_ANY_LB}/ &&
|
35
|
+
str !~ /^(#{REX_FLOAT}|#{REX_MERGE}|#{REX_NULL}|#{REX_TIMESTAMP}|#{REX_VALUE})$/x
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/cldr/export.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'i18n'
|
2
|
+
require 'i18n/locale/fallbacks'
|
3
|
+
require 'core_ext/string/camelize'
|
4
|
+
require 'core_ext/string/underscore'
|
5
|
+
require 'core_ext/hash/deep_stringify_keys'
|
6
|
+
|
7
|
+
class Cldr
|
8
|
+
module Export
|
9
|
+
autoload :Ruby, 'cldr/export/ruby'
|
10
|
+
autoload :Yaml, 'cldr/export/yaml'
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def base_path
|
14
|
+
@@base_path ||= File.expand_path('./data')
|
15
|
+
end
|
16
|
+
|
17
|
+
def base_path=(base_path)
|
18
|
+
@@base_path = base_path
|
19
|
+
end
|
20
|
+
|
21
|
+
def export(options = {}, &block)
|
22
|
+
locales = options[:locales] || Data.locales
|
23
|
+
components = options[:components] || Data.components
|
24
|
+
self.base_path = options[:target] if options[:target]
|
25
|
+
|
26
|
+
locales.each do |locale|
|
27
|
+
components.each do |component|
|
28
|
+
exporter(component, options[:format]).export(locale, component, options, &block)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def exporter(component, format)
|
34
|
+
name = format ? format : component.to_s == 'Plurals' ? 'ruby' : 'yaml'
|
35
|
+
const_get(name.to_s.camelize).new
|
36
|
+
end
|
37
|
+
|
38
|
+
def data(component, locale, options = {})
|
39
|
+
if component.to_s == 'Plurals'
|
40
|
+
Data.const_get(component.to_s.camelize).new(locale)
|
41
|
+
else
|
42
|
+
data = locales(locale, options).inject({}) do |result, locale|
|
43
|
+
data = Data.const_get(component.to_s.camelize).new(locale)
|
44
|
+
data ? data.deep_merge(result) : result
|
45
|
+
end
|
46
|
+
# data = resolve_links if options[:merge] TODO!!
|
47
|
+
data
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def locales(locale, options)
|
52
|
+
locale = locale.to_s.gsub('_', '-')
|
53
|
+
locales = options[:merge] ? I18n::Locale::Fallbacks.new[locale.to_sym] : [locale.to_sym]
|
54
|
+
locales << :root
|
55
|
+
locales
|
56
|
+
end
|
57
|
+
|
58
|
+
def write(path, data)
|
59
|
+
FileUtils.rm(path) if File.exists?(path)
|
60
|
+
FileUtils.mkdir_p(File.dirname(path))
|
61
|
+
File.open(path, 'w+') { |f| f.write(data) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def path(locale, component, extension)
|
65
|
+
"#{Export.base_path}/#{locale.to_s.gsub('_', '-')}/#{component.to_s.underscore}.#{extension}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
class Cldr
|
2
|
+
module Format
|
3
|
+
class Date < Datetime::Base
|
4
|
+
PATTERN = /G{1,5}|y+|Y+|Q{1,4}|q{1,5}|M{1,5}|L{1,5}|d{1,2}|F{1}|E{1,5}|e{1,5}|c{1,5}/
|
5
|
+
METHODS = { # ignoring u, l, g, j, A
|
6
|
+
'G' => :era,
|
7
|
+
'y' => :year,
|
8
|
+
'Y' => :year_of_week_of_year,
|
9
|
+
'Q' => :quarter,
|
10
|
+
'q' => :quarter_stand_alone,
|
11
|
+
'M' => :month,
|
12
|
+
'L' => :month_stand_alone,
|
13
|
+
'w' => :week_of_year,
|
14
|
+
'W' => :week_of_month,
|
15
|
+
'd' => :day,
|
16
|
+
'D' => :day_of_month,
|
17
|
+
'F' => :day_of_week_in_month,
|
18
|
+
'E' => :weekday,
|
19
|
+
'e' => :weekday_local,
|
20
|
+
'c' => :weekday_local_stand_alone,
|
21
|
+
}
|
22
|
+
|
23
|
+
def era(date, pattern, length)
|
24
|
+
raise 'not implemented'
|
25
|
+
end
|
26
|
+
|
27
|
+
def year(date, pattern, length)
|
28
|
+
year = date.year.to_s
|
29
|
+
year = year.length == 1 ? year : year[-2, 2] if length == 2
|
30
|
+
year = year.rjust(length, '0') if length > 1
|
31
|
+
year
|
32
|
+
end
|
33
|
+
|
34
|
+
def year_of_week_of_year(date, pattern, length)
|
35
|
+
raise 'not implemented'
|
36
|
+
end
|
37
|
+
|
38
|
+
def day_of_week_in_month(date, pattern, length) # e.g. 2nd Wed in July
|
39
|
+
raise 'not implemented'
|
40
|
+
end
|
41
|
+
|
42
|
+
def quarter(date, pattern, length)
|
43
|
+
quarter = (date.month.to_i - 1) / 3 + 1
|
44
|
+
case length
|
45
|
+
when 1
|
46
|
+
quarter.to_s
|
47
|
+
when 2
|
48
|
+
quarter.to_s.rjust(length, '0')
|
49
|
+
when 3
|
50
|
+
calendar[:quarters][:format][:abbreviated][quarter]
|
51
|
+
when 4
|
52
|
+
calendar[:quarters][:format][:wide][quarter]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def quarter_stand_alone(date, pattern, length)
|
57
|
+
quarter = (date.month.to_i - 1) / 3 + 1
|
58
|
+
case length
|
59
|
+
when 1
|
60
|
+
quarter.to_s
|
61
|
+
when 2
|
62
|
+
quarter.to_s.rjust(length, '0')
|
63
|
+
when 3
|
64
|
+
raise 'not yet implemented (requires cldr\'s "multiple inheritance")'
|
65
|
+
# calendar[:quarters][:'stand-alone'][:abbreviated][key]
|
66
|
+
when 4
|
67
|
+
raise 'not yet implemented (requires cldr\'s "multiple inheritance")'
|
68
|
+
# calendar[:quarters][:'stand-alone'][:wide][key]
|
69
|
+
when 5
|
70
|
+
calendar[:quarters][:'stand-alone'][:narrow][quarter]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def month(date, pattern, length)
|
75
|
+
case length
|
76
|
+
when 1
|
77
|
+
date.month.to_s
|
78
|
+
when 2
|
79
|
+
date.month.to_s.rjust(length, '0')
|
80
|
+
when 3
|
81
|
+
calendar[:months][:format][:abbreviated][date.month]
|
82
|
+
when 4
|
83
|
+
calendar[:months][:format][:wide][date.month]
|
84
|
+
when 5
|
85
|
+
raise 'not yet implemented (requires cldr\'s "multiple inheritance")'
|
86
|
+
calendar[:months][:format][:narrow][date.month]
|
87
|
+
else
|
88
|
+
# raise unknown date format
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def month_stand_alone(date, pattern, length)
|
93
|
+
case length
|
94
|
+
when 1
|
95
|
+
date.month.to_s
|
96
|
+
when 2
|
97
|
+
date.month.to_s.rjust(length, '0')
|
98
|
+
when 3
|
99
|
+
raise 'not yet implemented (requires cldr\'s "multiple inheritance")'
|
100
|
+
calendar[:months][:'stand-alone'][:abbreviated][date.month]
|
101
|
+
when 4
|
102
|
+
raise 'not yet implemented (requires cldr\'s "multiple inheritance")'
|
103
|
+
calendar[:months][:'stand-alone'][:wide][date.month]
|
104
|
+
when 5
|
105
|
+
calendar[:months][:'stand-alone'][:narrow][date.month]
|
106
|
+
else
|
107
|
+
# raise unknown date format
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def day(date, pattern, length)
|
112
|
+
case length
|
113
|
+
when 1
|
114
|
+
date.day.to_s
|
115
|
+
when 2
|
116
|
+
date.day.to_s.rjust(length, '0')
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
WEEKDAY_KEYS = [:sun, :mon, :tue, :wed, :thu, :fri, :sat]
|
121
|
+
|
122
|
+
def weekday(date, pattern, length)
|
123
|
+
key = WEEKDAY_KEYS[date.wday]
|
124
|
+
case length
|
125
|
+
when 1..3
|
126
|
+
calendar[:days][:format][:abbreviated][key]
|
127
|
+
when 4
|
128
|
+
calendar[:days][:format][:wide][key]
|
129
|
+
when 5
|
130
|
+
calendar[:days][:'stand-alone'][:narrow][key]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def weekday_local(date, pattern, length)
|
135
|
+
# "Like E except adds a numeric value depending on the local starting day of the week"
|
136
|
+
raise 'not implemented (need to defer a country to lookup the local first day of week from weekdata)'
|
137
|
+
end
|
138
|
+
|
139
|
+
def weekday_local_stand_alone(date, pattern, length)
|
140
|
+
raise 'not implemented (need to defer a country to lookup the local first day of week from weekdata)'
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Cldr
|
2
|
+
module Format
|
3
|
+
class Datetime
|
4
|
+
class Base
|
5
|
+
attr_reader :calendar
|
6
|
+
|
7
|
+
def initialize(format, calendar)
|
8
|
+
@calendar = calendar
|
9
|
+
compile(format)
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def compile(format)
|
15
|
+
(class << self; self; end).class_eval <<-code
|
16
|
+
def apply(date, options = {}); #{compile_format(format)}; end
|
17
|
+
code
|
18
|
+
end
|
19
|
+
|
20
|
+
# compile_format("EEEE, d. MMMM y") # =>
|
21
|
+
# '' + weekday(date, "EEEE", 4) + ', ' + day(date, "d", 1) + '. ' +
|
22
|
+
# month(date, "MMMM", 4) + ' ' + year(date, "y", 1) + ''
|
23
|
+
def compile_format(format)
|
24
|
+
"'" + format.gsub(self.class.const_get(:PATTERN)) do |token|
|
25
|
+
method = self.class.const_get(:METHODS)[token[0, 1]]
|
26
|
+
"' + #{method}(date, #{token.inspect}, #{token.length}) + '"
|
27
|
+
end + "'"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Cldr
|
2
|
+
module Format
|
3
|
+
class Datetime
|
4
|
+
autoload :Base, 'cldr/format/datetime/base'
|
5
|
+
|
6
|
+
attr_reader :date, :time
|
7
|
+
|
8
|
+
def initialize(format, date, time)
|
9
|
+
@date, @time = date, time
|
10
|
+
compile(format)
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def compile(format)
|
16
|
+
(class << self; self; end).class_eval <<-code
|
17
|
+
def apply(datetime, options = {}); #{compile_format(format)}; end
|
18
|
+
code
|
19
|
+
end
|
20
|
+
|
21
|
+
def compile_format(format)
|
22
|
+
"'" + format.gsub(%r(\{(0|1)\})) do |token|
|
23
|
+
"' + #{token == '{0}' ? 'time' : 'date'}.apply(datetime, options) + '"
|
24
|
+
end + "'"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Cldr
|
2
|
+
module Format
|
3
|
+
class Decimal
|
4
|
+
class Base
|
5
|
+
def interpolate(string, value, orientation = :right)
|
6
|
+
value = value.to_s
|
7
|
+
length = value.length
|
8
|
+
start, pad = orientation == :left ? [0, :rjust] : [-length, :ljust]
|
9
|
+
|
10
|
+
string = string.dup
|
11
|
+
string = string.ljust(length, '#') if string.length < length
|
12
|
+
string[start, length] = value
|
13
|
+
string.gsub('#', '')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Cldr
|
2
|
+
module Format
|
3
|
+
class Decimal
|
4
|
+
class Fraction < Base
|
5
|
+
attr_reader :format, :decimal, :precision
|
6
|
+
|
7
|
+
def initialize(format, symbols = {})
|
8
|
+
@format = format ? format.split('.').pop : ''
|
9
|
+
@decimal = symbols[:decimal] || '.'
|
10
|
+
@precision = @format.length
|
11
|
+
end
|
12
|
+
|
13
|
+
def apply(fraction, options = {})
|
14
|
+
precision = options[:precision] || self.precision
|
15
|
+
if precision > 0
|
16
|
+
decimal + interpolate(format(options), fraction, :left)
|
17
|
+
else
|
18
|
+
''
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def format(options)
|
23
|
+
options[:precision] ? '0' * options[:precision] : @format
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Cldr
|
2
|
+
module Format
|
3
|
+
class Decimal
|
4
|
+
class Integer < Base
|
5
|
+
attr_reader :format, :separator, :groups
|
6
|
+
|
7
|
+
def initialize(format, symbols = {})
|
8
|
+
format = format.split('.')[0]
|
9
|
+
@format = prepare_format(format, symbols)
|
10
|
+
@groups = parse_groups(format)
|
11
|
+
@separator = symbols[:group] || ','
|
12
|
+
end
|
13
|
+
|
14
|
+
def apply(number, options = {})
|
15
|
+
format_groups(interpolate(format, number.to_i))
|
16
|
+
end
|
17
|
+
|
18
|
+
def format_groups(string)
|
19
|
+
return string if groups.empty?
|
20
|
+
tokens = []
|
21
|
+
tokens << chop_group(string, groups.first)
|
22
|
+
tokens << chop_group(string, groups.last) while string.length > groups.last
|
23
|
+
tokens << string
|
24
|
+
tokens.compact.reverse.join(separator)
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_groups(format)
|
28
|
+
return [] unless index = format.rindex(',')
|
29
|
+
rest = format[0, index]
|
30
|
+
widths = [format.length - index - 1]
|
31
|
+
widths << rest.length - rest.rindex(',') - 1 if rest.rindex(',')
|
32
|
+
widths.compact.uniq
|
33
|
+
end
|
34
|
+
|
35
|
+
def chop_group(string, size)
|
36
|
+
string.slice!(-size, size) if string.length > size
|
37
|
+
end
|
38
|
+
|
39
|
+
def prepare_format(format, symbols)
|
40
|
+
signs = symbols.values_at(:plus_sign, :minus_sign)
|
41
|
+
format.tr(',', '').tr('+-', signs.join)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class Cldr
|
2
|
+
module Format
|
3
|
+
class Decimal
|
4
|
+
class Number
|
5
|
+
attr_reader :prefix, :suffix, :integer_format, :fraction_format, :symbols
|
6
|
+
|
7
|
+
DEFAULT_SYMBOLS = { :group => ',', :decimal => '.', :plus_sign => '+', :minus_sign => '-' }
|
8
|
+
FORMAT_PATTERN = /([^0#,\.]*)([0#,\.]+)([^0#,\.]*)$/
|
9
|
+
|
10
|
+
def initialize(format, symbols = {})
|
11
|
+
@symbols = DEFAULT_SYMBOLS.merge(symbols)
|
12
|
+
@prefix, @suffix, @integer_format, @fraction_format = *parse_format(format, symbols)
|
13
|
+
end
|
14
|
+
|
15
|
+
def apply(number, options = {})
|
16
|
+
int, fraction = parse_number(number, options)
|
17
|
+
|
18
|
+
result = integer_format.apply(int, options)
|
19
|
+
result << fraction_format.apply(fraction, options) if fraction
|
20
|
+
prefix + result + suffix
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def parse_format(format, symbols = {})
|
26
|
+
format =~ FORMAT_PATTERN
|
27
|
+
prefix, suffix, int, fraction = $1.to_s, $3.to_s, *$2.split('.')
|
28
|
+
[prefix, suffix, Integer.new(int, symbols), Fraction.new(fraction, symbols)]
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse_number(number, options = {})
|
32
|
+
precision = options[:precision] || fraction_format.precision
|
33
|
+
number = round_to(number, precision)
|
34
|
+
number.abs.to_s.split('.')
|
35
|
+
end
|
36
|
+
|
37
|
+
def round_to(number, precision)
|
38
|
+
factor = 10 ** precision
|
39
|
+
(number * factor).round.to_f / factor
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Cldr
|
2
|
+
module Format
|
3
|
+
class Decimal
|
4
|
+
autoload :Base, 'cldr/format/decimal/base'
|
5
|
+
autoload :Fraction, 'cldr/format/decimal/fraction'
|
6
|
+
autoload :Integer, 'cldr/format/decimal/integer'
|
7
|
+
autoload :Number, 'cldr/format/decimal/number'
|
8
|
+
|
9
|
+
attr_reader :positive, :negative
|
10
|
+
|
11
|
+
def initialize(format, symbols = {})
|
12
|
+
@positive, @negative = parse_format(format, symbols)
|
13
|
+
end
|
14
|
+
|
15
|
+
def apply(number, options = {})
|
16
|
+
number = Float(number)
|
17
|
+
format = number.abs == number ? positive : negative
|
18
|
+
format.apply(number, options)
|
19
|
+
rescue TypeError, ArgumentError
|
20
|
+
number
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def parse_format(format, symbols)
|
26
|
+
formats = format.split(symbols[:list] || ';')
|
27
|
+
formats << "#{symbols[:minus] || '-'}#{format}" if formats.size == 1
|
28
|
+
formats.map { |format| Number.new(format, symbols) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|