Seminarplanung mit Ruby

Veröffentlicht von Ramon Voges am 12.09.2017 9 Minuten zum Lesen

In diesem Beitrag zeige ich, wie mir Ruby bei dem Erstellen meiner Seminarpläne hilft. Dank eines kleinen Skriptes spare ich mir lästige Tipparbeit.

Das Problem

Zu Beginn eines Semesters erstelle ich für jedes Seminar, das ich gebe, einen Ablaufplan. Diese Pläne bieten den Studierenden eine Übersicht, wann welches Thema besprochen wird. Dafür verzeichnet eine Tabelle die Termine der einzelnen Sitzungen und ihren vorgesehenen Gegenstand.

Im Laufe meiner Lehrtätigkeit sind eine ganze Menge an Ablaufplänen zusammen gekommen. Jedesmal aufs Neue die Seminartermine herauszusuchen und dann von Hand Titel und Daten hinzuzufügen, ist mühselig und dröge. Dafür lässt sich doch bestimmt ein kleines Skript scheiben!

Ruby hilf!

Die Idee ist einfach: Ein Ruby-Skript sucht für mich in einem festgelegten Zeitraum alle Tage heraus, an denen eines meiner Seminare stattfindet. Diese Tage gibt dann mein Skript nach einander sortiert aus, sodass ich die fertige Liste nur noch in meine Tabelle auf meinem Verlaufsplan kopieren muss. Und am besten lässt sich das Programm nicht nur für eine Veranstaltung verwenden, sondern für alle möglichen.

Als erstes müssen wir dafür die Standard-Bibliothek date einbinden. Sie bietet alle Funktionen, die wir für die Berechnung unserer Termine benötigen. Anschließend legen wir unser Anfangs- und unser Enddatum fest. Die Termine entnehme ich der offiziellen Verlautbarung der Universität: Die Vorlesungszeit des Wintersemesters 2017/18 beginnt am 9. Oktober und endet am 2. Februar. Außerdem erstellen wir einen leeres Feld, in das wir unsere zu berechnenden Termine eintragen lassen.

require 'date'

semester_start = Date.new(2017, 10, 9)
semester_end = Date.new(2018, 2, 2)
dates = []

Als nächstes weisen wir den Ruby-Interpreter an, alle Termine innerhalb des Start- und Enddatums an einen Block zu übergeben. Dafür nutzen wir Rubys Fähigkeit, zwischen zwei Objekten einen Wertebereich mit den Methoden .. und ... aufzuspannen. Der Unterschied zwischen den beiden Methoden besteht darin, dass .. das Anfangs- und das Endobjekt mit einschließt, bei ... hingegen ist der Endpunkt vom Wertebereich ausgeschlossen. Für unseren Fall benötigen wir also ... Innerhalb des Blocks prüfen wir, ob der Termin ein Mittwoch ist. Wenn das zutrifft, also die Bedingung wahr ist, dann wird der Termin, der gerade an den Block übergeben wurde, in unser Datumsfeld eingetragen.

(semester_start..semester_end).each do |date|
  dates << date if date.wednesday?
end

Unter den berechneten Terminen befinden sich jetzt aber noch einige Feiertage, die wir im Seminarplan nicht berücksichtigen wollen. Wir schließen deshalb nachträglich die entsprechenden Daten aus. Dafür nutzen wir die Methode delete_if, die Arrays standardmäßig zur Verfügung stellen. delete_if greift auf jedes Objekt innerhalb des Feldes zurück und prüft, ob die im Block angegebene Bedingung zutrifft. In unserem Fall reichen uns vier Vergleiche mit den nicht erwünschten Daten. Falls in unserem Feld sich Datums-Objekte befinden, für die der Block true zurückgibt, wird das Objekt entfernt. Da es sich um relativ einfache Blöcke handelt, notieren wir sie mit geschweiften Klammern, also in der Kurzschreibweise.

# Löscht die Tage innerhalb Weihnachtsferien vom 24. Dezember
# bis zum 6. Januar.
dates.delete_if { |date| date > Date.new(2017, 12, 24) && date < Date.new(2018, 1, 6) }
# Löscht den Tag der Deutschen Einheit
dates.delete_if { |date| date == Date.new(2017, 10, 3) }
# Löscht Allerheiligen
dates.delete_if { |date| date == Date.new(2017, 11, 1) }

Danach formatieren wir die übrig gebliebenen Daten in unserem dates-Feld entsprechend dem üblichen deutschen Datumsformat DD.MM.YYYY. Hierfür verwenden wir die Methode collect, ergänzen sie allerdings um ein ‘!’ und machen sie damit zu einer Bang-Methode, das heißt zu einer destruktiven Methode, die das Objekt, auf dem sie angewendet wird, verändert. Mit der Zeile dates.collect! { |d| d.strftime('%d.%m.%Y') } bringen wir alle Datums-Objekte im Feld dates in das gewünschte Format.

