Added: felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Main.java URL: http://svn.apache.org/viewvc/felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Main.java?rev=1735995&view=auto ============================================================================== --- felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Main.java (added) +++ felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Main.java Mon Mar 21 16:53:06 2016 @@ -0,0 +1,113 @@ +/* + * 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.felix.gogo.jline; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; + +import org.apache.felix.gogo.jline.Shell.Context; +import org.apache.felix.gogo.jline.ssh.Ssh; +import org.apache.felix.gogo.jline.telnet.Telnet; +import org.apache.felix.gogo.runtime.CommandProcessorImpl; +import org.apache.felix.gogo.runtime.threadio.ThreadIOImpl; +import org.apache.felix.service.command.CommandSession; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; + +public class Main { + + public static void main(String[] args) throws IOException { + InputStream sin = System.in; + PrintStream sout = System.out; + PrintStream serr = System.err; + + try (Terminal terminal = TerminalBuilder.builder() + .name("gogo") + .type(System.getenv("TERM")) + .system(true) + .streams(sin, sout) + .build()) { + ThreadIOImpl tio = new ThreadIOImpl(); + tio.start(); + try { + CommandProcessorImpl processor = new CommandProcessorImpl(tio); + Context context = new MyContext(); + Shell shell = new Shell(context, processor, terminal); + processor.addCommand("gogo", processor, "addCommand"); + processor.addCommand("gogo", processor, "removeCommand"); + processor.addCommand("gogo", processor, "eval"); + register(processor, new Builtin(), Builtin.functions); + register(processor, new Procedural(), Procedural.functions); + register(processor, new Posix(), Posix.functions); + register(processor, shell, Shell.functions); + register(processor, new JLineCommands(processor), JLineCommands.functions); + try { + register(processor, new Telnet(processor), Telnet.functions); + } catch (Throwable t) { + // ignore + } + try { + register(processor, new Ssh(processor), Ssh.functions); + } catch (Throwable t) { + // ignore + } + PrintStream pout = new PrintStream(terminal.output()); + CommandSession session = processor.createSession(terminal.input(), pout, pout); + session.put(Shell.VAR_CONTEXT, context); + session.put(Shell.VAR_TERMINAL, terminal); + try { + String[] argv = new String[args.length + 1]; + argv[0] = "--login"; + System.arraycopy(args, 0, argv, 1, args.length); + shell.gosh(session, argv); + } catch (Exception e) { + Object loc = session.get(".location"); + if (null == loc || !loc.toString().contains(":")) { + loc = "gogo"; + } + + System.err.println(loc + ": " + e.getClass().getSimpleName() + ": " + e.getMessage()); + e.printStackTrace(); + } finally { + session.close(); + } + } finally { + tio.stop(); + } + } + } + + static void register(CommandProcessorImpl processor, Object target, String[] functions) { + for (String function : functions) { + processor.addCommand("gogo", target, function); + } + } + + private static class MyContext implements Context { + + public String getProperty(String name) { + return System.getProperty(name); + } + + public void exit() throws Exception { + System.exit(0); + } + } +}
Added: felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java URL: http://svn.apache.org/viewvc/felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java?rev=1735995&view=auto ============================================================================== --- felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java (added) +++ felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java Mon Mar 21 16:53:06 2016 @@ -0,0 +1,97 @@ +/* + * 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.felix.gogo.jline; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.felix.gogo.runtime.Parser.Program; +import org.apache.felix.gogo.runtime.Token; +import org.jline.reader.ParsedLine; + +public class ParsedLineImpl implements ParsedLine { + + private final Program program; + private final String source; + private final int cursor; + private final List<String> tokens; + private final int wordIndex; + private final int wordCursor; + + public ParsedLineImpl(Program program, Token line, int cursor, List<Token> tokens) { + this.program = program; + this.source = line.toString(); + this.cursor = cursor - line.start(); + this.tokens = new ArrayList<String>(); + for (Token token : tokens) { + this.tokens.add(token.toString()); + } + int wi = tokens.size(); + int wc = 0; + if (cursor >= 0) { + for (int i = 0; i < tokens.size(); i++) { + Token t = tokens.get(i); + if (t.start() > cursor) { + wi = i; + wc = 0; + this.tokens.add(i, ""); + break; + } + if (t.start() + t.length() >= cursor) { + wi = i; + wc = cursor - t.start(); + break; + } + } + } + if (wi == tokens.size()) { + this.tokens.add(""); + } + wordIndex = wi; + wordCursor = wc; + } + + public String word() { + return tokens.get(wordIndex()); + } + + public int wordCursor() { + return wordCursor; + } + + public int wordIndex() { + return wordIndex; + } + + public List<String> words() { + return tokens; + } + + public String line() { + return source; + } + + public int cursor() { + return cursor; + } + + public Program program() { + return program; + } +} Added: felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Parser.java URL: http://svn.apache.org/viewvc/felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Parser.java?rev=1735995&view=auto ============================================================================== --- felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Parser.java (added) +++ felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Parser.java Mon Mar 21 16:53:06 2016 @@ -0,0 +1,71 @@ +/* + * 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.felix.gogo.jline; + +import java.util.Collections; +import java.util.List; + +import org.apache.felix.gogo.runtime.EOFError; +import org.apache.felix.gogo.runtime.Parser.Program; +import org.apache.felix.gogo.runtime.Parser.Statement; +import org.apache.felix.gogo.runtime.SyntaxError; +import org.apache.felix.gogo.runtime.Token; +import org.jline.reader.ParsedLine; + +public class Parser implements org.jline.reader.Parser { + + public ParsedLine parse(String line, int cursor) throws org.jline.reader.SyntaxError { + try { + return doParse(line, cursor); + } catch (EOFError e) { + throw new org.jline.reader.EOFError(e.line(), e.column(), e.getMessage(), e.missing()); + } catch (SyntaxError e) { + throw new org.jline.reader.SyntaxError(e.line(), e.column(), e.getMessage()); + } + } + + private ParsedLine doParse(CharSequence line, int cursor) throws SyntaxError { + org.apache.felix.gogo.runtime.Parser parser = new org.apache.felix.gogo.runtime.Parser(line); + Program program = parser.program(); + List<Statement> statements = parser.statements(); + // Find corresponding statement + Statement statement = null; + for (int i = statements.size() - 1; i >= 0; i--) { + Statement s = statements.get(i); + if (s.start() <= cursor) { + boolean isOk = true; + // check if there are only spaces after the previous statement + if (s.start() + s.length() < cursor) { + for (int j = s.start() + s.length(); isOk && j < cursor; j++) { + isOk = Character.isWhitespace(line.charAt(j)); + } + } + statement = s; + break; + } + } + if (statement != null) { + return new ParsedLineImpl(program, statement, cursor, statement.tokens()); + } else { + // TODO: + return new ParsedLineImpl(program, program, cursor, Collections.<Token>singletonList(program)); + } + } + +} Added: felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java URL: http://svn.apache.org/viewvc/felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java?rev=1735995&view=auto ============================================================================== --- felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java (added) +++ felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java Mon Mar 21 16:53:06 2016 @@ -0,0 +1,816 @@ +/* + * 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.felix.gogo.jline; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.felix.service.command.CommandSession; +import org.jline.builtins.Options; + +/** + * Posix-like utilities. + * + * @see <a href="http://www.opengroup.org/onlinepubs/009695399/utilities/contents.html"> + * http://www.opengroup.org/onlinepubs/009695399/utilities/contents.html</a> + */ +public class Posix { + static final String[] functions = {"cat", "echo", "grep", "sort", "sleep", "cd", "pwd", "ls"}; + + static final String CWD = "_cwd"; + + public static File _pwd(CommandSession session) { + try { + File cwd = (File) session.get(CWD); + if (cwd == null) { + cwd = new File(".").getCanonicalFile(); + session.put(CWD, cwd); + } + return cwd; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static void sort(CommandSession session, String[] argv) throws IOException { + final String[] usage = { + "sort - writes sorted standard input to standard output.", + "Usage: sort [OPTIONS] [FILES]", + " -? --help show help", + " -f --ignore-case fold lower case to upper case characters", + " -r --reverse reverse the result of comparisons", + " -u --unique output only the first of an equal run", + " -t --field-separator=SEP use SEP instead of non-blank to blank transition", + " -b --ignore-leading-blanks ignore leading blancks", + " --numeric-sort compare according to string numerical value", + " -k --key=KEY fields to use for sorting separated by whitespaces"}; + + Options opt = Options.compile(usage).parse(argv); + + if (opt.isSet("help")) { + opt.usage(System.err); + return; + } + + List<String> args = opt.args(); + + List<String> lines = new ArrayList<String>(); + if (!args.isEmpty()) { + for (String filename : args) { + BufferedReader reader = new BufferedReader(new InputStreamReader( + Shell.cwd(session).resolve(filename).toURL().openStream())); + try { + read(reader, lines); + } finally { + reader.close(); + } + } + } else { + BufferedReader r = new BufferedReader(new InputStreamReader(System.in)); + read(r, lines); + } + + String separator = opt.get("field-separator"); + boolean caseInsensitive = opt.isSet("ignore-case"); + boolean reverse = opt.isSet("reverse"); + boolean ignoreBlanks = opt.isSet("ignore-leading-blanks"); + boolean numeric = opt.isSet("numeric-sort"); + boolean unique = opt.isSet("unique"); + List<String> sortFields = opt.getList("key"); + + char sep = (separator == null || separator.length() == 0) ? '\0' : separator.charAt(0); + Collections.sort(lines, new SortComparator(caseInsensitive, reverse, ignoreBlanks, numeric, sep, sortFields)); + String last = null; + for (String s : lines) { + if (!unique || last == null || !s.equals(last)) { + System.out.println(s); + } + last = s; + } + } + + protected static void read(BufferedReader r, List<String> lines) throws IOException { + for (String s = r.readLine(); s != null; s = r.readLine()) { + lines.add(s); + } + } + + public static List<String> parseSubstring(String value) { + List<String> pieces = new ArrayList<String>(); + StringBuilder ss = new StringBuilder(); + // int kind = SIMPLE; // assume until proven otherwise + boolean wasStar = false; // indicates last piece was a star + boolean leftstar = false; // track if the initial piece is a star + boolean rightstar = false; // track if the final piece is a star + + int idx = 0; + + // We assume (sub)strings can contain leading and trailing blanks + boolean escaped = false; + loop: + for (; ; ) { + if (idx >= value.length()) { + if (wasStar) { + // insert last piece as "" to handle trailing star + rightstar = true; + } else { + pieces.add(ss.toString()); + // accumulate the last piece + // note that in the case of + // (cn=); this might be + // the string "" (!=null) + } + ss.setLength(0); + break loop; + } + + // Read the next character and account for escapes. + char c = value.charAt(idx++); + if (!escaped && ((c == '(') || (c == ')'))) { + throw new IllegalArgumentException( + "Illegal value: " + value); + } else if (!escaped && (c == '*')) { + if (wasStar) { + // encountered two successive stars; + // I assume this is illegal + throw new IllegalArgumentException("Invalid filter string: " + value); + } + if (ss.length() > 0) { + pieces.add(ss.toString()); // accumulate the pieces + // between '*' occurrences + } + ss.setLength(0); + // if this is a leading star, then track it + if (pieces.size() == 0) { + leftstar = true; + } + wasStar = true; + } else if (!escaped && (c == '\\')) { + escaped = true; + } else { + escaped = false; + wasStar = false; + ss.append(c); + } + } + if (leftstar || rightstar || pieces.size() > 1) { + // insert leading and/or trailing "" to anchor ends + if (rightstar) { + pieces.add(""); + } + if (leftstar) { + pieces.add(0, ""); + } + } + return pieces; + } + + public static boolean compareSubstring(List<String> pieces, String s) { + // Walk the pieces to match the string + // There are implicit stars between each piece, + // and the first and last pieces might be "" to anchor the match. + // assert (pieces.length > 1) + // minimal case is <string>*<string> + + boolean result = true; + int len = pieces.size(); + + int index = 0; + + loop: + for (int i = 0; i < len; i++) { + String piece = pieces.get(i); + + // If this is the first piece, then make sure the + // string starts with it. + if (i == 0) { + if (!s.startsWith(piece)) { + result = false; + break loop; + } + } + + // If this is the last piece, then make sure the + // string ends with it. + if (i == len - 1) { + if (s.endsWith(piece)) { + result = true; + } else { + result = false; + } + break loop; + } + + // If this is neither the first or last piece, then + // make sure the string contains it. + if ((i > 0) && (i < (len - 1))) { + index = s.indexOf(piece, index); + if (index < 0) { + result = false; + break loop; + } + } + + // Move string index beyond the matching piece. + index += piece.length(); + } + + return result; + } + + private static void cat(final BufferedReader reader, boolean displayLineNumbers) throws IOException { + String line; + int lineno = 1; + try { + while ((line = reader.readLine()) != null) { + if (displayLineNumbers) { + System.out.print(String.format("%6d ", lineno++)); + } + System.out.println(line); + } + } finally { + reader.close(); + } + } + + private static <T> void addAll(List<? super T> list, T[] array) { + if (array != null) { + Collections.addAll(list, array); + } + } + + public File pwd(CommandSession session, String[] argv) throws IOException { + final String[] usage = { + "pwd - get current directory", + "Usage: pwd [OPTIONS]", + " -? --help show help" + }; + Options opt = Options.compile(usage).parse(argv); + if (opt.isSet("help")) { + opt.usage(System.err); + return null; + } + if (!opt.args().isEmpty()) { + System.err.println("usage: pwd"); + return null; + } + File cwd = (File) session.get(CWD); + if (cwd == null) { + cwd = new File(".").getCanonicalFile(); + session.put(CWD, cwd); + } + return cwd; + } + + public File cd(CommandSession session, String[] argv) + throws IOException { + final String[] usage = { + "cd - get current directory", + "Usage: cd [OPTIONS] DIRECTORY", + " -? --help show help" + }; + Options opt = Options.compile(usage).parse(argv); + if (opt.isSet("help")) { + opt.usage(System.err); + return null; + } + if (opt.args().size() != 1) { + System.err.println("usage: cd DIRECTORY"); + return null; + } + File cwd = pwd(session, new String[0]); + String dir = opt.args().get(0); + + URI curUri = cwd.toURI(); + URI newUri = curUri.resolve(dir); + + cwd = new File(newUri); + if (!cwd.exists()) { + throw new IOException("Directory does not exist"); + } else if (!cwd.isDirectory()) { + throw new IOException("Target is not a directory"); + } + session.put(CWD, cwd.getCanonicalFile()); + return cwd; + } + + public Collection<File> ls(CommandSession session, String[] argv) throws IOException { + final String[] usage = { + "ls - list files", + "Usage: ls [OPTIONS] PATTERNS...", + " -? --help show help" + }; + Options opt = Options.compile(usage).parse(argv); + if (opt.isSet("help")) { + opt.usage(System.err); + return null; + } + if (opt.args().isEmpty()) { + opt.args().add("*"); + } + opt.args().remove(0); + List<File> files = new ArrayList<File>(); + for (String pattern : opt.args()) { + pattern = ((pattern == null) || (pattern.length() == 0)) ? "." : pattern; + pattern = ((pattern.charAt(0) != File.separatorChar) && (pattern.charAt(0) != '.')) + ? "./" + pattern : pattern; + int idx = pattern.lastIndexOf(File.separatorChar); + String parent = (idx < 0) ? "." : pattern.substring(0, idx + 1); + String target = (idx < 0) ? pattern : pattern.substring(idx + 1); + + File actualParent = ((parent.charAt(0) == File.separatorChar) + ? new File(parent) + : new File(pwd(session, new String[0]), parent)).getCanonicalFile(); + + idx = target.indexOf(File.separatorChar, idx); + boolean isWildcarded = (target.indexOf('*', idx) >= 0); + if (isWildcarded) { + if (!actualParent.exists()) { + throw new IOException("File does not exist"); + } + final List<String> pieces = parseSubstring(target); + addAll(files, actualParent.listFiles(new FileFilter() { + public boolean accept(File pathname) { + return compareSubstring(pieces, pathname.getName()); + } + })); + } else { + File actualTarget = new File(actualParent, target).getCanonicalFile(); + if (!actualTarget.exists()) { + throw new IOException("File does not exist"); + } + if (actualTarget.isDirectory()) { + addAll(files, actualTarget.listFiles()); + } else { + files.add(actualTarget); + } + } + } + return files; + } + + public void cat(CommandSession session, String[] argv) throws Exception { + final String[] usage = { + "cat - concatenate and print FILES", + "Usage: cat [OPTIONS] [FILES]", + " -? --help show help", + " -n number the output lines, starting at 1" + }; + + Options opt = Options.compile(usage).parse(argv); + + if (opt.isSet("help")) { + opt.usage(System.err); + return; + } + + List<String> args = opt.args(); + if (args.isEmpty()) { + args = Collections.singletonList("-"); + } + + URI cwd = Shell.cwd(session); + for (String arg : args) { + InputStream is; + if ("-".equals(arg)) { + is = System.in; + + } else { + is = cwd.resolve(arg).toURL().openStream(); + } + cat(new BufferedReader(new InputStreamReader(is)), opt.isSet("n")); + } + } + + public void echo(Object[] argv) { + final String[] usage = { + "echo - echoes or prints ARGUMENT to standard output", + "Usage: echo [OPTIONS] [ARGUMENTS]", + " -? --help show help", + " -n no trailing new line" + }; + + Options opt = Options.compile(usage).parse(argv); + + if (opt.isSet("help")) { + opt.usage(System.err); + return; + } + + List<String> args = opt.args(); + StringBuilder buf = new StringBuilder(); + if (args != null) { + for (String arg : args) { + if (buf.length() > 0) + buf.append(' '); + buf.append(arg); + } + } + if (opt.isSet("n")) { + System.out.print(buf); + } else { + System.out.println(buf); + } + } + + public boolean grep(CommandSession session, String[] argv) throws IOException { + final String[] usage = { + "grep - search for PATTERN in each FILE or standard input.", + "Usage: grep [OPTIONS] PATTERN [FILES]", + " -? --help show help", + " -i --ignore-case ignore case distinctions", + " -n --line-number prefix each line with line number within its input file", + " -q --quiet, --silent suppress all normal output", + " -v --invert-match select non-matching lines"}; + + Options opt = Options.compile(usage).parse(argv); + + if (opt.isSet("help")) { + opt.usage(System.err); + return true; + } + + List<String> args = opt.args(); + + if (args.isEmpty()) { + throw opt.usageError("no pattern supplied."); + } + + String regex = args.remove(0); + if (opt.isSet("ignore-case")) { + regex = "(?i)" + regex; + } + + if (args.isEmpty()) { + args.add(null); + } + + StringBuilder buf = new StringBuilder(); + + if (args.size() > 1) { + buf.append("%1$s:"); + } + + if (opt.isSet("line-number")) { + buf.append("%2$s:"); + } + + buf.append("%3$s"); + String format = buf.toString(); + + Pattern pattern = Pattern.compile(regex); + boolean status = true; + boolean match = false; + + for (String arg : args) { + InputStream in = null; + + try { + URI cwd = Shell.cwd(session); + in = (arg == null) ? System.in : cwd.resolve(arg).toURL().openStream(); + + BufferedReader rdr = new BufferedReader(new InputStreamReader(in)); + int line = 0; + String s; + while ((s = rdr.readLine()) != null) { + line++; + Matcher matcher = pattern.matcher(s); + if (!(matcher.find() ^ !opt.isSet("invert-match"))) { + match = true; + if (opt.isSet("quiet")) + break; + + System.out.println(String.format(format, arg, line, s)); + } + } + + if (match && opt.isSet("quiet")) { + break; + } + } catch (IOException e) { + System.err.println("grep: " + e.getMessage()); + status = false; + } finally { + if (arg != null && in != null) { + in.close(); + } + } + } + + return match && status; + } + + public void sleep(String[] argv) throws InterruptedException { + final String[] usage = { + "sleep - suspend execution for an interval of time", + "Usage: sleep seconds", + " -? --help show help"}; + + Options opt = Options.compile(usage).parse(argv); + + if (opt.isSet("help")) { + opt.usage(System.err); + return; + } + + List<String> args = opt.args(); + if (args.size() != 1) { + System.err.println("usage: sleep seconds"); + } else { + int s = Integer.parseInt(args.get(0)); + Thread.sleep(s * 1000); + } + } + + public static class SortComparator implements Comparator<String> { + + private static Pattern fpPattern; + + static { + final String Digits = "(\\p{Digit}+)"; + final String HexDigits = "(\\p{XDigit}+)"; + final String Exp = "[eE][+-]?" + Digits; + final String fpRegex = "([\\x00-\\x20]*[+-]?(NaN|Infinity|(((" + Digits + "(\\.)?(" + Digits + "?)(" + Exp + ")?)|(\\.(" + Digits + ")(" + Exp + ")?)|(((0[xX]" + HexDigits + "(\\.)?)|(0[xX]" + HexDigits + "?(\\.)" + HexDigits + "))[pP][+-]?" + Digits + "))" + "[fFdD]?))[\\x00-\\x20]*)(.*)"; + fpPattern = Pattern.compile(fpRegex); + } + + private boolean caseInsensitive; + private boolean reverse; + private boolean ignoreBlanks; + private boolean numeric; + private char separator; + private List<Key> sortKeys; + + public SortComparator(boolean caseInsensitive, + boolean reverse, + boolean ignoreBlanks, + boolean numeric, + char separator, + List<String> sortFields) { + this.caseInsensitive = caseInsensitive; + this.reverse = reverse; + this.separator = separator; + this.ignoreBlanks = ignoreBlanks; + this.numeric = numeric; + if (sortFields == null || sortFields.size() == 0) { + sortFields = new ArrayList<String>(); + sortFields.add("1"); + } + sortKeys = new ArrayList<Key>(); + for (String f : sortFields) { + sortKeys.add(new Key(f)); + } + } + + public int compare(String o1, String o2) { + int res = 0; + + List<Integer> fi1 = getFieldIndexes(o1); + List<Integer> fi2 = getFieldIndexes(o2); + for (Key key : sortKeys) { + int[] k1 = getSortKey(o1, fi1, key); + int[] k2 = getSortKey(o2, fi2, key); + if (key.numeric) { + Double d1 = getDouble(o1, k1[0], k1[1]); + Double d2 = getDouble(o2, k2[0], k2[1]); + res = d1.compareTo(d2); + } else { + res = compareRegion(o1, k1[0], k1[1], o2, k2[0], k2[1], key.caseInsensitive); + } + if (res != 0) { + if (key.reverse) { + res = -res; + } + break; + } + } + return res; + } + + protected Double getDouble(String s, int start, int end) { + Matcher m = fpPattern.matcher(s.substring(start, end)); + m.find(); + return new Double(s.substring(0, m.end(1))); + } + + protected int compareRegion(String s1, int start1, int end1, String s2, int start2, int end2, boolean caseInsensitive) { + int n1 = end1, n2 = end2; + for (int i1 = start1, i2 = start2; i1 < end1 && i2 < n2; i1++, i2++) { + char c1 = s1.charAt(i1); + char c2 = s2.charAt(i2); + if (c1 != c2) { + if (caseInsensitive) { + c1 = Character.toUpperCase(c1); + c2 = Character.toUpperCase(c2); + if (c1 != c2) { + c1 = Character.toLowerCase(c1); + c2 = Character.toLowerCase(c2); + if (c1 != c2) { + return c1 - c2; + } + } + } else { + return c1 - c2; + } + } + } + return n1 - n2; + } + + protected int[] getSortKey(String str, List<Integer> fields, Key key) { + int start; + int end; + if (key.startField * 2 <= fields.size()) { + start = fields.get((key.startField - 1) * 2); + if (key.ignoreBlanksStart) { + while (start < fields.get((key.startField - 1) * 2 + 1) && Character.isWhitespace(str.charAt(start))) { + start++; + } + } + if (key.startChar > 0) { + start = Math.min(start + key.startChar - 1, fields.get((key.startField - 1) * 2 + 1)); + } + } else { + start = 0; + } + if (key.endField > 0 && key.endField * 2 <= fields.size()) { + end = fields.get((key.endField - 1) * 2); + if (key.ignoreBlanksEnd) { + while (end < fields.get((key.endField - 1) * 2 + 1) && Character.isWhitespace(str.charAt(end))) { + end++; + } + } + if (key.endChar > 0) { + end = Math.min(end + key.endChar - 1, fields.get((key.endField - 1) * 2 + 1)); + } + } else { + end = str.length(); + } + return new int[]{start, end}; + } + + protected List<Integer> getFieldIndexes(String o) { + List<Integer> fields = new ArrayList<Integer>(); + if (o.length() > 0) { + if (separator == '\0') { + int i = 0; + fields.add(0); + for (int idx = 1; idx < o.length(); idx++) { + if (Character.isWhitespace(o.charAt(idx)) && !Character.isWhitespace(o.charAt(idx - 1))) { + fields.add(idx - 1); + fields.add(idx); + } + } + fields.add(o.length() - 1); + } else { + int last = -1; + for (int idx = o.indexOf(separator); idx >= 0; idx = o.indexOf(separator, idx + 1)) { + if (last >= 0) { + fields.add(last); + fields.add(idx - 1); + } else if (idx > 0) { + fields.add(0); + fields.add(idx - 1); + } + last = idx + 1; + } + if (last < o.length()) { + fields.add(last < 0 ? 0 : last); + fields.add(o.length() - 1); + } + } + } + return fields; + } + + public class Key { + int startField; + int startChar; + int endField; + int endChar; + boolean ignoreBlanksStart; + boolean ignoreBlanksEnd; + boolean caseInsensitive; + boolean reverse; + boolean numeric; + + public Key(String str) { + boolean modifiers = false; + boolean startPart = true; + boolean inField = true; + boolean inChar = false; + for (char c : str.toCharArray()) { + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (!inField && !inChar) { + throw new IllegalArgumentException("Bad field syntax: " + str); + } + if (startPart) { + if (inChar) { + startChar = startChar * 10 + (c - '0'); + } else { + startField = startField * 10 + (c - '0'); + } + } else { + if (inChar) { + endChar = endChar * 10 + (c - '0'); + } else { + endField = endField * 10 + (c - '0'); + } + } + break; + case '.': + if (!inField) { + throw new IllegalArgumentException("Bad field syntax: " + str); + } + inField = false; + inChar = true; + break; + case 'n': + inField = false; + inChar = false; + modifiers = true; + numeric = true; + break; + case 'f': + inField = false; + inChar = false; + modifiers = true; + caseInsensitive = true; + break; + case 'r': + inField = false; + inChar = false; + modifiers = true; + reverse = true; + break; + case 'b': + inField = false; + inChar = false; + modifiers = true; + if (startPart) { + ignoreBlanksStart = true; + } else { + ignoreBlanksEnd = true; + } + break; + case ',': + inField = true; + inChar = false; + startPart = false; + break; + default: + throw new IllegalArgumentException("Bad field syntax: " + str); + } + } + if (!modifiers) { + ignoreBlanksStart = ignoreBlanksEnd = SortComparator.this.ignoreBlanks; + reverse = SortComparator.this.reverse; + caseInsensitive = SortComparator.this.caseInsensitive; + numeric = SortComparator.this.numeric; + } + if (startField < 1) { + throw new IllegalArgumentException("Bad field syntax: " + str); + } + } + } + } + +} Added: felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java URL: http://svn.apache.org/viewvc/felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java?rev=1735995&view=auto ============================================================================== --- felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java (added) +++ felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java Mon Mar 21 16:53:06 2016 @@ -0,0 +1,149 @@ +/* + * 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.felix.gogo.jline; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.felix.service.command.CommandSession; +import org.apache.felix.service.command.Function; + +public class Procedural { + static final String[] functions = {"each", "if", "not", "throw", "try", "until", + "while"}; + + public List<Object> each(CommandSession session, Collection<Object> list, + Function closure) throws Exception { + List<Object> args = new ArrayList<Object>(); + List<Object> results = new ArrayList<Object>(); + args.add(null); + + for (Object x : list) { + checkInterrupt(); + args.set(0, x); + results.add(closure.execute(session, args)); + } + + return results; + } + + public Object _if(CommandSession session, Function[] fns) throws Exception { + int length = fns.length; + if (length < 2) { + throw new IllegalArgumentException( + "Usage: if {condition} {if-action} ... {else-action}"); + } + + for (int i = 0; i < length; ++i) { + if (i == length - 1 || isTrue(fns[i++].execute(session, null))) { + return fns[i].execute(session, null); + } + } + + return null; + } + + public boolean not(CommandSession session, Function condition) throws Exception { + if (null == condition) { + return true; + } + + return !isTrue(condition.execute(session, null)); + } + + // Reflective.coerce() prefers to construct a new Throwable(String) + // than to call this method directly. + public void _throw(String message) { + throw new IllegalArgumentException(message); + } + + public void _throw(Exception e) throws Exception { + throw e; + } + + public void _throw(CommandSession session) throws Throwable { + Object exception = session.get("exception"); + if (exception instanceof Throwable) + throw (Throwable) exception; + else + throw new IllegalArgumentException("exception not set or not Throwable."); + } + + public Object _try(CommandSession session, Function func) throws Exception { + try { + return func.execute(session, null); + } catch (Exception e) { + session.put("exception", e); + return null; + } + } + + public Object _try(CommandSession session, Function func, Function error) + throws Exception { + try { + return func.execute(session, null); + } catch (Exception e) { + session.put("exception", e); + return error.execute(session, null); + } + } + + public void _while(CommandSession session, Function condition, Function ifTrue) + throws Exception { + while (isTrue(condition.execute(session, null))) { + ifTrue.execute(session, null); + } + } + + public void until(CommandSession session, Function condition, Function ifTrue) + throws Exception { + while (!isTrue(condition.execute(session, null))) { + ifTrue.execute(session, null); + } + } + + private boolean isTrue(Object result) throws InterruptedException { + checkInterrupt(); + + if (result == null) + return false; + + if (result instanceof Boolean) + return ((Boolean) result).booleanValue(); + + if (result instanceof Number) { + if (0 == ((Number) result).intValue()) + return false; + } + + if ("".equals(result)) + return false; + + if ("0".equals(result)) + return false; + + return true; + } + + private void checkInterrupt() throws InterruptedException { + if (Thread.currentThread().isInterrupted()) + throw new InterruptedException("loop interrupted"); + } +} Added: felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java URL: http://svn.apache.org/viewvc/felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java?rev=1735995&view=auto ============================================================================== --- felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java (added) +++ felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java Mon Mar 21 16:53:06 2016 @@ -0,0 +1,537 @@ +/* + * 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.felix.gogo.jline; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; +import java.nio.CharBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; + +import org.apache.felix.gogo.runtime.Closure; +import org.apache.felix.gogo.runtime.CommandProxy; +import org.apache.felix.gogo.runtime.CommandSessionImpl; +import org.apache.felix.gogo.runtime.Expander; +import org.apache.felix.gogo.runtime.Reflective; +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.CommandSession; +import org.apache.felix.service.command.Converter; +import org.apache.felix.service.command.Descriptor; +import org.apache.felix.service.command.Function; +import org.apache.felix.service.command.Parameter; +import org.jline.builtins.Completers.CompletionData; +import org.jline.builtins.Options; +import org.jline.reader.EndOfFileException; +import org.jline.reader.LineReader; +import org.jline.reader.LineReaderBuilder; +import org.jline.reader.ParsedLine; +import org.jline.reader.UserInterruptException; +import org.jline.reader.impl.history.history.FileHistory; +import org.jline.terminal.Terminal; + +public class Shell { + + public static final String VAR_COMPLETIONS = ".completions"; + public static final String VAR_COMMAND_LINE = ".commandLine"; + public static final String VAR_READER = ".reader"; + public static final String VAR_SESSION = ".session"; + public static final String VAR_PROCESSOR = ".processor"; + public static final String VAR_TERMINAL = ".terminal"; + public static final String VAR_EXCEPTION = "exception"; + public static final String VAR_LOCATION = ".location"; + public static final String VAR_PROMPT = "prompt"; + public static final String VAR_RPROMPT = "rprompt"; + public static final String VAR_SCOPE = "SCOPE"; + public static final String VAR_CONTEXT = org.apache.felix.gogo.runtime.activator.Activator.CONTEXT; + + static final String[] functions = {"gosh", "sh", "source", "help"}; + + private final URI baseURI; + private final String profile; + private final Context context; + private final CommandProcessor processor; + + public Shell(Context context, CommandProcessor processor, Terminal terminal) { + this(context, processor, terminal, null); + } + + public Shell(Context context, CommandProcessor processor, Terminal terminal, String profile) { + this.context = context; + this.processor = processor; + String baseDir = context.getProperty("gosh.home"); + baseDir = (baseDir == null) ? context.getProperty("user.dir") : baseDir; + this.baseURI = new File(baseDir).toURI(); + this.profile = profile != null ? profile : "gosh_profile"; + } + + public static Terminal getTerminal(CommandSession session) { + return (Terminal) session.get(VAR_TERMINAL); + } + + public static LineReader getReader(CommandSession session) { + return (LineReader) session.get(VAR_READER); + } + + public static CommandProcessor getProcessor(CommandSession session) { + return (CommandProcessor) session.get(VAR_PROCESSOR); + } + + @SuppressWarnings("unchecked") + public static Map<String, List<CompletionData>> getCompletions(CommandSession session) { + return (Map) session.get(VAR_COMPLETIONS); + } + + @SuppressWarnings("unchecked") + public static Set<String> getCommands(CommandSession session) { + return (Set<String>) session.get(CommandSessionImpl.COMMANDS); + } + + public static ParsedLine getParsedLine(CommandSession session) { + return (ParsedLine) session.get(VAR_COMMAND_LINE); + } + + public static String getPrompt(CommandSession session) { + return expand(session, VAR_PROMPT, "gl! "); + } + + public static String getRPrompt(CommandSession session) { + return expand(session, VAR_RPROMPT, null); + } + + public static String expand(CommandSession session, String name, String def) { + Object prompt = session.get(name); + if (prompt != null) { + try { + Object o = Expander.expand( + prompt.toString(), + new Closure((CommandSessionImpl) session, null, null)); + if (o != null) { + return o.toString(); + } + } catch (Exception e) { + // ignore + } + } + return def; + } + + public static String resolve(CommandSession session, String command) { + String resolved = command; + if (command.indexOf(':') < 0) { + Set<String> commands = getCommands(session); + Object path = session.get(VAR_SCOPE); + String scopePath = (null == path ? "*" : path.toString()); + for (String scope : scopePath.split(":")) { + for (String entry : commands) { + if ("*".equals(scope) && entry.endsWith(":" + command) + || entry.equals(scope + ":" + command)) { + resolved = entry; + break; + } + } + } + } + return resolved; + } + + public static CharSequence readScript(URI script) throws Exception { + URLConnection conn = script.toURL().openConnection(); + int length = conn.getContentLength(); + + if (length == -1) { + System.err.println("eek! unknown Contentlength for: " + script); + length = 10240; + } + + InputStream in = conn.getInputStream(); + CharBuffer cbuf = CharBuffer.allocate(length); + Reader reader = new InputStreamReader(in); + reader.read(cbuf); + in.close(); + cbuf.rewind(); + + return cbuf; + } + + @SuppressWarnings("unchecked") + static Set<String> getVariables(CommandSession session) { + return (Set<String>) session.get(".variables"); + } + + static URI cwd(CommandSession session) { + return Posix._pwd(session).toURI(); // _cwd is set by felixcommands:cd + } + + private static <T extends Annotation> T findAnnotation(Annotation[] anns, + Class<T> clazz) { + for (int i = 0; (anns != null) && (i < anns.length); i++) { + if (clazz.isInstance(anns[i])) { + return clazz.cast(anns[i]); + } + } + return null; + } + + public Object gosh(final CommandSession session, String[] argv) throws Exception { + final String[] usage = { + "gosh - execute script with arguments in a new session", + " args are available as session variables $1..$9 and $args.", + "Usage: gosh [OPTIONS] [script-file [args..]]", + " -c --command pass all remaining args to sub-shell", + " --nointeractive don't start interactive session", + " --login login shell (same session, reads etc/gosh_profile)", + " -s --noshutdown don't shutdown framework when script completes", + " -x --xtrace echo commands before execution", + " -? --help show help", + "If no script-file, an interactive shell is started, type $D to exit."}; + + Options opt = Options.compile(usage).setOptionsFirst(true).parse(argv); + List<String> args = opt.args(); + + boolean login = opt.isSet("login"); + boolean interactive = !opt.isSet("nointeractive"); + + if (opt.isSet("help")) { + opt.usage(System.err); + if (login && !opt.isSet("noshutdown")) { + shutdown(); + } + return null; + } + + if (opt.isSet("command") && args.isEmpty()) { + throw opt.usageError("option --command requires argument(s)"); + } + + CommandSession newSession = (login ? session : processor.createSession( + session.getKeyboard(), session.getConsole(), System.err)); + + if (opt.isSet("xtrace")) { + newSession.put("echo", true); + } + + // export variables starting with upper-case to newSession + for (String key : getVariables(session)) { + if (key.matches("[.]?[A-Z].*")) { + newSession.put(key, session.get(key)); + } + } + + Terminal terminal = getTerminal(session); + newSession.put(Shell.VAR_CONTEXT, context); + newSession.put(Shell.VAR_TERMINAL, terminal); + newSession.put(Shell.VAR_PROCESSOR, processor); + newSession.put(Shell.VAR_SESSION, session); + newSession.put("#TERM", (Function) (s, arguments) -> terminal.getType()); + newSession.put("#COLUMNS", (Function) (s, arguments) -> terminal.getWidth()); + newSession.put("#LINES", (Function) (s, arguments) -> terminal.getHeight()); + + LineReader reader = null; + if (args.isEmpty() && interactive) { + reader = LineReaderBuilder.builder() + .terminal(terminal) + .variables(((CommandSessionImpl) newSession).getVariables()) + .completer(new org.jline.builtins.Completers.Completer(new JLineCompletionEnvironment(newSession))) + .highlighter(new Highlighter(session)) + .history(new FileHistory(new File(System.getProperty("user.home"), ".gogo.history"))) + .parser(new Parser()) + .build(); + newSession.put(Shell.VAR_READER, reader); + newSession.put(Shell.VAR_COMPLETIONS, new HashMap()); + } + + if (login || interactive) { + URI uri = baseURI.resolve("etc/" + profile); + if (!new File(uri).exists()) { + URL url = getClass().getResource("/ext/" + profile); + if (url == null) { + url = getClass().getResource("/" + profile); + } + uri = (url == null) ? null : url.toURI(); + } + if (uri != null) { + source(newSession, uri.toString()); + } + } + + Object result = null; + + if (args.isEmpty()) { + if (interactive) { + while (true) { + try { + reader.readLine(Shell.getPrompt(session), Shell.getRPrompt(session), null, null); + ParsedLine parsedLine = reader.getParsedLine(); + if (parsedLine == null) { + throw new EndOfFileException(); + } + try { + result = session.execute(((ParsedLineImpl) parsedLine).program()); + session.put("_", result); // set $_ to last result + + if (result != null && !Boolean.FALSE.equals(session.get(".Gogo.format"))) { + System.out.println(session.format(result, Converter.INSPECT)); + } + } catch (Exception e) { + session.put(Shell.VAR_EXCEPTION, e); + Object loc = session.get(Shell.VAR_LOCATION); + + if (null == loc || !loc.toString().contains(":")) { + loc = "gogo"; + } + System.out.println(loc + ": " + e.getClass().getSimpleName() + ": " + + e.getMessage()); + } + + } catch (UserInterruptException e) { + // continue; + } catch (EndOfFileException e) { + try { + reader.getHistory().flush(); + } catch (IOException e1) { + e.addSuppressed(e1); + } + break; + } + } + } + } else { + CharSequence program; + + if (opt.isSet("command")) { + StringBuilder buf = new StringBuilder(); + for (String arg : args) { + if (buf.length() > 0) { + buf.append(' '); + } + buf.append(arg); + } + program = buf; + } else { + URI script = cwd(session).resolve(args.remove(0)); + + // set script arguments + newSession.put("0", script); + newSession.put("args", args); + + for (int i = 0; i < args.size(); ++i) { + newSession.put(String.valueOf(i + 1), args.get(i)); + } + + program = readScript(script); + } + + result = newSession.execute(program); + } + + if (login && interactive && !opt.isSet("noshutdown")) { + System.out.println("gosh: stopping framework"); + shutdown(); + } + + return result; + } + + public Object sh(final CommandSession session, String[] argv) throws Exception { + return gosh(session, argv); + } + + private void shutdown() throws Exception { + context.exit(); + } + + public Object source(CommandSession session, String script) throws Exception { + URI uri = cwd(session).resolve(script); + session.put("0", uri); + try { + return session.execute(readScript(uri)); + } finally { + session.put("0", null); // API doesn't support remove + } + } + + private Map<String, List<Method>> getReflectionCommands(CommandSession session) { + Map<String, List<Method>> commands = new TreeMap<String, List<Method>>(); + Set<String> names = getCommands(session); + for (String name : names) { + Function function = (Function) session.get(name); + if (function instanceof CommandProxy) { + Object target = ((CommandProxy) function).getTarget(); + List<Method> methods = new ArrayList<>(); + String func = name.substring(name.indexOf(':') + 1).toLowerCase(); + List<String> funcs = new ArrayList<>(); + funcs.add("is" + func); + funcs.add("get" + func); + funcs.add("set" + func); + if (Reflective.KEYWORDS.contains(func)) { + funcs.add("_" + func); + } else { + funcs.add(func); + } + for (Method method : target.getClass().getMethods()) { + if (funcs.contains(method.getName().toLowerCase())) { + methods.add(method); + } + } + commands.put(name, methods); + ((CommandProxy) function).ungetTarget(); + } + } + return commands; + } + + @Descriptor("displays available commands") + public void help(CommandSession session) { + Map<String, List<Method>> commands = getReflectionCommands(session); + for (String name : commands.keySet()) { + System.out.println(name); + } + } + + @Descriptor("displays information about a specific command") + public void help(CommandSession session, @Descriptor("target command") String name) { + Map<String, List<Method>> commands = getReflectionCommands(session); + + List<Method> methods = null; + + // If the specified command doesn't have a scope, then + // search for matching methods by ignoring the scope. + int scopeIdx = name.indexOf(':'); + if (scopeIdx < 0) { + for (Entry<String, List<Method>> entry : commands.entrySet()) { + String k = entry.getKey().substring(entry.getKey().indexOf(':') + 1); + if (name.equals(k)) { + name = entry.getKey(); + methods = entry.getValue(); + break; + } + } + } + // Otherwise directly look up matching methods. + else { + methods = commands.get(name); + } + + if ((methods != null) && (methods.size() > 0)) { + for (Method m : methods) { + Descriptor d = m.getAnnotation(Descriptor.class); + if (d == null) { + System.out.println("\n" + m.getName()); + } else { + System.out.println("\n" + m.getName() + " - " + d.value()); + } + + System.out.println(" scope: " + name.substring(0, name.indexOf(':'))); + + // Get flags and options. + Class<?>[] paramTypes = m.getParameterTypes(); + Map<String, Parameter> flags = new TreeMap<>(); + Map<String, String> flagDescs = new TreeMap<>(); + Map<String, Parameter> options = new TreeMap<>(); + Map<String, String> optionDescs = new TreeMap<>(); + List<String> params = new ArrayList<String>(); + Annotation[][] anns = m.getParameterAnnotations(); + for (int paramIdx = 0; paramIdx < anns.length; paramIdx++) { + Class<?> paramType = m.getParameterTypes()[paramIdx]; + if (paramType == CommandSession.class) { + /* Do not bother the user with a CommandSession. */ + continue; + } + Parameter p = findAnnotation(anns[paramIdx], Parameter.class); + d = findAnnotation(anns[paramIdx], Descriptor.class); + if (p != null) { + if (p.presentValue().equals(Parameter.UNSPECIFIED)) { + options.put(p.names()[0], p); + if (d != null) { + optionDescs.put(p.names()[0], d.value()); + } + } else { + flags.put(p.names()[0], p); + if (d != null) { + flagDescs.put(p.names()[0], d.value()); + } + } + } else if (d != null) { + params.add(paramTypes[paramIdx].getSimpleName()); + params.add(d.value()); + } else { + params.add(paramTypes[paramIdx].getSimpleName()); + params.add(""); + } + } + + // Print flags and options. + if (flags.size() > 0) { + System.out.println(" flags:"); + for (Entry<String, Parameter> entry : flags.entrySet()) { + // Print all aliases. + String[] names = entry.getValue().names(); + System.out.print(" " + names[0]); + for (int aliasIdx = 1; aliasIdx < names.length; aliasIdx++) { + System.out.print(", " + names[aliasIdx]); + } + System.out.println(" " + flagDescs.get(entry.getKey())); + } + } + if (options.size() > 0) { + System.out.println(" options:"); + for (Entry<String, Parameter> entry : options.entrySet()) { + // Print all aliases. + String[] names = entry.getValue().names(); + System.out.print(" " + names[0]); + for (int aliasIdx = 1; aliasIdx < names.length; aliasIdx++) { + System.out.print(", " + names[aliasIdx]); + } + System.out.println(" " + + optionDescs.get(entry.getKey()) + + ((entry.getValue().absentValue() == null) ? "" + : " [optional]")); + } + } + if (params.size() > 0) { + System.out.println(" parameters:"); + for (Iterator<String> it = params.iterator(); it.hasNext(); ) { + System.out.println(" " + it.next() + " " + it.next()); + } + } + } + } + } + + public interface Context { + String getProperty(String name); + + void exit() throws Exception; + } + +} Added: felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellCommand.java URL: http://svn.apache.org/viewvc/felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellCommand.java?rev=1735995&view=auto ============================================================================== --- felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellCommand.java (added) +++ felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellCommand.java Mon Mar 21 16:53:06 2016 @@ -0,0 +1,145 @@ +/* + * 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.felix.gogo.jline.ssh; + +import java.io.CharArrayWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.Reader; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.CommandSession; +import org.apache.sshd.server.Command; +import org.apache.sshd.server.Environment; +import org.apache.sshd.server.ExitCallback; +import org.apache.sshd.server.SessionAware; +import org.apache.sshd.server.session.ServerSession; + +public class ShellCommand implements Command, Runnable, SessionAware { + + public static final String SHELL_INIT_SCRIPT = "karaf.shell.init.script"; + public static final String EXEC_INIT_SCRIPT = "karaf.exec.init.script"; + + private static final Logger LOGGER = Logger.getLogger(ShellCommand.class.getName()); + + private String command; + private InputStream in; + private OutputStream out; + private OutputStream err; + private ExitCallback callback; + private ServerSession session; + private CommandProcessor processor; + private Environment env; + + public ShellCommand(CommandProcessor processor, String command) { + this.processor = processor; + this.command = command; + } + + public void setInputStream(InputStream in) { + this.in = in; + } + + public void setOutputStream(OutputStream out) { + this.out = out; + } + + public void setErrorStream(OutputStream err) { + this.err = err; + } + + public void setExitCallback(ExitCallback callback) { + this.callback = callback; + } + + public void setSession(ServerSession session) { + this.session = session; + } + + public void start(final Environment env) throws IOException { + this.env = env; + new Thread(this).start(); + } + + public void run() { + int exitStatus = 0; + try { + final CommandSession session = processor.createSession(in, new PrintStream(out), new PrintStream(err)); + for (Map.Entry<String, String> e : env.getEnv().entrySet()) { + session.put(e.getKey(), e.getValue()); + } + try { + String scriptFileName = System.getProperty(EXEC_INIT_SCRIPT); + if (scriptFileName == null) { + scriptFileName = System.getProperty(SHELL_INIT_SCRIPT); + } + executeScript(scriptFileName, session); + session.execute(command); + } catch (Throwable t) { + exitStatus = 1; + t.printStackTrace(); + } + } catch (Exception e) { + exitStatus = 1; + LOGGER.log(Level.SEVERE, "Unable to start shell", e); + } finally { + ShellFactoryImpl.close(in, out, err); + callback.onExit(exitStatus); + } + } + + public void destroy() { + } + + private void executeScript(String scriptFileName, CommandSession session) { + if (scriptFileName != null) { + Reader r = null; + try { + File scriptFile = new File(scriptFileName); + r = new InputStreamReader(new FileInputStream(scriptFile)); + CharArrayWriter w = new CharArrayWriter(); + int n; + char[] buf = new char[8192]; + while ((n = r.read(buf)) > 0) { + w.write(buf, 0, n); + } + session.execute(new String(w.toCharArray())); + } catch (Exception e) { + LOGGER.log(Level.FINE, "Error in initialization script", e); + } finally { + if (r != null) { + try { + r.close(); + } catch (IOException e) { + // Ignore + } + } + } + } + } + +} Added: felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellCommandFactory.java URL: http://svn.apache.org/viewvc/felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellCommandFactory.java?rev=1735995&view=auto ============================================================================== --- felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellCommandFactory.java (added) +++ felix/trunk/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellCommandFactory.java Mon Mar 21 16:53:06 2016 @@ -0,0 +1,37 @@ +/* + * 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.felix.gogo.jline.ssh; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.sshd.server.Command; +import org.apache.sshd.server.CommandFactory; + +public class ShellCommandFactory implements CommandFactory { + + private CommandProcessor processor; + + public ShellCommandFactory(CommandProcessor processor) { + this.processor = processor; + } + + public Command createCommand(String command) { + return new ShellCommand(processor, command); + } + +}
