--- toast	2005/10/13 04:43:43	1.421
+++ toast	2005/10/22 06:46:29	1.422
@@ -33,7 +33,7 @@
 {
   { my($ok); { local $SIG{'__WARN__'} = sub { die };
       $ok = !eval('1 + "a"') } $ok || warn("use warnings"); }
-  { eval('$foo = 1') && warn("use strict") }
+  { eval('$foo = 1') && warn("use strict"); $@ = undef }
 }
 
 ##############################################################################
@@ -91,8 +91,9 @@
 sub true() { 1 }
 sub false() { "" }
 
-sub emptytoundef(@) { map { defined($_) && $_ eq "" ? undef : $_ } @_; }
-sub undeftoempty(@) { map { defined($_) ? $_ : "" } @_; }
+sub scalify(@) { return @_ if wantarray; error if scalar(@_) > 1; $_[0] }
+sub emptytoundef(@) { scalify(map(defined($_) && $_ eq "" ? undef : $_, @_)) }
+sub undeftoempty(@) { scalify(map(defined($_) ? $_ : "", @_)) }
 sub firstdef(@) { return $_ foreach grep(defined($_), @_); undef; }
 
 sub samelist(\@\@)
@@ -263,6 +264,7 @@
     "postarmprog" => superuser ? "/sbin/ldconfig" : "",
     "editprog" => "",
     "defaultcmd" => "help",
+    "stickyopts" => "reconfigure confappend makeappend",
     "findsites" => "all",
     "httpproxy" => exists($ENV{http_proxy}) ? $ENV{http_proxy} : "",
     "ftpproxy" => exists($ENV{ftp_proxy}) ? $ENV{ftp_proxy} : "",
@@ -292,6 +294,7 @@
     "stoponerror" => "true",
     "ignorecase" => "true",
     "showurls" => "true",
+    "showopts" => "true",
     "infodir" => "true",
     "xmlcatalog" => "true",
     "hspkg" => "false",
@@ -339,45 +342,91 @@
     isopt($name) || error("no such option: $name");
   }
 
-  sub dotfile()
+  my(%cmdlineopt);
+
+  sub unloadopts(;$;$)
   {
+    my($n, $v) = @_;
+    my($key) = undeftoempty($n) . "/" . undeftoempty($v);
+    delete $optloaded{$key};
+  }
+
+  sub loadopts(;$;$)
+  {
+    my($n, $v) = @_;
+    my($key) = undeftoempty($n) . "/" . undeftoempty($v);
+    return $optloaded{$key} if exists($optloaded{$key});
+    my($opts) = $optloaded{$key} = {};
     my(@list);
     push(@list, laxpath($ENV{HOME}, qw[.toast conf]))
         if exists($ENV{HOME}) && length($ENV{HOME});
     push(@list, qw[/toast/conf /etc/toast.conf /usr/local/etc/toast.conf]);
-    -e && return $_ for(@list);
-    return undef;
-  }
-
-  sub loadopt($)
-  {
-    my($name) = @_;
-    error unless isopt($name);
-    return $optloaded{$name} if %optloaded;
-    $optloaded{1} = 1;
-    my($dotfile) = dotfile;
-    return unless defined($dotfile);
-    whilefile
+    @list = path(pkgpath($n, $v), "conf") if defined($n);
+    for(@list)
     {
-      s/^\s+//;
-      s/\s+$//;
-      return true if $_ eq "" || /^\#/;
-      /^([^\=]*?)\s*\=\s*(.*)$/ ||
-          error("$dotfile: line $.: missing \"=\"");
-      my($name, $val) = ($1, $2);
-      isopt($name) ||
-          error("$dotfile: line $.: unknown option name \"$name\"");
-      !isboolopt($name) || isboolean($val) ||
-          error("$dotfile: line $.: illegal boolean value: \"$val\"");
-      $optloaded{$name} = $val;
-      true;
-    } $dotfile;
-    $optloaded{$name};
-  }
+      my($dotfile) = $_;
+      if(-e($dotfile))
+      {
+        whilefile
+        {
+          s/^\s+//;
+          s/\s+$//;
+          return true if $_ eq "" || /^\#/;
+          /^([^\=]*?)\s*\=\s*(.*)$/ ||
+              error("$dotfile: line $.: missing \"=\"");
+          my($name, $val) = ($1, $2);
+          isopt($name) ||
+              error("$dotfile: line $.: unknown option name \"$name\"");
+          !isboolopt($name) || isboolean($val) ||
+              error("$dotfile: line $.: illegal boolean value: \"$val\"");
+          $opts->{$name} = $val;
+          true;
+        } $dotfile;
+        last unless defined($n);
+      }
 
