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.OptionCollectionParser;
34  import org.apache.rat.VersionInfo;
35  import org.apache.rat.ui.UIOptionCollection;
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       * Base class to perform help output.
58       */
59      protected AbstractHelp(final UIOptionCollection<?>  uiOptionCollection) {
60          helpFormatter = new RatHelpFormatter(uiOptionCollection);
61          versionInfo = new VersionInfo();
62      }
63  
64      /** Function to format deprecated display */
65      public static final Function<Option, String> DEPRECATED_MSG = o -> {
66          StringBuilder sb = new StringBuilder("[").append(o.getDeprecated().toString()).append("]");
67          if (o.getDescription() != null) {
68              sb.append(" ").append(o.getDescription());
69          }
70          return sb.toString();
71      };
72  
73      /**
74       * Create a padding.
75       * @param len The length of the padding in characters.
76       * @return a string with len blanks.
77       */
78      public static String createPadding(final int len) {
79          char[] padding = new char[len];
80          Arrays.fill(padding, ' ');
81          return new String(padding);
82      }
83  
84      /**
85       * Create a section header for the output.
86       * @param txt the text to put in the header.
87       * @return the Header string.
88       */
89      public static String header(final String txt) {
90          return String.format("%n====== %s ======%n", WordUtils.capitalizeFully(txt));
91      }
92  
93      /**
94       * Provides help for formatting text.
95       */
96      public class RatHelpFormatter extends HelpFormatter {
97          /** The UIOptionCollection to report help for. */
98          private final UIOptionCollection<?> uiOptionCollection;
99  
100         /**
101          * Create formatter with given UIOptionCollection.
102          * @param uiOptionCollection the UIOptionCollection to report help for.
103          */
104         RatHelpFormatter(final UIOptionCollection<?> uiOptionCollection) {
105             super();
106             this.uiOptionCollection = uiOptionCollection;
107             this.optionComparator = OptionCollectionParser.OPTION_COMPARATOR;
108             this.setWidth(HELP_WIDTH);
109         }
110 
111         /**
112          * Prints the help text.
113          * @param writer the writer to write to.
114          * @param cmdLineSyntax The command line syntax for the program.
115          * @param header Additional information to proceed the Options descriptions.
116          * @param options the Options to output help for.
117          * @param footer Additional information to follow the Options descriptions.
118          */
119         public void printHelp(final PrintWriter writer, final String cmdLineSyntax, final String header, final Options options, final String footer) {
120             if (StringUtils.isEmpty(cmdLineSyntax)) {
121                 throw new IllegalArgumentException("cmdLineSyntax not provided");
122             }
123 
124             helpFormatter.printUsage(writer, HELP_WIDTH, cmdLineSyntax);
125 
126             if (header != null && !header.isEmpty()) {
127                 helpFormatter.printWrapped(writer, HELP_WIDTH, header);
128             }
129             printOptions(writer, HELP_WIDTH, options, helpFormatter.getLeftPadding(), helpFormatter.getDescPadding());
130             if (footer != null && !footer.isEmpty()) {
131                 helpFormatter.printWrapped(writer, helpFormatter.getWidth(), footer);
132             }
133         }
134 
135         @Override
136         protected StringBuffer renderOptions(final StringBuffer sb, final int width, final Options options, final int leftPad, final int descPad) {
137             final String lpad = createPadding(leftPad);
138             final String dpad = createPadding(descPad);
139             // first create list containing only <lpad>-a,--aaa where
140             // -a is option and --aaa is long option; in parallel look for
141             // the longest option string; this list will then be used to
142             // sort options ascending
143             int max = 0;
144             final List<StringBuffer> prefixList = new ArrayList<>();
145             final List<Option> optList = new ArrayList<>(options.getOptions());
146             optList.sort(helpFormatter.getOptionComparator());
147 
148             for (final Option option : optList) {
149                 final StringBuffer optBuf = new StringBuffer();
150                 if (option.getOpt() == null) {
151                     optBuf.append(lpad).append("   ").append(getLongOptPrefix()).append(option.getLongOpt());
152                 } else {
153                     optBuf.append(lpad).append(getOptPrefix()).append(option.getOpt());
154                     if (option.hasLongOpt()) {
155                         optBuf.append(',').append(getLongOptPrefix()).append(option.getLongOpt());
156                     }
157                 }
158                 if (option.hasArg()) {
159                     final String argName = option.getArgName();
160                     if (argName != null && argName.isEmpty()) {
161                         // if the option has a blank argName
162                         optBuf.append(' ');
163                     } else {
164                         optBuf.append(option.hasLongOpt() ? helpFormatter.getLongOptSeparator() : " ");
165                         optBuf.append("<").append(argName != null ? option.getArgName() : getArgName()).append(">");
166                     }
167                 }
168                 prefixList.add(optBuf);
169                 max = Math.max(optBuf.length(), max);
170             }
171             int x = 0;
172             for (final Iterator<Option> it = optList.iterator(); it.hasNext();) {
173                 final Option option = it.next();
174                 final StringBuilder optBuf = new StringBuilder(prefixList.get(x++).toString());
175                 if (optBuf.length() < max) {
176                     optBuf.append(createPadding(max - optBuf.length()));
177                 }
178                 optBuf.append(dpad);
179                 final int nextLineTabStop = max + descPad;
180                 // check for deprecation
181                 if (option.isDeprecated()) {
182                     optBuf.append(DEPRECATED_MSG.apply(option).trim());
183                 } else if (option.getDescription() != null) {
184                     optBuf.append(option.getDescription());
185                 }
186                 // check for multiple values
187                 if (option.hasArgs()) {
188                     optBuf.append(END_OF_OPTION_MSG);
189                 }
190                 // check for default value
191                 String defaultValue = uiOptionCollection.defaultValue(option);
192                 if (defaultValue != null) {
193                     optBuf.append(format(" (Default value = %s)", defaultValue));
194                 }
195                 renderWrappedText(sb, width, nextLineTabStop, optBuf.toString());
196                 if (it.hasNext()) {
197                     sb.append(getNewLine());
198                 }
199             }
200             return sb;
201         }
202     }
203 }