Singleton-
# Methode definiert. Und \#{} wird interpoliert, wenn die
# Singleton-Methode aufgerufen wird.
eigenclass.class_eval %Q{
def #{m}(*args, &block)
begin
STDERR << "Eintritt: #{m}(\#{args.join(', ')})\n"
result = super
STDERR << "Ausgang: #{m} with \#{result}\n"
result
rescue
STDERR << "Abbruch: #{m}: \#{$!.class}: \#{$!.message}"
raise
end
end
}
end
end
# Tracing der angegebenen oder aller Methoden beenden
def untrace!(*methods)
# Wenn keine Methoden angegeben wurden, das
# Tracing aller zurzeit überwachten Methoden beenden
if methods.size == 0
methods = @_traced
STDERR <<
"Beende Tracing aller Methoden für #{object_id}\n"
else # Andernfalls Tracing aufheben
methods.map! {|m| m.to_sym } # Strings für alle angegebenen Tracing-Methoden in
# Symbole konvertieren
methods &= @_traced
STDERR <<
"Beende Tracing von #{methods.join(', ')} für #{object_id}\n"
end
# Aus der Menge der Tracing-Methoden entfernen
@_traced -= methods
# Tracing-Singleton-Methoden aus der Eigenclass entfernen
# Beachten Sie, dass class_eval hier einen Block und keinen String
# auswertet.
(class << self; self; end).class_eval do
methods.each do |m|
# undef_method würde nicht korrekt funktionieren.
remove_method m
end
end
# Wenn kein Methoden-Tracing mehr stattfindet, unsere Instanz-
# variable entfernen
if @_traced.empty?
remove_instance_variable :@_traced
end
end
end
Listing 8.10 Chaining mit Singleton-Methoden für Tracing
8.12 Domänenspezifische Sprachen
Der Zweck der Metaprogrammierung in Ruby besteht oft in der Erzeugung domänenspezifischer Sprachen ( domain-specific languages oder DSLs ). Eine DSL ist einfach eine Erweiterung der Ruby-Syntax (durch Methoden, die wie Schlüsselwörter aussehen) oder eine API, die Ihnen ermöglicht, auf natürlichere Weise ein Problem zu lösen oder Daten darzustellen, als es anderweitig möglich wäre. Für unsere Beispiele betrachten wir die Ausgabe formatierter XML-Daten als Problemdomäne und definieren zwei DSLs — eine sehr einfache und eine klügere — um dieses Problem anzugehen. [ 31 ]
8.12.1 Einfache XML-Ausgabe mit method_missing
Wir beginnen mit einer einfachen Klasse namens
XML
zur Erzeugung einer XML-Ausgabe. Hier sehen Sie ein Beispiel dafür, wie
XML
eingesetzt werden kann:
pagetitle = "Testseite für XML.generate"
XML.generate(STDOUT) do
html do
head do
title { pagetitle }
comment "Dies ist ein Test."
end
body do
h1(:style => "font-family:sans-serif") { pagetitle }
ul :type=>"square" do
li { Time.now }
li { RUBY_VERSION }
end
end
end
end
Das sieht nicht aus wie XML, sondern irgendwie wie Ruby. Hier die Ausgabe, die es erzeugt (mit einigen Zeilenumbrüchen, die für die Lesbarkeit hinzugefügt wurden):
Testseite für XML.generateTestseite für XML.generate
- 2007-08-19 16:19:58 −0700
- 1.9.0
Um diese Klasse und die von ihr unterstützte XML-Erzeugungssyntax zu implementieren, verlassen wir uns auf
Rubys Blockstruktur,
Rubys Methodenaufrufe mit optionalen Klammern,
Rubys Syntax zur Übergabe von Hash-Literalen ohne geschweifte Klammern an Methoden und
Die Methode
method_missing
.
Listing 8.11 zeigt die Implementierung dieser einfachen DSL.
class XML
# Eine Instanz dieser Klasse erzeugen, unter Angabe eines Stream
# oder eines Objekts für die Ausgabe. Das kann jedes Objekt sein,
# das auf <<(String) reagiert.
def initialize(out)
@out = out # Merken, wohin unsere Ausgabe gesendet wird
end
# Das angegebene Objekt als CDATA ausgeben, nil zurückgeben
def content(text)
@out << text.to_s
nil
end
# Ausgabe des Objekts als Kommentar, nil zurückgeben
def comment(text)
@out << ""
nil
end
# Ein Tag mit dem angegebenen Namen und den angegebenen
# Attributen ausgeben. Wenn ein Block vorhanden ist, wird er
# aufgerufen, um Inhalte auszugeben oder zurückzugeben.
# nil zurückgeben
def tag(tagname, attributes={})
# Ausgabe des Tag-Namens
@out << "<#{tagname}"
# Ausgabe der Attribute
attributes.each {|attr,value| @out << " #{attr}='#{value}'" }
if block_given?
# Dieser Block besitzt Inhalte.
@out << '>' # Ende des öffnenden Tags
# Block aufrufen, um Inhalte auszugeben oder zurückzugeben
content = yield
if content # Wenn Inhalte zurückgegeben werden
@out << content.to_s #