Here's a filtered file/dir copy, FWIW.

  • Thread starter Nick Sabalausky
  • Start date
N

Nick Sabalausky

I had a need to do a filtered directory-tree copy, but didn't see
anything in the std lib docs (or from googling) other than
FileUtils.cp_r which doesn't do filtering, so I made alternate versions
of that and FileUtils.copy_entry that do filtering.

Ordinarily, I'd try to form it into something appropriate for potential
inclusion in the official lib and submit a patch, but I'm still enough
of Ruby novice that I don't really have enough of a feel for what
exactly would be a "proper" way to integrate the feature, so I'm posting
(attaching) it here as-is in the hopes that someone may at least find it
useful. (FWIW, this is modified from the implementations in fileutils.rb
from Ruby 1.9.1p378).

Attachments:
http://www.ruby-forum.com/attachment/4467/filteredCopy.rb
 
I

Intransition

I had a need to do a filtered directory-tree copy, but didn't see
anything in the std lib docs (or from googling) other than
FileUtils.cp_r which doesn't do filtering, so I made alternate versions
of that and FileUtils.copy_entry that do filtering.

Ordinarily, I'd try to form it into something appropriate for potential
inclusion in the official lib and submit a patch, but I'm still enough
of Ruby novice that I don't really have enough of a feel for what
exactly would be a "proper" way to integrate the feature, so I'm posting
(attaching) it here as-is in the hopes that someone may at least find it
useful. (FWIW, this is modified from the implementations in fileutils.rb
from Ruby 1.9.1p378).

Seems like a useful idea. Join ruby-core mailing list and ask. And let
us know the outcome.
 
A

Albert Schlef

Thomas said:
Seems like a useful idea. Join ruby-core mailing list and ask. And let
us know the outcome.

I too think it's useful.

Nick, I hope your code doesn't create empty folders when none of the
files match the condition.
 
N

Nick Sabalausky

Albert said:
I too think it's useful.

Nick, I hope your code doesn't create empty folders when none of the
files match the condition.

I haven't tested that, but I don't think it will: Unless I'm missing
something, the only path into the code that does any actual copying at
all (file or directory) is through a conditional that calls the filter
and checks if it returns true.

Also, I'm a little confused about the comment about joining the
ruby-core list and asking: The ruby-forum.com interface I'm using (I
*hate* dealing with mailing lists directly, forums and newsgroups are
fine though) lists ruby-core as read only and there doesn't seem to be
anything anywhere about joining it. Plus it looks like it's primarily
just an automatic-tracker that submitted bug tickets get automatically
sent to...?
 
I

Intransition

Also, I'm a little confused about the comment about joining the
ruby-core list and asking: The ruby-forum.com interface I'm using (I
*hate* dealing with mailing lists directly, forums and newsgroups are
fine though) lists ruby-core as read only and there doesn't seem to be
anything anywhere about joining it. Plus it looks like it's primarily
just an automatic-tracker that submitted bug tickets get automatically
sent to...?

I don't like using my inbox for mailing lists either. You can use
google groups:

http://groups.google.com/group/ruby-core-google

(I am using ruby-talk-google right now in fact.)
 
N

Nick Sabalausky

Turns out there's a bug in that, but I can't figure out why...

Consider this part:

----------------------------
def copy_entryx(src, dest, filter, preserve = false, dereference_root =
false, remove_destination = false)
Entry_.new(src, nil, dereference_root).traverse do |ent|
if filter.call(ent.path) then
destent = Entry_.new(dest, ent.rel, false)
File.unlink destent.path if remove_destination &&
File.file?(destent.path)
ent.copy destent.path
ent.copy_metadata destent.path if preserve
end
end
end
----------------------------

