alchemist 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: f47215664aef5b08025bbf14fcb971c7b3d8d726
4
- data.tar.gz: 92dcc4243340af6f1e4cacfb9593f292a8fdf16c
2
+ SHA256:
3
+ metadata.gz: 2d96474ca74ad168ff21a950de907b6f09974ba748c66457bd0f6528ac6aafde
4
+ data.tar.gz: 756514b42731f45ed578e872943bd2004cdf4ec0ede8feb2fcdf204a6ece2431
5
5
  SHA512:
6
- metadata.gz: 6f38ec388bac7cea190f0ace7bca99c2d5da955f4fa6ce4719cbca2cd4df603ccdfb53bc33e8125ff87169d77bb63873fe927c5e42be43b02549802cf163cd39
7
- data.tar.gz: 085ddcae66c574428f71b8c9c2a2b149144ba80debc5585c88d82723e2bd0c0b96bd2fc71166d1920e2815adc242c989d4e49f59c852093d1bed281153ffb5e2
6
+ metadata.gz: f363df781906fb3e2defd4719adbeb90a285bedf7ad833233019697f11ce63caf96f31d80e5903433a4da32c009d79a1187457b7ff958319e23d3b3f87d7e60c
7
+ data.tar.gz: 2be97bbc13985330796ddebef1c611c495dd20fbb9885ee932b2930833578a046b71dcea47ccd3cd6bc1534b0fb57e3d65c76109ca6a35a77f12fed165e7852c
data/.gitignore CHANGED
@@ -1,3 +1,6 @@
1
1
  .DS_Store
2
2
  *.gem
3
3
  pkg
4
+ coverage
5
+ bin
6
+ .bundle
@@ -1,12 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- alchemist (0.1.6)
4
+ alchemist (0.1.8)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
9
  diff-lcs (1.2.4)
10
+ multi_json (1.8.2)
10
11
  rake (10.0.3)
11
12
  rspec (2.14.1)
12
13
  rspec-core (~> 2.14.0)
@@ -16,6 +17,10 @@ GEM
16
17
  rspec-expectations (2.14.0)
17
18
  diff-lcs (>= 1.1.3, < 2.0)
18
19
  rspec-mocks (2.14.1)
20
+ simplecov (0.7.1)
21
+ multi_json (~> 1.0)
22
+ simplecov-html (~> 0.7.1)
23
+ simplecov-html (0.7.1)
19
24
 
20
25
  PLATFORMS
21
26
  ruby
@@ -24,3 +29,7 @@ DEPENDENCIES
24
29
  alchemist!
25
30
  rake
26
31
  rspec
32
+ simplecov
33
+
34
+ BUNDLED WITH
35
+ 2.0.2
data/NEWS.md ADDED
@@ -0,0 +1,6 @@
1
+ # 0.1.8
2
+
3
+ * Use BigDecimal() instead of BigDecimal.new to avoid deprecation warnings
4
+ * raise custom error classes instead of Exception [anujbiyani]
5
+ * Store unit name
6
+ * Fixed issue with binary prefix parsing in regards to JEDEC
data/README.md CHANGED
@@ -10,19 +10,19 @@ code more readable.
10
10
  Having code that looks like this is meaningless
11
11
 
12
12
  ```ruby
13
- miles = 8 * 1609.344
13
+ meters = 8 * 1609.344
14
14
  ```
15
15
 
16
16
  You could add comments
17
17
 
18
18
  ```ruby
19
- miles = 8 * 1609.344 # converting meters to miles
19
+ meters = 8 * 1609.344 # converting miles to meters
20
20
  ```
21
21
 
22
22
  But why not have this!
23
23
 
24
24
  ```ruby
25
- 8.meters.to.miles
25
+ 8.miles.to.meters
26
26
  ```
27
27
 
28
28
  You can even perform mathematical operations
@@ -47,7 +47,7 @@ require 'alchemist/geospatial'
47
47
  To switch to the IEC memory standard, force SI units with
48
48
 
49
49
  ```ruby
50
- Alchemist::use_si = true
50
+ Alchemist.config.use_si = true
51
51
  ```
52
52
 
53
53
  To see all the units alchemist has built in conversion for, check out the [units file](lib/alchemist/data/units.yml)
@@ -78,8 +78,15 @@ if you only want to use one category for conversions you can load it individuall
78
78
  Alchemist.setup('distance') # This will load only distance
