Отправляет email-рассылки с помощью сервиса Sendsay

Perl - подпишись и учись!

  Все выпуски  

Perl - подпишись и учись! :: И снова Template::Toolkit


Информационный Канал Subscribe.Ru

    Приветствую всех!(А именно 11068 подписчиков)
Сегодня рассылка вновь будет посвящена Template::Toolkit.
Игорь Лобанов, http://template-toolkit.ru любезно предоставил свой новый перевод "по теме" :) Как всегда можете обсудить статью на форуме сайта http://perl.dp.ua.

Разделение кода, представления, и конфигурации

На Perl Review опубликована статья Брайана де Фоя (brian d foy) "Separating Code, Presentation, and Configuration". Ниже приводится перевод статьи.

Краткий обзор.

Я взял программу из предыдущей статьи и разделил код, представление и конфигурацию, чтобы сделать программу более гибкой и легкой для поддержки.

1. Введение.

В последнем выпуске я представил программу, которую я использую для того, чтобы забирать и отображать Rich Site Summaries (RSS) с других веб-сайтов1. В коде я использовал литеральные величины, чтобы указать какие файлы загружать и как представлять данные, и обещал что в этом выпуске я это исправлю.

Листинг 1 содержит программу, которую я представлял в предыдущем выпуске. В массиве @files хранятся файлы, которые необходимо загрузить, $base - каталог, где сохраняется вывод, и несколько выражений print создают HTML с подстановкой простых переменных (что предпочтительнее скажем чем использовать HTML-функции модуля CGI). Это негибкий и трудный для сопровождения код. Когда я захочу поменять список сайтов или вывод, я рискую сломать программу, если наберу что-то неправильно или сделаю другую ошибку.

Листинг 1: Загрузчик RSS с жесткопрописанными значениями.
1 #!/usr/bin/perl -w
2 use strict;
3
4 use LWP::Simple;
5 use XML::RSS;
6
7 my @files = qw(
8   http://use.perl.org/useperl.rss
9   http://search.cpan.org/rss/search.rss
10   http://jobs.perl.org/rss/standard.rss
11   http://www.perl.com/pace/perlnews.rdf
12   http://www.perlfoundation.org/perl-foundation.rdf
13   http://www.stonehenge.com/merlyn/UnixReview/ur.rss
14   http://www.stonehenge.com/merlyn/WebTechniques/wt.rss
15   http://www.stonehenge.com/merlyn/LinuxMag/lm.rss
16 );
17
18 my $base = '/usr/home/comdog/TPR/rss-html';
19
20 foreach my $url ( @files )
21   {
22   my $file = $url;
23
24   $file =~ s|.*/||;
25
26   my $result = open my $fh, "> $base/$file.html";
27
28   unless( $result )
29     {
30     warn "Could not open [$file] for writing! $!";
31     next;
32     }
33
34   select $fh;
35
36   my $rss = XML::RSS->new();
37   my $data = get( $url );
38   $rss->parse( $data );
39
40   my $channel = $rss->{channel};
41   my $image = $rss->{image};
42
43   print <<"HTML";
44   <table cellpadding=1><tr><td bgcolor="#000000">

45   <table cellpadding=5>
46     <tr><td bgcolor="#aaaaaa" align="center">
47 HTML
48
49   if( $image->{url} )
50     {
51     my $img = qq|<img src="$$image{url}" alt="$$channel{title}">|;
52     print qq|<a href="$$channel{link}">$img</a><br>\n|;
53     }
54   else
55     {
56     print qq|<a href="$$channel{link}">$$channel{title}</a><br>\n|;
57     }
58
59   print <<"HTML";
60   <font size="-1">$$channel{description}</font>

61   </td></tr>
62   <tr><td bgcolor="#bbbbff" width=200><font size="-1">
63 HTML
64
65   foreach my $item ( @{ $rss->{items} } )
66     {
67     print qq|<b>></b><a href="$$item{link}">$$item{title}</a><br><br>\n|;
68     }
69
70   print <<"HTML";
71     </font></td></tr>

72   </td></tr></table>
73   </td></tr></table>
74 HTML
75
76   close $fh;
77   }


2. Отделение представления.

