uom 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,145 @@
1
+ require 'set'
2
+ require 'active_support/inflector'
3
+ require 'uom/error'
4
+ require 'uom/units'
5
+
6
+ module UOM
7
+ # A UnitFactory creates Unit instances.
8
+ class UnitFactory
9
+ LABEL_EXPONENT_HASH = {:square=>2, :sq=>2, :cubic=>3, :cu=>3}
10
+
11
+ # Returns the unit with the given label. Creates a new unit if necessary.
12
+ #
13
+ # Raises MeasurementError if the unit could not be created.
14
+ def create(label)
15
+ create_atomic(label.to_s) or create_composite(label.to_s) or raise MeasurementError.new("Unit '#{label}' not found")
16
+ end
17
+
18
+ private
19
+
20
+ def unit_for(label)
21
+ # the label => Unit hash of known units
22
+ @label_unit_hash ||= Unit.extent.association
23
+ # test whether there is an association before accessing it by label, since the extent hash
24
+ # has a hash factory that creates a Unit on demand, which is not desirable here
25
+ @label_unit_hash[label.to_sym] if @label_unit_hash.has_key?(label.to_sym)
26
+ end
27
+
28
+ def create_atomic(label)
29
+ scalar, axis = partition_atomic_label(label)
30
+ return if axis.nil?
31
+ label = "#{scalar}#{axis}".to_sym
32
+ unit_for(label) or Unit.new(label, scalar, axis)
33
+ end
34
+
35
+ def create_composite(label)
36
+ create_division(label) or create_product(label)
37
+ end
38
+
39
+ def create_division(str)
40
+ labels = str.split('_per_')
41
+ return if labels.size < 2
42
+ units = labels.map { |label| create(label) or raise MeasurementError.new("Unit '#{label}' not found in #{str}") }
43
+ std_label = units.join('_per_')
44
+ unit_for(std_label) or units.inject { |quotient, divisor| quotient / divisor }
45
+ end
46
+
47
+ def create_product(str)
48
+ std_label, units = collect_product_units(str)
49
+ unit_for(std_label) or units.inject { |product, multiplier| product * multiplier } unless units.size < 2
50
+ end
51
+
52
+ def collect_product_units(str)
53
+ return nil, [] if str.nil? or str.empty?
54
+ prefix = str[/[[:alnum:]]+/]
55
+ exponent = LABEL_EXPONENT_HASH[prefix.to_sym]
56
+ if exponent.nil? then
57
+ label, unit = slice_atomic_unit(str)
58
+ rest_label, rest_units = collect_product_units(str[label.length + 1..-1])
59
+ return "#{unit.label}_#{rest_label}".to_sym, [unit].concat(rest_units)
60
+ else
61
+ label, unit = slice_atomic_unit(str[prefix.length + 1..-1])
62
+ return "#{prefix}_#{unit.label}".to_sym, Array.new(exponent, unit)
63
+ end
64
+ end
65
+
66
+ # Returns the Unit which matches the str prefix.
67
+ #
68
+ # Raises MeasurementError if the unit could not be determined.
69
+ def slice_atomic_unit(str)
70
+ label = ''
71
+ str.scan(/_?[^_]+/) do |chunk|
72
+ label << chunk
73
+ unit = create_atomic(label)
74
+ return label, unit if unit
75
+ end
76
+ raise MeasurementError.new("Unit '#{str}' not found")
77
+ end
78
+
79
+ # Returns the +[scale, axis]+ pair parsed from the label.
80
+ def partition_atomic_label(label)
81
+ # singularize the label if it contains more than two letters and ends in s but not ss. this condition
82
+ # avoids incorrect singularization of, e.g., ms (millisecond) and gauss.
83
+ label = label.singularize if label =~ /[[:alnum:]][^s]s$/
84
+ scalar, axis = parse_atomic_label(label) { |label| match_axis_label_suffix(label) }
85
+ return scalar, axis unless scalar.nil? and axis.nil?
86
+ parse_atomic_label(label) { |label| match_axis_abbreviation_suffix(label) }
87
+ end
88
+
89
+ def parse_atomic_label(label)
90
+ # match the axis from the label suffix
91
+ suffix, axis = yield label
92
+ return nil, axis if axis.nil? or suffix == label
93
+ prefix = suffix.nil? ? label : label[0...-suffix.to_s.length]
94
+ # remove trailing separator from prefix if necessary,
95
+ # e.g. the "milli_meter" prefix "milli_" becomes "milli"
96
+ prefix = prefix[0, prefix.length - 1] if prefix[prefix.length - 1] == '_'
97
+ # match the remaining prefix part of the label to a scalar
98
+ scalar = match_scalar(prefix)
99
+ return nil, nil if scalar.nil?
100
+ return scalar, axis
101
+ end
102
+
103
+ # Returns the axis label and Unit which match on the label suffix substring, or nil
104
+ # if no match. For example,
105
+ # match_axis_label_suffix(:millimeter)
106
+ # returns "meter" and the meter Unit.
107
+ def match_axis_label_suffix(str)
108
+ best = [nil, nil]
109
+ Unit.each do |unit|
110
+ label_s = unit.label.to_s
111
+ best = [label_s, unit] if suffix?(label_s, str) and (best.first.nil? or label_s.length > best.first.length)
112
+ end
113
+ best
114
+ end
115
+
116
+ # Returns the axis abbreviation and Unit which match on the label suffix substring, or nil
117
+ # if no match. For example,
118
+ # match_axis_abbreviation_suffix(:mm)
119
+ # returns "meter" and the meter Unit.
120
+ def match_axis_abbreviation_suffix(str)
121
+ best = [nil, nil]
122
+ Unit.each do |unit|
123
+ unit.abbreviations.each do |abbrev|
124
+ abbrev_s = abbrev.to_s
125
+ best = abbrev_s, unit if suffix?(abbrev_s, str) and (best.first.nil? or abbrev_s.length > best.first.length)
126
+ end
127
+ end
128
+ best
129
+ end
130
+
131
+ # Returns the Factor which matches the label, or nil if no match.
132
+ def match_scalar(label)
133
+ label = label.to_sym
134
+ Factor.detect do |factor|
135
+ return factor if factor.label == label or factor.abbreviation == label
136
+ end
137
+ end
138
+
139
+ # Returns whether text ends in suffix.
140
+ def suffix?(suffix, text)
141
+ len = suffix.length
142
+ len <= text.length and suffix == text[-len, len]
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,115 @@
1
+ require 'uom/dimensions'
2
+ require 'uom/factors'
3
+ require 'uom/unit'
4
+ require 'uom/composite_unit'
5
+
6
+ module UOM
7
+ ## STANDARD UNITS ##
8
+
9
+ # the standard units for each dimension
10
+ METER = Unit.new(:meter, :m, LENGTH, *METRIC_FACTORS)
11
+ GRAM = Unit.new(:gram, :gm, :g, MASS, *METRIC_FACTORS)
12
+ SECOND = Unit.new(:second, :sec, :s, TIME, MILLI, MICRO, NANO, PICO, FEMTO)
13
+ KELVIN = Unit.new(:kelvin, :K, TEMPERATURE)
14
+ CANDELA = Unit.new(:candela, :cd, INTENSITY)
15
+ AMPERE = Unit.new(:ampere, :A, CURRENT, *ELECTRONIC_FACTORS)
16
+ MOLE = Unit.new(:mole, :mol, MASS)
17
+ BYTE = Unit.new(:byte, :Byte, :B, :b, INFORMATION) # Scaled byte units, e.g KByte, are defined below
18
+
19
+ ## SCALED UNITS ##
20
+ # Just a few common metric units; units are typically referred to by label rather than a constant,
21
+ # e.g. <tt>Unit.for(:millimeter)</tt>
22
+ MILLIMETER = Unit.for(:millimeter)
23
+ CENTIMETER = Unit.for(:centimeter)
24
+ DECIMETER = Unit.for(:decimeter)
25
+ MICROGRAM = Unit.for(:microgram)
26
+ MILLIGRAM = Unit.for(:milligram)
27
+ KILOGRAM = Unit.for(:kilogram)
28
+
29
+ # The BYTE scale is a base two axis multiplier rather than a metric scalar multipler.
30
+ # TODO - express these with base-2 factors?
31
+ KILOBYTE = Unit.new(:kilobyte, :KByte, :KB, BYTE, 1024)
32
+ MEGABYTE = Unit.new(:megabyte, :MByte, :MB, KILOBYTE, 1024)
33
+ GIGABYTE = Unit.new(:gigabyte, :GByte, :GB, MEGABYTE, 1024)
34
+ TERABYTE = Unit.new(:terabyte, :TByte, :TB, GIGABYTE, 1024)
35
+ PETABYTE = Unit.new(:petabyte, :PByte, :PB, TERABYTE, 1024)
36
+
37
+ ## DERIVED UNITS ##
38
+
39
+ # astronomical distances
40
+ ANGSTROM = Unit.new(:angstrom, :a, METER, 10000000000)
41
+ AU = Unit.new(:astronomical_unit, :AU, METER, 149597870691)
42
+ LIGHT_YEAR = Unit.new(:light_year, :ly, METER, 9460730472580800)
43
+
44
+ # temperature with inverse converters
45
+ CELSIUS = Unit.new(:celsius, :C, KELVIN) { |celsius| celsius - 273.15 }
46
+ KELVIN.add_converter(CELSIUS) { |kelvin| kelvin + 273.15 }
47
+ FARENHEIT = Unit.new(:farenheit, :F, CELSIUS) { |farenheit| (farenheit - 32) * (5 / 9) }
48
+ CELSIUS.add_converter(FARENHEIT) { |celsius| (celsius + 32) * (9 / 5) }
49
+ # mark temperatures as uncountable for the parser
50
+ ActiveSupport::Inflector.inflections { |inflect| inflect.uncountable('celsius', 'kelvin', 'farenheit') }
51
+
52
+ # time
53
+ MINUTE = Unit.new(:minute, :min, SECOND, 60)
54
+ HOUR = Unit.new(:hour, :hr, MINUTE, 60)
55
+ DAY = Unit.new(:day, HOUR, 24)
56
+
57
+ # information
58
+ BIT = Unit.new(:bit, BYTE, 1.0 / 8)
59
+
60
+ # US Customary length units
61
+ INCH = Unit.new(:inch, :in, METER, 0.0254)
62
+ FOOT = Unit.new(:foot, :ft, INCH, 12)
63
+ YARD = Unit.new(:yard, :yd, FOOT, 3)
64
+ MILE = Unit.new(:mile, :mi, FOOT, 5280)
65
+
66
+ # US Customary weight units
67
+ OUNCE = Unit.new(:ounce, :oz, GRAM, 28.34952)
68
+ POUND = Unit.new(:pound, :lb, OUNCE, 16)
69
+ TON = Unit.new(:ton, POUND, 2000)
70
+ GRAIN = Unit.new(:grain, :gr, POUND, 1.0 / 7000)
71
+ DRAM = Unit.new(:dram, :dr, OUNCE, 1.0 / 16)
72
+
73
+ # Metric composite units
74
+ LITER = Unit.new(:liter, :l, :L, DECIMETER * DECIMETER * DECIMETER, *METRIC_FACTORS)
75
+ MILLILITER = Unit.for(:milliliter)
76
+ JOULE = Unit.new(:joule, :J, (KILOGRAM * (METER * METER)) / (SECOND * SECOND), *ELECTRONIC_FACTORS)
77
+ ERG = Unit.new(:erg, (GRAM * (CENTIMETER * CENTIMETER)) / (SECOND * SECOND), *ELECTRONIC_FACTORS)
78
+ DYNE = Unit.new(:dyne, :dyn, (GRAM * CENTIMETER) / (SECOND * SECOND), *ELECTRONIC_FACTORS)
79
+ BARYE = Unit.new(:barye, :Ba, GRAM / (CENTIMETER * (SECOND * SECOND)), *ELECTRONIC_FACTORS)
80
+ POISE = Unit.new(:poise, :P, GRAM / (CENTIMETER * SECOND), KILO, MILLI, MICRO, NANO, PICO)
81
+ COULOMB = Unit.new(:coulomb, AMPERE / SECOND, *ELECTRONIC_FACTORS) # C abbreviation is taken by CENTIGRADE
82
+ VOLT = Unit.new(:volt, :V, JOULE / COULOMB, *ELECTRONIC_FACTORS)
83
+ FARAD = Unit.new(:farad, COULOMB / VOLT, *ELECTRONIC_FACTORS) # F abbreviation is taken by FARENHEIT
84
+ WEBER = Unit.new(:weber, :Wb, VOLT * SECOND, *ELECTRONIC_FACTORS)
85
+ HENRY = Unit.new(:henry, :H, WEBER / AMPERE, METRIC_FACTORS)
86
+ MAXWELL = Unit.new(:maxwell, :Mx, WEBER, 1.0 / 10 ** 8, METRIC_FACTORS)
87
+ GAUSS = Unit.new(:gauss, :G, MAXWELL / (CENTIMETER * CENTIMETER), METRIC_FACTORS)
88
+ # mark gauss as irregular for the parser
89
+ ActiveSupport::Inflector.inflections { |inflect| inflect.irregular('gauss', 'gausses') }
90
+ TESLA = Unit.new(:tesla, :T, WEBER / (METER * METER), DECI, CENTI, MILLI, MICRO, NANO, PICO)
91
+
92
+ # The speed of light in a vacuum in cm/sec.
93
+ C = 29979245800
94
+
95
+ OHM = Unit.new(:ohm, SECOND / CENTIMETER, 10 ** 9 / C)
96
+
97
+ # US Customary liquid volume units
98
+ FLUID_OUNCE = Unit.new(:fluid_ounce, :fl_oz, MILLILITER, 29.57353)
99
+ TBSP = Unit.new(:tablespoon, :tbsp, FLUID_OUNCE, 2)
100
+ TSP = Unit.new(:teaspoon, :tsp, TBSP, 1.0 / 3)
101
+ CUP = Unit.new(:cup, :cp, FLUID_OUNCE, 8)
102
+ PINT = Unit.new(:pint, :pt, CUP, 2)
103
+ QUART = Unit.new(:quart, :qt, PINT, 2)
104
+ GALLON = Unit.new(:gallon, :gal, QUART, 4)
105
+
106
+ # US Customary dry volume units
107
+ DRY_PINT = Unit.new(:dry_pint, :dry_pt, LITER) { |liter| liter / 0.5506105 }
108
+ DRY_QUART = Unit.new(:dry_quart, :dry_qt, DRY_PINT, 2)
109
+ DRY_GALLON = Unit.new(:dry_gallon, :dry_gal, DRY_QUART, 4)
110
+ PECK = Unit.new(:peck, :pk, DRY_GALLON, 2)
111
+ BUSHEL = Unit.new(:bushel, :bu, PECK, 4)
112
+
113
+ # Pressure
114
+ PSI = Unit.for(:pounds_per_square_inch).add_abbreviation(:psi)
115
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: uom
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 2
9
+ - 1
10
+ version: 1.2.1
11
+ platform: ruby
12
+ authors:
13
+ - OHSU
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-04 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: extensional
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description: " UOM implements Units of Measurement based on the International System of Units (SI).\n The base SI units, metric scalar factors and all possible combinations of these units\n are supported out of the box.\n"
36
+ email: caruby.org@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - doc/units.txt
45
+ - lib/active_support/core_ext/string/inflections.rb
46
+ - lib/active_support/core_ext/string.rb
47
+ - lib/active_support/inflections.rb
48
+ - lib/active_support/inflector.rb
49
+ - lib/active_support/README.txt
50
+ - lib/uom/composite_unit.rb
51
+ - lib/uom/composite_unit_key_canonicalizer.rb
52
+ - lib/uom/dimension.rb
53
+ - lib/uom/dimensions.rb
54
+ - lib/uom/error.rb
55
+ - lib/uom/factor.rb
56
+ - lib/uom/factors.rb
57
+ - lib/uom/measurement.rb
58
+ - lib/uom/unit.rb
59
+ - lib/uom/unit_factory.rb
60
+ - lib/uom/units.rb
61
+ - lib/uom.rb
62
+ - History.txt
63
+ - LEGAL
64
+ - LICENSE
65
+ - README.md
66
+ has_rdoc: uom
67
+ homepage: http://github.com/caruby/uom/
68
+ licenses: []
69
+
70
+ post_install_message:
71
+ rdoc_options: []
72
+
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ hash: 3
81
+ segments:
82
+ - 0
83
+ version: "0"
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ hash: 3
90
+ segments:
91
+ - 0
92
+ version: "0"
93
+ requirements: []
94
+
95
+ rubyforge_project: caruby
96
+ rubygems_version: 1.3.7
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: Ruby Unit of Measurement library.
100
+ test_files: []
101
+