rubadana 0.0.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: 0a631bf26519d087f84658df366b3e8215ac07d8
4
+ data.tar.gz: c3e5b87d59a2e137288e54bea5c6f488f23d6f98
5
+ SHA512:
6
+ metadata.gz: ce23a984a846411b53048cfd7288082a3431ea4f653fb98e308e79b9a2f55ddc4914b3538b31cdc7cf02437269c1b455af784d8674bf726af519417881b79e28
7
+ data.tar.gz: fd437f3e95811f6f78746b093152aa02c13b35745a1c983dce38cd5f9e8333360132fe62188e0daee13c665ba3cb0b9e56a08d6f38f6d312cdfc6dbd4e1f581b
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ *.gem
2
+ /.bundle/
3
+ /.yardoc
4
+ /Gemfile.lock
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /tmp/
11
+ *.bundle
12
+ *.so
13
+ *.o
14
+ *.a
15
+ mkmf.log
16
+ .#*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rubadana.gemspec
4
+ gemspec
data/MIT-LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2016 conanite
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # Rubadana
2
+
3
+ Rubadana is an elementary ruby data-analysis package. It works with plain old ruby objects, not sql or databases or anything fancy like that.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'rubadana'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install rubadana
20
+
21
+ ## Usage
22
+
23
+ See spec for some examples. The basic idea:
24
+
25
+ 1. Create a Registry for your `Dimension` and `Accumulator` instances
26
+
27
+ my_registry = Rubadana::Registry.new
28
+
29
+ 2. Create and register some `Dimension` instances:
30
+
31
+ ```ruby
32
+ class SaleYear < Rubadana::Dimension
33
+ def name ; "yearly" ; end
34
+ def group_value_for thing ; thing.date.year ; end
35
+ def value_label_for value ; value ; end
36
+ end
37
+
38
+ my_registry.register_dimension SaleYear.new
39
+ ```
40
+
41
+ 3. Create and register some `Accumulator` instances:
42
+
43
+ ```ruby
44
+ class SumSaleAmount < Rubadana::Summation
45
+ def name ; "sum-sale-amount" ; end
46
+ def value_for thing ; thing.sale_amount ; end
47
+ end
48
+
49
+ my_registry.register_accumulator SumSaleAmount.new
50
+ ```
51
+
52
+ 4. Build an analysis program and run it:
53
+
54
+ ```ruby
55
+ # this is a program to analyse invoices by year and product, giving the
56
+ # number of sales, the sum of sales and the average sale in each case
57
+ my_program = register.build ["yearly", "invoice-product"], ["count", "sum-sale-amount", "avg-sale-amount"]
58
+
59
+ data = my_program.run(invoices)
60
+ ```
61
+
62
+ `#run` returns an array of `DataSet` each with the following attributes:
63
+
64
+ * `analyser` - a `Dimension` instance
65
+ * `group_value` - the common value of this dimension for all objects in this data-set
66
+ * `data` - either an accumulated value given by an accumulator, or a nested array of `DataSet` instances
67
+
68
+
69
+ ## Contributing
70
+
71
+ 1. Fork it ( https://github.com/[my-github-username]/rubadana/fork )
72
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
73
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
74
+ 4. Push to the branch (`git push origin my-new-feature`)
75
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,3 @@
1
+ module Rubadana
2
+ VERSION = "0.0.1"
3
+ end
data/lib/rubadana.rb ADDED
@@ -0,0 +1,77 @@
1
+ require "rubadana/version"
2
+
3
+ module Rubadana
4
+ class Registry
5
+ def initialize
6
+ @dimensions = Hash.new
7
+ @accumulators = Hash.new
8
+ end
9
+
10
+ def register_dimension d ; @dimensions[d.name.to_sym] = d ; end
11
+ def register_accumulator a ; @accumulators[a.name.to_sym] = a ; end
12
+ def dimensions ; @dimensions.values ; end
13
+ def accumulators ; @accumulators.values ; end
14
+ def not_nil attr, hsh, name ; hsh[name.to_sym] || raise("unknown #{attr} #{name.inspect}") ; end
15
+ def dimension name ; not_nil "dimension" , @dimensions , name ; end
16
+ def accumulator name ; not_nil "accumulator", @accumulators, name ; end
17
+
18
+ def build dnames, anames
19
+ dd = dnames.compact.map { |n| dimension n }
20
+ aa = anames.compact.map { |n| accumulator n }
21
+ Program.new(dd + aa)
22
+ end
23
+ end
24
+
25
+ class DataSet < Aduki::Initializable
26
+ attr_accessor :analyser, :group_value, :data
27
+ def value_label ; analyser.value_label_for group_value ; end
28
+ end
29
+
30
+ class Accumulator
31
+ def name ; raise "implement this and return a unique name for this accumulator" ; end
32
+ def accumulate things ; raise "implement this and return a value extracted from #things" ; end
33
+ def run things, after ; [DataSet.new(analyser: self, data: accumulate(things))] + after.run(things) ; end
34
+ end
35
+
36
+
37
+ class Summation < Accumulator
38
+ def value_for thing ; raise "implement this and return a value extracted from #thing" ; end
39
+ def accumulate things ; things.map { |thing| value_for(thing) }.reduce :+ ; end
40
+ end
41
+
42
+ class Counter < Accumulator
43
+ def name ; "count" ; end
44
+ def accumulate things ; things.uniq.count ; end
45
+ end
46
+
47
+ class Average < Summation
48
+ def accumulate things ; super / (1.0 * things.count) ; end
49
+ end
50
+
51
+ class Dimension
52
+ def name ; raise "implement this and return a unique name for this dimension" ; end
53
+ def group_value_for thing ; raise "implement this and return a value extracted from #thing" ; end
54
+ def value_label_for value ; raise "implement this to return a display value for #{value.inspect}" ; end
55
+
56
+ def run objects, after
57
+ objects.group_by { |obj| group_value_for obj }.map { |value, list|
58
+ DataSet.new analyser: self, group_value: value, data: after.run(list)
59
+ }
60
+ end
61
+ end
62
+
63
+ class Program
64
+ attr_accessor :dimension, :after
65
+
66
+ def initialize dimensions
67
+ if dimensions
68
+ self.dimension = dimensions.first
69
+ self.after = Program.new dimensions[1..-1]
70
+ end
71
+ end
72
+
73
+ def run objects
74
+ dimension ? dimension.run(objects, after) : []
75
+ end
76
+ end
77
+ end
data/rubadana.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rubadana/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rubadana"
8
+ spec.version = Rubadana::VERSION
9
+ spec.authors = ["Conan Dalton"]
10
+ spec.email = ["conan@conandalton.net"]
11
+ spec.summary = %q{ Simple data grouping and calculations }
12
+ spec.description = %q{ Simple data grouping and calculations. Bring your own extractors. }
13
+ spec.homepage = "http://github.com/conanite/rubadana"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "aduki", "~> 0.2.3"
22
+ spec.add_development_dependency "bundler", "~> 1.7"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency 'rspec'
25
+ end
@@ -0,0 +1,220 @@
1
+ require "spec_helper"
2
+
3
+ describe "analyse invoices" do
4
+ def date str ; Date.parse str ; end
5
+
6
+ class Invoice < Aduki::Initializable
7
+ attr_accessor :type, :date, :amount
8
+ end
9
+
10
+ class InvoiceMonth < Rubadana::Dimension
11
+ def name ; "monthly" ; end
12
+ def group_value_for thing ; Date.new(thing.date.year, thing.date.month, 1) ; end # rails just use #beginning_of_month
13
+ def value_label_for value ; value.strftime "%B %Y" ; end # better with I18n
14
+ end
15
+
16
+ class InvoiceYear < Rubadana::Dimension
17
+ def name ; "yearly" ; end
18
+ def group_value_for thing ; thing.date.year ; end
19
+ def value_label_for value ; value ; end
20
+ end
21
+
22
+ class InvoiceType < Rubadana::Dimension
23
+ def name ; "type" ; end
24
+ def group_value_for thing ; thing.type ; end
25
+ def value_label_for value ; value.to_s ; end
26
+ end
27
+
28
+ class InvoiceScale < Rubadana::Dimension
29
+ def name ; "scale" ; end
30
+ def group_value_for thing ; Math.log(thing.amount, 10).to_i ; end
31
+ def value_label_for value ; value ; end
32
+ end
33
+
34
+ class InvoiceSum < Rubadana::Summation
35
+ def name ; "sum-amount" ; end
36
+ def value_for thing ; thing.amount ; end
37
+ end
38
+
39
+ class InvoiceAvg < Rubadana::Average
40
+ def name ; "avg-amount" ; end
41
+ def value_for thing ; thing.amount ; end
42
+ end
43
+
44
+ let(:i00) { Invoice.new type: "SalesInvoice" , date: date("2020-02-01"), amount: 53 }
45
+ let(:i01) { Invoice.new type: "PurchaseInvoice" , date: date("2021-04-02"), amount: 1100 }
46
+ let(:i02) { Invoice.new type: "SalesCreditNote" , date: date("2020-02-03"), amount: 23000 }
47
+ let(:i03) { Invoice.new type: "SalesCreditNote" , date: date("2021-04-04"), amount: 3100 }
48
+ let(:i04) { Invoice.new type: "Quote" , date: date("2020-05-05"), amount: 43000 }
49
+ let(:i05) { Invoice.new type: "Order" , date: date("2021-12-06"), amount: 59 }
50
+ let(:i06) { Invoice.new type: "SalesInvoice" , date: date("2020-02-07"), amount: 6100 }
51
+ let(:i07) { Invoice.new type: "PurchaseInvoice" , date: date("2022-06-08"), amount: 79000 }
52
+ let(:i08) { Invoice.new type: "PurchaseCreditNote", date: date("2020-05-09"), amount: 83000 }
53
+ let(:i09) { Invoice.new type: "SalesInvoice" , date: date("2020-05-10"), amount: 990 }
54
+ let(:i10) { Invoice.new type: "SalesInvoice" , date: date("2022-06-11"), amount: 130 }
55
+ let(:i11) { Invoice.new type: "PurchaseInvoice" , date: date("2022-12-12"), amount: 1700 }
56
+ let(:i12) { Invoice.new type: "SalesInvoice" , date: date("2020-11-13"), amount: 19000 }
57
+ let(:i13) { Invoice.new type: "PurchaseCreditNote", date: date("2020-11-14"), amount: 23 }
58
+ let(:i14) { Invoice.new type: "SalesInvoice" , date: date("2021-04-15"), amount: 110 }
59
+ let(:i15) { Invoice.new type: "SalesInvoice" , date: date("2022-06-16"), amount: 170000 }
60
+
61
+ let(:invoices) { [ i00,i01,i02,i03,i04,i05,i06,i07,i08,i09,i10,i11,i12,i13,i14,i15 ]}
62
+ let(:register) { Rubadana::Registry.new }
63
+
64
+ before {
65
+ register.register_dimension InvoiceYear.new
66
+ register.register_dimension InvoiceMonth.new
67
+ register.register_dimension InvoiceType.new
68
+ register.register_dimension InvoiceScale.new
69
+ register.register_accumulator InvoiceSum.new
70
+ register.register_accumulator InvoiceAvg.new
71
+ register.register_accumulator Rubadana::Counter.new
72
+ }
73
+
74
+ it "groups items by month and counts them" do
75
+ program = register.build ["monthly"], ["count"]
76
+ data = program.run(invoices)
77
+ actual = data.sort_by(&:group_value).map { |d| [d.value_label] + d.data.map(&:data) }
78
+ expected = [
79
+ ["February 2020", 3],
80
+ ["May 2020" , 3],
81
+ ["November 2020", 2],
82
+ ["April 2021" , 3],
83
+ ["December 2021", 1],
84
+ ["June 2022" , 3],
85
+ ["December 2022", 1],
86
+ ]
87
+ expect(actual).to eq expected
88
+ end
89
+
90
+ it "groups items by year and sums them" do
91
+ program = register.build ["yearly"], ["sum-amount"]
92
+ data = program.run(invoices)
93
+ actual = data.sort_by(&:group_value).map { |d| [d.value_label] + d.data.map(&:data) }
94
+ expected = [
95
+ [2020, 175166],
96
+ [2021, 4369],
97
+ [2022, 250830],
98
+ ]
99
+ expect(actual).to eq expected
100
+ end
101
+
102
+ it "groups items by year and counts them" do
103
+ program = register.build ["yearly"], ["count"]
104
+ data = program.run(invoices)
105
+ actual = data.sort_by(&:group_value).map { |d| [d.value_label] + d.data.map(&:data) }
106
+ expected = [
107
+ [2020, 8],
108
+ [2021, 4],
109
+ [2022, 4],
110
+ ]
111
+ expect(actual).to eq expected
112
+ end
113
+
114
+ it "groups items by year and averages them" do
115
+ program = register.build ["yearly"], ["avg-amount"]
116
+ data = program.run(invoices)
117
+ actual = data.sort_by(&:group_value).map { |d| [d.value_label] + d.data.map(&:data) }
118
+ expected = [
119
+ [2020, 21895.75],
120
+ [2021, 1092.25],
121
+ [2022, 62707.5 ],
122
+ ]
123
+ expect(actual).to eq expected
124
+ end
125
+
126
+ it "groups items by year and gives the count, sum, and average" do
127
+ program = register.build ["yearly"], ["count", "sum-amount", "avg-amount"]
128
+ data = program.run(invoices)
129
+ actual = data.sort_by(&:group_value).map { |d| [d.value_label] + d.data.map(&:data) }
130
+ expected = [
131
+ [2020, 8, 175166, 21895.75],
132
+ [2021, 4, 4369, 1092.25],
133
+ [2022, 4, 250830, 62707.5 ],
134
+ ]
135
+ expect(actual).to eq expected
136
+ end
137
+
138
+ it "groups items by year and by type and by scale and counts them" do
139
+ program = register.build ["yearly", "type"], ["count"]
140
+ data = program.run(invoices)
141
+ actual = data.sort_by(&:group_value).inject([]) { |arr, d|
142
+ d.data.sort_by(&:group_value).each { |s|
143
+ arr << [d.value_label, s.value_label] + s.data.map(&:data) }
144
+ arr
145
+ }
146
+
147
+ expected = [
148
+ [2020 , "PurchaseCreditNote" , 2 ],
149
+ [2020 , "Quote" , 1 ],
150
+ [2020 , "SalesCreditNote" , 1 ],
151
+ [2020 , "SalesInvoice" , 4 ],
152
+ [2021 , "Order" , 1 ],
153
+ [2021 , "PurchaseInvoice" , 1 ],
154
+ [2021 , "SalesCreditNote" , 1 ],
155
+ [2021 , "SalesInvoice" , 1 ],
156
+ [2022 , "PurchaseInvoice" , 2 ],
157
+ [2022 , "SalesInvoice" , 2 ],
158
+ ]
159
+ expect(actual).to eq expected
160
+ end
161
+
162
+ it "groups items by scale and by type and sums them" do
163
+ program = register.build ["scale", "type"], ["sum-amount"]
164
+ data = program.run(invoices)
165
+ actual = data.sort_by(&:group_value).inject([]) { |arr, d|
166
+ d.data.sort_by(&:group_value).each { |s|
167
+ arr << [d.value_label, s.value_label] + s.data.map(&:data) }
168
+ arr
169
+ }
170
+
171
+ expected = [
172
+ [1 , "Order" , 59 ] ,
173
+ [1 , "PurchaseCreditNote" , 23 ] ,
174
+ [1 , "SalesInvoice" , 53 ] ,
175
+ [2 , "SalesInvoice" , 1230 ] ,
176
+ [3 , "PurchaseInvoice" , 2800 ] ,
177
+ [3 , "SalesCreditNote" , 3100 ] ,
178
+ [3 , "SalesInvoice" , 6100 ] ,
179
+ [4 , "PurchaseCreditNote" , 83000 ] ,
180
+ [4 , "PurchaseInvoice" , 79000 ] ,
181
+ [4 , "Quote" , 43000 ] ,
182
+ [4 , "SalesCreditNote" , 23000 ] ,
183
+ [4 , "SalesInvoice" , 19000 ] ,
184
+ [5 , "SalesInvoice" , 170000 ] ]
185
+
186
+ expect(actual).to eq expected
187
+ end
188
+
189
+ it "groups items by year and by type and by scale and counts them" do
190
+ program = register.build ["yearly", "type", "scale"], ["sum-amount"]
191
+ data = program.run(invoices)
192
+ actual = data.sort_by(&:group_value).inject([]) { |arr, d|
193
+ d.data.sort_by(&:group_value).each { |s|
194
+ s.data.sort_by(&:group_value).each { |z|
195
+ arr << [d.value_label, s.value_label, z.value_label] + z.data.map(&:data) }
196
+ }
197
+ arr
198
+ }
199
+
200
+ expected = [
201
+ [2020 , "PurchaseCreditNote" , 1 , 23.0 ],
202
+ [2020 , "PurchaseCreditNote" , 4 , 83000.0 ],
203
+ [2020 , "Quote" , 4 , 43000.0 ],
204
+ [2020 , "SalesCreditNote" , 4 , 23000.0 ],
205
+ [2020 , "SalesInvoice" , 1 , 53.0 ],
206
+ [2020 , "SalesInvoice" , 2 , 990.0 ],
207
+ [2020 , "SalesInvoice" , 3 , 6100.0 ],
208
+ [2020 , "SalesInvoice" , 4 , 19000.0 ],
209
+ [2021 , "Order" , 1 , 59.0 ],
210
+ [2021 , "PurchaseInvoice" , 3 , 1100.0 ],
211
+ [2021 , "SalesCreditNote" , 3 , 3100.0 ],
212
+ [2021 , "SalesInvoice" , 2 , 110.0 ],
213
+ [2022 , "PurchaseInvoice" , 3 , 1700.0 ],
214
+ [2022 , "PurchaseInvoice" , 4 , 79000.0 ],
215
+ [2022 , "SalesInvoice" , 2 , 130.0 ],
216
+ [2022 , "SalesInvoice" , 5 , 170000.0 ]
217
+ ]
218
+ expect(actual).to eq expected
219
+ end
220
+ end
@@ -0,0 +1,19 @@
1
+ require 'aduki'
2
+ require 'rubadana'
3
+
4
+ # This file was generated by the `rspec --init` command. Conventionally, all
5
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
6
+ # Require this file using `require "spec_helper"` to ensure that it is only
7
+ # loaded once.
8
+ #
9
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
10
+ RSpec.configure do |config|
11
+ config.run_all_when_everything_filtered = true
12
+ config.filter_run :focus
13
+
14
+ # Run specs in random order to surface order dependencies. If you find an
15
+ # order dependency and want to debug it, you can fix the order by providing
16
+ # the seed, which is printed after each run.
17
+ # --seed 1234
18
+ config.order = 'random'
19
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubadana
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Conan Dalton
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-05-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aduki
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.2.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.2.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: " Simple data grouping and calculations. Bring your own extractors. "
70
+ email:
71
+ - conan@conandalton.net
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".rspec"
78
+ - Gemfile
79
+ - MIT-LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - lib/rubadana.rb
83
+ - lib/rubadana/version.rb
84
+ - rubadana.gemspec
85
+ - spec/analyse_invoices_spec.rb
86
+ - spec/spec_helper.rb
87
+ homepage: http://github.com/conanite/rubadana
88
+ licenses:
89
+ - MIT
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 2.2.2
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: Simple data grouping and calculations
111
+ test_files:
112
+ - spec/analyse_invoices_spec.rb
113
+ - spec/spec_helper.rb