grouper-dev - [grouper-dev] JLine integration into gsh
Subject: Grouper Developers Forum
List archive
- From: Nicolas Marcotte <>
- To:
- Subject: [grouper-dev] JLine integration into gsh
- Date: Fri, 26 Aug 2016 11:06:19 -0400
- Ironport-phdr: 9a23:+i27cRTGWgqpnySVGvINz3orB9psv+yvbD5Q0YIujvd0So/mwa67bBaN2/xhgRfzUJnB7Loc0qyN7PCmBDdLuMvJmUtBWaIPfidNsd8RkQ0kDZzNImzAB9muURYHGt9fXkRu5XCxPBsdMs//Y1rPvi/6tmZKSV2sfTZyc+vvHZPKgt7y2+2s05zVfwhSgjehO/V/IAjlgx/Ws5wwgIBlLq8qgj7AuHBPZ/hbjTduJFmUmx/noMK55pVk7zhdk+8698NaW7/9eKc1C7dRWmd1e1sp7dHm4EGQBTCE4WERBz0b
Hello, I am currently evaluating grouper for my institution and I was about to be driven mad by the line handling of gsh. To avoid that unfortunate I took 2 hours to integrate JLine2 to gsh interactive mode. I submit you my patch-set with the copyright attributed to Internet2. Here are the feature added to gsh: -platform independent arrows key handling -sh style history -minimal tab completion: the suggestions
are from the current bsh dictionary and the lower case classes
from package edu.internet2.middleware.grouper.app.gsh without
contextual awareness....
I hope that this patch-set will help others to avoid getting mad at gsh ! Have a nice day!! Nicolas Nicolas Marcotte Analyste technologique – Architecture, conception et développement Système d’information - Service des technologies de l'information Université de Sherbrooke Tél: 819 821-8000 poste 63276 Courriel : |
# This patch file was generated by NetBeans IDE # Following Index: paths are relative to: /home/marn2402/NetBeansProjects/grouper/trunk/grouper # This patch can be applied using context Tools: Patch action on respective folder. # It uses platform neutral UTF-8 encoding and \n newlines. # Above lines and this line are ignored by the patching process. Index: pom.xml --- pom.xml Base (BASE) +++ pom.xml Locally Modified (Based On LOCAL) @@ -255,6 +255,11 @@ <artifactId>org.wso2.charon.samples</artifactId> <version>1.0.0</version> </dependency> + <dependency> + <groupId>jline</groupId> + <artifactId>jline</artifactId> + <version>2.14.2</version> + </dependency> </dependencies> <build> Index: src/grouper/edu/internet2/middleware/grouper/app/gsh/JlineInterpreter.java --- src/grouper/edu/internet2/middleware/grouper/app/gsh/JlineInterpreter.java No Base Revision +++ src/grouper/edu/internet2/middleware/grouper/app/gsh/JlineInterpreter.java Locally New @@ -0,0 +1,282 @@ +/** ***************************************************************************** + * Copyright 2012 Internet2 + * <p> + * Licensed 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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. + ***************************************************************************** */ +/* + * Copyright (C) 20016 nicolas marcotte. + * + * + * You may use and distribute under the same terms as Grouper itself. + */ +package edu.internet2.middleware.grouper.app.gsh; + +import bsh.BshMethod; +import bsh.Interpreter; +import edu.emory.mathcs.backport.java.util.Collections; +import edu.internet2.middleware.grouper.util.GrouperUtil; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.Reader; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.CharBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.TreeSet; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import jline.Terminal; +import jline.TerminalFactory; +import jline.console.ConsoleReader; +import jline.console.completer.ArgumentCompleter; +import jline.console.completer.Completer; +import static jline.internal.Preconditions.checkNotNull; +import org.apache.commons.logging.Log; + +/** + * + * @author + */ +public class JlineInterpreter { + + private static final String CLASSNAME = JlineInterpreter.class.getSimpleName(); + + private static final Log LOG = GrouperUtil.getLog(JlineInterpreter.class); + + private final Interpreter interpreter; + private final ConsoleReaderReader reader; + + public JlineInterpreter(String prompt, InputStream in, OutputStream out) { + final Terminal terminal = TerminalFactory.create(); + final ConsoleReader cr; + try { + cr = new ConsoleReader(prompt, in, out, terminal); + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + + reader = new ConsoleReaderReader(cr); + PrintStream printStream = new ConsoleReaderPrintStream(cr); + PrintStream printStreamError = new ConsoleReaderPrintStream(cr, true); + this.interpreter = new Interpreter(reader, printStream, printStreamError, true); + final ArgumentCompleter argumentCompleter = new ArgumentCompleter(new BshAllNameCompleter(getLowerCaseClassName())); + + cr.addCompleter(argumentCompleter); + + } + + public Interpreter getInterpreter() { + return interpreter; + } + + public Reader getReader() { + return reader; + } + private class BshAllNameCompleter implements Completer { + + private Collection<String> additionalStrings; + + public BshAllNameCompleter() { + this.additionalStrings = Collections.emptyList(); + } + + private BshAllNameCompleter(Collection<String> additionalStrings) { + this.additionalStrings = additionalStrings; + } + + @Override + public int complete(String buffer, int cursor, List<CharSequence> candidates) { + + TreeSet<String> localCandidates = new TreeSet<String>(additionalStrings); + localCandidates.addAll(Arrays.asList(interpreter.getNameSpace().getAllNames())); + + for (BshMethod m : interpreter.getNameSpace().getMethods()) { + + localCandidates.add(m.getName()); + + } + checkNotNull(candidates); + + if (buffer == null) { + candidates.addAll(localCandidates); + } else { + for (String match : localCandidates.tailSet(buffer)) { + if (!match.startsWith(buffer)) { + break; + } + + candidates.add(match); + } + } + + return candidates.isEmpty() ? -1 : 0; + + } + } + + private static class ConsoleReaderPrintStream extends PrintStream { + + private final ConsoleReader cr; + private boolean error; + + public ConsoleReaderPrintStream(final ConsoleReader cr) { + + super(new OutputStream() { + @Override + public void write(int b) throws IOException { + cr.getOutput().write(b); + cr.getOutput().flush(); + + } + }); + this.cr = cr; + } + + private ConsoleReaderPrintStream(ConsoleReader cr, boolean error) { + this(cr); + this.error = error; + } + + @Override + public void print(Object obj) { + print(obj.toString()); + } + + @Override + public void println(Object x) { + println(x.toString()); + } + + @Override + public void print(String s) { + try { + + while (s.contains("\n")) { + int newIndex = s.indexOf("\n"); + cr.println(s.substring(0, newIndex)); + + if (newIndex <= s.length()) { + s = ""; + } else { + s = s.substring(newIndex); + } + } + + if (!s.isEmpty()) { + cr.print(s); + } + + cr.getOutput().flush(); + + } catch (IOException ex) { + LOG.error(ex.getLocalizedMessage(), ex); + } + + } + + @Override + public void println(String s) { + try { + s = "error: " + s; + cr.println(s); + cr.getOutput().flush(); + } catch (IOException ex) { + LOG.error(ex.getLocalizedMessage(), ex); + } + } + } + + private static class ConsoleReaderReader extends Reader { + + private final ConsoleReader cr; + private CharBuffer currentLine; + + public ConsoleReaderReader(ConsoleReader cr) { + this.cr = cr; + } + + @Override + public void close() throws IOException { + cr.close(); + } + + @Override + public int read(char[] cbuf, int off, int len) throws IOException { + + if (currentLine == null || !currentLine.hasRemaining()) { + currentLine = CharBuffer.wrap(cr.readLine() + "\n"); + } + len = Math.min(currentLine.remaining(), len); + currentLine.get(cbuf, off, len); + + return len; + } + } + + private static Collection<String> getLowerCaseClassName() { + try { + ArrayList<String> retVal = new ArrayList<String>(); + Pattern pattern = Pattern.compile("([a-z]+[a-zA-Z]*)\\.class"); + URL resource = JlineInterpreter.class.getResource(CLASSNAME + ".class"); + + String[] classList = null; + if (resource.toString().startsWith("file:")) { //Running in ide + classList = new File(JlineInterpreter.class.getResource("/" + JlineInterpreter.class.getPackage().getName().replace(".", "/") + "/").toURI()).list(); + } else { + classList = listClassesFromJar(); + } + + for (String classFile : classList) { + Matcher matcher = pattern.matcher(classFile); + if (matcher.matches()) { + retVal.add(matcher.group(1)); + } + } + + return retVal; + } catch (IOException ex) { + throw new IllegalStateException(ex); + } catch (URISyntaxException ex) { + throw new IllegalStateException(ex); + } + } + private static String[] listClassesFromJar() throws IOException { + ArrayList<String> classes = new ArrayList<String>(); + String resourceUrl = JlineInterpreter.class.getResource(CLASSNAME + ".class").toString(); + String resourcePath = resourceUrl.substring("jar:".length(), resourceUrl.indexOf("!")); + String packagePath = resourceUrl.substring(resourceUrl.indexOf("!/") + 2, resourceUrl.lastIndexOf("/")); + JarInputStream jarInputStream = new JarInputStream(new URL(resourcePath).openConnection().getInputStream()); + try { + final int pakageNameLength = packagePath.length(); + for (JarEntry jarEntry = jarInputStream.getNextJarEntry(); jarEntry != null; jarEntry = jarInputStream.getNextJarEntry()) { + final String name = jarEntry.getName(); + + if (name.startsWith(packagePath) && name.endsWith(".class") && name.lastIndexOf("/") == pakageNameLength) { + classes.add(name.substring(name.lastIndexOf("/") + 1)); + } + jarInputStream.closeEntry(); + } + return classes.toArray(new String[classes.size()]); + } finally { + jarInputStream.close(); + } + } +} Index: src/grouper/edu/internet2/middleware/grouper/app/gsh/ShellCommandReader.java --- src/grouper/edu/internet2/middleware/grouper/app/gsh/ShellCommandReader.java Base (BASE) +++ src/grouper/edu/internet2/middleware/grouper/app/gsh/ShellCommandReader.java Locally Modified (Based On LOCAL) @@ -100,15 +100,20 @@ } } } else { + this.prompt = GrouperShell.NAME + " "; if (inputStreamParam != null) { this.in = new BufferedReader(new InputStreamReader(inputStreamParam)); + this.i = new Interpreter(this.in, System.out, System.err, false); } else { - this.in = new BufferedReader( new InputStreamReader(System.in) ); - this.prompt = GrouperShell.NAME + " "; + final JlineInterpreter jlineInterpreter = new JlineInterpreter(prompt, System.in, System.out); + + this.i = jlineInterpreter.getInterpreter(); + this.in =new BufferedReader(jlineInterpreter.getReader()); } } - this.i = new Interpreter(this.in, System.out, System.err, false); + + } // protected ShellCommandReader(args)
- [grouper-dev] JLine integration into gsh, Nicolas Marcotte, 08/26/2016
- RE: [grouper-dev] JLine integration into gsh, Hyzer, Chris, 08/26/2016
- Re: [grouper-dev] JLine integration into gsh, Nicolas Marcotte, 08/26/2016
- Re: [grouper-dev] JLine integration into gsh, William G. Thompson, Jr., 08/26/2016
- RE: [grouper-dev] JLine integration into gsh, Hyzer, Chris, 08/26/2016
Archive powered by MHonArc 2.6.19.