filewatch 0.1.2 → 0.2.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/bin/gtail ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+ require "rubygems"
3
+ require "filewatch/tailglob"
4
+ require "filewatch/buftok"
5
+ require "optparse"
6
+
7
+ def main(args)
8
+ with_filenames = true
9
+ exclude_patterns = []
10
+
11
+ opts = OptionParser.new do |opts|
12
+ opts.banner = "Usage: #{$0} [options] <path_or_glob> [path_or_glob2] [...]"
13
+
14
+ opts.on("-n", "--no-filename",
15
+ "Supress prefixing of output with file names") do |x|
16
+ with_filenames = false
17
+ end # -n
18
+
19
+ opts.on("-x EXCLUDE", "--exclude EXCLUDE",
20
+ "A pattern to ignore. Wildcard/globs accepted." \
21
+ " Can be specified multiple times") do |pattern|
22
+ exclude_patterns << pattern
23
+ end
24
+ end # OptionParser
25
+
26
+ opts.parse!(args)
27
+
28
+ if args.length == 0
29
+ puts opts.banner
30
+ return 1
31
+ end
32
+
33
+ tail = FileWatch::TailGlob.new
34
+ ARGV.each do |path|
35
+ tail.tail(path, :exclude => exclude_patterns)
36
+ end
37
+
38
+ buffer = BufferedTokenizer.new
39
+ tail.subscribe do |path, data|
40
+ buffer.extract(data).each do |line|
41
+ if with_filenames
42
+ puts "#{path}: #{line}"
43
+ else
44
+ puts line
45
+ end
46
+ end # buffer.extract
47
+ end # tail.subscribe
48
+ end # def main
49
+
50
+ exit(main(ARGV))
@@ -0,0 +1,139 @@
1
+ # BufferedTokenizer - Statefully split input data by a specifiable token
2
+ #
3
+ # Authors:: Tony Arcieri, Martin Emde
4
+ #
5
+ #----------------------------------------------------------------------------
6
+ #
7
+ # Copyright (C) 2006-07 by Tony Arcieri and Martin Emde
8
+ #
9
+ # Distributed under the Ruby license (http://www.ruby-lang.org/en/LICENSE.txt)
10
+ #
11
+ #---------------------------------------------------------------------------
12
+ #
13
+
14
+ # (C)2006 Tony Arcieri, Martin Emde
15
+ # Distributed under the Ruby license (http://www.ruby-lang.org/en/LICENSE.txt)
16
+
17
+ # BufferedTokenizer takes a delimiter upon instantiation, or acts line-based
18
+ # by default. It allows input to be spoon-fed from some outside source which
19
+ # receives arbitrary length datagrams which may-or-may-not contain the token
20
+ # by which entities are delimited.
21
+ #
22
+ # Commonly used to parse lines out of incoming data:
23
+ #
24
+ # module LineBufferedConnection
25
+ # def receive_data(data)
26
+ # (@buffer ||= BufferedTokenizer.new).extract(data).each do |line|
27
+ # receive_line(line)
28
+ # end
29
+ # end
30
+ # end
31
+
32
+ class BufferedTokenizer
33
+ # New BufferedTokenizers will operate on lines delimited by "\n" by default
34
+ # or allow you to specify any delimiter token you so choose, which will then
35
+ # be used by String#split to tokenize the input data
36
+ def initialize(delimiter = "\n", size_limit = nil)
37
+ # Store the specified delimiter
38
+ @delimiter = delimiter
39
+
40
+ # Store the specified size limitation
41
+ @size_limit = size_limit
42
+
43
+ # The input buffer is stored as an array. This is by far the most efficient
44
+ # approach given language constraints (in C a linked list would be a more
45
+ # appropriate data structure). Segments of input data are stored in a list
46
+ # which is only joined when a token is reached, substantially reducing the
47
+ # number of objects required for the operation.
48
+ @input = []
49
+
50
+ # Size of the input buffer
51
+ @input_size = 0
52
+ end
53
+
54
+ # Extract takes an arbitrary string of input data and returns an array of
55
+ # tokenized entities, provided there were any available to extract. This
56
+ # makes for easy processing of datagrams using a pattern like:
57
+ #
58
+ # tokenizer.extract(data).map { |entity| Decode(entity) }.each do ...
59
+ def extract(data)
60
+ # Extract token-delimited entities from the input string with the split command.
61
+ # There's a bit of craftiness here with the -1 parameter. Normally split would
62
+ # behave no differently regardless of if the token lies at the very end of the
63
+ # input buffer or not (i.e. a literal edge case) Specifying -1 forces split to
64
+ # return "" in this case, meaning that the last entry in the list represents a
65
+ # new segment of data where the token has not been encountered
66
+ entities = data.split @delimiter, -1
67
+
68
+ # Check to see if the buffer has exceeded capacity, if we're imposing a limit
69
+ if @size_limit
70
+ raise 'input buffer full' if @input_size + entities.first.size > @size_limit
71
+ @input_size += entities.first.size
72
+ end
73
+
74
+ # Move the first entry in the resulting array into the input buffer. It represents
75
+ # the last segment of a token-delimited entity unless it's the only entry in the list.
76
+ @input << entities.shift
77
+
78
+ # If the resulting array from the split is empty, the token was not encountered
79
+ # (not even at the end of the buffer). Since we've encountered no token-delimited
80
+ # entities this go-around, return an empty array.
81
+ return [] if entities.empty?
82
+
83
+ # At this point, we've hit a token, or potentially multiple tokens. Now we can bring
84
+ # together all the data we've buffered from earlier calls without hitting a token,
85
+ # and add it to our list of discovered entities.
86
+ entities.unshift @input.join
87
+
88
+ =begin
89
+ # Note added by FC, 10Jul07. This paragraph contains a regression. It breaks
90
+ # empty tokens. Think of the empty line that delimits an HTTP header. It will have
91
+ # two "\n" delimiters in a row, and this code mishandles the resulting empty token.
92
+ # It someone figures out how to fix the problem, we can re-enable this code branch.
93
+ # Multi-chara
94
+ cter token support.
95
+ # Split any tokens that were incomplete on the last iteration buf complete now.
96
+ entities.map! do |e|
97
+ e.split @delimiter, -1
98
+ end
99
+ # Flatten the resulting array. This has the side effect of removing the empty
100
+ # entry at the end that was produced by passing -1 to split. Add it again if
101
+ # necessary.
102
+ if (entities[-1] == [])
103
+ entities.flatten! << []
104
+ else
105
+ entities.flatten!
106
+ end
107
+ =end
108
+
109
+ # Now that we've hit a token, joined the input buffer and added it to the entities
110
+ # list, we can go ahead and clear the input buffer. All of the segments that were
111
+ # stored before the join can now be garbage collected.
112
+ @input.clear
113
+
114
+ # The last entity in the list is not token delimited, however, thanks to the -1
115
+ # passed to split. It represents the beginning of a new list of as-yet-untokenized
116
+ # data, so we add it to the start of the list.
117
+ @input << entities.pop
118
+
119
+ # Set the new input buffer size, provided we're keeping track
120
+ @input_size = @input.first.size if @size_limit
121
+
122
+ # Now we're left with the list of extracted token-delimited entities we wanted
123
+ # in the first place. Hooray!
124
+ entities
125
+ end
126
+
127
+ # Flush the contents of the input buffer, i.e. return the input buffer even though
128
+ # a token has not yet been encountered
129
+ def flush
130
+ buffer = @input.join
131
+ @input.clear
132
+ buffer
133
+ end
134
+
135
+ # Is the buffer empty?
136
+ def empty?
137
+ @input.empty?
138
+ end
139
+ end
@@ -0,0 +1,12 @@
1
+ require "filewatch/namespace"
2
+
3
+ class FileWatch::Exception < Exception
4
+ attr_accessor :fd
5
+ attr_accessor :path
6
+
7
+ def initialize(message, fd, path)
8
+ super(message)
9
+ @fd = fd
10
+ @path = path
11
+ end
12
+ end
@@ -12,6 +12,9 @@ class FileWatch::Inotify::Event < FFI::Struct
12
12
 
