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_prompt
Hooks und führe diese aus. - Prüfe auf
env_change
Hooks 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_execution
Hooks und führe diese aus. - Parse und werte die Benutzer Eingabe aus.
- Wurde ein Befehl nicht gefunden: Prüfe auf
command_not_found
Hooks. Wenn dieser Text zurückgibt, zeige ihn. - Wenn
display_output
definiert 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 = {
# ...other 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 = {
...other 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" }
]
}
}
}
Auch könnte es praktischer sein, die bestehende Konfiguration mit neuen Hooks zu aktualisieren, anstatt die gesamte Konfiguration von Grund auf neu zu definieren:
$env.config = ($env.config | upsert hooks {
pre_prompt: ...
pre_execution: ...
env_change: {
PWD: ...
}
})
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
eggs
Die 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 false
zurü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
eggs
Dieses 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)"
)
}
)
}
}
}