Ein Beispiel für Literate Programming mit Emacs org-mode

Einführung

org-mode wird als das schweizer Taschenmesser des Editors Emacs beschrieben. Das ist in der Tat eine gute Beschreibung. Mit org-mode lassen sich Projekte und Aufgaben planen, Bücher verfassen, Musik komponieren, Weblogs führen, Webseiten veröffentlichen, Zeiten erfassen, statistische Berichte dynamisch verfassen, und noch einiges mehr.

Eine der für Softwareentwickler interessanten Möglichkeiten, ist die weitgehend sprachagnostische „Literate Programming“ Unterstützung, die ich an einem kurzen, einfachen Beispiel eines Shell-Programms im Folgenden vorstelle.

Das von Donald E. Knuth propagierte literate Programming besteht in erster Linie aus das Programm erklärender Prosa, in die der Programmtext eingebettet wird. Dabei können über Referenzen (Namen von Codeblöcken) an anderer Stelle eingeführte Konzepte eingebunden werden und so die Reihenfolge der Dokumentation vom zu generierenden Code entkoppelt werden.

Das Generieren der Dokumentation wird als „weave“ bezeichnet, das Generieren des Code als „tangle“.

Der nächste Abschnitt entspricht der aus dem literate programmierten Kleinstwerkzeug erzeugten Dokumentation. Im Abschnitt danach betrachten wir das Endprodukt, das Shellprogramm, sowie den org-mode-Code, der dieses Dokument erzeugt.

Das Beispiel: Prozentual gefülltestes Blockgerät bestimmen

Wir konstruieren hier ein Shell-Programm, das von den aktuell eingebundenen Blockgeräten den Mountpunkt des prozentual gefülltestem ausgibt.

Das Vorgehen ist denkbar simpel: Wir benutzen klassische Unixwerkzeuge in einer Aufrufpipeline.

Das Beispiel ist direkt übernommen, aber übersetzt und deutlich ausgeweitet. Das Original ist in der org-mode Dokumentation zu finden.

Alle eingehängten Geräte bestimmen

Wir bestimmen erst einmal alle aktuell eingebundenen Geräte. Sicherlich könnten mir dazu mount nutzen und dann mit einiger Magie über die Ausgabe iterieren. Einfacher ist es jedoch, df zu bemühen ((mount-info)), da dessen Ausgabe bereits alle Informationen enthält, die wir benötigen.

Beachte die Leerzeile am Ende des Codeblocks: Sie ist nötig, um im generierten („tangled“) Code eine neue Zeile hinter dem Backslash zu erhalten. Ich denke das ist ein Bug in der aktuellen org-mode Version, der mit padline Behandlung bei noweb-Dereferenzierung zu tun hat. Edit: In org-mode Versionen/Releases ab heute (23.01.2012) ist das Leerzeilenproblem bei dieser Art von tangling über Unterbäume behoben; Eric Schulte hat, auf das Problem aufmerksam gemacht, im Handumdrehen eine Lösung eingecheckt.

1: df  (mount-info)
2: 

Die Kopfzeile entfernen

Die Ausgabe von df beginnt mit einer informativen Kopfzeile, die die einzelnen Spalten der Ausgabe benennt. Wir schneiden sie mit sed weg ((cut-head)).

3: | sed '1d'  (cut-head)
4: 

Die Ausgabe sortieren

Wir extrahieren die uns interessierenden Informationen – Prozentualer Füllungsgrad und Einhängepunkt – mittels awk ((extract-info)), so dass wir Zeilen der Form „Füllung-in-Prozent Einhängepunkt“ erhalten. Das Ergebnis können wir mit sort numerisch aufsteigend sortieren ((sort)).

5: | awk '{print $5 " " $6}' (extract-info)
6: | sort -n  (sort)
7: 

Den Einhängepunkt bestimmen

Da wir in der Pipeline aktuell alle eingehängten Geräte, aufsteigend nach ihrem prozentualem Füllungsgrad sortiert vorliegen haben, gestaltet sich der Rest der Aufgabe recht einfach: Wir extrahieren die letzte Zeile mittels tail -n 1 ((trim)) und nutzen ein weiteres Mal awk, um die Spalte der Zeile zu extrahieren die uns interessiert: Den Einhängepunkt ((final)).

 8: | tail -n 1  (trim)
 9: | awk '{print $2}' (final)
10: 

Das generierte Shell-Programm

1: #!/bin/sh
2: 
3: df  
4: | sed '1d'  
5: | awk '{print $5 " " $6}' 
6: | sort -n  
7: | tail -n 1  
8: | awk '{print $2}'
9: 

Der org-mode Code

#+POSTID: 411
#+DATE: [2011-12-09 Fri 11:29]
#+OPTIONS: toc:nil num:nil todo:nil pri:nil tags:nil ^:nil TeX:nil
#+CATEGORY: Geeks!
#+TAGS: Emacs, literate programming, org-mode
#+DESCRIPTION:
#+TITLE: Ein Beispiel für Literate Programming mit Emacs =org-mode=
#+EXPORT_EXCLUDE_TAGS: ignoreExport

