bolts 0.1.0dev1

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,3 @@
1
+ module Bolts
2
+ require 'bolts/version'
3
+ end
@@ -0,0 +1,69 @@
1
+ module Bolts
2
+ class Application
3
+
4
+ # @!attribute [r] name
5
+ # @return [String]
6
+ attr_reader :name
7
+
8
+ # @!attribute [r] initializers
9
+ # @return [Array<Object>] A list of initializers to run before
10
+ # application startup
11
+ attr_reader :initializers
12
+
13
+ # @!attribute [r] finalizers
14
+ # @return [Array<Object>] A list of finalizers to run after application
15
+ # completion
16
+ attr_reader :finalizers
17
+
18
+ # @!attribute [r] error_handlers
19
+ # @return [Array<Object>] A list of erro handles to run in case of
20
+ # application errors.
21
+ attr_reader :error_handlers
22
+
23
+ def initialize(name)
24
+ @name = name
25
+
26
+ @main = nil
27
+
28
+ @initializers = []
29
+ @finalizers = []
30
+
31
+ @error_handlers = []
32
+ end
33
+
34
+ # Run the main body of the application
35
+ #
36
+ # This method takes a block that actually runs under the application,
37
+ # performs all initialization steps, runs the block, performs finalization
38
+ # steps, and runs error handlers if an error was raised.
39
+ #
40
+ # @return [void]
41
+ def run!
42
+ @initializers.each { |action| action.call(self) }
43
+ @main.call(self)
44
+ @finalizers.each { |action| action.call(self) }
45
+ rescue => e
46
+ @error_handlers.each { |action| action.call(self) }
47
+ raise e
48
+ end
49
+
50
+ # Add a new action to run on app initialization
51
+ def on_initialize(&block)
52
+ @initializers << block
53
+ end
54
+
55
+ # Add a new action to run on app finalization
56
+ def on_finalize(&block)
57
+ @finalizers << block
58
+ end
59
+
60
+ # Add a new action to run on app error
61
+ def on_error(&block)
62
+ @error_handlers << block
63
+ end
64
+
65
+ def on_run(&block)
66
+ @main = block
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,7 @@
1
+ module Bolts
2
+ module Settings
3
+ require 'bolts/settings/registry'
4
+ require 'bolts/settings/container'
5
+ require 'bolts/settings/mixin'
6
+ end
7
+ end
@@ -0,0 +1,89 @@
1
+ require 'bolts/settings'
2
+
3
+ # Defines a collection for application settings
4
+ #
5
+ # This implements a hierarchical interface to application settings. Containers
6
+ # can define an optional parent container that will be used for default options
7
+ # if those options aren't set on the given container.
8
+ class Bolts::Settings::Container
9
+
10
+ # @!attribute [r] valid_keys
11
+ # @return [Set<Symbol>] All valid keys defined on the container or parent container.
12
+ attr_accessor :valid_keys
13
+
14
+ # @param parent [Bolts::Settings::Container] An optional parent container
15
+ def initialize(parent = nil)
16
+ @parent = parent
17
+
18
+ @valid_keys = Set.new
19
+ @settings = {}
20
+ end
21
+
22
+ # Look up a value in the container. The lookup checks the current container,
23
+ # and then falls back to the parent container if it's given.
24
+ #
25
+ # @param key [Symbol] The lookup key
26
+ #
27
+ # @return [Object, nil] The retrieved value if present.
28
+ #
29
+ # @raise [Bolts::Settings::Container::InvalidKey] If the looked up key isn't
30
+ # a valid key.
31
+ def [](key)
32
+ validate_key! key
33
+
34
+ if @settings[key]
35
+ @settings[key]
36
+ elsif @parent and (pkey = @parent[key])
37
+ @settings[key] = pkey
38
+ pkey
39
+ end
40
+ end
41
+
42
+ # Set a value on the container
43
+ #
44
+ # @param key [Symbol] The lookup key
45
+ # @param value [Object] The value to store in the container
46
+ #
47
+ # @raise [Bolts::Settings::Container::InvalidKey] If the looked up key isn't
48
+ # a valid key.
49
+ def []=(key, value)
50
+ validate_key! key
51
+
52
+ @settings[key] = value
53
+ end
54
+
55
+ # Define a valid container key
56
+ #
57
+ # @note This should only be used by {#Bolts::Settings::ClassSettings}
58
+ #
59
+ # @param key [Symbol]
60
+ # @return [void]
61
+ def add_valid_key(key)
62
+ @valid_keys.add(key)
63
+ end
64
+
65
+ # Determine if a key is a valid setting.
66
+ #
67
+ # @param key [Symbol]
68
+ #
69
+ # @return [true, false]
70
+ def valid_key?(key)
71
+ if @valid_keys.include?(key)
72
+ true
73
+ elsif @parent and @parent.valid_key?(key)
74
+ @valid_keys.add(key)
75
+ true
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def validate_key!(key)
82
+ unless valid_key?(key)
83
+ raise InvalidKey, "Key #{key} is not a valid key"
84
+ end
85
+ end
86
+
87
+ # @api private
88
+ class InvalidKey < StandardError; end
89
+ end
@@ -0,0 +1,21 @@
1
+ require 'bolts/settings'
2
+
3
+ class Bolts::Settings::Definition
4
+
5
+ attr_reader :name
6
+ attr_reader :desc
7
+ attr_reader :default
8
+
9
+ attr_writer :value
10
+
11
+ def initialize(name, desc, default)
12
+
13
+ @name = name
14
+ @desc = desc
15
+ @default = default
16
+ end
17
+
18
+ def value
19
+ (! @value.nil?) ? @value : @default
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ require 'bolts/settings'
2
+
3
+ module Bolts::Settings
4
+ class SettingsError < StandardError; end
5
+
6
+ class InvalidSetting < SettingsError; end
7
+ end
@@ -0,0 +1,58 @@
1
+ require 'bolts/settings'
2
+
3
+ module Bolts::Settings::Mixin
4
+
5
+ def self.included(klass)
6
+ klass.send(:include, InstanceMethods)
7
+ klass.send(:extend, ClassMethods)
8
+ end
9
+
10
+ module InstanceMethods
11
+
12
+ # @return [Bolts::Settings::Container] A settings container for the given instance.
13
+ def settings
14
+ @settings ||= Bolts::Settings::Container.new(self.class.settings)
15
+ end
16
+ end
17
+
18
+ module ClassMethods
19
+
20
+ # Bind to a setting registry so that global settings can change this class.
21
+ #
22
+ # @param registry [Bolts::Setting::Registry]
23
+ # @param name [String] The name of the setting to bind to
24
+ # @param block [Proc] An optional block to execute when the setting changes
25
+ def bind_setting(registry, name)
26
+ def_setting_attr(name)
27
+
28
+ registry.bind_watcher(self, name)
29
+ end
30
+
31
+ # Define a setting and optional default on the extending class.
32
+ #
33
+ # @param key [Symbol]
34
+ # @param default [Object]
35
+ #
36
+ # @return [void]
37
+ def def_setting_attr(key, default = nil)
38
+ defaults.add_valid_key(key)
39
+ defaults[key] = default if default
40
+ end
41
+
42
+ # A singleton settings container for storing immutable default configuration
43
+ # on the extending class.
44
+ #
45
+ # @return [Bolts::Settings::Container]
46
+ def defaults
47
+ @defaults ||= Bolts::Settings::Container.new
48
+ end
49
+
50
+ # A singleton settings container for storing manual setting configurations
51
+ # on the extending class.
52
+ #
53
+ # @return [Bolts::Settings::Container]
54
+ def settings
55
+ @settings ||= Bolts::Settings::Container.new(defaults)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,104 @@
1
+ require 'bolts/settings'
2
+ require 'bolts/settings/definition'
3
+
4
+ class Bolts::Settings::Registry
5
+
6
+ attr_reader :definitions
7
+
8
+ attr_reader :watchers
9
+
10
+ def initialize
11
+ @definitions = []
12
+
13
+ @watchers = Hash.new { |h, k| h[k] = [] }
14
+ end
15
+
16
+ # Create a new setting definition
17
+ #
18
+ # @example
19
+ # reg.define_setting(
20
+ # 'my-setting',
21
+ # :desc => 'setting description goes here',
22
+ # :default => 'default value',
23
+ # )
24
+ #
25
+ # @param name [String] The setting name
26
+ # @param opts [Hash]
27
+ #
28
+ # @param opts [String] :desc The setting description
29
+ # @param opts [Object] :default
30
+ def define_setting(name, options)
31
+ definition = Bolts::Settings::Definition.new(name, options[:desc], options[:default])
32
+ @definitions << definition
33
+ end
34
+
35
+ # Look up the definition with the given name.
36
+ #
37
+ # @param name [String]
38
+ # @return [Bolts::Setting::Definition, nil]
39
+ def definition(name)
40
+ @definitions.find { |definition| name == definition.name }
41
+ end
42
+
43
+ # Apply settings from a hash
44
+ #
45
+ # @param hash [Hash]
46
+ # @return [Hash] The hash with the processed values removed
47
+ def set_values(hash)
48
+ ret = hash.dup
49
+
50
+ hash.each_pair do |name, value|
51
+ if set_value(name, value)
52
+ ret.delete(name)
53
+ end
54
+ end
55
+
56
+ ret
57
+ end
58
+
59
+ # Set a definition value
60
+ #
61
+ # @param name [String]
62
+ # @param value [Object]
63
+ #
64
+ # @return [true, false] If the setting could be applied
65
+ def set_value(name, value)
66
+ if (setting_def = self.definition(name))
67
+ setting_def.value = value
68
+ true
69
+ else
70
+ false
71
+ end
72
+ end
73
+
74
+ alias :[]= :set_value
75
+
76
+ # Retrieve a definition value
77
+ #
78
+ # @param name [String]
79
+ #
80
+ # @return [Object]
81
+ def get_value(name)
82
+ if (setting_def = self.definition(name))
83
+ setting_def.value
84
+ end
85
+ end
86
+
87
+ alias :[] :get_value
88
+
89
+ # @param name [String] The setting name to bind to
90
+ # @param obj [#[]=] An object that can have properties set
91
+ def bind_watcher(name, settable)
92
+ @watchers[name] << settable
93
+ end
94
+
95
+ def notify_watchers
96
+ @watchers.each_pair do |name, list|
97
+ setting_def = self.definition(name)
98
+
99
+ list.each do |watcher|
100
+ watcher[name] = setting_def.value
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,3 @@
1
+ module Bolts
2
+ VERSION = '0.1.0dev1'
3
+ end
@@ -0,0 +1,3 @@
1
+ require 'bolts'
2
+
3
+ PROJECT_ROOT = File.expand_path('..', File.dirname(__FILE__))
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ require 'bolts/application'
4
+
5
+ describe Bolts::Application do
6
+ let(:sequence) { [] }
7
+ subject do
8
+ described_class.new('test').tap do |app|
9
+ app.on_run { sequence << :main }
10
+ end
11
+ end
12
+
13
+ describe "with an initializer" do
14
+ it "hands the application to the initializer" do
15
+ subject.on_initialize { |app| expect(app).to eq subject }
16
+ subject.run!
17
+ end
18
+
19
+ it "runs the initializer before the main action" do
20
+ subject.on_initialize { sequence << :initializer }
21
+ subject.run!
22
+
23
+ expect(sequence).to eq [:initializer, :main]
24
+ end
25
+ end
26
+
27
+ describe "with a finalizer" do
28
+ it "hands the application to the finalizer" do
29
+ subject.on_finalize { |app| expect(app).to eq subject }
30
+ subject.run!
31
+ end
32
+
33
+ it "runs the finalizer after the main action" do
34
+ subject.on_finalize { sequence << :finalizer }
35
+ subject.run!
36
+ expect(sequence).to eq [:main, :finalizer]
37
+ end
38
+ end
39
+
40
+ describe "with an error handler" do
41
+ it "hands the application to the error handler" do
42
+ subject.on_error { |app| expect(app).to eq subject }
43
+ subject.on_run { raise "kaboom!" }
44
+ expect { subject.run! }.to raise_error StandardError, /kaboom!/
45
+ end
46
+
47
+ it "runs the error handler if an exception was raised" do
48
+ subject.on_error { sequence << :error }
49
+ subject.on_run { sequence << :main; raise "kaboom!" }
50
+ expect { subject.run! }.to raise_error StandardError, /kaboom!/
51
+ expect(sequence).to eq [:main, :error]
52
+ end
53
+
54
+ it "doesn't run the error handler on normal application exit" do
55
+ subject.on_error { sequence << :error }
56
+ subject.run!
57
+ expect(sequence).to eq [:main]
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+
3
+ require 'bolts/settings'
4
+
5
+ describe Bolts::Settings::Container do
6
+
7
+ describe 'validating keys' do
8
+ it 'can add new valid keys' do
9
+ subject.add_valid_key(:v)
10
+ subject[:v]
11
+ end
12
+
13
+ it 'can check if a key is valid' do
14
+ subject.add_valid_key(:v)
15
+ expect(subject.valid_key?(:v)).to be_true
16
+ end
17
+
18
+ it 'can list all valid keys' do
19
+ subject.add_valid_key(:v)
20
+ subject.add_valid_key(:w)
21
+
22
+ expect(subject.valid_keys).to include :v
23
+ expect(subject.valid_keys).to include :w
24
+ end
25
+ end
26
+
27
+ describe 'specifying settings' do
28
+ it 'fails if a setting application uses an invalid key' do
29
+ expect { subject[:invalid] = 'fail' }.to raise_error Bolts::Settings::Container::InvalidKey
30
+ end
31
+
32
+ it 'can look up values that it sets' do
33
+ subject.add_valid_key :v
34
+ subject[:v] = 'set'
35
+ expect(subject[:v]).to eq 'set'
36
+ end
37
+ end
38
+
39
+ describe 'looking up settings' do
40
+ before do
41
+ subject.add_valid_key :v
42
+ end
43
+
44
+ it 'fails if a setting lookup uses an invalid key' do
45
+ expect { subject[:invalid] }.to raise_error Bolts::Settings::Container::InvalidKey
46
+ end
47
+
48
+ it 'returns nil if a key is valid but no setting is present' do
49
+ expect(subject[:v]).to be_nil
50
+ end
51
+
52
+ describe 'with a parent container' do
53
+ let(:parent) { described_class.new.tap { |p| p.add_valid_key :v } }
54
+ subject { described_class.new(parent) }
55
+
56
+ it 'uses its setting over a parent value' do
57
+ subject[:v] = 'child'
58
+ parent[:v] = 'parent'
59
+ expect(subject[:v]).to eq 'child'
60
+ end
61
+
62
+ it 'falls back to the parent value if it does not have a value' do
63
+ parent[:v] = 'parent'
64
+ expect(subject[:v]).to eq 'parent'
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+ require 'bolts/settings/definition'
3
+
4
+ describe Bolts::Settings::Definition do
5
+
6
+ subject do
7
+ described_class.new(
8
+ 'test',
9
+ 'description here',
10
+ 'default value'
11
+ )
12
+ end
13
+
14
+ it "has a name" do
15
+ expect(subject.name).to eq 'test'
16
+ end
17
+
18
+ it "has a description" do
19
+ expect(subject.desc).to eq 'description here'
20
+ end
21
+
22
+ it "has a default value" do
23
+ expect(subject.default).to eq 'default value'
24
+ end
25
+
26
+ it "prefers an explicit value over a default value" do
27
+ subject.value = 'explicit!'
28
+ expect(subject.value).to eq 'explicit!'
29
+ end
30
+
31
+ it "uses a default value if no explicit value is specified" do
32
+ subject.value = nil
33
+ expect(subject.value).to eq 'default value'
34
+ end
35
+ end
@@ -0,0 +1,95 @@
1
+ require 'spec_helper'
2
+ require 'bolts/settings/registry'
3
+
4
+ describe Bolts::Settings::Registry do
5
+
6
+ it "can define new settings" do
7
+ subject.define_setting('something', :desc => 'a setting definition!')
8
+ expect(subject.definitions).to have(1).items
9
+ expect(subject.definition('something')).to be_a_kind_of Bolts::Settings::Definition
10
+ end
11
+
12
+ describe "applying settings" do
13
+ before do
14
+ subject.define_setting('partycat', :desc => 'party cat loves to party')
15
+ subject.define_setting('partydog', :desc => 'party dog also loves to party')
16
+ end
17
+
18
+ it "applies settings from a hash" do
19
+ settings = {
20
+ 'partycat' => 'parties!',
21
+ 'partydog' => 'we love parties!',
22
+ }
23
+
24
+ subject.set_values(settings)
25
+
26
+ expect(subject.definition('partycat').value).to eq 'parties!'
27
+ expect(subject.definition('partydog').value).to eq 'we love parties!'
28
+ end
29
+
30
+ it "can directly retrieve a definition value" do
31
+ settings = {
32
+ 'partycat' => 'parties!',
33
+ 'partydog' => 'we love parties!',
34
+ }
35
+
36
+ subject.set_values(settings)
37
+
38
+ expect(subject.get_value('partycat')).to eq 'parties!'
39
+ expect(subject.get_value('partydog')).to eq 'we love parties!'
40
+ end
41
+
42
+ it "removes used settings from the returned hash" do
43
+ input = {
44
+ 'partycat' => 'parties!',
45
+ 'partydog' => 'we love parties!',
46
+ 'partysnake' => 'partysnake hates parties'
47
+ }
48
+
49
+ output = subject.set_values(input)
50
+
51
+ expect(output).to have(1).items
52
+ expect(output).to include('partysnake' => 'partysnake hates parties')
53
+ end
54
+
55
+ it "doesn't modify the hash in place" do
56
+ input = {
57
+ 'partycat' => 'parties!',
58
+ 'partydog' => 'we love parties!',
59
+ 'partysnake' => 'partysnake hates parties'
60
+ }
61
+
62
+ subject.set_values(input)
63
+
64
+ expect(input).to eq input
65
+ end
66
+
67
+ it "applies a single setting and value" do
68
+ subject.set_value('partycat', 'parties!')
69
+ expect(subject.definition('partycat').value).to eq 'parties!'
70
+ end
71
+ end
72
+
73
+ describe "binding values" do
74
+ let(:container) { double('settings container') }
75
+
76
+ before do
77
+ subject.define_setting('partycat', :desc => 'party cat loves to party')
78
+ subject.set_value('partycat', 'parties!')
79
+ end
80
+
81
+ it "can bind objects to settings" do
82
+ subject.bind_watcher('partycat', container)
83
+
84
+ expect(subject.watchers).to include('partycat' => [container])
85
+ end
86
+
87
+ it "can notify bound objects" do
88
+ subject.bind_watcher('partycat', container)
89
+
90
+ expect(container).to receive(:[]=).with('partycat', 'parties!')
91
+
92
+ subject.notify_watchers
93
+ end
94
+ end
95
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bolts
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0dev1
5
+ prerelease: 5
6
+ platform: ruby
7
+ authors:
8
+ - Adrien Thebo
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-12-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 2.14.0
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 2.14.0
30
+ description:
31
+ email: adrien@somethingsinistral.net
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - lib/bolts/settings.rb
37
+ - lib/bolts/settings/errors.rb
38
+ - lib/bolts/settings/registry.rb
39
+ - lib/bolts/settings/container.rb
40
+ - lib/bolts/settings/mixin.rb
41
+ - lib/bolts/settings/definition.rb
42
+ - lib/bolts/version.rb
43
+ - lib/bolts/application.rb
44
+ - lib/bolts.rb
45
+ - spec/spec_helper.rb
46
+ - spec/unit/settings/registry_spec.rb
47
+ - spec/unit/settings/container_spec.rb
48
+ - spec/unit/settings/definition_spec.rb
49
+ - spec/unit/application_spec.rb
50
+ homepage: http://github.com/adrienthebo/bolts
51
+ licenses:
52
+ - Apache 2.0
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>'
67
+ - !ruby/object:Gem::Version
68
+ version: 1.3.1
69
+ requirements: []
70
+ rubyforge_project:
71
+ rubygems_version: 1.8.23
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: Bolts is a foundation for writing applications
75
+ test_files:
76
+ - spec/unit/settings/registry_spec.rb
77
+ - spec/unit/settings/container_spec.rb
78
+ - spec/unit/settings/definition_spec.rb
79
+ - spec/unit/application_spec.rb
80
+ has_rdoc: