Skip to content

Commit 31c4c42

Browse files
authored
MONGOSH-237 - Allow custom print/printjson for individual platforms (#378)
1 parent efd8e46 commit 31c4c42

File tree

15 files changed

+173
-26
lines changed

15 files changed

+173
-26
lines changed

packages/browser-repl/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"@mongosh/history": "^0.4.2",
4343
"@mongosh/i18n": "^0.4.2",
4444
"@mongosh/service-provider-core": "^0.4.2",
45-
"@mongosh/shell-api": "^0.4.2",
45+
"@mongosh/shell-evaluator": "^0.4.2",
4646
"pretty-bytes": "^5.3.0",
4747
"text-table": "^0.2.0"
4848
},

packages/browser-repl/src/components/shell.spec.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ describe('<Shell />', () => {
3535
elementFocus = sinon.spy(HTMLElement.prototype, 'focus');
3636

3737
fakeRuntime = {
38-
evaluate: sinon.fake.returns({ printable: 'some result' })
38+
evaluate: sinon.fake.returns({ printable: 'some result' }),
39+
setEvaluationListener: () => {}
3940
};
4041

4142
onOutputChangedSpy = sinon.spy();
@@ -247,7 +248,8 @@ describe('<Shell />', () => {
247248
return new Promise(resolve => {
248249
onInputDone = resolve;
249250
});
250-
}
251+
},
252+
setEvaluationListener: () => {}
251253
} as any}
252254
/>);
253255

@@ -355,4 +357,12 @@ describe('<Shell />', () => {
355357

356358
expect(HTMLElement.prototype.focus).to.not.have.been.called;
357359
});
360+
361+
it('updated the output when .onPrint is called', () => {
362+
wrapper.instance().onPrint([{ type: null, printable: 42 }]);
363+
364+
expect(onOutputChangedSpy).to.have.been.calledWith([
365+
{ format: 'output', value: 42, type: null }
366+
]);
367+
});
358368
});

