unit_soup 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e113ee9650c678b102c4788dd2aa7e8f51f9176d
4
+ data.tar.gz: c614b7fd95cc1b150e1fee659305b80eaf0eb32e
5
+ SHA512:
6
+ metadata.gz: a1d5e2ddf493db14064bdb275ce195924658e23b5f280253ea97075ba2a004da198cd4db3191ba4dd71095e6ac9916ca06833a5dd18b3650d6b7caaca4aa1cd7
7
+ data.tar.gz: ffcb15850fef0a8601bea78c77faa78ca5480cf1edaf5c8b22c71e530de0cf846e96c4d10cfa91fc10e3c8bb6088a069e38945c9323548d86fd8f3bc5ce4a56e
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in unit_soup.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 rutvij
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # UnitSoup
2
+
3
+ Unit Soup is a DRY approach to unit conversion.
4
+ Specify a set of conversion rules and you can start converting from one unit to another.
5
+
6
+ ## Analogy
7
+ - `Mix` is a ready-made set of units and conversion rules.
8
+ - `Soup` is made from (soup) mixes.
9
+ - `Measurement` is amount of items of a particular type (unit). (e.g. 3 cars, 4 miles)
10
+ - `Soup` can convert one `measurement` to another if possible.
11
+
12
+ ## Usage
13
+ * Add as many `mixes` to `soup`.
14
+ * Make the `soup`.
15
+ * Start converting `measurements` from one unit to another.
16
+
17
+ ## Examples
18
+ #### Metric Units
19
+
20
+ ```ruby
21
+ require "unit_soup/mix"
22
+ require "unit_soup/soup"
23
+
24
+ metric_units_mix = Mix.new("Metric Units") do |m|
25
+ m << "1 cm = 10 mm"
26
+ m << "1 dm = 10 cm"
27
+ m << "1 m = 100 cm"
28
+ m << "1 dcm = 10 m"
29
+ m << "1 hcm = 100 m"
30
+ m << "1 km = 1000 m"
31
+ end
32
+ soup = Soup.new("Metric Soup (mm to km)")
33
+ soup << metric_units_mix
34
+ soup.make
35
+
36
+ # convert 550 centimeters to meters
37
+ soup.convert(550, :cm, :m) #=> 5.5
38
+ # convert 50000 centimeters to kilometers
39
+ soup.convert(50000, :cm, :km) #=> 0.5
40
+ ```
41
+
42
+ #### Simple expenses calculator
43
+
44
+ ```ruby
45
+ require "unit_soup/mix"
46
+ require "unit_soup/soup"
47
+
48
+ expenses_per_time_period_mix = Mix.new("Expenses/TimePeriod") do |mix|
49
+ mix << "1 per_day = 7 per_week"
50
+ mix << "1 per_week = 4 per_month"
51
+ mix << "1 per_month = 12 per_year"
52
+ mix << "1 per_weekend = 1 per_week"
53
+ mix << "1 per_weekday = 5 per_week"
54
+ end
55
+
56
+ soup = Soup.new("Expense calculator")
57
+ soup << expenses_per_time_period_mix
58
+ soup.make
59
+
60
+
61
+ parking_per_week = Measurement.new(50, "per_week")
62
+ parking_per_month = soup.convert(parking_per_week, :per_month) #=> 200
63
+ parking_per_year = soup.convert(parking_per_week, :per_year) #=> 2400
64
+ ```
65
+
66
+
67
+ ## Details
68
+ ### Unit
69
+ - Has `name` and `symbol`
70
+ - `Unit.new("Kilometer", :km)` or `Unit.new(:km)`
71
+ - Usually inferred from the rules
72
+
73
+ ### Measurement
74
+ - Describes things like "3 cars", "4 miles"
75
+ - Has `amount` and `unit`
76
+ - m = `Measurement.new("3 car")`
77
+ - `m.unit` => `:car`
78
+ - `m.amount` => `3/1 (rational)`
79
+ - Other ways to initialize
80
+ - `Measurement.from("4 mile")`
81
+ - `Measurement.new(4, :mile)`
82
+ - `Measurement.new(4, Unit.new(:mile))`
83
+ - `Measurement.new(2.5, :mile)`
84
+ - `Measurement.new("2.5", :mile)`
85
+ - `Measurement.new(3/2r, :mile)`
86
+ - `Measurement.new("3/2", :mile)`
87
+ - `Measurement.from(another_measurement)`
88
+
89
+ ### Rule
90
+ - Describes conversion rule from one unit to another.
91
+ - Can be specified as equality of two measurements.
92
+ - `1 km = 1000 m`
93
+ - `this_measurement` = `1 km`, `that_measurement` = `1000 m`
94
+ - r = `Rule.new("1 km = 1000 m")` or `Rule.new("1000 m = 1 km")`
95
+ - `valid?` can be used to check if a string can be parsed into a `Rule`
96
+ - Can be initialized as
97
+ - `Rule.new "1 km = 1000 m"`
98
+ - `Rule.from "1 km = 1000 m"`
99
+ - `Rule.from another_rule`
100
+ - `Rule.new measurement_a, measurement_b`
101
+ - `Rule.new Measurement.new("1 km"), Measurement.new(1000, :m)`
102
+
103
+ ### Mix
104
+ - Represents a set of rules
105
+ - Can be used to group related units together. e.g. Metric Units, time units, etc.
106
+ - Has a name and a Set of rules
107
+ - Initializing: time_units = `Mix.new("Time Units")` or `Mix.define("Time Units")`
108
+ - Adding Rules:
109
+ - `time_units << "1 minute = 60 second"`
110
+ - `time_units << Rule.new("1 minute = 60 second")`
111
+ - `time_units.add "1 minute = 60 second"`
112
+ - `time_units.add Rule.new("1 minute = 60 second")`
113
+ - From Rule strings list: `distance << ["1cm = 10mm", "1km = 1.60934mile"]`
114
+ - From Rule list: `distance << [Rule.new("1cm = 10mm"), Rule.new("1km = 1.60934mile")]`
115
+ - From mixed list: `distance << ["1cm = 10mm", Rule.new("1km = 1.60934mile")]`
116
+ - From other mixes:
117
+ - `my_mix << time_units`
118
+ - `my_mix << distance_units`
119
+
120
+ - Initialize via block:
121
+ ```
122
+ time_units = Mix.define("Time units") do |m|
123
+ m << "1 minute = 60 second"
124
+ m << ["1 hour = 60 minute", "1 millisecond = 1000 second"]
125
+ end
126
+ ```
127
+
128
+ ### Soup
129
+ - Soup allows conversion from one unit to another
130
+ - Has an optional name
131
+ - Supports adding rules from strings, lists and mixes exactly like `Mix`
132
+ - `Soup.new("time_soup") << time_units_mix`
133
+ - Add as many related/unrelated rules and mixes as you like to the soup
134
+ - `make`: Make the soup (internally creates a graph of all rules for conversion lookups)
135
+ - `convert(value, from, to)` => rational representing converted value in target unit
136
+ - `convert(2, :minute, :second)` => 120/1
137
+ - `convert(2, Unit.new(:minute), :second)` => 120/1
138
+ - `convert(50, :foo, :bar)` => `nil` #if conversion was not possible
139
+ - Return value is a rational to keep precision
140
+ - `make` needs to be called before `convert`s can work
141
+
142
+ #### Conversion
143
+ - `soup.make` Creates a graph from the rules. So,
144
+
145
+ ```ruby
146
+ soup = Soup.new("my_distance_converter")
147
+ soup << Mix.new("my_distance_mix") do |m|
148
+ m << "1 centimeter = 10 millimeter"
149
+ m << "1 decimeter = 10 centimeter"
150
+ m << "1 centimeter = 100 meter"
151
+ m << "1 centimeter = 0.39 inch"
152
+ m << "1 foot = 12 inch"
153
+ m << "1 kilometer = 1000 meter"
154
+ m << "1 kilometer = 0.62 mile"
155
+ end
156
+ soup.make
157
+ ```
158
+
159
+ becomes
160
+ ```
161
+ +------------+
162
+ | millimeter |
163
+ +------------+
164
+ |
165
+ |
166
+ |
167
+ +-----------+ +------------+ +------+ +------+
168
+ | decimeter | --- | centimeter | --- | inch | --- | foot |
169
+ +-----------+ +------------+ +------+ +------+
170
+ |
171
+ |
172
+ |
173
+ +-----------+ +------------+
174
+ | kilometer | --- | meter |
175
+ +-----------+ +------------+
176
+ |
177
+ |
178
+ |
179
+ +-----------+
180
+ | mile |
181
+ +-----------+
182
+ ```
183
+
184
+ - Any rules added after `soup.make` will not be a part of the graph until another call to `soup.make` is made.
185
+
186
+ - A `lookup` is initialized internally with all direct conversions inferred from the set of rules. e.g. From "1 foot = 12 inch" we can infer `lookup[:foot][:inch]=12` and `lookup[:inch][:foot]=1/12`
187
+
188
+ - `soup.convert(2, :foot, :inch)` - `lookup` has information to convert from `foot` to `inch`, so `convert` returns `2 * (lookup[:foot][:inch]=12) = 24/1`
189
+
190
+ - `soup.convert(200, :foot, :meter)` - `lookup` does not have this information, so a `bfs` graph traversal starting from `foot` is performed. It gives us the following chain that can be traversed to get the right fraction for multiplication to perform conversion:
191
+
192
+ ```
193
+ +-------+ +------------+ +------+ +------+
194
+ | meter | <--- | centimeter | <--- | inch | <--- | foot |
195
+ +-------+ +------------+ +------+ +------+
196
+
197
+ 200 * lookup[:foot][:inch] * lookup[:inch][:centimeter] * lookup[:centimeter][meter]
198
+ ```
199
+
200
+ This conversion fraction is also saved in `lookup[:foot][:meter]` and `lookup[:meter][:foot]` so the next conversion from `foot` to `meter` does not perform a graph traversal.
201
+
202
+ ## Installation
203
+
204
+ Add this line to your application's Gemfile:
205
+
206
+ ```ruby
207
+ gem 'unit_soup'
208
+ ```
209
+
210
+ And then execute:
211
+
212
+ $ bundle
213
+
214
+ Or install it yourself as:
215
+
216
+ $ gem install unit_soup
217
+
218
+ ## Development
219
+
220
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
221
+
222
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
223
+
224
+ ## Contributing
225
+
226
+ Bug reports and pull requests are welcome.
227
+
228
+ ## License
229
+
230
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "unit_soup"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/lib/unit_soup.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'unit_soup/unit'
2
+ require 'unit_soup/measurement'
3
+ require 'unit_soup/mix'
4
+ require 'unit_soup/rule'
5
+ require 'unit_soup/soup'
6
+
7
+ module UnitSoup
8
+ end
@@ -0,0 +1,57 @@
1
+ require "unit_soup/unit"
2
+
3
+ include UnitSoup
4
+
5
+ module UnitSoup
6
+ class Measurement
7
+ # captures "(num)(symbol)" where num = decimal|integer|fraction
8
+ @@measurement_format = %r{^(\d+|\d+\.\d+|\d+/[1-9]+|\d+\.\d+/[1-9]+)([a-zA-Z_]+)$}
9
+
10
+ def self.valid?(str)
11
+ str && !str.to_s.gsub("\s", "").match(@@measurement_format).nil?
12
+ end
13
+
14
+ def self.from(*args)
15
+ self.new *args
16
+ end
17
+
18
+ attr_reader :amount, :unit
19
+
20
+ def initialize(*args)
21
+ case args.length
22
+ when 1
23
+ if args[0].is_a? Measurement
24
+ @amount = args[0].amount
25
+ @unit = args[0].unit
26
+ else
27
+ str = args[0]
28
+ raise ArgumentError.new("No argument provided") unless str
29
+ str = str.to_s
30
+ match_data = str.to_s.gsub("\s", "").match(@@measurement_format)
31
+ raise ArgumentError.new("Format: 12 inch") unless match_data
32
+ @amount = match_data[1].to_r
33
+ @unit = Unit.new(match_data[2])
34
+ end
35
+ else
36
+ @amount = args[0].is_a?(String) ? args[0].to_r : args[0].rationalize
37
+ @unit = Unit.new(args[1].to_sym)
38
+ end
39
+ end
40
+
41
+ def to_s
42
+ "#{amount} #{unit}"
43
+ end
44
+
45
+ def ==(o)
46
+ amount == o.amount && unit == o.unit
47
+ end
48
+
49
+ def eql?(o)
50
+ amount == o.amount && unit == o.unit
51
+ end
52
+
53
+ def hash
54
+ to_s.hash
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,51 @@
1
+ require "unit_soup/rule"
2
+ require "unit_soup/unit"
3
+ require "set"
4
+
5
+ module UnitSoup
6
+ class Mix
7
+ def self.define(name, &block)
8
+ mix = Mix.new name
9
+ if(block)
10
+ block.arity < 1 ? mix.instance_eval(&block) : block.call(mix)
11
+ end
12
+ mix
13
+ end
14
+
15
+ attr_reader :name, :rules
16
+
17
+ def initialize(name, &block)
18
+ @name = name
19
+ @rules = Set.new
20
+ if(block)
21
+ block.arity < 1 ? instance_eval(&block) : block.call(self)
22
+ end
23
+ end
24
+
25
+ def <<(arg)
26
+ new_rules = []
27
+ if arg.is_a? Mix
28
+ new_rules += arg.rules.to_a
29
+ elsif arg.is_a? Rule
30
+ new_rules << arg
31
+ elsif arg.is_a? Enumerable
32
+ new_rules += arg.map do |a|
33
+ if a.is_a? Rule
34
+ a
35
+ else
36
+ Rule.new(a)
37
+ end
38
+ end
39
+ else
40
+ new_rules << Rule.new(arg)
41
+ end
42
+ new_rules.each {|r| @rules.add r}
43
+ end
44
+
45
+ alias_method :add, :<<
46
+
47
+ def add_rules_from_file(file)
48
+ #TODO: parse rules from file
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,56 @@
1
+ require "unit_soup/unit"
2
+ require "unit_soup/measurement"
3
+
4
+ include UnitSoup
5
+
6
+ module UnitSoup
7
+ class Rule
8
+ def self.valid?(str)
9
+ return false unless str
10
+ strs = str.split '='
11
+ strs.length > 1 && Measurement.valid?(strs[0]) && Measurement.valid?(strs[1])
12
+ end
13
+
14
+ def self.from(r)
15
+ self.new r
16
+ end
17
+
18
+ attr_reader :this_measurement, :that_measurement
19
+
20
+ def initialize(*args)
21
+ case args.length
22
+ when 1
23
+ r = args[0]
24
+ raise ArgumentError.new("No argument provided") unless r
25
+ if r.is_a? Rule
26
+ @this_measurement = Measurement.new r.this_measurement
27
+ @that_measurement = Measurement.new r.that_measurement
28
+ else
29
+ strs = r.to_s.split '='
30
+ raise ArgumentError.new("Format: 12 inch = 1 foot") unless strs.length == 2
31
+ @this_measurement = Measurement.new strs[0]
32
+ @that_measurement = Measurement.new strs[1]
33
+ end
34
+ else
35
+ @this_measurement = Measurement.new(args[0])
36
+ @that_measurement = Measurement.new(args[1])
37
+ end
38
+ end
39
+
40
+ def to_s
41
+ "#{this_measurement} = #{that_measurement}"
42
+ end
43
+
44
+ def ==(o)
45
+ this_measurement == o.this_measurement && that_measurement == o.that_measurement
46
+ end
47
+
48
+ def eql?(o)
49
+ this_measurement == o.this_measurement && that_measurement == o.that_measurement
50
+ end
51
+
52
+ def hash
53
+ to_s.hash
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,88 @@
1
+ require "unit_soup/rule"
2
+ require "unit_soup/unit"
3
+ require "unit_soup/mix"
4
+
5
+ include UnitSoup
6
+
7
+ module UnitSoup
8
+ class Soup
9
+ attr_reader :name
10
+ def initialize(name="_")
11
+ @name = name
12
+ @mix = Mix.new("default")
13
+ @units = Set.new
14
+ @rules = Set.new
15
+ # symbol -> unit
16
+ @symbols_map = {}
17
+ @lookup = {}
18
+ @graph = {}
19
+ end
20
+
21
+ def <<(m)
22
+ @mix << m
23
+ end
24
+
25
+ def rules
26
+ @mix.rules
27
+ end
28
+
29
+ def make
30
+ @units.clear
31
+ @lookup = {}
32
+ @graph = {}
33
+ @mix.rules.each do |r|
34
+ @units << r.this_measurement.unit
35
+ @units << r.that_measurement.unit
36
+ this_unit = r.this_measurement.unit
37
+ that_unit = r.that_measurement.unit
38
+
39
+ this_in_that = Measurement.new((r.that_measurement.amount/r.this_measurement.amount).rationalize, that_unit)
40
+ that_in_this = Measurement.new((r.this_measurement.amount/r.that_measurement.amount).rationalize, this_unit)
41
+
42
+ # add direct conversions to lookup
43
+ @lookup[[r.this_measurement.unit, r.that_measurement.unit]] = this_in_that
44
+ @lookup[[r.that_measurement.unit, r.this_measurement.unit]] = that_in_this
45
+
46
+ # build graph
47
+ # rule "3 foo = 4 bar" represented as {foo => [4/3 bar], bar => [3/4 foo]}
48
+ @graph[r.this_measurement.unit] = Set.new unless @graph.include?(r.this_measurement.unit)
49
+ @graph[r.this_measurement.unit] << this_in_that
50
+ @graph[r.that_measurement.unit] = Set.new unless @graph.include?(r.that_measurement.unit)
51
+ @graph[r.that_measurement.unit] << that_in_this
52
+ end
53
+ end
54
+
55
+ def convert(value, from, to)
56
+ from = Unit.new(from)
57
+ to = Unit.new(to)
58
+ return nil unless @units.include?(from) && @units.include?(to)
59
+ return (value * (@lookup[[from, to]]).amount) if @lookup.include? [from, to]
60
+ # bfs graph from from-to. When found, walk through parent path and multiply all conversion factors.
61
+ Struct.new("Child", :measurement, :parent)
62
+ queue = [Struct::Child.new(Measurement.new(value, from), nil)]
63
+ while !queue.empty? do
64
+ parent = queue.first
65
+ # Find all measurement matches
66
+ # e.g. if graph[:from] = ["3/4 :foo", "4/5 :bar", "3/1 :to", "5/1 :to"]
67
+ # then matches = ["3/1 :to", "5/1 :to"]
68
+ # Since there are two valid rules to convert :from -> :to, we pick the first one
69
+ matches = @graph[parent.measurement.unit].select{|m|m.unit == to}
70
+ if(matches.size > 0)
71
+ # create the chain [from, a, b, c, to], starting with 'to' and building back
72
+ chain_from_to = [Struct::Child.new(matches.first, parent)]
73
+ curr = chain_from_to.first
74
+ while(curr.parent) do
75
+ chain_from_to.unshift curr.parent # add current parent to front of chain
76
+ curr = curr.parent
77
+ end
78
+ return chain_from_to.map{|c|c.measurement.amount}.inject(1){|m1,m2| m1 * m2}
79
+ else
80
+ children = @graph[parent.measurement.unit]
81
+ children_not_in_queue = children.reject{|m| queue.include? m}
82
+ queue += children_not_in_queue.map{|m| Struct::Child.new(m, parent)}
83
+ queue.shift
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,35 @@
1
+ module UnitSoup
2
+
3
+ class Unit
4
+ attr_reader :name, :symbol
5
+
6
+ def initialize(name=nil, symbol)
7
+ @symbol = symbol.to_sym
8
+ @name = name.nil? ? @symbol.to_sym : name.to_sym
9
+ end
10
+
11
+ def name=(name)
12
+ @name = name.to_sym
13
+ end
14
+
15
+ def to_s
16
+ symbol.to_s
17
+ end
18
+
19
+ def to_sym
20
+ symbol.to_sym
21
+ end
22
+
23
+ def ==(o)
24
+ symbol == o.symbol
25
+ end
26
+
27
+ def eql?(o)
28
+ symbol == o.symbol
29
+ end
30
+
31
+ def hash
32
+ symbol.hash
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module UnitSoup
2
+ VERSION = "0.1.1"
3
+ end
data/tmp/test.rb ADDED
@@ -0,0 +1,5 @@
1
+ require_relative 'unit_soup'
2
+
3
+ puts UnitSoup::UnitSet.define do
4
+ "hello" + " world"
5
+ end
data/tmp/tmp.rb ADDED
@@ -0,0 +1,143 @@
1
+ # trying out api
2
+
3
+ pack = UnitPack.create(name: "time periods") do |p|
4
+ p.name = "time periods"
5
+ p.desc = "some description"
6
+ p.rules = [
7
+ "7 days = 1 week"
8
+ ]
9
+ p.rules File("conversion.rules")
10
+ # p.rules [
11
+ # {days: 7, week: 1}
12
+ # {weekday: 5, week: 1}
13
+ # {weekday: 1, days: 1}
14
+ # {weekend: 1, days: 2}
15
+ # {weekend: 1, week: 1}
16
+ # ]
17
+ p.aliases = {
18
+ days: %w(day days)
19
+
20
+ }
21
+ end
22
+
23
+
24
+ Rule.new("1 days", "24 hours")
25
+ Convertible::Unit(:name, %w(aliases), rules)
26
+ Convertible::Measurement
27
+ Convertible::Unit
28
+ Convertible::DefinitionSet
29
+ Convertible::RuleSet
30
+ Measurement/Reading
31
+ Rule = measurementA = measurementB
32
+ UnitPack.merge()
33
+ UnitPack.convert measurementA unitB
34
+ UnitPack.conversionFactor measurementA unitB
35
+ DefinitionSet/ConversionRuleSet/UnitPack/UnitGroup
36
+
37
+ UnitSoup::Unit
38
+ UnitSoup::Measurement
39
+ UnitSoup::Mix
40
+
41
+ soupmix = Mix.new()
42
+ soup.make
43
+ soup.add(Mix::ImperialUnits)
44
+ soup.make
45
+ soup.convert(measurement, "cm")
46
+ soup.conversion_factor("inch", "cm")
47
+ soup.conversion_path("inch", "cm")
48
+ soup.rules << Mix | Rule | rules
49
+ soup.rules = Mix | Rule | rules
50
+ soup.rules Mix | Rule | rules
51
+ soup.rule = Rule
52
+ soup.rule Rule
53
+
54
+ BudgetItem < Convertible::Measurement
55
+
56
+ SetDefinition
57
+
58
+ metric_units = Convertible::Set.define do
59
+ name "Metric Units"
60
+ rules [
61
+ "100 cm = 1 decimeter",
62
+ "100 decimeter = 1 meter",
63
+ "1000 meter = 1 km"
64
+ ]
65
+ rule "2.5 cm = 1 inch"
66
+ rules_from_file :file_path
67
+ unit(:km).same_as("KM", :kilo_meters) # aliases
68
+ unit(:centimeter).use_symbol("cm")
69
+ unit(:cm).use_plural("centimeters")
70
+ unit(:cm).use_singular(short: cm, long="centimeter")
71
+ unit(:cm).use(symbol: :cm, singular: :centimeter, :plural: "centimeters", name: "Centimeter")
72
+ unit(:cm).rule("2.5cm", "1 inch")
73
+ unit(:cm).rule("2.5", 1, unit(:inch))
74
+ unit(:cm).rule("2.5", unit(1, :inch))
75
+ end
76
+
77
+ Convertible::Set.from/with/union (Convertible::SetDefinition::Metric, dollar_units)
78
+ my_set.add(dollar_units).
79
+
80
+
81
+ # algo
82
+ @lookup @graph
83
+ convert(measurement, from, to)
84
+ return from lookup if lookup has from, to factor
85
+ bfssearch from to - when found, take path, collect factors
86
+ return converted or show error
87
+
88
+ when rules change
89
+ reset lookup and graph
90
+ build graph from rules
91
+ rules.each do
92
+ graph[this][that]=factor
93
+ graph[that][this]=1/factor
94
+
95
+ # classes
96
+ Rule = Measurement - Measurement
97
+ Measurement = Amount.to_r Unit
98
+ Unit(to_sym, symbols)
99
+ Amount(to_r)
100
+ Soup = set<rule>
101
+
102
+ Soup
103
+ @mix = Mix.new(name)
104
+ @units = []
105
+ @rules = []
106
+ @symbols_map = {:symbol => unit}
107
+ @lookup
108
+ @graph
109
+
110
+
111
+ # brainstorming unit singletons
112
+ mix << rules
113
+ mix[:unit].symbols << :cms
114
+ soup.prepare
115
+ soup.unit[:unit].same_as :longer_name
116
+ soup.make
117
+ soup = Soup.new("mysoup", mix1, mix2)
118
+ soup << mix3
119
+ soup << rule1, rule2, mix
120
+ soup.unit(:unit).field
121
+ soup.units[:unit].symbols
122
+
123
+
124
+
125
+ # unit additional functionality
126
+ :singular, :plural,
127
+ def initialize(options={})
128
+ @symbols = []
129
+ @name = options[:name].to_sym if options[:name]
130
+ @singular = options[:singular].to_sym if options[:singular]
131
+ @plural = options[:plural].to_sym if options[:plural]
132
+ @symbol = options[:use].to_sym if options[:use]
133
+ @symbol = options[:symbol].to_sym if @symbol.nil? && options[:symbol]
134
+ @symbol = options[:preffered].to_sym if @symbol.nil? && options[:preffered]
135
+ @symbols += options[:aliases] if options[:aliases]
136
+ @symbols += options[:symbols] if options[:symbols]
137
+ end
138
+
139
+ def symbols
140
+ (@symbols + [@name, @singular, @plural, @symbol]).reject{|s|s.blank?}.sort.uniq
141
+ end
142
+
143
+ alias_method :preffered, :symbol
data/unit_soup.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "unit_soup/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "unit_soup"
8
+ spec.version = UnitSoup::VERSION
9
+ spec.authors = ["Rutvij"]
10
+ spec.email = ["code@rutvijshah.com"]
11
+
12
+ spec.summary = %q{A DRY approach to unit conversion.}
13
+ spec.description = %q{A DRY approach to unit conversion. Define rules, make soup, convert.}
14
+ spec.homepage = "http://www.rutvijshah.com"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.15"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rspec", '~> 3.5', '>= 3.5.0'
27
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: unit_soup
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Rutvij
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-10-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.15'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.15'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.5'
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 3.5.0
51
+ type: :development
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - "~>"
56
+ - !ruby/object:Gem::Version
57
+ version: '3.5'
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 3.5.0
61
+ description: A DRY approach to unit conversion. Define rules, make soup, convert.
62
+ email:
63
+ - code@rutvijshah.com
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - ".gitignore"
69
+ - ".rspec"
70
+ - Gemfile
71
+ - LICENSE.txt
72
+ - README.md
73
+ - Rakefile
74
+ - bin/console
75
+ - bin/setup
76
+ - lib/unit_soup.rb
77
+ - lib/unit_soup/measurement.rb
78
+ - lib/unit_soup/mix.rb
79
+ - lib/unit_soup/rule.rb
80
+ - lib/unit_soup/soup.rb
81
+ - lib/unit_soup/unit.rb
82
+ - lib/unit_soup/version.rb
83
+ - tmp/test.rb
84
+ - tmp/tmp.rb
85
+ - unit_soup.gemspec
86
+ homepage: http://www.rutvijshah.com
87
+ licenses:
88
+ - MIT
89
+ metadata: {}
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 2.6.13
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: A DRY approach to unit conversion.
110
+ test_files: []