--- toast	2004/09/05 01:54:59	1.341
+++ toast	2004/09/05 22:03:00	1.342
@@ -132,6 +132,19 @@
   $result;
 }
 
+sub uniq(@)
+{
+  my(@list) = @_;
+  my(@result, %seen);
+  for(@list)
+  {
+    next if $seen{$_};
+    $seen{$_} = true;
+    push(@result, $_);
+  }
+  return @result;
+}
+
 ##############################################################################
 
 sub dirname($) { my($arg) = @_; $arg =~ s|/[^/]*$|| ? $arg : "."; }
@@ -251,6 +264,7 @@
     "httpproxy" => exists($ENV{http_proxy}) ? $ENV{http_proxy} : "",
     "ftpproxy" => exists($ENV{ftp_proxy}) ? $ENV{ftp_proxy} : "",
     "quiet" => "false",
+    "expand" => "true",
     "autofind" => "true",
     "autochange" => "true",
     "autorename" => "true",
@@ -1019,7 +1033,7 @@
   {
     push(@links, linksfromstring($_, $url));
   }
-  return @links;
+  return uniq(@links);
 }
 
 sub linksfromurl($)
@@ -2065,11 +2079,11 @@
 sub lookslikepkgurl($;$;$)
 {
   my($url, $name, $version) = @_;
-  return false unless $url =~ m!^(http|ftp)://!;
+  return false unless $url =~ m!^(http|ftp)://.*/[^/]+\.[a-z][^/]+$!i;
   return false if $url =~ m/\#/;
   my($noquery) = stripquery($url);
   return false unless $noquery =~ m!\.\w+$!;
-  return false if $noquery =~ m!\.(html?|php)$!i;
+  return false if $noquery =~ m!\.(html?|php|txt|gif|jpg|png|css)$!i;
   return true unless defined($name);
   my($basename) = basename($noquery);
   return false unless $basename =~ /\Q$name\E/i;
@@ -4674,10 +4688,11 @@
 
 sub parse(@)
 {
+  my(@args) = @_;
   my(@result) = ();
-  while(@_)
+  while(@args)
   {
-    local($_) = shift;
+    local($_) = shift(@args);
     my($name, $version, $build, @urls, $nourls, $multi, $split);
 
     if($_ ne "[" && m!^([^:/\.]*)(/([^:/]+)(/([1-9]\d*))?)?(:?)$!)
@@ -4689,8 +4704,8 @@
       {
         defined($oversion) || error("missing version number: \"$_\"");
         defined($build) && error("unexpected build number: \"$_\"");
-        @_ || error("expected file or URL after \"$_\"");
-        $_ = shift;
+        @args || error("expected file or URL after \"$_\"");
+        $_ = shift(@args);
       }
       elsif(defined($oversion) && !defined($version))
       {
@@ -4711,8 +4726,8 @@
         $multi = true unless s/\]$//;
         if($_ eq "")
         {
-          ($multi && @_) || error("expected file after \"[\"");
-          $_ = shift;
+          ($multi && @args) || error("expected file after \"[\"");
+          $_ = shift(@args);
         }
       }
 
@@ -4728,6 +4743,48 @@
         if(/^\w+:/)
         {
           $url = cleanurl($_);
+          if(expand && $url =~ m!/$!)
+          {
+            my(@expansion) = grep(lookslikepkgurl($_, $name, $version),
+                linksfromurl($url));
+
+            # filter out older versions of duplicated packages
+            my(%bestver);
+            for(@expansion)
+            {
+              my($n, $v) = guessnv($_);
+              $bestver{$n} = $v if defined($n) && defined($v) &&
+                  (!defined($bestver{$n}) ||
+                  (sort cmpab ($bestver{$n}, $v))[1] eq $v);
+            }
+            @expansion = grep
+            {
+              my($n, $v) = guessnv($_);
+              defined($n) && defined($v) && $bestver{$n} eq $v;
+            } @expansion;
+
+            # filter out foo.tar.gz if foo.tar.bz2 is also present
+            my(%expansion);
+            $expansion{$_} = true for @expansion;
+            @expansion = grep
+            {
+              my($xform) = $_;
+              !($xform =~ s/\.tar\.gz$/.tar.bz2/ && $expansion{$xform});
+            } @expansion;
+
+            error("unable to expand $url") unless @expansion;
+
+            $url = shift(@expansion);
+            if(@expansion)
+            {
+              if(defined($name) && !$multi)
+              {
+                $multi = true;
+                push(@expansion, "]");
+              }
+              unshift(@args, @expansion);
+            }
+          }
         }
         else
         {
@@ -4738,8 +4795,8 @@
 
         if($multi)
         {
-          @_ || error("expected \"]\" after \"$_\"");
-          $_ = shift;
+          @args || error("expected \"]\" after \"$_\"");
+          $_ = shift(@args);
         }
       }
 
@@ -4755,12 +4812,15 @@
 
 sub cmd_parse(@)
 {
-  print("parsed ", scalar(@_), " argument(s)\n");
+  my(@args) = @_;
+  my(@pkgs) = parse(@args);
+  print("parsed ", scalar(@args), " word(s) into ",
+      scalar(@pkgs), " package(s)\n");
   my($index) = 0;
-  for(parse(@_))
+  for(@pkgs)
   {
     my($name, $version, $build, @urls) = @$_;
-    print("argument ", ++$index, ":\n");
+    print("package ", ++$index, ":\n");
     print(defined($name) ? "  name: $name\n" : "  no name\n");
     print(defined($version) ? "  version: $version\n" : "  no version\n");
     print(defined($build) ? "  build: $build\n" : "  no build\n");
@@ -6088,6 +6148,24 @@
 When B<quiet> is enabled, most commands will produce output only on
 failure.  Some commands, such as B<toast status>, are not affected by
 this flag.  Default: disabled.
+
+=item S<B<--expand> | B<--noexpand>>
+
+When B<expand> is enabled, any URL ending in a slash encountered
+while parsing the command line that will be treated as a pointer to a
+directory listing whose contents are to be fetched, parsed, filtered, and
+interpolated into the command line.  The filtering heuristic scans for
+links that seem likely to be package URLs, without actually retrieving
+anything other than the single directory URL given.  If a package name
+and/or version number are given on the command line, those are taken
+into account by the filter.  Use square brackets on the command line or
+specify an explicit name or version number to force all of the resulting
+URLs to be grouped into a single package, or omit brackets and name to
+let B<toast> decide for itself whether and how to group the new URLs
+into packages.  If B<expand> is disabled, URLs ending in slashes will not
+receive any special treatment, and B<toast> will never attempt to fetch
+anything from the network while it is still parsing the command line.
+Default: enabled.
 
 =item S<B<--autofind> | B<--noautofind>>