measured 2.5.2 → 2.8.0

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
2
  SHA256:
3
- metadata.gz: ce0f6c7c10571db0dd0d844638dd0bae637d2f9b13e87c29700bf9db51d84197
4
- data.tar.gz: 936e5b74c36c65ca8393b94684b13341ff84d6981cfa34379af9cd0f8bdbed94
3
+ metadata.gz: 474f40f00059e15ec826a6ac74758ea6919f3b3c61a75bc42bcf181f9ac357dc
4
+ data.tar.gz: 356026fb5443a773928dbe21ac20f28f97ed72a987f6708b682cb665eeb511fc
5
5
  SHA512:
6
- metadata.gz: 95eea761fff7e89716925729c8dae771b6274705390622930cf58443bc1d18bd2405bc65b13fa46b1c232ad249a231b0c1d5d7bedafa5d8262f860d94ac24437
7
- data.tar.gz: b9d5ed9a9dc1a0fe8710a62c6c7d673d53c4fee4161f76452ab1f1b708edcb1a8ca081577dd4fb012358b375952d675f8e0bfc8d253f5b8519a024c944806bf1
6
+ metadata.gz: be4796ff68ae8e99abdd8361c68c79624e5b6b10f143a3314edc834e28cd79aa8c6f2c5707d49564d42c8d80fd0521a3db38023cd688a70e733a8f737991ff22
7
+ data.tar.gz: 56a1cf4956fc1eb5802bd74841dfcef1114eaf3514f1a0c3c3c9585aa7643e0c36f2ec287c8933c879ce4a5ecd2e1232ce2a4ea0e30f7cef7fc1a5cc90db69e8
@@ -0,0 +1,31 @@
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.6'
14
+ - '2.7'
15
+ - '3.0'
16
+ gemfile:
17
+ - Gemfile
18
+ - gemfiles/activesupport-5.2.gemfile
19
+ - gemfiles/activesupport-6.0.gemfile
20
+ - gemfiles/activesupport-6.1.gemfile
21
+ name: Ruby ${{ matrix.ruby }} ${{ matrix.gemfile }}
22
+ steps:
23
+ - uses: actions/checkout@v1
24
+ - name: Set up Ruby ${{ matrix.ruby }}
25
+ uses: ruby/setup-ruby@v1
26
+ with:
27
+ ruby-version: ${{ matrix.ruby }}
28
+ bundler-cache: true
29
+ - name: Run tests
30
+ run: |
31
+ bundle exec rake
data/CHANGELOG.md CHANGED
@@ -1,3 +1,31 @@
1
+ Unreleased
2
+ -----
3
+
4
+ 2.8.0
5
+ -----
6
+
7
+ * Drop support for Ruby 2.5
8
+ * Use Ruby 3.0.2 for development
9
+
10
+ 2.7.1
11
+ -----
12
+
13
+ * Fix Ruby 3.0 compatibility
14
+
15
+ 2.7.0
16
+ -----
17
+
18
+ * Raises an exception on cyclic conversions. (@arturopie)
19
+ * Deduplicate strings loaded from the cache.
20
+ * Deduplicate parsed units.
21
+
22
+ 2.6.0
23
+ -----
24
+
25
+ * Add `Measured::MissingConversionPath` and `Measured::UnitAlreadyAdded` as subclasses of `Measured::UnitError` to handle specific error cases. (@arturopie)
26
+ * Support only ActiveSupport 5.2 and above.
27
+
28
+
1
29
  2.5.2
2
30
  -----
