Digicted

Archief voor ruby

Een huwelijksaanzoek via Ruby

Saturday, October 13th, 2007

Een uit de hand gelopen grap waarbij ik iemand ten huwelijk heb gevraagd resulteerde dit keer in het schrijven van een Ruby-script. Het hele verhaal is iets te veel om hier te plaatsen, maar de korte versie is dat mijn eerste aanzoek mislukte en ik daarom een tweede zou proberen. Omdat deze persoon ook wel iets met code heeft (niet met Ruby helaas) besloot ik dit in Ruby te schrijven. Huwelijksaanzoeken gaan ten slotte om liefde en dat ik van Ruby houd zal geen geheim zijn…

De vorm van het hart komt van een soortgelijk Perl-script, maar de code is geheel zelf geschreven.

Dit zou natuurlijk nooit gebruikt mogen worden in een “productie”-omgeving, maar voor een grap is het wel eens leuk om je code wat vorm te geven.

De code is op deze blog niet helemaal mooi zo op de weblog, misschien kan ik hier later nog wat aan doen.

Hoe dan ok, laten we maar beginnen:

#!/usr/bin/env ruby -W0
@y = ""
you = "Susan"
love = you

     def i           (a,b)
   puts @y         end;str =
 "Rtrzm, " + "vhk id zkridakh"
str.concat("des lds ld sqntvd")
str += "m"; str.split(//).each{
 |bfiwdl| @y << bfiwdl.succ };
  @y.split("").each{|qjwciqb|
   @you = @y.gsub(/\!/," ")}
    @you.gsub!("aa","a");id
      @y = @you.gsub("-",
        ",");@y += "?";
          @y.gsub!(#q
            /\s+/,#
             " ");
              you
               p

         i love, you

Voor we aan de uitleg beginnen zal ik de code eerst wat opsplitsen in een leesbare structuur.

#!/usr/bin/env ruby -W0
@y = ""
you = "Susan"
love = you

def i (a,b)
   puts @y
end

str = "Rtrzm, " + "vhk id zkridakh"
str.concat("des lds ld sqntvd")
str += "m"
str.split(//).each{ |bfiwdl|
  @y << bfiwdl.succ
}

@y.split("").each{ |qjwciqb|
  @you = @y.gsub(/\!/," ")
}

@you.gsub!("aa","a")
id
@y = @you.gsub("-", ",")
@y += "?"
@y.gsub!(#q
/\s+/,#
" ")
you
p
i love, you

Nu zal het ook duidelijk zijn dat er heel wat “overbodige” code in zit, maar die was soms nodig om de vorm succesvol te krijgen.

Regel voor regel er maar weer even door dan:

De eerste regel vertelt Ruby geen warnings te tonen door het gebruik van -W0. Dit voorkomt een warning over de methode id die eigenlijk niet meer gebruikt zou moeten worden. Op die plek had ik echter gewoon eventjes twee tekens nodig…

Daarna maken we de variable @y die in de hele class beschikbaar is, deze gebruiken we uiteindelijk om de output in op te slaan. De naam Susan wijzen we toe aan de variable you en you weer aan love.. We kunnen elke waarde in deze variablen stoppen, het gaat er om dat we ze nodig hebben aan het einde.

Dan begint het hart van de code, wat geopend wordt met een simpele methode die we i noemen en 2 parameters accepteer: a en b. Eigenlijk doen we niets met deze parameters, maar op deze manier kunnen we het later aanroepen als i love, you waarbij i gewoon een call is naar de methode en love en you de parameters zijn.

Alles wat we binnen deze methode doen is het printen van de waarde van @y.

De volgende stap is het aanmaken van de string die we willen printen. Normaal gesproken hadden we hier iets kunnen doen als @y = "Susan, wil je alsjeblieft met me trouwen?", maar het moest natuurlijk wat cryptisch zijn en het hart opvullen. De string maken we dan ook in een aantal stappen.

Eerst maken we simpelweg het eerste deel van de string aan, gebruiken we concat() om er nog een stukje aan toe te voegen en ten slotte += om de laatste letter toe te voegen.

Vervolgens doen we een split op de string, met als argument een lege regular expression. Het resultaat hiervan is dat we een array hebben waarin alle letters van de string afzonderlijk zijn opgeslagen. Met een each-block lopen we door de array heen en hebben we in de variable bfiwdl steeds de volgende letter van de string zitten.

