Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Ruby > Forward references?

Reply
Thread Tools

Forward references?

 
 
Lloyd Zusman
Guest
Posts: n/a
 
      08-02-2004
"Robert Klemme" <(E-Mail Removed)> writes:

> "Lloyd Zusman" <(E-Mail Removed)> schrieb im Newsbeitrag
> news:(E-Mail Removed)...
>>
>> [ ... ]
>>
>> I refactored the code even more, based on our discussions and some ideas
>> of my own. If you're interested, I can privately email you the latest
>> version.

>
> [x] interested [ ] not interested


Here it is. Let me know what you think. And thank you for the useful
and interesting discussion in the mailing list.

#!/usr/local/bin/ruby

# Do a 'tail -f' simultaneously multiple files, interspersing their
# output. Continue tailing any file that has been replaced by a new
# version, as in the following, over-simplified example:
#
# while :
# do
# something >>something.log &
# pid=$!
# # ... time passes ...
# rm -f something.log.old
# mv something.log something.log.old
# kill $pid
# done
#
# See the 'usage' routine, below, for a description of the command
# line options and arguments.

require 'sync'
require 'getoptlong'

$program = File.basename($0)

$stdout.extend(Sync_m)
$stdout.sync = 1

$stderr.sync = 1

$waitTime = 0.25

$defColumns = 80
$defLines = 80

$maxBlocksize = 1024

# Default values for flags that are set via the command line.
$tailf = true
$fnamePrefix = false

$opts = GetoptLong.new(
[ "--lines", "-l", GetoptLong::REQUIRED_ARGUMENT ],
[ "--exit", "-x", GetoptLong::NO_ARGUMENT ],
[ "--name", "-n", GetoptLong::NO_ARGUMENT ],
[ "--help", "-h", GetoptLong::NO_ARGUMENT ]
)

# My list of threads.
$fileThreads = [].extend(Sync_m)

# Main routine
def rtail

# Calculate the size of a screen so we can choose a reasonable
# number of lines to tail.

screenColumns = (ENV['COLUMNS'] == nil ? $defColumns : ENV['COLUMNS']).to_i
if screenColumns < 1 then
# In case ENV['COLUMNS'] was set to something <= 0
screenColumns = $defColumns
end

screenLines = (ENV['LINES'] == nil ? $defLines : ENV['LINES']).to_i
if screenLines < 1 then
# In case ENV['LINES'] was set to something <= 0
screenLines = $defLines
end

# One more full line than the maximum that the screen can hold ...

$backwards = screenColumns * (screenLines + 1)


# Parse and evaluate command-line options. Temporarily change
# $0 to be the basename prepended by a newline so that the error
# message that GetoptLong outputs looks good in the case where
# an invalid option was entered.

oldDollar0 = $0
$0 = "\n" + $program

begin

$opts.each do

|opt, arg|

case opt
when "--exit"
$tailf = false
when "--lines"
screenLines = arg.to_i + 0
when "--name"
$fnamePrefix = true
when "--help"
usage
# notreached
else
usage
# notreached
end
end

rescue
usage
# notreached
ensure
$0 = oldDollar0 # just in case we need $0 later on
end

if ARGV.length < 1 then
usage
# notreached
end

# Signal handler.
[ 'SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGTERM' ].each {
|sig|
trap(sig) {
abortThreads(Thread.list.reject {
|t|
t == Thread.main
})
raise "\n!!! aborted"
# notreached
}
}

# Start a thread to tail each file whose name appears on
# the command line. The threads for any file that cannot
# be opened for reading will die and will be reaped in
# the main loop, below.
ARGV.each {
|arg|
$fileThreads.synchronize {
$fileThreads << Thread.new(arg, $tailf, &$fileReadProc)
}
}

# Main loop: reap dead threads and exit once there are no more
# threads that are alive.
loop {
tcount = 0
$fileThreads.synchronize {
tcount = $fileThreads.length
}
if tcount < 1 then
break
else
# Don't eat up too much of my CPU time
waitFor($waitTime)
end
}

# Bye-bye
return 0
end

# This is a mixin for adding a textfile? method to a class that
# behaves like IO. It also adds an externally callable
# TextTester.text? method to test a block of data.

module TextTester

private
# List of items that I want to treat as being normal text
# characters. The first line adds a lot of European characters
# that are not normally considered to be text characters in
# the traditional routines that distinguish between text and
# binary files. This is used within the 'textfile?' method.
@@textpats = [ "^ѡ",
"^ -~",
"^\b\f\t\r\n" ]

public

# This is my own, special-purpose test for text-ness. I don't want to
# treat certain European characters as binary. If the 'testsize'
# argument is non-nil, try to read a buffer of that size; otherwise,
# calculate the buffer size here. If the 'restorePosition' argument
# is true, make sure that the the position pointer within the IO
# handle gets repositioned back to its initial value after this test
# is performed.
#
# This method is callable directly from outside the module. Hence,
# I define it as self.text? here.

def self.text?(block, len = nil)
if len.nil? then
len = block.length
end
return (block.count(*@@textpats) < (len / 3.0) and block.count("\x00") < 1)
end

