File Coverage

blib/lib/WriteAt.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             #===============================================================================
2             #
3             # DESCRIPTION: WriteAt - suite for book writers
4             #
5             # AUTHOR: Aliaksandr P. Zahatski,
6             #===============================================================================
7             package WriteAt;
8              
9             =head1 NAME
10              
11             WriteAt - suite for make books and docs in pod6 format
12              
13             =head1 SYNOPSIS
14              
15              
16             =TITLE MyBook
17             =SUBTITLE My first free book
18             =AUTHOR Alex Green
19             =DESCRIPTION Short description about this book
20             =begin CHANGES
21             Aug 18th 2010(v0.2)[zag] preface
22            
23             May 27th 2010(v0.1)[zag] Initial version
24             =end CHANGES
25            
26             =Include src/book_preface.pod
27             =CHAPTER Intro
28            
29             B is an evolution of Perl 5's L|doc:perlpod>
30             (POD) markup. Compared to Perl 5 POD, Perldoc's Pod dialect is much more
31             uniform, somewhat more compact, and considerably more expressive. The
32             Pod dialect also differs in that it is a purely descriptive mark-up
33             notation, with no presentational components.
34              
35             =head1 DESCRIPTION
36              
37             Books must be high available for readers and writers !
38             WriteAt - suite for free book makers. It help make and prepare book for publishing.
39              
40             =head1 INSTALLATION
41              
42             There are several ways to install C to your system.
43              
44             =head2 Install under Ubuntu
45              
46             sudo add-apt-repository ppa:zahatski/ppa
47             sudo apt-get install writeat
48              
49             =head2 From CPAN
50              
51             cpanm WriteAt
52              
53             For book creation it is necessary following software:
54              
55             * docbook-4.5
56             * xslt processor
57             * GNU make
58              
59             =head2 Checkout templates
60              
61             Grab template:
62            
63             git clone https://github.com/zag/writeat-tmpl-firstbook.git
64             cd writeat-tmpl-firstbook
65             make
66              
67             Point your web brouser to C file in C directory.
68              
69             =cut
70              
71 1     1   43676 use strict;
  1         3  
  1         44  
72 1     1   8 use warnings;
  1         2  
  1         33  
73 1     1   18 use v5.10;
  1         4  
  1         62  
74             our $VERSION = '0.05';
75 1     1   527 use WriteAt::CHANGES;
  0            
  0            
