recognize more constructs such as C<$-> in pod (from Russ Allbery
[p5sagit/p5-mst-13.2.git] / lib / Pod / Text.pm
CommitLineData
6055f9d4 1# Pod::Text -- Convert POD data to formatted ASCII text.
2# $Id: Text.pm,v 0.2 1999/06/13 02:44:01 eagle Exp $
3#
4# Copyright 1999 by Russ Allbery <rra@stanford.edu>
5#
6# This program is free software; you can redistribute it and/or modify it
7# under the same terms as Perl itself.
8#
9# This module may potentially be a replacement for Pod::Text, although it
10# does not (at the current time) attempt to match the output of Pod::Text
11# and makes several different formatting choices (mostly in the direction of
12# less markup). It uses Pod::Parser and is designed to be very easy to
13# subclass.
14
15############################################################################
16# Modules and declarations
17############################################################################
69e00e79 18
6055f9d4 19package Pod::Text;
69e00e79 20
6055f9d4 21require 5.004;
22
23use Carp qw(carp);
24use Pod::Parser ();
25
26use strict;
27use vars qw(@ISA %ESCAPES $VERSION);
28
29@ISA = qw(Pod::Parser);
30
31$VERSION = '0.01';
32
33
34############################################################################
35# Table of supported E<> escapes
36############################################################################
37
38# This table is taken near verbatim from Pod::PlainText in Pod::Parser,
39# which got it near verbatim from Pod::Text. It is therefore credited to
40# Tom Christiansen, and I'm glad I didn't have to write it. :)
41%ESCAPES = (
42 'amp' => '&', # ampersand
43 'lt' => '<', # left chevron, less-than
44 'gt' => '>', # right chevron, greater-than
45 'quot' => '"', # double quote
46
47 "Aacute" => "\xC1", # capital A, acute accent
48 "aacute" => "\xE1", # small a, acute accent
49 "Acirc" => "\xC2", # capital A, circumflex accent
50 "acirc" => "\xE2", # small a, circumflex accent
51 "AElig" => "\xC6", # capital AE diphthong (ligature)
52 "aelig" => "\xE6", # small ae diphthong (ligature)
53 "Agrave" => "\xC0", # capital A, grave accent
54 "agrave" => "\xE0", # small a, grave accent
55 "Aring" => "\xC5", # capital A, ring
56 "aring" => "\xE5", # small a, ring
57 "Atilde" => "\xC3", # capital A, tilde
58 "atilde" => "\xE3", # small a, tilde
59 "Auml" => "\xC4", # capital A, dieresis or umlaut mark
60 "auml" => "\xE4", # small a, dieresis or umlaut mark
61 "Ccedil" => "\xC7", # capital C, cedilla
62 "ccedil" => "\xE7", # small c, cedilla
63 "Eacute" => "\xC9", # capital E, acute accent
64 "eacute" => "\xE9", # small e, acute accent
65 "Ecirc" => "\xCA", # capital E, circumflex accent
66 "ecirc" => "\xEA", # small e, circumflex accent
67 "Egrave" => "\xC8", # capital E, grave accent
68 "egrave" => "\xE8", # small e, grave accent
69 "ETH" => "\xD0", # capital Eth, Icelandic
70 "eth" => "\xF0", # small eth, Icelandic
71 "Euml" => "\xCB", # capital E, dieresis or umlaut mark
72 "euml" => "\xEB", # small e, dieresis or umlaut mark
73 "Iacute" => "\xCD", # capital I, acute accent
74 "iacute" => "\xED", # small i, acute accent
75 "Icirc" => "\xCE", # capital I, circumflex accent
76 "icirc" => "\xEE", # small i, circumflex accent
77 "Igrave" => "\xCD", # capital I, grave accent
78 "igrave" => "\xED", # small i, grave accent
79 "Iuml" => "\xCF", # capital I, dieresis or umlaut mark
80 "iuml" => "\xEF", # small i, dieresis or umlaut mark
81 "Ntilde" => "\xD1", # capital N, tilde
82 "ntilde" => "\xF1", # small n, tilde
83 "Oacute" => "\xD3", # capital O, acute accent
84 "oacute" => "\xF3", # small o, acute accent
85 "Ocirc" => "\xD4", # capital O, circumflex accent
86 "ocirc" => "\xF4", # small o, circumflex accent
87 "Ograve" => "\xD2", # capital O, grave accent
88 "ograve" => "\xF2", # small o, grave accent
89 "Oslash" => "\xD8", # capital O, slash
90 "oslash" => "\xF8", # small o, slash
91 "Otilde" => "\xD5", # capital O, tilde
92 "otilde" => "\xF5", # small o, tilde
93 "Ouml" => "\xD6", # capital O, dieresis or umlaut mark
94 "ouml" => "\xF6", # small o, dieresis or umlaut mark
95 "szlig" => "\xDF", # small sharp s, German (sz ligature)
96 "THORN" => "\xDE", # capital THORN, Icelandic
97 "thorn" => "\xFE", # small thorn, Icelandic
98 "Uacute" => "\xDA", # capital U, acute accent
99 "uacute" => "\xFA", # small u, acute accent
100 "Ucirc" => "\xDB", # capital U, circumflex accent
101 "ucirc" => "\xFB", # small u, circumflex accent
102 "Ugrave" => "\xD9", # capital U, grave accent
103 "ugrave" => "\xF9", # small u, grave accent
104 "Uuml" => "\xDC", # capital U, dieresis or umlaut mark
105 "uuml" => "\xFC", # small u, dieresis or umlaut mark
106 "Yacute" => "\xDD", # capital Y, acute accent
107 "yacute" => "\xFD", # small y, acute accent
108 "yuml" => "\xFF", # small y, dieresis or umlaut mark
109
110 "lchevron" => "\xAB", # left chevron (double less than)
111 "rchevron" => "\xBB", # right chevron (double greater than)
112);
69e00e79 113
69e00e79 114
6055f9d4 115############################################################################
116# Initialization
117############################################################################
69e00e79 118
6055f9d4 119# Initialize the object. Must be sure to call our parent initializer.
120sub initialize {
121 my $self = shift;
69e00e79 122
6055f9d4 123 $$self{alt} = 0 unless defined $$self{alt};
124 $$self{indent} = 4 unless defined $$self{indent};
125 $$self{loose} = 0 unless defined $$self{loose};
126 $$self{sentence} = 0 unless defined $$self{sentence};
127 $$self{width} = 76 unless defined $$self{width};
69e00e79 128
6055f9d4 129 $$self{BEGUN} = []; # Stack of =begin blocks.
130 $$self{INDENTS} = []; # Stack of indentations.
131 $$self{MARGIN} = $$self{indent}; # Current left margin in spaces.
69e00e79 132
6055f9d4 133 $self->SUPER::initialize;
134}
69e00e79 135
69e00e79 136
6055f9d4 137############################################################################
138# Core overrides
139############################################################################
140
141# Called for each command paragraph. Gets the command, the associated
142# paragraph, the line number, and a Pod::Paragraph object. Just dispatches
143# the command to a method named the same as the command. =cut is handled
144# internally by Pod::Parser.
145sub command {
146 my $self = shift;
147 my $command = shift;
148 return if $command eq 'pod';
149 return if ($$self{EXCLUDE} && $command ne 'end');
150 $self->item ("\n") if defined $$self{ITEM};
151 $command = 'cmd_' . $command;
152 $self->$command (@_);
153}
69e00e79 154
6055f9d4 155# Called for a verbatim paragraph. Gets the paragraph, the line number, and
156# a Pod::Paragraph object. Just output it verbatim, but with tabs converted
157# to spaces.
158sub verbatim {
159 my $self = shift;
160 return if $$self{EXCLUDE};
161 $self->item if defined $$self{ITEM};
162 local $_ = shift;
163 return if /^\s*$/;
164 s/^(\s*\S+)/(' ' x $$self{MARGIN}) . $1/gme;
165 $self->output ($_);
166}
69e00e79 167
6055f9d4 168# Called for a regular text block. Gets the paragraph, the line number, and
169# a Pod::Paragraph object. Perform interpolation and output the results.
170sub textblock {
171 my ($self, $text, $line) = @_;
172 return if $$self{EXCLUDE};
173 local $_ = $text;
174
175 # Perform a little magic to collapse multiple L<> references. This is
176 # here mostly for backwards-compatibility with Pod::Text. We'll just
177 # rewrite the whole thing into actual text at this part, bypassing the
178 # whole internal sequence parsing thing.
179 s{
180 (
181 L< # A link of the form L</something>.
182 /
183 (
184 [:\w]+ # The item has to be a simple word...
185 (\(\))? # ...or simple function.
186 )
187 >
188 (
189 ,?\s+(and\s+)? # Allow lots of them, conjuncted.
190 L<
191 /
192 (
193 [:\w]+
194 (\(\))?
195 )
196 >
197 )+
198 )
199 } {
200 local $_ = $1;
201 s%L</([^>]+)>%$1%g;
202 my @items = split /(?:,?\s+(?:and\s+)?)/;
203 my $string = "the ";
204 my $i;
205 for ($i = 0; $i < @items; $i++) {
206 $string .= $items[$i];
207 $string .= ", " if @items > 2 && $i != $#items;
208 $string .= " and " if ($i == $#items - 1);
209 }
210 $string .= " entries elsewhere in this document";
211 $string;
212 }gex;
213
214 # Now actually interpolate and output the paragraph.
215 $_ = $self->interpolate ($_, $line);
216 s/\s+$/\n/;
217 if (defined $$self{ITEM}) {
218 $self->item ($_ . "\n");
219 } else {
220 $self->output ($self->reformat ($_ . "\n"));
221 }
222}
69e00e79 223
6055f9d4 224# Called for an interior sequence. Gets the command, argument, and a
225# Pod::InteriorSequence object and is expected to return the resulting text.
226# Calls code, bold, italic, file, and link to handle those types of
227# sequences, and handles S<>, E<>, X<>, and Z<> directly.
228sub interior_sequence {
229 my $self = shift;
230 my $command = shift;
231 local $_ = shift;
232 return '' if ($command eq 'X' || $command eq 'Z');
69e00e79 233
6055f9d4 234 # Expand escapes into the actual character now, carping if invalid.
235 if ($command eq 'E') {
236 return $ESCAPES{$_} if defined $ESCAPES{$_};
237 carp "Unknown escape: E<$_>";
238 return "E<$_>";
239 }
69e00e79 240
6055f9d4 241 # For all the other sequences, empty content produces no output.
242 return unless $_;
69e00e79 243
6055f9d4 244 # For S<>, compress all internal whitespace and then map spaces to \01.
245 # When we output the text, we'll map this back.
246 if ($command eq 'S') {
247 s/\s{2,}/ /g;
248 tr/ /\01/;
249 return $_;
250 }
69e00e79 251
6055f9d4 252 # Anything else needs to get dispatched to another method.
253 if ($command eq 'B') { return $self->seq_b ($_) }
254 elsif ($command eq 'C') { return $self->seq_c ($_) }
255 elsif ($command eq 'F') { return $self->seq_f ($_) }
256 elsif ($command eq 'I') { return $self->seq_i ($_) }
257 elsif ($command eq 'L') { return $self->seq_l ($_) }
258 else { carp "Unknown sequence $command<$_>" }
259}
f02a87df 260
6055f9d4 261# Called for each paragraph that's actually part of the POD. We take
262# advantage of this opportunity to untabify the input.
263sub preprocess_paragraph {
264 my $self = shift;
265 local $_ = shift;
266 1 while s/^(.*?)(\t+)/$1 . ' ' x (length ($2) * 8 - length ($1) % 8)/me;
267 $_;
268}
3ec07288 269
69e00e79 270
6055f9d4 271############################################################################
272# Command paragraphs
273############################################################################
f2506fb2 274
6055f9d4 275# All command paragraphs take the paragraph and the line number.
69e00e79 276
6055f9d4 277# First level heading.
278sub cmd_head1 {
279 my $self = shift;
280 local $_ = shift;
281 s/\s+$//;
282 if ($$self{alt}) {
283 $self->output ("\n==== $_ ====\n\n");
284 } else {
285 $_ .= "\n" if $$self{loose};
286 $self->output ($_ . "\n");
287 }
288}
69e00e79 289
6055f9d4 290# Second level heading.
291sub cmd_head2 {
292 my $self = shift;
293 local $_ = shift;
294 s/\s+$//;
295 if ($$self{alt}) {
296 $self->output ("\n== $_ ==\n\n");
297 } else {
298 $self->output (' ' x ($$self{indent} / 2) . $_ . "\n\n");
299 }
300}
69e00e79 301
6055f9d4 302# Start a list.
303sub cmd_over {
304 my $self = shift;
305 local $_ = shift;
306 unless (/^[-+]?\d+\s+$/) { $_ = $$self{indent} }
307 push (@{ $$self{INDENTS} }, $$self{MARGIN});
308 $$self{MARGIN} += ($_ + 0);
309}
69e00e79 310
6055f9d4 311# End a list.
312sub cmd_back {
313 my $self = shift;
314 $$self{MARGIN} = pop @{ $$self{INDENTS} };
315 unless (defined $$self{MARGIN}) {
316 carp "Unmatched =back";
317 $$self{MARGIN} = $$self{indent};
318 }
69e00e79 319}
320
6055f9d4 321# An individual list item.
322sub cmd_item {
323 my $self = shift;
324 if (defined $$self{ITEM}) { $self->item }
325 local $_ = shift;
326 s/\s+$//;
327 $$self{ITEM} = $self->interpolate ($_);
328}
69e00e79 329
6055f9d4 330# Begin a block for a particular translator. To allow for weird nested
331# =begin blocks, keep track of how many blocks we were excluded from and
332# only unwind one level with each =end.
333sub cmd_begin {
334 my $self = shift;
335 local $_ = shift;
336 my ($kind) = /^(\S+)/ or return;
337 push (@{ $$self{BEGUN} }, $kind);
338 $$self{EXCLUDE}++ unless $kind eq 'text';
339}
f2506fb2 340
6055f9d4 341# End a block for a particular translator. We assume that all =begin/=end
342# pairs are properly nested and just pop the previous one.
343sub cmd_end {
344 my $self = shift;
345 my $kind = pop @{ $$self{BEGUN} };
346 $$self{EXCLUDE}-- if $$self{EXCLUDE};
347}
348
349# One paragraph for a particular translator. Ignore it unless it's intended
350# for text, in which case we treat it as either a normal text block or a
351# verbatim text block, depending on whether it's indented.
352sub cmd_for {
353 my $self = shift;
354 local $_ = shift;
355 my $line = shift;
356 return unless s/^text\b[ \t]*//;
357 if (/^\n\s+/) {
358 $self->verbatim ($_, $line);
359 } else {
360 $self->textblock ($_, $line);
361 }
362}
f2506fb2 363
69e00e79 364
6055f9d4 365############################################################################
366# Interior sequences
367############################################################################
69e00e79 368
6055f9d4 369# The simple formatting ones. These are here mostly so that subclasses can
370# override them and do more complicated things.
371sub seq_b { my $self = shift; return $$self{alt} ? "``$_[0]''" : $_[0] }
372sub seq_c { my $self = shift; return $$self{alt} ? "``$_[0]''" : "`$_[0]'" }
373sub seq_f { my $self = shift; return $$self{alt} ? "\"$_[0]\"" : $_[0] }
374sub seq_i { return '*' . $_[1] . '*' }
69e00e79 375
6055f9d4 376# The complicated one. Handle links. Since this is plain text, we can't
377# actually make any real links, so this is all to figure out what text we
378# print out.
379sub seq_l {
380 my $self = shift;
381 local $_ = shift;
69e00e79 382
6055f9d4 383 # Smash whitespace in case we were split across multiple lines.
384 s/\s+/ /g;
69e00e79 385
6055f9d4 386 # If we were given any explicit text, just output it.
387 if (/^([^|]+)\|/) { return $1 }
388
389 # Okay, leading and trailing whitespace isn't important; get rid of it.
390 s/^\s+//;
391 s/\s+$//;
392 chomp;
393
394 # Default to using the whole content of the link entry as a section
395 # name. Note that L<manpage/> forces a manpage interpretation, as does
396 # something looking like L<manpage(section)>. The latter is an
397 # enhancement over the original Pod::Text.
398 my ($manpage, $section) = ('', $_);
399 if (/^"\s*(.*?)\s*"$/) {
400 $section = '"' . $1 . '"';
401 } elsif (m/^[-:.\w]+(?:\(\S+\))?$/) {
402 ($manpage, $section) = ($_, '');
403 } elsif (m%/%) {
404 ($manpage, $section) = split (/\s*\/\s*/, $_, 2);
8c634b6e 405 }
406
6055f9d4 407 # Now build the actual output text.
408 my $text = '';
409 if (!length $section) {
410 $text = "the $manpage manpage" if length $manpage;
411 } elsif ($section =~ /^[:\w]+(?:\(\))?/) {
412 $text .= 'the ' . $section . ' entry';
413 $text .= (length $manpage) ? " in the $manpage manpage"
414 : " elsewhere in this document";
415 } else {
416 $section =~ s/^\"\s*//;
417 $section =~ s/\s*\"$//;
418 $text .= 'the section on "' . $section . '"';
419 $text .= " in the $manpage manpage" if length $manpage;
69e00e79 420 }
6055f9d4 421 $text;
69e00e79 422}
423
6055f9d4 424
425############################################################################
426# List handling
427############################################################################
428
429# This method is called whenever an =item command is complete (in other
430# words, we've seen its associated paragraph or know for certain that it
431# doesn't have one). It gets the paragraph associated with the item as an
432# argument. If that argument is empty, just output the item tag; if it
433# contains a newline, output the item tag followed by the newline.
434# Otherwise, see if there's enough room for us to output the item tag in the
435# margin of the text or if we have to put it on a separate line.
436sub item {
437 my $self = shift;
438 local $_ = shift;
439 my $tag = $$self{ITEM};
440 unless (defined $tag) {
441 carp "item called without tag";
442 return;
69e00e79 443 }
6055f9d4 444 undef $$self{ITEM};
445 my $indent = $$self{INDENTS}[-1];
446 unless (defined $indent) { $indent = $$self{indent} }
447 my $space = ' ' x $indent;
448 $space =~ s/^ /:/ if $$self{alt};
449 if (!$_ || /^\s+$/ || ($$self{MARGIN} - $indent < length ($tag) + 1)) {
450 $self->output ($space . $tag . "\n");
451 $self->output ($self->reformat ($_)) if /\S/;
452 } else {
453 $_ = $self->reformat ($_);
454 s/^ /:/ if ($$self{alt} && $indent > 0);
455 my $tagspace = ' ' x length $tag;
456 s/^($space)$tagspace/$1$tag/ or warn "Bizarre space in item";
457 $self->output ($_);
69e00e79 458 }
459}
460
69e00e79 461
6055f9d4 462############################################################################
463# Output formatting
464############################################################################
69e00e79 465
6055f9d4 466# Wrap a line, indenting by the current left margin. We can't use
467# Text::Wrap because it plays games with tabs. We can't use formline, even
468# though we'd really like to, because it screws up non-printing characters.
469# So we have to do the wrapping ourselves.
470sub wrap {
471 my $self = shift;
472 local $_ = shift;
473 my $output = '';
474 my $spaces = ' ' x $$self{MARGIN};
475 my $width = $$self{width} - $$self{MARGIN};
476 while (length > $width) {
477 if (s/^([^\n]{0,$width})\s+// || s/^([^\n]{$width})//) {
478 $output .= $spaces . $1 . "\n";
479 } else {
480 last;
481 }
69e00e79 482 }
6055f9d4 483 $output .= $spaces . $_;
484 $output =~ s/\s+$/\n\n/;
485 $output;
69e00e79 486}
487
6055f9d4 488# Reformat a paragraph of text for the current margin. Takes the text to
489# reformat and returns the formatted text.
490sub reformat {
491 my $self = shift;
492 local $_ = shift;
69e00e79 493
6055f9d4 494 # If we're trying to preserve two spaces after sentences, do some
495 # munging to support that. Otherwise, smash all repeated whitespace.
496 if ($$self{sentence}) {
497 s/ +$//mg;
498 s/\.\n/. \n/g;
499 s/\n/ /g;
500 s/ +/ /g;
69e00e79 501 } else {
6055f9d4 502 s/\s+/ /g;
69e00e79 503 }
6055f9d4 504 $self->wrap ($_);
69e00e79 505}
506
6055f9d4 507# Output text to the output device.
508sub output { $_[1] =~ tr/\01/ /; print { $_[0]->output_handle } $_[1] }
69e00e79 509
69e00e79 510
6055f9d4 511############################################################################
512# Module return value and documentation
513############################################################################
69e00e79 514
6055f9d4 5151;
516__END__
69e00e79 517
6055f9d4 518=head1 NAME
69e00e79 519
6055f9d4 520Pod::Text - Convert POD data to formatted ASCII text
69e00e79 521
6055f9d4 522=head1 SYNOPSIS
69e00e79 523
6055f9d4 524 use Pod::Text;
525 my $parser = Pod::Text->new (sentence => 0, width => 78);
69e00e79 526
6055f9d4 527 # Read POD from STDIN and write to STDOUT.
528 $parser->parse_from_filehandle;
69e00e79 529
6055f9d4 530 # Read POD from file.pod and write to file.txt.
531 $parser->parse_from_file ('file.pod', 'file.txt');
69e00e79 532
6055f9d4 533=head1 DESCRIPTION
5491a304 534
6055f9d4 535Pod::Text is a module that can convert documentation in the POD format
536(such as can be found throughout the Perl distribution) into formatted
537ASCII. It uses no special formatting controls or codes whatsoever, and its
538output is therefore suitable for nearly any device.
69e00e79 539
6055f9d4 540As a derived class from Pod::Parser, Pod::Text supports the same
541methods and interfaces. See L<Pod::Parser> for all the details; briefly,
542one creates a new parser with C<Pod::Text-E<gt>new()> and then calls
543either C<parse_from_filehandle()> or C<parse_from_file()>.
544
545C<new()> can take options, in the form of key/value pairs, that control the
546behavior of the parser. The currently recognized options are:
547
548=over 4
549
550=item alt
551
552If set to a true value, selects an alternate output format that, among other
553things, uses a different heading style and marks C<=item> entries with a
554colon in the left margin. Defaults to false.
555
556=item indent
557
558The number of spaces to indent regular text, and the default indentation for
559C<=over> blocks. Defaults to 4.
560
561=item loose
562
563If set to a true value, a blank line is printed after a C<=head1> heading.
564If set to false (the default), no blank line is printed after C<=head1>,
565although one is still printed after C<=head2>. This is the default because
566it's the expected formatting for manual pages; if you're formatting
567arbitrary text documents, setting this to true may result in more pleasing
568output.
569
570=item sentence
571
572If set to a true value, Pod::Text will assume that each sentence ends
573in two spaces, and will try to preserve that spacing. If set to false, all
574consecutive whitespace in non-verbatim paragraphs is compressed into a
575single space. Defaults to true.
576
577=item width
578
579The column at which to wrap text on the right-hand side. Defaults to 76.
580
581=back
582
583The standard Pod::Parser method C<parse_from_filehandle()> takes up to two
584arguments, the first being the file handle to read POD from and the second
585being the file handle to write the formatted output to. The first defaults
586to STDIN if not given, and the second defaults to STDOUT. The method
587C<parse_from_file()> is almost identical, except that its two arguments are
588the input and output disk files instead. See L<Pod::Parser> for the
589specific details.
590
591=head1 DIAGNOSTICS
592
593=over 4
594
595=item Unknown escape: %s
596
597The POD source contained an C<EE<lt>E<gt>> escape that Pod::Text
598didn't know about.
599
600=item Unknown sequence: %s
601
602The POD source contained a non-standard internal sequence (something of the
603form C<XE<lt>E<gt>>) that Pod::Text didn't know about.
604
605=item Unmatched =back
606
607Pod::Text encountered a C<=back> command that didn't correspond to an
608C<=over> command.
609
610=back
611
612=head1 NOTES
613
614I'm hoping this module will eventually replace Pod::Text in Perl core once
615Pod::Parser has been added to Perl core. Accordingly, don't be surprised if
616the name of this module changes to Pod::Text down the road.
617
618The original Pod::Text contained code to do formatting via termcap
619sequences, although it wasn't turned on by default and it was problematic to
620get it to work at all. This module doesn't even try to do that, but a
621subclass of it does. Look for Pod::Text::Termcap.
622
623=head1 SEE ALSO
624
625L<Pod::Parser|Pod::Parser>, L<Pod::Text::Termcap|Pod::Text::Termcap>
626
627=head1 AUTHOR
628
629Russ Allbery E<lt>rra@stanford.eduE<gt>, based I<very> heavily on the
630original Pod::Text by Tom Christiansen E<lt>tchrist@mox.perl.comE<gt> and
631its conversion to Pod::Parser by Brad Appleton
632E<lt>bradapp@enteract.comE<gt>.
633
634=cut