units-system 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,236 @@
1
+ # encoding: utf-8
2
+
3
+ module Units
4
+
5
+ # mathematical functions available to unit blocks are defined here
6
+ module Math
7
+ end
8
+
9
+ # unit methods and constants are defined here to be injected in units blocks
10
+ module System
11
+
12
+ extend Math
13
+
14
+ def self.define(unit)
15
+ define_var unit
16
+ PREFIXES.each do |prefix, (factor, name)|
17
+ define_var "#{prefix}#{unit}".to_sym
18
+ end
19
+ end
20
+
21
+ class <<self
22
+ private
23
+ def define_var(name)
24
+ name_initial = name.to_s[0,1]
25
+ if name_initial==name_initial.upcase && name_initial!=name_initial.downcase
26
+ # we could define a method with the requested name, but it would
27
+ # no be usable without qualification (System.X) or brackets (X())
28
+ # so we define a constant. But this requires Ruby 1.9 to be useful;
29
+ # otherwise the constant is not accesible without qualification in units blocks.
30
+ System.const_set name, Units.Measure(name)
31
+ end
32
+ self.class_eval do
33
+ define_method name do
34
+ Units.Measure(name)
35
+ end
36
+ module_function name
37
+ end
38
+ end
39
+ end
40
+
41
+ end # Units::System
42
+
43
+ def units(string=nil, &blk)
44
+ if string
45
+ if blk
46
+ raise ArgumentError, "wrong number of arguments (1 for 0)"
47
+ else
48
+ Units::System.class_eval(string)
49
+ end
50
+ else
51
+ Units::System.class_eval(&blk)
52
+ end
53
+ end
54
+ alias :u :units
55
+ module_function :units, :u
56
+
57
+ UnitDefinition = Struct.new(:dim, :factor, :name, :decomposition, :bias)
58
+
59
+ UNITS = {} # Hash.new{|h,k| h[k]=UnitDefinition.new()}
60
+
61
+ # get unit definition
62
+ def self.unit(unit_symbol)
63
+ ud = UNITS[unit_symbol]
64
+ if ud.nil?
65
+ factor = 1.0
66
+ if factor_name = PREFIXES[unit_symbol]
67
+ ud = UnitDefinition.new(nil, *factor_name)
68
+ else
69
+ u = unit_symbol.to_s
70
+ PREFIXES.each_pair do |prefix, (f,name)|
71
+ prefix = prefix.to_s
72
+ if u[0...prefix.length] == prefix
73
+ factor = f
74
+ ud = UNITS[u[prefix.length..-1].to_sym]
75
+ if ud
76
+ ud = ud.dup
77
+ ud.name = "#{name}#{ud.name}"
78
+ break
79
+ end
80
+ end
81
+ end
82
+ end
83
+ ud.factor *= factor if ud
84
+ ud.decomposition *= factor if ud && ud.decomposition
85
+ end
86
+ raise ArgumentError,"Invalid Units #{unit_symbol}" unless ud
87
+ ud
88
+ end
89
+
90
+ # Define new units.
91
+ # Define a base unit (with a factor for conversion to SI units)
92
+ # Units.define :unit_symbol, 'unit-name', :quantity, si_units_per_this_unit
93
+ # Define a unit in terms or another (valid for base or derived units)
94
+ # Units.define :unit_symbol, 'unit-name', value, :in_units
95
+ # Define a base unit as a measure-expression
96
+ # Units.define :unit_symbol, 'unit_name', u{...}
97
+ # Define a derived unit as a measure-expression
98
+ # Units.define :unit_symbol, 'unit_name', :quantity, u{...}
99
+ # For base dimensions the SI unit for a quantity must also be stablished with Unit.si_units;
100
+ # for derived units, SI units are automatically taken to be the first define unit of the quantity
101
+ # with unitary value in SI base units.
102
+ def self.define(unit_symbol, name, *args)
103
+ eqhivalence = nil
104
+ si_unit = false
105
+ bias = nil
106
+ if args.first.kind_of?(Symbol)
107
+ dim = args.shift
108
+ if args.first.kind_of?(Numeric)
109
+ # simple units
110
+ factor = args.shift
111
+ factor_units = args.shift
112
+ if factor_units
113
+ ud = unit(factor_units)
114
+ if ud.dim != dim
115
+ raise ArgumentError, "Inconsistent units #{factor_units} in definition of #{unit_symbol}"
116
+ end
117
+ # maybe it was not simple after all...
118
+ equivalence = factor*ud.decomposition if ud.decomposition
119
+ factor *= ud.factor
120
+ end
121
+ # si_unit = (factor==1.0) # to save si_units definitions # TODO: tolerance?
122
+ else
123
+ # compound unit
124
+ equivalence = args.shift
125
+ factor = equivalence.to_si.magnitude
126
+ si_unit = (factor==1.0) # TODO: tolerance?
127
+ if equivalence.units.empty?
128
+ # dimensionless compound dimension... (special case for angular units)
129
+ equivalence = nil
130
+ end
131
+ end
132
+ elsif args.first.kind_of?(Numeric)
133
+ # unit define in terms of other unit
134
+ factor = args.shift
135
+ factor_units = args.shift
136
+ u = unit(factor_units)
137
+ dim = u.dim
138
+ equivalence = factor*u.decomposition if u.decomposition
139
+ factor *= u.factor
140
+ bias = args.shift
141
+ else
142
+ # unit defined from a measure expression; the dimension must be already known or defined
143
+ # here (as as symbol preceding the expression).
144
+ definition = args.shift
145
+ dim = definition.dimension
146
+ raise ArgumentError,"To define a new compound unit a dimension must be specified" unless dim
147
+ equivalence = definition
148
+ factor = definition.to_si.magnitude
149
+ # si_unit = (factor==1.0) # to save si_units definitions # TODO: tolerance?
150
+ end
151
+ unit_def = UnitDefinition.new(dim, factor, name, equivalence, bias)
152
+ if UNITS.has_key?(unit_symbol)
153
+ raise "Redefinition of #{unit_symbol} as #{unit_def} (previously defined as #{UNITS[unit_symbol]})"
154
+ end
155
+ UNITS[unit_symbol] = unit_def
156
+ System.define unit_symbol
157
+ Units.si_units unit_def.dim, unit_symbol if si_unit && !SI_UNITS.has_key?(unit_def.dim)
158
+ end
159
+
160
+ SI_UNITS = {}
161
+
162
+ def self.si_units(dim, unit)
163
+ SI_UNITS[dim] = unit
164
+ end
165
+
166
+ def self.dimension(u)
167
+ unit(u).dim
168
+ end
169
+
170
+ def self.conversion_factor(from, to)
171
+ from_u = unit(from)
172
+ to_u = unit(to)
173
+ raise ArgumentError,"Inconsistent Units (#{from}, #{to})" if from_u.dim!=to_u.dim
174
+ from_u.factor/to_u.factor
175
+ end
176
+
177
+ def self.conversion_bias(from, to)
178
+ from_u = unit(from)
179
+ to_u = unit(to)
180
+ raise ArgumentError,"Inconsistent Units (#{from}, #{to})" if from_u.dim!=to_u.dim
181
+ factor = from_u.factor/to_u.factor
182
+ (from_u.bias||0)*factor - (to_u.bias||0)
183
+ end
184
+
185
+ # simple unit name
186
+ def self.unit_name(u)
187
+ uinfo = Units.unit(u)
188
+ uinfo && uinfo.name
189
+ end
190
+
191
+ def self.unit_descr(u, long=false, mult=1)
192
+ if long
193
+ u = unit_name(u)
194
+ if mult!=1
195
+ case mult
196
+ when 2
197
+ "squared #{u}"
198
+ when 3
199
+ "cubed #{u}"
200
+ else
201
+ "#{u} to the #{mult} power"
202
+ end
203
+ else
204
+ u
205
+ end
206
+ else
207
+ if mult!=1
208
+ "#{u}**#{mult}"
209
+ else
210
+ u.to_s
211
+ end
212
+ end
213
+ end
214
+
215
+ def self.units_descr(units, long=false)
216
+ units = units.values.sort_by{|u,m| -m}
217
+ pos_units = units.select{|u| u.last>0}
218
+ neg_units = units.select{|u| u.last<0}
219
+ times = long ? " " : "*"
220
+ num = pos_units.map{|u,m| unit_descr(u,long,m)}.join(times)
221
+ num = "(#{num})" if pos_units.size>1 && !neg_units.empty? && !long
222
+ den = neg_units.map{|u,m| unit_descr(u,long,-m)}.join("*")
223
+ den = "(#{den})" if neg_units.size>1 && !long
224
+ if pos_units.empty?
225
+ u_descr = "1/#{den}"
226
+ elsif neg_units.empty?
227
+ u_descr = num
228
+ else
229
+ connector = long ? " per " : "/"
230
+ u_descr = "#{num}#{connector}#{den}"
231
+ end
232
+ u_descr
233
+ end
234
+
235
+ end # Units
236
+
@@ -1,7 +1,7 @@
1
1
  require 'helper'
