Skip to content

Commit

Permalink
Merge pull request #109 from janpfeifer/script
Browse files Browse the repository at this point in the history
%%script implementation.
  • Loading branch information
janpfeifer authored Apr 5, 2024
2 parents 9c54685 + 446a672 commit abcfed7
Show file tree
Hide file tree
Showing 8 changed files with 410 additions and 158 deletions.
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

* Added special cell commands ("magic"):
* `%%writefile` to write contents of cell to file. See #103. Thanks @potoo0!
* `%%script`, `%%bash` and `%%sh` to execute the full cell contents with the given command (none of the extra flags are supported yet though).
* Added `dom.LoadScriptOrRequireJSModuleAndRun` and `dom.LoadScriptOrRequireJSModuleAndRunTransient` that dynamically decides
if to include script using `<script src=...>` or use RequireJS.
* Plotly library uses `dom.LoadScriptOrRequireJSModuleAndRun` now, allowing result to show up in the HTML export of
Expand Down
113 changes: 113 additions & 0 deletions examples/tests/script.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "fe105ef6-5264-4cb4-b467-60d9b0a337fd",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1 : a\n",
"2 : b\n",
"3 : c\n"
]
}
],
"source": [
"%%bash\n",
"((ii=1))\n",
"(\n",
" cat <<EOF\n",
"a\n",
"b\n",
"c\n",
"EOF\n",
") | while read str ; do \n",
" echo \"$ii : $str\"\n",
" ((ii+=1))\n",
"done"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "004e9622-e3a4-4780-816a-e5d1c77055b4",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"18\n"
]
}
],
"source": [
"%%script bc\n",
"7 + 11"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "a90b5943-54f0-4b12-b1b5-35d085a29bb2",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"19\n"
]
}
],
"source": [
"%%sh\n",
"X=19\n",
"echo $X"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "fdf96ecb-8342-479d-822b-ff9707977d9d",
"metadata": {},
"outputs": [
{
"ename": "ERROR",
"evalue": "executing special commands in cell: \"%%sh\" can only appear at the start of the cell",
"output_type": "error",
"traceback": [
"executing special commands in cell: \"%%sh\" can only appear at the start of the cell"
]
}
],
"source": [
"X=17\n",
"%%sh\n",
"echo $X"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Go (gonb)",
"language": "go",
"name": "gonb"
},
"language_info": {
"codemirror_mode": "",
"file_extension": ".go",
"mimetype": "",
"name": "go",
"nbconvert_exporter": "",
"pygments_lexer": "",
"version": "go1.22.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
31 changes: 31 additions & 0 deletions internal/jpyexec/jpyexec.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type Executor struct {
useNamedPipes bool
commsHandler CommsHandler
stdoutWriter, stderrWriter io.Writer
stdinContent []byte
millisecondsToInput int
inputPassword bool

Expand Down Expand Up @@ -145,6 +146,14 @@ func (exec *Executor) WithPassword(millisecondsWait int) *Executor {
return exec
}

// WithStaticInput configures the executor to run withe given fixed input.
//
// This conflicts with [Executor.WithInputs] and [Executor.WithPassword].
func (exec *Executor) WithStaticInput(stdinContent []byte) *Executor {
exec.stdinContent = stdinContent
return exec
}

// Exec executes the configured New configuration.
//
// It returns an error if it failed to execute or created the pipes -- but not if the executed
Expand Down Expand Up @@ -222,6 +231,10 @@ func (exec *Executor) Exec() error {
return errors.WithMessagef(err, "failed to start to execute command %q", exec.command)
}

if exec.stdinContent != nil {
exec.handleStaticInput()
}

// Wait for output pipes to finish.
streamersWG.Wait()
if err := cmd.Wait(); err != nil {
Expand Down Expand Up @@ -300,3 +313,21 @@ func (exec *Executor) handleJupyterInput() {
}
go schedulePromptFn()
}

func (exec *Executor) handleStaticInput() {
go func() {
// Write concurrently, not to block, in case program doesn't
// actually read anything from the stdin.
_, err := exec.cmdStdin.Write([]byte(exec.stdinContent))
if err != nil {
// Could happen if something was not fully written, and channel was closed, in
// which case it's ok.
klog.Warningf("failed to write to stdin of %q %v: %+v", exec.command, exec.args, err)
}
err = exec.cmdStdin.Close()
if err != nil {
klog.Warningf("failed to clsoe stdin of %q %v: %+v", exec.command, exec.args, err)
}

}()
}
115 changes: 115 additions & 0 deletions internal/nbtests/cellmagic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package nbtests

import (
"fmt"
"github.com/stretchr/testify/require"
"k8s.io/klog/v2"
"os"
"path"
"testing"
)

// TestWritefile tests that `%%writefile` tests that `%%writefile` works
func TestWritefile(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration (nbconvert) test for short tests.")
return
}
klog.Infof("GOCOVERDIR=%s", os.Getenv("GOCOVERDIR"))

// Create directory where to write the file, and set TEST_DIR env variable.
testDir, err := os.MkdirTemp("", "gonb_nbtests_writefile_")
require.NoError(t, err)
require.NoError(t, os.Setenv("TEST_DIR", testDir+"/"))
klog.Infof("TEST_DIR=%s/", testDir)

// Run notebook test.
notebook := "writefile"
f := executeNotebook(t, notebook)
err = Check(f,
Sequence(
Match(
OutputLine(1),
Separator,
fmt.Sprintf(`Cell contents written to "%s/poetry.txt".`, testDir),
Separator,
),
Match(
OutputLine(2),
Separator,
fmt.Sprintf(`Cell contents appended to "%s/poetry.txt".`, testDir),
Separator,
),
), *flagPrintNotebook)

require.NoError(t, err)
require.NoError(t, f.Close())
require.NoError(t, os.Remove(f.Name()))
clearNotebook(t, notebook)

// Checks the file was written.
filePath := path.Join(testDir, "poetry.txt")
contents, err := os.ReadFile(filePath)
require.NoErrorf(t, err, "Failed to read file %q that should have been written by the notebook.", filePath)
want := `Um trem-de-ferro é uma coisa mecânica,
mas atravessa a noite, a madrugada, o dia,
atravessou minha vida,
virou só sentimento.
Adélia Prado
`
require.Equalf(t, want, string(contents), "Contents written to %q don't match", filePath)

// Remove directory.
require.NoError(t, os.RemoveAll(testDir))
}

// TestScript tests the cell magic `%%script` (and `%%bash` and `%%sh`).
// It requires `bc` to be installed -- I assume available in most unixes.
func TestScript(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration (nbconvert) test for short tests.")
return
}
klog.Infof("GOCOVERDIR=%s", os.Getenv("GOCOVERDIR"))

// Run notebook test.
notebook := "script"
f := executeNotebook(t, notebook)
err := Check(f,
Sequence(
Match(
OutputLine(1),
Separator,
"1 : a",
"2 : b",
"3 : c",
Separator,
),
Match(
OutputLine(2),
Separator,
"18",
Separator,
),
Match(
OutputLine(3),
Separator,
"19",
Separator,
),
Match(
OutputLine(4),
Separator,
"",
"can only appear at the start", // ... can only appear ...
"",
Separator,
),
), *flagPrintNotebook)

require.NoError(t, err)
require.NoError(t, f.Close())
require.NoError(t, os.Remove(f.Name()))
clearNotebook(t, notebook)
}
55 changes: 0 additions & 55 deletions internal/nbtests/nbtests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -661,58 +661,3 @@ func TestGonbui(t *testing.T) {
require.NoError(t, os.Remove(f.Name()))
clearNotebook(t, notebook)
}

// TestWritefile tests that `%%writefile` tests that `%%writefile` works
func TestWritefile(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration (nbconvert) test for short tests.")
return
}
klog.Infof("GOCOVERDIR=%s", os.Getenv("GOCOVERDIR"))

// Create directory where to write the file, and set TEST_DIR env variable.
testDir, err := os.MkdirTemp("", "gonb_nbtests_writefile_")
require.NoError(t, err)
require.NoError(t, os.Setenv("TEST_DIR", testDir+"/"))
klog.Infof("TEST_DIR=%s/", testDir)

// Run notebook test.
notebook := "writefile"
f := executeNotebook(t, notebook)
err = Check(f,
Sequence(
Match(
OutputLine(1),
Separator,
fmt.Sprintf(`Cell contents written to "%s/poetry.txt".`, testDir),
Separator,
),
Match(
OutputLine(2),
Separator,
fmt.Sprintf(`Cell contents appended to "%s/poetry.txt".`, testDir),
Separator,
),
), *flagPrintNotebook)

require.NoError(t, err)
require.NoError(t, f.Close())
require.NoError(t, os.Remove(f.Name()))
clearNotebook(t, notebook)

// Checks the file was written.
filePath := path.Join(testDir, "poetry.txt")
contents, err := os.ReadFile(filePath)
require.NoErrorf(t, err, "Failed to read file %q that should have been written by the notebook.", filePath)
want := `Um trem-de-ferro é uma coisa mecânica,
mas atravessa a noite, a madrugada, o dia,
atravessou minha vida,
virou só sentimento.
Adélia Prado
`
require.Equalf(t, want, string(contents), "Contents written to %q don't match", filePath)

// Remove directory.
require.NoError(t, os.RemoveAll(testDir))
}
Loading

0 comments on commit abcfed7

Please sign in to comment.