matt 1.0.0

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
+ SHA256:
3
+ metadata.gz: 712f1cd0be5efa12e62fe0491c0c14028b76a5e4edcdaf792bf8ab9652397218
4
+ data.tar.gz: e133df58cfba2501cdd806668ff6e21363150a32a9563a6fa554938dc2bfed5c
5
+ SHA512:
6
+ metadata.gz: ae322c7f52f9bab450bb21781b5653bd87a6e40703fadfe2768cc56305508464945f411c15584b3702cfefc969baf2af0fe4e437b6b454355b934b7602d39920
7
+ data.tar.gz: d4ef94dc91c994bb4886a4b9c4beb0c863af6d62bc2c66c440fb0a594788640bca2c536bbe1f86a8f3e22ebb93bc357ef09e0e29c604256aa4f064b167ba33ef
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
data/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # Matt - A monitoring tool for data-driven business decisions
2
+
3
+ Matt is a CTO-oriented tool that helps capturing daily data points that are
4
+ relevant for conducting growth hacking and other data-driven decisions.
5
+
6
+ It consists of the following abstractions:
7
+
8
+ * Datasource: provides access to data from which measures are extracted
9
+ * Measure: standardized data vector for relevant decisions
10
+ * Exporter: a way to save measures for future analysis and visualization
11
+
12
+ ## How to install Matt?
13
+
14
+ There are various usage scenario to use matt, according to your needs and
15
+ types of environment and datasources.
16
+
17
+ ### As a commandline
18
+
19
+ To simply use Matt as a commandline and connect to local databases, simply
20
+ install and use the ruby gem:
21
+
22
+ ```
23
+ gem install matt
24
+ matt --help
25
+ ```
26
+
27
+ ### As a docker image
28
+
29
+ If you don't have ruby installed and want to use docker instead, we provide a
30
+ docker image that has Matt as entrypoint.
31
+
32
+ ```
33
+ docker run enspirit/matt --help
34
+ ```
35
+
36
+ Please check docker's network documentation if you want Matt to connect to
37
+ databases running on your host.
38
+
39
+ ### In a Gemfile
40
+
41
+ It may be useful to use Matt as part of a larger ruby gemset, for instance
42
+ because you need various ruby gems to connect to mysql, postgres, or whatever.
43
+
44
+ Then in your Gemfile:
45
+
46
+ ```
47
+ source https://rubygems.org
48
+
49
+ gem "matt"
50
+ ```
51
+
52
+ Then,
53
+
54
+ ```
55
+ bundle exec matt --help
56
+ ```
57
+
58
+ ### As a docker base image + a Gemfile
59
+
60
+ If Matt is part of a larger docker/kubernetes infrastructure, you probably need
61
+ the last scenario while also having your own docker image for the component.
62
+
63
+ This is a good basis for a Dockerfile:
64
+
65
+ ```
66
+ FROM enspirit/matt # or from ruby-3.0:alpine
67
+
68
+ COPY Gemfile .
69
+ RUN bundle install
70
+
71
+ ENTRYPOINT ['bundle', 'exec', 'matt']
72
+ ```
73
+
74
+ Let's say you build that image as `my/matt`, you can now simply run matt as
75
+ follows:
76
+
77
+ ```
78
+ docker run my/matt --help
79
+ ```
80
+
81
+ In such a scenario, you may want to use Matt's version that exists in the
82
+ docker container instead without having to specify it in the Gemfile.
83
+ The following Gemfile hack works:
84
+
85
+ ```
86
+ gem "matt", path: "/matt"
87
+ ```
88
+
89
+ ## Semantics versioning
90
+
91
+ Matt uses semantics versioning since 1.0.
92
+
93
+ The public API consists in:
94
+
95
+ * Matt public methods
96
+ * Matt::Configuration public methods
97
+ * Matt::Datasource public & protected methods exposed to including classes
98
+ * Matt::Exporter (same)
99
+ * Matt::Measure (same)
100
+ * `matt` commandline and its options
101
+ * `enspirit/matt` docker image with `/matt` and `/home/matt` behavior
102
+
103
+ ## Contribute
104
+
105
+ Please use github issues and pull requests for all questions, bug reports,
106
+ and contributions. Don't hesitate to get in touch with us with an early code
107
+ spike if you plan to add non trivial features.
108
+
109
+ ## Licence
110
+
111
+ This software is distributed by Enspirit SRL under a MIT Licence. Please
112
+ contact Bernard Lambeau (blambeau@gmail.com) with any question.
113
+
114
+ Enspirit (https://enspirit.be) and Klaro App (https://klaro.cards) are both
115
+ actively using and contributing to the library.
data/bin/matt ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'matt'
3
+ exitcode = Matt::Command.new.call(ARGV)
4
+ exit(exitcode)
data/lib/matt.rb ADDED
@@ -0,0 +1,39 @@
1
+ require 'date'
2
+ require 'ostruct'
3
+ require 'json'
4
+ require 'sequel'
5
+ require 'bmg'
6
+ require 'path'
7
+ require 'bmg/sequel'
8
+ module Matt
9
+
10
+ def env(which, default = nil)
11
+ val = ENV.has_key?(which) ? ENV[which] : default
12
+ val = val.strip if val.is_a?(String)
13
+ val
14
+ end
15
+ module_function :env
16
+
17
+ def alltime_predicate
18
+ Predicate.tautology
19
+ end
20
+ module_function :alltime_predicate
21
+
22
+ def yesterday_predicate
23
+ Predicate.gte(:at, Date.today - 1) & Predicate.lt(:at, Date.today)
24
+ end
25
+ module_function :yesterday_predicate
26
+
27
+ def today_predicate
28
+ Predicate.gte(:at, Date.today) & Predicate.lt(:at, Date.today + 1)
29
+ end
30
+ module_function :today_predicate
31
+
32
+ end # module Matt
33
+ require_relative 'matt/version'
34
+ require_relative 'matt/support'
35
+ require_relative 'matt/datasource'
36
+ require_relative 'matt/measure'
37
+ require_relative 'matt/exporter'
38
+ require_relative 'matt/configuration'
39
+ require_relative 'matt/command'
@@ -0,0 +1,211 @@
1
+ #/
2
+ #/ Matt - A monitoring tool for data-driven business decisions
3
+ #/ vVERSION - (c) Enspirit SRL, 2021 and beyond
4
+ #/
5
+ #/ Usage: matt [-f folder] command [options...] [args...]
6
+ #/
7
+ #/ show measure Show the datapoints of a given measure
8
+ #/ export measure* Export all (or specific) measure(s) to
9
+ #/ ping datasource* Ping all (or specific) datasource(s)
10
+ #/ their default exporters
11
+ #/
12
+ #/ -f FOLDER Use the folder as entry point (`pwd` by default)
13
+ #/ --all-time Do not restrict measures shown/exported
14
+ #/ --today Only show/export measures for today
15
+ #/ --yesterday Only show/export measures for yesterday's (default)
16
+ #/ --to=exporter,... Override the default exporters
17
+ #/ --json Use json when displaying measures on console
18
+ #/ --csv Use csv when displaying measures on console (default)
19
+ #/ -h, --help Show this help message
20
+ #/ --version Show matt version
21
+ #/
22
+ #/ Examples:
23
+ #/ matt show --all-time --json account_creations
24
+ #/ matt export --today --to=sql,prometheus account_creations
25
+ #/
26
+ require 'optparse'
27
+ module Matt
28
+ class Command
29
+ include Support::Puts
30
+
31
+ attr_accessor :exitcode
32
+ attr_accessor :output_format
33
+ attr_accessor :to
34
+
35
+ def initialize
36
+ @output_format = :csv
37
+ @to = nil
38
+ yield(self) if block_given?
39
+ end
40
+
41
+ def on_configuration(&bl)
42
+ @on_configuration = bl
43
+ self
44
+ end
45
+
46
+ def configuration
47
+ return @configuration if @configuration
48
+ self.configuration = Configuration.new(Path.pwd)
49
+ end
50
+
51
+ def configuration=(c)
52
+ @on_configuration.call(c) if @on_configuration
53
+ @configuration = c
54
+ end
55
+
56
+ def has_configuration?
57
+ !!@configuration
58
+ end
59
+
60
+ def parse_argv(argv)
61
+ opt_parser.parse!(argv)
62
+ end
63
+
64
+ def call(argv)
65
+ ok = false
66
+ catch :abort do
67
+ parse_argv(argv)
68
+ _call(argv)
69
+ ok = true
70
+ end
71
+ self.exitcode = ok ? 0 : 1
72
+ end
73
+
74
+ protected
75
+
76
+ def _call(argv)
77
+ if argv.empty?
78
+ puts_out opt_parser
79
+ abort
80
+ end
81
+
82
+ meth = :"do_#{argv.first}"
83
+ if self.respond_to?(meth, true)
84
+ send(meth, argv[1..-1])
85
+ else
86
+ puts_err "No such command #{argv.first}"
87
+ abort
88
+ end
89
+ end
90
+
91
+ def do_show(argv)
92
+ argv_count!(argv, 1)
93
+ m = measure_exists!(argv.first)
94
+ data = m.full_data.restrict(configuration.at_predicate)
95
+ case of = output_format
96
+ when :json
97
+ puts_out JSON.pretty_generate(data)
98
+ when :csv
99
+ puts_out data.to_csv(configuration.csv_options)
100
+ else
101
+ puts_err "Unknown format #{of}"
102
+ end
103
+ end
104
+
105
+ def do_ping(argv)
106
+ which_ones = if argv.empty?
107
+ configuration.datasources.to_h.values
108
+ else
109
+ argv.map{|arg| datasource_exists!(arg) }
110
+ end
111
+ which_ones.each do |d|
112
+ d.ping
113
+ end
114
+ end
115
+
116
+ def do_export(argv)
117
+ argv_count!(argv, 1)
118
+ m = measure_exists!(argv.first)
119
+ data = m.full_data.restrict(configuration.at_predicate)
120
+ (@to || m.exporters).each do |e|
121
+ exporter = exporter_exists!(e)
122
+ exporter.export(m, data)
123
+ end
124
+ end
125
+
126
+ def do_help(*args)
127
+ file = __FILE__
128
+ exec "grep ^#/<'#{file}'|cut -c4-|sed s/VERSION/#{Matt::VERSION}/g"
129
+ end
130
+
131
+ protected
132
+
133
+ def opt_parser
134
+ OptionParser.new do |opts|
135
+ opts.banner = "Usage: matt [options] COMMAND [args]"
136
+ opts.on("-f FOLDER") do |folder|
137
+ p = Path(folder)
138
+ if has_configuration?
139
+ puts_err "-f must be used before other configuration options"
140
+ abort
141
+ elsif p.exists? && p.directory?
142
+ self.configuration = Configuration.new(p)
143
+ else
144
+ puts_err "No such folder: #{folder}"
145
+ abort
146
+ end
147
+ end
148
+ opts.on("--all-time") do
149
+ self.configuration.at_predicate = Matt.alltime_predicate
150
+ end
151
+ opts.on("--yesterday") do
152
+ self.configuration.at_predicate = Matt.yesterday_predicate
153
+ end
154
+ opts.on("--today") do
155
+ self.configuration.at_predicate = Matt.today_predicate
156
+ end
157
+ opts.on("--to=EXPORTERS") do |exporters|
158
+ @to = (@to || []) + exporters.split(/\s*,\s*/).map{|e|
159
+ exporter_exists!(e.to_sym).name
160
+ }
161
+ end
162
+ opts.on("--json") do
163
+ self.output_format = :json
164
+ end
165
+ opts.on("--csv") do
166
+ self.output_format = :csv
167
+ end
168
+ opts.on('--version', "Show version number") do
169
+ puts_out "Matt v#{VERSION} - (c) Enspirit SRL"
170
+ abort
171
+ end
172
+ opts.on("-h", "--help", "Prints this help") do
173
+ do_help
174
+ abort
175
+ end
176
+ end
177
+ end
178
+
179
+ def argv_count!(argv, n)
180
+ return if argv.size == n
181
+ puts_err "#{n} arguments expected, got #{argv.size}"
182
+ abort
183
+ end
184
+
185
+ def measure_exists!(name)
186
+ m = configuration.measures.send(name.to_sym)
187
+ return m if m
188
+ puts_err "No such measure #{name}"
189
+ abort
190
+ end
191
+
192
+ def exporter_exists!(name)
193
+ m = configuration.exporters.send(name.to_sym)
194
+ return m if m
195
+ puts_err "No such exporter #{name}"
196
+ abort
197
+ end
198
+
199
+ def datasource_exists!(name)
200
+ d = configuration.datasources.send(name.to_sym)
201
+ return d if d
202
+ puts_err "No such datasource #{name}"
203
+ abort
204
+ end
205
+
206
+ def abort
207
+ throw :abort
208
+ end
209
+
210
+ end # class Command
211
+ end # module Matt
@@ -0,0 +1,69 @@
1
+ module Matt
2
+ class Configuration
3
+
4
+ def initialize(folder)
5
+ @folder = folder
6
+ @stdout = $stdout
7
+ @stderr = $stderr
8
+ @csv_options = {
9
+ :write_headers => true
10
+ }
11
+ @at_predicate = Matt.yesterday_predicate
12
+ yield(self) if block_given?
13
+ end
14
+ attr_accessor :folder
15
+ attr_accessor :stdout, :stderr
16
+ attr_accessor :csv_options
17
+ attr_accessor :at_predicate
18
+
19
+ def datasources_folder
20
+ folder/"datasources"
21
+ end
22
+
23
+ def datasources
24
+ @datasources ||= load_dynamic_objects(datasources_folder, Matt::Datasource)
25
+ end
26
+ alias :ds :datasources
27
+
28
+ def measures_folder
29
+ folder/"measures"
30
+ end
31
+
32
+ def measures
33
+ @measures ||= load_dynamic_objects(measures_folder, Matt::Measure)
34
+ end
35
+ alias :ms :measures
36
+
37
+ def exporters_folder
38
+ folder/"exporters"
39
+ end
40
+
41
+ def exporters
42
+ @exporters ||= load_dynamic_objects(exporters_folder, Matt::Exporter)
43
+ end
44
+ alias :es :exporters
45
+
46
+ private
47
+
48
+ def load_dynamic_objects(folder, expected_type)
49
+ struct = OpenStruct.new
50
+ folder.glob("*.rb").each_with_object(struct) do |file,memo|
51
+ obj = load_dynamic_object(file)
52
+ if obj.nil?
53
+ raise "Wrong file #{file}: eval returned nil"
54
+ elsif !obj.is_a?(expected_type)
55
+ raise "Wrong file #{file}: must include #{expected_type}"
56
+ else
57
+ obj.configuration = self
58
+ obj.name = file.basename.rm_ext.to_s
59
+ memo.send("#{obj.name}=", obj)
60
+ end
61
+ end
62
+ end
63
+
64
+ def load_dynamic_object(file)
65
+ self.instance_eval(file.read, file.to_s)
66
+ end
67
+
68
+ end # class Configuration
69
+ end # module Matt
@@ -0,0 +1,14 @@
1
+ module Matt
2
+ module Datasource
3
+ include Support::Puts
4
+
5
+ attr_accessor :name
6
+ attr_accessor :configuration
7
+
8
+ def ping
9
+ raise NotImplementedError, "#{self} should implement `ping`"
10
+ end
11
+
12
+ end # module Datasource
13
+ end # module Matt
14
+ require_relative 'datasource/sql'
@@ -0,0 +1,33 @@
1
+ module Matt
2
+ module Datasource
3
+ class Sql
4
+ include Matt::Datasource
5
+
6
+ def initialize(config)
7
+ @config = config
8
+ @sequel_db = ::Sequel.connect(config)
9
+ end
10
+ attr_reader :config, :sequel_db
11
+
12
+ def ping
13
+ sequel_db.test_connection
14
+ puts "#{self} -- Ok."
15
+ rescue => ex
16
+ puts_err "#{self} -- Ko: #{ex.message}"
17
+ end
18
+
19
+ def to_s
20
+ if config.is_a?(Hash)
21
+ "#{config[:host]}:#{config[:port]}/#{config[:database]}"
22
+ else
23
+ config.to_s
24
+ end
25
+ end
26
+
27
+ def sequel(*args)
28
+ Bmg.sequel(*args)
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,10 @@
1
+ module Matt
2
+ module Exporter
3
+ include Support::Puts
4
+
5
+ attr_accessor :name
6
+ attr_accessor :configuration
7
+
8
+ end # module Exporter
9
+ end # module Matt
10
+ require_relative 'exporter/sql'
@@ -0,0 +1,90 @@
1
+ module Matt
2
+ module Exporter
3
+ class Sql
4
+ include Matt::Exporter
5
+
6
+ def initialize(arg)
7
+ case @config = arg
8
+ when String
9
+ when Hash
10
+ when Sequel::Database
11
+ when Matt::Datasource::Sql
12
+ else
13
+ raise ArgumentError, "Unable to use `#{arg}` to create an Sql exporter"
14
+ end
15
+ end
16
+ attr_reader :config
17
+
18
+ def sequel_db
19
+ @sequel_db ||= case config
20
+ when Sequel::Database then config
21
+ when Matt::Datasource::Sql then config.sequel_db
22
+ else Sequel.connect(config)
23
+ end
24
+ end
25
+
26
+ def export(measure, data)
27
+ now = Time.now
28
+ data = data.constants(:created_at => now)
29
+ table = ensure_table!(measure)
30
+ remove_old_data!(table, data)
31
+ insert_new_data!(table, data)
32
+ end
33
+
34
+ private
35
+
36
+ def remove_old_data!(table, data)
37
+ dates = data.project([:at]).map{|t| t[:at] }
38
+ table.where(:at => dates).delete
39
+ end
40
+
41
+ def insert_new_data!(table, data)
42
+ table.multi_insert(data.to_a)
43
+ end
44
+
45
+ def ensure_table!(measure)
46
+ tb_name = measure.name.to_sym
47
+ fields = [:at] + measure.dimensions.keys + measure.metrics.keys
48
+ if sequel_db.table_exists?(tb_name)
49
+ align_table!(tb_name, measure, fields)
50
+ else
51
+ create_table!(tb_name, measure, fields)
52
+ end
53
+ sequel_db[tb_name]
54
+ end
55
+
56
+ def align_table!(tb_name, measure, fields)
57
+ schema = sequel_db.schema(tb_name)
58
+ existing = schema.map{|s| s.first } - [:created_at]
59
+ missing = fields - existing
60
+ missing = measure.dimensions.merge(measure.metrics).select{|x|
61
+ missing.include?(x)
62
+ }
63
+ outdated = existing - fields
64
+ return if missing.empty? && outdated.empty?
65
+ sequel_db.alter_table(tb_name) do
66
+ outdated.each do |out|
67
+ drop_column(out)
68
+ end
69
+ missing.each_pair do |mis, type|
70
+ add_column(mis, type, null: true)
71
+ end
72
+ end
73
+ end
74
+
75
+ def create_table!(tb_name, measure, fields)
76
+ sequel_db.create_table(tb_name) do
77
+ column(:at, Date, :null => false)
78
+ column(:created_at, :timestamp, :null => false)
79
+ measure.dimensions.each do |name,type|
80
+ column(name, type, :null => true)
81
+ end
82
+ measure.metrics.each do |name,type|
83
+ column(name, type, :null => true)
84
+ end
85
+ end
86
+ end
87
+
88
+ end # class Sql
89
+ end # module Exporter
90
+ end # module Matt
@@ -0,0 +1,25 @@
1
+ module Matt
2
+ module Measure
3
+ include Support::Puts
4
+
5
+ attr_accessor :name
6
+ attr_accessor :configuration
7
+
8
+ def dimensions
9
+ {}
10
+ end
11
+
12
+ def exporters
13
+ []
14
+ end
15
+
16
+ def metrics
17
+ raise NotImplementedError, "#{self} must implement `metrics`"
18
+ end
19
+
20
+ def ds
21
+ configuration.datasources
22
+ end
23
+
24
+ end # module Measure
25
+ end # module Matt
@@ -0,0 +1 @@
1
+ require_relative 'support/puts'
@@ -0,0 +1,26 @@
1
+ module Matt
2
+ module Support
3
+ module Puts
4
+
5
+ def stdout
6
+ configuration.stdout
7
+ end
8
+
9
+ def stderr
10
+ configuration.stderr
11
+ end
12
+
13
+ def puts_out(*args, &bl)
14
+ return $stdout.puts(*args, &bl) unless configuration
15
+ configuration.stdout.puts(*args, &bl)
16
+ end
17
+ alias :puts :puts_out
18
+
19
+ def puts_err(*args, &bl)
20
+ return $stderr.puts(*args, &bl) unless configuration
21
+ configuration.stderr.puts(*args, &bl)
22
+ end
23
+
24
+ end # module Puts
25
+ end # module Support
26
+ end # module Matt
@@ -0,0 +1,8 @@
1
+ module Matt
2
+ module Version
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ TINY = 0
6
+ end # module Version
7
+ VERSION = [Version::MAJOR, Version::MINOR, Version::TINY].join(".")
8
+ end # module Matt
metadata ADDED
@@ -0,0 +1,177 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: matt
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Bernard Lambeau
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-06-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: path
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '2.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: bmg
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 0.18.5
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '0.19'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 0.18.5
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '0.19'
53
+ - !ruby/object:Gem::Dependency
54
+ name: sequel
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '5.0'
60
+ - - "<"
61
+ - !ruby/object:Gem::Version
62
+ version: '6.19'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '5.0'
70
+ - - "<"
71
+ - !ruby/object:Gem::Version
72
+ version: '6.19'
73
+ - !ruby/object:Gem::Dependency
74
+ name: rake
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '13.0'
80
+ - - "<"
81
+ - !ruby/object:Gem::Version
82
+ version: '14.0'
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '13.0'
90
+ - - "<"
91
+ - !ruby/object:Gem::Version
92
+ version: '14.0'
93
+ - !ruby/object:Gem::Dependency
94
+ name: rspec
95
+ requirement: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '3.10'
100
+ - - "<"
101
+ - !ruby/object:Gem::Version
102
+ version: '4.0'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '3.10'
110
+ - - "<"
111
+ - !ruby/object:Gem::Version
112
+ version: '4.0'
113
+ - !ruby/object:Gem::Dependency
114
+ name: sqlite3
115
+ requirement: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: 1.4.2
120
+ - - "<"
121
+ - !ruby/object:Gem::Version
122
+ version: '2.0'
123
+ type: :development
124
+ prerelease: false
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: 1.4.2
130
+ - - "<"
131
+ - !ruby/object:Gem::Version
132
+ version: '2.0'
133
+ description: Matt help monitoring data points for better business decision.
134
+ email: blambeau@gmail.com
135
+ executables:
136
+ - matt
137
+ extensions: []
138
+ extra_rdoc_files: []
139
+ files:
140
+ - Gemfile
141
+ - README.md
142
+ - bin/matt
143
+ - lib/matt.rb
144
+ - lib/matt/command.rb
145
+ - lib/matt/configuration.rb
146
+ - lib/matt/datasource.rb
147
+ - lib/matt/datasource/sql.rb
148
+ - lib/matt/exporter.rb
149
+ - lib/matt/exporter/sql.rb
150
+ - lib/matt/measure.rb
151
+ - lib/matt/support.rb
152
+ - lib/matt/support/puts.rb
153
+ - lib/matt/version.rb
154
+ homepage: http://github.com/enspirit/matt
155
+ licenses:
156
+ - MIT
157
+ metadata: {}
158
+ post_install_message:
159
+ rdoc_options: []
160
+ require_paths:
161
+ - lib
162
+ required_ruby_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ required_rubygems_version: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - ">="
170
+ - !ruby/object:Gem::Version
171
+ version: '0'
172
+ requirements: []
173
+ rubygems_version: 3.2.15
174
+ signing_key:
175
+ specification_version: 4
176
+ summary: Matt help monitoring data points for better business decision.
177
+ test_files: []