def textfile?(testsize = nil, restorePosition = true)
begin
if restorePosition then
pos = self.pos
else
pos = nil
end
if testsize.nil? then
testsize = [ self.stat.blocksize,
self.stat.bytesize,
$maxBlocksize ].min
end
block = self.read(testsize)
len = block.length
if len < 1 then
return true # Provisionally treat a zero-length file as a text file.
end

# I need to call text? both inside and outside of this module.
# Therefore, I have to define that method as self.text?, which
# requires me to explicitly reference it off of TextTester here.
result = TextTester.text?(block, len)
unless pos.nil?
self.seek(pos, IO::SEEK_SET)
end
return result
rescue
return false
end
end
end

# Add the test for a text file into the IO class.

class IO
include TextTester
end


# Do a timed 'wait'.

def waitFor(duration)
startTime = Time.now.to_f
select(nil, nil, nil, duration)
Thread.pass
# We could be back here long before 'duration' has passed.
# The loop below makes sure that we wait at least as long
# as this specified interval.
while (elapsed = (Time.now.to_f - startTime)) < duration
select(nil, nil, nil, 0.001)
Thread.pass
end
# Return the actual amount of time that elapsed. This is
# guaranteed to be >= 'duration'.
return elapsed
end


# We make sure that $stdout is synchronized so that lines of
# data coming from different threads don't garble each other.

def syncwrite(text)
begin
$stdout.synchronize(Sync::EX) {
$stdout.write(text)
}
rescue
# Fall back to normal, non-sync writing
$stdout.write(text)
end
end


# Decide whether to output a block as is, or with a prefix
# at the beginning of each line. In the "as is" case, just
# send the whole block to 'syncwrite'; otherwise, split into
# lines and prepend the prefix before outputting. In other
# words, we only incur the cost of splitting the block when
# we absolutely have to.

def output(item)
prefix, block = item
if prefix.nil? or prefix.length < 1 then
syncwrite(block)
else
block.split(/\r*\n/).each {
|line|
syncwrite(prefix + line + "\n")
}
end
end


# Remove a group of threads from the list and kill each one.

def abortThreads(tlist)
$fileThreads.synchronize {
tlist.each {
|t|
$fileThreads.delete(t)
t.kill
}
}
end


# Remove myself from the thread list and kill myself.
def abortMyself
abortThreads([Thread.current])
# notreached
end


# Close the specified IO handle and kill the containing thread
# if this fails.

def closeOrDie(f)
begin
f.close()
rescue
output([nil, "!!! unable to close file: #{item}\n"])
abortMyself()
# notreached
end
end


# This is the main thread proc for tailing a given file.

$fileReadProc = Proc.new do
|item, follow|

# Open the file, make sure it's a text file, read the last bit
# at the end, and output it. Kill the containing thread if any
# of this fails.

begin
f = File.open(item, 'r')
rescue
output([nil, "!!! unable to open: #{item}\n"])
abortMyself()
# notreached
end

# Get some info about the open file
begin
f.sync = true
bytesize = f.stat.size
blocksize = f.stat.blksize
inode = f.stat.ino
rescue
f.close
output([nil, "!!! unable to stat: #{item}\n"])
abortMyself()
# notreached
end

# Blocksize will be nil or zero if the device being opened
# is not a disk file. Bytesize will also be nil in this case.
if blocksize.nil? or blocksize < 1 or bytesize.nil? then
f.close
output([nil, "!!! invalid device: #{item}\n"])
abortMyself()
# notreached
end

# Test for text-ness using one blocksize unit, or the length
# of the file if that is smaller. This is done in two statements
# because we need to use 'blocksize' by itself, further down in
# this procedure.
blocksize = [ blocksize, $maxBlocksize ].min
testsize = [ blocksize, bytesize ].min
unless f.textfile?(testsize, false) then
f.close
output([nil, "!!! not a text file: #{item}\n"])
abortMyself()
# notreached
end

# Set the optional output line prefix.
if $fnamePrefix then
prefix = File.basename(item) + ': '
else
prefix = nil
end

textTestLength = 0

# Position to a suitable point near the end of the file,
# and then read and output the data from that point until
# the end.
begin
if bytesize > $backwards then
pos = bytesize - $backwards
else
pos = 0
end
f.seek(pos, IO::SEEK_SET)
if pos > 0 then
f.gets # discard possible line fragment
end
readSoFar = f.read
textTestLength = readSoFar.length
output([prefix, readSoFar])
rescue
end

# If we have made it here, we've read the last bit of the file
# and have output it. Now, if we're not in 'follow' mode, we
# just exit.
unless follow then
f.close
abortMyself()
# notreached
end

