jac 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 8f6cffab29062b76d447fff5f941bebe410deb1f
4
- data.tar.gz: b940de7ad2090b3c32b6c8012acfc9f1ef4fe764
2
+ SHA256:
3
+ metadata.gz: 8aaaff9c5e46908f214469f894732bdbd178bdc92e91549180d7f3a45157e2e2
4
+ data.tar.gz: d6c97e79b6a72e363777af65b5d9e18fb7a2545dd56b3a523ed25d9c1cf33482
5
5
  SHA512:
6
- metadata.gz: 87be3a20069dadd3e4cb4118ffe24cb6eaa3c25eb574e12bc14e3631132b505199f3bc74efd3eba1bb8cefd8e91d0f37e8c098a3749905f696e6281195b56407
7
- data.tar.gz: 9360da4e099d453bfa85f1a8b6f3a65252403fa5e48a36b01e89bfd69ef4715874c93fd5d138059374612908d38e8b9c36ae92271b8b80dd8072393e320f3a62
6
+ metadata.gz: b2244d961c0c82fb75ae3dffc5ce7f42763533f0bc2794062ab2a0766ade890eca579aa7dbf49f28bb2759014b3daaf2f4fa077a5a1d1dde730b8e73226ae635
7
+ data.tar.gz: 6118f96a703d7a4049b404f6635bf78c878665a2f292cc7c5fc3b6812aa14ec9f2ba62c14118b6cf2f183c31466ea2a6510fda92efdae529586aff56e3c25f30
@@ -177,14 +177,28 @@ module Jac
177
177
  # If profile name matches multiple generic profiles it not defined
178
178
  # which profile will be used.
179
179
  module Configuration
180
+ # Name of top level profile. ^top profile is implicit
181
+ # it automaticly merged on top of resulting configuration
182
+ TOP_PROFILE_NAME = '^top'.freeze
183
+ # Name of base level profile. ^base profile is implicit
184
+ # resulting configuration automaticly merged on top of it.
185
+ BASE_PROFILE_NAME = '^base'.freeze
186
+ # Any configuration set always contains `default` profile
187
+ # which is loaded when no profile requested.
188
+ DEFAULT_PROFILE_NAME = 'default'.freeze
189
+ # List of default profiles. Having this list we can easily create default
190
+ # configuration.
191
+ BASIC_PROFILES = [
192
+ TOP_PROFILE_NAME,
193
+ BASE_PROFILE_NAME,
194
+ DEFAULT_PROFILE_NAME
195
+ ].freeze
196
+ # Key where all inherited profiles listed
197
+ EXTENDS_KEY = 'extends'.freeze
198
+
180
199
  # Reads and evaluates configuration for given set of streams
181
200
  # and profile
182
201
  class ConfigurationReader
183
- # Any configuration set always contains `default` profile
184
- # which is loaded when no profile requested.
185
- DEFAULT_PROFILE_NAME = 'default'.freeze
186
- # Creates "empty" config
187
- DEFAULT_CONFIGURATION = -> () { { DEFAULT_PROFILE_NAME => {} } }
188
202
  attr_reader :merger
189
203
  # Creates configuration reader
190
204
  # @param [Array] streams of pairs containing YAML document and provided
@@ -200,12 +214,22 @@ module Jac
200
214
  def read(*profile)
201
215
  result = @streams
202
216
  .flat_map { |stream, _name| read_stream(stream) }
203
- .inject(DEFAULT_CONFIGURATION.call) { |acc, elem| update(acc, elem) }
204
- OpenStruct.new(evaluate(resolve(profile, result)).merge('profile' => profile))
217
+ .inject(default_configuration) { |acc, elem| update(acc, elem) }
218
+ # Keep original profile name
219
+ original_profile = profile
220
+ # Add implicit profiles
221
+ profile =
222
+ [Configuration::BASE_PROFILE_NAME, profile, Configuration::TOP_PROFILE_NAME].flatten
223
+ OpenStruct.new(evaluate(resolve(profile, result).merge('profile' => original_profile)))
205
224
  end
206
225
 
207
226
  private
208
227
 
