measured 2.6.0 → 2.8.1
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.
Potentially problematic release.
This version of measured might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +3 -3
- data/CHANGELOG.md +30 -0
- data/README.md +9 -0
- data/dev.yml +1 -1
- data/lib/measured/arithmetic.rb +9 -0
- data/lib/measured/base.rb +1 -0
- data/lib/measured/cache/json.rb +11 -5
- data/lib/measured/conversion_table_builder.rb +19 -0
- data/lib/measured/cycle_detected.rb +11 -0
- data/lib/measured/measurable.rb +2 -2
- data/lib/measured/parser.rb +1 -1
- data/lib/measured/unit.rb +2 -2
- data/lib/measured/version.rb +1 -1
- data/test/arithmetic_test.rb +35 -0
- data/test/conversion_table_builder_test.rb +10 -0
- data/test/measurable_test.rb +5 -6
- data/test/unit_test.rb +4 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5ec3f063994e56291a70f5b2726890e03b43ef00e512894a901bc8ea924ae895
|
4
|
+
data.tar.gz: 442e6cca31eb154cbe318287a64f9379ab987b9ddcd59629a2780960b52ea00d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 141a1214a375f0af9ae4d92c1e9c51d77e2a98d2a174321ee9e85eb4387fa72c3c98c49dcc5448d6617f7cc7d6bdf67e9ca4f12772d7446cb00e827329108d4f
|
7
|
+
data.tar.gz: f5a34f7aa0baf05177f216089dd9b4443ca2b04eecda5c0b72bf5899973a9a5a2a462b2e4f11ea7087dffcc8927e35835e9555da211bc628d50f76fadd110c35
|
data/.github/workflows/ci.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,33 @@
|
|
1
|
+
Unreleased
|
2
|
+
-----
|
3
|
+
|
4
|
+
|
5
|
+
|
6
|
+
2.8.1
|
7
|
+
-----
|
8
|
+
|
9
|
+
* Allow format without the conversion string. (@saraid)
|
10
|
+
* Add special case for zero coercion to allow direct use of `sum` method. (@jmortlock)
|
11
|
+
* Add useful numeric methods `finite?`, `infinite?`, `zero?`, `nonzero?`, `positive?` and `negative?`. (@jmortlock)
|
12
|
+
|
13
|
+
2.8.0
|
14
|
+
-----
|
15
|
+
|
16
|
+
* Drop support for Ruby 2.5
|
17
|
+
* Use Ruby 3.0.2 for development
|
18
|
+
|
19
|
+
2.7.1
|
20
|
+
-----
|
21
|
+
|
22
|
+
* Fix Ruby 3.0 compatibility
|
23
|
+
|
24
|
+
2.7.0
|
25
|
+
-----
|
26
|
+
|
27
|
+
* Raises an exception on cyclic conversions. (@arturopie)
|
28
|
+
* Deduplicate strings loaded from the cache.
|
29
|
+
* Deduplicate parsed units.
|
30
|
+
|
1
31
|
2.6.0
|
2
32
|
-----
|
3
33
|
|
data/README.md
CHANGED
@@ -149,6 +149,15 @@ Measured::Weight.new("3.14", "grams").format("%.1<value>f %<unit>s")
|
|
149
149
|
|
150
150
|
If no string is passed to the `format` method it defaults to `"%.2<value>f %<unit>s"`.
|
151
151
|
|
152
|
+
If the unit isn't the standard SI unit, it will include a conversion string.
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
Measured::Weight.new("3.14", "kg").format
|
156
|
+
> "3.14 kg (1000/1 g)"
|
157
|
+
Measured::Weight.new("3.14", "kg").format(with_conversion_string: false)
|
158
|
+
> "3.14 kg"
|
159
|
+
```
|
160
|
+
|
152
161
|
## Units and conversions
|
153
162
|
|
154
163
|
### SI units support
|
data/dev.yml
CHANGED
data/lib/measured/arithmetic.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module Measured::Arithmetic
|
3
|
+
extend Forwardable
|
4
|
+
def_delegators :@value, :zero?, :positive?, :negative?, :finite?, :infinite?
|
5
|
+
|
3
6
|
def +(other)
|
4
7
|
arithmetic_operation(other, :+)
|
5
8
|
end
|
@@ -19,6 +22,8 @@ module Measured::Arithmetic
|
|
19
22
|
def coerce(other)
|
20
23
|
if other.is_a?(self.class)
|
21
24
|
[other, self]
|
25
|
+
elsif other.is_a?(Numeric) && other.zero?
|
26
|
+
[self.class.new(other, self.unit), self]
|
22
27
|
else
|
23
28
|
raise TypeError, "Cannot coerce #{other.class} to #{self.class}"
|
24
29
|
end
|
@@ -28,6 +33,10 @@ module Measured::Arithmetic
|
|
28
33
|
raise TypeError, "#{self.class} cannot be converted to an integer"
|
29
34
|
end
|
30
35
|
|
36
|
+
def nonzero?
|
37
|
+
value.nonzero? ? self : false
|
38
|
+
end
|
39
|
+
|
31
40
|
private
|
32
41
|
|
33
42
|
def arithmetic_operation(other, operator)
|
data/lib/measured/base.rb
CHANGED
data/lib/measured/cache/json.rb
CHANGED
@@ -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.
|
41
|
-
|
42
|
-
|
43
|
-
|
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,6 +39,23 @@ 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
|
|
@@ -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
@@ -43,10 +43,10 @@ class Measured::Measurable < Numeric
|
|
43
43
|
self.class.new(new_value, new_unit)
|
44
44
|
end
|
45
45
|
|
46
|
-
def format(format_string=nil)
|
46
|
+
def format(format_string=nil, with_conversion_string: true)
|
47
47
|
kwargs = {
|
48
48
|
value: self.value,
|
49
|
-
unit: self.unit,
|
49
|
+
unit: self.unit.to_s(with_conversion_string: with_conversion_string),
|
50
50
|
}
|
51
51
|
(format_string || DEFAULT_FORMAT_STRING) % kwargs
|
52
52
|
end
|
data/lib/measured/parser.rb
CHANGED
data/lib/measured/unit.rb
CHANGED
data/lib/measured/version.rb
CHANGED
data/test/arithmetic_test.rb
CHANGED
@@ -8,6 +8,41 @@ class Measured::ArithmeticTest < ActiveSupport::TestCase
|
|
8
8
|
@four = Magic.new(4, :magic_missile)
|
9
9
|
end
|
10
10
|
|
11
|
+
test "should be able to sum same units" do
|
12
|
+
assert_equal Magic.new(9, :magic_missile), [@two, @three, @four].sum
|
13
|
+
end
|
14
|
+
|
15
|
+
test 'can check for finite?' do
|
16
|
+
assert Magic.new(0, :magic_missile).finite?
|
17
|
+
refute Magic.new(Float::INFINITY, :magic_missile).finite?
|
18
|
+
end
|
19
|
+
|
20
|
+
test 'can check for infinite?' do
|
21
|
+
assert Magic.new(Float::INFINITY, :magic_missile).infinite?
|
22
|
+
refute Magic.new(0, :magic_missile).infinite?
|
23
|
+
end
|
24
|
+
|
25
|
+
test 'can check for zero?' do
|
26
|
+
assert Magic.new(0, :magic_missile).zero?
|
27
|
+
refute Magic.new(1, :magic_missile).zero?
|
28
|
+
end
|
29
|
+
|
30
|
+
test 'can check for nonzero?' do
|
31
|
+
assert_equal Magic.new(10, :magic_missile), Magic.new(10, :magic_missile).nonzero?
|
32
|
+
assert Magic.new(10, :magic_missile).nonzero?
|
33
|
+
refute Magic.new(0, :magic_missile).nonzero?
|
34
|
+
end
|
35
|
+
|
36
|
+
test 'can check for positive?' do
|
37
|
+
assert Magic.new(1, :magic_missile).positive?
|
38
|
+
refute Magic.new(-1, :magic_missile).positive?
|
39
|
+
end
|
40
|
+
|
41
|
+
test 'can check for negative?' do
|
42
|
+
assert Magic.new(-1, :magic_missile).negative?
|
43
|
+
refute Magic.new(1, :magic_missile).negative?
|
44
|
+
end
|
45
|
+
|
11
46
|
test "#+ should add together same units" do
|
12
47
|
assert_equal Magic.new(5, :magic_missile), @two + @three
|
13
48
|
assert_equal Magic.new(5, :magic_missile), @three + @two
|
@@ -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?
|
data/test/measurable_test.rb
CHANGED
@@ -232,6 +232,11 @@ class Measured::MeasurableTest < ActiveSupport::TestCase
|
|
232
232
|
assert_equal "9.34 magic_missile", Magic.new(9.342, :magic_missile).format
|
233
233
|
end
|
234
234
|
|
235
|
+
test "#format can drop the conversion amount" do
|
236
|
+
assert_equal "1.00 fireball (2/3 magic_missile)", Magic.new(1, :fireball).format
|
237
|
+
assert_equal "1.00 fireball", Magic.new(1, :fireball).format(with_conversion_string: false)
|
238
|
+
end
|
239
|
+
|
235
240
|
test "#humanize outputs the number and the unit properly pluralized" do
|
236
241
|
assert_equal "1 fireball", Magic.new("1", :fire).humanize
|
237
242
|
assert_equal "10 fireballs", Magic.new(10, :fire).humanize
|
@@ -246,12 +251,6 @@ class Measured::MeasurableTest < ActiveSupport::TestCase
|
|
246
251
|
assert_equal "#<Magic: 1.234 #<Measured::Unit: magic_missile (magic_missiles, magic missile)>>", Magic.new(1.234, :magic_missile).inspect
|
247
252
|
end
|
248
253
|
|
249
|
-
test "#zero? always returns false" do
|
250
|
-
refute_predicate Magic.new(0, :fire), :zero?
|
251
|
-
refute_predicate Magic.new(0.0, :fire), :zero?
|
252
|
-
refute_predicate Magic.new("0.0", :fire), :zero?
|
253
|
-
end
|
254
|
-
|
255
254
|
test "#<=> compares regardless of the unit" do
|
256
255
|
assert_equal (-1), @magic <=> Magic.new(20, :fire)
|
257
256
|
assert_equal 1, @magic <=> Magic.new(9, :magic_missile)
|
data/test/unit_test.rb
CHANGED
@@ -43,6 +43,10 @@ class Measured::UnitTest < ActiveSupport::TestCase
|
|
43
43
|
assert_equal "pie (1/2 sweet)", Measured::Unit.new(:pie, aliases: ["cake"], value: "0.5 sweet").to_s
|
44
44
|
end
|
45
45
|
|
46
|
+
test "#to_s can drop the conversion amount" do
|
47
|
+
assert_equal "pie", Measured::Unit.new(:pie).to_s(with_conversion_string: false)
|
48
|
+
end
|
49
|
+
|
46
50
|
test "#inspect returns an expected string" do
|
47
51
|
assert_equal "#<Measured::Unit: pie>", Measured::Unit.new(:pie).inspect
|
48
52
|
assert_equal "#<Measured::Unit: pie (cake, semi-sweet)>", Measured::Unit.new(:pie, aliases: ["cake", "semi-sweet"]).inspect
|
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.8.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-12-07 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activesupport
|
@@ -126,6 +126,7 @@ 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
|
130
131
|
- lib/measured/missing_conversion_path.rb
|
131
132
|
- lib/measured/parser.rb
|
@@ -179,7 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
179
180
|
- !ruby/object:Gem::Version
|
180
181
|
version: '0'
|
181
182
|
requirements: []
|
182
|
-
rubygems_version: 3.
|
183
|
+
rubygems_version: 3.2.20
|
183
184
|
signing_key:
|
184
185
|
specification_version: 4
|
185
186
|
summary: Encapsulate measurements with their units in Ruby
|