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.
data/.gitignore ADDED
@@ -0,0 +1,26 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .idea/
19
+ .project
20
+ emory.log
21
+
22
+ ## MAC OS
23
+ .DS_Store
24
+ .Trashes
25
+ .com.apple.timemachine.supported
26
+ .fseventsd
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in emory.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Tacit Knowledge
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,193 @@
1
+ Emory
2
+ =====
3
+
4
+ The Emory gem listens to file modifications and runs an action against it (for example, upload to a remote location).
5
+
6
+ Contents
7
+ --------
8
+
9
+ * [Installation](#installation)
10
+ * [Usage](#usage)
11
+ * [Emory configuration DSL](#emory-dsl)
12
+ * [handler](#emory-dsl-handler)
13
+ * [teleport](#emory-dsl-teleport)
14
+ * [Contributing](#contributing)
15
+ * [Authors](#authors)
16
+
17
+ <a name="installation" />
18
+ Installation
19
+ ------------
20
+
21
+ Add this line to your application's `Gemfile`:
22
+
23
+ ```ruby
24
+ gem 'emory'
25
+ ```
26
+
27
+ And then execute:
28
+
29
+ ```bash
30
+ $ bundle
31
+ ```
32
+
33
+ Or install it yourself as:
34
+
35
+ ```bash
36
+ $ gem install emory
37
+ ```
38
+
39
+ <a name="usage" />
40
+ Usage
41
+ -----
42
+
43
+ Emory is run from the command line. Please open your terminal and go to your project's work directory.
44
+
45
+ There you would need to create Emory config file `.emory` which in fact is nothing more than regular
46
+ Ruby code wrapped in gem's configuration DSL.
47
+
48
+ Example configuration:
49
+
50
+ ```ruby
51
+ require 'emory/handlers/stdout_handler'
52
+
53
+ handler do
54
+ name :listener
55
+ implementation Emory::Handlers::StdoutHandler
56
+ events :all
57
+ end
58
+
59
+ teleport do
60
+ path '~/_emory-test/'
61
+ handler :listener
62
+ ignore %r{ignored/}
63
+ filter /\.txt$/
64
+ end
65
+ ```
66
+
67
+ The gem supplies an executable file which scans for the configuration file in current directory
68
+ (and up the path if not found), configures itself and launches the process. If you need to terminate
69
+ execution then proceed like your operating system allows you to (Ctrl-C, for example).
70
+
71
+ ```bash
72
+ $ emory
73
+ [2012-05-31 12:08:23] INFO Emory::ConfigurationFile: Found config file: /Users/xxx/.emory
74
+ [2012-05-31 12:08:23] INFO Emory::Runner: Watching directory: /Users/xxx/_emory-test
75
+ ```
76
+
77
+ Emory outputs some information into the console where it was launched from. However if you would
78
+ like to consult more detailed information on what it's doing then feel free to supply `-d` or
79
+ `--debug` when launching it like in this example:
80
+
81
+ ```bash
82
+ $ emory --debug
83
+ [2012-05-31 12:08:29] DEBUG Emory::Runner: Looking for the configuration file
84
+ [2012-05-31 12:08:29] DEBUG Emory::ConfigurationFile: Examining directory: /Users/xxx/Projects
85
+ [2012-05-31 12:08:29] DEBUG Emory::ConfigurationFile: Examining directory: /Users/xxx
86
+ [2012-05-31 12:08:29] INFO Emory::ConfigurationFile: Found config file: /Users/xxx/.emory
87
+ [2012-05-31 12:08:29] DEBUG Emory::Runner: Reading configuration file contents
88
+ [2012-05-31 12:08:29] DEBUG Emory::Runner: Evaluating configuration file contents:
89
+ <<< the rest of the output is ommitted >>>
90
+ ```
91
+
92
+ <a name="emory-dsl" />
93
+ Emory configuration DSL
94
+ -----------------------
95
+
96
+ The Emory configuration DSL is evaluated as plain Ruby, so you can use normal Ruby code in your
97
+ `.emory` file. Emory itself provides the following DSL methods that can be used for configuration:
98
+
99
+ <a name="emory-dsl-handler" />
100
+ ### handler
101
+
102
+ A handler in Emory is an entity which knows how to react on file system modification events.
103
+ By default only two are provided:
104
+
105
+ - `Emory::Handlers::AbstractHandler` - defines common interface for other handlers to implement
106
+ - `Emory::Handlers::StdoutHandler` - spits out some information on what/how changed to the standard output
107
+
108
+ A handler can be configured with 3 mandatory and 1 optional parameter:
109
+
110
+ - mandatory
111
+ - name - defines a name for the handler so that it could be used in other parts of the configuration
112
+ - implementation - name of the specific class that conforms to `Emory::Handlers::AbstractHandler`'s interface
113
+ - events - a comma separated list of events the handler should react to (**:added**,
114
+ **:modified**, **:removed**) while ignoring the ones not mentioned. There's also an **:all**
115
+ shortcut to indicate all events without explicitly writing them.
116
+ - optional
117
+ - options - a hash of optional data that will be passed on during handler's construction. Please
118
+ note that the handler's class needs to know how to treat these otherwise it's a no-op. For
119
+ example, `Emory::Handlers::StdoutHandler` does not know how to deal with the options so it would
120
+ just ignore them.
121
+
122
+ Some examples of defining handlers
123
+
124
+ ```ruby
125
+ require 'emory/handlers/stdout_handler'
126
+ require 'some_company/integration/system_x_handler'
127
+
128
+ handler do
129
+ name :stdout_handler
130
+ implementation Emory::Handlers::StdoutHandler
131
+ events :added, :removed
132
+ end
133
+
134
+ handler do
135
+ name :integration_handler
136
+ implementation SomeCompany::Integration::SystemXHandler
137
+ events :modified
138
+ options host: 'host1.othercompany.com', port: 12345, username: 'bozo', password: 'p@ssw0rd'
139
+ # or the arrows syntax in Ruby 1.8
140
+ # options :host => 'host1.othercompany.com', :port => 12345, :username => 'bozo', :password => 'p@ssw0rd'
141
+ end
142
+ ```
143
+
144
+ <a name="emory-dsl-teleport" />
145
+ ### teleport
146
+
147
+ A teleport in Emory is an entity that knows that it needs to monitor some path (including sub-directories)
148
+ and notify the linked handler if something interesting happens. A teleport can be configured with
149
+ 2 mandatory and 2 optional parameters:
150
+
151
+ - mandatory
152
+ - path - the path to monitor (including its sub-directories). Can be either absolute or relative
153
+ to the location of the configuration file (`.emory`).
154
+ - handler - the handler to invoke/notify when the filesystem events occur in the path supplied above
155
+ - optional
156
+ - ignore - the regex patterns that need to be ignored by the teleport
157
+ - filter - the regex patterns that filter out unwanted monitoring events
158
+
159
+ An example teleport definition:
160
+
161
+ ```ruby
162
+ # Will monitor file system events starting under <config_location>/ftp/incoming/x/ directory,
163
+ # but will ignore paths containing reconciled/ and pending/ directories, and additionaly will
164
+ # apply only to .txt files. Once events are identified will notify the handler named :integration_handler.
165
+ teleport do
166
+ path 'ftp/incoming/x'
167
+ handler :integration_handler
168
+ ignore %r{reconciled/ pending/}
169
+ filter /\.txt$/
170
+ end
171
+ ```
172
+
173
+ <a name="contributing" />
174
+ Contributing
175
+ ------------
176
+
177
+ 1. Fork it
178
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
179
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
180
+ 4. Push to the branch (`git push origin my-new-feature`)
181
+ 5. Create new Pull Request
182
+
183
+ <a name="authors" />
184
+ Authors
185
+ -------
186
+
187
+ * [Vladislav Gangan](https://github.com/vgangan)
188
+
189
+ Contributors
190
+ ------------
191
+
192
+ * [Scott Askew](https://github.com/scottfromsf)
193
+ * [Ion Lenta](https://github.com/noi)
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ t.test_files = FileList['test/**/test*.rb']
7
+ end
8
+
9
+ require 'rspec/core/rake_task'
10
+ RSpec::Core::RakeTask.new('spec')
11
+
12
+ require 'bundler'
13
+ Bundler::GemHelper.install_tasks
14
+
15
+ desc "Run rspec tests"
16
+ task :default => :spec
data/bin/emory ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'logging'
5
+ require 'emory/runner'
6
+ require 'emory/version'
7
+
8
+ options = {}
9
+ OptionParser.new do |opts|
10
+ options[:log_level] = :info
11
+ opts.on('-d', '--debug', "Display debug output") do
12
+ options[:log_level] = :debug
13
+ end
14
+ opts.on('-v', '--version', 'Display version information') do
15
+ puts "Emory version #{Emory::VERSION}"
16
+ exit
17
+ end
18
+ opts.on_tail("-h", "--help", "Show this message") do
19
+ puts opts
20
+ exit
21
+ end
22
+ end.parse!
23
+
24
+ logging_lines_colors = {debug: :green, info: :light_green, warn: :yellow, error: :red, fatal: [:white, :on_red]}
25
+ Logging.color_scheme('bright', lines: logging_lines_colors)
26
+
27
+ logging_layouts_pattern = Logging.layouts.pattern(color_scheme: 'bright', pattern: '[%d] %-5l %c: %m\n')
28
+ Logging.appenders.stdout('stdout', layout: logging_layouts_pattern, level: options[:log_level])
29
+
30
+ Logging.logger.root.add_appenders('stdout')
31
+
32
+ Emory::Runner.start
data/emory.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'emory/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'emory'
7
+ s.version = Emory::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ['Scott Askew', 'Vladislav Gangan', 'Ion Lenta']
10
+ s.email = ['scott@tacitknowledge.com', 'vgangan@tacitknowledge.com', 'ilenta@tacitknowledge.com']
11
+ s.homepage = 'https://github.com/tacitknowledge/emory'
12
+ s.summary = 'Invokes a configured action when something interesting happens to a monitored file'
13
+ s.description = 'The Emory gem listens to file modifications and runs an action against it (for example, upload to a remote location)'
14
+
15
+ s.files = `git ls-files`.split($\)
16
+ s.executables = %w(emory)
17
+ s.test_files = Dir.glob("{spec,test}/**/*.rb")
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_development_dependency 'rspec', '~> 2.10'
21
+ s.add_development_dependency 'simplecov', '~> 0.6.2'
22
+ s.add_development_dependency 'rake'
23
+
24
+ s.add_runtime_dependency 'logging'
25
+ s.add_runtime_dependency 'listen'
26
+ end
@@ -0,0 +1,15 @@
1
+ require 'emory/handlers/stdout_handler'
2
+
3
+ # Define a handler emits any file add/modify/delete in ./src to STDOUT.
4
+ handler do
5
+ name :stdout
6
+ implementation Emory::Handlers::StdoutHandler
7
+ events :all
8
+ end
9
+
10
+ # Map ./src to the handler defined above. Note that the 'path' is relative
11
+ # to the location .emory (i.e. this file)
12
+ teleport do
13
+ path 'src'
14
+ handler :stdout
15
+ end
@@ -0,0 +1 @@
1
+ src/*
File without changes
@@ -0,0 +1,29 @@
1
+ require 'pathname'
2
+
3
+ module Emory
4
+
5
+ class EmoryConfigurationFileNotFoundException < Exception; end
6
+
7
+ class ConfigurationFile
8
+
9
+ LOGGER = Logging.logger[self]
10
+ CONFIG_FILE_NAME = ".emory"
11
+
12
+ class << self
13
+
14
+ def locate
15
+ Pathname.new(Dir.pwd).ascend do |dir|
16
+ LOGGER.debug "Examining directory: #{dir}"
17
+ config_file = File.join(dir, CONFIG_FILE_NAME)
18
+ next unless File.exists?(config_file)
19
+ LOGGER.info "Found config file: #{config_file}"
20
+ return config_file
21
+ end
22
+
23
+ raise EmoryConfigurationFileNotFoundException, 'Configuration file (.emory) was not found'
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,54 @@
1
+ require 'emory/teleport_config'
2
+ require 'emory/dsl/handler_builder'
3
+ require 'emory/dsl/teleport_config_builder'
4
+
5
+ module Emory
6
+ module DSL
7
+
8
+ class EmoryMisconfigurationException < Exception; end
9
+ class DuplicateHandlerNameException < Exception; end
10
+
11
+ class Dsl
12
+
13
+ LOGGER = Logging.logger[self]
14
+
15
+ class << self
16
+ def instance_eval_emoryfile(contents, config_path)
17
+ config = new
18
+ config.instance_eval(contents, config_path, 1)
19
+ config.handlers.freeze
20
+ config.teleports.freeze
21
+ config
22
+ rescue
23
+ LOGGER.error("Incorrect contents of .emory file, original error is:\n#{ $! }")
24
+ raise EmoryMisconfigurationException, 'Incorrect contents of .emory file'
25
+ end
26
+ end
27
+
28
+ def teleport(&block)
29
+ teleport_builder = TeleportConfigBuilder.new(handlers, &block)
30
+ teleport = teleport_builder.build
31
+ teleports << teleport
32
+ LOGGER.debug("Processed and added #{teleport} to the list: #{teleports}")
33
+ end
34
+
35
+ def handler(&block)
36
+ handler_builder = HandlerBuilder.new(&block)
37
+ handler = handler_builder.build
38
+ raise DuplicateHandlerNameException, "The handler name ':#{handler.name}' is defined more than once" if handlers.include?(handler.name)
39
+ handlers[handler.name] = handler
40
+ LOGGER.debug("Processed and added ':#{handler.name}' to the list: #{handlers}")
41
+ end
42
+
43
+ def handlers
44
+ @handlers ||= {}
45
+ end
46
+
47
+ def teleports
48
+ @teleports ||= []
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,78 @@
1
+ module Emory
2
+ module DSL
3
+
4
+ class HandlerConfigurationBlockMustBeSuppliedException < Exception; end
5
+ class HandlerNameMustBeSuppliedException < Exception; end
6
+ class HandlerImplementationMustBeSuppliedException < Exception; end
7
+ class HandlerActionAllMustBeSingletonException < Exception; end
8
+ class HandlerActionMustBeSuppliedException < Exception; end
9
+ class HandlerActionUnsupportedException < Exception; end
10
+
11
+ class HandlerBuilder
12
+
13
+ LOGGER = Logging.logger[self]
14
+ HANDLER_ACTION_ALL = :all
15
+ ALLOWED_HANDLER_ACTIONS = [HANDLER_ACTION_ALL, :added, :modified, :removed]
16
+
17
+ def initialize(&block)
18
+ LOGGER.debug('Initializing a new handler builder')
19
+ raise HandlerConfigurationBlockMustBeSuppliedException, "The configuration block with handler settings must be supplied" unless block_given?
20
+ @block = block
21
+ @options = {}
22
+ end
23
+
24
+ def build
25
+ LOGGER.debug('Evaluating handler configuration')
26
+ instance_eval &@block
27
+
28
+ raise HandlerNameMustBeSuppliedException, "The handler name must be supplied in its configuration" if @name.nil?
29
+ raise HandlerImplementationMustBeSuppliedException, "The handler implementation must be supplied in its configuration" if @implementation.nil?
30
+
31
+ LOGGER.debug('Creating a new handler instance')
32
+ handler = @implementation.new(@name, @options)
33
+
34
+ raise HandlerActionMustBeSuppliedException, "At least one handler action needs to be supplied" if @events.nil?
35
+ if @events.first != HANDLER_ACTION_ALL
36
+ handler.instance_eval { undef :added } unless @events.include?(:added)
37
+ handler.instance_eval { undef :modified } unless @events.include?(:modified)
38
+ handler.instance_eval { undef :removed } unless @events.include?(:removed)
39
+ end
40
+
41
+ handler
42
+ end
43
+
44
+ private
45
+
46
+ def name(name)
47
+ LOGGER.debug('Setting handler\'s name')
48
+ @name = name
49
+ end
50
+
51
+ def implementation(impl)
52
+ LOGGER.debug('Setting handler\'s implementation')
53
+ @implementation = impl
54
+ end
55
+
56
+ def options(opts)
57
+ LOGGER.debug('Setting handler\'s options')
58
+ @options = opts
59
+ end
60
+
61
+ def events(*actions)
62
+ LOGGER.debug('Setting handler\'s events')
63
+ uniq_actions = actions.uniq
64
+
65
+ raise HandlerActionMustBeSuppliedException, "At least one handler action needs to be supplied" if actions.empty?
66
+ raise HandlerActionAllMustBeSingletonException, "The handler action ':#{HANDLER_ACTION_ALL}' cannot be mixed with other types" if uniq_actions.include?(HANDLER_ACTION_ALL) and uniq_actions.size > 1
67
+ uniq_actions.each do |action|
68
+ raise HandlerActionUnsupportedException,
69
+ "The action ':#{action}' is unsupported. Supported actions are #{ALLOWED_HANDLER_ACTIONS}." unless ALLOWED_HANDLER_ACTIONS.include?(action)
70
+ end
71
+
72
+ @events = uniq_actions
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,62 @@
1
+ require 'emory/teleport_config'
2
+
3
+ module Emory
4
+ module DSL
5
+
6
+ class TeleportConfigurationBlockMustBeSuppliedException < Exception; end
7
+ class UndefinedTeleportHandlerException < Exception; end
8
+ class HandlerReferenceMustBeSuppliedException < Exception; end
9
+ class WatchedPathMustBeSuppliedException < Exception; end
10
+
11
+ class TeleportConfigBuilder
12
+
13
+ LOGGER = Logging.logger[self]
14
+
15
+ def initialize(handlers, &block)
16
+ LOGGER.debug('Initializing a new teleport builder')
17
+ raise TeleportConfigurationBlockMustBeSuppliedException, "The configuration block with teleport settings must be supplied" unless block_given?
18
+
19
+ @block = block
20
+ @available_handlers = handlers
21
+
22
+ LOGGER.debug('Creating a new teleport config instance')
23
+ @teleport_config = TeleportConfig.new
24
+ end
25
+
26
+ def build
27
+ LOGGER.debug('Evaluating teleport configuration')
28
+ instance_eval &@block
29
+
30
+ raise HandlerReferenceMustBeSuppliedException, "A reference to an existing handler must be supplied in teleport configuration" if @teleport_config.handler.nil?
31
+ raise WatchedPathMustBeSuppliedException, "A watched path must be supplied in teleport configuration" if @teleport_config.watched_path.nil?
32
+
33
+ @teleport_config
34
+ end
35
+
36
+ private
37
+
38
+ def path(path)
39
+ LOGGER.debug('Setting teleport\'s watched path')
40
+ @teleport_config.watched_path = path
41
+ end
42
+
43
+ def handler(handler_name)
44
+ LOGGER.debug('Setting teleport\'s handler')
45
+ raise UndefinedTeleportHandlerException, "The handler ':#{handler_name}' wired to teleport could not be found" unless @available_handlers.include?(handler_name)
46
+ @teleport_config.handler = @available_handlers[handler_name]
47
+ end
48
+
49
+ def ignore(ignore)
50
+ LOGGER.debug('Setting teleport\'s ignore expression')
51
+ @teleport_config.ignore = ignore
52
+ end
53
+
54
+ def filter(filter)
55
+ LOGGER.debug('Setting teleport\'s filter expression')
56
+ @teleport_config.filter = filter
57
+ end
58
+
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,35 @@
1
+ module Emory
2
+ module Handlers
3
+
4
+ class AbstractHandler
5
+
6
+ attr_reader :name
7
+
8
+ def initialize(name, options = {})
9
+ self.name = name
10
+ end
11
+
12
+ def added(file_path = nil)
13
+ raise_not_implemented_error(self.class, __method__)
14
+ end
15
+
16
+ def modified(file_path = nil)
17
+ raise_not_implemented_error(self.class, __method__)
18
+ end
19
+
20
+ def removed(file_path = nil)
21
+ raise_not_implemented_error(self.class, __method__)
22
+ end
23
+
24
+ private
25
+
26
+ attr_writer :name
27
+
28
+ def raise_not_implemented_error(class_name, method_name)
29
+ raise NotImplementedError, "This method is not implemented: #{class_name}##{method_name}"
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,33 @@
1
+ require 'emory/handlers/abstract_handler'
2
+
3
+ module Emory
4
+ module Handlers
5
+
6
+ class StdoutHandler < AbstractHandler
7
+
8
+ def initialize(name, options = {})
9
+ super
10
+ end
11
+
12
+ def added(file_path)
13
+ report_file_path_action(file_path, __method__)
14
+ end
15
+
16
+ def modified(file_path)
17
+ report_file_path_action(file_path, __method__)
18
+ end
19
+
20
+ def removed(file_path)
21
+ report_file_path_action(file_path, __method__)
22
+ end
23
+
24
+ private
25
+
26
+ def report_file_path_action(file_path, action)
27
+ puts ":#{name} ~> The file '#{file_path}' was '#{action}'"
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+ end