Hooks
Hooks ermöglichen es, einen Code-Snippet in vordefinierten Situationen auszuführen. Sie sind nur im interaktiven Modus verfügbar REPL, sie funktionieren nicht, wenn eine Nushell mit einem Skript (nu script.nu) oder dem Befehl (nu -c "print foo") Argument ausgefüht wird.
Derzeit unterstützen wir diese Arten von Hooks:
pre_prompt: Ausgelöst bevor die Eingabeaufforderung gezeichnet wirdpre_execution: Ausgelöst vor dem Ausführen der Zeileneingabeenv_change: Ausgelöst, wenn sich eine Umgebungsvariable ändertdisplay_output: Ein Block, an den die Ausgabe weitergeleitet wird (experimental.)command_not_found: Ausgelöst, wenn ein Befehl nicht gefunden wird.
Der Nushell Ausführungszyklus macht es klarer. Die Schritte zur Auswertung einer Zeile im REPL-Modus sind wie folgt:
- Prüfe auf
pre_promptHooks und führe diese aus. - Prüfe auf
env_changeHooks und führe diese aus. - Stelle die Eingabeaufforderung dar und warte auf eine Benutzer-Eingabe.
- Nachdem der Benutzer etwas eingegeben und "Enter" gedrückt hat: Prüfe auf
pre_executionHooks und führe diese aus. - Parse und werte die Benutzer Eingabe aus.
- Wurde ein Befehl nicht gefunden: Prüfe auf
command_not_foundHooks. Wenn dieser Text zurückgibt, zeige ihn. - Wenn
display_outputdefiniert ist, verwende diesen um die Ausgabe zu erstellen. - Beginne wieder mit 1.
Grundsätzliches zu Hooks
Um Hooks zu aktivieren, werden sie in der config definiert:
$env.config.hooks = {
pre_prompt: [{ print "pre prompt hook" }]
pre_execution: [{ print "pre exec hook" }]
env_change: {
PWD: [{|before, after| print $"changing directory from ($before) to ($after)" }]
}
}Bewegen wir uns mit obiger Konfiguration im Dateisystem, wird beim Ändern eines Verzeichnisses die PWD Umgebungsvariable verändert. Die Änderung löst den Hook aus und tauscht die entsprechenden Werte in before und after aus.
Anstatt nur einen einzigen Hook pro Trigger zu definieren, ist es möglich, eine Liste von Hooks zu definieren, die nacheinander durchlaufen werden:
$env.config.hooks = {
pre_prompt: [
{ print "pre prompt hook" }
{ print "pre prompt hook2" }
]
pre_execution: [
{ print "pre exec hook" }
{ print "pre exec hook2" }
]
env_change: {
PWD: [
{|before, after| print $"changing directory from ($before) to ($after)" }
{|before, after| print $"changing directory from ($before) to ($after) 2" }
]
}
}Anstatt alle hooks zu ersetzen können neue Hooks der Liste vorhandener Hooks angehängt werden:
$env.config.hooks.pre_execution = $env.config.hooks.pre_execution | append { print "pre exec hook3" }Changing Environment
Eine Besonderheit der Hooks ist, dass sie die Umgebung bewahren. Umgebungsvariablen im Hook Block werden in ähnlicher Weise wie def --env erhalten. Folgendes Beispiel zeigt dies:
$env.config = ($env.config | upsert hooks {
pre_prompt: { $env.SPAM = "eggs" }
})
$env.SPAM
# => eggsDie Hookblöcke folgen ansonsten den allgemeinen Scoping-Regeln, d.h. Befehle, Aliase, etc., die innerhalb des Blocks definiert sind, werden verworfen, sobald der Block endet.
Conditional Hooks
Nun wäre es verlockend eine Umgebung zu aktivieren wenn in ein Verzeichnis eingestiegen wird:
$env.config = ($env.config | upsert hooks {
env_change: {
PWD: [
{|before, after|
if $after == /some/path/to/directory {
load-env { SPAM: eggs }
}
}
]
}
})Dies wird jedoch nicht funktionieren, weil die Umgebung nur innerhalb des Blocks if aktiv ist. In diesem Fall könnte es als load-env neu geschrieben werden (load-env (if $after == ... { ... } else { {} })), aber dieses Muster ist ziemlich häufig und später werden wir sehen, dass nicht alle Fälle so geschrieben werden können.
Um das obige Problem zu lösen, führen wir eine neue Möglichkeit ein, einen Hook zu definieren -- einen record:
$env.config = ($env.config | upsert hooks {
env_change: {
PWD: [
{
condition: {|before, after| $after == /some/path/to/directory }
code: {|before, after| load-env { SPAM: eggs } }
}
]
}
})Wird der Hook getriggert, wird der condition Block ausgewertet. Wenn dieser true zurückgibt, wird der code Block ausgewertet. Wenn er falsezurückgibt, passiert nichts. Gibt er etwas anderes zurück, wird ein Fehler generiert. Das condition Feld kann auch weggelassen werden, womit der Hook immer ausgewertet wird.
Die pre_prompt und pre_execution Hook Typen unterstützen die Conditional Hooks, jedoch nicht die before und after Parameter.
Hooks als Strings
Bisher wurde ein Hook als Block definiert, der nur die Umgebung bewahrt, aber nichts anderes. Um Befehle oder Aliase definieren zu können, ist es möglich, das Codefeld als string zu definieren. Dies funktioniert, als ob der String in den REPL eingeben und Enter gedrückt wird. So kann der Hook aus dem vorherigen Abschnitt auch geschrieben werden als:
$env.config = ($env.config | upsert hooks {
pre_prompt: '$env.SPAM = "eggs"'
})
$env.SPAM
eggsDieses Feature kann z.B. verwendet werden, um abhängig vom aktuellen Verzeichnis Definitionen einzubringen:
$env.config = ($env.config | upsert hooks {
env_change: {
PWD: [
{
condition: {|_, after| $after == /some/path/to/directory }
code: 'def foo [] { print "foo" }'
}
{
condition: {|before, _| $before == /some/path/to/directory }
code: 'hide foo'
}
]
}
})Wird ein Hook als String definiert, werden die $before und $after Variablen auf die vorherigen und aktuellen Umgebungsvariablen gesetzt, analog dem vorherigen Beispiel:
$env.config = ($env.config | upsert hooks {
env_change: {
PWD: {
code: 'print $"changing directory from ($before) to ($after)"'
}
}
}Beispiele
Einen einzelnen Hook zur bestehenden Konfiguration hinzufügen
Beispiel eines PWD env Wechsel Hooks:
$env.config = ($env.config | upsert hooks.env_change.PWD {|config|
let val = ($config | get -i hooks.env_change.PWD)
if $val == $nothing {
$val | append {|before, after| print $"changing directory from ($before) to ($after)" }
} else {
[
{|before, after| print $"changing directory from ($before) to ($after)" }
]
}
})Automatisch eine Umgebung aktivieren, wenn ein Verzeichnis betreten wird
Dieses Beispiel sucht nach einem test-env.nu in einem Verzeichnis
$env.config = ($env.config | upsert hooks.env_change.PWD {
[
{
condition: {|_, after|
($after == '/path/to/target/dir'
and ($after | path join test-env.nu | path exists))
}
code: "overlay use test-env.nu"
}
{
condition: {|before, after|
('/path/to/target/dir' not-in $after
and '/path/to/target/dir' in $before
and 'test-env' in (overlay list))
}
code: "overlay hide test-env --keep-env [ PWD ]"
}
]
})Filtern oder Umlenken des Befehl-Outputs
Der display_output Hook kann verwendet werden, um die Ausgabe von Befehlen umzuleiten. Ein Block sollte so definiert werden, dass er mit allen Werttypen funktioniert. Die Ausgabe externer Befehle wird nicht durch display_output gefiltert.
Dieser Hook kann die Ausgabe in einem separaten Fenster anzeigen, vielleicht als HTML-Text. Hier ist die Grundidee, wie dies erreicht wird:
$env.config = ($env.config | upsert hooks {
display_output: { to html --partial --no-color | save --raw /tmp/nu-output.html }
})Das Ergebnis wird in der Datei:///tmp/nu-output.html ersichtlich in einem Webbrowser. Natürlich ist dies nicht sehr praktisch, es sei denn, der Browser wird automatisch neu geladen, wenn sich die Datei ändert. Anstelle des Befehls save würde normalerweise der HTML-Ausgang an ein gewünschtes Fenster gesendet.
command_not_found Hook in Arch Linux
Der folgende Hook verwendet den pkgfile Befehl, um in Arch Linux herauzufinden, zu welchem Packet ein Befehl gehört.
$env.config = {
...other config...
hooks: {
...other hooks...
command_not_found: {
|cmd_name| (
try {
let pkgs = (pkgfile --binaries --verbose $cmd_name)
if ($pkgs | is-empty) {
return null
}
(
$"(ansi $env.config.color_config.shape_external)($cmd_name)(ansi reset) " +
$"may be found in the following packages:\n($pkgs)"
)
}
)
}
}
}