From 7408543f2803301328360d7cad5262298f328b44 Mon Sep 17 00:00:00 2001 From: Dale Campbell Date: Sun, 12 Apr 2020 23:15:31 -0400 Subject: [PATCH 1/2] First stab at implementing shell command. Issue #196 --- .gitignore | 2 +- kernel.go | 40 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 2edd3a9..8f1b09b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ gophernotes .ipynb_checkpoints -Untitled.ipynb +Untitled*.ipynb diff --git a/kernel.go b/kernel.go index ba6b47e..510bc9a 100644 --- a/kernel.go +++ b/kernel.go @@ -10,6 +10,7 @@ import ( "io/ioutil" "log" "os" + "os/exec" "reflect" "runtime" "strings" @@ -630,9 +631,15 @@ func evalSpecialCommands(ir *interp.Interp, code string) string { lines := strings.Split(code, "\n") for i, line := range lines { line = strings.TrimSpace(line) - if len(line) != 0 && line[0] == '%' { - evalSpecialCommand(ir, line) - lines[i] = "" + if len(line) != 0 { + switch line[0] { + case '%': + evalSpecialCommand(ir, line) + lines[i] = "" + case '$': + evalShellCommand(ir, line) + lines[i] = "" + } } } return strings.Join(lines, "\n") @@ -640,7 +647,14 @@ func evalSpecialCommands(ir *interp.Interp, code string) string { // execute special command func evalSpecialCommand(ir *interp.Interp, line string) { - const help string = "available special commands:\n %go111module {on|off}\n %help" + const help string = ` +available special commands (%): +%help +%go111module {on|off} + +execute shell commands ($): +$ls -l + ` args := strings.SplitN(line, " ", 2) cmd := args[0] @@ -664,3 +678,21 @@ func evalSpecialCommand(ir *interp.Interp, line string) { panic(fmt.Errorf("unknown special command: %q\n%s", line, help)) } } + +// execute shell command +func evalShellCommand(ir *interp.Interp, line string) { + args := strings.Split(line, " ") + if len(args) <= 0 { + return + } + + command := strings.Replace(args[0], "$", "", 1) + cmd := exec.Command(command, args[1:]...) + out, err := cmd.CombinedOutput() + if err != nil { + panic(err) + } + + // TODO: Properly stream stdout/stderr to Jupyter. + panic(string(out)) +} From 29b2c792108887e935dc2d91e64815f3ee483762 Mon Sep 17 00:00:00 2001 From: Dale Campbell Date: Wed, 22 Apr 2020 00:49:10 -0400 Subject: [PATCH 2/2] Implement stdout/stderr pipe to Jupyter kernel. Issue #196 --- kernel.go | 51 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/kernel.go b/kernel.go index 510bc9a..a614b07 100644 --- a/kernel.go +++ b/kernel.go @@ -404,16 +404,17 @@ func (kernel *Kernel) handleExecuteRequest(receipt msgReceipt) error { var writersWG sync.WaitGroup writersWG.Add(2) + jupyterStdOut := JupyterStreamWriter{StreamStdout, &receipt} + jupyterStdErr := JupyterStreamWriter{StreamStderr, &receipt} + // Forward all data written to stdout/stderr to the front-end. go func() { defer writersWG.Done() - jupyterStdOut := JupyterStreamWriter{StreamStdout, &receipt} io.Copy(&jupyterStdOut, rOut) }() go func() { defer writersWG.Done() - jupyterStdErr := JupyterStreamWriter{StreamStderr, &receipt} io.Copy(&jupyterStdErr, rErr) }() @@ -427,7 +428,7 @@ func (kernel *Kernel) handleExecuteRequest(receipt msgReceipt) error { }() // eval - vals, types, executionErr := doEval(ir, code) + vals, types, executionErr := doEval(ir, jupyterStdOut, jupyterStdErr, code) // Close and restore the streams. wOut.Close() @@ -469,7 +470,7 @@ func (kernel *Kernel) handleExecuteRequest(receipt msgReceipt) error { // doEval evaluates the code in the interpreter. This function captures an uncaught panic // as well as the values of the last statement/expression. -func doEval(ir *interp.Interp, code string) (val []interface{}, typ []xreflect.Type, err error) { +func doEval(ir *interp.Interp, jupyterStdOut, jupyterStdErr JupyterStreamWriter, code string) (val []interface{}, typ []xreflect.Type, err error) { // Capture a panic from the evaluation if one occurs and store it in the `err` return parameter. defer func() { @@ -481,7 +482,7 @@ func doEval(ir *interp.Interp, code string) (val []interface{}, typ []xreflect.T } }() - code = evalSpecialCommands(ir, code) + code = evalSpecialCommands(ir, jupyterStdOut, jupyterStdErr, code) // Prepare and perform the multiline evaluation. compiler := ir.Comp @@ -627,7 +628,7 @@ func startHeartbeat(hbSocket Socket, wg *sync.WaitGroup) (shutdown chan struct{} } // find and execute special commands in code, remove them from returned string -func evalSpecialCommands(ir *interp.Interp, code string) string { +func evalSpecialCommands(ir *interp.Interp, jupyterStdOut, jupyterStdErr JupyterStreamWriter, code string) string { lines := strings.Split(code, "\n") for i, line := range lines { line = strings.TrimSpace(line) @@ -637,7 +638,7 @@ func evalSpecialCommands(ir *interp.Interp, code string) string { evalSpecialCommand(ir, line) lines[i] = "" case '$': - evalShellCommand(ir, line) + evalShellCommand(ir, jupyterStdOut, jupyterStdErr, line) lines[i] = "" } } @@ -680,19 +681,47 @@ $ls -l } // execute shell command -func evalShellCommand(ir *interp.Interp, line string) { +func evalShellCommand(ir *interp.Interp, jupyterStdOut, jupyterStdErr JupyterStreamWriter, line string) { args := strings.Split(line, " ") if len(args) <= 0 { return } + var writersWG sync.WaitGroup + writersWG.Add(2) + command := strings.Replace(args[0], "$", "", 1) cmd := exec.Command(command, args[1:]...) - out, err := cmd.CombinedOutput() + + stdout, err := cmd.StdoutPipe() + if err != nil { + panic(err) + } + + stderr, err := cmd.StderrPipe() + if err != nil { + panic(err) + } + + go func() { + defer writersWG.Done() + io.Copy(&jupyterStdOut, stdout) + }() + + go func() { + defer writersWG.Done() + io.Copy(&jupyterStdErr, stderr) + }() + + err = cmd.Start() + if err != nil { + panic(err) + } + + err = cmd.Wait() if err != nil { panic(err) } - // TODO: Properly stream stdout/stderr to Jupyter. - panic(string(out)) + writersWG.Wait() }