Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

Evaluating subroutines from within data

by Lady_Aleena (Deacon)
on Jan 06, 2012 at 23:50 UTC ( #946683=perlquestion: print w/ replies, xml ) Need Help??
Lady_Aleena has asked for the wisdom of the Perl Monks concerning the following question:

I discussed this a while ago in the chatter box, but I can't remember what was suggested.

I have a subroutine called print_story which, by default, wraps the lines of my __DATA__ or a file handle in <p></p> tags if the line doesn't start with a number between 1 and 6 or a < which indicates that the line has an HTML tag already. Normally this is all I need, however I've come upon a situation where I need to insert another subroutine into the data to keep the flow of what I'm printing to the screen and to avoid having to make a lot of little files for single sentences.

Here is a sample of use.

#!/usr/bin/perl use strict; use warnings FATAL => qw( all ); use CGI::Carp qw(fatalsToBrowser); use lib '../files/perl/lib'; use Base::HTML qw(print_story); print_story(*DATA,1); __DATA__ This is the first paragraph in the story. This is the last paragraph in the story.

Now, if the story has a definition list in it, I would like to use my subroutine print_definitions in my data to keep the flow.

#!/usr/bin/perl use strict; use warnings FATAL => qw( all ); use CGI::Carp qw(fatalsToBrowser); use lib '../files/perl/lib'; use Base::HTML qw(print_story); print_story(*DATA,1); __DATA__ Paragraph that opens the document. 2 Heading for definition list This is the paragraph that precedes the definition list. #This is the line where the definition list should be inserted. This is the paragraph that follows the definition list. 2 Heading for next definition list This is the paragraph that precedes the next definition list. #This is the line where the next definition list should be inserted. This is the paragraph that follows the next definition list. 2 Heading for the close of the document The paragraph that closes the document.

I've tried preceding the line with a & and then using eval. The only way that it would work is if I used the full name of the subroutine, which in this case would be Base::HTML::print_definitions(). In the future I may want to print a list of links or use another subroutine I am writing for printing tables. I really don't want to have to use the full names of each subroutine which is part of the text.

The following is the code for both print_story and print_definitions which prints the results I expect by themselves. However, I just haven't figured out the best way of getting them to work together. start_html and end_html are the templates I use on every page of my site. The line subroutine just adds tabs to the beginning of a line and then prints a newline after the line. get_hash does what it says, it gets a hash from a data file.

sub print_story { my ($source,$html) = @_; my $tab = $html ? 3 : 4; start_html() if $html; while (my $line = <$source>) { chomp($line); if ($line =~ m/^</) { line($tab,$line); } elsif ($line =~ /^[1-6]\s/) { my ($heading,$text) = split(/ /,$line,2); line($tab,qq(<h$heading>$text</h$heading>)); } else { line($tab,qq(<p>$line</p>)); } } line($tab,qq(<p class="author">written by $user</p>)) if $tab == 3; end_html if $html; } sub print_definitions { my (%opt) = @_; my $tab = exists($opt{html}) ? 3 : 4; my %definition_list; get_hash( hash => \%definition_list, file => exists($opt{file}) ? $opt{file} : get_data, headings => [@{$opt{headings}}], sort => 'yes', ); start_html() if exists($opt{html}); unless (exists($opt{html}) || exists($opt{heading})) { line(3,qq(<h2>$heading definitions</h2>)); } line($tab,q(<dl>)); my $term = shift @{$opt{headings}}; for my $term (sort {$definition_list{$a}{sort_number} <=> $definitio +n_list{$b}{sort_number}} keys %definition_list) { line($tab + 1,qq(<dt>$term</dt>)); if (scalar @{$opt{headings}} == 1) { line($tab + 2,qq(<dd>$definition_list{$term}{$opt{headings}->[0] +})); } else { for my $heading (@{$opt{headings}}) { my $upheading = ucfirst $heading; line($tab + 2,qq{<dd><b>$upheading:</b> }.encode_entities($def +inition_list{$term}{$heading}).qq{</dd>}); } } } line($tab,q(</dl>)); end_html if exists($opt{html}); }
Have a cookie and a very nice day!
Lady Aleena

Comment on Evaluating subroutines from within data
Select or Download Code
Re: Evaluating subroutines from within data
by Anonymous Monk on Jan 07, 2012 at 01:48 UTC

    I discussed this a while ago in the chatter box, but I can't remember what was suggested.

    Do you remember what you asked?

    I've read your question four times, and the closest I've seen you come to a question is in the middle :)

    I've tried preceding the line with a & and then using eval. The only way that it would work is if I used the full name of the subroutine, which in this case would be Base::HTML::print_definitions(). In the future I may want to print a list of links or use another subroutine I am writing for printing tables. I really don't want to have to use the full names of each subroutine which is part of the text

    The following is the code for both print_story and print_definitions which prints the results I expect by themselves. However, I just haven't figured out the best way of getting them to work together. start_html and end_html are the templates I use on every page of my site. The line subroutine just adds tabs to the beginning of a line and then prints a newline after the line. get_hash does what it says, it gets a hash from a data file.

    So you want to combine these fragment to do something, but your attempt to use eval to print_definitions isn't quite the best way ....

    You you almost have self-contained runnable code, you have some input data, what output do you want to produce?

      The following is a sample of what I want to be able to do.

      #!/usr/bin/perl use strict; use warnings FATAL => qw( all ); use CGI::Carp qw(fatalsToBrowser); use lib '../files/perl/lib'; use Base::HTML qw(print_story print_definitions); print_story(*DATA,1); __DATA__ Paragraph that opens the document. 2 Heading for definition list This is the paragraph that precedes the definition list. &print_definitions( file => "some_file_a.txt", headings => ["term","de +finition"],) This is the paragraph that follows the definition list. 2 Heading for next definition list This is the paragraph that precedes the next definition list. &print_definitions( file => "some_file_b.txt", headings => ["term","de +finition 1","definition 2"],) This is the paragraph that follows the next definition list. 2 Heading for the close of the document The paragraph that closes the document.

      In print_story I would like to have something simple like, but I doubt it will be that simple.

      elsif ($line =~ /^\&/) { eval($line); }
      Have a cookie and a very nice day!
      Lady Aleena

        In print_story I would like to have something simple like, but I doubt it will be that simple.

        Sure, it can be that simple, though I might feel safer trying to avoid arbitrary code execution with Safe like

        #!/usr/bin/perl -- use strict; use warnings; use Safe; my $str = <<'__STR__'; &print_definitions( file => "some_file_a.txt", headings => ["term","de +finition"],) &print_definitions( file => "some_file_b.txt", headings => ["term","de +finition 1","definition 2"],) &f() &f ( 1 ) &eff( 'a', "A\tB", 3 ) __STR__ my $namespace = __PACKAGE__; my %dispatch = ( eff => \&f, _default => sub {}, ); open my($in), '<', \$str; while( my $line = <$in> ){ if( my( $sub, $args ) = $line =~ /^\&([^\(\s]+)\s*(.*)/) { print "## $sub $args \n"; my $subref = $namespace->can( $sub ); $subref ||= $dispatch{$sub} || $dispatch{_default}; if( $subref ){ $subref->( length $args ? Safe->new->reval( $args ) : () ); } } } close $in; sub print_definitions { print "print_definitions says [ @_ ]\n\n"; } sub f { print "f says [ @_ ]\n\n"; } __END__ ## print_definitions ( file => "some_file_a.txt", headings => ["term" +,"definition"],) print_definitions says [ file some_file_a.txt headings ARRAY(0xb0b93c) + ] ## print_definitions ( file => "some_file_b.txt", headings => ["term" +,"definition 1","definition 2"],) print_definitions says [ file some_file_b.txt headings ARRAY(0xb0b5ec) + ] ## f () f says [ ] ## f ( 1 ) f says [ 1 ] ## eff ( 'a', "A\tB", 3 ) f says [ a A B 3 ]

        Does that clear things up for you?

