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