Ruby script to Module/Class refactor


#1

I have written my first live ruby script that actually performs useful
work as an exercise in syntax discovery. It is very much a straight
line utility that could just as easily be written in bash. What I would
like is for someone to look at the code reproduced below and guide me
through the steps to convert this into something approximating the ruby
OOP idiom. I come from a 4GL universe working on mid-range and high end
iron and OS rather than unix/linux and OOP. So, I have a major
challenge getting my head out of first gear.

Looking at this I wondered what can be placed into separate classes. It
seems to me that the check for environment variables and defaluts and
then creating the resulting directory strings and regexp objects is
either one functional group or two, depending if one considers the
search for environmental settings totally separate from the directory
validation process or not. I tend to see two classes; GetDirs and
CheckDirs. Another class would likely be MoveFiles.

module mv2csv

class GetDirs(envlist[],arglist[]=nil)

def initalize

end

def envlist.each do |env|

end
end

My difficulty is that I do not see how all this hangs together after the
classes and methods are written. Does the script end up as a ruby
module? Then what do I do? Do I have a “main” procedure below the class
definitions in the module? I would really appreciate it if someone
could invest the time to walk me through this because once I see how to
map what I presently do to the ruby way I expect that many things I am
unsure about will suddenly become clear. I am particularly interested
in how to redo this so that Test::Unit can be used to drive it for
testing.

Regards,
Jim

The working code:

#!/usr/bin/env ruby

#–

Put RDOC comments between ++ and – comment tags.

#–
#++

hll-qpxfr.rb - Byrne, James B. - JBB8 -

version: 1.0 2006 Feb 15 - Initial draught.

A ruby script to transfer csv data files delivered via FTP to

an anonymous world-readable site to a secure data directory

and append .csv extension to each.

The files originate on an HP3000 and are ascii csv files

sans header line. Their names take the form QP???#

#–

Standard code layout follows.

require # loads library first time encountered.

require ‘find’
require ‘fileutils’
include FileUtils::DryRun
require ‘syslog’

GLOBAL CONSTANTS

Run unit tests and traces?

VERBOSE_ON = TRUE

If the appropriate environmental variables are not set then

find files matching this pattern:

DFLT_FILE_RE = “^QPCCCMR[[:digit:]]$” # alternate “^QPCCCMR[1-3]$”

using these options:

DFLT_FILE_RE_OPT = “”

and add this extension to them:

DFLT_FILE_EXT = “.csv”

while searching this directory:

DFLT_DIR_IN = “./ruby/data/source”

and move the result to here.

DFLT_DIR_OUT = “./ruby/data/target”

Munge output filenames to lowercase? uppercase? Ignore? (niu:

2006-02-10)
#DFLT_FILE_OUT_CASE = “IGNORE”
#DFLT_FILE_OUT_CASE = “DNCASE” # down-shift case
#DFLT_FILE_OUT_CASE = “UPCASE” # up-shift case

module

class

include # add library code to class defintion

def

load # loads library each time encountered.

main line code after here

begin

Set up SYSLOG

log = Syslog.open($0)
log.info(“Searching for updated files from HP3000 in ftp drop.”)

ENV check for: HLL_QP_DIR_IN, HLL_QP_DIR_OUT, HLL_QP_REGEX,

HLL_QP_EXT and overide DEFAULT values if found.

Regardless, verify that a valid directory is provided.

