View Javadoc

1   package org.lsst.ccs.shell;
2   
3   import java.io.IOException;
4   import java.io.PrintWriter;
5   import java.util.List;
6   import jline.console.ConsoleReader;
7   import jline.console.completer.Completer;
8   import jline.console.history.History;
9   import org.apache.commons.cli.BasicParser;
10  import org.apache.commons.cli.CommandLine;
11  import org.apache.commons.cli.CommandLineParser;
12  import org.apache.commons.cli.HelpFormatter;
13  import org.apache.commons.cli.Options;
14  import org.lsst.ccs.command.CommandInvocationException;
15  import org.lsst.ccs.command.CommandSet;
16  import org.lsst.ccs.command.CommandSetBuilder;
17  import org.lsst.ccs.command.CompositeCommandSet;
18  import org.lsst.ccs.command.Dictionary;
19  import org.lsst.ccs.command.DictionaryCompleter;
20  import org.lsst.ccs.command.HelpGenerator;
21  import org.lsst.ccs.command.TokenizedCommand;
22  import org.lsst.ccs.command.annotations.Argument;
23  import org.lsst.ccs.command.annotations.Command;
24  
25  /**
26   * A simple shell for playing with the command parsing classes. This class is
27   * designed to be run from a terminal and uses JLine to interact with the user.
28   * The command shell has some built-in functionality, including the ability to
29   * provide help, and the ability to do tab completion.
30   *
31   * @author tonyj
32   */
33  public class JLineShell {
34  
35      private final CommandSet commands;
36      private boolean exitRequested;
37      private final ConsoleReader reader;
38      private final PrintWriter printWriter;
39      private CommandInvocationException lastException;
40  
41      /**
42       * Creates a JLineShell with the given set of user commands.
43       *
44       * @param userCommands The user defined commands which will be merged with
45       * the built-in commands provided by the shell itself. The CommandSet passed
46       * in can change dynamically (for example if it is in fact a
47       * CompositeCommandSet commands can be added and removed dynamically).
48       * @throws IOException If something goes horribly wrong.
49       */
50      public JLineShell(CommandSet userCommands) throws IOException {
51          this(userCommands, new ConsoleReader(), null);
52      }
53  
54      public JLineShell(CommandSet userCommands, ConsoleReader reader) {
55          this(userCommands, reader, null);
56      }
57  
58      public JLineShell(CommandSet userCommands, String prompt) throws IOException {
59          this(userCommands, new ConsoleReader(), prompt);
60      }
61  
62      public JLineShell(CommandSet userCommands, ConsoleReader reader, String prompt) {
63          this.reader = reader;
64          reader.setPrompt(prompt != null ? prompt : ">>>");
65          printWriter = new PrintWriter(reader.getOutput(), true);
66          printWriter.println("Type help for list of available commands");
67          CompositeCommandSet allCommands = new CompositeCommandSet();
68          CommandSetBuilder builder = new CommandSetBuilder();
69          allCommands.add(builder.buildCommandSet(new BuiltIns()));
70          allCommands.add(userCommands);
71          Dictionary commandDictionary = allCommands.getCommandDictionary();
72          final DictionaryCompleter dictionaryCompleter = new DictionaryCompleter(commandDictionary);
73          allCommands.add(builder.buildCommandSet(new HelpGenerator(printWriter, commandDictionary)));
74          commands = allCommands;
75          Completer completer = new Completer() {
76              @Override
77              public int complete(String string, int i, List<CharSequence> list) {
78                  return dictionaryCompleter.complete(string, i, list);
79              }
80          };
81          reader.addCompleter(completer);
82          reader.setCompletionHandler(new CommandCompletionHandler());
83      }
84  
85      /**
86       * Run the command shell. This method does not return until the user exits
87       * from the shell.
88       *
89       * @throws IOException If something goes horribly wrong.
90       */
91      public void run() throws IOException {
92          while (!exitRequested) {
93              String command = reader.readLine();
94              if (command == null) {
95                  printWriter.println();
96                  break;
97              }
98              try {
99                  TokenizedCommand tc = new TokenizedCommand(command);
100                 if (!tc.isEmpty()) {
101                     Object result = commands.invoke(tc);
102                     if (result != null) {
103                         printWriter.println(result.toString());
104                     }
105                 }
106             } catch (CommandInvocationException ex) {
107                 printWriter.println(ex.getMessage());
108                 lastException = ex;
109             }
110         }
111     }
112 
113     /**
114      * An enumeration of the arguments to the set command. Note that the built
115      * in tab completion understands enumerations.
116      */
117     public enum SetCommands {
118 
119         PROMPT
120     };
121 
122     /**
123      * The set of built in commands.
124      */
125     public class BuiltIns {
126 
127         @Command(description = "Exit from the shell")
128         public void exit() {
129             exitRequested = true;
130         }
131 
132         @Command(description = "Show command history")
133         public void history() {
134             History history = reader.getHistory();
135             for (int i = 0; i < history.size(); i++) {
136                 printWriter.printf("%3d: %s\n", i, history.get(i));
137             }
138         }
139 
140         @Command(description = "Show the full stacktrace of the most recent error", alias = "st")
141         public void stacktrace() {
142             if (lastException != null) {
143                 lastException.printStackTrace(printWriter);
144             }
145         }
146 
147         @Command(description = "Modify various settings")
148         public void set(@Argument(name = "item") SetCommands what, @Argument(name = "value") String value) {
149             switch (what) {
150                 case PROMPT:
151                     reader.setPrompt(value);
152             }
153         }
154     }
155 
156     /**
157      * Temporary method waiting for feedback. This main will start the shell by
158      * providing a single class name of the object the dictionary should be
159      * built of. This object must have an empty constructor.
160      *
161      * @param argv
162      * @throws Exception
163      */
164     public static void main(String[] argv) throws Exception {
165 
166         Options shellOptions = new Options();
167         shellOptions.addOption("h","help",false,"Print the help message");
168                        
169         shellOptions.addOption("dc","dictionaryClasses",true,"The comma separated list of classes to be used to build the command dictionary.\n"
170                 + "The dictionary classes must have an empty constructor in order to be loaded.");
171         
172         shellOptions.addOption("p", "prompt", true, "Set the initial prompt");
173 
174         CommandLineParser parser = new BasicParser();
175         CommandLine line = parser.parse(shellOptions, argv, true);
176 
177         if (line.hasOption("help")) {
178             HelpFormatter formatter = new HelpFormatter();
179             formatter.printHelp(80,"CommandShell", "", shellOptions, "", true);
180         } else {
181 
182             CommandSetBuilder builder = new CommandSetBuilder();
183             CompositeCommandSet compositeSet = new CompositeCommandSet();
184 
185 			String dictionaryClasses = line.getOptionValue("dictionaryClasses");
186 			if (dictionaryClasses != null) {
187 				String[] classTokens = dictionaryClasses.split(",");
188 				for (int i = 0; i < classTokens.length; i++) {
189 					String className = classTokens[i];
190 					try {
191 						Class<?> c = Class.forName(className);
192 						Object obj = c.newInstance();
193 						compositeSet.add(builder.buildCommandSet(obj));
194 					} catch (Exception e) {
195 						System.out.println("** Skipping class " + className
196 								+ ". It could not be loaded or created.");
197 						e.printStackTrace();
198 					}
199 				}
200 
201 			}
202             JLineShell shell = new JLineShell(compositeSet, line.getOptionValue("prompt"));
203             shell.run();
204         }
205         System.exit(0);
206     }
207 }