13
13
  attr_accessor :name
14
14
 
15
+ # Enum of :directory or :file
16
+ attr_accessor :type
17
+
15
18
  def initialize(pointer)
16
19
  if pointer.is_a?(String)
17
20
  pointer = FFI::MemoryPointer.from_string(pointer)
@@ -54,7 +57,11 @@ class FileWatch::Inotify::Event < FFI::Struct
54
57
 
55
58
  def from_stringpipeio(io)
56
59
  begin
57
- @name = io.read(self[:len], true)
60
+ if self[:len] > 0
61
+ @name = io.read(self[:len], true)
62
+ else
63
+ @name = nil
64
+ end
58
65
  rescue Errno::EINVAL => e
59
66
  $stderr.puts "Read was too small? Confused."
60
67
  raise e
@@ -73,7 +80,7 @@ class FileWatch::Inotify::Event < FFI::Struct
73
80
  end
74
81
 
75
82
  def to_s
76
- return "#{@name} (#{self.actions.join(", ")})"
83
+ return "#{@name} (#{self.actions.join(", ")}) [type=#{type}]"
77
84
  end
78
85
 
79
86
  def partial?
@@ -1,8 +1,10 @@
1
1
  require "rubygems"
2
2
  require "ffi"
3
+ require "fcntl"
3
4
  require "filewatch/inotify/event"
