conf-stack 1.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5acb2fa0a3cb439c2e5531bb4c3a679ec24f3a6c0bc79dbb4b3dafdd6fad5f8c
4
+ data.tar.gz: f235e532b5ed0beb98d51b2e3d3fd81d048d8b3942e7dee0f6c2871fc4a3ab17
5
+ SHA512:
6
+ metadata.gz: d36aa1d0f17da3cd9b0b3aeee1f01c3fcda9e483fea027c6906888369571770efe3c62ba3e9f8af4cf24e5cbb2a9fcaae26deaa1dbb86c23aed608b386f596cb
7
+ data.tar.gz: 5a3aef9729890caac18d5eb0d7e933686d307650c7e673e5d187d472a7ad16785f6f7afea5cc7ea9a7c530e51e76471e0d4d3ee02bac7986a9f69a7d8edb3b6e
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in conf-stack.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "rspec", "~> 3.0"
11
+
12
+ gem "pry", "~> 0.14.1"
data/Gemfile.lock ADDED
@@ -0,0 +1,40 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ conf-stack (1.0.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coderay (1.1.3)
10
+ diff-lcs (1.5.0)
11
+ method_source (1.0.0)
12
+ pry (0.14.1)
13
+ coderay (~> 1.1)
14
+ method_source (~> 1.0)
15
+ rake (13.0.6)
16
+ rspec (3.11.0)
17
+ rspec-core (~> 3.11.0)
18
+ rspec-expectations (~> 3.11.0)
19
+ rspec-mocks (~> 3.11.0)
20
+ rspec-core (3.11.0)
21
+ rspec-support (~> 3.11.0)
22
+ rspec-expectations (3.11.0)
23
+ diff-lcs (>= 1.2.0, < 2.0)
24
+ rspec-support (~> 3.11.0)
25
+ rspec-mocks (3.11.1)
26
+ diff-lcs (>= 1.2.0, < 2.0)
27
+ rspec-support (~> 3.11.0)
28
+ rspec-support (3.11.0)
29
+
30
+ PLATFORMS
31
+ x86_64-linux
32
+
33
+ DEPENDENCIES
34
+ conf-stack!
35
+ pry (~> 0.14.1)
36
+ rake (~> 13.0)
37
+ rspec (~> 3.0)
38
+
39
+ BUNDLED WITH
40
+ 2.2.22
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Chris Hall
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # ConfStack
2
+
3
+ ConfStack is a hierarchical configuration management library.
4
+
5
+ ## Usage
6
+
7
+ ConfStack configuration is loaded from `.confstack` files using a minimal DSL.
8
+ The name of this file is configurable when creating the configuration object by
9
+ passing a `filename:` argument (e.g. `ConfStack.new filename: 'my-cool-config'`).
10
+
11
+ ConfStack looks for and evaluates `.confstack` files recursively up the file
12
+ tree until it reaches the root of your project, if defined, or your home directory,
13
+ if it's not. Additionally, it always looks for and attempts to load a `.confstack`
14
+ file in your home directory, if one exists.
15
+
16
+ In this way, configuration for your tools can live where it makes the most sense
17
+ for it to live with as much or as little duplication as you deem necessary.
18
+
19
+ Short example:
20
+
21
+ ```ruby
22
+
23
+ at_project_root # Specifies that this is the root of your project
24
+
25
+ # These are functionally equivalent
26
+ configure some_value: 'foo'
27
+ configure :some_value, 'foo'
28
+
29
+ # These are also functionally equivalent
30
+ configure(:lazy_load) { "some_value is #{some_value}" }
31
+ configure lazy_load: proc { "some value is #{some_value}" }
32
+
33
+ see_also 'path/to/other/configuration'
34
+ ```
35
+
36
+ ### ConfStack DSL
37
+
38
+ #### `project_root [directory]`
39
+
40
+ Specifies the root of your project. Must be specified to prevent Mastermind
41
+ from scanning more of your file system than it needs to. The easiest way to
42
+ do this is to just specify `at_project_root` in a `.confstack` file in the
43
+ actual root of your project.
44
+
45
+ ----
46
+
47
+ #### `at_project_root`
48
+
49
+ Syntactic sugar for specifying that the current `.confstack` is in the root of your repository.
50
+
51
+ It's equivalent to `project_root __dir__`.
52
+
53
+ ----
54
+
55
+ #### `configure attribute [value] [&block]`
56
+
57
+ Alias: `set`
58
+
59
+ This command has two forms that are otherwise identical:
60
+
61
+ * `configure attribute, value # attribute and value as separate arguments`
62
+ * `configure attribute: value # attribute and value as key: value of a hash`
63
+
64
+ Used to set arbitrary configuration options. When a configuration option is
65
+ set in multiple `.confstack` files, the "closest" one to your invocation wins.
66
+ In other words, since Mastermind reads `.confstack` files starting in your
67
+ current directory and working it's way "up" the hierarchy, the first `.confstack`
68
+ that specifies a configuration option "wins".
69
+
70
+ When provided a block, the value is computed the first time the option is called
71
+ for. The block runs in the context of the `Configuration` object built up by
72
+ all the loaded `.confstack` files, so it has access to all previously set
73
+ configuration options.
74
+
75
+ The block is only executed once. After that, the value is cached so that it
76
+ doesn't need to be recomputed.
77
+
78
+ If both a block and a value are given, the block is ignored and only the value
79
+ is stored.
80
+
81
+ ----
82
+
83
+ #### `see_also filename`
84
+
85
+ Instructs Mastermind to also load the configuration specified in `filename`.
86
+ This file does _not_ have to be named `.confstack` but _does_ have to conform
87
+ to the syntax outlined here.
88
+
89
+ ## Installation
90
+
91
+ Add this line to your application's Gemfile:
92
+
93
+ ```ruby
94
+ gem conf-stack'
95
+ ```
96
+
97
+ And then execute:
98
+
99
+ $ bundle install
100
+
101
+ Or install it yourself as:
102
+
103
+ $ gem install conf-stack
104
+
105
+ ## Development
106
+
107
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
108
+
109
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
110
+
111
+ ## Contributing
112
+
113
+ Bug reports and pull requests are welcome on GitHub at https://github.com/chall8908/conf-stack.
114
+
115
+ ## License
116
+
117
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "conf-stack"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ require "pry"
12
+ Pry.start
data/bin/rake ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rake' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rake", "rake")
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/conf_stack/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "conf-stack"
7
+ spec.version = ConfStack::VERSION
8
+ spec.authors = ["Chris Hall"]
9
+ spec.email = ["chall8908@gmail.com"]
10
+
11
+ spec.summary = "Hierarchical configuration management"
12
+ # spec.description = "Hierarchical configuration management"
13
+ spec.homepage = "https://github.com/chall8908/conf_stack"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.4.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/chall8908/conf_stack"
19
+ spec.metadata["changelog_uri"] = "https://github.com/chall8908/conf_stack"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
25
+ end
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ # Uncomment to register a new dependency of your gem
31
+ # spec.add_dependency "example-gem", "~> 1.0"
32
+
33
+ # For more information and examples about making a new gem, checkout our
34
+ # guide at: https://bundler.io/guides/creating_gem.html
35
+ end
data/lib/conf-stack.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative 'conf_stack'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ConfStack
4
+ VERSION = "1.0.0"
5
+ end
data/lib/conf_stack.rb ADDED
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require_relative "conf_stack/version"
5
+
6
+ ##
7
+ # Main configuration object. Walks up the file tree looking for configuration
8
+ # files and loading them to build a the configuration.
9
+ #
10
+ # These configuarion files are loaded starting from the current working directory
11
+ # and traversing up until a file with a `at_project_root` directive or the directory
12
+ # specified by a `project_root` directive is reached.
13
+ #
14
+ # Configuration options set with `configure` are latched once set to something
15
+ # non-nil. This, along with the aforementioned load order of configuration files,
16
+ # means that configuration files closest to the source of your invokation will
17
+ # "beat" other configuration files.
18
+ #
19
+ # A global configuration located at $HOME/.confstack is loaded _last_. You can
20
+ # use this to specify plans you want accessible everywhere or global configuration
21
+ # that should apply everywhere (unless overridden by more proximal files).
22
+ #
23
+ # Additionally, there is a directive (`see_other`) that allows for configuration
24
+ # files outside of the lookup tree to be loaded.
25
+ #
26
+ # See {DSL} for a full list of the commands provided by ConfStack.
27
+ class ConfStack
28
+ class Error < StandardError; end
29
+
30
+ class MissingConfigurationError < Error
31
+ def initialize(attribute, filename)
32
+ super "#{attribute} has not been defined. Call `configure :#{attribute}[, value]` in a `#{filename}` to set it."
33
+ end
34
+ end
35
+
36
+ class InvalidDirectoryError < Error
37
+ def initialize(message, directory)
38
+ super "#{message}: #{directory} does not exist or is not a directory"
39
+ end
40
+ end
41
+
42
+ # Adds an arbitrary attribute given by +attribute+ to the configuration class
43
+ #
44
+ # @param attribute [String,Symbol] the attribute to define
45
+ #
46
+ # @!macro [attach] add_attribute
47
+ # @!attribute [rw] $1
48
+ def self.add_attribute(attribute)
49
+ return if self.method_defined? attribute
50
+
51
+ define_method "#{attribute}=" do |new_value=nil, &block|
52
+ self.instance_variable_set("@#{attribute}", new_value.nil? ? block : new_value) if self.instance_variable_get("@#{attribute}").nil?
53
+ end
54
+
55
+ define_method attribute do
56
+ value = self.instance_variable_get("@#{attribute}")
57
+ return value unless value.respond_to?(:call)
58
+
59
+ # Cache the value returned by the block so we're not doing potentially
60
+ # expensive operations mutliple times.
61
+ self.instance_variable_set("@#{attribute}", self.instance_eval(&value))
62
+ end
63
+
64
+ define_method "#{attribute}?" do
65
+ true
66
+ end
67
+
68
+ nil
69
+ end
70
+
71
+ # Specifies the directory that is the root of your project.
72
+ # This directory is where ConfStack will stop looking for more
73
+ # files, so it's important that it be set.
74
+ add_attribute :project_root
75
+
76
+ # @param filename [String] the filename to look for to build configuration
77
+ def initialize(filename: '.confstack')
78
+ @filename = filename
79
+ @loaded_conf_files = Set.new
80
+
81
+ lookup_and_load_configuration_files
82
+ load_configuration_file File.join(Dir.home, @filename)
83
+ end
84
+
85
+ # Loads a config file using the DSL, if it exists and hasn't been loaded already
86
+ #
87
+ # @param filename [String] the path to the config file to load
88
+ # @return [Void]
89
+ def load_configuration_file filename
90
+ if File.exists? filename and !@loaded_conf_files.include? filename
91
+ @loaded_conf_files << filename
92
+ DSL.new(self, filename)
93
+ end
94
+
95
+ nil
96
+ end
97
+
98
+ private
99
+
100
+ # Override the default NoMethodError with a more useful MissingConfigurationError.
101
+ #
102
+ # I
103
+ #
104
+ # Since the configuration object is used directly by plans for configuration information,
105
+ # accessing non-existant configuration can lead to unhelpful NoMethodErrors. This replaces
106
+ # those errors with more helpful errors.
107
+ def method_missing(symbol, *args)
108
+ return false if symbol.to_s.ends_with? '?'
109
+
110
+ super
111
+ rescue NoMethodError
112
+ raise MissingConfigurationError.new(symbol, @filename)
113
+ end
114
+
115
+ # Walks up the file tree looking for configuration files.
116
+ #
117
+ # @return [Void]
118
+ def lookup_and_load_configuration_files
119
+ load_configuration_file File.join(Dir.pwd, @filename)
120
+
121
+ # Walk up the tree until we reach the project root, the home directory, or
122
+ # the root directory
123
+ unless [project_root, Dir.home, '/'].include? Dir.pwd
124
+ Dir.chdir('..') { lookup_and_load_configuration_files }
125
+ end
126
+ end
127
+
128
+ # Describes the DSL used in configuration files.
129
+ class DSL
130
+ # @param config [ConfStack] the configuration object used by the DSL
131
+ # @param filename [String] the path to the configuration file to be loaded
132
+ def initialize(config, filename)
133
+ @config = config
134
+ @filename = filename
135
+ instance_eval(File.read(filename), filename, 0) if File.exists? filename
136
+ end
137
+
138
+ # Specifies that another file should also be loaded when loading
139
+ # this file. NOTE: This _immediately_ loads the other file.
140
+ #
141
+ # @param filename [String] the path to the file to be loaded
142
+ def see_also(filename)
143
+ @config.load_configuration_file(File.expand_path(filename))
144
+ end
145
+
146
+ # Specifies the root of the project.
147
+ # +root+ must be a directory.
148
+ #
149
+ # @param root [String] the root directory of the project
150
+ # @raise [InvalidDirectoryError] if +root+ is not a directory
151
+ def project_root(root)
152
+ unless Dir.exist? root
153
+ raise InvalidDirectoryError.new('Invalid project root', root)
154
+ end
155
+
156
+ @config.project_root = root
157
+ end
158
+
159
+ # Syntactic sugar on top of `project_root` to specify that the current
160
+ # file resides in the root of the project.
161
+ #
162
+ # @see project_root
163
+ def at_project_root
164
+ project_root File.dirname(@filename)
165
+ end
166
+
167
+ # Add arbitrary configuration attributes to the configuration object.
168
+ # Use this to add plan specific configuration options.
169
+ #
170
+ # @overload configure(attribute, value=nil, &block)
171
+ # @example configure(:foo, 'bar')
172
+ # @example configure(:foo) { 'bar' }
173
+ # @param attribute [String,Symbol] the attribute to define
174
+ # @param value [] the value to assign
175
+ # @param block [#call,nil] a callable that will return the value
176
+ #
177
+ # @overload configure(attribute)
178
+ # @example configure(foo: 'bar')
179
+ # @example configure('foo' => -> { 'bar' } # not recommended, but should work
180
+ # @param attribute [Hash] a single entry hash with the key as the attribute
181
+ # name and value as the corresponding value
182
+ def configure(attribute, value=nil, &block)
183
+ attribute, value = attribute.first if attribute.is_a? Hash
184
+
185
+ ConfStack.add_attribute(attribute)
186
+ @config.public_send "#{attribute}=", value, &block
187
+ end
188
+ alias_method :set, :configure
189
+
190
+ # The following methods are maintained for backwards compatability with
191
+ # .masterplan files used by Mastermind
192
+ def backward_compatability(*args, **kwargs)
193
+ end
194
+ alias_method :plan_files, :backward_compatability
195
+ alias_method :has_plan_files, :backward_compatability
196
+ alias_method :plan_file, :backward_compatability
197
+ alias_method :define_alias, :backward_compatability
198
+ alias_method :skip_confirmation, :backward_compatability
199
+ end
200
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: conf-stack
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Hall
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-04-27 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - chall8908@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - ".rspec"
22
+ - Gemfile
23
+ - Gemfile.lock
24
+ - LICENSE.txt
25
+ - README.md
26
+ - Rakefile
27
+ - bin/console
28
+ - bin/rake
29
+ - bin/setup
30
+ - conf-stack.gemspec
31
+ - lib/conf-stack.rb
32
+ - lib/conf_stack.rb
33
+ - lib/conf_stack/version.rb
34
+ homepage: https://github.com/chall8908/conf_stack
35
+ licenses:
36
+ - MIT
37
+ metadata:
38
+ homepage_uri: https://github.com/chall8908/conf_stack
39
+ source_code_uri: https://github.com/chall8908/conf_stack
40
+ changelog_uri: https://github.com/chall8908/conf_stack
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 2.4.0
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubygems_version: 3.2.22
57
+ signing_key:
58
+ specification_version: 4
59
+ summary: Hierarchical configuration management
60
+ test_files: []