Skip to content

Commit

Permalink
Support REPL languages that use colons (#393)
Browse files Browse the repository at this point in the history
Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>
  • Loading branch information
wyattades and paescuj committed Jun 9, 2023
1 parent 4f94ad7 commit ffd3760
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 19 deletions.
45 changes: 35 additions & 10 deletions src/flow-control/input-handler.spec.ts
Expand Up @@ -54,22 +54,48 @@ it('forwards input stream to target index specified in input', () => {
controller.handle(commands);

inputStream.write('1:something');
inputStream.write('1:multi\nline\n');

expect(commands[0].stdin?.write).not.toHaveBeenCalled();
expect(commands[1].stdin?.write).toHaveBeenCalledTimes(1);
expect(commands[1].stdin?.write).toHaveBeenCalledTimes(2);
expect(commands[1].stdin?.write).toHaveBeenCalledWith('something');
expect(commands[1].stdin?.write).toHaveBeenCalledWith('multi\nline\n');
});

it('forwards input stream to target index specified in input when input contains colon', () => {
controller.handle(commands);

inputStream.emit('data', Buffer.from('1::something'));
inputStream.emit('data', Buffer.from('1:some:thing'));
inputStream.emit('data', Buffer.from('1: :something'));
inputStream.emit('data', Buffer.from('1::something'));

expect(commands[0].stdin?.write).not.toHaveBeenCalled();
expect(commands[1].stdin?.write).toHaveBeenCalledTimes(2);
expect(commands[1].stdin?.write).toHaveBeenCalledWith(':something');
expect(commands[1].stdin?.write).toHaveBeenCalledTimes(3);
expect(commands[1].stdin?.write).toHaveBeenCalledWith('some:thing');
expect(commands[1].stdin?.write).toHaveBeenCalledWith(' :something');
expect(commands[1].stdin?.write).toHaveBeenCalledWith(':something');
});

it('does not forward input stream when input contains colon in a different format', () => {
controller.handle(commands);

inputStream.emit('data', Buffer.from('Ruby0::Const::Syntax'));
inputStream.emit('data', Buffer.from('1:Ruby1::Const::Syntax'));
inputStream.emit('data', Buffer.from('ruby_symbol_arg :my_symbol'));
inputStream.emit('data', Buffer.from('ruby_symbol_arg(:my_symbol)'));
inputStream.emit('data', Buffer.from('{ruby_key: :my_val}'));
inputStream.emit('data', Buffer.from('{:ruby_key=>:my_val}'));
inputStream.emit('data', Buffer.from('js_obj = {key: "my_val"}'));

expect(commands[1].stdin?.write).toHaveBeenCalledTimes(1);
expect(commands[1].stdin?.write).toHaveBeenCalledWith('Ruby1::Const::Syntax');
expect(commands[0].stdin?.write).toHaveBeenCalledTimes(6);
expect(commands[0].stdin?.write).toHaveBeenCalledWith('Ruby0::Const::Syntax');
expect(commands[0].stdin?.write).toHaveBeenCalledWith('ruby_symbol_arg :my_symbol');
expect(commands[0].stdin?.write).toHaveBeenCalledWith('ruby_symbol_arg(:my_symbol)');
expect(commands[0].stdin?.write).toHaveBeenCalledWith('{ruby_key: :my_val}');
expect(commands[0].stdin?.write).toHaveBeenCalledWith('{:ruby_key=>:my_val}');
expect(commands[0].stdin?.write).toHaveBeenCalledWith('js_obj = {key: "my_val"}');
});

it('forwards input stream to target name specified in input', () => {
Expand All @@ -90,20 +116,19 @@ it('logs error if command has no stdin open', () => {

expect(commands[1].stdin?.write).not.toHaveBeenCalled();
expect(logger.logGlobalEvent).toHaveBeenCalledWith(
'Unable to find command 0, or it has no stdin open\n'
'Unable to find command "0", or it has no stdin open\n'
);
});

it('logs error if command is not found', () => {
it('fallback to default input stream if command is not found', () => {
controller.handle(commands);

inputStream.write('foobar:something');

expect(commands[0].stdin?.write).not.toHaveBeenCalled();
expect(commands[0].stdin?.write).toHaveBeenCalledTimes(1);
expect(commands[0].stdin?.write).toHaveBeenCalledWith('foobar:something');
expect(commands[1].stdin?.write).not.toHaveBeenCalled();
expect(logger.logGlobalEvent).toHaveBeenCalledWith(
'Unable to find command foobar, or it has no stdin open\n'
);
expect(logger.logGlobalEvent).not.toHaveBeenCalled();
});

it('pauses input stream when finished', () => {
Expand Down
29 changes: 20 additions & 9 deletions src/flow-control/input-handler.ts
Expand Up @@ -48,24 +48,35 @@ export class InputHandler implements FlowController {
return { commands };
}

const commandsMap = new Map<string, Command>();
for (const command of commands) {
commandsMap.set(command.index.toString(), command);
commandsMap.set(command.name, command);
}

Rx.fromEvent(inputStream, 'data')
.pipe(map((data) => String(data)))
.subscribe((data) => {
const dataParts = data.split(/:(.+)/);
const targetId = dataParts.length > 1 ? dataParts[0] : this.defaultInputTarget;
const input = dataParts[1] || data;
let command: Command | undefined, input: string;

const dataParts = data.split(/:(.+)/s);
let target = dataParts[0];

const command = commands.find(
(command) =>
command.name === targetId ||
command.index.toString() === targetId.toString()
);
if (dataParts.length > 1 && (command = commandsMap.get(target))) {
input = dataParts[1];
} else {
// If `target` does not match a registered command,
// fallback to `defaultInputTarget` and forward the whole input data
target = this.defaultInputTarget.toString();
command = commandsMap.get(target);
input = data;
}

if (command && command.stdin) {
command.stdin.write(input);
} else {
this.logger.logGlobalEvent(
`Unable to find command ${targetId}, or it has no stdin open\n`
`Unable to find command "${target}", or it has no stdin open\n`
);
}
});
Expand Down

0 comments on commit ffd3760

Please sign in to comment.