The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
#!/usr/bin/perl

use strict;
use warnings;

use Test::More;

use ok 'IO::Handle::Util' => qw(:all);

use Scalar::Util qw(blessed);

ok( IO::Handle->can("print"), "IO::Handle loaded" );
ok( FileHandle->can("tell"), "FileHandle loaded" );

ok( !blessed(*STDIN), "STDIN not blessed" );
eval { STDIN->tell };
is( $@, '', "but responds to methods" );

sub new_fh {
    my ( $mode, $string ) = @_;
    open my $fh, $mode, \$string;
    return wantarray ? ( $fh, \$string ) : $fh;
}

{
    my ( $fh, $buf ) = new_fh ">", "";

    my $sub = io_to_write_cb($fh);

    is( ref $sub, 'CODE', "io_to_write_cb makes a code ref" );

    $sub->("foo\n");

    is( $$buf, "foo\n", "first invocation" );

    $sub->("blah\n");

    is( $$buf, "foo\nblah\n", "second invocation" );
}

{
    my $str = '';
    my $sub = io_to_write_cb(\$str);

    $sub->("foo");

    is($str, "foo", "coerced from scalar ref");

    local $\ = "\n";
    local $, = ", ";

    $sub->(qw(foo bar));

    is( $str, "foofoobar", "immune to ORS and OFS" );
}

{
    my $fh = new_fh "<", "foo\nbar\n";

    is( ref($fh), 'GLOB', "PerlIO handle is a glob" );

    is( io_to_glob($fh), $fh, 'io_to_glob isa passthrough' );
}

{
    my $fh = io_from_array [qw(foo bar)];

    isnt( ref($fh), 'GLOB' );

    my $glob = io_to_glob($fh);

    is( ref($glob), "GLOB", "io_to_glob" );

    isa_ok( tied(*$glob), "IO::Handle::Util::Tie", "tied" );

    is_deeply(
        [ <$glob> ],
        [qw(foo bar)],
        "readline builtin",
    );
}

{
    my $fh = io_from_array [qw(foo bar)];

    isnt( ref($fh), 'GLOB' );

    is_deeply(
        [ <$fh> ],
        [qw(foo bar)],
        "readline builtin through overloading",
    );
}

{
    my $fh = new_fh "<", "foo\nbar\n";

    my $sub = io_to_read_cb($fh);

    is( ref $sub, 'CODE', "io_to_read_cb makes a code ref" );

    is( $sub->(), "foo\n", "like getline" );
    is( $sub->(), "bar\n", "like getline" );
    is( $sub->(), undef, "like getline" );
}

{
    my $fh = new_fh "<", "foo\nbar\nbaz\ngorch";

    local $/ = "a";
    is( $fh->getline, "foo\nba", "getline with IRS" );

    is( io_to_string($fh), "r\nbaz\ngorch", "slurp with io_to_string" );

    is( io_to_string($fh), "", "IO depleted" );

    is( io_to_string("foo"), "foo", "strings pass through" );
}

{
    my $fh = new_fh "<", "foo\nbar\nbaz\ngorch";

    is_deeply(
        io_to_array($fh),
        [
            "foo\n",
            "bar\n",
            "baz\n",
            "gorch",
        ],
        "io_to_array",
    );

    is_deeply(
        [ io_to_list(new_fh "<", "foo\nbar\nbaz\n") ],
        [ "foo\n", "bar\n", "baz\n" ],
        "io_to_list",
    );

    is_deeply(
        io_to_array([qw(foo bar)]),
        [qw(foo bar)],
        "passthrough",
    );

    is_deeply(
        [io_to_list([qw(foo bar)])],
        [qw(foo bar)],
        "passthrough list context",
    );
}

sub io_ok ($;$) {
    my ( $fh, $desc ) = @_;

    local $Test::Builder::Level = $Test::Builder::Level + 1;

    ok($fh, $desc || "got IO" );

    if ( blessed($fh) ) {
        can_ok( $fh, "getline", "print" );
    } else {
        ok( ref($fh) eq 'GLOB' && *{$fh}{IO}, "unblessed GLOB with IO" );
    }

    return $fh;
}

{
    my $str = "foo\nbar\n";

    foreach my $arg ( $str, \$str ) {
        io_ok( my $fh = io_from_any($arg), "IO from " . lc(ref(\$arg)) );

        ok( !$fh->eof, "not eof" );
        is( $fh->getline, "foo\n", "getline" );
        ok( !$fh->eof, "not eof" );
        is( $fh->getline, "bar\n", "getline" );
        ok( $fh->eof, "eof" );
        is( $fh->getline, undef, "getline" );
        ok( $fh->eof, "eof" );
    }
}

{
    io_ok( my $fh = io_from_any([qw(foo bar gorch)]), "from array");

    isa_ok( $fh, "IO::Handle::Iterator" );

    is( $fh->getline, "foo", "fake getline" );

    is_deeply(
        [ $fh->getlines ],
        [ qw(bar gorch) ],
        "fake lines preserved",
    );
}

{
    io_ok( my $fh = io_from_any([qw(foo bar gorch)]), "from array");

    isa_ok( $fh, "IO::Handle::Iterator" );

    is( $fh->getline, "foo", "fake getline" );

    is_deeply(
        [ <$fh> ],
        [ qw(bar gorch) ],
        "getlines via readline operator",
    );
}

{
    my $str_fh = IO::String->new("foo");

    can_ok( $str_fh, qw(getline print) );

    io_ok( my $fh = io_from_any($str_fh), "from IO::String" );

    is( $fh, $str_fh, "passthrough" );
}

{
    my $perlio_string = new_fh "<", "foo";

    ok( !blessed($perlio_string), "PerlIO string handle not blessed" );

    io_ok( my $fh = io_from_any($perlio_string), "from PerlIO string" );

    is( $fh, $perlio_string, "passthrough" );
}

{
    my $fh = io_from_thunk sub {
        return "foo\nbar\n";
    };

    io_ok( $fh, "from thunk" );

    ok( !$fh->eof, "not eof" );
    is( $fh->getline, "foo\nbar\n", "getline" );
    is( $fh->getline, undef, "getline" );
    ok( $fh->eof, "eof" );
}

{
    my $fh = io_from_thunk sub {
        return qw(
            foo
            bar
        );
    };

    io_ok( $fh, "from list thunk" );

    ok( !$fh->eof, "not eof" );
    is( $fh->getline, "foo", "getline" );
    is( $fh->getline, "bar", "getline" );
    is( $fh->getline, undef, "getline" );
    ok( $fh->eof, "eof" );
}

{
    my @array = qw(foo bar);

    my $fh = io_from_getline sub {
        if ( @array ) {
            return shift @array;
        } else {
            return;
        }
    };

    io_ok( $fh, "from getline callback" );

    ok( !$fh->eof, "not eof" );
    is( $fh->getline, "foo", "getline" );
    is( $fh->getline, "bar", "getline" );
    is( $fh->getline, undef, "getline" );
    ok( $fh->eof, "eof" );
}

{
    my $buf = '';

    my $fh = io_from_write_cb sub {
        $buf .= $_[0];
    };

    io_ok( $fh, "write cb" );

    $fh->print("foo");
    is( $buf, "foo", "print" );

    $buf = '';
    local $\ = "bar";
    $fh->print("baz");
    is( $buf, 'bazbar', "respects ORS" );

    $buf = '';
    $fh->say("baz");
    is( $buf, "baz\n", "say localizes ORS" );

    $buf = '';
    local $, = ", ";
    $\ = "\n";
    $fh->print(qw(foo bar gorch));
    is( $buf, "foo, bar, gorch\n", "respects OFS" );

    $buf = '';
    $fh->write("foobar", 4, 2);
    is( $buf, 'obar', "handles offset/length in write" );
}

foreach my $fake (
    IO::String->new("blah"),
    IO::String->new(do { my $x = "blah"; \$x }),
    scalar(new_fh("<", "hello")),
    scalar(new_fh(">", "hello")),
) {
    ok( !is_real_fh($fake), "not a real handle ($fake)" );
}

{
    open my $fh, "<", __FILE__ or die $!;
    ok( is_real_fh($fh), "real fh" );
}

done_testing;

# ex: set sw=4 et: