Mein Blog komplett in R Markdown

Wie es dazu kam…

In den letzten Jahren habe ich für meine Blog Wordpress verwendet. Was mich dabei immer störte, war die Tatsache, dass ich sehr viel Zeit für Updates und Patching aufwenden musst. Und auch das Einbinden von Code und Grafiken war nicht so einfach, wie ich mir das wünschte. Ich bin der Ansicht, dass ein Grossteil dieser Sicherheitsprobleme aus dem Einsatz von PHP und Script-Sprachen auf dem Server herrührt.
Um das zu umgehen, gibt es meiner Meinung nach zwei Lösungsansätze:
* Reine statische Seiten, daher Verzicht auf Code-Ausführung auf dem Server. Leider selten wirklich möglich. * Verwendung von statisch typisierten Sprachen wie Java oder C# um mindestens die Parameter-Valdidierung zu vereinfachen.

Für mich war klar, dass für einen Blog die erste Variante die einfachste und am wenigstens Wartungs-Intensive Variante ist. Bis auf das Kontaktformular, welches für meinen Blog nicht wirklich nötig ist, brauche ich keine serverseitigen Code.

Also suchte ich nach einem Generator für statische Websites, welcher meine folgenden Anforderungen erfüllt: * Generierung statischer Websites auf meinem Rechner * keine Abhängigkeit auf serversetige Technologie wie PHP * Trennung von Inhalt und Darstellung (soweit möglich) * Erstellung von Inhalten in einheitlichem Design * Möglichktei um R Markdown und Python Notebooks einzubinden

Als erstes testete ich generatoren für statische Websites wie MkDocs und Hexo. Beide machten einen guten Eindruck, sind aber nicht so einfach zu konfigurieren. So richtig passte das noch nicht für meine Zwecke und somit blieb es bei Experimenten.

Da ich im Rahmen meiner DataScience Ausbildung immer mehr mit R Markdown, Shiny und Notebook Technologien wie Jupyter-Notebooks und Zeppelin-Notebooks arbeitete, kam der Wunsch auf, diese Exporte gleich im Blog verwenden zu können. Leider fand ich bis letzte Woche keine vernünftige Lösung dies in ein einheitliches Blog-Design zu giessen.

R Markdown für einen Blog? Ist das nicht zu kompliziert?

Nein ist es nicht. Eigentlich braucht es nur klassisches R Markdown plus eine _style.yml Datei plus ein Inhaltsverzeichnis.

In den letzten Wochen erstellte ich einige R Markdown Dokumente und stiess dabei auf die Möglichkeit gesamte Websites mittels R Markdown zu erstellen. Alles was dazu notwendig ist, ist eine _style.yml Datei, welche die allgemeinen Eigenschaften, wie Navigationsbar und Stylesheets definiert. Viel mehr ist nicht nötig. Eine gute Einführung findet man im Artikel R Markdown Websites

Der folgende Block zeigt die _style.yml dieses Blogs:

name: "bitweber.ch"
navbar:
  title: "bitweber.ch"
  left:
    - text: "Home"
      icon: fa-home
      href: index.html
    - text: "Info"
      icon: fa-info
      menu:
        - text: "Über..."
          href: about.html
        - text: "Impressum"
          href: impressum.html
output:
  html_document:
    theme: cerulean
    highlight: textmate
    css: styles.css

Einzelne Artikel sind noch kein Blog… Es braucht noch ein Inhaltsverzeichnis!

Um das Inhaltsverzeichnis zu generieren bediene ich mich zweier Quellen: Dem Dateinamen, der in einem bestimmten Format einen Artikel kennzeichnet und dem sogenannten Front Matter im Header einer Rmd-Datei, welcher Metadaten zur Datein enthält. Der Dateiname muss dazu nach einem bestimmten Muster aufgebaut sein, das mittels Regular Expressions erkannt werden kann. Wie das genau aussieht ist eigentlich egal. Ich finde es aber als hilfreich, wenn durch das Format gleich die passende Reihenfolgen entsteht. Somit habe ich mich für folgendes Schema entschieden: YYYY-MM-DD_Titel.Rmd.

Das Inhaltsverzeichnis wird durch den folgenden Code-Chunk mit den Optionen {r echo=FALSE, message=FALSE, results='asis'} erstellt:

library (plyr)
library(dplyr)
require(rmarkdown)
  
