ttilley-fssm 0.0.6
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/.document +5 -0
- data/.gitignore +7 -0
- data/LICENSE +20 -0
- data/README.markdown +55 -0
- data/Rakefile +69 -0
- data/VERSION.yml +4 -0
- data/example.rb +9 -0
- data/fssm.gemspec +73 -0
- data/lib/fssm/backends/fsevents.rb +37 -0
- data/lib/fssm/backends/polling.rb +26 -0
- data/lib/fssm/ext.rb +37 -0
- data/lib/fssm/fsevents.rb +129 -0
- data/lib/fssm/monitor.rb +25 -0
- data/lib/fssm/path.rb +91 -0
- data/lib/fssm/state.rb +54 -0
- data/lib/fssm/support.rb +22 -0
- data/lib/fssm/tree.rb +176 -0
- data/lib/fssm.rb +41 -0
- data/prof-cache.rb +40 -0
- data/spec/path_spec.rb +75 -0
- data/spec/root/duck/quack.txt +0 -0
- data/spec/root/file.css +0 -0
- data/spec/root/file.rb +0 -0
- data/spec/root/file.yml +0 -0
- data/spec/root/moo/cow.txt +0 -0
- data/spec/spec_helper.rb +14 -0
- metadata +100 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2009 Travis Tilley
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Monitor API
|
|
2
|
+
===========
|
|
3
|
+
|
|
4
|
+
There are three ways you can run the monitor.
|
|
5
|
+
|
|
6
|
+
1. call monitor with a path parameter, and define callbacks in a block
|
|
7
|
+
2. call monitor with a block to configure multiple paths and callbacks
|
|
8
|
+
3. create a monitor object and run each step manually
|
|
9
|
+
|
|
10
|
+
Monitor with path
|
|
11
|
+
-----------------
|
|
12
|
+
|
|
13
|
+
This form watches one path, and enters the run loop automatically. The first parameter is the path to watch, and the second parameter is an optional glob pattern or array of glob patterns that a file must match in order to trigger a callback. The default glob, if ommitted, is `'**/*'`.
|
|
14
|
+
|
|
15
|
+
FSSM.monitor('/some/directory/', '**/*') do
|
|
16
|
+
update {|base, relative|}
|
|
17
|
+
delete {|base, relative|}
|
|
18
|
+
create {|base, relative|}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Monitor with block
|
|
22
|
+
------------------
|
|
23
|
+
|
|
24
|
+
This form watches one or more paths, and enters the run loop automatically. The glob configuration call can be ommitted, and defaults to `'**/*'`.
|
|
25
|
+
|
|
26
|
+
FSSM.monitor do
|
|
27
|
+
path '/some/directory/' do
|
|
28
|
+
glob '**/*.yml'
|
|
29
|
+
|
|
30
|
+
update {|base, relative|}
|
|
31
|
+
delete {|base, relative|}
|
|
32
|
+
create {|base, relative|}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
path '/some/other/directory/' do
|
|
36
|
+
update {|base, relative|}
|
|
37
|
+
delete {|base, relative|}
|
|
38
|
+
create {|base, relative|}
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
Monitor object
|
|
43
|
+
--------------
|
|
44
|
+
|
|
45
|
+
This form doesn't enter the run loop automatically.
|
|
46
|
+
|
|
47
|
+
monitor = FSSM::Monitor.new
|
|
48
|
+
|
|
49
|
+
monitor.path '/some/directory/' do
|
|
50
|
+
update {|base, relative|}
|
|
51
|
+
delete {|base, relative|}
|
|
52
|
+
create {|base, relative|}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
monitor.run
|
data/Rakefile
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'rake'
|
|
3
|
+
|
|
4
|
+
begin
|
|
5
|
+
require 'jeweler'
|
|
6
|
+
Jeweler::Tasks.new do |gem|
|
|
7
|
+
gem.name = "fssm"
|
|
8
|
+
gem.summary = %Q{file system state monitor}
|
|
9
|
+
gem.description = %Q{file system state monitor}
|
|
10
|
+
gem.email = "ttilley@gmail.com"
|
|
11
|
+
gem.homepage = "http://github.com/ttilley/fssm"
|
|
12
|
+
gem.authors = ["Travis Tilley"]
|
|
13
|
+
gem.add_development_dependency "rspec"
|
|
14
|
+
gem.add_development_dependency "yard"
|
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
|
16
|
+
end
|
|
17
|
+
rescue LoadError
|
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
require 'spec/rake/spectask'
|
|
22
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
|
23
|
+
spec.libs << 'lib' << 'spec'
|
|
24
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
|
28
|
+
spec.libs << 'lib' << 'spec'
|
|
29
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
|
30
|
+
spec.rcov = true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
task :spec => :check_dependencies
|
|
34
|
+
|
|
35
|
+
begin
|
|
36
|
+
require 'reek/rake_task'
|
|
37
|
+
Reek::RakeTask.new do |t|
|
|
38
|
+
t.fail_on_error = true
|
|
39
|
+
t.verbose = false
|
|
40
|
+
t.source_files = 'lib/**/*.rb'
|
|
41
|
+
end
|
|
42
|
+
rescue LoadError
|
|
43
|
+
task :reek do
|
|
44
|
+
abort "Reek is not available. In order to run reek, you must: sudo gem install reek"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
begin
|
|
49
|
+
require 'roodi'
|
|
50
|
+
require 'roodi_task'
|
|
51
|
+
RoodiTask.new do |t|
|
|
52
|
+
t.verbose = false
|
|
53
|
+
end
|
|
54
|
+
rescue LoadError
|
|
55
|
+
task :roodi do
|
|
56
|
+
abort "Roodi is not available. In order to run roodi, you must: sudo gem install roodi"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
task :default => :spec
|
|
61
|
+
|
|
62
|
+
begin
|
|
63
|
+
require 'yard'
|
|
64
|
+
YARD::Rake::YardocTask.new
|
|
65
|
+
rescue LoadError
|
|
66
|
+
task :yardoc do
|
|
67
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
|
68
|
+
end
|
|
69
|
+
end
|
data/VERSION.yml
ADDED
data/example.rb
ADDED
data/fssm.gemspec
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Generated by jeweler
|
|
2
|
+
# DO NOT EDIT THIS FILE
|
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
|
4
|
+
# -*- encoding: utf-8 -*-
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |s|
|
|
7
|
+
s.name = %q{fssm}
|
|
8
|
+
s.version = "0.0.6"
|
|
9
|
+
|
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
11
|
+
s.authors = ["Travis Tilley"]
|
|
12
|
+
s.date = %q{2009-09-05}
|
|
13
|
+
s.description = %q{file system state monitor}
|
|
14
|
+
s.email = %q{ttilley@gmail.com}
|
|
15
|
+
s.extra_rdoc_files = [
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"README.markdown"
|
|
18
|
+
]
|
|
19
|
+
s.files = [
|
|
20
|
+
".document",
|
|
21
|
+
".gitignore",
|
|
22
|
+
"LICENSE",
|
|
23
|
+
"README.markdown",
|
|
24
|
+
"Rakefile",
|
|
25
|
+
"VERSION.yml",
|
|
26
|
+
"example.rb",
|
|
27
|
+
"fssm.gemspec",
|
|
28
|
+
"lib/fssm.rb",
|
|
29
|
+
"lib/fssm/backends/fsevents.rb",
|
|
30
|
+
"lib/fssm/backends/polling.rb",
|
|
31
|
+
"lib/fssm/ext.rb",
|
|
32
|
+
"lib/fssm/fsevents.rb",
|
|
33
|
+
"lib/fssm/monitor.rb",
|
|
34
|
+
"lib/fssm/path.rb",
|
|
35
|
+
"lib/fssm/state.rb",
|
|
36
|
+
"lib/fssm/support.rb",
|
|
37
|
+
"lib/fssm/tree.rb",
|
|
38
|
+
"prof-cache.rb",
|
|
39
|
+
"spec/path_spec.rb",
|
|
40
|
+
"spec/root/duck/quack.txt",
|
|
41
|
+
"spec/root/file.css",
|
|
42
|
+
"spec/root/file.rb",
|
|
43
|
+
"spec/root/file.yml",
|
|
44
|
+
"spec/root/moo/cow.txt",
|
|
45
|
+
"spec/spec_helper.rb"
|
|
46
|
+
]
|
|
47
|
+
s.homepage = %q{http://github.com/ttilley/fssm}
|
|
48
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
|
49
|
+
s.require_paths = ["lib"]
|
|
50
|
+
s.rubygems_version = %q{1.3.5}
|
|
51
|
+
s.summary = %q{file system state monitor}
|
|
52
|
+
s.test_files = [
|
|
53
|
+
"spec/path_spec.rb",
|
|
54
|
+
"spec/root/file.rb",
|
|
55
|
+
"spec/spec_helper.rb"
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
if s.respond_to? :specification_version then
|
|
59
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
|
60
|
+
s.specification_version = 3
|
|
61
|
+
|
|
62
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
|
63
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
|
64
|
+
s.add_development_dependency(%q<yard>, [">= 0"])
|
|
65
|
+
else
|
|
66
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
|
67
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
|
68
|
+
end
|
|
69
|
+
else
|
|
70
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
|
71
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'fssm/fsevents'
|
|
2
|
+
|
|
3
|
+
module FSSM::Backends
|
|
4
|
+
class FSEvents
|
|
5
|
+
def initialize
|
|
6
|
+
@handlers = {}
|
|
7
|
+
@fsevents = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def add_path(path, preload=true)
|
|
11
|
+
handler = FSSM::State.new(path)
|
|
12
|
+
@handlers["#{path}"] = handler
|
|
13
|
+
|
|
14
|
+
fsevent = Rucola::FSEvents.new("#{path}", {:latency => 0.5}) do |events|
|
|
15
|
+
events.each do |event|
|
|
16
|
+
handler.refresh(event.path)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
fsevent.create_stream
|
|
21
|
+
handler.refresh(path.to_pathname, true) if preload
|
|
22
|
+
fsevent.start
|
|
23
|
+
@fsevents << fsevent
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def run
|
|
27
|
+
begin
|
|
28
|
+
OSX.CFRunLoopRun
|
|
29
|
+
rescue Interrupt
|
|
30
|
+
@fsevents.each do |fsev|
|
|
31
|
+
fsev.stop
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module FSSM::Backends
|
|
2
|
+
class Polling
|
|
3
|
+
def initialize(options={})
|
|
4
|
+
@handlers = []
|
|
5
|
+
@latency = options[:latency] || 1
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def add_path(path, preload=true)
|
|
9
|
+
handler = FSSM::State.new(path)
|
|
10
|
+
handler.refresh(path.to_pathname, true) if preload
|
|
11
|
+
@handlers << handler
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run
|
|
15
|
+
begin
|
|
16
|
+
loop do
|
|
17
|
+
start = Time.now.to_f
|
|
18
|
+
@handlers.each {|handler| handler.refresh}
|
|
19
|
+
nap_time = @latency - (Time.now.to_f - start)
|
|
20
|
+
sleep nap_time if nap_time > 0
|
|
21
|
+
end
|
|
22
|
+
rescue Interrupt
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/fssm/ext.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
class Pathname
|
|
2
|
+
class << self
|
|
3
|
+
def for(path)
|
|
4
|
+
path.is_a?(Pathname) ? path : new(path)
|
|
5
|
+
end
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# before overwriting chop_basename:
|
|
9
|
+
# %total - 29.50%
|
|
10
|
+
# %self - 20.50%
|
|
11
|
+
# after overwriting chop_basename:
|
|
12
|
+
# %total - 24.36%
|
|
13
|
+
# %self - 15.47%
|
|
14
|
+
CHOP_PAT = /\A#{SEPARATOR_PAT}?\z/
|
|
15
|
+
def chop_basename(path)
|
|
16
|
+
base = File.basename(path)
|
|
17
|
+
# the original version of this method recalculates this regexp
|
|
18
|
+
# each run, despite the pattern never changing.
|
|
19
|
+
if CHOP_PAT =~ base
|
|
20
|
+
return nil
|
|
21
|
+
else
|
|
22
|
+
return path[0, path.rindex(base)], base
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def segments
|
|
27
|
+
prefix, names = split_names(@path)
|
|
28
|
+
names.unshift(prefix) unless prefix.empty?
|
|
29
|
+
names.shift if names[0] == '.'
|
|
30
|
+
names
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def names
|
|
34
|
+
prefix, names = split_names(@path)
|
|
35
|
+
names
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
OSX.require_framework '/System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework'
|
|
2
|
+
|
|
3
|
+
module Rucola
|
|
4
|
+
class FSEvents
|
|
5
|
+
class FSEvent
|
|
6
|
+
attr_reader :fsevents_object
|
|
7
|
+
attr_reader :id
|
|
8
|
+
attr_reader :path
|
|
9
|
+
def initialize(fsevents_object, id, path)
|
|
10
|
+
@fsevents_object, @id, @path = fsevents_object, id, path
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Returns an array of the files/dirs in the path that the event occurred in.
|
|
14
|
+
# The files are sorted by the modification time, the first entry is the last modified file.
|
|
15
|
+
def files
|
|
16
|
+
Dir.glob("#{File.expand_path(path)}/*").sort_by {|f| File.mtime(f) }.reverse
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Returns the last modified file in the path that the event occurred in.
|
|
20
|
+
def last_modified_file
|
|
21
|
+
files.first
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class StreamError < StandardError; end
|
|
26
|
+
|
|
27
|
+
attr_reader :paths
|
|
28
|
+
attr_reader :stream
|
|
29
|
+
|
|
30
|
+
attr_accessor :allocator
|
|
31
|
+
attr_accessor :context
|
|
32
|
+
attr_accessor :since
|
|
33
|
+
attr_accessor :latency
|
|
34
|
+
attr_accessor :flags
|
|
35
|
+
|
|
36
|
+
# Initializes a new FSEvents `watchdog` object and starts watching the directories you specify for events. The
|
|
37
|
+
# block is used as a handler for events, which are passed as the block's argument. This method is the easiest
|
|
38
|
+
# way to start watching some directories if you don't care about the details of setting up the event stream.
|
|
39
|
+
#
|
|
40
|
+
# Rucola::FSEvents.start_watching('/tmp') do |events|
|
|
41
|
+
# events.each { |event| log.debug("#{event.files.inspect} were changed.") }
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# Rucola::FSEvents.start_watching('/var/log/system.log', '/var/log/secure.log', :since => last_id, :latency => 5) do
|
|
45
|
+
# Growl.notify("Something was added to your log files!")
|
|
46
|
+
# end
|
|
47
|
+
#
|
|
48
|
+
# Note that the method also returns the FSEvents object. This enables you to control the event stream if you want to.
|
|
49
|
+
#
|
|
50
|
+
# fsevents = Rucola::FSEvents.start_watching('/Volumes') do |events|
|
|
51
|
+
# events.each { |event| Growl.notify("Volume changes: #{event.files.to_sentence}") }
|
|
52
|
+
# end
|
|
53
|
+
# fsevents.stop
|
|
54
|
+
def self.start_watching(*params, &block)
|
|
55
|
+
fsevents = new(*params, &block)
|
|
56
|
+
fsevents.create_stream
|
|
57
|
+
fsevents.start
|
|
58
|
+
fsevents
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Creates a new FSEvents `watchdog` object. You can specify a list of paths to watch and options to control the
|
|
62
|
+
# behaviour of the watchdog. The block you pass serves as a callback when an event is generated on one of the
|
|
63
|
+
# specified paths.
|
|
64
|
+
#
|
|
65
|
+
# fsevents = FSEvents.new('/etc/passwd') { Mailer.send_mail("Someone touched the password file!") }
|
|
66
|
+
# fsevents.create_stream
|
|
67
|
+
# fsevents.start
|
|
68
|
+
#
|
|
69
|
+
# fsevents = FSEvents.new('/home/upload', :since => UploadWatcher.last_event_id) do |events|
|
|
70
|
+
# events.each do |event|
|
|
71
|
+
# UploadWatcher.last_event_id = event.id
|
|
72
|
+
# event.files.each do |file|
|
|
73
|
+
# UploadWatcher.logfile.append("#{file} was changed")
|
|
74
|
+
# end
|
|
75
|
+
# end
|
|
76
|
+
# end
|
|
77
|
+
#
|
|
78
|
+
# *:since: The service will report events that have happened after the supplied event ID. Never use 0 because that
|
|
79
|
+
# will cause every fsevent since the "beginning of time" to be reported. Use OSX::KFSEventStreamEventIdSinceNow
|
|
80
|
+
# if you want to receive events that have happened after this call. (Default: OSX::KFSEventStreamEventIdSinceNow).
|
|
81
|
+
# You can find the ID's passed with :since in the events passed to your block.
|
|
82
|
+
# *:latency: Number of seconds to wait until an FSEvent is reported, this allows the service to bundle events. (Default: 0.0)
|
|
83
|
+
#
|
|
84
|
+
# Please refer to the Cocoa documentation for the rest of the options.
|
|
85
|
+
def initialize(*params, &block)
|
|
86
|
+
raise ArgumentError, 'No callback block was specified.' unless block_given?
|
|
87
|
+
|
|
88
|
+
options = params.last.kind_of?(Hash) ? params.pop : {}
|
|
89
|
+
@paths = params.flatten
|
|
90
|
+
|
|
91
|
+
paths.each { |path| raise ArgumentError, "The specified path (#{path}) does not exist." unless File.exist?(path) }
|
|
92
|
+
|
|
93
|
+
@allocator = options[:allocator] || OSX::KCFAllocatorDefault
|
|
94
|
+
@context = options[:context] || nil
|
|
95
|
+
@since = options[:since] || OSX::KFSEventStreamEventIdSinceNow
|
|
96
|
+
@latency = options[:latency] || 0.0
|
|
97
|
+
@flags = options[:flags] || 0
|
|
98
|
+
@stream = options[:stream] || nil
|
|
99
|
+
|
|
100
|
+
@user_callback = block
|
|
101
|
+
@callback = Proc.new do |stream, client_callback_info, number_of_events, paths_pointer, event_flags, event_ids|
|
|
102
|
+
paths_pointer.regard_as('*')
|
|
103
|
+
events = []
|
|
104
|
+
number_of_events.times {|i| events << Rucola::FSEvents::FSEvent.new(self, event_ids[i], paths_pointer[i]) }
|
|
105
|
+
@user_callback.call(events)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Create the stream.
|
|
110
|
+
# Raises a Rucola::FSEvents::StreamError if the stream could not be created.
|
|
111
|
+
def create_stream
|
|
112
|
+
@stream = OSX.FSEventStreamCreate(@allocator, @callback, @context, @paths, @since, @latency, @flags)
|
|
113
|
+
raise(StreamError, 'Unable to create FSEvents stream.') unless @stream
|
|
114
|
+
OSX.FSEventStreamScheduleWithRunLoop(@stream, OSX.CFRunLoopGetCurrent, OSX::KCFRunLoopDefaultMode)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Start the stream.
|
|
118
|
+
# Raises a Rucola::FSEvents::StreamError if the stream could not be started.
|
|
119
|
+
def start
|
|
120
|
+
raise(StreamError, 'Unable to start FSEvents stream.') unless OSX.FSEventStreamStart(@stream)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Stop the stream.
|
|
124
|
+
# You can resume it by calling `start` again.
|
|
125
|
+
def stop
|
|
126
|
+
OSX.FSEventStreamStop(@stream)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
data/lib/fssm/monitor.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class FSSM::Monitor
|
|
2
|
+
def initialize(options={})
|
|
3
|
+
@options = options
|
|
4
|
+
@backend = FSSM::Backends::Default.new
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def path(*args, &block)
|
|
8
|
+
path = FSSM::Path.new(*args)
|
|
9
|
+
|
|
10
|
+
if block_given?
|
|
11
|
+
if block.arity == 1
|
|
12
|
+
block.call(path)
|
|
13
|
+
else
|
|
14
|
+
path.instance_eval(&block)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
@backend.add_path(path)
|
|
19
|
+
path
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def run
|
|
23
|
+
@backend.run
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/fssm/path.rb
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
class FSSM::Path
|
|
2
|
+
def initialize(path=nil, glob=nil, &block)
|
|
3
|
+
set_path(path || '.')
|
|
4
|
+
set_glob(glob || '**/*')
|
|
5
|
+
init_callbacks
|
|
6
|
+
|
|
7
|
+
if block_given?
|
|
8
|
+
if block.arity == 1
|
|
9
|
+
block.call(self)
|
|
10
|
+
else
|
|
11
|
+
self.instance_eval(&block)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_s
|
|
17
|
+
@path.to_s
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_pathname
|
|
21
|
+
@path
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def glob(value=nil)
|
|
25
|
+
return @glob if value.nil?
|
|
26
|
+
set_glob(value)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def create(callback_or_path=nil, &block)
|
|
30
|
+
callback_action(:create, (block_given? ? block : callback_or_path))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def update(callback_or_path=nil, &block)
|
|
34
|
+
callback_action(:update, (block_given? ? block : callback_or_path))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def delete(callback_or_path=nil, &block)
|
|
38
|
+
callback_action(:delete, (block_given? ? block : callback_or_path))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def init_callbacks
|
|
44
|
+
do_nothing = lambda {|base, relative|}
|
|
45
|
+
@callbacks = Hash.new(do_nothing)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def callback_action(type, arg=nil)
|
|
49
|
+
if arg.is_a?(Proc)
|
|
50
|
+
set_callback(type, arg)
|
|
51
|
+
elsif arg.nil?
|
|
52
|
+
get_callback(type)
|
|
53
|
+
else
|
|
54
|
+
run_callback(type, arg)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def set_callback(type, arg)
|
|
59
|
+
raise ArgumentError, "Proc expected" unless arg.is_a?(Proc)
|
|
60
|
+
@callbacks[type] = arg
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def get_callback(type)
|
|
64
|
+
@callbacks[type]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def run_callback(type, arg)
|
|
68
|
+
base, relative = split_path(arg)
|
|
69
|
+
|
|
70
|
+
begin
|
|
71
|
+
@callbacks[type].call(base, relative)
|
|
72
|
+
rescue Exception => e
|
|
73
|
+
raise FSSM::CallbackError, "#{type} - #{base.join(relative)}: #{e.message}", e.backtrace
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def split_path(path)
|
|
78
|
+
path = Pathname.for(path)
|
|
79
|
+
[@path, (path.relative? ? path : path.relative_path_from(@path))]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def set_path(path)
|
|
83
|
+
path = Pathname.for(path)
|
|
84
|
+
raise FSSM::FileNotFoundError, "#{path}" unless path.exist?
|
|
85
|
+
@path = path.realpath
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def set_glob(glob)
|
|
89
|
+
@glob = glob.is_a?(Array) ? glob : [glob]
|
|
90
|
+
end
|
|
91
|
+
end
|
data/lib/fssm/state.rb
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
class FSSM::State
|
|
3
|
+
def initialize(path)
|
|
4
|
+
@path = path
|
|
5
|
+
@cache = FSSM::Tree::Cache.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def refresh(base=nil, skip_callbacks=false)
|
|
9
|
+
previous, current = recache(base || @path.to_pathname)
|
|
10
|
+
|
|
11
|
+
unless skip_callbacks
|
|
12
|
+
deleted(previous, current)
|
|
13
|
+
created(previous, current)
|
|
14
|
+
modified(previous, current)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def created(previous, current)
|
|
21
|
+
(current.keys - previous.keys).each {|created| @path.create(created)}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def deleted(previous, current)
|
|
25
|
+
(previous.keys - current.keys).each {|deleted| @path.delete(deleted)}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def modified(previous, current)
|
|
29
|
+
(current.keys & previous.keys).each do |file|
|
|
30
|
+
@path.update(file) if (current[file] <=> previous[file]) != 0
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def recache(base)
|
|
35
|
+
base = Pathname.for(base)
|
|
36
|
+
previous = @cache.files
|
|
37
|
+
snapshot(base)
|
|
38
|
+
current = @cache.files
|
|
39
|
+
[previous, current]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def snapshot(base)
|
|
43
|
+
base = Pathname.for(base)
|
|
44
|
+
@cache.unset(base)
|
|
45
|
+
@path.glob.each {|glob| add_glob(base, glob)}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def add_glob(base, glob)
|
|
49
|
+
Pathname.glob(base.join(glob)).each do |fn|
|
|
50
|
+
@cache.set(fn)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
end
|
data/lib/fssm/support.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module FSSM::Support
|
|
2
|
+
class << self
|
|
3
|
+
def backend
|
|
4
|
+
(mac? && carbon_core?) ? 'FSEvents' : 'Polling'
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def mac?
|
|
8
|
+
@@mac ||= RUBY_PLATFORM =~ /darwin/i
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def carbon_core?
|
|
12
|
+
@@carbon_core ||= begin
|
|
13
|
+
require 'osx/foundation'
|
|
14
|
+
OSX.require_framework '/System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework'
|
|
15
|
+
true
|
|
16
|
+
rescue LoadError
|
|
17
|
+
false
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/fssm/tree.rb
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
module FSSM::Tree
|
|
2
|
+
module NodeBase
|
|
3
|
+
def initialize
|
|
4
|
+
@children = {}
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
protected
|
|
8
|
+
|
|
9
|
+
def child(segment)
|
|
10
|
+
@children["#{segment}"]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def child!(segment)
|
|
14
|
+
(@children["#{segment}"] ||= Node.new)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def has_child?(segment)
|
|
18
|
+
@children.has_key?("#{segment}")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def remove_child(segment)
|
|
22
|
+
@children.delete("#{segment}")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def remove_children
|
|
26
|
+
@children.clear
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
module NodeEnumerable
|
|
31
|
+
include NodeBase
|
|
32
|
+
include Enumerable
|
|
33
|
+
|
|
34
|
+
def each(prefix=nil, &block)
|
|
35
|
+
@children.each do |segment, node|
|
|
36
|
+
cprefix = prefix ?
|
|
37
|
+
Pathname.for(prefix).join(segment) :
|
|
38
|
+
Pathname.for(segment)
|
|
39
|
+
block.call(cprefix, node)
|
|
40
|
+
node.each(cprefix, &block)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
module NodeInsertion
|
|
46
|
+
include NodeBase
|
|
47
|
+
|
|
48
|
+
def unset(path)
|
|
49
|
+
key = key_segments(path)
|
|
50
|
+
|
|
51
|
+
if key.empty?
|
|
52
|
+
remove_children
|
|
53
|
+
return nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
segment = key.pop
|
|
57
|
+
node = descendant(key)
|
|
58
|
+
|
|
59
|
+
return unless node
|
|
60
|
+
|
|
61
|
+
node.remove_child(segment)
|
|
62
|
+
|
|
63
|
+
nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def set(path)
|
|
67
|
+
node = descendant!(path)
|
|
68
|
+
node.from_path(path).mtime
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
protected
|
|
72
|
+
|
|
73
|
+
def key_segments(key)
|
|
74
|
+
return key if key.is_a?(Array)
|
|
75
|
+
Pathname.for(key).segments
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def descendant(path)
|
|
79
|
+
recurse(path, false)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def descendant!(path)
|
|
83
|
+
recurse(path, true)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def recurse(key, create=false)
|
|
87
|
+
key = key_segments(key)
|
|
88
|
+
node = self
|
|
89
|
+
|
|
90
|
+
until key.empty?
|
|
91
|
+
segment = key.shift
|
|
92
|
+
node = create ? node.child!(segment) : node.child(segment)
|
|
93
|
+
return nil unless node
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
node
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
module CacheDebug
|
|
101
|
+
def set(path)
|
|
102
|
+
FSSM.dbg("Cache#set(#{path})")
|
|
103
|
+
super
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def unset(path)
|
|
107
|
+
FSSM.dbg("Cache#unset(#{path})")
|
|
108
|
+
super
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def ftype(ft)
|
|
112
|
+
FSSM.dbg("Cache#ftype(#{ft})")
|
|
113
|
+
super
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
class Node
|
|
118
|
+
include NodeBase
|
|
119
|
+
include NodeEnumerable
|
|
120
|
+
|
|
121
|
+
attr_accessor :mtime
|
|
122
|
+
attr_accessor :ftype
|
|
123
|
+
|
|
124
|
+
def <=>(other)
|
|
125
|
+
return unless other.is_a?(::FSSM::Tree::Node)
|
|
126
|
+
self.mtime <=> other.mtime
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def from_path(path)
|
|
130
|
+
path = Pathname.for(path)
|
|
131
|
+
@ftype = path.ftype
|
|
132
|
+
# this handles bad symlinks without failing. why handle bad symlinks at
|
|
133
|
+
# all? well, we could still be interested in their creation and deletion.
|
|
134
|
+
@mtime = path.symlink? ? Time.at(0) : path.mtime
|
|
135
|
+
self
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
class Cache
|
|
140
|
+
include NodeBase
|
|
141
|
+
include NodeEnumerable
|
|
142
|
+
include NodeInsertion
|
|
143
|
+
include CacheDebug if $DEBUG
|
|
144
|
+
|
|
145
|
+
def set(path)
|
|
146
|
+
# all paths set from this level need to be absolute
|
|
147
|
+
# realpath will fail on broken links
|
|
148
|
+
path = Pathname.for(path).expand_path
|
|
149
|
+
super(path)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def files
|
|
153
|
+
ftype('file')
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def directories
|
|
157
|
+
ftype('directory')
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def links
|
|
161
|
+
ftype('link')
|
|
162
|
+
end
|
|
163
|
+
alias symlinks links
|
|
164
|
+
|
|
165
|
+
private
|
|
166
|
+
|
|
167
|
+
def ftype(ft)
|
|
168
|
+
inject({}) do |hash, entry|
|
|
169
|
+
path, node = entry
|
|
170
|
+
hash["#{path}"] = node.mtime if node.ftype == ft
|
|
171
|
+
hash
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
end
|
data/lib/fssm.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
dir = File.dirname(__FILE__)
|
|
2
|
+
$LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
|
|
3
|
+
|
|
4
|
+
module FSSM
|
|
5
|
+
FileNotFoundError = Class.new(StandardError)
|
|
6
|
+
CallbackError = Class.new(StandardError)
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def dbg(msg=nil)
|
|
10
|
+
STDERR.puts(msg)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def monitor(*args, &block)
|
|
14
|
+
monitor = FSSM::Monitor.new
|
|
15
|
+
context = args.empty? ? monitor : monitor.path(*args)
|
|
16
|
+
|
|
17
|
+
if block_given?
|
|
18
|
+
if block.arity == 1
|
|
19
|
+
block.call(context)
|
|
20
|
+
else
|
|
21
|
+
context.instance_eval(&block)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
monitor.run
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
require 'thread'
|
|
31
|
+
require 'pathname'
|
|
32
|
+
|
|
33
|
+
require 'fssm/ext'
|
|
34
|
+
require 'fssm/support'
|
|
35
|
+
require 'fssm/tree'
|
|
36
|
+
require 'fssm/path'
|
|
37
|
+
require 'fssm/state'
|
|
38
|
+
require 'fssm/monitor'
|
|
39
|
+
|
|
40
|
+
require "fssm/backends/#{FSSM::Support.backend.downcase}"
|
|
41
|
+
FSSM::Backends::Default = FSSM::Backends.const_get(FSSM::Support.backend)
|
data/prof-cache.rb
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
|
2
|
+
|
|
3
|
+
require 'fssm'
|
|
4
|
+
|
|
5
|
+
require 'rubygems'
|
|
6
|
+
require 'ruby-prof'
|
|
7
|
+
|
|
8
|
+
$test_path = Pathname.new('.')
|
|
9
|
+
$test_files = Pathname.glob(File.join($test_path, '**', '*'))
|
|
10
|
+
|
|
11
|
+
RubyProf.start
|
|
12
|
+
RubyProf.pause
|
|
13
|
+
|
|
14
|
+
cache = FSSM::Tree::Cache.new
|
|
15
|
+
|
|
16
|
+
5000.times do |num|
|
|
17
|
+
iteration = "%-5d" % (num + 1)
|
|
18
|
+
print "iteration #{iteration}"
|
|
19
|
+
|
|
20
|
+
print '!'
|
|
21
|
+
RubyProf.resume
|
|
22
|
+
cache.unset($test_path)
|
|
23
|
+
RubyProf.pause
|
|
24
|
+
print '!'
|
|
25
|
+
|
|
26
|
+
$test_files.each do |fn|
|
|
27
|
+
print '.'
|
|
28
|
+
RubyProf.resume
|
|
29
|
+
cache.set(fn)
|
|
30
|
+
RubyProf.pause
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
print "\n\n"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
result = RubyProf.stop
|
|
37
|
+
output = File.new('prof.html', 'w+')
|
|
38
|
+
|
|
39
|
+
printer = RubyProf::GraphHtmlPrinter.new(result)
|
|
40
|
+
printer.print(output, :min_percent => 1)
|
data/spec/path_spec.rb
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
2
|
+
|
|
3
|
+
describe "The File System State Monitor" do
|
|
4
|
+
describe "paths" do
|
|
5
|
+
it "should accept a valid filesystem directory" do
|
|
6
|
+
lambda {FSSM::Path.new("#{@watch_root}")}.should_not raise_error
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it "should not accept an invalid filesystem directory" do
|
|
10
|
+
lambda {FSSM::Path.new('/does/not/exist/kthxbye')}.should raise_error
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "should default the path to the current directory" do
|
|
14
|
+
path = FSSM::Path.new
|
|
15
|
+
here = Pathname.new('.').realpath
|
|
16
|
+
|
|
17
|
+
"#{here}".should == "#{path}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "should accept an optional glob array parameter" do
|
|
21
|
+
path = FSSM::Path.new('.', ['**/*.yml'])
|
|
22
|
+
path.glob.should == ['**/*.yml']
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "should accept an optional glob string parameter" do
|
|
26
|
+
path = FSSM::Path.new('.', '**/*.yml')
|
|
27
|
+
path.glob.should == ['**/*.yml']
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "should default the glob to ['**/*']" do
|
|
31
|
+
path = FSSM::Path.new
|
|
32
|
+
path.glob.should == ['**/*']
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "should accept a callback for update events" do
|
|
36
|
+
path = FSSM::Path.new
|
|
37
|
+
callback = lambda {|base, relative| return true}
|
|
38
|
+
path.update(callback)
|
|
39
|
+
(path.update).should == callback
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "should accept a callback for delete events" do
|
|
43
|
+
path = FSSM::Path.new
|
|
44
|
+
callback = lambda {|base, relative| return true}
|
|
45
|
+
path.delete(callback)
|
|
46
|
+
(path.delete).should == callback
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "should accept a callback for create events" do
|
|
50
|
+
path = FSSM::Path.new
|
|
51
|
+
callback = lambda {|base, relative| return true}
|
|
52
|
+
path.create(callback)
|
|
53
|
+
(path.create).should == callback
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "should accept a configuration block" do
|
|
57
|
+
path = FSSM::Path.new "#{@watch_root}" do
|
|
58
|
+
glob '**/*.yml'
|
|
59
|
+
update {|base, relative| 'success'}
|
|
60
|
+
delete {|base, relative| 'success'}
|
|
61
|
+
create {|base, relative| 'success'}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
"#{path}".should == "#{@watch_root}"
|
|
65
|
+
path.glob.should == ['**/*.yml']
|
|
66
|
+
path.update.should be_a_kind_of(Proc)
|
|
67
|
+
path.delete.should be_a_kind_of(Proc)
|
|
68
|
+
path.create.should be_a_kind_of(Proc)
|
|
69
|
+
path.update.call('','').should == 'success'
|
|
70
|
+
path.delete.call('','').should == 'success'
|
|
71
|
+
path.create.call('','').should == 'success'
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
75
|
+
end
|
|
File without changes
|
data/spec/root/file.css
ADDED
|
File without changes
|
data/spec/root/file.rb
ADDED
|
File without changes
|
data/spec/root/file.yml
ADDED
|
File without changes
|
|
File without changes
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
3
|
+
|
|
4
|
+
require 'pathname'
|
|
5
|
+
require 'fssm'
|
|
6
|
+
|
|
7
|
+
require 'spec'
|
|
8
|
+
require 'spec/autorun'
|
|
9
|
+
|
|
10
|
+
Spec::Runner.configure do |config|
|
|
11
|
+
config.before :all do
|
|
12
|
+
@watch_root = Pathname.new(__FILE__).dirname.join('root').expand_path
|
|
13
|
+
end
|
|
14
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ttilley-fssm
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.6
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Travis Tilley
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2009-09-05 00:00:00 -07:00
|
|
13
|
+
default_executable:
|
|
14
|
+
dependencies:
|
|
15
|
+
- !ruby/object:Gem::Dependency
|
|
16
|
+
name: rspec
|
|
17
|
+
type: :development
|
|
18
|
+
version_requirement:
|
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
20
|
+
requirements:
|
|
21
|
+
- - ">="
|
|
22
|
+
- !ruby/object:Gem::Version
|
|
23
|
+
version: "0"
|
|
24
|
+
version:
|
|
25
|
+
- !ruby/object:Gem::Dependency
|
|
26
|
+
name: yard
|
|
27
|
+
type: :development
|
|
28
|
+
version_requirement:
|
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: "0"
|
|
34
|
+
version:
|
|
35
|
+
description: file system state monitor
|
|
36
|
+
email: ttilley@gmail.com
|
|
37
|
+
executables: []
|
|
38
|
+
|
|
39
|
+
extensions: []
|
|
40
|
+
|
|
41
|
+
extra_rdoc_files:
|
|
42
|
+
- LICENSE
|
|
43
|
+
- README.markdown
|
|
44
|
+
files:
|
|
45
|
+
- .document
|
|
46
|
+
- .gitignore
|
|
47
|
+
- LICENSE
|
|
48
|
+
- README.markdown
|
|
49
|
+
- Rakefile
|
|
50
|
+
- VERSION.yml
|
|
51
|
+
- example.rb
|
|
52
|
+
- fssm.gemspec
|
|
53
|
+
- lib/fssm.rb
|
|
54
|
+
- lib/fssm/backends/fsevents.rb
|
|
55
|
+
- lib/fssm/backends/polling.rb
|
|
56
|
+
- lib/fssm/ext.rb
|
|
57
|
+
- lib/fssm/fsevents.rb
|
|
58
|
+
- lib/fssm/monitor.rb
|
|
59
|
+
- lib/fssm/path.rb
|
|
60
|
+
- lib/fssm/state.rb
|
|
61
|
+
- lib/fssm/support.rb
|
|
62
|
+
- lib/fssm/tree.rb
|
|
63
|
+
- prof-cache.rb
|
|
64
|
+
- spec/path_spec.rb
|
|
65
|
+
- spec/root/duck/quack.txt
|
|
66
|
+
- spec/root/file.css
|
|
67
|
+
- spec/root/file.rb
|
|
68
|
+
- spec/root/file.yml
|
|
69
|
+
- spec/root/moo/cow.txt
|
|
70
|
+
- spec/spec_helper.rb
|
|
71
|
+
has_rdoc: false
|
|
72
|
+
homepage: http://github.com/ttilley/fssm
|
|
73
|
+
post_install_message:
|
|
74
|
+
rdoc_options:
|
|
75
|
+
- --charset=UTF-8
|
|
76
|
+
require_paths:
|
|
77
|
+
- lib
|
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: "0"
|
|
83
|
+
version:
|
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: "0"
|
|
89
|
+
version:
|
|
90
|
+
requirements: []
|
|
91
|
+
|
|
92
|
+
rubyforge_project:
|
|
93
|
+
rubygems_version: 1.2.0
|
|
94
|
+
signing_key:
|
|
95
|
+
specification_version: 3
|
|
96
|
+
summary: file system state monitor
|
|
97
|
+
test_files:
|
|
98
|
+
- spec/path_spec.rb
|
|
99
|
+
- spec/root/file.rb
|
|
100
|
+
- spec/spec_helper.rb
|