NAME

    Devel::Examine::Subs - Get info, search/replace and inject code in Perl
    file subs.

SYNOPSIS

        use Devel::Examine::Subs;
    
        my $file = 'perl.pl'; # or directory name
        my $search = 'string';
    
        my $des = Devel::Examine::Subs->new({file => $file);

    Get all sub names in a file

        my $aref = $des->all();

    Print all subs within each Perl file under a directory

        my $files = $des->all({ file => 'lib/Devel/Examine' });
    
        for my $file (keys %$files){
            print "$file\n";
            print join('\t', @{$files->{$file}});
        }

    Get all subs containing "string" in the body

        my $aref = $des->has({search => $search});

    Search and replace code in subs

        $des->search_replace({
                        search => "$template = 'one.tmpl'",
                        replace => "$template = 'two.tmpl'",
                      });

    Inject code into sub after a search term (preserves previous line's
    indenting)

        my @code = <DATA>;
    
        $des->inject_after({
                        search => 'this',
                        code => \@code,
                      });
    
        __DATA__
    
        # previously uncaught issue
    
        if ($foo eq "bar"){
            croak 'big bad error';
        }

    Get all the subs as objects

        $aref = $des->objects(...)
    
        for my $sub (@$aref){
            $sub->name();       # name of sub
            $sub->start();      # number of first line in sub
            $sub->end();        # number of last line in sub
            $sub->num_lines();  # number of lines in sub
            $sub->code();       # entire sub code from file
            $sub->lines();      # see next example...
    
        }

    Print out all lines in all subs that contain a search term

        my $lines_with_search_term = $sub->lines();
    
        for (@$lines_with_search_term){
            my ($line_num, $text) = split /:/, $_, 2;
            say "Line num: $line_num";
            say "Code: $text\n";
        }

    The structures look a bit differently when 'file' is a directory. You
    need to add one more layer of extraction.

        my $files = $des->objects();
    
        for my $file (keys %$files){
            for my $sub (@{$files->{$file}}){
                ...
            }
        }

DESCRIPTION

    Gather information about subroutines in Perl files (and in-memory
    modules), with the ability to search/replace code, inject new code, get
    line counts, get start and end line numbers, access the sub's code and
    a myriad of other options.

FEATURES

    - uses PPI for Perl file parsing

    - search and replace code within subs, with the ability to include or
    exclude subs, something a global search/replace can't do (easily)

    - inject new code into subs following a found search pattern

    - retrieve all sub names where the sub does or doesn't contain a search
    term

    - retrieve a list of sub objects for subs that match a search term,
    where each object contains a variety of information about itself,
    acessible via access methods

    - include or exclude subs to be processed

    - differentiates a directory from a file, and acts accordingly by
    recursing and processing specified files

    - extremely modular and extensible; the core of the system uses
    plugin-type callbacks for everything

    - pre-defined callbacks are used by default, but user-supplied ones are
    loaded dynamically

    - can cache internally for repeated runs with the same object (in
    directory mode)

    - extensive test suite

METHODS

 new({ file => $filename, cache => 1 })

    Instantiates a new object.

    Takes the name of a file to search. If $filename is a directory, it
    will be searched recursively for files. You can set any and all
    parameters this module uses in any method, however only paramaters
    described in the PARAMETERS section are guaranteed to remain persistent
    until changed manually by the user.

    The PARAMETERS section contains the optional core global parameters
    that can and should be set here if you're to use them, which can then
    be omitted in subsequent call. If you set 'file' in new(), you can omit
    it in all subsequent method calls.

 all()

    Returns an array reference containing the names of all subroutines
    found in the file.

 has({ search => $text })

    Returns an array reference containing the names of the subs where the
    subroutine contains the text.

 missing({ search => $text })

    The exact opposite of has.

 module({ module => 'Devel::Examine::Subs' } )

    Returns an array reference containing the names of all subs found in
    the module's namespace symbol table.

 lines({ search => $text })

    Gathers together all line text and line number of all subs where the
    sub contains lines matching the search term.

    Returns a hash reference with the sub name as the key, the value being
    an array reference which contains a hash reference in the format
    line_number => line_text.

 search_replace({ $search => 'this', $replace => 'that', copy => 'file.ext'
 })

    Search for lines that contain certain text, and replace the search term
    with the replace term. If the optional parameter 'copy' is sent in, a
    copy of the original file will be created in the current directory with
    the name specified, and that file will be worked on instead. Good for
    testing to ensure The Right Thing will happen in a production file.

    This method will create a backup copy of the file with the same name
    appended with '.bak'.

 inject_after({ search => 'this', code => \@code })

    Injects the code in @code into the sub within the file, where the sub
    contains the search term. The same indentation level of the line that
    contains the search term is used for any new code injected. Set
    no_indent parameter to a true value to disable this feature.

    By default, an injection only happens after the first time a search
    term is found. Use the injects parameter (see PARAMETERS) to change
    this behaviour. Setting to a positive integer beyond 1 will inject
    after that many finds. Set to a negative integer will inject after all
    finds.

    The code array should contain one line of code (or blank line) per each
    element. (See SYNOPSIS for an example).

    Optional parameters:

    copy

      See search_replace() for a description of how this parameter is used.

 pre_procs()

    Returns a list of all available pre processor modules.

 pre_filters()

    Returns a list of all available built-in pre engine filter modules.

 engines()

    Returns a list of all available built-in 'engine' modules.

 run()

    All public methods call this method internally. The public methods set
    certain variables (filters, engines etc). You can get the same effect
    programatically by using run(). Here's an example that performs the
    same operation as the has() public method:

        my $params = {
                search => 'text',
                pre_filter => 'file_lines_contain',
                engine => 'has',
        };
    
        my $return = $des->run($params);

    This allows for very fine-grained interaction with the application, and
    makes it easy to write new engines and for testing.

 add_functionality()

    WARNING!: This method is highly experimental and is used for developing
    internal processors only. Only 'engine' is functional, and only half
    way. It's simply a proof-of-concept of the 'Processor' structure which
    I will be incorporating into a new module template system that allows
    people to replicate the base structure of this module (less the data
    and processors). DO NOT USE.

    While writing new processors, set the processor type to a callback
    within the local working file. When the code performs the actions you
    want it to, put a comment line before the code with #<des> and a line
    following the code with #</des>. DES will slurp in all of that code
    live-time, inject it into the specified processor, and configure it for
    use. See examples/write_new_engine.pl for an example of creating a new
    'engine' processor.

    Parameters:

    add_functionality

      Informs the system which type of processor to inject and configure.
      Permitted values are 'pre_proc', 'pre_filter' and 'engine'.

    add_functionality_prod

      Set to a true value, will update the code in the actual installed
      Perl module file, instead of a local copy.

    Optional parameters:

    copy

      Set it to a new file name which will copy the original, and only
      change the copy. Useful for verifying the changes took properly.

PARAMETERS

    There are various optional global parameters that can be used. These
    should be set in new(), unless you want them only briefly in which case
    just call them within the user public methods.

    file

      The name of a file, or a directory. If set in new, you can omit it
      from all subsequent method calls until you want it changed. Once
      changed in a call, the updated value will remain persistent until
      changed again.

    cache

      Cache results when working with a directory.

      If you'll be making multiple calls with the same instantiated object,
      set this parameter to a true value. It will cache the results of the
      Processor (collector) phase on the first run, and use that cache on
      subsequent runs, avoiding the need to recurse directories and
      recompile all of the data.

      Note that if any files change in the meantime, they will not be
      picked up until 'cache' is disabled.

      In a typical use case where the data is compiled and then nine
      subsequent calls are made through the same object, there's
      approximately a 1,000 times gain in speed by using cache:

          Benchmark: timing 100 iterations of disabled, enabled...
            disabled: 72 wallclock secs (66.33 usr +  5.18 sys = 71.51 CPU) @  1.40/s (n=100)
             enabled:  0 wallclock secs ( 0.06 usr +  0.01 sys =  0.07 CPU) @ 1428.57/s (n=100)

      See examples/cache_benchmark.pl for details.

    diff

      Not yet implemented.

      Compiles a diff after each edit using the methods that edit files.

    include

      An array reference containing the names of subs to include. This (and
      exclude) tell the Processor phase to generate only these subs,
      significantly reducing the work that needs to be done in subsequent
      method calls. Best to set it in the new() method.

    exclude

      An array reference of the names of subs to exclude. See include for
      further details.

      Note that exclude renders include useless.

    no_indent

      In the processes that write new code to files, the indentation level
      of the line the search term was found on is used for inserting the
      new code by default. Set this parameter to a true value to disable
      this feature and set the new code at the beginning column of the
      file.

    injects

      Informs inject_after() how many injections to perform. For instance,
      if a search term is found five times in a sub, how many of those do
      you want to inject the code after?

      Default is 1. Set to a higher value to achieve more injects. Set to a
      negative integer to inject after all.

    regex

      Set to a true value, all values in the 'search' parameter become
      regexes. For example with regex on, /thi?s/ will match "this", but
      without regex, it won't.

    extensions

      By default, we load only *.pm and *.pl files. Use this parameter to
      load different files. Only useful when a directory is passed in as
      opposed to a file.

      Values: Array reference where each element is the name of the
      extension (less the dot). For example, ['pm', 'pl'] is the default.

    cache_dump, pre_proc_dump, pre_filter_dump, engine_dump, core_dump

      Set to 1 to activate, exit()s after completion.

      Print to STDOUT using Data::Dumper the structure of the data
      following the respective phase. The core_dump will print the state of
      the data, as well as the current state of the entire DES object.

      NOTE: The 'pre_filter' phase is run in such a way that pre-filters
      can be daisy-chained. Due to this reason, the value of
      pre_filter_dump works a little differently. For example:

          pre_filter => 'one && two';

      ...will execute filter 'one' first, then filter 'two' with the data
      that came out of filter 'one'. Simply set the value to the number
      that coincides with the location of the filter. For instance,
      pre_filter_dump => 2; will dump the output from the second filter and
      likewise, 1 will dump after the first.

    pre_proc_return, pre_filter_return, engine_return

      Returns the structure of data immediately after being processed by
      the respective phase. Useful for writing new 'phases'. (See "SEE
      ALSO" for details).

      NOTE: pre_filter_return does not behave like pre_filter_dump. It will
      only return after all pre-filters have executed.

    clean_config

      Resets all configuration variables back to undef, less the global
      ones specified in PARAMETERS. Those ones need to be reset manually
      param = 0;> or delete $param-{param};>.

    config_dump

      Prints to STDOUT with Data::Dumper the current state of all loaded
      configuration parameters.

SEE ALSO

    As of 1.20 pre release, the following POD documents haven't been
    created.

    perldoc Devel::Examine::Subs::Preprocessor

      Information related to the 'pre_proc' phase core modules.

    perldoc Devel::Examine::Subs::Prefilter

      Information related to the 'pre_filter' phase core modules.

    perldoc Devel::Examine::Subs::Engine

      Information related to the 'engine' phase core modules.

AUTHOR

    Steve Bertrand, <steveb at cpan.org>

SUPPORT

    You can find documentation for this module with the perldoc command.

        perldoc Devel::Examine::Subs

LICENSE AND COPYRIGHT

    Copyright 2015 Steve Bertrand.

    This program is free software; you can redistribute it and/or modify it
    under the terms of either: the GNU General Public License as published
    by the Free Software Foundation; or the Artistic License.

    See http://dev.perl.org/licenses/ for more information.

