001/*
002 * Copyright 2012-2018 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.options;
018
019import java.io.ByteArrayOutputStream;
020import java.io.IOException;
021import java.io.OutputStream;
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.Collections;
025import java.util.Comparator;
026import java.util.LinkedHashSet;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030import java.util.TreeSet;
031
032import joptsimple.BuiltinHelpFormatter;
033import joptsimple.HelpFormatter;
034import joptsimple.OptionDescriptor;
035import joptsimple.OptionParser;
036import joptsimple.OptionSet;
037import joptsimple.OptionSpecBuilder;
038
039import org.springframework.boot.cli.command.OptionParsingCommand;
040import org.springframework.boot.cli.command.status.ExitStatus;
041
042/**
043 * Delegate used by {@link OptionParsingCommand} to parse options and run the command.
044 *
045 * @author Dave Syer
046 * @see OptionParsingCommand
047 * @see #run(OptionSet)
048 */
049public class OptionHandler {
050
051        private OptionParser parser;
052
053        private String help;
054
055        private Collection<OptionHelp> optionHelp;
056
057        public OptionSpecBuilder option(String name, String description) {
058                return getParser().accepts(name, description);
059        }
060
061        public OptionSpecBuilder option(List<String> aliases, String description) {
062                return getParser().acceptsAll(aliases, description);
063        }
064
065        public OptionParser getParser() {
066                if (this.parser == null) {
067                        this.parser = new OptionParser();
068                        options();
069                }
070                return this.parser;
071        }
072
073        protected void options() {
074        }
075
076        public final ExitStatus run(String... args) throws Exception {
077                String[] argsToUse = args.clone();
078                for (int i = 0; i < argsToUse.length; i++) {
079                        if ("-cp".equals(argsToUse[i])) {
080                                argsToUse[i] = "--cp";
081                        }
082                }
083                OptionSet options = getParser().parse(argsToUse);
084                return run(options);
085        }
086
087        /**
088         * Run the command using the specified parsed {@link OptionSet}.
089         * @param options the parsed option set
090         * @return an ExitStatus
091         * @throws Exception in case of errors
092         */
093        protected ExitStatus run(OptionSet options) throws Exception {
094                return ExitStatus.OK;
095        }
096
097        public String getHelp() {
098                if (this.help == null) {
099                        getParser().formatHelpWith(new BuiltinHelpFormatter(80, 2));
100                        OutputStream out = new ByteArrayOutputStream();
101                        try {
102                                getParser().printHelpOn(out);
103                        }
104                        catch (IOException ex) {
105                                return "Help not available";
106                        }
107                        this.help = out.toString().replace(" --cp ", " -cp  ");
108                }
109                return this.help;
110        }
111
112        public Collection<OptionHelp> getOptionsHelp() {
113                if (this.optionHelp == null) {
114                        OptionHelpFormatter formatter = new OptionHelpFormatter();
115                        getParser().formatHelpWith(formatter);
116                        try {
117                                getParser().printHelpOn(new ByteArrayOutputStream());
118                        }
119                        catch (Exception ex) {
120                                // Ignore and provide no hints
121                        }
122                        this.optionHelp = formatter.getOptionHelp();
123                }
124                return this.optionHelp;
125        }
126
127        private static class OptionHelpFormatter implements HelpFormatter {
128
129                private final List<OptionHelp> help = new ArrayList<>();
130
131                @Override
132                public String format(Map<String, ? extends OptionDescriptor> options) {
133                        Comparator<OptionDescriptor> comparator = Comparator.comparing(
134                                        (optionDescriptor) -> optionDescriptor.options().iterator().next());
135                        Set<OptionDescriptor> sorted = new TreeSet<>(comparator);
136                        sorted.addAll(options.values());
137                        for (OptionDescriptor descriptor : sorted) {
138                                if (!descriptor.representsNonOptions()) {
139                                        this.help.add(new OptionHelpAdapter(descriptor));
140                                }
141                        }
142                        return "";
143                }
144
145                public Collection<OptionHelp> getOptionHelp() {
146                        return Collections.unmodifiableList(this.help);
147                }
148
149        }
150
151        private static class OptionHelpAdapter implements OptionHelp {
152
153                private final Set<String> options;
154
155                private final String description;
156
157                OptionHelpAdapter(OptionDescriptor descriptor) {
158                        this.options = new LinkedHashSet<>();
159                        for (String option : descriptor.options()) {
160                                String prefix = (option.length() != 1) ? "--" : "-";
161                                this.options.add(prefix + option);
162                        }
163                        if (this.options.contains("--cp")) {
164                                this.options.remove("--cp");
165                                this.options.add("-cp");
166                        }
167                        this.description = descriptor.description();
168                }
169
170                @Override
171                public Set<String> getOptions() {
172                        return this.options;
173                }
174
175                @Override
176                public String getUsageHelp() {
177                        return this.description;
178                }
179
180        }
181
182}