ruby-cldr 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|