IO::CatHandle Reference
Use multiple IO handles as if they were one
What it is #
class IO::CatHandle is IO::Handle { }
Position in the hierarchy #
| Inherits from | IO::Handle |
| Does | — |
| Inherited by | IO::ArgFiles |
Routines #
Signatures are reproduced from the documentation, including their declared return types. A routine listed here is part of the type's published interface; whether Raku++ implements it is a separate question, answered by the examples below.
method new #
method new(*@handles, :&on-switch, :$chomp = True,
Creates a new IO::CatHandle object. The @handles positional argument indicates a source of handles for the IO::CatHandle to read from and can deal with a mixed collection of Cool, IO::Path, and IO::Handle (including IO::Pipe) objects. As input from IO::CatHandle is processed (so operations won't happen during .new call, but only when @handles' data is needed), it will walk
method chomp #
method chomp(IO::CatHandle:D:) is rw
Sets the invocant's $.chomp attribute to the assigned value. All source handles, including the active one will use the provided $.chomp value.
method nl-in #
method nl-in(IO::CatHandle:D:) is rw
Sets the invocant's $.nl-in attribute to the assigned value, which can be a Str or a List of Str, where each Str object represents the end-of-line string. All source handles, including the active one will use the provided $.nl-in value. Note that source handle boundary is always counted as a new line break.
method close #
method close(IO::CatHandle:D: --> True)
Returns True.
Closes the currently active source handle, as well as any already-open source handles, and empties the source handle queue. Unlike a regular IO::Handle, an explicit call to .close is often not necessary on an IO::CatHandle, as merely exhausting all the input closes all the handles that need to be closed.
method comb #
method comb(IO::CatHandle:D: |args --> Seq:D)
Returns Seq:D.
Read the handle and processes its contents the same way Str.comb does, taking the same arguments. Implementations may slurp the contents of all the source handles in their entirety when this method is called.
method DESTROY #
method DESTROY(IO::CatHandle:D:)
Calls .close. This method isn't to be used directly, but is something that's called during garbage collection.
method encoding #
multi method encoding(IO::CatHandle:D:)
multi method encoding(IO::CatHandle:D: $new-encoding)
Sets the invocant's $.encoding attribute to the provided value. Valid values are the same as those accepted by IO::Handle.encoding (use value Nil to switch to binary mode). All source handles, including the active one will use the provided $.encoding value.
method eof #
method eof(IO::CatHandle:D: --> Bool:D)
Returns Bool:D.
Returns True if the read operations have exhausted the source handle queue, including the contents of the last handle. Note: calling this method may cause one or more .on-switch calls, while the source handle queue is examined, and the source handle queue may get exhausted. The same caveats for non-seekable handles and empty files that apply to
method get #
method get(IO::CatHandle:D: --> Bool:D)
Returns Bool:D.
Returns a single line of input from the handle, with the new line string defined by the value(s) of $.nl-in attribute, which will be removed from the line if $.chomp attribute is set to True. Returns Nil when there is no more input. It is an error to call this method when the handle is in binary mode, resulting in
method getc #
method getc(IO::CatHandle:D: --> Bool:D)
Returns Bool:D.
Returns a single character of input from the handle. All the caveats described in IO::Handle.getc apply. Returns Nil when there is no more input. It is an error to call this method when the handle is in binary mode, resulting in X::IO::BinaryMode exception being thrown.
method handles #
method handles(IO::CatHandle:D: --> Seq:D)
Returns Seq:D.
Defines as: Returns a Seq containing the currently-active handle, as well as all the remaining source handles produced by calling next-handle. If the invocant has already been fully-consumed, returns an empty Seq. This method is especially handy when working with IO::ArgFiles, where you want to treat each filehandle separately:
method IO #
method IO(IO::CatHandle:D:)
Alias for .path
method lines #
method lines(IO::CatHandle:D: $limit = Inf, :$close --> Seq:D)
Returns Seq:D.
Same as IO::Handle.lines. Note that a boundary between source handles is considered to be a newline break. Note: if :$close is False, fully-consumed handles are still going to be closed.
method lock #
method lock(IO::CatHandle:D: Bool:D :$non-blocking = False, Bool:D :$shared = False --> True)
Returns True.
Same as IO::Handle.lock. Returns Nil if the source handle queue has been exhausted. Locks only the currently active source handle. The .on-switch Callable can be used to conveniently lock/unlock the handles as they're being processed by the CatHandle.
method native-descriptor #
method native-descriptor(IO::CatHandle:D: --> Int:D)
Returns Int:D.
Returns the native-descriptor of the currently active source handle or Nil if the source handle queue has been exhausted. Since the CatHandle closes a source handle, once it's done with it, it's possible for successive source handles to have the same native descriptor, if they were passed to .new as Cool
method next-handle #
method next-handle(IO::CatHandle:D: --> IO::Handle:D)
Returns IO::Handle:D.
Switches the active source handle to the next handle in the source handle queue, which is the sources given in @handles attribute to .new. The return value is the currently active source handle or Nil if the source handle queue has been exhausted. Coerces Cool source "handles" to IO::Path; opens IO::Path and unopened
method on-switch #
One of the attributes that can be set during .new call and changed later by assigning to. By default is not specified. Takes a Callable with .count of 0, 1, 2, or Inf. Gets called every time .next-handle is, which happens once during .new call and then each
method open #
method open(IO::CatHandle:D: --> IO::CatHandle:D)
Returns IO::CatHandle:D.
Returns the invocant. The intent of this method is to merely make CatHandle workable with things that open IO::Handle. You never have to call this method intentionally.
method opened #
method opened(IO::CatHandle:D: --> Bool:D)
Returns Bool:D.
Returns True if the invocant has any source handles, False otherwise.
method path #
method path(IO::CatHandle:D:)
Returns the value of .path attribute of the currently active source handle, or Nil if the source handle queue has been exhausted. Basically, if your CatHandle is based on files, this is the way to get the path of the file the CatHandle is currently reading from.
method read #
method read(IO::CatHandle:D: Int(Cool:D) $bytes = 65536 --> Buf:D)
Returns Buf:D.
Reads up to $bytes bytes from the handle and returns them in a Buf. $bytes defaults to an implementation-specific value (in Rakudo, the value of $*DEFAULT-READ-ELEMS, which by default is set to 65536). It is permitted to call this method on handles that are not in binary mode.
method readchars #
method readchars(IO::CatHandle:D: Int(Cool:D) $chars = 65536 --> Str:D)
Returns Str:D.
Returns a Str of up to $chars characters read from the handle. $chars defaults to an implementation-specific value (in Rakudo, the value of $*DEFAULT-READ-ELEMS, which by default is set to 65536). It is NOT permitted to call this method on handles opened in binary mode and doing so will result in X::IO::BinaryMode exception being thrown.
method seek #
method seek(IO::CatHandle:D: |c)
Calls .seek on the currently active source handle, forwarding it all the arguments, and returns the result. Returns Nil if the source handle queue has been exhausted. NOTE: this method does NOT perform any source handle switching, so seeking past the end of the current source handle will NOT seek to the next
method tell #
method tell(IO::CatHandle:D: --> Int:D)
Returns Int:D.
Calls .tell on the currently active source handle and returns the result. Returns Nil if the source handle queue has been exhausted.
method slurp #
method slurp(IO::CatHandle:D:)
Reads all of the available input from all the source handles and returns it as a Buf if the handle is in binary mode or as a Str otherwise. Returns Nil if the source handle queue has been exhausted.
method split #
method split(IO::CatHandle:D: |args --> Seq:D)
Returns Seq:D.
Read the handle and processes its contents the same way Str.split does, taking the same arguments. Implementations may slurp the contents of all the source handles in their entirety when this method is called.
method Str #
method Str(IO::CatHandle:D: --> Str:D)
Returns Str:D.
Calls .Str on the currently active source handle and returns the result. If the source handle queue has been exhausted, returns an implementation-defined string ('<closed IO::CatHandle>' in Rakudo).
method Supply #
method Supply(IO::CatHandle:D: :$size = 65536 --> Supply:D)
Returns Supply:D.
Returns a Supply fed with either .read, if the handle is in binary mode, or with .readchars, if it isn't, with reads of :$size bytes or characters. :$size defaults to an implementation-specific value (in Rakudo, the value of $*DEFAULT-READ-ELEMS, which by default is set to 65536).
method t #
method t(IO::CatHandle:D: --> Bool:D)
Returns Bool:D.
Calls .t, which tells if the handle is a TTY, on the currently active source handle and returns the result. If the source handle queue has been exhausted, returns False.
method unlock #
method unlock(IO::CatHandle:D:)
Same as IO::Handle.unlock. Returns Nil if the source handle queue has been exhausted. Unlocks only the currently active source handle. The .on-switch Callable can be used to conveniently lock/unlock the handles as they're being processed by the CatHandle.
method words #
method words(IO::CatHandle:D: $limit = Inf, :$close --> Seq:D)
Returns Seq:D.
Same as IO::Handle.words (including the caveat about more data read than needed to make some number of words). Note that a boundary between source handles is considered to be word boundary. Note: if :$close is False, fully-consumed handles are still going to be closed.
Examples, run three ways #
Every example below comes from the official documentation, together with the output that documentation asserts. Each was then executed by Rakudo and by Raku++ when this page was built. Where the three agree, one result is shown; where they do not, all three are — because which of them is wrong is exactly the information worth having.
8 all-differ · 7 no-output · 11 not-runnable · 2 rakupp-differs
:$nl-in = ["\n", "\r\n"], Str :$encoding, Bool :$bin)
Not executed: the documentation states no expected output for this example.
(my $f1 = 'foo'.IO).spurt: "A\nB\nC\n";
(my $f2 = 'bar'.IO).spurt: "D\nE\n";
with IO::CatHandle.new: $f1, $f2 {
# .chomp is True by default:
(.get xx 2).raku.say; # OUTPUT: «("A", "B").Seq»
.chomp = False;
(.get xx 3).raku.say; # OUTPUT: «("C\n", "D\n", "E\n").Seq»
.close
}Neither engine can run this in isolation — the example depends on context from the surrounding text.
(my $f1 = 'foo'.IO).spurt: "A\nB\nC";
(my $f2 = 'bar'.IO).spurt: "DxEx";
with IO::CatHandle.new: $f1, $f2 {
# .nl-in is ["\n", "\r\n"] by default:
(.get xx 2).raku.say; # OUTPUT: «("A", "B").Seq»
.nl-in = 'x';
(.get xx 3).raku.say; # OUTPUT: «("C", "D", "E").Seq»
.close
}Neither engine can run this in isolation — the example depends on context from the surrounding text.
with IO::CatHandle.new: @bunch-of-handles {
say .readchars: 42;
.close; # we are done; close all the open handles
}Not executed: the documentation states no expected output for this example.
(my $f1 = 'foo'.IO).spurt: 'foo';
(my $f2 = 'bar'.IO).spurt: 'bar';
IO::CatHandle.new($f1, $f2).comb(2).raku.say;
# OUTPUT: «("fo", "ob", "ar").Seq»("fo", "ob", "ar").Seq
("fi", "le", "s\t", "fo", "o ", "ba", "r").Seq
All three differ. Needs a human.
Not yet examined. Which of these is correct has not been established — do not treat either engine as settled here.
(my $f1 = 'foo'.IO).spurt: 'I ♥ Raku';
(my $f2 = 'bar'.IO).spurt: 'meow';
with IO::CatHandle.new: $f1, $f2 {
# .encoding is 'utf8' by default:
.readchars(5).say; # OUTPUT: «I ♥ R»
.encoding: Nil; # switch to binary mode
.slurp.say; # OUTPUT: «Buf[uint8]:0x<6B 75 6D 65 6F 77>»
}Neither engine can run this in isolation — the example depends on context from the surrounding text.
(my $f1 = 'foo'.IO).spurt: 'foo';
(my $f2 = 'bar'.IO).spurt: 'bar';
with IO::CatHandle.new: :on-switch{ print 'SWITCH! ' }, $f1, $f2 {
# OUTPUT: «SWITCH! »
.eof.say; # OUTPUT: «False»
.readchars(3);
.eof.say; # OUTPUT: «SWITCH! False»
.slurp; # OUTPUT: «SWITCH! »
.eof.say; # OUTPUT: «True»
}Neither engine can run this in isolation — the example depends on context from the surrounding text.
(my $f1 = 'foo'.IO).spurt: "a\nb\nc"; (my $f2 = 'bar'.IO).spurt: "d\ne"; my $cat = IO::CatHandle.new: $f1, $f2; .say while $_ = $cat.get; # OUTPUT: «abcde»
Neither engine can run this in isolation — the example depends on context from the surrounding text.
(my $f1 = 'foo'.IO).spurt: 'I ♥ Raku'; (my $f2 = 'bar'.IO).spurt: 'meow'; my $cat = IO::CatHandle.new: $f1, $f2; .say while $_ = $cat.getc; # OUTPUT: «I ♥ Rakumeow»
Neither engine can run this in isolation — the example depends on context from the surrounding text.
# print at most the first 2 lines of each file in $*ARGFILES: .say for flat $*ARGFILES.handles.map: *.lines: 2
Not executed: the documentation states no expected output for this example.
(my $file1 := 'file1'.IO).spurt: "1a\n1b\n1c";
(my $file2 := 'file2'.IO).spurt: "2a\n2b\n2c";
(my $file3 := 'file3'.IO).spurt: "3a\n3b\n3c";
my $cat := IO::CatHandle.new: $file1, $file2, $file3;
for $cat.handles {
say .lines: 2;
$cat.next-handle;
}
# OUTPUT: «(1a 1b)(3a 3b)»(1a 1b)
(3a 3b)
(1a 1b)
(3a 3b)
Raku++ disagrees with both the documentation and Rakudo — a defect.
(my $f1 = 'foo'.IO).spurt: "foo\nbar";
(my $f2 = 'bar'.IO).spurt: 'meow';
IO::CatHandle.new($f1, $f2).lines.raku.say;
# OUTPUT: «("foo", "bar", "meow").Seq»("foo", "bar", "meow").Seq
("files\tfoo bar",).Seq
All three differ. Needs a human.
Not yet examined. Which of these is correct has not been established — do not treat either engine as settled here.
(my $f1 = 'foo'.IO).spurt: 'foo';
(my $f2 = 'bar'.IO).spurt: 'bar';
with IO::CatHandle.new: $f1, $f2, $*IN {
repeat { .native-descriptor.say } while .next-handle;
# OUTPUT: «13139»
}Neither engine can run this in isolation — the example depends on context from the surrounding text.
(my $f1 = 'foo'.IO).spurt: "a\nb";
(my $f2 = 'bar'.IO).spurt: "c\nd";
with IO::CatHandle.new: :on-switch{ say '▸ Switching' }, $f1, $f2 {
say 'one';
.next-handle.^name.say;
say 'two';
.next-handle.^name.say;
say 'three';
.next-handle.^name.say;
# OUTPUT:
# ▸ Switching
# one
# ▸ Switching
# IO::Handle
# two
# ▸ Switching
# Nil
# three
# ▸ Switching
# Nil
}Not executed: the documentation states no expected output for this example.
has &.on-switch is rw
Not executed: the documentation states no expected output for this example.
(my $f1 = 'foo'.IO).spurt: "A\nB\nC";
(my $f2 = 'bar'.IO).spurt: "D\nE";
my $line;
my $cat = IO::CatHandle.new: :on-switch{ $line = 1 }, $f1, $f2;
say "{$cat.path}:{$line++} $_" for $cat.lines;
# OUTPUT:
# foo:1 A
# foo:2 B
# foo:3 C
# bar:1 D
# bar:2 ENot executed: the documentation states no expected output for this example.
my @old-stuff;
sub on-switch ($new, $old) {
$new and $new.seek: 1, SeekFromBeginning;
$old and @old-stuff.push: $old.open.slurp: :close;
}
(my $f1 = 'foo'.IO).spurt: "A\nB\nC";
(my $f2 = 'bar'.IO).spurt: "D\nE";
my $cat = IO::CatHandle.new: :&on-switch, $f1, $f2;
$cat.lines.raku.say; # OUTPUT: «("", "B", "C", "", "E").Seq»
@old-stuff.raku.say; # OUTPUT: «["A\nB\nC", "D\nE"]»("", "B", "C", "", "E").Seq
["A\nB\nC", "D\nE"]
("files\tfoo bar",).Seq
[]
All three differ. Needs a human.
Not yet examined. Which of these is correct has not been established — do not treat either engine as settled here.
say IO::CatHandle.new .opened; # OUTPUT: «False»
say IO::CatHandle.new($*IN).opened; # OUTPUT: «True»
(my $f1 = 'foo'.IO).spurt: "A\nB\nC";
with IO::CatHandle.new: $f1 {
.opened.say; # OUTPUT: «True»
.slurp;
.opened.say; # OUTPUT: «False»
}False
True
True
False
False
True
True
False
Raku++ disagrees with both the documentation and Rakudo — a defect.
(my $f1 = 'foo'.IO).spurt: "A\nB\nC";
(my $f2 = 'bar'.IO).spurt: "D\nE";
my $line;
my $cat = IO::CatHandle.new: :on-switch{ $line = 1 }, $f1, $f2;
say "{$cat.path}:{$line++} $_" for $cat.lines;
# OUTPUT:
# foo:1 A
# foo:2 B
# foo:3 C
# bar:1 D
# bar:2 ENot executed: the documentation states no expected output for this example.
(my $f1 = 'foo'.IO).spurt: 'meow';
(my $f2 = 'bar'.IO).spurt: Blob.new: 4, 5, 6;
with IO::CatHandle.new: :bin, $f1, $f2 {
say .read: 2; # OUTPUT: «Buf[uint8]:0x<6d 65>»
say .read: 2000; # OUTPUT: «Buf[uint8]:0x<6f 77 04 05 06>»
}
# Non-binary mode is OK too:
with IO::CatHandle.new: $f1, $f2 {
say .get; # OUTPUT: «meow»
say .read: 2000; # OUTPUT: «Buf[uint8]:0x<04 05 06>»
}Neither engine can run this in isolation — the example depends on context from the surrounding text.
(my $f1 = 'foo'.IO).spurt: 'Raku loves to';
(my $f2 = 'bar'.IO).spurt: ' meow';
with IO::CatHandle.new: $f1, $f2 {
say .readchars: 11; # OUTPUT: «Raku loves »
say .readchars: 1000; # OUTPUT: «to meow»
}Neither engine can run this in isolation — the example depends on context from the surrounding text.
(my $f1 = 'foo'.IO).spurt: 'foo';
(my $f2 = 'bar'.IO).spurt: 'bar';
with IO::CatHandle.new: $f1, $f2 {
.get.say; # OUTPUT: «foo»
.seek: -2, SeekFromCurrent;
.readchars(2).say; # OUTPUT: «oo»
.seek: 1000, SeekFromCurrent; # this doesn't switch to second handle!
.readchars(3).say; # OUTPUT: «bar»
try .seek: -4; # this won't seek to previous handle!
say ~$!; # OUTPUT: «Failed to seek in filehandle: 22»
}Neither engine can run this in isolation — the example depends on context from the surrounding text.
(my $f1 = 'foo'.IO).spurt: 'foo';
(my $f2 = 'bar'.IO).spurt: 'bar';
with IO::CatHandle.new: $f1, $f2 {
.get.say; # OUTPUT: «foo»
.tell.say; # OUTPUT: «3»
.seek: -2, SeekFromCurrent;
.tell.say; # OUTPUT: «1»
say .readchars: 3; # OUTPUT: «oob»
.tell.say; # OUTPUT: «2»
}Neither engine can run this in isolation — the example depends on context from the surrounding text.
(my $f1 = 'foo'.IO).spurt: 'foo'; (my $f2 = 'bar'.IO).spurt: 'bar'; IO::CatHandle.new( $f1, $f2).slurp.say; # OUTPUT: «foobar» IO::CatHandle.new(:bin, $f1, $f2).slurp.say; # OUTPUT: «Buf[uint8]:0x<66 6f 6f 62 61 72>» IO::CatHandle.new .slurp.say; # OUTPUT: «Nil»
foobar
Buf[uint8]:0x<66 6f 6f 62 61 72>
Nil
foo
foo
All three differ. Needs a human.
Not yet examined. Which of these is correct has not been established — do not treat either engine as settled here.
(my $f1 = 'foo'.IO).spurt: 'foo';
(my $f2 = 'bar'.IO).spurt: 'bar';
IO::CatHandle.new($f1, $f2).split(/o+/).raku.say;
# OUTPUT: «("f", "bar").Seq»("f", "bar").Seq
("files\tf", " bar").Seq
All three differ. Needs a human.
Not yet examined. Which of these is correct has not been established — do not treat either engine as settled here.
(my $f1 = 'foo'.IO).spurt: 'foo';
(my $f2 = 'bar'.IO).spurt: 'bar';
react whenever IO::CatHandle.new($f1, $f2).Supply: :2size {.say}
# OUTPUT: «foobar»
react whenever IO::CatHandle.new(:bin, $f1, $f2).Supply: :2size {.say}
# OUTPUT: «Buf[uint8]:0x<66 6f>Buf[uint8]:0x<6f 62>Buf[uint8]:0x<61 72>»fo
ob
ar
Buf[uint8]:0x<66 6f>
Buf[uint8]:0x<6f 62>
Buf[uint8]:0x<61 72>
files => ["foo".IO "bar".IO]
files => ["foo".IO "bar".IO]
All three differ. Needs a human.
Not yet examined. Which of these is correct has not been established — do not treat either engine as settled here.
(my $f1 = 'foo'.IO).spurt: 'foo';
with IO::CatHandle.new: $f1, $*IN {
repeat { .t.say } while .next-handle; # OUTPUT: «FalseTrue»
}False
True
False
False
All three differ. Needs a human.
Not yet examined. Which of these is correct has not been established — do not treat either engine as settled here.
(my $f1 = 'foo'.IO).spurt: 'foo bar';
(my $f2 = 'bar'.IO).spurt: 'meow';
IO::CatHandle.new($f1, $f2).words.raku.say;
# OUTPUT: «("foo", "bar", "meow").Seq»("foo", "bar", "meow").Seq
("files", "foo", "bar").Seq
All three differ. Needs a human.
Not yet examined. Which of these is correct has not been established — do not treat either engine as settled here.