units-system 0.0.5 → 0.1.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.
@@ -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: []