file-tail-1.1.1/0000755000004100000410000000000012726223433013414 5ustar www-datawww-datafile-tail-1.1.1/Rakefile0000644000004100000410000000205512726223433015063 0ustar www-datawww-data# vim: set filetype=ruby et sw=2 ts=2: require 'gem_hadar' GemHadar do name 'file-tail' path_name 'file/tail' author 'Florian Frank' email 'flori@ping.de' homepage "http://github.com/flori/#{name}" summary "#{path_name.camelize} for Ruby" description 'Library to tail files in Ruby' test_dir 'tests' ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock', 'coverage', '*.rbc', '.rbx', '.AppleDouble', '.bundle' readme 'README.rdoc' licenses << 'GPL-2.0' dependency 'tins', '~>1.0' development_dependency 'test-unit', '~>2.4.0' install_library do cd 'lib' do libdir = CONFIG["sitelibdir"] dest = File.join(libdir, 'file') mkdir_p(dest) dest = File.join(libdir, path_name) install(path_name + '.rb', dest + '.rb', :verbose => true) mkdir_p(dest) for file in Dir[File.join(path_name, '*.rb')] install(file, dest, :verbose => true) end end bindir = CONFIG["bindir"] install('bin/rtail', bindir, :verbose => true, :mode => 0755) end end file-tail-1.1.1/bin/0000755000004100000410000000000012726223433014164 5ustar www-datawww-datafile-tail-1.1.1/bin/rtail0000755000004100000410000000252312726223433015227 0ustar www-datawww-data#!/usr/bin/env ruby require 'file/tail' require 'tins/go' include Tins::GO require 'thread' Thread.abort_on_exception = true $opt = go 'n:m:Mh' if $opt['h'] puts < true) do |log| begin log.tail(height) { |line| puts line } print "Press return key to continue!" ; gets print " " redo rescue File::Tail::BreakException end end file-tail-1.1.1/examples/tail.rb0000755000004100000410000000034712726223433016517 0ustar www-datawww-data#!/usr/bin/env ruby require 'file/tail' filename = ARGV.pop or fail "Usage: #$0 number filename" number = (ARGV.pop || 0).to_i.abs File::Tail::Logfile.open(filename) do |log| log.backward(number).tail { |line| puts line } end file-tail-1.1.1/tests/0000755000004100000410000000000012726223433014556 5ustar www-datawww-datafile-tail-1.1.1/tests/test_helper.rb0000644000004100000410000000025012726223433017416 0ustar www-datawww-dataif ENV['START_SIMPLECOV'].to_i == 1 require 'simplecov' SimpleCov.start do add_filter "#{File.basename(File.dirname(__FILE__))}/" end end require 'test/unit' file-tail-1.1.1/tests/file_tail_group_test.rb0000644000004100000410000000356512726223433021317 0ustar www-datawww-data#!/usr/bin/env ruby require 'test_helper' require 'file/tail' require 'timeout' require 'thread' require 'tempfile' Thread.abort_on_exception = true class FileTailGroupTest < Test::Unit::TestCase include File::Tail def test_create_group t, = make_file g = Group[t] assert_equal t.path, g.each_tailer.first.file.path assert_equal t.path, g.each_file.first.path end def test_stop_group t, = make_file g = Group[t] assert_equal t.path, g.each_tailer.first.file.path assert_equal t.path, g.each_file.first.path g.stop assert_nil g.each_file.first end def test_add_file_to_group g = Group.new t, = make_file g.add_file t assert_equal t.path, g.each_tailer.first.file.path assert_equal t.path, g.each_file.first.path end def test_add_filename_to_group g = Group.new t, name = make_file t.close g.add_filename name assert_equal name, g.each_tailer.first.file.path assert_equal t.path, g.each_file.first.path end def test_add_generic_to_group g = Group.new t1, n1 = make_file t1.close t2, n1 = make_file g << n1 g << t2 assert g.each_tailer.any? { |t| t.file.path == n1 } assert g.each_tailer.any? { |t| t.file.path == t2.path } assert g.each_file.any? { |t| t.path == n1 } assert g.each_file.any? { |t| t.path == t2.path } end def test_tail_multiple_files t1, = make_file t1.max_interval = 0.1 t2, = make_file t2.max_interval = 0.1 g = Group[t1, t2] q = Queue.new t = Thread.new do g.tail { |l| q << l } end t1.puts "foo" assert_equal "foo\n", q.pop t2.puts "bar" assert_equal "bar\n", q.pop ensure t and t.exit end private def make_file name = File.expand_path(File.join(Dir.tmpdir, "tmp.#$$")) file = File.open(name, 'w+') file.extend File::Tail return file, name end end file-tail-1.1.1/tests/file_tail_test.rb0000644000004100000410000002043012726223433020071 0ustar www-datawww-data#!/usr/bin/env ruby require 'test_helper' require 'file/tail' require 'timeout' require 'thread' Thread.abort_on_exception = true class FileTailTest < Test::Unit::TestCase include File::Tail def setup @out = File.new("test.#$$", "wb") append(@out, 100) @in = File.new(@out.path, "rb") @in.extend(File::Tail) @in.interval = 0.4 @in.max_interval = 0.8 @in.reopen_deleted = true # is default @in.reopen_suspicious = true # is default @in.suspicious_interval = 60 end def test_forward [ 0, 1, 2, 10, 100 ].each do |lines| @in.forward(lines) assert_equal(100 - lines, count(@in)) end @in.forward(101) assert_equal(0, count(@in)) end def test_backward [ 0, 1, 2, 10, 100 ].each do |lines| @in.backward(lines) assert_equal(lines, count(@in)) end @in.backward(101) assert_equal(100, count(@in)) end def test_backward_small_buffer [ 0, 1, 2, 10, 100 ].each do |lines| @in.backward(lines, 100) assert_equal(lines, count(@in)) end @in.backward(101, 100) assert_equal(100, count(@in)) end def test_backward_small_buffer2 @in.default_bufsize = 100 [ 0, 1, 2, 10, 100 ].each do |lines| @in.backward(lines) assert_equal(lines, count(@in)) end @in.backward(101) assert_equal(100, count(@in)) end def test_tail_with_block_without_n Timeout::timeout(10) do lines = [] @in.backward(1) assert_raises(Timeout::Error) do Timeout::timeout(1) { @in.tail { |l| lines << l } } end assert_equal(1, lines.size) # lines = [] @in.backward(10) assert_raises(Timeout::Error) do Timeout::timeout(1) { @in.tail { |l| lines << l } } end assert_equal(10, lines.size) # lines = [] @in.backward(100) assert_raises(Timeout::Error) do Timeout::timeout(1) { @in.tail { |l| lines << l } } end assert_equal(100, lines.size) # lines = [] @in.backward(101) assert_raises(Timeout::Error) do Timeout::timeout(1) { @in.tail { |l| lines << l } } end end end def test_tail_with_block_with_n Timeout::timeout(10) do @in.backward(1) lines = [] Timeout::timeout(1) { @in.tail(0) { |l| lines << l } } assert_equal(0, lines.size) # @in.backward(1) lines = [] Timeout::timeout(1) { @in.tail(1) { |l| lines << l } } assert_equal(1, lines.size) # @in.backward(10) lines = [] Timeout::timeout(1) { @in.tail(10) { |l| lines << l } } assert_equal(10, lines.size) # @in.backward(100) lines = [] @in.backward(1) assert_raises(Timeout::Error) do Timeout::timeout(1) { @in.tail(2) { |l| lines << l } } end assert_equal(1, lines.size) # end end def test_tail_without_block_with_n Timeout::timeout(10) do @in.backward(1) lines = [] Timeout::timeout(1) { lines += @in.tail(0) } assert_equal(0, lines.size) # @in.backward(1) lines = [] Timeout::timeout(1) { lines += @in.tail(1) } assert_equal(1, lines.size) # @in.backward(10) lines = [] Timeout::timeout(1) { lines += @in.tail(10) } assert_equal(10, lines.size) # @in.backward(100) lines = [] @in.backward(1) assert_raises(Timeout::Error) do Timeout::timeout(1) { lines += @in.tail(2) } end assert_equal(0, lines.size) end end def test_tail_withappend @in.backward lines = [] logger = Thread.new do begin Timeout::timeout(1) { @in.tail { |l| lines << l } } rescue Timeout::Error end end appender = Thread.new { append(@out, 10) } appender.join logger.join assert_equal(10, lines.size) end def test_tail_truncated @in.backward lines = [] logger = Thread.new do begin Timeout::timeout(10) do @in.tail do |l| lines << l end end rescue Timeout::Error end end appender = Thread.new do until logger.stop? sleep 0.1 end @out.close File.truncate(@out.path, 0) @out = File.new(@in.path, "ab") append(@out, 10) end appender.join logger.join assert_equal(10, lines.size) end def test_tail_remove return if File::PATH_SEPARATOR == ';' # Grmpf! Windows... @in.backward reopened = false @in.after_reopen { |f| reopened = true } lines = [] logger = Thread.new do begin Timeout::timeout(2) do @in.tail do |l| lines << l end end rescue Timeout::Error end end appender = Thread.new do until logger.stop? sleep 0.1 end @out.close File.unlink(@out.path) @out = File.new(@in.path, "wb") append(@out, 10) end appender.join logger.join assert_equal(10, lines.size) assert reopened end def test_tail_remove2 return if File::PATH_SEPARATOR == ';' # Grmpf! Windows... @in.backward reopened = false @in.after_reopen { |f| reopened = true } lines = [] logger = Thread.new do begin Timeout::timeout(2) do @in.tail do |l| lines << l end end rescue Timeout::Error end end appender = Thread.new do until logger.stop? sleep 0.1 end @out.close File.unlink(@out.path) @out = File.new(@in.path, "wb") append(@out, 10) sleep 1 append(@out, 10) File.unlink(@out.path) @out = File.new(@in.path, "wb") append(@out, 10) end appender.join logger.join assert_equal(30, lines.size) assert reopened end def test_tail_remove3 return if File::PATH_SEPARATOR == ';' # Grmpf! Windows... @in.backward reopened = false @in.after_reopen { |f| reopened = true } lines = [] logger = Thread.new do begin Timeout::timeout(2) do @in.tail(15) do |l| lines << l end end rescue Timeout::Error end end appender = Thread.new do until logger.stop? sleep 0.1 end @out.close File.unlink(@out.path) @out = File.new(@in.path, "wb") append(@out, 10) sleep 1 append(@out, 10) File.unlink(@out.path) @out = File.new(@in.path, "wb") append(@out, 10) end appender.join logger.join assert_equal(15, lines.size) assert reopened end def test_tail_change return if File::PATH_SEPARATOR == ';' # Grmpf! Windows... @in.forward reopened = false assert_equal 0, @in.lineno @in.after_reopen { |f| reopened = true } lines = [] logger = Thread.new do begin Timeout::timeout(2) do @in.tail(110) do |l| lines << l end end rescue Timeout::Error end end appender = Thread.new do until logger.stop? sleep 0.1 end @out.close File.unlink(@out.path) @out = File.new(@in.path, "wb") append(@out, 10) end appender.join logger.join assert_equal(110, lines.size) assert reopened assert_equal 10, @in.lineno end def test_tail_change2 return if File::PATH_SEPARATOR == ';' # Grmpf! Windows... @in.forward reopened = false assert_equal 0, @in.lineno @in.after_reopen { |f| reopened = true } lines = [] logger = Thread.new do begin Timeout::timeout(2) do @in.tail(110) do |l| lines << l end end rescue Timeout::Error end end appender = Thread.new do until logger.stop? sleep 0.1 end @out.truncate 0 @out.close @out = File.new(@in.path, "wb") append(@out, 10) end appender.join logger.join assert_equal(110, lines.size) assert reopened assert_equal 10, @in.lineno end def teardown @in.close @out.close File.unlink(@out.path) end private def count(file) n = 0 until file.eof? file.readline n += 1 end return n end def append(file, n, size = 70) (1..n).each { |x| file << "#{x} #{"A" * size}\n" } file.flush end end file-tail-1.1.1/README.rdoc0000644000004100000410000000357012726223433015227 0ustar www-datawww-data= File::Tail for Ruby == Description This is a small ruby library that allows it to "tail" files in Ruby, including following a file, that still is growing like the unix command 'tail -f' can. == Download The latest version of File::Tail (file-tail) can be found at http://www.ping.de/~flori Online Documentation should be located at http://flori.github.com/file-tail == Installation To install file-tail via its gem type: # gem install file-tail To install from the source repository, just type into the command line as root: # rake install == Usage File::Tail is a module in the File class. A lightweight class interface for logfiles can be seen under File::Tail::Logfile. Direct extension of File objects with File::Tail works like that: File.open(filename) do |log| log.extend(File::Tail) log.interval = 10 log.backward(10) log.tail { |line| puts line } end It's also possible to mix File::Tail in your own File classes (see also File::Tail::Logfile): class MyFile < File include File::Tail end log = MyFile.new("myfile") log.interval = 10 log.backward(10) log.tail { |line| print line } The forward/backward method returns self, so it's possible to chain methods together like that: log.backward(10).tail { |line| puts line } A command line utility named rtail, that uses File::Tail is provided as well. == Documentation To create the documentation of this module, type $ rake doc and the API documentation is generated. In the examples direcotry is a small example of tail and pager program that use this module. You also may want look at the end of file/tail.rb for a little example. == Author Florian Frank mailto:flori@ping.de == License This is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License Version 2 as published by the Free Software Foundation: http://www.gnu.org/copyleft/gpl.html file-tail-1.1.1/.travis.yml0000644000004100000410000000021512726223433015523 0ustar www-datawww-datarvm: - 2.1 - 2.2 - 2.3.0 - ruby-head - jruby-head sudo: false matrix: allow_failures: - rvm: ruby-head - rvm: jruby-head file-tail-1.1.1/lib/0000755000004100000410000000000012726223433014162 5ustar www-datawww-datafile-tail-1.1.1/lib/file/0000755000004100000410000000000012726223433015101 5ustar www-datawww-datafile-tail-1.1.1/lib/file/tail.rb0000644000004100000410000002356712726223433016374 0ustar www-datawww-dataclass File # This module can be included in your own File subclasses or used to extend # files you want to tail. module Tail require 'file/tail/version' require 'file/tail/logfile' require 'file/tail/group' require 'file/tail/tailer' require 'file/tail/line_extension' # This is the base class of all exceptions that are raised # in File::Tail. class TailException < Exception; end # The DeletedException is raised if a file is # deleted while tailing it. class DeletedException < TailException; end # The ReturnException is raised and caught # internally to implement "tail -10" behaviour. class ReturnException < TailException; end # The BreakException is raised if the break_if_eof # attribute is set to a true value and the end of tailed file # is reached. class BreakException < TailException; end # The ReopenException is raised internally if File::Tail # gets suspicious something unusual has happend to # the tailed file, e. g., it was rotated away. The exception # is caught and an attempt to reopen it is made. class ReopenException < TailException attr_reader :mode # Creates an ReopenException object. The mode defaults to # :bottom which indicates that the file # should be tailed beginning from the end. :top # indicates, that it should be tailed from the beginning from the # start. def initialize(mode = :bottom) super(self.class.name) @mode = mode end end # The maximum interval File::Tail sleeps, before it tries # to take some action like reading the next few lines # or reopening the file. attr_accessor :max_interval # The start value of the sleep interval. This value # goes against max_interval if the tailed # file is silent for a sufficient time. attr_accessor :interval # If this attribute is set to a true value, File::Tail persists # on reopening a deleted file waiting max_interval seconds # between the attempts. This is useful if logfiles are # moved away while rotation occurs but are recreated at # the same place after a while. It defaults to true. attr_accessor :reopen_deleted # If this attribute is set to a true value, File::Tail # attempts to reopen it's tailed file after # suspicious_interval seconds of silence. attr_accessor :reopen_suspicious # The callback is called with _self_ as an argument after a reopen has # occured. This allows a tailing script to find out, if a logfile has been # rotated. def after_reopen(&block) @after_reopen = block end # This attribute is the invterval in seconds before File::Tail # gets suspicious that something has happend to it's tailed file # and an attempt to reopen it is made. # # If the attribute reopen_suspicious is # set to a non true value, suspicious_interval is # meaningless. It defaults to 60 seconds. attr_accessor :suspicious_interval # If this attribute is set to a true value, File::Fail's tail method # raises a BreakException if the end of the file is reached. attr_accessor :break_if_eof # If this attribute is set to a true value, File::Fail's tail method # just returns if the end of the file is reached. attr_accessor :return_if_eof # Default buffer size, that is used while going backward from a file's end. # This defaults to nil, which means that File::Tail attempts to derive this # value from the filesystem block size. attr_accessor :default_bufsize # Skip the first n lines of this file. The default is to don't # skip any lines at all and start at the beginning of this file. def forward(n = 0) rewind while n > 0 and not eof? readline n -= 1 end self end # Rewind the last n lines of this file, starting # from the end. The default is to start tailing directly from the # end of the file. # # The additional argument bufsize is # used to determine the buffer size that is used to step through # the file backwards. It defaults to the block size of the # filesystem this file belongs to or 8192 bytes if this cannot # be determined. def backward(n = 0, bufsize = nil) if n <= 0 seek(0, File::SEEK_END) return self end bufsize ||= default_bufsize || stat.blksize || 8192 size = stat.size begin if bufsize < size seek(0, File::SEEK_END) while n > 0 and tell > 0 do seek(-bufsize, File::SEEK_CUR) buffer = read(bufsize) n -= buffer.count("\n") seek(-bufsize, File::SEEK_CUR) end else rewind buffer = read(size) n -= buffer.count("\n") rewind end rescue Errno::EINVAL size = tell retry end pos = -1 while n < 0 # forward if we are too far back pos = buffer.index("\n", pos + 1) n += 1 end seek(pos + 1, File::SEEK_CUR) self end # This method tails this file and yields to the given block for # every new line that is read. # If no block is given an array of those lines is # returned instead. (In this case it's better to use a # reasonable value for n or set the # return_if_eof or break_if_eof # attribute to a true value to stop the method call from blocking.) # # If the argument n is given, only the next n # lines are read and the method call returns. Otherwise this method # call doesn't return, but yields to block for every new line read from # this file for ever. def tail(n = nil, &block) # :yields: line @n = n result = [] array_result = false unless block block = lambda { |line| result << line } array_result = true end preset_attributes unless defined? @lines loop do begin restat read_line(&block) redo rescue ReopenException => e until eof? || @n == 0 block.call readline @n -= 1 if @n end reopen_file(e.mode) @after_reopen.call self if defined? @after_reopen rescue ReturnException return array_result ? result : nil end end end private def read_line(&block) if @n until @n == 0 block.call readline @lines += 1 @no_read = 0 @n -= 1 output_debug_information end raise ReturnException else block.call readline @lines += 1 @no_read = 0 output_debug_information end rescue EOFError seek(0, File::SEEK_CUR) raise ReopenException if @reopen_suspicious and @no_read > @suspicious_interval raise BreakException if @break_if_eof raise ReturnException if @return_if_eof sleep_interval rescue Errno::ENOENT, Errno::ESTALE, Errno::EBADF raise ReopenException end def preset_attributes @reopen_deleted = true unless defined? @reopen_deleted @reopen_suspicious = true unless defined? @reopen_suspicious @break_if_eof = false unless defined? @break_if_eof @return_if_eof = false unless defined? @return_if_eof @max_interval ||= 10 @interval ||= @max_interval @suspicious_interval ||= 60 @lines = 0 @no_read = 0 @stat = nil end def restat stat = File.stat(path) if @stat if stat.ino != @stat.ino or stat.dev != @stat.dev @stat = nil raise ReopenException.new(:top) end if stat.size < @stat.size @stat = nil raise ReopenException.new(:top) end else @stat = stat end rescue Errno::ENOENT, Errno::ESTALE raise ReopenException end def sleep_interval if @lines > 0 # estimate how much time we will spend on waiting for next line @interval = (@interval.to_f / @lines) @lines = 0 else # exponential backoff if logfile is quiet @interval *= 2 end if @interval > @max_interval # max. wait @max_interval @interval = @max_interval end output_debug_information sleep @interval @no_read += @interval end def reopen_file(mode) $DEBUG and $stdout.print "Reopening '#{path}', mode = #{mode}.\n" @no_read = 0 reopen(path) if mode == :bottom backward elsif mode == :top forward end rescue Errno::ESTALE, Errno::ENOENT if @reopen_deleted sleep @max_interval retry else raise DeletedException end end def output_debug_information $DEBUG or return STDERR.puts({ :path => path, :lines => @lines, :interval => @interval, :no_read => @no_read, :n => @n, }.inspect) self end end end if $0 == __FILE__ filename = ARGV.shift or fail "Usage: #$0 filename [number]" number = (ARGV.shift || 0).to_i File.open(filename) do |log| log.extend(File::Tail) # Some settings to make watching tail.rb with "ruby -d" fun log.interval = 1 log.max_interval = 5 log.reopen_deleted = true # is default log.reopen_suspicious = true # is default log.suspicious_interval = 20 number >= 0 ? log.backward(number, 8192) : log.forward(-number) #loop do # grab 5 lines at a time and return # log.tail(5) { |line| puts line } # print "Got 5!\n" #end log.tail { |line| puts line } end end file-tail-1.1.1/lib/file/tail/0000755000004100000410000000000012726223433016032 5ustar www-datawww-datafile-tail-1.1.1/lib/file/tail/tailer.rb0000644000004100000410000000202712726223433017640 0ustar www-datawww-dataclass File module Tail # This class supervises activity on a tailed fail and collects newly read # lines until the Tail::Group fetches and processes them. class Tailer < ::Thread # True if there are any lines pending on this Tailer, false otherwise. def pending_lines? !queue.empty? end # Fetch all the pending lines from this Tailer and thereby remove them # from the Tailer's queue. def pending_lines Array.new(queue.size) { queue.deq(true) } end alias stop exit # Stop tailing this file and remove it from its File::Tail::Group. # Return true if the thread local variable +id+ is defined or if this # object responds to the method +id+. def respond_to?(id) !self[id].nil? || super end # Return the thread local variable +id+ if it is defined. def method_missing(id, *args, &block) if args.empty? && !(value = self[id]).nil? value else super end end end end end file-tail-1.1.1/lib/file/tail/line_extension.rb0000644000004100000410000000055512726223433021407 0ustar www-datawww-dataclass File module Tail # This module is used to extend all lines received via one of the tailers # of a File::Tail::Group. module LineExtension # The file as a File instance this line was read from. def file tailer.file end # This is the tailer this line was received from. attr_reader :tailer end end end file-tail-1.1.1/lib/file/tail/version.rb0000644000004100000410000000042312726223433020043 0ustar www-datawww-datamodule File::Tail # File::Tail version VERSION = '1.1.1' VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc: VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc: VERSION_MINOR = VERSION_ARRAY[1] # :nodoc: VERSION_BUILD = VERSION_ARRAY[2] # :nodoc: end file-tail-1.1.1/lib/file/tail/group.rb0000644000004100000410000000701112726223433017512 0ustar www-datawww-datarequire 'thread' class File module Tail # This class can be used to coordinate tailing of many files, which have # been added to the group. class Group # Creates a new File::Tail::Group instance. # # The following options can be given as arguments: # :files:: an array of files (or filenames to open) that are placed into # the group. def initialize(opts = {}) @tailers = ThreadGroup.new if files = opts[:files] Array(files).each { |file| add file } end end # Creates a group for +files+ (IO instances or filename strings). def self.[](*files) new(:files => files) end # Add a file (IO instance) or filename (responding to to_str) to this # group. def add(file_or_filename) if file_or_filename.respond_to?(:to_io) add_file file_or_filename.to_io elsif file_or_filename.respond_to?(:to_str) add_filename file_or_filename end end alias << add # Add the IO instance +file+ to this group. def add_file(file) setup_file_tailer file self end # Add a file created by opening +filename+ to this group after stepping # +n+ lines backwards from the end of it. def add_filename(filename, n = 0) file = Logfile.open(filename.to_str, :backward => n) file.backward n setup_file_tailer file self end # Iterate over all files contained in this group yielding to +block+ for # each of them. def each_file(&block) each_tailer { |t| t.file }.map(&block) end # Iterate over all tailers in this group yielding to +block+ for each of # them. def each_tailer(&block) @tailers.list.map(&block) end # Stop all tailers in this group at once. def stop each_tailer { |t| t.stop } each_tailer { |t| t.join } self end # Tail all the lines of all the files in the Tail::Group instance, that # is yield to each of them. # # Every line is extended with the LineExtension module, that adds some # methods to the line string. To get the path of the file this line was # received from call line.file.path. def tail wait_for_activity do |tailer| tailer.pending_lines.each do |line| line.extend LineExtension line.instance_variable_set :@tailer, tailer yield line end end end private def setup_file_tailer(file) file.extend File::Tail setup = ConditionVariable.new mutex = Mutex.new ft = nil mutex.synchronize do ft = Tailer.new do t = Thread.current t[:queue] = Queue.new t[:file] = file mutex.synchronize do setup.signal end file.tail { |line| t[:queue] << line } end setup.wait mutex end @tailers.add ft nil end # Wait until new input is receŃ–ved on any of the tailers in the group. If # so call +block+ with all of these trailers as an argument. def wait_for_activity(&block) loop do pending = each_tailer.select(&:pending_lines?) if pending.empty? interval = each_file.map { |t| t.interval }.compact.min || 0.1 sleep interval else pending.each(&block) end end end end end end file-tail-1.1.1/lib/file/tail/logfile.rb0000644000004100000410000000555212726223433020007 0ustar www-datawww-dataclass File module Tail # This is an easy to use Logfile class that includes # the File::Tail module. # # === Usage # The unix command "tail -10f filename" can be emulated like that: # File::Tail::Logfile.open(filename, :backward => 10) do |log| # log.tail { |line| puts line } # end # # Or a bit shorter: # File::Tail::Logfile.tail(filename, :backward => 10) do |line| # puts line # end # # To skip the first 10 lines of the file do that: # File::Tail::Logfile.open(filename, :forward => 10) do |log| # log.tail { |line| puts line } # end # # The unix command "head -10 filename" can be emulated like that: # File::Tail::Logfile.open(filename, :return_if_eof => true) do |log| # log.tail(10) { |line| puts line } # end class Logfile < File include File::Tail # This method creates an File::Tail::Logfile object and # yields to it, and closes it, if a block is given, otherwise it just # returns it. The opts hash takes an option like # * :backward => 10 to go backwards # * :forward => 10 to go forwards # in the logfile for 10 lines at the start. The buffersize # for going backwards can be set with the # * :bufsiz => 8192 option. # To define a callback, that will be called after a reopening occurs, use: # * :after_reopen => lambda { |file| p file } # # Every attribute of File::Tail can be set with a :attributename => # value option. def self.open(filename, opts = {}, &block) # :yields: file file = new filename opts.each do |o, v| writer = o.to_s + "=" file.__send__(writer, v) if file.respond_to? writer end if opts.key?(:wind) or opts.key?(:rewind) warn ":wind and :rewind options are deprecated, "\ "use :forward and :backward instead!" end if backward = opts[:backward] || opts[:rewind] (args = []) << backward args << opt[:bufsiz] if opts[:bufsiz] file.backward(*args) elsif forward = opts[:forward] || opts[:wind] file.forward(forward) end if opts[:after_reopen] file.after_reopen(&opts[:after_reopen]) end if block_given? begin block.call file ensure file.close nil end else file end end # Like open, but yields to every new line encountered in the logfile in # +block+. def self.tail(filename, opts = {}, &block) if ([ :forward, :backward ] & opts.keys).empty? opts[:backward] = 0 end open(filename, opts) do |log| log.tail { |line| block.call line } end end end end end file-tail-1.1.1/lib/file-tail.rb0000644000004100000410000000002412726223433016351 0ustar www-datawww-datarequire 'file/tail' file-tail-1.1.1/file-tail.gemspec0000644000004100000410000000465112726223433016635 0ustar www-datawww-data# -*- encoding: utf-8 -*- # stub: file-tail 1.1.1 ruby lib Gem::Specification.new do |s| s.name = "file-tail".freeze s.version = "1.1.1" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Florian Frank".freeze] s.date = "2016-04-19" s.description = "Library to tail files in Ruby".freeze s.email = "flori@ping.de".freeze s.extra_rdoc_files = ["README.rdoc".freeze, "lib/file-tail.rb".freeze, "lib/file/tail.rb".freeze, "lib/file/tail/group.rb".freeze, "lib/file/tail/line_extension.rb".freeze, "lib/file/tail/logfile.rb".freeze, "lib/file/tail/tailer.rb".freeze, "lib/file/tail/version.rb".freeze] s.files = [".gitignore".freeze, ".travis.yml".freeze, "CHANGES".freeze, "COPYING".freeze, "Gemfile".freeze, "README.rdoc".freeze, "Rakefile".freeze, "VERSION".freeze, "bin/rtail".freeze, "examples/pager.rb".freeze, "examples/tail.rb".freeze, "file-tail.gemspec".freeze, "lib/file-tail.rb".freeze, "lib/file/tail.rb".freeze, "lib/file/tail/group.rb".freeze, "lib/file/tail/line_extension.rb".freeze, "lib/file/tail/logfile.rb".freeze, "lib/file/tail/tailer.rb".freeze, "lib/file/tail/version.rb".freeze, "tests/file_tail_group_test.rb".freeze, "tests/file_tail_test.rb".freeze, "tests/test_helper.rb".freeze] s.homepage = "http://github.com/flori/file-tail".freeze s.licenses = ["GPL-2.0".freeze] s.rdoc_options = ["--title".freeze, "File-tail - File::Tail for Ruby".freeze, "--main".freeze, "README.rdoc".freeze] s.rubygems_version = "2.6.3".freeze s.summary = "File::Tail for Ruby".freeze s.test_files = ["tests/file_tail_group_test.rb".freeze, "tests/file_tail_test.rb".freeze, "tests/test_helper.rb".freeze] if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q.freeze, ["~> 1.7.1"]) s.add_development_dependency(%q.freeze, ["~> 2.4.0"]) s.add_runtime_dependency(%q.freeze, ["~> 1.0"]) else s.add_dependency(%q.freeze, ["~> 1.7.1"]) s.add_dependency(%q.freeze, ["~> 2.4.0"]) s.add_dependency(%q.freeze, ["~> 1.0"]) end else s.add_dependency(%q.freeze, ["~> 1.7.1"]) s.add_dependency(%q.freeze, ["~> 2.4.0"]) s.add_dependency(%q.freeze, ["~> 1.0"]) end end file-tail-1.1.1/.gitignore0000644000004100000410000000010512726223433015400 0ustar www-datawww-data*.rbc .*.sw[pon] .AppleDouble .bundle .rbx Gemfile.lock coverage pkg file-tail-1.1.1/VERSION0000644000004100000410000000000612726223433014460 0ustar www-datawww-data1.1.1 file-tail-1.1.1/CHANGES0000644000004100000410000001165212726223433014414 0ustar www-datawww-data2014-09-26 * 1.1.0 * Depend on tins ~ 1.0 2012-05-31 * 1.0.10 * Use rewind to force IO#lineno to be reset. 2012-05-31 * 1.0.9 * Reopen file in :top mode at the beginning. 2011-12-24 * 1.0.8 * Support simplecov. 2011-07-15 * 1.0.7 * Use gem_hadar to shorten Rakefile. 2011-06-25 * 1.0.6 * Create a gem spec file again. * Added a File::Tail::Group to tail multiple files more easily. 2010-03-25 * 1.0.5 * Added rtail executable, a nice app to supervise logfiles and logdirs. * Disabled creation of gem spec file. * Cleaned up documentation a bit. 2009-08-21 * 1.0.4 * Fixed the threaded tests for Ruby 1.9. * Create a gem spec file. * Some cleanup. 2008-04-07 * 1.0.3 * Danny Colligan reported a memory leak in long running scripts using file-tail. I think this might be a ruby related problem, which is caused/aggravated by using yield after having &block parameter in a method. I changed file-tail to only use block.call, which seems to improve the memory behaviour. I am still not sure, where the problem actually stems from, though. 2007-04-19 * 1.0.2 * make_doc.rb was missing from the source archive. Thanks to Rick Ohnemus for reporting it. 2007-04-19 * 1.0.1 * Bugfix: File::Tail::Logfile#open with block, now closes the file like File#open does. Found by Alex Doan , ruby-talk:248383. 2007-03-30 * 1.0.0 * Bugfix: David.Barzilay@swisscom.com reported, that file tails may skip some log file lines, after rotating it. I think, that I fixed that problem. I added a after_reopen callback as well, that is called after reopening of the tailed file has occured. * Removed rewind/wind methods even earlier than planned: I placed the deprecation warning for rewind method in File instead of File::Tail, which caused rewind to stop working completely after loading file/tail. Duh! I blame vim's matchit, because it jumped to the wrong end keyword. 2007-02-08 * 0.1.4 * Renamed rewind method to backward, and wind method to forward, because someone already had the good idea to name a method IO#rewind, which was overwritten by the mixed in File::Tail methods. The old methods are now deprecated and will be removed in a new 0.2.x version of the library. * Added a bit more of documentation. 2005-08-20 * 0.1.3 * Applied LOAD_PATH patch by Daniel Berger, binary mode changes were already in the CVS. Seemed to be like cheating to me, though. ;) * Skipping one windows test for the moment, too. Sigh! 2004-09-30 * 0.1.2 * First Rubyforge release * Added Rakefile * Supports gem build now. 2004-09-01 * 0.1.1 * Josh Endries found a bug that caused File::Tail to malfunction on FreeBSD. Hotfix: Use a side effect of seek to clearerr the tailed file handle after EOFError has been raised. 2004-04-13 * 0.1.0 * API documentation with rdoc. * return_if_eof attribute added. * Added array return mode for finite tail call without block given. * install.rb now uses ruby version site_dir. * Some code and directory structure cleanup. 2002-08-02 * 0.0.2 * Heavy refactoring, more and smaller methods and expception handling * Added check for inode and device equality of files as suggested by James F.Hranicky and Curt Sampson to cover remove rotation * If filesize shrinks suddenly, File::Tail assumes that copy and truncate rotation has happend: The file is reopened and every new line is handled. * NFS-Fix: Errno::ESTALE is caught. * wind added to skip the first n lines, as James F.Hranicky's suggested and changed name of last-method to rewind, because I liked his method names better than mine ;) * Renamed next to tail either. * The API has changed - but I think very few people care at the moment. * Lots of tests added. 2002-07-30 * 0.0.1 * Initial Release file-tail-1.1.1/COPYING0000644000004100000410000004330212726223433014451 0ustar www-datawww-data GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License.