File Coverage

blib/lib/App/Grok.pm
Criterion Covered Total %
statement 48 140 34.2
branch 1 64 1.5
condition 0 18 0.0
subroutine 17 29 58.6
pod 7 7 100.0
total 73 258 28.2


line stmt bran cond sub pod time code
1             package App::Grok;
2             BEGIN {
3 1     1   1463 $App::Grok::AUTHORITY = 'cpan:HINRIK';
4             }
5             {
6             $App::Grok::VERSION = '0.26';
7             }
8              
9 1     1   10 use strict;
  1         2  
  1         43  
10 1     1   7 use warnings FATAL => 'all';
  1         2  
  1         72  
11 1     1   1689 use App::Grok::Resource::File qw<:ALL>;
  1         3  
  1         284  
12 1     1   1617 use App::Grok::Resource::Functions qw<:ALL>;
  1         4  
  1         215  
13 1     1   1595 use App::Grok::Resource::Spec qw<:ALL>;
  1         3  
  1         225  
14 1     1   1364 use App::Grok::Resource::Tablet qw<:ALL>;
  1         3  
  1         200  
15 1     1   1278 use App::Grok::Resource::u4x qw<:ALL>;
  1         3  
  1         170  
16 1     1   6 use Config qw<%Config>;
  1         2  
  1         37  
17 1     1   6 use File::Temp qw<tempfile>;
  1         2  
  1         49  
18 1     1   5 use File::Spec::Functions qw<catdir>;
  1         2  
  1         50  
19 1     1   1377 use IO::Interactive qw<is_interactive>;
  1         4258  
  1         7  
20 1     1   1313 use Getopt::Long qw<:config bundling>;
  1         11478  
  1         6  
21 1     1   208 use List::Util qw<first>;
  1         2  
  1         103  
22 1     1   1731 use Pod::Usage;
  1         3520  
  1         217  
