modalh 1.0.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.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'units-system'
4
+
5
+ group :development do
6
+ gem "shoulda", ">= 0"
7
+ gem "rdoc", "~> 3.12"
8
+ gem "bundler", "~> 1"
9
+ gem "jeweler", "~> 1.8.3"
10
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,31 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ git (1.2.5)
5
+ jeweler (1.8.3)
6
+ bundler (~> 1.0)
7
+ git (>= 1.2.5)
8
+ rake
9
+ rdoc
10
+ json (1.6.6)
11
+ modalsupport (0.8.3)
12
+ rake (0.9.2.2)
13
+ rdoc (3.12)
14
+ json (~> 1.4)
15
+ shoulda (3.0.1)
16
+ shoulda-context (~> 1.0.0)
17
+ shoulda-matchers (~> 1.0.0)
18
+ shoulda-context (1.0.0)
19
+ shoulda-matchers (1.0.0)
20
+ units-system (0.2.1)
21
+ modalsupport
22
+
23
+ PLATFORMS
24
+ ruby
25
+
26
+ DEPENDENCIES
27
+ bundler (~> 1)
28
+ jeweler (~> 1.8.3)
29
+ rdoc (~> 3.12)
30
+ shoulda
31
+ units-system
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Javier Goizueta
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,97 @@
1
+ = ModalH
2
+
3
+ This is a plugin for localization & delocalization (parsing localized text representation) of data values (numbers, dates, etc.).
4
+ It also handles values with units (referred to as 'measures')
5
+
6
+ It lives inside the H (for human) namespace; the localization operation is referred to as H.to or to_h
7
+ (conversion of a value to a localized text representation for human consumption)
8
+ and delocalization (parsing) is referred to as H.from or from_h (conversion of a localized text for humans to a data value).
9
+
10
+ It can be integrated with ModalFields[https://github.com/jgoizueta/modalfields] to simplify the declaration of attributes
11
+ to be localized/delocalized.
12
+
13
+ Code for date parsing has been _borrowed_ from delocalize[https://github.com/clemens/delocalize].
14
+
15
+ == Localization/delocalization of general data values
16
+
17
+ In views and controllers the method @to_h@ can be used to localize a value for human consumption
18
+ and @from_h@ to parse human input into a value: (note that @I18n.locale@ will be use as a default)
19
+
20
+ puts to_h(3.4, :locale=>:es, :precision=>3) # => 3,400
21
+ puts from_h('3,400', :locale=>:es, :type=>Float) # => 3.4
22
+
23
+ There are also more type-specific methods @number_to_h@, @number_from_h@, @date_to_h@, @date_from_h@, etc.
24
+
25
+ In models, or any other place outside views or controller the forms @H.to@ (or @H.to_h@) and @H.from@ (or @H.from_h@)
26
+ are available with the same functionality:
27
+
28
+ puts H.to(3.4, :locale=>:es, :precision=>3) # => 3,400
29
+ puts H.from('3,400', :locale=>:es, :type=>Float) # => 3.4
30
+
31
+ Again, type-specific versions exist @H.number_to@, @H.number_from@, etc.
32
+
33
+ Data values can be formatted with units with the @magnitude@ variants:
34
+
35
+ puts H.magnitude_to(3.233, :units=>'km/h', :precision=>2) # => "3,23 km/h"
36
+
37
+ User input with optional units can be parsed and converted to de desired units:
38
+
39
+ puts H.magnitude_from('20 m/s', :units=>'km/h') # => 72.0
40
+
41
+ Inconsistent units will raise an error.
42
+
43
+ == Localization/delocalization of Model attributes
44
+
45
+ An attribute @attr@ can be declared to be localized in its model class like so:
46
+
47
+ number_h :attr, :precision=>3
48
+
49
+ This can also be handled automatically with ModalFields (see the h/fields.rb documentation).
50
+
51
+ When an attributes is declared to be localized, a localized representation of it is available as @attr_h@:
52
+
53
+ puts record.attr_h
54
+
55
+ For example, in a form we could have:
56
+
57
+ f.text_field :attr_h
58
+
59
+ To assign localized text to an attribute @attr_h=@ can be used:
60
+
61
+ record.attr_h = params[:model][:attr_h]
62
+
63
+ So, forms will work as usual by simply using the @_h@ variant of the fields that need localization.
64
+
65
+ When using ModalFields, the desired precision for a localized numeric attribute, if different from that of the database field,
66
+ can be specified with the @:h_precision@ parameter:
67
+
68
+ attr :decimal, :precision=>3, :h_precision=>2
69
+
70
+ An attribute can be declared to have units in its model using this syntax:
71
+
72
+ units_h :attr, :precision=>3, :units=>'mm'
73
+
74
+ With ModalFields and using the provided automatic units detection, an attribute simply has to be named with the units name
75
+ as a suffix (separated by an underscore):
76
+
77
+ attr_mm :decimal, :precision=>3
78
+
79
+ In this case, to prevent an attribute with such suffix from having units, a parameter @:units=>nil@ can be passed.
80
+
81
+ The localized form of an attribute with units, @attr_h@ will include its units as when using @H.magnitude_to@.
82
+
83
+ A localized attribute with units, when assigned through its @attr_h=@ form will accept units in the localized text
84
+ and will perform the proper conversion. Inconsistent units will produce a nil value for the attribute and will
85
+ fail to validate.
86
+
87
+ == TODO
88
+
89
+ * Tests
90
+ * Document translations used
91
+ * New attribute types: bank account number, credit card number, NIF, money, ...
92
+
93
+ == Copyright
94
+
95
+ Copyright (c) 2012 Javier Goizueta. See LICENSE.txt for
96
+ further details.
97
+
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "modalh"
18
+ gem.homepage = "http://github.com/jgoizueta/modalh"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Rails plugin for localization & delocalization}
21
+ gem.description = %Q{Rails plugin for localization & delocalization of data values}
22
+ gem.email = "jgoizueta@gmail.com"
23
+ gem.authors = ["Javier Goizueta"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rake/testtask'
29
+ Rake::TestTask.new(:test) do |test|
30
+ test.libs << 'lib' << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+
35
+ task :default => :test
36
+
37
+ require 'rdoc/task'
38
+ Rake::RDocTask.new do |rdoc|
39
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
+
41
+ rdoc.rdoc_dir = 'rdoc'
42
+ rdoc.title = "modalh #{version}"
43
+ rdoc.rdoc_files.include('README*')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,101 @@
1
+ module H
2
+
3
+ # Allow declaration of H conversion for Model attributes like this:
4
+ #
5
+ # date_h :start
6
+ # number_h :speed, :precision=>3
7
+ #
8
+ # The first argumet to each declaration is the attribute name; the rest of parameters are passed to the H conversion methods
9
+ module ActiveRecordExtensions
10
+
11
+ # Generic H field declarator
12
+ def _h(prefix, attr, *options)
13
+
14
+ instance_variable_set :"@#{attr}_h_options", options
15
+
16
+ class_eval do
17
+
18
+ validates_each attr do |record, attr_name, value|
19
+ record.errors.add attr_name.to_s if record.send(:"\#{attr_name}_h_invalid")
20
+ end
21
+
22
+ # attr=(v)
23
+ define_method :"#{attr}=" do |v|
24
+ write_attribute attr, v
25
+ instance_variable_set "@#{attr}_h", nil
26
+ end
27
+
28
+ # attr_h
29
+ define_method :"#{attr}_h" do
30
+ # don't cache it, because the locale may change
31
+ # unless instance_variable_defined? "@#{attr}_h"
32
+ instance_variable_set "@#{attr}_h", H.send(:"#{prefix}_to", send(attr), *self.class.instance_variable_get(:"@#{attr}_h_options"))
33
+ # end
34
+ instance_variable_get "@#{attr}_h"
35
+ end
36
+
37
+ # attr_h=(txt)
38
+ define_method :"#{attr}_h=" do |txt|
39
+ instance_variable_set "@#{attr}_h", txt
40
+ instance_variable_set "@#{attr}_h_invalid", false
41
+ write_attribute attr, nil
42
+ unless txt.blank?
43
+ begin
44
+ v = H.send(:"#{prefix}_from", txt, *self.class.instance_variable_get(:"@#{attr}_h_options"))
45
+ rescue
46
+ v = nil
47
+ end
48
+ instance_variable_set "@#{attr}_h_invalid", true if v.nil?
49
+ end
50
+ send :"#{attr}=", v
51
+ end
52
+
53
+ # attr_h? (returns true if it is valid and not blank)
54
+ define_method :"#{attr}_h?" do
55
+ !instance_variable_get("@#{attr}_h_invalid") && send(:"#{attr}_h")
56
+ end
57
+
58
+ # attr_h_invalid?
59
+ define_method :"#{attr}_h_invalid?" do
60
+ instance_variable_get "@#{attr}_h_invalid"
61
+ end
62
+
63
+ # attr_h_valid?
64
+ define_method :"#{attr}_h_valid?" do
65
+ !instance_variable_get "@#{attr}_h_invalid"
66
+ end
67
+
68
+ end
69
+
70
+ end
71
+
72
+ [:number, :integer, :date, :logical, :time, :datetime].each do |prefix|
73
+ define_method :"#{prefix}_h" do |attr, *options|
74
+ _h prefix, attr, *options
75
+ end
76
+ end
77
+
78
+ # TODO: support special suffix form of units, e.g. _m2 for m^2, for attribute names
79
+
80
+ def units_h(name, units, options={})
81
+ norm_units = H::Units.normalize_units(units)
82
+ raise ArgumentError, "invalid units #{units}" unless norm_units
83
+ options[:units] = norm_units
84
+ _h :magnitude, name, options
85
+ short_name = name.to_s.chomp("_#{units}")
86
+ class_eval do
87
+ define_method :"#{short_name}_measure" do
88
+ # ::Units::Measure[send(name), units.to_s]
89
+ v = send(name)
90
+ v && v*::Units.u(norm_units)
91
+ end
92
+ define_method :"#{short_name}_measure=" do |v|
93
+ # Units::Measure[send(name), units.to_s]
94
+ send :"#{name}=", v && v.in(norm_units)
95
+ end
96
+ end
97
+ end
98
+
99
+ end
100
+
101
+ end
data/lib/h/fields.rb ADDED
@@ -0,0 +1,142 @@
1
+ # :encoding: utf-8
2
+
3
+ module H
4
+
5
+ # Helpers to use in ModalFields hooks to automatically generate _h declarations
6
+ # Examples of use
7
+ #
8
+ # ModalFields.hook do
9
+ # decimal do |model, declaration|
10
+ # H::Fields.declare_numeric_field model, declaration
11
+ # end
12
+ #
13
+ # float do |model, declaration|
14
+ # H::Fields.declare_numeric_field model, declaration
15
+ # end
16
+ #
17
+ # integer do |model, declaration|
18
+ # H::Fields.declare_integer_field model, declaration
19
+ # end
20
+ # end
21
+ #
22
+ # ModalFields.hook do
23
+ # all_fields do |model, declaration|
24
+ # H::Fields.declare_auto_units_field model, declaration
25
+ # end
26
+ # end
27
+ #
28
+ # ModalFields.hook do
29
+ # all_fields do |model, declaration|
30
+ # H::Fields.declare_auto_field_with_units model, declaration
31
+ # end
32
+ # end
33
+ #
34
+ module Fields
35
+ class <<self
36
+
37
+ # # For numeric types, a display precision different from the stored precision can be selected with
38
+ # # the :h_precision attribute:
39
+ def declare_numeric_field(model, declaration)
40
+ options = declaration.attributes.slice(:precision)
41
+ if precision = declaration.attributes[:h_precision]
42
+ options[:precision] = precision
43
+ end
44
+ declaration.remove_attributes! :h_precision
45
+ model.number_h declaration.name, options
46
+ end
47
+
48
+ # Integers can also be assigned a precision and presented as generic numbers
49
+ def declare_integer_field(model, declaration)
50
+ precision = declaration.attributes[:h_precision] || declaration.attributes[:precision] || 0
51
+ declaration.remove_attributes! :h_precision, :precision
52
+ if precision==0
53
+ model.integer_h declaration.name
54
+ else
55
+ model.number_h declaration.name, :precision=>precision
56
+ end
57
+ end
58
+
59
+ def declare_units_field(model, declaration, suffix_units=nil)
60
+ options = declaration.attributes.slice(:precision, :units)
61
+ options[:units] ||= suffix_units
62
+ if precision = declaration.attributes[:h_precision]
63
+ options[:precision] = precision
64
+ end
65
+ options[:precision] ||= {'m'=>1, 'mm'=>0, 'cm'=>0, 'km'=>3}[options[:units]] # TODO AppSettings
66
+ declaration.remove_attributes! :h_precision, :units
67
+ model.units_h declaration.name, options[:units], options
68
+ end
69
+
70
+ def declare_date_field(model, declaration)
71
+ model.date_h declaration.name
72
+ end
73
+
74
+ def declare_time_field(model, declaration)
75
+ model.time_h declaration.name
76
+ end
77
+
78
+ def declare_datetime_field(model, declaration)
79
+ model.datetime_h declaration.name
80
+ end
81
+
82
+ def declare_boolean_field(model, declaration)
83
+ model.logical_h declaration.name
84
+ end
85
+
86
+ # This is handy to be used in all_fields to make any field with a :units parameter or a valid units suffix a units_h field
87
+ # (and make other numberic fields _h too); If a field with a suffix corresponding to valid units should not be a measure,
88
+ # a :units=>nil parameter should be added.
89
+ def declare_auto_units_field(model, declaration)
90
+ if declaration.type==:float || declaration.type==:decimal || declaration.type==:integer
91
+ units = declaration.attributes[:units]
92
+ unless declaration.attributes.has_key?(:units)
93
+ units = declaration.name.to_s.split('_').last
94
+ end
95
+ if units && H::Units.valid?(units)
96
+ declare_units_field model, declaration, units
97
+ else
98
+ raise ArgumentError, "Invalid units #{declaration.attributes[:units]} in declaration of #{model.name}" if declaration.attributes[:units]
99
+ if declaration.type==:integer
100
+ declare_integer_field model, declaration
101
+ else
102
+ declare_numeric_field model, declaration
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ def declare_auto_field_with_units(model, declaration)
109
+ case declaration.type
110
+ when :date
111
+ declare_date_field model, declaration
112
+ when :time
113
+ declare_time_field model, declaration
114
+ when :datetime
115
+ declare_datetime_field model, declaration
116
+ when :boolean
117
+ declare_boolean_field model, declaration
118
+ else
119
+ declare_auto_units_field model, declaration
120
+ end
121
+ end
122
+
123
+ def declare_auto_field_without_units(model, declaration)
124
+ case declaration.type
125
+ when :date
126
+ declare_date_field model, declaration
127
+ when :time
128
+ declare_time_field model, declaration
129
+ when :datetime
130
+ declare_datetime_field model, declaration
131
+ when :boolean
132
+ declare_boolean_field model, declaration
133
+ when :integer
134
+ declare_integer_field model, declaration
135
+ when :float, :decimal
136
+ declare_numeric_field model, declaration
137
+ end
138
+ end
139
+
140
+ end
141
+ end
142
+ end
data/lib/h/h.rb ADDED
@@ -0,0 +1,264 @@
1
+ # :encoding: utf-8
2
+
3
+ # Localized formatting for Human-iteraction
4
+ module H
5
+
6
+ class <<self
7
+ # localized conversions
8
+
9
+ # Produce data from human-localized text (from user interface)
10
+ def from(txt, options={})
11
+ type = options[:type] || Float
12
+ type = Float if type==:number
13
+ type = check_type(type)
14
+ if type.ancestors.include?(Numeric)
15
+ number_from(txt, options)
16
+ elsif type.respond_to?(:strftime)
17
+ date_from(txt, options)
18
+ elsif type==:logical || type==:boolean
19
+ logical_from(txt, options)
20
+ else
21
+ nil
22
+ end
23
+ end
24
+
25
+ # Generate human-localized text (for user interface) from data
26
+ def to(value, options={})
27
+ case value
28
+ when Numeric
29
+ number_to(value, options)
30
+ when Time, Date, DateTime
31
+ date_to(value, options)
32
+ when TrueClass, FalseClass
33
+ logical_to(value, options)
34
+ else
35
+ options[:blank] || ''
36
+ end
37
+ end
38
+
39
+ def number_to(value, options={})
40
+ options = I18n.translate(:'number.format', :locale => options[:locale]).except(:precision).merge(options)
41
+ precision = options[:precision]
42
+
43
+ return options[:blank] || '' if value.nil?
44
+ unless value.kind_of?(String)
45
+ value = round(value,precision)
46
+ if value.try.nan?
47
+ return options[:nan] || "--"
48
+ elsif value.try.infinite?
49
+ inf = options[:inf] || '∞'
50
+ return value<0 ? "-#{inf}" : inf
51
+ else
52
+ value = value.to_i if precision==0
53
+ value = value.to_s
54
+ value = value[0...-2] if value.ends_with?('.0')
55
+ end
56
+ end
57
+ if options[:delimiter]
58
+ txt = value.to_s.tr(' ','').tr('.,',options[:separator]+options[:delimiter]).tr(options[:delimiter],'')
59
+ else
60
+ txt = value.to_s.tr(' ,','').tr('.',options[:separator])
61
+ end
62
+ raise ArgumentError, "Invalid number #{txt}" unless /\A[+-]?\d+(?:#{Regexp.escape(options[:separator])}\d*)?(?:[eE][+-]?\d+)?\Z/.match(txt)
63
+ if precision && precision>0
64
+ p = txt.index(options[:separator])
65
+ if p.nil?
66
+ txt << options[:separator]
67
+ p = txt.size - 1
68
+ end
69
+ p += 1
70
+ txt << "0"*(precision-txt.size+p) if txt.size-p < precision
71
+ end
72
+ digit_grouping txt, 3, options[:delimiter], txt.index(/\d/), txt.index(options[:separator]) || txt.size
73
+ end
74
+
75
+ def number_from(txt, options={})
76
+ options = I18n.translate(:'number.format', :locale => options[:locale]).except(:precision).merge(options)
77
+ type = check_type(options[:type] || (options[:precision]==0 ? Integer : Float))
78
+
79
+ return nil if txt.to_s.strip.empty? || txt==options[:blank]
80
+
81
+ if options[:delimiter]
82
+ txt = txt.tr(' ','').tr(options[:delimiter]+options[:separator], ',.').tr(',','')
83
+ else
84
+ txt = txt.tr(' ','').tr(options[:separator], '.')
85
+ end
86
+ raise ArgumentError, "Invalid number #{txt}" unless /\A[+-]?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?\Z/.match(txt)
87
+ if type==Float
88
+ txt.to_f
89
+ elsif type==Integer
90
+ txt.to_i
91
+ else
92
+ type.new txt
93
+ end
94
+ end
95
+
96
+ def integer_to(value, options={})
97
+ options = I18n.translate(:'number.format', :locale => options[:locale]).merge(options)
98
+ if value.nil?
99
+ options[:blank] || ''
100
+ else
101
+ value = value.to_s
102
+ digit_grouping value, 3, options[:delimiter], value.index(/\d/), value.size
103
+ end
104
+ end
105
+
106
+ def integer_from(txt)
107
+ if txt.to_s.strip.empty? || txt==options[:blank]
108
+ nil
109
+ else
110
+ txt = txt.tr(' ','')
111
+ txt = txt.tr(options[:delimiter],'') if options[:delimiter]
112
+ txt = txt.tr(options[:separator],'.')
113
+ end
114
+ raise ArgumentError, "Invalid integer #{txt}" unless /\A[+-]?\d+(?:\.0*)?\Z/.match(txt)
115
+ txt.to_i
116
+ end
117
+
118
+ def date_to(value, options={})
119
+ I18n.l(value, options)
120
+ end
121
+
122
+ def date_from(txt, options={})
123
+ options = I18n.translate(:'number.format', :locale => options[:locale]).merge(options)
124
+ type = check_type(options[:type] || Date)
125
+
126
+ return nil if txt.to_s.strip.empty? || txt==options[:blank]
127
+ return txt if txt.respond_to?(:strftime)
128
+
129
+ translate_month_and_day_names! txt, options[:locale]
130
+ input_formats(type).each do |original_format|
131
+ next unless txt =~ /^#{apply_regex(original_format)}$/
132
+
133
+ txt = DateTime.strptime(txt, original_format)
134
+ return Date == type ?
135
+ txt.to_date :
136
+ Time.zone.local(txt.year, txt.mon, txt.mday, txt.hour, txt.min, txt.sec)
137
+ end
138
+ default_parse(txt, type)
139
+ end
140
+
141
+ def time_to(value, options={})
142
+ date_to value, options.reverse_merge(:type=>Time)
143
+ end
144
+
145
+ def time_from(txt, options={})
146
+ date_from value, options.reverse_merge(:type=>Time)
147
+ end
148
+
149
+ def datetime_to(value, options={})
150
+ date_to value, options.reverse_merge(:type=>DateTime)
151
+ end
152
+
153
+ def datetime_from(txt, options={})
154
+ date_from value, options.reverse_merge(:type=>DateTime)
155
+ end
156
+
157
+ def logical_to(value, options={})
158
+ options = I18n.translate(:'logical.format', :locale => options[:locale]).merge(options)
159
+ value.nil? ? options[:blank] : (value ? options[:true] : options[:false])
160
+ end
161
+
162
+ def logical_from(txt, options={})
163
+ options = I18n.translate(:'logical.format', :locale => options[:locale]).merge(options)
164
+ txt = normalize_txt(txt)
165
+ trues = options[:trues]
166
+ trues ||= [normalize_txt(options[:true])]
167
+ falses = options[:falses]
168
+ falses ||= [normalize_txt(options[:falses])]
169
+ trues.include?(txt) ? true : falses.include?(txt) ? false : nil
170
+ end
171
+
172
+ # TODO: currency, money, bank accounts, credit card numbers, ...
173
+
174
+ private
175
+ # include ActionView::Helpers::NumberHelper
176
+
177
+ def round(v, ndec)
178
+ return v if v.try.nan? || v.try.infinite?
179
+ if ndec
180
+ case v
181
+ when BigDecimal
182
+ v = v.round(ndec)
183
+ when Float
184
+ k = 10**ndec
185
+ v = (k*v).round.to_f/k
186
+ end
187
+ end
188
+ v
189
+ end
190
+
191
+ # pos0 first digit, pos1 one past last integral digit
192
+ def digit_grouping(txt,n,sep,pos0,pos1)
193
+ if sep
194
+ while pos1>pos0
195
+ pos1 -= n
196
+ txt.insert pos1, sep if pos1>pos0
197
+ end
198
+ end
199
+ txt
200
+ end
201
+
202
+ # Date-parsing has been taken from https://github.com/clemens/delocalize
203
+
204
+ REGEXPS = {
205
+ '%B' => "(#{Date::MONTHNAMES.compact.join('|')})", # long month name
206
+ '%b' => "(#{Date::ABBR_MONTHNAMES.compact.join('|')})", # short month name
207
+ '%m' => "(\\d{1,2})", # numeric month
208
+ '%A' => "(#{Date::DAYNAMES.join('|')})", # full day name
209
+ '%a' => "(#{Date::ABBR_DAYNAMES.join('|')})", # short day name
210
+ '%Y' => "(\\d{4})", # long year
211
+ '%y' => "(\\d{2})", # short year
212
+ '%e' => "(\\s\\d|\\d{2})", # short day
213
+ '%d' => "(\\d{1,2})", # full day
214
+ '%H' => "(\\d{2})", # hour (24)
215
+ '%M' => "(\\d{2})", # minute
216
+ '%S' => "(\\d{2})" # second
217
+ }
218
+
219
+ def default_parse(datetime, type)
220
+ return if datetime.blank?
221
+ begin
222
+ today = Date.current
223
+ parsed = Date._parse(datetime)
224
+ return if parsed.empty? # the datetime value is invalid
225
+ # set default year, month and day if not found
226
+ parsed.reverse_merge!(:year => today.year, :mon => today.mon, :mday => today.mday)
227
+ datetime = Time.zone.local(*parsed.values_at(:year, :mon, :mday, :hour, :min, :sec))
228
+ Date == type ? datetime.to_date : datetime
229
+ rescue
230
+ datetime
231
+ end
232
+ end
233
+
234
+ def translate_month_and_day_names!(datetime, locale=nil)
235
+ translated = I18n.t([:month_names, :abbr_month_names, :day_names, :abbr_day_names], :scope => :date, :locale=>locale).flatten.compact
236
+ original = (Date::MONTHNAMES + Date::ABBR_MONTHNAMES + Date::DAYNAMES + Date::ABBR_DAYNAMES).compact
237
+ translated.each_with_index { |name, i| datetime.gsub!(name, original[i]) }
238
+ end
239
+
240
+ def input_formats(type, locale=nil)
241
+ # Date uses date formats, all others use time formats
242
+ type = type == Date ? :date : :time
243
+ I18n.t(:"#{type}.formats", :locale=>locale).slice(*I18n.t(:"#{type}.input.formats", :locale=>locale)).values
244
+ end
245
+
246
+ def apply_regex(format)
247
+ format.gsub(/(#{REGEXPS.keys.join('|')})/) { |s| REGEXPS[$1] }
248
+ end
249
+
250
+ def normalize_txt(txt)
251
+ txt.mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/n,'').downcase.strip.to_s
252
+ end
253
+
254
+ def check_type(type)
255
+ orig_type = type
256
+ type = type.to_s.camelcase.safe_constantize if type.kind_of?(Symbol)
257
+ raise ArgumentError, "Invalid type #{orig_type}" unless type && type.class==Class
258
+ type
259
+ end
260
+
261
+
262
+ end
263
+
264
+ end
data/lib/h/helpers.rb ADDED
@@ -0,0 +1,17 @@
1
+ module H
2
+
3
+ # Define methods to_h for H.to, from_h from H.from, xxx_to_h from H.xxx_to, xxx_from_h from H.xxx_from
4
+ module Helpers
5
+
6
+ (%w{date number integer logical magnitude}<<nil).each do |type|
7
+ %w{from to}.each do |kind|
8
+ name = [type,kind].compact*'_'
9
+ define_method "#{name}_h" do |*args|
10
+ H.send name, *args
11
+ end
12
+ end
13
+ end
14
+
15
+ end
16
+
17
+ end
data/lib/h/units.rb ADDED
@@ -0,0 +1,82 @@
1
+ # :encoding: utf-8
2
+
3
+ module H
4
+
5
+ module Units
6
+
7
+ UNIT_SYN = {
8
+ 'm2'=>'m**2',
9
+ 'kp/m2'=>'kp/m**2',
10
+ '"'=>:inch,
11
+ '\''=>:ft,
12
+ "''"=>:inch,
13
+ 'in'=>:inch
14
+ }
15
+
16
+ class <<self
17
+
18
+ def valid?(u)
19
+ u = normalize_units(u)
20
+ u && ::Units.u(u) rescue nil
21
+ end
22
+
23
+ # Convert a units expression to a Ruby expression valid for units-syste
24
+ def normalize_units(u)
25
+ if u.blank?
26
+ u = nil
27
+ else
28
+ u = u.to_s
29
+ u = UNIT_SYN[u] || u
30
+ u = u.to_s.gsub('^','**').tr(' ','*')
31
+ begin
32
+ ::Units.u(u)
33
+ rescue
34
+ u = nil
35
+ end
36
+ end
37
+ u
38
+ end
39
+
40
+ # Convert a units expression to the format to be presented to the user
41
+ def denormalize_units(u)
42
+ if u.blank?
43
+ u = nil
44
+ else
45
+ u = u.to_s.gsub('**','^').tr('*',' ')
46
+ end
47
+ u
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+
54
+ class <<self
55
+
56
+ def magnitude_to(v, options={})
57
+ return options[:blank] || '' if v.nil?
58
+ norm_units = options[:units]
59
+ txt = number_to(v, options)
60
+ txt << " #{H::Units.denormalize_units(norm_units)}"
61
+ txt
62
+ end
63
+
64
+ def magnitude_from(txt, options={})
65
+ return nil if txt.to_s.strip.empty? || txt==options[:blank]
66
+ norm_units = options[:units]
67
+ if txt.match(/^\s*([0-9\.,+-]+)\s*([a-zA-Z\"\'][a-zA-Z1-3\_\/\*\^\"\']*)\s*$/)
68
+ txt = $1
69
+ from_units = $2 || norm_units
70
+ else
71
+ from_units = norm_units
72
+ end
73
+ from_units = H::Units.normalize_units(from_units)
74
+ raise ArgumentError, "Invalid units for #{norm_units}: #{from_units}}" unless from_units
75
+ v = number_from(txt, options)
76
+ v *= ::Units.u(from_units)
77
+ v.in(norm_units)
78
+ end
79
+
80
+ end
81
+
82
+ end
data/lib/modalh.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'h/h'
2
+ require 'h/units'
3
+ require 'h/helpers'
4
+ require 'h/active_record_extensions'
5
+ require 'h/fields'
6
+
7
+ # Define xxxx_h field declarators for Model classes
8
+ if defined?(ActiveRecord::Base)
9
+ ActiveRecord::Base.extend H::ActiveRecordExtensions
10
+ end
11
+
12
+ # Add controller & view xxx_to_h/xxx_from_h helpers
13
+ if defined?(ActionController::Base)
14
+ class ActionController::Base
15
+ include H::Helpers
16
+ end
17
+ ActionController::Base.helper_method *H::Helpers.instance_methods
18
+ end
19
+
20
+ # Make same helpers accessible with the H prefix from elsewhere
21
+ # (not really needed; can use H.to/H.from instead)
22
+ H.extend H::Helpers
data/modalh.gemspec ADDED
@@ -0,0 +1,67 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "modalh"
8
+ s.version = "1.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Javier Goizueta"]
12
+ s.date = "2012-04-21"
13
+ s.description = "Rails plugin for localization & delocalization of data values"
14
+ s.email = "jgoizueta@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE.txt",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "lib/h/active_record_extensions.rb",
28
+ "lib/h/fields.rb",
29
+ "lib/h/h.rb",
30
+ "lib/h/helpers.rb",
31
+ "lib/h/units.rb",
32
+ "lib/modalh.rb",
33
+ "modalh.gemspec",
34
+ "test/helper.rb",
35
+ "test/test_modalh.rb"
36
+ ]
37
+ s.homepage = "http://github.com/jgoizueta/modalh"
38
+ s.licenses = ["MIT"]
39
+ s.require_paths = ["lib"]
40
+ s.rubygems_version = "1.8.21"
41
+ s.summary = "Rails plugin for localization & delocalization"
42
+
43
+ if s.respond_to? :specification_version then
44
+ s.specification_version = 3
45
+
46
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
47
+ s.add_runtime_dependency(%q<units-system>, [">= 0"])
48
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
49
+ s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
50
+ s.add_development_dependency(%q<bundler>, ["~> 1"])
51
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
52
+ else
53
+ s.add_dependency(%q<units-system>, [">= 0"])
54
+ s.add_dependency(%q<shoulda>, [">= 0"])
55
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
56
+ s.add_dependency(%q<bundler>, ["~> 1"])
57
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
58
+ end
59
+ else
60
+ s.add_dependency(%q<units-system>, [">= 0"])
61
+ s.add_dependency(%q<shoulda>, [">= 0"])
62
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
63
+ s.add_dependency(%q<bundler>, ["~> 1"])
64
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
65
+ end
66
+ end
67
+
data/test/helper.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+ require 'shoulda'
12
+
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
15
+ require 'modalh'
16
+
17
+ class Test::Unit::TestCase
18
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestModalh < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: modalh
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Javier Goizueta
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: units-system
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: shoulda
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rdoc
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '3.12'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '3.12'
62
+ - !ruby/object:Gem::Dependency
63
+ name: bundler
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '1'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '1'
78
+ - !ruby/object:Gem::Dependency
79
+ name: jeweler
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 1.8.3
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 1.8.3
94
+ description: Rails plugin for localization & delocalization of data values
95
+ email: jgoizueta@gmail.com
96
+ executables: []
97
+ extensions: []
98
+ extra_rdoc_files:
99
+ - LICENSE.txt
100
+ - README.rdoc
101
+ files:
102
+ - .document
103
+ - Gemfile
104
+ - Gemfile.lock
105
+ - LICENSE.txt
106
+ - README.rdoc
107
+ - Rakefile
108
+ - VERSION
109
+ - lib/h/active_record_extensions.rb
110
+ - lib/h/fields.rb
111
+ - lib/h/h.rb
112
+ - lib/h/helpers.rb
113
+ - lib/h/units.rb
114
+ - lib/modalh.rb
115
+ - modalh.gemspec
116
+ - test/helper.rb
117
+ - test/test_modalh.rb
118
+ homepage: http://github.com/jgoizueta/modalh
119
+ licenses:
120
+ - MIT
121
+ post_install_message:
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ none: false
127
+ requirements:
128
+ - - ! '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ segments:
132
+ - 0
133
+ hash: -2675616586541666340
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ none: false
136
+ requirements:
137
+ - - ! '>='
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ requirements: []
141
+ rubyforge_project:
142
+ rubygems_version: 1.8.21
143
+ signing_key:
144
+ specification_version: 3
145
+ summary: Rails plugin for localization & delocalization
146
+ test_files: []