environment_config 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 94877d33ba5f427d4f5d7c8098a1b472c59a873ea53b037e661a5aa564b37e79
4
+ data.tar.gz: 8bd759e5b24c255952398bb3cb72776d0e49c7eb6618a77058b9a440c875ba09
5
+ SHA512:
6
+ metadata.gz: 36f865ef45d16d05b5706048b41afb743ae5af8b30d43a0e4c45f0ae3aa9c1594030a432e9ab37442823bbf96f621420fb13ccf281b2f62dfab354e09d6c4804
7
+ data.tar.gz: 0afe62776474b2cc33e9be969133fdd507ecf9f6e01a5ef1c3f4f3c3278d326b9b64c279cb4618d99356f732d487aaf6d479c9bc966dc8f546052f303fae7a3f
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ Gemfile.lock
2
+
3
+ spec/examples.txt
4
+
5
+ *.gem
data/.gitlab-ci.yml ADDED
@@ -0,0 +1,17 @@
1
+ image: ruby:2.4.1
2
+
3
+ variables:
4
+ SSH_AUTH_SOCK: "/ssh-agent"
5
+
6
+ before_script:
7
+ - mkdir -p /root/.ssh && ssh-keyscan -H codevault.io > /root/.ssh/known_hosts 2>/dev/null
8
+ - bundle config jobs 8
9
+ - bundle install --path=/tmp/bundler --quiet
10
+
11
+ rspec:
12
+ script:
13
+ - bundle exec rspec
14
+
15
+ rubocop:
16
+ script:
17
+ - bundle exec rubocop
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,3 @@
1
+ inherit_gem:
2
+ kp_cop:
3
+ - default.yml
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ ## 1.2.0
2
+
3
+ * Add support for complex datatypes via `json` and `yaml` parsers
4
+
5
+ ## 1.1.0
6
+
7
+ * Add `EnvironmentConfig.ensure`, which is a more expressive way to
8
+ check the presence of environment variables without using them
9
+ * Add `EnvironmentConfig.fetch_*` methods that will fetch a single
10
+ environment variable expecting the given type (e.g. `fetch_string`).
11
+ * Improve error readout for unparsable integer
12
+
13
+ ## 1.0.1
14
+
15
+ * Make method private that should never have been public
16
+ * The `store` and `fetch` methods are not really part of the public interface
17
+ of the config, however since `fetch` is equal to a method on hashes,
18
+ it is quite likely to be called by accident
19
+
20
+ ## 1.0.0
21
+
22
+ * Initial release
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+ git_source(:codevault) { |repo_name| "git@codevault.io:#{repo_name}.git" }
5
+
6
+ # Specify your gem's dependencies in auth_connector.gemspec
7
+ gemspec
8
+
9
+ # Need to mention it here instead of gemspec, since we pull it from a git repo
10
+ gem 'kp_cop', codevault: 'kaeuferportal/kp_cop'
data/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # EnvironmentConfig
2
+
3
+ This gem provides a unified way to read configuration parameters
4
+ from environment variables.
5
+
6
+
7
+ ## Usage
8
+
9
+ You can load a configuration from the environment using a `load` block:
10
+
11
+ ```ruby
12
+ config = EnvironmentConfig.load do |c|
13
+ c.string 'FOO'
14
+ c.integer 'BAR', 42
15
+ end
16
+ ```
17
+
18
+ If any required variable is missing, an error will be raised. Errors can be
19
+ suppressed by providing a default value as second parameter.
20
+
21
+ Albeit environment variables being string only, you can specify types when
22
+ defining parameters:
23
+
24
+ * `string`
25
+ * `symbol`
26
+ * `integer` (base 10 encoded)
27
+ * `boolean` (only accepts `true` and `false`)
28
+ * `string_list` (comma separated)
29
+ * `json`
30
+ * `yaml`
31
+
32
+ Type conversions try to be strict and will throw an error, if the conversion
33
+ can't be performed safely (e.g. an integer receiving `abc` will raise an error
34
+ instead of parsing to `0`).
35
+
36
+ Be mindful, that `json` and `yaml` allow you to pass rather complex configuration, but will not be able to validate the content beyond checking it is valid JSON/YAML.
37
+
38
+ ### Accessing values
39
+
40
+ After building the configuration, your values will be available:
41
+
42
+ * as methods on the config object
43
+ * as hash with string keys using `to_string_hash`
44
+ * as hash with symbol keys using `to_symbol_hash`
45
+
46
+ All keys are lower cased, so `FOO_ARG` will become `config.foo_arg` or `config.to_string_hash['foo']`.
47
+
48
+ ### Avoiding prefix duplication
49
+
50
+ For environment variables you will usually want to assign common prefixes, e.g.
51
+
52
+ ```bash
53
+ export FOO_HOST=host
54
+ export FOO_PASSWORD=password
55
+ ```
56
+
57
+ Oftentimes you don't want to have those prefixes in your application config,
58
+ so you can strip them using `strip_prefix`:
59
+
60
+ ```ruby
61
+ # defining it
62
+ foo_config = EnvironmentConfig.load(strip_prefix: 'FOO_') do |c|
63
+ c.string 'FOO_HOST', 'default_host'
64
+ c.string 'FOO_PASSWORD'
65
+ c.string 'OTHER_FOO_VALUE', 'test'
66
+ end
67
+
68
+ # using it
69
+ foo_config.host # default_host
70
+ foo_config.other_foo_value # test
71
+ ```
72
+
73
+ ### Accessing single values
74
+
75
+ Sometimes you just want to fetch a few unrelated values from the environment,
76
+ but not create a configuration object.
77
+
78
+ There are fetch methods for all known types. For example:
79
+
80
+ ```ruby
81
+ EnvironmentConfig.fetch_integer('MY_PORT')
82
+ => 42
83
+ ```
84
+
85
+ ### Expecting environment variables
86
+
87
+ If you want to check the correct definition of environment variables that you do
88
+ not consume yourself (e.g. you know that a gem will consume them), just use
89
+ the `ensure` helper method:
90
+
91
+ ```ruby
92
+ EnvironmentConfig.ensure do |c|
93
+ c.string 'MY_GEM_CONFIG_VALUE'
94
+ c.integer 'MY_GEM_OTHER_VALUE'
95
+ end
96
+ ```
97
+
98
+ ### Integration with Rails
99
+
100
+ One possible integration into a Rails application could look like this:
101
+
102
+ ```ruby
103
+ # config/application.rb
104
+ module MyRailsApp
105
+ class Application < Rails::Application
106
+ # ...
107
+
108
+ config.some_sub_configuration = EnvironmentConfig.load do |c|
109
+ c.string 'FOO_HOST', 'default_host'
110
+ c.string 'FOO_PASSWORD'
111
+ c.boolean 'SOME_BOOL', false
112
+ end
113
+ end
114
+ end
115
+
116
+ # somewhere else
117
+ Rails.application.config.some_sub_configuration.foo
118
+ ```
data/bin/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'environment_config'
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
+ require 'pry'
10
+ Pry.start
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ lib = File.expand_path('lib', __dir__)
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+ require 'environment_config/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'environment_config'
10
+ spec.version = EnvironmentConfig::VERSION
11
+ spec.authors = ['Jan Sandbrink']
12
+ spec.email = ['jan.sandbrink@kaeuferportal.de']
13
+
14
+ spec.summary = 'Gem to unify reading configuration from env variables.'
15
+ spec.homepage = 'https://codevault.io/kaeuferportal/environment_config'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ .reject { |f| f.match(%r{^spec/}) }
19
+ spec.bindir = 'exe'
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.10'
24
+ spec.add_development_dependency 'pry'
25
+ spec.add_development_dependency 'rspec', '~> 3.6'
26
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'environment_config/builder'
4
+ require 'environment_config/type_fetcher_methods'
5
+
6
+ class EnvironmentConfig
7
+ include TypeFetcherMethods
8
+
9
+ class << self
10
+ # Accepts the same block as `load` does, but only validates
11
+ # the presence and type of environment variables. Does not return
12
+ # a configuration.
13
+ def ensure(&block)
14
+ load(&block)
15
+ nil
16
+ end
17
+
18
+ # Loads a configuration from environment variables as specified
19
+ # by the block given (using the environment config DSL)
20
+ def load(**options)
21
+ builder = Builder.new(**options)
22
+ yield builder
23
+ builder.config
24
+ end
25
+ end
26
+
27
+ def initialize
28
+ @store = {}
29
+ end
30
+
31
+ def method_missing(method_name, *_arguments, &block)
32
+ return fetch method_name if known_key?(method_name)
33
+
34
+ super
35
+ end
36
+
37
+ def respond_to_missing?(method_name, _include_private = false)
38
+ known_key?(method_name) || super
39
+ end
40
+
41
+ def to_h
42
+ # This method solely exists for purposes of "irb discoverability"
43
+ raise NotImplementedError,
44
+ 'Please choose between to_string_hash and to_symbol_hash, ' \
45
+ 'depending on the key type you want to get.'
46
+ end
47
+
48
+ def to_string_hash
49
+ @store.dup
50
+ end
51
+
52
+ def to_symbol_hash
53
+ @store.map { |k, v| [k.to_sym, v] }.to_h
54
+ end
55
+
56
+ def store(key, value)
57
+ @store[key.to_s] = value
58
+ end
59
+
60
+ private
61
+
62
+ def fetch(key)
63
+ @store[key.to_s]
64
+ end
65
+
66
+ def known_key?(key)
67
+ @store.key? key.to_s
68
+ end
69
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'environment_config/typed_env'
4
+ require 'environment_config/types'
5
+
6
+ class EnvironmentConfig
7
+ class Builder
8
+ attr_reader :config
9
+
10
+ def initialize(strip_prefix: nil)
11
+ @strip_prefix = strip_prefix
12
+ @config = EnvironmentConfig.new
13
+ end
14
+
15
+ def method_missing(method_name, *args)
16
+ if Types.known_type?(method_name)
17
+ type = method_name
18
+ key = args.shift
19
+ convert_and_store(type, key, *args)
20
+ else
21
+ super
22
+ end
23
+ end
24
+
25
+ def respond_to_missing?(method_name, *args)
26
+ Types.known_type?(method_name) || super
27
+ end
28
+
29
+ private
30
+
31
+ def convert_and_store(type, key, *args)
32
+ value = TypedEnv.fetch(type, key, *args)
33
+ store(key, value)
34
+ end
35
+
36
+ def store(key, value)
37
+ if @strip_prefix && key.start_with?(@strip_prefix)
38
+ key = key[@strip_prefix.size..-1]
39
+ end
40
+ config.store(key.downcase, value)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'environment_config/typed_env'
4
+ require 'environment_config/types'
5
+
6
+ class EnvironmentConfig
7
+ module TypeFetcherMethods
8
+ class << self
9
+ def included(base)
10
+ base.extend(ClassMethods)
11
+ end
12
+ end
13
+
14
+ module ClassMethods
15
+ def method_missing(method_name, *args)
16
+ type = type_of_fetch_method(method_name)
17
+ if type && Types.known_type?(type)
18
+ key = args.shift
19
+ TypedEnv.fetch(type, key, *args)
20
+ else
21
+ super
22
+ end
23
+ end
24
+
25
+ def respond_to_missing?(method_name, *_args)
26
+ type = type_of_fetch_method(method_name)
27
+ (type && Types.known_type?(type)) || super
28
+ end
29
+
30
+ private
31
+
32
+ def type_of_fetch_method(method_name)
33
+ match = /\Afetch_(.+)/.match(method_name)
34
+ return nil unless match
35
+
36
+ match[1]
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'environment_config/types'
4
+
5
+ class EnvironmentConfig
6
+ class TypedEnv
7
+ class << self
8
+ def fetch(type, key, *args)
9
+ Types.convert(type, key, fetch_raw(key, *args))
10
+ end
11
+
12
+ private
13
+
14
+ def fetch_raw(key, *args)
15
+ ENV.fetch(key, *args)
16
+ rescue KeyError => e
17
+ raise e,
18
+ "Expected environment variable #{key} to be set, but was missing."
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'environment_config/types/boolean'
4
+ require 'environment_config/types/integer'
5
+ require 'environment_config/types/json'
6
+ require 'environment_config/types/string'
7
+ require 'environment_config/types/string_list'
8
+ require 'environment_config/types/symbol'
9
+ require 'environment_config/types/yaml'
10
+
11
+ require 'environment_config/types/type_error'
12
+
13
+ class EnvironmentConfig
14
+ module Types
15
+ ALL = [
16
+ ::EnvironmentConfig::Types::Boolean,
17
+ ::EnvironmentConfig::Types::Integer,
18
+ ::EnvironmentConfig::Types::Json,
19
+ ::EnvironmentConfig::Types::String,
20
+ ::EnvironmentConfig::Types::StringList,
21
+ ::EnvironmentConfig::Types::Symbol,
22
+ ::EnvironmentConfig::Types::Yaml
23
+ ].freeze
24
+
25
+ class << self
26
+ def convert(type_name, key, value)
27
+ type = type_map.fetch(type_name.to_sym)
28
+ type.convert(value)
29
+ rescue Types::TypeError => e
30
+ raise ArgumentError,
31
+ "Environment variable #{key} could not be read as #{type_name}." \
32
+ " Expected: #{e.expected_message}" \
33
+ " Got: #{e.value}"
34
+ end
35
+
36
+ def type_names
37
+ type_map.keys
38
+ end
39
+
40
+ def known_type?(type_name)
41
+ type_map.key?(type_name.to_sym)
42
+ end
43
+
44
+ private
45
+
46
+ def type_map
47
+ @type_map ||= ALL.map { |type| [type.name, type] }.to_h
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EnvironmentConfig
4
+ module Types
5
+ class Boolean
6
+ BOOLEAN_VALUES = %w[true false].freeze
7
+
8
+ class << self
9
+ def name
10
+ :boolean
11
+ end
12
+
13
+ def convert(value)
14
+ # ensure compatibility when receiving correctly typed default value
15
+ value = value.to_s
16
+
17
+ unless BOOLEAN_VALUES.include?(value)
18
+ raise TypeError.new(BOOLEAN_VALUES.join('/'), value)
19
+ end
20
+
21
+ value == 'true'
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EnvironmentConfig
4
+ module Types
5
+ class Integer
6
+ class << self
7
+ def name
8
+ :integer
9
+ end
10
+
11
+ def convert(value)
12
+ Integer(value)
13
+ rescue ArgumentError
14
+ raise TypeError.new('base 10 integer', value)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ class EnvironmentConfig
6
+ module Types
7
+ class Json
8
+ class << self
9
+ def name
10
+ :json
11
+ end
12
+
13
+ def convert(value)
14
+ return value if value.is_a?(Array) || value.is_a?(Hash)
15
+ return nil if value.empty?
16
+
17
+ JSON.parse(value)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EnvironmentConfig
4
+ module Types
5
+ class String
6
+ class << self
7
+ def name
8
+ :string
9
+ end
10
+
11
+ def convert(value)
12
+ value.to_s
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EnvironmentConfig
4
+ module Types
5
+ class StringList
6
+ class << self
7
+ def name
8
+ :string_list
9
+ end
10
+
11
+ def convert(value)
12
+ return value if value.is_a? Array
13
+
14
+ value.split(',')
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EnvironmentConfig
4
+ module Types
5
+ class Symbol
6
+ class << self
7
+ def name
8
+ :symbol
9
+ end
10
+
11
+ def convert(value)
12
+ value.to_sym
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EnvironmentConfig
4
+ module Types
5
+ class TypeError < StandardError
6
+ attr_reader :expected_message, :value
7
+
8
+ def initialize(expected_message, value)
9
+ @expected_message = expected_message
10
+ @value = value
11
+
12
+ super()
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ class EnvironmentConfig
6
+ module Types
7
+ class Yaml
8
+ class << self
9
+ def name
10
+ :yaml
11
+ end
12
+
13
+ def convert(value)
14
+ return value if value.is_a?(Array) || value.is_a?(Hash)
15
+ return nil if value.empty?
16
+
17
+ YAML.safe_load(value)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EnvironmentConfig
4
+ VERSION = '1.2.0'.freeze
5
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: environment_config
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Jan Sandbrink
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-11-20 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.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '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.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.6'
55
+ description:
56
+ email:
57
+ - jan.sandbrink@kaeuferportal.de
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".gitlab-ci.yml"
64
+ - ".rspec"
65
+ - ".rubocop.yml"
66
+ - CHANGELOG.md
67
+ - Gemfile
68
+ - README.md
69
+ - bin/console
70
+ - environment_config.gemspec
71
+ - lib/environment_config.rb
72
+ - lib/environment_config/builder.rb
73
+ - lib/environment_config/type_fetcher_methods.rb
74
+ - lib/environment_config/typed_env.rb
75
+ - lib/environment_config/types.rb
76
+ - lib/environment_config/types/boolean.rb
77
+ - lib/environment_config/types/integer.rb
78
+ - lib/environment_config/types/json.rb
79
+ - lib/environment_config/types/string.rb
80
+ - lib/environment_config/types/string_list.rb
81
+ - lib/environment_config/types/symbol.rb
82
+ - lib/environment_config/types/type_error.rb
83
+ - lib/environment_config/types/yaml.rb
84
+ - lib/environment_config/version.rb
85
+ homepage: https://codevault.io/kaeuferportal/environment_config
86
+ licenses: []
87
+ metadata: {}
88
+ post_install_message:
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubyforge_project:
104
+ rubygems_version: 2.7.6
105
+ signing_key:
106
+ specification_version: 4
107
+ summary: Gem to unify reading configuration from env variables.
108
+ test_files: []