Re: Evaluating subroutines from within data
by graff (Chancellor) on Jan 07, 2012 at 04:20 UTC
    If you really think you "need to insert another subroutine into the data", then you should think seriously about refactoring your app to use a real templating system. I've used Template::Toolkit to good effect (despite a daunting array of documentation), though maybe you'd prefer something simpler like HTML::Template.

    I'm pretty sure there are other template systems as well. The whole point is that they support the idea of allowing "text data" (the template) to function as code when you want it to.

    If I understand your description, you'd like to have a "definition list" stored in one file, and then have one line in the data for a given page that will simply incorporate the def.list at a chosen point. That's one of the nice things that a template system will do very easily. (Other things you might like include loops and conditionals, all tied up in a format that still ends up looking a lot like the actual document structure that defines your page layout.)

    Maybe the amount of refactoring you'd have to do to switch whole-hog to a template system is more work that you want to do, which would be a shame, because it's not clear that carrying on in your current direction, without a template system, will be any less arduous.

    I actually think there's a good chance that you can try a simpler template system just for this one (class of) page layout(s) that you're struggling with, and not break the rest of your app. Then you can gradually expand your use of templates as you continue to grow and update the app.

      graff, I have looked at both of those modules, and neither would do the templating I need for my site. I have yet to find anything that will insert a bunch of code before the main body of the page and another bunch of code at the end of the main body of the page to my specifications. Also, I keep all of my data stored in pipe delineated text files. I've put a sample of what I want to do in my reply to the anonymous monk above.

      If you want to see start_html and end_html, let me know; I'll put it on my scratchpad for you to look at.

      Have a cookie and a very nice day!
      Lady Aleena

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others about the Monastery: (5)
As of 2014-10-26 06:09 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    For retirement, I am banking on:










    Results (151 votes), past polls