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 CHANGED
@@ -1,3 +1,5 @@
1
+ 2011-06-25 * 1.0.6 * Create a gem spec file again.
2
+ * Added a File::Tail::Group to tail multiple files more easily.
1
3
  2010-03-25 * 1.0.5 * Added rtail executable, a nice app to supervise logfiles
2
4
  and logdirs.
3
5
  * Disabled creation of gem spec file.
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # vim: set filetype=ruby et sw=2 ts=2:
2
+
3
+ source :rubygems
4
+
5
+ gem 'spruz', '~>0.2'
6
+
7
+ group :development do
8
+ gem 'sdoc'
9
+ gem 'rcov'
10
+ end
@@ -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
- or
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 'rake/gempackagetask'
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 "Installing library"
16
+ desc "Install executable/library into site_ruby directories"
17
17
  task :install do
18
- ruby 'install.rb'
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 "Creating documentation"
36
+ desc "Create documentation"
22
37
  task :doc do
23
- ruby 'make_doc.rb'
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.rb}
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
- system %{rcov -x '\\btests\/' -Ilib tests/test_file-tail.rb}
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', '>=0.1.0'
70
+ s.add_dependency 'spruz', '~>0.2'
49
71
 
50
- s.has_rdoc = true
51
- s.rdoc_options << '--main' << 'README' << '--title' << 'File::Tail - Tailing files in Ruby'
52
- s.extra_rdoc_files << 'README'
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
- Rake::GemPackageTask.new(spec) do |pkg|
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
- task :release => [ :clean, :version, :package ]
117
+ desc "Prepare release of the library"
118
+ task :release => [ :clean, :gemspec, :package ]
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.5
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:h'
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
- def add_log(logfile)
26
- logfile = File.expand_path logfile
27
- $log_threads.key?(logfile) and return
28
- warn "Tailing '#{logfile}'."
29
- $log_threads[logfile] = Thread.new do
30
- File.open(logfile) do |l|
31
- l.sync = true
32
- l.extend File::Tail
33
- l.backward
34
- l.tail do |line|
35
- $log_mutex.synchronize do
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
- def add_logs(logfiles)
44
- for l in logfiles
45
- if $opt['m']
46
- File.fnmatch?($opt['m'], l) and add_log l
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
- add_log l
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 = Dir[File.join(d, '*')].select do |x|
56
+ logfiles.concat Dir[File.join(d, '*')].select { |x|
59
57
  File.file?(x) || File.symlink?(x)
60
- end
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
- # This is an easy to use Logfile class that includes
6
- # the File::Tail module.
7
- #
8
- # === Usage
9
- # The unix command "tail -10f filename" can be emulated like that:
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>bufsiz</code> is
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, bufsiz = nil)
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
- bufsiz ||= stat.blksize || 8192
122
+ bufsize ||= default_bufsize || stat.blksize || 8192
195
123
  size = stat.size
196
124
  begin
197
- if bufsiz < size
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(-bufsiz, File::SEEK_CUR)
202
- buffer = read(bufsiz)
129
+ seek(-bufsize, File::SEEK_CUR)
130
+ buffer = read(bufsize)
203
131
  n -= buffer.count("\n")
204
- seek(-bufsiz, File::SEEK_CUR)
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 += 1
198
+ @lines += 1
271
199
  @no_read = 0
272
- @n -= 1
273
- debug
200
+ @n -= 1
201
+ output_debug_information
274
202
  end
275
203
  raise ReturnException
276
204
  else
277
205
  block.call readline
278
- @lines += 1
206
+ @lines += 1
279
207
  @no_read = 0
280
- debug
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
- debug
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 debug
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
@@ -1,7 +1,7 @@
1
1
  class File
2
2
  module Tail
3
3
  # File::Tail version
4
- VERSION = '1.0.5'
4
+ VERSION = '1.0.6'
5
5
  VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc:
6
6
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
7
7
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
@@ -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 TC_FileTail < Test::Unit::TestCase
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" * 70}\n" }
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
- version: 1.0.5
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: 2010-03-25 00:00:00 +01:00
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
- type: :runtime
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
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.1.0
24
- version:
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.3.5
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
data/make_doc.rb DELETED
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- $outdir = 'doc/'
4
- puts "Creating documentation in '#$outdir'."
5
- system "rdoc -m README -t 'File::Tail - Tailing files in Ruby' -o #$outdir README #{Dir['lib/**/*.rb'] * ' '}"