Digicted

Archief voor Programmeren

Een patch voor het cache path probleem in PHPDelicious

Wednesday, December 24th, 2008

Voor mijn stage bij Bemba was ik bezig met de del.icio.us API in PHP, waardoor ik natuurlijk koos voor de PHPDelicious library. Helaas zat er een foutje in de originele library, waardoor er niet gecached kon worden als het cache path niet expliciet werd meegegeven aan de constructor.

De fix voor dit probleem is gelukkig erg simpel. In cache.inc.php zoek je naar de volgende regel:

public function __construct($sKey, $iCacheTime, $sPrefix='', $sCachePath = CACHE_PATH) {

Dit zou normaal regel 44 moeten zijn. Het probleem zit ‘m in CACHE_PATH, wat eigenlijk PHP_DELICIOUS_CACHE_PATH zou moeten zijn. De nieuwe regel wordt dus:

public function __construct($sKey, $iCacheTime, $sPrefix='', $sCachePath = PHP_DELICIOUS_CACHE_PATH) {

Na deze kleine wijziging is alles ok. Ik heb een patch gestuurd naar de auteur, en naar mijn ervaring wordt de library wel gepatched.

De patch kan toegepast worden door de patch-file in dezelfde directory te plaatsen als PHPDelicious en dan patch -p0 -i fix_default_cache_path.patch te typen op je terminal.

UPDATE: ik bedacht me dat wij intern eigenlijk nog een extra patch gebruiken. Als er geen verbinding gemaakt kan worden met del.icio.us (zoals in de trein kennelijk.. duurde even voordat ik dat door had) is een duidelijke foutmelding wel handig. Hiervoor heb ik deze patch gemaakt.

Als de curl_exec() faalt, krijg je nu een PHP_DELICIOUS_ERR_CONNECTION_FAILED te zien.

UPDATE 2: inmiddels heb ik een email gehad van de auteur, waarin staat dat de CACHE_PATH-patch zal mee worden genomen in de nieuwe versie van PHPDelicious.

Shared items van Google Reader automatisch naar del.icio.us sturen

Monday, January 14th, 2008

De shared items feature van Google Reader is de laatste tijd veel in het nieuws geweest. En hoewel niet alle verhalen positief waren, zijn er nog steeds genoeg mensen die dankbaar gebruik maken van de shared items.

Zelf ben ik er nooit echt mee aan de slag geweest, toen ik er gisteren naar keek zag ik dat ik in totaal twee items geshared heb. Dit was waarschijnlijk meer om te testen, dan dat ik het echt nuttig vond.

Meestal bookmark ik links gewoon naar del.icio.us, waar ze ook voor iedereen beschikbaar zijn.

Ook @Polledemaagt en @frankmeeuwsen delen hun items via del.icio.us, maar Polle gaf zaterdag op Twitter aan op zoek te zijn naar een methode om zijn shared items in Google Reader naar del.icio.us te krijgen.

Mijn eerste idee was het gebruik van een Greasemonkey script, maar dit betekent nog steeds handmatig werk - het maakt het proces hooguit iets gemakkelijker.

Daarom besloot ik om een eigen script te schrijven met de RSS-feed van Google Reader shared items en de API van del.icio.us, het resultaat: gr2dic.

Het principe is simpel: je vult je Google Shared items feed URL in (zie deze screenshot), je del.icio.us-username en je del.icio.us-password. Vervolgens worden al je shared items elke dag om 9 uur naar del.icio.us gepost en automatisch getagged met “gr2dic”.

De source code zal ik waarschijnlijk binnenkort beschikbaar maken in een andere post. Wel wil ik alvast een patch delen die ik geschreven heb voor PhpDelicious: addpost_and_deletepost_fix.patch. Zonder deze patch zullen addPost() en deletePost() altijd false returnen en een notice geven.

De auteur van PhpDelicious heb ik ook op de hoogte gesteld van deze patch, dus wellicht zal het binnenkort ook goed zijn in de officiële release.

Inmiddels een email terug van de developer, hij gaat de patch toepassen en opnieuw uploaden.

Update: Op lifehacking.nl schreef Frank meeuwsen al over mijn “oude oplossing” in het artikel Google Reader en del.icio.us Samenvoegen. De methode hierboven beschreven is dus een handigere methode dan op LH.nl gepost is.

Update 2: Er zijn vandaag wat problemen geweest met het script, maar die zouden inmiddels opgelost moeten zijn.

Nieuwe pagina: scripts

Thursday, January 3rd, 2008

Om dingen wat overzichtelijk te maken heb ik een extra pagina toegevoegd aan deze website: scripts.

Hier komen verschillende scripts te staan die ik door de tijd heen heb geschreven voor deze blog. Alles is onder een open source licensie en mag daardoor vrij gebruikt worden.

Nieuwe scripts zullen aan de lijst toegevoegd worden en ook updates van de huidige scripts zullen daar vermelden worden.

Binnenkort een nieuwe toevoeging!

Wordpress Plugin: Bulk Tag Renamer

Sunday, December 9th, 2007

Naar aanleiding van een gesprek met een vriendin heb ik mijn eerste Wordpress-plugin geschreven.

Het was haar opgevallen dat het niet mogelijk is de naam van een tag te veranderen. Bijvoorbeeld alles wat getagged is met “appel” wijzigen naar “apple”.

Omdat ik al langer een Wordpress-plugin wilde schrijven en deze plugin niet al te moeilijk leek (dat viel nog wat tegen, omdat ik eerst moest leren hoe Wordpress-plugins werken) nam ik de taak op mij. Het resultaat is een plugin waarmee je snel de naam van een tag kunt wijzigen en nog een klein beetje andere functionaliteit.

Aangezien ik er een voorstander van ben om dingen één ding te laten doen, en dat ene ding dan gelijk goed, wordt het geheel mogelijk nog gesplitst naar twee plugins.

De huidige feature-list:

  • Tag hernoemen
  • “Slug” (permalink) wel of niet updaten
  • Categorieën met dezelfde naam wel of niet updaten
  • Bestaande tags aanklikken en zo in het “tagveld” zetten bij post schrijven

Installatie is simpel:

  1. Extract het zipbestand
  2. Upload de map naar wp-content/plugins/
  3. Activeer de plugin via het plugins-menu in de admin

Dat is alles! Vervolgens kun je het een link vinden in het menu manage.

Download de plugin.

Perl warnings op Leopard oplossen

Sunday, November 11th, 2007

Voor mensen die Leopard gebruken en het volgende probleem hebben met Perl:

perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
   LC_ALL = (unset),
   LC_CTYPE = "en_US.UTF-8",
   LANG = "UTF-8"
   are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").

Zet dit in je .profile:

# This setting is for the new UTF-8 terminal support
export LC_CTYPE=en_US.UTF-8
export LC_ALL=en_US.UTF-8

En de warnings zijn weg…

Oplossing gevonden via Rubify.

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? ;-)

Tellen in SQL of PHP: wat is sneller?

Tuesday, August 21st, 2007

Afgelopen zondag plaatste Nick een blogpost over MySQL en de fouten die hij mensen hier mee ziet maken.

In de reacties kwam er toen even een discussie of je COUNT() in je query moest gebruiken of een mysql_num_rows in je PHP-code als je het aantal rijen wilt weten.

Ik heb toen even een heel simpel scriptje gedraaid om gewoon snel te checken of mijn stelling klopte en inderdaad: COUNT() leek sneller te zijn.

Gisteren heb ik toen met een database met 1 miljoen rijen (testdata) een paar keer een script gedraaid en de resultaten opgeslagen in een database. Het testsysteem is een Intel PentiumD 930 met 2GB geheugen. PHP 5 en MySQL 5, draaiend op Windows Vista.

Eerst draaide ik het script op de MacBook Pro (wat voor de nodige load zorgde), maar toen bedacht ik me dat de PHP dan werd uitgevoerd op de laptop, terwijl ik de MySQL-server op de Windows-PC draaide. Totaal geen eerlijke vergelijking dus, dus verplaatst.

Het script haalt 1.000.000 rijen op uit een tabel met twee velden: id en name. ID is de primary key met een auto_increment en name is een tinytext-veld met willekeurige strings van willekeurige lengte. Na het ophalen worden de rijen geteld.

De resultaten (in seconden) zijn uiteindelijk als volgt:

PHP SQL
5866.500 503.085
1813.99 1237.07
917.166 490.60
476.952 442.147
562.155 459.487

Gemiddeld duurt het dus 626.47846069336 seconden om het aantal rijen te tellen in MySQL. In PHP duurt het gemiddeld 1927.3516174316 seconden.

De snelste run in PHP duurde 476.952 seconden, de snelste in SQL duurde slechts 442.147 seconden.

De traagste was voor PHP 5866.5 seconden en voor MySQL 1237.07 seconden.

Uit deze getallen kunnen we concluderen dat een COUNT() in MySQL sneller is dan mysql_num_rows() in PHP.

Echter zijn er nadelen verbonden aan het gebruik van COUNT(). Zo heb je waarschijnlijk meer code nodig wat niet altijd even goed is voor de leesbaarheid. Ook in gevallen dat je mogelijk geen rijen terugkrijgt kun je dit vaak niet gebruiken.

Is je applicatie echt zo performance-kritisch en heb je een grote dataset, kan dit dus iets zijn om naar te kijken. Maar in het algemeen zou ik gewoon mysql_num_rows (of een dergelijk alternatief) aanraden.

Ten slotte: voor de geïnteresseerden is alles wat ik gebruikt hebt gewoon beschikbaar. De source van mijn benchmarkscript (met een kleine wijziging naar aanleiding van een tip van Daan) en de SQL voor de tabel met de statistieken De dataset wil ik op aanvraag sturen naar mensen die de SQL-file willen hebben.

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>

Welk platform kies je voor je weblog?

Saturday, July 28th, 2007

Sanne Roemen plaatste vandaag (technisch gezien gisteren) een interessant bericht op haar weblog: “Blogger, Typepad en Wordpress. Welk platform kies je voor je weblog?“. Nu hoor ik ook wel eens mensen die graag een eigen weblog zouden beginnen. Hoewel ik ondernemers dan graag doorverwijs naar Silverware waar een weblog ook tot de mogelijkheden behoort zijn niet al mijn bekenden ondernemers.

En aangezien Sanne altijd leuke handleidingen weeet te schrijven (eerder verwees ik al eens naar haar handleiding: abonneren op rss feeds met Bloglines.), dus waarom zelf het zware werk doen? ;)