76             use WriteAt::AUTHOR;
77             use WriteAt::To::DocBook;
78             use utf8;
79              
80             =head1 FUNCTIONS
81              
82             =cut
83              
84             sub get_name_from_locale {
85             my $name = shift;
86             my %SEM = (
87             TITLE => [ qr/TITLE/, qr/^ЗАГОЛОВОК/ ],
88             SUBTITLE => [ qr/SUBTITLE/, qr/ПОДЗАГОЛОВОК/ ],
89             AUTHOR => [ qr/AUTHOR/, qr/АВТОР/ ],
90             CHANGES => [ qr/CHANGES/, qr/ИЗМЕНЕНИЯ/ ],
91             DESCRIPTION => [ qr/DESCRIPTION/, qr/ОПИСАНИЕ/ ]
92             );
93             while ( my ( $k, $v ) = each %SEM ) {
94             foreach my $reg (@$v) {
95             if ( $name =~ $reg ) {
96             return $k;
97             }
98             }
99              
100             }
101             return undef;
102             }
103              
104             sub get_book_info_blocks {
105             my $tree = shift;
106             my $res = shift || return;
107             my $to = shift;
108             my @nodes = ref($tree) eq 'ARRAY' ? @$tree : ($tree);
109             my @tree = ();
110             foreach my $n (@nodes) {
111             unless ( ref($n) ) { #skip text
112             push @tree, $n;
113             next;
114             }
115              
116             #convert =Include $n to DOM if To::* passed
117             if ( $to && $n->name eq 'Include' ) {
118             $n = $to->_make_dom_node($n);
119              
120             #set current path
121             $to->context->custom->{src} = $n->{PATH};
122              
123             }
124             if ( my $converted_block_name = &get_name_from_locale( $n->name ) ) {
125             push @{ $res->{$converted_block_name} }, $n;
126              
127             # overwrite original name
128             $n->{name} = $converted_block_name;
129             }
130             else {
131             push @tree, $n;
132             $n->childs( &get_book_info_blocks( $n->childs, $res, $to ) );
133             }
134             }
135             \@tree;
136             }
137              
138             =pod
139              
140             {
141             =tagname
142             =childs
143             }
144              
145             =cut
146              
147             sub get_childs {
148             my ( $name, $level, $tree ) = @_;
149             my @childs = ();
150             while ( my $current = shift @$tree ) {
151             my $cname = $current->name;
152             my $clevel = $current->{level};
153              
154             #set level 0 for semantic blocks
155             $clevel = 0 if $cname eq uc($cname);
156              
157             if (
158             ( defined($clevel) and ( $clevel < $level ) )
159             || ( ( $cname eq $name )
160             && ( $level == $clevel ) )
161              
162             )
163             {
164             unshift @$tree, $current;
165             return @childs;
166             }
167             push @childs, $current;
168             }
169             return @childs;
170             }
171              
172             =head2 make_levels ( blockname, level, $parsed_tree )
173              
174             Make tree using levels
175              
176             my $tree = Perl6::Pod::Utl::parse_pod( $t, default_pod => 1 )
177             || die "Can't parse ";
178             my ($root) = @$tree;
179             my $tree1 = $tree;
180             if ( $root->name eq 'pod' ) {
181             $tree1 = $root->childs;
182             }
183            
184             my $levels = &WriteAt::make_levels( "CHAPTER", 0, $tree1 );
185              
186             return
187              
188             [
189             {
190             node => {ref to object},
191             childs => [ array of childs]
192            
193             },
194             ...
195             ]
196             =cut
197              
198             sub make_levels {
199             my ( $name, $level, $tree ) = @_;
200              
201             #check if root node pod
202             # call for childs
203             if ( my $first = $tree->[0] ) {
204             return &make_levels( $name, $level, $first->childs )
205             if $first->name eq 'pod';
206             }
207              
208             my @res = ();
209             while ( my $current = shift @$tree ) {
210             next unless $current->name eq $name;
211             my $clevel = $current->{level};
212             my $cname = $current->name;
213              
214             #set level 0 for semantic blocks
215             $clevel = 0 if $cname eq uc($cname);
216              
217             if ( defined($clevel) ) {
218             next unless $clevel == $level;
219             }
220             push @res,
221             {
222             node => $current,
223             childs => [ &get_childs( $name, $level, $tree ) ]
224             };
225             }
226             return \@res;
227             }
228              
229             =head2 get_text(node1, node2, ...)
230              
231             return string of all childs texts nodes
232              
233             =cut
234              
235             sub get_text {
236             my @nodes = @_;
237             my $txt = '';
238             foreach my $n (@nodes) {
239             unless ( ref($n) ) {
240             $txt .= $n
241             } elsif ( $n->{type} eq 'text' ) {
242             $txt .= join "" => @{ $n->childs };
243             }
244             else {
245             $txt .= &get_text( @{ $n->childs } );
246             }
247             }
248             chomp($txt);
249             return $txt;
250             }
251              
252             =head2 rus2lat
253              
254             Translit rus to lat ( gost 7.79-2000 )
255              
256             rus2lat('russian text');
257              
258             =cut
259              
260             sub rus2lat($) {
261             my %hs = (
262             'аА' => 'a',
263             'бБ' => 'b',
264             'вВ' => 'v',
265             'гГ' => 'g',
266             'дД' => 'd',
267             'еЕ' => 'e',
268             'ёЁ' => 'jo',
269             'жЖ' => 'zh',
270             'зЗ' => 'z',
271             'иИ' => 'i',
272             'йЙ' => 'j',
273             'кК' => 'k',
274             'лЛ' => 'l',
275             'мМ' => 'm',
276             'нН' => 'n',
277             'оО' => 'o',
278             'пП' => 'p',
279             'рР' => 'r',
280             'сС' => 's',
281             'тТ' => 't',
282             'уУ' => 'u',
283             'фФ' => 'f',
284             'хХ' => 'kh',
285             'цЦ' => 'c',
286             'чЧ' => 'ch',
287             'шШ' => 'sh',
288             'щЩ' => 'shh',
289             'ъЪ' => '',
290             'ыЫ' => 'y',
291             'ьЬ' => '',
292             'эЭ' => 'eh',
293             'юЮ' => 'ju',
294             'яЯ' => 'ja'
295             );
296             my $z = shift;
297             $z =~ s|[$_]|$hs{$_}|gi for keys %hs;
298             $z;
299             }
300              
301             =head2 get_time_stamp_from_string
302              
303             Get time stamp from strnigs like this:
304              
305             2012-11-27T09:39:19Z
306             2012-11-27 09:39:19
307             2012-11-27 09:39
308             2012-11-27 09
309             2012-11-27
310              
311             return unixtimestamp
312              
313             =cut
314              
315             sub get_time_stamp_from_string {
316             my $str = shift || return;
317             use DateTime::Format::W3CDTF;
318             #if w3cdtf time
319             if ( $str =~ /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(Z|.\d{2}:\d{2})/ ) {
320             my $dt = DateTime::Format::W3CDTF->new();
321             return $dt->parse_datetime($str)->epoch();
322             }
323             elsif ( $str =~
324             /^(\d{4})-(\d{2})-(\d{2})(?:.(\d{2})(?::(\d{2})(?::(\d{2}))?)?)?/ )
325             {
326             my $dt = DateTime->new(
327             year => $1,
328             month => $2,
329             day => $3,
330             hour => $4 || 0,
331             minute => $5 || 0,
332             second => $6 || 0,
333             nanosecond => 500000000,
334             );
335             return $dt->epoch;
336             }
337             die "Bad srting $str";
338             }
339              
340             =head2 unixtime_to_string timestamp
341              
342             Return
343              
344             =cut
345              
346             sub unixtime_to_string {
347             my $time = shift || return;
348             my ( $sec, $min, $hour, $mday, $mon, $year, $wday ) = gmtime($time);
349             $year += 1900;
350             return sprintf( "%04d-%02d-%02dT%02d:%02d:%02dZ",
351             $year, $mon + 1, $mday, $hour, $min, $sec );
352             }
353              
354             sub filter_published {
355             my $tree = shift;
356             my $ctx = shift || return;
357             unless (ref($ctx)) {
358             $ctx = new WriteAt::UtilCTX:: (filter_time=>&get_time_stamp_from_string($ctx))
359             }
360             my @nodes = ref($tree) eq 'ARRAY' ? @$tree : ($tree);
361             my @tree = ();
362             foreach my $n (@nodes) {
363             unless ( ref($n) ) { #skip text
364             push @tree, $n;
365             next;
366             }
367             if ($n->name eq 'pod' ) {
368             push @tree, $n;
369             $n->childs( &filter_published( $n->childs, $ctx ) );
370             next;
371             }
372             # handle publish attr
373             my $pub_time =
374             &get_time_stamp_from_string( $n->get_attr->{published} )
375             || $ctx->get_current_level_time()
376             || next; #if publish time empty skipit
377             my $name = $n->name;
378             #prcess head levels
379             if ( $name eq 'head' ) {
380             $ctx->switch_head_level( $n->{level}, $pub_time );
381             } elsif ( $name eq uc($name)) {
382             $pub_time = &get_time_stamp_from_string( $n->get_attr->{published} );
383             $ctx->switch_head_level( 0, $pub_time || 0);
384             next unless $pub_time;
385             }
386             my $filter_time = $ctx->get_filter_time;
387             #skip node
388             next if ( $pub_time > $filter_time );
389             push @tree, $n;
390             $n->childs( &filter_published( $n->childs, $ctx ) );
391             }
392             \@tree;
393             }
394              
395              
396             =head1 METHODS
397              
398             =cut
399              
400             sub new {
401             my $class = shift;
402             bless( ( $#_ == 0 ) ? shift : {@_}, ref($class) || $class );
403             }
404              
405             1;
406             package WriteAt::UtilCTX;
407             use strict;
408             use warnings;
409             sub new {
410             my $class = shift;
411             my $self = bless( ( $#_ == 0 ) ? shift : {@_}, ref($class) || $class );
412             #init head levels
413             $self->{HEAD_LEVELS} = 0;
414             $self->{stack} = [];
415             $self;
416             }
417              
418             sub get_filter_time {
419             my $self = shift;
420             return $self->{filter_time}
421             }
422             sub get_current_level_time {
423             my $self = shift;
424             return $self->{stack}->[-1]
425             }
426             sub switch_head_level {
427             my $self = shift;
428             my $level = shift;
429             my $pub_time = shift;
430             my $prev = $self->{HEAD_LEVELS};
431             my $time_stack = $self->{stack};
432             if (defined($level) && $level == $prev ) {
433             $time_stack->[$level] = $pub_time;
434             } elsif ( $prev < $level ) {
435             push @{$time_stack}, $pub_time for ( 1..$level-$prev);
436             } else #$prev > $level
437             {
438             pop @{$time_stack} for ( 1..$prev-$level);
439             $time_stack->[$level] = $pub_time if defined($pub_time);
440             }
441             $self->{HEAD_LEVELS} = $level;
442             return $prev
443             }
444             1;
445             __END__