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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.2.0
5
+ module Qonfig::Loaders
6
+ require_relative 'loaders/basic'
7
+ require_relative 'loaders/json'
8
+ require_relative 'loaders/yaml'
9
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.2.0
5
+ class Qonfig::Loaders::Basic
6
+ class << self
7
+ # @param data [String]
8
+ # @return [void]
9
+ #
10
+ # @api private
11
+ # @since 0.5.0
12
+ def load(data)
13
+ nil # NOTE: consciously return nil (for clarity)
14
+ end
15
+
16
+ # @return [void]
17
+ #
18
+ # @api private
19
+ # @since 0.5.0
20
+ def load_empty_data
21
+ nil # NOTE: consciously return nil (for clarity)
22
+ end
23
+
24
+ # @param file_path [String]
25
+ # @option fail_on_unexist [Boolean]
26
+ # @return [Object]
27
+ #
28
+ # @raise [Qonfig::FileNotFoundError]
29
+ #
30
+ # @api private
31
+ # @since 0.5.0
32
+ def load_file(file_path, fail_on_unexist: true)
33
+ load(::File.read(file_path))
34
+ rescue Errno::ENOENT => error
35
+ fail_on_unexist ? (raise Qonfig::FileNotFoundError, error.message) : load_empty_data
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.5.0
5
+ class Qonfig::Loaders::JSON < Qonfig::Loaders::Basic
6
+ class << self
7
+ # @param data [String]
8
+ # @return [Object]
9
+ #
10
+ # @api private
11
+ # @since 0.5.0
12
+ def load(data)
13
+ ::JSON.parse(data, max_nesting: false, allow_nan: true)
14
+ end
15
+
16
+ # @return [Object]
17
+ #
18
+ # @api private
19
+ # @since 0.5.0
20
+ def load_empty_data
21
+ load('{}')
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.2.0
5
+ class Qonfig::Loaders::YAML < Qonfig::Loaders::Basic
6
+ class << self
7
+ # @param data [String]
8
+ # @return [Object]
9
+ #
10
+ # @api private
11
+ # @since 0.2.0
12
+ def load(data)
13
+ ::YAML.load(ERB.new(data).result)
14
+ end
15
+
16
+ # @return [Object]
17
+ #
18
+ # @api private
19
+ # @since 0.5.0
20
+ def load_empty_data
21
+ load('{}')
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api public
4
+ # @since 0.4.0
5
+ module Qonfig::Plugins
6
+ require_relative 'plugins/registry'
7
+ require_relative 'plugins/access_mixin'
8
+ require_relative 'plugins/abstract'
9
+ require_relative 'plugins/toml'
10
+
11
+ # @since 0.4.0
12
+ @plugin_registry = Registry.new
13
+ # @since 0.4.0
14
+ @access_lock = Mutex.new
15
+
16
+ class << self
17
+ # @param plugin_name [Symbol, String]
18
+ # @return [void]
19
+ #
20
+ # @api public
21
+ # @since 0.4.0
22
+ def load(plugin_name)
23
+ thread_safe { plugin_registry[plugin_name].load! }
24
+ end
25
+
26
+ # @return [Array<String>]
27
+ #
28
+ # @api public
29
+ # @since 0.4.0
30
+ def names
31
+ thread_safe { plugin_registry.names }
32
+ end
33
+
34
+ # @param plugin_name [Symbol, String]
35
+ # @return [void]
36
+ #
37
+ # @api private
38
+ # @since 0.4.0
39
+ def register_plugin(plugin_name, plugin_module)
40
+ thread_safe { plugin_registry[plugin_name] = plugin_module }
41
+ end
42
+
43
+ private
44
+
45
+ # @return [Qonfig::Plugins::Registry]
46
+ #
47
+ # @api private
48
+ # @since 0.4.0
49
+ attr_reader :plugin_registry
50
+
51
+ # @return [Mutex]
52
+ #
53
+ # @api private
54
+ # @since 0.4.0
55
+ attr_reader :access_lock
56
+
57
+ # @return [void]
58
+ #
59
+ # @api private
60
+ # @since 0.4.0
61
+ def thread_safe
62
+ access_lock.synchronize { yield if block_given? }
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.4.0
5
+ class Qonfig::Plugins::Abstract
6
+ class << self
7
+ # @return [void]
8
+ #
9
+ # @api private
10
+ # @since 0.4.0
11
+ def load!; end
12
+ end
13
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.4.0
5
+ module Qonfig::Plugins::AccessMixin
6
+ # @param plugin_name [Symbol, String]
7
+ # @return [void]
8
+ #
9
+ # @see Qonfig::Plugins
10
+ #
11
+ # @api public
12
+ # @since 0.4.0
13
+ def plugin(plugin_name)
14
+ Qonfig::Plugins.load(plugin_name)
15
+ end
16
+
17
+ # @return [Array<String>]
18
+ #
19
+ # @see Qonfig::Plugins
20
+ #
21
+ # @api public
22
+ # @since 0.4.0
23
+ def plugins
24
+ Qonfig::Plugins.names
25
+ end
26
+
27
+ # @param plugin_name [String, Symbol]
28
+ # @param plugin_klass [Class<Qonfig::Plugins::Abstract>]
29
+ # @return [void]
30
+ #
31
+ # @see Qonfig::Plugins
32
+ #
33
+ # @api public
34
+ # @since 0.12.0
35
+ def register_plugin(plugin_name, plugin_klass)
36
+ Qonfig::Plugins.register_plugin(plugin_name, plugin_klass)
37
+ end
38
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.4.0
5
+ class Qonfig::Plugins::Registry
6
+ # @return [void]
7
+ #
8
+ # @api private
9
+ # @since 0.4.0
10
+ def initialize
11
+ @plugin_set = {}
12
+ @access_lock = Mutex.new
13
+ end
14
+
15
+ # @param plugin_name [Symbol, String]
16
+ # @return [Qonfig::Plugins::Abstract]
17
+ #
18
+ # @api private
19
+ # @since 0.4.0
20
+ def [](plugin_name)
21
+ thread_safe { fetch(plugin_name) }
22
+ end
23
+
24
+ # @param plugin_name [Symbol, String]
25
+ # @param plugin_module [Qonfig::Plugins::Abstract]
26
+ # @return [void]
27
+ #
28
+ # @api private
29
+ # @since 0.4.0
30
+ def register(plugin_name, plugin_module)
31
+ thread_safe { apply(plugin_name, plugin_module) }
32
+ end
33
+ alias_method :[]=, :register
34
+
35
+ # @return [Array<String>]
36
+ #
37
+ # @api private
38
+ # @since 0.4.0
39
+ def names
40
+ thread_safe { plugin_names }
41
+ end
42
+
43
+ private
44
+
45
+ # @return [Hash]
46
+ #
47
+ # @api private
48
+ # @since 0.4.0
49
+ attr_reader :plugin_set
50
+
51
+ # @return [Mutex]
52
+ #
53
+ # @api private
54
+ # @since 0.4.0
55
+ attr_reader :access_lock
56
+
57
+ # @return [void]
58
+ #
59
+ # @api private
60
+ # @since 0.4.0
61
+ def thread_safe
62
+ access_lock.synchronize { yield if block_given? }
63
+ end
64
+
65
+ # @return [Array<String>]
66
+ #
67
+ # @api private
68
+ # @since 0.4.0
69
+ def plugin_names
70
+ plugin_set.keys
71
+ end
72
+
73
+ # @param plugin_name [String]
74
+ # @return [Boolean]
75
+ #
76
+ # @api private
77
+ # @since 0.4.0
78
+ def registered?(plugin_name)
79
+ plugin_set.key?(plugin_name)
80
+ end
81
+
82
+ # @param plugin_name [Symbol, String]
83
+ # @param plugin_module [Qonfig::Plugins::Abstract]
84
+ # @return [void]
85
+ #
86
+ # @raise [Qonfig::AlreadyRegisteredPluginError]
87
+ #
88
+ # @api private
89
+ # @since 0.4.0
90
+ def apply(plugin_name, plugin_module)
91
+ plugin_name = indifferently_accessable_plugin_name(plugin_name)
92
+
93
+ if registered?(plugin_name)
94
+ raise Qonfig::AlreadyRegisteredPluginError, "#{plugin_name} plugin already exist"
95
+ end
96
+
97
+ plugin_set[plugin_name] = plugin_module
98
+ end
99
+
100
+ # @param plugin_name [Symbol, String]
101
+ # @return [Qonfig::Plugins::Abstract]
102
+ #
103
+ # @raise [Qonfig::UnregisteredPluginError]
104
+ #
105
+ # @api private
106
+ # @since 0.4.0
107
+ def fetch(plugin_name)
108
+ plugin_name = indifferently_accessable_plugin_name(plugin_name)
109
+
110
+ unless registered?(plugin_name)
111
+ raise Qonfig::UnregisteredPluginError, "#{plugin_name} plugin is not registered"
112
+ end
113
+
114
+ plugin_set[plugin_name]
115
+ end
116
+
117
+ # @param key [Symbol, String]
118
+ # @return [String]
119
+ #
120
+ # @api private
121
+ # @since 0.4.0
122
+ def indifferently_accessable_plugin_name(plugin_name)
123
+ plugin_name.to_s
124
+ end
125
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.12.0
5
+ class Qonfig::Plugins::TOML < Qonfig::Plugins::Abstract
6
+ class << self
7
+ # @return [void]
8
+ #
9
+ # @api private
10
+ # @since 0.12.0
11
+ def load!
12
+ raise(
13
+ Qonfig::UnresolvedPluginDependencyError,
14
+ '::TomlRB does not exist or "toml-rb" gem is not loaded'
15
+ ) unless const_defined?('::TomlRB')
16
+
17
+ require_relative 'toml/tomlrb_fixes'
18
+ require_relative 'toml/loaders/toml'
19
+ require_relative 'toml/uploaders/toml'
20
+ require_relative 'toml/commands/load_from_toml'
21
+ require_relative 'toml/commands/expose_toml'
22
+ require_relative 'toml/data_set'
23
+ require_relative 'toml/dsl'
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.12.0
5
+ class Qonfig::Commands::ExposeTOML < Qonfig::Commands::Base
6
+ # @return [Hash]
7
+ #
8
+ # @api private
9
+ # @since 0.12.0
10
+ EXPOSERS = { file_name: :file_name, env_key: :env_key }.freeze
11
+
12
+ # @return [Hash]
13
+ #
14
+ # @api private
15
+ # @since 0.12.0
16
+ EMPTY_TOML_DATA = {}.freeze
17
+
18
+ # @return [String]
19
+ #
20
+ # @api private
21
+ # @since 0.12.0
22
+ attr_reader :file_path
23
+
24
+ # @return [Boolean]
25
+ #
26
+ # @api private
27
+ # @since 0.12.0
28
+ attr_reader :strict
29
+
30
+ # @return [Symbol]
31
+ #
32
+ # @api private
33
+ # @since 0.12.0
34
+ attr_reader :via
35
+
36
+ # @return [Symbol, String]
37
+ #
38
+ # @api private
39
+ # @since 0.12.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.12.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.12.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.12.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
+ toml_data = load_toml_data(realfile)
99
+ toml_based_settings = build_data_set_class(toml_data).new.settings
100
+
101
+ settings.__append_settings__(toml_based_settings)
102
+ end
103
+ # rubocop:enable Metrics/AbcSize
104
+
105
+ # @param settings [Qonfig::Settings]
106
+ # @return [void]
107
+ #
108
+ # @raise [Qonfig::ExposeError]
109
+ #
110
+ # @api private
111
+ # @since 0.12.0
112
+ # rubocop:disable Metrics/AbcSize
113
+ def expose_env_key!(settings)
114
+ toml_data = load_toml_data(file_path)
115
+ toml_data_slice = toml_data[env] || toml_data[env.to_s] || toml_data[env.to_sym]
116
+ toml_data_slice = EMPTY_TOML_DATA.dup if toml_data_slice.nil? && !strict
117
+
118
+ raise(
119
+ Qonfig::ExposeError,
120
+ "#{file_path} file does not contain settings with <#{env}> environment key!"
121
+ ) unless toml_data_slice
122
+
123
+ toml_based_settings = build_data_set_class(toml_data_slice).new.settings
124
+
125
+ settings.__append_settings__(toml_based_settings)
126
+ end
127
+ # rubocop:enable Metrics/AbcSize
128
+
129
+ # @param file_path [String]
130
+ # @return [Hash]
131
+ #
132
+ # @api private
133
+ # @since 0.12.0
134
+ def load_toml_data(file_path)
135
+ Qonfig::Loaders::TOML.load_file(file_path, fail_on_unexist: strict)
136
+ end
137
+
138
+ # @param toml_data [Hash]
139
+ # @return [Class<Qonfig::DataSet>]
140
+ #
141
+ # @api private
142
+ # @since 0.12.0
143
+ def build_data_set_class(toml_data)
144
+ Qonfig::DataSet::ClassBuilder.build_from_hash(toml_data)
145
+ end
146
+ end