Forum: Ruby Ruby script to Module/Class refactor

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
171ea139761951336b844e708d1547ab?d=identicon&s=25 James B. Byrne (Guest)
on 2006-02-15 17:38
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 <library>    # 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 <Modulename>
#
## class <Classname>
#
## include <library>    # add library code to class defintion
#
## def <methodname>
#
## load <library>       # 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
6a9b3325b0fffcb4ab26a03da4c31a2a?d=identicon&s=25 Eric Schwartz (Guest)
on 2006-02-15 19:26
(Received via mailing list)
"James B. Byrne" <ByrneJB@Harte-Lyne.ca> 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. ;-)

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
171ea139761951336b844e708d1547ab?d=identicon&s=25 James b. Byrne (Guest)
on 2006-02-16 01:45
Eric Schwartz 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. ;-)
>
> ...
> 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
6a9b3325b0fffcb4ab26a03da4c31a2a?d=identicon&s=25 Eric Schwartz (Guest)
on 2006-02-16 01:55
(Received via mailing list)
"James b. Byrne" <ByrneJB@Harte-Lyne.ca> 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
D63c268960051bc17a310aa29fffd979?d=identicon&s=25 Dave Cantrell (Guest)
on 2006-02-16 04:17
(Received via mailing list)
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! :)

[0] http://www.rubygarden.org/ruby/ruby?WhatIsAnObject
[1] http://www.javaworld.com/javaworld/jw-09-2003/jw-0...
[2] http://www.pragmaticprogrammer.com/ppllc/papers/1998_05.html
171ea139761951336b844e708d1547ab?d=identicon&s=25 James B. Byrne (Guest)
on 2006-02-17 02:40
>
> 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! :)
>
> [0] http://www.rubygarden.org/ruby/ruby?WhatIsAnObject
> [1] http://www.javaworld.com/javaworld/jw-09-2003/jw-0...
> [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 <str>       Append <str> as suffix to each
#                                 filename in source.
#           -g  --glob <str>      Use <str> 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 <str>    Use <str> as path to source directory.
#           -t  --target <str>    Use <str> 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?
2ee1a7960cc761a6e92efb5000c0f2c9?d=identicon&s=25 William James (Guest)
on 2006-02-17 05:10
(Received via mailing list)
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] ) }
2ee1a7960cc761a6e92efb5000c0f2c9?d=identicon&s=25 William James (Guest)
on 2006-02-17 05:25
(Received via mailing list)
William James 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]
171ea139761951336b844e708d1547ab?d=identicon&s=25 James B. Byrne (Guest)
on 2006-02-17 16:58
William James wrote:
> William James 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
171ea139761951336b844e708d1547ab?d=identicon&s=25 James B. Byrne (Guest)
on 2006-02-17 22:14
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
171ea139761951336b844e708d1547ab?d=identicon&s=25 James B. Byrne (Guest)
on 2006-02-17 22:59
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.
171ea139761951336b844e708d1547ab?d=identicon&s=25 James B. Byrne (Guest)
on 2006-02-21 05:09
I really would appreciate a critque of class Mv2Dir by someone with a
deal more experience with OOP and Ruby than I.
Cb48ca5059faf7409a5ab3745a964696?d=identicon&s=25 unknown (Guest)
on 2006-02-24 18:46
(Received via mailing list)
On Fri, 17 Feb 2006, William James 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
171ea139761951336b844e708d1547ab?d=identicon&s=25 James Byrne (byrnejb)
on 2006-02-24 19:42
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
This topic is locked and can not be replied to.