config_x 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 50dda4ca7833458346a6d039197b8bcd9f82ab65
4
+ data.tar.gz: 5d3f59ab67ec4a6e6c1fa53170faf4bc4a7acd39
5
+ SHA512:
6
+ metadata.gz: 56f38f6a6fd11f65ef16449b54f7f7e4ef7909ec66a46fbf920c6938689870f45bb902d5d261a26e4aa196e3adf1f9b13a986e60accf691215685bc8a1684d68
7
+ data.tar.gz: 0e626869ec50ac3fbbbef79919464f10a5cd675000527ca2e9a61a3c21c383253f537a7f7119efdd560edf515e3309fc7ea40acfc49e286fbe0d8cbc481ce65f
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1 @@
1
+ 2.3.3
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.3
5
+ before_install: gem install bundler -v 1.14.4
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in config_x.gemspec
4
+ gemspec
@@ -0,0 +1,109 @@
1
+ # ConfigX
2
+
3
+ ConfigX provides you with a simple DSL to configure classes, modules and objects. It comes with a few features:
4
+
5
+ 1. Default values
6
+ 2. Attr accessors
7
+ 3. Memoization of values
8
+ 4. Inheritance (You can also access the config of a class in their instances)
9
+ 5. Blocks can be evaluated within your configurable
10
+ 6. You can nest your configs
11
+ 7. You can clone your configurables. When you do that blocks will be evaluated in the cloned object.
12
+ 8. You can call `to_h` on a config to get your config values in a hash
13
+
14
+ The `configurable` method builds the interface for your configuration at parse time. All necessary methods are defined
15
+ into modules that are then used to extend your classes and objects before you actually use them in your application.
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ ```ruby
22
+ gem 'config_x'
23
+ ```
24
+
25
+ And then execute:
26
+
27
+ $ bundle
28
+
29
+ Or install it yourself as:
30
+
31
+ $ gem install config_x
32
+
33
+ ## Usage
34
+
35
+ ### Configure on class level with `configurable :class`
36
+
37
+ ```ruby
38
+ class Client
39
+ extend ConfigX
40
+
41
+ configurable :class do
42
+ attr :env, accessor: true do
43
+ attr :url, default: 'www.example.com'
44
+ attr :api_key, memoize: true
45
+ end
46
+ end
47
+ end
48
+
49
+ Client.config.env.api_key { SecureRandom.hex }
50
+
51
+ p Client.config.env.api_key # 2594c07670bd16dfdd48f9874d190f80
52
+ p Client.config.env.url # www.example.com"
53
+ p Client.config.to_h # {:env=>{:url=>"www.example.com", :api_key=>"2594c07670bd16dfdd48f9874d190f80"}}
54
+ ```
55
+
56
+ ### Configure on an instance level with `configurable :instance`
57
+
58
+ ```ruby
59
+ class Client
60
+ extend ConfigX
61
+
62
+ configurable :instance do
63
+ attr :env, accessor: true do
64
+ attr :url, default: 'www.example.com'
65
+ attr :api_key, memoize: true
66
+ attr :timeout, instance_exec: true
67
+ end
68
+ end
69
+
70
+ def timeout
71
+ 5
72
+ end
73
+ end
74
+
75
+ client = Client.new
76
+ client.config.env.api_key { SecureRandom.hex }
77
+ client.config.env.timeout { timeout } # or client.config.env.timeout { |client| client.timeout }
78
+
79
+ p client.config.env.api_key # 2594c07670bd16dfdd48f9874d190f80
80
+ p client.config.env.url # www.example.com"
81
+ p client.config.env.timeout # 5
82
+ p client.config.to_h # {:env=>{:url=>"www.example.com", :api_key=>"2594c07670bd16dfdd48f9874d190f80", :timeout=>5}}
83
+ ```
84
+
85
+ ### `configure & configure!`
86
+
87
+ You can use the `configure`method to assign values and `configure!` will freeze the underlying hash so that values are
88
+ safe from being overwritten. Blocks cannot be memoized when you use `configure!`
89
+
90
+ ```ruby
91
+ client = Client.new
92
+ client.configure! do |config|
93
+ config.env.api_key = '123123'
94
+ config.env.timeout = 5
95
+ end
96
+ ```
97
+
98
+ For further examples just have a look at the specs.
99
+
100
+ ## Development
101
+
102
+ 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.
103
+
104
+ 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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
105
+
106
+ ## Contributing
107
+
108
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Andreas Robecke/config_x.
109
+
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "config_x"
5
+
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__)
@@ -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,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'config_x/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'config_x'
8
+ spec.version = ConfigX::VERSION
9
+ spec.authors = ['Andreas Robecke']
10
+ spec.email = ['andreas@robecke.de']
11
+
12
+ spec.summary = %q{A simple DSL to configure classes and objects}
13
+ spec.description = %q{ConfigX provides you with a simple DSL to configure classes, modules and objects}
14
+ spec.license = 'MIT'
15
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
16
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+ spec.bindir = "exe"
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.14"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ spec.add_development_dependency "rspec", "~> 3.0"
28
+ spec.add_development_dependency "pry"
29
+ end
@@ -0,0 +1,34 @@
1
+ require 'config_x/version'
2
+ require 'config_x/namespace_delegator'
3
+ require 'config_x/interface/base'
4
+ require 'config_x/interface/builder'
5
+
6
+ module ConfigX
7
+ CONFIG_NAMESPACE = 'config'
8
+
9
+ def configurable(type, &block)
10
+ store_maps = {}
11
+ config_interface = Module.new
12
+ owner_interface = Module.new
13
+
14
+ store_maps[type] = Interface::Builder.new(config_interface, owner_interface).build(&block)
15
+
16
+ interfaces = [
17
+ Interface::Base,
18
+ config_interface,
19
+ owner_interface
20
+ ]
21
+
22
+ inclusion_method = case type
23
+ when :instance
24
+ :include
25
+ when :class
26
+ :extend
27
+ else
28
+ raise StandardError, "Type #{type} not supported in configurable. Only :instance and :class"
29
+ end
30
+
31
+ interfaces.each { |interface| send inclusion_method, interface }
32
+ instance_variable_set(:@store_maps, store_maps)
33
+ end
34
+ end
@@ -0,0 +1,68 @@
1
+ module ConfigX
2
+ module Interface
3
+ NAMESPACE_SEPARATOR = '->'.freeze
4
+ PROC_SEPARATOR = '->'.freeze
5
+ TO_H_METHOD_NAME = 'config->to_h'.freeze
6
+
7
+ module Base
8
+ def config
9
+ @config ||= NamespaceDelegator.new(self)
10
+ end
11
+
12
+ def configure
13
+ yield(config)
14
+ end
15
+
16
+ def configure!
17
+ yield(config)
18
+ config_store.freeze
19
+ end
20
+
21
+ def clone
22
+ cloned = super
23
+ cloned_store = config_store.clone
24
+ cloned.instance_variable_set(:@config_store, cloned_store)
25
+ cloned.instance_variable_set(:@config, NamespaceDelegator.new(cloned))
26
+ cloned
27
+ end
28
+
29
+ define_method TO_H_METHOD_NAME do |map = store_map|
30
+ map.inject({}) do |acc, (k,v)|
31
+ key = k.to_sym
32
+ if v.is_a?(Hash)
33
+ acc[key] = send(TO_H_METHOD_NAME, v)
34
+ else
35
+ acc[key] = self.send(v)
36
+ end
37
+
38
+ acc
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def config_store
45
+ @config_store ||= {}
46
+ end
47
+
48
+ def evaluate_config_proc_or_value(value, instance_exec: false)
49
+ return unless value
50
+ return value unless value.respond_to?(:call)
51
+
52
+ if instance_exec
53
+ value.arity == 1 ? instance_exec(self, &value) : instance_exec(&value)
54
+ else
55
+ value.call
56
+ end
57
+ end
58
+
59
+ def store_map
60
+ if is_a?(Class)
61
+ instance_variable_get(:@store_maps)[:class]
62
+ else
63
+ self.class.instance_variable_get(:@store_maps)[:instance]
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,152 @@
1
+ module ConfigX
2
+ module Interface
3
+ class Builder
4
+ def initialize(config_interface, owner_interface, namespace = [], store_map = {})
5
+ @config_interface = config_interface
6
+ @owner_interface = owner_interface
7
+ @namespace = namespace
8
+ @store_map = store_map
9
+ end
10
+
11
+ def build(&block)
12
+ instance_exec(&block)
13
+ @store_map
14
+ end
15
+
16
+ def attr(name, opts = {}, &block)
17
+ name = name.to_s
18
+ validate_attr_name(name)
19
+
20
+ if block
21
+ define_nested_attr_interface(name, opts, &block)
22
+ else
23
+ define_attr_interface(name, opts, &block)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def define_attr_interface(name, opts)
30
+ config_prefixed_namespaced_name = config_prefixed_namespaced_name(name)
31
+ @store_map[name] = config_prefixed_namespaced_name
32
+
33
+ define_getter(
34
+ @config_interface,
35
+ name,
36
+ @namespace,
37
+ config_prefixed_namespaced_name,
38
+ opts
39
+ )
40
+
41
+ define_setter(
42
+ @config_interface,
43
+ name,
44
+ @namespace,
45
+ config_prefixed_namespaced_name
46
+ )
47
+
48
+ return unless opts[:accessor]
49
+
50
+ define_getter(
51
+ @owner_interface,
52
+ name,
53
+ @namespace,
54
+ namespaced_name(name, '_'),
55
+ opts
56
+ )
57
+
58
+ define_setter(
59
+ @owner_interface,
60
+ name,
61
+ @namespace,
62
+ namespaced_name(name, '_')
63
+ )
64
+ end
65
+
66
+ def define_nested_attr_interface(name, opts, &block)
67
+ config_prefixed_namespaced_name = config_prefixed_namespaced_name(name)
68
+
69
+ Builder.new(
70
+ @config_interface,
71
+ @owner_interface,
72
+ @namespace + [name],
73
+ @store_map[name] ||= {}
74
+ ).build(&block)
75
+
76
+ define_delegator(
77
+ @config_interface,
78
+ config_prefixed_namespaced_name,
79
+ @namespace + [name]
80
+ )
81
+
82
+ return unless opts[:accessor]
83
+
84
+ define_delegator(
85
+ @owner_interface,
86
+ namespaced_name(name),
87
+ @namespace + [name]
88
+ )
89
+ end
90
+
91
+ def validate_attr_name(name)
92
+ return if name =~ /\A[0-9a-zA-z_]+\z/
93
+ raise StandardError, "Attribute name #{name} is not allowed. Only [0-9a-zA-z_]+"
94
+ end
95
+
96
+ def define_getter(target, variable_name, namespace, method_name, opts = {})
97
+ proc_name = "#{variable_name}#{PROC_SEPARATOR}proc"
98
+
99
+ target.send :define_method, method_name do |&block|
100
+ namespaced_config = config_store
101
+
102
+ if namespace.any?
103
+ namespaced_config = namespace.inject(config_store) do |acc, key|
104
+ acc[key] ||= {}
105
+ end
106
+ end
107
+
108
+ if block
109
+ namespaced_config[proc_name] = block
110
+ elsif namespaced_config[variable_name]
111
+ namespaced_config[variable_name]
112
+ elsif value = namespaced_config[proc_name] || opts[:default]
113
+ value = evaluate_config_proc_or_value(value, instance_exec: opts[:instance_exec])
114
+ namespaced_config[variable_name] = value if opts[:memoize]
115
+ value
116
+ end
117
+ end
118
+ end
119
+
120
+ def define_setter(target, variable_name, namespace, method_name = variable_name)
121
+ target.send :define_method, "#{method_name}=" do |value|
122
+ namespaced_config = config_store
123
+
124
+ if namespace.any?
125
+ namespaced_config = namespace.inject(config_store) do |acc, key|
126
+ acc[key] ||= {}
127
+ end
128
+ end
129
+ namespaced_config[variable_name] = value
130
+ end
131
+ end
132
+
133
+ def config_prefixed_namespaced_name(name)
134
+ ([CONFIG_NAMESPACE] + namespace_for(name)).join(NAMESPACE_SEPARATOR)
135
+ end
136
+
137
+ def namespaced_name(name, separator = NAMESPACE_SEPARATOR)
138
+ namespace_for(name).join(separator)
139
+ end
140
+
141
+ def namespace_for(name)
142
+ (@namespace + [name]).compact
143
+ end
144
+
145
+ def define_delegator(target, method_name, namespace)
146
+ target.send :define_method, method_name do
147
+ NamespaceDelegator.new(self, namespace)
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,22 @@
1
+ module ConfigX
2
+ class NamespaceDelegator
3
+ def initialize(target, namespace = [])
4
+ @target = target
5
+ @namespace = namespace
6
+ end
7
+
8
+ attr_accessor :target
9
+
10
+ def method_missing(*args, &block)
11
+ method_name = args.shift
12
+ target.send(config_prefixed_name(method_name), *args, &block)
13
+ end
14
+
15
+ private
16
+
17
+ def config_prefixed_name(name)
18
+ parts = ([CONFIG_NAMESPACE] + @namespace + [name]).compact
19
+ parts.join(Interface::NAMESPACE_SEPARATOR)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module ConfigX
2
+ VERSION = '0.1.0'
3
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: config_x
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andreas Robecke
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-05-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: ConfigX provides you with a simple DSL to configure classes, modules
70
+ and objects
71
+ email:
72
+ - andreas@robecke.de
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".ruby-version"
80
+ - ".travis.yml"
81
+ - Gemfile
82
+ - README.md
83
+ - Rakefile
84
+ - bin/console
85
+ - bin/setup
86
+ - config_x.gemspec
87
+ - lib/config_x.rb
88
+ - lib/config_x/interface/base.rb
89
+ - lib/config_x/interface/builder.rb
90
+ - lib/config_x/namespace_delegator.rb
91
+ - lib/config_x/version.rb
92
+ homepage:
93
+ licenses:
94
+ - MIT
95
+ metadata: {}
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubyforge_project:
112
+ rubygems_version: 2.5.2
113
+ signing_key:
114
+ specification_version: 4
115
+ summary: A simple DSL to configure classes and objects
116
+ test_files: []