001 /*
002 * Copyright (C) 2012 eXo Platform SAS.
003 *
004 * This is free software; you can redistribute it and/or modify it
005 * under the terms of the GNU Lesser General Public License as
006 * published by the Free Software Foundation; either version 2.1 of
007 * the License, or (at your option) any later version.
008 *
009 * This software is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public
015 * License along with this software; if not, write to the Free
016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018 */
019
020 package org.crsh.shell.impl.command.spi;
021
022 import org.crsh.cli.descriptor.CommandDescriptor;
023 import org.crsh.cli.descriptor.Format;
024 import org.crsh.cli.impl.Delimiter;
025 import org.crsh.cli.impl.completion.CompletionException;
026 import org.crsh.cli.impl.completion.CompletionMatch;
027 import org.crsh.cli.impl.completion.CompletionMatcher;
028 import org.crsh.cli.impl.invocation.InvocationMatch;
029 import org.crsh.cli.impl.invocation.InvocationMatcher;
030 import org.crsh.cli.impl.lang.Util;
031 import org.crsh.cli.spi.Completer;
032 import org.crsh.cli.spi.Completion;
033 import org.crsh.command.RuntimeContext;
034 import org.crsh.command.SyntaxException;
035
036 import java.io.IOException;
037 import java.util.Collections;
038 import java.util.List;
039 import java.util.Map;
040
041 /**
042 * A command as seen by the shell.
043 */
044 public abstract class ShellCommand<T> {
045
046 /**
047 * Returns the command descriptor.
048 *
049 * @return the descriptor
050 */
051 public abstract CommandDescriptor<T> getDescriptor();
052
053 /**
054 * Returns a completer for this command.
055 *
056 * @param context the related runtime context
057 * @return the completer
058 * @throws CommandCreationException anything that would prevent completion to happen
059 */
060 protected abstract Completer getCompleter(RuntimeContext context) throws CommandCreationException;
061
062 /**
063 * Resolve the real command for a specified invocation match.
064 *
065 * @param match the match
066 * @return the command
067 */
068 protected abstract Command<?, ?> resolveCommand(InvocationMatch<T> match);
069
070 public final String describe(final InvocationMatch<T> match, Format format) {
071
072 //
073 final Command<?, ?> command = resolveCommand(match);
074
075 //
076 if (format instanceof Format.Man) {
077 final Format.Man man = (Format.Man)format;
078 format = new Format.Man() {
079 @Override
080 public void printSynopsisSection(CommandDescriptor<?> descriptor, Appendable stream) throws IOException {
081 man.printSynopsisSection(descriptor, stream);
082
083 // Extra stream section
084 if (match.getDescriptor().getSubordinates().isEmpty()) {
085 stream.append("STREAM\n");
086 stream.append(Util.MAN_TAB);
087 printFQN(descriptor, stream);
088 stream.append(" <").append(command.getConsumedType().getName()).append(", ").append(command.getProducedType().getName()).append('>');
089 stream.append("\n\n");
090 }
091 }
092 };
093 }
094
095 //
096 try {
097 StringBuffer buffer = new StringBuffer();
098 match.getDescriptor().print(format, buffer);
099 return buffer.toString();
100 }
101 catch (IOException e) {
102 throw new AssertionError(e);
103 }
104 }
105
106 /**
107 * Provide completions for the specified arguments.
108 *
109 * @param context the command context
110 * @param line the original command line arguments
111 * @return the completions
112 */
113 public final CompletionMatch complete(RuntimeContext context, String line) throws CommandCreationException {
114 CompletionMatcher matcher = getDescriptor().completer();
115 Completer completer = getCompleter(context);
116 try {
117 return matcher.match(completer, line);
118 }
119 catch (CompletionException e) {
120 // command.log.log(Level.SEVERE, "Error during completion of line " + line, e);
121 return new CompletionMatch(Delimiter.EMPTY, Completion.create());
122 }
123 }
124
125 /**
126 * Returns a description of the command or null if none can be found.
127 *
128 * @param line the usage line
129 * @param format the description format
130 * @return the description
131 */
132 public final String describe(String line, Format format) {
133 InvocationMatcher<T> analyzer = getDescriptor().matcher();
134 InvocationMatch<T> match;
135 try {
136 match = analyzer.parse(line);
137 }
138 catch (org.crsh.cli.SyntaxException e) {
139 throw new SyntaxException(e.getMessage());
140 }
141 return describe(match, format);
142 }
143
144 /**
145 * Provides an invoker for the command line specified as a command line to parse.
146 *
147 * @param line the command line arguments
148 * @return the command
149 */
150 public final CommandInvoker<?, ?> resolveInvoker(String line) throws CommandCreationException {
151 return resolveCommand(line).getInvoker();
152 }
153
154 public final Command<?, ?> resolveCommand(String line) throws CommandCreationException {
155 CommandDescriptor<T> descriptor = getDescriptor();
156 InvocationMatcher<T> analyzer = descriptor.matcher();
157 InvocationMatch<T> match;
158 try {
159 match = analyzer.parse(line);
160 }
161 catch (org.crsh.cli.SyntaxException e) {
162 throw new SyntaxException(e.getMessage());
163 }
164 return resolveCommand(match);
165 }
166
167 /**
168 * Provides an invoker for the command line specified in a detyped manner.
169 *
170 * @param options the base options
171 * @param subordinate the subordinate command name, might null
172 * @param subordinateOptions the subordinate options
173 * @param arguments arguments
174 * @return the command
175 */
176 public final CommandInvoker<?, ?> resolveInvoker(Map<String, ?> options, String subordinate, Map<String, ?> subordinateOptions, List<?> arguments) throws CommandCreationException {
177 InvocationMatcher<T> matcher = getDescriptor().matcher();
178
179 //
180 if (options != null && options.size() > 0) {
181 for (Map.Entry<String, ?> option : options.entrySet()) {
182 matcher = matcher.option(option.getKey(), Collections.singletonList(option.getValue()));
183 }
184 }
185
186 //
187 if (subordinate != null && subordinate.length() > 0) {
188 matcher = matcher.subordinate(subordinate);
189
190 // Minor : remove that and use same signature
191 if (subordinateOptions != null && subordinateOptions.size() > 0) {
192 for (Map.Entry<String, ?> option : subordinateOptions.entrySet()) {
193 matcher = matcher.option(option.getKey(), Collections.singletonList(option.getValue()));
194 }
195 }
196 }
197
198 //
199 InvocationMatch<T> match = matcher.arguments(arguments != null ? arguments : Collections.emptyList());
200
201 //
202 return resolveCommand(match).getInvoker();
203 }
204 }