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
020package org.crsh.shell.impl.command.spi;
021
022import org.crsh.cli.descriptor.CommandDescriptor;
023import org.crsh.cli.descriptor.Format;
024import org.crsh.cli.impl.Delimiter;
025import org.crsh.cli.impl.completion.CompletionException;
026import org.crsh.cli.impl.completion.CompletionMatch;
027import org.crsh.cli.impl.completion.CompletionMatcher;
028import org.crsh.cli.impl.invocation.InvocationMatch;
029import org.crsh.cli.impl.invocation.InvocationMatcher;
030import org.crsh.cli.impl.lang.Util;
031import org.crsh.cli.spi.Completer;
032import org.crsh.cli.spi.Completion;
033import org.crsh.command.RuntimeContext;
034import org.crsh.shell.ErrorKind;
035
036import java.io.IOException;
037import java.util.Collections;
038import java.util.List;
039import java.util.Map;
040
041/**
042 * A command as seen by the shell.
043 */
044public abstract class Command<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 CommandException anything that would prevent completion to happen
059   */
060  protected abstract Completer getCompleter(RuntimeContext context) throws CommandException;
061
062  /**
063   * Resolve the real match for a specified invocation match.
064   *
065   * @param match the match
066   * @return the command
067   */
068  protected abstract CommandMatch<?, ?> resolve(InvocationMatch<T> match);
069
070  public final String describe(final InvocationMatch<T> match, Format format) {
071
072    //
073    final CommandMatch<?, ?> commandMatch = resolve(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(commandMatch.getConsumedType().getName()).append(", ").append(commandMatch.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 CommandException {
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) throws CommandException {
133    CommandDescriptor<T> descriptor = getDescriptor();
134    InvocationMatcher<T> analyzer = descriptor.matcher();
135    InvocationMatch<T> match;
136    try {
137      match = analyzer.parse(line);
138    }
139    catch (org.crsh.cli.impl.SyntaxException e) {
140      throw new CommandException(ErrorKind.SYNTAX, "Syntax exception when evaluating " + descriptor.getName(), e);
141    }
142    return describe(match, format);
143  }
144
145  /**
146   * Provides an invoker for the command line specified as a command line to parse.
147   *
148   * @param line the command line arguments
149   * @return the command
150   */
151  public final CommandInvoker<?, ?> resolveInvoker(String line) throws CommandException {
152    return resolveCommand(line).getInvoker();
153  }
154
155  public final CommandMatch<?, ?> resolveCommand(String line) throws CommandException {
156    CommandDescriptor<T> descriptor = getDescriptor();
157    InvocationMatcher<T> analyzer = descriptor.matcher();
158    InvocationMatch<T> match;
159    try {
160      match = analyzer.parse(line);
161    }
162    catch (org.crsh.cli.impl.SyntaxException e) {
163      throw new CommandException(ErrorKind.SYNTAX, "Syntax exception when evaluating "+ getDescriptor().getName(), e);
164    }
165    return resolve(match);
166  }
167
168  /**
169   * Provides an invoker for the command line specified in a detyped manner.
170   *
171   * @param options the base options
172   * @param subordinate the subordinate command name, might null
173   * @param subordinateOptions the subordinate options
174   * @param arguments arguments
175   * @return the command
176   */
177  public final CommandMatch<?, ?> resolveCommand(Map<String, ?> options, String subordinate, Map<String, ?> subordinateOptions, List<?> arguments) throws CommandException {
178    InvocationMatcher<T> matcher = getDescriptor().matcher();
179
180    //
181    InvocationMatch<T> match;
182    try {
183      if (options != null && options.size() > 0) {
184        for (Map.Entry<String, ?> option : options.entrySet()) {
185          matcher = matcher.option(option.getKey(), Collections.singletonList(option.getValue()));
186        }
187      }
188
189      //
190      if (subordinate != null && subordinate.length() > 0) {
191        matcher = matcher.subordinate(subordinate);
192
193        // Minor : remove that and use same signature
194        if (subordinateOptions != null && subordinateOptions.size() > 0) {
195          for (Map.Entry<String, ?> option : subordinateOptions.entrySet()) {
196            matcher = matcher.option(option.getKey(), Collections.singletonList(option.getValue()));
197          }
198        }
199      }
200
201      //
202      match = matcher.arguments(arguments != null ? arguments : Collections.emptyList());
203    }
204    catch (org.crsh.cli.impl.SyntaxException e) {
205      throw new CommandException(ErrorKind.EVALUATION, "Could not resolve command " + getDescriptor().getName(), e);
206    }
207
208    //
209    return resolve(match);
210  }
211}