When I first saw this quiz I thought, "I'm going to add this as a command for my
text editor!"  It was probably a day or two later when I learned that it was
already in there and had been for some time now.  I guess other people thought
it was a good idea too.

Let's dive right into a solution.  Here's the start of some code by Stefano
Taschini:

	#!/usr/bin/env ruby
	
	require 'optparse'
	require 'net/http'
	
	# Command-Line Interface.
	class Cli
	
	 Languages = %w{ C89 C C++ C# Java Pascal Perl PHP PL/I Python Ruby
	                 SQL VB Plain\ Text }
	 Aliases = {"c99" => "C", "visual basic" => "VB", "text" => "Plain Text"}
	 PasteUrl = "http://rafb.net/paste/paste.php"
	
	 attr :parser
	 attr :opt
	 
	 # ...

Obviously this is just some basic setup.  You can see that Stefano plans to use
OptionParser for the interface and Net::HTTP for the networking.  You can also
see the list of supported languages here, complete with aliases, which I thought
was a nice touch.

Here's the interface code:

	 # ...
	 
	 # Initialize the command-line parser and set default values for the
	 # options.
	 def initialize
	   @opt = {
	     :lang => "Plain Text",
	     :nick => "",
	     :desc => "",
	     :tabs => "No",
	     :help => false}
	   @parser = OptionParser.new do |cli|
	     cli.banner += " [file ...]"
	     cli.on('-l','--lang=L', 'select language') { |s|
	       l = s.downcase
	       opt[:lang] =
	       if Aliases.include?(l) then
	         Aliases[l]
	       else
	         Languages.find(proc{ raise OptionParser::InvalidArgument,l }) { |x|
	           x.downcase == l
	         }
	       end
	     }
	     cli.on('-n', '--nick=NAME', 'use NAME as nickname') { |s| opt[:nick] = s}
	     cli.on('-d', '--desc=TEXT', 'use TEXT as description') { |s|
	       opt[:desc] << s
	      }
	     cli.on('--tabs=N', Integer, 'expand tabs to N blanks (N >= 0)') { |n|
	       raise OptionParser::InvalidArgument, n unless n>=0
	       opt[:tabs] = n
	     }
	     cli.on('-h', '--help', 'show this information and quit') {
	       opt[:help] = true
	     }
	     cli.separator ""
	     cli.separator "Languages (case insensitive):"
	     cli.separator " " +
	                   (Languages+Aliases.keys).map{|x|x.downcase}.sort.join(",")
	   end
	 end
	 
	 # ...

I know that looks like a lot of code, but it's all just trivial declarations. 
This program supports all of NoPaste's form elements through setting
command-line options.

You can see that the only option handler worth mentioning is the language
handler.  All that happens in there is to make sure a valid language is
selected.  This section of the code uses the default parameter to find() which I
don't often come across.  When passes a Proc object, find() will call it when a
matching object cannot be found.  Generally the result of that call is returned,
but in this case an Exception is raised before that can happen.

Ready for the huge section of networking code?

	 # ...
	 
	 # Post the given text with the current options to the given uri and
	 # return the uri for the posted text.
	 def paste(uri, text)
		 response = Net::HTTP.post_form(
		   uri,
		   { "lang" => opt[:lang],
		     "nick" => opt[:nick],
		     "desc" => opt[:desc],
		     "cvt_tabs" => opt[:tabs],
		     "text" => text,
		     "submit" => "Paste" })
		 uri.merge response['location'] || raise("No URL returned by server.")
	 end
	 
	 # ...

There's not a lot of magic here, is there?  One call to post_form() hands the
data to the server.  After that, the answer is pulled from a header of the
response (the url of the post).  It doesn't get much easier than that.

