Das Letzte seiner Art, oder: Textpattern-Artikel in Jekyll importieren

Gestern war es soweit: Ich habe mein letztes Textpattern abgeschaltet. Das erste Blog »das Netzbuch« moderte in seinem guten alten Textpattern noch fröhlich auf dem Server herum, nach all den Jahren noch stets eifrig besucht von Bots und Google-Kundschaft, ca. 300 bis 400 Visits/Tag. Was schon erstaunlich ist, wer einmal in der Maschine steckt bleibt für immer drin…

Im Rahmen einer »Weblog-Archiv-Konsolidierung« wurde alles Erhaltenswerte in Textile-Dateien konvertiert (dazu mehr hinter dem »Weiter«) und mit Jekyll in statisches HTML umgewandelt, damit man sich nicht wg. eines 10 Jahre alten Blogs ständig um Updates des CMS kümmern muss.

Nun kann das ganze gebloggte Geseier aus 14 Jahren fröhlich, ohne Angst vor dem ge0wned-werden, auf dem Server herummodern. Die gesamte Blog-Historie dieses kleinen Blogs, das erst »das Netzbuch« (von 2002 bis 2006), dann »uninformation.org« (2006 bis 2009) und seit 2010 »der Uninformat« heisst, ist nun im Archiv zugänglich.

12 Jahre Textpattern

Im Juni 2004 hatte ich das damalige Blog »das Netzbuch« von der mittlerweile noch als »bloated« ExpressionEngine herumvegetierenden pMachine auf Textpattern umgestellt und nie ein größeres Problem damit gehabt. Die Updates hielten sich in Grenzen, es gab keine wirklich schlimmen Sicherheitsprobleme, und alles funktionierte stets wie es funktionieren soll. Ein gutes Werkzeug!

Im Grunde kann man Textpattern auch heute noch problemlos verwenden. Auch wenn mir im Laufe der Jahre das Konzept, alle Templates der Site in HTML-Forms zu packen und in der Datenbank abzulegen, immer weniger gefiel. Aber das ist natürlich Geschmackssache.

Da ich aber mit meinen Blogs gerne herumspiele und -bastle und heutzutage nun einmal Ruby mein bevorzugtes Spielzeug in dieser Hinsicht ist, war es Zeit alle Varianten meiner 14 Jahre Bloggen in ein Werkzeug (Jekyll) zu »konsolidieren«. Um es mal in der allseits beliebten Floskelsprache wg. des Geldes wechselnder Fußballer auszudrücken: »Das war eine Entscheidung für Jekyll, und nicht gegen Textpattern…«

Ein einfacher Textpattern-Importer

Jekyll bringt von Haus aus einen Importer für Textpattern mit, der für meine Zwecke aber nicht geeignet war. Denn man kann dort wenig konfigurieren, ich wollte aber für die alten Artikel die grundsätzliche Struktur der TXP-Permalinks erhalten. Diese sehen so aus, also Section (article), Artikel-ID (1680) und »Slug« (good-bye-pmachine-hello-textpattern):

http://www.das-netzbuch.de/article/1680/good-bye-pmachine-hello-textpattern/

Dieser Permalink sollte für Jekyll umgewandelt werden in:

https://uninform.at/netzbuch/1680/good-bye-pmachine-hello-textpattern/

Lösung: Man schreibt sich flugs selbst einen Konverter, was nicht so übermäßig schwierig ist. Mit gem install sequel macht man sich vorher einen Datenbank-Zugang verfügbar (zur TXP-Datenbank natürlich), liest die Daten aus, und schreibt sie als Textile-Dateien in den Ordner _posts.

Das Besondere: Man schreibt in das Jekyll-Front-Matter explizit den gewünschten Permalink (und weiteres Front Matter nach eigenem Gutdünken zur Verarbeitung in den Templates), so dass Jekyll nicht die in der _config.yml definierte Standard-Konfiguration für Permalinks verwendet, sondern die gewünschte zu erhaltende TXP-Logik.

So ein einfacher Importer sieht so aus:

require 'rubygems'
require 'sequel'
require 'fileutils'

