NAME

Devel::Comments - Debug with executable smart comments to logs

VERSION

This document describes Devel::Comments version 1.1.4

SYNOPSIS

    use Devel::Comments;                    # acts just like Smart::Comments
    
    # Dumps...
    my $scalar      = 42;
    ### $scalar                             # prints to STDERR:    
                                            ### $my_scalar: 42
    
    ### @array                              # dumps more complex 
    ### $HoHoA                              #   data structures, too
    
    ### Just in the neighborhood            # prints literal message
    
    # Level control...
    use Devel::Comments '###';              # only activate level 3
    
    ### $scalar                             # this prints
    #### $scalar                            # this doesn't
    
    # Output control...
    use Devel::Comments *STDOUT;            # prints to STDOUT instead
    use Devel::Comments \*FH;               # prints to some FH
    use Devel::Comments $fh;                # prints to some $fh

    use Devel::Comments ({                  # hashref calling syntax
        -file           => 'my.log',            # opens my.log and prints to it
    }); 
    
    # Assertions...
    my ($x, $y)     = 1, 0;
    ### check  $x == $y                     # simulates warning and dumps info
    ### insist $x == $y                     # dumps and dies
    
    # Progress bars...
    for my $i (0..1e6) {   ### Working===[%]     done
        do_something_expensive_with($i);
    }

DESCRIPTION

I get the feeling that the computer just skips over all the comments. -- a grad student

Devel::Comments is a source filter for your Perl code, intended to be used only during development. Specially-formatted 'smart' comments are replaced by executable code to dump variables to screen or to file, display loop progress bars, or enforce conditions. These smart comments can all be disabled at once by commenting out the use Devel::Comments line, whereupon they return to being simple, dumb comments. Your debugging code can remain in place, guaranteed harmless, ready for the next development cycle.

Devel::Comments is a fork of Smart::Comments; current intention is to add new features without breaking backward compatibility. Version 1.1.2 implements the 'any filehandle' feature, allowing smart output to go to any filehandle opened for writing. You may instead pass in a filename, which DC will open for you. Future plans include extended calling syntax, numerical level enabling, improved progress bars, dump method callback, and execution of arbitrary code. Bugs raised against Smart::Comments 1.0.4 will be fixed in DC.

INTERFACE

There are two main parts to the DC interface: arguments passed on the use line; and 'smart' comments, which are specially-formatted comments introduced by three or more octothorpes, such as '###', '####', or even '########'. Use-line arguments may also be passed in an environment variable.

DC provides no run-time public variables, functions, routines, or methods. DC is a source filter and does its work at "compile-time". (Some DC routines are called at run-time from within replacement code previously filtered in.)

The Use Line

Most setup is done when the module is loaded via use Devel::Comments. If called with vanilla Smart::Comments arguments, DC will behave the same; it's a drop-in replacement. Backwards compatibility to Smart::Comments 1.0.4 is promised through DC 1.x.x.

Smart::Comments required arguments to be passed, in any order, as one flat list. While this is convenient for a small number of restricted-value arguments, it may "getcha" when attempted with many arguments whose values are unrestricted. This "free-form" calling syntax does not even have the security of positional parameters.

While every attempt will be made to interpret a flat list correctly, we will make a transition to named parameters as elements of a hash reference. Devel::Comments users are encouraged to use this newer calling syntax.

Following sections are headed by the appropriate hashref key, which begins always with a leading dash. NOTE: This early version 1.1.2 does not yet implement hashref calling syntax for parameters other than -filename. Other sections are headed by the hashref keys that will name their parameters. If the named parameter is unimplemented, you can still pass the argument in the flat list.

-fh

named parameter syntax unimplemented

Example arguments: *STDOUT, \*FH, $fh

Accepts an open, writable filehandle (typeglob or object) as an argument. Caller must do whatever is needed to manage that filehandle, such as opening (but probably not closing) it.

Value must be acceptable as a filehandle:

    $fh         # indirect filehandle (perhaps IO::File object); recommended.
    \*FH        # reference to a typeglob
    *FH         # typeglob
    "FH"        # please don't do this; probably won't work as expected.

Except for *STDOUT you should probably avoid the typeglob notation. (No need to specify *STDERR explicitly; it's the default.) DC will try to work with a typeglob but there are risks. You'd better localize the typeglob; a lexical may not work. (See "Perl Cookbook Recipie 7.16".) Passing a string will probably fail.

See also "perldoc perlopentut".

Note that, effectively, modules are used within a BEGIN block. Therefore, your filehandle must be opened within a BEGIN block prior to the use line. If caller needs to do anything else with that filehandle, you might as well store it in a package variable (since source filtering is global anyway). Do not enclose the open and the use line in the same BEGIN block.

The filehandle must be opened, obviously, in some writable mode.

    BEGIN {                             # get $::fh open early enough
        my $filename    = 'my.log';
        open my $::fh, '>', $filename
            or die "Couldn't open $filename to write", $!;
    }
    use Devel::Comments $::fh;
    {...}   # do some work
    ### $some_variable
    print {$::fh} 'Some message...';    # do something else with $::fh

-file

flat list parameter syntax unimplemented

Example arguments: '/var/my.log', "$0.log", 'ziggy.txt'

Value can be any filename or path, relative or fully qualified. The file will be created if it doesn't exist, truncated by default, opened for writing, and set to autoflush. All directory components must exist.

Until your entire program ends, there's no way to be sure that caller won't come into scope (say, a sub called from some other script or module). So DC can't do an explicit close(). That shouldn't be a problem, since perl will close the filehandle when program terminates. If you need to do something differently, supply a filehandle and manage it yourself.

You may, in an upcoming version, pass a filename as a flat list argument. There's an issue here in that a filename might be just about any string; if you've chosen a peculiar filename such as '###' or '-ENV', there's going to be confusion. For now, this is unimplemented.

-level

named parameter syntax unimplemented

numerical levels unimplemented

Devel::Comments accepts arguments like '###', '####', and so forth. If none are given, then all comments introduced with 3 or more octothorpes are considered smart. Otherwise, only those comments introduced with a matching quantity are smart:

    use Devel::Comments '###', '#####'; 
    ### This is smart.
    #### This is dumb.
    ##### This is also smart. 

Soon, you will be able to pass an integer or a list of integers:

    use Devel::Comments ({-level => [3, 5] }); 
    ### This is smart.
    #### This is dumb.
    ##### This is also smart. 
    

But not quite yet.

A level of 1 or 2 simply doesn't work. So don't do that.

-env

named parameter syntax unimplemented

Example: use Devel::Comments -ENV;

Yet another way of specifying arguments (besides as a list or hashref in the use line) is to pass them in the environment variable $ENV{Devel_Comments}. But to enable this, you must pass -ENV in the use line or define -env in a hashref passed in the use line.

See "CONFIGURATION AND ENVIRONMENT".

Don't try to pass a hashref inside of the environment variable; you won't like the result.

Smart Comments Format

In some small way, smart comments comprise an alternate language embedded within Perl. If you don't have any smart comments in your code, Devel::Comments, like Smart::Comments before it, will do essentially nothing. If you disable Devel::Comments (see "DISABLING"), then smart comments are guaranteed to do nothing at all, since they are then interpreted by perl as plain old dumb comments.

All smart comments, without exception, are introduced by a series of three or more octothorpes: '###' at a minimum. This is not likely to change; the '##' sequence is used by Perl::Tidy to signal the end of lengthy constructs.

Aspects of this miniature language-within-a-language now include introducers, messages, dumps, assertions, and progress bars. Extensions are planned.

Introducers

A basic smart comment is any line beginning with '###':

    ### This comment is smart at debug level 3.

This is considered a level 3 comment; it will only be active if level 3 is enabled by one means or another. More octothorpes increase the debug level:

    ##### This comment is smart at debug level 5. 

The number of debugging levels is essentially unlimited; so introducers may be of any length. However, this rapidly becomes unwieldy.

unimplemented: An alternate means of specifying the debug level is:

    ###4 This comment is smart at debug level 4. 

Every introducer ends with a space or tab (m/[ \t]/); anything before the first white character is considered part of the introducer.

unimplemented: An introducer ending in an ampersand (&) marks raw Perl code; in effect, the introducer is simply stripped off if it is at an enabled debug level:

    ###& push @zoo, $monkey     # Put the monkey in the zoo at debug level 3.

Note that, with the exception of progress bars, a smart comment must begin its line; that is, only whitespace can intervene between an introducer and the preceeding newline. Trailing smart comments may be a future feature.

Messages

Any smart comment not matching other patterns will be dumped as is:

    ### Hello, World!

In a message, <now>, <time>, or <when> is replaced by a timestamp (same timestamp for all three). Also, <here>, <place>, or <where> is replaced by what Damian Conway calls a "spacestamp" similar to what you see by default in die() or warn():

    ### Here <here>
    ### Now <now>

prints something like:

    ### Here "util/demo.pl", line 71
    ### Now Fri Aug  6 07:50:51 2010

Note that no colon follows 'Here' or 'Now'. Any text would do as well but no text at all -- the <now> alone -- gets confused. This is considered a bug.

    ### <here>      <now>

... works fine and is an excellent way to start off a logging session.

Original SC documentation required that such plain text messages be terminated with a simulated elipsis:

    ### This text is printed...

This was not actually enforced and is not required in DC.

Dumps

Any scalar, array, hash; reference to any of these, or for that matter, more complex structure can be dumped just by typing the variable:

    ### $dump_me

The dump will be labeled with the variable name, including sigil. You can supply your own label if you like:

    ### Working tree: $tree

The automatic labeling is the real driving force behind DC, though. Even dark magiks involving Pad::Walker and rooting around in symbol tables has trouble getting the right name for a variable and its value. The only place it is convenient to do this is in the same scope as the variable itself; hence, a source filter.

You can dump an arbitrary expression:

    my $index   = 8;
    ### Add five: $index + 5

prints:

    ### Add five: 13

However, this will not work if you don't supply your own label.

Beware side effects:

    my @array   = ( 1, 2, 3 );
    say @array;
    ### Pop: pop @array
    say @array;

prints:

    123

    ### Pop: 3
    12

If you don't want the verbosity of <here>, try:

    #### At: __LINE__

Assertions

Seven keywords cannot be used as labels. If one of them is used to label an expression, it is evaluated in boolean context and, if the expression is true, nothing is output. If the expression is false, a message announcing the failure is output, similar to warn():

    ### check:      1 == 0

prints something like:

    ### 1 == 0 was not true at util/demo.pl line 92.

The assertions:

    ### check:      BOOL
    ### confirm:    BOOL
    ### verify:     BOOL

... simulate warn() on failure, although the smart output goes to the chosen output file or filehandle, not necessarily STDERR.

    ### assert:     BOOL
    ### ensure:     BOOL
    ### insist:     BOOL
    ### require:    BOOL

... print the same message on failure, then call die().

Note that these seven keywords are supported in the current version of DC but all except check and assert are deprecated.

Progress Bars

Only in these can a smart comment appear on the same line with Perl code:

    for (@candidates) {       ### Evaluating |===[%]    |

prints, in succession:

    Evaluating |[0%]                       |
    Evaluating |=[25%]                     |
    Evaluating |========[50%]              |
    Evaluating |===============[75%]       |
    Evaluating |===========================|

At each step, the previous bar is erased and overwritten by the next; when the loop completes, the last bar is erased, too.

There are a great number of possible progress bar formats and they are very clever indeed. There is, however, among developers polled, almost no interest in them; and they are difficult to support. It's not clear that they're truly useful in debugging. So, although they are supported in the current DC release, they likely will be deprecated or replaced by a different loop reporting function.

Both Vanilla and DC animate the progress bar by printing the "\r" character and wiping the line with spaces. This is unchanged when smart output goes to a disk file. Depending on your method of reading that file, you may see multiple lines or nothing at all. But if, for some reason, the loop aborts, you may see how far along it got.

If you want to experiment with progress bars, you may want to look at the Smart::Comments documentation. If you like them, please be sure to indicate your support.

DISABLING

Source filters are a bit dicey; the saving grace of DC (and its parent) is that it can be disabled easily and completely; all specially-formatted smart comments return to being plain old dumb comments, guaranteed not to interfere with normal execution:

    #use Devel::Comments;       # disable in production
    ### assert 0 == 1           # does nothing at all

There are other methods of disabling DC.

If you write:

    use Devel::Comments -ENV;   # heed environment variable

... then DC will only be active if $ENV{Devel_Comments} is set, possibly to some other DC use-line arguments or mererly to 1. If it is set to 0 or deleted, then DC is disabled.

DC can be restricted to a certain span of code. If you write:

    ### Hello, Andy!
    use Devel::Comments;
    ### Hello, Bob!
    no  Devel::Comments;
    ### Hello, Cindy!

then Bob will be greeted but not Andy or Cindy. Note that docs for Filter::Simple suggest other possible text for the statement that terminates filtering; these others don't seem to work, so don't do that.

You might load DC in the shell invocation:

    $ perl -d:Comments myscript.pl

Next time, don't do that and DC won't load, of course. This loading method is untested but if there are requests for it, I'll work it up.

Any given smart comment can be disabled by changing the introducer to a level that's disabled in the use line, or to an invalid introducer:

    use Devel::Comments '###';
    ### grin
    #### and
    # ### bear
    # ## it

prints:

    ### grin

HOW IT WORKS

Technically, arguments present on any use line are presented to a module's import() method. Devel::Comments uses Filter::Simple to do the heavy lifting; FS converts a call to Filter::Simple::FILTER into Devel::Comments::import().

All of the following code, to end of file or any no Devel::Comments line, is filtered. All smart comments (with correct introducers) are replaced by executable Perl code.

If you write something funky, like:

    my $string = q{
        bobby says
        ### think
    };

... then you are asking for trouble and will likely get it.

    my $string = q{\n    bobby says\n    ### think\n};

... is perfectly safe and will be ignored by DC.

Dumps of complex structures are done by the venerable Data::Dumper. The output is cleaned up a bit before being printed; the all-important variable identifier is inserted.

Scope, State, Output Regimes

DC may be called more than once in the same program, e.g., from two different loaded modules. As does vanilla SC, DC has effect until the end of the file or a no Devel::Comments line (which must be the first thing on its line). If used again, DC will parse the new use line and apply it to your source code from there on out.

This required no special logic in Vanilla; the filter is applied once per use and although multiple modules might call S::C routines from within filtered code, all output went to STDERR. But multiple uses of DC may choose different output regimes. So state information is stored for each caller.

If you supply a filehandle (other than STDOUT or STDERR), your (filtered) code will need that later to print smart output where you want it to go. If you supply a package variable as an indirect filehandle (such as $My::Module::fh), then all is well. If you supply a lexical (my) variable, DC will still work, even after it goes out of scope in your package, because a reference is stored in DC's namespace. But by the same token, don't expect it to be garbage-collected. You may as well use a package "global" variable, since source filtering is pretty much a global operation anyway.

If you pass a filename but no filehandle, you'll get smart output but you won't have any way to write directly to the file (should you take that notion). Not recommended to open the file again within your script, although that might work.

DIAGNOSTICS

    Internal error: _get_outfh called with no or false arg. $!
    Internal error: $caller_id not defined in %state_of. $!
    Internal error: No output filehandle found in %state_of for $caller_id. $!
    Internal error: -caller_id not passed in call to _init_state(). $!
    Internal error: -outfh not passed in call to _init_state(). $!

You should never see any of these errors involving state maintenance. If you do, please contact the author with as much information as possible.

    Can't open $out_filename to write.

You passed in a filename that couldn't be written to. Check to see that all directory components of the path exist and that you have permission to write to the target file.

    Filesystem IO error: Failed to print to output filehandle for $caller_id 

Gee, that's funny. But if DC can't write to a filehandle you supplied, it's probably not something I can do anything about. Perhaps the disk is full or the socket is closed? Be sure you have opened the filehandle for writing in a BEGIN block prior to the use Devel::Comments; line. Check to see you can write to it.

    Internal error: DATA. $!

You should never see this error either. If you do, please contact the author with as much information as possible.

    ### $assertion was not true

This is not a module error but smart output you generated. See "ASSERTIONS"

CONFIGURATION AND ENVIRONMENT

Devel::Comments can make use of an environment variable from your shell: Devel_Comments. This variable can be specified either with a true/false value (i.e. 1 or 0) or with the same arguments as may be passed on the use line when loading the module (see "INTERFACE"). The following table summarizes the behaviour:

         Value of
    $ENV{Devel_Comments}          Equivalent Perl

            1                     use Devel::Comments;
            0                      no Devel::Comments;
        '###:####'                use Devel::Comments qw(### ####);
        '### ####'                use Devel::Comments qw(### ####);

To enable the Devel_Comments environment variable, you need to load the module with the -ENV flag:

    use Devel::Comments -ENV;

Note that you can still specify other arguments in the use statement:

    use Devel::Comments -ENV, qw(### #####);

In this case, the contents of the environment variable replace the -ENV in the argument list.

DEPENDENCIES

The module requires the following modules:

  • Filter::Simple

  • version.pm

  • List::Util

  • Data::Dumper

  • Text::Balanced

INCOMPATIBILITIES

It is known that IO::Capture::Tie_STDx 0.05 does not implement a TELL() method. This causes trouble if smart output is directed to a captured filehandle. Workaround is to install IO::Capture::Tellfix, included with this distribution.

Not recommended to use DC is combination with other source filters.

BUGS AND LIMITATIONS

Tellfix is ugly and causes a warning to be raised under some circumstances. Intent is to move off IO::Capture::* altogether in favor of Test::Trap; so this issue will not be fixed directly.

The current testing paradigm is flawed; it has too many dependencies, including perl 5.010. We ship with a cut-down "user" test suite, which should run fine under perl 5.008; this is mostly a rehash of the original Smart::Comments test suite and doesn't fully exercise DC's new features. Those interested may want to run the full test suite found in t/all/.

A number of features are marked as unimplemented.

Bugs outstanding against SC 1.0.4 can be found at https://rt.cpan.org/Dist/Display.html?Queue=Smart-Comments and they are probably all present in this version of DC. You are welcome to relist against DC any that you find; but I will be working off that list, too.

Please report any bugs or feature requests to https://rt.cpan.org/Public/Bug/Report.html?Queue=Devel-Comments or email <XIONG@cpan.org>. These are welcome and will be acted upon.

TODO

Argment passing will be made orthogonal, as much as possible. Arguments can be passed either as one flat list or as named elements of a single hashref.

Debug levels passed numerically and numerical introducers.

Invocation of client methods for dumping objects.

Pass-through execution of arbitrary debugging code.

Police up scraps of stuff currently left in caller's namespace. Store all state entirely within DC.

THANKS

  • Mike Stok <MIKESTOK@cpan.org> for reporting RT#62599 and fixing it.

  • Kevin Ryde for reporting RT#69712 and reviving the project.

AUTHOR

Xiong Changnian <XIONG@cpan.org>

LICENCE AND COPYRIGHT

Copyright (c) 2010, 2011, Xiong Changnian <XIONG@cpan.org>. All rights reserved.

Based almost entirely on Smart::Comments, Copyright (c) 2005, Damian Conway <DCONWAY@cpan.org>. All rights reserved.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

DISCLAIMER OF WARRANTY

BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.