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.shell.ErrorKind; 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 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 }