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 }