# Cat2rafb (#77)

**URL:** <https://www.ruby-forum.com/t/cat2rafb-77/94477>\
**Category:** Ruby\
**Created:** [May 4, 2006, 2:53pm UTC](https://www.ruby-forum.com/t/cat2rafb-77/94477 "2006-05-04T14:53:21Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [May 4, 2006, 2:53pm UTC](https://www.ruby-forum.com/t/cat2rafb-77/94477/1 "2006-05-04T14:53:21Z")

</div>

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](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 P.
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 B. problems, and trust  
me, they  
are good stuff…

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [May 5, 2006, 8:20am UTC](https://www.ruby-forum.com/t/cat2rafb-77/94477/2 "2006-05-05T08:20:23Z")

</div>

I suppose Sean C.'s solution came in too late for the summary, but I  
think it’s definitely worth looking at for its terseness. Here’s a bonus  
summary.

> #!/usr/bin/ruby  
> require ‘uri’  
> require ‘net/http’

So Sean’s using a couple of libraries like the other solutions.  
Actually, just one, really: net/http. You don’t need to explicitly  
require ‘uri’; net/http requires it. No command-line switches here.

Watch as a complete solution unfolds in just three lines. You can  
probably read it just as well without my commentary.

> result = Net::HTTP.post\_form(URI.parse(‘[http://rafb.net/paste/paste.php](http://rafb.net/paste/paste.php)’),  
> {:text =\> ARGF.readlines,  
> :nick =\> ‘paste user’,  
> :lang =\> ‘Ruby’})

Line one uses Net::HTTP.post\_form to send a form-style POST request and  
get back the result. post\_form takes two parameters, the target URI, and  
a hash of form data.

The value of the form variable “text” comes from ARGF (like Stefano  
Taschini’s solution and others) which will get input from filenames  
given as command-line arguments or from standard input.

After one line, we have the paste URL that the quiz wants in  
result[‘location’], but Sean’s going for bonus points!

> result = Net::HTTP.get\_response ‘[rubyurl.com](http://rubyurl.com)’,  
> ‘/rubyurl/remote?website\_url=[http://rafb.net](http://rafb.net)’ + result[‘location’]

RubyURL provides a URL specifically to be used in other ways than  
through its form: “[http://rubyurl.com/rubyurl/remote](http://rubyurl.com/rubyurl/remote)”. Line two uses  
Net::HTTP.get\_response to send a GET request to this location, passing  
as the website\_url the URL from line one’s result.

> puts result[‘location’].sub(‘rubyurl/show/’, ‘’)

The result from line two is an HTTP redirect with a Location like  
“[http://rubyurl/rubyurl/show/xyz](http://rubyurl/rubyurl/show/xyz)”. This page displays the new RubyURL  
that’s been made, but we don’t want that, we want the RubyURL itself.  
That can be obtained just by removing the middle bit of the URL.

And we’re done. Too easy!

Cheers,  
Dave

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [May 5, 2006, 7:57pm UTC](https://www.ruby-forum.com/t/cat2rafb-77/94477/3 "2006-05-05T19:57:47Z")

</div>

On 5/5/06, Dave B. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> require ‘uri’; net/http requires it. No command-line switches here.  
> get back the result. post\_form takes two parameters, the target URI, and  
> a hash of form data.
> 
> The value of the form variable “text” comes from ARGF (like Stefano  
> Taschini’s solution and others) which will get input from filenames  
> given as command-line arguments or from standard input.

Yes, Sean’s RubyGolf implementation is very good, and plus. it showed  
the essence of problem space in about 1 line of code. A lot of the  
other solutions (even mine!) went to some expense setting up variable  
options, error handing, well factored code, etc. But the goal of the  
quiz was to paste the input to [rafb.net](http://rafb.net) and get the URL back. Which  
his does elegantly.

Not that the other solutions were not good. They showed how to write  
idiomatic ruby to solve this general class of problems, and I learned  
a lot from reading them, which is the goal of the quiz in general.

Sean freely admits his approach was “slash and burn farming,” and it  
wouldn’t work if you wanted to paste “Plain Text” or some other  
language. I say, however, that the nopaste website and using it with  
IRC indicates a simple solution. If you were pairing or collaborating  
over the net and wanted something like this and you didn’t have it,  
you could code it in Ruby in a very few minutes.

BTW, we (Sean, Craig Bucheck and I,) worked on this as this week’s  
StL.rb (St. Louis Ruby group) weekly hacking night project. Ruby group  
members across the land are meeting on the same weeknight as the  
regular meeting to participate in Ruby code pair-programming or  
working on common problems for general edification. The RubyQuiz which  
comes out about once a week makes for a good topic. Try it out in your  
own HackNight.

> Cheers,  
> Dave

Till later,  
Ed

---

<div class="post-metadata">

**Author:** ![bbazzarrakk](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/47e85d/32.png) [@bbazzarrakk](https://www.ruby-forum.com/u/bbazzarrakk)\
**Post date:** [May 5, 2006, 11:04pm UTC](https://www.ruby-forum.com/t/cat2rafb-77/94477/4 "2006-05-05T23:04:42Z")

</div>

On 5/5/06, Ed Howland [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> > Actually, just one, really: net/http. You don’t need to explicitly  
> > Line one uses Net::HTTP.post\_form to send a form-style POST request and  
> > options, error handing, well factored code, etc. But the goal of the  
> > IRC indicates a simple solution. If you were pairing or collaborating
> 
> > Cheers,  
> > Dave
> 
> Till later,  
> Ed

I forgot to link to the code for my solution, actually. Doh!  
Running site: [http://rpaste.com/](http://rpaste.com/)  
Code: [http://svn.supremetyrant.com/paste/](http://svn.supremetyrant.com/paste/)  
Code LOC: 66 Test LOC: 75 Code to Test Ratio: 1:1.1