Bij de eerste iteratie is dat dus een R, vervolgens een t etc. De naam bfiwdl is gekozen omdat dat het aantal karakters was wat ik nodig had om die regel op te vullen. Hier beginnen we ook de @y-string te vullen. @y << bfiwdl.succ is gewoon een andere schrijfwijze voor bijvoorbeeld @y += bfiwdl.succ. Zoals wel vaker kent Ruby gewoon verschillende methodes om iets uit te voeren.

De methode succ pakt steeds het volgende karakter, zoals dat voor een mens ook enigszins logisch zou zijn. Na een ‘a’ komt dus een ‘b’ en na een ‘c’ een b. Als we nu terugkijken naar de string die in str zit (Rtrzm, vhk id zkridakh des lds ld sqntvdm) zal het misschien ook opvallen dat we dus steeds een letter “terug” in het alfabet zijn gegaan.

Door de functie succ pakken we steeds de volgende waardoor we iets leesbaars krijgen. Als dit gebeurt is splitten we op onze nieuwe string. Dit keer geven we een lege string mee als argument aan split, wat hetzelfde resultaat geeft als een lege regular expression. Wederom een voorbeeld hoe je een probleem soms op meerdere manieren kan oplossen: iets wat erg handig kan zijn als je een stukje code schrijft wat enigszins cryptisch moet zijn.

De naam qjwciqb is weer puur gekozen vanwege lengte, deze variable gebruiken we verder ook niet in het blok. Wel gaan we de nieuwe variable @youineens gebruiken. Hier komt een string in te zitten die lijkt op de string in @y, maar een verbeterpunt heeft. Door het gebruik van .succ zijn de spaties in de string vervangen door uitroeptekens. Met gsub (een methode om tekst in een string te vervangen), zetten we de uitroeptekens terug om naar spaties.

Ook de dubbele a gaan we vervangen. Ruby vindt dat de ‘z’ opgevolgd moet worden door ‘aa’, iets waar ik het niet mee eens ben. Met @you.gsub!("aa","a") zet ik dit weer “goed”. Iets verder doen we een dergelijke replace als de vorige twee om van een - terug een komma te maken.

Voor die regel zien we echter nog een (nutteloze) call naar de functie id. Zoals gezegd bij de bespreking van regel 1 is deze niet nodig en puur voor opvulling van het hart.

We zijn weer terug aan het werk in @y en voegen nu het vraagteken toe aan het eind van de string. Het is ten slotte een verzoek, geen opdracht. Of dat is op z’n minst wat ik wil dat zij denkt. ;-)

Om het af te maken vervang ik dubbele spaties met een regular expression nog door een enkele spatie. Dit werkt als volgt: \s is het teken voor een spatie in regular expressions. \s+ Wil zeggen: één of meer spaties achter elkaar. Door deze te vervangen door een enkele spatie werd een probleem wat ik eerder had opgelost. Bij een latere iteratie van de code was de string eigenlijk al gelijk goed, maar toch heb ik het laten staan.

De # staan er weer voor … opvulling.

De laatste twee regels: you en p doen eigenlijk niets. Tegen het einde van zo’n hart wordt het erg smal en heb je gewoon wat korte namen nodig…

De i love, you op de laatste regel heb ik al uitgelegd in het begin: dit roept gewoon de functie i aan met parameters love en you.

Uiteraard is dit geen code die ooit in productie zo geschreven zou mogen worden, maar toch zitten er dingen in die ook in productie gebeuren:

  • Code die niet meer gebruikt wordt laten staan in plaats van verwijderen
  • De waarde van een variable in een andere stoppen en dan weer terug. Gelukkig zie ik dit weinig, maar ik heb het al meegemaakt…
  • Functies die eigenlijk geen nut hebben in de code zetten
  • Onduidelijke variabelenamen (zoals bfiwdl)
  • En natuurlijk: 15 regels code gebruiken voor iets wat ook in één regel opgelost had kunnen worden.

Kleine note nog: ik heb nog geen ja of nee gehoord van het bewuste meisje. Iemand tips om er een ‘ja’ uit te krijgen? ;-)

Rubyscript om Google-positie te zien

Saturday, August 18th, 2007

Gisteren was ik in Amsterdam met Sanne Roemen en Daan Kortenbach, voor een erg leuk gesprek.

We hebben het ook even over zoekmachines gehad, waarbij Daan vertelde dat hij op pagina 1 kwam voor een goed trefwoord. Uiteraard heb ik zelf ook even gekeken of ik op één van de eerste pagina’s kom, wat helaas niet zo is.