(The formatting might have gotten messed up. Check the 'copy_entryx'
function in the OP's attachment for the correct formatting.)

What that *should* be doing is: For each 'ent' in the traversal, check
if the filter returns true, and if so, do the copy. Then, regardless of
whether or not it passed the filter and was copied, move on to the next
'ent'.

What is *actually* does but shouldn't do: As soon as filter returns
false, instead of moving on the the next 'ent', the entire 'copy_entryx'
function exits (I've checked this with debugging 'puts' statements, if I
put one right on a new line right between the last two 'end' statements,
it never shows unless the filter always returns true).

Why does it do that, and how can I fix it?
 
R

Ryan Davis

Turns out there's a bug in that, but I can't figure out why...
=20
Consider this part:
=20
----------------------------
def copy_entryx(src, dest, filter, preserve =3D false, = dereference_root =3D=20
false, remove_destination =3D false)
Entry_.new(src, nil, dereference_root).traverse do |ent|
if filter.call(ent.path) then
destent =3D Entry_.new(dest, ent.rel, false)
File.unlink destent.path if remove_destination &&=20
File.file?(destent.path)
ent.copy destent.path
ent.copy_metadata destent.path if preserve
end
end
end
----------------------------
=20
[...]
=20
Why does it do that, and how can I fix it?

I have no idea because there is no test to look at that reproduces this =
problem. So, write a failing test and make it work. This would be =
especially easy if you refactored that File.unlink to be a method of =
Entry_ (don't name it that. ugh!).
 
N

Nick Sabalausky

Ryan said:
I have no idea because there is no test to look at that reproduces this
problem. So, write a failing test and make it work. This would be
especially easy if you refactored that File.unlink to be a method of
Entry_ (don't name it that. ugh!).

Ok, I've done a simplified test:

-----------------------
def callWith123 &dg
puts "dg.call(1) returns #{dg.call(1)}"
puts "dg.call(2) returns #{dg.call(2)}"
puts "dg.call(3) returns #{dg.call(3)}"
end

def testIt
puts "Entering testIt"
callWith123 do |value|
if value == 2 then
return false
end
true
end
puts "Leaving testIt"
end

puts "Start"
testIt
puts "End"
-----------------------

What I expected:

-----------------------
Start
Entering testIt
dg.call(1) returns true
dg.call(2) returns false
dg.call(3) returns true
Leaving testIt
End
-----------------------

What I got:

-----------------------
Start
Entering testIt
dg.call(1) returns true
End
-----------------------

Apparently, the "return false" returns from testIt instead of merely
returning from the delegate. So it turns out that when you do something
like this:

-----------------------
def foo &dg
#stuff here
end

foo do |value|
#stuff here
end
-----------------------

That "do |value|...end" is *not* a full function literal like I was
expecting it to be, but some kind of...like a closure of a portion of
the function it's contained in. More like using cross-function goto's
than calling a passed-in function.

So the cp_rx function I posted *does* work right, it was just that the
filter I was passing in to it was malformed and returning from the top
few entires in the call stack instead of just the top one.
 
R

Ryan Davis

That "do |value|...end" is *not* a full function literal like I was
expecting it to be, but some kind of...like a closure of a portion of
the function it's contained in. More like using cross-function goto's
than calling a passed-in function.

That is exactly what a block is... a closure.
So the cp_rx function I posted *does* work right, it was just that the
filter I was passing in to it was malformed and returning from the top
few entires in the call stack instead of just the top one.

exactly.

The proper way to write your reproduction is not:
callWith123 do |value|
if value == 2 then
return false
end
true
end
but:

callWith123 do |value|
value != 2
end

And again, you need to ACTUALLY write tests. /soapbox
 
N

Nick Sabalausky

Ryan said:
That is exactly what a block is... a closure.

Soapbox on terminology: Though these terms *frequently* get mixed up,
"closure" merely refers to the ability of code to access locals from the
function it's defined within regardless of whether or not that enclosing
function has returned (And yes, Ruby's closures fit this definition, or
at least as far as I can tell).

But, "closure" does *not* imply that it isn't a function - in fact, in
most languages, closures *are* full-fledged functions that are,
depending on the language, created with either nested functions and/or
anonymous function literals. I've dealt with closures in plenty of
different languages and this is still the first one I've come across
where returning from a closure also returned from the function that
invoked the closure and all the way down the stack through the function
it was created within. So no, that's not how closures necessarily work.
 
R

Ryan Davis

=20
Soapbox on terminology: Though these terms *frequently* get mixed up,=20=
"closure" merely refers to the ability of code to access locals from = the=20
function it's defined within regardless of whether or not that = enclosing=20
function has returned (And yes, Ruby's closures fit this definition, = or=20
at least as far as I can tell).

Wow. Thanks for the attitude after helping you with your problem. Maybe =
I should have focused on "like I was expecting it to be" and pointed out =
that studying one's language is tantamount to success.

Soapbox on history:

Maybe you should look at (real) programming languages like smalltalk, =
commonlisp, or scheme. Ruby bases a lot of its semantics on languages =
like lisp and smalltalk. The following wikipedia entry has code samples =
of each (and ruby and many others) describing exactly this problem.

http://en.wikipedia.org/wiki/Closure_(computer_science)
But, "closure" does *not* imply that it isn't a function - in fact, in=20=
most languages, closures *are* full-fledged functions that are,=20
depending on the language, created with either nested functions and/or=20=
anonymous function literals. I've dealt with closures in plenty of=20
different languages and this is still the first one I've come across=20=
where returning from a closure also returned from the function that=20
invoked the closure and all the way down the stack through the = function=20
it was created within. So no, that's not how closures necessarily =
work.

Not only did I _not_ imply that a closure isn't a function, I didn't =
imply that ruby blocks were not functions (or methods). They are. That =
has little to do with this UNLESS you also carry definitional baggage =
from other languages.

Also, I'd watch the use of "most" there. Maybe if you said "most =
languages I've used"...

In most languages _I've_ used, closures behave like ruby's.
 
N

Nick Sabalausky

Ryan said:
Wow. Thanks for the attitude after helping you with your problem. Maybe
I should have focused on "like I was expecting it to be" and pointed out
that studying one's language is tantamount to success.

There was no attitude intended. In fact, I deliberately overlooked what
I saw as attitude in "And again, you need to ACTUALLY write tests.
/soapbox".
Soapbox on history:

Maybe you should look at (real) programming languages like smalltalk,
commonlisp, or scheme. Ruby bases a lot of its semantics on languages
like lisp and smalltalk. The following wikipedia entry has code samples
of each (and ruby and many others) describing exactly this problem.

http://en.wikipedia.org/wiki/Closure_(computer_science)

This snippet is very helpful:

------
def bar
f = lambda { return "return from lambda" }
f.call # control does not leave bar here
return "return from bar"
end

puts bar # prints "return from bar"
------

I was not aware of that alternate form of ruby closure.
Not only did I _not_ imply that a closure isn't a function, I didn't
imply that ruby blocks were not functions (or methods).

Then there's no need to take it personally. :)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,743
Messages
2,569,478
Members
44,898
Latest member
BlairH7607

Latest Threads

Top