qonfig 0.0.0 → 0.12.0

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