emory 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,75 @@
1
+ require 'listen'
2
+ require 'pathname'
3
+ require 'emory/configuration_file'
4
+ require 'emory/dsl/dsl'
5
+
6
+ module Emory
7
+
8
+ class Runner
9
+
10
+ LOGGER = Logging.logger[self]
11
+
12
+ class << self
13
+ def start
14
+ begin
15
+ LOGGER.debug('Looking for the configuration file')
16
+ emory_config_file = ConfigurationFile.locate
17
+
18
+ LOGGER.debug('Reading configuration file contents')
19
+ emory_config_contents = File.read(emory_config_file)
20
+
21
+ LOGGER.debug("Evaluating configuration file contents:\n====\n#{emory_config_contents}===")
22
+ config = DSL::Dsl.instance_eval_emoryfile(emory_config_contents, emory_config_file)
23
+
24
+ LOGGER.debug('Configuring listeners')
25
+ configure_listeners(config, File.dirname(emory_config_file))
26
+
27
+ Thread.current.join
28
+ rescue Interrupt
29
+ LOGGER.info('Shutting down Emory')
30
+ rescue EmoryConfigurationFileNotFoundException => fileNotFoundException
31
+ LOGGER.error("#{fileNotFoundException.message}. Please refer to http://tacitknowledge.com/emory for information on how to use Emory.")
32
+ rescue Exception => e
33
+ LOGGER.error(e)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def configure_listeners(config, config_location)
40
+ LOGGER.debug("Config\'s directory is: #{config_location}")
41
+ config.teleports.each do |teleport|
42
+ watched_path = normalize_watched_path(config_location, teleport.watched_path)
43
+ LOGGER.info("Watching directory: #{watched_path}")
44
+
45
+ listener = Listen.to(watched_path)
46
+ listener.ignore(teleport.ignore) unless teleport.ignore.nil?
47
+ listener.filter(teleport.filter) unless teleport.filter.nil?
48
+ listener.change(&get_handler_callback(teleport.handler))
49
+ listener.start(false)
50
+ end
51
+ end
52
+
53
+ def get_handler_callback(handler)
54
+ Proc.new do |modified, added, removed|
55
+ trigger_handler_method(modified, handler, :modified)
56
+ trigger_handler_method(added, handler, :added)
57
+ trigger_handler_method(removed, handler, :removed)
58
+ end
59
+ end
60
+
61
+ def trigger_handler_method(paths, handler, operation)
62
+ unless paths.empty?
63
+ paths.each { |path| handler.send(operation, path) } if handler.respond_to?(operation)
64
+ end
65
+ end
66
+
67
+ def normalize_watched_path(config_location, watched_path)
68
+ return watched_path if Pathname.new(watched_path).absolute?
69
+ File.expand_path(watched_path, config_location)
70
+ end
71
+
72
+ end
73
+ end
74
+
75
+ end
@@ -0,0 +1,7 @@
1
+ module Emory
2
+
3
+ class TeleportConfig
4
+ attr_accessor :watched_path, :handler, :ignore, :filter
5
+ end
6
+
7
+ end
@@ -0,0 +1,3 @@
1
+ module Emory
2
+ VERSION = "0.1.0"
3
+ end
data/lib/emory.rb ADDED
@@ -0,0 +1 @@
1
+ require "emory/version"
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+ require 'emory/configuration_file'
3
+
4
+ module Emory
5
+
6
+ describe ConfigurationFile do
7
+
8
+ context "class object" do
9
+
10
+ it "finds config file on its path" do
11
+ Dir.should_receive(:pwd).and_return('/qwe/asd/zxc/')
12
+ File.should_receive(:exists?).with('/qwe/asd/zxc/.emory').and_return(false)
13
+ File.should_receive(:exists?).with('/qwe/asd/.emory').and_return(false)
14
+ File.should_receive(:exists?).with('/qwe/.emory').and_return(true)
15
+
16
+ ConfigurationFile.locate.should == '/qwe/.emory'
17
+ end
18
+
19
+ it "finds config file in current directory" do
20
+ Dir.should_receive(:pwd).and_return('/qwe/asd/zxc/')
21
+ File.should_receive(:exists?).with('/qwe/asd/zxc/.emory').and_return(true)
22
+
23
+ ConfigurationFile.locate.should == '/qwe/asd/zxc/.emory'
24
+ end
25
+
26
+ it "raises exception if config file was not found on its path up to root" do
27
+ Dir.should_receive(:pwd).and_return('/qwe/asd/zxc/')
28
+ File.should_receive(:exists?).with('/qwe/asd/zxc/.emory').and_return(false)
29
+ File.should_receive(:exists?).with('/qwe/asd/.emory').and_return(false)
30
+ File.should_receive(:exists?).with('/qwe/.emory').and_return(false)
31
+ File.should_receive(:exists?).with('/.emory').and_return(false)
32
+
33
+ proc {
34
+ ConfigurationFile.locate
35
+ }.should raise_error(EmoryConfigurationFileNotFoundException,
36
+ /Configuration file \(\.emory\) was not found/)
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+
43
+ end
@@ -0,0 +1,91 @@
1
+ require 'spec_helper'
2
+ require 'emory/dsl/dsl'
3
+ require 'emory/dsl/handler_builder'
4
+ require 'emory/dsl/teleport_config_builder'
5
+ require 'emory/handlers/abstract_handler'
6
+
7
+ module Emory
8
+ module DSL
9
+
10
+ describe Dsl do
11
+ let(:dsl) { Dsl.new }
12
+
13
+ context "class object" do
14
+ it "parses the supplied teleport config and configures correct object types in frozen collections" do
15
+ contents = <<-EOM
16
+ require 'emory/handlers/abstract_handler'
17
+
18
+ handler do
19
+ name :something
20
+ implementation Emory::Handlers::AbstractHandler
21
+ events :all
22
+ end
23
+
24
+ teleport do
25
+ path '/path/to/dir'
26
+ handler :something
27
+ end
28
+ EOM
29
+
30
+ config = Dsl.instance_eval_emoryfile(contents, '/path/to/file')
31
+ config.should have(1).handlers
32
+ config.handlers.should be_frozen
33
+ config.handlers[:something].class.should == Emory::Handlers::AbstractHandler
34
+ config.should have(1).teleports
35
+ config.teleports.should be_frozen
36
+ config.teleports[0].class.should == Emory::TeleportConfig
37
+ end
38
+
39
+ it "detects and reports incorrect usage of DSL" do
40
+ contents = <<-EOM
41
+ blah_blah_blah
42
+ EOM
43
+
44
+ proc {
45
+ Dsl.instance_eval_emoryfile(contents, '/path/to/file')
46
+ }.should raise_error(EmoryMisconfigurationException,
47
+ /Incorrect contents of .emory file/)
48
+ end
49
+ end
50
+
51
+ context "'handler' method" do
52
+ it "does not allow to add multiple handlers with the same name" do
53
+ dsl.handlers[:something] = Object.new
54
+ builder = double('handler_builder')
55
+ handler = double('handler')
56
+ HandlerBuilder.should_receive(:new).and_return(builder)
57
+ builder.should_receive(:build).and_return(handler)
58
+ handler.should_receive(:name).twice.and_return(:something)
59
+
60
+ proc {
61
+ dsl.handler {}
62
+ }.should raise_error(DuplicateHandlerNameException)
63
+ end
64
+
65
+ it "adds a handler to its list" do
66
+ builder = double('handler_builder')
67
+ handler = double('handler')
68
+ HandlerBuilder.should_receive(:new).and_return(builder)
69
+ builder.should_receive(:build).and_return(handler)
70
+ handler.should_receive(:name).exactly(3).times.and_return(:something)
71
+
72
+ dsl.handler {}
73
+ dsl.should have(1).handlers
74
+ end
75
+ end
76
+
77
+ context "'teleport' method" do
78
+ it "adds a teleport to its list" do
79
+ builder = double('teleport_builder')
80
+ teleport = double('teleport')
81
+ TeleportConfigBuilder.should_receive(:new).and_return(builder)
82
+ builder.should_receive(:build).and_return(teleport)
83
+
84
+ dsl.teleport {}
85
+ dsl.should have(1).teleports
86
+ end
87
+ end
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,163 @@
1
+ require 'spec_helper'
2
+ require 'emory/dsl/handler_builder'
3
+ require 'emory/handlers/stdout_handler'
4
+
5
+ module Emory
6
+ module DSL
7
+
8
+ describe HandlerBuilder do
9
+
10
+ it "mandates a block needs to be supplied to builder constructor" do
11
+ proc {
12
+ HandlerBuilder.new
13
+ }.should raise_error(HandlerConfigurationBlockMustBeSuppliedException)
14
+ end
15
+
16
+ it "mandates action of type ':all' to be exclusive of other types" do
17
+ p = proc {
18
+ name :test_handler
19
+ implementation ::Emory::Handlers::StdoutHandler
20
+ events :added, :all
21
+ }
22
+
23
+ builder = HandlerBuilder.new(&p)
24
+ proc {
25
+ builder.build
26
+ }.should raise_error(HandlerActionAllMustBeSingletonException)
27
+ end
28
+
29
+ it "mandates at least one action type to be supplied to 'events'" do
30
+ p = proc {
31
+ name :test_handler
32
+ implementation ::Emory::Handlers::StdoutHandler
33
+ events
34
+ }
35
+
36
+ builder = HandlerBuilder.new(&p)
37
+ proc {
38
+ builder.build
39
+ }.should raise_error(HandlerActionMustBeSuppliedException)
40
+ end
41
+
42
+ it "mandates 'events' configuration to be supplied to proc" do
43
+ p = proc {
44
+ name :test_handler
45
+ implementation ::Emory::Handlers::StdoutHandler
46
+ }
47
+
48
+ builder = HandlerBuilder.new(&p)
49
+ proc {
50
+ builder.build
51
+ }.should raise_error(HandlerActionMustBeSuppliedException)
52
+ end
53
+
54
+ it "mandates correct handler action types are required to be supplied" do
55
+ p = proc {
56
+ name :test_handler
57
+ implementation ::Emory::Handlers::StdoutHandler
58
+ events :foo
59
+ }
60
+
61
+ builder = HandlerBuilder.new(&p)
62
+ proc {
63
+ builder.build
64
+ }.should raise_error(HandlerActionUnsupportedException)
65
+ end
66
+
67
+ it "mandates 'name' configuration to be supplied to proc" do
68
+ p = proc {
69
+ implementation ::Emory::Handlers::StdoutHandler
70
+ }
71
+
72
+ builder = HandlerBuilder.new(&p)
73
+ proc {
74
+ builder.build
75
+ }.should raise_error(HandlerNameMustBeSuppliedException)
76
+ end
77
+
78
+ it "mandates 'implementation' configuration to be supplied to proc" do
79
+ p = proc {
80
+ name :test_handler
81
+ }
82
+
83
+ builder = HandlerBuilder.new(&p)
84
+ proc {
85
+ builder.build
86
+ }.should raise_error(HandlerImplementationMustBeSuppliedException)
87
+ end
88
+
89
+ it "passes the supplied name and options on to the handler's constructor" do
90
+ p = proc {
91
+ name :test_handler
92
+ implementation ::Emory::Handlers::StdoutHandler
93
+ events :all
94
+ options host: 'localhost', port: '8080'
95
+ }
96
+
97
+ Emory::Handlers::StdoutHandler.should_receive(:new).with(:test_handler, {host: 'localhost', port: '8080'})
98
+
99
+ builder = HandlerBuilder.new(&p)
100
+ builder.build
101
+ end
102
+
103
+ it "passes the supplied name and empty hash on to the handler's constructor if options configuration is not supplied" do
104
+ p = proc {
105
+ name :test_handler
106
+ implementation ::Emory::Handlers::StdoutHandler
107
+ events :all
108
+ }
109
+
110
+ Emory::Handlers::StdoutHandler.should_receive(:new).with(:test_handler, {})
111
+
112
+ builder = HandlerBuilder.new(&p)
113
+ builder.build
114
+ end
115
+
116
+ it "with action of type ':all' does not undefine any handler's methods" do
117
+ p = proc {
118
+ name :test_handler
119
+ implementation ::Emory::Handlers::StdoutHandler
120
+ events :all
121
+ }
122
+
123
+ builder = HandlerBuilder.new(&p)
124
+ handler = builder.build
125
+
126
+ handler.respond_to?(:added).should == true
127
+ handler.respond_to?(:modified).should == true
128
+ handler.respond_to?(:removed).should == true
129
+ end
130
+
131
+ it "with all action types does not undefine any handler's methods" do
132
+ p = proc {
133
+ name :test_handler
134
+ implementation ::Emory::Handlers::StdoutHandler
135
+ events :added, :modified, :removed
136
+ }
137
+
138
+ builder = HandlerBuilder.new(&p)
139
+ handler = builder.build
140
+
141
+ handler.respond_to?(:added).should == true
142
+ handler.respond_to?(:modified).should == true
143
+ handler.respond_to?(:removed).should == true
144
+ end
145
+
146
+ it "undefines handler's methods which are not supplied" do
147
+ p = proc {
148
+ name :test_handler
149
+ implementation ::Emory::Handlers::StdoutHandler
150
+ events :added
151
+ }
152
+
153
+ builder = HandlerBuilder.new(&p)
154
+ handler = builder.build
155
+
156
+ handler.respond_to?(:added).should == true
157
+ handler.respond_to?(:modified).should == false
158
+ handler.respond_to?(:removed).should == false
159
+ end
160
+
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,116 @@
1
+ require 'spec_helper'
2
+ require 'emory/dsl/teleport_config_builder'
3
+
4
+ module Emory
5
+ module DSL
6
+
7
+ describe TeleportConfigBuilder do
8
+
9
+ it "mandates handlers and a block need to be supplied to builder constructor" do
10
+ proc {
11
+ TeleportConfigBuilder.new({})
12
+ }.should raise_error(TeleportConfigurationBlockMustBeSuppliedException)
13
+ end
14
+
15
+ it "mandates a handler reference to be supplied" do
16
+ p = proc {}
17
+
18
+ handlers = {test_handler: Object.new}
19
+ builder = TeleportConfigBuilder.new(handlers, &p)
20
+ proc {
21
+ builder.build
22
+ }.should raise_error(HandlerReferenceMustBeSuppliedException)
23
+ end
24
+
25
+ it "mandates an existing handler reference to be supplied" do
26
+ p = proc {
27
+ handler :does_not_exist
28
+ }
29
+
30
+ handlers = {test_handler: Object.new}
31
+ builder = TeleportConfigBuilder.new(handlers, &p)
32
+ proc {
33
+ builder.build
34
+ }.should raise_error(UndefinedTeleportHandlerException)
35
+ end
36
+
37
+ it "mandates a watched path to be supplied" do
38
+ p = proc {
39
+ handler :test_handler
40
+ }
41
+
42
+ handlers = {test_handler: Object.new}
43
+ builder = TeleportConfigBuilder.new(handlers, &p)
44
+ proc {
45
+ builder.build
46
+ }.should raise_error(WatchedPathMustBeSuppliedException)
47
+ end
48
+
49
+ it "verifies mandatory configuration settings" do
50
+ p = proc {
51
+ handler :test_handler
52
+ path '/path/to/file'
53
+ }
54
+
55
+ handler = Object.new
56
+ handlers = {test_handler: handler}
57
+ builder = TeleportConfigBuilder.new(handlers, &p)
58
+
59
+ teleport = builder.build
60
+ teleport.watched_path.should == '/path/to/file'
61
+ teleport.handler.should == handler
62
+ end
63
+
64
+ it "verifies complete configuration settings" do
65
+ p = proc {
66
+ handler :test_handler
67
+ path '/path/to/file'
68
+ ignore %r{ignored/}
69
+ filter /\.txt$/
70
+ }
71
+
72
+ handler = Object.new
73
+ handlers = {test_handler: handler}
74
+ builder = TeleportConfigBuilder.new(handlers, &p)
75
+
76
+ teleport = builder.build
77
+ teleport.watched_path.should == '/path/to/file'
78
+ teleport.handler.should == handler
79
+ teleport.ignore.should == %r{ignored/}
80
+ teleport.filter.should == /\.txt$/
81
+ end
82
+
83
+ it "verifies optional 'ignore' configuration setting" do
84
+ p = proc {
85
+ handler :test_handler
86
+ path '/path/to/file'
87
+ ignore %r{ignored/}
88
+ }
89
+
90
+ handler = Object.new
91
+ handlers = {test_handler: handler}
92
+ builder = TeleportConfigBuilder.new(handlers, &p)
93
+
94
+ teleport = builder.build
95
+ teleport.ignore.should == %r{ignored/}
96
+ end
97
+
98
+ it "verifies optional 'filter' configuration setting" do
99
+ p = proc {
100
+ handler :test_handler
101
+ path '/path/to/file'
102
+ filter /\.txt$/
103
+ }
104
+
105
+ handler = Object.new
106
+ handlers = {test_handler: handler}
107
+ builder = TeleportConfigBuilder.new(handlers, &p)
108
+
109
+ teleport = builder.build
110
+ teleport.filter.should == /\.txt$/
111
+ end
112
+
113
+ end
114
+
115
+ end
116
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+ require 'emory/handlers/abstract_handler'
3
+
4
+ module Emory
5
+ module Handlers
6
+
7
+ describe AbstractHandler do
8
+ context "is abstract so its method" do
9
+ let(:handler) { AbstractHandler.new(:test_handler) }
10
+
11
+ it "'added' raises NotImplementedError" do
12
+ proc {
13
+ handler.added
14
+ }.should raise_error(NotImplementedError,
15
+ /This method is not implemented: Emory::Handlers::AbstractHandler#added/)
16
+ end
17
+
18
+ it "'modified' raises NotImplementedError" do
19
+ proc {
20
+ handler.modified
21
+ }.should raise_error(NotImplementedError,
22
+ /This method is not implemented: Emory::Handlers::AbstractHandler#modified/)
23
+ end
24
+
25
+ it "'removed' raises NotImplementedError" do
26
+ proc {
27
+ handler.removed
28
+ }.should raise_error(NotImplementedError,
29
+ /This method is not implemented: Emory::Handlers::AbstractHandler#removed/)
30
+ end
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+ require 'emory/handlers/stdout_handler'
3
+
4
+ module Emory
5
+ module Handlers
6
+
7
+ describe StdoutHandler do
8
+ context "works with standard output" do
9
+ let(:handler) { StdoutHandler.new(:test_handler) }
10
+
11
+ it "'added' just calls 'puts' with right params" do
12
+ handler.should_receive(:puts).with(":test_handler ~> The file '/path/to/file' was 'added'")
13
+ handler.added '/path/to/file'
14
+ end
15
+
16
+ it "'modified' just calls 'puts' with right params" do
17
+ handler.should_receive(:puts).with(":test_handler ~> The file '/path/to/file' was 'modified'")
18
+ handler.modified '/path/to/file'
19
+ end
20
+
21
+ it "'removed' just calls 'puts' with right params" do
22
+ handler.should_receive(:puts).with(":test_handler ~> The file '/path/to/file' was 'removed'")
23
+ handler.removed '/path/to/file'
24
+ end
25
+ end
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+ require 'listen'
3
+ require 'pathname'
4
+ require 'emory/runner'
5
+ require 'emory/dsl/dsl'
6
+ require 'emory/teleport_config'
7
+ require 'emory/handlers/stdout_handler'
8
+
9
+ module Emory
10
+
11
+ describe Runner do
12
+ context "class object" do
13
+ it "starts the config file location process, creates necessary listener with absolute path and joins current thread" do
14
+ dsl = DSL::Dsl.new
15
+ teleport = TeleportConfig.new
16
+ teleport.watched_path = 'watched_path'
17
+ teleport.handler = Handlers::StdoutHandler.new(:handler_name)
18
+ teleport.ignore = 'ignore_path'
19
+ teleport.filter = 'filter_expression'
20
+ dsl.teleports << teleport
21
+
22
+ path_to_config_file = '/path/to/config/file'
23
+ ConfigurationFile.stub(:locate => path_to_config_file)
24
+ File.stub(:read).with(path_to_config_file).and_return('contents')
25
+ DSL::Dsl.stub(:instance_eval_emoryfile).with('contents', path_to_config_file).and_return(dsl)
26
+
27
+ Pathname.any_instance.stub(:absolute?).and_return(true)
28
+
29
+ mock_listener = double("listener")
30
+ Listen.stub(:to).with('watched_path').and_return(mock_listener)
31
+ mock_listener.should_receive(:ignore).with(teleport.ignore)
32
+ mock_listener.should_receive(:filter).with(teleport.filter)
33
+ mock_listener.should_receive(:change)
34
+ mock_listener.should_receive(:start).with(false)
35
+
36
+ mock_thread = double("thread")
37
+ Thread.stub(:current => mock_thread)
38
+ mock_thread.should_receive(:join)
39
+
40
+ Runner.start
41
+ end
42
+
43
+ it "starts the config file location process, creates necessary listener with relative path and joins current thread" do
44
+ dsl = DSL::Dsl.new
45
+ teleport = TeleportConfig.new
46
+ teleport.watched_path = 'watched_path'
47
+ teleport.handler = Handlers::StdoutHandler.new(:handler_name)
48
+ teleport.ignore = 'ignore_path'
49
+ teleport.filter = 'filter_expression'
50
+ dsl.teleports << teleport
51
+
52
+ path_to_config_file = '/path/to/config/file'
53
+ ConfigurationFile.stub(:locate => path_to_config_file)
54
+ File.stub(:read).with(path_to_config_file).and_return('contents')
55
+ DSL::Dsl.stub(:instance_eval_emoryfile).with('contents', path_to_config_file).and_return(dsl)
56
+
57
+ Pathname.any_instance.stub(:absolute?).and_return(false)
58
+ File.should_receive(:expand_path).with('watched_path', '/path/to/config').and_return('/path/to/config/watched_path')
59
+
60
+ mock_listener = double("listener")
61
+ Listen.stub(:to).with('/path/to/config/watched_path').and_return(mock_listener)
62
+ mock_listener.should_receive(:ignore).with(teleport.ignore)
63
+ mock_listener.should_receive(:filter).with(teleport.filter)
64
+ mock_listener.should_receive(:change)
65
+ mock_listener.should_receive(:start).with(false)
66
+
67
+ mock_thread = double("thread")
68
+ Thread.stub(:current => mock_thread)
69
+ mock_thread.should_receive(:join)
70
+
71
+ Runner.start
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,10 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
4
+ require 'rspec'
5
+ require 'logging'
6
+
7
+ RSpec.configure do |config|
8
+ config.color_enabled = true
9
+ config.formatter = 'documentation'
10
+ end
@@ -0,0 +1,10 @@
1
+ require 'test/unit'
2
+ require 'emory/configuration_file'
3
+
4
+ class TestConfigurationFile < Test::Unit::TestCase
5
+
6
+ def test_read_file_not_found
7
+ #TODO: test come here
8
+ end
9
+
10
+ end