unitwise 0.1.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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +110 -0
  8. data/Rakefile +21 -0
  9. data/data/base_unit.yaml +43 -0
  10. data/data/derived_unit.yaml +3394 -0
  11. data/data/prefix.yaml +121 -0
  12. data/lib/unitwise.rb +25 -0
  13. data/lib/unitwise/atom.rb +84 -0
  14. data/lib/unitwise/base.rb +45 -0
  15. data/lib/unitwise/composable.rb +25 -0
  16. data/lib/unitwise/errors.rb +7 -0
  17. data/lib/unitwise/expression.rb +25 -0
  18. data/lib/unitwise/expression/composer.rb +41 -0
  19. data/lib/unitwise/expression/decomposer.rb +42 -0
  20. data/lib/unitwise/expression/matcher.rb +41 -0
  21. data/lib/unitwise/expression/parser.rb +53 -0
  22. data/lib/unitwise/expression/transformer.rb +22 -0
  23. data/lib/unitwise/ext.rb +2 -0
  24. data/lib/unitwise/ext/numeric.rb +13 -0
  25. data/lib/unitwise/ext/string.rb +5 -0
  26. data/lib/unitwise/function.rb +51 -0
  27. data/lib/unitwise/functional.rb +21 -0
  28. data/lib/unitwise/measurement.rb +89 -0
  29. data/lib/unitwise/prefix.rb +17 -0
  30. data/lib/unitwise/scale.rb +61 -0
  31. data/lib/unitwise/standard.rb +26 -0
  32. data/lib/unitwise/standard/base.rb +73 -0
  33. data/lib/unitwise/standard/base_unit.rb +21 -0
  34. data/lib/unitwise/standard/derived_unit.rb +49 -0
  35. data/lib/unitwise/standard/extras.rb +17 -0
  36. data/lib/unitwise/standard/function.rb +35 -0
  37. data/lib/unitwise/standard/prefix.rb +17 -0
  38. data/lib/unitwise/standard/scale.rb +25 -0
  39. data/lib/unitwise/term.rb +106 -0
  40. data/lib/unitwise/unit.rb +89 -0
  41. data/lib/unitwise/version.rb +3 -0
  42. data/test/test_helper.rb +7 -0
  43. data/test/unitwise/atom_test.rb +122 -0
  44. data/test/unitwise/base_test.rb +6 -0
  45. data/test/unitwise/expression/decomposer_test.rb +36 -0
  46. data/test/unitwise/expression/matcher_test.rb +42 -0
  47. data/test/unitwise/expression/parser_test.rb +91 -0
  48. data/test/unitwise/ext/numeric_test.rb +46 -0
  49. data/test/unitwise/ext/string_test.rb +13 -0
  50. data/test/unitwise/function_test.rb +42 -0
  51. data/test/unitwise/measurement_test.rb +168 -0
  52. data/test/unitwise/prefix_test.rb +25 -0
  53. data/test/unitwise/term_test.rb +44 -0
  54. data/test/unitwise/unit_test.rb +57 -0
  55. data/test/unitwise_test.rb +7 -0
  56. data/unitwise.gemspec +28 -0
  57. metadata +213 -0
@@ -0,0 +1,21 @@
1
+ module Unitwise::Standard
2
+ class BaseUnit < Base
3
+
4
+ def self.remote_key
5
+ "base_unit"
6
+ end
7
+
8
+ def property
9
+ attributes["property"].to_s
10
+ end
11
+
12
+ def dim
13
+ attributes["@dim"]
14
+ end
15
+
16
+ def to_hash
17
+ super.merge property: property, dim: dim
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,49 @@
1
+ module Unitwise::Standard
2
+ class DerivedUnit < Base
3
+
4
+ def self.remote_key
5
+ "unit"
6
+ end
7
+
8
+ def self.local_key
9
+ "derived_unit"
10
+ end
11
+
12
+ def property
13
+ attributes["property"].to_s
14
+ end
15
+
16
+ def scale
17
+ Scale.new(attributes["value"]) unless special?
18
+ end
19
+
20
+ def function
21
+ Function.new(attributes["value"]) if special?
22
+ end
23
+
24
+ def classification
25
+ attributes["@class"]
26
+ end
27
+
28
+ def metric?
29
+ attributes["@isMetric"] == 'yes'
30
+ end
31
+
32
+ def special?
33
+ attributes["@isSpecial"] == 'yes'
34
+ end
35
+
36
+ def arbitrary?
37
+ attributes["@isArbitrary"] == 'yes'
38
+ end
39
+
40
+ def to_hash
41
+ hash = super()
42
+ hash[:scale] = (special? ? function.to_hash : scale.to_hash)
43
+ hash.merge({classification: classification,
44
+ property: property, metric: metric?,
45
+ special: special?, arbitrary: arbitrary?})
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,17 @@
1
+ module Unitwise::Standard
2
+ module Extras
3
+ def hash_to_markup(hash)
4
+ hash.map do |k,v|
5
+ if v.respond_to?(:to_xml)
6
+ "<#{k}>#{v.to_xml}</#{k}>"
7
+ elsif v.respond_to?(:map)
8
+ v.map do |i|
9
+ "<#{k}>#{i}</#{k}>"
10
+ end.join('')
11
+ else
12
+ "<#{k}>#{v}</#{k}>"
13
+ end
14
+ end.join('')
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,35 @@
1
+ module Unitwise::Standard
2
+ class Function
3
+
4
+ attr_accessor :attributes
5
+
6
+ def initialize(attributes)
7
+ @attributes = attributes
8
+ end
9
+
10
+ def name
11
+ attributes["function"]["@name"]
12
+ end
13
+
14
+ def value
15
+ attributes["function"]["@value"].to_f
16
+ end
17
+
18
+ def unit
19
+ attributes["function"]["@Unit"]
20
+ end
21
+
22
+ def primary
23
+ attributes["@Unit"].gsub(/\(.*\)/, '')
24
+ end
25
+
26
+ def secondary
27
+ attributes["@UNIT"]
28
+ end
29
+
30
+ def to_hash
31
+ {function_code: primary, value: value, unit_code: unit}
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,17 @@
1
+ module Unitwise::Standard
2
+ class Prefix < Base
3
+
4
+ def self.remote_key
5
+ "prefix"
6
+ end
7
+
8
+ def scale
9
+ attributes["value"].attributes["value"]
10
+ end
11
+
12
+ def to_hash
13
+ super().merge(scalar: scale)
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ module Unitwise::Standard
2
+ class Scale
3
+ attr_accessor :nori
4
+
5
+ def initialize(nori)
6
+ @nori = nori
7
+ end
8
+
9
+ def value
10
+ nori.attributes["value"].to_f
11
+ end
12
+
13
+ def primary_unit_code
14
+ nori.attributes["Unit"]
15
+ end
16
+
17
+ def secondary_unit_code
18
+ nori.attributes["UNIT"]
19
+ end
20
+
21
+ def to_hash
22
+ {value: value, unit_code: primary_unit_code}
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,106 @@
1
+ require 'signed_multiset'
2
+ module Unitwise
3
+ class Term
4
+ attr_writer :atom_code, :prefix_code, :atom, :prefix
5
+ attr_writer :factor, :exponent
6
+ attr_accessor :annotation
7
+
8
+ include Unitwise::Composable
9
+
10
+ def initialize(attributes)
11
+ attributes.each do |k,v|
12
+ public_send :"#{k}=", v
13
+ end
14
+ end
15
+
16
+ def prefix_code
17
+ @prefix_code ||= (@prefix.primary_code if @prefix)
18
+ end
19
+
20
+ def prefix
21
+ @prefix ||= (Prefix.find(@prefix_code) if @prefix_code)
22
+ end
23
+
24
+ def atom_code
25
+ @atom_code ||= (@atom.primary_code if @atom)
26
+ end
27
+
28
+ def atom
29
+ @atom ||= (Atom.find(@atom_code) if @atom_code)
30
+ end
31
+
32
+ def special?
33
+ atom.special rescue false
34
+ end
35
+
36
+ def depth
37
+ atom ? atom.depth + 1 : 0
38
+ end
39
+
40
+ def terminal?
41
+ depth <= 3
42
+ end
43
+
44
+ def factor
45
+ @factor ||= 1
46
+ end
47
+
48
+ def exponent
49
+ @exponent ||= 1
50
+ end
51
+
52
+ def scalar
53
+ (factor * (prefix ? prefix.scalar : 1) * (atom ? atom.scalar : 1)) ** exponent
54
+ end
55
+
56
+ def functional(value, direction=1)
57
+ (factor * (prefix ? prefix.scalar : 1)) * (atom ? atom.functional(value, direction) : 1) ** exponent
58
+ end
59
+
60
+ def root_terms
61
+ if terminal?
62
+ [self]
63
+ else
64
+ atom.scale.root_terms.map do |t|
65
+ self.class.new(atom: t.atom, exponent: t.exponent * exponent)
66
+ end
67
+ end
68
+ end
69
+
70
+ def to_hash
71
+ [:prefix, :atom, :exponent, :factor, :annotation].inject({}) do |h, a|
72
+ h[a] = send a; h
73
+ end
74
+ end
75
+
76
+ def *(other)
77
+ if other.respond_to?(:terms)
78
+ Unit.new(other.terms << self)
79
+ else
80
+ Unit.new([self, other])
81
+ end
82
+ end
83
+
84
+ def /(other)
85
+ if other.respond_to?(:terms)
86
+ Unit.new(other.terms.map{|t| t ** -1} << self)
87
+ else
88
+ Unit.new([self, other ** -1])
89
+ end
90
+ end
91
+
92
+ def **(integer)
93
+ self.class.new(to_hash.merge(exponent: exponent * integer))
94
+ end
95
+
96
+ def to_s
97
+ [(factor if factor != 1), prefix_code,
98
+ atom_code, (exponent if exponent != 1)].compact.join('')
99
+ end
100
+
101
+ def inspect
102
+ "<#{self.class} #{to_s}>"
103
+ end
104
+
105
+ end
106
+ end
@@ -0,0 +1,89 @@
1
+ module Unitwise
2
+ class Unit
3
+ include Unitwise::Composable
4
+ attr_writer :terms, :expression
5
+
6
+ def initialize(input)
7
+ if input.respond_to?(:expression)
8
+ @expression = input.expression
9
+ elsif input.respond_to?(:each)
10
+ @terms = input
11
+ else
12
+ @expression = input.to_s
13
+ end
14
+ end
15
+
16
+ def expression
17
+ @expression ||= (Expression.compose(@terms) if @terms)
18
+ end
19
+
20
+ def terms
21
+ @terms ||= (Expression.decompose(@expression) if @expression)
22
+ end
23
+
24
+ def atoms
25
+ terms.map(&:atom)
26
+ end
27
+
28
+ def special?
29
+ terms.count == 1 && terms.all?(&:special?)
30
+ end
31
+
32
+ def functional(value, direction=1)
33
+ terms.first.functional(value, direction)
34
+ end
35
+
36
+ def dup
37
+ self.class.new(expression)
38
+ end
39
+
40
+ def depth
41
+ terms.map(&:depth).max + 1
42
+ end
43
+
44
+ def terminal?
45
+ depth <= 3
46
+ end
47
+
48
+ def root_terms
49
+ terms.flat_map(&:root_terms)
50
+ end
51
+
52
+ def scalar
53
+ if terms.empty?
54
+ 1
55
+ else
56
+ terms.map(&:scalar).inject(&:*)
57
+ end
58
+ end
59
+
60
+ def *(other)
61
+ if other.respond_to?(:terms)
62
+ self.class.new(terms + other.terms)
63
+ else
64
+ raise TypeError, "Can't multiply #{inspect} by #{other}."
65
+ end
66
+ end
67
+
68
+ def /(other)
69
+ if other.respond_to?(:terms)
70
+ self.class.new(terms + other.terms.map{ |t| t ** -1})
71
+ else
72
+ raise TypeError, "Can't divide #{inspect} by #{other}."
73
+ end
74
+ end
75
+
76
+ def **(number)
77
+ self.class.new(terms.map{ |t| t ** number })
78
+ end
79
+
80
+ def to_s
81
+ expression
82
+ end
83
+
84
+ def inspect
85
+ "<#{self.class} #{to_s}>"
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,3 @@
1
+ module Unitwise
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,7 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
4
+ require 'minitest/autorun'
5
+ require 'minitest/pride'
6
+
7
+ require 'unitwise'
@@ -0,0 +1,122 @@
1
+ require 'test_helper'
2
+
3
+ describe Unitwise::Atom do
4
+ subject { Unitwise::Atom }
5
+ describe "::data" do
6
+ it "must have data" do
7
+ subject.data.must_be_instance_of Array
8
+ subject.data.count.must_be :>, 0
9
+ end
10
+ end
11
+
12
+ describe "::all" do
13
+ it "must be an Array of instances" do
14
+ subject.all.must_be_instance_of Array
15
+ subject.all.first.must_be_instance_of Unitwise::Atom
16
+ end
17
+ end
18
+
19
+ describe "::find" do
20
+ it "must find atoms" do
21
+ subject.find("m").must_be_instance_of Unitwise::Atom
22
+ subject.find("V").must_be_instance_of Unitwise::Atom
23
+ end
24
+ end
25
+
26
+ let(:second) { Unitwise::Atom.find("s") }
27
+ let(:yard) { Unitwise::Atom.find("[yd_i]")}
28
+ let(:pi) { Unitwise::Atom.find("[pi]")}
29
+ let(:celsius) { Unitwise::Atom.find("Cel")}
30
+ let(:pfu) { Unitwise::Atom.find("[PFU]")}
31
+ describe "#scale" do
32
+ it "must be nil for base atoms" do
33
+ second.scale.must_equal nil
34
+ end
35
+ it "sould be a Scale object for derived atoms" do
36
+ yard.scale.must_be_instance_of Unitwise::Scale
37
+ end
38
+ it "must be a FunctionalScale object for special atoms" do
39
+ celsius.scale.must_be_instance_of Unitwise::Functional
40
+ end
41
+ end
42
+
43
+ describe "#base?" do
44
+ it "must be true for base atoms" do
45
+ second.base?.must_equal true
46
+ end
47
+ it "must be false for derived atoms" do
48
+ yard.base?.must_equal false
49
+ pi.base?.must_equal false
50
+ end
51
+ end
52
+
53
+ describe "#derived?" do
54
+ it "must be false for base atoms" do
55
+ second.derived?.must_equal false
56
+ end
57
+ it "must be true for derived atoms" do
58
+ yard.derived?.must_equal true
59
+ celsius.derived?.must_equal true
60
+ end
61
+ end
62
+
63
+ describe "#metric?" do
64
+ it "must be true for base atoms" do
65
+ second.metric?.must_equal true
66
+ end
67
+ it "must be false for english atoms" do
68
+ yard.metric?.must_equal false
69
+ end
70
+ end
71
+
72
+ describe "#special?" do
73
+ it "must be true for special atoms" do
74
+ celsius.special?.must_equal true
75
+ end
76
+ it "must be false for non-special atoms" do
77
+ second.special?.must_equal false
78
+ end
79
+ end
80
+
81
+ describe "#arbitrary?" do
82
+ it "must be true for arbitrary atoms" do
83
+ pfu.arbitrary?.must_equal true
84
+ end
85
+ it "must be false for non-arbitrary atoms" do
86
+ yard.arbitrary?.must_equal false
87
+ celsius.arbitrary?.must_equal false
88
+ end
89
+ end
90
+
91
+ describe "#terminal?" do
92
+ it "must be true for atoms without a valid measurement atom" do
93
+ second.terminal?.must_equal true
94
+ pi.terminal?.must_equal true
95
+ end
96
+ it "must be false for child atoms" do
97
+ yard.terminal?.must_equal false
98
+ end
99
+ end
100
+
101
+ describe "#scalar" do
102
+ it "must return scalar relative to terminal atom" do
103
+ second.scalar.must_equal 1
104
+ yard.scalar.must_equal 0.9144000000000001
105
+ pi.scalar.must_equal 3.141592653589793
106
+ end
107
+ end
108
+
109
+ describe "#key" do
110
+ it "must return the dim or the property" do
111
+ second.key.must_equal "T"
112
+ pi.key.must_equal "number"
113
+ celsius.key.must_equal "temperature"
114
+ end
115
+ end
116
+
117
+ describe "#measurement=" do
118
+ it "must create a new measurement object and set attributes" do
119
+ skip("need to figure out mocking and stubbing with minitest")
120
+ end
121
+ end
122
+ end