Author: gnodet Date: Fri Sep 3 19:51:43 2010 New Revision: 992437 URL: http://svn.apache.org/viewvc?rev=992437&view=rev Log: KARAF-187: The completers do not work when several commands are on the same line separated by a column or pipe
Added: karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/Parser.java karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/console/completer/ karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/console/completer/ArgumentCompleterTest.java Removed: karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/SessionScopeCompleter.java Modified: karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/ArgumentCompleter.java karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/CommandsCompleter.java karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/jline/Console.java Modified: karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/ArgumentCompleter.java URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/ArgumentCompleter.java?rev=992437&r1=992436&r2=992437&view=diff ============================================================================== --- karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/ArgumentCompleter.java (original) +++ karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/ArgumentCompleter.java Fri Sep 3 19:51:43 2010 @@ -24,7 +24,8 @@ */ package org.apache.karaf.shell.console.completer; -import java.util.*; +import java.util.LinkedList; +import java.util.List; import org.apache.karaf.shell.console.Completer; @@ -62,7 +63,7 @@ public class ArgumentCompleter implement * @param completers the embedded argument completers */ public ArgumentCompleter(final Completer[] completers) { - this(completers, new WhitespaceArgumentDelimiter()); + this(completers, new GogoArgumentDelimiter()); } /** @@ -210,69 +211,32 @@ public class ArgumentCompleter implement } /** - * Abstract implementation of a delimiter that uses the - * {...@link #isDelimiter} method to determine if a particular - * character should be used as a delimiter. + * Implementation of a delimiter that uses the + * Gogo parser. * - * @author <a href="mailto:m...@cornell.edu">Marc Prud'hommeaux</a> + * @author <a href="mailto:gno...@gmail.com">Guillaume Nodet</a> */ - public abstract static class AbstractArgumentDelimiter - implements ArgumentDelimiter { - private char[] quoteChars = new char[] { '\'', '"' }; - private char[] escapeChars = new char[] { '\\' }; - - public void setQuoteChars(final char[] quoteChars) { - this.quoteChars = quoteChars; - } - - public char[] getQuoteChars() { - return this.quoteChars; - } - - public void setEscapeChars(final char[] escapeChars) { - this.escapeChars = escapeChars; - } - - public char[] getEscapeChars() { - return this.escapeChars; - } + public static class GogoArgumentDelimiter implements ArgumentDelimiter { public ArgumentList delimit(final String buffer, final int cursor) { - List<String> args = new LinkedList<String>(); - StringBuffer arg = new StringBuffer(); - int argpos = -1; - int bindex = -1; - - for (int i = 0; (buffer != null) && (i <= buffer.length()); i++) { - // once we reach the cursor, set the - // position of the selected index - if (i == cursor) { - bindex = args.size(); - // the position in the current argument is just the - // length of the current argument - argpos = arg.length(); - } - - if ((i == buffer.length()) || isDelimiter(buffer, i)) { - if (arg.length() > 0) { - args.add(arg.toString()); - arg.setLength(0); // reset the arg - } - } else { - arg.append(buffer.charAt(i)); + Parser parser = new Parser(buffer, cursor); + try { + List<List<List<CharSequence>>> program = parser.program(); + List<CharSequence> pipe = program.get(parser.c0).get(parser.c1); + List<String> args = new LinkedList<String>(); + for (CharSequence arg : pipe) { + args.add(arg.toString()); } + return new ArgumentList(args.toArray(new String[args.size()]), parser.c2, parser.c3, cursor); + } catch (Throwable t) { + return new ArgumentList(new String[] { buffer }, 0, cursor, cursor); } - - return new ArgumentList(args. - toArray(new String[args.size()]), bindex, argpos, cursor); } /** * Returns true if the specified character is a whitespace * parameter. Check to ensure that the character is not - * escaped by any of - * {...@link #getQuoteChars}, and is not escaped by ant of the - * {...@link #getEscapeChars}, and returns true from + * escaped and returns true from * {...@link #isDelimiterChar}. * * @param buffer the complete command buffer @@ -280,64 +244,21 @@ public class ArgumentCompleter implement * @return true if the character should be a delimiter */ public boolean isDelimiter(final String buffer, final int pos) { - if (isQuoted(buffer, pos)) { - return false; - } - - if (isEscaped(buffer, pos)) { - return false; - } - - return isDelimiterChar(buffer, pos); - } - - public boolean isQuoted(final String buffer, final int pos) { - return false; + return !isEscaped(buffer, pos) && isDelimiterChar(buffer, pos); } public boolean isEscaped(final String buffer, final int pos) { - if (pos <= 0) { - return false; - } - - for (int i = 0; (escapeChars != null) && (i < escapeChars.length); - i++) { - if (buffer.charAt(pos) == escapeChars[i]) { - return !isEscaped(buffer, pos - 1); // escape escape - } - } - - return false; + return pos > 0 && buffer.charAt(pos) == '\\' && !isEscaped(buffer, pos - 1); } /** - * Returns true if the character at the specified position - * if a delimiter. This method will only be called if the - * character is not enclosed in any of the - * {...@link #getQuoteChars}, and is not escaped by ant of the - * {...@link #getEscapeChars}. To perform escaping manually, - * override {...@link #isDelimiter} instead. - */ - public abstract boolean isDelimiterChar(String buffer, int pos); - } - - /** - * {...@link ArgumentCompleter.ArgumentDelimiter} - * implementation that counts all - * whitespace (as reported by {...@link Character#isWhitespace}) - * as being a delimiter. - * - * @author <a href="mailto:m...@cornell.edu">Marc Prud'hommeaux</a> - */ - public static class WhitespaceArgumentDelimiter - extends AbstractArgumentDelimiter { - /** * The character is a delimiter if it is whitespace, and the * preceeding character is not an escape character. */ public boolean isDelimiterChar(String buffer, int pos) { return Character.isWhitespace(buffer.charAt(pos)); } + } /** Modified: karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/CommandsCompleter.java URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/CommandsCompleter.java?rev=992437&r1=992436&r2=992437&view=diff ============================================================================== --- karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/CommandsCompleter.java (original) +++ karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/CommandsCompleter.java Fri Sep 3 19:51:43 2010 @@ -67,7 +67,7 @@ public class CommandsCompleter implement function = unProxy(function); if (function instanceof AbstractCommand) { List<Completer> cl = new ArrayList<Completer>(); - cl.add(new StringsCompleter(new String[] { command })); + cl.add(new StringsCompleter(getNames(command))); if (function instanceof CompletableFunction) { List<Completer> fcl = ((CompletableFunction) function).getCompleters(); if (fcl != null) { @@ -87,6 +87,11 @@ public class CommandsCompleter implement } } + private String[] getNames(String command) { + String[] s = command.split(":"); + return new String[] { command, s[1] }; + } + protected Function unProxy(Function function) { try { if (function instanceof CommandProxy) { Added: karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/Parser.java URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/Parser.java?rev=992437&view=auto ============================================================================== --- karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/Parser.java (added) +++ karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/Parser.java Fri Sep 3 19:51:43 2010 @@ -0,0 +1,472 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +// DWB14: parser loops if // comment at start of program +// DWB15: allow program to have trailing ';' +package org.apache.karaf.shell.console.completer; + +import java.util.ArrayList; +import java.util.List; + +public class Parser +{ + int current = 0; + CharSequence text; + boolean escaped; + static final String SPECIAL = "<;|{[\"'$`(="; + + List<List<List<CharSequence>>> program; + List<List<CharSequence>> statements; + List<CharSequence> statement; + int cursor; + int start = -1; + int c0; + int c1; + int c2; + int c3; + + public Parser(CharSequence text, int cursor) + { + this.text = text; + this.cursor = cursor; + } + + void ws() + { + // derek: BUGFIX: loop if comment at beginning of input + //while (!eof() && Character.isWhitespace(peek())) { + while (!eof() && (!escaped && Character.isWhitespace(peek()) || current == 0)) + { + if (current != 0 || !escaped && Character.isWhitespace(peek())) + { + current++; + } + if (peek() == '/' && current < text.length() - 2 + && text.charAt(current + 1) == '/') + { + comment(); + } + if (current == 0) + { + break; + } + } + } + + private void comment() + { + while (!eof() && peek() != '\n' && peek() != '\r') + { + next(); + } + } + + boolean eof() + { + return current >= text.length(); + } + + char peek() + { + return peek(false); + } + + char peek(boolean increment) + { + escaped = false; + if (eof()) + { + return 0; + } + + int last = current; + char c = text.charAt(current++); + + if (c == '\\') + { + escaped = true; + if (eof()) + { + throw new RuntimeException("Eof found after \\"); // derek + } + + c = text.charAt(current++); + + switch (c) + { + case 't': + c = '\t'; + break; + case '\r': + case '\n': + c = ' '; + break; + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 'u': + c = unicode(); + current += 4; + break; + default: + // We just take the next character literally + // but have the escaped flag set, important for {},[] etc + } + } + if (cursor > last && cursor <= current) + { + c0 = program != null ? program.size() : 0; + c1 = statements != null ? statements.size() : 0; + c2 = statement != null ? statement.size() : 0; + c3 = (start >= 0) ? current - start : 0; + } + if (!increment) + { + current = last; + } + return c; + } + + public List<List<List<CharSequence>>> program() + { + program = new ArrayList<List<List<CharSequence>>>(); + ws(); + if (!eof()) + { + program.add(pipeline()); + while (peek() == ';') + { + current++; + List<List<CharSequence>> pipeline = pipeline(); + program.add(pipeline); + } + } + if (!eof()) + { + throw new RuntimeException("Program has trailing text: " + context(current)); + } + + List<List<List<CharSequence>>> p = program; + program = null; + return p; + } + + CharSequence context(int around) + { + return text.subSequence(Math.max(0, current - 20), Math.min(text.length(), + current + 4)); + } + + public List<List<CharSequence>> pipeline() + { + statements = new ArrayList<List<CharSequence>>(); + statements.add(statement()); + while (peek() == '|') + { + current++; + ws(); + if (!eof()) + { + statements.add(statement()); + } + else + { + statements.add(new ArrayList<CharSequence>()); + break; + } + } + List<List<CharSequence>> s = statements; + statements = null; + return s; + } + + public List<CharSequence> statement() + { + statement = new ArrayList<CharSequence>(); + statement.add(value()); + while (!eof()) + { + ws(); + if (peek() == '|' || peek() == ';') + { + break; + } + + if (!eof()) + { + statement.add(messy()); + } + } + List<CharSequence> s = statement; + statement = null; + return s; + } + + public CharSequence messy() + { + char c = peek(); + if (c > 0 && SPECIAL.indexOf(c) < 0) + { + start = current++; + try { + while (!eof()) + { + c = peek(); + if (!escaped && (c == ';' || c == '|' || Character.isWhitespace(c))) + { + break; + } + next(); + } + return text.subSequence(start, current); + } finally { + start = -1; + } + } + else + { + return value(); + } + } + + CharSequence value() + { + ws(); + + start = current; + try { + char c = next(); + if (!escaped) + { + switch (c) + { + case '{': + return text.subSequence(start, find('}', '{')); + case '(': + return text.subSequence(start, find(')', '(')); + case '[': + return text.subSequence(start, find(']', '[')); + case '<': + return text.subSequence(start, find('>', '<')); + case '=': + return text.subSequence(start, current); + case '"': + case '\'': + quote(c); + break; + } + } + + // Some identifier or number + while (!eof()) + { + c = peek(); + if (!escaped) + { + if (Character.isWhitespace(c) || c == ';' || c == '|' || c == '=') + { + break; + } + else if (c == '{') + { + next(); + find('}', '{'); + } + else if (c == '(') + { + next(); + find(')', '('); + } + else if (c == '<') + { + next(); + find('>', '<'); + } + else if (c == '[') + { + next(); + find(']', '['); + } + else if (c == '\'' || c == '"') + { + next(); + quote(c); + next(); + } + else + { + next(); + } + } + else + { + next(); + } + } + return text.subSequence(start, current); + } finally { + start = -1; + } + } + + boolean escaped() + { + return escaped; + } + + char next() + { + return peek(true); + } + + char unicode() + { + if (current + 4 > text.length()) + { + throw new IllegalArgumentException("Unicode \\u escape at eof at pos ..." + + context(current) + "..."); + } + + String s = text.subSequence(current, current + 4).toString(); + int n = Integer.parseInt(s, 16); + return (char) n; + } + + int find(char target, char deeper) + { + int start = current; + int level = 1; + + while (level != 0) + { + if (eof()) + { + throw new RuntimeException("Eof found in the middle of a compound for '" + + target + deeper + "', begins at " + context(start)); + } + + char c = next(); + if (!escaped) + { + if (c == target) + { + level--; + } + else + { + if (c == deeper) + { + level++; + } + else + { + if (c == '"') + { + quote('"'); + } + else + { + if (c == '\'') + { + quote('\''); + } + else + { + if (c == '`') + { + quote('`'); + } + } + } + } + } + } + } + return current; + } + + int quote(char which) + { + while (!eof() && (peek() != which || escaped)) + { + next(); + } + + return current++; + } + + CharSequence findVar() + { + int start = current; + char c = peek(); + + if (c == '{') + { + next(); + int end = find('}', '{'); + return text.subSequence(start, end); + } + if (c == '(') + { + next(); + int end = find(')', '('); + return text.subSequence(start, end); + } + + if (Character.isJavaIdentifierPart(c)) + { + while (c == '$') + { + c = next(); + } + while (!eof() && (Character.isJavaIdentifierPart(c) || c == '.') && c != '$') + { + next(); + c = peek(); + } + return text.subSequence(start, current); + } + throw new IllegalArgumentException( + "Reference to variable does not match syntax of a variable: " + + context(start)); + } + + public String toString() + { + return "..." + context(current) + "..."; + } + + public String unescape() + { + StringBuilder sb = new StringBuilder(); + while (!eof()) + { + sb.append(next()); + } + return sb.toString(); + } +} Modified: karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/jline/Console.java URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/jline/Console.java?rev=992437&r1=992436&r2=992437&view=diff ============================================================================== --- karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/jline/Console.java (original) +++ karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/jline/Console.java Fri Sep 3 19:51:43 2010 @@ -28,7 +28,6 @@ import java.io.InterruptedIOException; import java.io.PrintStream; import java.io.PrintWriter; import java.io.Reader; -import java.util.Arrays; import java.util.Map; import java.util.Properties; import java.util.concurrent.ArrayBlockingQueue; @@ -42,9 +41,7 @@ import jline.Terminal; import jline.UnsupportedTerminal; import org.apache.karaf.shell.console.CloseShellException; import org.apache.karaf.shell.console.Completer; -import org.apache.karaf.shell.console.completer.AggregateCompleter; import org.apache.karaf.shell.console.completer.CommandsCompleter; -import org.apache.karaf.shell.console.completer.SessionScopeCompleter; import org.fusesource.jansi.Ansi; import org.osgi.service.command.CommandProcessor; import org.osgi.service.command.CommandSession; @@ -242,13 +239,7 @@ public class Console implements Runnable } protected Completer createCompleter() { - Completer completer = new CommandsCompleter(session); - return new AggregateCompleter( - Arrays.asList( - completer, - new SessionScopeCompleter( session, completer ) - ) - ); + return new CommandsCompleter(session); } protected Properties loadBrandingProperties() { Added: karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/console/completer/ArgumentCompleterTest.java URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/console/completer/ArgumentCompleterTest.java?rev=992437&view=auto ============================================================================== --- karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/console/completer/ArgumentCompleterTest.java (added) +++ karaf/trunk/shell/console/src/test/java/org/apache/karaf/shell/console/completer/ArgumentCompleterTest.java Fri Sep 3 19:51:43 2010 @@ -0,0 +1,77 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.karaf.shell.console.completer; + +import java.util.List; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class ArgumentCompleterTest { + + @Test + public void testParser1() throws Exception { + Parser parser = new Parser("echo foo | cat bar ; ta", 23); + List<List<List<CharSequence>>> p = parser.program(); + assertEquals(1, parser.c0); + assertEquals(0, parser.c1); + assertEquals(0, parser.c2); + assertEquals(2, parser.c3); + } + + @Test + public void testParser2() throws Exception { + Parser parser = new Parser("echo foo ; cat bar | ta", 23); + List<List<List<CharSequence>>> p = parser.program(); + assertEquals(1, parser.c0); + assertEquals(1, parser.c1); + assertEquals(0, parser.c2); + assertEquals(2, parser.c3); + } + + @Test + public void testParser3() throws Exception { + Parser parser = new Parser("echo foo ; cat bar | ta", 22); + List<List<List<CharSequence>>> p = parser.program(); + assertEquals(1, parser.c0); + assertEquals(1, parser.c1); + assertEquals(0, parser.c2); + assertEquals(1, parser.c3); + } + + @Test + public void testParser4() throws Exception { + Parser parser = new Parser("echo foo ; cat bar | ta reta", 27); + List<List<List<CharSequence>>> p = parser.program(); + assertEquals(1, parser.c0); + assertEquals(1, parser.c1); + assertEquals(1, parser.c2); + assertEquals(3, parser.c3); + } + + @Test + public void testParser5() throws Exception { + Parser parser = new Parser("echo foo ; cat bar | ta reta", 24); + List<List<List<CharSequence>>> p = parser.program(); + assertEquals(1, parser.c0); + assertEquals(1, parser.c1); + assertEquals(1, parser.c2); + assertEquals(0, parser.c3); + } + +}