File Coverage

blib/lib/Fred/Fish/DBUG/TIE.pm
Criterion Covered Total %
statement 95 134 70.9
branch 23 58 39.6
condition 9 15 60.0
subroutine 13 15 86.6
pod 4 4 100.0
total 144 226 63.7


line stmt bran cond sub pod time code
1             ###
2             ### Copyright (c) 2019 - 2025 Curtis Leach. All rights reserved.
3             ###
4             ### Based on the Fred Fish DBUG macros in C/C++.
5             ### This Algorithm is in the public domain!
6             ###
7             ### Module: Fred::Fish::DBUG::TIE
8              
9             =head1 NAME
10              
11             Fred::Fish::DBUG::TIE - Fred Fish library extension to trap STDERR & STDOUT.
12              
13             =head1 SYNOPSIS
14              
15             use Fred::Fish::DBUG::TIE;
16             or
17             require Fred::Fish::DBUG::TIE;
18              
19             =head1 DESCRIPTION
20              
21             F is an extension to the Fred Fish DBUG module that
22             allows your program to trap all output written to STDOUT & STDERR to also be
23             merged into your B logs.
24              
25             It's very usefull when a module that doesn't use B writes it's logging
26             information to your screen and you want to put this output into context with
27             your program's B logs.
28              
29             This is implemented via Perl's B feature. Please remember that perl only
30             allows one B per filehandle. But if multiple ties are required, this
31             module provides a way to chain them together.
32              
33             =head1 FUNCTIONS
34              
35             =over 4
36              
37             =cut
38              
39             package Fred::Fish::DBUG::TIE;
40              
41 1     1   1153 use strict;
  1         2  
  1         29  
42 1     1   3 use warnings;
  1         1  
  1         38  
43              
44 1     1   4 use vars qw( @ISA @EXPORT @EXPORT_OK $VERSION );
  1         1  
  1         52  
45 1     1   4 use Exporter;
  1         1  
  1         28  
46              
47             # TIE always assumes Fish calls are live.
48 1     1   4 use Fred::Fish::DBUG::ON;
  1         1  
  1         1158  
