-
Notifications
You must be signed in to change notification settings - Fork 212
/
Repl.java
374 lines (352 loc) · 16.3 KB
/
Repl.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
/*
* Copyright (c) 2002-2020, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* https://opensource.org/licenses/BSD-3-Clause
*/
package org.jline.demo;
import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.jline.builtins.*;
import org.jline.builtins.Nano.SyntaxHighlighter;
import org.jline.builtins.Completers.OptionCompleter;
import org.jline.console.impl.*;
import org.jline.console.CommandInput;
import org.jline.console.CommandMethods;
import org.jline.console.CommandRegistry;
import org.jline.console.ConsoleEngine;
import org.jline.console.Printer;
import org.jline.keymap.KeyMap;
import org.jline.reader.*;
import org.jline.reader.LineReader.Option;
import org.jline.reader.impl.DefaultParser;
import org.jline.reader.impl.LineReaderImpl;
import org.jline.reader.impl.DefaultParser.Bracket;
import org.jline.reader.impl.completer.ArgumentCompleter;
import org.jline.reader.impl.completer.NullCompleter;
import org.jline.reader.impl.completer.StringsCompleter;
import org.jline.script.GroovyCommand;
import org.jline.script.GroovyEngine;
import org.jline.terminal.Size;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import org.jline.terminal.Terminal.Signal;
import org.jline.utils.InfoCmp;
import org.jline.utils.InfoCmp.Capability;
import org.jline.utils.OSUtils;
import org.jline.widget.TailTipWidgets;
import org.jline.widget.TailTipWidgets.TipType;
import org.jline.widget.Widgets;
/**
* Demo how to create REPL app with JLine.
*
* @author <a href="mailto:matti.rintanikkola@gmail.com">Matti Rinta-Nikkola</a>
*/
public class Repl {
protected static class MyCommands extends JlineCommandRegistry implements CommandRegistry {
private LineReader reader;
private final Supplier<Path> workDir;
public MyCommands(Supplier<Path> workDir) {
super();
this.workDir = workDir;
Map<String,CommandMethods> commandExecute = new HashMap<>();
commandExecute.put("tput", new CommandMethods(this::tput, this::tputCompleter));
commandExecute.put("testkey", new CommandMethods(this::testkey, this::defaultCompleter));
commandExecute.put("clear", new CommandMethods(this::clear, this::defaultCompleter));
commandExecute.put("!", new CommandMethods(this::shell, this::defaultCompleter));
commandExecute.put("objarg", new CommandMethods(this::objarg, this::defaultCompleter));
registerCommands(commandExecute);
}
public void setLineReader(LineReader reader) {
this.reader = reader;
}
private Terminal terminal() {
return reader.getTerminal();
}
private Object objarg(CommandInput input) {
final String[] usage = {
"objarg - manage correctly object parameters",
" parse input.xargs, return opt.argObjects[0]",
"Usage: objarg [OBJECT]",
" -? --help Displays command help"
};
Object out = null;
try {
Options opt = parseOptions(usage, input.xargs());
List<Object> xargs = opt.argObjects();
out = xargs.size() > 0 ? xargs.get(0) : null;
} catch (Exception e) {
saveException(e);
}
return out;
}
private void tput(CommandInput input) {
final String[] usage = {
"tput - put terminal capability",
"Usage: tput [CAPABILITY]",
" -? --help Displays command help"
};
try {
Options opt = parseOptions(usage, input.args());
List<String> argv = opt.args();
if (argv.size() == 1) {
Capability vcap = Capability.byName(argv.get(0));
if (vcap != null) {
terminal().puts(vcap);
} else {
terminal().writer().println("Unknown capability");
}
} else {
terminal().writer().println("Usage: tput [CAPABILITY]");
}
} catch (Exception e) {
saveException(e);
}
}
private void testkey(CommandInput input) {
final String[] usage = {
"testkey - display the key events",
"Usage: testkey",
" -? --help Displays command help"
};
try {
parseOptions(usage, input.args());
terminal().writer().write("Input the key event(Enter to complete): ");
terminal().writer().flush();
StringBuilder sb = new StringBuilder();
while (true) {
int c = ((LineReaderImpl) reader).readCharacter();
if (c == 10 || c == 13) break;
sb.append(new String(Character.toChars(c)));
}
terminal().writer().println(KeyMap.display(sb.toString()));
terminal().writer().flush();
} catch (Exception e) {
saveException(e);
}
}
private void clear(CommandInput input) {
final String[] usage = {
"clear - clear terminal",
"Usage: clear",
" -? --help Displays command help"
};
try {
parseOptions(usage, input.args());
terminal().puts(Capability.clear_screen);
terminal().flush();
} catch (Exception e) {
saveException(e);
}
}
private void executeCmnd(List<String> args) throws Exception {
ProcessBuilder builder = new ProcessBuilder();
List<String> _args = new ArrayList<>();
if (OSUtils.IS_WINDOWS) {
_args.add("cmd.exe");
_args.add("/c");
} else {
_args.add("sh");
_args.add("-c");
}
_args.add(String.join(" ", args));
builder.command(_args);
builder.directory(workDir.get().toFile());
Process process = builder.start();
StreamGobbler streamGobbler = new StreamGobbler(process.getInputStream(), System.out::println);
new Thread(streamGobbler).start();
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new Exception("Failed to execute: " + String.join(" ", args.subList(2, args.size())));
}
}
private void shell(CommandInput input) {
final String[] usage = { "!<command> - execute shell command"
, "Usage: !<command>"
, " -? --help Displays command help" };
if (input.args().length == 1 && (input.args()[0].equals("-?") || input.args()[0].equals("--help"))) {
try {
parseOptions(usage, input.args());
} catch (Exception e) {
saveException(e);
}
} else {
List<String> argv = new ArrayList<>(Arrays.asList(input.args()));
if (!argv.isEmpty()) {
try {
executeCmnd(argv);
} catch (Exception e) {
saveException(e);
}
}
}
}
private Set<String> capabilities() {
return InfoCmp.getCapabilitiesByName().keySet();
}
private List<Completer> tputCompleter(String command) {
List<Completer> completers = new ArrayList<>();
completers.add(new ArgumentCompleter(NullCompleter.INSTANCE
, new OptionCompleter(new StringsCompleter(this::capabilities)
, this::commandOptions
, 1)
));
return completers;
}
}
private static class StreamGobbler implements Runnable {
private final InputStream inputStream;
private final Consumer<String> consumer;
public StreamGobbler(InputStream inputStream, Consumer<String> consumer) {
this.inputStream = inputStream;
this.consumer = consumer;
}
@Override
public void run() {
new BufferedReader(new InputStreamReader(inputStream)).lines()
.forEach(consumer);
}
}
private static Path workDir() {
return Paths.get(System.getProperty("user.dir"));
}
public static void main(String[] args) {
try {
//
// Parser & Terminal
//
DefaultParser parser = new DefaultParser();
parser.setEofOnUnclosedBracket(Bracket.CURLY, Bracket.ROUND, Bracket.SQUARE);
parser.setEofOnUnclosedQuote(true);
parser.setEscapeChars(null);
parser.setRegexCommand("[:]{0,1}[a-zA-Z!]{1,}\\S*"); // change default regex to support shell commands
Terminal terminal = TerminalBuilder.builder().build();
if (terminal.getWidth() == 0 || terminal.getHeight() == 0) {
terminal.setSize(new Size(120, 40)); // hard coded terminal size when redirecting
}
Thread executeThread = Thread.currentThread();
terminal.handle(Signal.INT, signal -> executeThread.interrupt());
//
// Create jnanorc config file for demo
//
File file = new File(Repl.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
String root = file.getCanonicalPath().replace("classes", "").replaceAll("\\\\", "/"); // forward slashes works better also in windows!
File jnanorcFile = Paths.get(root, "jnanorc").toFile();
if (!jnanorcFile.exists()) {
FileWriter fw = new FileWriter(jnanorcFile);
fw.write("include " + root + "nanorc/*.nanorc\n");
fw.close();
}
//
// ScriptEngine and command registries
//
GroovyEngine scriptEngine = new GroovyEngine();
scriptEngine.put("ROOT", root);
ConfigurationPath configPath = new ConfigurationPath(Paths.get(root), Paths.get(root));
Printer printer = new DefaultPrinter(scriptEngine, configPath);
ConsoleEngineImpl consoleEngine = new ConsoleEngineImpl(scriptEngine
, printer
, Repl::workDir, configPath);
Builtins builtins = new Builtins(Repl::workDir, configPath, (String fun)-> new ConsoleEngine.WidgetCreator(consoleEngine, fun));
MyCommands myCommands = new MyCommands(Repl::workDir);
SystemRegistryImpl systemRegistry = new SystemRegistryImpl(parser, terminal, Repl::workDir, configPath);
systemRegistry.register("groovy", new GroovyCommand(scriptEngine, printer));
systemRegistry.setCommandRegistries(consoleEngine, builtins, myCommands);
systemRegistry.addCompleter(scriptEngine.getScriptCompleter());
systemRegistry.setScriptDescription(scriptEngine::scriptDescription);
//
// LineReader
//
Path jnanorc = configPath.getConfig("jnanorc");
SyntaxHighlighter commandHighlighter = SyntaxHighlighter.build(jnanorc,"COMMAND");
SyntaxHighlighter argsHighlighter = SyntaxHighlighter.build(jnanorc,"ARGS");
SyntaxHighlighter groovyHighlighter = SyntaxHighlighter.build(jnanorc,"Groovy");
LineReader reader = LineReaderBuilder.builder()
.terminal(terminal)
.completer(systemRegistry.completer())
.parser(parser)
.highlighter(new SystemHighlighter(commandHighlighter, argsHighlighter, groovyHighlighter))
.variable(LineReader.SECONDARY_PROMPT_PATTERN, "%M%P > ")
.variable(LineReader.INDENTATION, 2)
.variable(LineReader.LIST_MAX, 100)
.variable(LineReader.HISTORY_FILE, Paths.get(root, "history"))
.option(Option.INSERT_BRACKET, true)
.option(Option.EMPTY_WORD_OPTIONS, false)
.option(Option.USE_FORWARD_SLASH, true) // use forward slash in directory separator
.option(Option.DISABLE_EVENT_EXPANSION, true)
.build();
if (OSUtils.IS_WINDOWS) {
reader.setVariable(LineReader.BLINK_MATCHING_PAREN, 0); // if enabled cursor remains in begin parenthesis (gitbash)
}
//
// complete command registries
//
consoleEngine.setLineReader(reader);
builtins.setLineReader(reader);
myCommands.setLineReader(reader);
//
// widgets and console initialization
//
new TailTipWidgets(reader, systemRegistry::commandDescription, 5, TipType.COMPLETER);
KeyMap<Binding> keyMap = reader.getKeyMaps().get("main");
keyMap.bind(new Reference(Widgets.TAILTIP_TOGGLE), KeyMap.alt("s"));
systemRegistry.initialize(Paths.get(root, "init.jline").toFile());
//
// REPL-loop
//
System.out.println(terminal.getName() + ": " + terminal.getType());
while (true) {
try {
systemRegistry.cleanUp(); // delete temporary variables and reset output streams
String line = reader.readLine("groovy-repl> ");
line = parser.getCommand(line).startsWith("!") ? line.replaceFirst("!", "! ") : line;
Object result = systemRegistry.execute(line);
consoleEngine.println(result);
}
catch (UserInterruptException e) {
// Ignore
}
catch (EndOfFileException e) {
String pl = e.getPartialLine();
if (pl != null) { // execute last line from redirected file (required for Windows)
try {
consoleEngine.println(systemRegistry.execute(pl));
} catch (Exception e2) {
systemRegistry.trace(e2);
}
}
break;
}
catch (Exception e) {
systemRegistry.trace(e); // print exception and save it to console variable
}
}
systemRegistry.close(); // persist pipeline completer names etc
Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
boolean groovyRunning=false; // check Groovy GUI apps
for (Thread t : threadSet) {
if (t.getName().startsWith("AWT-Shut")) {
groovyRunning = true;
break;
}
}
if (groovyRunning) {
consoleEngine.println("Please, close Groovy Consoles/Object Browsers!");
}
}
catch (Throwable t) {
t.printStackTrace();
}
}
}