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..a614b07 100644 --- a/kernel.go +++ b/kernel.go @@ -10,6 +10,7 @@ import ( "io/ioutil" "log" "os" + "os/exec" "reflect" "runtime" "strings" @@ -403,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) }() @@ -426,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() @@ -468,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() { @@ -480,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 @@ -626,13 +628,19 @@ 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) - 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, jupyterStdOut, jupyterStdErr, line) + lines[i] = "" + } } } return strings.Join(lines, "\n") @@ -640,7 +648,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 +679,49 @@ 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, 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:]...) + + 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) + } + + writersWG.Wait() +}