2
2
 
3
3
  class TestUnitsSystem < Test::Unit::TestCase
4
-
4
+
5
5
  include Units::UseBlocks
6
6
 
7
7
  should "be possible to define Measures with a units block" do
@@ -71,4 +71,49 @@ class TestUnitsSystem < Test::Unit::TestCase
71
71
  assert_raise(RuntimeError){Units.units{(m/s).to(m/s**2)}}
72
72
  end
73
73
 
74
+ should "define measures with bracket constructor" do
75
+ assert_equal Units::Measure, Units::Measure[1.0, :m].class
76
+ assert_equal 1.0, Units::Measure[1.0, :m].magnitude
77
+ assert_equal [:m, 1], Units::Measure[1.0, :m].units[:length]
78
+ end
79
+
80
+ should "be flexible with constructor arguments" do
81
+ assert_equal Units::Measure[1.0, :m], Units::Measure[:m]
82
+ assert_equal Units::Measure[2.0, {}], Units::Measure[2.0]
83
+ assert_equal Units::Measure[1.0, {}], Units::Measure[]
84
+ end
85
+
86
+ should "define measures with method constructor" do
87
+ assert_equal Units::Measure, Units.Measure(1.0, :m).class
88
+ assert_equal 1.0, Units.Measure(1.0, :m).magnitude
89
+ assert_equal [:m, 1], Units.Measure(1.0, :m).units[:length]
90
+ end
91
+
92
+ should "compare measure objects" do
93
+ assert_equal Units::Measure[1.0, :m], Units::Measure[1.0, :m]
94
+ assert_not_equal Units::Measure[1.0, :m], Units::Measure[2.0, :m]
95
+ assert_not_equal Units::Measure[1.0, :m], Units::Measure[1.0, :s]
96
+ assert_equal Units.u{m}, Units::Measure[1.0, :m]
97
+ assert_not_equal Units.u{m}, Units.u{s}
98
+ assert_not_equal Units.u{2*m}, Units.u{3*m}
99
+ assert_equal Units.u{2*m}, Units.u{2*m}
100
+ assert_equal Units.u{2*m/s}, Units.u{2*m/s}
101
+ assert_not_equal Units.u{2*m/s}, Units.u{2*m/h}
102
+ assert_not_equal Units.u{2*m/s}, Units.u{2*m/kg}
103
+ assert_not_equal Units.u{2*m/s}, Units.u{3*m/s}
104
+ end
105
+
106
+ should "admit units defined as text string" do
107
+ assert_equal Units::Measure, Units.units('m').class
108
+ assert_equal 1.0, Units.units('m').magnitude
109
+ assert_equal [:m, 1], Units.units('m').units[:length]
110
+ assert_equal :speed, Units.u('m/s').dimension
111
+ assert_equal Units.u{m}, Units.u("m")
112
+ assert_equal 5, Units.u("3*m+2*m").magnitude
113
+ assert_equal Units.u{3*m+2*m}, Units.u("3*m+2*m")
114
+ assert_equal 5, Units.units("3*m+2*m").magnitude
115
+ assert_equal Units.u{3*m+2*m}, Units.units("3*m+2*m")
116
+ assert_raise(ArgumentError){Units.u("m"){m}}
117
+ end
118
+
74
119
  end
data/units-system.gemspec CHANGED
@@ -1,54 +1,66 @@
1
1
  # Generated by jeweler
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
- s.name = %q{units-system}
8
- s.version = "0.0.5"
7
+ s.name = "units-system"
8
+ s.version = "0.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Javier Goizueta"]
12
- s.date = %q{2010-06-01}
13
- s.description = %q{Experimental unit conversion & arithmetic for Ruby 1.9}
14
- s.email = %q{jgoizueta@gmail.com}
12
+ s.date = "2012-04-20"
13
+ s.description = "Experimental unit conversion & arithmetic for Ruby 1.9"
14
+ s.email = "jgoizueta@gmail.com"
15
15
  s.extra_rdoc_files = [
16
16
  "LICENSE",
17
- "README.rdoc"
17
+ "README.rdoc"
18
18
  ]
19
19
  s.files = [
20
20
  ".document",
21
- ".gitignore",
22
- "LICENSE",
23
- "README.rdoc",
24
- "Rakefile",
25
- "VERSION",
26
- "lib/units-system.rb",
27
- "test/helper.rb",
28
- "test/test_units-system.rb",
29
- "units-system.gemspec"
30
- ]
31
- s.homepage = %q{http://github.com/jgoizueta/units-system}
32
- s.rdoc_options = ["--charset=UTF-8"]
33
- s.require_paths = ["lib"]
34
- s.rubygems_version = %q{1.3.7}
35
- s.summary = %q{Arithmetic with units of measure}
36
- s.test_files = [
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "lib/units-system.rb",
28
+ "lib/units/definitions.rb",
29
+ "lib/units/math.rb",
30
+ "lib/units/measure.rb",
31
+ "lib/units/prefixes.rb",
32
+ "lib/units/system.rb",
37
33
  "test/helper.rb",
38
- "test/test_units-system.rb"
34
+ "test/test_units-system.rb",
35
+ "units-system.gemspec"
39
36
  ]
37
+ s.homepage = "http://github.com/jgoizueta/units-system"
38
+ s.require_paths = ["lib"]
39
+ s.rubygems_version = "1.8.21"
40
+ s.summary = "Arithmetic with units of measure"
40
41
 
41
42
  if s.respond_to? :specification_version then
42
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
43
43
  s.specification_version = 3
44
44
 
45
45
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
46
- s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
46
+ s.add_runtime_dependency(%q<modalsupport>, [">= 0"])
47
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
48
+ s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
49
+ s.add_development_dependency(%q<bundler>, ["~> 1"])
50
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
47
51
  else
48
- s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
52
+ s.add_dependency(%q<modalsupport>, [">= 0"])
53
+ s.add_dependency(%q<shoulda>, [">= 0"])
54
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
55
+ s.add_dependency(%q<bundler>, ["~> 1"])
56
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
49
57
  end
50
58
  else
51
- s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
59
+ s.add_dependency(%q<modalsupport>, [">= 0"])
60
+ s.add_dependency(%q<shoulda>, [">= 0"])
61
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
62
+ s.add_dependency(%q<bundler>, ["~> 1"])
63
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
52
64
  end
53
65
  end
54
66
 
metadata CHANGED
@@ -1,91 +1,145 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: units-system
3
- version: !ruby/object:Gem::Version
4
- hash: 21
5
- prerelease: false
6
- segments:
7
- - 0
8
- - 0
9
- - 5
10
- version: 0.0.5
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Javier Goizueta
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2010-06-01 00:00:00 +02:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
22
- name: thoughtbot-shoulda
12
+ date: 2012-04-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: modalsupport
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
23
55
  prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
56
+ version_requirements: !ruby/object:Gem::Requirement
25
57
  none: false
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- hash: 3
30
- segments:
31
- - 0
32
- version: "0"
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
33
86
  type: :development
34
- version_requirements: *id001
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
35
94
  description: Experimental unit conversion & arithmetic for Ruby 1.9
36
95
  email: jgoizueta@gmail.com
37
96
  executables: []
38
-
39
97
  extensions: []
40
-
41
- extra_rdoc_files:
98
+ extra_rdoc_files:
42
99
  - LICENSE
43
100
  - README.rdoc
44
- files:
101
+ files:
45
102
  - .document
46
- - .gitignore
103
+ - Gemfile
104
+ - Gemfile.lock
47
105
  - LICENSE
48
106
  - README.rdoc
49
107
  - Rakefile
50
108
  - VERSION
51
109
  - lib/units-system.rb
110
+ - lib/units/definitions.rb
111
+ - lib/units/math.rb
112
+ - lib/units/measure.rb
113
+ - lib/units/prefixes.rb
114
+ - lib/units/system.rb
52
115
  - test/helper.rb
53
116
  - test/test_units-system.rb
54
117
  - units-system.gemspec
55
- has_rdoc: true
56
118
  homepage: http://github.com/jgoizueta/units-system
57
119
  licenses: []
58
-
59
120
  post_install_message:
60
- rdoc_options:
61
- - --charset=UTF-8
62
- require_paths:
121
+ rdoc_options: []
122
+ require_paths:
63
123
  - lib
64
- required_ruby_version: !ruby/object:Gem::Requirement
124
+ required_ruby_version: !ruby/object:Gem::Requirement
65
125
  none: false
66
- requirements:
67
- - - ">="
68
- - !ruby/object:Gem::Version
69
- hash: 3
70
- segments:
126
+ requirements:
127
+ - - ! '>='
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ segments:
71
131
  - 0
72
- version: "0"
73
- required_rubygems_version: !ruby/object:Gem::Requirement
132
+ hash: 524794383718381749
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
134
  none: false
75
- requirements:
76
- - - ">="
77
- - !ruby/object:Gem::Version
78
- hash: 3
79
- segments:
80
- - 0
81
- version: "0"
135
+ requirements:
136
+ - - ! '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
82
139
  requirements: []
83
-
84
140
  rubyforge_project:
85
- rubygems_version: 1.3.7
141
+ rubygems_version: 1.8.21
86
142
  signing_key:
87
143
  specification_version: 3
88
144
  summary: Arithmetic with units of measure
89
- test_files:
90
- - test/helper.rb
91
- - test/test_units-system.rb
145
+ test_files: []