class IO::CatHandle

Use multiple IO handles as if they were one
class IO::CatHandle is IO::Handle { }

This class has been available in Rakudo since version 2017.06.

The IO::CatHandle class provides a means to create an IO::Handle that seamlessly gathers input from multiple IO::Handle and IO::Pipe sources.

All of the IO::Handle's methods are implemented, and while attempt to use write methods will (currently) throw an exception; an IO::CatHandle is usable anywhere a read-only IO::Handle can be used.

Methods

method new

method new(*@handles, :&on-switch, :$chomp = True,
           :$nl-in = ["\n", "\r\n"], Str :$encoding, Bool :$bin)

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 through the @handles list, processing each argument as follows:

  • the Cool objects will be coerced to IO::Path;

  • IO::Path objects will be opened for reading using the IO::CatHandle's (invocant's) attributes for open calls;

  • un-opened IO::Handle objects will be opened in the same fashion as IO::Path objects;

  • and already opened IO::Handle objects will have all of their attributes set to the attributes of the invocant IO::CatHandle.

In short, all the @handles end up as IO::Handle objects opened in the same mode and with the same attributes as the invocant IO::CatHandle.

See .on-switch method for details on the :&on-switch named argument, which by default is not set.

The :$encoding named argument specifies the handle's encoding and accepts the same values as IO::Handle.encoding. Set :$bin named argument to True if you wish the handle to be in binary mode. Attempting to specify both a defined :$encoding and a True :$bin is a fatal error resulting in X::IO::BinaryAndEncoding exception thrown. If neither :$encoding is set nor :$bin set to a true value, the handle will default to utf8 encoding.

The :$chomp and :$nl-in arguments have the same meaning as in IO::Handle and take and default to the same values.

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.

(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
}

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.

(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
}

method close

