middleman-core 3.0.0.alpha.7 → 3.0.0.alpha.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. data/features/preview_changes.feature +12 -1
  2. data/lib/middleman-core/base.rb +1 -1
  3. data/lib/middleman-core/cli/server.rb +1 -1
  4. data/lib/middleman-core/core_extensions/file_watcher.rb +2 -0
  5. data/lib/middleman-core/core_extensions/front_matter.rb +0 -1
  6. data/lib/middleman-core/core_extensions/rendering.rb +8 -1
  7. data/lib/middleman-core/renderers/erb.rb +10 -0
  8. data/lib/middleman-core/step_definitions/middleman_steps.rb +10 -8
  9. data/lib/middleman-core/step_definitions/server_steps.rb +4 -3
  10. data/lib/middleman-core/vendor/rb-fsevent-0.4.3.1/LICENSE +20 -0
  11. data/lib/middleman-core/vendor/rb-fsevent-0.4.3.1/README.rdoc +254 -0
  12. data/lib/middleman-core/vendor/rb-fsevent-0.4.3.1/ext/extconf.rb +61 -0
  13. data/lib/middleman-core/vendor/rb-fsevent-0.4.3.1/ext/fsevent/fsevent_watch.c +226 -0
  14. data/lib/middleman-core/vendor/rb-fsevent-0.4.3.1/lib/rb-fsevent/fsevent.rb +105 -0
  15. data/lib/middleman-core/vendor/rb-fsevent-0.4.3.1/lib/rb-fsevent/version.rb +3 -0
  16. data/lib/middleman-core/vendor/rb-fsevent-0.4.3.1/lib/rb-fsevent.rb +2 -0
  17. data/lib/middleman-core/vendor/rb-inotify-0.8.8/.yardopts +4 -0
  18. data/lib/middleman-core/vendor/rb-inotify-0.8.8/MIT-LICENSE +20 -0
  19. data/lib/middleman-core/vendor/rb-inotify-0.8.8/README.md +66 -0
  20. data/lib/middleman-core/vendor/rb-inotify-0.8.8/Rakefile +54 -0
  21. data/lib/middleman-core/vendor/rb-inotify-0.8.8/VERSION +1 -0
  22. data/lib/middleman-core/vendor/rb-inotify-0.8.8/lib/rb-inotify/event.rb +139 -0
  23. data/lib/middleman-core/vendor/rb-inotify-0.8.8/lib/rb-inotify/native/flags.rb +89 -0
  24. data/lib/middleman-core/vendor/rb-inotify-0.8.8/lib/rb-inotify/native.rb +31 -0
  25. data/lib/middleman-core/vendor/rb-inotify-0.8.8/lib/rb-inotify/notifier.rb +308 -0
  26. data/lib/middleman-core/vendor/rb-inotify-0.8.8/lib/rb-inotify/watcher.rb +83 -0
  27. data/lib/middleman-core/vendor/rb-inotify-0.8.8/lib/rb-inotify.rb +17 -0
  28. data/lib/middleman-core/vendor/rb-inotify-0.8.8/rb-inotify.gemspec +53 -0
  29. data/lib/middleman-core/version.rb +1 -1
  30. data/lib/middleman-core/{guard.rb → watcher.rb} +62 -58
  31. data/lib/middleman-core.rb +1 -1
  32. data/middleman-core.gemspec +1 -1
  33. metadata +29 -10