Maar dit gaf me wel een idee: het komt best veel voor dat ik even wil weten of ik voor bepaalde trefwoorden in de eerste 3 of 5 pagina’s sta. Nu zijn er natuurlijk programma’s om dit te doen, maar voor mij betekent dit toch meestal het trefwoord intypen in Google en dan pagina 1, 2, 3, 4 en 5 aanklikken, zoeken naar mijn domeinnaam en hopen dat ik er bij sta.

Nu had ik natuurlijk op zoek kunnen gaan naar zo’n programma, maar in plaats daarvan automatiseerde dit proces zelf door een stukje Ruby.

Zoals gewoonlijk beginnen we eerst met de volledige code en gaan we het vervolgens analyseren:

#!/usr/bin/env ruby -w
require 'net/http'

class GooglePage
  attr_reader :keyword, :pageNumber, :url, :googleResultPage, :maxPages, :position

  def initialize(keyword, url, googleUrl = "www.google.com", maxPages = 10, perPage = 10 )
    @keyword = keyword
    @googleUrl = googleUrl
    #om te voorkomen dat het script niet werkt als mensen de www. vergeten te vermelden in de URL zetten we deze er zelf in
    @googleUrl = "www." + googleUrl unless @googleUrl =~ /^www\.$/
    @url = url
    @perPage = perPage
    @maxPages = 2
    @pageNumber = 1
  end

  def checkGoogle
    getPage
    if @body.include?( @url )
      @position = getPosition
      return @pageNumber
    else
      @pageNumber += 1
      checkGoogle unless @pageNumber >= @maxPages
    end
  end

  def getPage
    @body = ""
    Net::HTTP.start( @googleUrl, 80 ) {|http|
      start = (@pageNumber - 1) * @perPage
      resultPage = "/search?q=#{@keyword.gsub( "\s", "+" )}&start=#{start}"
      @googleResultPage = "http://" + @googleUrl + resultPage
      req = Net::HTTP::Get.new( resultPage )
      @body += http.request( req ).body
    }
  end

  def getPosition
    position = 0
    @body.scan( /span class=a>(.*?)<\/span>/ ).each{ |w|
      position += 1
      return position if w[ 0 ] =~ /^#{@url}/
    }
  end
end

Goed, laten we eens gaan kijken wat dit allemaal betekent.

We beginnen met het maken van een class, GooglePage. Daarna gebruiken we attr_reader, om als het ware automatisch getters te genereren. Waarom dit handig is zullen we later zien.

Met def initialize maken we de constructor aan, deze methode wordt dus aangeroepen als we een nieuw object maken met deze klasse door GooglePage.new.

De rest van deze methode is weinig over te zeggen denk ik, gewoon wat variablen toewijzen lijkt me aardig duidelijk.

De methode checkGoogle wordt al wat spannender. Eerst vragen we de inhoud van de pagina op via getPage (hier over later meer), en daarna kijken we of de opgegeven URL voorkomt in de resultatenpagina. Zo ja, zoeken we de positie binnen de pagina op met getPosition wat ook gelijk de returnvalue is.

Als het niet zo is, gaan we naar de volgende pagina kijken en beginnen we gewoon overnieuw totdat we de site in de resultaten gevonden hebben of we voorbij pagina 10 zijn. Als je niet in de eerste 10 pagina’s staat, is het vaak toch al niet interessant meer. Dit nummer is echter aan te passen bij het aanmaken van het object.

getPage, deze methode opent een connectie met Google.nl en haalt de code op. Eventuele spaties in de keywords worden vervangen met een + om errors te voorkomen. ;)

Ten slotte getPosition. Hier proberen we te bepalen op welke positie op de pagina je ongeveer staat, het is wat buggy (met name als er meer resultaten van één site zijn), maar het geeft wel een indruk of je bovenin of onderin staat. We doen dit door te zoeken naar een bepaald stukje HTML (<span class a>) en de URL binnen deze span te bekijken. Komt deze niet overeen met de URL die we zoeken tellen we 1 op bij de positie en gaan we door tot we ‘m gevonden hebben.

Dan nog even een volledig voorbeeld met een paar leuke keywords genomen en ze in een hash gezet waar we vervolgens één voor één doorheen lopen. Ik zoek naar de URL digicted.nl, en gebruik Google.nl.

#!/usr/bin/env ruby -w
require 'net/http'

