simple_feature_flags 1.2.0 → 1.4.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.
@@ -0,0 +1,332 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require 'yaml'
5
+
6
+ module SimpleFeatureFlags
7
+ # Abstract class for all storage adapters.
8
+ class BaseStorage
9
+ extend T::Sig
10
+ extend T::Helpers
11
+
12
+ abstract!
13
+
14
+ # Path to the file with feature flags
15
+ sig { abstract.returns(String) }
16
+ def file; end
17
+
18
+ sig { abstract.returns(T::Array[String]) }
19
+ def mandatory_flags; end
20
+
21
+ # Checks whether the flag is active. Returns `true`, `false`, `:globally` or `:partially`
22
+ sig { abstract.params(feature: T.any(Symbol, String)).returns(T.any(Symbol, T::Boolean)) }
23
+ def active(feature); end
24
+
25
+ # Checks whether the flag is active.
26
+ sig { abstract.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
27
+ def active?(feature); end
28
+
29
+ # Checks whether the flag is inactive.
30
+ sig { abstract.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
31
+ def inactive?(feature); end
32
+
33
+ # Checks whether the flag is active globally, for every object.
34
+ sig { abstract.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
35
+ def active_globally?(feature); end
36
+
37
+ # Checks whether the flag is inactive globally, for every object.
38
+ sig { abstract.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
39
+ def inactive_globally?(feature); end
40
+
41
+ # Checks whether the flag is active partially, only for certain objects.
42
+ sig { abstract.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
43
+ def active_partially?(feature); end
44
+
45
+ # Checks whether the flag is inactive partially, only for certain objects.
46
+ sig { abstract.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
47
+ def inactive_partially?(feature); end
48
+
49
+ # Checks whether the flag is active for the given object.
50
+ sig do
51
+ abstract
52
+ .params(
53
+ feature: T.any(Symbol, String),
54
+ object: Object,
55
+ object_id_method: Symbol,
56
+ )
57
+ .returns(T::Boolean)
58
+ end
59
+ def active_for?(feature, object, object_id_method: :id); end
60
+
61
+ # Checks whether the flag is inactive for the given object.
62
+ sig do
63
+ abstract
64
+ .params(
65
+ feature: T.any(Symbol, String),
66
+ object: Object,
67
+ object_id_method: Symbol,
68
+ )
69
+ .returns(T::Boolean)
70
+ end
71
+ def inactive_for?(feature, object, object_id_method: :id); end
72
+
73
+ # Checks whether the flag exists.
74
+ sig { abstract.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
75
+ def exists?(feature); end
76
+
77
+ # Returns the description of the flag if it exists.
78
+ sig { abstract.params(feature: T.any(Symbol, String)).returns(T.nilable(String)) }
79
+ def description(feature); end
80
+
81
+ # Calls the given block if the flag is active.
82
+ sig do
83
+ abstract
84
+ .params(
85
+ feature: T.any(Symbol, String),
86
+ block: T.proc.void,
87
+ ).void
88
+ end
89
+ def when_active(feature, &block); end
90
+
91
+ # Calls the given block if the flag is inactive.
92
+ sig do
93
+ abstract
94
+ .params(
95
+ feature: T.any(Symbol, String),
96
+ block: T.proc.void,
97
+ ).void
98
+ end
99
+ def when_inactive(feature, &block); end
100
+
101
+ # Calls the given block if the flag is active globally.
102
+ sig do
103
+ abstract
104
+ .params(
105
+ feature: T.any(Symbol, String),
106
+ block: T.proc.void,
107
+ ).void
108
+ end
109
+ def when_active_globally(feature, &block); end
110
+
111
+ # Calls the given block if the flag is inactive globally.
112
+ sig do
113
+ abstract
114
+ .params(
115
+ feature: T.any(Symbol, String),
116
+ block: T.proc.void,
117
+ ).void
118
+ end
119
+ def when_inactive_globally(feature, &block); end
120
+
121
+ # Calls the given block if the flag is active partially.
122
+ sig do
123
+ abstract
124
+ .params(
125
+ feature: T.any(Symbol, String),
126
+ block: T.proc.void,
127
+ ).void
128
+ end
129
+ def when_active_partially(feature, &block); end
130
+
131
+ # Calls the given block if the flag is inactive partially.
132
+ sig do
133
+ abstract
134
+ .params(
135
+ feature: T.any(Symbol, String),
136
+ block: T.proc.void,
137
+ ).void
138
+ end
139
+ def when_inactive_partially(feature, &block); end
140
+
141
+ # Calls the given block if the flag is active for the given object.
142
+ sig do
143
+ abstract
144
+ .params(
145
+ feature: T.any(Symbol, String),
146
+ object: Object,
147
+ object_id_method: Symbol,
148
+ block: T.proc.void,
149
+ ).void
150
+ end
151
+ def when_active_for(feature, object, object_id_method: CONFIG.default_id_method, &block); end
152
+
153
+ # Calls the given block if the flag is inactive for the given object.
154
+ sig do
155
+ abstract
156
+ .params(
157
+ feature: T.any(Symbol, String),
158
+ object: Object,
159
+ object_id_method: Symbol,
160
+ block: T.proc.void,
161
+ ).void
162
+ end
163
+ def when_inactive_for(feature, object, object_id_method: CONFIG.default_id_method, &block); end
164
+
165
+ # Activates the given flag. Returns `false` if it does not exist.
166
+ sig { abstract.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
167
+ def activate(feature); end
168
+
169
+ # Activates the flag, calls the block and restores the previous state of the flag.
170
+ sig do
171
+ abstract
172
+ .type_parameters(:R)
173
+ .params(
174
+ feature: T.any(Symbol, String),
175
+ block: T.proc.returns(T.type_parameter(:R)),
176
+ )
177
+ .returns(T.type_parameter(:R))
178
+ end
179
+ def do_activate(feature, &block); end
180
+
181
+ # Activates the given flag globally. Returns `false` if it does not exist.
182
+ sig { abstract.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
183
+ def activate_globally(feature); end
184
+
185
+ # Activates the flag globally, calls the block and restores the previous state of the flag.
186
+ sig do
187
+ abstract
188
+ .type_parameters(:R)
189
+ .params(
190
+ feature: T.any(Symbol, String),
191
+ block: T.proc.returns(T.type_parameter(:R)),
192
+ )
193
+ .returns(T.type_parameter(:R))
194
+ end
195
+ def do_activate_globally(feature, &block); end
196
+
197
+ # Activates the given flag partially. Returns `false` if it does not exist.
198
+ sig { abstract.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
199
+ def activate_partially(feature); end
200
+
201
+ # Activates the flag partially, calls the block and restores the previous state of the flag.
202
+ sig do
203
+ abstract
204
+ .type_parameters(:R)
205
+ .params(
206
+ feature: T.any(Symbol, String),
207
+ block: T.proc.returns(T.type_parameter(:R)),
208
+ )
209
+ .returns(T.type_parameter(:R))
210
+ end
211
+ def do_activate_partially(feature, &block); end
212
+
213
+ # Activates the given flag for the given objects. Returns `false` if it does not exist.
214
+ sig do
215
+ abstract
216
+ .params(
217
+ feature: T.any(Symbol, String),
218
+ objects: Object,
219
+ object_id_method: Symbol,
220
+ ).void
221
+ end
222
+ def activate_for(feature, *objects, object_id_method: CONFIG.default_id_method); end
223
+
224
+ # Activates the given flag for the given objects and sets the flag as partially active.
225
+ # Returns `false` if it does not exist.
226
+ sig do
227
+ abstract
228
+ .params(
229
+ feature: T.any(Symbol, String),
230
+ objects: Object,
231
+ object_id_method: Symbol,
232
+ ).void
233
+ end
234
+ def activate_for!(feature, *objects, object_id_method: CONFIG.default_id_method); end
235
+
236
+ # Deactivates the given flag for all objects.
237
+ # Resets the list of objects that this flag has been turned on for.
238
+ # Returns `false` if it does not exist.
239
+ sig { abstract.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
240
+ def deactivate!(feature); end
241
+
242
+ # Deactivates the given flag globally.
243
+ # Does not reset the list of objects that this flag has been turned on for.
244
+ # Returns `false` if it does not exist.
245
+ sig { abstract.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
246
+ def deactivate(feature); end
247
+
248
+ # Returns a hash of Objects that the given flag is turned on for.
249
+ # The keys are class/model names, values are arrays of IDs of instances/records.
250
+ #
251
+ # looks like this:
252
+ #
253
+ # { "Page" => [25, 89], "Book" => [152] }
254
+ #
255
+ sig do
256
+ abstract
257
+ .params(feature: T.any(Symbol, String))
258
+ .returns(T::Hash[String, T::Array[Object]])
259
+ end
260
+ def active_objects(feature); end
261
+
262
+ # Deactivates the given flag for the given objects. Returns `false` if it does not exist.
263
+ sig do
264
+ abstract
265
+ .params(
266
+ feature: T.any(Symbol, String),
267
+ objects: Object,
268
+ object_id_method: Symbol,
269
+ ).void
270
+ end
271
+ def deactivate_for(feature, *objects, object_id_method: CONFIG.default_id_method); end
272
+
273
+ # Returns the data of the flag in a hash.
274
+ sig do
275
+ abstract
276
+ .params(
277
+ feature: T.any(Symbol, String),
278
+ ).returns(T.nilable(T::Hash[String, T.anything]))
279
+ end
280
+ def get(feature); end
281
+
282
+ # Adds the given feature flag.
283
+ sig do
284
+ abstract
285
+ .params(
286
+ feature: T.any(Symbol, String),
287
+ description: String,
288
+ active: T.any(String, Symbol, T::Boolean, NilClass),
289
+ ).returns(T.nilable(T::Hash[String, T.anything]))
290
+ end
291
+ def add(feature, description = '', active = 'false'); end
292
+
293
+ # Removes the given feature flag.
294
+ # Returns its data or nil if it does not exist.
295
+ sig do
296
+ abstract
297
+ .params(
298
+ feature: T.any(Symbol, String),
299
+ ).returns(T.nilable(T::Hash[String, T.anything]))
300
+ end
301
+ def remove(feature); end
302
+
303
+ # Returns the data of all feature flags.
304
+ sig do
305
+ abstract.returns(T::Array[T::Hash[String, T.anything]])
306
+ end
307
+ def all; end
308
+
309
+ private
310
+
311
+ sig { params(objects: T::Array[Object], object_id_method: Symbol).returns(T::Hash[String, T::Array[Object]]) }
312
+ def objects_to_hash(objects, object_id_method: CONFIG.default_id_method)
313
+ objects.group_by { |ob| ob.class.to_s }
314
+ .transform_values { |arr| arr.map(&object_id_method) }
315
+ end
316
+
317
+ sig { void }
318
+ def import_flags_from_file
319
+ changes = YAML.load_file(file)
320
+ changes = { mandatory: [], remove: [] } unless changes.is_a? ::Hash
321
+
322
+ changes[:mandatory].each do |el|
323
+ mandatory_flags << el['name']
324
+ add(el['name'], el['description'], el['active'])
325
+ end
326
+
327
+ changes[:remove].each do |el|
328
+ remove(el)
329
+ end
330
+ end
331
+ end
332
+ end
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'fileutils'
@@ -5,15 +6,21 @@ require 'fileutils'
5
6
  module SimpleFeatureFlags
6
7
  module Cli
7
8
  module Command
9
+ # Implements the `generate` CLI command
8
10
  class Generate
9
- CONFIG_FILE = 'simple_feature_flags.yml'
11
+ extend T::Sig
10
12
 
13
+ CONFIG_FILE = T.let('simple_feature_flags.yml', String)
14
+
15
+ sig { returns(Options) }
11
16
  attr_reader :options
12
17
 
18
+ sig { params(options: Options).void }
13
19
  def initialize(options)
14
20
  @options = options
15
21
  end
16
22
 
23
+ sig { void }
17
24
  def run
18
25
  if options.rails
19
26
  generate_for_rails
@@ -29,10 +36,11 @@ module SimpleFeatureFlags
29
36
 
30
37
  private
31
38
 
39
+ sig { void }
32
40
  def generate_for_rails
33
41
  ::FileUtils.cp_r example_config_dir, destination_dir
34
42
 
35
- puts "Generated:"
43
+ puts 'Generated:'
36
44
  puts '----------'
37
45
  puts "- #{::File.join(destination_dir, 'config')}"
38
46
  print_dir_tree(example_config_dir, 1)
@@ -62,21 +70,30 @@ module SimpleFeatureFlags
62
70
  system 'bundle'
63
71
  end
64
72
 
73
+ sig do
74
+ params(
75
+ file_path: String,
76
+ regexp: Regexp,
77
+ block: T.proc.params(arg0: String).returns(String),
78
+ ).void
79
+ end
65
80
  def file_gsub(file_path, regexp, &block)
66
81
  new_content = File.read(file_path).gsub(regexp, &block)
67
- File.open(file_path, 'wb') { |file| file.write(new_content) }
82
+ File.binwrite(file_path, new_content)
68
83
  end
69
84
 
85
+ sig { params(file_path: String, line: String).void }
70
86
  def file_append(file_path, line)
71
87
  new_content = File.read(file_path)
72
88
  new_content = "#{new_content}\n#{line}\n"
73
- File.open(file_path, 'wb') { |file| file.write(new_content) }
89
+ File.binwrite(file_path, new_content)
74
90
  end
75
91
 
92
+ sig { params(dir: String, embed_level: Integer).void }
76
93
  def print_dir_tree(dir, embed_level = 0)
77
94
  padding = ' ' * (embed_level * 2)
78
95
 
79
- children = ::Dir.new(dir).entries.reject { |el| /^\.{1,2}$/ =~ el }
96
+ children = ::Dir.new(dir).entries.grep_v(/^\.{1,2}$/)
80
97
 
81
98
  children.each do |child|
82
99
  child_dir = ::File.join(dir, child)
@@ -88,32 +105,42 @@ module SimpleFeatureFlags
88
105
  end
89
106
  end
90
107
 
108
+ sig { returns String }
91
109
  def initializer_file
92
110
  ::File.join(destination_dir, 'config', 'initializers', 'simple_feature_flags.rb')
93
111
  end
94
112
 
113
+ sig { returns String }
95
114
  def gemfile
96
115
  ::File.join(destination_dir, 'Gemfile')
97
116
  end
98
117
 
118
+ sig { returns String }
99
119
  def routes_rb
100
120
  ::File.join(destination_dir, 'config', 'routes.rb')
101
121
  end
102
122
 
123
+ sig { returns String }
103
124
  def example_config_dir
104
125
  ::File.join(::File.expand_path(__dir__), '..', '..', '..', 'example_files', 'config')
105
126
  end
106
127
 
128
+ sig { returns String }
107
129
  def example_config_file
108
130
  ::File.join(example_config_dir, CONFIG_FILE)
109
131
  end
110
132
 
133
+ sig { returns String }
111
134
  def destination_dir
112
- raise IncorrectWorkingDirectoryError, "You should enter the main directory of your Rails project!" if options.rails && !::Dir.new(::Dir.pwd).entries.include?('config')
135
+ if options.rails && !::Dir.new(::Dir.pwd).entries.include?('config')
136
+ raise IncorrectWorkingDirectoryError,
137
+ 'You should enter the main directory of your Rails project!'
138
+ end
113
139
 
114
140
  ::Dir.pwd
115
141
  end
116
142
 
143
+ sig { returns String }
117
144
  def destination_file
118
145
  @destination_file ||= ::File.join(destination_dir, CONFIG_FILE)
119
146
  end
@@ -1,9 +1,11 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module SimpleFeatureFlags
4
5
  module Cli
6
+ # Contains CLI commands
5
7
  module Command; end
6
8
  end
7
9
  end
8
10
 
9
- Dir[File.expand_path('command/*.rb', __dir__)].sort.each { |file| require file }
11
+ Dir[File.expand_path('command/*.rb', __dir__)].each { |file| require file }
@@ -1,15 +1,31 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'optparse'
4
5
 
5
6
  module SimpleFeatureFlags
6
7
  module Cli
8
+ # Parses CLI options.
7
9
  class Options
8
- attr_reader :opt_parser, :generate, :help, :rails, :ui
10
+ extend T::Sig
9
11
 
12
+ sig { returns(OptionParser) }
13
+ attr_reader :opt_parser
14
+
15
+ sig { returns(T::Boolean) }
16
+ attr_reader :generate
17
+
18
+ sig { returns(T::Boolean) }
19
+ attr_reader :rails
20
+
21
+ sig { returns(T::Boolean) }
22
+ attr_reader :ui
23
+
24
+ sig { params(args: T::Array[String]).void }
10
25
  def initialize(args)
11
- @rails = true
12
- @ui = false
26
+ @rails = T.let(true, T::Boolean)
27
+ @ui = T.let(false, T::Boolean)
28
+ @generate = T.let(false, T::Boolean)
13
29
 
14
30
  @opt_parser = ::OptionParser.new do |opts|
15
31
  opts.banner = 'Usage: simple_feature_flags [options]'
@@ -1,20 +1,28 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module SimpleFeatureFlags
4
5
  module Cli
6
+ # Runs CLI commands
5
7
  class Runner
8
+ extend T::Sig
9
+
10
+ sig { returns(Options) }
6
11
  attr_reader :options
7
12
 
13
+ sig { params(args: T::Array[String]).void }
8
14
  def initialize(args = ARGV)
9
15
  @options = Options.new(args)
10
16
  end
11
17
 
18
+ sig { void }
12
19
  def run
13
- command_class = if @options.generate
14
- ::SimpleFeatureFlags::Cli::Command::Generate
15
- else
16
- raise NoSuchCommandError, 'No such command!'
17
- end
20
+ command_class =
21
+ if @options.generate
22
+ ::SimpleFeatureFlags::Cli::Command::Generate
23
+ else
24
+ raise NoSuchCommandError, 'No such command!'
25
+ end
18
26
 
19
27
  command_class.new(options).run
20
28
  end
@@ -1,7 +1,9 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module SimpleFeatureFlags
5
+ # Handles the CLI
4
6
  module Cli; end
5
7
  end
6
8
 
7
- Dir[File.expand_path('cli/*.rb', __dir__)].sort.each { |file| require file }
9
+ Dir[File.expand_path('cli/*.rb', __dir__)].each { |file| require file }
@@ -1,9 +1,15 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module SimpleFeatureFlags
5
+ # The main configuration object of the library.
4
6
  class Configuration
7
+ extend T::Sig
8
+
9
+ sig { returns(Symbol) }
5
10
  attr_accessor :default_id_method
6
11
 
12
+ sig { void }
7
13
  def initialize
8
14
  @default_id_method = :id
9
15
  end