In de reacties kwam het antwoord:

Misschien zou het net iets overzichtelijker zijn als al deze informatie in een tabel gezet zou worden? Is een idee. :-)

En de reactie van Sanne:

@Roel, thanx! Een tabel is inderdaad een idee. Eerlijk gezegd had ik geen zin meer om te html-en want in de typepad editor zit geen handig tabellending. (ben ook maar een mens :-D

Toen besloot ik dus dat ik toch een stukje zelf moest doen… De tabel maken. De meeste informatie komt direct uit het bericht van Sanne, dus alle credits naar haar voor het research daarvan - ik heb zelf maar een paar kleine dingen opgezocht.

Blogger Typepad Wordpress.com Wordpress.org
Eigen host nodig Nee Nee Nee Ja
Mogelijkheid tot statische pagina’s Nee Ja Ja Ja
Layout van je pagina De standaard templates zijn makkelijk te kiezen en als je je een beetje verdiept in de code kan je er ook vrij makkelijk wijzigingen in aanbrengen. Je werkt wel altijd binnen een stramien dus 100% vrijheid heb je nooit.. Sommige templates zijn makkelijker aan te passen dan anderen. De flexibele templates bieden een wysiwyg (what you see is what you get) interface waarmee je kleuren en letters kan aanpassen. Ook hier geldt dat je binnen een stramien werkt dus geen 100% vrijheid hebt. Je kan je template wel helemaal zelf bouwen met advanced templates, maar daar moet je toch wel behoorlijk technisch voor zijn. Keuze uit een ongeevenaarde hoeveelheid templates (hier heten ze ‘Thema’s’). De templates kunnen ook van elkaar verschillen qua functionaliteiten die ze bieden. Je hebt weinig vrijheid om ze zelf nog aan te passen, soms kan je een eigen grafische balk bovenaan plaatsen. Wordpress heeft mooiere templates dan de andere twee. Voor je zelf gehoste blog kan je kiezen uit de gratis templates die online beschikbaar zijn maar je kan er ook een helemaal zelf laten ontwerpen en bouwen (dan zit je met kosten voor een ontwerper en een programmeur). Hier heb je echter wel 100% vrijheid (rekening houdend met de ‘normale’ grenzen die internet stelt).
Plugins Beperkt aantal “widgets” Voor zover ik kan zien alleen voor statistieken, maar Movable Type (het platform waar TypePad op draait ondersteunt wel plugins)Ze bestaan wel zegt Sanne. Aantal plugins standaard ingebouwd, geen eigen plugins Een zeer ruime keuze aan plugins
Ondersteuning voor meerdere auteurs Ja Alleen met Pro Level ($149.50 per jaar) of Premium Level ($299.50) Ja Ja
Kosten Gratis Vanaf $49.50 Gratis Gratis (let op: je hebt wel hosting nodig wat niet gratis is)

Let op: een eigen domein als “digicted.nl” (dus niet digicted.wordpress.com) is niet gratis.

Silverware is niet opgenomen, omdat wij niet echt een blogservice zijn, maar het een onderdeel is van het totaalpakket. Voor de mensen die het toch graag willen weten: je hebt geen eigen host nodig, je kan een eigen logo uploaden, plugins zijn niet mogelijk, er is ondersteuning voor meerdere auteurs en een ondernemersabonnement is momenteel al beschikbaar vanaf 12 euro exclusief BTW.

Statische pagina’s zijn een beetje een verhaal apart bij ons, maar dat is buiten de scope van deze post. Kijk daarvoor naar de abonnementen van Silverware of neem contact op met mij.

Update: een oplettende lezer wees mij er op dat de prijzen niet meer klopte. Dit is waarschijnlijk misgegaan bij het terugzetten van een backup. Excuses hiervoor.