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.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(Collection<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<OptionHelp>(); 130 131 @Override 132 public String format(Map<String, ? extends OptionDescriptor> options) { 133 Comparator<OptionDescriptor> comparator = new Comparator<OptionDescriptor>() { 134 @Override 135 public int compare(OptionDescriptor first, OptionDescriptor second) { 136 return first.options().iterator().next() 137 .compareTo(second.options().iterator().next()); 138 } 139 }; 140 141 Set<OptionDescriptor> sorted = new TreeSet<OptionDescriptor>(comparator); 142 sorted.addAll(options.values()); 143 144 for (OptionDescriptor descriptor : sorted) { 145 if (!descriptor.representsNonOptions()) { 146 this.help.add(new OptionHelpAdapter(descriptor)); 147 } 148 } 149 return ""; 150 } 151 152 public Collection<OptionHelp> getOptionHelp() { 153 return Collections.unmodifiableList(this.help); 154 } 155 156 } 157 158 private static class OptionHelpAdapter implements OptionHelp { 159 160 private final LinkedHashSet<String> options; 161 162 private final String description; 163 164 OptionHelpAdapter(OptionDescriptor descriptor) { 165 this.options = new LinkedHashSet<String>(); 166 for (String option : descriptor.options()) { 167 this.options.add((option.length() == 1 ? "-" : "--") + option); 168 } 169 if (this.options.contains("--cp")) { 170 this.options.remove("--cp"); 171 this.options.add("-cp"); 172 } 173 this.description = descriptor.description(); 174 } 175 176 @Override 177 public Set<String> getOptions() { 178 return this.options; 179 } 180 181 @Override 182 public String getUsageHelp() { 183 return this.description; 184 } 185 186 } 187 188}