Coverage Report - org.apache.commons.cli2.util.HelpFormatter
 
Classes in this File Line Coverage Branch Coverage Complexity
HelpFormatter
98%
192/194
93%
67/72
1.975
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  You may obtain a copy of the License at
 8  
  *
 9  
  *     http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.apache.commons.cli2.util;
 18  
 
 19  
 import java.io.PrintWriter;
 20  
 import java.util.ArrayList;
 21  
 import java.util.Collections;
 22  
 import java.util.Comparator;
 23  
 import java.util.HashSet;
 24  
 import java.util.Iterator;
 25  
 import java.util.List;
 26  
 import java.util.Set;
 27  
 
 28  
 import org.apache.commons.cli2.DisplaySetting;
 29  
 import org.apache.commons.cli2.Group;
 30  
 import org.apache.commons.cli2.HelpLine;
 31  
 import org.apache.commons.cli2.Option;
 32  
 import org.apache.commons.cli2.OptionException;
 33  
 import org.apache.commons.cli2.resource.ResourceConstants;
 34  
 import org.apache.commons.cli2.resource.ResourceHelper;
 35  
 
 36  
 /**
 37  
  * Presents on screen help based on the application's Options
 38  
  */
 39  
 public class HelpFormatter {
 40  
     /**
 41  
      * The default screen width
 42  
      */
 43  
     public static final int DEFAULT_FULL_WIDTH = 80;
 44  
 
 45  
     /**
 46  
      * The default screen furniture left of screen
 47  
      */
 48  
     public static final String DEFAULT_GUTTER_LEFT = "";
 49  
 
 50  
     /**
 51  
      * The default screen furniture right of screen
 52  
      */
 53  
     public static final String DEFAULT_GUTTER_CENTER = "    ";
 54  
 
 55  
     /**
 56  
      * The default screen furniture between columns
 57  
      */
 58  
     public static final String DEFAULT_GUTTER_RIGHT = "";
 59  
 
 60  
     /**
 61  
      * The default DisplaySettings used to select the elements to display in the
 62  
      * displayed line of full usage information.
 63  
      *
 64  
      * @see DisplaySetting
 65  
      */
 66  
     public static final Set DEFAULT_FULL_USAGE_SETTINGS;
 67  
 
 68  
     /**
 69  
      * The default DisplaySettings used to select the elements of usage per help
 70  
      * line in the main body of help
 71  
      *
 72  
      * @see DisplaySetting
 73  
      */
 74  
     public static final Set DEFAULT_LINE_USAGE_SETTINGS;
 75  
 
 76  
     /**
 77  
      * The default DisplaySettings used to select the help lines in the main
 78  
      * body of help
 79  
      */
 80  
     public static final Set DEFAULT_DISPLAY_USAGE_SETTINGS;
 81  
 
 82  
     static {
 83  1
         final Set fullUsage = new HashSet(DisplaySetting.ALL);
 84  1
         fullUsage.remove(DisplaySetting.DISPLAY_ALIASES);
 85  1
         fullUsage.remove(DisplaySetting.DISPLAY_GROUP_NAME);
 86  1
         fullUsage.remove(DisplaySetting.DISPLAY_OPTIONAL_CHILD_GROUP);
 87  1
         DEFAULT_FULL_USAGE_SETTINGS = Collections.unmodifiableSet(fullUsage);
 88  
 
 89  1
         final Set lineUsage = new HashSet();
 90  1
         lineUsage.add(DisplaySetting.DISPLAY_ALIASES);
 91  1
         lineUsage.add(DisplaySetting.DISPLAY_GROUP_NAME);
 92  1
         lineUsage.add(DisplaySetting.DISPLAY_PARENT_ARGUMENT);
 93  1
         DEFAULT_LINE_USAGE_SETTINGS = Collections.unmodifiableSet(lineUsage);
 94  
 
 95  1
         final Set displayUsage = new HashSet(DisplaySetting.ALL);
 96  1
         displayUsage.remove(DisplaySetting.DISPLAY_PARENT_ARGUMENT);
 97  1
         DEFAULT_DISPLAY_USAGE_SETTINGS = Collections.unmodifiableSet(displayUsage);
 98  1
     }
 99  
 
 100  108
     private Set fullUsageSettings = new HashSet(DEFAULT_FULL_USAGE_SETTINGS);
 101  108
     private Set lineUsageSettings = new HashSet(DEFAULT_LINE_USAGE_SETTINGS);
 102  108
     private Set displaySettings = new HashSet(DEFAULT_DISPLAY_USAGE_SETTINGS);
 103  108
     private OptionException exception = null;
 104  
     private Group group;
 105  108
     private Comparator comparator = null;
 106  108
     private String divider = null;
 107  108
     private String header = null;
 108  108
     private String footer = null;
 109  108
     private String shellCommand = "";
 110  108
     private PrintWriter out = new PrintWriter(System.out);
 111  
 
 112  
     //or should this default to .err?
 113  
     private final String gutterLeft;
 114  
     private final String gutterCenter;
 115  
     private final String gutterRight;
 116  
     private final int pageWidth;
 117  
 
 118  
     /**
 119  
      * Creates a new HelpFormatter using the defaults
 120  
      */
 121  
     public HelpFormatter() {
 122  83
         this(DEFAULT_GUTTER_LEFT, DEFAULT_GUTTER_CENTER, DEFAULT_GUTTER_RIGHT, DEFAULT_FULL_WIDTH);
 123  83
     }
 124  
 
 125  
     /**
 126  
      * Creates a new HelpFormatter using the specified parameters
 127  
      * @param gutterLeft the string marking left of screen
 128  
      * @param gutterCenter the string marking center of screen
 129  
      * @param gutterRight the string marking right of screen
 130  
      * @param fullWidth the width of the screen
 131  
      */
 132  
     public HelpFormatter(final String gutterLeft,
 133  
                          final String gutterCenter,
 134  
                          final String gutterRight,
 135  108
                          final int fullWidth) {
 136  
         // default the left gutter to empty string
 137  108
         this.gutterLeft = (gutterLeft == null) ? DEFAULT_GUTTER_LEFT : gutterLeft;
 138  
 
 139  
         // default the center gutter to a single space
 140  108
         this.gutterCenter = (gutterCenter == null) ? DEFAULT_GUTTER_CENTER : gutterCenter;
 141  
 
 142  
         // default the right gutter to empty string
 143  108
         this.gutterRight = (gutterRight == null) ? DEFAULT_GUTTER_RIGHT : gutterRight;
 144  
 
 145  
         // calculate the available page width
 146  108
         this.pageWidth = fullWidth - this.gutterLeft.length() - this.gutterRight.length();
 147  
 
 148  
         // check available page width is valid
 149  108
         int availableWidth = fullWidth - pageWidth + this.gutterCenter.length();
 150  
 
 151  108
         if (availableWidth < 2) {
 152  0
             throw new IllegalArgumentException(ResourceHelper.getResourceHelper().getMessage(ResourceConstants.HELPFORMATTER_GUTTER_TOO_LONG));
 153  
         }
 154  108
     }
 155  
 
 156  
     /**
 157  
      * Prints the Option help.
 158  
      */
 159  
     public void print() {
 160  12
         printHeader();
 161  12
         printException();
 162  12
         printUsage();
 163  12
         printHelp();
 164  12
         printFooter();
 165  12
         out.flush();
 166  12
     }
 167  
 
 168  
     /**
 169  
      * Prints any error message.
 170  
      */
 171  
     public void printException() {
 172  13
         if (exception != null) {
 173  3
             printDivider();
 174  3
             printWrapped(exception.getMessage());
 175  
         }
 176  13
     }
 177  
 
 178  
     /**
 179  
      * Prints detailed help per option.
 180  
      */
 181  
     public void printHelp() {
 182  15
         printDivider();
 183  
 
 184  
         final Option option;
 185  
 
 186  15
         if ((exception != null) && (exception.getOption() != null)) {
 187  3
             option = exception.getOption();
 188  
         } else {
 189  12
             option = group;
 190  
         }
 191  
 
 192  
         // grab the HelpLines to display
 193  15
         final List helpLines = option.helpLines(0, displaySettings, comparator);
 194  
 
 195  
         // calculate the maximum width of the usage strings
 196  15
         int usageWidth = 0;
 197  
 
 198  15
         for (final Iterator i = helpLines.iterator(); i.hasNext();) {
 199  119
             final HelpLine helpLine = (HelpLine) i.next();
 200  119
             final String usage = helpLine.usage(lineUsageSettings, comparator);
 201  119
             usageWidth = Math.max(usageWidth, usage.length());
 202  119
         }
 203  
 
 204  
         // build a blank string to pad wrapped descriptions
 205  15
         final StringBuffer blankBuffer = new StringBuffer();
 206  
 
 207  316
         for (int i = 0; i < usageWidth; i++) {
 208  301
             blankBuffer.append(' ');
 209  
         }
 210  
 
 211  
         // determine the width available for descriptions
 212  15
         final int descriptionWidth = Math.max(1, pageWidth - gutterCenter.length() - usageWidth);
 213  
 
 214  
         // display each HelpLine
 215  15
         for (final Iterator i = helpLines.iterator(); i.hasNext();) {
 216  
             // grab the HelpLine
 217  119
             final HelpLine helpLine = (HelpLine) i.next();
 218  
 
 219  
             // wrap the description
 220  119
             final List descList = wrap(helpLine.getDescription(), descriptionWidth);
 221  119
             final Iterator descriptionIterator = descList.iterator();
 222  
 
 223  
             // display usage + first line of description
 224  119
             printGutterLeft();
 225  119
             pad(helpLine.usage(lineUsageSettings, comparator), usageWidth, out);
 226  119
             out.print(gutterCenter);
 227  119
             pad((String) descriptionIterator.next(), descriptionWidth, out);
 228  119
             printGutterRight();
 229  119
             out.println();
 230  
 
 231  
             // display padding + remaining lines of description
 232  288
             while (descriptionIterator.hasNext()) {
 233  169
                 printGutterLeft();
 234  
 
 235  
                 //pad(helpLine.getUsage(),usageWidth,out);
 236  169
                 out.print(blankBuffer);
 237  169
                 out.print(gutterCenter);
 238  169
                 pad((String) descriptionIterator.next(), descriptionWidth, out);
 239  169
                 printGutterRight();
 240  169
                 out.println();
 241  
             }
 242  119
         }
 243  
 
 244  15
         printDivider();
 245  15
     }
 246  
 
 247  
     /**
 248  
      * Prints a single line of usage information (wrapping if necessary)
 249  
      */
 250  
     public void printUsage() {
 251  13
         printDivider();
 252  
 
 253  13
         final StringBuffer buffer = new StringBuffer("Usage:\n");
 254  13
         buffer.append(shellCommand).append(' ');
 255  13
         group.appendUsage(buffer, fullUsageSettings, comparator, " ");
 256  13
         printWrapped(buffer.toString());
 257  13
     }
 258  
 
 259  
     /**
 260  
      * Prints a header string if necessary
 261  
      */
 262  
     public void printHeader() {
 263  13
         if (header != null) {
 264  4
             printDivider();
 265  4
             printWrapped(header);
 266  
         }
 267  13
     }
 268  
 
 269  
     /**
 270  
      * Prints a footer string if necessary
 271  
      */
 272  
     public void printFooter() {
 273  13
         if (footer != null) {
 274  4
             printWrapped(footer);
 275  4
             printDivider();
 276  
         }
 277  13
     }
 278  
 
 279  
     /**
 280  
      * Prints a string wrapped if necessary
 281  
      * @param text the string to wrap
 282  
      */
 283  
     public void printWrapped(final String text) {
 284  24
         for (final Iterator i = wrap(text, pageWidth).iterator(); i.hasNext();) {
 285  47
             printGutterLeft();
 286  47
             pad((String) i.next(), pageWidth, out);
 287  47
             printGutterRight();
 288  47
             out.println();
 289  
         }
 290  
 
 291  24
         out.flush();
 292  24
     }
 293  
 
 294  
     /**
 295  
      * Prints the left gutter string
 296  
      */
 297  
     public void printGutterLeft() {
 298  335
         if (gutterLeft != null) {
 299  335
             out.print(gutterLeft);
 300  
         }
 301  335
     }
 302  
 
 303  
     /**
 304  
      * Prints the right gutter string
 305  
      */
 306  
     public void printGutterRight() {
 307  335
         if (gutterRight != null) {
 308  335
             out.print(gutterRight);
 309  
         }
 310  335
     }
 311  
 
 312  
     /**
 313  
      * Prints the divider text
 314  
      */
 315  
     public void printDivider() {
 316  55
         if (divider != null) {
 317  19
             out.println(divider);
 318  
         }
 319  55
     }
 320  
 
 321  
     protected static void pad(final String text,
 322  
                               final int width,
 323  
                               final PrintWriter writer) {
 324  
         final int left;
 325  
 
 326  
         // write the text and record how many characters written
 327  458
         if (text == null) {
 328  1
             left = 0;
 329  
         } else {
 330  457
             writer.write(text);
 331  457
             left = text.length();
 332  
         }
 333  
 
 334  
         // pad remainder with spaces
 335  7860
         for (int i = left; i < width; ++i) {
 336  7402
             writer.write(' ');
 337  
         }
 338  458
     }
 339  
 
 340  
     protected static List wrap(final String text,
 341  
                                final int width) {
 342  
         // check for valid width
 343  151
         if (width < 1) {
 344  1
             throw new IllegalArgumentException(ResourceHelper.getResourceHelper().getMessage(ResourceConstants.HELPFORMATTER_WIDTH_TOO_NARROW,
 345  
                                                                                              new Object[] {
 346  
                                                                                                  new Integer(width)
 347  
                                                                                              }));
 348  
         }
 349  
 
 350  
         // handle degenerate case
 351  150
         if (text == null) {
 352  22
             return Collections.singletonList("");
 353  
         }
 354  
 
 355  128
         final List lines = new ArrayList();
 356  128
         final char[] chars = text.toCharArray();
 357  128
         int left = 0;
 358  
 
 359  
         // for each character in the string
 360  338
         while (left < chars.length) {
 361  
             // sync left and right indeces
 362  332
             int right = left;
 363  
 
 364  
             // move right until we run out of characters, width or find a newline
 365  4987
             while ((right < chars.length) && (chars[right] != '\n') &&
 366  
                        (right < (left + width + 1))) {
 367  4655
                 right++;
 368  
             }
 369  
 
 370  
             // if a newline was found
 371  332
             if ((right < chars.length) && (chars[right] == '\n')) {
 372  
                 // record the substring
 373  21
                 final String line = new String(chars, left, right - left);
 374  21
                 lines.add(line);
 375  
 
 376  
                 // move to the end of the substring
 377  21
                 left = right + 1;
 378  
 
 379  21
                 if (left == chars.length) {
 380  1
                     lines.add("");
 381  
                 }
 382  
 
 383  
                 // restart the loop
 384  
                 continue;
 385  
             }
 386  
 
 387  
             // move to the next ideal wrap point
 388  311
             right = (left + width) - 1;
 389  
 
 390  
             // if we have run out of characters
 391  311
             if (chars.length <= right) {
 392  
                 // record the substring
 393  122
                 final String line = new String(chars, left, chars.length - left);
 394  122
                 lines.add(line);
 395  
 
 396  
                 // abort the loop
 397  122
                 break;
 398  
             }
 399  
 
 400  
             // back track the substring end until a space is found
 401  460
             while ((right >= left) && (chars[right] != ' ')) {
 402  271
                 right--;
 403  
             }
 404  
 
 405  
             // if a space was found
 406  189
             if (right >= left) {
 407  
                 // record the substring to space
 408  27
                 final String line = new String(chars, left, right - left);
 409  27
                 lines.add(line);
 410  
 
 411  
                 // absorb all the spaces before next substring
 412  54
                 while ((right < chars.length) && (chars[right] == ' ')) {
 413  27
                     right++;
 414  
                 }
 415  
 
 416  27
                 left = right;
 417  
 
 418  
                 // restart the loop
 419  27
                 continue;
 420  
             }
 421  
 
 422  
             // move to the wrap position irrespective of spaces
 423  162
             right = Math.min(left + width, chars.length);
 424  
 
 425  
             // record the substring
 426  162
             final String line = new String(chars, left, right - left);
 427  162
             lines.add(line);
 428  
 
 429  
             // absorb any the spaces before next substring
 430  187
             while ((right < chars.length) && (chars[right] == ' ')) {
 431  25
                 right++;
 432  
             }
 433  
 
 434  162
             left = right;
 435  162
         }
 436  
 
 437  128
         return lines;
 438  
     }
 439  
 
 440  
     /**
 441  
      * The Comparator to use when sorting Options
 442  
      * @param comparator Comparator to use when sorting Options
 443  
      */
 444  
     public void setComparator(Comparator comparator) {
 445  1
         this.comparator = comparator;
 446  1
     }
 447  
 
 448  
     /**
 449  
      * The DisplaySettings used to select the help lines in the main body of
 450  
      * help
 451  
      *
 452  
      * @param displaySettings the settings to use
 453  
      * @see DisplaySetting
 454  
      */
 455  
     public void setDisplaySettings(Set displaySettings) {
 456  1
         this.displaySettings = displaySettings;
 457  1
     }
 458  
 
 459  
     /**
 460  
      * Sets the string to use as a divider between sections of help
 461  
      * @param divider the dividing string
 462  
      */
 463  
     public void setDivider(String divider) {
 464  23
         this.divider = divider;
 465  23
     }
 466  
 
 467  
     /**
 468  
      * Sets the exception to document
 469  
      * @param exception the exception that occured
 470  
      */
 471  
     public void setException(OptionException exception) {
 472  4
         this.exception = exception;
 473  4
     }
 474  
 
 475  
     /**
 476  
      * Sets the footer text of the help screen
 477  
      * @param footer the footer text
 478  
      */
 479  
     public void setFooter(String footer) {
 480  24
         this.footer = footer;
 481  24
     }
 482  
 
 483  
     /**
 484  
      * The DisplaySettings used to select the elements to display in the
 485  
      * displayed line of full usage information.
 486  
      * @see DisplaySetting
 487  
      * @param fullUsageSettings
 488  
      */
 489  
     public void setFullUsageSettings(Set fullUsageSettings) {
 490  2
         this.fullUsageSettings = fullUsageSettings;
 491  2
     }
 492  
 
 493  
     /**
 494  
      * Sets the Group of Options to document
 495  
      * @param group the options to document
 496  
      */
 497  
     public void setGroup(Group group) {
 498  42
         this.group = group;
 499  42
     }
 500  
 
 501  
     /**
 502  
      * Sets the footer text of the help screen
 503  
      * @param header the footer text
 504  
      */
 505  
     public void setHeader(String header) {
 506  24
         this.header = header;
 507  24
     }
 508  
 
 509  
     /**
 510  
      * Sets the DisplaySettings used to select elements in the per helpline
 511  
      * usage strings.
 512  
      * @see DisplaySetting
 513  
      * @param lineUsageSettings the DisplaySettings to use
 514  
      */
 515  
     public void setLineUsageSettings(Set lineUsageSettings) {
 516  1
         this.lineUsageSettings = lineUsageSettings;
 517  1
     }
 518  
 
 519  
     /**
 520  
      * Sets the command string used to invoke the application
 521  
      * @param shellCommand the invokation command
 522  
      */
 523  
     public void setShellCommand(String shellCommand) {
 524  26
         this.shellCommand = shellCommand;
 525  26
     }
 526  
 
 527  
     /**
 528  
      * @return the Comparator used to sort the Group
 529  
      */
 530  
     public Comparator getComparator() {
 531  1
         return comparator;
 532  
     }
 533  
 
 534  
     /**
 535  
      * @return the DisplaySettings used to select HelpLines
 536  
      */
 537  
     public Set getDisplaySettings() {
 538  1
         return displaySettings;
 539  
     }
 540  
 
 541  
     /**
 542  
      * @return the String used as a horizontal section divider
 543  
      */
 544  
     public String getDivider() {
 545  1
         return divider;
 546  
     }
 547  
 
 548  
     /**
 549  
      * @return the Exception being documented by this HelpFormatter
 550  
      */
 551  
     public OptionException getException() {
 552  0
         return exception;
 553  
     }
 554  
 
 555  
     /**
 556  
      * @return the help screen footer text
 557  
      */
 558  
     public String getFooter() {
 559  1
         return footer;
 560  
     }
 561  
 
 562  
     /**
 563  
      * @return the DisplaySettings used in the full usage string
 564  
      */
 565  
     public Set getFullUsageSettings() {
 566  5
         return fullUsageSettings;
 567  
     }
 568  
 
 569  
     /**
 570  
      * @return the group documented by this HelpFormatter
 571  
      */
 572  
     public Group getGroup() {
 573  1
         return group;
 574  
     }
 575  
 
 576  
     /**
 577  
      * @return the String used as the central gutter
 578  
      */
 579  
     public String getGutterCenter() {
 580  2
         return gutterCenter;
 581  
     }
 582  
 
 583  
     /**
 584  
      * @return the String used as the left gutter
 585  
      */
 586  
     public String getGutterLeft() {
 587  2
         return gutterLeft;
 588  
     }
 589  
 
 590  
     /**
 591  
      * @return the String used as the right gutter
 592  
      */
 593  
     public String getGutterRight() {
 594  2
         return gutterRight;
 595  
     }
 596  
 
 597  
     /**
 598  
      * @return the help screen header text
 599  
      */
 600  
     public String getHeader() {
 601  1
         return header;
 602  
     }
 603  
 
 604  
     /**
 605  
      * @return the DisplaySettings used in the per help line usage strings
 606  
      */
 607  
     public Set getLineUsageSettings() {
 608  4
         return lineUsageSettings;
 609  
     }
 610  
 
 611  
     /**
 612  
      * @return the width of the screen in characters
 613  
      */
 614  
     public int getPageWidth() {
 615  1
         return pageWidth;
 616  
     }
 617  
 
 618  
     /**
 619  
      * @return the command used to execute the application
 620  
      */
 621  
     public String getShellCommand() {
 622  1
         return shellCommand;
 623  
     }
 624  
 
 625  
     /**
 626  
      * @param out the PrintWriter to write to
 627  
      */
 628  
     public void setPrintWriter(PrintWriter out) {
 629  24
         this.out = out;
 630  24
     }
 631  
 
 632  
     /**
 633  
      * @return the PrintWriter that will be written to
 634  
      */
 635  
     public PrintWriter getPrintWriter() {
 636  1
         return out;
 637  
     }
 638  
 }