File Coverage

blib/lib/Devel/STDERR/Indent.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Devel::STDERR::Indent;
4 1     1   35575 use Moose;
  0            
  0            
5              
6             no warnings 'recursion';
7              
8             use Scalar::Util qw(weaken);
9              
10             use namespace::clean -except => "meta";
11              
12             use Sub::Exporter -setup => {
13             exports => [qw(indent)],
14             };
15              
16             our $VERSION = "0.06";
17              
18             sub indent {
19             my $h = __PACKAGE__->new(@_);
20             $h->enter;
21             return $h;
22             }
23              
24             sub BUILDARGS {
25             my ( $class, @args ) = @_;
26              
27             unshift @args, "message" if @args % 2 == 1;
28              
29             return {@args};
30             }
31              
32             has message => (
33             isa => "Str",
34             is => "ro",
35             predicate => "has_message",
36             );
37              
38             has indent_string => (
39             isa => "Str",
40             is => "ro",
41             default => " ",
42             );
43              
44             has enter_string => (
45             isa => "Str",
46             is => "ro",
47             default => " -> ",
48             );
49              
50             has leave_string => (
51             isa => "Str",
52             is => "ro",
53             default => " <- ",
54             );
55              
56             has _previous_hook => (
57             is => "rw",
58             predicate => "_has_previous_hook",
59             );
60              
61             has _active => (
62             isa => "Bool",
63             is => "rw",
64             );
65              
66             sub DEMOLISH {
67             my $self = shift;
68             $self->leave;
69             }
70              
71             sub enter {
72             my $self = shift;
73              
74             return if $self->_active;
75              
76             $self->install;
77              
78             if ( $self->has_message ) {
79             $self->emit( $self->enter_string . $self->message, "\n" );
80             }
81              
82             $self->_active(1);
83             }
84              
85             sub leave {
86             my $self = shift;
87              
88             return unless $self->_active;
89              
90             if ( $self->has_message ) {
91             $self->emit( $self->leave_string . $self->message, "\n" );
92             }
93              
94             $self->uninstall;
95              
96             $self->_active(0);
97             }
98              
99             sub warn {
100             my ( $self, @output ) = @_;
101              
102             $self->emit( $self->format(@output) );
103             }
104              
105             sub emit {
106             my ( $self, @output ) = @_;
107              
108             if ( my $hook = $self->_previous_hook ) {
109             $hook->(@output);
110             } else {
111             local $,;
112             local $\;
113             print STDERR @output;
114             }
115             }
116              
117             sub format {
118             my ( $self, @str ) = @_;
119              
120             my $str = join "", @str;
121              
122             if ( $self->should_indent ) {
123             my $indent = $self->indent_string;
124              
125             # indent every line
126             $str =~ s/^/$indent/gm;
127              
128             return $str;
129             } else {
130             return $str;
131             }
132             }
133              
134             sub should_indent {
135             my $self = shift;
136              
137             # always indent if there's an enter/leave message
138             return 1 if $self->has_message;
139              
140             # indent if we're nested
141             if ( $self->_has_previous_hook ) {
142             my $hook = $self->_previous_hook;
143             if ( blessed($hook) and $hook->isa("Devel::STDERR::Indent::Hook") ) {
144             return 1;
145             }
146             }
147              
148             # otherwise we're at the top level, don't indent unnecessarily, it's distracting
149             return;
150             }
151              
152             sub install {
153             my $self = shift;
154              
155             my $weak = $self;
156             weaken($weak);
157              
158             if ( my $prev = $SIG{__WARN__} ) {
159             $self->_previous_hook($prev);
160             }
161              
162             $SIG{__WARN__} = bless sub { $weak->warn(@_) }, "Devel::STDERR::Indent::Hook";
163             }
164              
165             sub uninstall {
166             my $self = shift;
167              
168             if ( my $prev = $self->_previous_hook ) {
169             $SIG{__WARN__} = $prev;
170             } else {
171             delete $SIG{__WARN__};
172             }
173             }
174              
175             __PACKAGE__;
176              
177             __END__
178              
179             =pod
180              
181             =head1 NAME
182              
183             Devel::STDERR::Indent - Indents STDERR to aid in print-debugging recursive algorithms.
184              
185             =head1 SYNOPSIS
186              
187             use Devel::STDERR::Indent qw/indent/;
188              
189             sub factorial {
190             my $h = indent; # causes indentation
191              
192             my $n = shift;
193             warn "computing factorial $n"; # indented based on call depth
194              
195             if ($n == 0) {
196             return 1
197             } else {
198             my $got = factorial($n - 1);
199             warn "got back $got, multiplying by $n";
200             return $n * $got;
201             }
202             }
203              
204             =head1 DESCRIPTION
205              
206             When debugging recursive code it's very usefl to indent traces, but often too
207             much trouble.
208              
209             This module makes automates the indentation. When you call the C<indent>
210             function the indentation level is increased for as long as you keep the value
211             you got back. Once that goes out of scope the indentation level is decreased
212             again.
213              
214             =head1 EXPORTS
215              
216             All exports are optional, and may be accessed fully qualified instead.
217              
218             =over 4
219              
220             =head1 indent
221              
222             Returns an object which you keep around for as long as you want another indent
223             level:
224              
225             my $h = $indent;
226             # ... all warnings are indented by one additional level
227             $h = undef; # one indentation level removed
228              
229             Instantiates a new indentation guard and calls C<enter> on it before returning it.
230              
231             Parameters are passed to C<new>:
232              
233             indent "foo"; # will print enter/leave messages too
234              
235             =back
236              
237             =head1 METHODS
238              
239             =over1
240              
241             =item new
242              
243             Creates the indentation helper, but does not install it yet.
244              
245             If given a single argument it is assumed to be for the C<message> attribute.
246              
247             =item emit
248              
249             Output a warning with the previous installed hook.
250              
251             =item format
252              
253             Indent a message.
254              
255             =item warn
256              
257             Calls C<format> and then C<emit>.
258              
259             =item enter
260              
261             Calls C<install> the hook and outputs the optional message.
262              
263             =item leave
264              
265             Calls C<uninstall> the hook and outputs the optional message.
266              
267             =item install
268              
269             Installs the hook in C<$SIG{__WARN__}>.
270              
271             =item uninstall
272              
273             Uninstalls the hook restoring the previous value.
274              
275             =back
276              
277             =head1 ATTRIBUTES
278              
279             =over 4
280              
281             =item message
282              
283             If supplied will be printed in C<enter> prefixed by C<enter_string> and in
284             C<leave> prefixed by C<leave_string>.
285              
286             =item indent_string
287              
288             Defaults to C<' '> (four spaces).
289              
290             =item enter_string
291              
292             Defaults to C<< ' -> ' >>.
293              
294             =item leave_string
295              
296             Defaults to C<< ' <- ' >>.
297              
298             =back
299              
300             =head1 VERSION CONTROL
301              
302             L<http://nothingmuch.woobling.org/code>
303              
304             =cut
305              
306