79
79
  ```
80
80
 
81
- Rails Warning
82
- -------------
81
+ Rails
82
+ -----
83
+
84
+ #### Setup
85
+
86
+ It is suggested that you add your `Alchemist.setup` call to `config/initializers/alchemist.rb` and then restart your rails server.
87
+
88
+
89
+ #### Rails Warning
83
90
 
84
91
  Rails adds some methods like `bytes` to `Numeric` so it's highly recommended that instead of trying to call `bytes` on a numeric, you should use the `measure` method:
85
92
 
data/Rakefile CHANGED
@@ -4,3 +4,8 @@ require 'rspec/core/rake_task'
4
4
 
5
5
  RSpec::Core::RakeTask.new(:spec)
6
6
  task default: :spec
7
+
8
+ task :cov do
9
+ ENV["COVERAGE"] = "true"
10
+ Rake::Task[:spec].execute
11
+ end
@@ -9,8 +9,8 @@ Gem::Specification.new do |gem|
9
9
  gem.authors = ["Matthew Mongeau"]
10
10
  gem.email = ["halogenandtoast@gmail.com"]
11
11
  gem.homepage = 'http://github.com/halogenandtoast/alchemist'
12
- gem.summary = 'Conversions... like you\'ve never seen them before!'
13
- gem.description = 'Conversions... like you\'ve never seen them before!!'
12
+ gem.summary = 'A scientific conversion library'
13
+ gem.description = 'A scientific conversion library'
14
14
  gem.license = 'MIT'
15
15
 
16
16
  gem.files = `git ls-files`.split($/)
@@ -19,4 +19,5 @@ Gem::Specification.new do |gem|
19
19
 
20
20
  gem.add_development_dependency 'rake'
21
21
  gem.add_development_dependency 'rspec'
22
+ gem.add_development_dependency 'simplecov'
22
23
  end
@@ -4,19 +4,56 @@ require "alchemist/compound_measurement"
4
4
  require "alchemist/module_builder"
5
5
  require "alchemist/configuration"
6
6
  require "alchemist/library"
7
+ require "alchemist/conversion_calculator"
7
8
 
8
9
  module Alchemist
10
+
9
11
  autoload :Earth, "alchemist/objects/planets/earth"
10
12
 
13
+ class IncompatibleTypeError < StandardError ; end
14
+ class GeospatialArgumentError < StandardError ; end
15
+
11
16
  def self.setup category = nil
12
17
  if category
13
- Numeric.send(:include, ModuleBuilder.new(category).build)
18
+ load_category category
14
19
  else
15
- library.categories.each { |category| Numeric.send(:include, ModuleBuilder.new(category).build) }
20
+ load_all_categories
16
21
  end
17
22
  end
18
23
 
19
24
  def self.measure value, unit, exponent = 1.0
20
25
  Measurement.new value, unit, exponent
21
26
  end
27
+
28
+ def self.measure_prefixed value, prefix, unit
29
+ exponent = library.exponent_for(unit, prefix)
30
+ Measurement.new value, unit, exponent, prefix: prefix
31
+ end
32
+
33
+ def self.library
34
+ @library ||= Library.new
35
+ end
36
+
37
+ def self.config
38
+ @configuration ||= Configuration.new
39
+ end
40
+
41
+ def self.register(types, names, value)
42
+ library.register(types, names, value)
43
+ end
44
+
45
+ def self.reset!
46
+ @library = nil
47
+ @configuration = nil
48
+ end
49
+
50
+ private
51
+
52
+ def self.load_all_categories
53
+ library.load_all_categories
54
+ end
55
+
56
+ def self.load_category category
57
+ library.load_category(category)
58
+ end
22
59
  end
@@ -1,13 +1,22 @@
1
1
  module Alchemist
2
2
  class CompoundMeasurement
3
+ include Comparable
3
4
  attr_accessor :numerators, :denominators
4
5
 
5
6
  def initialize(numerator)
6
- @coefficient = 1
7
- @numerators = [numerator]
7
+ @coefficient = numerator.value
8
+ @numerators = [numerator / @coefficient]
8
9
  @denominators = []
9
10
  end
10
11
 
12
+ def <=> other
13
+ if @coefficient == other.coefficient
14
+ [@numerators, @denominators] <=> [other.numerators, other.denominators]
15
+ else
16
+ @coefficient <=> other.coefficient
17
+ end
18
+ end
19
+
11
20
  def *(value)
12
21
  case value
13
22
  when Numeric
@@ -19,6 +28,8 @@ module Alchemist
19
28
  end
20
29
  end
21
30
 
31
+ private
32
+
22
33
  def consolidate
23
34
  compress_units
24
35
  set_coefficients
@@ -61,5 +72,9 @@ module Alchemist
61
72
  consolidate
62
73
  end
63
74
  end
75
+
76
+ protected
77
+
78
+ attr_reader :coefficient
64
79
  end
65
80
  end
@@ -1,20 +1,14 @@
1
- require 'singleton'
2
-
3
1
  module Alchemist
4
- def self.config
5
- Configuration.instance
6
- end
7
-
8
2
  class Configuration
3
+
9
4
  DATA_DIR = File.join(File.dirname(__FILE__), "data")
10
5
  DEFAULT_UNITS_FILE = File.join(DATA_DIR, "units.yml")
11
6
  DEFAULT_SI_UNITS_FILE = File.join(DATA_DIR, "si_units.yml")
12
7
  DEFAULT_BINARY_PREFIXES_FILE = File.join(DATA_DIR, "binary_prefixes.yml")
13
8
  DEFAULT_UNIT_PREFIXES_FILE = File.join(DATA_DIR, "unit_prefixes.yml")
14
9
 
15
- include Singleton
16
-
17
10
  attr_accessor :use_si
11
+
18
12
  def initialize
19
13
  @use_si = false
20
14
  end
@@ -0,0 +1,47 @@
1
+ module Alchemist
2
+ class ConversionCalculator
3
+ def initialize(from, parsed_unit)
4
+ @from = from
5
+ @parsed_unit = parsed_unit
6
+ end
7
+
8
+ def calculate
9
+ Measurement.new(value / exponent, unit_name, exponent)
10
+ end
11
+
12
+ private
13
+ attr_reader :from, :parsed_unit
14
+
15
+ def exponent
16
+ parsed_unit.exponent
17
+ end
18
+
19
+ def unit_name
20
+ parsed_unit.unit_name
21
+ end
22
+
23
+ def value
24
+ if proc_based?
25
+ factor[1].call(base)
26
+ else
27
+ base / BigDecimal(factor.to_s)
28
+ end
29
+ end
30
+
31
+ def proc_based?
32
+ factor.is_a? Array
33
+ end
34
+
35
+ def base
36
+ @base ||= BigDecimal(from.base(type).to_s)
37
+ end
38
+
39
+ def type
40
+ @type ||= parsed_unit.guess_type(from)
41
+ end
42
+
43
+ def factor
44
+ @factor = Alchemist.library.conversion_base_for(type, unit_name)
45
+ end
46
+ end
47
+ end
@@ -1,12 +1,5 @@
1
- require 'singleton'
2
-
3
1
  module Alchemist
4
- def self.library
5
- Library.instance
6
- end
7
-
8
2
  class Library
9
- include Singleton
10
3
 
11
4
  attr_reader :si_units, :unit_prefixes, :conversion_table, :binary_prefixes
12
5
 
@@ -15,14 +8,42 @@ module Alchemist
15
8
  @si_units = YAML.load_file(Configuration::DEFAULT_SI_UNITS_FILE)
16
9
  @unit_prefixes = YAML.load_file(Configuration::DEFAULT_UNIT_PREFIXES_FILE)
17
10
  @binary_prefixes = YAML.load_file(Configuration::DEFAULT_BINARY_PREFIXES_FILE)
11
+ @loaded_modules = {}
12
+ end
13
+
14
+ def exponent_for(name, prefix)
15
+ if binary_unit?(name)
16
+ binary_prefixes[prefix]
17
+ else
18
+ unit_prefixes[prefix]
19
+ end
20
+ end
21
+
22
+ def binary_unit?(name)
23
+ using_binary? && measurement_for(name).include?(:information_storage)
24
+ end
25
+
26
+ def using_binary?
27
+ !Alchemist.config.use_si?
18
28
  end
19
29
 
20
30
  def categories
21
31
  @conversion_table.keys
22
32
  end
23
33
 
34
+ def load_category(category)
35
+ @loaded_modules[category] ||= ModuleBuilder.new(category)
36
+ Numeric.send :include, @loaded_modules[category]
37
+ end
38
+
39
+ def load_all_categories
40
+ categories.each do |category|
41
+ load_category category
42
+ end
43
+ end
44
+
24
45
  def unit_names category
25
- @conversion_table[category.to_sym].map { |values| values[0] }
46
+ @conversion_table[category.to_sym].map(&:first)
26
47
  end
27
48
 
28
49
  def measurement_for unit_name
@@ -68,6 +89,8 @@ module Alchemist
68
89
  conversions[name] << type
69
90
  conversion_table[type][name] = value
70
91
  end
92
+
93
+ @loaded_modules[type].define_unit_method(names)
71
94
  end
72
95
 
73
96
  private
@@ -1,15 +1,21 @@
1
+ require "bigdecimal"
1
2
  require "alchemist/measurement_convertor"
2
3
 
3
4
  module Alchemist
4
5
  class Measurement
5
6
  include Comparable
6
7
 
7
- attr_reader :unit_name, :exponent, :value
8
+ attr_reader :unprefixed_unit_name, :exponent, :value, :prefix
8
9
 
9
- def initialize value, unit_name, exponent = 1.0
10
+ def initialize value, unit_name, exponent = 1.0, options = {}
10
11
  @value = value.to_f
11
- @unit_name = unit_name
12
+ @unprefixed_unit_name = unit_name.to_sym
12
13
  @exponent = exponent
14
+ @prefix = options[:prefix] || ""
15
+ end
16
+
17
+ def unit_name
18
+ "#{prefix}#{unprefixed_unit_name}"
13
19
  end
14
20
 
15
21
  def per
@@ -27,13 +33,15 @@ module Alchemist
27
33
  def + measurement
28
34
  ensure_shared_type!(measurement)
29
35
  converted = measurement.to(unit_name)
30
- remeasure(value + converted.value)
36
+ addend = converted.value / exponent
37
+ remeasure(value + addend)
31
38
  end
32
39
 
33
40
  def - measurement
34
41
  ensure_shared_type!(measurement)
35
42
  converted = measurement.to(unit_name)
36
- remeasure(value - converted.value)
43
+ subtrahend = converted.value / exponent
44
+ remeasure(value - subtrahend)
37
45
  end
38
46
 
39
47
  def / measurement
@@ -68,15 +76,19 @@ module Alchemist
68
76
  end
69
77
 
70
78
  def to_f
71
- @value * exponent
79
+ (precise_value * exponent).to_f
80
+ end
81
+
82
+ def <=> other
83
+ to_f <=> other.to(unit_name).to_f
72
84
  end
73
85
 
74
- def <=>(other)
75
- self.to_f <=> other.to(unit_name).to_f
86
+ def == other
87
+ to_f <=> other.to(unit_name).to_f
76
88
  end
77
89
 
78
90
  def types
79
- library.measurement_for(unit_name)
91
+ library.measurement_for(unprefixed_unit_name)
80
92
  end
81
93
 
82
94
  def shared_types other_unit_name
@@ -115,12 +127,12 @@ module Alchemist
115
127
  end
116
128
 
117
129
  def library
118
- Library.instance
130
+ Alchemist.library
119
131
  end
120
132
 
121
133
  def ensure_shared_type! measurement
122
- if !has_shared_types?(measurement.unit_name)
123
- incompatible_types
134
+ if !has_shared_types?(measurement.unprefixed_unit_name)
135
+ incompatible_type_error
124
136
  end
125
137
  end
126
138
 
@@ -128,12 +140,12 @@ module Alchemist
128
140
  if conversion_base.is_a?(Array)
129
141
  exponent * conversion_base.first.call(value)
130
142
  else
131
- exponent * value * conversion_base
143
+ precise_value * conversion_base * exponent
132
144
  end
133
145
  end
134
146
 
135
147
  def conversion_base_for unit_type
136
- library.conversion_base_for(unit_type, unit_name)
148
+ library.conversion_base_for(unit_type, unprefixed_unit_name)
137
149
  end
138
150
 
139
151
  def has_shared_types? other_unit_name
@@ -147,15 +159,21 @@ module Alchemist
147
159
  return Alchemist.measure(value * measurement.to_f, new_type)
148
160
  end
149
161
  end
150
- incompatible_types
151
- end
152
-
153
- def incompatible_types
154
- raise Exception, "Incompatible Types"
162
+ incompatible_type_error
155
163
  end
156
164
 
157
165
  def convertor
158
166
  MeasurementConvertor.new(self)
159
167
  end
168
+
169
+ def precise_value
170
+ BigDecimal(@value.to_s)
171
+ end
172
+
173
+ private
174
+
175
+ def incompatible_type_error
176
+ raise IncompatibleTypeError, "Incompatible Types"
177
+ end
160
178
  end
161
179
  end
@@ -8,44 +8,24 @@ module Alchemist
8
8
  end
9
9
 
10
10
  def method_missing method, *args, &block
11
- exponent, unit_name = PrefixParser.new.parse(method)
12
- convert(from.shared_types(unit_name), unit_name, args.first || exponent)
11
+ parsed_unit = parse_prefix(method)
12
+ convert(parsed_unit)
13
13
  end
14
14
 
15
15
  private
16
16
  attr_reader :from
17
17
 
18
- def library
19
- Library.instance
18
+ def parse_prefix(name)
19
+ PrefixParser.new(name)
20
20
  end
21
21
 
22
- def convert types, unit_name, exponent
23
- if type = types[0]
24
- convert_from_type(type, unit_name, exponent)
25
- else
26
- raise Exception, "Incompatible Types"
27
- end
28
- end
29
22
 
30
- def convert_from_type(type, unit_name, exponent)
31
- conversion_base = BigDecimal.new(from.base(type).to_s)
32
- conversion_factor = library.conversion_base_for(type, unit_name)
33
-
34
- value = value_from(conversion_base, conversion_factor)
35
- Measurement.new(value / exponent, unit_name, exponent)
36
- end
37
-
38
- def value_from(base, factor)
39
- if proc_based?(factor)
40
- factor[1].call(base)
23
+ def convert parsed_unit
24
+ if parsed_unit.shares_type?(from)
25
+ ConversionCalculator.new(from, parsed_unit).calculate
41
26
  else
42
- base / BigDecimal.new(factor.to_s)
27
+ raise IncompatibleTypeError, "Incompatible Types"
43
28
  end
44
-
45
- end
46
-
47
- def proc_based? conversion_factor
48
- conversion_factor.is_a? Array
49
29
  end
50
30
  end
51
31
  end
@@ -1,41 +1,51 @@
1
1
  module Alchemist
2
- class ModuleBuilder
2
+ class ModuleBuilder < Module
3
3
  def initialize category
4
- @category = category
4
+ define_inspect_method(category)
5
+ define_unit_methods(category)
5
6
  end
6
7
 
7
- def build
8
- Module.new.tap do |category_module|
9
- category_module.class_eval %(def self.inspect() "#<Module(#{category})>" end)
10
- category_module.class_eval category_methods
8
+ def define_unit_method(names)
9
+ names.each do |name|
10
+ define_method(name.to_sym) { Alchemist.measure self, name.to_sym }
11
11
  end
12
12
  end
13
13
 
14
14
  private
15
- attr_reader :category
16
15
 
17
- def category_methods
18
- unit_names.map do |name|
19
- %(define_method("#{name}") { Alchemist.measure self, :#{name} }) + "\n" + prefixed_methods(name)
20
- end.join("\n")
16
+ def define_inspect_method(category)
17
+ define_method :inspect do
18
+ "#<Module(#{category})>"
19
+ end
20
+ end
21
+
22
+ def define_unit_methods(category)
23
+ unit_names(category).map do |name|
24
+ define_method name do
25
+ Alchemist.measure self, name.to_sym
26
+ end
27
+ prefixes_for(name).map do |prefix|
28
+ define_method "#{prefix}#{name}" do
29
+ Alchemist.measure_prefixed self, prefix.to_sym, name.to_sym
30
+ end
31
+ end
32
+ end
21
33
  end
22
34
 
23
- def unit_names
24
- Library.instance.unit_names(category)
35
+ def library
36
+ Alchemist.library
25
37
  end
26
38
 
27
- def prefixes_with_value(name)
28
- if Library.instance.si_units.include?(name.to_s)
29
- Library.instance.unit_prefixes
39
+ def unit_names(category)
40
+ library.unit_names(category)
41
+ end
42
+
43
+ def prefixes_for(name)
44
+ if library.si_units.include?(name.to_s)
45
+ library.unit_prefixes.keys
30
46
  else
31
47
  []
32
48
  end
33
49
  end
34
-
35
- def prefixed_methods(name)
36
- prefixes_with_value(name).map do |prefix, value|
37
- %(define_method("#{prefix}#{name}") { Alchemist.measure self, :#{name}, #{value} })
38
- end.join("\n")
39
- end
40
50
  end
41
51
  end
@@ -12,7 +12,7 @@ module Alchemist
12
12
  elsif types.include?(:distance)
13
13
  geospatial_arc_to_angle
14
14
  else
15
- raise Exception, "geospatial must either be angles or distance"
15
+ raise GeospatialArgumentError, "geospatial must either be angles or distance"
16
16
  end
17
17
  end
18
18
 
@@ -1,14 +1,18 @@
1
1
  module Alchemist
2
2
  class PrefixParser
3
- def parse(unit)
3
+ attr_reader :unit_name, :exponent
4
+
5
+ def initialize(unit)
6
+ @unit = unit
4
7
  matches = unit.to_s.match(prefix_matcher)
5
8
  prefix, parsed_unit = matches.captures
6
9
 
7
10
  if prefix && library.si_units.include?(parsed_unit)
8
- value = prefixed_value_for(prefix.to_sym, parsed_unit)
9
- [value, parsed_unit.to_sym]
11
+ @exponent = library.exponent_for(parsed_unit, prefix.to_sym)
12
+ @unit_name = parsed_unit.to_sym
10
13
  else
11
- [1, unit]
14
+ @exponent = 1
15
+ @unit_name = unit
12
16
  end
13
17
  end
14
18
 
@@ -16,8 +20,21 @@ module Alchemist
16
20
  @prefix_matcher ||= generate_prefix_matcher
17
21
  end
18
22
 
23
+ def shares_type?(from)
24
+ guess_type(from)
25
+ end
26
+
27
+ def guess_type(from)
28
+ shared_types(from).first
29
+ end
30
+
19
31
  private
20
32
 
33
+ def shared_types from
34
+ library.measurement_for(from.unprefixed_unit_name) & library.measurement_for(unit_name)
35
+ end
36
+
37
+
21
38
  def library
22
39
  Alchemist.library
23
40
  end
@@ -26,17 +43,5 @@ module Alchemist
26
43
  prefix_keys = library.unit_prefixes.keys.map(&:to_s).sort{ |a,b| b.length <=> a.length }
27
44
  %r{^(#{prefix_keys.join('|')})?(.+)}
28
45
  end
29
-
30
- def prefixed_value_for prefix, unit
31
- if use_binary_prefix? unit
32
- library.binary_prefixes[prefix]
33
- else
34
- library.unit_prefixes[prefix]
35
- end
36
- end
37
-
38
- def use_binary_prefix? unit
39
- !Alchemist.config.use_si? && library.measurement_for(unit).include?(:information_storage)
40
- end
41
46
  end
42
47
  end
@@ -1,3 +1,3 @@
1
1
  module Alchemist
2
- VERSION = "0.1.7"
2
+ VERSION = "0.1.8"
3
3
  end
@@ -1,32 +1,81 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Alchemist do
4
+ it "loads a single category into the library" do
5
+ library_double = stub_library
4
6
 
5
- it "sets up Numeric" do
6
- category_module = build_category_module
7
- fake_numeric = build_fake_numeric
8
7
  Alchemist.setup('distance')
9
- expect(Alchemist::ModuleBuilder).to have_received(:new).with('distance')
10
- expect(fake_numeric).to have_received(:include).with(category_module)
8
+
9
+ expect(library_double).to have_received(:load_category).with('distance')
10
+ end
11
+
12
+ it "loads all categories into the library" do
13
+ library_double = stub_library
14
+
15
+ Alchemist.setup
16
+
17
+ expect(library_double).to have_received(:load_all_categories)
11
18
  end
12
19
 
13
20
  it "creates a measurement" do
14
21
  unit = Alchemist.measure(1, :meter)
22
+
15
23
  expect(unit).to eq(1.meter)
16
24
  end
17
25
 
18
- def build_category_module
19
- double.tap do |category_module|
20
- module_builder = double()
21
- allow(module_builder).to receive(:build) { category_module }
22
- Alchemist::ModuleBuilder.stub(:new).and_return(module_builder)
26
+ it "delegates register to the Library" do
27
+ stub_library
28
+
29
+ Alchemist.register(:foo, :bar, :baz)
30
+
31
+ expect(Alchemist.library).to have_received(:register).with(:foo, :bar, :baz)
32
+ end
33
+
34
+ it "builds a library" do
35
+ library_double = stub_library
36
+
37
+ expect(Alchemist.library).to eq library_double
38
+ end
39
+
40
+ it "builds its configuration" do
41
+ configuration_double = stub_configuration
42
+
43
+ expect(Alchemist.config).to eq configuration_double
44
+ end
45
+
46
+ it "will reset! the library" do
47
+ stub_library
48
+ 2.times { Alchemist.library }
49
+ Alchemist.reset!
50
+ Alchemist.library
51
+ expect(Alchemist::Library).to have_received(:new).twice
52
+ end
53
+
54
+ it "will reset! the configuration" do
55
+ stub_configuration
56
+ 2.times { Alchemist.config }
57
+ Alchemist.reset!
58
+ Alchemist.config
59
+ expect(Alchemist::Configuration).to have_received(:new).twice
60
+ end
61
+
62
+ def stub_library
63
+ double(Alchemist::Library, library_methods).tap do |library_double|
64
+ allow(Alchemist::Library).to receive(:new).and_return(library_double)
23
65
  end
24
66
  end
25
67
 
26
- def build_fake_numeric
27
- double.tap do |fake_module|
28
- allow(fake_module).to receive(:include)
29
- stub_const("Numeric", fake_module)
68
+ def library_methods
69
+ {
70
+ register: true,
71
+ load_category: true,
72
+ load_all_categories: true
73
+ }
74
+ end
75
+
76
+ def stub_configuration
77
+ double(Alchemist::Configuration).tap do |configuration_double|
78
+ allow(Alchemist::Configuration).to receive(:new).and_return(configuration_double)
30
79
  end
31
80
  end
32
81
  end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ module Alchemist
4
+ RSpec.describe "byte conversions" do
5
+ it "uses JEDEC specification" do
6
+ expect(1.kb.to.b.to_f).to eq(1024.0)
7
+ end
8
+
9
+ it "uses SI when configured to do so" do
10
+ Alchemist.config.use_si = true
11
+ expect(1.kb.to.b.to_f).to eq(1000.0)
12
+ end
13
+ end
14
+ end
@@ -9,5 +9,21 @@ module Alchemist
9
9
  it "can handle elimination of a unit" do
10
10
  expect(4.m.per.m).to eq(4)
11
11
  end
12
+
13
+ it "can multiply a coefficient" do
14
+ expect(10.miles.per.second * 3).to eq(30.miles.per.second)
15
+ end
16
+
17
+ it "considers units different by coefficient" do
18
+ expect(10.miles.per.second).not_to eq(30.miles.per.second)
19
+ end
20
+
21
+ it "considers unit different by numerators" do
22
+ expect(10.miles.per.second).not_to eq(10.feet.per.second)
23
+ end
24
+
25
+ it "considers unit different by denominators" do
26
+ expect(10.miles.per.second).not_to eq(10.miles.per.minutes)
27
+ end
12
28
  end
13
29
  end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ module Alchemist
4
+ describe Configuration do
5
+ it "defaults use_si to false" do
6
+ config = Configuration.new
7
+ expect(config.use_si?).to be false
8
+ end
9
+
10
+ it "allows use_si to be changed" do
11
+ config = Configuration.new
12
+ config.use_si = true
13
+ expect(config.use_si?).to be true
14
+ end
15
+ end
16
+ end
@@ -2,27 +2,17 @@ require 'spec_helper'
2
2
 
3
3
  module Alchemist
4
4
  describe ConversionTable do
5
- after(:each) do
6
- load_file default_file
7
- end
8
-
9
- it "should use the new file" do
10
- load_file good_file
11
- expect(conversion_table[:volume]).to eq({ litre: 1.0, swallow: 0.006, pint: 0.5506105 })
12
- end
13
5
 
14
- it "should use the defaults when it fails to load" do
15
- load_file bad_file
16
- expect(5280.feet).to eq(1.mile.to.feet)
6
+ it "loads a properly formed file" do
7
+ conversion_table = ConversionTable.new
8
+ expect(conversion_table.load_all(good_file)).not_to be_nil
17
9
  end
18
10
 
19
- def conversion_table
20
- Alchemist.library.conversion_table
11
+ it "fails to load a improperly formed file" do
12
+ conversion_table = ConversionTable.new
13
+ expect(conversion_table.load_all(bad_file)).to be_nil
21
14
  end
22
15
 
23
- def load_file file
24
- Alchemist.library.load_conversion_table file
25
- end
26
16
 
27
17
  def good_file
28
18
  File.join(File.dirname(__FILE__), "fixtures", "good_test.yml")
@@ -31,9 +21,5 @@ module Alchemist
31
21
  def bad_file
32
22
  File.join(File.dirname(__FILE__), "fixtures", "bad_test.yml")
33
23
  end
34
-
35
- def default_file
36
- Configuration::DEFAULT_UNITS_FILE
37
- end
38
24
  end
39
25
  end
@@ -0,0 +1,11 @@
1
+ require "spec_helper"
2
+
3
+ describe Alchemist, "errors" do
4
+ it "raises IncompatibleTypeError when invalid types are used" do
5
+ expect { 1.second + 1.meter }.to raise_error(Alchemist::IncompatibleTypeError)
6
+ end
7
+
8
+ it "throws GeospatialArgumentError when invalid type is used" do
9
+ expect { 1.second.geospatial }.to raise_error(Alchemist::GeospatialArgumentError)
10
+ end
11
+ end
@@ -3,10 +3,14 @@ require 'spec_helper'
3
3
  module Alchemist
4
4
  describe Library do
5
5
  it "returns a list of the binary prefixes" do
6
+ library = Library.new
6
7
  expect(library.binary_prefixes[:kilo]).to eq(1024.0)
7
8
  end
8
9
 
9
10
  it "can register units with formulas" do
11
+ stub_loading
12
+ library = Library.new
13
+ library.load_category :yetis
10
14
  to = lambda { |t| t + 1 }
11
15
  from = lambda { |t| t - 1 }
12
16
  library.register(:yetis, :yeti, [to, from])
@@ -14,26 +18,36 @@ module Alchemist
14
18
  end
15
19
 
16
20
  it "can register units with plural names" do
21
+ stub_loading
22
+ library = Library.new
23
+ library.load_category :beards
17
24
  library.register(:beards, [:beard_second, :beard_seconds], 1.0)
18
25
  expect(library.conversion_table[:beards]).to eq({:beard_second=>1.0, :beard_seconds=>1.0})
19
26
  end
20
27
 
21
28
  it "can register units" do
29
+ stub_loading
30
+ library = Library.new
31
+ library.load_category :quux
22
32
  library.register :quux, :qaat, 1.0
23
33
  library.register :quux, :quut, 3.0
24
34
  expect(library.conversion_table[:quux]).to eq({:qaat=>1.0, :quut=>3.0})
25
35
  end
26
36
 
27
37
  it "knows if it has a measurement" do
28
- expect(library.has_measurement?(:meter)).to be_true
38
+ library = Library.new
39
+ expect(library.has_measurement?(:meter)).to be true
29
40
  end
30
41
 
31
42
  it "knows if it doesn't have a measurement" do
32
- expect(library.has_measurement?(:wombat)).to be_false
43
+ library = Library.new
44
+ expect(library.has_measurement?(:wombat)).to be false
33
45
  end
34
46
 
35
- def library
36
- Library.instance
47
+ def stub_loading
48
+ module_double = double(Module, define_unit_method: true)
49
+ allow(ModuleBuilder).to receive(:new).and_return(module_double)
50
+ allow(Numeric).to receive(:send).with(:include, module_double).and_return(true)
37
51
  end
38
52
  end
39
53
  end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ module Alchemist
4
+ describe MeasurementConvertor do
5
+
6
+ end
7
+ end
@@ -6,6 +6,10 @@ module Alchemist
6
6
  expect(1.m).to eq(1.meter)
7
7
  end
8
8
 
9
+ it "has a unit name" do
10
+ expect(1.kilometer.unit_name).to eq("kilometer")
11
+ end
12
+
9
13
  it "can be converted" do
10
14
  expect(5.grams).to eq(0.005.kilograms)
11
15
  end
@@ -32,6 +36,10 @@ module Alchemist
32
36
  expect(5.meters + 2.inches).to eq(5.0508.meters)
33
37
  end
34
38
 
39
+ it "can add different measurements with exponents" do
40
+ expect(10.kilometers + 1.mile).to eq(11.609344.kilometers)
41
+ end
42
+
35
43
  it "can be subtracted" do
36
44
  expect(3.meters - 2.meters).to eq(1.meter)
37
45
  end
@@ -40,6 +48,10 @@ module Alchemist
40
48
  expect(5.meters - 2.inches).to eq(4.9492.meters)
41
49
  end
42
50
 
51
+ it "can subtract measurements with exponents" do
52
+ expect(10.kilometers - 1.mile).to eq(8.390656.kilometers)
53
+ end
54
+
43
55
  it "can provide an integer value" do
44
56
  expect(10.meters.to_i).to eq(10)
45
57
  end
@@ -78,7 +90,7 @@ module Alchemist
78
90
  describe '#geospatial' do
79
91
 
80
92
  it 'should convert angles to meters' do
81
- expect(1.degree.geospatial).to eq(111318.84502145034.meters)
93
+ expect(1.degree.geospatial).to eq(111318.84502145035.meters)
82
94
  end
83
95
 
84
96
  it 'should convert distances to radians' do
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ module Alchemist
4
+ describe ModuleBuilder do
5
+ it "allows methods to be added to the build module" do
6
+ allow(Alchemist).to receive(:library).and_return(library_double)
7
+ builder = ModuleBuilder.new(:test)
8
+ builder.define_unit_method([:wombat])
9
+ expect(builder.instance_methods).to include(:wombat)
10
+ end
11
+
12
+ def library_double
13
+ double(Alchemist::Library, unit_names: [], si_units: [])
14
+ end
15
+ end
16
+ end
@@ -3,8 +3,20 @@ require 'spec_helper'
3
3
  module Alchemist
4
4
  describe PrefixParser do
5
5
  it "can parse a prefix" do
6
- parsed = PrefixParser.new.parse(:kilometer)
7
- expect(parsed).to eq([1000.0, :meter])
6
+ parsed = PrefixParser.new(:kilometer)
7
+ expect(parsed.exponent).to eq(1000.0)
8
+ expect(parsed.unit_name).to eq(:meter)
9
+ end
10
+
11
+ it "can parse binary prefixes if si is off" do
12
+ parsed = PrefixParser.new(:gigabyte)
13
+ expect(parsed.exponent).to eq(1024 ** 3)
14
+ end
15
+
16
+ it "can parse si prefixes if si is on" do
17
+ Alchemist.config.use_si = true
18
+ parsed = PrefixParser.new(:gigabyte)
19
+ expect(parsed.exponent).to eq(1000 ** 3)
8
20
  end
9
21
  end
10
22
  end
@@ -1,6 +1,19 @@
1
1
  $LOAD_PATH.unshift File.join(File.dirname(__FILE__), "..", "lib")
2
2
 
3
+ if ENV["COVERAGE"]
4
+ require 'simplecov'
5
+ SimpleCov.start do
6
+ add_filter "/spec/"
7
+ end
8
+ end
9
+
3
10
  require 'alchemist'
4
11
  require 'alchemist/geospatial'
5
12
 
13
+ RSpec.configure do |config|
14
+ config.before(:each) do
15
+ Alchemist.reset!
16
+ end
17
+ end
18
+
6
19
  Alchemist.setup
metadata CHANGED
@@ -1,59 +1,75 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alchemist
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew Mongeau
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-09-15 00:00:00.000000000 Z
11
+ date: 2019-06-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- description: Conversions... like you've never seen them before!!
41
+ - !ruby/object:Gem::Dependency
42
+ name: simplecov
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: A scientific conversion library
42
56
  email:
43
57
  - halogenandtoast@gmail.com
44
58
  executables: []
45
59
  extensions: []
46
60
  extra_rdoc_files: []
47
61
  files:
48
- - .gitignore
62
+ - ".gitignore"
49
63
  - Gemfile
50
64
  - Gemfile.lock
65
+ - NEWS.md
51
66
  - README.md
52
67
  - Rakefile
53
68
  - alchemist.gemspec
54
69
  - lib/alchemist.rb
55
70
  - lib/alchemist/compound_measurement.rb
56
71
  - lib/alchemist/configuration.rb
72
+ - lib/alchemist/conversion_calculator.rb
57
73
  - lib/alchemist/conversion_table.rb
58
74
  - lib/alchemist/data/binary_prefixes.yml
59
75
  - lib/alchemist/data/si_units.yml
@@ -69,12 +85,17 @@ files:
69
85
  - lib/alchemist/setup.rb
70
86
  - lib/alchemist/version.rb
71
87
  - spec/alchemist_spec.rb
88
+ - spec/byte_spec.rb
72
89
  - spec/compound_measurement_spec.rb
90
+ - spec/configuration_spec.rb
73
91
  - spec/conversion_table_spec.rb
92
+ - spec/error_spec.rb
74
93
  - spec/fixtures/bad_test.yml
75
94
  - spec/fixtures/good_test.yml
76
95
  - spec/library_spec.rb
96
+ - spec/measurement_convertor_spec.rb
77
97
  - spec/measurement_spec.rb
98
+ - spec/module_builder_spec.rb
78
99
  - spec/prefix_parser_spec.rb
79
100
  - spec/spec_helper.rb
80
101
  homepage: http://github.com/halogenandtoast/alchemist
@@ -87,28 +108,32 @@ require_paths:
87
108
  - lib
88
109
  required_ruby_version: !ruby/object:Gem::Requirement
89
110
  requirements:
90
- - - '>='
111
+ - - ">="
91
112
  - !ruby/object:Gem::Version
92
113
  version: '0'
93
114
  required_rubygems_version: !ruby/object:Gem::Requirement
94
115
  requirements:
95
- - - '>='
116
+ - - ">="
96
117
  - !ruby/object:Gem::Version
97
118
  version: '0'
98
119
  requirements: []
99
120
  rubyforge_project:
100
- rubygems_version: 2.0.2
121
+ rubygems_version: 2.7.6
101
122
  signing_key:
102
123
  specification_version: 4
103
- summary: Conversions... like you've never seen them before!
124
+ summary: A scientific conversion library
104
125
  test_files:
105
126
  - spec/alchemist_spec.rb
127
+ - spec/byte_spec.rb
106
128
  - spec/compound_measurement_spec.rb
129
+ - spec/configuration_spec.rb
107
130
  - spec/conversion_table_spec.rb
131
+ - spec/error_spec.rb
108
132
  - spec/fixtures/bad_test.yml
109
133
  - spec/fixtures/good_test.yml
110
134
  - spec/library_spec.rb
135
+ - spec/measurement_convertor_spec.rb
111
136
  - spec/measurement_spec.rb
137
+ - spec/module_builder_spec.rb
112
138
  - spec/prefix_parser_spec.rb
113
139
  - spec/spec_helper.rb
114
- has_rdoc: