eventmachine-tail 0.0.1 → 0.6.1
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/emtail +83 -0
- data/bin/rtail +83 -0
- data/lib/em/filetail.rb +372 -87
- data/lib/em/globwatcher.rb +244 -51
- data/lib/eventmachine-tail.rb +2 -0
- data/samples/glob-tail.rb +47 -0
- data/samples/globwatch.rb +23 -0
- data/samples/tail-with-block.rb +30 -0
- data/samples/tail.rb +38 -0
- data/test/alltests.rb +5 -0
- data/test/logrotate.conf +4 -0
- data/test/test_filetail.rb +232 -0
- data/test/test_glob.rb +146 -0
- data/test/testcase_helpers.rb +13 -0
- metadata +43 -14
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'rubygems'
|
|
4
|
+
$:.unshift "#{File.dirname(__FILE__)}/../lib"
|
|
5
|
+
require 'eventmachine-tail'
|
|
6
|
+
require 'tempfile'
|
|
7
|
+
require 'test/unit'
|
|
8
|
+
require 'timeout'
|
|
9
|
+
require 'testcase_helpers.rb'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Generate some data
|
|
13
|
+
DATA = (1..10).collect { |i| rand.to_s }
|
|
14
|
+
SLEEPMAX = 1
|
|
15
|
+
|
|
16
|
+
class Reader < EventMachine::FileTail
|
|
17
|
+
def initialize(path, startpos=-1, testobj=nil)
|
|
18
|
+
super(path, startpos)
|
|
19
|
+
@data = DATA.clone
|
|
20
|
+
@buffer = BufferedTokenizer.new
|
|
21
|
+
@testobj = testobj
|
|
22
|
+
@lineno = 0
|
|
23
|
+
end # def initialize
|
|
24
|
+
|
|
25
|
+
def receive_data(data)
|
|
26
|
+
@buffer.extract(data).each do |line|
|
|
27
|
+
@lineno += 1
|
|
28
|
+
expected = @data.shift
|
|
29
|
+
@testobj.assert_equal(expected, line,
|
|
30
|
+
"Expected '#{expected}' on line #{@lineno}, but got '#{line}'")
|
|
31
|
+
end # @buffer.extract
|
|
32
|
+
end # def receive_data
|
|
33
|
+
|
|
34
|
+
# This effectively tests EOF handling by requiring it to work in order
|
|
35
|
+
# for the tests to pass.
|
|
36
|
+
def eof
|
|
37
|
+
if @data.length == 0
|
|
38
|
+
close
|
|
39
|
+
@testobj.finish
|
|
40
|
+
end
|
|
41
|
+
end # def eof
|
|
42
|
+
end # class Reader
|
|
43
|
+
|
|
44
|
+
class TestFileTail < Test::Unit::TestCase
|
|
45
|
+
include EventMachineTailTestHelpers
|
|
46
|
+
|
|
47
|
+
# This test should run slow. We are trying to ensure that
|
|
48
|
+
# our file_tail correctly reads data slowly fed into the file
|
|
49
|
+
# as 'tail -f' would.
|
|
50
|
+
def test_filetail
|
|
51
|
+
tmp = Tempfile.new("testfiletail")
|
|
52
|
+
data = DATA.clone
|
|
53
|
+
EM.run do
|
|
54
|
+
abort_after_timeout(DATA.length * SLEEPMAX + 10)
|
|
55
|
+
|
|
56
|
+
EM::file_tail(tmp.path, Reader, -1, self)
|
|
57
|
+
timer = EM::PeriodicTimer.new(0.2) do
|
|
58
|
+
tmp.puts data.shift
|
|
59
|
+
tmp.flush
|
|
60
|
+
sleep(rand * SLEEPMAX)
|
|
61
|
+
timer.cancel if data.length == 0
|
|
62
|
+
end
|
|
63
|
+
end # EM.run
|
|
64
|
+
end # def test_filetail
|
|
65
|
+
|
|
66
|
+
def test_filetail_with_seek
|
|
67
|
+
tmp = Tempfile.new("testfiletail")
|
|
68
|
+
data = DATA.clone
|
|
69
|
+
data.each { |i| tmp.puts i }
|
|
70
|
+
tmp.flush
|
|
71
|
+
EM.run do
|
|
72
|
+
abort_after_timeout(2)
|
|
73
|
+
|
|
74
|
+
# Set startpos of 0 (beginning of file)
|
|
75
|
+
EM::file_tail(tmp.path, Reader, 0, self)
|
|
76
|
+
end # EM.run
|
|
77
|
+
end # def test_filetail
|
|
78
|
+
|
|
79
|
+
def test_filetail_with_block
|
|
80
|
+
tmp = Tempfile.new("testfiletail")
|
|
81
|
+
data = DATA.clone
|
|
82
|
+
EM.run do
|
|
83
|
+
abort_after_timeout(DATA.length * SLEEPMAX + 10)
|
|
84
|
+
|
|
85
|
+
lineno = 0
|
|
86
|
+
EM::file_tail(tmp.path) do |filetail, line|
|
|
87
|
+
lineno += 1
|
|
88
|
+
expected = data.shift
|
|
89
|
+
assert_equal(expected, line,
|
|
90
|
+
"Expected '#{expected}' on line #{@lineno}, but got '#{line}'")
|
|
91
|
+
finish if data.length == 0
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
data_copy = data.clone
|
|
95
|
+
timer = EM::PeriodicTimer.new(0.2) do
|
|
96
|
+
tmp.puts data_copy.shift
|
|
97
|
+
tmp.flush
|
|
98
|
+
sleep(rand * SLEEPMAX)
|
|
99
|
+
timer.cancel if data_copy.length == 0
|
|
100
|
+
end
|
|
101
|
+
end # EM.run
|
|
102
|
+
end # def test_filetail_with_block
|
|
103
|
+
|
|
104
|
+
def test_filetail_tracks_renames
|
|
105
|
+
tmp = Tempfile.new("testfiletail")
|
|
106
|
+
data = DATA.clone
|
|
107
|
+
filename = tmp.path
|
|
108
|
+
|
|
109
|
+
data_copy = data.clone
|
|
110
|
+
|
|
111
|
+
# Write first so the first read happens immediately
|
|
112
|
+
tmp.puts data_copy.shift
|
|
113
|
+
tmp.flush
|
|
114
|
+
EM.run do
|
|
115
|
+
abort_after_timeout(DATA.length * SLEEPMAX + 10)
|
|
116
|
+
|
|
117
|
+
lineno = 0
|
|
118
|
+
# Start at file position 0.
|
|
119
|
+
EM::file_tail(tmp.path, nil, 0) do |filetail, line|
|
|
120
|
+
lineno += 1
|
|
121
|
+
expected = data.shift
|
|
122
|
+
#puts "Got #{lineno}: #{line}"
|
|
123
|
+
assert_equal(expected, line,
|
|
124
|
+
"Expected '#{expected}' on line #{lineno}, but got '#{line}'")
|
|
125
|
+
finish if data.length == 0
|
|
126
|
+
|
|
127
|
+
# Start a timer on the first read.
|
|
128
|
+
# This is to ensure we have the file tailing before
|
|
129
|
+
# we try to rename.
|
|
130
|
+
if lineno == 1
|
|
131
|
+
timer = EM::PeriodicTimer.new(0.2) do
|
|
132
|
+
value = data_copy.shift
|
|
133
|
+
tmp.puts value
|
|
134
|
+
tmp.flush
|
|
135
|
+
sleep(rand * SLEEPMAX)
|
|
136
|
+
|
|
137
|
+
# Rename the file, create a new one in it's place.
|
|
138
|
+
# This is to simulate log rotation, etc.
|
|
139
|
+
path_newname = "#{filename}_#{value}"
|
|
140
|
+
File.rename(filename, path_newname)
|
|
141
|
+
File.delete(path_newname)
|
|
142
|
+
tmp = File.open(filename, "w")
|
|
143
|
+
timer.cancel if data_copy.length == 0
|
|
144
|
+
end # timer
|
|
145
|
+
end # if lineno == 1
|
|
146
|
+
end # EM::filetail(...)
|
|
147
|
+
end # EM.run
|
|
148
|
+
|
|
149
|
+
File.delete(filename)
|
|
150
|
+
end # def test_filetail_tracks_renames
|
|
151
|
+
|
|
152
|
+
def test_filetail_tracks_symlink_changes
|
|
153
|
+
to_delete = []
|
|
154
|
+
link = Tempfile.new("testlink")
|
|
155
|
+
File.delete(link.path)
|
|
156
|
+
to_delete << link
|
|
157
|
+
tmp = Tempfile.new("testfiletail")
|
|
158
|
+
to_delete << tmp
|
|
159
|
+
data = DATA.clone
|
|
160
|
+
File.symlink(tmp.path, link.path)
|
|
161
|
+
|
|
162
|
+
data_copy = data.clone
|
|
163
|
+
|
|
164
|
+
# Write first so the first read happens immediately
|
|
165
|
+
tmp.puts data_copy.shift
|
|
166
|
+
tmp.flush
|
|
167
|
+
EM.run do
|
|
168
|
+
abort_after_timeout(DATA.length * SLEEPMAX + 10)
|
|
169
|
+
|
|
170
|
+
lineno = 0
|
|
171
|
+
# Start at file position 0.
|
|
172
|
+
EM::file_tail(link.path, nil, 0) do |filetail, line|
|
|
173
|
+
# This needs to be less than the interval at which we are changing symlinks.
|
|
174
|
+
filetail.symlink_check_interval = 0.1
|
|
175
|
+
|
|
176
|
+
lineno += 1
|
|
177
|
+
expected = data.shift
|
|
178
|
+
puts "Got #{lineno}: #{line}" if $debug
|
|
179
|
+
assert_equal(expected, line,
|
|
180
|
+
"Expected '#{expected}' on line #{lineno}, but got '#{line}'")
|
|
181
|
+
finish if data.length == 0
|
|
182
|
+
|
|
183
|
+
# Start a timer on the first read.
|
|
184
|
+
# This is to ensure we have the file tailing before
|
|
185
|
+
# we try to rename.
|
|
186
|
+
if lineno == 1
|
|
187
|
+
timer = EM::PeriodicTimer.new(0.2) do
|
|
188
|
+
value = data_copy.shift
|
|
189
|
+
tmp.puts value
|
|
190
|
+
tmp.flush
|
|
191
|
+
sleep(rand * SLEEPMAX)
|
|
192
|
+
|
|
193
|
+
# Make a new file and update the symlink to point to it.
|
|
194
|
+
# This is to simulate log rotation, etc.
|
|
195
|
+
tmp.close
|
|
196
|
+
tmp = Tempfile.new("testfiletail")
|
|
197
|
+
to_delete << tmp
|
|
198
|
+
File.delete(link.path)
|
|
199
|
+
File.symlink(tmp.path, link.path)
|
|
200
|
+
puts "#{tmp.path} => #{link.path}" if $debug
|
|
201
|
+
timer.cancel if data_copy.length == 0
|
|
202
|
+
end # timer
|
|
203
|
+
end # if lineno == 1
|
|
204
|
+
end # EM::filetail(...)
|
|
205
|
+
end # EM.run
|
|
206
|
+
|
|
207
|
+
to_delete.each do |f|
|
|
208
|
+
File.delete(f.path)
|
|
209
|
+
end
|
|
210
|
+
end # def test_filetail_tracks_renames
|
|
211
|
+
|
|
212
|
+
def test_encoding
|
|
213
|
+
return if RUBY_VERSION < '1.9.0'
|
|
214
|
+
tmp = Tempfile.new("testfiletail")
|
|
215
|
+
data = DATA.clone
|
|
216
|
+
EM.run do
|
|
217
|
+
abort_after_timeout(1)
|
|
218
|
+
|
|
219
|
+
EM::file_tail(tmp.path) do |filetail, line|
|
|
220
|
+
assert_equal(Encoding.default_external, line.encoding,
|
|
221
|
+
"Expected the read data to have the encoding specified in Encoding.default_external (#{Encoding.default_external}, but was #{line.encoding})")
|
|
222
|
+
finish
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
EM.next_tick do
|
|
226
|
+
tmp.puts(data.shift)
|
|
227
|
+
tmp.flush
|
|
228
|
+
end
|
|
229
|
+
end # EM.run
|
|
230
|
+
end # def test_encoding
|
|
231
|
+
end # class TestFileTail
|
|
232
|
+
|
data/test/test_glob.rb
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'rubygems'
|
|
4
|
+
$:.unshift "#{File.dirname(__FILE__)}/../lib"
|
|
5
|
+
require 'eventmachine-tail'
|
|
6
|
+
require 'tempfile'
|
|
7
|
+
require 'test/unit'
|
|
8
|
+
require 'timeout'
|
|
9
|
+
require 'tmpdir'
|
|
10
|
+
|
|
11
|
+
require 'testcase_helpers'
|
|
12
|
+
|
|
13
|
+
class Watcher < EventMachine::FileGlobWatch
|
|
14
|
+
def initialize(path, interval, data, testobj)
|
|
15
|
+
super(path, interval)
|
|
16
|
+
@data = data
|
|
17
|
+
@testobj = testobj
|
|
18
|
+
end # def initialize
|
|
19
|
+
|
|
20
|
+
def file_found(path)
|
|
21
|
+
# Use .include? here because files aren't going to be found in any
|
|
22
|
+
# particular order.
|
|
23
|
+
@testobj.assert(@data.include?(path), "Expected #{path} in \n#{@data.join("\n")}")
|
|
24
|
+
@data.delete(path)
|
|
25
|
+
@testobj.finish if @data.length == 0
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def file_deleted(path)
|
|
29
|
+
@testobj.assert(@data.include?(path), "Expected #{path} in \n#{@data.join("\n")}")
|
|
30
|
+
@data.delete(path)
|
|
31
|
+
@testobj.finish if @data.length == 0
|
|
32
|
+
end
|
|
33
|
+
end # class Reader
|
|
34
|
+
|
|
35
|
+
class TestGlobWatcher < Test::Unit::TestCase
|
|
36
|
+
include EventMachineTailTestHelpers
|
|
37
|
+
SLEEPMAX = 1
|
|
38
|
+
|
|
39
|
+
def setup
|
|
40
|
+
@watchinterval = 0.2
|
|
41
|
+
@dir = Dir.mktmpdir
|
|
42
|
+
@data = []
|
|
43
|
+
@data << "#{@dir}/#{rand}"
|
|
44
|
+
@data << "#{@dir}/#{rand}"
|
|
45
|
+
@data << "#{@dir}/#{rand}"
|
|
46
|
+
@data << "#{@dir}/#{rand}"
|
|
47
|
+
@data << "#{@dir}/#{rand}"
|
|
48
|
+
@data << "#{@dir}/#{rand}"
|
|
49
|
+
@data << "#{@dir}/#{rand}"
|
|
50
|
+
@data << "#{@dir}/#{rand}.gz"
|
|
51
|
+
@data << "#{@dir}/#{rand}.gz"
|
|
52
|
+
@data << "#{@dir}/#{rand}.tar.gz"
|
|
53
|
+
end # def setup
|
|
54
|
+
|
|
55
|
+
def teardown
|
|
56
|
+
@data.each do |file|
|
|
57
|
+
#puts "Deleting #{file}"
|
|
58
|
+
File.delete(file) rescue nil
|
|
59
|
+
end
|
|
60
|
+
Dir.delete(@dir)
|
|
61
|
+
end # def teardown
|
|
62
|
+
|
|
63
|
+
def finish
|
|
64
|
+
EM.stop_event_loop
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_glob_finds_existing_files
|
|
68
|
+
EM.run do
|
|
69
|
+
abort_after_timeout(SLEEPMAX * @data.length + 10)
|
|
70
|
+
|
|
71
|
+
@data.each do |path|
|
|
72
|
+
File.new(path, "w").close
|
|
73
|
+
end
|
|
74
|
+
EM::watch_glob("#{@dir}/*", Watcher, @watchinterval, @data.clone, self)
|
|
75
|
+
end # EM.run
|
|
76
|
+
end # def test_glob_finds_existing_files
|
|
77
|
+
|
|
78
|
+
# This test should run slow. We are trying to ensure that
|
|
79
|
+
# our file_tail correctly reads data slowly fed into the file
|
|
80
|
+
# as 'tail -f' would.
|
|
81
|
+
def test_glob_finds_newly_created_files_at_runtime
|
|
82
|
+
EM.run do
|
|
83
|
+
abort_after_timeout(SLEEPMAX * @data.length + 10)
|
|
84
|
+
|
|
85
|
+
EM::watch_glob("#{@dir}/*", Watcher, @watchinterval, @data.clone, self)
|
|
86
|
+
datacopy = @data.clone
|
|
87
|
+
timer = EM::PeriodicTimer.new(0.2) do
|
|
88
|
+
#puts "Creating: #{datacopy.first}"
|
|
89
|
+
File.new(datacopy.shift, "w").close
|
|
90
|
+
sleep(rand * SLEEPMAX)
|
|
91
|
+
timer.cancel if datacopy.length == 0
|
|
92
|
+
end
|
|
93
|
+
end # EM.run
|
|
94
|
+
end # def test_glob_finds_newly_created_files_at_runtime
|
|
95
|
+
|
|
96
|
+
def test_glob_ignores_file_renames
|
|
97
|
+
EM.run do
|
|
98
|
+
abort_after_timeout(SLEEPMAX * @data.length + 10)
|
|
99
|
+
|
|
100
|
+
EM::watch_glob("#{@dir}/*", Watcher, @watchinterval, @data.clone, self)
|
|
101
|
+
|
|
102
|
+
datacopy = @data.clone
|
|
103
|
+
timer = EM::PeriodicTimer.new(0.2) do
|
|
104
|
+
filename = datacopy.shift
|
|
105
|
+
File.new(filename, "w").close
|
|
106
|
+
sleep(rand * SLEEPMAX)
|
|
107
|
+
|
|
108
|
+
# This file rename should be ignored.
|
|
109
|
+
EM::Timer.new(2) do
|
|
110
|
+
newname = "#{filename}.renamed"
|
|
111
|
+
File.rename(filename, newname)
|
|
112
|
+
|
|
113
|
+
# Track the new filename so teardown removes it.
|
|
114
|
+
@data << newname
|
|
115
|
+
end
|
|
116
|
+
timer.cancel if datacopy.length == 0
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end # def test_glob_ignores_file_renames
|
|
120
|
+
|
|
121
|
+
def test_glob_ignores_duplicate_hardlinks
|
|
122
|
+
EM.run do
|
|
123
|
+
abort_after_timeout(SLEEPMAX * @data.length + 10)
|
|
124
|
+
|
|
125
|
+
EM::watch_glob("#{@dir}/*", Watcher, @watchinterval, @data.clone, self)
|
|
126
|
+
|
|
127
|
+
datacopy = @data.clone
|
|
128
|
+
timer = EM::PeriodicTimer.new(0.2) do
|
|
129
|
+
filename = datacopy.shift
|
|
130
|
+
File.new(filename, "w").close
|
|
131
|
+
sleep(rand * SLEEPMAX)
|
|
132
|
+
|
|
133
|
+
# This file rename should be ignored.
|
|
134
|
+
EM::Timer.new(2) do
|
|
135
|
+
newname = "#{filename}.renamed"
|
|
136
|
+
File.link(filename, newname)
|
|
137
|
+
|
|
138
|
+
# Track the new filename so teardown removes it.
|
|
139
|
+
@data << newname
|
|
140
|
+
end
|
|
141
|
+
timer.cancel if datacopy.length == 0
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end # def test_glob_ignores_file_renames
|
|
145
|
+
end # class TestGlobWatcher
|
|
146
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
|
|
2
|
+
module EventMachineTailTestHelpers
|
|
3
|
+
def abort_after_timeout(seconds)
|
|
4
|
+
EM::Timer.new(seconds) do
|
|
5
|
+
EM.stop_event_loop
|
|
6
|
+
flunk("Timeout (#{seconds} seconds) while running tests. Failing.")
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def finish
|
|
11
|
+
EventMachine.stop_event_loop
|
|
12
|
+
end
|
|
13
|
+
end # module EventMachineTailTestHelpers
|
metadata
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: eventmachine-tail
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
|
|
4
|
+
hash: 5
|
|
5
|
+
prerelease:
|
|
6
|
+
segments:
|
|
7
|
+
- 0
|
|
8
|
+
- 6
|
|
9
|
+
- 1
|
|
10
|
+
version: 0.6.1
|
|
5
11
|
platform: ruby
|
|
6
12
|
authors:
|
|
7
13
|
- Jordan Sissel
|
|
@@ -9,32 +15,49 @@ autorequire:
|
|
|
9
15
|
bindir: bin
|
|
10
16
|
cert_chain: []
|
|
11
17
|
|
|
12
|
-
date:
|
|
18
|
+
date: 2011-02-22 00:00:00 -08:00
|
|
13
19
|
default_executable:
|
|
14
20
|
dependencies:
|
|
15
21
|
- !ruby/object:Gem::Dependency
|
|
16
22
|
name: eventmachine
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
prerelease: false
|
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
25
|
+
none: false
|
|
20
26
|
requirements:
|
|
21
27
|
- - ">="
|
|
22
28
|
- !ruby/object:Gem::Version
|
|
29
|
+
hash: 3
|
|
30
|
+
segments:
|
|
31
|
+
- 0
|
|
23
32
|
version: "0"
|
|
24
|
-
|
|
25
|
-
|
|
33
|
+
type: :runtime
|
|
34
|
+
version_requirements: *id001
|
|
35
|
+
description: Add file 'tail' implemented with EventMachine. Also includes a 'glob watch' class for watching a directory pattern for new matches, like /var/log/*.log
|
|
26
36
|
email: jls@semicomplete.com
|
|
27
|
-
executables:
|
|
28
|
-
|
|
37
|
+
executables:
|
|
38
|
+
- rtail
|
|
39
|
+
- emtail
|
|
29
40
|
extensions: []
|
|
30
41
|
|
|
31
42
|
extra_rdoc_files: []
|
|
32
43
|
|
|
33
44
|
files:
|
|
45
|
+
- lib/eventmachine-tail.rb
|
|
34
46
|
- lib/em/filetail.rb
|
|
35
47
|
- lib/em/globwatcher.rb
|
|
48
|
+
- samples/tail.rb
|
|
49
|
+
- samples/glob-tail.rb
|
|
50
|
+
- samples/globwatch.rb
|
|
51
|
+
- samples/tail-with-block.rb
|
|
52
|
+
- test/test_filetail.rb
|
|
53
|
+
- test/logrotate.conf
|
|
54
|
+
- test/test_glob.rb
|
|
55
|
+
- test/alltests.rb
|
|
56
|
+
- test/testcase_helpers.rb
|
|
57
|
+
- bin/rtail
|
|
58
|
+
- bin/emtail
|
|
36
59
|
has_rdoc: true
|
|
37
|
-
homepage: http://code.google.com/p/semicomplete/wiki/
|
|
60
|
+
homepage: http://code.google.com/p/semicomplete/wiki/EventMachineTail
|
|
38
61
|
licenses: []
|
|
39
62
|
|
|
40
63
|
post_install_message:
|
|
@@ -44,23 +67,29 @@ require_paths:
|
|
|
44
67
|
- lib
|
|
45
68
|
- lib
|
|
46
69
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
70
|
+
none: false
|
|
47
71
|
requirements:
|
|
48
72
|
- - ">="
|
|
49
73
|
- !ruby/object:Gem::Version
|
|
74
|
+
hash: 3
|
|
75
|
+
segments:
|
|
76
|
+
- 0
|
|
50
77
|
version: "0"
|
|
51
|
-
version:
|
|
52
78
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
|
+
none: false
|
|
53
80
|
requirements:
|
|
54
81
|
- - ">="
|
|
55
82
|
- !ruby/object:Gem::Version
|
|
83
|
+
hash: 3
|
|
84
|
+
segments:
|
|
85
|
+
- 0
|
|
56
86
|
version: "0"
|
|
57
|
-
version:
|
|
58
87
|
requirements: []
|
|
59
88
|
|
|
60
89
|
rubyforge_project:
|
|
61
|
-
rubygems_version: 1.
|
|
90
|
+
rubygems_version: 1.5.0
|
|
62
91
|
signing_key:
|
|
63
92
|
specification_version: 3
|
|
64
|
-
summary: eventmachine tail - a file tail implementation
|
|
93
|
+
summary: eventmachine tail - a file tail implementation with glob support
|
|
65
94
|
test_files: []
|
|
66
95
|
|