confuse 0.1.8 → 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.
data/README.md CHANGED
@@ -1,12 +1,15 @@
1
- # Configuration
1
+ # Confuse
2
2
 
3
- TODO: Write a gem description
3
+ This gem is used to simplify specifying and reading configuration.
4
+
5
+ You can define what you expect to be configured, and this can be read from ini
6
+ files, yaml files or environment variables.
4
7
 
5
8
  ## Installation
6
9
 
7
10
  Add this line to your application's Gemfile:
8
11
 
9
- gem 'configuration'
12
+ gem 'confuse'
10
13
 
11
14
  And then execute:
12
15
 
@@ -18,7 +21,112 @@ Or install it yourself as:
18
21
 
19
22
  ## Usage
20
23
 
21
- TODO: Write usage instructions here
24
+ Basic usage:
25
+
26
+ config = Confuse.config do |conf|
27
+ conf.add_item :foo, :description => 'Foo', :default => 1
28
+ end
29
+
30
+ config[:foo]
31
+ config.foo
32
+
33
+ Types:
34
+
35
+ You can choose where and your config is stored by passing a :path variable to
36
+ Confuse.config:
37
+
38
+ config = Confuse.config :path => 'config.ini' do |conf|
39
+ conf.add_item :foo, :description => 'Foo', :default => 1
40
+ end
41
+
42
+ Confuse will attempt to work out the type of file from the extension (Supports
43
+ .ini for ini files, and .yml or .yaml for yaml files). You can override this by
44
+ passing in a :type option:
45
+
46
+ config = Confuse.config :path => 'foo.conf' :type => :yaml do |conf|
47
+ conf.add_item :foo, :description => 'Foo', :default => 1
48
+ end
49
+
50
+ If no type or path is given, it will default to environment variables.
51
+
52
+ Environment variables have one extra feature, which is the ability to provide a
53
+ prefix:
54
+
55
+
56
+ config = Confuse.config :prefix => 'FOO' :type => :yaml do |conf|
57
+ conf.add_item :foo, :description => 'Foo', :default => 1
58
+ end
59
+
60
+ This means Confuse will lookup environment varialbles with that prefix with an
61
+ underscore followed by the configuration name. If none is given, it will lookup
62
+ environment variables as is.
63
+
64
+ Namespaces:
65
+
66
+ You can separate your configuration into different namespaces:
67
+
68
+ config = Confuse.config :type => :env do |conf|
69
+ conf.add_namespace :foo do |ns|
70
+ ns.add_item :foo, :description => 'Foo', :default => 1
71
+ end
72
+ end
73
+
74
+ For simplicity, you can fetch these items using a single []:
75
+
76
+ config[:foo_bar]
77
+
78
+ Or a method call:
79
+
80
+ config.foo_bar
81
+
82
+ However, beware of adding an item at the top level with the same name.
83
+
84
+ Check:
85
+
86
+ If you want to make sure all your configuration items are in place before
87
+ running the program you can call .check on it.
88
+
89
+ config = Confuse.config do |conf|
90
+ conf.add_item :foo, :description => 'Foo', :default => 1
91
+ end
92
+
93
+ config.check
94
+
95
+ If any of the items haven't been set, and don't have a default value, an
96
+ exception will be thrown. If you don't care about certain variables, you can
97
+ pass :required => false to them (or give them a default value).
98
+
99
+ Use a different source:
100
+
101
+ If you want to use a source that isn't an ini file or yaml file, or environment
102
+ variable. It is very easy to create your own one. It simply needs to be a class
103
+ that has an initialize that takes a hash, and a [] method that takes a
104
+ namespace and a key argument.
105
+
106
+ class MyOwnSource
107
+ def initialize(options = {})
108
+ ...
109
+ end
110
+
111
+ def [](namespace, key)
112
+ ...
113
+ end
114
+ end
115
+
116
+ Confuse::Source.register(:foo, MyOwnSource)
117
+
118
+ definition = Confuse.config, :type => :foo do |conf|
119
+ conf.add_item :foo
120
+ end
121
+
122
+ config = Confuse::Config.new(definition, my_own_source)
123
+
124
+ If your class takes a :path variable, it will autodetect the extension for
125
+ whatever you register. You can register more than one extension if you wish.
126
+
127
+ You could use this to store data in whatever fancy file format you like, or
128
+ perhaps even a database table, or one of those fancy NoSQL datastores, you
129
+ could even write your config in Ruby code.
22
130
 
23
131
  ## Contributing
24
132
 
data/Rakefile CHANGED
@@ -1 +1,8 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs.push 'lib'
6
+ t.test_files = FileList['test/test_*.rb']
7
+ t.verbose = true
8
+ end
@@ -22,4 +22,5 @@ Gem::Specification.new do |spec|
22
22
 
23
23
  spec.add_development_dependency 'bundler', '~> 1.3'
24
24
  spec.add_development_dependency 'minitest'
25
+ spec.add_development_dependency 'rake'
25
26
  end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ require 'confuse'
