filewatch 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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