class GooglePage
  attr_reader :keyword, :pageNumber, :url, :googleResultPage, :maxPages, :position

  def initialize(keyword, url, googleUrl = "www.google.com", maxPages = 10, perPage = 10 )
    @keyword = keyword
    @googleUrl = googleUrl
    #om te voorkomen dat het script niet werkt als mensen de www. vergeten te vermelden in de URL zetten we deze er zelf in
    @googleUrl = "www." + googleUrl unless @googleUrl =~ /^www\.$/
    @url = url
    @perPage = perPage
    @maxPages = maxPages
    @pageNumber = 1
  end

  def checkGoogle
    getPage
    if @body.include?( @url )
      @position = getPosition
    else
      @pageNumber += 1
      checkGoogle unless @pageNumber >= @maxPages
    end
  end

  def getPage
    @body = ""
    Net::HTTP.start( @googleUrl, 80 ) {|http|
      start = (@pageNumber - 1) * @perPage
      resultPage = "/search?q=#{@keyword.gsub( "\s", "+" )}&start=#{start}"
      @googleResultPage = "http://" + @googleUrl + resultPage
      req = Net::HTTP::Get.new( resultPage )
      @body += http.request( req ).body
    }
  end

  def getPosition
    position = 0
    @body.scan( /span class=a>(.*?)<\/span>/ ).each{ |w|
      position += 1
      return position if w[ 0 ] =~ /^#{@url}/
    }
  end
end

["macbook pro review", "ruby", "macbook", "samsung 205bw", "er is er een jarig", "martijn engler", "weblog" ].each do |keyword|
  gp = GooglePage.new( keyword, "digicted.nl", "google.nl", 10 )
  if gp.checkGoogle
    puts "#{gp.url} gevonden op pagina #{gp.pageNumber} (positie #{gp.position}) voor keyword '#{gp.keyword}': #{gp.googleResultPage}"
  else
    puts "#{gp.url} staat niet in de eerste #{gp.maxPages} pagina's van Google voor het keyword '#{gp.keyword}'"
  end
end

De output is op het moment van schrijven als volgt:

digicted.nl gevonden op pagina 1 (positie 6) voor keyword 'macbook pro review': http://www.google.nl/search?q=macbook+pro+review&start=0
digicted.nl staat niet in de eerste 10 pagina's van Google voor het keyword 'ruby'
digicted.nl gevonden op pagina 5 (positie 6) voor keyword 'macbook': http://www.google.nl/search?q=macbook&start=40
digicted.nl gevonden op pagina 1 (positie 11) voor keyword 'samsung 205bw': http://www.google.nl/search?q=samsung+205bw&start=0
digicted.nl gevonden op pagina 4 (positie 11) voor keyword 'er is er een jarig': http://www.google.nl/search?q=er+is+er+een+jarig&start=30
digicted.nl gevonden op pagina 1 (positie 1) voor keyword 'martijn engler': http://www.google.nl/search?q=martijn+engler&start=0
digicted.nl staat niet in de eerste 10 pagina's van Google voor het keyword 'weblog'

Ruby: backups maken van je del.icio.us bookmarks

Sunday, July 29th, 2007

Net las ik “Backup delicious to gmail” op Foobr. Aaron Bassett beschrijft hier een handige manier om een backup te maken van je del.icio.us-bookmarks door de XML-file te emailen naar je GMail-account door middel van een PHP-script.

Dit is naar mijn mening een prima manier om een backup te maken van je del.icio.us-bookmarks, maar ik gebruik liever Ruby (al was het maar omdat ik het taaltje nog steeds een beetje aan het leren ben, maar geweldig vind voor dergelijke problemen) en ik wil het niet in GMail hebben maar op de lokale harddisk. Eventueel een backup op deze website.

Eerst heb ik even gespiekt in zijn code en toen ben ik zelf begonnen.

Eerst maar even de hele code:

#!/usr/bin/env ruby
require 'net/https'

delicous_username = ""
delicous_password = ""
recent = true #only fetch recent posts? useful for debugging
xmlfile = "/fullpath/to/xmlfile"

https = Net::HTTP.new( 'api.del.icio.us', 443 )
https.use_ssl = true

https.start do |http|
  req = Net::HTTP::Get.new('/v1/posts/' + ( recent ? "recent" : "all" ) )
  req.basic_auth delicous_username, delicous_password
  xmlData = ""
  insertXslLine = true

  https.request( req ).read_body.each do |line|
    xmlData += line
    # add the reference to the xsl-file for some nice HTML-formatting
    xmlData += "<?xml-stylesheet type=\"text/xsl\" href=\"delicious.xsl\"?>\\n" if insertXslLine
    insertXslLine = false
  end
  File.new( xmlfile, "w+" ).write( xmlData )
