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}