4
5
  require "filewatch/namespace"
5
6
  require "filewatch/stringpipeio"
7
+ require "filewatch/exception"
6
8
 
7
9
  class FileWatch::Inotify::FD
8
10
  include Enumerable
@@ -12,7 +14,7 @@ class FileWatch::Inotify::FD
12
14
  ffi_lib FFI::Library::LIBC
13
15
 
14
16
  attach_function :inotify_init, [], :int
15
- attach_function :inotify_init1, [:int], :int
17
+ attach_function :fcntl, [:int, :int, :long], :int
16
18
  attach_function :inotify_add_watch, [:int, :string, :uint32], :int
17
19
 
18
20
  # So we can read and poll inotify from jruby.
@@ -24,6 +26,9 @@ class FileWatch::Inotify::FD
24
26
 
25
27
  INOTIFY_CLOEXEC = 02000000
26
28
  INOTIFY_NONBLOCK = 04000
29
+
30
+ F_SETFL = 4
31
+ O_NONBLOCK = 04000
27
32
 
28
33
  WATCH_BITS = {
29
34
  :access => 1 << 0,
@@ -60,12 +65,19 @@ class FileWatch::Inotify::FD
60
65
  @watches = {}
61
66
  @buffer = FileWatch::StringPipeIO.new
62
67
 
63
- @fd = CInotify.inotify_init1(INOTIFY_NONBLOCK)
68
+ #@fd = CInotify.inotify_init1(INOTIFY_NONBLOCK)
69
+ @fd = CInotify.inotify_init()
64
70
 
65
71
  if java?
66
72
  @io = nil
73
+ @rc = CInotify.fcntl(@fd, F_SETFL, O_NONBLOCK)
74
+ if @rc == -1
75
+ raise FileWatch::Exception.new(
76
+ "fcntl(#{@fd}, F_SETFL, O_NONBLOCK) failed. #{$?}", @fd, nil)
77
+ end
67
78
  else
68
79
  @io = IO.for_fd(@fd)
80
+ @io.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK)
69
81
  end
70
82
  end
71
83
 
@@ -110,10 +122,10 @@ class FileWatch::Inotify::FD
110
122
  def watch(path, *what_to_watch)
111
123
  mask = what_to_watch.inject(0) { |m, val| m |= WATCH_BITS[val] }
112
124
  watch_descriptor = CInotify.inotify_add_watch(@fd, path, mask)
