goalkeeper 0.0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +21 -0
- data/Rakefile +2 -0
- data/goalkeeper.gemspec +2 -2
- data/lib/goalkeeper.rb +117 -31
- data/lib/goalkeeper/version.rb +1 -1
- data/test/goalkeeper_test.rb +102 -59
- data/test/support/redis_instance.rb +136 -0
- data/test/test_helper.rb +6 -0
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 151b87201e8fbaa657d6aad6ef7aed33f8bf328a
|
4
|
+
data.tar.gz: 5a2b70b08cc0aa919d13cd3283300240d1895cdc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2bb9d5d7819f6f778525ce4d1fbb638ac986f4a3644b26c306e07292396d8207adbb9499421fd33ce5f17034f03dc71c97cb3e893be537b3e761af16da1b3270
|
7
|
+
data.tar.gz: 55e43ca0fe7926ef24984da40b29f82af3dd8e9ff48d111b01e932e130a73dc9379ec16b59044d2802b6d1bdaf3bdbd016fde60bef2863cb0f65f8ac85c4dccd
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 John Weir, Pharos Enterprise Intelligence LLC
|
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/Rakefile
CHANGED
data/goalkeeper.gemspec
CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["John Weir"]
|
10
10
|
spec.email = ["john@smokinggun.com"]
|
11
11
|
spec.summary = %q{A Todo App for your application.}
|
12
|
-
spec.description = %q{Goalkeeper is a system for
|
13
|
-
spec.homepage = ""
|
12
|
+
spec.description = %q{Goalkeeper is a system for verifing if specific goals have been met by an application.}
|
13
|
+
spec.homepage = "https://github.com/jweir/goalkeeper"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
data/lib/goalkeeper.rb
CHANGED
@@ -1,17 +1,101 @@
|
|
1
1
|
require "goalkeeper/version"
|
2
2
|
require 'forwardable'
|
3
3
|
require 'redis'
|
4
|
-
|
4
|
+
require 'time' # for Time.parse
|
5
|
+
|
6
|
+
# Goalkeeper provides methods to track if specific events(Goals) have been completed(met).
|
7
|
+
#
|
8
|
+
# It is not a complicated system and it is easy enough to roll your own. This
|
9
|
+
# is an extraction from a system Pharos EI has been using.
|
10
|
+
#
|
11
|
+
# A Goal is just a unique string. It is up to your application to
|
12
|
+
# define any schema for the Goal's label.
|
13
|
+
#
|
14
|
+
# For example you might have your Goals labeled by date and company id:
|
15
|
+
# "job:2016-01-17:company:7"
|
16
|
+
#
|
17
|
+
# When a Goal is met a record is created in Redis with a timestamp, this is the only
|
18
|
+
# persistent layer.
|
19
|
+
# Goalkeeper.met!("jobkey")
|
20
|
+
# # or
|
21
|
+
# Goalkeeper::Goal.new("jobkey").met!
|
22
|
+
#
|
23
|
+
# To check if a Goal as been met
|
24
|
+
# Goalkeeper::Goal.new("jobkey").met?
|
25
|
+
#
|
26
|
+
# Customize the redis client by setting it in your application
|
27
|
+
# Goalkeeper.redis = your_redis_client
|
28
|
+
#
|
29
|
+
# Each record has a default expiration of 24 hours, but this can be modified.
|
30
|
+
# Goalkeeper.expiration = number_of_seconds
|
31
|
+
#
|
32
|
+
# Redis keys are stored under the default namespace of "Goalkeeper:". The namespace can be configured:
|
33
|
+
#
|
34
|
+
# Goalkeeper.namespace = string
|
35
|
+
#
|
5
36
|
class Goalkeeper
|
6
37
|
|
38
|
+
# Set the Redis client to a non default setting
|
39
|
+
def self.redis=(redis)
|
40
|
+
@redis = redis
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.redis
|
44
|
+
@redis ||= Redis.new
|
45
|
+
end
|
46
|
+
|
7
47
|
# Creates a persistent Goal market with the given label.
|
8
48
|
def self.met!(label)
|
9
49
|
Goal.new(label).met!
|
10
50
|
end
|
11
51
|
|
52
|
+
# The TTL set for each met Goal record created in Redis
|
53
|
+
# Default is 24 hours
|
54
|
+
def self.expiration
|
55
|
+
@expiration ||= 24 * 60 * 60
|
56
|
+
end
|
57
|
+
|
58
|
+
# Overwrite the default expiration
|
59
|
+
def self.expiration=(number_of_seconds)
|
60
|
+
@expiration = number_of_seconds
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.namespace
|
64
|
+
@namespace ||= "Goalkeeper"
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.namespace=(ns)
|
68
|
+
@namespace = ns
|
69
|
+
end
|
70
|
+
|
71
|
+
# List is a collection of Goals to simplify tracking multiple goals.
|
72
|
+
#
|
73
|
+
# Create a new list
|
74
|
+
# mylist = Goalkeeper::List.new
|
75
|
+
#
|
76
|
+
# Add Goals you want to check for completion
|
77
|
+
# mylist.add("job1").add("job2")
|
78
|
+
# mylist.size
|
79
|
+
# #=> 2
|
80
|
+
#
|
81
|
+
# Check if all the goals are completed
|
82
|
+
# mylist.met?
|
83
|
+
# #=> false
|
84
|
+
#
|
85
|
+
# Get the unmet Goals
|
86
|
+
# mylist.unmet
|
87
|
+
# #=> [...]
|
88
|
+
#
|
89
|
+
# Get the met Goals
|
90
|
+
# mylist.met
|
91
|
+
# #=> [...]
|
92
|
+
#
|
93
|
+
# Iterate all Goals
|
94
|
+
# myslist.each {|goal| ...}
|
95
|
+
# myslist.map {|goal| ...}
|
12
96
|
class List
|
13
97
|
extend Forwardable
|
14
|
-
def_delegators :@list, :size, :[]
|
98
|
+
def_delegators :@list, :size, :[], :each, :map
|
15
99
|
|
16
100
|
def initialize
|
17
101
|
@list = []
|
@@ -24,6 +108,7 @@ class Goalkeeper
|
|
24
108
|
self
|
25
109
|
end
|
26
110
|
|
111
|
+
# met? returns true if all Goals in the set have been met.
|
27
112
|
def met?
|
28
113
|
unmet.empty?
|
29
114
|
end
|
@@ -38,61 +123,62 @@ class Goalkeeper
|
|
38
123
|
end
|
39
124
|
|
40
125
|
class Goal
|
126
|
+
# The unique label to identify this Goal
|
41
127
|
attr_reader :label
|
128
|
+
|
129
|
+
# An optional object refrence which allows an application author to
|
130
|
+
# associate this goal to an object. The +ref+ is not used by Goalkeeper.
|
42
131
|
attr_reader :ref
|
43
132
|
|
44
|
-
#
|
133
|
+
# the TTL value for the Redis record. Defalts to Goalkeeper.expiration
|
134
|
+
attr_reader :expiration
|
135
|
+
|
136
|
+
# +label+ is a unique string to identify this Goal.
|
45
137
|
# There is no checking if it is truly unique.
|
46
138
|
#
|
47
139
|
# +ref+ is an optional reference to any object. This
|
48
140
|
# would be used by the end user's application.
|
49
|
-
|
141
|
+
#
|
142
|
+
# +expiration+ can be set to override the gobal expiratin.
|
143
|
+
def initialize(label, ref: nil, expiration: Goalkeeper.expiration)
|
50
144
|
@label = label
|
51
145
|
@ref = ref
|
146
|
+
@expiration = expiration
|
52
147
|
end
|
53
148
|
|
54
149
|
def met!
|
55
|
-
|
150
|
+
write
|
56
151
|
self
|
57
152
|
end
|
58
153
|
|
59
154
|
def met?
|
60
|
-
!
|
155
|
+
! read.nil?
|
61
156
|
end
|
62
|
-
end
|
63
|
-
|
64
|
-
class Store
|
65
|
-
EXPIRATION = 60 * 60 * 24 # 1 day
|
66
157
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
158
|
+
# Time the goal was completed.
|
159
|
+
# WARNING retuns nil if the job is not met
|
160
|
+
def met_at
|
161
|
+
if met?
|
162
|
+
Time.parse(read)
|
163
|
+
else
|
164
|
+
nil
|
165
|
+
end
|
71
166
|
end
|
72
167
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
end
|
77
|
-
|
78
|
-
def self.remove(label)
|
79
|
-
nl = ns(label)
|
80
|
-
client.del nl
|
168
|
+
# a namespaced key for the goal
|
169
|
+
def key
|
170
|
+
"#{Goalkeeper.namespace}:#{label}"
|
81
171
|
end
|
82
172
|
|
83
173
|
protected
|
84
174
|
|
85
|
-
def
|
86
|
-
|
175
|
+
def write
|
176
|
+
Goalkeeper.redis.set(self.key, Time.now)
|
177
|
+
Goalkeeper.redis.expire(self.key, self.expiration)
|
87
178
|
end
|
88
179
|
|
89
|
-
def
|
90
|
-
|
180
|
+
def read
|
181
|
+
Goalkeeper.redis.get self.key
|
91
182
|
end
|
92
|
-
|
93
|
-
def self.client
|
94
|
-
@client ||= Redis.new
|
95
|
-
end
|
96
|
-
|
97
183
|
end
|
98
184
|
end
|
data/lib/goalkeeper/version.rb
CHANGED
data/test/goalkeeper_test.rb
CHANGED
@@ -1,23 +1,33 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
# API
|
4
|
-
# Goal.configure
|
5
|
-
# Goal.met label, ttl
|
6
|
-
# Goal::List.new
|
7
|
-
# .add
|
8
|
-
# .met
|
9
|
-
# .unmet
|
10
|
-
# .met?
|
11
|
-
#
|
12
|
-
# configure
|
13
|
-
# redis client
|
14
|
-
# namespace
|
15
|
-
#
|
16
|
-
# Goal -> score, completed, met
|
17
|
-
#
|
18
|
-
# was a Goal met?
|
19
|
-
# when was it met?
|
20
3
|
describe Goalkeeper do
|
4
|
+
before do
|
5
|
+
Goalkeeper.redis.flushdb
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "::redis" do
|
9
|
+
it "returns the Redis client" do
|
10
|
+
assert Goalkeeper.redis.is_a?(Redis)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "::namespace" do
|
15
|
+
it "defaults to Goalkeeper" do
|
16
|
+
assert_equal "Goalkeeper", Goalkeeper.namespace
|
17
|
+
end
|
18
|
+
|
19
|
+
it "can be user defined" do
|
20
|
+
ns = Goalkeeper.namespace
|
21
|
+
|
22
|
+
Goalkeeper.namespace = "NewNamespace"
|
23
|
+
assert_equal "NewNamespace", Goalkeeper.namespace
|
24
|
+
goal = Goalkeeper::Goal.new("x")
|
25
|
+
assert_equal "NewNamespace:x", goal.key
|
26
|
+
|
27
|
+
# reset
|
28
|
+
Goalkeeper.namespace = ns
|
29
|
+
end
|
30
|
+
end
|
21
31
|
|
22
32
|
describe Goalkeeper::List do
|
23
33
|
before do
|
@@ -25,7 +35,7 @@ describe Goalkeeper do
|
|
25
35
|
end
|
26
36
|
|
27
37
|
describe "#add" do
|
28
|
-
it "
|
38
|
+
it "creates a Goal" do
|
29
39
|
@goals.add("a:1")
|
30
40
|
assert_equal 1, @goals.size
|
31
41
|
assert_equal "a:1", @goals[0].label
|
@@ -42,61 +52,94 @@ describe Goalkeeper do
|
|
42
52
|
end
|
43
53
|
end
|
44
54
|
|
45
|
-
describe "
|
46
|
-
|
47
|
-
|
55
|
+
describe "with goals" do
|
56
|
+
before do
|
57
|
+
@goals.add("x").add("y")
|
58
|
+
end
|
48
59
|
|
49
|
-
|
50
|
-
|
51
|
-
|
60
|
+
describe "#met" do
|
61
|
+
it "returns all Goals which have been met" do
|
62
|
+
assert @goals.met.empty?
|
63
|
+
@goals[0].met!
|
64
|
+
assert_equal ["x"], @goals.met.map(&:label)
|
65
|
+
@goals[1].met!
|
66
|
+
assert_equal ["x","y"], @goals.met.map(&:label)
|
67
|
+
end
|
68
|
+
end
|
52
69
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
70
|
+
describe "#unmet" do
|
71
|
+
it "returns all Goals which have not been met" do
|
72
|
+
assert_equal ["x","y"], @goals.unmet.map(&:label)
|
73
|
+
@goals[0].met!
|
74
|
+
assert_equal ["y"], @goals.unmet.map(&:label)
|
75
|
+
@goals[1].met!
|
76
|
+
assert @goals.unmet.empty?
|
77
|
+
end
|
78
|
+
end
|
57
79
|
|
58
|
-
|
59
|
-
|
60
|
-
|
80
|
+
describe "#met?" do
|
81
|
+
it "is true when all Goals have been met" do
|
82
|
+
assert ! @goals.met?
|
83
|
+
@goals.each(&:met!)
|
84
|
+
assert @goals.met?
|
85
|
+
end
|
86
|
+
end
|
61
87
|
end
|
62
|
-
|
63
|
-
it "has a default ttl expiration"
|
64
|
-
it "takes an optional at: timestamp"
|
65
|
-
it "takes an optional ttl for expiration"
|
66
88
|
end
|
67
89
|
|
68
|
-
describe
|
69
|
-
|
90
|
+
describe Goalkeeper::Goal do
|
91
|
+
before do
|
92
|
+
@goal = Goalkeeper::Goal.new("b")
|
93
|
+
end
|
70
94
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
95
|
+
it "has a label" do
|
96
|
+
assert_equal "b", @goal.label
|
97
|
+
end
|
75
98
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
Redis.new.flushdb
|
80
|
-
end
|
99
|
+
it "has a namespaced key" do
|
100
|
+
assert_equal "Goalkeeper:b", @goal.key
|
101
|
+
end
|
81
102
|
|
82
|
-
|
83
|
-
|
103
|
+
it "is met? if the label has a Redis record" do
|
104
|
+
assert ! @goal.met?
|
105
|
+
Goalkeeper.redis.set @goal.key, Time.now
|
106
|
+
assert @goal.met?
|
107
|
+
end
|
84
108
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
109
|
+
describe "met_at" do
|
110
|
+
it "is nil if the Goal is not met" do
|
111
|
+
assert_equal nil, @goal.met_at
|
112
|
+
end
|
89
113
|
|
90
|
-
|
114
|
+
it "is the timestamp that the Goal was met" do
|
115
|
+
@t = Time.parse(Time.now.to_s)
|
116
|
+
@goal.met!
|
117
|
+
assert_equal @t, @goal.met_at
|
118
|
+
end
|
119
|
+
end
|
91
120
|
|
92
|
-
|
93
|
-
|
121
|
+
describe "#met!" do
|
122
|
+
it "creates a Redis record" do
|
123
|
+
assert Goalkeeper.redis.get(@goal.key).nil?
|
124
|
+
@goal.met!
|
125
|
+
assert ! Goalkeeper.redis.get(@goal.key).nil?
|
126
|
+
end
|
94
127
|
|
95
|
-
|
128
|
+
it "has a default ttl expiration" do
|
129
|
+
@goal.met!
|
130
|
+
assert_equal @goal.expiration, Goalkeeper.redis.ttl(@goal.key)
|
131
|
+
end
|
132
|
+
end
|
96
133
|
|
97
|
-
|
134
|
+
describe "#expiration" do
|
135
|
+
it "has a default of 24 hours" do
|
136
|
+
assert_equal 24 * 60 * 60, @goal.expiration
|
137
|
+
end
|
98
138
|
|
99
|
-
|
139
|
+
it "can be set at initialization" do
|
140
|
+
g = Goalkeeper::Goal.new("x", expiration: 60)
|
141
|
+
assert_equal 60, g.expiration
|
142
|
+
end
|
143
|
+
end
|
100
144
|
end
|
101
145
|
end
|
102
|
-
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# RedisInstance is copied from
|
2
|
+
# https://github.com/resque/resque-scheduler/blob/master/test/support/redis_instance.rb
|
3
|
+
# released under an MIT license
|
4
|
+
# modifications have been made
|
5
|
+
require 'socket'
|
6
|
+
require 'timeout'
|
7
|
+
require 'fileutils'
|
8
|
+
|
9
|
+
class RedisInstance
|
10
|
+
class << self
|
11
|
+
@running = false
|
12
|
+
@port = nil
|
13
|
+
@pid = nil
|
14
|
+
@waiting = false
|
15
|
+
|
16
|
+
def run_if_needed!
|
17
|
+
run! unless @running
|
18
|
+
end
|
19
|
+
|
20
|
+
def run!
|
21
|
+
ensure_redis_server_present!
|
22
|
+
ensure_pid_directory
|
23
|
+
start_redis_server
|
24
|
+
post_boot_waiting_and_such
|
25
|
+
|
26
|
+
@running = true
|
27
|
+
client
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop!
|
31
|
+
$stdout.puts "Sending TERM to Redis (#{pid})..." if $stdout.tty?
|
32
|
+
Process.kill('TERM', pid)
|
33
|
+
|
34
|
+
@port = nil
|
35
|
+
@running = false
|
36
|
+
@pid = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def client
|
40
|
+
Redis.new(
|
41
|
+
hostname: '127.0.0.1', port: port, thread_safe: true
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def post_boot_waiting_and_such
|
48
|
+
wait_for_pid
|
49
|
+
puts "Booted isolated Redis on #{port} with PID #{pid}."
|
50
|
+
|
51
|
+
wait_for_redis_boot
|
52
|
+
|
53
|
+
# Ensure we tear down Redis on Ctrl+C / test failure.
|
54
|
+
at_exit { stop! }
|
55
|
+
end
|
56
|
+
|
57
|
+
def ensure_redis_server_present!
|
58
|
+
unless system('redis-server -v')
|
59
|
+
fail "** can't find `redis-server` in your path"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def wait_for_redis_boot
|
64
|
+
Timeout.timeout(10) do
|
65
|
+
loop do
|
66
|
+
begin
|
67
|
+
break if client.ping == "PONG"
|
68
|
+
rescue Redis::CannotConnectError
|
69
|
+
@waiting = true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
@waiting = false
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def ensure_pid_directory
|
77
|
+
FileUtils.mkdir_p(File.dirname(pid_file))
|
78
|
+
end
|
79
|
+
|
80
|
+
def start_redis_server
|
81
|
+
IO.popen('redis-server -', 'w+') do |server|
|
82
|
+
server.write(config)
|
83
|
+
server.close_write
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def pid
|
88
|
+
@pid ||= File.read(pid_file).to_i
|
89
|
+
end
|
90
|
+
|
91
|
+
def wait_for_pid
|
92
|
+
Timeout.timeout(10) do
|
93
|
+
loop { break if File.exist?(pid_file) }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def port
|
98
|
+
@port ||= random_port
|
99
|
+
end
|
100
|
+
|
101
|
+
def pid_file
|
102
|
+
'/tmp/redis-scheduler-test.pid'
|
103
|
+
end
|
104
|
+
|
105
|
+
def config
|
106
|
+
<<-EOF
|
107
|
+
daemonize yes
|
108
|
+
pidfile #{pid_file}
|
109
|
+
port #{port}
|
110
|
+
EOF
|
111
|
+
end
|
112
|
+
|
113
|
+
# Returns a random port in the upper (10000-65535) range.
|
114
|
+
def random_port
|
115
|
+
ports = (10_000..65_535).to_a
|
116
|
+
|
117
|
+
loop do
|
118
|
+
port = ports[rand(ports.size)]
|
119
|
+
return port if port_available?('127.0.0.1', port)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def port_available?(ip, port, seconds = 1)
|
124
|
+
Timeout.timeout(seconds) do
|
125
|
+
begin
|
126
|
+
TCPSocket.new(ip, port).close
|
127
|
+
false
|
128
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
129
|
+
true
|
130
|
+
end
|
131
|
+
end
|
132
|
+
rescue Timeout::Error
|
133
|
+
true
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: goalkeeper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: '0.2'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Weir
|
@@ -52,8 +52,8 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '10.0'
|
55
|
-
description: Goalkeeper is a system for
|
56
|
-
|
55
|
+
description: Goalkeeper is a system for verifing if specific goals have been met by
|
56
|
+
an application.
|
57
57
|
email:
|
58
58
|
- john@smokinggun.com
|
59
59
|
executables: []
|
@@ -62,6 +62,7 @@ extra_rdoc_files: []
|
|
62
62
|
files:
|
63
63
|
- ".gitignore"
|
64
64
|
- Gemfile
|
65
|
+
- LICENSE
|
65
66
|
- LICENSE.txt
|
66
67
|
- README.md
|
67
68
|
- Rakefile
|
@@ -69,8 +70,9 @@ files:
|
|
69
70
|
- lib/goalkeeper.rb
|
70
71
|
- lib/goalkeeper/version.rb
|
71
72
|
- test/goalkeeper_test.rb
|
73
|
+
- test/support/redis_instance.rb
|
72
74
|
- test/test_helper.rb
|
73
|
-
homepage:
|
75
|
+
homepage: https://github.com/jweir/goalkeeper
|
74
76
|
licenses:
|
75
77
|
- MIT
|
76
78
|
metadata: {}
|
@@ -96,4 +98,5 @@ specification_version: 4
|
|
96
98
|
summary: A Todo App for your application.
|
97
99
|
test_files:
|
98
100
|
- test/goalkeeper_test.rb
|
101
|
+
- test/support/redis_instance.rb
|
99
102
|
- test/test_helper.rb
|