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}