-  my(%optcurrent);
+      next unless defined($n);
+      next if (defined($v) ? 1 : 0) eq (&crossversion ? 1 : 0);
 
-  sub setopt($$)
+      my($changed);
+      for(map($_ ne "all" ? $_ : grep($_ ne "stickyopts", keys(%cmdlineopt)),
+          map(lc, split(/\W+/, getopt("stickyopts")))))
+      {
+        my($new) = $cmdlineopt{$_};
+        next unless defined($new);
+        my($old) = $opts->{$_};
+        if($new eq getglobalopt($_))
+        {
+          next unless defined($old);
+          delete($opts->{$_});
+          $changed = true;
+        }
+        elsif(!defined($old) || $new ne $old)
+        {
+          $new =~ s/\n/ /g;
+          $opts->{$_} = $new;
+          $changed = true;
+        }
+      }
+      if($changed)
+      {
+        my(@lines);
+        for(sort(keys(%{$opts})))
+        {
+          my($value) = $opts->{$_};
+          next unless defined($value);
+          push(@lines,
+              "$_=" . (!isboolopt($_) ? $value : $value ? "1" : "0") . "\n");
+        }
+        writefile($dotfile, @lines) if @lines;
+        rm($dotfile) unless @lines;
+      }
+
+    }
+    return $opts;
+  }
+
+  sub parseopt($$)
   {
     my($name, $val) = @_;
     checkoptname($name);
@@ -396,20 +445,69 @@
         $val = path(&storedir, $val);
       }
     }
-    $optcurrent{$name} = $val;
+    return $val;
   }
 
+  sub setopt($$)
+  {
+    my($name, $val) = @_;
+    $cmdlineopt{$name} = $val;
+  }
+
+  sub getglobalopt($)
+  {
+    my($name) = @_;
+    error unless isopt($name);
+    return firstdef(envopt($name), loadopts()->{$name}, $optdefault{$name});
+  }
+
+  my($an, $av);
+
+  sub setactivepkg(;$;$)
+  {
+    unloadopts($an, $av) if defined($an);
+    ($an, $av) = @_;
+    unloadopts($an, $av) if defined($an);
+    loadopts($an, $av) if defined($an);
+  }
+
   sub getopt($)
   {
     my($name) = @_;
-    setopt($name, firstdef(envopt($name), loadopt($name), $optdefault{$name}))
-        unless exists($optcurrent{$name});
-    return $optcurrent{$name};
+    error unless isopt($name);
+    my($nvopts, $nopts);
+    $nvopts = loadopts($an, $av) if defined($av);
+    $nopts = loadopts($an) if defined($an);
+    return parseopt($name, firstdef($cmdlineopt{$name},
+        $nvopts->{$name}, $nopts->{$name}, getglobalopt($name)));
   }
 
   checkedeval("sub $_() { getopt('$_') }") foreach keys(%optdefault);
 }
 
+sub cmd_getopt(@)
+{
+  my($name) = shift;
+  error("option name required") unless defined($name);
+  error("invalid option name: $name") unless isopt($name);
+  my(@pkgs) = parse(@_);
+  error("only one package allowed") if scalar(@pkgs) > 1;
+  setactivepkg();
+  for(@pkgs)
+  {
+    my($n, $v) = @$_;
+    setactivepkg($n, $v);
+  }
+  my($result);
+  eval { $result = getopt($name) };
+  my($err) = $@;
+  setactivepkg();
+  die($err) if $err;
+  $result = ($result ? "true" : "false") if isboolopt($name);
+  error unless defined($result);
+  print("$result\n");
+}
+
 ##############################################################################
 
 sub pkgdir() { "pkg" }