228
+ # Creates empty configuration object.
229
+ def default_configuration
230
+ Configuration::BASIC_PROFILES.inject({}) { |acc, elem| acc.update(elem => {}) }
231
+ end
232
+
209
233
  def read_stream(stream)
210
234
  # Each stream consists of one or more documents
211
235
  YAML.parse_stream(stream).children.flat_map do |document|
@@ -256,9 +280,6 @@ module Jac
256
280
 
257
281
  # Describes profile resolving strategy
258
282
  class ProfileResolver
259
- # Key where all inherited profiles listed
260
- EXTENDS_KEY = 'extends'.freeze
261
-
262
283
  attr_reader :config, :merger
263
284
 
264
285
  def initialize(config)
@@ -273,8 +294,8 @@ module Jac
273
294
  raise(ArgumentError, msg)
274
295
  end
275
296
  profile_values = find_profile(elem)
276
- # Find all inheritors
277
- extends = *profile_values[EXTENDS_KEY] || []
297
+ # Find all parent_profiles
298
+ extends = parent_profiles(profile_values)
278
299
  # We can do not check extends. Empty profile returns {}
279
300
  # Inherited values goes first
280
301
  inherited = merger.merge(acc, resolve(extends, resolved + [elem]))
@@ -282,6 +303,17 @@ module Jac
282
303
  end
283
304
  end
284
305
 
306
+ # Fetches list of parent profile
307
+ # @param [Hash] values current profile values
308
+ # @return [Array] of profile names
309
+ def parent_profiles(values)
310
+ extends = *values[Configuration::EXTENDS_KEY] || []
311
+ [Configuration::TOP_PROFILE_NAME, Configuration::BASE_PROFILE_NAME].each do |implicit|
312
+ raise(ArgumentError, "`#{implicit}` is not allowed here") if extends.include?(implicit)
313
+ end
314
+ extends
315
+ end
316
+
285
317
  def find_profile(profile_name)
286
318
  return config[profile_name] if config.key?(profile_name)
287
319
  # First and last chars are '/'
@@ -317,8 +349,6 @@ module Jac
317
349
  .keys
318
350
  .select { |k| k[0] == '/' && k[-1] == '/' }
319
351
  .map { |k| [k, Regexp.new(k[1..-2])] }
320
-
321
- @generic_profiles
322
352
  end
323
353
  end
324
354
 
@@ -326,29 +356,27 @@ module Jac
326
356
  # when referencing profile inside evaluated
327
357
  # expressions
328
358
  class EvaluationContext
329
- def initialize(evaluator)
359
+ def initialize(hash, evaluator)
330
360
  @evaluator = evaluator
331
- end
332
-
333
- def respond_to_missing?(_meth, _args, &_block)
334
- true
335
- end
336
-
337
- def method_missing(meth, *args, &block)
338
- # rubocop ispection hack
339
- return super unless respond_to_missing?(meth, args, &block)
340
- @evaluator.evaluate(meth.to_s)
361
+ hash.each_key do |key|
362
+ exp = <<-EVAL.strip_indent
363
+ def #{key}
364
+ @evaluator.evaluate("#{key}")
365
+ end
366
+ EVAL
367
+ eval(exp, binding)
368
+ end
341
369
  end
342
370
  end
343
371
 
344
372
  # Evaluates all strings inside resolved profile
345
373
  # object
346
374
  class ConfigurationEvaluator
347
- def initialize(src_object, dst_object)
375
+ def initialize(src_object)
348
376
  @object = src_object
349
- @evaluated = dst_object
350
- @context = EvaluationContext.new(self)
351
- resolve_object
377
+ @evaluated = {}
378
+ # Initialize context object with root
379
+ @context = EvaluationContext.new(@object, self)
352
380
  end
353
381
 
354
382
  def evaluate(key)
@@ -364,27 +392,32 @@ module Jac
364
392
  alias conf c
365
393
  alias cfg c
366
394
 
367
- private
368
-
369
- def resolve_object
395
+ def evaluate_values
396
+ @ctx_object = @context
370
397
  @object.each_key { |k| evaluate(k) }
371
398
  # Cleanup accidentally created values (when referencing missing values)
