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::Prototype::Fallback';

sub check_write_fh {
    my ( $fh, $buf ) = @_;

    isa_ok( $fh, "IO::Handle::Prototype::Fallback" );
    isa_ok( $fh, "IO::Handle::Prototype" );
    isa_ok( $fh, "IO::Handle" );

    can_ok( $fh, qw(getline read print write) );

    eval { $fh->getline };
    like( $@, qr/getline/, "dies on missing callback" );

    eval { $fh->getc };
    like( $@, qr/getc/, "dies on missing callback" );

    eval { $fh->read };
    like( $@, qr/read/, "dies on missing callback" );

    eval { $fh->print("foo") };
    is( $@, '', "no error" );
    is( $$buf, "foo", "print worked" );

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

    eval { $fh->print("foo", "bar") };
    is( $@, '', "no error" );
    is( $$buf, "foofoo bar\n", "print worked" );

    eval { $fh->write("foo") };
    is( $@, '', "no error" );
    is( $$buf, "foofoo bar\nfoo", "write worked" );

    eval { $fh->syswrite("foo", 1, 1) };
    is( $@, '', "no error" );
    is( $$buf, "foofoo bar\nfooo", "write worked" );

    eval { $fh->printf("%d hens", 5) };
    is( $@, '', "no error" );
    is( $$buf, "foofoo bar\nfooo5 hens\n", "printf worked" );

    $\ = "%%";

    eval { $fh->print("foo") };
    is( $@, '', "no error" );
    is( $$buf, "foofoo bar\nfooo5 hens\nfoo%%", "print worked" );

    eval { $fh->say("Hello, World!") };
    is( $@, '', "no error" );
    is( $$buf, "foofoo bar\nfooo5 hens\nfoo%%Hello, World!\n", "say worked" );
}

{
    my $buf = '';

    my $fh = IO::Handle::Prototype::Fallback->new(
        print => sub {
            my ( $self, @stuff ) = @_;
            no warnings 'uninitialized';
            $buf .= join($,, @stuff) . $\;
        },
    );

    check_write_fh($fh, \$buf);
}

foreach my $write (qw(write syswrite)) {
    my $buf = '';

    my $fh = IO::Handle::Prototype::Fallback->new(
        $write => sub {
            my ( $self, $str, $length, $offset ) = @_;
            $buf .= substr($str, $offset || 0, $length || length($str));
        },
    );

    check_write_fh($fh, \$buf);
}

{
    my $buf = '';

    my $fh = IO::Handle::Prototype::Fallback->new(
        __write => sub { $buf .= $_[1] },
    );
}

my $str = <<'EOF';
OH HAI
I am a file
with
several lines

it's fun to read me

using
methods
EOF

sub check_read_fh {
    my $make = shift;

    {
        my $fh = $make->($str);

        isa_ok( $fh, "IO::Handle::Prototype::Fallback" );
        isa_ok( $fh, "IO::Handle::Prototype" );
        isa_ok( $fh, "IO::Handle" );

        can_ok( $fh, qw(getline read print write) );

        eval { $fh->print("foo") };
        like( $@, qr/print/, "dies on missing callback" );

        eval { $fh->say("foo") };
        like( $@, qr/say/, "dies on missing callback" );

        local $/ = "\n";

        ok( !$fh->eof, "not eof" );

        my $line = <$fh>;

        is( $line, "OH HAI\n", "getline" );

        is( $fh->read(my $buf, 4), 4, "read 4 chars" );

        is($buf, "I am", "read with length");

        is( $fh->read($buf, 4, 4), 4, "read 4 more chars" );

        is( $buf, "I am a f", "read with offset" );

        ok( !$fh->eof, "not eof" );

        $buf .= $fh->getline;

        is( $buf, "I am a file\n", "getline interleaved with read" );

        $/ = \10;

        $buf = $fh->getline;

        is( length($buf), ${ $/ }, "getline with ref in IRS" );
        is( $buf, "with\nsever", "correct data" );

        is( $fh->getc(), 'a', "getc" );

        $fh->ungetc(ord('%'));
        is( $fh->getc(), '%', 'ungetc' );

        $buf .= 'a';

        $/ = "\n";

        $buf .= $fh->getline;

        is( $buf, "with\nseveral lines\n", "with undef IRS" );

        ok( !$fh->eof, "not eof" );

        my @lines = <$fh>;

        ok( $fh->eof, "eof reached" );

        is_deeply(
            \@lines,
            [
                "\n",
                "it's fun to read me\n",
                "\n",
                "using\n",
                "methods\n",
            ],
            "getlines",
        );

        is( $fh->getline, undef, "getline at EOF" );
    }

    {
        my $fh = $make->(join "\n", qw(foo bar gorch baz));

        ok( !$fh->eof, "not eof" );

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

        ok( !$fh->eof, "not eof" );

        local $/;

        is( $fh->getline, "bar\ngorch\nbaz", "slurp" );

        ok( $fh->eof, "eof" );
    }

    {
        my $fh = $make->("foobar");

        ok( !$fh->eof, "not eof" );

        is( $fh->read(my $buf, 4), 4, "read" );
        is( $buf, "foob" );

        ok( !$fh->eof, "not eof" );

        is( $fh->read($buf, 4), 2, "read to EOF" );
        is( $buf, 'ar' );

        ok( $fh->eof, "eof" );
    }
}

foreach my $cb ( qw(__read getline) ) {
    check_read_fh(sub {
        my $str = shift;

        IO::Handle::Prototype::Fallback->new(
            $cb => sub {
                if ( defined $str ) {
                    my $ret = $str;
                    undef $str;
                    return $ret;
                } else {
                    return;
                }
            },
        );
    });

    check_read_fh(sub {
        my $str = shift;

        my @array = split //, $str;

        IO::Handle::Prototype::Fallback->new(
            $cb => sub {
                if ( @array ) {
                    return shift @array;
                } else {
                    return;
                }
            },
        );
    });
}

check_read_fh(sub {
    my $str = shift;

    IO::Handle::Prototype::Fallback->new(
        read => sub {
            my ( $self, undef, $length ) = @_;

            if ( length($str) > $length ) {
                $_[1] = substr($str, 0, $length, '');
            } else {
                $_[1] = $str;
                $str = '';
            }

            return length($_[1]);
        },
    );
});

done_testing;

# ex: set sw=4 et: