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 https://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 */ 017package org.apache.commons.cli.help; 018 019import java.io.IOException; 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.Comparator; 023import java.util.Iterator; 024import java.util.List; 025import java.util.Objects; 026import java.util.function.Function; 027import java.util.function.Supplier; 028 029import org.apache.commons.cli.Option; 030import org.apache.commons.cli.OptionGroup; 031import org.apache.commons.cli.Options; 032 033/** 034 * Helps formatters provides the framework to link a {@link HelpAppendable} with a {@link OptionFormatter} and a default {@link TableDefinition} so to produce 035 * standardized format help output. 036 * 037 * @since 1.10.0 038 */ 039public abstract class AbstractHelpFormatter { 040 041 /** 042 * Abstracts building instances for subclasses. 043 * <ul> 044 * <li>helpAppendable = a {@link TextHelpAppendable} writing to {@code System.out}</li> 045 * <li>optionFormatter.Builder = the default {@link OptionFormatter.Builder}</li> 046 * </ul> 047 * 048 * @param <B> The builder type. 049 * @param <T> The type to build. 050 */ 051 public abstract static class Builder<B extends Builder<B, T>, T extends AbstractHelpFormatter> implements Supplier<T> { 052 053 /** The comparator to sort lists of options */ 054 private Comparator<Option> comparator = DEFAULT_COMPARATOR; 055 056 /** The {@link HelpAppendable}. */ 057 private HelpAppendable helpAppendable = TextHelpAppendable.systemOut(); 058 059 /** The {@link OptionFormatter.Builder} to use to format options in the table. */ 060 private OptionFormatter.Builder optionFormatBuilder = OptionFormatter.builder(); 061 062 /** The string to separate option groups. */ 063 private String optionGroupSeparator = DEFAULT_OPTION_GROUP_SEPARATOR; 064 065 /** 066 * Constructs a new instance. 067 * <p> 068 * Sets {@code showSince} to {@code true}. 069 * </p> 070 */ 071 protected Builder() { 072 // empty 073 } 074 075 /** 076 * Returns this instance cast to {@code B}. 077 * 078 * @return this instance cast to {@code B}. 079 */ 080 @SuppressWarnings("unchecked") 081 protected B asThis() { 082 return (B) this; 083 } 084 085 /** 086 * Gets the comparator to sort lists of options. 087 * 088 * @return the comparator to sort lists of options. 089 */ 090 protected Comparator<Option> getComparator() { 091 return comparator; 092 } 093 094 /** 095 * Gets {@link HelpAppendable}. 096 * 097 * @return the {@link HelpAppendable}. 098 */ 099 protected HelpAppendable getHelpAppendable() { 100 return helpAppendable; 101 } 102 103 /** 104 * Gets {@link OptionFormatter.Builder} to use to format options in the table. 105 * 106 * @return the {@link OptionFormatter.Builder} to use to format options in the table. 107 */ 108 protected OptionFormatter.Builder getOptionFormatBuilder() { 109 return optionFormatBuilder; 110 } 111 112 /** 113 * Gets string to separate option groups. 114 * 115 * @return the string to separate option groups. 116 */ 117 protected String getOptionGroupSeparator() { 118 return optionGroupSeparator; 119 } 120 121 /** 122 * Sets the comparator to use for sorting options. If set to {@code null} no sorting is performed. 123 * 124 * @param comparator The comparator to use for sorting options. 125 * @return this 126 */ 127 public B setComparator(final Comparator<Option> comparator) { 128 this.comparator = comparator; 129 return asThis(); 130 } 131 132 /** 133 * Sets the {@link HelpAppendable}. 134 * 135 * @param helpAppendable the {@link HelpAppendable} to use. 136 * @return this 137 */ 138 public B setHelpAppendable(final HelpAppendable helpAppendable) { 139 this.helpAppendable = helpAppendable != null ? helpAppendable : TextHelpAppendable.systemOut(); 140 return asThis(); 141 } 142 143 /** 144 * Sets the {@link OptionFormatter.Builder}. 145 * 146 * @param optionFormatBuilder the {@link OptionFormatter.Builder} to use. 147 * @return this 148 */ 149 public B setOptionFormatBuilder(final OptionFormatter.Builder optionFormatBuilder) { 150 this.optionFormatBuilder = optionFormatBuilder != null ? optionFormatBuilder : OptionFormatter.builder(); 151 return asThis(); 152 } 153 154 /** 155 * Sets the OptionGroup separator. Normally " | " or something similar to denote that only one option may be chosen. 156 * 157 * @param optionGroupSeparator the string to separate option group elements with. 158 * @return this 159 */ 160 public B setOptionGroupSeparator(final String optionGroupSeparator) { 161 this.optionGroupSeparator = Util.defaultValue(optionGroupSeparator, ""); 162 return asThis(); 163 } 164 165 } 166 167 /** 168 * The default comparator for {@link Option} implementations. 169 */ 170 public static final Comparator<Option> DEFAULT_COMPARATOR = (opt1, opt2) -> opt1.getKey().compareToIgnoreCase(opt2.getKey()); 171 172 /** 173 * The default separator between {@link OptionGroup} elements: {@value}. 174 */ 175 public static final String DEFAULT_OPTION_GROUP_SEPARATOR = " | "; 176 177 /** 178 * The string to display at the beginning of the usage statement: {@value}. 179 */ 180 public static final String DEFAULT_SYNTAX_PREFIX = "usage: "; 181 182 /** The comparator for sorting {@link Option} collections */ 183 private final Comparator<Option> comparator; 184 /** 185 * The {@link HelpAppendable} that produces the final output. 186 */ 187 private final HelpAppendable helpAppendable; 188 189 /** 190 * The OptionFormatter.Builder used to display options within the help page 191 */ 192 private final OptionFormatter.Builder optionFormatBuilder; 193 194 /** The separator between {@link OptionGroup} components. */ 195 private final String optionGroupSeparator; 196 197 /** 198 * The phrase printed before the syntax line. 199 */ 200 private String syntaxPrefix = DEFAULT_SYNTAX_PREFIX; 201 202 /** 203 * Constructs the base formatter. 204 * 205 * @param builder the builder 206 */ 207 protected AbstractHelpFormatter(final Builder<?, ?> builder) { 208 this.helpAppendable = Objects.requireNonNull(builder.getHelpAppendable(), "helpAppendable"); 209 this.optionFormatBuilder = Objects.requireNonNull(builder.getOptionFormatBuilder(), "optionFormatBuilder"); 210 this.comparator = Objects.requireNonNull(builder.getComparator(), "comparator"); 211 this.optionGroupSeparator = Util.defaultValue(builder.getOptionGroupSeparator(), ""); 212 } 213 214 /** 215 * Gets the comparator for sorting options. 216 * 217 * @return The comparator for sorting options. 218 */ 219 protected Comparator<Option> getComparator() { 220 return comparator; 221 } 222 223 /** 224 * Gets the help appendable. 225 * 226 * @return The help appendable. 227 */ 228 protected HelpAppendable getHelpAppendable() { 229 return helpAppendable; 230 } 231 232 /** 233 * Gets the option formatter builder. 234 * 235 * @return The option formatter builder. 236 */ 237 protected OptionFormatter.Builder getOptionFormatBuilder() { 238 return optionFormatBuilder; 239 } 240 241 /** 242 * Constructs an {@link OptionFormatter} for the specified {@link Option}. 243 * 244 * @param option The Option to format. 245 * @return an {@link OptionFormatter} for the specified {@link Option}. 246 */ 247 public final OptionFormatter getOptionFormatter(final Option option) { 248 return optionFormatBuilder.build(option); 249 } 250 251 /** 252 * Gets the option group separator. 253 * 254 * @return The option group separator. 255 */ 256 protected String getOptionGroupSeparator() { 257 return optionGroupSeparator; 258 } 259 260 /** 261 * Gets the {@link HelpAppendable} associated with this help formatter. 262 * 263 * @return The {@link HelpAppendable} associated with this help formatter. 264 */ 265 public final HelpAppendable getSerializer() { 266 return helpAppendable; 267 } 268 269 /** 270 * Gets the currently set syntax prefix. 271 * 272 * @return The currently set syntax prefix. 273 */ 274 public final String getSyntaxPrefix() { 275 return syntaxPrefix; 276 } 277 278 /** 279 * Converts a collection of {@link Option}s into a {@link TableDefinition}. 280 * 281 * @param options The options to create a table for. 282 * @return the TableDefinition. 283 */ 284 protected abstract TableDefinition getTableDefinition(Iterable<Option> options); 285 286 /** 287 * Prints the help for a collection of {@link Option}s with the specified command line syntax. 288 * 289 * @param cmdLineSyntax the syntax for this application 290 * @param header the banner to display at the beginning of the help 291 * @param options the collection of {@link Option} objects to print. 292 * @param footer the banner to display at the end of the help 293 * @param autoUsage whether to print an automatically generated usage statement 294 * @throws IOException If the output could not be written to the {@link HelpAppendable} 295 */ 296 public void printHelp(final String cmdLineSyntax, final String header, final Iterable<Option> options, final String footer, final boolean autoUsage) 297 throws IOException { 298 if (Util.isEmpty(cmdLineSyntax)) { 299 throw new IllegalArgumentException("cmdLineSyntax not provided"); 300 } 301 if (autoUsage) { 302 helpAppendable.appendParagraphFormat("%s %s %s", syntaxPrefix, cmdLineSyntax, toSyntaxOptions(options)); 303 } else { 304 helpAppendable.appendParagraphFormat("%s %s", syntaxPrefix, cmdLineSyntax); 305 } 306 if (!Util.isEmpty(header)) { 307 helpAppendable.appendParagraph(header); 308 } 309 helpAppendable.appendTable(getTableDefinition(options)); 310 if (!Util.isEmpty(footer)) { 311 helpAppendable.appendParagraph(footer); 312 } 313 } 314 315 /** 316 * Prints the help for {@link Options} with the specified command line syntax. 317 * 318 * @param cmdLineSyntax the syntax for this application 319 * @param header the banner to display at the beginning of the help 320 * @param options the {@link Options} to print 321 * @param footer the banner to display at the end of the help 322 * @param autoUsage whether to print an automatically generated usage statement 323 * @throws IOException If the output could not be written to the {@link HelpAppendable} 324 */ 325 public final void printHelp(final String cmdLineSyntax, final String header, final Options options, final String footer, final boolean autoUsage) 326 throws IOException { 327 printHelp(cmdLineSyntax, header, options.getOptions(), footer, autoUsage); 328 } 329 330 /** 331 * Prints the option table for a collection of {@link Option} objects to the {@link HelpAppendable}. 332 * 333 * @param options the collection of Option objects to print in the table. 334 * @throws IOException If the output could not be written to the {@link HelpAppendable} 335 */ 336 public final void printOptions(final Iterable<Option> options) throws IOException { 337 printOptions(getTableDefinition(options)); 338 } 339 340 /** 341 * Prints the option table for the specified {@link Options} to the {@link HelpAppendable}. 342 * 343 * @param options the Options to print in the table. 344 * @throws IOException If the output could not be written to the {@link HelpAppendable} 345 */ 346 public final void printOptions(final Options options) throws IOException { 347 printOptions(options.getOptions()); 348 } 349 350 /** 351 * Prints a {@link TableDefinition} to the {@link HelpAppendable}. 352 * 353 * @param tableDefinition the {@link TableDefinition} to print. 354 * @throws IOException If the output could not be written to the {@link HelpAppendable} 355 */ 356 public final void printOptions(final TableDefinition tableDefinition) throws IOException { 357 helpAppendable.appendTable(tableDefinition); 358 } 359 360 /** 361 * Sets the syntax prefix. This is the phrase that is printed before the syntax line. 362 * 363 * @param prefix the new value for the syntax prefix. 364 */ 365 public final void setSyntaxPrefix(final String prefix) { 366 this.syntaxPrefix = prefix; 367 } 368 369 /** 370 * Creates a new list of options ordered by the comparator. 371 * 372 * @param options the Options to sort. 373 * @return a new list of options ordered by the comparator. 374 */ 375 public List<Option> sort(final Iterable<Option> options) { 376 final List<Option> result = new ArrayList<>(); 377 if (options != null) { 378 options.forEach(result::add); 379 result.sort(comparator); 380 } 381 return result; 382 } 383 384 /** 385 * Creates a new list of options ordered by the comparator. 386 * 387 * @param options the Options to sort. 388 * @return a new list of options ordered by the comparator. 389 */ 390 public List<Option> sort(final Options options) { 391 return sort(options == null ? null : options.getOptions()); 392 } 393 394 /** 395 * Formats the {@code argName} as an argument a defined in the enclosed {@link OptionFormatter.Builder} 396 * 397 * @param argName the string to format as an argument. 398 * @return the {@code argName} formatted as an argument. 399 */ 400 public final String toArgName(final String argName) { 401 return optionFormatBuilder.toArgName(argName); 402 } 403 404 /** 405 * Return the string representation of the options as used in the syntax display. 406 * 407 * @param options The collection of {@link Option} instances to create the string representation for. 408 * @return the string representation of the options as used in the syntax display. 409 */ 410 public String toSyntaxOptions(final Iterable<Option> options) { 411 return toSyntaxOptions(options, o -> null); 412 } 413 414 /** 415 * Return the string representation of the options as used in the syntax display. 416 * 417 * @param options The options to create the string representation for. 418 * @param lookup a function to determine if the Option is part of an OptionGroup that has already been processed. 419 * @return the string representation of the options as used in the syntax display. 420 */ 421 protected String toSyntaxOptions(final Iterable<Option> options, final Function<Option, OptionGroup> lookup) { 422 // list of groups that have been processed. 423 final Collection<OptionGroup> processedGroups = new ArrayList<>(); 424 final List<Option> optList = sort(options); 425 final StringBuilder buff = new StringBuilder(); 426 String prefix = ""; 427 // iterate over the options 428 for (final Option option : optList) { 429 // get the next Option 430 // check if the option is part of an OptionGroup 431 final OptionGroup optionGroup = lookup.apply(option); 432 // if the option is part of a group 433 if (optionGroup != null) { 434 // and if the group has not already been processed 435 if (!processedGroups.contains(optionGroup)) { 436 // add the group to the processed list 437 processedGroups.add(optionGroup); 438 // add the usage clause 439 buff.append(prefix).append(toSyntaxOptions(optionGroup)); 440 prefix = " "; 441 } 442 // otherwise the option was displayed in the group previously so ignore it. 443 } 444 // if the Option is not part of an OptionGroup 445 else { 446 buff.append(prefix).append(optionFormatBuilder.build(option).toSyntaxOption()); 447 prefix = " "; 448 } 449 } 450 return buff.toString(); 451 } 452 453 /** 454 * Return the string representation of the options as used in the syntax display. 455 * 456 * @param group The OptionGroup to create the string representation for. 457 * @return the string representation of the options as used in the syntax display. 458 */ 459 public String toSyntaxOptions(final OptionGroup group) { 460 final StringBuilder buff = new StringBuilder(); 461 final List<Option> optList = sort(group.getOptions()); 462 OptionFormatter formatter = null; 463 // for each option in the OptionGroup 464 final Iterator<Option> iter = optList.iterator(); 465 while (iter.hasNext()) { 466 formatter = optionFormatBuilder.build(iter.next()); 467 // whether the option is required or not is handled at group level 468 buff.append(formatter.toSyntaxOption(true)); 469 470 if (iter.hasNext()) { 471 buff.append(optionGroupSeparator); 472 } 473 } 474 if (formatter != null) { 475 return group.isRequired() ? buff.toString() : formatter.toOptional(buff.toString()); 476 } 477 return ""; // there were no entries in the group. 478 } 479 480 /** 481 * Return the string representation of the options as used in the syntax display. 482 * 483 * @param options The {@link Options} to create the string representation for. 484 * @return the string representation of the options as used in the syntax display. 485 */ 486 public String toSyntaxOptions(final Options options) { 487 return toSyntaxOptions(options.getOptions(), options::getOptionGroup); 488 } 489}