@@ -2364,6 +2462,8 @@
   $version = "unknown" unless $goodver;
   my($verdir) = pkgpath($name, $version);
 
+  setactivepkg($name, $version);
+
   if($goodver)
   {
     if(!-d($verdir))
@@ -2409,6 +2509,7 @@
 
   setpkgurls($name, $version, @urls);
 
+  setactivepkg($name, $version);
   ($name, $version, @urls);
 }
 
@@ -4668,6 +4769,22 @@
 
 ##############################################################################
 
+sub printopts($;$)
+{
+  my($name, $version) = @_;
+  return true unless showopts;
+  my($indent) = defined($version) ? "  " : "";
+  my($opts) = loadopts($name, $version);
+  return unless %{$opts};
+  print("$indent  options:\n");
+  for(sort(keys(%{$opts})))
+  {
+    my($value) = $opts->{$_};
+    print("$indent    --" .  (!isboolopt($_) ? "$_=" . quote($value) :
+        $value ? "$_" : "no$_") . "\n");
+  }
+}
+
 sub status(@)
 {
   my($name, $version, $build, @urls) = @_;
@@ -4678,6 +4795,7 @@
   for $name (allnames($name))
   {
     print("$name\n");
+    printopts($name);
 
     for $version (allversions($name, $version))
     {
@@ -4697,6 +4815,8 @@
         }
       }
 
+      printopts($name, $version);
+
       for $build (allbuilds($name, $version, $build))
       {
         my($status, @notes, @armedin);
@@ -5649,6 +5769,7 @@
 sub cmd(@)
 {
   my($cmd, @args) = @_;
+  setactivepkg();
   return badcmd unless defined($cmd);
   $cmd = lc($cmd);
   my($cmdsub) = getsub("cmd_$cmd");
@@ -5660,10 +5781,14 @@
   my($pid) = $$;
   for(&$parser(@args))
   {
+    my($name, $version) = @$_;
+    setactivepkg($name, $version);
     $result = false unless eval { &$doer(@$_) };
-    if($@)
+    my($err) = $@;
+    setactivepkg();
+    if($err)
     {
-      die($@) if $$ != $pid;
+      die($err) if $$ != $pid;
       print STDERR "$@\n";
       return false if stoponerror;
     }
@@ -6755,6 +6880,31 @@
 equivalent to running B<toast help>, regardless of this option's setting.
 Default: C<help>.
 
+=item B<--stickyopts=>I<OPTNAMES>
+
+Sets the list of per-package command-line options implicitly stored
+in B<storedir> for automatic re-use by later invocations of B<toast>.
+I<OPTNAMES> is either a whitespace- and/or punctuation-separated list
+of option names.  The word C<all> may also appear on the list; it
+stands for the names of all options except for the B<stickyopts> option
+itself.  The empty list is allowed.  Case is not significant.  Note that
+B<stickyopts> only determines which options are stored (or removed from
+storage); it can not be used to prevent previously stored options from
+being implicitly loaded and re-used.  B<toast> will implicitly store
+an option listed in I<OPTNAMES> only if it is specifically mentioned
+on the command line; options read from the environment or from files on
+disk are never implicitly stored.  If the value explicitly given on the
+command line is the same value that would otherwise have been used if
+no command-line options had been given and no per-package options were
+stored, and that option is already stored, the option is removed from
+storage instead of being stored.  If B<crossversion> is enabled, this
+option writes to a file that applies equally to all same-name packages,
+regardless of version number; otherwise, this option writes to a different
+file that applies only to a single package name and version number taken
+together.  Values stored in the more specific file (name and version)
+always override values stored in the less-specific file (name only).
+Default: C<reconfigure confappend makeappend>.
+
 =item B<--findsites=>I<SITELIST>
 
 Set the list of web sites or other locations searched by B<toast
@@ -6792,11 +6942,13 @@
 Specifies additional arguments for B<toast build> to pass to a package's
 C<configure> script or equivalent, if any.  This should never be necessary
 in order to build a package correctly (if it is, send me a bug report!),
-but it can be awfully handy at times.  The I<ARGS> string is treated as
-a list of space-separated words, each of which may be optionally quoted
-with single or double quotes with backslash acting as an escape character;
-the resulting words are added to the end of the command line.  Default:
-empty string.
+but it can be awfully handy at times.  The I<ARGS> string is treated
+as a list of space-separated words, each of which may be optionally
+quoted with single or double quotes with backslash acting as an escape
+character; the resulting words are added to the end of the command line.
+Note that by default, this option's value may be saved with the package
+and reused by future invocations of B<toast>; see the B<stickyopts>
+option for details.  Default: empty string.
 
 =item B<--makeappend=>I<ARGS>
 
@@ -6998,7 +7150,9 @@
 may be undesirable for other reasons.  If B<reconfigure> is disabled,
 B<toast build> does not try to do anything beyond the minimum steps
 required to correctly build and install whatever files the package builds
-and installs by default.  Default: enabled.
+and installs by default.  Note that by default, this option's value may
+be saved with the package and reused by future invocations of B<toast>;
+see the B<stickyopts> option for details.  Default: enabled.
 
 =item S<B<--fixliblinks> | B<--nofixliblinks>>
 
@@ -7051,12 +7205,19 @@
 
 =item S<B<--showurls> | B<--noshowurls>>
 
-When B<showurls> is enabled, B<toast show> always displays the stored
+When B<showurls> is enabled, B<toast status> always displays the stored
 URLs associated with each displayed package.  If B<showurls> is disabled,
-B<toast show> only displays a package's URLs if a different list of URLs
+B<toast status> only displays a package's URLs if a different list of URLs
 for that package was given explicitly on the command line.  Default:
 enabled.
 
+=item S<B<--showopts> | B<--noshowopts>>
+
+When B<showopts> is enabled, B<toast status> always displays any stored
+options associated with each displayed package.  If B<showopts> is
+disabled, B<toast status> never displays this information.  Default:
+enabled.
+
 =item S<B<--infodir> | B<--noinfodir>>
 
 When B<infodir> is enabled, B<toast arm> and B<toast disarm> will create
@@ -7159,15 +7320,25 @@
 
 =item 2.
 
+Package-specific configuration files.  If they exist at all, these
+files are normally created and updated by B<toast>.  Package-specific
+configuration files can live in two different places: the first applies
+to a specific package with a given name and version number, the second
+applies to all packages with the given name.  If both files exist, both
+are checked in that order, and the first one to supply a value for the
+option in question wins.  See the B<stickyopts> option for details.
+
+=item 3.
+
 The environment.  If option I<NAME> is not given a value on the command
 line, will be read from the environment variable B<TOAST_>I<NAME> (all
 uppercase) if it exists.  Note that environment variables whose names
 contain lowercase letters will be silently ignored!  In the case of a
 boolean option, one of the explicit values listed in item 1 must be given.
 
-=item 3.
+=item 4.
 
-The configuration file.  If option I<NAME> has not been assigned a value
+The main configuration file.  If option I<NAME> has not been assigned a value
 through any of the above methods, its value will be taken from a line of
 the form I<NAME>B<=>I<VALUE> in the configuration file, if such a line
 exists.  I<NAME> is case-insensitive in this context.  Any whitespace
@@ -7183,7 +7354,7 @@
 1 for allowed forms), B<toast> will normally give an error message at
 startup and refuse to execute any commands.
 
-=item 4.
+=item 5.
 
 The built-in default value.  See the full list of options elsewhere in
 this document for the specific default value used for each option.
@@ -7223,6 +7394,8 @@
 
 Known bugs:
 
+  - badness likely if storedir/armdir contains .../{dev,proc,tmp}/...
+  - "toast arm udev" causes assertion failure
   - toast add misguesses device-mapper name/version from URL
   - toast add misguesses [ foo-1.0-src.tar.gz foo-1.0-src.diff.gz ]
   - autofind hangs in httphead() when going through tinyproxy?
@@ -7267,6 +7440,7 @@
   - allow package name/version as URL; expand w/ autofind iff missing
   - optionally have toast add imply change, then make get+build smarter
   - toast status/env should allow multiple read-only storedirs/armdirs
+  - have toast status show/sort by size/timestamp
   - fold archives by URL and/or hash?
   - zsh completions!