qonfig 0.0.0 → 0.12.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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -2
  3. data/.jrubyrc +1 -0
  4. data/.rspec +1 -1
  5. data/.rubocop.yml +15 -0
  6. data/.travis.yml +43 -4
  7. data/CHANGELOG.md +121 -0
  8. data/Gemfile +4 -2
  9. data/LICENSE.txt +1 -1
  10. data/README.md +1060 -19
  11. data/Rakefile +18 -4
  12. data/bin/console +5 -11
  13. data/bin/rspec +55 -0
  14. data/bin/setup +1 -0
  15. data/gemfiles/with_external_deps.gemfile +8 -0
  16. data/gemfiles/without_external_deps.gemfile +5 -0
  17. data/lib/qonfig.rb +22 -2
  18. data/lib/qonfig/command_set.rb +67 -0
  19. data/lib/qonfig/commands.rb +15 -0
  20. data/lib/qonfig/commands/add_nested_option.rb +45 -0
  21. data/lib/qonfig/commands/add_option.rb +41 -0
  22. data/lib/qonfig/commands/base.rb +12 -0
  23. data/lib/qonfig/commands/compose.rb +37 -0
  24. data/lib/qonfig/commands/expose_yaml.rb +159 -0
  25. data/lib/qonfig/commands/load_from_env.rb +95 -0
  26. data/lib/qonfig/commands/load_from_env/value_converter.rb +84 -0
  27. data/lib/qonfig/commands/load_from_json.rb +56 -0
  28. data/lib/qonfig/commands/load_from_self.rb +73 -0
  29. data/lib/qonfig/commands/load_from_yaml.rb +58 -0
  30. data/lib/qonfig/configurable.rb +116 -0
  31. data/lib/qonfig/data_set.rb +213 -0
  32. data/lib/qonfig/data_set/class_builder.rb +27 -0
  33. data/lib/qonfig/data_set/validator.rb +7 -0
  34. data/lib/qonfig/dsl.rb +122 -0
  35. data/lib/qonfig/errors.rb +111 -0
  36. data/lib/qonfig/loaders.rb +9 -0
  37. data/lib/qonfig/loaders/basic.rb +38 -0
  38. data/lib/qonfig/loaders/json.rb +24 -0
  39. data/lib/qonfig/loaders/yaml.rb +24 -0
  40. data/lib/qonfig/plugins.rb +65 -0
  41. data/lib/qonfig/plugins/abstract.rb +13 -0
  42. data/lib/qonfig/plugins/access_mixin.rb +38 -0
  43. data/lib/qonfig/plugins/registry.rb +125 -0
  44. data/lib/qonfig/plugins/toml.rb +26 -0
  45. data/lib/qonfig/plugins/toml/commands/expose_toml.rb +146 -0
  46. data/lib/qonfig/plugins/toml/commands/load_from_toml.rb +49 -0
  47. data/lib/qonfig/plugins/toml/data_set.rb +19 -0
  48. data/lib/qonfig/plugins/toml/dsl.rb +27 -0
  49. data/lib/qonfig/plugins/toml/loaders/toml.rb +24 -0
  50. data/lib/qonfig/plugins/toml/tomlrb_fixes.rb +92 -0
  51. data/lib/qonfig/plugins/toml/uploaders/toml.rb +25 -0
  52. data/lib/qonfig/settings.rb +457 -0
  53. data/lib/qonfig/settings/builder.rb +18 -0
  54. data/lib/qonfig/settings/key_guard.rb +71 -0
  55. data/lib/qonfig/settings/lock.rb +60 -0
  56. data/lib/qonfig/uploaders.rb +10 -0
  57. data/lib/qonfig/uploaders/base.rb +18 -0
  58. data/lib/qonfig/uploaders/file.rb +55 -0
  59. data/lib/qonfig/uploaders/json.rb +35 -0
  60. data/lib/qonfig/uploaders/yaml.rb +93 -0
  61. data/lib/qonfig/version.rb +7 -1
  62. data/qonfig.gemspec +29 -17
  63. metadata +122 -16
