matt 1.0.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.
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: []