# We only arrive here if we're in 'follow' mode. In this case,
# we keep looping to test if there is any more data to output.
loop {
#
# The file might have been closed due to it having disappeared
# or having changed names. If so, reopen it.
#
if f.closed? then
begin
f = File.open(item, 'r')
f.sync = true
textTextLength = 0
readSoFar = ''
inode = f.stat.ino
output([nil, "!!! reopened: #{item}\n"])
# Fall through to the EOF test.
rescue
output([nil, "!!! disappeared: #{item}\n"])
begin
f.close
rescue
end
abortMyself()
# notreached
end
else # file is not closed
#
# File was not previously closed, so we can test to see if it
# has changed or disappeared.
#
# Get the current inode of the file. This is needed to test
# whether or not the file has disappeared and whether or not there
# is a new file by the same name. This is not 100-percent
# conclusive, since a new file might accidentally end up with the
# same inode of an older, deleted file.
#
begin
newinode = File.stat(item).ino
# Fall through to the EOF test.
rescue
# If we're here, the file has disappeared. Close the handle,
# wait a bit, and then try to reopen it.
closeOrDie(f)
waitFor($waitTime)
redo # go back and iterate again
# notreached
end

if newinode != inode then
# If we're here, the file was replaced by a new file of
# the same name. Close the handle, wait a bit, and then
# try to reopen it.
closeOrDie(f)
waitFor($waitTime)
redo # go back and iterate again
# notreached
end

end # f.closed? ... else ...

# The only way that we can get to this point is if the file is
# properly open and it hasn't been deleted or replaced.

if f.eof? then
# If we're here, we're at EOF. Reset the EOF indicator and
# try again.
f.seek(0, IO::SEEK_CUR)
waitFor($waitTime)
redo # go back and iterate again
# notreached
end

# If we're here, we're not at EOF.

if f.pos < f.stat.size then

# If we're here, more data was added to the file since the last
# time we checked. Output this data, relinquish control to
# other threads, and then repeat the loop.
#
# If we haven't yet tested a full block's worth of bytes
# for text-ness, continue that test here.

data = f.read
if textTestLength < blocksize then
len = data.length
textTestLength += len
readSoFar << data
if len > 0 and not TextTester.text?(readSoFar) then
# If we're here, it's not a text file after all.
closeOrDie(f)
output([nil, "!!! not a text file: #{item}\n"])
abortMyself()
# notreached
end
end
output([prefix, data])
Thread.pass
redo # go back and iterate again
# notreached
end

# If we're here, the file hasn't changed since last time.
# Wait a bit so as to not eat up too much CPU time.

waitFor($waitTime)

} # end of loop

end # end of thread proc


# Print a usage message and exit.
def usage
raise <<EOD

usage: #{$program} [ options ] file [ ... ]

options:

--help, -h print this usage message

--lines=<n>, -l <n> tail <n> lines of each file (default #{$defLines})

--exit, -x exit after showing initial tail

--name, -n prepend file basename on each line that is output

EOD
# notreached

end


# Run it
begin
result = rtail
rescue Exception => e
$stderr.puts(e)
result = 1
end
exit(result)

__END__


--
Lloyd Zusman
http://www.velocityreviews.com/forums/(E-Mail Removed)
God bless you.



 
Reply With Quote
 
 
 
 
Lloyd Zusman
Guest
Posts: n/a
 
      08-02-2004
Lloyd Zusman <(E-Mail Removed)> writes:

> [ ... ]
>
> Here it is. Let me know what you think. And thank you for the useful
> and interesting discussion in the mailing list.
>
> [ ... ]


OOPS! I apologize to the list for the bandwidth. I forgot to remove
the Newsgroups: line when I emailed this to Robert (I post via gnus
and access this mailing list via gmane).


--
Lloyd Zusman
(E-Mail Removed)
God bless you.



 
Reply With Quote
 
 
 
 
Lloyd Zusman
Guest
Posts: n/a
 
      08-03-2004
Clifford Heath <(E-Mail Removed)> writes:

> Lloyd Zusman wrote:
>> Although the mutex and monitor classes also can work
>> here, I prefer Sync_m in this case because its name reflects the exact
>> use to which I am putting it: synchronization.

>
> Except of course that you are in fact doing the exact opposite
> of synchronizing - you're *asynchronizing*. Or enforcing MUTual
> EXclusion... names mean different things to different people,
> you seem to have absorbed the inverted (Java?) meaning of
> "synchronize" .


Yes, I guess I have. But then again, each of these mixins (Mutex_m,
MonitorMixin, Sync_m) has a method called "synchronize" which is used in
pretty much the same way as the Java keyword by the same name ...

Java:

synchronize(object) {
... do stuff ...
}

Ruby:

object.extend(Mutex_m)

object.synchronize {
... do stuff ...
}


> Nice little program BTW, thanks for posting it, if inadvertently.


Thanks!


--
Lloyd Zusman
(E-Mail Removed)
God bless you.



 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Thunderbird 0.7 / 0.8 Forward Mark Dalgua Firefox 1 10-22-2004 06:56 AM
How to forward ports... =?Utf-8?B?Y29tZWR5XzE3?= Wireless Networking 1 08-03-2004 11:45 PM
Thunderbird 0.5 doesn't forward attachments + little issue with news carreg3|j'aime pas le spam| Firefox 5 04-04-2004 12:59 PM
Re-forward declaration of types which were already forward declared qazmlp C++ 1 02-15-2004 07:00 PM
Hot to change the forward header...? Marius Firefox 0 08-27-2003 06:42 PM



Advertisments