@@ -0,0 +1,226 @@
1
+ #include <stdio.h>
2
+ #include <stdlib.h>
3
+ #include <unistd.h>
4
+
5
+ #include <CoreServices/CoreServices.h>
6
+
7
+
8
+ // Structure for storing metadata parsed from the commandline
9
+ static struct {
10
+ FSEventStreamEventId sinceWhen;
11
+ CFTimeInterval latency;
12
+ FSEventStreamCreateFlags flags;
13
+ CFMutableArrayRef paths;
14
+ } config = {
15
+ (UInt64) kFSEventStreamEventIdSinceNow,
16
+ (double) 0.3,
17
+ (UInt32) kFSEventStreamCreateFlagNone,
18
+ NULL
19
+ };
20
+
21
+ // Prototypes
22
+ static void append_path(const char *path);
23
+ static inline void parse_cli_settings(int argc, const char *argv[]);
24
+ static void callback(FSEventStreamRef streamRef,
25
+ void *clientCallBackInfo,
26
+ size_t numEvents,
27
+ void *eventPaths,
28
+ const FSEventStreamEventFlags eventFlags[],
29
+ const FSEventStreamEventId eventIds[]);
30
+
31
+
32
+ // Resolve a path and append it to the CLI settings structure
33
+ // The FSEvents API will, internally, resolve paths using a similar scheme.
34
+ // Performing this ahead of time makes things less confusing, IMHO.
35
+ static void append_path(const char *path)
36
+ {
37
+ #ifdef DEBUG
38
+ fprintf(stderr, "\n");
39
+ fprintf(stderr, "append_path called for: %s\n", path);
40
+ #endif
41
+
42
+ char fullPath[PATH_MAX];
43
+
44
+ if (realpath(path, fullPath) == NULL) {
45
+ #ifdef DEBUG
46
+ fprintf(stderr, " realpath not directly resolvable from path\n");
47
+ #endif
48
+
49
+ if (path[0] != '/') {
50
+ #ifdef DEBUG
51
+ fprintf(stderr, " passed path is not absolute\n");
52
+ #endif
53
+ size_t len;
54
+ getcwd(fullPath, sizeof(fullPath));
55
+ #ifdef DEBUG
56
+ fprintf(stderr, " result of getcwd: %s\n", fullPath);
57
+ #endif
58
+ len = strlen(fullPath);
59
+ fullPath[len] = '/';
60
+ strlcpy(&fullPath[len + 1], path, sizeof(fullPath) - (len + 1));
61
+ } else {
62
+ #ifdef DEBUG
63
+ fprintf(stderr, " assuming path does not YET exist\n");
64
+ #endif
65
+ strlcpy(fullPath, path, sizeof(fullPath));
66
+ }
67
+ }
68
+
69
+ #ifdef DEBUG
70
+ fprintf(stderr, " resolved path to: %s\n", fullPath);
71
+ fprintf(stderr, "\n");
72
+ fflush(stderr);
73
+ #endif
74
+
75
+ CFStringRef pathRef = CFStringCreateWithCString(kCFAllocatorDefault,
76
+ fullPath,
77
+ kCFStringEncodingUTF8);
78
+ CFArrayAppendValue(config.paths, pathRef);
79
+ CFRelease(pathRef);
80
+ }
81
+
82
+ // Parse commandline settings
83
+ static inline void parse_cli_settings(int argc, const char *argv[])
84
+ {
85
+ config.paths = CFArrayCreateMutable(NULL,
86
+ (CFIndex)0,
87
+ &kCFTypeArrayCallBacks);
88
+
89
+ for (int i = 1; i < argc; i++) {
90
+ if (strcmp(argv[i], "--since-when") == 0) {
91
+ config.sinceWhen = strtoull(argv[++i], NULL, 0);
92
+ } else if (strcmp(argv[i], "--latency") == 0) {
93
+ config.latency = strtod(argv[++i], NULL);
94
+ } else if (strcmp(argv[i], "--no-defer") == 0) {
95
+ config.flags |= kFSEventStreamCreateFlagNoDefer;
96
+ } else if (strcmp(argv[i], "--watch-root") == 0) {
97
+ config.flags |= kFSEventStreamCreateFlagWatchRoot;
98
+ } else if (strcmp(argv[i], "--ignore-self") == 0) {
99
+ #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
100
+ config.flags |= kFSEventStreamCreateFlagIgnoreSelf;
101
+ #else
102
+ fprintf(stderr, "MacOSX10.6.sdk is required for --ignore-self\n");
103
+ #endif
104
+ } else {
105
+ append_path(argv[i]);
106
+ }
107
+ }
108
+
109
+ if (CFArrayGetCount(config.paths) == 0) {
110
+ append_path(".");
111
+ }
112
+
113
+ #ifdef DEBUG
114
+ fprintf(stderr, "config.sinceWhen %llu\n", config.sinceWhen);
115
+ fprintf(stderr, "config.latency %f\n", config.latency);
116
+ fprintf(stderr, "config.flags %#.8x\n", config.flags);
117
+ fprintf(stderr, "config.paths\n");
118
+
119
+ long numpaths = CFArrayGetCount(config.paths);
120
+
121
+ for (long i = 0; i < numpaths; i++) {
122
+ char path[PATH_MAX];
123
+ CFStringGetCString(CFArrayGetValueAtIndex(config.paths, i),
124
+ path,
125
+ PATH_MAX,
126
+ kCFStringEncodingUTF8);
127
+ fprintf(stderr, " %s\n", path);
128
+ }
129
+
130
+ fprintf(stderr, "\n");
131
+ fflush(stderr);
132
+ #endif
133
+ }
134
+
135
+ static void callback(FSEventStreamRef streamRef,
136
+ void *clientCallBackInfo,
137
+ size_t numEvents,
138
+ void *eventPaths,
139
+ const FSEventStreamEventFlags eventFlags[],
140
+ const FSEventStreamEventId eventIds[])
141
+ {
142
+ char **paths = eventPaths;
143
+
144
+ #ifdef DEBUG
145
+ fprintf(stderr, "\n");
146
+ fprintf(stderr, "FSEventStreamCallback fired!\n");
147
+ fprintf(stderr, " numEvents: %lu\n", numEvents);
148
+
149
+ for (size_t i = 0; i < numEvents; i++) {
150
+ fprintf(stderr, " event path: %s\n", paths[i]);
151
+ fprintf(stderr, " event flags: %#.8x\n", eventFlags[i]);
152
+ fprintf(stderr, " event ID: %llu\n", eventIds[i]);
153
+ }
154
+
155
+ fprintf(stderr, "\n");
156
+ fflush(stderr);
157
+ #endif
158
+
159
+ for (size_t i = 0; i < numEvents; i++) {
160
+ fprintf(stdout, "%s", paths[i]);
161
+ fprintf(stdout, ":");
162
+ }
163
+
164
+ fprintf(stdout, "\n");
165
+ fflush(stdout);
166
+ }
167
+
168
+ int main(int argc, const char *argv[])
169
+ {
170
+ /*
171
+ * a subprocess will initially inherit the process group of its parent. the
172
+ * process group may have a control terminal associated with it, which would
173
+ * be the first tty device opened by the group leader. typically the group
174
+ * leader is your shell and the control terminal is your login device. a
175
+ * subset of signals triggered on the control terminal are sent to all members
176
+ * of the process group, in large part to facilitate sane and consistent
177
+ * cleanup (ex: control terminal was closed).
178
+ *
179
+ * so why the overly descriptive lecture style comment?
180
+ * 1. SIGINT and SIGQUIT are among the signals with this behavior
181
+ * 2. a number of applications gank the above for their own use
182
+ * 3. ruby's insanely useful "guard" is one of these applications
183
+ * 4. despite having some level of understanding of POSIX signals and a few
184
+ * of the scenarios that might cause problems, i learned this one only
185
+ * after reading ruby 1.9's process.c
186
+ * 5. if left completely undocumented, even slightly obscure bugfixes
187
+ * may be removed as cruft by a future maintainer
188
+ *
189
+ * hindsight is 20/20 addition: if you're single-threaded and blocking on IO
190
+ * with a subprocess, then handlers for deferrable signals might not get run
191
+ * when you expect them to. In the case of Ruby 1.8, that means making use of
192
+ * IO::select, which will preserve correct signal handling behavior.
193
+ */
194
+ if (setpgid(0,0) < 0) {
195
+ fprintf(stderr, "Unable to set new process group.\n");
196
+ return 1;
197
+ }
198
+
199
+ parse_cli_settings(argc, argv);
200
+
201
+ FSEventStreamContext context = {0, NULL, NULL, NULL, NULL};
202
+ FSEventStreamRef stream;
203
+ stream = FSEventStreamCreate(kCFAllocatorDefault,
204
+ (FSEventStreamCallback)&callback,
205
+ &context,
206
+ config.paths,
207
+ config.sinceWhen,
208
+ config.latency,
209
+ config.flags);
210
+
211
+ #ifdef DEBUG
212
+ FSEventStreamShow(stream);
213
+ fprintf(stderr, "\n");
214
+ fflush(stderr);
215
+ #endif
216
+
217
+ FSEventStreamScheduleWithRunLoop(stream,
218
+ CFRunLoopGetCurrent(),
219
+ kCFRunLoopDefaultMode);
220
+ FSEventStreamStart(stream);
221
+ CFRunLoopRun();
222
+ FSEventStreamFlushSync(stream);
223
+ FSEventStreamStop(stream);
224
+
225
+ return 0;
226
+ }
@@ -0,0 +1,105 @@
1
+ class FSEvent
2
+ class << self
3
+ class_eval <<-END
4
+ def root_path
5
+ "#{File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))}"
6
+ end
7
+ END
8
+ class_eval <<-END
9
+ def watcher_path
10
+ "#{File.join(FSEvent.root_path, 'bin', 'fsevent_watch')}"
11
+ end
12
+ END
13
+ end
14
+
15
+ attr_reader :paths, :callback
16
+
17
+ def watch(watch_paths, options=nil, &block)
18
+ @paths = watch_paths.kind_of?(Array) ? watch_paths : [watch_paths]
19
+ @callback = block
20
+
21
+ if options.kind_of?(Hash)
22
+ @options = parse_options(options)
23
+ elsif options.kind_of?(Array)
24
+ @options = options
25
+ else
26
+ @options = []
27
+ end
28
+ end
29
+
30
+ def run
31
+ @running = true
32
+ # please note the use of IO::select() here, as it is used specifically to
33
+ # preserve correct signal handling behavior in ruby 1.8.
34
+ while @running && IO::select([pipe], nil, nil, nil)
35
+ if line = pipe.readline
36
+ modified_dir_paths = line.split(":").select { |dir| dir != "\n" }
37
+ callback.call(modified_dir_paths)
38
+ end
39
+ end
40
+ rescue Interrupt, IOError
41
+ ensure
42
+ stop
43
+ end
44
+
45
+ def stop
46
+ if pipe
47
+ Process.kill("KILL", pipe.pid)
48
+ pipe.close
49
+ end
50
+ rescue IOError
51
+ ensure
52
+ @pipe = @running = nil
53
+ end
54
+
55
+ if RUBY_VERSION < '1.9'
56
+ def pipe
57
+ @pipe ||= IO.popen("#{self.class.watcher_path} #{options_string} #{shellescaped_paths}")
58
+ end
59
+
60
+ private
61
+
62
+ def options_string
63
+ @options.join(' ')
64
+ end
65
+
66
+ def shellescaped_paths
67
+ @paths.map {|path| shellescape(path)}.join(' ')
68
+ end
69
+
70
+ # for Ruby 1.8.6 support
71
+ def shellescape(str)
72
+ # An empty argument will be skipped, so return empty quotes.
73
+ return "''" if str.empty?
74
+
75
+ str = str.dup
76
+
77
+ # Process as a single byte sequence because not all shell
78
+ # implementations are multibyte aware.
79
+ str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1")
80
+
81
+ # A LF cannot be escaped with a backslash because a backslash + LF
82
+ # combo is regarded as line continuation and simply ignored.
83
+ str.gsub!(/\n/, "'\n'")
84
+
85
+ return str
86
+ end
87
+ else
88
+ def pipe
89
+ @pipe ||= IO.popen([self.class.watcher_path] + @options + @paths)
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def parse_options(options={})
96
+ opts = []
97
+ opts.concat(['--since-when', options[:since_when]]) if options[:since_when]
98
+ opts.concat(['--latency', options[:latency]]) if options[:latency]
99
+ opts.push('--no-defer') if options[:no_defer]
100
+ opts.push('--watch-root') if options[:watch_root]
101
+ # ruby 1.9's IO.popen(array-of-stuff) syntax requires all items to be strings
102
+ opts.map {|opt| "#{opt}"}
103
+ end
104
+
105
+ end
@@ -0,0 +1,3 @@
1
+ class FSEvent
2
+ VERSION = "0.4.3.1"
3
+ end
@@ -0,0 +1,2 @@
1
+ require 'rb-fsevent/fsevent'
2
+ require 'rb-fsevent/version'
@@ -0,0 +1,4 @@
1
+ --readme README.md
2
+ --markup markdown
3
+ --markup-provider maruku
4
+ --no-private
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Nathan Weizenbaum
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.
@@ -0,0 +1,66 @@
1
+ # rb-inotify
2
+
3
+ This is a simple wrapper over the [inotify](http://en.wikipedia.org/wiki/Inotify) Linux kernel subsystem
4
+ for monitoring changes to files and directories.
5
+ It uses the [FFI](http://wiki.github.com/ffi/ffi) gem to avoid having to compile a C extension.
6
+
7
+ [API documentation is available on rdoc.info](http://rdoc.info/projects/nex3/rb-inotify).
8
+
9
+ ## Basic Usage
10
+
11
+ The API is similar to the inotify C API, but with a more Rubyish feel.
12
+ First, create a notifier:
13
+
14
+ notifier = INotify::Notifier.new
15
+
16
+ Then, tell it to watch the paths you're interested in
17
+ for the events you care about:
18
+
19
+ notifier.watch("path/to/foo.txt", :modify) {puts "foo.txt was modified!"}
20
+ notifier.watch("path/to/bar", :moved_to, :create) do |event|
21
+ puts "#{event.name} is now in path/to/bar!"
22
+ end
23
+
24
+ Inotify can watch directories or individual files.
25
+ It can pay attention to all sorts of events;
26
+ for a full list, see [the inotify man page](http://www.tin.org/bin/man.cgi?section=7&topic=inotify).
27
+
28
+ Finally, you get at the events themselves:
29
+
30
+ notifier.run
31
+
32
+ This will loop infinitely, calling the appropriate callbacks when the files are changed.
33
+ If you don't want infinite looping,
34
+ you can also block until there are available events,
35
+ process them all at once,
36
+ and then continue on your merry way:
37
+
38
+ notifier.process
39
+
40
+ ## Advanced Usage
41
+
42
+ Sometimes it's necessary to have finer control over the underlying IO operations
43
+ than is provided by the simple callback API.
44
+ The trick to this is that the \{INotify::Notifier#to_io Notifier#to_io} method
45
+ returns a fully-functional IO object,
46
+ with a file descriptor and everything.
47
+ This means, for example, that it can be passed to `IO#select`:
48
+
49
+ # Wait 10 seconds for an event then give up
50
+ if IO.select([notifier.to_io], [], [], 10)
51
+ notifier.process
52
+ end
53
+
54
+ It can even be used with EventMachine:
55
+
56
+ require 'eventmachine'
57
+
58
+ EM.run do
59
+ EM.watch notifier.to_io do
60
+ notifier.process
61
+ end
62
+ end
63
+
64
+ Unfortunately, this currently doesn't work under JRuby.
65
+ JRuby currently doesn't use native file descriptors for the IO object,
66
+ so we can't use the notifier's file descriptor as a stand-in.
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "rb-inotify"
8
+ gem.summary = "A Ruby wrapper for Linux's inotify, using FFI"
9
+ gem.description = gem.summary
10
+ gem.email = "nex342@gmail.com"
11
+ gem.homepage = "http://github.com/nex3/rb-inotify"
12
+ gem.authors = ["Nathan Weizenbaum"]
13
+ gem.add_dependency "ffi", ">= 0.5.0"
14
+ gem.add_development_dependency "yard", ">= 0.4.0"
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
19
+ end
20
+
21
+ task(:permissions) {sh %{chmod -R a+r .}}
22
+ Rake::Task[:build].prerequisites.unshift('permissions')
23
+
24
+ module Jeweler::VersionHelper::PlaintextExtension
25
+ def write_with_inotify
26
+ write_without_inotify
27
+ filename = File.join(File.dirname(__FILE__), "lib/rb-inotify.rb")
28
+ text = File.read(filename)
29
+ File.open(filename, 'w') do |f|
30
+ f.write text.gsub(/^( VERSION = ).*/, '\1' + [major, minor, patch].inspect)
31
+ end
32
+ end
33
+ alias_method :write_without_inotify, :write
34
+ alias_method :write, :write_with_inotify
35
+ end
36
+
37
+ class Jeweler::Commands::Version::Base
38
+ def commit_version_with_inotify
39
+ return unless self.repo
40
+ self.repo.add(File.join(File.dirname(__FILE__), "lib/rb-inotify.rb"))
41
+ commit_version_without_inotify
42
+ end
43
+ alias_method :commit_version_without_inotify, :commit_version
44
+ alias_method :commit_version, :commit_version_with_inotify
45
+ end
46
+
47
+ begin
48
+ require 'yard'
49
+ YARD::Rake::YardocTask.new
50
+ rescue LoadError
51
+ task :yardoc do
52
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
53
+ end
54
+ end
@@ -0,0 +1,139 @@
1
+ module INotify
2
+ # An event caused by a change on the filesystem.
3
+ # Each {Watcher} can fire many events,
4
+ # which are passed to that watcher's callback.
5
+ class Event
6
+ # A list of other events that are related to this one.
7
+ # Currently, this is only used for files that are moved within the same directory:
8
+ # the `:moved_from` and the `:moved_to` events will be related.
9
+ #
10
+ # @return [Array<Event>]
11
+ attr_reader :related
12
+
13
+ # The name of the file that the event occurred on.
14
+ # This is only set for events that occur on files in directories;
15
+ # otherwise, it's `""`.
16
+ # Similarly, if the event is being fired for the directory itself
17
+ # the name will be `""`
18
+ #
19
+ # This pathname is relative to the enclosing directory.
20
+ # For the absolute pathname, use \{#absolute\_name}.
21
+ # Note that when the `:recursive` flag is passed to {Notifier#watch},
22
+ # events in nested subdirectories will still have a `#name` field
23
+ # relative to their immediately enclosing directory.
24
+ # For example, an event on the file `"foo/bar/baz"`
25
+ # will have name `"baz"`.
26
+ #
27
+ # @return [String]
28
+ attr_reader :name
29
+
30
+ # The {Notifier} that fired this event.
31
+ #
32
+ # @return [Notifier]
33
+ attr_reader :notifier
34
+
35
+ # An integer specifying that this event is related to some other event,
36
+ # which will have the same cookie.
37
+ #
38
+ # Currently, this is only used for files that are moved within the same directory.
39
+ # Both the `:moved_from` and the `:moved_to` events will have the same cookie.
40
+ #
41
+ # @private
42
+ # @return [Fixnum]
43
+ attr_reader :cookie
44
+
45
+ # The {Watcher#id id} of the {Watcher} that fired this event.
46
+ #
47
+ # @private
48
+ # @return [Fixnum]
49
+ attr_reader :watcher_id
50
+
51
+ # Returns the {Watcher} that fired this event.
52
+ #
53
+ # @return [Watcher]
54
+ def watcher
55
+ @watcher ||= @notifier.watchers[@watcher_id]
56
+ end
57
+
58
+ # The absolute path of the file that the event occurred on.
59
+ #
60
+ # This is actually only as absolute as the path passed to the {Watcher}
61
+ # that created this event.
62
+ # However, it is relative to the working directory,
63
+ # assuming that hasn't changed since the watcher started.
64
+ #
65
+ # @return [String]
66
+ def absolute_name
67
+ return watcher.path if name.empty?
68
+ return File.join(watcher.path, name)
69
+ end
70
+
71
+ # Returns the flags that describe this event.
72
+ # This is generally similar to the input to {Notifier#watch},
73
+ # except that it won't contain options flags nor `:all_events`,
74
+ # and it may contain one or more of the following flags:
75
+ #
76
+ # `:unmount`
77
+ # : The filesystem containing the watched file or directory was unmounted.
78
+ #
79
+ # `:ignored`
80
+ # : The \{#watcher watcher} was closed, or the watched file or directory was deleted.
81
+ #
82
+ # `:isdir`
83
+ # : The subject of this event is a directory.
84
+ #
85
+ # @return [Array<Symbol>]
86
+ def flags
87
+ @flags ||= Native::Flags.from_mask(@native[:mask])
88
+ end
89
+
90
+ # Constructs an {Event} object from a string of binary data,
91
+ # and destructively modifies the string to get rid of the initial segment
92
+ # used to construct the Event.
93
+ #
94
+ # @private
95
+ # @param data [String] The string to be modified
96
+ # @param notifier [Notifier] The {Notifier} that fired the event
97
+ # @return [Event, nil] The event, or `nil` if the string is empty
98
+ def self.consume(data, notifier)
99
+ return nil if data.empty?
100
+ ev = new(data, notifier)
101
+ data.replace data[ev.size..-1]
102
+ ev
103
+ end
104
+
105
+ # Creates an event from a string of binary data.
106
+ # Differs from {Event.consume} in that it doesn't modify the string.
107
+ #
108
+ # @private
109
+ # @param data [String] The data string
110
+ # @param notifier [Notifier] The {Notifier} that fired the event
111
+ def initialize(data, notifier)
112
+ ptr = FFI::MemoryPointer.from_string(data)
113
+ @native = Native::Event.new(ptr)
114
+ @related = []
115
+ @cookie = @native[:cookie]
116
+ @name = data[@native.size, @native[:len]].gsub(/\0+$/, '')
117
+ @notifier = notifier
118
+ @watcher_id = @native[:wd]
119
+
120
+ raise Exception.new("inotify event queue has overflowed.") if @native[:mask] & Native::Flags::IN_Q_OVERFLOW != 0
121
+ end
122
+
123
+ # Calls the callback of the watcher that fired this event,
124
+ # passing in the event itself.
125
+ #
126
+ # @private
127
+ def callback!
128
+ watcher.callback!(self)
129
+ end
130
+
131
+ # Returns the size of this event object in bytes,
132
+ # including the \{#name} string.
133
+ #
134
+ # @return [Fixnum]
135
+ def size
136
+ @native.size + @native[:len]
137
+ end
138
+ end
139
+ end