feature_creep 0.0.1

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.
@@ -0,0 +1,24 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
19
+
20
+ *~
21
+ \#*
22
+ .\#*
23
+
24
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
@@ -0,0 +1,48 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ feature_creep (0.0.1)
5
+ redis
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ bourne (1.0)
11
+ mocha (= 0.9.8)
12
+ coderay (1.0.7)
13
+ diff-lcs (1.1.3)
14
+ git (1.2.5)
15
+ jeweler (1.6.4)
16
+ bundler (~> 1.0)
17
+ git (>= 1.2.5)
18
+ rake
19
+ method_source (0.8)
20
+ mocha (0.9.8)
21
+ rake
22
+ pry (0.9.10)
23
+ coderay (~> 1.0.5)
24
+ method_source (~> 0.8)
25
+ slop (~> 3.3.1)
26
+ rake (0.9.2.2)
27
+ redis (3.0.2)
28
+ rspec (2.10.0)
29
+ rspec-core (~> 2.10.0)
30
+ rspec-expectations (~> 2.10.0)
31
+ rspec-mocks (~> 2.10.0)
32
+ rspec-core (2.10.1)
33
+ rspec-expectations (2.10.0)
34
+ diff-lcs (~> 1.1.3)
35
+ rspec-mocks (2.10.1)
36
+ slop (3.3.3)
37
+
38
+ PLATFORMS
39
+ ruby
40
+
41
+ DEPENDENCIES
42
+ bourne (= 1.0)
43
+ bundler (>= 1.0.0)
44
+ feature_creep!
45
+ jeweler (~> 1.6.4)
46
+ mocha (= 0.9.8)
47
+ pry (= 0.9.10)
48
+ rspec (~> 2.10.0)
data/License ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2009 James Golick
2
+ Copyright (c) 2012 Christian Trosclair
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,25 @@
1
+ feature_creep
2
+ =============
3
+
4
+ I've got your feature flags implementation right here.
5
+
6
+ This is a fork/rewrite of James Golick's Rollout with messy and incomplete docs.
7
+
8
+ So far, the specs are green and are mostly inherited from Rollout.
9
+ This is not a drop in replacement. The API has aleadey changed.
10
+
11
+ Namely, `groups` are now `scopes` and `users` are `agent_ids`.
12
+ Agent_ids are expected to be uuids, not the object itself.
13
+
14
+ The class constructor now takes a datastore and an options hash.
15
+
16
+ options[:info] should be a lambda that returns a hash when called
17
+
18
+ options[:warden] is also a lambda that encapsulates the business logic for FeatureCreep#active?
19
+
20
+ options[:scopes] is a hash that expects a string key and a lambda that encapsulates the business logic for membership in a scope
21
+
22
+
23
+ At this point it should be easy to extend.
24
+ I would expect the API to stablize over the next couple of weeks.
25
+
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "feature_creep/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "feature_creep"
7
+ s.version = FeatureCreep::VERSION
8
+ s.authors = ["Christian Trosclair"]
9
+ s.email = ["xn@gotham.us"]
10
+ s.description = "Feature Flag implementation"
11
+ s.summary = "Extensible Feature Flags"
12
+ s.homepage = "https://github.com/xn/feature_creep"
13
+
14
+ s.require_paths = ["lib"]
15
+
16
+ s.rubyforge_project = "feature_creep"
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ["lib"]
22
+
23
+ # specify any dependencies here; for example:
24
+ s.add_development_dependency "rspec", "~> 2.10.0"
25
+ s.add_development_dependency "bundler", ">= 1.0.0"
26
+ s.add_development_dependency "jeweler", "~> 1.6.4"
27
+ s.add_development_dependency "bourne", "1.0"
28
+ s.add_development_dependency "mocha", "0.9.8"
29
+ s.add_development_dependency "pry", "0.9.10"
30
+
31
+ s.add_runtime_dependency "redis"
32
+ end
@@ -0,0 +1,131 @@
1
+ class FeatureCreep
2
+
3
+ attr_accessor :scopes, :info, :warden
4
+
5
+ def initialize(datastore, options = {})
6
+ @datastore = datastore
7
+ @scopes = {"all" => lambda { |agent_id| true }}
8
+ if options.has_key?(:scopes)
9
+ options[:scopes].each do |name,membership_block|
10
+ @scopes[name.to_s] = membership_block
11
+ end
12
+ end
13
+ @warden = if options.has_key?(:warden)
14
+ options[:warden]
15
+ else
16
+ lambda { |feature,agent_id|
17
+ if agent_id
18
+ active_globally?(feature) ||
19
+ agent_id_in_active_scope?(feature, agent_id) ||
20
+ agent_id_active?(feature, agent_id) ||
21
+ agent_id_within_active_percentage?(feature, agent_id)
22
+ else
23
+ active_globally?(feature)
24
+ end
25
+ }
26
+ end
27
+ @info = if options.has_key?(:info)
28
+ options[:info]
29
+ else lambda { |feature|
30
+ if feature
31
+ {
32
+ :percentage => (active_percentage(feature) || 0).to_i,
33
+ :scopes => active_scopes(feature).map { |g| g.to_sym },
34
+ :agent_ids => active_agent_id_ids(feature),
35
+ :global => active_global_features
36
+ }
37
+ else
38
+ {
39
+ :global => active_global_features
40
+ }
41
+ end
42
+ }
43
+ end
44
+ end
45
+
46
+ def activate_globally(feature)
47
+ @datastore.activate_globally(feature)
48
+ end
49
+
50
+ def deactivate_globally(feature)
51
+ @datastore.deactivate_globally(feature)
52
+ end
53
+
54
+ def activate_scope(feature, scope)
55
+ @datastore.activate_scope(feature, scope)
56
+ end
57
+
58
+ def deactivate_scope(feature, scope)
59
+ @datastore.deactivate_scope(feature, scope)
60
+ end
61
+
62
+ def deactivate_all(feature)
63
+ @datastore.deactivate_all(feature)
64
+ end
65
+
66
+ def activate_agent_id(feature, agent_id)
67
+ @datastore.activate_agent_id(feature, agent_id)
68
+ end
69
+
70
+ def deactivate_agent_id(feature, agent_id)
71
+ @datastore.deactivate_agent_id(feature, agent_id)
72
+ end
73
+
74
+ def active?(feature, agent_id = nil)
75
+ @warden.call(feature,agent_id)
76
+ end
77
+
78
+ def activate_percentage(feature, percentage)
79
+ @datastore.activate_percentage(feature, percentage)
80
+ end
81
+
82
+ def deactivate_percentage(feature)
83
+ @datastore.deactivate_percentage(feature)
84
+ end
85
+
86
+ def current_features
87
+ @datastore.current_features
88
+ end
89
+
90
+ def add_feature(feature)
91
+ @datastore.add_feature(feature)
92
+ end
93
+
94
+ def info(feature = nil)
95
+ @info.call(feature)
96
+ end
97
+
98
+ def active_scopes(feature)
99
+ @datastore.active_scopes(feature)
100
+ end
101
+
102
+ def active_agent_id_ids(feature)
103
+ @datastore.active_agent_id_ids(feature)
104
+ end
105
+
106
+ def active_global_features
107
+ @datastore.active_global_features
108
+ end
109
+
110
+ def active_percentage(feature)
111
+ @datastore.active_percentage(feature)
112
+ end
113
+
114
+ def active_globally?(feature)
115
+ @datastore.active_globally?(feature)
116
+ end
117
+
118
+ def agent_id_in_active_scope?(feature, agent_id)
119
+ active_scopes(feature).any? do |scope|
120
+ @scopes.key?(scope) && @scopes[scope].call(agent_id)
121
+ end
122
+ end
123
+
124
+ def agent_id_active?(feature, agent_id)
125
+ @datastore.agent_id_active?(feature, agent_id)
126
+ end
127
+
128
+ def agent_id_within_active_percentage?(feature, agent_id)
129
+ @datastore.agent_id_within_active_percentage?(feature, agent_id)
130
+ end
131
+ end
@@ -0,0 +1,133 @@
1
+ class FeatureCreep
2
+ class RedisDataStore
3
+
4
+ def initialize(datastore = nil, key_prefix = "feature_creep")
5
+ @key_prefix = key_prefix
6
+ @redis = datastore || Redis.new
7
+ end
8
+
9
+ def activate_globally(feature)
10
+ @redis.sadd(global_key, feature)
11
+ end
12
+
13
+ def deactivate_globally(feature)
14
+ @redis.srem(global_key, feature)
15
+ end
16
+
17
+ def activate_scope(feature, scope)
18
+ @redis.sadd(scope_key(feature), scope)
19
+ end
20
+
21
+ def deactivate_scope(feature, scope)
22
+ @redis.srem(scope_key(feature), scope)
23
+ end
24
+
25
+ def deactivate_all(feature)
26
+ @redis.del(scope_key(feature))
27
+ @redis.del(agent_id_key(feature))
28
+ @redis.del(percentage_key(feature))
29
+ deactivate_globally(feature)
30
+ end
31
+
32
+ def activate_agent_id(feature, agent_id)
33
+ @redis.sadd(agent_id_key(feature), agent_id)
34
+ end
35
+
36
+ def deactivate_agent_id(feature, agent_id)
37
+ @redis.srem(agent_id_key(feature), agent_id)
38
+ end
39
+
40
+ def active?(feature, agent_id = nil)
41
+ if agent_id
42
+ active_globally?(feature) ||
43
+ agent_id_in_active_scope?(feature, agent_id) ||
44
+ agent_id_active?(feature, agent_id) ||
45
+ agent_id_within_active_percentage?(feature, agent_id)
46
+ else
47
+ active_globally?(feature)
48
+ end
49
+ end
50
+
51
+ def activate_percentage(feature, percentage)
52
+ @redis.set(percentage_key(feature), percentage)
53
+ end
54
+
55
+ def deactivate_percentage(feature)
56
+ @redis.del(percentage_key(feature))
57
+ end
58
+
59
+ def info(feature = nil)
60
+ if feature
61
+ {
62
+ :percentage => (active_percentage(feature) || 0).to_i,
63
+ :scopes => active_scopes(feature).map { |g| g.to_sym },
64
+ :agent_ids => active_agent_id_ids(feature),
65
+ :global => active_global_features
66
+ }
67
+ else
68
+ {
69
+ :features => current_features
70
+ }
71
+ end
72
+ end
73
+
74
+ def active_scopes(feature)
75
+ @redis.smembers(scope_key(feature)) || []
76
+ end
77
+
78
+ def active_agent_id_ids(feature)
79
+ @redis.smembers(agent_id_key(feature)).map { |id| id.to_i }
80
+ end
81
+
82
+ def active_global_features
83
+ (@redis.smembers(global_key) || []).map(&:to_sym)
84
+ end
85
+
86
+ def active_percentage(feature)
87
+ @redis.get(percentage_key(feature))
88
+ end
89
+
90
+ def active_globally?(feature)
91
+ @redis.sismember(global_key, feature)
92
+ end
93
+
94
+ def agent_id_active?(feature, agent_id)
95
+ @redis.sismember(agent_id_key(feature), agent_id)
96
+ end
97
+
98
+ def agent_id_within_active_percentage?(feature, agent_id)
99
+ percentage = active_percentage(feature)
100
+ return false if percentage.nil?
101
+ agent_id % 100 < percentage.to_i
102
+ end
103
+
104
+ def current_features
105
+ @redis.smembers(@key_prefix)
106
+ end
107
+
108
+ def add_feature(feature)
109
+ redis.sadd(@key_prefix, feature)
110
+ end
111
+
112
+ private
113
+ def key(name)
114
+ "#{@key_prefix}:#{name}"
115
+ end
116
+
117
+ def scope_key(name)
118
+ "#{key(name)}:scopes"
119
+ end
120
+
121
+ def agent_id_key(name)
122
+ "#{key(name)}:agent_ids"
123
+ end
124
+
125
+ def percentage_key(name)
126
+ "#{key(name)}:percentage"
127
+ end
128
+
129
+ def global_key
130
+ "#{@key_prefix}:__global__"
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,3 @@
1
+ class FeatureCreep
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,222 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "FeatureCreep" do
4
+ before do
5
+ @datastore = FeatureCreep::RedisDataStore.new
6
+ scopes = {
7
+ :fivesonly => lambda { |agent_id| agent_id == 5 }
8
+ }
9
+ @feature_creep = FeatureCreep.new(@datastore, {:scopes => scopes})
10
+ end
11
+
12
+ describe "when a scope is activated" do
13
+ before do
14
+ @feature_creep.activate_scope(:chat, :fivesonly)
15
+ end
16
+
17
+ it "the feature is active for agent_ids for which the block evaluates to true" do
18
+ @feature_creep.should be_active(:chat, 5)
19
+ end
20
+
21
+ it "is not active for agent_ids for which the block evaluates to false" do
22
+ @feature_creep.should_not be_active(:chat, 1)
23
+ end
24
+
25
+ it "is not active if a scope is found in Redis but not defined in FeatureCreep" do
26
+ @feature_creep.activate_scope(:chat, :fake)
27
+ @feature_creep.should_not be_active(:chat, 1)
28
+ end
29
+ end
30
+
31
+ describe "the default all scope" do
32
+ before do
33
+ @feature_creep.activate_scope(:chat, :all)
34
+ end
35
+
36
+ it "evaluates to true no matter what" do
37
+ @feature_creep.should be_active(:chat, 0)
38
+ end
39
+ end
40
+
41
+ describe "deactivating a scope" do
42
+ before do
43
+ @feature_creep.activate_scope(:chat, :all)
44
+ @feature_creep.activate_scope(:chat, :fivesonly)
45
+ @feature_creep.deactivate_scope(:chat, :all)
46
+ end
47
+
48
+ it "deactivates the rules for that scope" do
49
+ @feature_creep.should_not be_active(:chat, 10)
50
+ end
51
+
52
+ it "leaves the other scopes active" do
53
+ @feature_creep.should be_active(:chat, 5)
54
+ end
55
+ end
56
+
57
+ describe "deactivating a feature completely" do
58
+ before do
59
+ @feature_creep.activate_scope(:chat, :all)
60
+ @feature_creep.activate_scope(:chat, :fivesonly)
61
+ @feature_creep.activate_agent_id(:chat, 51)
62
+ @feature_creep.activate_percentage(:chat, 100)
63
+ @feature_creep.activate_globally(:chat)
64
+ @feature_creep.deactivate_all(:chat)
65
+ end
66
+
67
+ it "removes all of the scopes" do
68
+ @feature_creep.should_not be_active(:chat, 0)
69
+ end
70
+
71
+ it "removes all of the agent_ids" do
72
+ @feature_creep.should_not be_active(:chat, 51)
73
+ end
74
+
75
+ it "removes the percentage" do
76
+ @feature_creep.should_not be_active(:chat, 24)
77
+ end
78
+
79
+ it "removes globally" do
80
+ @feature_creep.should_not be_active(:chat)
81
+ end
82
+ end
83
+
84
+ describe "activating a specific agent_id" do
85
+ before do
86
+ @feature_creep.activate_agent_id(:chat, 42)
87
+ end
88
+
89
+ it "is active for that agent_id" do
90
+ @feature_creep.should be_active(:chat, 42)
91
+ end
92
+
93
+ it "remains inactive for other agent_ids" do
94
+ @feature_creep.should_not be_active(:chat, 24)
95
+ end
96
+ end
97
+
98
+ describe "deactivating a specific agent_id" do
99
+ before do
100
+ @feature_creep.activate_agent_id(:chat, 42)
101
+ @feature_creep.activate_agent_id(:chat, 24)
102
+ @feature_creep.deactivate_agent_id(:chat, 42)
103
+ end
104
+
105
+ it "that agent_id should no longer be active" do
106
+ @feature_creep.should_not be_active(:chat, 42)
107
+ end
108
+
109
+ it "remains active for other active agent_ids" do
110
+ @feature_creep.should be_active(:chat, 24)
111
+ end
112
+ end
113
+
114
+ describe "activating a feature globally" do
115
+ before do
116
+ @feature_creep.activate_globally(:chat)
117
+ end
118
+
119
+ it "activates the feature" do
120
+ @feature_creep.should be_active(:chat)
121
+ end
122
+ end
123
+
124
+ describe "activating a feature for a percentage of agent_ids" do
125
+ before do
126
+ @feature_creep.activate_percentage(:chat, 20)
127
+ end
128
+
129
+ it "activates the feature for that percentage of the agent_ids" do
130
+ (1..120).select { |id| @feature_creep.active?(:chat, id) }.length.should == 39
131
+ end
132
+ end
133
+
134
+ describe "activating a feature for a percentage of agent_ids" do
135
+ before do
136
+ @feature_creep.activate_percentage(:chat, 20)
137
+ end
138
+
139
+ it "activates the feature for that percentage of the agent_ids" do
140
+ (1..200).select { |id| @feature_creep.active?(:chat, id) }.length.should == 40
141
+ end
142
+ end
143
+
144
+ describe "activating a feature for a percentage of agent_ids" do
145
+ before do
146
+ @feature_creep.activate_percentage(:chat, 5)
147
+ end
148
+
149
+ it "activates the feature for that percentage of the agent_ids" do
150
+ (1..100).select { |id| @feature_creep.active?(:chat, id) }.length.should == 5
151
+ end
152
+ end
153
+
154
+
155
+ describe "deactivating the percentage of agent_ids" do
156
+ before do
157
+ @feature_creep.activate_percentage(:chat, 100)
158
+ @feature_creep.deactivate_percentage(:chat)
159
+ end
160
+
161
+ it "becomes inactivate for all agent_ids" do
162
+ @feature_creep.should_not be_active(:chat, 24)
163
+ end
164
+ end
165
+
166
+ describe "deactivating the feature globally" do
167
+ before do
168
+ @feature_creep.activate_globally(:chat)
169
+ @feature_creep.deactivate_globally(:chat)
170
+ end
171
+
172
+ it "becomes inactivate" do
173
+ @feature_creep.should_not be_active(:chat)
174
+ end
175
+ end
176
+
177
+ describe "#info" do
178
+ context "global features" do
179
+ let(:features) { [:signup, :chat, :table] }
180
+
181
+ before do
182
+ features.each do |f|
183
+ @feature_creep.activate_globally(f)
184
+ end
185
+ end
186
+
187
+ it "returns all global features" do
188
+ @feature_creep.info.should eq({ :global => features.reverse })
189
+ end
190
+ end
191
+
192
+ describe "with a percentage set" do
193
+ before do
194
+ @feature_creep.activate_percentage(:chat, 10)
195
+ @feature_creep.activate_scope(:chat, :caretakers)
196
+ @feature_creep.activate_scope(:chat, :greeters)
197
+ @feature_creep.activate_globally(:signup)
198
+ @feature_creep.activate_agent_id(:chat, 42)
199
+ end
200
+
201
+ it "returns info about all the activations" do
202
+ @feature_creep.info(:chat).should == {
203
+ :percentage => 10,
204
+ :scopes => [:greeters, :caretakers],
205
+ :agent_ids => [42],
206
+ :global => [:signup]
207
+ }
208
+ end
209
+ end
210
+
211
+ describe "without a percentage set" do
212
+ it "defaults to 0" do
213
+ @feature_creep.info(:chat).should == {
214
+ :percentage => 0,
215
+ :scopes => [],
216
+ :agent_ids => [],
217
+ :global => []
218
+ }
219
+ end
220
+ end
221
+ end
222
+ end
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'feature_creep'
4
+ require 'feature_creep/redis_datastore'
5
+ require 'rspec'
6
+ require 'bourne'
7
+ require 'redis'
8
+
9
+ RSpec.configure do |config|
10
+ config.mock_with :mocha
11
+ config.before { Redis.new.flushdb }
12
+ end
metadata ADDED
@@ -0,0 +1,172 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: feature_creep
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Christian Trosclair
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 2.10.0
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 2.10.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: bundler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 1.0.0
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 1.0.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: jeweler
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 1.6.4
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 1.6.4
62
+ - !ruby/object:Gem::Dependency
63
+ name: bourne
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - '='
68
+ - !ruby/object:Gem::Version
69
+ version: '1.0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - '='
76
+ - !ruby/object:Gem::Version
77
+ version: '1.0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: mocha
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - '='
84
+ - !ruby/object:Gem::Version
85
+ version: 0.9.8
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - '='
92
+ - !ruby/object:Gem::Version
93
+ version: 0.9.8
94
+ - !ruby/object:Gem::Dependency
95
+ name: pry
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - '='
100
+ - !ruby/object:Gem::Version
101
+ version: 0.9.10
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - '='
108
+ - !ruby/object:Gem::Version
109
+ version: 0.9.10
110
+ - !ruby/object:Gem::Dependency
111
+ name: redis
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: Feature Flag implementation
127
+ email:
128
+ - xn@gotham.us
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - .gitignore
134
+ - Gemfile
135
+ - Gemfile.lock
136
+ - License
137
+ - README.md
138
+ - feature_creep.gemspec
139
+ - lib/feature_creep.rb
140
+ - lib/feature_creep/redis_datastore.rb
141
+ - lib/feature_creep/version.rb
142
+ - spec/feature_creep_spec.rb
143
+ - spec/spec.opts
144
+ - spec/spec_helper.rb
145
+ homepage: https://github.com/xn/feature_creep
146
+ licenses: []
147
+ post_install_message:
148
+ rdoc_options: []
149
+ require_paths:
150
+ - lib
151
+ required_ruby_version: !ruby/object:Gem::Requirement
152
+ none: false
153
+ requirements:
154
+ - - ! '>='
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
158
+ none: false
159
+ requirements:
160
+ - - ! '>='
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ requirements: []
164
+ rubyforge_project: feature_creep
165
+ rubygems_version: 1.8.24
166
+ signing_key:
167
+ specification_version: 3
168
+ summary: Extensible Feature Flags
169
+ test_files:
170
+ - spec/feature_creep_spec.rb
171
+ - spec/spec.opts
172
+ - spec/spec_helper.rb