-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
979c979
commit a224964
Showing
10 changed files
with
209 additions
and
140 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { rawPrompt } from '../prompt' | ||
import { bold, italic } from '../../../statistics' | ||
import { commands, ReplCommand } from './main' | ||
|
||
|
||
|
||
const longestKey = Array.from(Object.keys(commands), k => k.length).reduce((p, n) => Math.max(p, n), 0) | ||
function padCmd<T>(string: T) { | ||
return String(string).padEnd(longestKey + 2, ' ') | ||
} | ||
|
||
export const helpCommand: ReplCommand = { | ||
description: 'Show help information', | ||
script: false, | ||
usageExample: ':help', | ||
fn: () => { | ||
console.log(` | ||
You can always just enter a R expression which gets evaluated: | ||
${rawPrompt} ${bold('1 + 1')} | ||
${italic('[1] 2')} | ||
Besides that, you can use the following commands. The scripts ${italic('can')} accept further arguments. There are the following basic commands: | ||
${ | ||
Array.from(Object.entries(commands)).filter(([, {script}]) => !script).map( | ||
([command, { description }]) => ` ${bold(padCmd(':' + command))}${description}`).join('\n') | ||
} | ||
Furthermore, you can directly call the following scripts which accept arguments. If you are unsure, try to add ${italic('--help')} after the command. | ||
${ | ||
Array.from(Object.entries(commands)).filter(([, {script}]) => script).map( | ||
([command, { description }]) => ` ${bold(padCmd(':' + command))}${description}`).join('\n') | ||
} | ||
`) | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { RShell, TokenMap } from '../../../r-bridge' | ||
import { helpCommand } from './help' | ||
import { quitCommand } from './quit' | ||
import { scripts } from '../../scripts-info' | ||
import { waitOnScript } from '../execute' | ||
import { splitArguments } from '../../../util/args' | ||
|
||
/** | ||
* Content of a single command in the repl. | ||
*/ | ||
export interface ReplCommand { | ||
/** human-readable description of what the command does */ | ||
description: string | ||
/** does the command invoke another script? this is mainly used to automatically generate two separate lists when asking for help */ | ||
script: boolean | ||
/** example of how to use the command, for example `:slicer --help` */ | ||
usageExample: string | ||
/** function to execute when the command is invoked */ | ||
fn: (shell: RShell, tokenMap: TokenMap, remainingLine: string) => Promise<void> | void | ||
} | ||
|
||
|
||
export const commands: Record<string, ReplCommand> = { | ||
'help': helpCommand, | ||
'quit': quitCommand | ||
} | ||
|
||
|
||
for(const [script, { target, description, type}] of Object.entries(scripts)) { | ||
if(type === 'master script') { | ||
commands.script = { | ||
description, | ||
script: true, | ||
usageExample: `:${script} --help`, | ||
fn: async(_s, _t, remainingLine) => { | ||
await waitOnScript(`${__dirname}/${target}`, splitArguments(remainingLine)) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { ReplCommand } from './main' | ||
import { log } from '../../../util/log' | ||
|
||
export const quitCommand: ReplCommand = { | ||
description: 'End the repl', | ||
usageExample: ':quit', | ||
script: false, | ||
fn: () => { log.info('bye'); process.exit(0) } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/** | ||
* Basically a helper file to allow the main 'flowr' script (located in the source root) to provide its repl | ||
* | ||
* @module | ||
*/ | ||
import { getStoredTokenMap, RShell, TokenMap } from '../../r-bridge' | ||
import readline from 'readline/promises' | ||
import { bold, italic } from '../../statistics' | ||
import { prompt } from './prompt' | ||
import { commands, ReplCommand } from './commands/main' | ||
|
||
|
||
|
||
|
||
const replCompleterKeywords = Array.from(Object.keys(commands), s => `:${s}`) | ||
|
||
/** | ||
* Used by the repl to provide automatic completions for a given (partial) input line | ||
*/ | ||
export function replCompleter(line: string): [string[], string] { | ||
return [replCompleterKeywords.filter(k => k.startsWith(line)), line] | ||
} | ||
|
||
/** | ||
* Provides a never-ending repl (read-evaluate-print loop) processor that can be used to interact with a {@link RShell} as well as all flowR scripts. | ||
* | ||
* The repl allows for two kinds of inputs: | ||
* - Starting with a colon `:`, indicating a command (probe `:help`, and refer to {@link commands}) </li> | ||
* - Starting with anything else, indicating default R code to be directly executed. If you kill the underlying shell, that is on you! </li> | ||
* | ||
* @param shell - The shell to use, if you do not pass one it will automatically create a new one with the `revive` option set to 'always' | ||
* @param tokenMap - The pre-retrieved token map, if you pass none, it will be retrieved automatically (using the default {@link getStoredTokenMap}). | ||
* @param rl - A potentially customized readline interface to be used for the repl to *read* from the user, we write the output with `console.log`. | ||
* If you want to provide a custom one but use the same `completer`, refer to {@link replCompleter}. | ||
*/ | ||
export async function repl(shell = new RShell({ revive: 'always' }), tokenMap?: TokenMap, rl = readline.createInterface({ | ||
input: process.stdin, | ||
output: process.stdout, | ||
tabSize: 4, | ||
terminal: true, | ||
removeHistoryDuplicates: true, | ||
completer: replCompleter | ||
})) { | ||
|
||
tokenMap ??= await getStoredTokenMap(shell) | ||
|
||
// the incredible repl :D, we kill it with ':quit' | ||
// eslint-disable-next-line no-constant-condition,@typescript-eslint/no-unnecessary-condition | ||
while(true) { | ||
const answer: string = await rl.question(prompt()) | ||
|
||
if(answer.startsWith(':')) { | ||
const command = answer.slice(1).split(' ')[0].toLowerCase() | ||
const processor = commands[command] as (ReplCommand | undefined) | ||
if(processor) { | ||
await processor.fn(shell, tokenMap, answer.slice(command.length + 2).trim()) | ||
} else { | ||
console.log(`the command '${command}' is unknown, try ${bold(':help')} for more information`) | ||
} | ||
} else { | ||
try { | ||
const result = await shell.sendCommandWithOutput(answer, { | ||
from: 'both', | ||
automaticallyTrimOutput: true | ||
}) | ||
console.log(`${italic(result.join('\n'))}\n`) | ||
} catch(e) { | ||
console.error(`Error while executing '${answer}': ${(e as Error).message}`) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { log } from '../../util/log' | ||
import cp from 'child_process' | ||
|
||
/** | ||
* Run the given module with the presented arguments, and wait for it to exit. | ||
*/ | ||
export async function waitOnScript(module: string, args: string[]): Promise<void> { | ||
log.info(`starting script ${module} with args ${JSON.stringify(args)}`) | ||
const child = cp.fork(module, args) | ||
child.on('exit', (code, signal) => { | ||
if (code) { | ||
console.error(`Script ${module} exited with code ${JSON.stringify(code)} and signal ${JSON.stringify(signal)}`) | ||
process.exit(code) | ||
} | ||
}) | ||
await new Promise<void>(resolve => child.on('exit', resolve)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export * from './server' | ||
export * from './core' | ||
export * from './prompt' | ||
export * from './server' | ||
export * from './execute' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { ColorEffect, Colors, formatter } from '../../statistics' | ||
|
||
export const rawPrompt = 'R>' | ||
// is a function as the 'formatter' is configured only after the cli options have been read | ||
export const prompt = () => `${formatter.format(rawPrompt, { color: Colors.cyan, effect: ColorEffect.foreground })} ` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/** | ||
* Provides the capability of connecting to the repl of flowr via messages. | ||
* | ||
* @module | ||
*/ | ||
import { RExpressionList } from '../../r-bridge' | ||
|
||
interface RequestMessage { | ||
type: 'request', | ||
command: string, | ||
arguments: string[] | ||
} | ||
|
||
interface ResponseMessage<T> { | ||
type: 'response', | ||
success: boolean, | ||
message: T | ||
} | ||
|
||
interface NormalizedAstRequestMessage extends RequestMessage { | ||
command: 'normalized' | ||
} | ||
|
||
type NormalizedAstResponseMessage = ResponseMessage<RExpressionList> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters