ottlinger commented on code in PR #257: URL: https://github.com/apache/creadur-rat/pull/257#discussion_r1614864730
########## apache-rat-core/src/main/java/org/apache/rat/OptionCollection.java: ########## @@ -0,0 +1,638 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + */ +package org.apache.rat; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Converter; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.DeprecatedAttributes; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.OptionGroup; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.filefilter.FalseFileFilter; +import org.apache.commons.io.filefilter.IOFileFilter; +import org.apache.commons.io.filefilter.NameFileFilter; +import org.apache.commons.io.filefilter.OrFileFilter; +import org.apache.commons.io.filefilter.RegexFileFilter; +import org.apache.commons.io.filefilter.WildcardFileFilter; +import org.apache.commons.io.function.IOSupplier; +import org.apache.commons.lang3.StringUtils; +import org.apache.rat.api.Document; +import org.apache.rat.config.AddLicenseHeaders; +import org.apache.rat.document.impl.FileDocument; +import org.apache.rat.license.LicenseSetFactory; +import org.apache.rat.report.IReportable; +import org.apache.rat.utils.DefaultLog; +import org.apache.rat.utils.Log; +import org.apache.rat.walker.ArchiveWalker; +import org.apache.rat.walker.DirectoryWalker; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.TreeMap; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.regex.PatternSyntaxException; +import java.util.stream.Collectors; + +import static java.lang.String.format; + +/** + * The collection of standard options for the CLI as well as utility methods to manage them and methods to create the + * ReportConfiguration from the options and an array of arguments. + */ +public class OptionCollection { + + /* + START OF OPTION LIST + */ + + /** + * Adds license headers to files missing headers. + */ + public static final Option ADD_LICENSE = new Option("A", "addLicense", false, + "Add the default license header to any file with an unknown license that is not in the exclusion list. " + + "By default new files will be created with the license header, " + + "to force the modification of existing files use the --force option."); + /** + * Defines the output for the file. + * + * @since 0.16 + */ + public static final Option OUT = Option.builder().option("o").longOpt("out").hasArg() + .desc("Define the output file where to write a report to (default is System.out).") + .type(File.class) + .converter(Converter.FILE).build(); + /** + * Name of File to exclude from report consideration. + */ + public static final Option EXCLUDE_CLI = Option.builder("e").longOpt("exclude").hasArgs().argName("Expression") + .desc("Excludes files matching wildcard <Expression>. May be followed by multiple arguments. " + + "Note that '--' or a following option is required when using this parameter.") + .build(); + /** + * Name of file that contains a list of files to exclude from report + * consideration. + */ + public static final Option EXCLUDE_FILE_CLI = Option.builder("E").longOpt("exclude-file") + .argName("FileOrURI") + .hasArg().desc("Excludes files matching regular expression in the input file.") + .build(); + /** + * The stylesheet to use to style the XML output. + */ + public static final Option STYLESHEET_CLI = Option.builder("s").longOpt("stylesheet").hasArg().argName("StyleSheet") + .desc("XSLT stylesheet to use when creating the report. Not compatible with -x. " + + "Either an external xsl file may be specified or one of the internal named sheets: plain-rat (default), missing-headers, or unapproved-licenses") + .build(); + /** + * Produce help + */ + public static final Option HELP = new Option("h", "help", false, "Print help for the RAT command line interface and exit."); + /** + * Flag to identify a file with license definitions. + * + * @since 0.16 + */ + public static final Option LICENSES = Option.builder().longOpt("licenses").hasArgs().argName("FileOrURI") + .desc("File names or URLs for license definitions. May be followed by multiple arguments. " + + "Note that '--' or a following option is required when using this parameter.") + .build(); + /** + * Do not use the default files. + * @since 0.16 + */ + public static final Option NO_DEFAULTS = new Option(null, "no-default-licenses", false, "Ignore default configuration. By default all approved default licenses are used"); + + /** + * Scan hidden directories. + */ + public static final Option SCAN_HIDDEN_DIRECTORIES = new Option(null, "scan-hidden-directories", false, "Scan hidden directories"); + + /** + * List the licenses that were used for the run. + * @since 0.16 + */ + public static final Option LIST_LICENSES = Option.builder().longOpt("list-licenses").hasArg().argName("LicenseFilter") + .desc("List the defined licenses (default is NONE). Valid options are: " + asString(LicenseSetFactory.LicenseFilter.values())) + .converter(s -> LicenseSetFactory.LicenseFilter.valueOf(s.toUpperCase())) + .build(); + + /** + * List the all families for the run. + * @since 0.16 + */ + public static final Option LIST_FAMILIES = Option.builder().longOpt("list-families").hasArg().argName("LicenseFilter") + .desc("List the defined license families (default is NONE). Valid options are: " + asString(LicenseSetFactory.LicenseFilter.values())) + .converter(s -> LicenseSetFactory.LicenseFilter.valueOf(s.toUpperCase())) + .build(); + + /** + * Specify the log level for output + * @since 0.16 + */ + public static final Option LOG_LEVEL = Option.builder().longOpt("log-level") + .hasArg().argName("LogLevel") + .desc("sets the log level.") + .converter(s -> Log.Level.valueOf(s.toUpperCase())) + .build(); + + /** + * Do not update files. + * @since 0.16 + */ + public static final Option DRY_RUN = Option.builder().longOpt("dry-run") + .desc("If set do not update the files but generate the reports.") + .build(); + /** + * Set unstyled XML output + */ + public static final Option XML = new Option("x", "xml", false, "Output the report in raw XML format. Not compatible with -s"); + + /** + * Specify the processing of ARCHIVE files. + * @since 0.17 + */ + public static final Option ARCHIVE = Option.builder().longOpt("archive").hasArg().argName("ProcessingType") + .desc(format("Specifies the level of detail in ARCHIVE file reporting. (default is %s)", + ReportConfiguration.Processing.NOTIFICATION)) + .converter(s -> ReportConfiguration.Processing.valueOf(s.toUpperCase())) + .build(); + + /** + * Specify the processing of STANDARD files. + */ + public static final Option STANDARD = Option.builder().longOpt("standard").hasArg().argName("ProcessingType") + .desc(format("Specifies the level of detail in STANDARD file reporting. (default is %s)", + Defaults.STANDARD_PROCESSING)) + .converter(s -> ReportConfiguration.Processing.valueOf(s.toUpperCase())) + .build(); + + /** + * Ths option to signal the end of an argument list and the start of the directory/archive arguments. + */ + // TODO rework when commons-cli 1.7.1 or higher is available. + static final DeprecatedAttributes DIR_ATTRIBUTES = DeprecatedAttributes.builder().setForRemoval(true).setSince("0.17") + .setDescription("Use '--'").get(); + public static final Option DIR = Option.builder().option("d").longOpt("dir").hasArg() + .desc(format("[%s] %s", DIR_ATTRIBUTES, "Used to indicate end of list when using --exclude.")).argName("DirOrArchive") + .deprecated(DIR_ATTRIBUTES).build(); + + /** + * Option to signal that license text should be added to the files. + */ + // TODO rework when Commons-CLI version 1.7.1 or higher is available. + private static final DeprecatedAttributes ADD_ATTRIBUTES = DeprecatedAttributes.builder().setForRemoval(true).setSince("0.17") + .setDescription("Use '-A' or '--addLicense' instead.").get(); + static final OptionGroup ADD = new OptionGroup() + .addOption(Option.builder("a").hasArg(false) + .desc(format("[%s]", ADD_ATTRIBUTES)) + .deprecated(ADD_ATTRIBUTES) + .build()) + .addOption(ADD_LICENSE); + + /** + * Defines the copyright header to add to the file. + */ + public static final Option COPYRIGHT = Option.builder().option("c").longOpt("copyright").hasArg() + .desc(format("The copyright message to use in the license headers, usually in the form of \"Copyright 2008 Foo\". Only valid with --%s", + ADD.getOptions().stream().filter(o -> !o.isDeprecated()).findAny().get().getLongOpt())) + .build(); + + /** + * Forces changes to be written to new files. + */ + public static final Option FORCE = new Option("f", "force", false, + format("Forces any changes in files to be written directly to the source files (i.e. new files are not created). Only valid with --%s", + ADD.getOptions().stream().filter(o -> !o.isDeprecated()).findAny().get().getLongOpt())); + + + /* + END OF OPTION LIST + */ + + /** + * The enumeration of system defined stylesheets. + */ + private enum StyleSheets { + PLAIN("plain-rat", "The default style"), + MISSING_HEADERS("missing-headers", "Produces a report of files that are missing headers"), + UNAPPROVED_LICENSES("unapproved-licenses", "Produces a report of the files with unapproved licenses"); + private final String arg; + private final String desc; + + StyleSheets(final String arg, final String description) { + this.arg = arg; + this.desc = description; + } + + public String arg() { + return arg; + } + + public String desc() { + return desc; + } + } + + /** + * A mapping of {@code argName(value)} values to a description of those values. + */ + private static final Map<String, Supplier<String>> ARGUMENT_TYPES; + static { + ARGUMENT_TYPES = new TreeMap<>(); + ARGUMENT_TYPES.put("FileOrURI", () -> "A file name or URI"); + ARGUMENT_TYPES.put("DirOrArchive", () -> "A directory or archive file to scan"); + ARGUMENT_TYPES.put("Expression", () -> "A wildcard file matching pattern. example: *-test-*.txt"); + ARGUMENT_TYPES.put("LicenseFilter", () -> format("A defined filter for the licenses to include. Valid values: %s.", + asString(LicenseSetFactory.LicenseFilter.values()))); + ARGUMENT_TYPES.put("LogLevel", () -> format("The log level to use. Valid values %s.", asString(Log.Level.values()))); + ARGUMENT_TYPES.put("ProcessingType", () -> format("Specifies how to process file types. Valid values are: %s", + Arrays.stream(ReportConfiguration.Processing.values()) + .map(v -> format("\t%s: %s", v.name(), v.desc())) + .collect(Collectors.joining("")))); + ARGUMENT_TYPES.put("StyleSheet", () -> format("Either an external xsl file or maybe one of the internal named sheets. Internal sheets are: %s.", + Arrays.stream(StyleSheets.values()) + .map(v -> format("\t%s: %s", v.arg(), v.desc())) + .collect(Collectors.joining("")))); + } + + /** + * Gets the mapping of {@code argName(value)} values to a description of those values. + * @return the mapping of {@code argName(value)} values to a description of those values. + */ + public static Map<String, Supplier<String>> getArgumentTypes() { + return Collections.unmodifiableMap(ARGUMENT_TYPES); + } + + /** + * Join a collection of objects together as a comma separated list of their string values. + * @param args the objects to join together. + * @return the comma separated string. + */ + private static String asString(final Object[] args) { + return Arrays.stream(args).map(Object::toString).collect(Collectors.joining(", ")); + } + + /** + * Parses the standard options to create a ReportConfiguration. + * + * @param args the arguments to parse + * @param helpCmd the help command to run when necessary. + * @return a ReportConfiguration or null if Help was printed. + * @throws IOException on error. + */ + public static ReportConfiguration parseCommands(final String[] args, final Consumer<Options> helpCmd) throws IOException { + return parseCommands(args, helpCmd, false); + } + + /** + * Parses the standard options to create a ReportConfiguration. + * + * @param args the arguments to parse + * @param helpCmd the help command to run when necessary. + * @param noArgs If true then the commands do not need extra arguments + * @return a ReportConfiguration or null if Help was printed. + * @throws IOException on error. + */ + public static ReportConfiguration parseCommands(final String[] args, final Consumer<Options> helpCmd, final boolean noArgs) throws IOException { + Options opts = buildOptions(); + CommandLine cl; + Log log = DefaultLog.getInstance(); + try { + cl = DefaultParser.builder().setDeprecatedHandler(DeprecationReporter.getLogReporter(log)).build().parse(opts, args); + } catch (ParseException e) { + log.error(e.getMessage()); + log.error("Please use the \"--help\" option to see a list of valid commands and options"); + System.exit(1); + return null; // dummy return (won't be reached) to avoid Eclipse complaint about possible NPE + // for "cl" + } + + if (cl.hasOption(LOG_LEVEL)) { + DeprecationReporter.logDeprecated(log, LOG_LEVEL); + if (log instanceof DefaultLog) { + DefaultLog dLog = (DefaultLog) log; + try { + dLog.setLevel(cl.getParsedOptionValue(LOG_LEVEL)); + } catch (ParseException e) { + logParseException(log, e, LOG_LEVEL, cl, dLog.getLevel()); + } + } else { + log.error("log was not a DefaultLog instance. LogLevel not set."); Review Comment: typo: Two spaces in Log message -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: dev-unsubscr...@creadur.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org