5
+
6
+ config = Confuse.config :prefix => 'CONF' do |conf|
7
+ conf.add_item :foo, :description => "How many Foo's are there?", :default => 1
8
+ end
9
+
10
+ puts config.foo
@@ -0,0 +1 @@
1
+ foo = 10
@@ -0,0 +1 @@
1
+ :foo: 10
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ require 'confuse'
5
+
6
+ config = Confuse.config :path => 'example/example.ini' do |conf|
7
+ conf.add_item :foo, :description => "How many Foo's are there?", :default => 1
8
+ end
9
+
10
+ puts config.foo
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ require 'confuse'
5
+
6
+ config = Confuse.config :path => 'example/example.yaml' do |conf|
7
+
8
+ conf.add_item :foo, :description => "How many Foo's are there?", :default => 1
9
+ end
10
+
11
+ puts config.foo
@@ -1,11 +1,30 @@
1
+ # coding: utf-8
2
+
1
3
  require 'confuse/version'
2
4
 
5
+ require 'confuse/key_splitter'
3
6
  require 'confuse/config'
4
- require 'confuse/config_item'
5
- require 'confuse/config_mixin'
6
- require 'confuse/dsl'
7
+ require 'confuse/item'
8
+ require 'confuse/definition'
7
9
  require 'confuse/namespace'
10
+ require 'confuse/source'
11
+ require 'confuse/errors'
8
12
 
13
+ # Top level namespace for confuse gem
9
14
  module Confuse
10
- end
15
+ class << self
16
+ def define(&block)
17
+ Definition.new(&block)
18
+ end
11
19
 
20
+ def source(options = {})
21
+ Source.create(options)
22
+ end
23
+
24
+ def config(options = {}, &block)
25
+ definition = Definition.new(&block)
26
+ source = Source.create(options)
27
+ Config.new(definition, source)
28
+ end
29
+ end
30
+ end
@@ -1,48 +1,37 @@
1
- require 'confuse/config_mixin'
2
- require 'confuse/dsl'
1
+ # coding: utf-8
3
2
 
4
3
  module Confuse
5
- # The default module used for configuration.
6
- module Config
7
- extend ConfigMixin
8
- end
4
+ class Config
5
+ def initialize(definition, source)
6
+ @definition = definition
7
+ @source = source
8
+ end
9
9
 
10
- # Super class for configuration in order to have multiple instances.
11
- class ConfigBase
12
- include ConfigMixin
13
- extend DSL
10
+ def respond_to?(name)
11
+ @definition.defines?(name) || super
12
+ end
14
13
 
15
- def namespaces
16
- @namespaces ||= {}
14
+ def method_missing(name, *_args)
15
+ self[name] if respond_to?(name)
17
16
  end
18
17
 
19
- def initialize(options = {})
20
- load_namespaces(self.class.namespaces.clone)
21
- paths = options[:paths] || []
22
- if paths.flatten.empty?
23
- read_files(self.class.config_path.flatten)
24
- else
25
- read_files(paths.flatten)
26
- end
27
- options[:conf].tap { |conf| conf && mixin_config!(conf) }
18
+ def [](name)
19
+ namespace, key = @definition.namespace_and_key(name)
20
+ lookup(namespace, key)
28
21
  end
29
22
 
30
- def config
31
- self
23
+ def lookup(namespace, key)
24
+ @source[namespace, key] || @definition.default(namespace, key)
32
25
  end
33
26
 
34
- def self.params_hash
35
- namespaces.reduce({}) do |memo, (name, namespace)|
36
- namespace.keys.each do |key|
37
- item = namespace.get_item(key)
38
- memo[:"#{name}_#{key}"] = {
39
- :type => item.type,
40
- :doc => item.description,
41
- :default => item.default_value }
27
+ # check items have a value. Will raise Undefined error if a required item
28
+ # has no value.
29
+ def check
30
+ @definition.namespaces.each do |(namespace, ns)|
31
+ ns.items.each do |key, _|
32
+ lookup(namespace, key)
42
33
  end
43
- memo
44
34
  end
45
35
  end
46
-
47
36
  end
48
37
  end
