bolts 0.1.0dev1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: