Multiplexer.pm revision 1.4
1package TAP::Parser::Multiplexer;
2
3use strict;
4use warnings;
5
6use IO::Select;
7
8use base 'TAP::Object';
9
10use constant IS_WIN32 => $^O =~ /^(MS)?Win32$/;
11use constant IS_VMS => $^O eq 'VMS';
12use constant SELECT_OK => !( IS_VMS || IS_WIN32 );
13
14=head1 NAME
15
16TAP::Parser::Multiplexer - Multiplex multiple TAP::Parsers
17
18=head1 VERSION
19
20Version 3.42
21
22=cut
23
24our $VERSION = '3.42';
25
26=head1 SYNOPSIS
27
28    use TAP::Parser::Multiplexer;
29
30    my $mux = TAP::Parser::Multiplexer->new;
31    $mux->add( $parser1, $stash1 );
32    $mux->add( $parser2, $stash2 );
33    while ( my ( $parser, $stash, $result ) = $mux->next ) {
34        # do stuff
35    }
36
37=head1 DESCRIPTION
38
39C<TAP::Parser::Multiplexer> gathers input from multiple TAP::Parsers.
40Internally it calls select on the input file handles for those parsers
41to wait for one or more of them to have input available.
42
43See L<TAP::Harness> for an example of its use.
44
45=head1 METHODS
46
47=head2 Class Methods
48
49=head3 C<new>
50
51    my $mux = TAP::Parser::Multiplexer->new;
52
53Returns a new C<TAP::Parser::Multiplexer> object.
54
55=cut
56
57# new() implementation supplied by TAP::Object
58
59sub _initialize {
60    my $self = shift;
61    $self->{select} = IO::Select->new;
62    $self->{avid}   = [];                # Parsers that can't select
63    $self->{count}  = 0;
64    return $self;
65}
66
67##############################################################################
68
69=head2 Instance Methods
70
71=head3 C<add>
72
73  $mux->add( $parser, $stash );
74
75Add a TAP::Parser to the multiplexer. C<$stash> is an optional opaque
76reference that will be returned from C<next> along with the parser and
77the next result.
78
79=cut
80
81sub add {
82    my ( $self, $parser, $stash ) = @_;
83
84    if ( SELECT_OK && ( my @handles = $parser->get_select_handles ) ) {
85        my $sel = $self->{select};
86
87        # We have to turn handles into file numbers here because by
88        # the time we want to remove them from our IO::Select they
89        # will already have been closed by the iterator.
90        my @filenos = map { fileno $_ } @handles;
91        for my $h (@handles) {
92            $sel->add( [ $h, $parser, $stash, @filenos ] );
93        }
94
95        $self->{count}++;
96    }
97    else {
98        push @{ $self->{avid} }, [ $parser, $stash ];
99    }
100}
101
102=head3 C<parsers>
103
104  my $count   = $mux->parsers;
105
106Returns the number of parsers. Parsers are removed from the multiplexer
107when their input is exhausted.
108
109=cut
110
111sub parsers {
112    my $self = shift;
113    return $self->{count} + scalar @{ $self->{avid} };
114}
115
116sub _iter {
117    my $self = shift;
118
119    my $sel   = $self->{select};
120    my $avid  = $self->{avid};
121    my @ready = ();
122
123    return sub {
124
125        # Drain all the non-selectable parsers first
126        if (@$avid) {
127            my ( $parser, $stash ) = @{ $avid->[0] };
128            my $result = $parser->next;
129            shift @$avid unless defined $result;
130            return ( $parser, $stash, $result );
131        }
132
133        unless (@ready) {
134            return unless $sel->count;
135            @ready = $sel->can_read;
136        }
137
138        my ( $h, $parser, $stash, @handles ) = @{ shift @ready };
139        my $result = $parser->next;
140
141        unless ( defined $result ) {
142            $sel->remove(@handles);
143            $self->{count}--;
144
145            # Force another can_read - we may now have removed a handle
146            # thought to have been ready.
147            @ready = ();
148        }
149
150        return ( $parser, $stash, $result );
151    };
152}
153
154=head3 C<next>
155
156Return a result from the next available parser. Returns a list
157containing the parser from which the result came, the stash that
158corresponds with that parser and the result.
159
160    my ( $parser, $stash, $result ) = $mux->next;
161
162If C<$result> is undefined the corresponding parser has reached the end
163of its input (and will automatically be removed from the multiplexer).
164
165When all parsers are exhausted an empty list will be returned.
166
167    if ( my ( $parser, $stash, $result ) = $mux->next ) {
168        if ( ! defined $result ) {
169            # End of this parser
170        }
171        else {
172            # Process result
173        }
174    }
175    else {
176        # All parsers finished
177    }
178
179=cut
180
181sub next {
182    my $self = shift;
183    return ( $self->{_iter} ||= $self->_iter )->();
184}
185
186=head1 See Also
187
188L<TAP::Parser>
189
190L<TAP::Harness>
191
192=cut
193
1941;
195