File Coverage

blib/lib/App/tldr.pm
Criterion Covered Total %
statement 38 140 27.1
branch 0 54 0.0
condition 0 13 0.0
subroutine 13 25 52.0
pod 0 3 0.0
total 51 235 21.7


line stmt bran cond sub pod time code
1             package App::tldr 0.20;
2 1     1   55415 use v5.16;
  1         3  
3 1     1   4 use warnings;
  1         1  
  1         21  
4              
5 1     1   460 use Encode ();
  1         8274  
  1         19  
6 1     1   5 use File::Spec;
  1         2  
  1         17  
7 1     1   358 use File::Which ();
  1         796  
  1         17  
8 1     1   569 use Getopt::Long ();
  1         10238  
  1         28  
9 1     1   564 use HTTP::Tiny;
  1         40022  
  1         36  
10 1     1   675 use IO::Socket::SSL;
  1         37657  
  1         7  
11 1     1   596 use Pod::Usage ();
  1         31090  
  1         25  
12 1     1   1581 use Term::ReadKey ();
  1         1573  
  1         33  
13 1     1   382 use Text::Fold ();
  1         4562  
  1         27  
14              
15 1     1   5 use constant DEBUG => !!$ENV{TLDR_DEBUG};
  1         1  
  1         72  
16 1     1   5 use constant REPOSITORY => !!$ENV{TLDR_REPOSITORY};
  1         1  
  1         1729  
