feature_creep 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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