Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support custom cell executions #613

Merged
merged 10 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 22 additions & 13 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,30 @@ You can copy also results from the inline executed shell:
openssl rand -base64 32
```

## Non-Supported Languages
## Non-Shell Languages

These are shown as simple markdowns, e.g:
These are sometimes executable by default, like for python:

```py { readonly=true }
def hello():
print("Hello World")
```py
print("Hello World")
```

Otherwise, execution can be set with the `interpreter` annotation, like so:

```yaml { interpreter=cat }
config:
nested:
para: true
```

Non-shell scripts can also access environment variables, and are run from the current working directory:

```sh
export YOUR_NAME=enter your name
```

```javascript { name=echo-hello-js }
console.log(`Hello, ${process.env.YOUR_NAME}, from ${__dirname}!`)
```

## Curl an image
Expand All @@ -159,11 +176,3 @@ With [`antonmedv/fx`](https://github.com/antonmedv/fx) you can inspect JSON file
```sh { terminalRows=20 }
curl -s "https://api.marquee.activecove.com/getWeather?lat=52&lon=10" | fx
```

## YAML

```yaml
config:
netsed:
para: true
```
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,6 @@
"test:format:fix": "prettier --write .",
"test:unit": "vitest -c ./vitest.conf.ts",
"test:e2e": "wdio run ./tests/e2e/wdio.conf.ts",
"pretest:e2e": "npm run download:binary",
"watch": "npm run build:dev -- --watch",
"vscode:prepublish": "npm run prepare-binary",
"download:binary": "npm run prepare-binary",
Expand Down
6 changes: 6 additions & 0 deletions src/client/components/configuration/annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ export class Annotations extends LitElement {
description: "Cell's canonical name for easy referencing in the CLI.",
docs: 'https://docs.runme.dev/configuration#cell-options',
},
interpreter: {
description: 'Script shebang line',
// FIXME: update docs link
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll do that before releasing 👍

docs: '',
},
category: {
description: 'Execute this code cell within a category.',
docs: 'https://docs.runme.dev/configuration#run-all-cells-by-category',
Expand Down Expand Up @@ -446,6 +451,7 @@ export class Annotations extends LitElement {
<div class="box">${this.renderTextFieldTabEntry('mimeType')}</div>
<div class="box">${this.renderCategoryTabEntry('category')}</div>
<div class="box">${this.renderTextFieldTabEntry('terminalRows')}</div>
<div class="box">${this.renderTextFieldTabEntry('interpreter')}</div>
</div>
</vscode-panel-view>
</vscode-panels>
Expand Down
8 changes: 6 additions & 2 deletions src/extension/executors/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { toggleTerminal } from '../commands'
import { NotebookCellOutputManager } from '../cell'

import { closeTerminalByEnvID } from './task'
import { getCellShellPath, parseCommandSeq, getCellCwd } from './utils'
import { parseCommandSeq, getCellCwd, getCellProgram } from './utils'
import { handleVercelDeployOutput, isVercelDeployScript } from './vercel'

import type { IEnvironmentManager } from '.'
Expand Down Expand Up @@ -124,8 +124,10 @@ export async function executeRunner(
}
}

const { programName, commandMode } = getCellProgram(exec.cell, exec.cell.notebook, execKey)

const program = await runner.createProgramSession({
programName: getCellShellPath(exec.cell, exec.cell.notebook, execKey) ?? execKey,
programName,
environment,
exec: execution,
envs: Object.entries(envs).map(([k, v]) => `${k}=${v}`),
Expand All @@ -134,6 +136,8 @@ export async function executeRunner(
tty: interactive,
convertEol: !mimeType || mimeType === 'text/plain',
storeLastOutput: true,
languageId: exec.cell.document.languageId,
commandMode,
})

context.subscriptions.push(program)
Expand Down
61 changes: 59 additions & 2 deletions src/extension/executors/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ import {

import { ENV_STORE } from '../constants'
import { DEFAULT_PROMPT_ENV, OutputType } from '../../constants'
import type { CellOutputPayload, Serializer } from '../../types'
import type { CellOutputPayload, Serializer, ShellType } from '../../types'
import { NotebookCellOutputManager } from '../cell'
import { getAnnotations, getWorkspaceFolder } from '../utils'
import { CommandMode } from '../grpc/runnerTypes'

const ENV_VAR_REGEXP = /(\$\w+)/g
/**
Expand Down Expand Up @@ -206,8 +207,8 @@ export async function retrieveShellCommand(
* @param execKey Used as fallback in case `$SHELL` is not present
*/
export function getSystemShellPath(): string | undefined
export function getSystemShellPath(execKey: string | undefined): string | undefined
export function getSystemShellPath(execKey: string): string
export function getSystemShellPath(execKey?: string): string | undefined
export function getSystemShellPath(execKey?: string): string | undefined {
return process.env.SHELL ?? execKey
}
Expand Down Expand Up @@ -236,6 +237,62 @@ export function getCellShellPath(
return getSystemShellPath(execKey)
}

export function isShellLanguage(languageId: string): ShellType | undefined {
switch (languageId.toLowerCase()) {
case 'sh':
case 'bash':
case 'zsh':
case 'ksh':
case 'shell':
case 'shellscript':
return 'sh'

case 'bat':
case 'cmd':
return 'cmd'

case 'powershell':
case 'pwsh':
return 'powershell'

case 'fish':
return 'fish'

default:
return undefined
}
}

export function getCellProgram(
cell: NotebookCell | NotebookCellData | Serializer.Cell,
notebook: NotebookData | Serializer.Notebook | NotebookDocument,
execKey: string
): { programName: string; commandMode: CommandMode } {
let result: { programName: string; commandMode: CommandMode }
const { interpreter } = getAnnotations(cell.metadata)

if (isShellLanguage(execKey)) {
const shellPath = getCellShellPath(cell, notebook, execKey) ?? execKey

result = {
programName: shellPath,
commandMode: CommandMode.INLINE_SHELL,
}
} else {
// TODO(mxs): make this configurable!!
result = {
programName: '',
commandMode: CommandMode.TEMP_FILE,
}
}

if (interpreter) {
result.programName = interpreter
}

return result
}

export async function getCellCwd(
cell: NotebookCell | NotebookCellData | Serializer.Cell,
notebook?: NotebookData | NotebookDocument | Serializer.Notebook,
Expand Down
23 changes: 12 additions & 11 deletions src/extension/kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ import {
processEnviron,
isWindows,
setNotebookCategories,
isShellLanguage,
getTerminalRunmeId,
} from './utils'
import { isShellLanguage } from './executors/utils'
import './wasm/wasm_exec.js'
import { IRunner, IRunnerEnvironment } from './runner'
import { executeRunner } from './executors/runner'
Expand Down Expand Up @@ -494,7 +494,7 @@ export class Kernel implements Disposable {
// TODO(mxs): support windows shells
!isWindows()
) {
const runScript = (execKey: string = 'sh') =>
const runScript = (key: string = execKey) =>
executeRunner(
this,
this.context,
Expand All @@ -503,7 +503,7 @@ export class Kernel implements Disposable {
runningCell,
this.messaging,
uuid,
execKey,
key,
outputs,
this.environment,
environmentManager
Expand All @@ -513,9 +513,9 @@ export class Kernel implements Disposable {
return false
})

if (isShellLanguage(execKey)) {
if (isShellLanguage(execKey) || !(execKey in executor)) {
successfulCellExecution = await runScript(execKey)
} else if (execKey in executor) {
} else {
successfulCellExecution = await executor[execKey as keyof typeof executor].call(
this,
exec,
Expand All @@ -524,22 +524,23 @@ export class Kernel implements Disposable {
runScript,
environmentManager
)
} else {
this.#shebangComingSoon.open()

successfulCellExecution = false
}
} else {
} else if (execKey in executor) {
/**
* check if user is running experiment to execute shell via runme cli
*/
successfulCellExecution = await executor[execKey as keyof typeof executor]?.call(
successfulCellExecution = await executor[execKey as keyof typeof executor].call(
this,
exec,
runningCell,
outputs
)
} else {
window.showErrorMessage('Cell language is not executable')

successfulCellExecution = false
}

TelemetryReporter.sendTelemetryEvent('cell.endExecute', {
'cell.success': successfulCellExecution?.toString(),
})
Expand Down
11 changes: 9 additions & 2 deletions src/extension/provider/runmeTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { getAnnotations, getWorkspaceEnvs, prepareCmdSeq } from '../utils'
import { Serializer, RunmeTaskDefinition } from '../../types'
import { SerializerBase } from '../serializer'
import type { IRunner, IRunnerEnvironment, RunProgramOptions } from '../runner'
import { getCellCwd, getCellShellPath, parseCommandSeq } from '../executors/utils'
import { getCellCwd, getCellProgram, parseCommandSeq } from '../executors/utils'
import { Kernel } from '../kernel'

type TaskOptions = Pick<RunmeTaskDefinition, 'closeTerminalOnSuccess' | 'isBackground' | 'cwd'>
Expand Down Expand Up @@ -118,6 +118,12 @@ export class RunmeTaskProvider implements TaskProvider {

const isBackground = options.isBackground || background

const { programName, commandMode } = getCellProgram(
cell,
notebook,
('languageId' in cell && cell.languageId) || 'sh'
)

const name = `${command}`

const task = new Task(
Expand Down Expand Up @@ -145,7 +151,7 @@ export class RunmeTaskProvider implements TaskProvider {
}

const runOpts: RunProgramOptions = {
programName: getCellShellPath(cell, notebook) ?? 'sh',
programName,
exec: {
type: 'commands',
commands: commands ?? [''],
Expand All @@ -156,6 +162,7 @@ export class RunmeTaskProvider implements TaskProvider {
convertEol: true,
envs: Object.entries(envs).map(([k, v]) => `${k}=${v}`),
storeLastOutput: true,
commandMode,
}

const program = await runner.createProgramSession(runOpts)
Expand Down
22 changes: 22 additions & 0 deletions src/extension/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import {
type Disposable,
type TerminalDimensions,
EventEmitter,
window,
} from 'vscode'
import { RpcError } from '@protobuf-ts/runtime-rpc'

import type { DisposableAsync } from '../types'

import {
CommandMode,
CreateSessionRequest,
ExecuteRequest,
ExecuteResponse,
Expand Down Expand Up @@ -51,6 +53,9 @@ export interface RunProgramOptions {
background?: boolean
convertEol?: boolean
storeLastOutput?: boolean
languageId?: string
fileExtension?: string
commandMode?: CommandMode
}

export interface IRunner extends Disposable {
Expand Down Expand Up @@ -407,6 +412,17 @@ export class GrpcRunnerProgramSession implements IRunnerProgramSession {

this.session.responses.onError((error) => {
if (error instanceof RpcError) {
if (error.message.includes('invalid LanguageId')) {
window.showErrorMessage(
// eslint-disable-next-line max-len
'Unable to automatically execute cell. To execute this cell, set the "program" field in the configuration foldout!'
mxsdev marked this conversation as resolved.
Show resolved Hide resolved
)
}

if (error.message.includes('invalid ProgramName')) {
window.showErrorMessage(`Unable to locate program "${this.opts.programName}"`)
}

console.error(
'RpcError occurred!',
{
Expand Down Expand Up @@ -712,6 +728,9 @@ export class GrpcRunnerProgramSession implements IRunnerProgramSession {
terminalDimensions,
background,
storeLastOutput,
fileExtension,
languageId,
commandMode,
}: RunProgramOptions): ExecuteRequest {
if (environment && !(environment instanceof GrpcRunnerEnvironment)) {
throw new Error('Expected gRPC environment!')
Expand All @@ -729,6 +748,9 @@ export class GrpcRunnerProgramSession implements IRunnerProgramSession {
...(exec?.type === 'script' && { script: exec.script }),
...(terminalDimensions && { winsize: terminalDimensionsToWinsize(terminalDimensions) }),
storeLastOutput,
fileExtension,
languageId,
commandMode,
})
}

Expand Down
Loading
Loading