File Coverage

blib/lib/Text/ASCIIPipe.pm
Criterion Covered Total %
statement 3 95 3.1
branch 0 66 0.0
condition 0 3 0.0
subroutine 1 16 6.2
pod 9 9 100.0
total 13 189 6.8


line stmt bran cond sub pod time code
1             package Text::ASCIIPipe;
2             # See POD below.
3              
4             # TODO: sanitize line end from handlers!
5              
6 1     1   23717 use strict;
  1         2  
  1         1510  
7             # major.minor.bugfix, the latter two with 3 digits each
8             # or major.minor_alpha
9             our $VERSION = '1.001000';
10             $VERSION = eval $VERSION;
11              
12              
13             # State codes. The numerocal values are important for the code in this module (used as array indices).
14             our $funky = 0;
15             our $line = 1;
16             our $begin = 2; # From here identical to ASCII code
17             our $end = 3;
18             our $allend = 4;
19              
20             # The actualy control characters.
21             my %control = (stx=>"\002", eot=>"\003", etx=>"\004");
22              
23             # Map plain character codes to state codes.
24             # I do not really care about codes 0 and 1.
25             my @codemap = ($funky, $funky, $begin, $end, $allend);
26              
27             sub fetch
28             {
29 0     0 1   my $fh = shift;
30 0 0         $fh = \*STDIN unless defined $fh;
31              
32             # Note to self: MAC style line endings will only work here for an old-style MAC Perl. All others see one big line with CRs in it.
33 0           $_[0] = <$fh>;
34 0 0         return undef unless defined $_[0];
35 0           my $code = ord($_[0]);
36 0 0         return $line if $code > 4;
37 0           return $codemap[$code];
38             }
39              
40             # plain text means: first byte does not qualify as control code
41             sub plaintext
42             {
43 0     0 1   my $code = ord($_[0]);
44 0           return ($code > 4);
45             }
46              
47             sub process
48             {
49 0     0 1   my %arg = @_;
50              
51 0           my $fline;
52 0           my $lend = undef;
53 0           my $in = $arg{in};
54 0           my $out = $arg{out};
55 0 0         $out = \*STDOUT unless defined $out;
56 0 0         $arg{flush} = 1 unless defined $arg{flush};
57              
58             # The begin function is special because we ensure that the output gets flushed to prevent stalling of the pipe readers.
59             # Function table according to numerical value of state codes.
60             # A handler gets the current line as first in/output argument and the detected line end as second.
61 0 0   0     my $prefilter = defined $arg{pre} ? $arg{pre} : sub {};
  0            
62             my @handlers =
63             (
64 0     0     sub {} # 0 is some unknown crap. Ignore.
65 0     0     ,defined $arg{line} ? $arg{line} : sub {}
66             ,sub
67             {
68             # That flush may be better placed after $arg{end} ...
69 0 0   0     if($arg{flush})
70             {
71 0           my $old = select($out);
72 0           $|=1;
73 0           select($old);
74             }
75 0 0         &{$arg{begin}}(@_) if defined $arg{begin};
  0            
76             }
77 0     0     ,defined $arg{end} ? $arg{end} : sub {}
78 0     0     ,defined $arg{allend} ? $arg{allend} : sub {}
79 0 0         );
    0          
    0          
80              
81 0           my @gotcode; # Need to check if control codes were actually present.
82             my @prearg;
83 0 0         push(@prearg, $arg{handle}) if defined $arg{handle};
84 0           while(defined (my $state = fetch($in, $fline)))
85             {
86             # Store first encountered line end to be used for constructed lines.
87 0 0         unless(defined $lend)
88             {
89 0           $fline =~ m/([\012\015]*)$/;
90 0           $lend = $1;
91             }
92 0           $gotcode[$state] = 1;
93 0 0         if(not $gotcode[$begin])
94             {
95 0           my $extraline = '';
96             # Trigger begin hook implicitly when just encountering data.
97 0           &{$handlers[$begin]}(@prearg, $extraline, $lend);
  0            
98 0           print $out $extraline;
99 0           $gotcode[$begin] = 1;
100             }
101 0 0         if($state == $line)
102             {
103 0 0         next if &{$prefilter}(@prearg, $fline, $lend);
  0            
104             # A line handler shall modify the line.
105 0           &{$handlers[$state]}(@prearg, $fline, $lend);
  0            
106 0 0         print $out $fline if $fline ne '';
107             }
108             else
109             {
110             # Other handlers could generate something.
111 0           my $extraline = '';
112 0           &{$handlers[$state]}(@prearg, $extraline, $lend);
  0            
113 0 0         print $out ($state == $begin ? $fline.$extraline : $extraline.$fline);
114             }
115              
116 0 0         return if($state == $allend);
117             # If we got proper end code, clear records for next file.
118 0 0         @gotcode = () if($state == $end);
119             }
120              
121             # Make sure that handlers get called even for empty file, or partial transfer with missing end markers.
122             # I wonder if $lend should be simply guessed to "\n".
123 0           for my $c ($begin,$end,$allend)
124             {
125 0 0         next if $gotcode[$c];
126             # The handlers are allowed to generate some output.
127 0           my $extraline = '';
128 0           &{$handlers[$c]}(@prearg, $extraline, $lend);
  0            
129 0           print $out $extraline;
130             }
131             }
132              
133             # Simple shortcut to pull a full file from pipe handle into an output file.
134             # Returns indication if this was not the last file (so you can loop until it returns a non-true value).
135             # return value:
136             # <0: Pull was invalid, no data for file (not even an empty one).
137             # 0: Pull successful, but this was it, no more files to expect.
138             # >0: Pull successful and no indication that there is not more to come.
139              
140             sub pull_file
141             {
142 0     0 1   my ($from, $to) = @_;
143 0 0         $from = \*STDIN unless defined $from;
144 0 0         $to = \*STDOUT unless defined $to;
145 0           my $payload;
146             my $state;
147 0           while(defined ($state = fetch($from, $payload)))
148             {
149 0 0         next if $state == $begin;
150 0 0         last if $state != $line;
151 0           print $to $payload;
152             }
153              
154             # All fine: Loop ended with orderly file end marker.
155 0 0 0       return 1 if(defined $state and $state != $allend);
156             # Ended on EOF (or some esoteric error we still treat as such),
157             # as there was no allend or error before that, just assume normal end of things.
158 0 0         return 0 if(not defined $state);
159             # If we hit allend, we did not stop with an orderly file end,
160             # so must assume we got nothing at all.
161 0           return -1; # if($state == allend) is already implied
162             }
163              
164             # The senders.
165              
166             sub push_file
167             {
168 0     0 1   my ($from, $to) = @_;
169 0 0         $from = \*STDIN unless defined $from;
170 0 0         $to = \*STDOUT unless defined $to;
171              
172 0           file_begin($to);
173 0           while(<$from>)
174             {
175 0           print $to $_;
176             }
177 0           file_end($to);
178 0           return 1; # Should it return something special?
179             }
180              
181             sub file_begin
182             {
183 0     0 1   my $to = shift;
184 0 0         $to = \*STDOUT unless defined $to;
185              
186 0           print $to $control{stx}."\n";
187             }
188              
189             sub file_lines
190             {
191 0     0 1   my $to = shift;
192 0 0         $to = \*STDOUT unless defined $to;
193              
194 0           for(@_){ print $to $_; }
  0            
195             }
196              
197             sub file_end
198             {
199 0     0 1   my $to = shift;
200 0 0         $to = \*STDOUT unless defined $to;
201              
202 0           print $to $control{eot}."\n";
203             }
204              
205             sub done
206             {
207 0     0 1   my $to = shift;
208 0 0         $to = \*STDOUT unless defined $to;
209              
210 0           print $to $control{etx}."\n";
211             }
212              
213             1;
214              
215             __END__