17              
18             my $URL = "https://raw.githubusercontent.com/tldr-pages/tldr/main/pages%s/%s/%s.md";
19              
20             sub new {
21 0     0 0   my ($class, %option) = @_;
22 0           my $http = HTTP::Tiny->new(verify_SSL => 1);
23 0           bless { http => $http, %option }, $class;
24             }
25              
26             sub parse_options {
27 0     0 0   my ($self, @argv) = @_;
28 0           $self->{platform} = [];
29              
30 0 0 0       $self->{unicode} = ($ENV{LANG} || "") =~ /UTF-8/i ? 1 : 0;
31 0           my $parser = Getopt::Long::Parser->new(
32             config => [qw(no_auto_abbrev no_ignore_case)],
33             );
34             $parser->getoptionsfromarray(
35             \@argv,
36 0     0     "h|help" => sub { print $self->_help; exit },
  0            
37             "l|language=s" => \my $language,
38             "o|os=s@" => \($self->{platform}),
39 0     0     "v|version" => sub { printf "%s %s\n", ref $self, $self->VERSION; exit },
  0            
40             "pager=s" => \my $pager,
41             "no-pager" => \my $no_pager,
42             "unicode!" => \$self->{unicode},
43 0 0         ) or exit(2);
44 0           $self->{argv} = \@argv;
45 0 0         if ($language) {
46 0 0         $language = $language =~ /^\./ ? $language : ".$language";
47             }
48 0   0       $self->{language} = $language || '';
49 0 0 0       if (!$no_pager and -t STDOUT and my $guess = $self->_guess_pager($pager)) {
      0        
50 0           $self->{pager} = $guess;
51             }
52 0           push @{$self->{platform}}, $self->_guess_platform, "common";
  0            
53 0           $self;
54             }
55              
56             sub _guess_pager {
57 0     0     my $self = shift;
58              
59 0           my $cmd;
60 0           for my $try (grep $_, @_, $ENV{PAGER}, "less", "more") {
61 0 0         if (my $found = File::Which::which($try)) {
62 0           $cmd = $found, last;
63             }
64             }
65 0 0         return if !$cmd;
66 0 0         [$cmd, $cmd =~ /\bless$/ ? "-R" : ()];
67             }
68              
69             sub _help {
70 0     0     my ($self, $exit) = @_;
71 0           open my $fh, '>', \my $out;
72 0           Pod::Usage::pod2usage
73             exitval => 'noexit',
74             input => $0,
75             output => $fh,
76             sections => 'SYNOPSIS',
77             verbose => 99,
78             ;
79 0           $out =~ s/^Usage:\n//;
80 0           $out =~ s/^[ ]{6}//mg;
81 0           $out =~ s/\n$//;
82 0           $out;
83             }
84              
85              
86             # XXX
87             sub _guess_platform {
88 0 0   0     $^O =~ /darwin/i ? "osx" :
    0          
    0          
89             $^O =~ /linux/i ? "linux" :
90             $^O =~ /sunos/i ? "sunos" : ();
91             }
92              
93             sub _get {
94 0     0     my $self = shift;
95 0           if (REPOSITORY) {
96             $self->_local_get(@_);
97             } else {
98 0           $self->_http_get(@_);
99             }
100             }
101              
102             sub _http_get {
103 0     0     my ($self, $query, $platform) = @_;
104 0           my $url = sprintf $URL, $self->{language}, $platform, $query;
105 0           my $res = $self->{http}->get($url);
106 0 0         if ($res->{success}) {
107 0           (Encode::decode_utf8($res->{content}), undef);
108             } else {
109 0           my $err = "$res->{status} $res->{reason}";
110 0 0         if ($res->{status} == 599) {
111 0           $err .= ", $res->{content}";
112             }
113 0           (undef, "$url: $err");
114             }
115             }
116              
117             sub _local_get {
118 0     0     my ($self, $query, $platform) = @_;
119 0           my $file = File::Spec->catfile(REPOSITORY, "pages$self->{language}", $platform, "$query.md");
120 0 0         if (-f $file) {
121 0 0         open my $fh, "<:utf8", $file or die "$file: $!";
122 0           local $/;
123 0           (<$fh>, undef);
124             } else {
125 0           (undef, "Missing $file");
126             }
127             }
128              
129             sub run {
130 0     0 0   my $self = shift;
131 0 0         my $arg = shift @{$self->{argv}} or die $self->_help(1);
  0            
132 0           my $content;
133 0           for my $platform (@{ $self->{platform} }) {
  0            
134 0           ($content, my $err) = $self->_get($arg, $platform);
135 0 0         if ($content) {
136 0           last;
137 0           } elsif (DEBUG) {
138             warn "-> $err\n";
139             }
140             }
141 0 0         die "Couldn't find tldr for '$arg'\n" unless $content;
142 0           $self->_render($content, $arg);
143             }
144              
145             my $CHECK = "\N{U+2713}";
146             my $SUSHI = "\N{U+1F363}";
147              
148             sub _render {
149 0     0     my ($self, $content, $query) = @_;
150              
151 0 0         my ($check, $prompt) = $self->{unicode} ? ($CHECK, $SUSHI) : ('*', '$');
152              
153 0   0       my $width = $ENV{COLUMNS} || (Term::ReadKey::GetTerminalSize())[0];
154 0           $width -= 4;
155              
156 0           my @line = split /\n/, $content;
157              
158 0           my $out;
159 0 0         if ($self->{pager}) {
160 0 0         open $out, "|-", @{$self->{pager}} or die "failed to exec @{$self->{pager}}: $!";
  0            
  0            
161             } else {
162 0           $out = \*STDOUT;
163             }
164 0           binmode $out, ":utf8";
165              
166 0           while (defined(my $line = shift @line)) {
167 0 0         if ($line =~ /^#/) {
    0          
    0          
    0          
168             # skip
169             } elsif ($line =~ s/^\>\s*//) {
170 0           my $description = $line;
171 0           while (1) {
172 0           my $next = shift @line;
173 0 0         if ($next eq "") {
    0          
174 0           next;
175             } elsif ($next =~ s/^\>\s*//) {
176 0           $description .= "\n$next";
177             } else {
178 0           unshift @line, $next;
179 0           last;
180             }
181             }
182 0           my $fold = Text::Fold::fold_text($description, $width);
183 0           $out->print("\n");
184 0           $out->print(" \e[32m$_\e[m\n") for split /\n/, $fold;
185 0           $out->print("\n");
186             } elsif ($line =~ s/^[*-]\s*//) {
187 0           my $fold = Text::Fold::fold_text($line, $width - 2);
188 0           my ($first, @rest) = split /\n/, $fold;
189 0           $out->print(" \e[1m$check \e[4m$first\e[m\n");
190 0           $out->print(" \e[1m\e[4m$_\e[m\n") for @rest;
191 0           $out->print("\n");
192             } elsif ($line =~ /`([^`]+)`/) {
193 0           my $code = $1;
194 0           $code =~ s/\b$query\b/
195 0           "\e[32m$query\e[m"
196             /eg;
197 0           $out->print(" $prompt $code\n\n");
198             }
199             }
200             }
201              
202             1;
203             __END__