packages/browser-repl/src/components/shell.tsx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ export class Shell extends Component<ShellProps, ShellState> {
104104
let outputLine;
105105

106106
try {
107+
this.props.runtime.setEvaluationListener(this);
107108
const result = await this.props.runtime.evaluate(code);
108109
outputLine = {
109110
format: 'output',
@@ -156,6 +157,16 @@ export class Shell extends Component<ShellProps, ShellState> {
156157
this.props.onOutputChanged(output);
157158
};
158159

160+
onPrint = (result: { type: string | null; printable: any }[]): void => {
161+
const output = this.addEntriesToOutput(result.map((entry) => ({
162+
format: 'output',
163+
type: entry.type,
164+
value: entry.printable
165+
})));
166+
this.setState({ output });
167+
this.props.onOutputChanged(output);
168+
};
169+
159170
private onInput = async(code: string): Promise<void> => {
160171
if (!code || code.trim() === '') {
161172
this.appendEmptyInput();
@@ -167,25 +178,22 @@ export class Shell extends Component<ShellProps, ShellState> {
167178
value: code
168179
};
169180

181+
let output = this.addEntriesToOutput([inputLine]);
170182
this.setState({
171-
operationInProgress: true
183+
operationInProgress: true,
184+
output
172185
});
186+
this.props.onOutputChanged(output);
173187

174188
const outputLine = await this.evaluate(code);
175189

176-
const output = this.addEntriesToOutput([
177-
inputLine,
178-
outputLine
179-
]);
180-
190+
output = this.addEntriesToOutput([outputLine]);
181191
const history = this.addEntryToHistory(code);
182-
183192
this.setState({
184193
operationInProgress: false,
185194
output,
186195
history
187196
});
188-
189197
this.props.onOutputChanged(output);
190198
this.props.onHistoryChanged(history);
191199
};

packages/browser-repl/src/iframe-runtime/iframe-runtime.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,28 @@ import {
99
} from '@mongosh/browser-runtime-core';
1010

1111
import { ServiceProvider } from '@mongosh/service-provider-core';
12-
import { ShellResult } from '@mongosh/shell-api';
12+
import { ShellResult, EvaluationListener } from '@mongosh/shell-evaluator';
1313

1414
export class IframeRuntime implements Runtime {
1515
private openContextRuntime: OpenContextRuntime;
1616
private iframe: HTMLIFrameElement;
1717
private container: HTMLDivElement;
1818
private serviceProvider: ServiceProvider;
19+
private evaluationListener: EvaluationListener | null = null;
1920

2021
constructor(serviceProvider: ServiceProvider) {
2122
this.serviceProvider = serviceProvider;
2223
}
2324

25+
setEvaluationListener(listener: EvaluationListener): EvaluationListener | null {
26+
const prev = this.evaluationListener;
27+
this.evaluationListener = listener;
28+
if (this.openContextRuntime) {
29+
this.openContextRuntime.setEvaluationListener(listener);
30+
}
31+
return prev;
32+
}
33+
2434
async evaluate(code: string): Promise<ShellResult> {
2535
if (!this.openContextRuntime) {
2636
await this.initialize();
@@ -60,6 +70,9 @@ export class IframeRuntime implements Runtime {
6070

6171
const environment = new IframeInterpreterEnvironment(this.iframe.contentWindow);
6272
this.openContextRuntime = new OpenContextRuntime(this.serviceProvider, environment);
73+
if (this.evaluationListener) {
74+
this.openContextRuntime.setEvaluationListener(this.evaluationListener);
75+
}
6376

6477
return await ready;
6578
}

packages/browser-runtime-core/src/open-context-runtime.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Runtime } from './runtime';
66
import { EventEmitter } from 'events';
77
import { ShellInternalState, ShellResult } from '@mongosh/shell-api';
88

9-
import ShellEvaluator from '@mongosh/shell-evaluator';
9+
import { ShellEvaluator, EvaluationListener } from '@mongosh/shell-evaluator';
1010

1111
/**
1212
* This class is the core implementation for a runtime which is not isolated
@@ -22,6 +22,7 @@ export class OpenContextRuntime implements Runtime {
2222
private autocompleter: ShellApiAutocompleter;
2323
private shellEvaluator: ShellEvaluator;
2424
private internalState: ShellInternalState;
25+
private evaluationListener: EvaluationListener | null = null;
2526

2627
constructor(
2728
serviceProvider: ServiceProvider,
@@ -55,4 +56,10 @@ export class OpenContextRuntime implements Runtime {
5556
''
5657
);
5758
}
59+
60+
setEvaluationListener(listener: EvaluationListener): EvaluationListener | null {
61+
const prev = this.evaluationListener;
62+
this.shellEvaluator.setEvaluationListener(listener);
63+
return prev;
64+
}
5865
}

packages/browser-runtime-core/src/runtime.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
import { Completion } from './autocompleter/autocompleter';
2-
import { ShellResult } from '@mongosh/shell-api';
2+
import { ShellResult, EvaluationListener } from '@mongosh/shell-evaluator';
33

44
export type ContextValue = any;
55

66
export interface Runtime {
7+
/**
8+
* Sets a listener for certain events, e.g. onPrint() when print() is called
9+
* in the shell.
10+
*
11+
* @param {EvaluationListener} listener - The new listener.
12+
* @return {EvaluationListener | null} The previous listener, if any.
13+
*/
14+
setEvaluationListener(listener: EvaluationListener): EvaluationListener | null;
15+
716
/**
817
* Evaluates code
918
*

packages/browser-runtime-electron/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"dependencies": {
3232
"@mongosh/browser-runtime-core": "^0.4.2",
3333
"@mongosh/service-provider-core": "^0.4.2",
34-
"@mongosh/shell-api": "^0.4.2"
34+
"@mongosh/shell-evaluator": "^0.4.2"
3535
},
3636
"devDependencies": {
3737
"@mongosh/service-provider-server": "^0.4.2",

packages/browser-runtime-electron/src/electron-runtime.spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,23 @@ import { CliServiceProvider } from '@mongosh/service-provider-server';
88
import { bson } from '@mongosh/service-provider-core';
99
import { ElectronRuntime } from './electron-runtime';
1010
import { EventEmitter } from 'events';
11+
import { EvaluationListener } from '@mongosh/shell-evaluator';
1112

1213
describe('Electron runtime', function() {
1314
let serviceProvider: SinonStubbedInstance<CliServiceProvider>;
1415
let messageBus: SinonStubbedInstance<EventEmitter>;
16+
let evaluationListener: SinonStubbedInstance<EvaluationListener>;
1517
let electronRuntime: ElectronRuntime;
1618

1719
beforeEach(async() => {
1820
serviceProvider = sinon.createStubInstance(CliServiceProvider);
1921
serviceProvider.bsonLibrary = bson;
22+
serviceProvider.getConnectionInfo.resolves({ extraInfo: { uri: '' } });
2023
messageBus = sinon.createStubInstance(EventEmitter);
24+
evaluationListener = sinon.createStubInstance(class FakeListener {});
25+
evaluationListener.onPrint = sinon.stub();
2126
electronRuntime = new ElectronRuntime(serviceProvider, messageBus);
27+
electronRuntime.setEvaluationListener(evaluationListener);
2228
});
2329

2430
it('can evaluate simple js', async() => {
@@ -79,4 +85,36 @@ describe('Electron runtime', function() {
7985
await electronRuntime.evaluate('use db1');
8086
expect(messageBus.emit).to.have.been.calledWith('mongosh:use');
8187
});
88+
89+
describe('onPrint', () => {
90+
it('allows getting the output of print() statements', async() => {
91+
await electronRuntime.evaluate('print("foo");');
92+
expect(evaluationListener.onPrint).to.have.been.calledWithMatch(
93+
sinon.match((array) => (
94+
array.length === 1 &&
95+
array[0].type === null &&
96+
array[0].printable === 'foo')));
97+
});
98+
99+
it('allows getting the output of console.log() statements', async() => {
100+
await electronRuntime.evaluate('console.log("foo");');
101+
expect(evaluationListener.onPrint).to.have.been.calledWithMatch(
102+
sinon.match((array) => (
103+
array.length === 1 &&
104+
array[0].type === null &&
105+
array[0].printable === 'foo')));
106+
});
107+
108+
it('allows getting the output of multi-arg console.log() statements', async() => {
109+
await electronRuntime.evaluate('console.log("foo", "bar");');
110+
expect(evaluationListener.onPrint).to.have.been.calledWithMatch(
111+
sinon.match((array) => (
112+
array.length === 2 &&
113+
array[0].type === null &&
114+
array[0].printable === 'foo' &&
115+
array[1].type === null &&
116+
array[1].printable === 'bar')));
117+
expect(evaluationListener.onPrint).to.have.been.calledOnce;
118+
});
119+
});
82120
});

packages/browser-runtime-electron/src/electron-runtime.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from '@mongosh/browser-runtime-core';
1111

1212
import { ServiceProvider } from '@mongosh/service-provider-core';
13-
import { ShellResult } from '@mongosh/shell-api';
13+
import { ShellResult, EvaluationListener } from '@mongosh/shell-evaluator';
1414

1515
declare const __webpack_require__: any;
1616
declare const __non_webpack_require__: any;
@@ -43,6 +43,10 @@ export class ElectronRuntime implements Runtime {
4343
);
4444
}
4545

46+
setEvaluationListener(listener: EvaluationListener): EvaluationListener | null {
47+
return this.openContextRuntime.setEvaluationListener(listener);
48+
}
49+
4650
async evaluate(code: string): Promise<ShellResult> {
4751
return await this.openContextRuntime.evaluate(code);
4852
}

packages/cli-repl/src/cli-repl.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { CliServiceProvider, NodeOptions, CliOptions } from '@mongosh/service-provider-server';
44
import { getShellApiType, ShellInternalState } from '@mongosh/shell-api';
5-
import ShellEvaluator from '@mongosh/shell-evaluator';
5+
import { ShellEvaluator, ShellResult } from '@mongosh/shell-evaluator';
66
import formatOutput, { formatError } from './format-output';
77
import { LineByLineInput } from './line-by-line-input';
88
import { TELEMETRY, MONGOSH_WIKI } from './constants';
@@ -84,6 +84,7 @@ class CliRepl {
8484
const initialServiceProvider = await this.connect(driverUri, driverOptions);
8585
this.internalState = new ShellInternalState(initialServiceProvider, this.bus, this.options);
8686
this.shellEvaluator = new ShellEvaluator(this.internalState, this);
87+
this.shellEvaluator.setEvaluationListener(this);
8788
await this.internalState.fetchConnectionInfo();
8889
this.start();
8990
}
@@ -467,6 +468,13 @@ class CliRepl {
467468
console.error(formatError(error));
468469
return process.exit(1);
469470
}
471+
472+
onPrint(values: ShellResult[]): void {
473+
const joined = values.map(this.writer).join(' ');
474+
// `as any` becomes unnecessary after
475+
// https://github.com/DefinitelyTyped/DefinitelyTyped/pull/48646
476+
(this.repl as any).output.write(joined + '\n');
477+
}
470478
}
471479

472480
export default CliRepl;

0 commit comments

Comments
 (0)