<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>bin/atom-grep</filename>
    </added>
    <added>
      <filename>bin/atom-post</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -1,4 +1,4 @@
-#!/usr/bin/ruby
+#!/usr/bin/env ruby
 
 =begin
 Usage: atom-cp [options] source destination
@@ -12,7 +12,7 @@ require 'atom/tools'
 include Atom::Tools
 
 def parse_options
-  options = { :complete =&gt; true }
+  options = { }
 
   opts = OptionParser.new do |opts|
     opts.banner = &lt;&lt;END
@@ -24,19 +24,20 @@ Usage: #{$0} [options] source destination
 
 END
 
-    opts.on('-c', '--no-complete', &quot;Don't follow previous and next links in the source feed&quot;) do
-     options[:complete] = false
+    opts.on('-c', '--complete', &quot;follow previous and next links in the source feed to obtain the entire logical feed&quot;) do
+     options[:complete] = true
     end
-    opts.on('-s', '--infer-slugs', 'Try to infer entry slugs') { options[:infer_slugs] = true }
 
-    # XXX fetch each entry
+    opts.on('-s', '--infer-slugs', 'try to infer entry slugs') { options[:infer_slugs] = true }
+
+    opts.on('-v', '--verbose') { options[:verbose] = true }
 
     atom_options opts, options
   end
 
   opts.parse!(ARGV)
 
-  if ARGV.length &lt; 2
+  if ARGV.length != 2
     puts opts
     exit
   end
@@ -44,37 +45,115 @@ END
   options
 end
 
-def infer_slug entry
-  slug = nil
-  alt = e.links.find { |l| l['rel'] == 'alternate' }
+# like dir_to_entries, but returns an Array of [slug, entry] pairs
+def dir_to_entries_with_slug path
+  raise ArgumentError, &quot;#{path} is not a directory&quot; unless File.directory? path
 
-  alt and alt['href'].split('/').last
+  Dir[path+'/*.atom'].map do |e|
+    slug = e.match(/.*\/(.*)\.atom/)[1]
+    slug = nil if slug and slug.match /^0x/
+
+    entry = Atom::Entry.parse(File.read(e))
+
+    [slug, entry]
+  end
 end
 
-if __FILE__ == $0
-  require 'optparse'
+# like entries_to_http, but takes an Array of [slug, entry] pairs
+def entries_to_http_with_slug entries, url, http = Atom::HTTP.new
+  coll = Atom::Collection.new url, http
 
-  options = parse_options
+  entries.each do |slug, entry|
+    coll.post! entry, slug
+  end
+end
 
-  source = ARGV[0]
-  dest = ARGV[1]
+# like entries_to_dir, but takes an Array of [slug, entry] pairs
+def entries_to_dir_with_slug entries, path
+  if File.exists? path
+    raise &quot;directory #{path} already exists&quot;
+  else
+    Dir.mkdir path
+  end
+
+  entries.each do |slug,entry|
+    e = entry.to_s
+
+    new_filename = if slug
+                     path + '/' + slug + '.atom'
+                   else
+                     path + '/0x' + MD5.new(e).hexdigest[0,8] + '.atom'
+                   end
 
-  feed = parse_input source, options
+    File.open(new_filename, 'w') { |f| f.write e }
+  end
+end
+
+def parse_input_with_slug source, options
+  entries = if source.match /^http/
+    http = Atom::HTTP.new
+
+    setup_http http, options
+
+    http_to_entries(source, options[:complete], http).map do |e|
+      [nil, e]
+    end
+  elsif source == '-'
+    stdin_to_entries.map do |e|
+      [nil, e]
+    end
+  else
+    dir_to_entries_with_slug source
+  end
 
   if options[:verbose]
-    feed.each do |slug,entry|
+    entries.each do |slug,entry|
       print &quot;got #{entry.title}  &quot;
-      puts &quot;(/#{slug})&quot;
+      puts (slug ? &quot;(/#{slug})&quot; : '')
     end
   end
 
   if options[:infer_slugs]
-    feed.map! do |slug,entry|
+    entries.map! do |slug,entry|
       slug ||= infer_slug entry
 
       [slug, entry]
     end
   end
 
-  write_output feed, dest, options
+  entries
+end
+
+def write_output_with_slug entries, dest, options
+  if dest.match /^http/
+    http = Atom::HTTP.new
+
+    setup_http http, options
+
+    entries_to_http_with_slug entries, dest, http
+  elsif dest == '-'
+    entries_to_stdout entries.map { |s,e| e }
+  else
+    entries_to_dir_with_slug entries, dest
+  end
+end
+
+# make up a slug based on the alternate link
+def infer_slug entry
+  slug = nil
+  alt = e.links.find { |l| l['rel'] == 'alternate' }
+
+  alt and alt['href'].split('/').last
+end
+
+if __FILE__ == $0
+  require 'optparse'
+
+  options = parse_options
+
+  source = ARGV[0]
+  dest = ARGV[1]
+
+  entries = parse_input_with_slug source, options
+  write_output_with_slug entries, dest, options
 end</diff>
      <filename>bin/atom-cp</filename>
    </modified>
    <modified>
      <diff>@@ -1,10 +1,10 @@
-#!/usr/bin/ruby
+#!/usr/bin/env ruby
 
 =begin
 Usage: atom-purge [options] collection
     delete all the entries in an Atom Collection
 
-    'collection' and be a path on the local filesystem, the
+    'collection' can be a path on the local filesystem, the
   URL of an Atom Collection or '-' for stdin. the feed is parsed
   and every Member URI found in it is DELETEd.
 =end
@@ -13,29 +13,33 @@ require 'atom/tools'
 include Atom::Tools
 
 def parse_options
-  options = { :complete =&gt; true }
+  options = {}
 
   opts = OptionParser.new do |opts|
     opts.banner = &lt;&lt;END
 Usage: #{$0} [options] collection
     delete all the entries in an Atom Collection
 
-    'collection' and be a path on the local filesystem, the
+    'collection' can be a path on the local filesystem, the
   URL of an Atom Collection or '-' for stdin. the feed is parsed
   and every Member URI found in it is DELETEd.
 
 END
 
-    opts.on('-c', '--no-complete', &quot;Don't follow previous and next links in the source feed&quot;) do
+    opts.on('-c', '--no-complete', &quot;don't follow previous and next links in the source feed&quot;) do
      options[:complete] = false
     end
 
+    opts.on('-v', '--verbose') { options[:verbose] = true }
+
+    opts.on('-i', '--interactive', &quot;ask before each DELETE&quot;) { options[:interactive] = true }
+
     atom_options opts, options
   end
 
   opts.parse!(ARGV)
 
-  if ARGV.length &lt; 1
+  if ARGV.length != 1
     puts opts
     exit
   end
@@ -51,25 +55,28 @@ if __FILE__ == $0
   source = ARGV[0]
   dest = ARGV[1]
 
-  feed = parse_input source, options
-
-  if options[:verbose]
-    feed.each do |slug,entry|
-      print &quot;got #{entry.title}  &quot;
-      puts &quot;(/#{slug})&quot;
-    end
-  end
-
-  uris = feed.map do |s,e|
-    e.edit_url
-  end
+  entries = parse_input source, options
 
   http = Atom::HTTP.new
-
   setup_http http, options
 
-  uris.compact.each do |uri|
+  tty = File.open('/dev/tty', 'w+') if options[:interactive]
+
+  uris = entries.each do |e|
+    next unless (uri = e.edit_url)
+
     puts &quot;deleting #{uri}&quot; if options[:verbose]
+
+    if options[:interactive]
+      tty.puts &quot;delete #{uri}&quot;
+      tty.puts &quot;title: #{e.title}&quot;
+      tty.puts e.content.to_s
+      tty.puts
+      tty.print &quot;? &quot;
+
+      next unless ['y', 'yes'].member? tty.gets.chomp
+    end
+
     http.delete uri
   end
 end</diff>
      <filename>bin/atom-purge</filename>
    </modified>
    <modified>
      <diff>@@ -1,8 +1,10 @@
 require 'atom/collection'
 
+# methods to make writing commandline Atom tools more convenient
+
 module Atom::Tools
-  # fetch and parse a URL
-  def http_to_feed url, complete_feed = false, http = Atom::HTTP.new
+  # fetch and parse a Feed URL, returning the entries found
+  def http_to_entries url, complete_feed = false, http = Atom::HTTP.new
     feed = Atom::Feed.new url, http
 
     if complete_feed
@@ -11,118 +13,150 @@ module Atom::Tools
       feed.update!
     end
 
-    feed.entries.map { |e| [nil, e] }
+    feed.entries
   end
 
   # parse a directory of entries
-  def dir_to_feed path
+  def dir_to_entries path
     raise ArgumentError, &quot;#{path} is not a directory&quot; unless File.directory? path
 
     Dir[path+'/*.atom'].map do |e|
-      slug = e.match(/.*\/(.*)\.atom/)[1]
-      slug = nil if slug and slug.match /^0x/
-
-      entry = Atom::Entry.parse(File.read(e))
-
-      [slug, entry]
+      Atom::Entry.parse(File.read(e))
     end
   end
 
-  def stdin_to_feed
-    feed = Atom::Feed.parse $stdin
-
-    slug_etc_from feed
+  # parse a Feed on stdin
+  def stdin_to_entries
+    Atom::Feed.parse($stdin).entries
   end
 
-  def feed_to_http feed, url, http = Atom::HTTP.new
+  # POSTs an Array of Atom::Entrys to an Atom Collection
+  def entries_to_http entries, url, http = Atom::HTTP.new
     coll = Atom::Collection.new url, http
 
-    feed.each do |slug,entry|
-      coll.post! entry, slug
-    end
+    entries.each { |entry| coll.post! entry }
   end
 
-  def feed_to_dir feed, path
+  # saves an Array of Atom::Entrys to a directory
+  def entries_to_dir entries, path
     if File.exists? path
       raise &quot;directory #{path} already exists&quot;
     else
       Dir.mkdir path
     end
 
-    feed.each do |slug,entry|
+    entries.each do |entry|
       e = entry.to_s
 
-      new_filename = if slug
-                       path + '/' + slug + '.atom'
-                     else
-                       path + '/0x' + MD5.new(e).hexdigest[0,8] + '.atom'
-                     end
+      new_filename = path + '/0x' + MD5.new(e).hexdigest[0,8] + '.atom'
 
       File.open(new_filename, 'w') { |f| f.write e }
     end
   end
 
-  def feed_to_stdout feed
-    f = Atom::Feed.new
+  # dumps an Array of Atom::Entrys into a Feed on stdout
+  def entries_to_stdout entries
+    feed = Atom::Feed.new
 
-    feed.each do |slug,entry|
-      f.entries &lt;&lt; entry
+    entries.each do |entry|
+      puts entry.inspect
+      feed.entries &lt;&lt; entry
     end
 
-    puts f.to_s
+    puts feed.to_s
   end
 
+  # turns a collection of Atom Entries into an Array of Atom::Entrys
+  #
+  # source: a URL, a directory or &quot;-&quot; for an Atom Feed on stdin
+  # options:
+  #   :complete - whether to fetch the complete logical feed
+  #   :user - username to use for HTTP requests (if required)
+  #   :pass - password to use for HTTP requests (if required)
   def parse_input source, options
-    if source.match /^http/
-      http = Atom::HTTP.new
-
-      setup_http http, options
-
-      http_to_feed source, options[:complete], http
-    elsif source == '-'
-      stdin_to_feed
-    else
-      dir_to_feed source
+    entries = if source.match /^http/
+             http = Atom::HTTP.new
+
+             setup_http http, options
+
+             http_to_entries source, options[:complete], http
+           elsif source == '-'
+             stdin_to_entries
+           else
+             dir_to_entries source
+           end
+
+    if options[:verbose]
+      entries.each do |entry|
+        puts &quot;got #{entry.title}&quot;
+      end
     end
+
+    entries
   end
 
-  def write_output feed, dest, options
+  # turns an Array of Atom::Entrys into a collection of Atom Entries
+  #
+  # entries: an Array of Atom::Entrys pairs
+  # dest: a URL, a directory or &quot;-&quot; for an Atom Feed on stdout
+  # options:
+  #   :user - username to use for HTTP requests (if required)
+  #   :pass - password to use for HTTP requests (if required)
+  def write_output entries, dest, options
     if dest.match /^http/
       http = Atom::HTTP.new
 
       setup_http http, options
 
-      feed_to_http feed, dest, http
+      entries_to_http entries, dest, http
     elsif dest == '-'
-      feed_to_stdout feed
+      entries_to_stdout entries
     else
-      feed_to_dir feed, dest
+      entries_to_dir entries, dest
     end
   end
 
   # set up some common OptionParser settings
   def atom_options opts, options
     opts.on('-u', '--user NAME', 'username for HTTP auth') { |u| options[:user] = u }
-    opts.on('-v', '--verbose') { options[:verbose] = true }
-
-    opts.on_tail('-h', '--help', 'Show this usage statement') { |h| puts opts; exit }
-    opts.on_tail('-p', '--password [PASSWORD]', 'password for HTTP auth') do |p|
-      p ||= begin
-              require 'highline'
 
-              HighLine.new.ask('Password: ') { |q| q.echo = false }
-            rescue LoadError
-              # Highline isn't installed, take the password anyway
-              gets.chomp
-            end
+    opts.on_tail('-h', '--help', 'show this usage statement') { |h| puts opts; exit }
 
+    opts.on_tail('-p', '--password [PASSWORD]', 'password for HTTP auth') do |p|
       options[:pass] = p
     end
   end
 
+
+  # obtain a password from the TTY, hiding the user's input
+  # this will fail if you don't have the program 'stty'
+  def obtain_password
+    i = o = File.open('/dev/tty', 'w+')
+
+    o.print 'Password: '
+
+    # store original settings
+    state = `stty -F /dev/tty -g`
+
+    # don't echo input
+    system &quot;stty -F /dev/tty -echo&quot;
+
+    p = i.gets.chomp
+
+    # restore original settings
+    system &quot;stty -F /dev/tty #{state}&quot;
+
+    p
+  end
+
   def setup_http http, options
-    if options[:user] and options[:pass]
+    if options[:user]
       http.user = options[:user]
+
+      unless options[:pass]
+        options[:pass] = obtain_password
+      end
+
       http.pass = options[:pass]
     end
   end</diff>
      <filename>lib/atom/tools.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>48bbeaa17b04e28a296e7b3bc28c8e9203432bfc</id>
    </parent>
  </parents>
  <author>
    <name>Brendan Taylor</name>
    <email>whateley@gmail.com</email>
  </author>
  <url>http://github.com/sr/atom-tools/commit/d8acab870db9cce25cab99176c35585f182f8e26</url>
  <id>d8acab870db9cce25cab99176c35585f182f8e26</id>
  <committed-date>2008-03-28T19:49:38-07:00</committed-date>
  <authored-date>2008-03-28T19:49:38-07:00</authored-date>
  <message>more-tools

darcs-hash:20080329024938-ce558-d201e1a48e51548838712e3ce276d29e177c6395.gz</message>
  <tree>c045f81fc9015935a212094b779e637fa664fd32</tree>
  <committer>
    <name>Brendan Taylor</name>
    <email>whateley@gmail.com</email>
  </committer>
</commit>
