measured 2.5.0 → 2.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +32 -0
- data/CHANGELOG.md +40 -0
- data/README.md +3 -2
- data/gemfiles/{activesupport-5.0.gemfile → activesupport-5.2.gemfile} +1 -1
- data/gemfiles/activesupport-6.0.gemfile +1 -1
- data/gemfiles/{activesupport-5.1.gemfile → activesupport-6.1.gemfile} +1 -1
- data/lib/measured.rb +1 -0
- data/lib/measured/arithmetic.rb +1 -0
- data/lib/measured/base.rb +7 -5
- data/lib/measured/cache/json.rb +12 -5
- data/lib/measured/cache/json_writer.rb +1 -0
- data/lib/measured/cache/null.rb +1 -0
- data/lib/measured/conversion_table_builder.rb +21 -2
- data/lib/measured/cycle_detected.rb +11 -0
- data/lib/measured/measurable.rb +18 -21
- data/lib/measured/missing_conversion_path.rb +12 -0
- data/lib/measured/parser.rb +2 -1
- data/lib/measured/unit.rb +16 -26
- data/lib/measured/unit_already_added.rb +11 -0
- data/lib/measured/unit_error.rb +4 -0
- data/lib/measured/unit_system.rb +16 -20
- data/lib/measured/unit_system_builder.rb +4 -3
- data/lib/measured/units/length.rb +1 -0
- data/lib/measured/units/volume.rb +1 -0
- data/lib/measured/units/weight.rb +1 -0
- data/lib/measured/version.rb +2 -1
- data/measured.gemspec +10 -2
- data/test/arithmetic_test.rb +1 -0
- data/test/cache/json_test.rb +1 -0
- data/test/cache/json_writer_test.rb +1 -0
- data/test/cache/null_test.rb +1 -0
- data/test/cache_consistency_test.rb +1 -0
- data/test/conversion_table_builder_test.rb +11 -0
- data/test/measurable_test.rb +1 -1
- data/test/parser_test.rb +1 -0
- data/test/support/always_true_cache.rb +1 -0
- data/test/support/fake_system.rb +1 -0
- data/test/support/subclasses.rb +1 -0
- data/test/test_helper.rb +2 -1
- data/test/unit_error_test.rb +1 -0
- data/test/unit_system_builder_test.rb +28 -2
- data/test/unit_system_test.rb +1 -0
- data/test/unit_test.rb +1 -0
- data/test/units/length_test.rb +5 -4
- data/test/units/volume_test.rb +6 -5
- data/test/units/weight_test.rb +1 -0
- metadata +19 -14
- data/.travis.yml +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2641c37b73279794a9877f2c22d4fc40ed49e3fb66ec879863d631251befec60
|
4
|
+
data.tar.gz: d065ce4bd8cc5dd3b75844aa08a2bbd931ca83bf214ea3eb5fb7db1e67c08a23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8b7b34fd57b3bff6c467ba62156d21a89fadcd32e009cecb3c1ca36b609134ed4abd1950ac7513ba5ced802627a723086ba44ee563286e1294759dee1a0eeeb1
|
7
|
+
data.tar.gz: 9e2d5a86949c31b44c810c186d46b080bbc599d3f87a276676eb249b6b0841b41dc7964b4c071c98c21be3ab071f8db0085d9cf50d9134b1e377e62b545aae41
|
@@ -0,0 +1,32 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
env:
|
9
|
+
BUNDLE_GEMFILE: ${{ matrix.gemfile }}
|
10
|
+
strategy:
|
11
|
+
matrix:
|
12
|
+
ruby:
|
13
|
+
- '2.5'
|
14
|
+
- '2.6'
|
15
|
+
- '2.7'
|
16
|
+
- '3.0'
|
17
|
+
gemfile:
|
18
|
+
- Gemfile
|
19
|
+
- gemfiles/activesupport-5.2.gemfile
|
20
|
+
- gemfiles/activesupport-6.0.gemfile
|
21
|
+
- gemfiles/activesupport-6.1.gemfile
|
22
|
+
name: Ruby ${{ matrix.ruby }} ${{ matrix.gemfile }}
|
23
|
+
steps:
|
24
|
+
- uses: actions/checkout@v1
|
25
|
+
- name: Set up Ruby ${{ matrix.ruby }}
|
26
|
+
uses: ruby/setup-ruby@v1
|
27
|
+
with:
|
28
|
+
ruby-version: ${{ matrix.ruby }}
|
29
|
+
bundler-cache: true
|
30
|
+
- name: Run tests
|
31
|
+
run: |
|
32
|
+
bundle exec rake
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
Unreleased
|
2
|
+
-----
|
3
|
+
|
4
|
+
2.7.1
|
5
|
+
-----
|
6
|
+
|
7
|
+
* Fix Ruby 3.0 compatibility
|
8
|
+
|
9
|
+
2.7.0
|
10
|
+
-----
|
11
|
+
|
12
|
+
* Raises an exception on cyclic conversions. (@arturopie)
|
13
|
+
* Deduplicate strings loaded from the cache.
|
14
|
+
* Deduplicate parsed units.
|
15
|
+
|
16
|
+
2.6.0
|
17
|
+
-----
|
18
|
+
|
19
|
+
* Add `Measured::MissingConversionPath` and `Measured::UnitAlreadyAdded` as subclasses of `Measured::UnitError` to handle specific error cases. (@arturopie)
|
20
|
+
* Support only ActiveSupport 5.2 and above.
|
21
|
+
|
22
|
+
|
23
|
+
2.5.2
|
24
|
+
-----
|
25
|
+
|
26
|
+
* Allow unit values to be declared in the unit system through aliases and not just the base unit name.
|
27
|
+
* Fix some deprecations in tests and CI.
|
28
|
+
|
29
|
+
2.5.1
|
30
|
+
----
|
31
|
+
|
32
|
+
* Get rid of most memoizations in favor of eager computations.
|
33
|
+
|
34
|
+
2.5.0
|
35
|
+
-----
|
36
|
+
|
37
|
+
* Add `CHANGELOG.md`.
|
38
|
+
* Fix some deprecations and warnings.
|
39
|
+
* Support Rails 6 and Ruby 2.6.
|
40
|
+
* Cache conversion table in JSON file for first load performance.
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Measured [![Build Status](https://
|
1
|
+
# Measured [![Build Status](https://github.com/Shopify/measured/workflows/CI/badge.svg)](https://github.com/Shopify/measured/actions?query=workflow%3ACI)
|
2
2
|
|
3
3
|
Encapsulates measurements with their units. Provides easy conversion between units. Built in support for weight, length, and volume.
|
4
4
|
|
@@ -273,12 +273,13 @@ Existing alternatives which were considered:
|
|
273
273
|
|
274
274
|
### Gem: [unitwise](https://github.com/joshwlewis/unitwise)
|
275
275
|
* **Pros**
|
276
|
-
* Well written
|
276
|
+
* Well written.
|
277
277
|
* Conversions done with Unified Code for Units of Measure (UCUM) so highly accurate and reliable.
|
278
278
|
* **Cons**
|
279
279
|
* Lots of code. Good code, but lots of it.
|
280
280
|
* Many modifications to core types.
|
281
281
|
* ActiveRecord adapter exists but is written and maintained by a different person/org.
|
282
|
+
* Not actively maintained.
|
282
283
|
|
283
284
|
## Contributing
|
284
285
|
|
data/lib/measured.rb
CHANGED
data/lib/measured/arithmetic.rb
CHANGED
data/lib/measured/base.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "forwardable"
|
2
3
|
require "measured/version"
|
3
4
|
require "active_support/all"
|
@@ -5,8 +6,6 @@ require "bigdecimal"
|
|
5
6
|
require "json"
|
6
7
|
|
7
8
|
module Measured
|
8
|
-
class UnitError < StandardError ; end
|
9
|
-
|
10
9
|
class << self
|
11
10
|
def build(&block)
|
12
11
|
builder = UnitSystemBuilder.new
|
@@ -23,10 +22,9 @@ module Measured
|
|
23
22
|
|
24
23
|
def method_missing(method, *args)
|
25
24
|
class_name = "Measured::#{ method }"
|
25
|
+
klass = class_name.safe_constantize
|
26
26
|
|
27
|
-
if Measurable
|
28
|
-
klass = class_name.constantize
|
29
|
-
|
27
|
+
if klass && klass < Measurable
|
30
28
|
Measured.define_singleton_method(method) do |value, unit|
|
31
29
|
klass.new(value, unit)
|
32
30
|
end
|
@@ -39,6 +37,10 @@ module Measured
|
|
39
37
|
end
|
40
38
|
end
|
41
39
|
|
40
|
+
require "measured/unit_error"
|
41
|
+
require "measured/cycle_detected"
|
42
|
+
require "measured/unit_already_added"
|
43
|
+
require "measured/missing_conversion_path"
|
42
44
|
require "measured/arithmetic"
|
43
45
|
require "measured/parser"
|
44
46
|
require "measured/unit"
|
data/lib/measured/cache/json.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Measured::Cache
|
2
3
|
class Json
|
3
4
|
attr_reader :filename, :path
|
@@ -13,7 +14,7 @@ module Measured::Cache
|
|
13
14
|
|
14
15
|
def read
|
15
16
|
return unless exist?
|
16
|
-
decode(JSON.load(File.read(@path)))
|
17
|
+
decode(JSON.load(File.read(@path), nil, freeze: true))
|
17
18
|
end
|
18
19
|
|
19
20
|
def write(table)
|
@@ -36,11 +37,17 @@ module Measured::Cache
|
|
36
37
|
end
|
37
38
|
|
38
39
|
def decode(table)
|
39
|
-
table.
|
40
|
-
|
41
|
-
|
42
|
-
|
40
|
+
table.transform_values do |value1|
|
41
|
+
if value1.is_a?(Hash)
|
42
|
+
value1.transform_values do |value2|
|
43
|
+
if value2.is_a?(Hash)
|
44
|
+
Rational(value2["numerator"], value2["denominator"])
|
45
|
+
else
|
46
|
+
value2
|
47
|
+
end
|
43
48
|
end
|
49
|
+
else
|
50
|
+
value1
|
44
51
|
end
|
45
52
|
end
|
46
53
|
end
|
data/lib/measured/cache/null.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
class Measured::ConversionTableBuilder
|
2
3
|
attr_reader :units
|
3
4
|
|
@@ -23,6 +24,8 @@ class Measured::ConversionTableBuilder
|
|
23
24
|
private
|
24
25
|
|
25
26
|
def generate_table
|
27
|
+
validate_no_cycles
|
28
|
+
|
26
29
|
units.map(&:name).each_with_object({}) do |to_unit, table|
|
27
30
|
to_table = {to_unit => Rational(1, 1)}
|
28
31
|
|
@@ -36,10 +39,27 @@ class Measured::ConversionTableBuilder
|
|
36
39
|
end
|
37
40
|
end
|
38
41
|
|
42
|
+
def validate_no_cycles
|
43
|
+
graph = units.select { |unit| unit.conversion_unit.present? }.group_by { |unit| unit.name }
|
44
|
+
validate_acyclic_graph(graph, from: graph.keys[0])
|
45
|
+
end
|
46
|
+
|
47
|
+
# This uses a depth-first search algorithm: https://en.wikipedia.org/wiki/Depth-first_search
|
48
|
+
def validate_acyclic_graph(graph, from:, visited: [])
|
49
|
+
graph[from]&.each do |edge|
|
50
|
+
adjacent_node = edge.conversion_unit
|
51
|
+
if visited.include?(adjacent_node)
|
52
|
+
raise Measured::CycleDetected.new(edge)
|
53
|
+
else
|
54
|
+
validate_acyclic_graph(graph, from: adjacent_node, visited: visited + [adjacent_node])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
39
59
|
def find_conversion(to:, from:)
|
40
60
|
conversion = find_direct_conversion_cached(to: to, from: from) || find_tree_traversal_conversion(to: to, from: from)
|
41
61
|
|
42
|
-
raise Measured::
|
62
|
+
raise Measured::MissingConversionPath.new(from, to) unless conversion
|
43
63
|
|
44
64
|
conversion
|
45
65
|
end
|
@@ -87,5 +107,4 @@ class Measured::ConversionTableBuilder
|
|
87
107
|
|
88
108
|
nil
|
89
109
|
end
|
90
|
-
|
91
110
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Measured
|
3
|
+
class CycleDetected < UnitError
|
4
|
+
attr_reader :unit
|
5
|
+
|
6
|
+
def initialize(unit)
|
7
|
+
super("The following conversion introduces cycles in the unit system: #{unit}. Remove the conversion or fix the cycle.")
|
8
|
+
@unit = unit
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/measured/measurable.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
class Measured::Measurable < Numeric
|
2
|
-
DEFAULT_FORMAT_STRING =
|
3
|
+
DEFAULT_FORMAT_STRING = "%.2<value>f %<unit>s"
|
3
4
|
|
4
5
|
include Measured::Arithmetic
|
5
6
|
|
@@ -19,6 +20,18 @@ class Measured::Measurable < Numeric
|
|
19
20
|
else
|
20
21
|
BigDecimal(value)
|
21
22
|
end
|
23
|
+
|
24
|
+
@value_string = begin
|
25
|
+
str = case value
|
26
|
+
when Rational
|
27
|
+
value.denominator == 1 ? value.numerator.to_s : value.to_f.to_s
|
28
|
+
when BigDecimal
|
29
|
+
value.to_s("F")
|
30
|
+
else
|
31
|
+
value.to_f.to_s
|
32
|
+
end
|
33
|
+
str.gsub(/\.0*\Z/, "")
|
34
|
+
end.freeze
|
22
35
|
end
|
23
36
|
|
24
37
|
def convert_to(new_unit)
|
@@ -39,18 +52,16 @@ class Measured::Measurable < Numeric
|
|
39
52
|
end
|
40
53
|
|
41
54
|
def to_s
|
42
|
-
|
55
|
+
"#{@value_string} #{unit.name}"
|
43
56
|
end
|
44
57
|
|
45
58
|
def humanize
|
46
|
-
|
47
|
-
|
48
|
-
"#{value_string} #{unit_string}"
|
49
|
-
end
|
59
|
+
unit_string = value == 1 ? unit.name : ActiveSupport::Inflector.pluralize(unit.name)
|
60
|
+
"#{@value_string} #{unit_string}"
|
50
61
|
end
|
51
62
|
|
52
63
|
def inspect
|
53
|
-
|
64
|
+
"#<#{self.class}: #{@value_string} #{unit.inspect}>"
|
54
65
|
end
|
55
66
|
|
56
67
|
def <=>(other)
|
@@ -86,18 +97,4 @@ class Measured::Measurable < Numeric
|
|
86
97
|
def unit_from_unit_or_name!(value)
|
87
98
|
value.is_a?(Measured::Unit) ? value : self.class.unit_system.unit_for!(value)
|
88
99
|
end
|
89
|
-
|
90
|
-
def value_string
|
91
|
-
@value_string ||= begin
|
92
|
-
str = case value
|
93
|
-
when Rational
|
94
|
-
value.denominator == 1 ? value.numerator.to_s : value.to_f.to_s
|
95
|
-
when BigDecimal
|
96
|
-
value.to_s("F")
|
97
|
-
else
|
98
|
-
value.to_f.to_s
|
99
|
-
end
|
100
|
-
str.gsub(/\.0*\Z/, "")
|
101
|
-
end
|
102
|
-
end
|
103
100
|
end
|
data/lib/measured/parser.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Measured::Parser
|
2
3
|
extend self
|
3
4
|
|
@@ -32,6 +33,6 @@ module Measured::Parser
|
|
32
33
|
|
33
34
|
raise Measured::UnitError, "Cannot parse measurement from '#{string}'" unless result
|
34
35
|
|
35
|
-
[result.captures[0].to_r, result.captures[1]]
|
36
|
+
[result.captures[0].to_r, -result.captures[1]]
|
36
37
|
end
|
37
38
|
end
|
data/lib/measured/unit.rb
CHANGED
@@ -1,43 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
class Measured::Unit
|
2
3
|
include Comparable
|
3
4
|
|
4
|
-
attr_reader :name, :aliases, :conversion_amount, :conversion_unit, :unit_system
|
5
|
+
attr_reader :name, :names, :aliases, :conversion_amount, :conversion_unit, :unit_system, :inverse_conversion_amount
|
5
6
|
|
6
7
|
def initialize(name, aliases: [], value: nil, unit_system: nil)
|
7
8
|
@name = name.to_s.freeze
|
8
9
|
@aliases = aliases.map(&:to_s).map(&:freeze).freeze
|
10
|
+
@names = ([@name] + @aliases).sort!.freeze
|
9
11
|
@conversion_amount, @conversion_unit = parse_value(value) if value
|
12
|
+
@inverse_conversion_amount = (1 / conversion_amount if conversion_amount)
|
13
|
+
@conversion_string = ("#{conversion_amount} #{conversion_unit}" if conversion_amount || conversion_unit)
|
10
14
|
@unit_system = unit_system
|
11
15
|
end
|
12
16
|
|
13
|
-
def
|
17
|
+
def with(name: nil, unit_system: nil, aliases: nil, value: nil)
|
14
18
|
self.class.new(
|
15
|
-
name,
|
16
|
-
aliases: aliases,
|
17
|
-
value: conversion_string,
|
18
|
-
unit_system: unit_system
|
19
|
+
name || self.name,
|
20
|
+
aliases: aliases || self.aliases,
|
21
|
+
value: value || @conversion_string,
|
22
|
+
unit_system: unit_system || self.unit_system
|
19
23
|
)
|
20
24
|
end
|
21
25
|
|
22
|
-
def names
|
23
|
-
@names ||= ([name] + aliases).sort!.freeze
|
24
|
-
end
|
25
|
-
|
26
26
|
def to_s
|
27
|
-
|
28
|
-
"#{name} (#{conversion_string})".freeze
|
27
|
+
if @conversion_string
|
28
|
+
"#{name} (#{@conversion_string})".freeze
|
29
29
|
else
|
30
30
|
name
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
34
|
def inspect
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
"#<#{self.class.name}: #{pieces.join(" ")}>".freeze
|
40
|
-
end
|
35
|
+
pieces = [name]
|
36
|
+
pieces << "(#{aliases.join(", ")})" if aliases.any?
|
37
|
+
pieces << @conversion_string if @conversion_string
|
38
|
+
"#<#{self.class.name}: #{pieces.join(" ")}>".freeze
|
41
39
|
end
|
42
40
|
|
43
41
|
def <=>(other)
|
@@ -53,16 +51,8 @@ class Measured::Unit
|
|
53
51
|
end
|
54
52
|
end
|
55
53
|
|
56
|
-
def inverse_conversion_amount
|
57
|
-
@inverse_conversion_amount ||= 1 / conversion_amount if conversion_amount
|
58
|
-
end
|
59
|
-
|
60
54
|
private
|
61
55
|
|
62
|
-
def conversion_string
|
63
|
-
@conversion_string ||= ("#{conversion_amount} #{conversion_unit}" if conversion_amount || conversion_unit)
|
64
|
-
end
|
65
|
-
|
66
56
|
def parse_value(tokens)
|
67
57
|
case tokens
|
68
58
|
when String
|
data/lib/measured/unit_system.rb
CHANGED
@@ -1,17 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
class Measured::UnitSystem
|
2
|
-
attr_reader :units
|
3
|
+
attr_reader :units, :unit_names, :unit_names_with_aliases
|
3
4
|
|
4
5
|
def initialize(units, cache: nil)
|
5
|
-
@units = units.map { |unit| unit.
|
6
|
+
@units = units.map { |unit| unit.with(unit_system: self) }
|
7
|
+
@units = @units.map do |unit|
|
8
|
+
next unit unless unit.conversion_unit
|
9
|
+
conversion_unit = @units.find { |u| u.names.include?(unit.conversion_unit) }
|
10
|
+
next unit unless conversion_unit
|
11
|
+
unit.with(value: [unit.conversion_amount, conversion_unit.name])
|
12
|
+
end
|
13
|
+
@unit_names = @units.map(&:name).sort.freeze
|
14
|
+
@unit_names_with_aliases = @units.flat_map(&:names).sort.freeze
|
15
|
+
@unit_name_to_unit = @units.each_with_object({}) do |unit, hash|
|
16
|
+
unit.names.each { |name| hash[name.to_s] = unit }
|
17
|
+
end
|
6
18
|
@conversion_table_builder = Measured::ConversionTableBuilder.new(@units, cache: cache)
|
7
|
-
|
8
|
-
|
9
|
-
def unit_names_with_aliases
|
10
|
-
@unit_names_with_aliases ||= @units.flat_map(&:names).sort
|
11
|
-
end
|
12
|
-
|
13
|
-
def unit_names
|
14
|
-
@unit_names ||= @units.map(&:name).sort
|
19
|
+
@conversion_table = @conversion_table_builder.to_h.freeze
|
15
20
|
end
|
16
21
|
|
17
22
|
def unit_or_alias?(name)
|
@@ -51,14 +56,5 @@ class Measured::UnitSystem
|
|
51
56
|
|
52
57
|
protected
|
53
58
|
|
54
|
-
|
55
|
-
@conversion_table ||= @conversion_table_builder.to_h
|
56
|
-
end
|
57
|
-
|
58
|
-
def unit_name_to_unit
|
59
|
-
@unit_name_to_unit ||= @units.inject({}) do |hash, unit|
|
60
|
-
unit.names.each { |name| hash[name.to_s] = unit }
|
61
|
-
hash
|
62
|
-
end
|
63
|
-
end
|
59
|
+
attr_reader :unit_name_to_unit, :conversion_table
|
64
60
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
class Measured::UnitSystemBuilder
|
2
3
|
def initialize
|
3
4
|
@units = []
|
@@ -45,8 +46,8 @@ class Measured::UnitSystemBuilder
|
|
45
46
|
["P", "peta", 15],
|
46
47
|
["E", "exa", 18],
|
47
48
|
["Z", "zetta", 21],
|
48
|
-
["Y", "yotta", 24]
|
49
|
-
]
|
49
|
+
["Y", "yotta", 24],
|
50
|
+
].map(&:freeze).freeze
|
50
51
|
|
51
52
|
def build_si_units(name, aliases: [], value: nil)
|
52
53
|
si_units = [build_unit(name, aliases: aliases, value: value)]
|
@@ -66,7 +67,7 @@ class Measured::UnitSystemBuilder
|
|
66
67
|
def check_for_duplicate_unit_names!(unit)
|
67
68
|
names = @units.flat_map(&:names)
|
68
69
|
if names.any? { |name| unit.names.include?(name) }
|
69
|
-
raise Measured::
|
70
|
+
raise Measured::UnitAlreadyAdded.new(unit.name)
|
70
71
|
end
|
71
72
|
end
|
72
73
|
end
|
data/lib/measured/version.rb
CHANGED
data/measured.gemspec
CHANGED
@@ -13,16 +13,24 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.homepage = "https://github.com/Shopify/measured"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
17
|
+
# delete this section to allow pushing this gem to any host.
|
18
|
+
if spec.respond_to?(:metadata)
|
19
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
20
|
+
else
|
21
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
22
|
+
end
|
23
|
+
|
16
24
|
spec.files = `git ls-files -z`.split("\x0")
|
17
25
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
26
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
27
|
spec.require_paths = ["lib"]
|
20
28
|
|
21
|
-
spec.add_runtime_dependency "activesupport", ">= 5.
|
29
|
+
spec.add_runtime_dependency "activesupport", ">= 5.2"
|
22
30
|
|
23
31
|
spec.add_development_dependency "rake", "> 10.0"
|
24
32
|
spec.add_development_dependency "minitest", "> 5.5.1"
|
25
33
|
spec.add_development_dependency "minitest-reporters"
|
26
|
-
spec.add_development_dependency "mocha", "
|
34
|
+
spec.add_development_dependency "mocha", ">= 1.4.0"
|
27
35
|
spec.add_development_dependency "pry"
|
28
36
|
end
|
data/test/arithmetic_test.rb
CHANGED
data/test/cache/json_test.rb
CHANGED
data/test/cache/null_test.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "test_helper"
|
2
3
|
|
3
4
|
class Measured::ConversionTableBuilderTest < ActiveSupport::TestCase
|
@@ -116,6 +117,16 @@ class Measured::ConversionTableBuilderTest < ActiveSupport::TestCase
|
|
116
117
|
end
|
117
118
|
end
|
118
119
|
|
120
|
+
test "#to_h raises exception when there are cycles" do
|
121
|
+
unit1 = Measured::Unit.new(:pallets, value: "1 liters")
|
122
|
+
unit2 = Measured::Unit.new(:liters, value: "0.1 cases")
|
123
|
+
unit3 = Measured::Unit.new(:cases, value: "0.1 pallets")
|
124
|
+
|
125
|
+
assert_raises(Measured::CycleDetected) do
|
126
|
+
Measured::ConversionTableBuilder.new([unit1, unit2, unit3]).to_h
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
119
130
|
test "#cached? returns true if there's a cache" do
|
120
131
|
builder = Measured::ConversionTableBuilder.new([Measured::Unit.new(:test)], cache: { class: AlwaysTrueCache })
|
121
132
|
assert_predicate builder, :cached?
|
data/test/measurable_test.rb
CHANGED
data/test/parser_test.rb
CHANGED
data/test/support/fake_system.rb
CHANGED
data/test/support/subclasses.rb
CHANGED
data/test/test_helper.rb
CHANGED
data/test/unit_error_test.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "test_helper"
|
2
3
|
|
3
4
|
class Measured::UnitSystemBuilderTest < ActiveSupport::TestCase
|
@@ -11,15 +12,17 @@ class Measured::UnitSystemBuilderTest < ActiveSupport::TestCase
|
|
11
12
|
end
|
12
13
|
|
13
14
|
test "#unit cannot add duplicate unit names" do
|
14
|
-
assert_raises Measured::
|
15
|
+
error = assert_raises Measured::UnitAlreadyAdded do
|
15
16
|
Measured.build do
|
16
17
|
unit :m
|
17
18
|
unit :in, aliases: [:inch], value: "0.0254 m"
|
18
19
|
unit :in, aliases: [:thing], value: "123 m"
|
19
20
|
end
|
20
21
|
end
|
22
|
+
assert_equal("in", error.unit_name)
|
23
|
+
assert_equal("Unit in has already been added.", error.message)
|
21
24
|
|
22
|
-
assert_raises Measured::
|
25
|
+
assert_raises Measured::UnitAlreadyAdded do
|
23
26
|
Measured.build do
|
24
27
|
unit :m
|
25
28
|
unit :in, aliases: [:inch], value: "0.0254 m"
|
@@ -28,6 +31,19 @@ class Measured::UnitSystemBuilderTest < ActiveSupport::TestCase
|
|
28
31
|
end
|
29
32
|
end
|
30
33
|
|
34
|
+
test "#unit raises when cannot find conversion path" do
|
35
|
+
error = assert_raises Measured::MissingConversionPath do
|
36
|
+
Measured.build do
|
37
|
+
unit :m
|
38
|
+
unit :in, value: "0.0254 m"
|
39
|
+
unit :pallets, value: "5 cases"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
assert_equal("pallets", error.from)
|
43
|
+
assert_equal("m", error.to)
|
44
|
+
assert_equal("Cannot find conversion path from pallets to m.", error.message)
|
45
|
+
end
|
46
|
+
|
31
47
|
test "#unit is case sensitive" do
|
32
48
|
measurable = Measured.build do
|
33
49
|
unit :normal
|
@@ -38,6 +54,16 @@ class Measured::UnitSystemBuilderTest < ActiveSupport::TestCase
|
|
38
54
|
assert_equal 'BOLD', measurable.unit_system.unit_for!(:BOLD).name
|
39
55
|
end
|
40
56
|
|
57
|
+
test "#unit traverses aliases" do
|
58
|
+
measurable = Measured.build do
|
59
|
+
unit :piece, aliases: [:pieces]
|
60
|
+
unit :dozen, aliases: [:dz], value: "12 pieces"
|
61
|
+
end
|
62
|
+
|
63
|
+
assert_equal 12, measurable.unit_system.units.last.conversion_amount
|
64
|
+
assert_equal "piece", measurable.unit_system.units.last.conversion_unit
|
65
|
+
end
|
66
|
+
|
41
67
|
test "#si_unit adds 21 new units" do
|
42
68
|
measurable = Measured.build do
|
43
69
|
unit :ft
|
data/test/unit_system_test.rb
CHANGED
data/test/unit_test.rb
CHANGED
data/test/units/length_test.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "test_helper"
|
2
3
|
|
3
4
|
class Measured::LengthTest < ActiveSupport::TestCase
|
@@ -142,7 +143,7 @@ class Measured::LengthTest < ActiveSupport::TestCase
|
|
142
143
|
end
|
143
144
|
|
144
145
|
test ".convert_to from km to mi" do
|
145
|
-
assert_conversion Measured::Length, "2000 km", "0.1242742384475E4 mi"
|
146
|
+
assert_conversion Measured::Length, "2000 km", "0.1242742384475E4 mi"
|
146
147
|
end
|
147
148
|
|
148
149
|
test ".convert_to from km to mm" do
|
@@ -150,7 +151,7 @@ class Measured::LengthTest < ActiveSupport::TestCase
|
|
150
151
|
end
|
151
152
|
|
152
153
|
test ".convert_to from km to yd" do
|
153
|
-
assert_conversion Measured::Length, "2000 km", "0.218722659667542E7 yd"
|
154
|
+
assert_conversion Measured::Length, "2000 km", "0.218722659667542E7 yd"
|
154
155
|
end
|
155
156
|
|
156
157
|
test ".convert_to from m to cm" do
|
@@ -206,7 +207,7 @@ class Measured::LengthTest < ActiveSupport::TestCase
|
|
206
207
|
end
|
207
208
|
|
208
209
|
test ".convert_to from mi to mi" do
|
209
|
-
assert_exact_conversion Measured::Length, "2000 mi", "2000 mi"
|
210
|
+
assert_exact_conversion Measured::Length, "2000 mi", "2000 mi"
|
210
211
|
end
|
211
212
|
|
212
213
|
test ".convert_to from mi to mm" do
|
@@ -214,7 +215,7 @@ class Measured::LengthTest < ActiveSupport::TestCase
|
|
214
215
|
end
|
215
216
|
|
216
217
|
test ".convert_to from mi to yd" do
|
217
|
-
assert_exact_conversion Measured::Length, "2000 mi", "3520000 yd"
|
218
|
+
assert_exact_conversion Measured::Length, "2000 mi", "3520000 yd"
|
218
219
|
end
|
219
220
|
|
220
221
|
test ".convert_to from mm to cm" do
|
data/test/units/volume_test.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "test_helper"
|
2
3
|
|
3
4
|
class Measured::VolumeTest < ActiveSupport::TestCase
|
@@ -68,7 +69,7 @@ class Measured::VolumeTest < ActiveSupport::TestCase
|
|
68
69
|
test ".unit_names should be the list of base unit names" do
|
69
70
|
expected_units = %w(l m3 ft3 in3 gal us_gal qt us_qt pt us_pt oz us_oz)
|
70
71
|
expected_units += Measured::UnitSystemBuilder::SI_PREFIXES.map { |short, _, _| "#{short}l" }
|
71
|
-
assert_equal expected_units.sort, Measured::Volume.unit_names
|
72
|
+
assert_equal expected_units.sort, Measured::Volume.unit_names
|
72
73
|
end
|
73
74
|
|
74
75
|
test ".name" do
|
@@ -95,7 +96,7 @@ class Measured::VolumeTest < ActiveSupport::TestCase
|
|
95
96
|
test ".convert_to from gal to gal" do
|
96
97
|
assert_conversion Measured::Volume, "2000 gal", "2000 gal"
|
97
98
|
end
|
98
|
-
|
99
|
+
|
99
100
|
test ".convert_to from us_gal to us_gal" do
|
100
101
|
assert_conversion Measured::Volume, "2000 us_gal", "2000 us_gal"
|
101
102
|
end
|
@@ -123,7 +124,7 @@ class Measured::VolumeTest < ActiveSupport::TestCase
|
|
123
124
|
test ".convert_to from us_oz to us_oz" do
|
124
125
|
assert_conversion Measured::Volume, "2000 us_oz", "2000 us_oz"
|
125
126
|
end
|
126
|
-
|
127
|
+
|
127
128
|
test ".convert_to from ml to m3" do
|
128
129
|
assert_conversion Measured::Volume, "2000 ml", "0.002 m3"
|
129
130
|
end
|
@@ -303,7 +304,7 @@ class Measured::VolumeTest < ActiveSupport::TestCase
|
|
303
304
|
test ".convert_to from gal to us_oz" do
|
304
305
|
assert_conversion Measured::Volume, "2 gal", "307.4431809292 us_oz"
|
305
306
|
end
|
306
|
-
|
307
|
+
|
307
308
|
test ".convert_to from us_gal to qt" do
|
308
309
|
assert_conversion Measured::Volume, "2 us_gal", "6.661393477 qt"
|
309
310
|
end
|
@@ -387,4 +388,4 @@ class Measured::VolumeTest < ActiveSupport::TestCase
|
|
387
388
|
test ".convert_to from oz to us_oz" do
|
388
389
|
assert_conversion Measured::Volume, "2 oz", "1.9215207864 us_oz"
|
389
390
|
end
|
390
|
-
end
|
391
|
+
end
|
data/test/units/weight_test.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: measured
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin McPhillips
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2021-05-07 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activesupport
|
@@ -18,14 +18,14 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - ">="
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: '5.
|
21
|
+
version: '5.2'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
requirements:
|
26
26
|
- - ">="
|
27
27
|
- !ruby/object:Gem::Version
|
28
|
-
version: '5.
|
28
|
+
version: '5.2'
|
29
29
|
- !ruby/object:Gem::Dependency
|
30
30
|
name: rake
|
31
31
|
requirement: !ruby/object:Gem::Requirement
|
@@ -72,16 +72,16 @@ dependencies:
|
|
72
72
|
name: mocha
|
73
73
|
requirement: !ruby/object:Gem::Requirement
|
74
74
|
requirements:
|
75
|
-
- - "
|
75
|
+
- - ">="
|
76
76
|
- !ruby/object:Gem::Version
|
77
|
-
version: 1.
|
77
|
+
version: 1.4.0
|
78
78
|
type: :development
|
79
79
|
prerelease: false
|
80
80
|
version_requirements: !ruby/object:Gem::Requirement
|
81
81
|
requirements:
|
82
|
-
- - "
|
82
|
+
- - ">="
|
83
83
|
- !ruby/object:Gem::Version
|
84
|
-
version: 1.
|
84
|
+
version: 1.4.0
|
85
85
|
- !ruby/object:Gem::Dependency
|
86
86
|
name: pry
|
87
87
|
requirement: !ruby/object:Gem::Requirement
|
@@ -104,8 +104,9 @@ executables: []
|
|
104
104
|
extensions: []
|
105
105
|
extra_rdoc_files: []
|
106
106
|
files:
|
107
|
+
- ".github/workflows/ci.yml"
|
107
108
|
- ".gitignore"
|
108
|
-
-
|
109
|
+
- CHANGELOG.md
|
109
110
|
- Gemfile
|
110
111
|
- LICENSE
|
111
112
|
- README.md
|
@@ -115,9 +116,9 @@ files:
|
|
115
116
|
- cache/volume.json
|
116
117
|
- cache/weight.json
|
117
118
|
- dev.yml
|
118
|
-
- gemfiles/activesupport-5.
|
119
|
-
- gemfiles/activesupport-5.1.gemfile
|
119
|
+
- gemfiles/activesupport-5.2.gemfile
|
120
120
|
- gemfiles/activesupport-6.0.gemfile
|
121
|
+
- gemfiles/activesupport-6.1.gemfile
|
121
122
|
- lib/measured.rb
|
122
123
|
- lib/measured/arithmetic.rb
|
123
124
|
- lib/measured/base.rb
|
@@ -125,9 +126,13 @@ files:
|
|
125
126
|
- lib/measured/cache/json_writer.rb
|
126
127
|
- lib/measured/cache/null.rb
|
127
128
|
- lib/measured/conversion_table_builder.rb
|
129
|
+
- lib/measured/cycle_detected.rb
|
128
130
|
- lib/measured/measurable.rb
|
131
|
+
- lib/measured/missing_conversion_path.rb
|
129
132
|
- lib/measured/parser.rb
|
130
133
|
- lib/measured/unit.rb
|
134
|
+
- lib/measured/unit_already_added.rb
|
135
|
+
- lib/measured/unit_error.rb
|
131
136
|
- lib/measured/unit_system.rb
|
132
137
|
- lib/measured/unit_system_builder.rb
|
133
138
|
- lib/measured/units/length.rb
|
@@ -158,7 +163,8 @@ files:
|
|
158
163
|
homepage: https://github.com/Shopify/measured
|
159
164
|
licenses:
|
160
165
|
- MIT
|
161
|
-
metadata:
|
166
|
+
metadata:
|
167
|
+
allowed_push_host: https://rubygems.org
|
162
168
|
post_install_message:
|
163
169
|
rdoc_options: []
|
164
170
|
require_paths:
|
@@ -174,8 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
174
180
|
- !ruby/object:Gem::Version
|
175
181
|
version: '0'
|
176
182
|
requirements: []
|
177
|
-
|
178
|
-
rubygems_version: 2.7.6
|
183
|
+
rubygems_version: 3.0.3
|
179
184
|
signing_key:
|
180
185
|
specification_version: 4
|
181
186
|
summary: Encapsulate measurements with their units in Ruby
|
data/.travis.yml
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
language: ruby
|
2
|
-
sudo: false
|
3
|
-
cache: bundler
|
4
|
-
rvm:
|
5
|
-
- 2.3.8
|
6
|
-
- 2.4.5
|
7
|
-
- 2.5.5
|
8
|
-
- 2.6.3
|
9
|
-
gemfile:
|
10
|
-
- Gemfile
|
11
|
-
- gemfiles/activesupport-5.0.gemfile
|
12
|
-
- gemfiles/activesupport-5.1.gemfile
|
13
|
-
- gemfiles/activesupport-6.0.gemfile
|
14
|
-
before_script:
|
15
|
-
- gem update --system
|
16
|
-
matrix:
|
17
|
-
exclude:
|
18
|
-
- gemfile: gemfiles/activesupport-6.0.gemfile
|
19
|
-
rvm: 2.3.8
|
20
|
-
- gemfile: gemfiles/activesupport-6.0.gemfile
|
21
|
-
rvm: 2.4.5
|