Gokee2 has asked for the wisdom of the Perl Monks concerning the following question:

Hello all, I am making a archive section for a website so far it looks like http://skagitattic.no-ip.org/gokee2/grandpa/website/website/names.cgi. I made it using mostly a cgi file with print statements and a few of the prints just printing whatever is shoved into them from tt2. However as you can see from my post the code looks like a mess and my tt file is not much better. I can find where stuff is in the source now but I hate to think what it will be like a few weeks away... I was wondering what I should do to clean it up? I have seen mention of a tt2 mod for to have apache do cgi with a tt2 file, anyone know if that works well? Converting all my perl code to tt2 would take a while but if it cleans it up might be worth it. I made the code quite a while ago and it has some rather bad parts anyway. So what I am really asking is a design issue. The code works as it but is a pain to work with. Should I try to go all tt2 or try another mix? I was thinking about making a .tt file holds the perl code and trying to make another file that puts it together but it looked like a mess when I started to put it together. It does not seem like what I am doing is that complicated. Anyway here is the code.
#!/usr/bin/perl use strict; [% PROCESS archives.tt -%] #loads mods for easy web page CGI use CGI qw(:standard); use CGI::Carp qw(warningsToBrowser fatalsToBrowser); #Loads a mod for easy file access use Tie::File; #Loads a mod used for determining how many pages worth of names there +are use POSIX; print "Content-type: text/html\n\n"; our $csv_file = '../archives.csv'; our $delete; our $search; our $page_start; our $page_end; our $host = "$ENV{HTTP_HOST}"; our $edit; our $current_page = 1; our @list; our $top; our $footer; our ($url_to_myself, $info) = split (/names.cgi\?/, $ENV{REQUEST_URI}) +; our $lines_per_page = 3; our $lines; our $pages; #our $lines_in_file = @names; our ($lines_in_file, undef) = split(/\s/, `wc -l $csv_file`); our $pages_in_file = ceil($lines_in_file/$lines_per_page); our @line_number; our $search_for; #Puts the information from the end of the url in usable form my @info = split("&", $info); foreach my $part(@info){ my ($property, $value) = split("=", $part); if ($property eq "page"){$current_page = "$value";} if ($property eq "edit"){$edit = "$value";} if ($property eq "search"){$search = $value;} if ($property eq "delete"){$delete = $value;} } if ($edit){ edit(); } elsif ($search && $search ne ""){ $search_for = $search; search(); page_search(); } elsif ($delete){ delete_name(); } else{ page_list(); } sub top{ if ($search && $search ne ""){ } else{ tie @list, 'Tie::File', "$csv_file" or print '<BR><BR><H3> The archi +ves are missing! Please email the webmaster.</H3>' and exit; } print '<FORM action="'. "names.cgi". '" method="get"> Search: <INPUT type="text" name="search" size="12" value="'. "$search_for". '"> <INPUT type="submit" value="Go"> <BR><BR> '; $lines = scalar @list; $pages = ceil($lines/$lines_per_page); } sub pages{ if ($pages > 1){ print "\n<BR><BR>Page\n"; my $page = 0; while ($page < $pages){ $page = $page+1; if ($current_page ne $page){ print "<A href=\"names.cgi?page=$page&search=$search_for\">$pa +ge</A>\n"; } else{ print "<B>$page</B>\n"; } } } print '[% foot %]'; } sub page_list{ print '[% head %]'; print '<H1 id="title">Archives</H1>'. "\n"; top(); print '<TABLE border="1">'; list(); print '</TABLE>'; pages{}; } sub page_search{ print '[% head %]'; print '<H1 id="title">Resaults</H1>'. "\n"; top(); if ($lines <= 0){ print "<BR>\n\"$search_for\" - did not match any names. "; } else{ print "Found $lines results for $search_for.\n<BR>"; print '<TABLE border="1">'; list(); print '</TABLE>'; } pages{}; } #list(); #@names = sort {uc($a) cmp uc($b)} @names; sub list{ my $page_end; if ($current_page > $pages){$current_page = 1;} my $page_start = (($current_page-1)*$lines_per_page); if ($current_page == $pages){$page_end = $lines;} else {$page_end = ($current_page*($lines_per_page));} my $count = ($page_start); while ($count < $page_end){ my $name; my $title; my $description; if ( $list[($count)]=~/"(.+)","(.+)","(.*)"/ ){ $name = $1; $title = $2; $description = $3; } else{ $name = ""; $title = "Bad Line"; $description = "Malformed line at line number ". ($count+1). "." +; } print "<TR><TD><A href=\"$name\">$title</A></TD><TD>$description< +/TD></TR>\n"; $count ++; } } sub search { tie my @data, 'Tie::File', "$csv_file" or print '<BR><BR><H3> The arch +ives are missing! Please email the webmaster.</H3>' and exit; my $count = 0; foreach my $line(@data){ if ($line =~ /$search_for/i){ push (@list, $line); push (@line_number, $count); } $count ++; } }
And the .tt file archives.tt looks like
[%- head = BLOCK -%] [% top_selected = "Archives"; PROCESS top.tt -%] <link rel="stylesheet" type="text/css" href="[%before%]style.css"> </head> <BODY id="main"> [% PROCESS topbar.tt %] <DIV class="normal"> <BR> [%- END %] [% outro = BLOCK %] Test [%- foovar %] [%END%] [%- foot = BLOCK%] </DIV> </DIV> </BODY> </html> [%- END %]