49              
50             # use Perl::OSType ':all';
51             # use FileHandle;
52             # use File::Basename;
53             # use Cwd 'abs_path';
54             # use Config qw( %Config );
55             # use Sub::Identify 'sub_fullname';
56              
57             $VERSION = "2.10";
58             @ISA = qw( Exporter );
59              
60             @EXPORT = qw(
61             DBUG_TIE_STDERR DBUG_TIE_STDOUT
62             DBUG_UNTIE_STDERR DBUG_UNTIE_STDOUT
63             );
64              
65             # ==============================================================================
66             # Start of TIE to STDOUT/STDERR Extenstion to this module ...
67             # ==============================================================================
68             # NOTE: You may not use AUTOLOAD in this modue. It will break the TIE Logic
69              
70             # Helper functions to make it easier to call things in Fred::Fish::DBUG::ON
71             # that are not exposed.
72              
73 22     22   72 sub _dbug_hack { return ( Fred::Fish::DBUG::ON::_dbug_hack (@_) ); }
74 11     11   41 sub _get_func_info { return ( Fred::Fish::DBUG::ON::_get_func_info (@_) ); }
75              
76              
77             =item DBUG_TIE_STDERR ( [$callback_func [, $ignore_chaining [, $caller ]]] )
78              
79             This method ties what's written to STDERR to also appear in the B logs
80             with the tag of "B". This B will happen even if the B
81             logging is currently turned off.
82              
83             If the B is already owned by this module then future calls just steals
84             the old B chain if not told to ignore it.
85              
86             It returns B<1> on a successful setup, and B<0> or B on failure.
87              
88             If I<$callback_func> is provided, each time you call a B command against
89             STDERR, it will call this function for you after it first writes the message to
90             B and then chains to the previous B or prints to STDERR. If either
91             step encounters any errors, the callback will not be made.
92              
93             The number of arguments the callback function expects is based on the context
94             of the print request. S<"print @list"> passes the callback @list. But
95             S<"printf $fmt, @values"> passes you the single S print message.
96              
97             Your callback should return B<1> on success or B<0>/B on failure. Any
98             failure is reported as the return value of the original B command. If
99             B has been redirected to your screen, B will be disabled during
100             the callback.
101              
102             If I<$ignore_chaining> is true, it will ignore any existing B against this
103             file handle. The default is to chain to it if a B exists. Assuming that
104             if you already have an established B that it must be important. So it
105             won't toss it in favor of this logging unless you explicitly tell it to do so!
106              
107             If I<$caller> is true, it will identify where in the code the trapped print
108             request can be found. If 0 it will surpress caller info. If undef it will use
109             the current I setting from DBUG_PUSH to make this decision.
110              
111             =cut
112              
113             # ==============================================================
114              
115             sub DBUG_TIE_STDERR
116             {
117 3     3 1 23 my $callback = shift;
118 3         7 my $ignore_chain = shift;
119 3         5 my $caller = shift;
120              
121 3         5 my $hd;
122 3         89 my $sts = open ( $hd, '>&', *STDERR );
123              
124 3 50       16 if ( $sts ) {
125 3         9 my $func = _get_func_info ($callback, "tie STDERR callback");
126              
127             # Get the previous tie if it exists & it was asked for.
128 3 50 50     21 my $old_tie = $ignore_chain ? "" : (tied (*STDERR) || "");
129              
130 3         27 my $h = tie ( *STDERR, __PACKAGE__, $hd, "STDERR", $func, $old_tie, $caller );
131 3         10 $sts = ( ref ($h) eq __PACKAGE__ );
132             }
133              
134 3         11 return ( $sts );
135             }
136              
137              
138             =item DBUG_TIE_STDOUT ( [$callback_func [, $ignore_chaining [, $caller ]]] )
139              
140             This method ties what's written to STDOUT to also appear in the B logs
141             with the tag of "B". This B will happen even if the B
142             logging is currently turned off.
143              
144             If the B is already owned by this module then future calls just steals
145             the old B chain if not told to ignore it.
146              
147             It returns B<1> on a successful setup, and B<0> or B on failure.
148              
149             See DBUG_TIE_STDERR for more info on the parameters.
150              
151             =cut
152              
153             # ==============================================================
154              
155             sub DBUG_TIE_STDOUT
156             {
157 8     8 1 86 my $callback = shift;
158 8         14 my $ignore_chain = shift;
159 8         30 my $caller = shift;
160              
161 8         17 my $hd;
162 8         191 my $sts = open ( $hd, '>&', *STDOUT );
163              
164 8 50       32 if ( $sts ) {
165 8         20 my $func = _get_func_info ($callback, "tie STDOUT callback");
166              
167             # Get the previous tie if it exists & it was asked for.
168 8 50 100     45 my $old_tie = $ignore_chain ? "" : (tied (*STDOUT) || "");
169              
170 8         58 my $h = tie ( *STDOUT, __PACKAGE__, $hd, "STDOUT", $func, $old_tie, $caller );
171 8         21 $sts = ( ref ($h) eq __PACKAGE__ );
172             }
173              
174 8         26 return ( $sts );
175             }
176              
177              
178             =item DBUG_UNTIE_STDERR ( )
179              
180             This method breaks the tie between STDERR and the B logs. Any writes to
181             STDERR after this call will no longer be written to B. It will not call
182             B if someone else owns the STDERR B.
183              
184             It returns B<1> on success, and B<0> on failure.
185              
186             Currently if it's chaining STDERR to a previous B it can't preserve that
187             inforation.
188              
189             =cut
190              
191             # ==============================================================
192              
193             sub DBUG_UNTIE_STDERR
194             {
195 3     3 1 20 my ($sts, $chain);
196 3         10 my $t = tied ( *STDERR ); # Can't untie while $t is still in scope!
197 3         9 my $pkg = ref ( $t );
198              
199 3 50       13 if ( $pkg eq "" ) {
    50          
200 0         0 $sts = 1; # Nothing tied ...
201              
202             } elsif ( $pkg ne __PACKAGE__ ) {
203 0         0 warn ("You can't use DBUG_UNTIE_STDERR to untie from package $pkg!\n");
204 0         0 $sts = 0;
205              
206             } else {
207 3         9 $chain = $t->{chain};
208 3         25 my $fh = $t->{fh};
209 3         80 close ( $fh );
210 3         10 $t = undef; # Force out of scope ... so untie will work!
211 3         27 untie ( *STDERR );
212 3         15 $sts = 1;
213             }
214              
215 3 50       12 if ( $chain ) {
216             # TODO: Put $chain as the new tie if I can figure out how to do it!
217             }
218              
219 3         8 return ($sts);
220             }
221              
222              
223             =item DBUG_UNTIE_STDOUT ( )
224              
225             This method breaks the tie between STDOUT and the B logs. Any writes to
226             STDOUT after this call will no longer be written to B. It will not call
227             B if someone else owns the STDOUT B.
228              
229             It returns B<1> on success, and B<0> on failure.
230              
231             Currently if it's chaining STDOUT to a previous B it can't preserve that
232             inforation.
233              
234             =cut
235              
236             # ==============================================================
237              
238             sub DBUG_UNTIE_STDOUT
239             {
240 7     7 1 70 my ($sts, $chain);
241 7         20 my $t = tied ( *STDOUT ); # Can't untie while $t is still in scope!
242 7         18 my $pkg = ref ( $t );
243              
244 7 50       25 if ( $pkg eq "" ) {
    50          
245 0         0 $sts = 1; # Nothing tied ...
246              
247             } elsif ( $pkg ne __PACKAGE__ ) {
248 0         0 warn ("You can't use DBUG_UNTIE_STDOUT to untie from package $pkg!\n");
249 0         0 $sts = 0;
250              
251             } else {
252 7         19 $chain = $t->{chain};
253 7         11 my $fh = $t->{fh};
254 7         169 close ( $fh );
255 7         17 $t = undef; # Force out of scope ... so untie will work!
256 7         44 untie ( *STDOUT );
257 7         30 $sts = 1;
258             }
259              
260 7 100       20 if ( $chain ) {
261             # TODO: Put $chain as the new tie if I can figure out how to do it!
262             }
263              
264 7         58 return ($sts);
265             }
266              
267             # ===========================================================================
268             # The required functions to implement the TIE ...
269             # ===========================================================================
270             # See: https://perldoc.perl.org/functions/tie.html
271             # ---------------------------------------------------------------------------
272              
273             # Initializes the tie ...
274             sub TIEHANDLE {
275 11     11   28 my $class = shift;
276 11         18 my $which = shift; # Linked file handle to *STDERR or *STDOUT ...
277 11         21 my $tag = shift; # "STDERR" or "STDOUT" ...
278 11         16 my $callback = shift; # An optional calback function to call ...
279 11         19 my $pkg = shift; # Current holder of the tie ... (or "" for none)
280 11         44 my $line = shift; # Include caller info in fish ... ?
281              
282             # Can't chain to myself, so just steal it's chain setting ...
283 11 50       32 $pkg = $pkg->{chain} if ( $pkg eq $class );
284              
285 11         156 my $self = bless ( { fh => $which,
286             tag => $tag,
287             callback => $callback,
288             chain => $pkg,
289             who => $line
290             }, $class );
291              
292 11         69 return ( $self );
293             }
294              
295              
296             # -------------------------------------------------------------
297             # Handles all calls to: "print STDxxx @args"
298              
299             sub PRINT {
300 11     11   141 my $self = shift;
301 11         31 my @args = @_;
302              
303 11         31 my $fh = $self->{fh}; # The untied file handle to print to.
304              
305             # -------------------------------------------------------------
306             # Were we called by something from within the DBUG module?
307             # If so we don't want to do anything with it besides writing
308             # this info the the proper file handle. Do anything else
309             # and we risk infinite recursion!
310             # -------------------------------------------------------------
311 11 50       27 my $ind = ( $self->{called_by_other_print_func} ) ? 2 : 1;
312 11   50     74 my $called_by = (caller($ind))[3] || "";
313 11         37 local $self->{called_by_other_print_func} = 0;
314              
315 11 50       33 if ( $called_by =~ m/^Fred::Fish::DBUG::/ ) {
316 0         0 return ( print $fh @args );
317             }
318              
319             # -------------------------------------------------------------
320             # Check if we trapped a print from the callback function itself!
321             # DBUG_PRINT results in infinite recursion if writing to screen!
322             # -------------------------------------------------------------
323 11         21 my $recursion = 0;
324 11 50       24 if ( $self->{callback_recursion} ) {
325 0         0 $recursion = 1;
326             } else {
327 11 100       33 my $other = ( $self->{tag} eq "STDERR" ) ? tied (*STDOUT) : tied (*STDERR);
328 11 50 66     36 $recursion = 1 if ( $other && $other->{callback_recursion} );
329             }
330              
331 11 50       27 if ( $recursion ) {
332             # Only write to fish if it's going to a file ...
333 0 0       0 if ( DBUG_EXECUTE ( $self->{tag} ) == 1 ) {
334             _dbug_hack ( delay => 0, who_called => $self->{who},
335 0         0 \&DBUG_PRINT, $self->{tag}, join ("", @args) );
336             }
337             # Notice we didn't chain for the callback function ...
338             # Or loop back to the callback function again.
339 0         0 return ( print $fh @args );
340             }
341              
342             # -------------------------------------------------------------
343             # Handles print requests from everyone else ...
344             # -------------------------------------------------------------
345              
346             # Calling the internal "hack" method instead of the public method due to
347             # some possible option combinations I want to avoid here.
348             # DBUG_PRINT ( $self->{tag}, $msg );
349             _dbug_hack ( delay => 0, who_called => $self->{who},
350 11         54 \&DBUG_PRINT, $self->{tag}, join ("", @args) );
351              
352 11         22 my $res;
353              
354             # Did we previously tie this file handle to something else?
355 11 100 66     68 if ( $self->{chain} && $self->{chain}->can ("PRINT") ) {
356 1         6 $res = $self->{chain}->PRINT ( @args );
357              
358             # Else print the message to the original file handle ...
359             } else {
360 10         140 $res = print $fh @args;
361             }
362              
363             # Will pause fish in the callback if fish is writting to the screen.
364             # This prevents potential infinite loop situations.
365 11 50 33     87 if ( $res && $self->{callback} ) {
366 11         21 my $pause;
367 11         41 local $self->{callback_recursion} = 1; # See test for it above!
368 11 50       35 $pause = 1 if ( DBUG_ACTIVE () == -1 ); # Screen test.
369 11         32 $res = _dbug_hack ( pause => ${pause}, $self->{callback}, @args );
370             }
371              
372 11         61 return ($res);
373             }
374              
375              
376             # -------------------------------------------------------------
377             # Handles all calls to: "printf STDxxx $fmt, @args"
378              
379             sub PRINTF {
380 0     0     my $self = shift;
381 0           my $fmt = shift;
382 0           my @lst = shift;
383              
384             # So I'm not blamed for calling PRINT().
385 0           local $self->{called_by_other_print_func} = 1;
386              
387 0           my $data = sprintf ( $fmt, @lst );
388 0           return ( $self->PRINT ( $data ) );
389             }
390              
391              
392             # -------------------------------------------------------------
393             # Used during calls to syswrite() ...
394              
395             sub WRITE {
396 0     0     my $self = shift;
397 0           my $scalar = shift; # Required.
398 0           my $length = shift; # Optional ...
399 0           my $offset = shift; # Optional, may only be used if $length is uses 1st!
400              
401 0           my $len = length ( $scalar );
402              
403 0           my $data;
404 0 0         unless ( defined $length ) {
    0          
    0          
    0          
405 0           $data = $scalar;
406              
407 0 0         } elsif ( (! defined $offset) || $offset == 0 ) {
408 0 0         my $max = ($len < $length) ? $len : $length;
409 0           $data = substr ( $scalar, 0, $max );
410              
411 0           } elsif ( abs ($offset) > $len ) {
412 0           $data = ""; # Offset was out of bounds ...
413              
414 0           } elsif ( $offset < 0 ) {
415 0           $len = -$offset;
416 0 0         my $max = ($len < $length) ? $len : $length;
417 0           $data = substr ( $scalar, $offset, $max );
418              
419             } else {
420 0           $len = $len - $offset;
421 0 0         my $max = ($len < $length) ? $len : $length;
422 0           $data = substr ( $scalar, $offset, $max );
423             }
424              
425             # So I'm not blamed for calling PRINT().
426 0           local $self->{called_by_other_print_func} = 1;
427              
428 0           return ( $self->PRINT ( $data ) );
429             }
430              
431             # ---------------------------------------------------------------------------
432             # End of Fred::Fish::DBUG::TIE ...
433             # ---------------------------------------------------------------------------
434              
435             =back
436              
437             =head1 CREDITS
438              
439             To Fred Fish for developing the basic algorithm and putting it into the
440             public domain! Any bugs in its implementation are purely my fault.
441              
442             =head1 SEE ALSO
443              
444             L - The controling module which you should be using to enable
445             this module.
446              
447             L - The live version of the DBUG module.
448              
449             L - The stub version of the DBUG module.
450              
451             L - Allows you to trap and log signals to B.
452              
453             L - Allows you to implement action
454             DBUG_SIG_ACTION_LOG for B. Really dangerous to use. Will break most
455             code bases.
456              
457             L - A L wrapper to redirect test results to
458             B.
459              
460             L - Sample code demonstrating using DBUG module.
461              
462             =head1 COPYRIGHT
463              
464             Copyright (c) 2019 - 2025 Curtis Leach. All rights reserved.
465              
466             This program is free software. You can redistribute it and/or modify it
467             under the same terms as Perl itself.
468              
469             =cut
470              
471             # ============================================================
472             #required if module is included w/ require command;
473             1;
474