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 {@code 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 {@link Options} 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 Options optionsObject = new Options(); 299 options.forEach(optionsObject::addOption); 300 printHelp(cmdLineSyntax, header, optionsObject, footer, autoUsage); 301 } 302 303 /** 304 * Prints the help for a collection of {@link Option}s with the specified command line syntax. 305 * 306 * @param cmdLineSyntax the syntax for this application. 307 * @param header the banner to display at the beginning of the help. 308 * @param options the collection of {@link Option} objects to print. 309 * @param footer the banner to display at the end of the help. 310 * @param autoUsage whether to print an automatically generated usage statement. 311 * @throws IOException If the output could not be written to the {@link HelpAppendable}. 312 */ 313 public void printHelp(final String cmdLineSyntax, final String header, final Options options, final String footer, final boolean autoUsage) 314 throws IOException { 315 if (Util.isEmpty(cmdLineSyntax)) { 316 throw new IllegalArgumentException("cmdLineSyntax not provided"); 317 } 318 if (autoUsage) { 319 helpAppendable.appendParagraphFormat("%s %s %s", syntaxPrefix, cmdLineSyntax, toSyntaxOptions(options)); 320 } else { 321 helpAppendable.appendParagraphFormat("%s %s", syntaxPrefix, cmdLineSyntax); 322 } 323 if (!Util.isEmpty(header)) { 324 helpAppendable.appendParagraph(header); 325 } 326 helpAppendable.appendTable(getTableDefinition(options.getOptions())); 327 if (!Util.isEmpty(footer)) { 328 helpAppendable.appendParagraph(footer); 329 } 330 } 331 332 /** 333 * Prints the option table for a collection of {@link Option} objects to the {@link HelpAppendable}. 334 * 335 * @param options the collection of Option objects to print in the table. 336 * @throws IOException If the output could not be written to the {@link HelpAppendable}. 337 */ 338 public final void printOptions(final Iterable<Option> options) throws IOException { 339 printOptions(getTableDefinition(options)); 340 } 341 342 /** 343 * Prints the option table for the specified {@link Options} to the {@link HelpAppendable}. 344 * 345 * @param options the Options to print in the table. 346 * @throws IOException If the output could not be written to the {@link HelpAppendable}. 347 */ 348 public final void printOptions(final Options options) throws IOException { 349 printOptions(options.getOptions()); 350 } 351 352 /** 353 * Prints a {@link TableDefinition} to the {@link HelpAppendable}. 354 * 355 * @param tableDefinition the {@link TableDefinition} to print. 356 * @throws IOException If the output could not be written to the {@link HelpAppendable}. 357 */ 358 public final void printOptions(final TableDefinition tableDefinition) throws IOException { 359 helpAppendable.appendTable(tableDefinition); 360 } 361 362 /** 363 * Sets the syntax prefix. This is the phrase that is printed before the syntax line. 364 * 365 * @param prefix the new value for the syntax prefix. 366 */ 367 public final void setSyntaxPrefix(final String prefix) { 368 this.syntaxPrefix = prefix; 369 } 370 371 /** 372 * Creates a new list of options ordered by the comparator. 373 * 374 * @param options the Options to sort. 375 * @return a new list of options ordered by the comparator. 376 */ 377 public List<Option> sort(final Iterable<Option> options) { 378 final List<Option> result = new ArrayList<>(); 379 if (options != null) { 380 options.forEach(result::add); 381 result.sort(comparator); 382 } 383 return result; 384 } 385 386 /** 387 * Creates a new list of options ordered by the comparator. 388 * 389 * @param options the Options to sort. 390 * @return a new list of options ordered by the comparator. 391 */ 392 public List<Option> sort(final Options options) { 393 return sort(options == null ? null : options.getOptions()); 394 } 395 396 /** 397 * Formats the {@code argName} as an argument a defined in the enclosed {@link OptionFormatter.Builder}. 398 * 399 * @param argName the string to format as an argument. 400 * @return the {@code argName} formatted as an argument. 401 */ 402 public final String toArgName(final String argName) { 403 return optionFormatBuilder.toArgName(argName); 404 } 405 406 /** 407 * Return the string representation of the options as used in the syntax display. 408 * <p> 409 * This is probably not the method you want. This method does not track the presence 410 * of option groups. To display the option grouping use {@link #toSyntaxOptions(Options)} or 411 * {@link #toSyntaxOptions(OptionGroup)} for individual groups. 412 * </p> 413 * @param options The collection of {@link Option} instances to create the string representation for. 414 * @return the string representation of the options as used in the syntax display. 415 */ 416 public String toSyntaxOptions(final Iterable<Option> options) { 417 return toSyntaxOptions(options, o -> null); 418 } 419 420 /** 421 * Return the string representation of the options as used in the syntax display. 422 * 423 * @param options The options to create the string representation for. 424 * @param lookup a function to determine if the Option is part of an OptionGroup that has already been processed. 425 * @return the string representation of the options as used in the syntax display. 426 */ 427 protected String toSyntaxOptions(final Iterable<Option> options, final Function<Option, OptionGroup> lookup) { 428 // list of groups that have been processed. 429 final Collection<OptionGroup> processedGroups = new ArrayList<>(); 430 final List<Option> optList = sort(options); 431 final StringBuilder buff = new StringBuilder(); 432 String prefix = ""; 433 // iterate over the options 434 for (final Option option : optList) { 435 // get the next Option 436 // check if the option is part of an OptionGroup 437 final OptionGroup optionGroup = lookup.apply(option); 438 // if the option is part of a group 439 if (optionGroup != null) { 440 // and if the group has not already been processed 441 if (!processedGroups.contains(optionGroup)) { 442 // add the group to the processed list 443 processedGroups.add(optionGroup); 444 // add the usage clause 445 buff.append(prefix).append(toSyntaxOptions(optionGroup)); 446 prefix = " "; 447 } 448 // otherwise the option was displayed in the group previously so ignore it. 449 } 450 // if the Option is not part of an OptionGroup 451 else { 452 buff.append(prefix).append(optionFormatBuilder.build(option).toSyntaxOption()); 453 prefix = " "; 454 } 455 } 456 return buff.toString(); 457 } 458 459 /** 460 * Return the string representation of the options as used in the syntax display. 461 * 462 * @param group The OptionGroup to create the string representation for. 463 * @return the string representation of the options as used in the syntax display. 464 */ 465 public String toSyntaxOptions(final OptionGroup group) { 466 final StringBuilder buff = new StringBuilder(); 467 final List<Option> optList = sort(group.getOptions()); 468 OptionFormatter formatter = null; 469 // for each option in the OptionGroup 470 final Iterator<Option> iter = optList.iterator(); 471 while (iter.hasNext()) { 472 formatter = optionFormatBuilder.build(iter.next()); 473 // whether the option is required or not is handled at group level 474 buff.append(formatter.toSyntaxOption(true)); 475 if (iter.hasNext()) { 476 buff.append(optionGroupSeparator); 477 } 478 } 479 if (formatter != null) { 480 return group.isRequired() ? buff.toString() : formatter.toOptional(buff.toString()); 481 } 482 return ""; // there were no entries in the group. 483 } 484 485 /** 486 * Return the string representation of the options as used in the syntax display. 487 * 488 * @param options The {@link Options} to create the string representation for. 489 * @return the string representation of the options as used in the syntax display. 490 */ 491 public String toSyntaxOptions(final Options options) { 492 return toSyntaxOptions(options.getOptions(), options::getOptionGroup); 493 } 494}