alchemist 0.1.7 → 0.1.8

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.
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: