1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.rat;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.PrintWriter;
24 import java.io.Serializable;
25 import java.util.Arrays;
26 import java.util.Collections;
27 import java.util.Comparator;
28 import java.util.Map;
29 import java.util.TreeMap;
30 import java.util.function.Consumer;
31 import java.util.function.Supplier;
32 import java.util.stream.Collectors;
33
34 import org.apache.commons.cli.CommandLine;
35 import org.apache.commons.cli.DefaultParser;
36 import org.apache.commons.cli.Option;
37 import org.apache.commons.cli.Options;
38 import org.apache.commons.cli.ParseException;
39 import org.apache.rat.api.Document;
40 import org.apache.rat.commandline.Arg;
41 import org.apache.rat.commandline.ArgumentContext;
42 import org.apache.rat.commandline.StyleSheets;
43 import org.apache.rat.config.exclusion.StandardCollection;
44 import org.apache.rat.document.DocumentName;
45 import org.apache.rat.document.DocumentNameMatcher;
46 import org.apache.rat.document.FileDocument;
47 import org.apache.rat.help.Licenses;
48 import org.apache.rat.license.LicenseSetFactory;
49 import org.apache.rat.report.IReportable;
50 import org.apache.rat.report.claim.ClaimStatistic;
51 import org.apache.rat.utils.DefaultLog;
52 import org.apache.rat.utils.Log.Level;
53 import org.apache.rat.walker.ArchiveWalker;
54 import org.apache.rat.walker.DirectoryWalker;
55
56 import static java.lang.String.format;
57
58
59
60
61
62 public final class OptionCollection {
63
64 private OptionCollection() {
65
66 }
67
68
69 public static final Comparator<Option> OPTION_COMPARATOR = new OptionComparator();
70
71
72 public static final Option HELP = new Option("?", "help", false, "Print help for the RAT command line interface and exit.");
73
74
75 public static final Option HELP_LICENSES = Option.builder().longOpt("help-licenses")
76 .desc("Print help for the RAT command line interface and exit.").build();
77
78
79 private static final Map<String, Supplier<String>> ARGUMENT_TYPES;
80 static {
81 ARGUMENT_TYPES = new TreeMap<>();
82 ARGUMENT_TYPES.put("File", () -> "A file name.");
83 ARGUMENT_TYPES.put("Integer", () -> "An integer value.");
84 ARGUMENT_TYPES.put("DirOrArchive", () -> "A directory or archive file to scan.");
85 ARGUMENT_TYPES.put("Expression", () -> "A file matching pattern usually of the form used in Ant build files and " +
86 "'.gitignore' files (see https://ant.apache.org/manual/dirtasks.html#patterns for examples). " +
87 "Regular expression patterns may be specified by surrounding the pattern with '%regex[' and ']'. " +
88 "For example '%regex[[A-Z].*]' would match files and directories that start with uppercase latin letters.");
89 ARGUMENT_TYPES.put("LicenseFilter", () -> format("A defined filter for the licenses to include. Valid values: %s.",
90 asString(LicenseSetFactory.LicenseFilter.values())));
91 ARGUMENT_TYPES.put("LogLevel", () -> format("The log level to use. Valid values %s.", asString(Level.values())));
92 ARGUMENT_TYPES.put("ProcessingType", () -> format("Specifies how to process file types. Valid values are: %s%n",
93 Arrays.stream(ReportConfiguration.Processing.values())
94 .map(v -> format("\t%s: %s", v.name(), v.desc()))
95 .collect(Collectors.joining(System.lineSeparator()))));
96 ARGUMENT_TYPES.put("StyleSheet", () -> format("Either an external xsl file or one of the internal named sheets. Internal sheets are: %s",
97 Arrays.stream(StyleSheets.values())
98 .map(v -> format("\t%s: %s", v.arg(), v.desc()))
99 .collect(Collectors.joining(System.lineSeparator()))));
100 ARGUMENT_TYPES.put("LicenseID", () -> "The ID for a license.");
101 ARGUMENT_TYPES.put("FamilyID", () -> "The ID for a license family.");
102 ARGUMENT_TYPES.put("StandardCollection", () -> format("Defines standard expression patterns (see above). Valid values are: %s%n",
103 Arrays.stream(StandardCollection.values())
104 .map(v -> format("\t%s: %s", v.name(), v.desc()))
105 .collect(Collectors.joining(System.lineSeparator()))));
106 ARGUMENT_TYPES.put("CounterPattern", () -> format("A pattern comprising one of the following prefixes followed by " +
107 "a colon and a count (e.g. %s:5). Prefixes are %n%s.", ClaimStatistic.Counter.UNAPPROVED,
108 Arrays.stream(ClaimStatistic.Counter.values())
109 .map(v -> format("\t%s: %s Default range [%s, %s]", v.name(), v.getDescription(),
110 v.getDefaultMinValue(),
111 v.getDefaultMaxValue() == -1 ? "unlimited" : v.getDefaultMaxValue()))
112 .collect(Collectors.joining(System.lineSeparator()))));
113 }
114
115
116
117
118
119 public static Map<String, Supplier<String>> getArgumentTypes() {
120 return Collections.unmodifiableMap(ARGUMENT_TYPES);
121 }
122
123
124
125
126
127
128 private static String asString(final Object[] args) {
129 return Arrays.stream(args).map(Object::toString).collect(Collectors.joining(", "));
130 }
131
132
133
134
135
136
137
138
139
140
141 public static ReportConfiguration parseCommands(final File workingDirectory, final String[] args, final Consumer<Options> helpCmd) throws IOException {
142 return parseCommands(workingDirectory, args, helpCmd, false);
143 }
144
145
146
147
148
149
150
151
152
153
154
155 public static ReportConfiguration parseCommands(final File workingDirectory, final String[] args,
156 final Consumer<Options> helpCmd, final boolean noArgs) throws IOException {
157 Options opts = buildOptions();
158 CommandLine commandLine;
159 try {
160 commandLine = DefaultParser.builder().setDeprecatedHandler(DeprecationReporter.getLogReporter())
161 .setAllowPartialMatching(true).build().parse(opts, args);
162 } catch (ParseException e) {
163 DefaultLog.getInstance().error(e.getMessage());
164 DefaultLog.getInstance().error("Please use the \"--help\" option to see a list of valid commands and options.", e);
165 System.exit(1);
166 return null;
167
168 }
169
170 Arg.processLogLevel(commandLine);
171
172 ArgumentContext argumentContext = new ArgumentContext(workingDirectory, commandLine);
173 if (commandLine.hasOption(HELP)) {
174 helpCmd.accept(opts);
175 return null;
176 }
177
178 if (commandLine.hasOption(HELP_LICENSES)) {
179 new Licenses(createConfiguration(argumentContext), new PrintWriter(System.out)).printHelp();
180 return null;
181 }
182
183 if (commandLine.hasOption(Arg.HELP_LICENSES.option())) {
184 new Licenses(createConfiguration(argumentContext), new PrintWriter(System.out)).printHelp();
185 return null;
186 }
187
188 ReportConfiguration configuration = createConfiguration(argumentContext);
189 if (!noArgs && !configuration.hasSource()) {
190 String msg = "No directories or files specified for scanning. Did you forget to close a multi-argument option?";
191 DefaultLog.getInstance().error(msg);
192 helpCmd.accept(opts);
193 throw new ConfigurationException(msg);
194 }
195
196 return configuration;
197 }
198
199
200
201
202
203
204
205
206
207
208 static ReportConfiguration createConfiguration(final ArgumentContext argumentContext) {
209 argumentContext.processArgs();
210 final ReportConfiguration configuration = argumentContext.getConfiguration();
211 final CommandLine commandLine = argumentContext.getCommandLine();
212 if (Arg.DIR.isSelected()) {
213 try {
214 configuration.addSource(getReportable(commandLine.getParsedOptionValue(Arg.DIR.getSelected()), configuration));
215 } catch (ParseException e) {
216 throw new ConfigurationException("Unable to set parse " + Arg.DIR.getSelected(), e);
217 }
218 }
219 for (String s : commandLine.getArgs()) {
220 IReportable reportable = getReportable(new File(s), configuration);
221 if (reportable != null) {
222 configuration.addSource(reportable);
223 }
224 }
225 return configuration;
226 }
227
228
229
230
231
232
233 public static Options buildOptions() {
234 return Arg.getOptions().addOption(HELP);
235 }
236
237
238
239
240
241
242
243
244
245 static IReportable getReportable(final File base, final ReportConfiguration config) {
246 File absBase = base.getAbsoluteFile();
247 DocumentName documentName = DocumentName.builder(absBase).build();
248 if (!absBase.exists()) {
249 DefaultLog.getInstance().error("Directory '" + documentName + "' does not exist.");
250 return null;
251 }
252 DocumentNameMatcher documentExcluder = config.getDocumentExcluder(documentName);
253
254 Document doc = new FileDocument(documentName, absBase, documentExcluder);
255 if (!documentExcluder.matches(doc.getName())) {
256 DefaultLog.getInstance().error("Directory '" + documentName + "' is in excluded list.");
257 return null;
258 }
259
260 if (absBase.isDirectory()) {
261 return new DirectoryWalker(doc);
262 }
263
264 return new ArchiveWalker(doc);
265 }
266
267
268
269
270 private static class OptionComparator implements Comparator<Option>, Serializable {
271
272 private static final long serialVersionUID = 5305467873966684014L;
273
274 private String getKey(final Option opt) {
275 String key = opt.getOpt();
276 key = key == null ? opt.getLongOpt() : key;
277 return key;
278 }
279
280
281
282
283
284
285
286
287
288
289
290 @Override
291 public int compare(final Option opt1, final Option opt2) {
292 return getKey(opt1).compareToIgnoreCase(getKey(opt2));
293 }
294 }
295 }