xref: /openbsd-src/gnu/usr.bin/perl/cpan/Pod-Simple/lib/Pod/Simple/HTMLBatch.pm (revision a0747c9f67a4ae71ccb71e62a28d1ea19e06a63c)
1
2require 5;
3package Pod::Simple::HTMLBatch;
4use strict;
5use vars qw( $VERSION $HTML_RENDER_CLASS $HTML_EXTENSION
6 $CSS $JAVASCRIPT $SLEEPY $SEARCH_CLASS @ISA
7);
8$VERSION = '3.40';
9@ISA = ();  # Yup, we're NOT a subclass of Pod::Simple::HTML!
10
11# TODO: nocontents stylesheets. Strike some of the color variations?
12
13use Pod::Simple::HTML ();
14BEGIN {*esc = \&Pod::Simple::HTML::esc }
15use File::Spec ();
16
17use Pod::Simple::Search;
18$SEARCH_CLASS ||= 'Pod::Simple::Search';
19
20BEGIN {
21  if(defined &DEBUG) { } # no-op
22  elsif( defined &Pod::Simple::DEBUG ) { *DEBUG = \&Pod::Simple::DEBUG }
23  else { *DEBUG = sub () {0}; }
24}
25
26$SLEEPY = 1 if !defined $SLEEPY and $^O =~ /mswin|mac/i;
27# flag to occasionally sleep for $SLEEPY - 1 seconds.
28
29$HTML_RENDER_CLASS ||= "Pod::Simple::HTML";
30
31#
32# Methods beginning with "_" are particularly internal and possibly ugly.
33#
34
35Pod::Simple::_accessorize( __PACKAGE__,
36 'verbose', # how verbose to be during batch conversion
37 'html_render_class', # what class to use to render
38 'search_class', # what to use to search for POD documents
39 'contents_file', # If set, should be the name of a file (in current directory)
40                  # to write the list of all modules to
41 'index', # will set $htmlpage->index(...) to this (true or false)
42 'progress', # progress object
43 'contents_page_start',  'contents_page_end',
44
45 'css_flurry', '_css_wad', 'javascript_flurry', '_javascript_wad',
46 'no_contents_links', # set to true to suppress automatic adding of << links.
47 '_contents',
48);
49
50# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
51# Just so we can run from the command line more easily
52sub go {
53  @ARGV == 2 or die sprintf(
54    "Usage: perl -M%s -e %s:go indirs outdir\n  (or use \"\@INC\" for indirs)\n",
55    __PACKAGE__, __PACKAGE__,
56  );
57
58  if(defined($ARGV[1]) and length($ARGV[1])) {
59    my $d = $ARGV[1];
60    -e $d or die "I see no output directory named \"$d\"\nAborting";
61    -d $d or die "But \"$d\" isn't a directory!\nAborting";
62    -w $d or die "Directory \"$d\" isn't writeable!\nAborting";
63  }
64
65  __PACKAGE__->batch_convert(@ARGV);
66}
67# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
68
69
70sub new {
71  my $new = bless {}, ref($_[0]) || $_[0];
72  $new->html_render_class($HTML_RENDER_CLASS);
73  $new->search_class($SEARCH_CLASS);
74  $new->verbose(1 + DEBUG);
75  $new->_contents([]);
76
77  $new->index(1);
78
79  $new->       _css_wad([]);         $new->css_flurry(1);
80  $new->_javascript_wad([]);  $new->javascript_flurry(1);
81
82  $new->contents_file(
83    'index' . ($HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION)
84  );
85
86  $new->contents_page_start( join "\n", grep $_,
87    $Pod::Simple::HTML::Doctype_decl,
88    "<html><head>",
89    "<title>Perl Documentation</title>",
90    $Pod::Simple::HTML::Content_decl,
91    "</head>",
92    "\n<body class='contentspage'>\n<h1>Perl Documentation</h1>\n"
93  ); # override if you need a different title
94
95
96  $new->contents_page_end( sprintf(
97    "\n\n<p class='contentsfooty'>Generated by %s v%s under Perl v%s\n<br >At %s GMT, which is %s local time.</p>\n\n</body></html>\n",
98    esc(
99      ref($new),
100      eval {$new->VERSION} || $VERSION,
101      $], scalar(gmtime), scalar(localtime),
102  )));
103
104  return $new;
105}
106
107# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
108
109sub muse {
110  my $self = shift;
111  if($self->verbose) {
112    print 'T+', int(time() - $self->{'_batch_start_time'}), "s: ", @_, "\n";
113  }
114  return 1;
115}
116
117# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
118
119sub batch_convert {
120  my($self, $dirs, $outdir) = @_;
121  $self ||= __PACKAGE__; # tolerate being called as an optionless function
122  $self = $self->new unless ref $self; # tolerate being used as a class method
123
124  if(!defined($dirs)  or  $dirs eq ''  or  $dirs eq '@INC' ) {
125    $dirs = '';
126  } elsif(ref $dirs) {
127    # OK, it's an explicit set of dirs to scan, specified as an arrayref.
128  } else {
129    # OK, it's an explicit set of dirs to scan, specified as a
130    #  string like "/thing:/also:/whatever/perl" (":"-delim, as usual)
131    #  or, under MSWin, like "c:/thing;d:/also;c:/whatever/perl" (";"-delim!)
132    require Config;
133    my $ps = quotemeta( $Config::Config{'path_sep'} || ":" );
134    $dirs = [ grep length($_), split qr/$ps/, $dirs ];
135  }
136
137  $outdir = $self->filespecsys->curdir
138   unless defined $outdir and length $outdir;
139
140  $self->_batch_convert_main($dirs, $outdir);
141}
142
143# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
144
145sub _batch_convert_main {
146  my($self, $dirs, $outdir) = @_;
147  # $dirs is either false, or an arrayref.
148  # $outdir is a pathspec.
149
150  $self->{'_batch_start_time'} ||= time();
151
152  $self->muse( "= ", scalar(localtime) );
153  $self->muse( "Starting batch conversion to \"$outdir\"" );
154
155  my $progress = $self->progress;
156  if(!$progress and $self->verbose > 0 and $self->verbose() <= 5) {
157    require Pod::Simple::Progress;
158    $progress = Pod::Simple::Progress->new(
159        ($self->verbose  < 2) ? () # Default omission-delay
160      : ($self->verbose == 2) ? 1  # Reduce the omission-delay
161                              : 0  # Eliminate the omission-delay
162    );
163    $self->progress($progress);
164  }
165
166  if($dirs) {
167    $self->muse(scalar(@$dirs), " dirs to scan: @$dirs");
168  } else {
169    $self->muse("Scanning \@INC.  This could take a minute or two.");
170  }
171  my $mod2path = $self->find_all_pods($dirs ? $dirs : ());
172  $self->muse("Done scanning.");
173
174  my $total = keys %$mod2path;
175  unless($total) {
176    $self->muse("No pod found.  Aborting batch conversion.\n");
177    return $self;
178  }
179
180  $progress and $progress->goal($total);
181  $self->muse("Now converting pod files to HTML.",
182    ($total > 25) ? "  This will take a while more." : ()
183  );
184
185  $self->_spray_css(        $outdir );
186  $self->_spray_javascript( $outdir );
187
188  $self->_do_all_batch_conversions($mod2path, $outdir);
189
190  $progress and $progress->done(sprintf (
191    "Done converting %d files.",  $self->{"__batch_conv_page_count"}
192  ));
193  return $self->_batch_convert_finish($outdir);
194  return $self;
195}
196
197
198sub _do_all_batch_conversions {
199  my($self, $mod2path, $outdir) = @_;
200  $self->{"__batch_conv_page_count"} = 0;
201
202  foreach my $module (sort {lc($a) cmp lc($b)} keys %$mod2path) {
203    $self->_do_one_batch_conversion($module, $mod2path, $outdir);
204    sleep($SLEEPY - 1) if $SLEEPY;
205  }
206
207  return;
208}
209
210sub _batch_convert_finish {
211  my($self, $outdir) = @_;
212  $self->write_contents_file($outdir);
213  $self->muse("Done with batch conversion.  $$self{'__batch_conv_page_count'} files done.");
214  $self->muse( "= ", scalar(localtime) );
215  $self->progress and $self->progress->done("All done!");
216  return;
217}
218
219# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
220
221sub _do_one_batch_conversion {
222  my($self, $module, $mod2path, $outdir, $outfile) = @_;
223
224  my $retval;
225  my $total    = scalar keys %$mod2path;
226  my $infile   = $mod2path->{$module};
227  my @namelets = grep m/\S/, split "::", $module;
228        # this can stick around in the contents LoL
229  my $depth    = scalar @namelets;
230  die "Contentless thingie?! $module $infile" unless @namelets; #sanity
231
232  $outfile  ||= do {
233    my @n = @namelets;
234    $n[-1] .= $HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION;
235    $self->filespecsys->catfile( $outdir, @n );
236  };
237
238  my $progress = $self->progress;
239
240  my $page = $self->html_render_class->new;
241  if(DEBUG > 5) {
242    $self->muse($self->{"__batch_conv_page_count"} + 1, "/$total: ",
243      ref($page), " render ($depth) $module => $outfile");
244  } elsif(DEBUG > 2) {
245    $self->muse($self->{"__batch_conv_page_count"} + 1, "/$total: $module => $outfile")
246  }
247
248  # Give each class a chance to init the converter:
249  $page->batch_mode_page_object_init($self, $module, $infile, $outfile, $depth)
250   if $page->can('batch_mode_page_object_init');
251  # Init for the index (TOC), too.
252  $self->batch_mode_page_object_init($page, $module, $infile, $outfile, $depth)
253   if $self->can('batch_mode_page_object_init');
254
255  # Now get busy...
256  $self->makepath($outdir => \@namelets);
257
258  $progress and $progress->reach($self->{"__batch_conv_page_count"}, "Rendering $module");
259
260  if( $retval = $page->parse_from_file($infile, $outfile) ) {
261    ++ $self->{"__batch_conv_page_count"} ;
262    $self->note_for_contents_file( \@namelets, $infile, $outfile );
263  } else {
264    $self->muse("Odd, parse_from_file(\"$infile\", \"$outfile\") returned false.");
265  }
266
267  $page->batch_mode_page_object_kill($self, $module, $infile, $outfile, $depth)
268   if $page->can('batch_mode_page_object_kill');
269  # The following isn't a typo.  Note that it switches $self and $page.
270  $self->batch_mode_page_object_kill($page, $module, $infile, $outfile, $depth)
271   if $self->can('batch_mode_page_object_kill');
272
273  DEBUG > 4 and printf STDERR "%s %sb < $infile %s %sb\n",
274     $outfile, -s $outfile, $infile, -s $infile
275  ;
276
277  undef($page);
278  return $retval;
279}
280
281# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
282sub filespecsys { $_[0]{'_filespecsys'} || 'File::Spec' }
283
284# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
285
286sub note_for_contents_file {
287  my($self, $namelets, $infile, $outfile) = @_;
288
289  # I think the infile and outfile parts are never used. -- SMB
290  # But it's handy to have them around for debugging.
291
292  if( $self->contents_file ) {
293    my $c = $self->_contents();
294    push @$c,
295     [ join("::", @$namelets), $infile, $outfile, $namelets ]
296     #            0               1         2         3
297    ;
298    DEBUG > 3 and print STDERR "Noting @$c[-1]\n";
299  }
300  return;
301}
302
303#_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
304
305sub write_contents_file {
306  my($self, $outdir) = @_;
307  my $outfile  = $self->_contents_filespec($outdir) || return;
308
309  $self->muse("Preparing list of modules for ToC");
310
311  my($toplevel,           # maps  toplevelbit => [all submodules]
312     $toplevel_form_freq, # ends up being  'foo' => 'Foo'
313    ) = $self->_prep_contents_breakdown;
314
315  my $Contents = eval { $self->_wopen($outfile) };
316  if( $Contents ) {
317    $self->muse( "Writing contents file $outfile" );
318  } else {
319    warn "Couldn't write-open contents file $outfile: $!\nAbort writing to $outfile at all";
320    return;
321  }
322
323  $self->_write_contents_start(  $Contents, $outfile, );
324  $self->_write_contents_middle( $Contents, $outfile, $toplevel, $toplevel_form_freq );
325  $self->_write_contents_end(    $Contents, $outfile, );
326  return $outfile;
327}
328
329# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
330
331sub _write_contents_start {
332  my($self, $Contents, $outfile) = @_;
333  my $starter = $self->contents_page_start || '';
334
335  {
336    my $css_wad = $self->_css_wad_to_markup(1);
337    if( $css_wad ) {
338      $starter =~ s{(</head>)}{\n$css_wad\n$1}i;  # otherwise nevermind
339    }
340
341    my $javascript_wad = $self->_javascript_wad_to_markup(1);
342    if( $javascript_wad ) {
343      $starter =~ s{(</head>)}{\n$javascript_wad\n$1}i;   # otherwise nevermind
344    }
345  }
346
347  unless(print $Contents $starter, "<dl class='superindex'>\n" ) {
348    warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all";
349    close($Contents);
350    return 0;
351  }
352  return 1;
353}
354
355# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
356
357sub _write_contents_middle {
358  my($self, $Contents, $outfile, $toplevel2submodules, $toplevel_form_freq) = @_;
359
360  foreach my $t (sort keys %$toplevel2submodules) {
361    my @downlines = sort {$a->[-1] cmp $b->[-1]}
362                          @{ $toplevel2submodules->{$t} };
363
364    printf $Contents qq[<dt><a name="%s">%s</a></dt>\n<dd>\n],
365      esc( $t, $toplevel_form_freq->{$t} )
366    ;
367
368    my($path, $name);
369    foreach my $e (@downlines) {
370      $name = $e->[0];
371      $path = join( "/", '.', esc( @{$e->[3]} ) )
372        . ($HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION);
373      print $Contents qq{  <a href="$path">}, esc($name), "</a>&nbsp;&nbsp;\n";
374    }
375    print $Contents "</dd>\n\n";
376  }
377  return 1;
378}
379
380# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
381
382sub _write_contents_end {
383  my($self, $Contents, $outfile) = @_;
384  unless(
385    print $Contents "</dl>\n",
386      $self->contents_page_end || '',
387  ) {
388    warn "Couldn't write to $outfile: $!";
389  }
390  close($Contents) or warn "Couldn't close $outfile: $!";
391  return 1;
392}
393
394# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
395
396sub _prep_contents_breakdown {
397  my($self) = @_;
398  my $contents = $self->_contents;
399  my %toplevel; # maps  lctoplevelbit => [all submodules]
400  my %toplevel_form_freq; # ends up being  'foo' => 'Foo'
401                               # (mapping anycase forms to most freq form)
402
403  foreach my $entry (@$contents) {
404    my $toplevel =
405      $entry->[0] =~ m/^perl\w*$/ ? 'perl_core_docs'
406          # group all the perlwhatever docs together
407      : $entry->[3][0] # normal case
408    ;
409    ++$toplevel_form_freq{ lc $toplevel }{ $toplevel };
410    push @{ $toplevel{ lc $toplevel } }, $entry;
411    push @$entry, lc($entry->[0]); # add a sort-order key to the end
412  }
413
414  foreach my $toplevel (sort keys %toplevel) {
415    my $fgroup = $toplevel_form_freq{$toplevel};
416    $toplevel_form_freq{$toplevel} =
417    (
418      sort { $fgroup->{$b} <=> $fgroup->{$a}  or  $a cmp $b }
419        keys %$fgroup
420      # This hash is extremely unlikely to have more than 4 members, so this
421      # sort isn't so very wasteful
422    )[0];
423  }
424
425  return(\%toplevel, \%toplevel_form_freq) if wantarray;
426  return \%toplevel;
427}
428
429# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
430
431sub _contents_filespec {
432  my($self, $outdir) = @_;
433  my $outfile = $self->contents_file;
434  return unless $outfile;
435  return $self->filespecsys->catfile( $outdir, $outfile );
436}
437
438#_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
439
440sub makepath {
441  my($self, $outdir, $namelets) = @_;
442  return unless @$namelets > 1;
443  for my $i (0 .. ($#$namelets - 1)) {
444    my $dir = $self->filespecsys->catdir( $outdir, @$namelets[0 .. $i] );
445    if(-e $dir) {
446      die "$dir exists but not as a directory!?" unless -d $dir;
447      next;
448    }
449    DEBUG > 3 and print STDERR "  Making $dir\n";
450    mkdir $dir, 0777
451     or die "Can't mkdir $dir: $!\nAborting"
452    ;
453  }
454  return;
455}
456
457#_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
458
459sub batch_mode_page_object_init {
460  my $self = shift;
461  my($page, $module, $infile, $outfile, $depth) = @_;
462
463  # TODO: any further options to percolate onto this new object here?
464
465  $page->default_title($module);
466  $page->index( $self->index );
467
468  $page->html_css(        $self->       _css_wad_to_markup($depth) );
469  $page->html_javascript( $self->_javascript_wad_to_markup($depth) );
470
471  $self->add_header_backlink($page, $module, $infile, $outfile, $depth);
472  $self->add_footer_backlink($page, $module, $infile, $outfile, $depth);
473
474
475  return $self;
476}
477
478# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
479
480sub add_header_backlink {
481  my $self = shift;
482  return if $self->no_contents_links;
483  my($page, $module, $infile, $outfile, $depth) = @_;
484  $page->html_header_after_title( join '',
485    $page->html_header_after_title || '',
486
487    qq[<p class="backlinktop"><b><a name="___top" href="],
488    $self->url_up_to_contents($depth),
489    qq[" accesskey="1" title="All Documents">&lt;&lt;</a></b></p>\n],
490  )
491   if $self->contents_file
492  ;
493  return;
494}
495
496sub add_footer_backlink {
497  my $self = shift;
498  return if $self->no_contents_links;
499  my($page, $module, $infile, $outfile, $depth) = @_;
500  $page->html_footer( join '',
501    qq[<p class="backlinkbottom"><b><a name="___bottom" href="],
502    $self->url_up_to_contents($depth),
503    qq[" title="All Documents">&lt;&lt;</a></b></p>\n],
504
505    $page->html_footer || '',
506  )
507   if $self->contents_file
508  ;
509  return;
510}
511
512sub url_up_to_contents {
513  my($self, $depth) = @_;
514  --$depth;
515  return join '/', ('..') x $depth, esc($self->contents_file);
516}
517
518#_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
519
520sub find_all_pods {
521  my($self, $dirs) = @_;
522  # You can override find_all_pods in a subclass if you want to
523  #  do extra filtering or whatnot.  But for the moment, we just
524  #  pass to modnames2paths:
525  return $self->modnames2paths($dirs);
526}
527
528#_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
529
530sub modnames2paths { # return a hashref mapping modulenames => paths
531  my($self, $dirs) = @_;
532
533  my $m2p;
534  {
535    my $search = $self->search_class->new;
536    DEBUG and print STDERR "Searching via $search\n";
537    $search->verbose(1) if DEBUG > 10;
538    $search->progress( $self->progress->copy->goal(0) ) if $self->progress;
539    $search->shadows(0);  # don't bother noting shadowed files
540    $search->inc(     $dirs ? 0      :  1 );
541    $search->survey(  $dirs ? @$dirs : () );
542    $m2p = $search->name2path;
543    die "What, no name2path?!" unless $m2p;
544  }
545
546  $self->muse("That's odd... no modules found!") unless keys %$m2p;
547  if( DEBUG > 4 ) {
548    print STDERR "Modules found (name => path):\n";
549    foreach my $m (sort {lc($a) cmp lc($b)} keys %$m2p) {
550      print STDERR "  $m  $$m2p{$m}\n";
551    }
552    print STDERR "(total ",     scalar(keys %$m2p), ")\n\n";
553  } elsif( DEBUG ) {
554    print STDERR      "Found ", scalar(keys %$m2p), " modules.\n";
555  }
556  $self->muse( "Found ", scalar(keys %$m2p), " modules." );
557
558  # return the Foo::Bar => /whatever/Foo/Bar.pod|pm hashref
559  return $m2p;
560}
561
562#===========================================================================
563
564sub _wopen {
565  # this is abstracted out so that the daemon class can override it
566  my($self, $outpath) = @_;
567  require Symbol;
568  my $out_fh = Symbol::gensym();
569  DEBUG > 5 and print STDERR "Write-opening to $outpath\n";
570  return $out_fh if open($out_fh, "> $outpath");
571  require Carp;
572  Carp::croak("Can't write-open $outpath: $!");
573}
574
575#==========================================================================
576
577sub add_css {
578  my($self, $url, $is_default, $name, $content_type, $media, $_code) = @_;
579  return unless $url;
580  unless($name) {
581    # cook up a reasonable name based on the URL
582    $name = $url;
583    if( $name !~ m/\?/ and $name =~ m{([^/]+)$}s ) {
584      $name = $1;
585      $name =~ s/\.css//i;
586    }
587  }
588  $media        ||= 'all';
589  $content_type ||= 'text/css';
590
591  my $bunch = [$url, $name, $content_type, $media, $_code];
592  if($is_default) { unshift @{ $self->_css_wad }, $bunch }
593  else            { push    @{ $self->_css_wad }, $bunch }
594  return;
595}
596
597# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
598
599sub _spray_css {
600  my($self, $outdir) = @_;
601
602  return unless $self->css_flurry();
603  $self->_gen_css_wad();
604
605  my $lol = $self->_css_wad;
606  foreach my $chunk (@$lol) {
607    my $url = $chunk->[0];
608    my $outfile;
609    if( ref($chunk->[-1]) and $url =~ m{^(_[-a-z0-9_]+\.css$)} ) {
610      $outfile = $self->filespecsys->catfile( $outdir, "$1" );
611      DEBUG > 5 and print STDERR "Noting $$chunk[0] as a file I'll create.\n";
612    } else {
613      DEBUG > 5 and print STDERR "OK, noting $$chunk[0] as an external CSS.\n";
614      # Requires no further attention.
615      next;
616    }
617
618    #$self->muse( "Writing autogenerated CSS file $outfile" );
619    my $Cssout = $self->_wopen($outfile);
620    print $Cssout ${$chunk->[-1]}
621     or warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all";
622    close($Cssout);
623    DEBUG > 5 and print STDERR "Wrote $outfile\n";
624  }
625
626  return;
627}
628
629# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
630
631sub _css_wad_to_markup {
632  my($self, $depth) = @_;
633
634  my @css  = @{ $self->_css_wad || return '' };
635  return '' unless @css;
636
637  my $rel = 'stylesheet';
638  my $out = '';
639
640  --$depth;
641  my $uplink = $depth ? ('../' x $depth) : '';
642
643  foreach my $chunk (@css) {
644    next unless $chunk and @$chunk;
645
646    my( $url1, $url2, $title, $type, $media) = (
647      $self->_maybe_uplink( $chunk->[0], $uplink ),
648      esc(grep !ref($_), @$chunk)
649    );
650
651    $out .= qq{<link rel="$rel" title="$title" type="$type" href="$url1$url2" media="$media" >\n};
652
653    $rel = 'alternate stylesheet'; # alternates = all non-first iterations
654  }
655  return $out;
656}
657
658# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
659sub _maybe_uplink {
660  # if the given URL looks relative, return the given uplink string --
661  # otherwise return emptystring
662  my($self, $url, $uplink) = @_;
663  ($url =~ m{^\./} or $url !~ m{[/\:]} )
664    ? $uplink
665    : ''
666    # qualify it, if/as needed
667}
668
669# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
670sub _gen_css_wad {
671  my $self = $_[0];
672  my $css_template = $self->_css_template;
673  foreach my $variation (
674
675   # Commented out for sake of concision:
676   #
677   #  011n=black_with_red_on_white
678   #  001n=black_with_yellow_on_white
679   #  101n=black_with_green_on_white
680   #  110=white_with_yellow_on_black
681   #  010=white_with_green_on_black
682   #  011=white_with_blue_on_black
683   #  100=white_with_red_on_black
684    '110n=blkbluw',  # black_with_blue_on_white
685    '010n=blkmagw',  # black_with_magenta_on_white
686    '100n=blkcynw',  # black_with_cyan_on_white
687    '101=whtprpk',   # white_with_purple_on_black
688    '001=whtnavk',   # white_with_navy_blue_on_black
689    '010a=grygrnk',  # grey_with_green_on_black
690    '010b=whtgrng',  # white_with_green_on_grey
691    '101an=blkgrng', # black_with_green_on_grey
692    '101bn=grygrnw', # grey_with_green_on_white
693  ) {
694
695    my $outname = $variation;
696    my($flipmode, @swap) = ( ($4 || ''), $1,$2,$3)
697      if $outname =~ s/^([012])([012])([[012])([a-z]*)=?//s;
698    @swap = () if '010' eq join '', @swap; # 010 is a swop-no-op!
699
700    my $this_css =
701      "/* This file is autogenerated.  Do not edit.  $variation */\n\n"
702      . $css_template;
703
704    # Only look at three-digitty colors, for now at least.
705    if( $flipmode =~ m/n/ ) {
706      $this_css =~ s/(#[0-9a-fA-F]{3})\b/_color_negate($1)/eg;
707      $this_css =~ s/\bthin\b/medium/g;
708    }
709    $this_css =~ s<#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])\b>
710                  < join '', '#', ($1,$2,$3)[@swap] >eg   if @swap;
711
712    if(   $flipmode =~ m/a/)
713       { $this_css =~ s/#fff\b/#999/gi } # black -> dark grey
714    elsif($flipmode =~ m/b/)
715       { $this_css =~ s/#000\b/#666/gi } # white -> light grey
716
717    my $name = $outname;
718    $name =~ tr/-_/  /;
719    $self->add_css( "_$outname.css", 0, $name, 0, 0, \$this_css);
720  }
721
722  # Now a few indexless variations:
723  for (my ($outfile, $variation) = each %{{
724      blkbluw => 'black_with_blue_on_white',
725      whtpurk => 'white_with_purple_on_black',
726      whtgrng => 'white_with_green_on_grey',
727      grygrnw => 'grey_with_green_on_white',
728  }}) {
729    my $this_css = join "\n",
730      "/* This file is autogenerated.  Do not edit.  $outfile */\n",
731      "\@import url(\"./_$variation.css\");",
732      ".indexgroup { display: none; }",
733      "\n",
734    ;
735    my $name = $outfile;
736    $name =~ tr/-_/  /;
737    $self->add_css( "_$outfile.css", 0, $name, 0, 0, \$this_css);
738  }
739
740  return;
741}
742
743sub _color_negate {
744  my $x = lc $_[0];
745  $x =~ tr[0123456789abcdef]
746          [fedcba9876543210];
747  return $x;
748}
749
750#===========================================================================
751
752sub add_javascript {
753  my($self, $url, $content_type, $_code) = @_;
754  return unless $url;
755  push  @{ $self->_javascript_wad }, [
756    $url, $content_type || 'text/javascript', $_code
757  ];
758  return;
759}
760
761sub _spray_javascript {
762  my($self, $outdir) = @_;
763  return unless $self->javascript_flurry();
764  $self->_gen_javascript_wad();
765
766  my $lol = $self->_javascript_wad;
767  foreach my $script (@$lol) {
768    my $url = $script->[0];
769    my $outfile;
770
771    if( ref($script->[-1]) and $url =~ m{^(_[-a-z0-9_]+\.js$)} ) {
772      $outfile = $self->filespecsys->catfile( $outdir, "$1" );
773      DEBUG > 5 and print STDERR "Noting $$script[0] as a file I'll create.\n";
774    } else {
775      DEBUG > 5 and print STDERR "OK, noting $$script[0] as an external JavaScript.\n";
776      next;
777    }
778
779    #$self->muse( "Writing JavaScript file $outfile" );
780    my $Jsout = $self->_wopen($outfile);
781
782    print $Jsout ${$script->[-1]}
783     or warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all";
784    close($Jsout);
785    DEBUG > 5 and print STDERR "Wrote $outfile\n";
786  }
787
788  return;
789}
790
791sub _gen_javascript_wad {
792  my $self = $_[0];
793  my $js_code = $self->_javascript || return;
794  $self->add_javascript( "_podly.js", 0, \$js_code);
795  return;
796}
797
798sub _javascript_wad_to_markup {
799  my($self, $depth) = @_;
800
801  my @scripts  = @{ $self->_javascript_wad || return '' };
802  return '' unless @scripts;
803
804  my $out = '';
805
806  --$depth;
807  my $uplink = $depth ? ('../' x $depth) : '';
808
809  foreach my $s (@scripts) {
810    next unless $s and @$s;
811
812    my( $url1, $url2, $type, $media) = (
813      $self->_maybe_uplink( $s->[0], $uplink ),
814      esc(grep !ref($_), @$s)
815    );
816
817    $out .= qq{<script type="$type" src="$url1$url2"></script>\n};
818  }
819  return $out;
820}
821
822#===========================================================================
823
824sub _css_template { return $CSS }
825sub _javascript   { return $JAVASCRIPT }
826
827$CSS = <<'EOCSS';
828/* For accessibility reasons, never specify text sizes in px/pt/pc/in/cm/mm */
829
830@media all { .hide { display: none; } }
831
832@media print {
833  .noprint, div.indexgroup, .backlinktop, .backlinkbottom { display: none }
834
835  * {
836    border-color: black !important;
837    color: black !important;
838    background-color: transparent !important;
839    background-image: none !important;
840  }
841
842  dl.superindex > dd  {
843    word-spacing: .6em;
844  }
845}
846
847@media aural, braille, embossed {
848  div.indexgroup  { display: none; }  /* Too noisy, don't you think? */
849  dl.superindex > dt:before { content: "Group ";  }
850  dl.superindex > dt:after  { content: " contains:"; }
851  .backlinktop    a:before  { content: "Back to contents"; }
852  .backlinkbottom a:before  { content: "Back to contents"; }
853}
854
855@media aural {
856  dl.superindex > dt  { pause-before: 600ms; }
857}
858
859@media screen, tty, tv, projection {
860  .noscreen { display: none; }
861
862  a:link    { color: #7070ff; text-decoration: underline; }
863  a:visited { color: #e030ff; text-decoration: underline; }
864  a:active  { color: #800000; text-decoration: underline; }
865  body.contentspage a            { text-decoration: none; }
866  a.u { color: #fff !important; text-decoration: none; }
867
868  body.pod {
869    margin: 0 5px;
870    color:            #fff;
871    background-color: #000;
872  }
873
874  body.pod h1, body.pod h2, body.pod h3, body.pod h4  {
875    font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
876    font-weight: normal;
877    margin-top: 1.2em;
878    margin-bottom: .1em;
879    border-top: thin solid transparent;
880    /* margin-left: -5px;  border-left: 2px #7070ff solid;  padding-left: 3px; */
881  }
882
883  body.pod h1  { border-top-color: #0a0; }
884  body.pod h2  { border-top-color: #080; }
885  body.pod h3  { border-top-color: #040; }
886  body.pod h4  { border-top-color: #010; }
887
888  p.backlinktop + h1 { border-top: none; margin-top: 0em;  }
889  p.backlinktop + h2 { border-top: none; margin-top: 0em;  }
890  p.backlinktop + h3 { border-top: none; margin-top: 0em;  }
891  p.backlinktop + h4 { border-top: none; margin-top: 0em;  }
892
893  body.pod dt {
894    font-size: 105%; /* just a wee bit more than normal */
895  }
896
897  .indexgroup { font-size: 80%; }
898
899  .backlinktop,   .backlinkbottom    {
900    margin-left:  -5px;
901    margin-right: -5px;
902    background-color:         #040;
903    border-top:    thin solid #050;
904    border-bottom: thin solid #050;
905  }
906
907  .backlinktop a, .backlinkbottom a  {
908    text-decoration: none;
909    color: #080;
910    background-color:  #000;
911    border: thin solid #0d0;
912  }
913  .backlinkbottom { margin-bottom: 0; padding-bottom: 0; }
914  .backlinktop    { margin-top:    0; padding-top:    0; }
915
916  body.contentspage {
917    color:            #fff;
918    background-color: #000;
919  }
920
921  body.contentspage h1  {
922    color:            #0d0;
923    margin-left: 1em;
924    margin-right: 1em;
925    text-indent: -.9em;
926    font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
927    font-weight: normal;
928    border-top:    thin solid #fff;
929    border-bottom: thin solid #fff;
930    text-align: center;
931  }
932
933  dl.superindex > dt  {
934    font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
935    font-weight: normal;
936    font-size: 90%;
937    margin-top: .45em;
938    /* margin-bottom: -.15em; */
939  }
940  dl.superindex > dd  {
941    word-spacing: .6em;    /* most important rule here! */
942  }
943  dl.superindex > a:link  {
944    text-decoration: none;
945    color: #fff;
946  }
947
948  .contentsfooty {
949    border-top: thin solid #999;
950    font-size: 90%;
951  }
952
953}
954
955/* The End */
956
957EOCSS
958
959#==========================================================================
960
961$JAVASCRIPT = <<'EOJAVASCRIPT';
962
963// From http://www.alistapart.com/articles/alternate/
964
965function setActiveStyleSheet(title) {
966  var i, a, main;
967  for(i=0  ;  (a = document.getElementsByTagName("link")[i])  ;  i++) {
968    if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title")) {
969      a.disabled = true;
970      if(a.getAttribute("title") == title) a.disabled = false;
971    }
972  }
973}
974
975function getActiveStyleSheet() {
976  var i, a;
977  for(i=0  ;  (a = document.getElementsByTagName("link")[i])  ;  i++) {
978    if(   a.getAttribute("rel").indexOf("style") != -1
979       && a.getAttribute("title")
980       && !a.disabled
981       ) return a.getAttribute("title");
982  }
983  return null;
984}
985
986function getPreferredStyleSheet() {
987  var i, a;
988  for(i=0  ;  (a = document.getElementsByTagName("link")[i])  ;  i++) {
989    if(   a.getAttribute("rel").indexOf("style") != -1
990       && a.getAttribute("rel").indexOf("alt") == -1
991       && a.getAttribute("title")
992       ) return a.getAttribute("title");
993  }
994  return null;
995}
996
997function createCookie(name,value,days) {
998  if (days) {
999    var date = new Date();
1000    date.setTime(date.getTime()+(days*24*60*60*1000));
1001    var expires = "; expires="+date.toGMTString();
1002  }
1003  else expires = "";
1004  document.cookie = name+"="+value+expires+"; path=/";
1005}
1006
1007function readCookie(name) {
1008  var nameEQ = name + "=";
1009  var ca = document.cookie.split(';');
1010  for(var i=0  ;  i < ca.length  ;  i++) {
1011    var c = ca[i];
1012    while (c.charAt(0)==' ') c = c.substring(1,c.length);
1013    if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
1014  }
1015  return null;
1016}
1017
1018window.onload = function(e) {
1019  var cookie = readCookie("style");
1020  var title = cookie ? cookie : getPreferredStyleSheet();
1021  setActiveStyleSheet(title);
1022}
1023
1024window.onunload = function(e) {
1025  var title = getActiveStyleSheet();
1026  createCookie("style", title, 365);
1027}
1028
1029var cookie = readCookie("style");
1030var title = cookie ? cookie : getPreferredStyleSheet();
1031setActiveStyleSheet(title);
1032
1033// The End
1034
1035EOJAVASCRIPT
1036
1037# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
10381;
1039__END__
1040
1041
1042=head1 NAME
1043
1044Pod::Simple::HTMLBatch - convert several Pod files to several HTML files
1045
1046=head1 SYNOPSIS
1047
1048  perl -MPod::Simple::HTMLBatch -e 'Pod::Simple::HTMLBatch::go' in out
1049
1050
1051=head1 DESCRIPTION
1052
1053This module is used for running batch-conversions of a lot of HTML
1054documents
1055
1056This class is NOT a subclass of Pod::Simple::HTML
1057(nor of bad old Pod::Html) -- although it uses
1058Pod::Simple::HTML for doing the conversion of each document.
1059
1060The normal use of this class is like so:
1061
1062  use Pod::Simple::HTMLBatch;
1063  my $batchconv = Pod::Simple::HTMLBatch->new;
1064  $batchconv->some_option( some_value );
1065  $batchconv->some_other_option( some_other_value );
1066  $batchconv->batch_convert( \@search_dirs, $output_dir );
1067
1068=head2 FROM THE COMMAND LINE
1069
1070Note that this class also provides
1071(but does not export) the function Pod::Simple::HTMLBatch::go.
1072This is basically just a shortcut for C<<
1073Pod::Simple::HTMLBatch->batch_convert(@ARGV) >>.
1074It's meant to be handy for calling from the command line.
1075
1076However, the shortcut requires that you specify exactly two command-line
1077arguments, C<indirs> and C<outdir>.
1078
1079Example:
1080
1081  % mkdir out_html
1082  % perl -MPod::Simple::HTMLBatch -e Pod::Simple::HTMLBatch::go @INC out_html
1083      (to convert the pod from Perl's @INC
1084       files under the directory ./out_html)
1085
1086(Note that the command line there contains a literal atsign-I-N-C.  This
1087is handled as a special case by batch_convert, in order to save you having
1088to enter the odd-looking "" as the first command-line parameter when you
1089mean "just use whatever's in @INC".)
1090
1091Example:
1092
1093  % mkdir ../seekrut
1094  % chmod og-rx ../seekrut
1095  % perl -MPod::Simple::HTMLBatch -e Pod::Simple::HTMLBatch::go . ../seekrut
1096      (to convert the pod under the current dir into HTML
1097       files under the directory ./seekrut)
1098
1099Example:
1100
1101  % perl -MPod::Simple::HTMLBatch -e Pod::Simple::HTMLBatch::go happydocs .
1102      (to convert all pod from happydocs into the current directory)
1103
1104
1105
1106=head1 MAIN METHODS
1107
1108=over
1109
1110=item $batchconv = Pod::Simple::HTMLBatch->new;
1111
1112This creates a new batch converter.  The method doesn't take parameters.
1113To change the converter's attributes, use the L<"/ACCESSOR METHODS">
1114below.
1115
1116=item $batchconv->batch_convert( I<indirs>, I<outdir> );
1117
1118This searches the directories given in I<indirs> and writes
1119HTML files for each of these to a corresponding directory
1120in I<outdir>.  The directory I<outdir> must exist.
1121
1122=item $batchconv->batch_convert( undef    , ...);
1123
1124=item $batchconv->batch_convert( q{@INC}, ...);
1125
1126These two values for I<indirs> specify that the normal Perl @INC
1127
1128=item $batchconv->batch_convert( \@dirs , ...);
1129
1130This specifies that the input directories are the items in
1131the arrayref C<\@dirs>.
1132
1133=item $batchconv->batch_convert( "somedir" , ...);
1134
1135This specifies that the director "somedir" is the input.
1136(This can be an absolute or relative path, it doesn't matter.)
1137
1138A common value you might want would be just "." for the current
1139directory:
1140
1141     $batchconv->batch_convert( "." , ...);
1142
1143
1144=item $batchconv->batch_convert( 'somedir:someother:also' , ...);
1145
1146This specifies that you want the dirs "somedir", "someother", and "also"
1147scanned, just as if you'd passed the arrayref
1148C<[qw( somedir someother also)]>.  Note that a ":"-separator is normal
1149under Unix, but Under MSWin, you'll need C<'somedir;someother;also'>
1150instead, since the pathsep on MSWin is ";" instead of ":".  (And
1151I<that> is because ":" often comes up in paths, like
1152C<"c:/perl/lib">.)
1153
1154(Exactly what separator character should be used, is gotten from
1155C<$Config::Config{'path_sep'}>, via the L<Config> module.)
1156
1157=item $batchconv->batch_convert( ... , undef );
1158
1159This specifies that you want the HTML output to go into the current
1160directory.
1161
1162(Note that a missing or undefined value means a different thing in
1163the first slot than in the second.  That's so that C<batch_convert()>
1164with no arguments (or undef arguments) means "go from @INC, into
1165the current directory.)
1166
1167=item $batchconv->batch_convert( ... , 'somedir' );
1168
1169This specifies that you want the HTML output to go into the
1170directory 'somedir'.
1171(This can be an absolute or relative path, it doesn't matter.)
1172
1173=back
1174
1175
1176Note that you can also call C<batch_convert> as a class method,
1177like so:
1178
1179  Pod::Simple::HTMLBatch->batch_convert( ... );
1180
1181That is just short for this:
1182
1183  Pod::Simple::HTMLBatch-> new-> batch_convert(...);
1184
1185That is, it runs a conversion with default options, for
1186whatever inputdirs and output dir you specify.
1187
1188
1189=head2 ACCESSOR METHODS
1190
1191The following are all accessor methods -- that is, they don't do anything
1192on their own, but just alter the contents of the conversion object,
1193which comprises the options for this particular batch conversion.
1194
1195We show the "put" form of the accessors below (i.e., the syntax you use
1196for setting the accessor to a specific value).  But you can also
1197call each method with no parameters to get its current value.  For
1198example, C<< $self->contents_file() >> returns the current value of
1199the contents_file attribute.
1200
1201=over
1202
1203
1204=item $batchconv->verbose( I<nonnegative_integer> );
1205
1206This controls how verbose to be during batch conversion, as far as
1207notes to STDOUT (or whatever is C<select>'d) about how the conversion
1208is going.  If 0, no progress information is printed.
1209If 1 (the default value), some progress information is printed.
1210Higher values print more information.
1211
1212
1213=item $batchconv->index( I<true-or-false> );
1214
1215This controls whether or not each HTML page is liable to have a little
1216table of contents at the top (which we call an "index" for historical
1217reasons).  This is true by default.
1218
1219
1220=item $batchconv->contents_file( I<filename> );
1221
1222If set, should be the name of a file (in the output directory)
1223to write the HTML index to.  The default value is "index.html".
1224If you set this to a false value, no contents file will be written.
1225
1226=item $batchconv->contents_page_start( I<HTML_string> );
1227
1228This specifies what string should be put at the beginning of
1229the contents page.
1230The default is a string more or less like this:
1231
1232  <html>
1233  <head><title>Perl Documentation</title></head>
1234  <body class='contentspage'>
1235  <h1>Perl Documentation</h1>
1236
1237=item $batchconv->contents_page_end( I<HTML_string> );
1238
1239This specifies what string should be put at the end of the contents page.
1240The default is a string more or less like this:
1241
1242  <p class='contentsfooty'>Generated by
1243  Pod::Simple::HTMLBatch v3.01 under Perl v5.008
1244  <br >At Fri May 14 22:26:42 2004 GMT,
1245  which is Fri May 14 14:26:42 2004 local time.</p>
1246
1247
1248
1249=item $batchconv->add_css( $url );
1250
1251TODO
1252
1253=item $batchconv->add_javascript( $url );
1254
1255TODO
1256
1257=item $batchconv->css_flurry( I<true-or-false> );
1258
1259If true (the default value), we autogenerate some CSS files in the
1260output directory, and set our HTML files to use those.
1261TODO: continue
1262
1263=item $batchconv->javascript_flurry( I<true-or-false> );
1264
1265If true (the default value), we autogenerate a JavaScript in the
1266output directory, and set our HTML files to use it.  Currently,
1267the JavaScript is used only to get the browser to remember what
1268stylesheet it prefers.
1269TODO: continue
1270
1271=item $batchconv->no_contents_links( I<true-or-false> );
1272
1273TODO
1274
1275=item $batchconv->html_render_class( I<classname> );
1276
1277This sets what class is used for rendering the files.
1278The default is "Pod::Simple::HTML".  If you set it to something else,
1279it should probably be a subclass of Pod::Simple::HTML, and you should
1280C<require> or C<use> that class so that's it's loaded before
1281Pod::Simple::HTMLBatch tries loading it.
1282
1283=item $batchconv->search_class( I<classname> );
1284
1285This sets what class is used for searching for the files.
1286The default is "Pod::Simple::Search".  If you set it to something else,
1287it should probably be a subclass of Pod::Simple::Search, and you should
1288C<require> or C<use> that class so that's it's loaded before
1289Pod::Simple::HTMLBatch tries loading it.
1290
1291=back
1292
1293
1294
1295
1296=head1 NOTES ON CUSTOMIZATION
1297
1298TODO
1299
1300  call add_css($someurl) to add stylesheet as alternate
1301  call add_css($someurl,1) to add as primary stylesheet
1302
1303  call add_javascript
1304
1305  subclass Pod::Simple::HTML and set $batchconv->html_render_class to
1306    that classname
1307  and maybe override
1308    $page->batch_mode_page_object_init($self, $module, $infile, $outfile, $depth)
1309  or maybe override
1310    $batchconv->batch_mode_page_object_init($page, $module, $infile, $outfile, $depth)
1311  subclass Pod::Simple::Search and set $batchconv->search_class to
1312    that classname
1313
1314
1315=head1 SEE ALSO
1316
1317L<Pod::Simple>, L<Pod::Simple::HTMLBatch>, L<perlpod>, L<perlpodspec>
1318
1319=head1 SUPPORT
1320
1321Questions or discussion about POD and Pod::Simple should be sent to the
1322pod-people@perl.org mail list. Send an empty email to
1323pod-people-subscribe@perl.org to subscribe.
1324
1325This module is managed in an open GitHub repository,
1326L<https://github.com/perl-pod/pod-simple/>. Feel free to fork and contribute, or
1327to clone L<git://github.com/perl-pod/pod-simple.git> and send patches!
1328
1329Patches against Pod::Simple are welcome. Please send bug reports to
1330<bug-pod-simple@rt.cpan.org>.
1331
1332=head1 COPYRIGHT AND DISCLAIMERS
1333
1334Copyright (c) 2002 Sean M. Burke.
1335
1336This library is free software; you can redistribute it and/or modify it
1337under the same terms as Perl itself.
1338
1339This program is distributed in the hope that it will be useful, but
1340without any warranty; without even the implied warranty of
1341merchantability or fitness for a particular purpose.
1342
1343=head1 AUTHOR
1344
1345Pod::Simple was created by Sean M. Burke <sburke@cpan.org>.
1346But don't bother him, he's retired.
1347
1348Pod::Simple is maintained by:
1349
1350=over
1351
1352=item * Allison Randal C<allison@perl.org>
1353
1354=item * Hans Dieter Pearcey C<hdp@cpan.org>
1355
1356=item * David E. Wheeler C<dwheeler@cpan.org>
1357
1358=back
1359
1360=cut
1361