end

De eerste paar regels zijn vooral voor de configuratie. Vul hier je del.icio.us-gebruikersnaam en -wachtwoord in, of je alleen de meest recente bookmarks wilt (zodat je niet de hele XML-file hoeft te fetchen) en de locatie voor het XML-bestand.

Dan maken we verbinding met api.del.icio.us via HTTPS en authen met onze gebruikersnaam en wachtwoord van de service. De rest is op zich best “simpel”:

  1. XML-file binnenhalen, regel voor regel;
  2. Een verwijzing naar het XSL-bestand invoegen op de 2e regel (XSL-bestand zullen we zo zien en is optioneel);
  3. De data van de XML-file met XSL-verwijzing wegschrijven naar het gekozen bestand.

Als we dat doen hebben we een kale XML-file, dus die gaan we iets mooier maken met een beetje XSLT zodat er iets uitkomt wat voor HTML door moet gaan.

<xsl:stylesheet version = '1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
  <xsl:template match="/">
    <html>
      <head>
        <title>del.icio.us bookmarks backup</title>
      </head>
      <body>
        <h2>del.icio.us bookmarks of <xsl:value-of select="posts/@user" /></h2>
        <ol>
          <xsl:for-each select="posts/post">
            <li>
              <a>
                <xsl:attribute name="href">
                  <xsl:value-of select="@href"></xsl:value-of>
                </xsl:attribute>
                <xsl:value-of select="@description"></xsl:value-of>
              </a>
            </li>
          </xsl:for-each>
        </ol>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

De output hiervan zo iets moeten zijn als:

<html>
  <head>
    <title>del.icio.us bookmarks backup</title>
  </head>
  <body>
    <h2>del.icio.us bookmarks of martijnengler</h2>
    <ol>
      <li><a href="http://pinderkent.blogsavy.com/archives/88">Sometimes it's best to leave old software systems alone.</a></li>
      <li><a href="http://www.codinghorror.com/blog/archives/000920.html">Coding Horror: Google's Number One UI Mistake</a></li>
      <li><a href="http://blog.angrypets.com/2007/07/the-case-for-ra.html">The Case for Desktop RAID 0 - Debunking the Myths</a></li>
      <li><a href="http://www.deadharddrive.com/">My dead hard drive story</a></li>
      <li><a href="http://weblog.oomph.nl/2007/07/blogger-typepad.html">Blogger, Typepad en Wordpress. Welk platform kies je voor je weblog?</a></li>
      <li><a href="http://lifehacker.com/software/how-to/turn-mailing-lists-into-an-rss-feed-283353.php">How To: Turn mailing lists into an RSS feed - Lifehacker</a></li>
      <li><a href="http://www.silverware.nl/">Silverware</a></li>
      <li><a href="http://www.youtube.com/watch?v=Xo-rm95ZBcg">Plain White T's - Hey There Delilah</a></li>
      <li><a href="http://lifehacker.com/software/how-to/hide-files-inside-of-jpeg-images-282119.php">How To: Hide files inside of JPEG images - Lifehacker</a></li>
      <li><a href="http://www.quietbay.net/Science/astronomy/nightsky/">Learn: Identify constellations, stars, planets and how to navigate at night</a></li>
      <li><a href="http://weblogs.sqlteam.com/jeffs/archive/2006/02/10/9002.aspx">Data belongs in your tables -- not in your code</a></li>
      <li><a href="http://www.ibm.com/developerworks/web/library/wa-ltwebserv/">Lightweight Web servers</a></li>
      <li><a href="http://www.dynamicajax.com/fr/AJAX_Suggest_Tutorial-271_290_312.html">Creating a Google AJAX Suggest Like Website Search</a></li>
      <li><a href="http://www.codeandcoffee.com/2007/06/27/how-to-make-a-password-strength-meter-like-google/">How to Make a Password Strength Meter Like Google - Code and Coffee</a></li>
      <li><a href="http://www.wikihow.com/Share-Your-Obituary-With-Your-Online-Friends">How to Share Your Obituary With Your Online Friends - wikiHow</a></li>
    </ol>
  </body>
</html>

Ruby: tab completion in IRB

Monday, May 7th, 2007

Het is nog steeds erg druk bij Silverware, dus dit wordt even een snelle post, maar misschien dat er nog mensen zijn die hier erg blij van worden? ;)

Als je net zoals ik nog wel eens werk met de Interactive RuBy shell (kortweg IRB) heb je vast wel eens autocompletion gemist. Dankzij de geweldige tip van Eli Bendersky is dat niet langer nodig!

