Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

locating specific function calls

by markov (Scribe)
on Apr 11, 2007 at 12:33 UTC ( #609351=perlquestion: print w/ replies, xml ) Need Help??
markov has asked for the wisdom of the Perl Monks concerning the following question:

In an attempt to do quality-control, I am looking for a way to run all occurrences of a certain function in a file. For instance:
use Locale::TextDomain; my $q = __x ("Error reading file '{file}': {err}", file => $file, err => $!);

When testing, I would like to call this __x() to check that the file and err parameters are present. Of course, a different __x() during test than at run-time. The same for all uses of the same function within the code, also in the hard-to-reach corners.

I prefer to avoid the use of PPI to process dozens of files, in favor of some B:: trick. Any ideas?

Comment on locating specific function calls
Select or Download Code
Re: locating specific function calls
by Corion (Pope) on Apr 11, 2007 at 12:41 UTC

    I'm not sure I understand what you want correctly. Let me restate your problem and propose a solution - I think this solution is not a good solution but I think it would solve the problem as I perceive it:

    I think your problem is that you have a printf-like function __x() and want to make sure that wherever you have a call to it all template names in the string are present in the additional parameter list. This is complicated by the fact that people could use @additional_args, or by helper subroutines like __x_err, which is

    sub __x_err { my $msg = shift; __x("$msg: {err}", err => $!, @_); };
    , so a simple regular expression won't help too much.

    I think one tedious solution would be to use Devel::Cover to map out all branches in your code that lead to a call of __x() and then to (manually) generate those parameter conditions that are missing from your test suite to make sure all those branches are actually reached.

    I thought of writing a program that generates such conditions automatically, but so far I haven't felt the need - but maybe there is existing prior art. Hence my solution requires lots of manual work just to make sure your calling conventions are always followed. Using a regular expression to fish out all calls to __x() and then verifying that they all match a certain pattern might be saner, but there are always edge cases that might make this infeasible as well, unless you find that all your uses of __x() and __x_err() are simple uses (as they should maybe be, for a simple logging routine).

    Now, did I understand your problem well enough to propose a (bad, tedious) solution or did I run off in the wrong direction alltogether? :)

      My implementation will be a module, which does help people with internationalization. I can offer the automatic checks on call correctness, when the users refrain from the use of @additional_args and $msg, as in your example. So that's not really a no-go.

      What certainly is not possible, it to generate all conditions (automatically) to reach all corners of the code.

      I know that the first parameter of the function is a string, and that the other parameters are named. I only have to check wether the names are within the string. As second activity, for each of the strings I like to see that they are used: they must be translatable using gettext().

      Thanks for your ideas so far.

Re: locating specific function calls
by shmem (Canon) on Apr 11, 2007 at 12:44 UTC
    I am looking for a way to run all occurrences of a certain function in a file.

    I don't get what you are up to. Run the file, and all ocurrences of the function will eventually been run... what is your __x() function about?

    Maybe you are looking for a way to wrap functions?

    if ($debug) { my $sub = \&some_function; my $wrapsub = sub { __x(@_); $sub->(@_) }; *some_function = $wrapsub; }

    Then, whenever some_function() is invoked, your __x() is called first.

    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
      No, that's not the direction I want to go. I would like to find the calls to a function without running the module; the calls can be anywhere, even within an if() which is always false. I still need to find that application of the function.
Re: locating specific function calls
by Fletch (Chancellor) on Apr 11, 2007 at 13:10 UTC

    B::Xref may be of use? I'm likewise fuzzy on what's being requested. Perhaps Params::Validate would be another way of attacking this from the callee's end instead of verifying the callers.

      Ok, let tell me a little more about the plan.

      When your program needs to support multiple languages, then you can use gettext. Its use has various syntaxes, of which Locale::TextDomain seems to be the nicest: __x("found {count} files", count => 6). To use gettext(), the string needs to be translated into all supported languages.

      After translation, these messages have to go somewhere. Of course, you can simply die/warn/croak, but a generally applicable module is not sure about the destination of the output. More complex applications have to apply nasty tricks to catch and handle "die()" in third-party code. With Log::Dispatch and Log::Log4perl you can help the message find its way cleaner, but do not translate

      So, what I want to do, is link the translation framework with the distribution network. Any piece of distributed text must be translated, and therefore I would like to avoid the explicit call to __x(). I try to write this:

      use Log::Report textdomain => 'my-domain'; report trace => "{count} files", count => 10;
      with "compile-time" parameter checking. In stead of the unchecked
      use Log::Report textdomain => 'my-domain'; report trace => __x("{count} files", count => 10);

      In the major module of a set of related modules, you will be able to say:

      use Log::Report textdomain => 'my-domain' , directory => '/usr/share/locale';
      etc: I do not want to repeat configuration information in each pm file. And I do not want to limit the whole application (containing multiple distributions) to one domain. The default use of the current modules have these limitations: I wish to change the default.

      In the "main" script, you must be able to say something like:

      use Log::Report destinations => [ CRITICAL => 'syslog' , 'ERRORS-' => 'die' , 'TRACE,INFO' => 'ignore' ];

      Be aware: all syntax still under development, and will certainly be clearer.

      To come back to my original question: I want to simplify the use and automatically check the "report()" calls without running the program.

Re: locating specific function calls
by Moron (Curate) on Apr 11, 2007 at 13:46 UTC
    ^C It sounds like you are talking about subroutine call profiling, ^I for which Devel::AutoProfiler might be useful.
    __________________________________________________________________________________

    ^M Free your mind!

    Key to hats: ^I=white ^B=black ^P=yellow ^E=red ^C=green ^M=blue - see Moron's scratchpad for fuller explanation.

      Although a good hint (thanks), this collects the other side of the subroutine interface: it creates wrappers around the subroutine definitions (which are simple to find). What I need is access to the subroutine application... without running the code.

Re: locating specific function calls
by Moron (Curate) on Apr 11, 2007 at 14:22 UTC
    ^C Next interpretation of OP then: to find all calls to a subroutine without executing it.

    ^I The PPI set of modules provide the means to parse Perl code without executing it and includes facilities for documenting and structurising the parse tree.

    __________________________________________________________________________________

    ^M Free your mind!

    Key to hats: ^I=white ^B=black ^P=yellow ^E=red ^C=green ^M=blue - see Moron's scratchpad for fuller explanation.

      My question explicitly states that I do not want to parse thousands of lines with PPI. I am looking for a different solution.

      In my terminolgy, the application is the end-user/final product, where modules are (reusable) components.

        Oops forgot that, sorry!
        __________________________________________________________________________________

        ^M Free your mind!

        Key to hats: ^I=white ^B=black ^P=yellow ^E=red ^C=green ^M=blue - see Moron's scratchpad for fuller explanation.

Re: locating specific function calls
by Moron (Curate) on Apr 11, 2007 at 14:45 UTC
    ^C Is this then a 'cheapo' parse?

    ^I on *NIX you can grep for the subroutine identifier, or on Windows command line prompt, something like:

    type *.pl | perl -e 'print grep /\b__x\b/, <>;'
    __________________________________________________________________________________

    ^M Free your mind!

    Key to hats: ^I=white ^B=black ^P=yellow ^E=red ^C=green ^M=blue - see Moron's scratchpad for fuller explanation.

      Yes, that is about what xgettext is doing to create a lexicon which needs to be translated into all languages. I would really love to build this lexicon without that trick.
      You need double quote in Windows. But doesn't this work?
      perl -e "print grep /\b__x\b/, <>;" *.pl

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://609351]
Approved by Corion
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others cooling their heels in the Monastery: (8)
As of 2014-10-25 20:40 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    For retirement, I am banking on:










    Results (148 votes), past polls