f_dir_in = ENV[“HLL_QP_DIR_IN”] ? ENV[“HLL_QP_DIR_IN”].to_s :
DFLT_DIR_IN
if !FileTest.directory?(f_dir_in) then
$stderr.print “Invalid source directory " + f_dir_in + " specified.\n”
log.err((“Invalid source directory " + f_dir_in + " specified”))
if ENV[“HLL_QP_DIR_IN”] then
$stderr.print “Check setting of ENV: HLL_QP_DIR_IN\n”
log.err((“Check setting of ENV: HLL_QP_DIR_IN”))
end
Process.exit
else
log.info((“Source directory is: " + f_dir_in))
if !File.readable?(f_dir_in) then
$stderr.print f_dir_in + " is NOT readable. Process aborting.\n”
log.err((f_dir_in + " is NOT readable. Process aborting"))
Process.exit
end
end

f_dir_out = ENV[“HLL_QP_DIR_OUT”] ? ENV[“HLL_QP_DIR_OUT”].to_s :
DFLT_DIR_OUT
if !FileTest.directory?(f_dir_out) then
$stderr.print “Invalid target directory " + f_dir_out + "
specified.\n”
log.err((“Invalid target directory " + f_dir_out + " specified”))
if ENV[“HLL_QP_DIR_OUT”] then
$stderr.print “Check setting of ENV: HLL_QP_DIR_OUT\n”
log.err((“Check setting of ENV: HLL_QP_DIR_OUT”))
end
Process.exit
else
log.info((“Target directory is: " + f_dir_out))
if !File.writable?(f_dir_out) then
$stderr.print f_dir_out + " is NOT writable. Process aborting.\n”
log.err((f_dir_out + " is NOT writable. Process aborting"))
Process.exit
end
end

set the file extension value if present. May be nil.

f_nam_ext = ENV[“HLL_QP_EXT”] ? ENV[“HLL_QP_EXT”].to_s : DFLT_FILE_EXT

create a valid RE for search. Note that setting options in

Regexp.new(RE[,opt]) requires a bit pattern expressed as an integer or

the

equivilent library CONSTANTS (REGEXP::INSENSITIVE, etc.) not, as might

be expected, a character string. Further, if any non-nil value is

passed as an option then the /i switch is turned on even if the option

value is invalid. (So much for the principle of least surprise!)

f_nam_re_s = ENV[“HLL_QP_REGEX”] ? ENV[“HLL_QP_REGEX”].to_s :
DFLT_FILE_RE
f_nam_re = Regexp.new(f_nam_re_s)

check that any files in the source directory are read-write

enabled for current process gid or uid.

Find.find(f_dir_in) do |filename|
if VERBOSE_ON then
puts filename + " " + File.basename(filename) + " " +
f_nam_re.to_s
end
if FileTest.file?(filename) and
File.basename(filename) =~ f_nam_re
then
if VERBOSE_ON then
puts filename
puts FileTest.readable?(filename) ? “\treadable”
: “\tnot readable”
puts FileTest.writable?(filename) ? “\twritable”
: “\tnot writable”
puts filename + " moves to " +
(File.join(f_dir_out, (File.basename(filename) +
f_nam_ext)))
else
# Move oldfile to same name in target dir with extension
appended.
# If we ever enable case munging then this is where it will
happen.
FileUtils.move((filename),
(File.join(f_dir_out, (File.basename(filename) +
f_nam_ext))))
log.info((“Moving " + filename + " to " +
(File.join(f_dir_out, (File.basename(filename) +
f_nam_ext)))))
end
next
else
# do not recurse directories.
if FileTest.directory?(filename)
if VERBOSE_ON then puts filename + " is a directory.” end
if File.basename(filename)!= File.basename(f_dir_in)
if VERBOSE_ON then
puts File.basename(filename) + " is not a regular file."
end
Find.prune
else
next
end
end
end
end

catch problems below here

rescue
if VERBOSE_ON then
puts "rescue block entered: " + $!.to_s
puts
end

put finalizer code below here

ensure
if VERBOSE_ON then
puts "ensure block entered: " + $!.to_s
puts
end

##-- put test code below here
if VERBOSE_ON

Display working data set.

puts "\t Input directory: " + f_dir_in.to_s
puts "\tTarget directory: " + f_dir_out.to_s
puts "\tSet extension to: " + f_nam_ext.to_s
puts “\nThe passed regexp is: \n\t” + f_nam_re_s
puts "The regexp used is: "
p f_nam_re
puts "$= is: " + $=.to_s

Set up a set of strings for possible file names

qpa =
[“qpcccmr1”,“QPCCCMR1”,“QPCCMMR1”,“qpCCCMR1”,“QPCCCMRC”,“QPCCCMR1.csv”]
qpa += [“QPCCCMR9”,“XXQPCCCMR9”,“QPCCCMR9yyy”,“XyZQPCCCMR3AbC”]

Test to see which names match

qpa.each do |a|
case a
when f_nam_re then puts a + " matches"
else puts a + " does not match"
end
end

puts "Ruby run of " + FILE.to_s + " as " + $0.to_s +
" ends at line: " + LINE.to_s

#end VERBOSE_ON block
end

log.info(($0 + " finished"))
#end main
end


#2

“James B. Byrne” removed_email_address@domain.invalid writes:

I have written my first live ruby script that actually performs useful
work as an exercise in syntax discovery. It is very much a straight
line utility that could just as easily be written in bash. What I would
like is for someone to look at the code reproduced below and guide me
through the steps to convert this into something approximating the ruby
OOP idiom. I come from a 4GL universe working on mid-range and high end
iron and OS rather than unix/linux and OOP. So, I have a major
challenge getting my head out of first gear.

OOP is a different mindset. But given that you work on big iron, you
might have an advantage that you don’t know about. I find that when I
describe OOP to people unfamiliar with it, it’s easiest to understand
if you start with the data, and add the methods later. Given that
your universe revolves around data, this might not be so hard.

Looking at this I wondered what can be placed into separate classes. It
seems to me that the check for environment variables and defaluts and
then creating the resulting directory strings and regexp objects is
either one functional group or two, depending if one considers the
search for environmental settings totally separate from the directory
validation process or not. I tend to see two classes; GetDirs and
CheckDirs.

Here, your focus is on what your code is doing. Think, instead,
about what it is. That is, what data are you collecting, and what
are you doing with it? I would argue that instead what you need is a
Configuration object that stores data like, “What directories should I
be fetching from which machines, and where should I put them?”, and
give that object methods like validate_directories() and so on.

Then think of what other things you want to store data about. Say,
your machines. You could define a Machine class that stored generic
data, such as its name, location, and other generic info. You then
could subclass that into HP3000, ZOS_box, and so on. Each of those
would contain methods like get_file(), which would fetch a file from
that machine to the local directory, and maybe reboot(), which would
annoy everybody else using that machine. :wink:

A very loose description might be something like this:

class Configuration
def initialize()
# set all variables to their default values,
# overriding with ENV vars as required.
end

def validate_directories()
   # ensure all directories you rely on are present
end

end

class Machine
def intialize(name, location)
# set generic info about Machine here
end
end

class HP3000
def initialize(name, location)
super(); # let Machine handle this
end

def get_file(filename, destination)
    # do stuff to put filename on the hp3000 into
    # destination
end

end

Notice again how my focus is on the nouns (what things are) as
opposed to the verbs (what things do). Also notice that the verbs
are attached to the thing that does them in most cases. You could
make an argument that get_file doesn’t belong with the HP3000, that it
should be its own top-level subroutine. My reason for placing it
there is this: there is probably a different procedure for each type
of machine you deal with for getting files off of it. By putting the
get_file() method in the machine-specific class, you are putting all
the machine class-specific information in one place, instead of
scattering it all around.

My two major principles when breaking things down are:

  • What is the data?
  • What does it do?

(In that order)

My difficulty is that I do not see how all this hangs together after the
classes and methods are written. Does the script end up as a ruby
module?

A module is a chunk of code you want to re-use elsewhere. If this
script is supposed to be invoked on its own, then no, you don’t want
to make it a module. You may, however, want to put the various
classes you create into their own files so you can re-use them
elsewhere.

Then what do I do? Do I have a “main” procedure below the class
definitions in the module?

Any code you do not put in a method or other limited scope gets
executed in the order you see it. So basically, there is no main(),
you just start doing stuff (much as in a shell script).

I would really appreciate it if someone could invest the time to
walk me through this because once I see how to map what I presently
do to the ruby way I expect that many things I am unsure about will
suddenly become clear. I am particularly interested in how to redo
this so that Test::Unit can be used to drive it for testing.

I’m in a bit of a rush right now, but maybe someone else will explain
how a method such as I proposed makes it easy to test using
Test::Unit-- otherwise, I’ll get back to you later and try and explain
it.

-=Eric


#3

Eric S. wrote:

Here, your focus is on what your code is doing. Think, instead,
about what it is. That is, what data are you collecting, and what
are you doing with it? I would argue that instead what you need is a
Configuration object that stores data like, “What directories should I
be fetching from which machines, and where should I put them?”, and
give that object methods like validate_directories() and so on.


Then think of what other things you want to store data about. Say,
your machines. You could define a Machine class that stored generic
data, such as its name, location, and other generic info. You then
could subclass that into HP3000, ZOS_box, and so on. Each of those
would contain methods like get_file(), which would fetch a file from
that machine to the local directory, and maybe reboot(), which would
annoy everybody else using that machine. :wink:


I’m in a bit of a rush right now, but maybe someone else will explain
how a method such as I proposed makes it easy to test using
Test::Unit-- otherwise, I’ll get back to you later and try and explain
it.

-=Eric

Many thanks. I have clue where to begin now. I will try and redo this
more or less along the lines you recommend and post the result back
here. Just to clarify one point, the HP3000 drops off the files onto
the host that is running the Ruby script. There is no service running
on the HP3000 that will respond to a file transfer request, although, as
you point out, that does not preclude a scenario where the drop machine
is not the script runner.

Regards,
Jim


#4

“James b. Byrne” removed_email_address@domain.invalid writes:

Many thanks. I have clue where to begin now. I will try and redo this
more or less along the lines you recommend and post the result back
here. Just to clarify one point, the HP3000 drops off the files onto
the host that is running the Ruby script. There is no service running
on the HP3000 that will respond to a file transfer request, although, as
you point out, that does not preclude a scenario where the drop machine
is not the script runner.

Exactly. HP3000#get_file might in that case look something like:

class HP3000
def initialize(hostname)
@drop_dir = “/path/to/dropdir/#{hostname}”
end

def get_file(filename, destination)
system(“cp #{@dropdir}/#{filename} #{destination}”)
end
end

Or some such silliness-- and yes, there are more Rubyish ways to do
that, but, like Perl, one of the things I like about Ruby is that
you can do more or less in pure Ruby, depending on your taste and your
application.

-=Eric


#5

James B. Byrne wrote:

seems to me that the check for environment variables and defaluts and
then creating the resulting directory strings and regexp objects is
either one functional group or two, depending if one considers the
search for environmental settings totally separate from the directory
validation process or not. I tend to see two classes; GetDirs and
CheckDirs. Another class would likely be MoveFiles.

James,

Try reading the RubyGarden wiki page WhatIsAnObject[0] and Allen Holub’s
“Why Getter and Setter Methods are Evil” article and you should have a
better idea of what OO is supposed to look and feel like. *

And I just found “Tell, Don’t Ask”[2] by the Pragmatic Programmers,
which helps me better understand OO design.

HTH,
-dave

  • Both links were recently posted in other threads, and I learned a lot
    from them. Thanks! :slight_smile:

[0] http://www.rubygarden.org/ruby/ruby?WhatIsAnObject
[1] http://www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html
[2] http://www.pragmaticprogrammer.com/ppllc/papers/1998_05.html


#6

James,

Try reading the RubyGarden wiki page WhatIsAnObject[0] and Allen Holub’s
“Why Getter and Setter Methods are Evil” article and you should have a
better idea of what OO is supposed to look and feel like. *

And I just found “Tell, Don’t Ask”[2] by the Pragmatic Programmers,
which helps me better understand OO design.

HTH,
-dave

  • Both links were recently posted in other threads, and I learned a lot
    from them. Thanks! :slight_smile:

[0] http://www.rubygarden.org/ruby/ruby?WhatIsAnObject
[1] http://www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html
[2] http://www.pragmaticprogrammer.com/ppllc/papers/1998_05.html

Thanks, I will check these out directly. In the mean time I have come
up with this as my initial take on things. It does not yet actually
move things about but it seems to encompass most of the directory stuff
contained in the original script.


#++

mv2dir.rb

version 1.0 - 2006 Feb 16 - James B. Byrne -

find, move and optionally rename data files that match glob expression

provided.

ruby mv2dir.rb source_dir glob_exp [target_dir=./] [ext]

or

ruby mv2dir.rb -s source_dir -g glob_exp [-t target_dir] [-e ext]

TODO:

Options:

-e --ext Append as suffix to each

filename in source.

-g --glob Use as glob pattern to

select source files.

-h --help Display version, help text and exit.

-l --log Log activity to syslog (DEFAULT).

-L --nolog Do not log activity to syslog.

-v --version Display version and exit.

-V --verbose Display activity to STDOUT.

-s --source Use as path to source directory.

-t --target Use as path to target directory.

#<<TODO:
#–

class Mv2Dir

attr_reader :path, :message

def initialize(path)
@path = path
@message = nil

if !File.exists?(path) then
  @message = "#{path} does not exist on this system."
elsif
  !File.directory?(path) then
  @message = "#{path} exists but is not a directory."
else
  @message = "#{path} is a valid directory."
end

end

def log
#TODO add singleton check for open logging process
# open one if none exists. Log message to syslog.
puts “Log this message #{@message}”
end

def readable?
if !File.readable?(@path) then
@message = “#{@path} is not readable.”
FALSE
else
@message = “#{@path} is readable.”
TRUE
end
end

def writable?
if !File.writable?(@path) then
@message = “#{@path} is not writeable.”
FALSE
else
@message = “#{@path} is writeable.”
TRUE
end
end

def writeable?
self.writable?
end

end # End class Mv2Dir

Run as script after here:

if FILE == $0 then

Run as script after here:

require ‘optparse’

options = OptionParser.new

puts ARGV.to_s

options.on("-h", “-?”, “–help”, “–about”) do |opt|
puts “Help text goes here.”
Process.exit
end

define input and output directories with test data files:

dir_data_in = “/home/byrnejb/ruby/data/source”
dir_data_out = “/home/byrnejb/ruby/data/target”
fnm_glob = “QPCCCMR[1-3]”

reference these here lest they go out of scope below.

test_in = nil
test_out = nil

place these last in test case so that they are available later:

["/","/tmp/","/tmp",“no such place/”,dir_data_in].each do |din|
puts “testing input dir: #{din}”
test_in = Mv2Dir.new(din)
puts “#{test_in.path.to_s} #{test_in.message.to_s}”
puts “#{test_in.readable?.to_s} #{test_in.message.to_s}”
puts “#{test_in.writeable?.to_s} #{test_in.message.to_s}”
end

["./",dir_data_out].each do |dout|
puts “Testing output dir: #{dout}”
test_out = Mv2Dir.new(dout)
puts “#{test_out.path.to_s} #{test_out.message.to_s}”
puts “#{test_out.writeable?.to_s} #{test_out.message.to_s}”
end

puts test_in.path
puts test_out.path

end


I have to ask why in Ruby ‘elsif’ is not ‘elseif’ and ‘writable?’ is not
‘writeable?’ Is there a shortage of 'e’s in the the ruby world?

On a more serious note, can anyone tell me why the optparse construct
given below does not behave as I intend when the script is called thus:

#ruby mv2dir.rb --help

options.on("-h", “-?”, “–help”, “–about”) do |opt|
puts “Help text goes here.”
Process.exit
end

Finally, I can use suggestions on how to code the log method. I would
like to be able to write things like: test_dir =
Mv2Dir.new("/some/path").log or test_dir.writeable?.log but as
(test_dir.writeable? = TRUE) this becomes TRUE.log which is, of course,
undefined. I am not sure how to do this but what I want is to force a
logging event force any Mv2Dir method by appending the .log. Any ideas?


#7

William J. wrote:

Regexp.new(ARGV[1]).match File.basename( p ) }.each{ |p|
FileUtils.move( p, ARGV[2] ) }

Please pardon previous prolixity.

require ‘fileutils’
FileUtils.move Dir[ File.join(ARGV[0], “*”) ].select{ |p|
Regexp.new(ARGV[1]).match File.basename( p ) }, ARGV[2]


#8

James B. Byrne wrote:

find, move and optionally rename data files that match glob expression

provided.

ruby mv2dir.rb source_dir glob_exp [target_dir=./] [ext]

ruby mv2dir.rb source_dir glob_exp target_dir

require ‘fileutils’
Dir[ File.join(ARGV[0], “*”) ].select{ |p|
Regexp.new(ARGV[1]).match File.basename( p ) }.each{ |p|
FileUtils.move( p, ARGV[2] ) }


#9

William J. wrote:

William J. wrote:

Regexp.new(ARGV[1]).match File.basename( p ) }.each{ |p|
FileUtils.move( p, ARGV[2] ) }

Please pardon previous prolixity.

require ‘fileutils’
FileUtils.move Dir[ File.join(ARGV[0], “*”) ].select{ |p|
Regexp.new(ARGV[1]).match File.basename( p ) }, ARGV[2]

$cat mver.rb
require ‘fileutils’
ARGV.each {|a| puts “ARGV[#{ARGV.index(a)}]: #{a}”}

FileUtils.move Dir[ File.join(ARGV[0], “*”) ].select{ |p|
Regexp.new(ARGV[1]).match File.basename( p ) }, ARGV[2]

Does not meet specs:

$ ll data/source data/target
data/source:
total 2268
-rw-rw-rw- 1 byrnejb byrnejb 238480 Feb 16 20:10 QPCCCMR1
-rw-rw-rw- 1 byrnejb byrnejb 785642 Feb 3 11:07 QPCCCMR2
-rw-rw-rw- 1 byrnejb byrnejb 1258930 Feb 16 20:10 QPCCCMR3
-rw-rw-rw- 1 byrnejb byrnejb 60 Feb 16 20:10 XyZQPCCCMR9aBc

data/target:
total 0

[byrnejb@inet05 ruby]$ ruby mver.rb data/source “QPCCCMR*” data/target
ARGV[0]: data/source
ARGV[1]: QPCCCMR*
ARGV[2]: data/target
ARGV[3]: nil
[byrnejb@inet05 ruby]$ ll data/source data/target
data/source:
total 0

data/target:
total 2268
-rw-rw-rw- 1 byrnejb byrnejb 238480 Feb 16 20:10 QPCCCMR1
-rw-rw-rw- 1 byrnejb byrnejb 785642 Feb 3 11:07 QPCCCMR2
-rw-rw-rw- 1 byrnejb byrnejb 1258930 Feb 16 20:10 QPCCCMR3
-rw-rw-rw- 1 byrnejb byrnejb 60 Feb 16 20:10 XyZQPCCCMR9aBc

$ mv data/target/QPCCCMR* data/source
$ ll data/source data/target
data/source:
total 2260
-rw-rw-rw- 1 byrnejb byrnejb 238480 Feb 16 20:10 QPCCCMR1
-rw-rw-rw- 1 byrnejb byrnejb 785642 Feb 3 11:07 QPCCCMR2
-rw-rw-rw- 1 byrnejb byrnejb 1258930 Feb 16 20:10 QPCCCMR3

data/target:
total 8
-rw-rw-rw- 1 byrnejb byrnejb 60 Feb 16 20:10 XyZQPCCCMR9aBc

$ ruby mver.rb data/source QPCCCMR.* data/target .ext
ARGV[0]: data/source
ARGV[1]: QPCCCMR.*
ARGV[2]: data/target
ARGV[3]: .ext
[byrnejb@inet05 ruby]$ ll data/source data/target
data/source:
total 0

data/target:
total 2268
-rw-rw-rw- 1 byrnejb byrnejb 238480 Feb 16 20:10 QPCCCMR1
-rw-rw-rw- 1 byrnejb byrnejb 785642 Feb 3 11:07 QPCCCMR2
-rw-rw-rw- 1 byrnejb byrnejb 1258930 Feb 16 20:10 QPCCCMR3
-rw-rw-rw- 1 byrnejb byrnejb 60 Feb 16 20:10 XyZQPCCCMR9aBc

Globbing (not regexp, there are differences that operations personnel
neither anticipate nor appreciate) does not operate as required,
optional extension is not appended as required, non-positional option
setting is not implemented. Does not log missing files or files moved
as required. etc.

While I do very much appreciate the information given in your example I
can just as easily write a one line script in bash to accomplish this
task if I exclude all the bells and whistles a production environment
with extensive auditing requirements entails. But that is not what I am
trying to accomplish. I am finding my way from procedurally based design
into OOd using this embelished script as a baseline for mapping a new
approach to one I am intimately familiar.

Regards,
Jim


#10

This class seems to provide all but one element of the functionality
that I was seeking. I would very much appreciate a critique how it
varies from OOP ideals and how those weaknesses should be corrected. I
would also like a few pointers on how I would go about using Test::Unit
and Fixtures to test drive this class.

There appears to be something wrong with either my appreciation of how
Syslog is supposed to work, with its implementation, or with how I
understand class variables are preserved.

The problem is that given these three lines:

test1 = Mv2Dir.new("./").log
test2 = Mv2Dir.new("./").log
test1 = Mv2Dir.new("./").log

The third line that reassigns a new instance of Mv2Dir to test1 results
in @@logid being treated as nil in .log and the subsequent @@logid.info
calls fail. However, when I test for @@logid == nil and open syslog when
true then the third line fails with the error that syslog is already
opened. There seems to be a problem with Mv2Dir and Syslog when a
particular object name is reused. I cannot see what the problem is
though. Can anyone provide me with some ideas?

#------------------------------------------------------------------------------
class Mv2Dir

require ‘fileutils’
require ‘syslog’

attr_reader :path, :message

def get(sourcedir,globspec,ext=nil)
# given a valid source dir and file globbing spec get
# all the files in source dir that match and move them
# to @path, appending ext if given. (See: put)
sourcedir = File.expand_path(sourcedir.to_s)
if self.validpath?(sourcedir) then
Dir.glob(File.join(sourcedir.to_s,globspec.to_s)).each do |ff|
fo = File.join(@path,File.basename(ff)) + ext.to_s
@message = “Moving #{ff} to #{fo}”
FileUtils.mv(ff,fo)
self.log
end
end
return self
end

def initialize(path="./")
@path = File.expand_path(path.to_s)
@message = nil
@@logid = nil
self.validpath?(@path)
return self
end

def log(*message)
# Singleton check for open logging proces,
# open one if none exists. Log message to syslog.
# This has problems if an instance name is reused.
if !@@logid then @@logid = Syslog.open($0) end
if message.size > 0 then
message.each { |m| @@logid.info(m.to_s) }
else
@@logid.info(@message.to_s)
end
return self
end

def put(targetdir,globspec,ext=nil)
# given a valid target dir and file globbing spec get
# all the files in @path that match and move them to
# target dir appending ext if given. (See: get)
targetdir = File.expand_path(targetdir.to_s)
if self.validpath?(targetdir) then
Dir.glob(File.join(@path,globspec.to_s)).each do |ff|
fo = File.join(targetdir,File.basename(ff)) + ext.to_s
puts “Moving #{ff} to #{fo}”
@message = “Moving #{ff} to #{fo}”
FileUtils.mv(ff,fo)
self.log
end
end
return self
end

def readable?
if !File.readable?(@path) then
@message = “#{@path} is not readable.”
FALSE
else
@message = “#{@path} is readable.”
TRUE
end
end

def trim(filename,ext)
File.basename(filename.to_s, ext.to_s)
end

def validpath?(path)
if !File.exists?(path) then
@message = “#{path} does not exist on this system.”
FALSE
elsif
!File.directory?(path) then
@message = “#{path} exists but is not a directory.”
FALSE
else
@message = “#{path} is a valid directory.”
TRUE
end
end

def writable?
if !File.writable?(@path) then
@message = “#{@path} is not writeable.”
FALSE
else
@message = “#{@path} is writeable.”
TRUE
end
end

def writeable?
self.writable?
end

end # End class Mv2Dir

#------------------------------------------------------------------------------
if FILE == $0

test_in = Mv2Dir.new("~/ruby/data/source").log
test_out = Mv2Dir.new("~/ruby/data/target").log
test_glob = “QPCCCMR*”

test_in.put(test_out.path,test_glob,".csv").log

#or this will do the same thing.
#test_out.get(test_in.path,test_glob,".csv").log


#11

James B. Byrne wrote:

There appears to be something wrong with either my appreciation of how
Syslog is supposed to work, with its implementation, or with how I
understand class variables are preserved.

I misunderstood where @@variables have to be declared. I moved @@logid
from the initialize method into the class definition and thereafter.log
worked as I intended.


#12

I really would appreciate a critque of class Mv2Dir by someone with a
deal more experience with OOP and Ruby than I.


#13

On Fri, 17 Feb 2006, William J. wrote:

Dir[ File.join(ARGV[0], “*”) ].select{ |p|
Regexp.new(ARGV[1]).match File.basename( p ) }.each{ |p|
FileUtils.move( p, ARGV[2] ) }

Please pardon previous prolixity.

require ‘fileutils’
FileUtils.move Dir[ File.join(ARGV[0], “*”) ].select{ |p|
Regexp.new(ARGV[1]).match File.basename( p ) }, ARGV[2]

a glob is not a regexp

harp:~ > irb
irb(main):001:0> Dir::glob “*.{jpg,png}”
=> [“mqmapgend.png”, “tshirt2.png”, “tshirt.png”, “boulder.png”,
“map.png”, “seg_lights.png”]

irb(main):002:0> Dir::glob("").select{|file| file =~ /.{jpg,png}/}
SyntaxError: compile error
(irb):2: invalid regular expression; there’s no previous pattern, to
which '’ would define cardinality at 1: /.{jpg,png}/
from (irb):2
from :0

so almost any valid glob will explode the above when it fails to compile
as a
regexp.

also, for large dirs this will be extremely slow since all results are
returned
at once. best to use the block form of globbing to accomplish this

harp:~ > ls *png
boulder.png map.png mqmapgend.png seg_lights.png tshirt.png
tshirt2.png

harp:~ > cat a.rb
(src, glob, dst, ext = ARGV) and (dst ||= “.”) and (f = File)
Dir::glob(f.join(src, glob)){|e| f.rename(e, f.join(dst,
f.basename(e) + ext.to_s))}

harp:~ > ruby a.rb . ‘*.png’ tmp .bak

harp:~ > ls tmp
boulder.png.bak map.png.bak mqmapgend.png.bak seg_lights.png.bak
tshirt.png.bak tshirt2.png.bak

regards.

-a


#14

unknown wrote:

harp:~ > cat a.rb
(src, glob, dst, ext = ARGV) and (dst ||= ".") and (f = File)
Dir::glob(f.join(src, glob)){|e|
  f.rename(e, f.join(dst, f.basename(e) + ext.to_s))}

Thank you.
Jim