372
399
  @evaluated.delete_if { |k, _v| !@object.key?(k) }
373
400
  end
374
401
 
375
- def get_binding(obj)
402
+ private
403
+
404
+ def get_binding(object)
376
405
  binding
377
406
  end
378
407
 
379
- def evaluate_deep(object)
408
+ def evaluate_deep(object) # rubocop:disable Metrics/MethodLength
380
409
  case object
381
410
  when String
382
411
  eval_string(object)
383
412
  when Array
384
- object.map { |e| evaluate_deep(e) }
413
+ contextual(object) do
414
+ object.map { |e| evaluate_deep(e) }
415
+ end
385
416
  when Hash
386
417
  # Evaluating values only by convention
387
- object.inject({}) { |acc, elem| acc.update(elem.first => evaluate_deep(elem.last)) }
418
+ contextual(object) do
419
+ object.inject({}) { |acc, elem| acc.update(elem.first => evaluate_deep(elem.last)) }
420
+ end
388
421
  else
389
422
  object
390
423
  end
@@ -392,17 +425,40 @@ module Jac
392
425
 
393
426
  def eval_string(o)
394
427
  evaluated = /#\{.+?\}/.match(o) do
395
- eval('"' + o + '"', get_binding(self))
428
+ eval('"' + o + '"', get_binding(@ctx_object))
396
429
  end
397
430
 
398
431
  evaluated || o
399
432
  end
400
433
 
434
+ def respond_to_missing?(meth, args, &block)
435
+ @ctx_object && @ctx_object.respond_to?(meth, args, &block)
436
+ end
437
+
438
+ def method_missing(meth, *args, &block)
439
+ # rubocop inspection hack
440
+ return super unless respond_to_missing?(meth, args, &block)
441
+ @ctx_object.send(meth, *args, &block)
442
+ end
443
+
444
+ # Keeps track of current context objects
445
+ # This trick allows us to reference local variables
446
+ # using `self` calls.
447
+ # @param ctx [Object] object to use in current evaluation
448
+ # @param &block [Proc] evaluation logic
449
+ # @return [Object] result of evaluation
450
+ def contextual(ctx)
451
+ memo = @ctx_object
452
+ @ctx_object = ctx
453
+ result = yield
454
+ @ctx_object = memo
455
+
456
+ result
457
+ end
458
+
401
459
  class << self
402
460
  def evaluate(o)
403
- dst = {}
404
- ConfigurationEvaluator.new(o, dst)
405
- dst
461
+ ConfigurationEvaluator.new(o).evaluate_values
406
462
  end
407
463
  end
408
464
  end
@@ -424,7 +480,7 @@ module Jac
424
480
  # names to read
425
481
  # @return [OpenStruct] instance which contains all resolved profile fields
426
482
  def read(profile, *streams)
427
- profile = [ConfigurationReader::DEFAULT_PROFILE_NAME] if profile.empty?
483
+ profile = [Configuration::DEFAULT_PROFILE_NAME] if profile.empty?
428
484
  ConfigurationReader.new(streams).read(*profile)
429
485
  end
430
486
 
@@ -0,0 +1,3 @@
1
+ module Jac
2
+ VERSION = [0, 0, 4].freeze
3
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jac
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - ilya.arkhanhelsky
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-15 00:00:00.000000000 Z
11
+ date: 2018-08-06 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Profile based configuration lib
14
14
  email: ilya.arkhanhelsky@vizor-games.com
@@ -20,6 +20,7 @@ files:
20
20
  - lib/jac/configuration.rb
21
21
  - lib/jac/merger.rb
22
22
  - lib/jac/parser.rb
23
+ - lib/jac/version.rb
23
24
  homepage: https://github.com/vizor-games/jac
24
25
  licenses:
25
26
  - MIT
@@ -40,7 +41,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
40
41
  version: '0'
41
42
  requirements: []
42
43
  rubyforge_project:
43
- rubygems_version: 2.6.13
44
+ rubygems_version: 2.7.6
44
45
  signing_key:
45
46
  specification_version: 4
46
47
  summary: Just Another Configuration Lib