Skip to content

Commit 1e54350

Browse files
committed
shell commands: improve parsing and error messages
1 parent 29b2c79 commit 1e54350

File tree

2 files changed

+37
-22
lines changed

2 files changed

+37
-22
lines changed

kernel.go

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ func (kernel *Kernel) handleExecuteRequest(receipt msgReceipt) error {
406406

407407
jupyterStdOut := JupyterStreamWriter{StreamStdout, &receipt}
408408
jupyterStdErr := JupyterStreamWriter{StreamStderr, &receipt}
409+
outerr := OutErr{&jupyterStdOut, &jupyterStdErr}
409410

410411
// Forward all data written to stdout/stderr to the front-end.
411412
go func() {
@@ -428,7 +429,7 @@ func (kernel *Kernel) handleExecuteRequest(receipt msgReceipt) error {
428429
}()
429430

430431
// eval
431-
vals, types, executionErr := doEval(ir, jupyterStdOut, jupyterStdErr, code)
432+
vals, types, executionErr := doEval(ir, outerr, code)
432433

433434
// Close and restore the streams.
434435
wOut.Close()
@@ -470,7 +471,7 @@ func (kernel *Kernel) handleExecuteRequest(receipt msgReceipt) error {
470471

471472
// doEval evaluates the code in the interpreter. This function captures an uncaught panic
472473
// as well as the values of the last statement/expression.
473-
func doEval(ir *interp.Interp, jupyterStdOut, jupyterStdErr JupyterStreamWriter, code string) (val []interface{}, typ []xreflect.Type, err error) {
474+
func doEval(ir *interp.Interp, outerr OutErr, code string) (val []interface{}, typ []xreflect.Type, err error) {
474475

475476
// Capture a panic from the evaluation if one occurs and store it in the `err` return parameter.
476477
defer func() {
@@ -482,7 +483,7 @@ func doEval(ir *interp.Interp, jupyterStdOut, jupyterStdErr JupyterStreamWriter,
482483
}
483484
}()
484485

485-
code = evalSpecialCommands(ir, jupyterStdOut, jupyterStdErr, code)
486+
code = evalSpecialCommands(ir, outerr, code)
486487

487488
// Prepare and perform the multiline evaluation.
488489
compiler := ir.Comp
@@ -628,34 +629,43 @@ func startHeartbeat(hbSocket Socket, wg *sync.WaitGroup) (shutdown chan struct{}
628629
}
629630

630631
// find and execute special commands in code, remove them from returned string
631-
func evalSpecialCommands(ir *interp.Interp, jupyterStdOut, jupyterStdErr JupyterStreamWriter, code string) string {
632+
func evalSpecialCommands(ir *interp.Interp, outerr OutErr, code string) string {
632633
lines := strings.Split(code, "\n")
634+
stop := false
633635
for i, line := range lines {
634636
line = strings.TrimSpace(line)
635637
if len(line) != 0 {
636638
switch line[0] {
637639
case '%':
638-
evalSpecialCommand(ir, line)
640+
evalSpecialCommand(ir, outerr, line)
639641
lines[i] = ""
640642
case '$':
641-
evalShellCommand(ir, jupyterStdOut, jupyterStdErr, line)
643+
evalShellCommand(ir, outerr, line)
642644
lines[i] = ""
645+
default:
646+
// if a line is NOT a special command,
647+
// stop processing special commands
648+
stop = true
643649
}
644650
}
651+
if stop {
652+
break
653+
}
645654
}
646655
return strings.Join(lines, "\n")
647656
}
648657

649-
// execute special command
650-
func evalSpecialCommand(ir *interp.Interp, line string) {
658+
// execute special command. line must start with '%'
659+
func evalSpecialCommand(ir *interp.Interp, outerr OutErr, line string) {
651660
const help string = `
652661
available special commands (%):
653662
%help
654663
%go111module {on|off}
655664
656-
execute shell commands ($):
665+
execute shell commands ($): $command [args...]
666+
example:
657667
$ls -l
658-
`
668+
`
659669

660670
args := strings.SplitN(line, " ", 2)
661671
cmd := args[0]
@@ -674,53 +684,52 @@ $ls -l
674684
panic(fmt.Errorf("special command %s: expecting a single argument 'on' or 'off', found: %q", cmd, arg))
675685
}
676686
case "%help":
677-
panic(help)
687+
fmt.Fprint(outerr.out, help)
678688
default:
679689
panic(fmt.Errorf("unknown special command: %q\n%s", line, help))
680690
}
681691
}
682692

683-
// execute shell command
684-
func evalShellCommand(ir *interp.Interp, jupyterStdOut, jupyterStdErr JupyterStreamWriter, line string) {
685-
args := strings.Split(line, " ")
693+
// execute shell command. line must start with '$'
694+
func evalShellCommand(ir *interp.Interp, outerr OutErr, line string) {
695+
args := strings.Fields(line[1:])
686696
if len(args) <= 0 {
687697
return
688698
}
689699

690700
var writersWG sync.WaitGroup
691701
writersWG.Add(2)
692702

693-
command := strings.Replace(args[0], "$", "", 1)
694-
cmd := exec.Command(command, args[1:]...)
703+
cmd := exec.Command(args[0], args[1:]...)
695704

696705
stdout, err := cmd.StdoutPipe()
697706
if err != nil {
698-
panic(err)
707+
panic(fmt.Errorf("Command.StdoutPipe() failed: %v", err))
699708
}
700709

701710
stderr, err := cmd.StderrPipe()
702711
if err != nil {
703-
panic(err)
712+
panic(fmt.Errorf("Command.StderrPipe() failed: %v", err))
704713
}
705714

706715
go func() {
707716
defer writersWG.Done()
708-
io.Copy(&jupyterStdOut, stdout)
717+
io.Copy(outerr.out, stdout)
709718
}()
710719

711720
go func() {
712721
defer writersWG.Done()
713-
io.Copy(&jupyterStdErr, stderr)
722+
io.Copy(outerr.err, stderr)
714723
}()
715724

716725
err = cmd.Start()
717726
if err != nil {
718-
panic(err)
727+
panic(fmt.Errorf("error starting command '%s': %v", line[1:], err))
719728
}
720729

721730
err = cmd.Wait()
722731
if err != nil {
723-
panic(err)
732+
panic(fmt.Errorf("error waiting for command '%s': %v", line[1:], err))
724733
}
725734

726735
writersWG.Wait()

messages.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto/sha256"
66
"encoding/hex"
77
"encoding/json"
8+
"io"
89
"time"
910

1011
"github.com/go-zeromq/zmq4"
@@ -340,3 +341,8 @@ func (writer *JupyterStreamWriter) Write(p []byte) (int, error) {
340341

341342
return n, nil
342343
}
344+
345+
type OutErr struct {
346+
out io.Writer
347+
err io.Writer
348+
}

0 commit comments

Comments
 (0)