* Einführung
[[http://orgmode.org][=org-mode=]] wird als das schweizer Taschenmesser des Editors Emacs
beschrieben. Das ist in der Tat eine gute Beschreibung. Mit =org-mode=
lassen sich Projekte und Aufgaben planen, Bücher verfassen, Musik
komponieren, Weblogs führen, Webseiten veröffentlichen, Zeiten
erfassen, statistische Berichte dynamisch verfassen, und noch einiges
mehr.

Eine der für Softwareentwickler interessanten Möglichkeiten, ist
die weitgehend sprachagnostische  "Literate Programming"
Unterstützung, die ich an einem kurzen, einfachen Beispiel eines
Shell-Programms im Folgenden vorstelle. 

Das von Donald E. Knuth propagierte literate Programming besteht in
erster Linie aus das Programm erklärender Prosa, in die der
Programmtext eingebettet wird. Dabei können über Referenzen (Namen von
Codeblöcken) an anderer Stelle eingeführte Konzepte eingebunden werden
und so die Reihenfolge der Dokumentation vom zu generierenden Code
entkoppelt werden.

Das Generieren der Dokumentation wird als "weave" bezeichnet, das
Generieren des Code als "tangle".

Der nächste Abschnitt entspricht der aus dem literate programmierten
Kleinstwerkzeug erzeugten Dokumentation. Im Abschnitt danach
betrachten wir das Endprodukt, das Shellprogramm, sowie den
org-mode-Code, der dieses Dokument erzeugt. 

* Das Beispiel: Prozentual gefülltestes Blockgerät bestimmen
:PROPERTIES:
:noweb-ref: fullest-disk
:END:
Wir konstruieren hier ein Shell-Programm, das von den aktuell
eingebundenen Blockgeräten den Mountpunkt des prozentual gefülltestem
ausgibt.

Das Vorgehen ist denkbar simpel: Wir benutzen klassische Unixwerkzeuge
in einer Aufrufpipeline.

Das Beispiel ist direkt übernommen, aber übersetzt und deutlich
ausgeweitet. Das Original ist in der =org-mode= [[http://orgmode.org/org.html#noweb-ref][Dokumentation zu
finden]].

** Alle eingehängten Geräte bestimmen
Wir bestimmen erst einmal alle aktuell eingebundenen Geräte. Sicherlich
könnten mir dazu =mount= nutzen und dann mit einiger Magie über die
Ausgabe iterieren. Einfacher ist es jedoch, =df= zu bemühen ([[(mount-info)]]), da dessen
Ausgabe bereits alle Informationen enthält, die wir benötigen.

Beachte die Leerzeile am Ende des Codeblocks: Sie ist nötig, um im
generierten ("tangled") Code eine neue Zeile hinter dem Backslash zu
erhalten. Ich denke das ist ein Bug in der aktuellen =org-mode=
Version, der mit =padline= Behandlung bei =noweb=-Dereferenzierung zu
tun hat.
#+begin_src sh -n
 df  (mount-info)

#+end_src

** Die Kopfzeile entfernen
Die Ausgabe von =df= beginnt mit einer informativen Kopfzeile, die die
einzelnen Spalten der Ausgabe benennt. Wir schneiden sie mit =sed=
weg ([[(cut-head)]]).

#+begin_src sh +n
 | sed '1d'  (cut-head)

#+end_src

** Die Ausgabe sortieren
Wir extrahieren die uns interessierenden Informationen -- Prozentualer
Füllungsgrad und Einhängepunkt -- mittels =awk= ([[(extract-info)]]), so dass wir Zeilen der
Form "Füllung-in-Prozent  Einhängepunkt" erhalten. Das Ergebnis können wir
mit =sort= numerisch aufsteigend sortieren ([[(sort)]]).

#+begin_src sh +n
 | awk '{print $5 " " $6}' (extract-info)
 | sort -n  (sort)

#+end_src

** Den Einhängepunkt bestimmen
Da wir in der Pipeline aktuell alle eingehängten Geräte, aufsteigend nach
ihrem prozentualem Füllungsgrad sortiert vorliegen haben, gestaltet
sich der Rest der Aufgabe recht einfach: Wir extrahieren die letzte
Zeile mittels =tail -n 1= ([[(trim)]]) und nutzen ein weiteres Mal =awk=, um die
Spalte der Zeile zu extrahieren die uns interessiert: Den
Einhängepunkt ([[(final)]]).
#+begin_src sh +n
 | tail -n 1  (trim)
 | awk '{print $2}' (final)

#+end_src

* Das generierte Shell-Programm

#+begin_src sh -r -n :tangle no :noweb yes :shebang #!/bin/sh
    #!/bin/sh

    <<fullest-disk>>
#+end_src

* Der =org-mode= Code
#+INCLUDE: test_sh_tabgle.org example

* Programm erstellen /oder/ tangle                            :ignoreExport:
  :PROPERTIES:
  :comments: no
  :ID:       579cc2c8-2c45-4e28-acaf-2863986e37eb
  :END:

Dieser Abschnitt wird bei der Generierung der Dokumentation
("weaving") nicht berücksichtigt. Er sorgt aber durch den folgenden
Codeblock, bzw. dessen Header, zur Zusammenführung des oben erklärten
und geschriebenen Code.

- =:tangle yes= sorgt dafür, dass der Block beim Generieren des Code
  berücksichtigt wird
- =:noweb yes= sorgt dafür, dass NoWeb Referenzen aufgelöst werden.
  Da der =org-mode=-Teilbaum "Prozentual gefülltestes..." in seinem
  ~PROPERTIES~-Drawer =:noweb-ref: fullest-disk= gesetzt hat, führt
  das Auflösen der Referenz hier zum Konkatenieren aller Codeblöcke
  dieses Teilbaumes, und damit zu unserem vollständigen Programm.
- =:shebang #!/bin/sh= lässt =org-mode= die generierte Datei mit der
  angegebenen Zeile beginnen und das executable-Bit setzen.

#+begin_src sh -r  :tangle yes :noweb tangle :shebang #!/bin/sh
    <<fullest-disk>>
#+end_src

#+results:

#  LocalWords:  Kleinstwerkzeug gefülltestes Unixwerkzeuge
#  LocalWords:  Aufrufpipeline