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 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'] * ' '}"