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.command.SyntaxException; 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 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}