Хороший дизайн не связывает себя с частным представлением данных. Моя программа должна забрать данные и сделать их доступными чему-то, что их представляет - то, что я работаю с RSS не должно иметь значения. Я могу захотеть производить HTML, TeX, простой текст, или даже какой-то формат, который не могу себе представить.

Возможно кто-то захочет написать собственную систему для работы с шаблонами, но мне нравится модуль Text::Template Марка-Джейсона Доминуса (Mark-Jason Dominus). Он делает все что мне необходимо, не требует для своей работы дополнительных программ и написан полностью на Perl. У него простой интерфейс и мне не требуется изучать язык шаблонов, поскольку шаблоны используют Perl.

Листинг 2 - это та же программа, только вместо внедренного HTML используется Text::Template. В строке 5 я импортирую метод fill_in_file(). В строке 13 указываю шаблон, который буду использовать. Весь HTML, используемый программой, теперь вынесен в файл шаблона, приведенный в листинге 3.

Модуль Text::Template может принимать данные как хеш. Ключи хеша становятся именами переменных в шаблоне, а значения - значениями переменных шаблона, а также определяют тип переменных. Если значение хеша простой скаляр, переменная шаблона скаляр. Если значение хеша - анонимный массив, переменная шаблона - массив, и так далее.

Объект, создаваемый XML::RSS - анонимный хеш. Модуль предоставляет абстрактный интерфейс для создания, но не для доступа. Это отчасти именно то, что я должен передать моему шаблону. В шаблоне $rss->channel, которая в качестве значения содержит анонимный хеш, становится %channel, а $rss->items, содержащая анонимный массив становится @items.

Листинг 2: Использование шаблона.
1 #!/usr/bin/perl -w
2 use strict;
3
4 use LWP::Simple;
5 use Text::Template qw(fill_in_file);
6 use XML::RSS;
7
8 my @files = qw(
9   http://use.perl.org/useperl.rss
10 );
11
12 my $base = '.';
13 my $template = 'rss-html.tmpl';
14
15 foreach my $url ( @files )
16   {
17   my $file = $url;
18
19   $file =~ s|.*/||;
20
21   my $result = open my $fh, "> $base/$file.html";
22
23   unless( $result )
24     {
25     warn "Could not open [$file] for writing! $!";
26     next;
27     }
28
29   my $rss = XML::RSS->new();
30   my $data = get( $url );
31   $rss->parse( $data );
32
33   print fill_in_file( $template, HASH => $rss );
34   close $fh;
35   }


Внутри шаблона Text::Template исполняет блоки кода, которые он находит между фигурными скобками. Он заменяет блок последним вычисленным выражением. Имена переменных - ключи хеша, ссылку на который я передал в качестве аргумента функции fill_in_file() в коде, приведенном в листинге 2.

Листинг 3: HTML шаблон.
1 <table cellpadding=1><tr><td bgcolor="#000000">

2 <table cellpadding=5>
3   <tr>
4     <td bgcolor="#aaaaaa" align="center">
5     <a href="{ $channel{link} }">{
6
7     $image ? qq|<img src="$image" alt="$channel{title}">| : $channel{title}
8
9     }</a><br>

10
11     { $channel{description} }
12     </td>
13   </tr>
14
15   <tr>
16     <td bgcolor="#bbbbff" width=200><font size="-1">

17 {
18     my $str;
19
20     foreach my $item ( @items )
21       {
22       $str .= qq|<b>></b><a href="$$item{link}">$$item{title}</a><br><br>\n|;
23       }
24
25     $str;
26 }</font></td>
27   </tr>
28 </td></tr></table>

29 </td></tr></table>


Как только система шаблонов задейстована, я могу менять представление не меняя логику кода. Если я приму решение изменить вид представления данных, я поменяю только шаблон. Если мне вместо HTML требуется простой текст, я только изменю под новый формат шаблон, как это сделано в листинге 4.

Листинг 4: Шаблон для обычного текста.
1 { $channel{title} }
2
3 { $channel{description} }
4
5 {
6 my $str;
7
8 foreach my $item ( @items )
9   {
10   $str .= qq|* $$item{title}\n|;
11   }
12
13 $str;
14 }


3. Отделение конфигурации.