getPosts <- function(file.pattern){
  posts.filenames <- dir(pattern = file.pattern, ignore.case = T)
  posts.listOfFrontmatters <- lapply(posts.filenames, FUN = function(x){
    fm <- yaml_front_matter(x)
    append(list(filename = x), fm)
  })
  posts.df <- ldply(posts.listOfFrontmatters, data.frame, stringsAsFactors=FALSE)
  posts.df$link <- paste(file_path_sans_ext(posts.df$filename), ".html", sep = "")
  posts.df <- arrange(posts.df, desc(filename))
  posts.df
}

library(htmltools)
library(tools)
getPostSummary <- function(post) {
  as.character(
    tags$div(class="col-sm-12",
      tags$h3(class="blog-post-toc-title", post["title"]),
      tags$p(class="blog-post-toc-info", paste(post["date"], post["author"], sep = ", ")),
      tags$p(class="blog-post-toc-abstract", post["abstract"]),
      tags$p(tags$a(class="blog-post-toc-link", title = post["title"], href = post["link"], "Weiter lesen")),
      tags$br(),
      tags$hr()
    )
  )
}

posts <- getPosts("(\\d{4}-\\d{2}-\\d{2}_)(.+)(\\.Rmd)")
toc <- apply(posts, 1, getPostSummary)
cat(paste(toc, collapse = ' '))

Ein vollständiger Blog in R Markdown

  1. März 2017, Martin Weber

    In diesem Beitrag wird beschrieben, wie dieser Blog komplett in R Markdown erstellt wird. Ausserdem wird die Erstellung eines Inhaltsverzeichnisses mittels R-Code beschrieben. Das Inhaltsverzeichnis wird aus den Feldern in den Front Matter Headern der R Markdown Artikel generiert.

    Weiter lesen



Durch die Funktion getPosts("(\\d{4}-\\d{2}-\\d{2}_)(.+)(\\.Rmd)") werden die Artikel Dateien anhand des Dateinamens ermittelt. Für jeden Eintrag in dieser Liste wird dann mittels getPostSummary ein Eintrag für das Inhaltsverzeichnis erstellt. Hier gab es noch Probleme, wenn die div-Tags zu tief verschachtelt werden. Dann interpretiert der pandoc-Markdown Konverter das als Code-Schnipsel und fügt <pre><code>...</code></pre> rund um die Tags ein und erstellt so Code-Schnippsel für die inneren Tags. Um dies zu verhindern, muss die md_extension raw_html hinzugefügt werden und markdown_in_html_blocks entfernt werden. Dies geschieht mittels YAML im Front Matter:

---
title: "Willkommen auf bitweber.ch"
output: 
  html_document:
    md_extensions: +raw_html-markdown_in_html_blocks
---

Jetzt braucht es nur noch Artikel für den Blog.

Den ersten Artikel lesen Sie hier gerade. Damit das Inhaltverzeichnis generiert werden kann, kann eine normale R Markdown Datei erstellt werden, deren Dateiname dem gegebenen Muster entspricht, hier YYYY-MM-DD_Titel.Rmd als z.B. für diesen Artikel 2017-03-12_Ein_Blog_in_RMarkdown.Rmd.

Für das Inhaltsverzeichnis müssen im Front Matter die Attribute title, author, date, abstract gsetzt sein:

---
title: "Ein vollständiger Blog in R Markdown"
author: "Martin Weber"
date: "12. März 2017"
abstract: "In diesem Beitrag wird beschrieben, wie dieser Blog komplett in R Markdown erstellt wird. Ausserdem wird die Erstellung eines Inhaltsverzeichnisses mittels R-Code beschrieben. Das Inhaltsverzeichnis wird aus den Feldern in den Front Matter Headern der R Markdown Artikel generiert."
output: html_document
---

So wirds gemacht…

Um nun jeweils aus den R Markdown Quellen den kompletten Blog zu generieren, wird der Befehl rmarkdown::render_site() in der Konsole abgesetzt. Um aufzuräumen kann der Befehl rmarkdown::clean_site() verwendet werden.

Fazit

Im Grossen und Ganzen ist das für mich die perfekte Lösung für meinen Blog. Der einzige Nachteil ist, dass damit noch keine Formulare definiert werden können. Dazu ist dennoch serverseitiger Code nötig. Dies ist durch die Einbindung von z.B. PHP-Dateien problemlos möglich.