file-tail 1.0.5 → 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +2 -0
- data/Gemfile +10 -0
- data/{README → README.rdoc} +5 -9
- data/Rakefile +45 -15
- data/VERSION +1 -1
- data/bin/rtail +26 -28
- data/file-tail.gemspec +34 -0
- data/lib/file-tail.rb +1 -0
- data/lib/file/tail.rb +28 -98
- data/lib/file/tail/group.rb +125 -0
- data/lib/file/tail/line_extension.rb +15 -0
- data/lib/file/tail/logfile.rb +86 -0
- data/lib/file/tail/tailer.rb +29 -0
- data/lib/file/tail/version.rb +1 -1
- data/tests/test_file-tail.rb +22 -4
- data/tests/test_file-tail_group.rb +91 -0
- metadata +32 -25
- data/install.rb +0 -21
- data/make_doc.rb +0 -5
data/CHANGES
CHANGED
data/Gemfile
ADDED
data/{README → README.rdoc}
RENAMED
@@ -1,3 +1,5 @@
|
|
1
|
+
= File::Tail for Ruby
|
2
|
+
|
1
3
|
== Description
|
2
4
|
|
3
5
|
This is a small ruby library that allows it to "tail" files in Ruby, including
|
@@ -23,10 +25,6 @@ To install from the source repository, just type into the command line as root:
|
|
23
25
|
|
24
26
|
# rake install
|
25
27
|
|
26
|
-
or
|
27
|
-
|
28
|
-
# ruby install.rb
|
29
|
-
|
30
28
|
== Usage
|
31
29
|
|
32
30
|
File::Tail is a module in the File class. A lightweight class interface for
|
@@ -54,17 +52,15 @@ The forward/backward method returns self, so it's possible to chain
|
|
54
52
|
methods together like that:
|
55
53
|
log.backward(10).tail { |line| puts line }
|
56
54
|
|
55
|
+
A command line utility named rtail, that uses File::Tail is provided as well.
|
56
|
+
|
57
57
|
== Documentation
|
58
58
|
|
59
59
|
To create the documentation of this module, type
|
60
60
|
|
61
61
|
$ rake doc
|
62
62
|
|
63
|
-
|
64
|
-
|
65
|
-
$ ruby make_doc.rb
|
66
|
-
|
67
|
-
and the API documentation is generated by your rdoc command.
|
63
|
+
and the API documentation is generated.
|
68
64
|
|
69
65
|
In the examples direcotry is a small example of tail and
|
70
66
|
pager program that use this module. You also may want look
|
data/Rakefile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# vim: set filetype=ruby et sw=2 ts=2:
|
2
2
|
|
3
3
|
begin
|
4
|
-
require '
|
4
|
+
require 'rubygems/package_task'
|
5
5
|
rescue LoadError
|
6
6
|
end
|
7
7
|
require 'rake/clean'
|
@@ -10,27 +10,49 @@ include Config
|
|
10
10
|
|
11
11
|
PKG_NAME = 'file-tail'
|
12
12
|
PKG_VERSION = File.read('VERSION').chomp
|
13
|
-
PKG_FILES = FileList["**/*"].exclude(/^(pkg|coverage|doc)/)
|
13
|
+
PKG_FILES = FileList["**/*"].exclude(/^(pkg|coverage|doc|\..*|Gemfile.lock)/)
|
14
14
|
CLEAN.include 'coverage', 'doc'
|
15
15
|
|
16
|
-
desc "
|
16
|
+
desc "Install executable/library into site_ruby directories"
|
17
17
|
task :install do
|
18
|
-
|
18
|
+
cd 'lib' do
|
19
|
+
libdir = CONFIG["sitelibdir"]
|
20
|
+
|
21
|
+
dest = File.join(libdir, 'file')
|
22
|
+
mkdir_p(dest)
|
23
|
+
file = File.join('file', 'tail.rb')
|
24
|
+
install(file, dest, :verbose => true)
|
25
|
+
|
26
|
+
dest = File.join(dest, 'tail')
|
27
|
+
mkdir_p(dest)
|
28
|
+
for file in Dir[File.join('file', 'tail', '*.rb')]
|
29
|
+
install(file, dest, :verbose => true)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
bindir = CONFIG["bindir"]
|
33
|
+
install('bin/rtail', bindir, :verbose => true, :mode => 0755)
|
19
34
|
end
|
20
35
|
|
21
|
-
desc "
|
36
|
+
desc "Create documentation"
|
22
37
|
task :doc do
|
23
|
-
|
38
|
+
sh "sdoc -m README.rdoc -t 'File::Tail - Tailing files in Ruby' README.rdoc #{Dir['lib/**/*.rb'] * ' '}"
|
24
39
|
end
|
25
40
|
|
26
41
|
desc "Testing library"
|
27
42
|
task :test do
|
28
|
-
ruby %{-Ilib tests/test_file-tail
|
43
|
+
ruby %{-Ilib tests/test_file-tail*.rb}
|
29
44
|
end
|
30
45
|
|
31
46
|
desc "Testing library with rcov"
|
32
47
|
task :coverage do
|
33
|
-
|
48
|
+
sh %{rcov -x '\\b/gems\/' -x '\\btests\/' -Ilib tests/test_file-tail*.rb}
|
49
|
+
end
|
50
|
+
|
51
|
+
namespace :gems do
|
52
|
+
desc "Install all gems from the Gemfile"
|
53
|
+
task :install do
|
54
|
+
sh 'bundle install'
|
55
|
+
end
|
34
56
|
end
|
35
57
|
|
36
58
|
if defined? Gem
|
@@ -45,12 +67,11 @@ if defined? Gem
|
|
45
67
|
|
46
68
|
s.require_path = 'lib'
|
47
69
|
|
48
|
-
s.add_dependency 'spruz', '
|
70
|
+
s.add_dependency 'spruz', '~>0.2'
|
49
71
|
|
50
|
-
s.
|
51
|
-
s.
|
52
|
-
s.
|
53
|
-
s.test_files << 'tests/test_file-tail.rb'
|
72
|
+
s.rdoc_options << '--main' << 'README.rdoc' << '--title' << 'File::Tail - Tailing files in Ruby'
|
73
|
+
s.extra_rdoc_files << 'README.rdoc'
|
74
|
+
s.test_files.concat Dir['tests/test_*.rb']
|
54
75
|
|
55
76
|
s.author = "Florian Frank"
|
56
77
|
s.email = "flori@ping.de"
|
@@ -58,7 +79,14 @@ if defined? Gem
|
|
58
79
|
s.rubyforge_project = PKG_NAME
|
59
80
|
end
|
60
81
|
|
61
|
-
|
82
|
+
desc 'Create a gemspec file'
|
83
|
+
task :gemspec => :version do
|
84
|
+
File.open('file-tail.gemspec', 'w') do |gemspec|
|
85
|
+
gemspec.write spec.to_ruby
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
Gem::PackageTask.new(spec) do |pkg|
|
62
90
|
pkg.need_tar = true
|
63
91
|
pkg.package_files += PKG_FILES
|
64
92
|
end
|
@@ -83,6 +111,8 @@ EOT
|
|
83
111
|
end
|
84
112
|
end
|
85
113
|
|
114
|
+
desc "Run the tests by default"
|
86
115
|
task :default => [ :version, :test ]
|
87
116
|
|
88
|
-
|
117
|
+
desc "Prepare release of the library"
|
118
|
+
task :release => [ :clean, :gemspec, :package ]
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.
|
1
|
+
1.0.6
|
data/bin/rtail
CHANGED
@@ -6,60 +6,58 @@ include Spruz::GO
|
|
6
6
|
require 'thread'
|
7
7
|
Thread.abort_on_exception = true
|
8
8
|
|
9
|
-
$opt = go 'm:
|
9
|
+
$opt = go 'n:m:Mh'
|
10
10
|
if $opt['h']
|
11
11
|
puts <<EOT
|
12
12
|
Usage: #{File.basename($0)} [OPTS] PATHES
|
13
13
|
|
14
14
|
OPTS are
|
15
|
+
-n NUMBER show the last NUMBER of lines in the tailed files
|
15
16
|
-m PATTERN only tail files matching PATTERN, e. g. '*.log'
|
17
|
+
-M prefix every line with the logfile name
|
16
18
|
-h to display this help
|
17
19
|
|
18
20
|
EOT
|
21
|
+
exit
|
19
22
|
end
|
20
23
|
|
21
24
|
dirs, logfiles = ARGV.partition { |path| File.directory?(path) }
|
22
|
-
$log_threads = {}
|
23
|
-
$log_mutex = Mutex.new
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
print line
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
26
|
+
$n = ($opt['n'] || 0).to_i
|
27
|
+
$logfiles = File::Tail::Group.new
|
28
|
+
|
29
|
+
def add_logfiles(logfiles)
|
30
|
+
logfiles = logfiles.map { |l| File.expand_path(l) }
|
31
|
+
$opt['m'] and logfiles =
|
32
|
+
logfiles.select { |l| !$opt['m'] || File.fnmatch?($opt['m'], File.basename(l)) }
|
33
|
+
for l in logfiles
|
34
|
+
$logfiles.each_file.any? { |f| l == f.path } and next
|
35
|
+
warn "Tailing '#{l}'."
|
36
|
+
$logfiles.add_filename l, $n
|
40
37
|
end
|
41
38
|
end
|
42
39
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
40
|
+
add_logfiles logfiles
|
41
|
+
|
42
|
+
t = Thread.new do
|
43
|
+
$logfiles.tail do |line|
|
44
|
+
if $opt['M']
|
45
|
+
puts "#{line.file.path}: #{line}"
|
47
46
|
else
|
48
|
-
|
47
|
+
puts line
|
49
48
|
end
|
50
49
|
end
|
51
50
|
end
|
52
51
|
|
53
|
-
add_logs(logfiles)
|
54
|
-
|
55
52
|
begin
|
56
53
|
loop do
|
54
|
+
logfiles = []
|
57
55
|
for d in dirs
|
58
|
-
logfiles
|
56
|
+
logfiles.concat Dir[File.join(d, '*')].select { |x|
|
59
57
|
File.file?(x) || File.symlink?(x)
|
60
|
-
|
61
|
-
add_logs logfiles
|
58
|
+
}
|
62
59
|
end
|
60
|
+
add_logfiles logfiles
|
63
61
|
sleep 1
|
64
62
|
end
|
65
63
|
rescue Interrupt
|
data/file-tail.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{file-tail}
|
5
|
+
s.version = "1.0.6"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = [%q{Florian Frank}]
|
9
|
+
s.date = %q{2011-06-25}
|
10
|
+
s.description = %q{Library to tail files in Ruby}
|
11
|
+
s.email = %q{flori@ping.de}
|
12
|
+
s.executables = [%q{rtail}]
|
13
|
+
s.extra_rdoc_files = [%q{README.rdoc}]
|
14
|
+
s.files = [%q{tests}, %q{tests/test_file-tail_group.rb}, %q{tests/test_file-tail.rb}, %q{examples}, %q{examples/tail.rb}, %q{examples/pager.rb}, %q{COPYING}, %q{file-tail.gemspec}, %q{Rakefile}, %q{lib}, %q{lib/file}, %q{lib/file/tail.rb}, %q{lib/file/tail}, %q{lib/file/tail/version.rb}, %q{lib/file/tail/line_extension.rb}, %q{lib/file/tail/tailer.rb}, %q{lib/file/tail/group.rb}, %q{lib/file/tail/logfile.rb}, %q{lib/file-tail.rb}, %q{Gemfile}, %q{README.rdoc}, %q{CHANGES}, %q{bin}, %q{bin/rtail}, %q{VERSION}]
|
15
|
+
s.homepage = %q{http://flori.github.com/file-tail}
|
16
|
+
s.rdoc_options = [%q{--main}, %q{README.rdoc}, %q{--title}, %q{File::Tail - Tailing files in Ruby}]
|
17
|
+
s.require_paths = [%q{lib}]
|
18
|
+
s.rubyforge_project = %q{file-tail}
|
19
|
+
s.rubygems_version = %q{1.8.5}
|
20
|
+
s.summary = %q{File::Tail for Ruby}
|
21
|
+
s.test_files = [%q{tests/test_file-tail_group.rb}, %q{tests/test_file-tail.rb}]
|
22
|
+
|
23
|
+
if s.respond_to? :specification_version then
|
24
|
+
s.specification_version = 3
|
25
|
+
|
26
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
27
|
+
s.add_runtime_dependency(%q<spruz>, ["~> 0.2"])
|
28
|
+
else
|
29
|
+
s.add_dependency(%q<spruz>, ["~> 0.2"])
|
30
|
+
end
|
31
|
+
else
|
32
|
+
s.add_dependency(%q<spruz>, ["~> 0.2"])
|
33
|
+
end
|
34
|
+
end
|
data/lib/file-tail.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'file/tail'
|
data/lib/file/tail.rb
CHANGED
@@ -1,89 +1,12 @@
|
|
1
|
-
require 'file/tail/version'
|
2
|
-
|
3
1
|
class File
|
2
|
+
# This module can be included in your own File subclasses or used to extend
|
3
|
+
# files you want to tail.
|
4
4
|
module Tail
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
# File::Tail::Logfile.open(filename, :backward => 10) do |log|
|
11
|
-
# log.tail { |line| puts line }
|
12
|
-
# end
|
13
|
-
#
|
14
|
-
# Or a bit shorter:
|
15
|
-
# File::Tail::Logfile.tail(filename, :backward => 10) do |line|
|
16
|
-
# puts line
|
17
|
-
# end
|
18
|
-
#
|
19
|
-
# To skip the first 10 lines of the file do that:
|
20
|
-
# File::Tail::Logfile.open(filename, :forward => 10) do |log|
|
21
|
-
# log.tail { |line| puts line }
|
22
|
-
# end
|
23
|
-
#
|
24
|
-
# The unix command "head -10 filename" can be emulated like that:
|
25
|
-
# File::Tail::Logfile.open(filename, :return_if_eof => true) do |log|
|
26
|
-
# log.tail(10) { |line| puts line }
|
27
|
-
# end
|
28
|
-
class Logfile < File
|
29
|
-
include File::Tail
|
30
|
-
|
31
|
-
# This method creates an File::Tail::Logfile object and
|
32
|
-
# yields to it, and closes it, if a block is given, otherwise it just
|
33
|
-
# returns it. The opts hash takes an option like
|
34
|
-
# * <code>:backward => 10</code> to go backwards
|
35
|
-
# * <code>:forward => 10</code> to go forwards
|
36
|
-
# in the logfile for 10 lines at the start. The buffersize
|
37
|
-
# for going backwards can be set with the
|
38
|
-
# * <code>:bufsiz => 8192</code> option.
|
39
|
-
# To define a callback, that will be called after a reopening occurs, use:
|
40
|
-
# * <code>:after_reopen => lambda { |file| p file }</code>
|
41
|
-
#
|
42
|
-
# Every attribute of File::Tail can be set with a <code>:attributename =>
|
43
|
-
# value</code> option.
|
44
|
-
def self.open(filename, opts = {}, &block) # :yields: file
|
45
|
-
file = new filename
|
46
|
-
opts.each do |o, v|
|
47
|
-
writer = o.to_s + "="
|
48
|
-
file.__send__(writer, v) if file.respond_to? writer
|
49
|
-
end
|
50
|
-
if opts.key?(:wind) or opts.key?(:rewind)
|
51
|
-
warn ":wind and :rewind options are deprecated, "\
|
52
|
-
"use :forward and :backward instead!"
|
53
|
-
end
|
54
|
-
if backward = opts[:backward] || opts[:rewind]
|
55
|
-
(args = []) << backward
|
56
|
-
args << opt[:bufsiz] if opts[:bufsiz]
|
57
|
-
file.backward(*args)
|
58
|
-
elsif forward = opts[:forward] || opts[:wind]
|
59
|
-
file.forward(forward)
|
60
|
-
end
|
61
|
-
if opts[:after_reopen]
|
62
|
-
file.after_reopen(&opts[:after_reopen])
|
63
|
-
end
|
64
|
-
if block_given?
|
65
|
-
begin
|
66
|
-
block.call file
|
67
|
-
ensure
|
68
|
-
file.close
|
69
|
-
nil
|
70
|
-
end
|
71
|
-
else
|
72
|
-
file
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
# Like open, but yields to every new line encountered in the logfile in
|
77
|
-
# +block+.
|
78
|
-
def self.tail(filename, opts = {}, &block)
|
79
|
-
if ([ :forward, :backward ] & opts.keys).empty?
|
80
|
-
opts[:backward] = 0
|
81
|
-
end
|
82
|
-
open(filename, opts) do |log|
|
83
|
-
log.tail { |line| block.call line }
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
5
|
+
require 'file/tail/version'
|
6
|
+
require 'file/tail/logfile'
|
7
|
+
require 'file/tail/group'
|
8
|
+
require 'file/tail/tailer'
|
9
|
+
require 'file/tail/line_extension'
|
87
10
|
|
88
11
|
# This is the base class of all exceptions that are raised
|
89
12
|
# in File::Tail.
|
@@ -166,6 +89,11 @@ class File
|
|
166
89
|
# just returns if the end of the file is reached.
|
167
90
|
attr_accessor :return_if_eof
|
168
91
|
|
92
|
+
# Default buffer size, that is used while going backward from a file's end.
|
93
|
+
# This defaults to nil, which means that File::Tail attempts to derive this
|
94
|
+
# value from the filesystem block size.
|
95
|
+
attr_accessor :default_bufsize
|
96
|
+
|
169
97
|
# Skip the first <code>n</code> lines of this file. The default is to don't
|
170
98
|
# skip any lines at all and start at the beginning of this file.
|
171
99
|
def forward(n = 0)
|
@@ -181,27 +109,27 @@ class File
|
|
181
109
|
# from the end. The default is to start tailing directly from the
|
182
110
|
# end of the file.
|
183
111
|
#
|
184
|
-
# The additional argument <code>
|
112
|
+
# The additional argument <code>bufsize</code> is
|
185
113
|
# used to determine the buffer size that is used to step through
|
186
114
|
# the file backwards. It defaults to the block size of the
|
187
115
|
# filesystem this file belongs to or 8192 bytes if this cannot
|
188
116
|
# be determined.
|
189
|
-
def backward(n = 0,
|
117
|
+
def backward(n = 0, bufsize = nil)
|
190
118
|
if n <= 0
|
191
119
|
seek(0, File::SEEK_END)
|
192
120
|
return self
|
193
121
|
end
|
194
|
-
|
122
|
+
bufsize ||= default_bufsize || stat.blksize || 8192
|
195
123
|
size = stat.size
|
196
124
|
begin
|
197
|
-
if
|
125
|
+
if bufsize < size
|
198
126
|
seek(0, File::SEEK_END)
|
199
127
|
while n > 0 and tell > 0 do
|
200
128
|
start = tell
|
201
|
-
seek(-
|
202
|
-
buffer = read(
|
129
|
+
seek(-bufsize, File::SEEK_CUR)
|
130
|
+
buffer = read(bufsize)
|
203
131
|
n -= buffer.count("\n")
|
204
|
-
seek(-
|
132
|
+
seek(-bufsize, File::SEEK_CUR)
|
205
133
|
end
|
206
134
|
else
|
207
135
|
seek(0, File::SEEK_SET)
|
@@ -267,17 +195,17 @@ class File
|
|
267
195
|
if @n
|
268
196
|
until @n == 0
|
269
197
|
block.call readline
|
270
|
-
@lines
|
198
|
+
@lines += 1
|
271
199
|
@no_read = 0
|
272
|
-
@n
|
273
|
-
|
200
|
+
@n -= 1
|
201
|
+
output_debug_information
|
274
202
|
end
|
275
203
|
raise ReturnException
|
276
204
|
else
|
277
205
|
block.call readline
|
278
|
-
@lines
|
206
|
+
@lines += 1
|
279
207
|
@no_read = 0
|
280
|
-
|
208
|
+
output_debug_information
|
281
209
|
end
|
282
210
|
rescue EOFError
|
283
211
|
seek(0, File::SEEK_CUR)
|
@@ -333,7 +261,7 @@ class File
|
|
333
261
|
# max. wait @max_interval
|
334
262
|
@interval = @max_interval
|
335
263
|
end
|
336
|
-
|
264
|
+
output_debug_information
|
337
265
|
sleep @interval
|
338
266
|
@no_read += @interval
|
339
267
|
end
|
@@ -354,14 +282,16 @@ class File
|
|
354
282
|
end
|
355
283
|
end
|
356
284
|
|
357
|
-
def
|
285
|
+
def output_debug_information
|
358
286
|
$DEBUG or return
|
359
287
|
STDERR.puts({
|
288
|
+
:path => path,
|
360
289
|
:lines => @lines,
|
361
290
|
:interval => @interval,
|
362
291
|
:no_read => @no_read,
|
363
292
|
:n => @n,
|
364
293
|
}.inspect)
|
294
|
+
self
|
365
295
|
end
|
366
296
|
end
|
367
297
|
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
class File
|
4
|
+
module Tail
|
5
|
+
# This class can be used to coordinate tailing of many files, which have
|
6
|
+
# been added to the group.
|
7
|
+
class Group
|
8
|
+
# Creates a new File::Tail::Group instance.
|
9
|
+
#
|
10
|
+
# The following options can be given as arguments:
|
11
|
+
# :files:: an array of files (or filenames to open) that are placed into
|
12
|
+
# the group.
|
13
|
+
def initialize(opts = {})
|
14
|
+
@tailers = ThreadGroup.new
|
15
|
+
if files = opts[:files]
|
16
|
+
Array(files).each { |file| add file }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Creates a group for +files+ (IO instances or filename strings).
|
21
|
+
def self.[](*files)
|
22
|
+
new(:files => files)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Add a file (IO instance) or filename (responding to to_str) to this
|
26
|
+
# group.
|
27
|
+
def add(file_or_filename)
|
28
|
+
if file_or_filename.respond_to?(:to_io)
|
29
|
+
add_file file_or_filename.to_io
|
30
|
+
elsif file_or_filename.respond_to?(:to_str)
|
31
|
+
add_filename file_or_filename
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
alias << add
|
36
|
+
|
37
|
+
# Add the IO instance +file+ to this group.
|
38
|
+
def add_file(file)
|
39
|
+
setup_file_tailer file
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
# Add a file created by opening +filename+ to this group after stepping
|
44
|
+
# +n+ lines backwards from the end of it.
|
45
|
+
def add_filename(filename, n = 0)
|
46
|
+
file = Logfile.open(filename.to_str, :backward => n)
|
47
|
+
file.backward n
|
48
|
+
setup_file_tailer file
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
# Iterate over all files contained in this group yielding to +block+ for
|
53
|
+
# each of them.
|
54
|
+
def each_file(&block)
|
55
|
+
each_tailer { |t| t.file }.map(&block)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Iterate over all tailers in this group yielding to +block+ for each of
|
59
|
+
# them.
|
60
|
+
def each_tailer(&block)
|
61
|
+
@tailers.list.map { |t| t }.map(&block)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Stop all tailers in this group at once.
|
65
|
+
def stop
|
66
|
+
each_tailer { |t| t.stop }
|
67
|
+
each_tailer { |t| t.join }
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
# Tail all the lines of all the files in the Tail::Group instance, that
|
72
|
+
# is yield to each of them.
|
73
|
+
#
|
74
|
+
# Every line is extended with the LineExtension module, that adds some
|
75
|
+
# methods to the line string. To get the path of the file this line was
|
76
|
+
# received from call line.file.path.
|
77
|
+
def tail
|
78
|
+
wait_for_activity do |tailer|
|
79
|
+
tailer.pending_lines.each do |line|
|
80
|
+
line.extend LineExtension
|
81
|
+
line.instance_variable_set :@tailer, tailer
|
82
|
+
yield line
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def setup_file_tailer(file)
|
90
|
+
file.extend File::Tail
|
91
|
+
setup = ConditionVariable.new
|
92
|
+
mutex = Mutex.new
|
93
|
+
ft = nil
|
94
|
+
mutex.synchronize do
|
95
|
+
ft = Tailer.new do
|
96
|
+
t = Thread.current
|
97
|
+
t[:queue] = Queue.new
|
98
|
+
t[:file] = file
|
99
|
+
mutex.synchronize do
|
100
|
+
setup.signal
|
101
|
+
end
|
102
|
+
file.tail { |line| t[:queue] << line }
|
103
|
+
end
|
104
|
+
setup.wait mutex
|
105
|
+
end
|
106
|
+
@tailers.add ft
|
107
|
+
nil
|
108
|
+
end
|
109
|
+
|
110
|
+
# Wait until new input is receіved on any of the tailers in the group. If
|
111
|
+
# so call +block+ with all of these trailers as an argument.
|
112
|
+
def wait_for_activity(&block)
|
113
|
+
loop do
|
114
|
+
pending = each_tailer.select(&:pending_lines?)
|
115
|
+
if pending.empty?
|
116
|
+
interval = each_file.map { |t| t.interval }.compact.min || 0.1
|
117
|
+
sleep interval
|
118
|
+
else
|
119
|
+
pending.each(&block)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class File
|
2
|
+
module Tail
|
3
|
+
# This module is used to extend all lines received via one of the tailers
|
4
|
+
# of a File::Tail::Group.
|
5
|
+
module LineExtension
|
6
|
+
# The file as a File instance this line was read from.
|
7
|
+
def file
|
8
|
+
tailer.file
|
9
|
+
end
|
10
|
+
|
11
|
+
# This is the tailer this line was received from.
|
12
|
+
attr_reader :tailer
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
class File
|
2
|
+
module Tail
|
3
|
+
# This is an easy to use Logfile class that includes
|
4
|
+
# the File::Tail module.
|
5
|
+
#
|
6
|
+
# === Usage
|
7
|
+
# The unix command "tail -10f filename" can be emulated like that:
|
8
|
+
# File::Tail::Logfile.open(filename, :backward => 10) do |log|
|
9
|
+
# log.tail { |line| puts line }
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# Or a bit shorter:
|
13
|
+
# File::Tail::Logfile.tail(filename, :backward => 10) do |line|
|
14
|
+
# puts line
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# To skip the first 10 lines of the file do that:
|
18
|
+
# File::Tail::Logfile.open(filename, :forward => 10) do |log|
|
19
|
+
# log.tail { |line| puts line }
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# The unix command "head -10 filename" can be emulated like that:
|
23
|
+
# File::Tail::Logfile.open(filename, :return_if_eof => true) do |log|
|
24
|
+
# log.tail(10) { |line| puts line }
|
25
|
+
# end
|
26
|
+
class Logfile < File
|
27
|
+
include File::Tail
|
28
|
+
|
29
|
+
# This method creates an File::Tail::Logfile object and
|
30
|
+
# yields to it, and closes it, if a block is given, otherwise it just
|
31
|
+
# returns it. The opts hash takes an option like
|
32
|
+
# * <code>:backward => 10</code> to go backwards
|
33
|
+
# * <code>:forward => 10</code> to go forwards
|
34
|
+
# in the logfile for 10 lines at the start. The buffersize
|
35
|
+
# for going backwards can be set with the
|
36
|
+
# * <code>:bufsiz => 8192</code> option.
|
37
|
+
# To define a callback, that will be called after a reopening occurs, use:
|
38
|
+
# * <code>:after_reopen => lambda { |file| p file }</code>
|
39
|
+
#
|
40
|
+
# Every attribute of File::Tail can be set with a <code>:attributename =>
|
41
|
+
# value</code> option.
|
42
|
+
def self.open(filename, opts = {}, &block) # :yields: file
|
43
|
+
file = new filename
|
44
|
+
opts.each do |o, v|
|
45
|
+
writer = o.to_s + "="
|
46
|
+
file.__send__(writer, v) if file.respond_to? writer
|
47
|
+
end
|
48
|
+
if opts.key?(:wind) or opts.key?(:rewind)
|
49
|
+
warn ":wind and :rewind options are deprecated, "\
|
50
|
+
"use :forward and :backward instead!"
|
51
|
+
end
|
52
|
+
if backward = opts[:backward] || opts[:rewind]
|
53
|
+
(args = []) << backward
|
54
|
+
args << opt[:bufsiz] if opts[:bufsiz]
|
55
|
+
file.backward(*args)
|
56
|
+
elsif forward = opts[:forward] || opts[:wind]
|
57
|
+
file.forward(forward)
|
58
|
+
end
|
59
|
+
if opts[:after_reopen]
|
60
|
+
file.after_reopen(&opts[:after_reopen])
|
61
|
+
end
|
62
|
+
if block_given?
|
63
|
+
begin
|
64
|
+
block.call file
|
65
|
+
ensure
|
66
|
+
file.close
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
else
|
70
|
+
file
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Like open, but yields to every new line encountered in the logfile in
|
75
|
+
# +block+.
|
76
|
+
def self.tail(filename, opts = {}, &block)
|
77
|
+
if ([ :forward, :backward ] & opts.keys).empty?
|
78
|
+
opts[:backward] = 0
|
79
|
+
end
|
80
|
+
open(filename, opts) do |log|
|
81
|
+
log.tail { |line| block.call line }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class File
|
2
|
+
module Tail
|
3
|
+
# This class supervises activity on a tailed fail and collects newly read
|
4
|
+
# lines until the Tail::Group fetches and processes them.
|
5
|
+
class Tailer < ::Thread
|
6
|
+
|
7
|
+
# True if there are any lines pending on this Tailer, false
|
8
|
+
# otherwise.
|
9
|
+
def pending_lines?
|
10
|
+
!queue.empty?
|
11
|
+
end
|
12
|
+
|
13
|
+
# Fetch all the pending lines from this Tailer and thereby remove them from the Tailer's queue.
|
14
|
+
def pending_lines
|
15
|
+
Array.new(queue.size) { queue.deq(true) }
|
16
|
+
end
|
17
|
+
|
18
|
+
alias stop exit # Stop tailing this file and remove it from its File::Tail::Group.
|
19
|
+
|
20
|
+
def method_missing(id, *args, &block)
|
21
|
+
if args.empty? && !(value = self[id]).nil?
|
22
|
+
value
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/file/tail/version.rb
CHANGED
data/tests/test_file-tail.rb
CHANGED
@@ -8,12 +8,11 @@ end
|
|
8
8
|
|
9
9
|
require 'test/unit'
|
10
10
|
require 'file/tail'
|
11
|
-
require 'tempfile'
|
12
11
|
require 'timeout'
|
13
12
|
require 'thread'
|
14
13
|
Thread.abort_on_exception = true
|
15
14
|
|
16
|
-
class
|
15
|
+
class TestFileTail < Test::Unit::TestCase
|
17
16
|
include File::Tail
|
18
17
|
|
19
18
|
def setup
|
@@ -46,6 +45,25 @@ class TC_FileTail < Test::Unit::TestCase
|
|
46
45
|
assert_equal(100, count(@in))
|
47
46
|
end
|
48
47
|
|
48
|
+
def test_backward_small_buffer
|
49
|
+
[ 0, 1, 2, 10, 100 ].each do |lines|
|
50
|
+
@in.backward(lines, 100)
|
51
|
+
assert_equal(lines, count(@in))
|
52
|
+
end
|
53
|
+
@in.backward(101, 100)
|
54
|
+
assert_equal(100, count(@in))
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_backward_small_buffer2
|
58
|
+
@in.default_bufsize = 100
|
59
|
+
[ 0, 1, 2, 10, 100 ].each do |lines|
|
60
|
+
@in.backward(lines)
|
61
|
+
assert_equal(lines, count(@in))
|
62
|
+
end
|
63
|
+
@in.backward(101)
|
64
|
+
assert_equal(100, count(@in))
|
65
|
+
end
|
66
|
+
|
49
67
|
def test_tail_with_block_without_n
|
50
68
|
timeout(10) do
|
51
69
|
lines = []
|
@@ -294,8 +312,8 @@ class TC_FileTail < Test::Unit::TestCase
|
|
294
312
|
return n
|
295
313
|
end
|
296
314
|
|
297
|
-
def append(file, n)
|
298
|
-
(1..n).each { |x| file << "#{x} #{"A" *
|
315
|
+
def append(file, n, size = 70)
|
316
|
+
(1..n).each { |x| file << "#{x} #{"A" * size}\n" }
|
299
317
|
file.flush
|
300
318
|
end
|
301
319
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
base = File.basename(Dir.pwd)
|
4
|
+
if base == 'tests' || base =~ /file-tail/
|
5
|
+
Dir.chdir('..') if base == 'tests'
|
6
|
+
$LOAD_PATH.unshift(File.join(Dir.pwd, 'lib'))
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'test/unit'
|
10
|
+
require 'file/tail'
|
11
|
+
require 'timeout'
|
12
|
+
require 'thread'
|
13
|
+
require 'tempfile'
|
14
|
+
Thread.abort_on_exception = true
|
15
|
+
|
16
|
+
class TestFileTailGroup < Test::Unit::TestCase
|
17
|
+
include File::Tail
|
18
|
+
|
19
|
+
def test_create_group
|
20
|
+
t, = make_file
|
21
|
+
g = Group[t]
|
22
|
+
assert_equal t.path, g.each_tailer.first.file.path
|
23
|
+
assert_equal t.path, g.each_file.first.path
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_stop_group
|
27
|
+
t, = make_file
|
28
|
+
g = Group[t]
|
29
|
+
assert_equal t.path, g.each_tailer.first.file.path
|
30
|
+
assert_equal t.path, g.each_file.first.path
|
31
|
+
g.stop
|
32
|
+
assert_nil g.each_file.first
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_add_file_to_group
|
36
|
+
g = Group.new
|
37
|
+
t, = make_file
|
38
|
+
g.add_file t
|
39
|
+
assert_equal t.path, g.each_tailer.first.file.path
|
40
|
+
assert_equal t.path, g.each_file.first.path
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_add_filename_to_group
|
44
|
+
g = Group.new
|
45
|
+
t, name = make_file
|
46
|
+
t.close
|
47
|
+
g.add_filename name
|
48
|
+
assert_equal name, g.each_tailer.first.file.path
|
49
|
+
assert_equal t.path, g.each_file.first.path
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_add_generic_to_group
|
53
|
+
g = Group.new
|
54
|
+
t1, n1 = make_file
|
55
|
+
t1.close
|
56
|
+
t2, n1 = make_file
|
57
|
+
g << n1
|
58
|
+
g << t2
|
59
|
+
assert g.each_tailer.any? { |t| t.file.path == n1 }
|
60
|
+
assert g.each_tailer.any? { |t| t.file.path == t2.path }
|
61
|
+
assert g.each_file.any? { |t| t.path == n1 }
|
62
|
+
assert g.each_file.any? { |t| t.path == t2.path }
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_tail_multiple_files
|
66
|
+
t1, = make_file
|
67
|
+
t1.max_interval = 0.1
|
68
|
+
t2, = make_file
|
69
|
+
t2.max_interval = 0.1
|
70
|
+
g = Group[t1, t2]
|
71
|
+
q = Queue.new
|
72
|
+
t = Thread.new do
|
73
|
+
g.tail { |l| q << l }
|
74
|
+
end
|
75
|
+
t1.puts "foo"
|
76
|
+
assert_equal "foo\n", q.pop
|
77
|
+
t2.puts "bar"
|
78
|
+
assert_equal "bar\n", q.pop
|
79
|
+
ensure
|
80
|
+
t and t.exit
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def make_file
|
86
|
+
name = File.expand_path(File.join(Dir.tmpdir, "tmp.#$$"))
|
87
|
+
file = File.open(name, 'w+')
|
88
|
+
file.extend File::Tail
|
89
|
+
return file, name
|
90
|
+
end
|
91
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: file-tail
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease:
|
5
|
+
version: 1.0.6
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- Florian Frank
|
@@ -9,19 +10,19 @@ autorequire:
|
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
12
|
|
12
|
-
date:
|
13
|
-
default_executable:
|
13
|
+
date: 2011-06-25 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: spruz
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
20
|
requirements:
|
21
|
-
- -
|
21
|
+
- - ~>
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 0.
|
24
|
-
|
23
|
+
version: "0.2"
|
24
|
+
type: :runtime
|
25
|
+
version_requirements: *id001
|
25
26
|
description: Library to tail files in Ruby
|
26
27
|
email: flori@ping.de
|
27
28
|
executables:
|
@@ -29,51 +30,57 @@ executables:
|
|
29
30
|
extensions: []
|
30
31
|
|
31
32
|
extra_rdoc_files:
|
32
|
-
- README
|
33
|
+
- README.rdoc
|
33
34
|
files:
|
35
|
+
- tests/test_file-tail_group.rb
|
36
|
+
- tests/test_file-tail.rb
|
37
|
+
- examples/tail.rb
|
38
|
+
- examples/pager.rb
|
39
|
+
- COPYING
|
40
|
+
- file-tail.gemspec
|
41
|
+
- Rakefile
|
42
|
+
- lib/file/tail.rb
|
43
|
+
- lib/file/tail/version.rb
|
44
|
+
- lib/file/tail/line_extension.rb
|
45
|
+
- lib/file/tail/tailer.rb
|
46
|
+
- lib/file/tail/group.rb
|
47
|
+
- lib/file/tail/logfile.rb
|
48
|
+
- lib/file-tail.rb
|
49
|
+
- Gemfile
|
50
|
+
- README.rdoc
|
34
51
|
- CHANGES
|
35
52
|
- bin/rtail
|
36
53
|
- VERSION
|
37
|
-
- README
|
38
|
-
- make_doc.rb
|
39
|
-
- Rakefile
|
40
|
-
- examples/pager.rb
|
41
|
-
- examples/tail.rb
|
42
|
-
- lib/file/tail/version.rb
|
43
|
-
- lib/file/tail.rb
|
44
|
-
- tests/test_file-tail.rb
|
45
|
-
- COPYING
|
46
|
-
- install.rb
|
47
|
-
has_rdoc: true
|
48
54
|
homepage: http://flori.github.com/file-tail
|
49
55
|
licenses: []
|
50
56
|
|
51
57
|
post_install_message:
|
52
58
|
rdoc_options:
|
53
59
|
- --main
|
54
|
-
- README
|
60
|
+
- README.rdoc
|
55
61
|
- --title
|
56
62
|
- File::Tail - Tailing files in Ruby
|
57
63
|
require_paths:
|
58
64
|
- lib
|
59
65
|
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
60
67
|
requirements:
|
61
68
|
- - ">="
|
62
69
|
- !ruby/object:Gem::Version
|
63
70
|
version: "0"
|
64
|
-
version:
|
65
71
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
66
73
|
requirements:
|
67
74
|
- - ">="
|
68
75
|
- !ruby/object:Gem::Version
|
69
76
|
version: "0"
|
70
|
-
version:
|
71
77
|
requirements: []
|
72
78
|
|
73
79
|
rubyforge_project: file-tail
|
74
|
-
rubygems_version: 1.
|
80
|
+
rubygems_version: 1.8.5
|
75
81
|
signing_key:
|
76
82
|
specification_version: 3
|
77
83
|
summary: File::Tail for Ruby
|
78
84
|
test_files:
|
85
|
+
- tests/test_file-tail_group.rb
|
79
86
|
- tests/test_file-tail.rb
|
data/install.rb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'rbconfig'
|
4
|
-
require 'fileutils'
|
5
|
-
include FileUtils::Verbose
|
6
|
-
|
7
|
-
include Config
|
8
|
-
|
9
|
-
cd 'lib' do
|
10
|
-
libdir = CONFIG["sitelibdir"]
|
11
|
-
|
12
|
-
dest = File.join(libdir, 'file')
|
13
|
-
mkdir_p(dest)
|
14
|
-
file = File.join('file', 'tail.rb')
|
15
|
-
install(file, dest)
|
16
|
-
|
17
|
-
dest = File.join(dest, 'tail')
|
18
|
-
mkdir_p(dest)
|
19
|
-
file = File.join('file', 'tail', 'version.rb')
|
20
|
-
install(file, dest)
|
21
|
-
end
|