3
31
 
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Measured [![Build Status](https://travis-ci.org/Shopify/measured.svg)](https://travis-ci.org/Shopify/measured) [![Gem Version](https://badge.fury.io/rb/measured.svg)](http://badge.fury.io/rb/measured)
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 and maintained.
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/dev.yml CHANGED
@@ -2,7 +2,7 @@ name: measured
2
2
 
3
3
  up:
4
4
  - ruby:
5
- version: 2.6.3
5
+ version: 3.0.2
6
6
  - bundler
7
7
 
8
8
  commands:
@@ -2,4 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec path: '..'
4
4
 
5
- gem 'activesupport', '~> 5.0'
5
+ gem 'activesupport', '~> 5.2'
@@ -2,4 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec path: '..'
4
4
 
5
- gem 'activesupport', '~> 6.0.0.rc1'
5
+ gem 'activesupport', '~> 6.0'
@@ -2,4 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec path: '..'
4
4
 
5
- gem 'activesupport', '~> 5.1'
5
+ gem 'activesupport', '~> 6.1'
data/lib/measured/base.rb CHANGED
@@ -6,8 +6,6 @@ require "bigdecimal"
6
6
  require "json"
7
7
 
8
8
  module Measured
9
- class UnitError < StandardError ; end
10
-
11
9
  class << self
12
10
  def build(&block)
13
11
  builder = UnitSystemBuilder.new
@@ -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"
@@ -14,7 +14,7 @@ module Measured::Cache
14
14
 
15
15
  def read
16
16
  return unless exist?
17
- decode(JSON.load(File.read(@path)))
17
+ decode(JSON.load(File.read(@path), nil, freeze: true))
18
18
  end
19
19
 
20
20
  def write(table)
@@ -37,11 +37,17 @@ module Measured::Cache
37
37
  end
38
38
 
39
39
  def decode(table)
40
- table.each_with_object(table.dup) do |(k1, v1), accu|
41
- v1.each do |k2, v2|
42
- if v2.is_a?(Hash)
43
- accu[k1][k2] = Rational(v2["numerator"], v2["denominator"])
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
44
48
  end
49
+ else
50
+ value1
45
51
  end
46
52
  end
47
53
  end
@@ -24,6 +24,8 @@ class Measured::ConversionTableBuilder
24
24
  private
25
25
 
26
26
  def generate_table
27
+ validate_no_cycles
28
+
27
29
  units.map(&:name).each_with_object({}) do |to_unit, table|
28
30
  to_table = {to_unit => Rational(1, 1)}
29
31
 
@@ -37,10 +39,27 @@ class Measured::ConversionTableBuilder
37
39
  end
38
40
  end
39
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
+
40
59
  def find_conversion(to:, from:)
41
60
  conversion = find_direct_conversion_cached(to: to, from: from) || find_tree_traversal_conversion(to: to, from: from)
42
61
 
43
- raise Measured::UnitError, "Cannot find conversion path from #{ from } to #{ to }." unless conversion
62
+ raise Measured::MissingConversionPath.new(from, to) unless conversion
44
63
 
45
64
  conversion
46
65
  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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ module Measured
3
+ class MissingConversionPath < UnitError
4
+ attr_reader :from, :to
5
+
6
+ def initialize(from, to)
7
+ super("Cannot find conversion path from #{from} to #{to}.")
8
+ @from = from
9
+ @to = to
10
+ end
11
+ end
12
+ end
@@ -33,6 +33,6 @@ module Measured::Parser
33
33
 
34
34
  raise Measured::UnitError, "Cannot parse measurement from '#{string}'" unless result
35
35
 
36
- [result.captures[0].to_r, result.captures[1]]
36
+ [result.captures[0].to_r, -result.captures[1]]
37
37
  end
38
38
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ module Measured
3
+ class UnitAlreadyAdded < UnitError
4
+ attr_reader :unit_name
5
+
6
+ def initialize(unit_name)
7
+ super("Unit #{unit_name} has already been added.")
8
+ @unit_name = unit_name
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module Measured
3
+ class UnitError < StandardError ; end
4
+ end
@@ -67,7 +67,7 @@ class Measured::UnitSystemBuilder
67
67
  def check_for_duplicate_unit_names!(unit)
68
68
  names = @units.flat_map(&:names)
69
69
  if names.any? { |name| unit.names.include?(name) }
70
- raise Measured::UnitError, "Unit #{unit.name} has already been added."
70
+ raise Measured::UnitAlreadyAdded.new(unit.name)
71
71
  end
72
72
  end
73
73
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Measured
3
- VERSION = "2.5.2"
3
+ VERSION = "2.8.0"
4
4
  end
data/measured.gemspec CHANGED
@@ -13,12 +13,20 @@ 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.0"
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"
@@ -117,6 +117,16 @@ class Measured::ConversionTableBuilderTest < ActiveSupport::TestCase
117
117
  end
118
118
  end
119
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
+
120
130
  test "#cached? returns true if there's a cache" do
121
131
  builder = Measured::ConversionTableBuilder.new([Measured::Unit.new(:test)], cache: { class: AlwaysTrueCache })
122
132
  assert_predicate builder, :cached?
@@ -12,15 +12,17 @@ class Measured::UnitSystemBuilderTest < ActiveSupport::TestCase
12
12
  end
13
13
 
14
14
  test "#unit cannot add duplicate unit names" do
15
- assert_raises Measured::UnitError do
15
+ error = assert_raises Measured::UnitAlreadyAdded do
16
16
  Measured.build do
17
17
  unit :m
18
18
  unit :in, aliases: [:inch], value: "0.0254 m"
19
19
  unit :in, aliases: [:thing], value: "123 m"
20
20
  end
21
21
  end
22
+ assert_equal("in", error.unit_name)
23
+ assert_equal("Unit in has already been added.", error.message)
22
24
 
23
- assert_raises Measured::UnitError do
25
+ assert_raises Measured::UnitAlreadyAdded do
24
26
  Measured.build do
25
27
  unit :m
26
28
  unit :in, aliases: [:inch], value: "0.0254 m"
@@ -29,6 +31,19 @@ class Measured::UnitSystemBuilderTest < ActiveSupport::TestCase
29
31
  end
30
32
  end
31
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
+
32
47
  test "#unit is case sensitive" do
33
48
  measurable = Measured.build do
34
49
  unit :normal
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.5.2
4
+ version: 2.8.0
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: 2020-01-08 00:00:00.000000000 Z
13
+ date: 2021-11-08 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.0'
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.0'
28
+ version: '5.2'
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: rake
31
31
  requirement: !ruby/object:Gem::Requirement
@@ -104,8 +104,8 @@ executables: []
104
104
  extensions: []
105
105
  extra_rdoc_files: []
106
106
  files:
107
+ - ".github/workflows/ci.yml"
107
108
  - ".gitignore"
108
- - ".travis.yml"
109
109
  - CHANGELOG.md
110
110
  - Gemfile
111
111
  - LICENSE
@@ -116,9 +116,9 @@ files:
116
116
  - cache/volume.json
117
117
  - cache/weight.json
118
118
  - dev.yml
119
- - gemfiles/activesupport-5.0.gemfile
120
- - gemfiles/activesupport-5.1.gemfile
119
+ - gemfiles/activesupport-5.2.gemfile
121
120
  - gemfiles/activesupport-6.0.gemfile
121
+ - gemfiles/activesupport-6.1.gemfile
122
122
  - lib/measured.rb
123
123
  - lib/measured/arithmetic.rb
124
124
  - lib/measured/base.rb
@@ -126,9 +126,13 @@ files:
126
126
  - lib/measured/cache/json_writer.rb
127
127
  - lib/measured/cache/null.rb
128
128
  - lib/measured/conversion_table_builder.rb
129
+ - lib/measured/cycle_detected.rb
129
130
  - lib/measured/measurable.rb
131
+ - lib/measured/missing_conversion_path.rb
130
132
  - lib/measured/parser.rb
131
133
  - lib/measured/unit.rb
134
+ - lib/measured/unit_already_added.rb
135
+ - lib/measured/unit_error.rb
132
136
  - lib/measured/unit_system.rb
133
137
  - lib/measured/unit_system_builder.rb
134
138
  - lib/measured/units/length.rb
@@ -159,7 +163,8 @@ files:
159
163
  homepage: https://github.com/Shopify/measured
160
164
  licenses:
161
165
  - MIT
162
- metadata: {}
166
+ metadata:
167
+ allowed_push_host: https://rubygems.org
163
168
  post_install_message:
164
169
  rdoc_options: []
165
170
  require_paths:
@@ -175,7 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
175
180
  - !ruby/object:Gem::Version
176
181
  version: '0'
177
182
  requirements: []
178
- rubygems_version: 3.0.3
183
+ rubygems_version: 3.2.20
179
184
  signing_key:
180
185
  specification_version: 4
181
186
  summary: Encapsulate measurements with their units in Ruby
data/.travis.yml DELETED
@@ -1,17 +0,0 @@
1
- language: ruby
2
- sudo: false
3
- cache: bundler
4
- rvm:
5
- - 2.4.9
6
- - 2.5.7
7
- - 2.6.5
8
- - 2.7.0
9
- gemfile:
10
- - Gemfile
11
- - gemfiles/activesupport-5.0.gemfile
12
- - gemfiles/activesupport-5.1.gemfile
13
- - gemfiles/activesupport-6.0.gemfile
14
- matrix:
15
- exclude:
16
- - gemfile: gemfiles/activesupport-6.0.gemfile
17
- rvm: 2.4.9