data/Rakefile CHANGED
@@ -1,6 +1,20 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
3
2
 
4
- RSpec::Core::RakeTask.new(:spec)
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop'
6
+ require 'rubocop-rspec'
7
+ require 'rubocop-performance'
8
+ require 'rubocop/rake_task'
5
9
 
6
- task :default => :spec
10
+ RuboCop::RakeTask.new(:rubocop) do |t|
11
+ config_path = File.expand_path(File.join('.rubocop.yml'), __dir__)
12
+
13
+ t.options = ['--config', config_path]
14
+ t.requires << 'rubocop-rspec'
15
+ t.requires << 'rubocop-performance'
16
+ end
17
+
18
+ RSpec::Core::RakeTask.new(:rspec)
19
+
20
+ task default: :rspec
@@ -1,14 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "qonfig"
4
+ require 'bundler/setup'
5
+ require 'qonfig'
5
6
 
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start(__FILE__)
7
+ require 'pry'
8
+ Pry.start
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'pathname'
5
+ require 'optparse'
6
+
7
+ module QonfigSpecRunner
8
+ extend self
9
+
10
+ expand_gemfile_path = lambda do |gemfile_name|
11
+ File.expand_path(File.join('..', 'gemfiles', gemfile_name), __dir__)
12
+ end
13
+
14
+ GEMFILES = {
15
+ with_external_deps: expand_gemfile_path.call('with_external_deps.gemfile'),
16
+ without_external_deps: expand_gemfile_path.call('without_external_deps.gemfile')
17
+ }.freeze
18
+
19
+ def run!
20
+ OptionParser.new do |opts|
21
+ opts.banner = 'Usage: bin/rspec [options]'
22
+
23
+ opts.on(
24
+ '-w', '--with-plugins',
25
+ 'Run tests with test for plugins'
26
+ ) { run_with_specs_for_plugins! }
27
+
28
+ opts.on(
29
+ '-n', '--without-plugins',
30
+ 'Run tests without tests for plugins'
31
+ ) { run_without_specs_for_plugins! }
32
+ end.parse!
33
+ end
34
+
35
+ private
36
+
37
+ def run_with_specs_for_plugins!
38
+ ENV['TEST_PLUGINS'] = 'true'
39
+ ENV['BUNDLE_GEMFILE'] = GEMFILES[:with_external_deps]
40
+ run_tests!
41
+ end
42
+
43
+ def run_without_specs_for_plugins!
44
+ ENV['BUNDLE_GEMFILE'] = GEMFILES[:without_external_deps]
45
+ run_tests!
46
+ end
47
+
48
+ def run_tests!
49
+ require 'rubygems'
50
+ require 'bundler/setup'
51
+ load Gem.bin_path('rspec-core', 'rspec')
52
+ end
53
+ end
54
+
55
+ QonfigSpecRunner.run!
data/bin/setup CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env bash
2
+
2
3
  set -euo pipefail
3
4
  IFS=$'\n\t'
4
5
  set -vx
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # @since 0.12.0 (qonfig: :toml plugin)
6
+ gem 'toml-rb', '>= 1'
7
+
8
+ gemspec path: '..'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec path: '..'
@@ -1,5 +1,25 @@
1
- require "qonfig/version"
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'json'
5
+ require 'erb'
2
6
 
3
7
  module Qonfig
4
- # Your code goes here...
8
+ require_relative 'qonfig/errors'
9
+ require_relative 'qonfig/loaders'
10
+ require_relative 'qonfig/uploaders'
11
+ require_relative 'qonfig/commands'
12
+ require_relative 'qonfig/command_set'
13
+ require_relative 'qonfig/settings'
14
+ require_relative 'qonfig/dsl'
15
+ require_relative 'qonfig/data_set'
16
+ require_relative 'qonfig/configurable'
17
+ require_relative 'qonfig/plugins'
18
+
19
+ # @api public
20
+ # @since 0.4.0
21
+ extend Plugins::AccessMixin
22
+
23
+ # @since 0.12.0
24
+ register_plugin('toml', Qonfig::Plugins::TOML)
5
25
  end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ class Qonfig::CommandSet
