001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.geronimo.cli; 018 019 import java.io.BufferedReader; 020 import java.io.IOException; 021 import java.io.OutputStream; 022 import java.io.PrintWriter; 023 import java.io.StringReader; 024 import java.util.ArrayList; 025 import java.util.Collection; 026 import java.util.Collections; 027 import java.util.Comparator; 028 import java.util.Iterator; 029 import java.util.List; 030 031 import org.apache.commons.cli.Option; 032 import org.apache.commons.cli.OptionGroup; 033 import org.apache.commons.cli.Options; 034 035 /** 036 * This code is borrowed from commons-cli <code>org.apache.commons.cli.HelpFormatter</code> class. Its authors are 037 * Slawek Zachcial and John Keyes (john at integralsource.com). This class has been slightly updated to meet specific 038 * requirements. 039 * 040 * @version $Rev: 476049 $ $Date: 2006-11-17 15:35:17 +1100 (Fri, 17 Nov 2006) $ 041 */ 042 public class PrintHelper { 043 044 public static String reformat(String source, int indent, int endCol) { 045 if(endCol-indent < 10) { 046 throw new IllegalArgumentException("This is ridiculous!"); 047 } 048 StringBuffer buf = new StringBuffer((int)(source.length()*1.1)); 049 String prefix = indent == 0 ? "" : buildIndent(indent); 050 try { 051 BufferedReader in = new BufferedReader(new StringReader(source)); 052 String line; 053 int pos; 054 while((line = in.readLine()) != null) { 055 if(buf.length() > 0) { 056 buf.append('\n'); 057 } 058 while(line.length() > 0) { 059 line = prefix + line; 060 if(line.length() > endCol) { 061 pos = line.lastIndexOf(' ', endCol); 062 if(pos < indent) { 063 pos = line.indexOf(' ', endCol); 064 if(pos < indent) { 065 pos = line.length(); 066 } 067 } 068 buf.append(line.substring(0, pos)).append('\n'); 069 if(pos < line.length()-1) { 070 line = line.substring(pos+1); 071 } else { 072 break; 073 } 074 } else { 075 buf.append(line).append("\n"); 076 break; 077 } 078 } 079 } 080 } catch (IOException e) { 081 throw new AssertionError("This should be impossible"); 082 } 083 return buf.toString(); 084 } 085 086 private static String buildIndent(int indent) { 087 StringBuffer buf = new StringBuffer(indent); 088 for(int i=0; i<indent; i++) { 089 buf.append(' '); 090 } 091 return buf.toString(); 092 } 093 094 public static final int DEFAULT_WIDTH = 76; 095 public static final int DEFAULT_LEFT_PAD = 1; 096 public static final int DEFAULT_DESC_PAD = 3; 097 public static final String DEFAULT_SYNTAX_PREFIX = "usage: "; 098 public static final String DEFAULT_OPT_PREFIX = "-"; 099 public static final String DEFAULT_LONG_OPT_PREFIX = "--"; 100 public static final String DEFAULT_ARG_NAME = "arg"; 101 102 private final OutputStream outputStream; 103 public int defaultWidth; 104 public int defaultLeftPad; 105 public int defaultDescPad; 106 public String defaultSyntaxPrefix; 107 public String defaultNewLine; 108 public String defaultOptPrefix; 109 public String defaultLongOptPrefix; 110 public String defaultArgName; 111 112 public PrintHelper(OutputStream outputStream) { 113 if (null == outputStream) { 114 throw new IllegalArgumentException("outputStream is required"); 115 } 116 this.outputStream = outputStream; 117 118 defaultWidth = DEFAULT_WIDTH; 119 defaultLeftPad = DEFAULT_LEFT_PAD; 120 defaultDescPad = DEFAULT_DESC_PAD; 121 defaultSyntaxPrefix = DEFAULT_SYNTAX_PREFIX; 122 defaultNewLine = System.getProperty("line.separator"); 123 defaultOptPrefix = DEFAULT_OPT_PREFIX; 124 defaultLongOptPrefix = DEFAULT_LONG_OPT_PREFIX; 125 defaultArgName = DEFAULT_ARG_NAME; 126 } 127 128 public void printHelp(String cmdLineSyntax, String header, Options options, String footer, boolean autoUsage) { 129 printHelp(defaultWidth, cmdLineSyntax, header, options, footer, autoUsage); 130 } 131 132 public void printHelp(int width, 133 String cmdLineSyntax, 134 String header, 135 Options options, 136 String footer, 137 boolean autoUsage) { 138 PrintWriter pw = new PrintWriter(outputStream); 139 printHelp(pw, width, cmdLineSyntax, header, options, defaultLeftPad, defaultDescPad, footer, autoUsage); 140 pw.flush(); 141 } 142 143 public void printHelp(PrintWriter pw, 144 int width, 145 String cmdLineSyntax, 146 String header, 147 Options options, 148 int leftPad, 149 int descPad, 150 String footer, 151 boolean autoUsage) throws IllegalArgumentException { 152 if (cmdLineSyntax == null || cmdLineSyntax.length() == 0) { 153 throw new IllegalArgumentException("cmdLineSyntax not provided"); 154 } 155 156 if (autoUsage) { 157 printUsage(pw, width, cmdLineSyntax, options); 158 } else { 159 printUsage(pw, width, cmdLineSyntax); 160 } 161 162 if (header != null && header.trim().length() > 0) { 163 printWrapped(pw, width, header); 164 } 165 printOptions(pw, width, options, leftPad, descPad); 166 if (footer != null && footer.trim().length() > 0) { 167 printWrapped(pw, width, footer); 168 } 169 } 170 171 public void printUsage(PrintWriter pw, int width, String app, Options options) { 172 // create a list for processed option groups 173 ArrayList list = new ArrayList(); 174 175 StringBuffer optionsBuff = new StringBuffer(); 176 177 // temp variable 178 Option option; 179 180 // iterate over the options 181 for (Iterator i = options.getOptions().iterator(); i.hasNext();) { 182 // get the next Option 183 option = (Option) i.next(); 184 185 // check if the option is part of an OptionGroup 186 OptionGroup group = options.getOptionGroup(option); 187 188 // if the option is part of a group and the group has not already 189 // been processed 190 if (group != null && !list.contains(group)) { 191 192 // add the group to the processed list 193 list.add(group); 194 195 // get the names of the options from the OptionGroup 196 Collection names = group.getNames(); 197 198 optionsBuff.append("["); 199 200 // for each option in the OptionGroup 201 for (Iterator iter = names.iterator(); iter.hasNext();) { 202 optionsBuff.append(iter.next()); 203 if (iter.hasNext()) { 204 optionsBuff.append("|"); 205 } 206 } 207 optionsBuff.append("] "); 208 } else if (group == null) { 209 // if the Option is not part of an OptionGroup 210 // if the Option is not a required option 211 if (!option.isRequired()) { 212 optionsBuff.append("["); 213 } 214 215 if (!" ".equals(option.getOpt())) { 216 optionsBuff.append("-").append(option.getOpt()); 217 } else { 218 optionsBuff.append("--").append(option.getLongOpt()); 219 } 220 221 if (option.hasArg()) { 222 optionsBuff.append(" "); 223 } 224 225 // if the Option has a value 226 if (option.hasArg()) { 227 optionsBuff.append(option.getArgName()); 228 } 229 230 // if the Option is not a required option 231 if (!option.isRequired()) { 232 optionsBuff.append("]"); 233 } 234 optionsBuff.append(" "); 235 } 236 } 237 238 app = app.replace("$options", optionsBuff.toString()); 239 240 // call printWrapped 241 printWrapped(pw, width, app.indexOf(' ') + 1, app); 242 } 243 244 public void printUsage(PrintWriter pw, int width, String cmdLineSyntax) { 245 int argPos = cmdLineSyntax.indexOf(' ') + 1; 246 printWrapped(pw, width, defaultSyntaxPrefix.length() + argPos, defaultSyntaxPrefix + cmdLineSyntax); 247 } 248 249 public void printOptions(PrintWriter pw, int width, Options options, int leftPad, int descPad) { 250 StringBuffer sb = new StringBuffer(); 251 renderOptions(sb, width, options, leftPad, descPad, true); 252 pw.println(sb.toString()); 253 } 254 255 public void printOptions(PrintWriter pw, Options options) { 256 StringBuffer sb = new StringBuffer(); 257 renderOptions(sb, defaultWidth, options, defaultLeftPad, defaultDescPad, true); 258 pw.println(sb.toString()); 259 } 260 261 public void printOptionsNoDesc(PrintWriter pw, Options options) { 262 StringBuffer sb = new StringBuffer(); 263 renderOptions(sb, defaultWidth, options, defaultLeftPad, defaultDescPad, false); 264 pw.println(sb.toString()); 265 } 266 267 public void printWrapped(PrintWriter pw, int width, String text) { 268 printWrapped(pw, width, 0, text); 269 } 270 271 public void printWrapped(PrintWriter pw, int width, int nextLineTabStop, String text) { 272 StringBuffer sb = new StringBuffer(text.length()); 273 renderWrappedText(sb, width, nextLineTabStop, text); 274 pw.println(sb.toString()); 275 } 276 277 protected StringBuffer renderOptions(StringBuffer sb, int width, Options options, int leftPad, int descPad, boolean displayDesc) { 278 final String lpad = createPadding(leftPad); 279 final String dpad = createPadding(descPad); 280 281 //first create list containing only <lpad>-a,--aaa where -a is opt and --aaa is 282 //long opt; in parallel look for the longest opt string 283 //this list will be then used to sort options ascending 284 int max = 0; 285 StringBuffer optBuf; 286 List prefixList = new ArrayList(); 287 Option option; 288 List optList = new ArrayList(options.getOptions()); 289 Collections.sort(optList, new StringBufferComparator()); 290 for (Iterator i = optList.iterator(); i.hasNext();) { 291 option = (Option) i.next(); 292 optBuf = new StringBuffer(8); 293 294 if (option.getOpt().equals(" ")) { 295 optBuf.append(lpad).append(" " + defaultLongOptPrefix).append(option.getLongOpt()); 296 } else { 297 optBuf.append(lpad).append(defaultOptPrefix).append(option.getOpt()); 298 if (option.hasLongOpt()) { 299 optBuf.append(',').append(defaultLongOptPrefix).append(option.getLongOpt()); 300 } 301 302 } 303 304 if (option.hasArg()) { 305 if (option.hasArgName()) { 306 optBuf.append(" <").append(option.getArgName()).append('>'); 307 } else { 308 optBuf.append(' '); 309 } 310 } 311 312 prefixList.add(optBuf); 313 max = optBuf.length() > max ? optBuf.length() : max; 314 } 315 int x = 0; 316 for (Iterator i = optList.iterator(); i.hasNext();) { 317 option = (Option) i.next(); 318 optBuf = new StringBuffer(prefixList.get(x++).toString()); 319 320 if (optBuf.length() < max) { 321 optBuf.append(createPadding(max - optBuf.length())); 322 } 323 optBuf.append(dpad); 324 325 if (displayDesc) { 326 optBuf.append(option.getDescription()); 327 } 328 int nextLineTabStop = max + descPad; 329 renderWrappedText(sb, width, nextLineTabStop, optBuf.toString()); 330 if (i.hasNext()) { 331 sb.append(defaultNewLine); 332 if (displayDesc) { 333 sb.append(defaultNewLine); 334 } 335 } 336 } 337 338 return sb; 339 } 340 341 protected StringBuffer renderWrappedText(StringBuffer sb, int width, int nextLineTabStop, String text) { 342 int pos = findWrapPos(text, width, 0); 343 if (pos == -1) { 344 sb.append(rtrim(text)); 345 return sb; 346 } else { 347 sb.append(rtrim(text.substring(0, pos))).append(defaultNewLine); 348 } 349 350 //all following lines must be padded with nextLineTabStop space characters 351 final String padding = createPadding(nextLineTabStop); 352 353 while (true) { 354 text = padding + text.substring(pos).trim(); 355 pos = findWrapPos(text, width, 0); 356 if (pos == -1) { 357 sb.append(text); 358 return sb; 359 } 360 361 sb.append(rtrim(text.substring(0, pos))).append(defaultNewLine); 362 } 363 364 } 365 366 protected int findWrapPos(String text, int width, int startPos) { 367 int pos = -1; 368 // the line ends before the max wrap pos or a new line char found 369 if (((pos = text.indexOf('\n', startPos)) != -1 && pos <= width) 370 || ((pos = text.indexOf('\t', startPos)) != -1 && pos <= width)) { 371 return pos; 372 } else if ((startPos + width) >= text.length()) { 373 return -1; 374 } 375 376 //look for the last whitespace character before startPos+width 377 pos = startPos + width; 378 char c; 379 while (pos >= startPos && (c = text.charAt(pos)) != ' ' && c != '\n' && c != '\r') { 380 --pos; 381 } 382 //if we found it - just return 383 if (pos > startPos) { 384 return pos; 385 } else { 386 //must look for the first whitespace chearacter after startPos + width 387 pos = startPos + width; 388 while (pos <= text.length() && (c = text.charAt(pos)) != ' ' && c != '\n' && c != '\r') { 389 ++pos; 390 } 391 return pos == text.length() ? -1 : pos; 392 } 393 } 394 395 protected String createPadding(int len) { 396 StringBuffer sb = new StringBuffer(len); 397 for (int i = 0; i < len; ++i) { 398 sb.append(' '); 399 } 400 return sb.toString(); 401 } 402 403 protected String rtrim(String s) { 404 if (s == null || s.length() == 0) { 405 return s; 406 } 407 408 int pos = s.length(); 409 while (pos >= 0 && Character.isWhitespace(s.charAt(pos - 1))) { 410 --pos; 411 } 412 return s.substring(0, pos); 413 } 414 415 private static class StringBufferComparator implements Comparator { 416 417 public int compare(Object o1, Object o2) { 418 String str1 = stripPrefix(o1.toString()); 419 String str2 = stripPrefix(o2.toString()); 420 return (str1.compareTo(str2)); 421 } 422 423 private String stripPrefix(String strOption) { 424 // Strip any leading '-' characters 425 int iStartIndex = strOption.lastIndexOf('-'); 426 if (iStartIndex == -1) { 427 iStartIndex = 0; 428 } 429 return strOption.substring(iStartIndex); 430 431 } 432 } 433 434 }