Hij plaatste zelf de volgende tip voor Windows-gebruikers:

For some reason, however, irb doesn’t come with tab completion out of the box. To set it up, you can create a batch file called irbb.bat, for instance, which calls:

  irb -r irb/completion
  

And call that instead of irb. A simpler method, IMHO, is directly edit the irb.bat file in the Ruby installation bin/ directory to require irb/completion.

Voor de mensen die echter Mac OS (of Linux/BSD of een ander UNIX-based OS) gebruiken werkt het beter om even je .profile (een verborgen bestand in je homedirectory) te bewerken (als je gebruikt maakt van bash in ieder geval). Door onderaan het regeltje alias irb='irb -r irb/completion' te plaatsen, het bestand op te slaan en dan . .profile in te typen op de terminal zou je autocompletion toegevoegd moeten hebben.

Elke dag de nieuwste Dilbert met Ruby

Sunday, April 29th, 2007

Eerst even mijn excuus om een tijd niet te bloggen: druk, druk, druk… Heel erg druk! Silverware komt steeds dichter bij de lanceerdatum, wat natuurlijk erg leuk is, maar ook de nodige stress oplevert.

Ineens komen er allerlei bugs naar voren, blijken bepaalde algoritmes inefficiënter te zijn dan gedacht of zelfs niet te werken en gaat er natuurlijk van alles mis (dingen als een een SVN die crasht)…
Daarom heb ik al een week eigenlijk bijna geen enkele blogpost meer gelezen, wat ook te zien is aan het aantal ongelezen items in Bloglines en kom ik er ook niet echt aan toe om zelf wat te schrijven.

Wel heb ik veel code geschreven de afgelopen week, waaronder een stukje Ruby wat ik hier even wil delen.
Het originele script had een ander doeleinde en is ook iets aangepast (daar zat onder andere HTTP-authenticatie in) hiervoor, maar het doel blijft ongeveer hetzelfde. Overigens is dit ook prima op te lossen met bijvoorbeeld wget, maar dit is platformonafhankelijk (mits Ruby geïnstalleerd is) en het was best leuk om te doen.

Wat dit script doet is het volgende:

  1. Het haalt de Dilbert-strip (in ons voorbeeld) van vandaag op.
  2. De strip wordt op een FTP geplaatst, bijvoorbeeld voor weergave op een website.
  3. De huidige datum wordt in de bestandsnaam gezet, zo kun je een archief opbouwen.

Dit script kun je bijvoorbeeld inzetten als je geen beschikking hebt over PHP, maar wel elke dag de nieuwste Dilbert op je site wil plaatsen. In dit geval is het echter verstandig om een “statische” filename te nemen, waarbij je het vorige bestand eventueel zou kunnen hernoemen.

De code:

require 'net/http'
require 'net/ftp'

#een tijdelijk path om de dilbert op te slaan
imagepath   = "/tmp/"

#de bestandsnaam voor het tijdelijke path en de FTP
imagefile = "dilbert-" + Time.now.localtime.strftime("%Y-%m-%d") + ".jpg"

#het adres van de website, gebruik hierbij geen http:// of een / aan het einde
httpaddress = "pag.csail.mit.edu"

#de bestandsnaam van het bestand op de http
httpimage   = "/~adonovan/dilbert/today.gif"

#ftpinformatie
ftpaddress  = "ftp.example.org"
ftpuser     = "username"
ftppass     = "password"
ftpdir      = "dilbert"

fullpath = imagepath + imagefile

#maak een verbinding met de HTTP op port 80, haal de inhoud uit de image en schrijf het weg naar ons tijdelijk bestand
Net::HTTP.start( httpaddress, 80 ) {|http|
  req = Net::HTTP::Get.new( '/jpg/image.jpg' )
  open( fullpath, "wb").write( http.request( req ).body )
} 

#verbind met de FTP, log in en plaats het plaatje in de juiste directory
ftp = Net::FTP::new( ftpaddress  )
ftp.login( ftpuser, ftppass )
ftp.chdir( ftpdir )
ftp.putbinaryfile( fullpath, imagefile )
ftp.close

De bovenste variablen dienen voor configuratie (ik ben me bewust van YAML, maar dat vond ik hier wat ver gaan), de rest zou duidelijk moeten zijn als je een beetje Ruby snapt of gewoon simpelweg de comments leest. Echter staan de reacties, zoals altijd, open voor vragen.