method close(IO::CatHandle:D: --> 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.

with IO::CatHandle.new: @bunch-of-handles {
    say .readchars: 42;
    .close; # we are done; close all the open handles
}

method comb

method comb(IO::CatHandle:D: |args --> 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.

(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␤»

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.

(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>␤»
}

method eof

method eof(IO::CatHandle:D: --> 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.

(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␤»
}

The same caveats for non-seekable handles and empty files that apply to IO::Handle.eof apply here.

method get

method get(IO::CatHandle:D: --> 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 X::IO::BinaryMode exception being thrown.

(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: «a␤b␤c␤d␤e␤»

method getc

method getc(IO::CatHandle:D: --> 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.

(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␤ ␤♥␤ ␤R␤a␤k␤u␤m␤e␤o␤w␤»

method handles

Defines as:

method handles(IO::CatHandle:D: --> Seq:D)

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:

# print at most the first 2 lines of each file in $*ARGFILES:
    .say for flat $*ARGFILES.handles.map: *.lines: 2

It is acceptable to call this method multiple times; .handles.head is a valid idiom for obtaining the currently-active handle. If, between reification of the elements of the returned Seq the handles get switched by some other means, the next element produced by the Seq would be the next handle of the invocant, not the handle that would've been produced if no switching occurred:

(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)␤»

Likewise, reifying the returned Seq consumes the invocant's source handles and once it is fully reified the invocant becomes fully-consumed.

method IO

method IO(IO::CatHandle:D:)

Alias for .path

method lines

method lines(IO::CatHandle:D: $limit = Inf, :$close --> Seq:D)

Same as IO::Handle.lines. Note that a boundary between source handles is considered to be a newline break.

(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␤»

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)

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 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 or IO::Path objects.

(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: «13␤13␤9␤»
}

method next-handle

method next-handle(IO::CatHandle:D: --> 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 IO::Handle source handles for reading using the invocant's $.nl-in, $.chomp, and $.encoding attributes; those same attributes of already-opened IO::Handle objects will be changed to the values of the invocant's attributes.

This method is called automatically whenever CatHandle's methods require a switch to the next source handle, triggers .on-switch Callable to be called, and is called once during .new call. The .on-switch will continue to be triggered each time this method is called, even after the source handle queue has been exhausted. Note that generally reaching the EOF of the currently active source handle does not trigger the .next-handle call, but rather further read operations that need more data do.

(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
}

method on-switch

has &.on-switch is rw

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 time a source handle is switched to the next one in the queue, or when the .next-handle method is called manually.

If the .count of &.on-switch is 0, it receives no arguments; if it's 1, it receives the currently active handle, and if it's 2 or Inf, it receives the currently active handle, and the last active handle as positional arguments (in that order). On the very first &.on-switch execution, the "last active handle" argument is Nil. Upon source handle queue exhaustion the "currently active handle" argument is Nil, and all the executions made afterwards have both arguments as Nil.

(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 E
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"]␤»

method open

method open(IO::CatHandle:D: --> 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 True if the invocant has any source handles, False otherwise.

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␤»
}

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.

(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 E

method read

method read(IO::CatHandle:D: Int(Cool:D) $bytes = 65536 --> 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.

(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>␤»
}

method readchars

method readchars(IO::CatHandle:D: Int(Cool:D) $chars = 65536 --> 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.

(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␤»
}

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 source handle in the queue and seeking past the beginning of the current source handle is a fatal error. Also see .next-handle, to learn the details on when source handles are switched.

(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␤»
}

method tell

method tell(IO::CatHandle:D: --> Int:D)

Calls .tell on the currently active source handle and returns the result. Returns Nil if the source handle queue has been exhausted.

(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␤»
    }

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.

(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␤»

method split

method split(IO::CatHandle:D: |args --> 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.

(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␤»

method Str

method Str(IO::CatHandle:D: --> 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 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).

(my $f1 = 'foo'.IO).spurt: 'foo';
(my $f2 = 'bar'.IO).spurt: 'bar';
react whenever IO::CatHandle.new($f1, $f2).Supply: :2size {.say}
# OUTPUT: «fo␤ob␤ar␤»

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>␤»

method t

method t(IO::CatHandle:D: --> 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.

(my $f1 = 'foo'.IO).spurt: 'foo';
with IO::CatHandle.new: $f1, $*IN {
    repeat { .t.say } while .next-handle; # OUTPUT: «False␤True␤»
}

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)

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.

(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␤»

Note: if :$close is False, fully-consumed handles are still going to be closed.

See Also

class Attribute

Member variable

class Cancellation

Removal of a task from a Scheduler before normal completion

class Channel

Thread-safe queue for sending values from producers to consumers

class CompUnit

CompUnit

class CompUnit::Repository::FileSystem

CompUnit::Repository::FileSystem

class CompUnit::Repository::Installation

CompUnit::Repository::Installation

class Distro

Distribution related information

class Grammar

Formal grammar made up of named regexes

class IO::ArgFiles

Iterate over contents of files specified on command line

class IO::Handle

Opened file or stream

class IO::Notification

Asynchronous notification for file and directory changes

class IO::Notification::Change

Changes in a file, produced by watch-file

class IO::Path

File or directory path

class IO::Path::Cygwin

IO::Path pre-loaded with IO::Spec::Cygwin

class IO::Path::Parts

IO::Path parts encapsulation

class IO::Path::QNX

IO::Path pre-loaded with IO::Spec::QNX

class IO::Path::Unix

IO::Path pre-loaded with IO::Spec::Unix

class IO::Path::Win32

IO::Path pre-loaded with IO::Spec::Win32

class IO::Pipe

Buffered inter-process string or binary stream

class IO::Socket::Async

Asynchronous socket in TCP or UDP

class IO::Socket::Async::ListenSocket

A tap for listening TCP sockets

class IO::Socket::INET

TCP Socket

class IO::Spec

Platform specific operations on file and directory paths

class IO::Spec::Cygwin

Platform specific operations on file and directory paths for Cygwin

class IO::Spec::QNX

Platform specific operations on file and directory paths QNX

class IO::Spec::Unix

Platform specific operations on file and directory paths for POSIX

class IO::Spec::Win32

Platform specific operations on file and directory paths for Windows

class IO::Special

Path to special I/O device

class Kernel

Kernel related information

class Lock

A low-level, re-entrant, mutual exclusion lock

class Lock::ConditionVariable

Condition variables used in locks

class Match

Result of a successful regex match

class Pod::Block

Block in a Pod document

class Pod::Block::Code

Verbatim code block in a Pod document

class Pod::Block::Comment

Comment in a Pod document

class Pod::Block::Declarator

Declarator block in a Pod document

class Pod::Block::Named

Named block in a Pod document

class Pod::Block::Para

Paragraph in a Pod document

class Pod::Block::Table

Table in a Pod document

class Pod::Defn

Pod definition list

class Pod::FormattingCode

Pod formatting code

class Pod::Heading

Heading in a Pod document

class Pod::Item

Item in a Pod enumeration list

class Proc

Running process (filehandle-based interface)

class Proc::Async

Running process (asynchronous interface)

class Promise

Status/result of an asynchronous computation

class Regex

String pattern

class Semaphore

Control access to shared resources by multiple threads

class Supplier

Live Supply factory

class Supplier::Preserving

Cached live Supply factory

class Supply

Asynchronous data stream with multiple subscribers

class Tap

Subscription to a Supply

class Thread

Concurrent execution of code (low-level)

class ThreadPoolScheduler

Scheduler that distributes work among a pool of threads

class Unicode

Unicode related information

class VM

Raku Virtual Machine related information

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.