measure 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/COPYING.LIB ADDED
@@ -0,0 +1,166 @@
1
+ GNU LESSER GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+
9
+ This version of the GNU Lesser General Public License incorporates
10
+ the terms and conditions of version 3 of the GNU General Public
11
+ License, supplemented by the additional permissions listed below.
12
+
13
+ 0. Additional Definitions.
14
+
15
+ As used herein, "this License" refers to version 3 of the GNU Lesser
16
+ General Public License, and the "GNU GPL" refers to version 3 of the GNU
17
+ General Public License.
18
+
19
+ "The Library" refers to a covered work governed by this License,
20
+ other than an Application or a Combined Work as defined below.
21
+
22
+ An "Application" is any work that makes use of an interface provided
23
+ by the Library, but which is not otherwise based on the Library.
24
+ Defining a subclass of a class defined by the Library is deemed a mode
25
+ of using an interface provided by the Library.
26
+
27
+ A "Combined Work" is a work produced by combining or linking an
28
+ Application with the Library. The particular version of the Library
29
+ with which the Combined Work was made is also called the "Linked
30
+ Version".
31
+
32
+ The "Minimal Corresponding Source" for a Combined Work means the
33
+ Corresponding Source for the Combined Work, excluding any source code
34
+ for portions of the Combined Work that, considered in isolation, are
35
+ based on the Application, and not on the Linked Version.
36
+
37
+ The "Corresponding Application Code" for a Combined Work means the
38
+ object code and/or source code for the Application, including any data
39
+ and utility programs needed for reproducing the Combined Work from the
40
+ Application, but excluding the System Libraries of the Combined Work.
41
+
42
+ 1. Exception to Section 3 of the GNU GPL.
43
+
44
+ You may convey a covered work under sections 3 and 4 of this License
45
+ without being bound by section 3 of the GNU GPL.
46
+
47
+ 2. Conveying Modified Versions.
48
+
49
+ If you modify a copy of the Library, and, in your modifications, a
50
+ facility refers to a function or data to be supplied by an Application
51
+ that uses the facility (other than as an argument passed when the
52
+ facility is invoked), then you may convey a copy of the modified
53
+ version:
54
+
55
+ a) under this License, provided that you make a good faith effort to
56
+ ensure that, in the event an Application does not supply the
57
+ function or data, the facility still operates, and performs
58
+ whatever part of its purpose remains meaningful, or
59
+
60
+ b) under the GNU GPL, with none of the additional permissions of
61
+ this License applicable to that copy.
62
+
63
+ 3. Object Code Incorporating Material from Library Header Files.
64
+
65
+ The object code form of an Application may incorporate material from
66
+ a header file that is part of the Library. You may convey such object
67
+ code under terms of your choice, provided that, if the incorporated
68
+ material is not limited to numerical parameters, data structure
69
+ layouts and accessors, or small macros, inline functions and templates
70
+ (ten or fewer lines in length), you do both of the following:
71
+
72
+ a) Give prominent notice with each copy of the object code that the
73
+ Library is used in it and that the Library and its use are
74
+ covered by this License.
75
+
76
+ b) Accompany the object code with a copy of the GNU GPL and this license
77
+ document.
78
+
79
+ 4. Combined Works.
80
+
81
+ You may convey a Combined Work under terms of your choice that,
82
+ taken together, effectively do not restrict modification of the
83
+ portions of the Library contained in the Combined Work and reverse
84
+ engineering for debugging such modifications, if you also do each of
85
+ the following:
86
+
87
+ a) Give prominent notice with each copy of the Combined Work that
88
+ the Library is used in it and that the Library and its use are
89
+ covered by this License.
90
+
91
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
92
+ document.
93
+
94
+ c) For a Combined Work that displays copyright notices during
95
+ execution, include the copyright notice for the Library among
96
+ these notices, as well as a reference directing the user to the
97
+ copies of the GNU GPL and this license document.
98
+
99
+ d) Do one of the following:
100
+
101
+ 0) Convey the Minimal Corresponding Source under the terms of this
102
+ License, and the Corresponding Application Code in a form
103
+ suitable for, and under terms that permit, the user to
104
+ recombine or relink the Application with a modified version of
105
+ the Linked Version to produce a modified Combined Work, in the
106
+ manner specified by section 6 of the GNU GPL for conveying
107
+ Corresponding Source.
108
+
109
+ 1) Use a suitable shared library mechanism for linking with the
110
+ Library. A suitable mechanism is one that (a) uses at run time
111
+ a copy of the Library already present on the user's computer
112
+ system, and (b) will operate properly with a modified version
113
+ of the Library that is interface-compatible with the Linked
114
+ Version.
115
+
116
+ e) Provide Installation Information, but only if you would otherwise
117
+ be required to provide such information under section 6 of the
118
+ GNU GPL, and only to the extent that such information is
119
+ necessary to install and execute a modified version of the
120
+ Combined Work produced by recombining or relinking the
121
+ Application with a modified version of the Linked Version. (If
122
+ you use option 4d0, the Installation Information must accompany
123
+ the Minimal Corresponding Source and Corresponding Application
124
+ Code. If you use option 4d1, you must provide the Installation
125
+ Information in the manner specified by section 6 of the GNU GPL
126
+ for conveying Corresponding Source.)
127
+
128
+ 5. Combined Libraries.
129
+
130
+ You may place library facilities that are a work based on the
131
+ Library side by side in a single library together with other library
132
+ facilities that are not Applications and are not covered by this
133
+ License, and convey such a combined library under terms of your
134
+ choice, if you do both of the following:
135
+
136
+ a) Accompany the combined library with a copy of the same work based
137
+ on the Library, uncombined with any other library facilities,
138
+ conveyed under the terms of this License.
139
+
140
+ b) Give prominent notice with the combined library that part of it
141
+ is a work based on the Library, and explaining where to find the
142
+ accompanying uncombined form of the same work.
143
+
144
+ 6. Revised Versions of the GNU Lesser General Public License.
145
+
146
+ The Free Software Foundation may publish revised and/or new versions
147
+ of the GNU Lesser General Public License from time to time. Such new
148
+ versions will be similar in spirit to the present version, but may
149
+ differ in detail to address new problems or concerns.
150
+
151
+ Each version is given a distinguishing version number. If the
152
+ Library as you received it specifies that a certain numbered version
153
+ of the GNU Lesser General Public License "or any later version"
154
+ applies to it, you have the option of following the terms and
155
+ conditions either of that published version or of any later version
156
+ published by the Free Software Foundation. If the Library as you
157
+ received it does not specify a version number of the GNU Lesser
158
+ General Public License, you may choose any version of the GNU Lesser
159
+ General Public License ever published by the Free Software Foundation.
160
+
161
+ If the Library as you received it specifies that a proxy can decide
162
+ whether future versions of the GNU Lesser General Public License shall
163
+ apply, that proxy's public statement of acceptance of any version is
164
+ permanent authorization for you to choose that version for the
165
+ Library.
166
+
data/ChangeLog ADDED
@@ -0,0 +1,10 @@
1
+ 2008-08-11 Kenta Murata <muraken@gmail.com>
2
+
3
+ * Rakefile: replace all Spec::VERSION module to Measure::VERSION.
4
+
5
+ * lib/measure.rb (Measure.dim): add Measure.dim as alias to
6
+ Measure.dimension.
7
+
8
+ 2008-08-11 Kenta Murata <mrkn@mrkn.jp>
9
+
10
+ * version 0.1.0
data/README ADDED
@@ -0,0 +1,19 @@
1
+ = Measure library
2
+
3
+ Measure is a library to handle measurment numbers.
4
+
5
+ == Installation
6
+
7
+ The simplest approach is to install the gem (as root in some environments):
8
+
9
+ gem install -r measure
10
+
11
+ == Building the Measure gem
12
+
13
+ If you prefer to build the gem locally:
14
+
15
+ git clone git://github.com/mrkn/ruby-measure.git
16
+ cd ruby-measure
17
+ rake gem
18
+ gem install pkg/measure-0.x.x.gem # as root
19
+
data/Rakefile ADDED
@@ -0,0 +1,150 @@
1
+ $LOAD_PATH.unshift 'lib'
2
+ require 'rubygems'
3
+ require 'rake/gempackagetask'
4
+ require 'rake/contrib/rubyforgepublisher'
5
+ require 'rake/clean'
6
+ require 'rake/rdoctask'
7
+ require 'rake/testtask'
8
+ require 'spec/rake/spectask'
9
+ require 'measure/version'
10
+ dir = File.dirname(__FILE__)
11
+
12
+ PKG_NAME = 'measure'
13
+ PKG_VERSION = Measure::VERSION::STRING
14
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
15
+ PKG_FILES = FileList[
16
+ '[A-Z]*',
17
+ 'lib/**/*.rb',
18
+ 'spec/**/*',
19
+ ]
20
+
21
+ task :default => [ :spec ]
22
+
23
+ desc 'Run all specs'
24
+ Spec::Rake::SpecTask.new do |t|
25
+ t.spec_files = FileList['spec/*_spec.rb']
26
+ t.spec_opts = ['--options', 'spec/spec.opts']
27
+ end
28
+
29
+ desc 'Run all specs and store html output in doc/output/report.html'
30
+ Spec::Rake::SpecTask.new('spec_html') do |t|
31
+ t.spec_files = FileList['spec/**/*_spec.rb']
32
+ t.spec_opts = [ '--format html:../../../../doc/output/report.html',
33
+ '--format progress',
34
+ '--backtrace' ]
35
+ end
36
+
37
+ desc 'Generate RDoc'
38
+ rd = Rake::RDocTask.new do |rdoc|
39
+ rdoc.rdoc_dir = File.join('..', 'doc', 'output', 'rdoc')
40
+ rdoc.options << '--title' << 'Measure' << '--line-numbers'
41
+ rdoc.options << '--inline-source' << '--main' << 'README'
42
+ rdoc.rdoc_files.include('README', 'CHANGES', 'COPYING.LIB', 'lib/**/*.rb')
43
+ end
44
+
45
+ spec = Gem::Specification.new do |s|
46
+ s.name = PKG_NAME
47
+ s.version = PKG_VERSION
48
+ s.summary = Measure::VERSION::SUMMARY
49
+ s.description = <<-EOF
50
+ Measure is a library to handle measurement numbers.
51
+ EOF
52
+
53
+ s.files = PKG_FILES.to_a
54
+ s.require_path = 'lib'
55
+
56
+ s.has_rdoc = true
57
+ s.rdoc_options = rd.options
58
+ s.extra_rdoc_files = rd.rdoc_files.reject {|fn| fn =~ /\.rb$/ }.to_a
59
+
60
+ s.author = 'Kenta Murata'
61
+ s.email = 'measure-devel@rubyforge.org'
62
+ s.homepage = 'http://measure.rubyforge.org'
63
+ s.platform = Gem::Platform::RUBY
64
+ s.rubyforge_project = 'measure'
65
+ end
66
+
67
+ Rake::GemPackageTask.new(spec) do |pkg|
68
+ pkg.need_zip = true
69
+ pkg.need_tar = true
70
+ end
71
+
72
+ def egrep(pattern)
73
+ Dir['**/*.rb'].each do |fn|
74
+ count = 0
75
+ open(fn) do |f|
76
+ while l = f.gets
77
+ count +=1
78
+ puts "#{fn}:#{count}:#{line}" if line =~ pattern
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ desc 'Look for TODO and FIXME tags in the code'
85
+ task :todo do
86
+ egrep /(FIXME|TODO)/
87
+ end
88
+
89
+ task :verify_user do
90
+ raise "RUBYFORGE_USER environment variable not set!" unless ENV['RUBYFORGE_USER']
91
+ end
92
+
93
+ desc 'Publish gem+tgz+zip on RubyForge. You must make sure lib/version.rb is aligned with the ChangeLog file'
94
+ task :publish_packages => [ :verify_user, :package, :pkg ] do
95
+ release_files = FileList[
96
+ File.join('pkg', "#{PKG_FILE_NAME}.gem"),
97
+ File.join('pkg', "#{PKG_FILE_NAME}.zip"),
98
+ File.join('pkg', "#{PKG_FILE_NAME}.tgz"),
99
+ 'CHANGES'
100
+ ]
101
+ unless defined? Measure::VERSION::RELEASE_CANDIDATE and Measure::VERSION::RELEASE_CANDIDATE
102
+ require 'meta_project'
103
+ require 'rake/contrib/xforge'
104
+
105
+ project = MetaProject::Project::XForge::RubyForge.new(PKG_NAME)
106
+ Rake::XForge::Release.new(project) do |xf|
107
+ # Never hardcode user name and password in the Rakefile!
108
+ xf.user_name = ENV['RUBYFORGE_USER']
109
+ xf.files = release_files.to_a
110
+ xf.release_name = "Measure #{PKG_NAME}"
111
+ end
112
+ else
113
+ puts 'SINCE THIS IS A PRERELEASE, FILES ARE UPLOADED WITH SSH, NOT TO THE RUBYFORGE FILE SECTION'
114
+ puts 'YOU MUST TYPE THE PASSWORD #{release_files.length} TIMES...'
115
+
116
+ host = 'measure-website@rubyforge.org'
117
+ remote_dir = '/var/www/gforge-projects/#{PKG_NAME}'
118
+
119
+ publisher = Rake::SshFilePublisher.new(
120
+ host,
121
+ remote_dir,
122
+ File.dirname(__FILE__),
123
+ *release_files)
124
+ publisher.upload
125
+
126
+ puts 'UPLOADED THE FOLLOWING FILES:'
127
+ release_files.each do |fn|
128
+ name = File.basename(fn)
129
+ puts "* http://measure.rubyforge.org/#{name}"
130
+ end
131
+
132
+ puts "They are not linked to anywhere, so don't forget to tell people!"
133
+ end
134
+ end
135
+
136
+ desc 'Publish news on RubyForge'
137
+ task :publish_news => [ :verify_user ] do
138
+ unless Measure::VERSION::RELEASE_CANDIDATE
139
+ require 'meta_project'
140
+ require 'rake/contrib/xforge'
141
+
142
+ project = MetaProject::Project::XForge::RubyForge.new(PKG_NAME)
143
+ Rake::XForge::NewsPublisher.new(project) do |news|
144
+ # Never hardcode user name and password in the Rakefile!
145
+ news.user_name = ENV['RUBYFORGE_USER']
146
+ end
147
+ else
148
+ puts '** Not publishing news to RubyForge because this is a prerelease'
149
+ end
150
+ end
data/TODO ADDED
@@ -0,0 +1,13 @@
1
+ = Current version
2
+
3
+ - To write a spec for Measure#convert method with a unit same as receiver's one.
4
+ - To write specs for Measure#to_{a,i,f,s}.
5
+ - To write a spec for Measure#coerce.
6
+ - To write specs for binary operation.
7
+ - To write specs for comparison methods.
8
+ - To write RDoc documents.
9
+
10
+ = Future works
11
+
12
+ - To support composite dimension.
13
+ - To define well-known physical constants.
data/lib/measure.rb ADDED
@@ -0,0 +1,348 @@
1
+ # = Measure
2
+ #
3
+ # Author:: Kenta Murata
4
+ # Copyright:: Copyright (C) 2008 Kenta Murata
5
+ # License:: LGPL version 3.0
6
+
7
+ class Measure
8
+ class UnitRedefinitionError < StandardError; end
9
+ class InvalidUnitError < StandardError; end
10
+ class CompatibilityError < StandardError; end
11
+
12
+ @@units = []
13
+ @@dimension_map = {}
14
+ @@conversion_map = {}
15
+ @@alias_map = {}
16
+
17
+ class << self
18
+ def conversion_map
19
+ @@conversion_map
20
+ end
21
+
22
+ def has_unit?(unit)
23
+ unit = resolve_alias unit
24
+ return @@units.include? unit
25
+ end
26
+ alias defined? has_unit?
27
+
28
+ def compatible?(u1, u2)
29
+ u1 = resolve_alias u1
30
+ raise InvalidUnitError, "unknown unit: #{u1}" unless self.defined? u1
31
+ u2 = resolve_alias u2
32
+ raise InvalidUnitError, "unknown unit: #{u2}" unless self.defined? u2
33
+ return true if u1 == u2
34
+ return true if @@conversion_map.has_key? u1 and @@conversion_map[u1].has_key? u2
35
+ return true if @@conversion_map.has_key? u2 and @@conversion_map[u2].has_key? u1
36
+ return false
37
+ end
38
+
39
+ def clear_units
40
+ @@units.clear
41
+ @@dimension_map.clear
42
+ @@conversion_map.clear
43
+ @@alias_map.clear
44
+ return nil
45
+ end
46
+
47
+ def units(dimension=nil)
48
+ return @@units.dup if dimension.nil?
49
+ @@dimension_map.select {|k, v| v == dimension }.collect{|k, v| k }
50
+ end
51
+
52
+ def num_units
53
+ return @@units.length
54
+ end
55
+
56
+ def define_unit(unit, dimension=1)
57
+ if @@units.include?(unit)
58
+ if self.dimension(unit) != dimension
59
+ raise UnitRedefinitionError, "unit [#{unit}] is already defined"
60
+ end
61
+ else
62
+ @@units << unit
63
+ @@dimension_map[unit] = dimension
64
+ end
65
+ end
66
+
67
+ def define_alias(unit, base)
68
+ if self.defined?(unit)
69
+ raise UnitRedefinitionError, "unit [#{unit}] is already defined"
70
+ end
71
+ raise InvalidUnitError, "unknown unit: #{base}" unless self.defined? base
72
+ @@alias_map[unit] = base
73
+ end
74
+
75
+ def define_conversion(base, conversion)
76
+ base = resolve_alias base
77
+ raise InvalidUnitError, "unknown unit: #{base}" unless self.defined? base
78
+ @@conversion_map[base] ||= {}
79
+ conversion.each {|unit, factor|
80
+ unit = resolve_alias unit
81
+ raise InvalidUnitError, "unknown unit: #{unit}" unless self.defined? unit
82
+ @@conversion_map[base][unit] = factor
83
+ }
84
+ return nil
85
+ end
86
+
87
+ def dimension(unit)
88
+ return @@dimension_map[resolve_alias unit]
89
+ end
90
+ alias dim dimension
91
+
92
+ def resolve_alias(unit)
93
+ while @@alias_map.has_key? unit
94
+ unit = @@alias_map[unit]
95
+ end
96
+ return unit
97
+ end
98
+
99
+ def find_multi_hop_conversion(u1, u2)
100
+ visited = []
101
+ queue = [[u1]]
102
+ while route = queue.shift
103
+ next if visited.include? route.last
104
+ visited.push route.last
105
+ return route if route.last == u2
106
+ neighbors(route.last).each{|u|
107
+ queue.push(route + [u]) unless visited.include? u }
108
+ end
109
+ return nil
110
+ end
111
+
112
+ # def encode_dimension(dim)
113
+ # case dim
114
+ # when Symbol
115
+ # return dim.to_s
116
+ # else
117
+ # units = dim.sort {|a, b| a[1] <=> b[1] }.reverse
118
+ # nums = []
119
+ # units.select {|u, e| e > 0 }.each {|u, e| nums << "#{u}^#{e}" }
120
+ # dens = []
121
+ # units.select {|u, e| e < 0 }.each {|u, e| dens << "#{u}^#{-e}" }
122
+ # return nums.join(' ') + ' / ' + dens.join(' ')
123
+ # end
124
+ # end
125
+
126
+ private
127
+
128
+ def neighbors(unit)
129
+ res = []
130
+ if @@conversion_map.has_key?(unit)
131
+ res += @@conversion_map[unit].keys
132
+ end
133
+ @@conversion_map.each {|k, v| res << k if v.has_key? unit }
134
+ return res
135
+ end
136
+ end # class << self
137
+
138
+ def initialize(value, unit)
139
+ @value, @unit = value, unit
140
+ return nil
141
+ end
142
+
143
+ attr_reader :value, :unit
144
+
145
+ def <(other)
146
+ case other
147
+ when Measure
148
+ if self.unit == other.unit
149
+ return self.value < other.value
150
+ else
151
+ return self < other.convert(self.value)
152
+ end
153
+ when Numeric
154
+ return self.value < other
155
+ else
156
+ raise ArgumentError, 'unable to compare with #{other.inspect}'
157
+ end
158
+ end
159
+
160
+ def >(other)
161
+ case other
162
+ when Measure
163
+ if self.unit == other.unit
164
+ return self.value > other.value
165
+ else
166
+ return self > other.convert(self.value)
167
+ end
168
+ when Numeric
169
+ return self.value > other
170
+ else
171
+ raise ArgumentError, 'unable to compare with #{other.inspect}'
172
+ end
173
+ end
174
+
175
+ def ==(other)
176
+ return self.value == other.value if self.unit == other.unit
177
+ if Measure.compatible? self.unit, other.unit
178
+ return self == other.convert(self.unit)
179
+ elsif Measure.compatible? other.unit, self.unit
180
+ return self.convert(other.unit) == other
181
+ else
182
+ return false
183
+ end
184
+ end
185
+
186
+ def +(other)
187
+ case other
188
+ when Measure
189
+ if self.unit == other.unit
190
+ return Measure(self.value + other.value, self.unit)
191
+ elsif Measure.dim(self.unit) == Measure.dim(other.unit)
192
+ return Measure(self.value + other.convert(self.unit).value, self.unit)
193
+ else
194
+ raise TypeError, "incompatible dimensions: " +
195
+ "#{Measure.dim(self.unit)} and #{Measure.dim(other.unit)}"
196
+ end
197
+ when Numeric
198
+ return Measure(self.value + other, self.unit)
199
+ else
200
+ check_coercable other
201
+ a, b = other.coerce self
202
+ return a + b
203
+ end
204
+ end
205
+
206
+ def -(other)
207
+ case other
208
+ when Measure
209
+ if self.unit == other.unit
210
+ return Measure(self.value - other.value, self.unit)
211
+ elsif Measure.dim(self.unit) == Measure.dim(other.unit)
212
+ return Measure(self.value - other.convert(self.unit).value, self.unit)
213
+ else
214
+ raise TypeError, "incompatible dimensions: " +
215
+ "#{Measure.dim(self.unit)} and #{Measure.dim(other.unit)}"
216
+ end
217
+ when Numeric
218
+ return Measure(self.value - other, self.unit)
219
+ else
220
+ check_coerecable other
221
+ a, b = other.coerce self
222
+ return a - b
223
+ end
224
+ end
225
+
226
+ def *(other)
227
+ case other
228
+ when Measure
229
+ # TODO: dimension
230
+ return other * self.value if self.unit == 1
231
+ raise NotImplementedError, "this feature has not implemented yet"
232
+ # if self.unit == other.unit
233
+ # return Measure(self.value * other.value, self.unit)
234
+ # elsif Measure.dim(self.unit) == Measure.dim(other.unit)
235
+ # return Measure(self.value - other.convert(self.unit).value, self.unit)
236
+ # else
237
+ # return Measure(self.value * other.convert(self.unit).value, self.unit)
238
+ # end
239
+ when Numeric
240
+ return Measure(self.value * other, self.unit)
241
+ else
242
+ check_coercable other
243
+ a, b = other.coerce self
244
+ return a * b
245
+ end
246
+ end
247
+
248
+ def /(other)
249
+ case other
250
+ when Measure
251
+ # TODO: dimension
252
+ raise NotImplementedError, "this feature has not implemented yet"
253
+ # if self.unit == other.unit
254
+ # return Measure(self.value / other.value, self.unit)
255
+ # else
256
+ # return Measure(self.value / other.convert(self.unit).value, self.unit)
257
+ # end
258
+ when Numeric
259
+ return Measure(self.value / other, self.unit)
260
+ else
261
+ check_coercable other
262
+ a, b = other.coerce self
263
+ return a / b
264
+ end
265
+ end
266
+
267
+ def coerce(other)
268
+ case other
269
+ when Numeric
270
+ return [Measure(other, 1), self]
271
+ else
272
+ raise TypeError, "#{other.class} can't convert into #{self.class}"
273
+ end
274
+ end
275
+
276
+ def abs
277
+ return Measure(self.value.abs, self.unit)
278
+ end
279
+
280
+ def to_s
281
+ return "#{self.value} [#{self.unit}]"
282
+ end
283
+
284
+ def to_i
285
+ return self.value.to_i
286
+ end
287
+
288
+ def to_f
289
+ return self.value.to_f
290
+ end
291
+
292
+ def to_a
293
+ return [self.value, self.unit]
294
+ end
295
+
296
+ def convert(unit)
297
+ return self if unit == self.unit
298
+ to_unit = Measure.resolve_alias unit
299
+ raise InvalidUnitError, "unknown unit: #{unit}" unless Measure.defined? unit
300
+ from_unit = Measure.resolve_alias self.unit
301
+ if Measure.compatible? from_unit, to_unit
302
+ # direct conversion
303
+ if @@conversion_map.has_key? from_unit and @@conversion_map[from_unit].has_key? to_unit
304
+ value = self.value * @@conversion_map[from_unit][to_unit]
305
+ else
306
+ value = self.value / @@conversion_map[to_unit][from_unit].to_f
307
+ end
308
+ elsif route = Measure.find_multi_hop_conversion(from_unit, to_unit)
309
+ u1 = route.shift
310
+ value = self.value
311
+ while u2 = route.shift
312
+ if @@conversion_map.has_key? u1 and @@conversion_map[u1].has_key? u2
313
+ value *= @@conversion_map[u1][u2]
314
+ else
315
+ value /= @@conversion_map[u2][u1].to_f
316
+ end
317
+ u1 = u2
318
+ end
319
+ else
320
+ raise CompatibilityError, "units not compatible: #{self.unit} and #{unit}"
321
+ end
322
+ # Second
323
+ return Measure.new(value, unit)
324
+ end
325
+
326
+ alias saved_method_missing method_missing
327
+ private_methods :saved_method_missing
328
+
329
+ def method_missing(name, *args)
330
+ if /^as_(\w+)/.match(name.to_s)
331
+ unit = $1.to_sym
332
+ return convert(unit)
333
+ end
334
+ return saved_method_missing(name, *args)
335
+ end
336
+
337
+ private
338
+
339
+ def check_coercable(other)
340
+ unless other.respond_to? :coerce
341
+ raise TypeError, "#{other.class} can't be coerced into #{self.class}"
342
+ end
343
+ end
344
+ end
345
+
346
+ def Measure(value, unit=1)
347
+ return Measure.new(value, unit)
348
+ end