File Coverage

blib/lib/App/Rakubrew/Shell.pm
Criterion Covered Total %
statement 64 190 33.6
branch 9 90 10.0
condition 1 84 1.1
subroutine 14 22 63.6
pod 0 9 0.0
total 88 395 22.2


line stmt bran cond sub pod time code
1             package App::Rakubrew::Shell;
2 15     15   144496 use strict;
  15         37  
  15         708  
3 15     15   88 use warnings;
  15         29  
  15         888  
4 15     15   275 use 5.010;
  15         54  
5 15     15   702 use Encode::Locale qw(env);
  15         43614  
  15         1265  
6 15     15   593 use File::Spec::Functions qw(catdir catfile updir splitpath);
  15         816  
  15         2631  
7 15     15   618 use Try::Tiny;
  15         2783  
  15         1098  
8 15     15   632 use App::Rakubrew::Tools;
  15         58  
  15         1101  
9 15     15   543 use App::Rakubrew::Variables;
  15         29  
  15         2941  
10 15     15   564 use App::Rakubrew::VersionHandling;
  15         32  
  15         2495  
11              
12             # Turn on substring-based command line completion where possible contrary to the
13             # "start of the line completion". I.e., to visualize the difference, 'ver'
14             # string would result in the following command candidates:
15             # SUBSTRING_COMPLETION==1 -> version versions rakubrew-version
16             # SUBSTRING_COMPLETION==0 -> version versions
17 15     15   99 use constant SUBSTRING_COMPLETION => 1;
  15         28  
  15         40206  
