Skip to content

Commit 8ea92ce

Browse files
authored
fix: Make command execution order consistent in SystemRegistryImpl.execute method (#1237)
This fixes issue #1232 by changing the order of command execution checking in the execute method to be consistent with other methods in the class. Now it checks custom registries first, then scripts, and finally local commands, which allows overriding built-in commands like 'exit' and 'clear' with custom implementations. Added a test that demonstrates the ability to override built-in commands with custom implementations.
1 parent 97c0ad9 commit 8ea92ce

File tree

2 files changed

+151
-10
lines changed

2 files changed

+151
-10
lines changed

console/src/main/java/org/jline/console/impl/SystemRegistryImpl.java

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,18 +1229,16 @@ private Object execute(String command, String rawLine, String[] args) throws Exc
12291229
throw new UnknownCommandException("Invalid command: " + rawLine);
12301230
}
12311231
Object out;
1232-
if (isLocalCommand(command)) {
1232+
int id = registryId(command);
1233+
if (id > -1) {
1234+
Object[] _args = consoleId != null ? consoleEngine().expandParameters(args) : args;
1235+
out = commandRegistries[id].invoke(outputStream.getCommandSession(), command, _args);
1236+
} else if (scriptStore.hasScript(command) && consoleEngine() != null) {
1237+
out = consoleEngine().execute(command, rawLine, args);
1238+
} else if (isLocalCommand(command)) {
12331239
out = localExecute(command, consoleId != null ? consoleEngine().expandParameters(args) : args);
12341240
} else {
1235-
int id = registryId(command);
1236-
if (id > -1) {
1237-
Object[] _args = consoleId != null ? consoleEngine().expandParameters(args) : args;
1238-
out = commandRegistries[id].invoke(outputStream.getCommandSession(), command, _args);
1239-
} else if (scriptStore.hasScript(command) && consoleEngine() != null) {
1240-
out = consoleEngine().execute(command, rawLine, args);
1241-
} else {
1242-
throw new UnknownCommandException("Unknown command: " + command);
1243-
}
1241+
throw new UnknownCommandException("Unknown command: " + command);
12441242
}
12451243
return out;
12461244
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Copyright (c) 2024, the original author(s).
3+
*
4+
* This software is distributable under the BSD license. See the terms of the
5+
* BSD license in the documentation provided with this software.
6+
*
7+
* https://opensource.org/licenses/BSD-3-Clause
8+
*/
9+
package org.jline.console.impl;
10+
11+
import java.io.IOException;
12+
import java.nio.file.Path;
13+
import java.nio.file.Paths;
14+
import java.util.ArrayList;
15+
import java.util.HashMap;
16+
import java.util.List;
17+
import java.util.Map;
18+
import java.util.Set;
19+
import java.util.function.Supplier;
20+
21+
import org.jline.builtins.ConfigurationPath;
22+
import org.jline.console.CommandInput;
23+
import org.jline.console.CommandMethods;
24+
import org.jline.console.CommandRegistry;
25+
import org.jline.reader.Parser;
26+
import org.jline.reader.impl.DefaultParser;
27+
import org.jline.terminal.Terminal;
28+
import org.jline.terminal.TerminalBuilder;
29+
import org.junit.jupiter.api.BeforeEach;
30+
import org.junit.jupiter.api.Test;
31+
32+
import static org.junit.jupiter.api.Assertions.assertEquals;
33+
34+
/**
35+
* Tests for SystemRegistryImpl class.
36+
*/
37+
public class SystemRegistryImplTest {
38+
39+
private Terminal terminal;
40+
private Parser parser;
41+
private Supplier<Path> workDir;
42+
private ConfigurationPath configPath;
43+
private SystemRegistryImpl registry;
44+
private TestCommandRegistry testRegistry;
45+
private StringBuilder output;
46+
47+
@BeforeEach
48+
public void setUp() throws IOException {
49+
terminal = TerminalBuilder.builder().dumb(true).build();
50+
parser = new DefaultParser();
51+
workDir = () -> Paths.get(System.getProperty("user.dir"));
52+
configPath = new ConfigurationPath(Paths.get("."), Paths.get("."));
53+
output = new StringBuilder();
54+
55+
registry = new SystemRegistryImpl(parser, terminal, workDir, configPath);
56+
testRegistry = new TestCommandRegistry(output);
57+
58+
// Set up the registry with our test command registry
59+
registry.setCommandRegistries(testRegistry);
60+
}
61+
62+
/**
63+
* Test that demonstrates the ability to override built-in commands like "exit"
64+
* with custom implementations in a command registry.
65+
*
66+
* This test verifies the fix for issue #1232 where the order of command execution
67+
* checking in the execute method was inconsistent with other methods.
68+
*/
69+
@Test
70+
public void testOverrideBuiltinCommand() throws Exception {
71+
// The "exit" command is a built-in command in SystemRegistryImpl
72+
// Our TestCommandRegistry also has an "exit" command
73+
// After our fix, the registry should use the TestCommandRegistry's "exit" command
74+
75+
// Execute the "exit" command
76+
registry.execute("exit");
77+
78+
// Verify that our custom "exit" command was executed
79+
assertEquals("Custom exit command executed", output.toString().trim());
80+
}
81+
82+
/**
83+
* A test command registry that provides a custom implementation of the "exit" command.
84+
*/
85+
private static class TestCommandRegistry implements CommandRegistry {
86+
private final Map<String, CommandMethods> commandExecute = new HashMap<>();
87+
private final StringBuilder output;
88+
89+
public TestCommandRegistry(StringBuilder output) {
90+
this.output = output;
91+
// Register our custom "exit" command
92+
commandExecute.put("exit", new CommandMethods(this::exit, this::defaultCompleter));
93+
}
94+
95+
private Object exit(CommandInput input) {
96+
output.append("Custom exit command executed");
97+
return null;
98+
}
99+
100+
private List<org.jline.reader.Completer> defaultCompleter(String command) {
101+
return new ArrayList<>();
102+
}
103+
104+
@Override
105+
public Object invoke(CommandRegistry.CommandSession session, String command, Object... args) throws Exception {
106+
return commandExecute.get(command).execute().apply(new CommandInput(command, args, session));
107+
}
108+
109+
@Override
110+
public boolean hasCommand(String command) {
111+
return commandExecute.containsKey(command);
112+
}
113+
114+
@Override
115+
public Set<String> commandNames() {
116+
return commandExecute.keySet();
117+
}
118+
119+
@Override
120+
public Map<String, String> commandAliases() {
121+
return new HashMap<>();
122+
}
123+
124+
@Override
125+
public List<String> commandInfo(String command) {
126+
List<String> info = new ArrayList<>();
127+
if (command.equals("exit")) {
128+
info.add("Custom exit command");
129+
}
130+
return info;
131+
}
132+
133+
@Override
134+
public org.jline.console.CmdDesc commandDescription(List<String> args) {
135+
return new org.jline.console.CmdDesc(false);
136+
}
137+
138+
@Override
139+
public org.jline.reader.impl.completer.SystemCompleter compileCompleters() {
140+
return new org.jline.reader.impl.completer.SystemCompleter();
141+
}
142+
}
143+
}

0 commit comments

Comments
 (0)