6
+ # @return [Array<Qonfig::Commands::Base>]
7
+ #
8
+ # @api private
9
+ # @since 0.1.0
10
+ attr_reader :commands
11
+
12
+ # @api private
13
+ # @since 0.1.0
14
+ def initialize
15
+ @commands = []
16
+ @access_lock = Mutex.new
17
+ end
18
+
19
+ # @param command [Qonfig::Commands::Base]
20
+ # @return [void]
21
+ #
22
+ # @api private
23
+ # @since 0.1.0
24
+ def add_command(command)
25
+ thread_safe { commands << command }
26
+ end
27
+ alias_method :<<, :add_command
28
+
29
+ # @param block [Proc]
30
+ # @return [Enumerable]
31
+ #
32
+ # @api private
33
+ # @since 0.1.0
34
+ def each(&block)
35
+ thread_safe { block_given? ? commands.each(&block) : commands.each }
36
+ end
37
+
38
+ # @param command_set [Qonfig::CommandSet]
39
+ # @return [void]
40
+ #
41
+ # @api private
42
+ # @since 0.1.0
43
+ def concat(command_set)
44
+ thread_safe { commands.concat(command_set.commands) }
45
+ end
46
+
47
+ # @return [Qonfig::CommandSet]
48
+ #
49
+ # @api private
50
+ # @since 0.2.0
51
+ def dup
52
+ thread_safe do
53
+ self.class.new.tap { |duplicate| duplicate.concat(self) }
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ # @param block [Proc]
60
+ # @return [Object]
61
+ #
62
+ # @api private
63
+ # @since 0.2.0
64
+ def thread_safe(&block)
65
+ @access_lock.synchronize(&block)
66
+ end
67
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ module Qonfig::Commands
6
+ require_relative 'commands/base'
7
+ require_relative 'commands/add_option'
8
+ require_relative 'commands/add_nested_option'
9
+ require_relative 'commands/compose'
10
+ require_relative 'commands/load_from_yaml'
11
+ require_relative 'commands/load_from_json'
12
+ require_relative 'commands/load_from_self'
13
+ require_relative 'commands/load_from_env'
14
+ require_relative 'commands/expose_yaml'
15
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ class Qonfig::Commands::AddNestedOption < Qonfig::Commands::Base
6
+ # @return [Symbol, String]
7
+ #
8
+ # @api private
9
+ # @since 0.1.0
10
+ attr_reader :key
11
+
12
+ # @return [Class<Qonfig::DataSet>]
13
+ #
14
+ # @api private
15
+ # @since 0.2.0
16
+ attr_reader :nested_data_set_klass
17
+
18
+ # @param key [Symbol, String]
19
+ # @param nested_definitions [Proc]
20
+ #
21
+ # @raise [Qonfig::ArgumentError]
22
+ # @raise [Qonfig::CoreMethodIntersectionError]
23
+ #
24
+ # @api private
25
+ # @since 0.1.0
26
+ def initialize(key, nested_definitions)
27
+ Qonfig::Settings::KeyGuard.prevent_incomparabilities!(key)
28
+
29
+ @key = key
30
+ @nested_data_set_klass = Class.new(Qonfig::DataSet).tap do |data_set|
31
+ data_set.instance_eval(&nested_definitions)
32
+ end
33
+ end
34
+
35
+ # @param settings [Qonfig::Settings]
36
+ # @return [void]
37
+ #
38
+ # @api private
39
+ # @since 0.1.0
40
+ def call(settings)
41
+ nested_settings = nested_data_set_klass.new.settings
42
+
43
+ settings.__define_setting__(key, nested_settings)
44
+ end
45
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ class Qonfig::Commands::AddOption < Qonfig::Commands::Base
6
+ # @return [Symbol, String]
7
+ #
8
+ # @api private
9
+ # @since 0.1.0
10
+ attr_reader :key
11
+
12
+ # @return [Object]
13
+ #
14
+ # @api private
15
+ # @since 0.1.0
16
+ attr_reader :value
17
+
18
+ # @param key [Symbol, String]
19
+ # @param value [Object]
20
+ #
21
+ # @raise [Qonfig::ArgumentError]
22
+ # @raise [Qonfig::CoreMethodIntersectionError]
23
+ #
24
+ # @api private
25
+ # @since 0.1.0
26
+ def initialize(key, value)
27
+ Qonfig::Settings::KeyGuard.prevent_incomparabilities!(key)
28
+
29
+ @key = key
30
+ @value = value
31
+ end
32
+
33
+ # @param settings [Qonfig::Settings]
34
+ # @return [void]
35
+ #
36
+ # @api private
37
+ # @since 0.1.0
38
+ def call(settings)
39
+ settings.__define_setting__(key, value)
40
+ end
41
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ class Qonfig::Commands::Base
6
+ # @param settings [Qonfig::Settings]
7
+ # @return [void]
8
+ #
9
+ # @api private
10
+ # @since 0.1.0
11
+ def call(settings); end
12
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ class Qonfig::Commands::Compose < Qonfig::Commands::Base
6
+ # @return [Qonfig::DataSet]
7
+ #
8
+ # @api private
9
+ # @since 0.1.0
10
+ attr_reader :data_set_klass
11
+
12
+ # @param data_set_klass [Qonfig::DataSet]
13
+ #
14
+ # @raise [Qonfig::ArgumentError]
15
+ #
16
+ # @api private
17
+ # @since 0.1.0
18
+ def initialize(data_set_klass)
19
+ raise(
20
+ Qonfig::ArgumentError,
21
+ 'Composed config class should be a subtype of Qonfig::DataSet'
22
+ ) unless data_set_klass.is_a?(Class) && data_set_klass < Qonfig::DataSet
23
+
24
+ @data_set_klass = data_set_klass
25
+ end
26
+
27
+ # @param settings [Qonfig::Settings]
28
+ # @return [void]
29
+ #
30
+ # @api private
31
+ # @since 0.1.0
32
+ def call(settings)
33
+ composite_settings = data_set_klass.new.settings
34
+
35
+ settings.__append_settings__(composite_settings)
36
+ end
37
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.7.0
5
+ class Qonfig::Commands::ExposeYAML < Qonfig::Commands::Base
6
+ # @return [Hash]
7
+ #
8
+ # @api private
9
+ # @since 0.7.0
10
+ EXPOSERS = { file_name: :file_name, env_key: :env_key }.freeze
11
+
12
+ # @return [Hash]
13
+ #
14
+ # @api private
15
+ # @since 0.7.0
16
+ EMPTY_YAML_DATA = {}.freeze
17
+
18
+ # @return [String]
19
+ #
20
+ # @api private
21
+ # @since 0.7.0
22
+ attr_reader :file_path
23
+
24
+ # @return [Boolean]
25
+ #
26
+ # @api private
27
+ # @since 0.7.0
28
+ attr_reader :strict
29
+
30
+ # @return [Symbol]
31
+ #
32
+ # @api private
33
+ # @since 0.7.0
34
+ attr_reader :via
35
+
36
+ # @return [Symbol, String]
37
+ #
38
+ # @api private
39
+ # @since 0.7.0
40
+ attr_reader :env
41
+
42
+ # @param file_path [String]
43
+ # @option strict [Boolean]
44
+ # @option via [Symbol]
45
+ # @option env [String, Symbol]
46
+ #
47
+ # @api private
48
+ # @since 0.7.0
49
+ def initialize(file_path, strict: true, via:, env:)
50
+ unless env.is_a?(Symbol) || env.is_a?(String) || env.is_a?(Numeric)
51
+ raise Qonfig::ArgumentError, ':env should be a string or a symbol'
52
+ end
53
+
54
+ raise Qonfig::ArgumentError, ':env should be provided' if env.to_s.empty?
55
+ raise Qonfig::ArgumentError, 'used :via is unsupported' unless EXPOSERS.key?(via)
56
+
57
+ @file_path = file_path
58
+ @strict = strict
59
+ @via = via
60
+ @env = env
61
+ end
62
+
63
+ # @param settings [Qonfig::Settings]
64
+ # @return [void]
65
+ #
66
+ # @api private
67
+ # @since 0.7.0
68
+ def call(settings)
69
+ case via
70
+ when EXPOSERS[:file_name]
71
+ expose_file_name!(settings)
72
+ when EXPOSERS[:env_key]
73
+ expose_env_key!(settings)
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ # @param settings [Qonfig::Settings]
80
+ # @return [void]
81
+ #
82
+ # @api private
83
+ # @since 0.7.0
84
+ # rubocop:disable Metrics/AbcSize
85
+ def expose_file_name!(settings)
86
+ # NOTE: transform file name (insert environment name into the file name)
87
+ # from: path/to/file/file_name.file_extension
88
+ # to: path/to/file/file_name.env_name.file_extension
89
+
90
+ pathname = Pathname.new(file_path)
91
+ dirname = pathname.dirname
92
+ extname = pathname.extname.to_s
93
+ basename = pathname.basename.to_s.sub!(extname, '')
94
+ envname = [env.to_s, extname].reject(&:empty?).join('')
95
+ envfile = [basename, envname].reject(&:empty?).join('.')
96
+ realfile = dirname.join(envfile).to_s
97
+
98
+ yaml_data = load_yaml_data(realfile)
99
+ yaml_based_settings = build_data_set_class(yaml_data).new.settings
100
+
101
+ settings.__append_settings__(yaml_based_settings)
102
+ end
103
+ # rubocop:enable Metrics/AbcSize
104
+
105
+ # @param settings [Qonfig::Settings]
106
+ # @return [void]
107
+ #
108
+ # @raise [Qonfig::ExposeError]
109
+ # @raise [Qonfig::IncompatibleYAMLStructureError]
110
+ #
111
+ # @api private
112
+ # @since 0.7.0
113
+ # rubocop:disable Metrics/AbcSize
114
+ def expose_env_key!(settings)
115
+ yaml_data = load_yaml_data(file_path)
116
+ yaml_data_slice = yaml_data[env] || yaml_data[env.to_s] || yaml_data[env.to_sym]
117
+ yaml_data_slice = EMPTY_YAML_DATA.dup if yaml_data_slice.nil? && !strict
118
+
119
+ raise(
120
+ Qonfig::ExposeError,
121
+ "#{file_path} file does not contain settings with <#{env}> environment key!"
122
+ ) unless yaml_data_slice
123
+
124
+ raise(
125
+ Qonfig::IncompatibleYAMLStructureError,
126
+ 'YAML content should have a hash-like structure'
127
+ ) unless yaml_data_slice.is_a?(Hash)
128
+
129
+ yaml_based_settings = build_data_set_class(yaml_data_slice).new.settings
130
+
131
+ settings.__append_settings__(yaml_based_settings)
132
+ end
133
+ # rubocop:enable Metrics/AbcSize
134
+
135
+ # @param file_path [String]
136
+ # @return [Hash]
137
+ #
138
+ # @raise [Qonfig::IncompatibleYAMLStructureError]
139
+ #
140
+ # @api private
141
+ # @since 0.7.0
142
+ def load_yaml_data(file_path)
143
+ Qonfig::Loaders::YAML.load_file(file_path, fail_on_unexist: strict).tap do |yaml_data|
144
+ raise(
145
+ Qonfig::IncompatibleYAMLStructureError,
146
+ 'YAML content should have a hash-like structure'
147
+ ) unless yaml_data.is_a?(Hash)
148
+ end
149
+ end
150
+
151
+ # @param yaml_data [Hash]
152
+ # @return [Class<Qonfig::DataSet>]
153
+ #
154
+ # @api private
155
+ # @since 0.7.0
156
+ def build_data_set_class(yaml_data)
157
+ Qonfig::DataSet::ClassBuilder.build_from_hash(yaml_data)
158
+ end
159
+ end