file-tail 1.0.5 → 1.0.6
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/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
|