Хороший дизайн также позволяет адаптировать скрипт к различному окружению. В листинге 1 я жестко прописал значение директории для вывода, что делает мой скрипт хрупким - если моя домашняя директория поменяется, скрипт сломается. Кроме того в листинге 2 жестко прописано имя шаблона, несмотря на то что я могу менять представление, изменяя шаблон. Мне нужно иметь возможность давать каждому шаблону содержательное имя вместо использования одного и того же имени для разного содержимого.

Многие свободно-доступные скрипты, которые я нашел в Интернет требуют, чтобы пользователь отредактировал верхнюю часть скрипта или включаемую библиотеку, которая содержит только конфигурационные данные. Такой подход требует, чтобы конечный пользователь знал основы языка программирования и правил скрипт - ошибка сломает скрипт. Плохие конфигурационные данные могут привести к неожиданным результатам, но они не поломают программу.

Я могу указать текущие конфигурационные данные несколькими способами и покажу только один из них. Архив Comprehensive Perl Archive Network (CPAN)2 содержит несколько модулей для разбора конфигурационных файлов в различных форматах или аргументов командной строки. Дизайнеры должны выбирать подход, который удовлетворяет их нуждам.

Когда я впервые начал отделять данные конфигурации от моих скриптов, я перебрал несколько модулей на CPAN и остановился на ConfigReader::Simple, который использует построчный формат ключ-значение. Я использовал его настолько часто, что начал отправлять мои изменения Беку Оберину (Bek Oberin), автору оригинальной версии, затем полностью взял на себя поддержку модуля.

Листинг 5 адаптирует листинг 2 к использованию ConfigReader::Simple. Я создаю новый объект конфигурации, затем читаю значения из объекта. Модуль преобразует имена ключей конфигурации в имена методов для простого доступа (хотя для доступа к значениям ключей с экзотическими именами, которые не могут быть преобразованы в идентификаторы Perl приходится использовать метод get()). Листинг 6 содержит конфигурационный файл.

Листинг 5: Использование ConfigReader::Simple.
1 #!/usr/bin/perl -w
2 use strict;
3
4 use ConfigReader::Simple;
5 use LWP::Simple;
6 use Text::Template qw(fill_in_file);
7 use XML::RSS;
8
9 my $config = ConfigReader::Simple->new( './rss.config' );
10
11 my $base = $config->base;
12 my $template = $config->template;
13 my $extension = $config->extension;
14
15 my @files = split /\s+/, $config->files;
16
17 foreach my $url ( @files )
18   {
19   my $file = $url;
20
21   $file =~ s|.*/||;
22
23   my $result = open my $fh, "> $base/$file.$extension";
24
25   unless( $result )
26     {
27     warn "Could not open [$file] for writing! $!";
28     next;
29     }
30
31   my $rss = XML::RSS->new();
32   my $data = get( $url );
33   $rss->parse( $data );
34
35   print $fh fill_in_file( $template, HASH => $rss );
36   close $fh;
37   }

Листинг 6: Файл конфигурации.
1 base .
2 template rss-html.tmpl
3 files http://use.perl.org/useperl.rss
4 extension html


4. Заключение.

Я могу уменьшить размер моих программ, отделяя код от логики представления и конфигурационной информации. Это разделение делает программу более гибкой и простой в адаптации к новым окружениям. Шаблоны позволяют изменять вывод, а конфигурационные файлы управлять работой программы без изменения кода. Text::Template и ConfigReader::Simple делают это настолько простым, насколько возможно.

5. Ссылки

Все модули, упоминаемые в этой статье, можно найти на Comprehensive Perl Archive Network (CPAN) - http://search.cpan.org/

6. Об авторе.

Брайан де Фой (brian d foy) - издатель The Perl Review.

Примечания.

1 "Simple RSS with Perl" by brian d foy, The Perl Review v0 i5, November 2002, http://www.theperlreview.com/

2 http://search.cpan.org/

оригинал статьи на Perl Review (PDF)
перевод статьи на форуме Template Toolkit


http://subscribe.ru/
http://subscribe.ru/feedback/
Подписан адрес:
Код этой рассылки: comp.soft.prog.nicksperl
Отписаться

В избранное