Pass StringReader instead of strings to CommandDispatcher

This commit is contained in:
boq 2018-07-16 16:21:03 +02:00
parent 235f0c9b28
commit 75dcc36066
8 changed files with 129 additions and 9 deletions

View file

@ -64,6 +64,10 @@ public class CommandDispatcher<S> {
} }
public int execute(final String input, final S source) throws CommandSyntaxException { public int execute(final String input, final S source) throws CommandSyntaxException {
return execute(new StringReader(input), source);
}
public int execute(final StringReader input, final S source) throws CommandSyntaxException {
final ParseResults<S> parse = parse(input, source); final ParseResults<S> parse = parse(input, source);
return execute(parse); return execute(parse);
} }
@ -151,9 +155,12 @@ public class CommandDispatcher<S> {
} }
public ParseResults<S> parse(final String command, final S source) { public ParseResults<S> parse(final String command, final S source) {
final StringReader reader = new StringReader(command); return parse(new StringReader(command), source);
}
public ParseResults<S> parse(final StringReader command, final S source) {
final CommandContextBuilder<S> context = new CommandContextBuilder<>(this, source, 0); final CommandContextBuilder<S> context = new CommandContextBuilder<>(this, source, 0);
return parseNodes(root, reader, context); return parseNodes(root, command, context);
} }
private static class PartialParse<S> { private static class PartialParse<S> {
@ -206,7 +213,7 @@ public class CommandDispatcher<S> {
childContext.withNode(child.getRedirect(), StringRange.between(cursor, reader.getCursor() - 1)); childContext.withNode(child.getRedirect(), StringRange.between(cursor, reader.getCursor() - 1));
final ParseResults<S> parse = parseNodes(child.getRedirect(), reader, childContext); final ParseResults<S> parse = parseNodes(child.getRedirect(), reader, childContext);
context.withChild(parse.getContext()); context.withChild(parse.getContext());
return new ParseResults<>(context, parse.getReader(), parse.getExceptions()); return new ParseResults<>(context, originalReader.getCursor(), parse.getReader(), parse.getExceptions());
} else { } else {
final ParseResults<S> parse = parseNodes(child, reader, context); final ParseResults<S> parse = parseNodes(child, reader, context);
if (potentials == null) { if (potentials == null) {
@ -218,7 +225,7 @@ public class CommandDispatcher<S> {
if (potentials == null) { if (potentials == null) {
potentials = new ArrayList<>(1); potentials = new ArrayList<>(1);
} }
potentials.add(new PartialParse<>(context, new ParseResults<>(context, reader, Collections.emptyMap()))); potentials.add(new PartialParse<>(context, new ParseResults<>(context, originalReader.getCursor(), reader, Collections.emptyMap())));
} }
} }
@ -243,7 +250,7 @@ public class CommandDispatcher<S> {
return potentials.get(0).parse; return potentials.get(0).parse;
} }
return new ParseResults<>(contextSoFar, originalReader, errors == null ? Collections.emptyMap() : errors); return new ParseResults<>(contextSoFar, originalReader.getCursor(), originalReader, errors == null ? Collections.emptyMap() : errors);
} }
public String[] getAllUsage(final CommandNode<S> node, final S source, final boolean restricted) { public String[] getAllUsage(final CommandNode<S> node, final S source, final boolean restricted) {
@ -346,7 +353,7 @@ public class CommandDispatcher<S> {
if (context.getNodes().isEmpty()) { if (context.getNodes().isEmpty()) {
parent = root; parent = root;
start = 0; start = parse.getStartIndex();
} else if (parse.getReader().canRead()) { } else if (parse.getReader().canRead()) {
final Map.Entry<CommandNode<S>, StringRange> entry = Iterables.getLast(context.getNodes().entrySet()); final Map.Entry<CommandNode<S>, StringRange> entry = Iterables.getLast(context.getNodes().entrySet());
parent = entry.getKey(); parent = entry.getKey();
@ -361,7 +368,7 @@ public class CommandDispatcher<S> {
start = entry.getValue().getEnd() + 1; start = entry.getValue().getEnd() + 1;
} else { } else {
parent = root; parent = root;
start = 0; start = parse.getStartIndex();
} }
@SuppressWarnings("unchecked") final CompletableFuture<Suggestions>[] futures = new CompletableFuture[parent.getChildren().size()]; @SuppressWarnings("unchecked") final CompletableFuture<Suggestions>[] futures = new CompletableFuture[parent.getChildren().size()];

View file

@ -10,16 +10,22 @@ import java.util.Map;
public class ParseResults<S> { public class ParseResults<S> {
private final CommandContextBuilder<S> context; private final CommandContextBuilder<S> context;
private final Map<CommandNode<S>, CommandSyntaxException> exceptions; private final Map<CommandNode<S>, CommandSyntaxException> exceptions;
private final int startIndex;
private final ImmutableStringReader reader; private final ImmutableStringReader reader;
public ParseResults(final CommandContextBuilder<S> context, final ImmutableStringReader reader, final Map<CommandNode<S>, CommandSyntaxException> exceptions) { public ParseResults(final CommandContextBuilder<S> context, final int startIndex, final ImmutableStringReader reader, final Map<CommandNode<S>, CommandSyntaxException> exceptions) {
this.context = context; this.context = context;
this.startIndex = startIndex;
this.reader = reader; this.reader = reader;
this.exceptions = exceptions; this.exceptions = exceptions;
} }
public ParseResults(final CommandContextBuilder<S> context) { public ParseResults(final CommandContextBuilder<S> context) {
this(context, new StringReader(""), Collections.emptyMap()); this(context, 0, new StringReader(""), Collections.emptyMap());
}
public int getStartIndex() {
return startIndex;
} }
public CommandContextBuilder<S> getContext() { public CommandContextBuilder<S> getContext() {

View file

@ -120,4 +120,9 @@ public class ArgumentCommandNode<S, T> extends CommandNode<S> {
public Collection<String> getExamples() { public Collection<String> getExamples() {
return type.getExamples(); return type.getExamples();
} }
@Override
public String toString() {
return "<argument " + name + ":" + type +">";
}
} }

View file

@ -118,4 +118,9 @@ public class LiteralCommandNode<S> extends CommandNode<S> {
public Collection<String> getExamples() { public Collection<String> getExamples() {
return Collections.singleton(literal); return Collections.singleton(literal);
} }
@Override
public String toString() {
return "<literal " + literal + ">";
}
} }

View file

@ -62,4 +62,9 @@ public class RootCommandNode<S> extends CommandNode<S> {
public Collection<String> getExamples() { public Collection<String> getExamples() {
return Collections.emptyList(); return Collections.emptyList();
} }
@Override
public String toString() {
return "<root>";
}
} }

View file

@ -43,6 +43,12 @@ public class CommandDispatcherTest {
when(command.run(any())).thenReturn(42); when(command.run(any())).thenReturn(42);
} }
private static StringReader inputWithOffset(final String input, final int offset) {
final StringReader result = new StringReader(input);
result.setCursor(offset);
return result;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void testCreateAndExecuteCommand() throws Exception { public void testCreateAndExecuteCommand() throws Exception {
@ -52,6 +58,15 @@ public class CommandDispatcherTest {
verify(command).run(any(CommandContext.class)); verify(command).run(any(CommandContext.class));
} }
@SuppressWarnings("unchecked")
@Test
public void testCreateAndExecuteOffsetCommand() throws Exception {
subject.register(literal("foo").executes(command));
assertThat(subject.execute(inputWithOffset("/foo", 1), source), is(42));
verify(command).run(any(CommandContext.class));
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void testCreateAndMergeCommands() throws Exception { public void testCreateAndMergeCommands() throws Exception {

View file

@ -102,6 +102,10 @@ public class CommandDispatcherUsagesTest {
return Iterators.getLast(subject.parse(command, source).getContext().getNodes().keySet().iterator()); return Iterators.getLast(subject.parse(command, source).getContext().getNodes().keySet().iterator());
} }
private CommandNode<Object> get(final StringReader command) {
return Iterators.getLast(subject.parse(command, source).getContext().getNodes().keySet().iterator());
}
@Test @Test
public void testAllUsage_noCommands() throws Exception { public void testAllUsage_noCommands() throws Exception {
subject = new CommandDispatcher<>(); subject = new CommandDispatcher<>();
@ -174,4 +178,18 @@ public class CommandDispatcherUsagesTest {
.build() .build()
)); ));
} }
@Test
public void testSmartUsage_offsetH() throws Exception {
final StringReader offsetH = new StringReader("/|/|/h");
offsetH.setCursor(5);
final Map<CommandNode<Object>, String> results = subject.getSmartUsage(get(offsetH), source);
assertThat(results, equalTo(ImmutableMap.builder()
.put(get("h 1"), "[1] i")
.put(get("h 2"), "[2] i ii")
.put(get("h 3"), "[3]")
.build()
));
}
} }

View file

@ -30,6 +30,12 @@ public class CommandSuggestionsTest {
subject = new CommandDispatcher<>(); subject = new CommandDispatcher<>();
} }
private static StringReader inputWithOffset(final String input, final int offset) {
final StringReader result = new StringReader(input);
result.setCursor(offset);
return result;
}
@Test @Test
public void getCompletionSuggestions_rootCommands() throws Exception { public void getCompletionSuggestions_rootCommands() throws Exception {
subject.register(literal("foo")); subject.register(literal("foo"));
@ -42,6 +48,18 @@ public class CommandSuggestionsTest {
assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.at(0), "bar"), new Suggestion(StringRange.at(0), "baz"), new Suggestion(StringRange.at(0), "foo")))); assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.at(0), "bar"), new Suggestion(StringRange.at(0), "baz"), new Suggestion(StringRange.at(0), "foo"))));
} }
@Test
public void getCompletionSuggestions_rootCommands_withInputOffset() throws Exception {
subject.register(literal("foo"));
subject.register(literal("bar"));
subject.register(literal("baz"));
final Suggestions result = subject.getCompletionSuggestions(subject.parse(inputWithOffset("OOO", 3), source)).join();
assertThat(result.getRange(), equalTo(StringRange.at(3)));
assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.at(3), "bar"), new Suggestion(StringRange.at(3), "baz"), new Suggestion(StringRange.at(3), "foo"))));
}
@Test @Test
public void getCompletionSuggestions_rootCommands_partial() throws Exception { public void getCompletionSuggestions_rootCommands_partial() throws Exception {
subject.register(literal("foo")); subject.register(literal("foo"));
@ -54,6 +72,18 @@ public class CommandSuggestionsTest {
assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.between(0, 1), "bar"), new Suggestion(StringRange.between(0, 1), "baz")))); assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.between(0, 1), "bar"), new Suggestion(StringRange.between(0, 1), "baz"))));
} }
@Test
public void getCompletionSuggestions_rootCommands_partial_withInputOffset() throws Exception {
subject.register(literal("foo"));
subject.register(literal("bar"));
subject.register(literal("baz"));
final Suggestions result = subject.getCompletionSuggestions(subject.parse(inputWithOffset("Zb", 1), source)).join();
assertThat(result.getRange(), equalTo(StringRange.between(1, 2)));
assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.between(1, 2), "bar"), new Suggestion(StringRange.between(1, 2), "baz"))));
}
@Test @Test
public void getCompletionSuggestions_subCommands() throws Exception { public void getCompletionSuggestions_subCommands() throws Exception {
subject.register( subject.register(
@ -85,6 +115,22 @@ public class CommandSuggestionsTest {
assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.between(7, 8), "bar"), new Suggestion(StringRange.between(7, 8), "baz")))); assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.between(7, 8), "bar"), new Suggestion(StringRange.between(7, 8), "baz"))));
} }
@Test
public void getCompletionSuggestions_subCommands_partial_withInputOffset() throws Exception {
subject.register(
literal("parent")
.then(literal("foo"))
.then(literal("bar"))
.then(literal("baz"))
);
final ParseResults<Object> parse = subject.parse(inputWithOffset("junk parent b", 5), source);
final Suggestions result = subject.getCompletionSuggestions(parse).join();
assertThat(result.getRange(), equalTo(StringRange.between(12, 13)));
assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.between(12, 13), "bar"), new Suggestion(StringRange.between(12, 13), "baz"))));
}
@Test @Test
public void getCompletionSuggestions_redirect() throws Exception { public void getCompletionSuggestions_redirect() throws Exception {
final LiteralCommandNode<Object> actual = subject.register(literal("actual").then(literal("sub"))); final LiteralCommandNode<Object> actual = subject.register(literal("actual").then(literal("sub")));
@ -109,6 +155,19 @@ public class CommandSuggestionsTest {
assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.between(9, 10), "sub")))); assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.between(9, 10), "sub"))));
} }
@Test
public void getCompletionSuggestions_redirectPartial_withInputOffset() throws Exception {
final LiteralCommandNode<Object> actual = subject.register(literal("actual").then(literal("sub")));
subject.register(literal("redirect").redirect(actual));
final ParseResults<Object> parse = subject.parse(inputWithOffset("/redirect s", 1), source);
final Suggestions result = subject.getCompletionSuggestions(parse).join();
assertThat(result.getRange(), equalTo(StringRange.between(10, 11)));
assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.between(10, 11), "sub"))));
}
@Test @Test
public void getCompletionSuggestions_redirect_lots() throws Exception { public void getCompletionSuggestions_redirect_lots() throws Exception {
final LiteralCommandNode<Object> loop = subject.register(literal("redirect")); final LiteralCommandNode<Object> loop = subject.register(literal("redirect"));