X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=lib%2FPublicInbox%2FLEI.pm;h=39278de640a0ab9004597d281ae3484caa18b140;hb=b6b86cfd238c170ea3e2c4d4179f06c7af139086;hp=4b87c104bec98df6936a4af1dc81d56e57f41ee9;hpb=3052acd27f3119510b3efa33d77b610d6c4f1c85;p=public-inbox.git diff --git a/lib/PublicInbox/LEI.pm b/lib/PublicInbox/LEI.pm index 4b87c104..39278de6 100644 --- a/lib/PublicInbox/LEI.pm +++ b/lib/PublicInbox/LEI.pm @@ -64,32 +64,42 @@ sub opt_dash ($$) { ($spec, '<>' => $cb, $GLP_PASS) # for Getopt::Long } -sub rel2abs ($$) { +# rel2abs preserves symlinks in parent, unlike abs_path +sub rel2abs { my ($self, $p) = @_; - return $p if index($p, '/') == 0; # already absolute + if (index($p, '/') == 0) { # already absolute + $p =~ tr!/!/!s; # squeeze redundant slashes + return $p; + } my $pwd = $self->{env}->{PWD}; + my $cwd; if (defined $pwd) { - my $cwd = $self->{3} // getcwd() // die "getcwd(PWD=$pwd): $!"; + my $xcwd = $self->{3} // + ($cwd = getcwd() // die "getcwd(PWD=$pwd): $!"); if (my @st_pwd = stat($pwd)) { - my @st_cwd = stat($cwd) or die "stat($cwd): $!"; + my @st_cwd = stat($xcwd) or die "stat($xcwd): $!"; "@st_pwd[1,0]" eq "@st_cwd[1,0]" or - $self->{env}->{PWD} = $pwd = $cwd; + $self->{env}->{PWD} = $pwd = undef; } else { # PWD was invalid - delete $self->{env}->{PWD}; - undef $pwd; + $self->{env}->{PWD} = $pwd = undef; } } - $pwd //= $self->{env}->{PWD} = getcwd() // die "getcwd(PWD=$pwd): $!"; + $pwd //= $self->{env}->{PWD} = $cwd // getcwd() // die "getcwd: $!"; File::Spec->rel2abs($p, $pwd); } -sub store_path ($) { +# abs_path resolves symlinks in parent iff all parents exist +sub abs_path { Cwd::abs_path($_[1]) // rel2abs(@_) } + +sub share_path ($) { # $HOME/.local/share/lei/$FOO my ($self) = @_; rel2abs($self, ($self->{env}->{XDG_DATA_HOME} // ($self->{env}->{HOME} // '/nonexistent').'/.local/share') - .'/lei/store'); + .'/lei'); } +sub store_path ($) { share_path($_[0]) . '/store' } + sub _config_path ($) { my ($self) = @_; rel2abs($self, ($self->{env}->{XDG_CONFIG_HOME} // @@ -135,8 +145,9 @@ our %CMD = ( # sorted in order of importance/use: sort|s=s reverse|r offset=i pretty jobs|j=s globoff|g augment|a import-before! lock=s@ rsyncable alert=s@ mua=s verbose|v+), @c_opt, opt_dash('limit|n=i', '[0-9]+') ], -'up' => [ 'SEARCH_TERMS...', 'update saved search', - qw(jobs|j=s lock=s@ alert=s@ mua=s verbose|v+), @c_opt ], + +'up' => [ 'OUTPUT|--all', 'update saved search', + qw(jobs|j=s lock=s@ alert=s@ mua=s verbose|v+ all:s), @c_opt ], 'blob' => [ 'OID', 'show a git blob, reconstructing from mail if necessary', qw(git-dir=s@ cwd! verbose|v+ mail! oid-a|A=s path-a|a=s path-b|b=s), @@ -154,10 +165,12 @@ our %CMD = ( # sorted in order of importance/use: 'exclude further results from a publicinbox|extindex', qw(prune), @c_opt ], -'ls-query' => [ '[FILTER...]', 'list saved search queries', - qw(name-only format|f=s), @c_opt ], -'rm-query' => [ 'QUERY_NAME', 'remove a saved search', @c_opt ], -'mv-query' => [ qw(OLD_NAME NEW_NAME), 'rename a saved search', @c_opt ], +'ls-search' => [ '[PREFIX]', 'list saved search queries', + qw(format|f=s pretty l ascii z|0), @c_opt ], +'forget-search' => [ 'OUTPUT', 'forget a saved search', + qw(verbose|v+), @c_opt ], +'edit-search' => [ 'OUTPUT', "edit saved search via `git config --edit'", + @c_opt ], 'plonk' => [ '--threads|--from=IDENT', 'exclude mail matching From: or threads from non-Message-ID searches', @@ -175,7 +188,7 @@ our %CMD = ( # sorted in order of importance/use: qw(exact! all jobs:i indexed), @c_opt ], 'add-watch' => [ 'LOCATION', 'watch for new messages and flag changes', - qw(import! kw|keywords|flags! interval=s recursive|r + qw(import! kw! interval=s recursive|r exclude=s include=s), @c_opt ], 'ls-watch' => [ '[FILTER...]', 'list active watches with numbers and status', qw(format|f=s z), @c_opt ], @@ -187,11 +200,11 @@ our %CMD = ( # sorted in order of importance/use: 'import' => [ 'LOCATION...|--stdin', 'one-time import/update from URL or filesystem', qw(stdin| offset=i recursive|r exclude=s include|I=s - lock=s@ in-format|F=s kw|keywords|flags! verbose|v+), @c_opt ], + lock=s@ in-format|F=s kw! verbose|v+ incremental! sync!), @c_opt ], 'convert' => [ 'LOCATION...|--stdin', 'one-time conversion from URL or filesystem to another format', qw(stdin| in-format|F=s out-format|f=s output|mfolder|o=s - lock=s@ kw|keywords|flags!), @c_opt ], + lock=s@ kw!), @c_opt ], 'p2q' => [ 'FILE|COMMIT_OID|--stdin', "use a patch to generate a query for `lei q --stdin'", qw(stdin| want|w=s@ uri debug), @c_opt ], @@ -199,6 +212,9 @@ our %CMD = ( # sorted in order of importance/use: 'git-config(1) wrapper for '._config_path($_[0]); }, qw(config-file|system|global|file|f=s), # for conflict detection qw(c=s@ C=s@), pass_through('git config') ], +'inspect' => [ 'ITEMS...', 'inspect lei/store and/or local external', + qw(pretty ascii dir=s), @c_opt ], + 'init' => [ '[DIRNAME]', sub { "initialize storage, default: ".store_path($_[0]); }, @c_opt ], @@ -238,6 +254,7 @@ my %OPTDESC = ( 'lock=s@' => [ 'METHOD|dotlock|fcntl|flock|none', 'mbox(5) locking method(s) to use (default: fcntl,dotlock)' ], +'incremental! import' => 'import already seen IMAP and NNTP articles', 'globoff|g' => "do not match locations using '*?' wildcards ". "and\xa0'[]'\x{a0}ranges", 'verbose|v+' => 'be more verbose', @@ -309,7 +326,9 @@ my %OPTDESC = ( 'jobs|j=i add-external' => 'set parallelism when indexing after --mirror', 'in-format|F=s' => $stdin_formats, -'format|f=s ls-query' => $ls_format, +'format|f=s ls-search' => ['OUT|json|jsonl|concatjson', + 'listing output format' ], +'l ls-search' => 'long listing format', 'format|f=s ls-external' => $ls_format, 'limit|n=i@' => ['NUM', 'limit on number of matches (default: 10000)' ], @@ -326,6 +345,8 @@ my %OPTDESC = ( 'remote' => 'limit operations to those requiring network access', 'remote!' => 'prevent operations requiring network access', +'all:s up' => ['local', 'update all (local) saved searches' ], + 'mid=s' => 'specify the Message-ID of a message', 'oid=s' => 'specify the git object ID of a message', @@ -338,7 +359,7 @@ my %OPTDESC = ( 'by-mid|mid:s' => [ 'MID', 'match only by Message-ID, ignoring contents' ], -'kw|keywords|flags!' => 'disable/enable importing flags', +'kw!' => 'disable/enable importing keywords (aka "flags")', # xargs, env, use "-0", git(1) uses "-z". We support z|0 everywhere 'z|0' => 'use NUL \\0 instead of newline (CR) to delimit lines', @@ -350,7 +371,7 @@ my %CONFIG_KEYS = ( 'leistore.dir' => 'top-level storage location', ); -my @WQ_KEYS = qw(lxs l2m imp mrr cnv p2q tag sol); # internal workers +my @WQ_KEYS = qw(lxs l2m imp mrr cnv p2q tag sol lsss); # internal workers sub _drop_wq { my ($self) = @_; @@ -695,8 +716,7 @@ sub _lei_cfg ($;$) { $cur_st = pack('dd', $st[10], $st[7]); qerr($self, "# $f created") if $self->{cmd} ne 'config'; } - my $cfg = PublicInbox::Config::git_config_dump($f); - bless $cfg, 'PublicInbox::Config'; + my $cfg = PublicInbox::Config->git_config_dump($f); $cfg->{-st} = $cur_st; $cfg->{'-f'} = $f; if ($sto && File::Spec->canonpath($sto_dir // store_path($self)) @@ -837,11 +857,17 @@ sub start_mua { @cmd = map { $_ eq '%f' ? ($replaced = $mfolder) : $_ } @cmd; } push @cmd, $mfolder unless defined($replaced); - if (my $sock = $self->{sock}) { # lei(1) client process runs it - send($sock, exec_buf(\@cmd, {}), MSG_EOR); + if ($self->{sock}) { # lei(1) client process runs it + # restore terminal: echo $query | lei q -stdin --mua=... + my $io = []; + $io->[0] = $self->{1} if $self->{opt}->{stdin} && -t $self->{1}; + send_exec_cmd($self, $io, \@cmd, {}); } elsif ($self->{oneshot}) { my $pid = fork // die "fork: $!"; if ($pid > 0) { # original process + if ($self->{opt}->{stdin} && -t STDOUT) { + open STDIN, '+<&', \*STDOUT or die "dup2: $!"; + } exec(@cmd); warn "exec @cmd: $!\n"; POSIX::_exit(1); @@ -1196,4 +1222,11 @@ sub DESTROY { $? = $err if $err; # preserve ->fail or ->x_it code } +sub wq_done_wait { # dwaitpid callback + my ($arg, $pid) = @_; + my ($wq, $lei, $e) = @$arg; + $? and $lei->child_error($?, $e ? "$e errors during $lei->{cmd}" : ()); + $lei->dclose; +} + 1;