Skip to Content.
Sympa Menu

grouper-dev - [grouper-dev] JLine integration into gsh

Subject: Grouper Developers Forum

List archive

[grouper-dev] JLine integration into gsh


Chronological Thread 
  • 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)
 
 



Archive powered by MHonArc 2.6.19.

Top of Page