# DB-Zugangsdaten, ggf. :encoding anpassen:
db = Sequel.mysql(
'bonnie_auld_txp_db', 
:user => 'dbchef', 
:password => 'db123', 
:host => 'localhost', 
:encoding => 'latin1')

# Query, mit Category1 NOT IN kann man unerwuenschte
# Kategorien ausschließen:
sql = "SELECT 
  Posted,Title,Body,Excerpt,Category1,Category2,
  url_title,ID,Keywords 
  FROM textpattern 
  WHERE Status=4 AND Section='article' 
  AND Category1 NOT IN ('Welt als solche')
  ORDER BY Posted ASC"

# Ueber das Ergebnis iterieren
db[sql].each do |post|

  # Dateiname erstellen
  fname = "#{post[:Posted].strftime("%Y-%m-%d")}-#{post[:ID]}-#{post[:url_title]}.textile"
  
  # Der gewuenschte Permalink
  permalink = "/netzbuch/#{post[:ID]}/#{post[:url_title]}/"

  # TXP-Posted in Time-Objekt umwandeln
  post_time = Time.local(
    post[:Posted].strftime("%Y").to_i,
    post[:Posted].strftime("%m").to_i,
    post[:Posted].strftime("%d").to_i,
    post[:Posted].strftime("%H").to_i,
    post[:Posted].strftime("%M").to_i)

  # Datei schreiben
  File.open("_posts/#{fname}", "w") do |f|
    f.puts "---"
    f.puts "layout: post_netzbuch"
    f.puts "title: '#{post[:Title].gsub(/\'/,'')}'"
    f.puts "date: #{post_time.to_s.gsub(/\ \+/,"+")}"
    f.puts "categories: #{post[:Category1].gsub(/\ /,"")} #{post[:Category2].gsub(/\ /,"")} "
    f.puts "tags: #{post[:Keywords]}"
    # source ist eine Hilfsvariable die man
    # ggf in Templates verwenden kann
    f.puts "source: netzbuch"
    f.puts "permalink: #{permalink}"
    f.puts "---"
    # Trennung von Excerpt und Body mit <!--more-->
    # Kann in Templates verarbeitet werden
    unless post[:Excerpt]==""
      f.puts ""
      f.puts post[:Excerpt]
      f.puts ""
      f.puts "<!--more-->"
    end
    f.puts ""
    f.puts post[:Body]
    f.puts ""
  end

end

Die Umwandlung von »Posted« in das Ruby Time-Objekt post_time sieht ein bisschen wild aus. Das wurde gemacht, um das Artikel-Datum im passenden Format im Front Matter ausgeben zu können. Desweiteren wurde für die alten Artikel ein spezielles Layout definiert mit layout: post_netzbuch. Und im SQL eine Kategorie ausgeschlossen, die fast nur aus Rants bestand, die selbst mir heutzutage peinlich sind. ;-) Das kann man natürlich nach Belieben anpassen.

Diesen Code packt man in eine Ruby-Datei (bspw. importer.rb) im Root-Verzeichnis des Jekyll-Blogs und führt sie dann mit ruby importer.rb aus. Und hat danach im Verzeichnis _posts alle TXP-Artikel liegen.

Bonus: Alte TXP-Kommentare statisch erhalten

Den Konverter kann man nun noch aufbohren, indem man die Kommentare zu jedem Artikel ausliest und als statisches HTML ablegt. Denn gerade »damals«, als noch viel in Blogs kommentiert wurde, war ja manch wertvoller Hinweis in den Kommentaren eines Blog-Posts enthalten.

Also baut man in die Schleife über die ausgelesenen Artikel eine weitere SQL-Abfrage mit Schleife zum Auslesen der Kommentare aus der TXP-DB ein:

require 'rubygems'
require 'sequel'
require 'fileutils'

# DB-Zugangsdaten, ggf. :encoding anpassen:
db = Sequel.mysql(
'bonnie_auld_txp_db', 
:user => 'dbchef', 
:password => 'db123', 
:host => 'localhost', 
:encoding => 'latin1')