Zu guter letzt müssen wir nur noch das Feld ausgeben. Das machen wir mit puts dates. Fertig! Wirklich?

Und die anderen Tage?

Damit wir auch für andere Tage unser kleines Skript nutzen können, müssen wir noch eine Verzweigung einbauen. Da wir unser Skript von der Kommandozeile aufrufen, macht es Sinn, dort auch den Tag anzugeben, nach dem wir suchen. Die Argumente, die beim Start an ein Skript übergeben werden, speichert Ruby in der Konstante ARGV. Mit ARGV.shift rufen wir das erste Objekt ab, das ARGV beim Start des Programms zugewiesen wurde. Wir legen daher zunächst den Wochentag, um den es uns gehen soll, in einer Variable ‘day’ ab: day = ARGV.shift.

Anschließend testen wir, ob das Argument beim Aufruf des Skripts tatsächlich angeben wurde. Falls nicht, informiert unser Programm kurz über seine Verwendung.

if day.nil?
  puts 'Outputs the dates of a given weekday in the winter semester 2017/18.'
  puts
  abort 'USAGE: semester_dates weekday'
end

Daraufhin ersetzen wir unseren kurzen Block, in dem wir prüfen, ob es sich bei dem weitergereichten Datum aus dem gewünschten Zeitraum um einen Mittwoch handelt, mit einer case-Verzweigung. Sie prüft, welcher Wochentag in day gespeichert wurde, und wählt die entsprechende Methode aus, um die gesuchten Tage in das dates-Feld abzulegen.

(semester_start..semester_end).each do |date|
  case day
  when 'monday'
    dates << date if date.monday?
  when 'tuesday'
    dates << date if date.tuesday?
  when 'wednesday'
    dates << date if date.wednesday?
  when 'thursday'
    dates << date if date.thursday?
  when 'friday'
    dates << date if date.friday?
  when 'saturday'
    dates << date if date.saturday?
  when 'sunday'
    dates << date if date.sunday?
  else
    abort 'ERROR: Please enter a weekday as an argument.'
  end
end

Falls das angegebene Argument nicht gefunden wird, wirft das Skript noch einen kurzen Fehler aus.

Um das Skript auch ohne ruby [Skriptname].rb aufrufen zu können, fügen wir am Anfang noch ein Shebang hinzu, damit unser Kommandozeileinterpreter weiß, dass es sich um ein Ruby-Skript handelt: #!/usr/bin/env ruby.

Das fertige Skript

Der Rest bleibt, wie gehabt. Alles in allem sieht dann unser Skript wie folgt aus:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/usr/bin/env ruby

require 'date'

semester_start = Date.new(2017, 10, 9)
semester_end = Date.new(2018, 2, 2)
dates = []
day = ARGV.shift

if day.nil?
  puts 'Outputs the dates of a given weekday in the winter semester 2017/18.'
  puts
  abort 'USAGE: semester_dates weekday'
end

(semester_start..semester_end).each do |date|
  case day
  when 'monday'
    dates << date if date.monday?
  when 'tuesday'
    dates << date if date.tuesday?
  when 'wednesday'
    dates << date if date.wednesday?
  when 'thursday'
    dates << date if date.thursday?
  when 'friday'
    dates << date if date.friday?
  when 'saturday'
    dates << date if date.saturday?
  when 'sunday'
    dates << date if date.sunday?
  else
    abort 'ERROR: Please enter a weekday as an argument.'
  end
end

# Delete the days during Christmas Holidays
dates.delete_if { |date| date > Date.new(2017, 12, 24) && date < Date.new(2018, 1, 6) }
# Delete if Tag der Deutschen Einheit
dates.delete_if { |date| date == Date.new(2017, 10, 3) }
# Delete if Allerheiligen
dates.delete_if { |date| date == Date.new(2017, 11, 1) }

# Format dates according to DD.MM.YYYY
dates.collect! { |d| d.strftime('%d.%m.%Y') }

puts dates

Fazit

Mit ein paar Zeilen Code haben wir es geschafft, uns für jede Lehrveranstaltung und den dazugehörigen Verlaufsplan eine Menge Tipparbeit zu sparen. Mehr noch, wir laufen nicht Gefahr, uns beim Ablesen der Daten z.B. aus einem Terminkalender zu vertun. Wir sparen also mithilfe von Ruby nicht nur Arbeit, sondern vermeiden auch Fehlerquellen.