001/* 002 * Copyright 2012-2016 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.cli.command.shell; 018 019import java.io.IOException; 020import java.util.ArrayList; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024 025import jline.console.ConsoleReader; 026import jline.console.completer.AggregateCompleter; 027import jline.console.completer.ArgumentCompleter; 028import jline.console.completer.ArgumentCompleter.ArgumentDelimiter; 029import jline.console.completer.Completer; 030import jline.console.completer.FileNameCompleter; 031import jline.console.completer.StringsCompleter; 032 033import org.springframework.boot.cli.command.Command; 034import org.springframework.boot.cli.command.options.OptionHelp; 035import org.springframework.boot.cli.util.Log; 036 037/** 038 * JLine {@link Completer} for Spring Boot {@link Command}s. 039 * 040 * @author Jon Brisbin 041 * @author Phillip Webb 042 */ 043public class CommandCompleter extends StringsCompleter { 044 045 private final Map<String, Completer> commandCompleters = new HashMap<String, Completer>(); 046 047 private final List<Command> commands = new ArrayList<Command>(); 048 049 private final ConsoleReader console; 050 051 public CommandCompleter(ConsoleReader consoleReader, 052 ArgumentDelimiter argumentDelimiter, Iterable<Command> commands) { 053 this.console = consoleReader; 054 List<String> names = new ArrayList<String>(); 055 for (Command command : commands) { 056 this.commands.add(command); 057 names.add(command.getName()); 058 List<String> options = new ArrayList<String>(); 059 for (OptionHelp optionHelp : command.getOptionsHelp()) { 060 options.addAll(optionHelp.getOptions()); 061 } 062 AggregateCompleter argumentCompleters = new AggregateCompleter( 063 new StringsCompleter(options), new FileNameCompleter()); 064 ArgumentCompleter argumentCompleter = new ArgumentCompleter(argumentDelimiter, 065 argumentCompleters); 066 argumentCompleter.setStrict(false); 067 this.commandCompleters.put(command.getName(), argumentCompleter); 068 } 069 getStrings().addAll(names); 070 } 071 072 @Override 073 public int complete(String buffer, int cursor, List<CharSequence> candidates) { 074 int completionIndex = super.complete(buffer, cursor, candidates); 075 int spaceIndex = buffer.indexOf(' '); 076 String commandName = (spaceIndex == -1) ? "" : buffer.substring(0, spaceIndex); 077 if (!"".equals(commandName.trim())) { 078 for (Command command : this.commands) { 079 if (command.getName().equals(commandName)) { 080 if (cursor == buffer.length() && buffer.endsWith(" ")) { 081 printUsage(command); 082 break; 083 } 084 Completer completer = this.commandCompleters.get(command.getName()); 085 if (completer != null) { 086 completionIndex = completer.complete(buffer, cursor, candidates); 087 break; 088 } 089 } 090 } 091 } 092 return completionIndex; 093 } 094 095 private void printUsage(Command command) { 096 try { 097 int maxOptionsLength = 0; 098 List<OptionHelpLine> optionHelpLines = new ArrayList<OptionHelpLine>(); 099 for (OptionHelp optionHelp : command.getOptionsHelp()) { 100 OptionHelpLine optionHelpLine = new OptionHelpLine(optionHelp); 101 optionHelpLines.add(optionHelpLine); 102 maxOptionsLength = Math.max(maxOptionsLength, 103 optionHelpLine.getOptions().length()); 104 } 105 106 this.console.println(); 107 this.console.println("Usage:"); 108 this.console.println(command.getName() + " " + command.getUsageHelp()); 109 for (OptionHelpLine optionHelpLine : optionHelpLines) { 110 this.console.println(String.format("\t%" + maxOptionsLength + "s: %s", 111 optionHelpLine.getOptions(), optionHelpLine.getUsage())); 112 } 113 this.console.drawLine(); 114 } 115 catch (IOException ex) { 116 Log.error(ex.getMessage() + " (" + ex.getClass().getName() + ")"); 117 } 118 } 119 120 /** 121 * Encapsulated options and usage help. 122 */ 123 private static class OptionHelpLine { 124 125 private final String options; 126 127 private final String usage; 128 129 OptionHelpLine(OptionHelp optionHelp) { 130 StringBuilder options = new StringBuilder(); 131 for (String option : optionHelp.getOptions()) { 132 options.append(options.length() == 0 ? "" : ", "); 133 options.append(option); 134 } 135 this.options = options.toString(); 136 this.usage = optionHelp.getUsageHelp(); 137 } 138 139 public String getOptions() { 140 return this.options; 141 } 142 143 public String getUsage() { 144 return this.usage; 145 } 146 147 } 148 149}