113
- #puts "watch #{path} => #{watch_descriptor}"
114
125
 
115
126
  if watch_descriptor == -1
116
- raise "inotify_add_watch(#{@fd}, #{path}, #{mask}) failed. #{$?}"
127
+ raise FileWatch::Exception.new(
128
+ "inotify_add_watch(#{@fd}, #{path}, #{mask}) failed. #{$?}", @fd, path)
117
129
  end
118
130
  @watches[watch_descriptor] = {
119
131
  :path => path,
@@ -167,11 +179,13 @@ class FileWatch::Inotify::FD
167
179
  if event.name == nil
168
180
  # Some events don't have the name at all, so add our own.
169
181
  event.name = watchpath
182
+ event.type = :file
170
183
  else
171
184
  # Event paths are relative to the watch, if a directory. Prefix to make
172
185
  # the full path.
173
186
  if watch[:is_directory]
174
187
  event.name = File.join(watchpath, event.name)
188
+ event.type = :directory
175
189
  end
176
190
  end
177
191
 
@@ -30,6 +30,7 @@ class FileWatch::Tail
30
30
  if @files.include?(path)
31
31
  file = @files[path]
32
32
  event.actions.each do |action|
33
+ # call method 'file_action_<action>' like 'file_action_modify'
33
34
  method = "file_action_#{action}".to_sym
34
35
  if respond_to?(method)
35
36
  send(method, file, event, &block)
@@ -0,0 +1,182 @@
1
+ require "filewatch/namespace"
2
+ require "filewatch/exception"
3
+ require "filewatch/watchglob"
4
+
5
+ class FileWatch::TailGlob
6
+
7
+ public
8
+ def initialize
9
+ @watch = FileWatch::WatchGlob.new
10
+
11
+ # hash of string path => File
12
+ @files = {}
13
+ @options = {}
14
+
15
+ # hash of string path => action to take on EOF
16
+ #@eof_actions = {}
17
+ end # def initialize
18
+
19
+ # Watch a path glob.
20
+ #
21
+ # Options is a hash of:
22
+ # :exclude => array of globs to ignore.
23
+ public
24
+ def tail(path, options)
25
+ what_to_watch = [ :create, :modify, :delete ]
26
+
27
+ # Save glob options
28
+ @options[path] = options
29
+
30
+ @watch.watch(path, *what_to_watch) do |path|
31
+ # Save per-path options
32
+ @options[path] = options
33
+
34
+ # for each file found by the glob, open it.
35
+ follow_file(path, :end)
36
+ end # @watch.watch
37
+ end # def watch
38
+
39
+ private
40
+ def follow_file(path, seek=:end)
41
+ # Don't follow things that aren't files.
42
+ if !File.file?(path)
43
+ puts "Skipping follow on #{path}, File.file? == false"
44
+ return
45
+ end
46
+
47
+ options = @options[path] || {}
48
+ if options.include?(:exclude)
49
+ options[:exclude].each do |exclusion|
50
+ if File.fnmatch?(exclusion, path)
51
+ puts "Skipping #{path}, matches #{exclusion}"
52
+ return
53
+ end
54
+ end
55
+ end
56
+
57
+ close(path) if @files.include?(path)
58
+ @files[path] = File.new(path, "r")
59
+
60
+ # TODO(sissel): Support 'since'-like support.
61
+ case seek
62
+ when :end; @files[path].sysseek(0, IO::SEEK_END)
63
+ when :beginning; # nothing
64
+ else
65
+ if seek.is_a?(Numeric)
66
+ # 'seek' could be a number that is an offset from
67
+ # the start of the file. We should seek to that point.
68
+ @files[path].sysseek(seek, IO::SEEK_SET)
69
+ end # seek.is_a?(Numeric)
70
+ end # case seek
71
+ end # def follow_file
72
+
73
+ public
74
+ def subscribe(handler=nil, &block)
75
+ # TODO(sissel): Add handler support.
76
+ @watch.subscribe do |event|
77
+ path = event.name
78
+
79
+ event.actions.each do |action|
80
+ method = "file_action_#{action}".to_sym
81
+ if respond_to?(method)
82
+ send(method, path, event, &block)
83
+ else
84
+ $stderr.puts "Unsupported method #{self.class.name}##{method}"
85
+ end
86
+ end
87
+ end # @watch.subscribe
88
+ end # def subscribe
89
+
90
+ protected
91
+ def file_action_modify(path, event, &block)
92
+ loop do
93
+ file = @files[path]
94
+ begin
95
+ data = file.sysread(4096)
96
+ yield event.name, data
97
+ rescue EOFError
98
+ check_for_truncation_or_deletion(path, event, &block)
99
+ #case @eof_actions[path]
100
+ #when :reopen
101
+ #puts "Reopening #{path} due to eof and new file"
102
+ #reopen(path)
103
+ #end
104
+
105
+ break
106
+ end
107
+ end
108
+ end
109
+
110
+ protected
111
+ def check_for_truncation_or_deletion(path, event, &block)
112
+ file = @files[path]
113
+ pos = file.sysseek(0, IO::SEEK_CUR)
114
+ #puts "EOF(#{path}), pos: #{pos}"
115
+
116
+ # Truncation is determined by comparing the current read position in the
117
+ # file against the size of the file. If the file shrank, than we should
118
+ # assume truncation and seek to the beginning.
119
+ begin
120
+ stat = file.stat
121
+ #p stat.size => pos
122
+ if stat.size < pos
123
+ # Truncated. Seek to beginning and read.
124
+ file.sysseek(0, IO::SEEK_SET)
125
+ file_action_modify(path, event, &block)
126
+ end
127
+ rescue Errno::ENOENT
128
+ # File was deleted or renamed. Stop following it.
129
+ close(path)
130
+ end
131
+ end # def follow_file
132
+
133
+ protected
134
+ def reopen(path)
135
+ close(path)
136
+ follow_file(path, :beginning)
137
+ end # def reopen
138
+
139
+ protected
140
+ def close(path)
141
+ @files[path].close rescue nil
142
+ @files.delete(path)
143
+ return nil
144
+ end
145
+
146
+ protected
147
+ def file_action_create(path, event, &block)
148
+ if following?(path)
149
+ # TODO(sissel): If we are watching this file already, schedule it to be
150
+ # opened the next time we hit EOF on the current file descriptor for the
151
+ # same file. Maybe? Or is reopening now fine?
152
+ #
153
+ reopen(path)
154
+
155
+ # Schedule a reopen at EOF
156
+ #@eof_actions[path] = :reopen
157
+ else
158
+ # If we are not yet watching this file, watch it.
159
+ follow_file(path, :beginning)
160
+
161
+ # Then read all of the data so far since this is a new file.
162
+ file_action_modify(path, event, &block)
163
+ end
164
+ end # def file_action_create
165
+
166
+ def file_action_delete(path, event, &block)
167
+ close(path)
168
+ # ignore
169
+ end
170
+
171
+ def file_action_delete_self(path, event, &block)
172
+ close(path)
173
+ # ignore
174
+ #p :delete_self => path
175
+ end
176
+
177
+ # Returns true if we are currently following the file at the given path.
178
+ public
179
+ def following?(path)
180
+ return @files.include?(path)
181
+ end # def following
182
+ end # class FileWatch::Tail
@@ -1,5 +1,6 @@
1
- require "filewatch/inotify/fd"
2
1
  require "filewatch/namespace"
2
+ require "filewatch/inotify/fd"
3
+ require "filewatch/exception"
3
4
 
4
5
  class FileWatch::Watch
5
6
  # This class exists to wrap inotify, kqueue, periodic polling, etc,
@@ -0,0 +1,79 @@
1
+ require "filewatch/namespace"
2
+ require "filewatch/exception"
3
+ require "filewatch/watch"
4
+
5
+ class FileWatch::WatchGlob
6
+ # This class exists to wrap inotify, kqueue, periodic polling, etc,
7
+ # to provide you with a way to watch files and directories.
8
+ #
9
+ # For now, it only supports inotify.
10
+ def initialize
11
+ @watch = FileWatch::Watch.new
12
+ @globdirs = []
13
+ @globs = []
14
+ end
15
+
16
+ public
17
+ def watch(glob, *what_to_watch, &block)
18
+ @globs << [glob, what_to_watch]
19
+
20
+ watching = []
21
+ errors = []
22
+ paths = Dir.glob(glob)
23
+ paths.each do |path|
24
+ begin
25
+ next if watching.include?(path)
26
+ puts "Watching #{path}"
27
+ @watch.watch(path, :create, :delete, :modify)
28
+ watching << path
29
+
30
+ # Yield each file found by glob to the block.
31
+ # This allows initialization on new files found at start.
32
+ yield path if block_given?
33
+ rescue FileWatch::Exception => e
34
+ $stderr.puts "Failed starting watch on #{path} - #{e}"
35
+ errors << e
36
+ end
37
+ end
38
+
39
+ # Go through the glob and look for paths leading into a '*'
40
+ splitpath = glob.split(File::SEPARATOR)
41
+ splitpath.each_with_index do |part, i|
42
+ current = File.join(splitpath[0 .. i])
43
+ current = "/" if current.empty?
44
+ next if watching.include?(current)
45
+ # TODO(sissel): Do better glob detection
46
+ if part.include?("*")
47
+ globprefix = File.join(splitpath[0 ... i])
48
+ Dir.glob(globprefix).each do |path|
49
+ next if watching.include?(path)
50
+ p "Watching dir #{path}"
51
+ @watch.watch(path, :create)
52
+ @globdirs << path
53
+ end
54
+ end
55
+ end
56
+ end # def watch
57
+
58
+ # TODO(sissel): implement 'unwatch' or cancel?
59
+
60
+ def subscribe(handler=nil, &block)
61
+ @watch.subscribe do |event|
62
+ # If this event is a directory event and the file matches a watched glob,
63
+ # then it should be a new-file creation event. Watch the file.
64
+ if event.type == :directory and @globdirs.include?(File.dirname(event.name))
65
+ glob, what = @globs.find { |glob, what| File.fnmatch?(glob, event.name) }
66
+ if glob
67
+ @watch.watch(event.name, *what)
68
+ end
69
+ end
70
+
71
+ # Push the event to our callback.
72
+ block.call event
73
+ end
74
+ end
75
+
76
+ def each(&block)
77
+ @inotify.each(&block)
78
+ end # def each
79
+ end # class FileWatch::Watch
@@ -0,0 +1,21 @@
1
+ import org.apache.log4j.Logger;
2
+ import org.apache.log4j.PropertyConfigurator;
3
+
4
+ public class LogTest {
5
+ private static Logger logger = Logger.getLogger(LogTest.class);
6
+
7
+ public static void main(String[] args) {
8
+ //BasicConfigurator.configure();
9
+ PropertyConfigurator.configure("log4j.properties");
10
+
11
+ int i = 0;
12
+ while (true) {
13
+ logger.info("Testing: " + i);
14
+ i++;
15
+ try {
16
+ Thread.sleep(100);
17
+ } catch (Exception e) {
18
+ }
19
+ }
20
+ } /* public static void main(String[]) */
21
+ } /* public class LogTest */
@@ -0,0 +1,20 @@
1
+ log4j.rootLogger=INFO,stdout,rolling
2
+ log4j.appender.stdout=org.apache.log4j.ConsoleAppender
3
+ log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
4
+ log4j.appender.stdout.layout.ConversionPattern=%5p %d{HH:mm:ss,SSS} %m%n
5
+
6
+ log4j.appender.rolling=org.apache.log4j.RollingFileAppender
7
+ log4j.appender.rolling.maxFileSize=1000
8
+ log4j.appender.rolling.maxBackupIndex=10
9
+ log4j.appender.rolling.layout=org.apache.log4j.PatternLayout
10
+ log4j.appender.rolling.layout.ConversionPattern=%5p [%t] %d{ISO8601} %F (line %L) %m%n
11
+ log4j.appender.rolling.File=logs/logtest.log
12
+ log4j.appender.rolling.DatePattern=.yyyy-MM-dd.HH-mm-ss
13
+
14
+ #log4j.appender.D=org.apache.log4j.DailyRollingFileAppender
15
+ #log4j.appender.D.layout=org.apache.log4j.PatternLayout
16
+ #log4j.appender.D.layout=org.apache.log4j.JSONLayout
17
+ #log4j.appender.D.layout.ConversionPattern=%5p [%t] %d{ISO8601} %F (line %L) %m%n
18
+ #log4j.appender.D.File=logs/logtest.log
19
+ #log4j.appender.D.DatePattern=.yyyy-MM-dd.HH-mm-ss
20
+
metadata CHANGED
@@ -1,16 +1,21 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: filewatch
3
3
  version: !ruby/object:Gem::Version
4
+ hash: 23
4
5
  prerelease:
5
- version: 0.1.2
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
6
11
  platform: ruby
7
12
  authors:
8
- - Jordan Sissel
13
+ - Jordan Sissel
9
14
  autorequire:
10
15
  bindir: bin
11
16
  cert_chain: []
12
17
 
13
- date: 2011-03-19 00:00:00 -07:00
18
+ date: 2011-04-12 00:00:00 -07:00
14
19
  default_executable:
15
20
  dependencies: []
16
21
 
@@ -23,13 +28,20 @@ extensions: []
23
28
  extra_rdoc_files: []
24
29
 
25
30
  files:
26
- - lib/filewatch/watch.rb
27
- - lib/filewatch/namespace.rb
28
- - lib/filewatch/tail.rb
29
- - lib/filewatch/stringpipeio.rb
30
- - lib/filewatch/inotify/emhandler.rb
31
- - lib/filewatch/inotify/fd.rb
32
- - lib/filewatch/inotify/event.rb
31
+ - lib/filewatch/buftok.rb
32
+ - lib/filewatch/exception.rb
33
+ - lib/filewatch/tail.rb
34
+ - lib/filewatch/stringpipeio.rb
35
+ - lib/filewatch/inotify/emhandler.rb
36
+ - lib/filewatch/inotify/fd.rb
37
+ - lib/filewatch/inotify/event.rb
38
+ - lib/filewatch/watchglob.rb
39
+ - lib/filewatch/watch.rb
40
+ - lib/filewatch/namespace.rb
41
+ - lib/filewatch/tailglob.rb
42
+ - test/log4j/log4j.properties
43
+ - test/log4j/LogTest.java
44
+ - bin/gtail
33
45
  has_rdoc: true
34
46
  homepage: https://github.com/jordansissel/ruby-filewatch
35
47
  licenses: []
@@ -38,24 +50,30 @@ post_install_message:
38
50
  rdoc_options: []
39
51
 
40
52
  require_paths:
41
- - lib
42
- - lib
53
+ - lib
54
+ - lib
43
55
  required_ruby_version: !ruby/object:Gem::Requirement
44
56
  none: false
45
57
  requirements:
46
- - - ">="
47
- - !ruby/object:Gem::Version
48
- version: "0"
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 3
61
+ segments:
62
+ - 0
63
+ version: "0"
49
64
  required_rubygems_version: !ruby/object:Gem::Requirement
50
65
  none: false
51
66
  requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: "0"
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ hash: 3
70
+ segments:
71
+ - 0
72
+ version: "0"
55
73
  requirements: []
56
74
 
57
75
  rubyforge_project:
58
- rubygems_version: 1.5.1
76
+ rubygems_version: 1.6.2
59
77
  signing_key:
60
78
  specification_version: 3
61
79
  summary: filewatch - file watching for ruby