+ _fill($self, $pfx);
+}
+
+sub lookup_name ($$) {
+ my ($self, $name) = @_;
+ $self->{-by_name}->{$name} || _fill($self, "publicinbox.$name");
+}
+
+sub each_inbox {
+ my ($self, $cb) = @_;
+ my %seen;
+ foreach my $k (keys %$self) {
+ $k =~ /\Apublicinbox\.([A-Z0-9a-z-]+)\.mainrepo\z/ or next;
+ next if $seen{$1};
+ $seen{$1} = 1;
+ my $ibx = lookup_name($self, $1) or next;
+ $cb->($ibx);
+ }
+}
+
+sub lookup_newsgroup {
+ my ($self, $ng) = @_;
+ $ng = lc($ng);
+ my $rv = $self->{-by_newsgroup}->{$ng};
+ return $rv if $rv;
+
+ foreach my $k (keys %$self) {
+ $k =~ /\A(publicinbox\.[\w-]+)\.newsgroup\z/ or next;
+ my $v = $self->{$k};
+ my $pfx = $1;
+ if ($v eq $ng) {
+ $rv = _fill($self, $pfx);
+ return $rv;
+ }
+ }
+ undef;
+}
+
+sub limiter {
+ my ($self, $name) = @_;
+ $self->{-limiters}->{$name} ||= do {
+ require PublicInbox::Qspawn;
+ my $max = $self->{"publicinboxlimiter.$name.max"};
+ PublicInbox::Qspawn::Limiter->new($max);
+ };
+}
+
+sub config_dir { $ENV{PI_DIR} || "$ENV{HOME}/.public-inbox" }
+
+sub default_file {
+ my $f = $ENV{PI_CONFIG};
+ return $f if defined $f;
+ config_dir() . '/config';
+}
+
+sub git_config_dump {
+ my ($file) = @_;
+ my ($in, $out);
+ my @cmd = (qw/git config/, "--file=$file", '-l');
+ my $cmd = join(' ', @cmd);
+ my $fh = popen_rd(\@cmd) or die "popen_rd failed for $file: $!\n";
+ my %rv;
+ local $/ = "\n";
+ while (defined(my $line = <$fh>)) {
+ chomp $line;
+ my ($k, $v) = split(/=/, $line, 2);
+ my $cur = $rv{$k};
+
+ if (defined $cur) {
+ if (ref($cur) eq "ARRAY") {
+ push @$cur, $v;
+ } else {
+ $rv{$k} = [ $cur, $v ];
+ }
+ } else {
+ $rv{$k} = $v;
+ }
+ }
+ close $fh or die "failed to close ($cmd) pipe: $?";