File Coverage

blib/lib/App/tldr.pm
Criterion Covered Total %
statement 42 141 29.7
branch 0 52 0.0
condition 0 13 0.0
subroutine 14 26 53.8
pod 0 3 0.0
total 56 235 23.8


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