View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one   *
3    * or more contributor license agreements.  See the NOTICE file *
4    * distributed with this work for additional information        *
5    * regarding copyright ownership.  The ASF licenses this file   *
6    * to you under the Apache License, Version 2.0 (the            *
7    * "License"); you may not use this file except in compliance   *
8    * with the License.  You may obtain a copy of the License at   *
9    *                                                              *
10   *   http://www.apache.org/licenses/LICENSE-2.0                 *
11   *                                                              *
12   * Unless required by applicable law or agreed to in writing,   *
13   * software distributed under the License is distributed on an  *
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15   * KIND, either express or implied.  See the License for the    *
16   * specific language governing permissions and limitations      *
17   * under the License.                                           *
18   */
19  package org.apache.rat.help;
20  
21  import java.io.PrintWriter;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.function.Function;
27  
28  import org.apache.commons.cli.HelpFormatter;
29  import org.apache.commons.cli.Option;
30  import org.apache.commons.cli.Options;
31  import org.apache.commons.lang3.StringUtils;
32  import org.apache.commons.text.WordUtils;
33  import org.apache.rat.CLIOptionCollection;
34  import org.apache.rat.OptionCollection;
35  import org.apache.rat.VersionInfo;
36  
37  import static java.lang.String.format;
38  
39  /**
40   * The base class to perform Help processing for programs.
41   */
42  public abstract class AbstractHelp {
43      /** Text to display when multiple options are supported */
44      private static final String END_OF_OPTION_MSG = " Multiple values may be specified. " +
45              "Note that '--' or a following option is required when using this parameter.";
46  
47      /** The width of the help report in chars. */
48      public static final int HELP_WIDTH = 120;
49      /** The number of chars to indent output with. */
50      public static final int HELP_PADDING = 4;
51  
52      /** The help formatter for this instance */
53      protected final RatHelpFormatter helpFormatter;
54      /** The version info for this instance */
55      protected final VersionInfo versionInfo;
56  
57      /**
58       * The collection of client options.
59       */
60      protected final CLIOptionCollection cliOptionCollection = new CLIOptionCollection();
61  
62      /**
63       * Base class to perform help output.
64       */
65      protected AbstractHelp() {
66          helpFormatter = new RatHelpFormatter();
67          versionInfo = new VersionInfo();
68      }
69  
70      /** Function to format deprecated display */
71      public static final Function<Option, String> DEPRECATED_MSG = o -> {
72          StringBuilder sb = new StringBuilder("[").append(o.getDeprecated().toString()).append("]");
73          if (o.getDescription() != null) {
74              sb.append(" ").append(o.getDescription());
75          }
76          return sb.toString();
77      };
78  
79      /**
80       * Create a padding.
81       * @param len The length of the padding in characters.
82       * @return a string with len blanks.
83       */
84      public static String createPadding(final int len) {
85          char[] padding = new char[len];
86          Arrays.fill(padding, ' ');
87          return new String(padding);
88      }
89  
90      /**
91       * Create a section header for the output.
92       * @param txt the text to put in the header.
93       * @return the Header string.
94       */
95      public static String header(final String txt) {
96          return String.format("%n====== %s ======%n", WordUtils.capitalizeFully(txt));
97      }
98  
99      /**
100      * Provides help for formatting text.
101      */
102     public class RatHelpFormatter extends HelpFormatter {
103 
104         /**
105          * Constructor
106          */
107         RatHelpFormatter() {
108             super();
109             this.optionComparator = OptionCollection.OPTION_COMPARATOR;
110             this.setWidth(HELP_WIDTH);
111         }
112 
113         /**
114          * Prints the help text.
115          * @param writer the writer to write to.
116          * @param cmdLineSyntax The command line syntax for the program.
117          * @param header Additional information to proceed the Options descriptions.
118          * @param options the Options to output help for.
119          * @param footer Additional information to follow the Options descriptions.
120          */
121         public void printHelp(final PrintWriter writer, final String cmdLineSyntax, final String header, final Options options, final String footer) {
122             if (StringUtils.isEmpty(cmdLineSyntax)) {
123                 throw new IllegalArgumentException("cmdLineSyntax not provided");
124             }
125 
126             helpFormatter.printUsage(writer, HELP_WIDTH, cmdLineSyntax);
127 
128             if (header != null && !header.isEmpty()) {
129                 helpFormatter.printWrapped(writer, HELP_WIDTH, header);
130             }
131             printOptions(writer, HELP_WIDTH, options, helpFormatter.getLeftPadding(), helpFormatter.getDescPadding());
132             if (footer != null && !footer.isEmpty()) {
133                 helpFormatter.printWrapped(writer, helpFormatter.getWidth(), footer);
134             }
135         }
136 
137         @Override
138         protected StringBuffer renderOptions(final StringBuffer sb, final int width, final Options options, final int leftPad, final int descPad) {
139             final String lpad = createPadding(leftPad);
140             final String dpad = createPadding(descPad);
141             // first create list containing only <lpad>-a,--aaa where
142             // -a is option and --aaa is long option; in parallel look for
143             // the longest option string; this list will then be used to
144             // sort options ascending
145             int max = 0;
146             final List<StringBuffer> prefixList = new ArrayList<>();
147             final List<Option> optList = new ArrayList<>(options.getOptions());
148             optList.sort(helpFormatter.getOptionComparator());
149 
150             for (final Option option : optList) {
151                 final StringBuffer optBuf = new StringBuffer();
152                 if (option.getOpt() == null) {
153                     optBuf.append(lpad).append("   ").append(getLongOptPrefix()).append(option.getLongOpt());
154                 } else {
155                     optBuf.append(lpad).append(getOptPrefix()).append(option.getOpt());
156                     if (option.hasLongOpt()) {
157                         optBuf.append(',').append(getLongOptPrefix()).append(option.getLongOpt());
158                     }
159                 }
160                 if (option.hasArg()) {
161                     final String argName = option.getArgName();
162                     if (argName != null && argName.isEmpty()) {
163                         // if the option has a blank argName
164                         optBuf.append(' ');
165                     } else {
166                         optBuf.append(option.hasLongOpt() ? helpFormatter.getLongOptSeparator() : " ");
167                         optBuf.append("<").append(argName != null ? option.getArgName() : getArgName()).append(">");
168                     }
169                 }
170                 prefixList.add(optBuf);
171                 max = Math.max(optBuf.length(), max);
172             }
173             int x = 0;
174             for (final Iterator<Option> it = optList.iterator(); it.hasNext();) {
175                 final Option option = it.next();
176                 final StringBuilder optBuf = new StringBuilder(prefixList.get(x++).toString());
177                 if (optBuf.length() < max) {
178                     optBuf.append(createPadding(max - optBuf.length()));
179                 }
180                 optBuf.append(dpad);
181                 final int nextLineTabStop = max + descPad;
182                 // check for deprecation
183                 if (option.isDeprecated()) {
184                     optBuf.append(DEPRECATED_MSG.apply(option).trim());
185                 } else if (option.getDescription() != null) {
186                     optBuf.append(option.getDescription());
187                 }
188                 // check for multiple values
189                 if (option.hasArgs()) {
190                     optBuf.append(END_OF_OPTION_MSG);
191                 }
192                 // check for default value
193                 String defaultValue = cliOptionCollection.defaultValue(option);
194                 if (defaultValue != null) {
195                     optBuf.append(format(" (Default value = %s)", defaultValue));
196                 }
197                 renderWrappedText(sb, width, nextLineTabStop, optBuf.toString());
198                 if (it.hasNext()) {
199                     sb.append(getNewLine());
200                 }
201             }
202             return sb;
203         }
204     }
205 }