@@ -0,0 +1,52 @@
1
+ # coding: utf-8
2
+
3
+ module Confuse
4
+ class Definition
5
+ def initialize(&block)
6
+ @default_namespace = :default
7
+ block.call(self)
8
+ end
9
+
10
+ def defines?(name)
11
+ !!find_item_by_name(name)
12
+ end
13
+
14
+ def default(namespace, key)
15
+ (item = find_item(namespace, key)) && item.default
16
+ end
17
+
18
+ def add_namespace(name, &block)
19
+ new_namespace = Namespace.new(&block)
20
+ namespaces[name.to_sym] = new_namespace
21
+ end
22
+
23
+ def add_item(name, opts = {})
24
+ namespaces[@default_namespace].add_item(name, opts)
25
+ end
26
+
27
+ def namespaces
28
+ @namespaces ||= {
29
+ @default_namespace => Namespace.new(&(Proc.new {}))
30
+ }
31
+ end
32
+
33
+ def namespace_and_key(name)
34
+ KeySplitter.new(name).split.find { |(ns, _)| namespaces[ns] } ||
35
+ [nil, name]
36
+ end
37
+
38
+ private
39
+
40
+ def find_item_by_name(name)
41
+ namespace, key = namespace_and_key(name)
42
+ end
43
+
44
+ def find_item(namespace, key)
45
+ (ns = find_namespace(namespace)) && ns[key]
46
+ end
47
+
48
+ def find_namespace(name)
49
+ namespaces[name || @default_namespace]
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,12 @@
1
+ # coding: utf-8
2
+
3
+ module Confuse
4
+ module Errors
5
+ class Undefined < StandardError
6
+ def initialize(name)
7
+ @name = name
8
+ super
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,20 @@
1
+ # coding: utf-8
2
+
3
+ module Confuse
4
+ # An {Item} is a class for storing a description piece of config.
5
+ class Item
6
+ def initialize(key, opts = {})
7
+ @key = key
8
+ @default, @description = opts.values_at(:default, :description)
9
+ @required = opts.key?(:required) ? opts[:required] : true
10
+ end
11
+
12
+ attr_reader :description, :required
13
+
14
+ def default
15
+ res = @default
16
+ raise Errors::Undefined.new(@key) if @required && !res
17
+ res
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,40 @@
1
+ # coding: utf-8
2
+
3
+ module Confuse
4
+ # This class encapsulates the code required to support searching for items
5
+ # with a single key item even for nested items.
6
+ # config[:foo_bar] instead of config[:foo][:bar]
7
+ class KeySplitter
8
+ def initialize(key)
9
+ @key = key
10
+ end
11
+
12
+ def split
13
+ possible_namespaces.map do |ns|
14
+ [ns, rest_of_key(ns)]
15
+ end
16
+ end
17
+
18
+ # Returns an array of possible namespaces based on splitting the key at
19
+ # every underscore.
20
+ def possible_namespaces
21
+ namespaces = []
22
+ key = @key.to_s
23
+ while (index = key.rindex('_'))
24
+ key = key[0, index]
25
+ namespaces << key.to_sym
26
+ end
27
+ namespaces
28
+ end
29
+
30
+ # Returns the rest of the key for a given namespace
31
+ def rest_of_key(namespace)
32
+ return nil if @key == namespace
33
+
34
+ key = @key.to_s
35
+
36
+ index = key.index(namespace.to_s) && (namespace.to_s.length + 1)
37
+ key[index, key.length].to_sym if index
38
+ end
39
+ end
40
+ end
@@ -1,75 +1,22 @@
1
- require 'confuse/config_item'
1
+ # coding: utf-8
2
2
 
3
3
  module Confuse
4
4
  # A {Namespace} is a container to keep configuration data seperate from the
5
5
  # rest of the config.
6
6
  class Namespace
7
-
8
- attr_reader :items, :supress_warnings_flag, :strict_flag
7
+ attr_reader :items
9
8
 
10
9
  def initialize(&block)
11
10
  @items = {}
12
- @supress_warnings_flag = false
13
- @strict_flag = false
14
- instance_eval(&block)
15
- end
16
-
17
- def define(name, &block)
18
- @items[name] = ConfigItem.new(name, &block)
19
- end
20
-
21
- def supress_warnings
22
- @supress_warnings_flag = true
23
- end
24
-
25
- def strict
26
- @strict_flag = true
27
- end
28
-
29
- def [](key, config = nil)
30
- value = (i = get_item(key)) && i.value
31
- if value.respond_to?(:call) && !config.nil?
32
- value.call(config)
33
- else
34
- value
35
- end
11
+ block.call(self) if block_given?
36
12
  end
37
13
 
38
- def []=(key, value)
39
- item = get_item(key) || create_new_key(key, value)
40
- item && item.value = value
14
+ def add_item(name, opts = {})
15
+ @items[name] = Item.new(name, opts)
41
16
  end
42
17
 
43
- def keys
44
- @items.keys
45
- end
46
-
47
- def create_new_key(key, value)
48
- if @supress_warnings_flag
49
- puts "Warning: config includes unknown option '#{key}'"
50
- end
51
- @items[key] = ConfigItem.new(key, &nil) unless @strict_flag
52
- end
53
-
54
- def get_item(key)
18
+ def [](key)
55
19
  @items[key]
56
20
  end
57
-
58
- def merge!(namespace)
59
- @strict_flag = namespace.strict
60
- @supress_warnings_flag = namespace.supress_warnings
61
- @items.merge! namespace.clone.items
62
- end
63
-
64
- def clone
65
- c = super
66
- items = @items.reduce({}) do |m, (k, v)|
67
- m[k] = v.clone
68
- m
69
- end
70
- c.instance_variable_set(:"@items", items)
71
- c
72
- end
73
-
74
21
  end
75
22
  end