Here's the last little bit of code that turns all of this into an application:

	 # ...
	 
	 # Parse the command-line and post the content of the input files to
	 # PasteUrl.  Standard input is used if no input files are specified
	 # or whenever a single dash is specified as input file.
	 def run
		 parser.parse!(ARGV)
		 if opt[:help]
		   puts parser.help
		 else
		   puts paste(URI.parse(PasteUrl), ARGF.read)
		 end
	 rescue OptionParser::ParseError => error
		 puts error
		 puts parser.help()
	 end
	
	end
	
	if __FILE__ == $0
	 Cli.new.run
	end

That's as simple as it looks folks.  Parse the arguments, then show usage if
requested or paste the code.  Any argument errors also trigger a usage
statement, after the error is shown.

I thought that was a nice example of a feature rich, yet still simple solution.

There are other ways to handle the networking though and I want to look at
another solution with a different approach.  Here's the start of Aaron
Patterson's code:

	# Solution to [QUIZ] cat2rafb (#77) 
	# By Aaron Patterson
	require 'rubygems'
	require 'mechanize'
	require 'getoptlong'
	
	PASTE_URL = 'http://rafb.net/paste/'
	RUBY_URL  = 'http://rubyurl.com/'
	
	# Get options
	parser = GetoptLong.new
	parser.set_options( ['--lang', '-l', GetoptLong::OPTIONAL_ARGUMENT],
	                    ['--nick', '-n', GetoptLong::OPTIONAL_ARGUMENT],
	                    ['--desc', '-d', GetoptLong::OPTIONAL_ARGUMENT],
	                    ['--cvt_tabs', '-t', GetoptLong::OPTIONAL_ARGUMENT]
	                  )
	opt_hash = {}
	parser.each_option { |name, arg| opt_hash[name.sub(/^--/, '')] = arg }
	
	# ...

Here GetoptLong is used for the interface and WWW::Mechanize is loaded for the
networking.  We will get to the networking in a bit, but above we have the
option code.  Basically GetoptLong is told of the options, and then they can be
iterated over and collected into a Hash.  This version does not validate the
choices though.

Next we need the text to paste:

	# ...
	
	# Get the text to be uploaded
	buffer = String.new
	if ARGV.length > 0
	  ARGV.each { |f| File.open(f, "r") { |file| buffer << file.read } }
	else
	  buffer = $stdin.read
	end
	
	# ...

What does this do?  Treat all arguments as files and slurp their contents into a
buffer, or read from $stdin if no files were given.  Anyone for a round of golf?
Ruby has a special input object for this exact purpose and with it you can
collapse the above to a simple one line assignment.  Try to come up with the
answer, then check your solution by glancing back at how Stefano read the input.

Finally, we are ready for some WWW::Mechanize code:

	# ...
	
	agent = WWW::Mechanize.new
	
	# Get the Paste() page
	page = agent.get(PASTE_URL)
	form = page.forms.first
	form.fields.name('text').first.value = buffer
	
	# Set all the options
	opt_hash.each { |k,v| form.fields.name(k).first.value = v }
	
	# Submit the form
	page = agent.submit(form)
	text_url = page.uri.to_s
	
	# Submit the link to RUBY URL
	page = agent.get(RUBY_URL)
	form = page.forms.first
	form.fields.name('rubyurl[website_url]').first.value = text_url
	page = agent.submit(form)
	puts page.links.find { |l| l.text == l.href }.href

If you haven't seen WWW::Mechanize in action before, I hope you are suitably
impressed by this.  The library is basically a code based browser.  You load
pages, fill out forms, and submit your answers just as you would with your
browser.

You can see that this code also filters the results through RubyURL.  With
WWW::Mechanize you even have access to great iterators for the tags as we see
here.  Check out that final find() of the link, for example.

If you need to walk some web pages, WWW::Mechanize is definitely worth a look.

My thanks to the quiz creator for a wonderful automation problem and to all the
solvers for their great examples of how simple something like this can be.

Starting tomorrow we have two weeks of Ross Bamford problems, and trust me, they
are good stuff...