23              
24             my %opt;
25              
26             our $GOT_ANSI;
27             BEGIN {
28 1 50   1   7 if ($^O eq 'Win32') {
29 0         0 eval {
30 0         0 require Win32::Console::ANSI;
31 0         0 $GOT_ANSI = 1;
32             }
33             }
34             else {
35 1         458 $GOT_ANSI = 1;
36             }
37             }
38              
39             sub new {
40 0     0 1   my ($package, %self) = @_;
41 0           return bless \%self, $package;
42             }
43              
44             sub run {
45 0     0 1   my ($self) = @_;
46              
47 0           $self->_get_options();
48              
49 0 0         if ($opt{update}) {
    0          
50 0           spec_update();
51 0           tablet_update();
52 0           return;
53             }
54             elsif ($opt{index}) {
55 0           my @index = $self->target_index();
56 0           print "$_\n" for @index;
57 0           return;
58             }
59              
60 0 0         my $target = defined $opt{file} ? $opt{file} : $ARGV[0];
61              
62 0 0         if ($opt{locate}) {
63 0 0         if (defined $opt{file}) {
64 0           print file_locate($opt{file}), "\n";
65             }
66             else {
67 0           my $file = $self->locate_target($target);
68 0 0         defined $file
69             ? print $file, "\n"
70             : die "Target file not found\n";
71             ;
72             }
73             }
74             else {
75 0           my $rendered;
76 0 0         if ($opt{file}) {
77 0           $rendered = $self->render_file($opt{file}, $opt{output});
78             }
79             else {
80 0           $rendered = $self->render_target($target, $opt{output});
81             }
82              
83 0 0         die "Target '$target' not recognized\n" if !defined $rendered;
84 0           $self->_print($rendered, $opt{output});
85             }
86              
87 0           return;
88             }
89              
90             sub _get_options {
91 0     0     my ($self) = @_;
92              
93             GetOptions(
94             'F|file=s' => \$opt{file},
95 0     0     'h|help' => sub { pod2usage(1) },
96             'i|index' => \$opt{index},
97             'l|locate' => \$opt{locate},
98             'o|output=s' => \($opt{output} = $GOT_ANSI ? 'ansi' : 'text'),
99             'T|no-pager' => \$opt{no_pager},
100 0     0     'u|unformatted' => sub { $opt{output} = 'pod' },
101             'U|update' => \$opt{update},
102             'V|version' => sub {
103 1     1   11 no strict 'vars';
  1         3  
  1         1267  
104 0 0   0     my $version = defined $VERSION ? $VERSION : 'dev-git';
105 0           print "grok $version\n";
106 0           exit;
107             },
108 0 0         ) or pod2usage();
    0          
109              
110 0 0 0       if (!$opt{update} && !$opt{index} && !defined $opt{file} && !@ARGV) {
      0        
      0        
111 0           warn "Too few arguments\n";
112 0           pod2usage();
113             }
114              
115 0           return;
116             }
117              
118             sub target_index {
119 0     0 1   my ($self) = @_;
120 0           my %index;
121 0           @index{tablet_index()} = 1;
122 0           @index{spec_index()} = 1;
123 0           @index{func_index()} = 1;
124 0           @index{u4x_index()} = 1;
125 0           return keys %index;
126             }
127              
128             sub locate_target {
129 0     0 1   my ($self, $target) = @_;
130              
131 0           my $found = u4x_locate($target);
132 0 0         $found = func_locate($target) if !defined $found;
133 0 0         $found = spec_locate($target) if !defined $found;
134 0 0         $found = tablet_locate($target) if !defined $found;
135 0 0         $found = file_locate($target) if !defined $found;
136              
137 0 0         return $found if defined $found;
138 0           return;
139             }
140              
141             sub detect_source {
142 0     0 1   my ($self, $target) = @_;
143              
144 0           $target =~ s/.*^=encoding\b.*$//m; # skip over =encoding
145 0           my ($first_pod) = $target =~ /^(=\S+)/m;
146 0 0         return if !defined $first_pod; # no Pod found
147              
148 0 0 0       if ($first_pod =~ /^=(?:pod|head\d+|over)$/
149             || $target =~ /^=cut\b/m) {
150 0           return 'App::Grok::Parser::Pod5';
151             }
152             else {
153 0           return 'App::Grok::Parser::Pod6';
154             }
155             }
156              
157             sub render_target {
158 0     0 1   my ($self, $target, $output) = @_;
159              
160 0           my $found = u4x_fetch($target);
161 0 0         $found = func_fetch($target) if !defined $found;
162 0 0         $found = spec_fetch($target) if !defined $found;
163 0 0         $found = tablet_fetch($target) if !defined $found;
164 0 0         $found = file_fetch($target) if !defined $found;
165 0 0         die "Target '$target' not recognized\n" if !defined $found;
166              
167 0           my $parser = $self->detect_source($found);
168 0           eval "require $parser";
169 0 0         die $@ if $@;
170 0           return $parser->new->render_string($found, $output);
171             }
172              
173             sub render_file {
174 0     0 1   my ($self, $file, $output) = @_;
175            
176 0 0         open my $handle, '<', $file or die "Can't open $file: $!\n";
177 0           my $pod = do { local $/ = undef; scalar <$handle> };
  0            
  0            
178              
179 0           my $parser = $self->detect_source($pod);
180 0           close $handle;
181 0           eval "require $parser";
182 0 0         die $@ if $@;
183 0           return $parser->new->render_string($pod, $output);
184             }
185              
186             sub _print {
187 0     0     my ($self, $rendered, $output) = @_;
188              
189 0 0 0       if ($opt{no_pager} || !is_interactive()) {
190 0           print $rendered;
191             }
192             else {
193 0 0         my $pager = defined $ENV{PAGER} ? $ENV{PAGER} : $Config{pager};
194              
195 0           my @args;
196             # tell less(1) to display colors without a fuss
197 0 0 0       push @args, '-f', '-R' if $pager =~ /less/ && $output eq 'ansi';
198              
199 0           my ($temp_fh, $temp) = tempfile(UNLINK => 1);
200 0           print $temp_fh $rendered;
201 0           close $temp_fh;
202              
203             # $pager might contain options (e.g. "more /e") so we pass a string
204 0 0         $^O eq 'MSWin32'
205             ? system $pager . qq{ @args "$temp"}
206             : system $pager . qq{ @args '$temp'}
207             ;
208             }
209              
210 0           return;
211             }
212              
213             1;
214              
215             =encoding utf8
216              
217             =head1 NAME
218              
219             App::Grok - Does most of grok's heavy lifting
220              
221             =head1 DESCRIPTION
222              
223             This class provides the main functionality needed by grok. It has some
224             methods you can use if you need to hook into grok.
225              
226             =head1 METHODS
227              
228             =head2 C<new>
229              
230             This is the constructor. It takes no arguments.
231              
232             =head2 C<run>
233              
234             If you call this method, it will look at the command line arguments in
235             C<@ARGV> and act accordingly. This is basically what the L<C<grok>|grok>
236             program does. Takes no arguments.
237              
238             =head2 C<target_index>
239              
240             Takes no arguments. Returns a list of all the targets known to C<grok>.
241              
242             =head2 C<detect_source>
243              
244             Takes a filename as an argument. Returns the name of the appropriate
245             C<App::Grok::*> class to parse it. Returns nothing if the file doesn't contain
246             any Pod.
247              
248             =head2 C<locate_target>
249              
250             Takes a target name as an argument. Returns the path to the target, or nothing
251             if the target is not recognized.
252              
253             =head2 C<render_target>
254              
255             Takes two arguments, a target and the name of an output format. Returns a
256             string containing the rendered documentation, or nothing if the target is
257             unrecognized.
258              
259             =head2 C<render_file>
260              
261             Takes two arguments, a filename and the name of an output format. Returns
262             a string containing the rendered document. B<Note:> this method is called
263             by L<C<render_target>|/render_target>.
264              
265             =head1 AUTHOR
266              
267             Hinrik Örn Sigurðsson, L<hinrik.sig@gmail.com>
268              
269             =head1 LICENSE AND COPYRIGHT
270              
271             Copyright 2009 Hinrik Örn Sigurðsson
272              
273             C<grok> is distributed under the terms of the Artistic License 2.0.
274             For more details, see the full text of the license in the file F<LICENSE>
275             that came with this distribution.
276              
277             =cut