| # Copyright (C) all contributors <meta@public-inbox.org> |
| # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt> |
| |
| # *-external commands of lei |
| package PublicInbox::LeiExternal; |
| use strict; |
| use v5.10.1; |
| use PublicInbox::Config qw(glob2re); |
| |
| sub externals_each { |
| my ($self, $cb, @arg) = @_; |
| my $cfg = $self->_lei_cfg; |
| my %boost; |
| for my $sec (grep(/\Aexternal\./, @{$cfg->{-section_order}})) { |
| my $loc = substr($sec, length('external.')); |
| $boost{$loc} = $cfg->{"$sec.boost"}; |
| } |
| return \%boost if !wantarray && !$cb; |
| |
| # highest boost first, but stable for alphabetic tie break |
| use sort 'stable'; |
| my @order = sort { $boost{$b} <=> $boost{$a} } sort keys %boost; |
| if (ref($cb) eq 'CODE') { |
| for my $loc (@order) { |
| $cb->(@arg, $loc, $boost{$loc}); |
| } |
| } elsif (ref($cb) eq 'HASH') { |
| %$cb = %boost; |
| } |
| @order; # scalar or array |
| } |
| |
| sub ext_canonicalize { |
| my $location = $_[-1]; # $_[0] may be $lei |
| if ($location !~ m!\Ahttps?://!) { |
| PublicInbox::Config::rel2abs_collapsed($location); |
| } else { |
| require URI; |
| my $uri = URI->new($location)->canonical; |
| my $path = $uri->path . '/'; |
| $path =~ tr!/!/!s; # squeeze redundant '/' |
| $uri->path($path); |
| $uri->as_string; |
| } |
| } |
| |
| # get canonicalized externals list matching $loc |
| # $is_exclude denotes it's for --exclude |
| # otherwise it's for --only/--include is assumed |
| sub get_externals { |
| my ($self, $loc, $is_exclude) = @_; |
| return (ext_canonicalize($loc)) if -e $loc; |
| my @m; |
| my @cur = externals_each($self); |
| my $do_glob = !$self->{opt}->{globoff}; # glob by default |
| if ($do_glob && (my $re = glob2re($loc))) { |
| @m = grep(m!$re/?\z!, @cur); |
| return @m if scalar(@m); |
| } elsif (index($loc, '/') < 0) { # exact basename match: |
| @m = grep(m!/\Q$loc\E/?\z!, @cur); |
| return @m if scalar(@m) == 1; |
| } elsif ($is_exclude) { # URL, maybe: |
| my $canon = ext_canonicalize($loc); |
| @m = grep(m!\A\Q$canon\E\z!, @cur); |
| return @m if scalar(@m) == 1; |
| } else { # URL: |
| return (ext_canonicalize($loc)); |
| } |
| if (scalar(@m) == 0) { |
| die "`$loc' is unknown\n"; |
| } else { |
| die("`$loc' is ambiguous:\n", map { "\t$_\n" } @m, "\n"); |
| } |
| } |
| |
| sub canonicalize_excludes { |
| my ($lei, $excludes) = @_; |
| my %x; |
| for my $loc (@$excludes) { |
| my @l = get_externals($lei, $loc, 1); |
| $x{$_} = 1 for @l; |
| } |
| \%x; |
| } |
| |
| # returns an anonymous sub which returns an array of potential results |
| sub complete_url_prepare { |
| my $argv = $_[-1]; # $_[0] may be $lei |
| # Workaround bash default COMP_WORDBREAKS splitting URLs to |
| # ['https', ':', '//', ...]. COMP_WORDBREAKS is global for all |
| # completions loaded, not just ours, so we can't change it. |
| # cf. contrib/completion/lei-completion.bash |
| my ($pfx, $cur) = ('', pop(@$argv) // ''); |
| if (@$argv) { |
| my @x = @$argv; |
| if ($cur =~ /\A[:;=]\z/) { # COMP_WORDBREAKS + URL union |
| push @x, $cur; |
| $cur = ''; |
| } |
| while (@x && $pfx !~ m!\A(?: (?:[\+\-]?(?:L|kw):) | |
| (?:(?:imap|nntp|http)s?:) | |
| (?:--\w?\z)|(?:-\w?\z) )!x) { |
| $pfx = pop(@x).$pfx; |
| } |
| } |
| my $re = qr!\A\Q$pfx\E(\Q$cur\E.*)!; |
| my $match_cb = sub { |
| # the "//;" here (for AUTH=ANONYMOUS) interacts badly with |
| # bash tab completion, strip it out for now since our commands |
| # work w/o it. Not sure if there's a better solution... |
| $_[0] =~ s!//;AUTH=ANONYMOUS\@!//!i; |
| # only return the part specified on the CLI |
| # don't duplicate if already 100% completed |
| $_[0] =~ $re ? ($cur eq $1 ? () : $1) : () |
| }; |
| wantarray ? ($pfx, $cur, $match_cb) : $match_cb; |
| } |
| |
| 1; |