# Query, mit Category1 NOT IN kann man unerwuenschte
# Kategorien ausschließen:
sql = "SELECT 
  Posted,Title,Body,Excerpt,Category1,Category2,
  url_title,ID,Keywords 
  FROM textpattern 
  WHERE Status=4 AND Section='article' 
  AND Category1 NOT IN ('Welt als solche')
  ORDER BY Posted ASC"

# Ueber das Ergebnis iterieren
db[sql].each do |post|

  # Dateiname erstellen
  fname = "#{post[:Posted].strftime("%Y-%m-%d")}-#{post[:ID]}-#{post[:url_title]}.textile"
  
  # Der gewuenschte Permalink
  permalink = "/netzbuch/#{post[:ID]}/#{post[:url_title]}/"

  # TXP-Posted in Time-Objekt umwandeln
  post_time = Time.local(
    post[:Posted].strftime("%Y").to_i,
    post[:Posted].strftime("%m").to_i,
    post[:Posted].strftime("%d").to_i,
    post[:Posted].strftime("%H").to_i,
    post[:Posted].strftime("%M").to_i)

  # * * * KOMMENTARE AUSLESEN UND IN HTML PACKEN:
  # Zähler-Variable
  c_count = 0
  # Nimmt das HTML der Kommentare auf,
  # bleibt einfach leer wenn es keine gibt
  comments = ''

  # SQL-Abfrage
  csql = "SELECT name, posted, web, message
    FROM txp_discuss 
    WHERE visible=1 AND parentid=#{post[:ID]} 
    ORDER BY posted ASC"

  # Über Kommentare iterieren und in beliebiges HTML packen:
  db[csql].each do |c|
    if c[:web].empty?
      name = "<p><b>#{c[:name]} am #{c[:posted]}:</b></p>"
    else
      link = c[:web].gsub(/http\:\/\//,"")
      name = "<p><b><a href=\"http://#{link}\">#{c[:name]}</a> am #{c[:posted].strftime("%d.%m.%Y")}:</b></p>"
    end
    comments << "<article>#{name}
    #{c[:message]}</article>"

    c_count = c_count + 1
  end
  # * * * /KOMMENTARE

  # Datei schreiben
  File.open("_posts/#{fname}", "w") do |f|
    f.puts "---"
    f.puts "layout: post_netzbuch"
    f.puts "title: '#{post[:Title].gsub(/\'/,'')}'"
    f.puts "date: #{post_time.to_s.gsub(/\ \+/,"+")}"
    f.puts "categories: #{post[:Category1].gsub(/\ /,"")} #{post[:Category2].gsub(/\ /,"")} "
    f.puts "tags: #{post[:Keywords]}"
    # source ist eine Hilfsvariable die man
    # ggf in Templates verwenden kann
    f.puts "source: netzbuch"
    f.puts "permalink: #{permalink}"
    f.puts "---"
    # Trennung von Excerpt und Body mit <!--more-->
    # Kann in Templates verarbeitet werden
    unless post[:Excerpt]==""
      f.puts ""
      f.puts post[:Excerpt]
      f.puts ""
      f.puts "<!--more-->"
    end
    f.puts ""
    f.puts post[:Body]
    f.puts ""

    # Kommentare ausgeben, wenn c_count > 0
    if c_count > 0
      f.puts "<!--comments-->"
      f.puts "<div class=\"archiv_kommentare\">"
      if c_count==1
        f.puts "<h4>1 Kommentar</h4>"
      else
        f.puts "<h4>#{c_count} Kommentare</h4>"
      end
      f.puts comments
      f.puts "</div>"
    end

  end

end

Und schon hat einen Ordner voller alter TXP-Article nebst Kommentaren, die mit Jekyll statisch gerendert und damit bis ans Ende der Zeiten erhalten werden können…

Ergebnis

Für die Artikel aus uninformation.org habe ich ein ähnliches Skript gebaut, so dass nun sämtliches erhaltenswertes persönliches Geblogge seit 2002 hier im Uninformat statisch abgelegt ist.

Irgendwelche Prognosen und vollmundigen Ankündigungen zu zukünftigen Blogaktivitäten ersparen wir uns heute, wir werden sehen was es hier geben wird. Und zumindest drüben im Fußball-Blog wird, wenn auch etwas monothematisch, gebloggt wie einst im Mai…

textpattern jekyll weblogs