3030package org .scijava .script ;
3131
3232import java .io .BufferedReader ;
33+ import java .io .ByteArrayOutputStream ;
3334import java .io .IOException ;
3435import java .io .InputStream ;
3536import java .io .InputStreamReader ;
3839import java .lang .reflect .Constructor ;
3940import java .util .ArrayList ;
4041import java .util .List ;
42+ import java .util .function .Consumer ;
43+ import java .util .function .Supplier ;
4144
4245import javax .script .Bindings ;
4346import javax .script .ScriptException ;
@@ -67,9 +70,9 @@ public class ScriptREPL {
6770 @ Parameter (required = false )
6871 private PluginService pluginService ;
6972
70- private final PrintStream out ;
73+ private final Consumer < String > out ;
7174
72- private String languagePreference = null ;
75+ private String languagePreference ;
7376
7477 /** List of interpreter-friendly script languages. */
7578 private List <ScriptLanguage > languages ;
@@ -84,19 +87,26 @@ public ScriptREPL(final Context context) {
8487 this (context , System .out );
8588 }
8689
90+ public ScriptREPL (final Context context , final String language ) {
91+ this (context , language , System .out );
92+ }
93+
8794 public ScriptREPL (final Context context , final OutputStream out ) {
88- context .inject (this );
89- this .out = out instanceof PrintStream ?
90- (PrintStream ) out : new PrintStream (out );
95+ this (context , null , out );
9196 }
9297
93- public ScriptREPL (final Context context , final String language ) {
94- this (context , language , System .out );
98+ public ScriptREPL (final Context context , final String language ,
99+ final OutputStream out )
100+ {
101+ this (context , language , outputStreamConsumer (out ));
95102 }
96103
97- public ScriptREPL (final Context context , final String language , final OutputStream out ) {
98- this (context , out );
104+ public ScriptREPL (final Context context , final String language ,
105+ final Consumer <String > out )
106+ {
107+ context .inject (this );
99108 languagePreference = language ;
109+ this .out = out ;
100110 }
101111
102112 /**
@@ -132,11 +142,39 @@ public void loop() throws IOException {
132142 * @param in Input stream from which commands are read.
133143 */
134144 public void loop (final InputStream in ) throws IOException {
135- initialize ();
136145 final BufferedReader bin = new BufferedReader (new InputStreamReader (in ));
146+ try {
147+ loop (() -> {
148+ try {
149+ return bin .readLine ();
150+ }
151+ catch (final IOException exc ) {
152+ throw new RuntimeException (exc );
153+ }
154+ });
155+ }
156+ catch (final RuntimeException exc ) {
157+ // NB: This convolution lets us throw IOException from inside a
158+ // Supplier.get implementation, by wrapping in a RuntimeException.
159+ // We then unwrap it again and throw it here, where we said we would.
160+ final Throwable cause = exc .getCause ();
161+ if (cause instanceof IOException ) throw (IOException ) cause ;
162+ else throw exc ;
163+ }
164+ }
165+
166+ /**
167+ * Starts a Read-Eval-Print-Loop from the given source, returning when
168+ * the loop terminates.
169+ *
170+ * @param in Source from which commands are read.
171+ */
172+ public void loop (final Supplier <?> in ) {
173+ initialize ();
137174 while (true ) {
138175 prompt ();
139- final String line = bin .readLine ();
176+ final Object input = in .get ();
177+ final String line = input == null ? null : input .toString ();
140178 if (line == null ) break ;
141179 if (!evaluate (line )) return ;
142180 }
@@ -157,54 +195,40 @@ public void initialize() {
157195 */
158196 public void initialize (final boolean verbose ) {
159197 if (verbose ) {
160- out . println ("Welcome to the SciJava REPL!" );
161- out . println ();
198+ println ("Welcome to the SciJava REPL!" );
199+ println ();
162200 help ();
163201 }
164202 final List <ScriptLanguage > langs = getInterpretedLanguages ();
165203 if (verbose ) {
166204 if (langs .isEmpty ()) {
167- out . println ("--------------------------------------------------------------" );
168- out . println ("Uh oh! There are no SciJava script languages available!" );
169- out . println ("Are any on your classpath? E.g.: org.scijava:scripting-groovy?" );
170- out . println ("--------------------------------------------------------------" );
171- out . println ();
205+ println ("--------------------------------------------------------------" );
206+ println ("Uh oh! There are no SciJava script languages available!" );
207+ println ("Are any on your classpath? E.g.: org.scijava:scripting-groovy?" );
208+ println ("--------------------------------------------------------------" );
209+ println ();
172210 return ;
173211 }
174- out .println ("Have fun!" );
175- out .println ();
176-
177- if (languagePreference != null ) {
178- selectPreferredLanguage (langs );
179- } else {
180- lang (langs .get (0 ).getLanguageName ());
181- }
212+ println ("Have fun!" );
213+ println ();
182214 }
183- else if (!langs .isEmpty ()) {
184- if (languagePreference != null ) {
185- selectPreferredLanguage (langs );
186- } else {
187- lang (langs .get (0 ));
188- }
215+ if (!langs .isEmpty ()) {
216+ if (languagePreference != null ) selectPreferredLanguage (langs );
217+ else lang (langs .get (0 ));
189218 }
190-
191219 populateBindings (interpreter .getBindings ());
192220 }
193221
194222 private void selectPreferredLanguage (List <ScriptLanguage > langs ) {
195- final ScriptLanguage preference = langs
196- .stream ().filter (lang -> languagePreference .equals (lang .getLanguageName ()))
197- .findAny ().orElse (null );
198- if (preference != null ) {
199- lang (preference );
200- } else {
201- lang (langs .get (0 ).getLanguageName ());
202- }
223+ final ScriptLanguage preference = langs .stream ()
224+ .filter (lang -> languagePreference .equals (lang .getLanguageName ()))
225+ .findFirst ().orElse (langs .get (0 ));
226+ lang (preference );
203227 }
204228
205229 /** Outputs the prompt. */
206230 public void prompt () {
207- out . print (interpreter == null || interpreter .isReady () ? "> " : "\\ " );
231+ print (interpreter == null || interpreter .isReady () ? "> " : "\\ " );
208232 }
209233
210234 /**
@@ -230,22 +254,22 @@ public boolean evaluate(final String line) {
230254 // pass the input to the current interpreter for evaluation
231255 final Object result = interpreter .interpret (line );
232256 if (result != ScriptInterpreter .MORE_INPUT_PENDING ) {
233- out . println (s (result ));
257+ println (s (result ));
234258 }
235259 }
236260 }
237261 catch (final ScriptException exc ) {
238262 // NB: Something went wrong interpreting the line of code.
239263 // Let's just display the error message, unless we are in debug mode.
240- if (debug ) exc . printStackTrace (out );
264+ if (debug ) printStackTrace (exc );
241265 else {
242266 final String msg = exc .getMessage ();
243- out . println (msg == null ? exc .getClass ().getName () : msg );
267+ println (msg == null ? exc .getClass ().getName () : msg );
244268 }
245269 }
246270 catch (final Throwable exc ) {
247271 // NB: Something unusual went wrong. Dump the whole exception always.
248- exc . printStackTrace (out );
272+ printStackTrace (exc );
249273 }
250274 return true ;
251275 }
@@ -254,17 +278,17 @@ public boolean evaluate(final String line) {
254278
255279 /** Prints a usage guide. */
256280 public void help () {
257- out . println ("Available built-in commands:" );
258- out . println ();
259- out . println (" :help | this handy list of commands" );
260- out . println (" :vars | dump a list of variables" );
261- out . println (" :lang <name> | switch the active language" );
262- out . println (" :langs | list available languages" );
263- out . println (" :debug | toggle full stack traces" );
264- out . println (" :quit | exit the REPL" );
265- out . println ();
266- out . println ("Or type a statement to evaluate it with the active language." );
267- out . println ();
281+ println ("Available built-in commands:" );
282+ println ();
283+ println (" :help | this handy list of commands" );
284+ println (" :vars | dump a list of variables" );
285+ println (" :lang <name> | switch the active language" );
286+ println (" :langs | list available languages" );
287+ println (" :debug | toggle full stack traces" );
288+ println (" :quit | exit the REPL" );
289+ println ();
290+ println ("Or type a statement to evaluate it with the active language." );
291+ println ();
268292 }
269293
270294 /** Lists variables in the script context. */
@@ -294,11 +318,11 @@ public void lang(final String langName) {
294318 // create the new interpreter
295319 final ScriptLanguage language = scriptService .getLanguageByName (langName );
296320 if (language == null ) {
297- out . println ("No such language: " + langName );
321+ println ("No such language: " + langName );
298322 return ;
299323 }
300324 lang (language );
301- out . println ("language -> " + interpreter .getLanguage ().getLanguageName ());
325+ println ("language -> " + interpreter .getLanguage ().getLanguageName ());
302326 }
303327
304328 /**
@@ -316,7 +340,7 @@ public void lang(final ScriptLanguage language) {
316340 copyBindings (interpreter , newInterpreter );
317341 }
318342 catch (final Throwable t ) {
319- t . printStackTrace (out );
343+ printStackTrace (t );
320344 }
321345 interpreter = newInterpreter ;
322346 }
@@ -335,7 +359,7 @@ public void langs() {
335359
336360 public void debug () {
337361 debug = !debug ;
338- out . println ("debug mode -> " + debug );
362+ println ("debug mode -> " + debug );
339363 }
340364
341365 // -- Main method --
@@ -417,7 +441,7 @@ private List<Gateway> gateways() {
417441 gateways .add (gateway );
418442 }
419443 catch (final Throwable t ) {
420- t . printStackTrace (out );
444+ printStackTrace (t );
421445 }
422446 }
423447 return gateways ;
@@ -442,6 +466,17 @@ private String type(final Object value) {
442466 return "[" + decoded .getClass ().getName () + "]" ;
443467 }
444468
469+ private static final String NL = System .getProperty ("line.separator" );
470+ private void print (String s ) { out .accept (s ); }
471+ private void println () { print (NL ); }
472+ private void println (final String s ) { print (s + NL ); }
473+
474+ private void printStackTrace (final Throwable t ) {
475+ final ByteArrayOutputStream baos = new ByteArrayOutputStream ();
476+ t .printStackTrace (new PrintStream (baos ));
477+ println (baos .toString ());
478+ }
479+
445480 private void printColumns (final List <?>... columns ) {
446481 final int pad = 2 ;
447482
@@ -456,18 +491,26 @@ private void printColumns(final List<?>... columns) {
456491 }
457492
458493 // output the columns
494+ final StringBuilder sb = new StringBuilder ();
459495 for (int i = 0 ; i < columns [0 ].size (); i ++) {
496+ sb .setLength (0 );
460497 for (int c = 0 ; c < columns .length ; c ++) {
461498 final String s = s (columns [c ].get (i ));
462- out . print (s );
499+ sb . append (s );
463500 for (int p = s .length (); p < widths [c ] + pad ; p ++) {
464- out . print (' ' );
501+ sb . append (' ' );
465502 }
466503 }
467- out . println ();
504+ println (sb . toString () );
468505 }
469506 }
470507
508+ private static Consumer <String > outputStreamConsumer (final OutputStream out ) {
509+ final PrintStream ps = out instanceof PrintStream ?
510+ (PrintStream ) out : new PrintStream (out );
511+ return s -> { ps .print (s ); ps .flush (); };
512+ }
513+
471514 private static String lowerCamelCase (final String s ) {
472515 final StringBuilder sb = new StringBuilder (s );
473516 for (int i =0 ; i <sb .length (); i ++) {
@@ -481,5 +524,4 @@ private static String lowerCamelCase(final String s) {
481524 private static String s (final Object o ) {
482525 return o == null ? NULL : o .toString ();
483526 }
484-
485527}
0 commit comments