Replies are listed 'Best First'.
Re: tt2 with perl cgi and a csv file
by stiller (Friar) on Mar 26, 2008 at 09:18 UTC
    At first sight, this looks like a mess, but it's actually not so bad.
    The good part: you have divided the work into manageable pieces (subs). The bad part: an enormous amount of global vars make it hard to follow.
    To rewrite, I would start by building up an array of hashes. The array represents the lines from the csv-file, and the hashes is the data on each line. Iterating over the csv file you decide if the current line is to be included or treated special. A simple rewrite of the template, and feed the AoH to it. Done.
    cheers
      "I would start by building up an array of hashes. The array represents the lines from the csv-file, and the hashes is the data on each line." So you would load the whole csv file into memory? "Iterating over the csv file you decide if the current line is to be included or treated special." So now I have only parts of the csv file in memory? So if I searched to foo I would only then have the lines that have foo in then in my array? What about editing? In my older even worse write of this (kept track of a mailing list) I would edit the item based on line number. I guess I could put the line number into my hash in the array though. I guess I am not really seeing how this cleans it up much. I might try it though, I will think about it some more. I have just been changing it about so that all the url options go into %options. That is helping a bit. I have also tried to get rid of some of the global vars that are not used anymore. Then I put my page_list and page_search right into the "if ($options{search}){" and else. I put head foot and outside of the if statement. Its helping a little. Also what is "AoH"? Thanks!
        AoH is the Array (lines) of Hashes (fields).

        Your Mother's answer below is very sound advice. If you have more than one script / template, time invested in learning a framework will pay back great. If you want to try that, I'd recomend strongly that you check out catalyst. The new book they advertise is very good (I've read it) if you like a stepwise and practical introduction. Otherwise they have lots of online tutorials and documentation if you prefer that.

        If you don't want to go that route yet, try factor out the parsing of the csv data (take a look at Text::CSV_XS and DBD::CSV. The latter of these will make your csv file feel like a database, simplifying searching and paging. Also, pull together one coherent template by extracting the print statements from the code.

        hth

Re: tt2 with perl cgi and a csv file
by Your Mother (Archbishop) on Mar 26, 2008 at 16:01 UTC

    For what it's worth, I've done a couple of fairly large CGIs (a few thousand lines; almost all template code, which was in turn mostly XHTML, with a dispatch hash and related subs). You break your templates into blocks which can be named or put into vars like you've done above. Then stuff the Template code under __DATA__ and something like-

    print CGI::header(); $template->process(\*DATA, $template_data) or warn $Template::ERROR;

    Now that that's out there I'd recommend against it. It's okay for standalone, one-off stuff (in fact that's exactly what it's for because all the code is bundled in one file so it's easy to deploy and it's sometimes easier to edit code in one big file than 30 different ones). For stuff with room to grow it's an idea that will come back and bite.

    It sounds like you're ready for CGI::Application, Catalyst, and friends. There is a learning curve but those sorts of packages abstract away *so* much many of the messy details (especially Catalyst) that you only realize how awesome it is after doing it manually yourself. Three weeks dedicated to reading docs and writing test code with Catalyst would likely shave at least that much time off a large project done with it.

      I looked at CGI::Application and Catalyst. I have rather been hoping to be all done inside of one or two weeks though. I guess I need to see a few more messy details before I am ready for them. This project should not get real big. I just need a way to edit the lines and a auto-create, to create a line for every new file dropped into archives, and that should be it. When it at last gets top heavy (like my own template system did before I started using tt2), I will look back into CGI::Application and Catalyst. Thanks!

        I'm all for it but let me regale you with a tale of my false economy.

        I did a standalone CGI, DB driven site for myself with really some of the tightest, nicest code I've ever written. It was entirely similar to a straightforward CGI::Application or Catalyst app, using URI dispatching and method checking and such. Because it was somewhere around the 1,000th CGI I'd written, it was easy, it was clean, and it took only 3 days to write because I could lean on experience + CPAN for sessions and DB stuff.

        Then I realized I wanted RSS/Atom on it. Well, there's another half day coding. Then comments. Another day or two gone and the code is now not looking so clean. I realized I'd like admin editing to work differently and have queues for drafts and published pages and be searchable and have topics/tags and Ajax and email updates and better error reporting and self-validate XHTML and... Holy crap, that is gonna be a drag... Redid it in Catalyst.

        Extra tasks like those when done in Catalyst can often be as short as 10 new lines of code. Easier to read. Easier to test. Easier to maintain. Fewer chances you left some exploit accidentally. Plus more jobs for Cat devs. :)