18              
19             my $shell_hook;
20              
21             sub initialize {
22 18     18 0 219385 my $class = shift;
23 18         79 my $shell = shift;
24              
25 18 50 33     216 if (!shell_exists('Dummy self', $shell) || $shell eq 'auto') {
26 0         0 $shell = detect_shell();
27             }
28              
29 18         1456 eval "require App::Rakubrew::Shell::$shell";
30 18 50       113 if ($@) {
31 0         0 die "Loading shell hook failed: " . $@;
32             }
33 18         116 $shell_hook = bless {}, "App::Rakubrew::Shell::$shell";
34 18         91 return $shell_hook;
35             }
36              
37             sub detect_shell {
38 0 0   0 0 0 if ($^O =~ /win32/i) {
39             # https://stackoverflow.com/a/8547234
40 0         0 my $psmodpath = env('PSMODULEPATH');
41 0 0       0 if ($psmodpath =~ /\\Documents\\(?:Windows)?PowerShell\\Modules(?:;|$)/) {
42 0         0 return 'PowerShell';
43             }
44             else {
45 0         0 return 'Cmd';
46             }
47             }
48             else {
49 0   0     0 my $shell = env('SHELL') || '/bin/bash';
50 0         0 $shell = (splitpath( $shell))[2];
51 0         0 $shell =~ s/[^a-z]+$//; # remove version numbers
52              
53             # tcsh claims it's csh on FreeBSD. Try to detect that.
54 0 0 0     0 if ($shell eq 'csh' && env('tcsh')) {
55 0         0 $shell = 'tcsh';
56             }
57              
58 0         0 $shell = ucfirst $shell;
59              
60 0 0       0 if (!shell_exists('Dummy self', $shell)) {
61 0         0 $shell = 'Sh';
62             }
63              
64 0         0 return $shell;
65             }
66             }
67              
68             sub get {
69 0     0 0 0 my $self = shift;
70 0         0 return $shell_hook;
71             }
72              
73             sub available_shells {
74 0     0 0 0
75             }
76              
77             sub shell_exists {
78 18     18 0 51 my $self = shift;
79 18         55 my $shell = shift;
80              
81 18         2403 eval "require App::Rakubrew::Shell::$shell";
82 18 50       330 return $@ ? 0 : 1;
83             }
84              
85             sub print_shellmod_code {
86 0     0 0 0 my $self = shift;
87 0         0 my @params = @_;
88 0   0     0 my $command = shift(@params) // '';
89 0         0 my $mode = get_brew_mode(1);
90 0         0 my $version;
91              
92 0 0       0 my $sep = $^O =~ /win32/i ? ';' : ':';
93              
94 0 0 0     0 if ($command eq 'shell' && @params) {
    0 0        
    0          
95 0         0 $version = $params[0];
96 0 0       0 if ($params[0] eq '--unset') {
    0          
97 0         0 say $self->get_shell_unsetter_code();
98             }
99             elsif (! is_version_broken($params[0])) {
100 0         0 say $self->get_shell_setter_code($params[0]);
101             }
102             }
103             elsif ($command eq 'mode' && $mode eq 'shim') { # just switched to shim mode
104 0         0 my $path = env('PATH');
105 0         0 $path = $self->clean_path($path);
106 0         0 $path = $shim_dir . $sep . $path;
107 0         0 say $self->get_path_setter_code($path);
108             }
109             elsif ($mode eq 'env') {
110 0         0 $version = get_version();
111             }
112              
113 0 0       0 if ($mode eq 'env') {
114 0         0 my $path = env('PATH');
115 0         0 $path = $self->clean_path($path);
116              
117 0 0       0 if ($version ne 'system') {
118 0 0       0 if ($version eq '--unset') {
119             # Get version ignoring the still set shell version.
120 0         0 $version = get_version('shell');
121             }
122 0 0       0 return if is_version_broken($version);
123 0         0 $path = join($sep, get_bin_paths($version), $path);
124             }
125              
126             # In env mode several commands require changing PATH, so we just always
127             # construct a new PATH and see if it's different.
128 0 0       0 if ($path ne env('PATH')) {
129 0         0 say $self->get_path_setter_code($path);
130             }
131             }
132             }
133              
134             sub clean_path {
135 1     1 0 3 my $self = shift;
136 1         2 my $path = shift;
137 1         2 my $also_clean_path = shift;
138              
139 1 50       8 my $sep = $^O =~ /win32/i ? ';' : ':';
140              
141 1         3 my @paths;
142 1         6 for my $version (get_versions()) {
143 3 100       42 next if $version eq 'system';
144 2 100       8 next if is_version_broken($version);
145             try {
146 1     1   79 push @paths, get_bin_paths($version);
147             }
148       0     catch {
149             # Version is broken. So it's likely not in path anyways.
150             # -> ignore it
151 1         13 };
152             }
153 1         5 push @paths, $versions_dir;
154 1         2 push @paths, $shim_dir;
155 1 50       5 push @paths, $also_clean_path if $also_clean_path;
156 1         4 @paths = map { "\Q$_\E" } @paths;
  4         13  
157 1         5 my $paths_regex = join "|", @paths;
158              
159 1         3 my $old_path;
160 1         18 do {
161 1         3 $old_path = $path;
162 1         174 $path =~ s/^($paths_regex)[^$sep]*$//g;
163 1         121 $path =~ s/^($paths_regex)[^$sep]*$sep//g;
164 1         108 $path =~ s/$sep($paths_regex)[^$sep]*$//g;
165 1         64 $path =~ s/$sep($paths_regex)[^$sep]*$sep/$sep/g;
166             } until $path eq $old_path;
167 1         9 return $path;
168             }
169              
170             # Strips out all elements in arguments array up to and including $bre_name
171             # command. The first argument is index where the completion should look for the
172             # word to be completed.
173             sub strip_executable {
174 0     0 0   my $self = shift;
175 0           my $index = shift;
176              
177 0           my $cmd_pos = 0;
178 0           foreach my $word (@_) {
179 0           ++$cmd_pos;
180 0           --$index;
181 0 0         last if $word =~ /(^|\W)$brew_name$/;
182             }
183 0           return ($index, @_[$cmd_pos..$#_])
184             }
185              
186             =pod
187              
188             Returns a list of completion candidates.
189             This function takes two parameters:
190              
191             =over 4
192              
193             =item * Index of the word to complete, 0-based. If C<-1> is passed then list of all commands is returned.
194              
195             =item * A list of words already entered
196              
197             =back
198              
199             =cut
200              
201             sub _filter_candidates {
202 0     0     my $self = shift;
203 0           my $seed = shift;
204             return
205             # If a shell preserves ordering then put the prefix-mathing candidates first. I.e. for 'ver' 'version' would
206             # precede 'rakudo-version'
207 0           sort { index($a, $seed) cmp index($b, $seed) }
208             grep {
209 0           my $pos = index($_, $seed);
  0            
210 0           SUBSTRING_COMPLETION ? $pos >= 0 : $pos == 0
211             } @_
212             }
213              
214             sub get_completions {
215 0     0 0   my $self = shift;
216 0           my ($index, @words) = @_;
217              
218 0           my @commands = qw(version current versions list global switch shell local nuke unregister rehash available list-available build register build-zef download exec which whence mode self-upgrade triple test home rakubrew-version);
219              
220 0 0 0       if ($index <= 0) { # if @words is empty then $index == -1
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
      0        
      0        
221 0 0 0       my $candidate = $index < 0 || !$words[0] ? '' : $words[0];
222 0           my @c = $self->_filter_candidates($candidate, @commands, 'help');
223 0           return @c;
224             }
225             elsif($index == 1 && ($words[0] eq 'version' || $words[0] eq 'current')) {
226 0   0       my $candidate = $words[1] // '';
227 0           return $self->_filter_candidates($candidate, '--short');
228             }
229             elsif($index == 1 && ($words[0] eq 'global' || $words[0] eq 'switch' || $words[0] eq 'shell' || $words[0] eq 'local' || $words[0] eq 'nuke' || $words[0] eq 'test')) {
230 0           my @versions = get_versions();
231 0 0         push @versions, 'all' if $words[0] eq 'test';
232 0 0         push @versions, '--unset' if $words[0] eq 'shell';
233 0   0       my $candidate = $words[1] // '';
234 0           return $self->_filter_candidates($candidate, @versions);
235             }
236             elsif($index == 1 && $words[0] eq 'exec') {
237 0   0       my $candidate = $words[1] // '';
238 0           return $self->_filter_candidates($candidate, '--with');
239             }
240             elsif($index == 2 && $words[0] eq 'exec' && $words[1] eq '--with') {
241 0           my @versions = get_versions();
242 0   0       my $candidate = $words[2] // '';
243 0           return $self->_filter_candidates($candidate, @versions);
244             }
245             elsif($index == 1 && $words[0] eq 'build') {
246 0   0       my $candidate = $words[1] // '';
247 0           return $self->_filter_candidates($candidate, (App::Rakubrew::Variables::available_backends(), 'all'));
248             }
249             elsif($index == 2 && $words[0] eq 'build') {
250 0 0         my @installed = map { if ($_ =~ /^\Q$words[1]\E-(.*)$/) {$1} else { () } } get_versions();
  0            
  0            
  0            
251 0           my @installables = grep({ my $able = $_; !grep({ $able eq $_ } @installed) } App::Rakubrew::Build::available_rakudos());
  0            
  0            
  0            
252 0   0       my $candidate = $words[2] // '';
253 0           return $self->_filter_candidates($candidate, @installables);
254             }
255             elsif($index == 1 && $words[0] eq 'download') {
256 0   0       my $candidate = $words[1] // '';
257 0           return $self->_filter_candidates($candidate, ('moar'));
258             }
259             elsif($index == 2 && $words[0] eq 'download') {
260 0 0         my @installed = map { if ($_ =~ /^\Q$words[1]\E-(.*)$/) {$1} else { () } } get_versions();
  0            
  0            
  0            
261 0           my @installables = map { $_->{ver} } App::Rakubrew::Download::available_precomp_archives();
  0            
262 0           @installables = grep { my $able = $_; !grep({ $able eq $_ } @installed) } @installables;
  0            
  0            
  0            
263 0   0       my $candidate = $words[2] // '';
264 0           return $self->_filter_candidates($candidate, @installables);
265             }
266             elsif($index == 1 && $words[0] eq 'mode') {
267 0           my @modes = qw(env shim);
268 0   0       my $candidate = $words[2] // '';
269 0           return $self->_filter_candidates($candidate, @modes);
270             }
271             elsif($index == 2 && $words[0] eq 'register') {
272 0           my @completions;
273              
274 0           my $path = $words[2];
275 0           my ($volume, $directories, $file) = splitpath($path);
276 0           $path = catdir($volume, $directories, $file); # Normalize the path
277 0           my $basepath = catdir($volume, $directories);
278 0 0         opendir(my $dh, $basepath) or return '';
279 0           while (my $entry = readdir $dh) {
280 0           my $candidate = catdir($basepath, $entry);
281 0 0         next if $entry =~ /^\./;
282 0 0         next if substr($candidate, 0, length($path)) ne $path;
283 0 0         next if !-d $candidate;
284 0 0 0       $candidate .= '/' if length($candidate) > 0 && substr($candidate, -1) ne '/';
285 0           push @completions, $candidate;
286             }
287 0           closedir $dh;
288 0           return @completions;
289             }
290             elsif($index == 1 && $words[0] eq 'help') {
291 0   0       my $candidate = $words[1] // '';
292 0           my @topics = @commands;
293 0           push @topics, '--verbose';
294 0           return $self->_filter_candidates($candidate, @topics);
295             }
296             }
297              
298             1;