| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
# You may distribute under the terms of either the GNU General Public License |
|
2
|
|
|
|
|
|
|
# or the Artistic License (the same terms as Perl itself) |
|
3
|
|
|
|
|
|
|
# |
|
4
|
|
|
|
|
|
|
# (C) Paul Evans, 2021 -- leonerd@leonerd.org.uk |
|
5
|
|
|
|
|
|
|
|
|
6
|
1
|
|
|
1
|
|
1050
|
use v5.26; |
|
|
1
|
|
|
|
|
5
|
|
|
7
|
1
|
|
|
1
|
|
16
|
use utf8; |
|
|
1
|
|
|
|
|
22
|
|
|
|
1
|
|
|
|
|
9
|
|
|
8
|
|
|
|
|
|
|
|
|
9
|
1
|
|
|
1
|
|
29
|
use Object::Pad; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
5
|
|
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
package App::podman 0.01; |
|
12
|
|
|
|
|
|
|
class App::podman; |
|
13
|
|
|
|
|
|
|
|
|
14
|
1
|
|
|
1
|
|
275
|
use List::Keywords qw( first ); |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
5
|
|
|
15
|
|
|
|
|
|
|
|
|
16
|
1
|
|
|
1
|
|
633
|
use Term::Size; |
|
|
1
|
|
|
|
|
621
|
|
|
|
1
|
|
|
|
|
56
|
|
|
17
|
1
|
|
|
1
|
|
595
|
use Convert::Color; |
|
|
1
|
|
|
|
|
23332
|
|
|
|
1
|
|
|
|
|
54
|
|
|
18
|
1
|
|
|
1
|
|
534
|
use Convert::Color::XTerm 0.06; |
|
|
1
|
|
|
|
|
3261
|
|
|
|
1
|
|
|
|
|
39
|
|
|
19
|
1
|
|
|
1
|
|
558
|
use String::Tagged::Terminal; |
|
|
1
|
|
|
|
|
9073
|
|
|
|
1
|
|
|
|
|
2395
|
|
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=head1 NAME |
|
22
|
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
C - a terminal document viewer for POD and other syntaxes |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
use App::podman; |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
exit App::podman->new->run( "some-file.pod" ); |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
32
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
This module implements a terminal-based program for viewing structured |
|
34
|
|
|
|
|
|
|
documents. It currently understands POD and some simple Markdown formatting, |
|
35
|
|
|
|
|
|
|
though future versions are expected to handle nroff (for manpages) and other |
|
36
|
|
|
|
|
|
|
styles. |
|
37
|
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
To actually use it, you likely wanted wanted to see the F script. |
|
39
|
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
=cut |
|
41
|
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
my %FORMATSTYLES = ( |
|
43
|
|
|
|
|
|
|
B => { bold => 1 }, |
|
44
|
|
|
|
|
|
|
I => { italic => 1 }, |
|
45
|
|
|
|
|
|
|
F => { italic => 1, under => 1 }, |
|
46
|
|
|
|
|
|
|
C => { monospace => 1, bg => Convert::Color->new( "xterm:235" ) }, |
|
47
|
|
|
|
|
|
|
L => { under => 1, fg => Convert::Color->new( "xterm:rgb(3,3,5)" ) }, # light blue |
|
48
|
|
|
|
|
|
|
); |
|
49
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
my %PARASTYLES = ( |
|
51
|
|
|
|
|
|
|
head1 => { fg => Convert::Color->new( "vga:yellow" ), bold => 1 }, |
|
52
|
|
|
|
|
|
|
head2 => { fg => Convert::Color->new( "vga:cyan" ), bold => 1, indent => 2 }, |
|
53
|
|
|
|
|
|
|
head3 => { fg => Convert::Color->new( "vga:green" ), bold => 1, indent => 4 }, |
|
54
|
|
|
|
|
|
|
# TODO head4 |
|
55
|
|
|
|
|
|
|
plain => { indent => 6, blank_after => 1 }, |
|
56
|
|
|
|
|
|
|
verbatim => { indent => 8, blank_after => 1, $FORMATSTYLES{C}->%* }, |
|
57
|
|
|
|
|
|
|
); |
|
58
|
|
|
|
|
|
|
$PARASTYLES{item} = $PARASTYLES{plain}; |
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
my @PARSER_CLASSES = qw( |
|
61
|
|
|
|
|
|
|
App::podman::Parser::Pod |
|
62
|
|
|
|
|
|
|
App::podman::Parser::Markdown |
|
63
|
|
|
|
|
|
|
); |
|
64
|
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
require ( "$_.pm" =~ s{::}{/}gr ) for @PARSER_CLASSES; |
|
66
|
|
|
|
|
|
|
|
|
67
|
0
|
|
|
|
|
|
method run ( $file, %opts ) |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
68
|
0
|
|
|
0
|
0
|
|
{ |
|
69
|
0
|
|
|
|
|
|
my $parser_class; |
|
70
|
|
|
|
|
|
|
|
|
71
|
0
|
0
|
|
|
|
|
if( defined $opts{format} ) { |
|
72
|
0
|
0
|
|
|
|
|
$parser_class = first { $_->format eq $opts{format} } @PARSER_CLASSES or |
|
|
0
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
die "Unrecognised format name $opts{format}\n"; |
|
74
|
|
|
|
|
|
|
} |
|
75
|
|
|
|
|
|
|
|
|
76
|
0
|
0
|
|
|
|
|
if( ! -f $file ) { |
|
77
|
0
|
|
|
|
|
|
open my $f, "-|", "perldoc", "-l", $file; |
|
78
|
0
|
0
|
|
|
|
|
$file = <$f>; chomp $file if defined $file; |
|
|
0
|
|
|
|
|
|
|
|
79
|
0
|
|
|
|
|
|
close $f; |
|
80
|
0
|
0
|
|
|
|
|
$? and return $? >> 8; |
|
81
|
|
|
|
|
|
|
} |
|
82
|
|
|
|
|
|
|
|
|
83
|
0
|
|
0
|
|
|
|
$parser_class //= do { |
|
84
|
0
|
0
|
|
|
|
|
first { $_->can_parse_file( $file ) } @PARSER_CLASSES or |
|
|
0
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
die "Unable to find a handler for $file\n"; |
|
86
|
|
|
|
|
|
|
}; |
|
87
|
|
|
|
|
|
|
|
|
88
|
0
|
|
|
|
|
|
my @paragraphs = $parser_class->new->parse_file( $file ); |
|
89
|
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
# Unless -n switch |
|
91
|
0
|
|
|
|
|
|
open my $outh, "|-", "less", "+R"; |
|
92
|
0
|
|
|
|
|
|
$outh->binmode( ":encoding(UTF-8)" ); |
|
93
|
0
|
|
|
|
|
|
select $outh; |
|
94
|
|
|
|
|
|
|
|
|
95
|
0
|
|
|
|
|
|
my $TERMWIDTH = Term::Size::chars; |
|
96
|
|
|
|
|
|
|
|
|
97
|
0
|
|
|
|
|
|
my $nextblank; |
|
98
|
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
# To avoid recusion over a bunch of variables as state, we'll maintain a queue |
|
100
|
0
|
|
|
|
|
|
while ( @paragraphs ) { |
|
101
|
0
|
|
|
|
|
|
my $para = shift @paragraphs; |
|
102
|
|
|
|
|
|
|
|
|
103
|
0
|
|
|
|
|
|
my $margin; |
|
104
|
|
|
|
|
|
|
my $leader; |
|
105
|
0
|
|
|
|
|
|
my %typestyle; |
|
106
|
|
|
|
|
|
|
|
|
107
|
0
|
0
|
|
|
|
|
if( ref $para eq "HASH" ) { |
|
108
|
0
|
|
|
|
|
|
$margin = $para->{margin}; |
|
109
|
0
|
|
|
|
|
|
$leader = sprintf "%-*s", $margin, $para->{leader}; |
|
110
|
|
|
|
|
|
|
|
|
111
|
0
|
|
|
|
|
|
%typestyle = ( $para->%* ); |
|
112
|
|
|
|
|
|
|
|
|
113
|
0
|
|
|
|
|
|
$para = $para->{para}; |
|
114
|
|
|
|
|
|
|
} |
|
115
|
|
|
|
|
|
|
|
|
116
|
0
|
0
|
|
|
|
|
if( $para->type =~ m/^list-(.*)$/ ) { |
|
117
|
0
|
|
|
|
|
|
my $listtype = $1; |
|
118
|
|
|
|
|
|
|
|
|
119
|
0
|
|
|
|
|
|
my $n = 1; |
|
120
|
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
unshift @paragraphs, map { |
|
122
|
0
|
|
|
|
|
|
my $item = $_; |
|
|
0
|
|
|
|
|
|
|
|
123
|
0
|
0
|
|
|
|
|
if( $item->type ne "item" ) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
# non-items just stand as they are + indent |
|
125
|
0
|
|
|
|
|
|
{ para => $item, margin => $margin + $para->indent } |
|
126
|
|
|
|
|
|
|
} |
|
127
|
|
|
|
|
|
|
elsif( $listtype eq "bullet" ) { |
|
128
|
0
|
|
|
|
|
|
{ para => $item, margin => $margin + $para->indent, leader => "*" } |
|
129
|
|
|
|
|
|
|
} |
|
130
|
|
|
|
|
|
|
elsif( $listtype eq "number" ) { |
|
131
|
0
|
|
|
|
|
|
{ para => $item, margin => $margin + $para->indent, leader => sprintf "%d.", $n++ } |
|
132
|
|
|
|
|
|
|
} |
|
133
|
|
|
|
|
|
|
elsif( $listtype eq "text" ) { |
|
134
|
0
|
|
|
|
|
|
{ para => $item, margin => $margin, blank_after => 0 } |
|
135
|
|
|
|
|
|
|
} |
|
136
|
|
|
|
|
|
|
} $para->items; |
|
137
|
0
|
|
|
|
|
|
next; |
|
138
|
|
|
|
|
|
|
} |
|
139
|
|
|
|
|
|
|
|
|
140
|
0
|
0
|
|
|
|
|
say "" if $nextblank; |
|
141
|
|
|
|
|
|
|
|
|
142
|
0
|
|
|
|
|
|
%typestyle = ( $PARASTYLES{ $para->type }->%*, %typestyle ); |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
my $s = $para->text->clone( |
|
145
|
|
|
|
|
|
|
convert_tags => { |
|
146
|
0
|
|
|
0
|
|
|
( map { $_ => do { my $k = $_; sub { $FORMATSTYLES{$k}->%* } } } keys %FORMATSTYLES ), |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
}, |
|
148
|
|
|
|
|
|
|
); |
|
149
|
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
$typestyle{$_} and $s->apply_tag( 0, -1, $_ => $typestyle{$_} ) |
|
151
|
0
|
|
0
|
|
|
|
for qw( fg bg bold under italic monospace ); |
|
152
|
|
|
|
|
|
|
|
|
153
|
0
|
|
|
|
|
|
$nextblank = !!$typestyle{blank_after}; |
|
154
|
|
|
|
|
|
|
|
|
155
|
0
|
|
|
|
|
|
my @lines = $s->split( qr/\n/ ); |
|
156
|
|
|
|
|
|
|
|
|
157
|
0
|
|
0
|
|
|
|
my $indent = $typestyle{indent} // 0; |
|
158
|
0
|
|
|
|
|
|
$indent += $margin; |
|
159
|
|
|
|
|
|
|
|
|
160
|
0
|
|
|
|
|
|
foreach my $line ( @lines ) { |
|
161
|
0
|
0
|
|
|
|
|
length $line or |
|
162
|
|
|
|
|
|
|
( print "\n" ), next; |
|
163
|
|
|
|
|
|
|
|
|
164
|
0
|
|
|
|
|
|
$s = String::Tagged::Terminal->new_from_formatting( $line ); |
|
165
|
|
|
|
|
|
|
|
|
166
|
0
|
|
0
|
|
|
|
my $width = $TERMWIDTH - $indent - length($leader // ""); |
|
167
|
|
|
|
|
|
|
|
|
168
|
0
|
|
|
|
|
|
while( length $s ) { |
|
169
|
0
|
|
|
|
|
|
my $part; |
|
170
|
0
|
0
|
|
|
|
|
if( length($s) > $width ) { |
|
171
|
0
|
0
|
|
|
|
|
if( substr($s, 0, $width) =~ m/(\s+)\S*$/ ) { |
|
172
|
0
|
|
|
|
|
|
my $partlen = $-[1]; |
|
173
|
0
|
|
|
|
|
|
my $chopat = $+[1]; |
|
174
|
|
|
|
|
|
|
|
|
175
|
0
|
|
|
|
|
|
$part = $s->substr( 0, $partlen ); |
|
176
|
0
|
|
|
|
|
|
$s->set_substr( 0, $chopat, "" ); |
|
177
|
|
|
|
|
|
|
} |
|
178
|
|
|
|
|
|
|
else { |
|
179
|
0
|
|
|
|
|
|
die "ARGH: notsure how to trim this one\n"; |
|
180
|
|
|
|
|
|
|
} |
|
181
|
|
|
|
|
|
|
} |
|
182
|
|
|
|
|
|
|
else { |
|
183
|
0
|
|
|
|
|
|
$part = $s; |
|
184
|
0
|
|
|
|
|
|
$s = ""; |
|
185
|
|
|
|
|
|
|
} |
|
186
|
|
|
|
|
|
|
|
|
187
|
0
|
|
0
|
|
|
|
print " "x($indent - length($leader // "")); |
|
188
|
0
|
0
|
|
|
|
|
print $leader if defined $leader; |
|
189
|
0
|
|
|
|
|
|
print $part->build_terminal . "\n"; |
|
190
|
|
|
|
|
|
|
|
|
191
|
0
|
|
|
|
|
|
undef $leader; |
|
192
|
|
|
|
|
|
|
} |
|
193
|
|
|
|
|
|
|
} |
|
194
|
|
|
|
|
|
|
} |
|
195
|
|
|
|
|
|
|
} |
|
196
|
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
=head1 TODO |
|
198
|
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
=over 4 |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
=item * |
|
202
|
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
Add more formats. nroff and ReST at least. Perhaps others. |
|
204
|
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=item * |
|
206
|
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
Improved Markdown parser. Currently the parser is very simple. |
|
208
|
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
=item * |
|
210
|
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
Other outputs. Consider a L-based frontend. Also some structured file |
|
212
|
|
|
|
|
|
|
writers - allowing cross-conversion between POD, Markdown, ReST, nroff and |
|
213
|
|
|
|
|
|
|
maybe also HTML output. |
|
214
|
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
=back |
|
216
|
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
=cut |
|
218
|
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
=head1 AUTHOR |
|
220
|
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
Paul Evans |
|
222
|
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
=cut |
|
224
|
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
0x55AA; |