1 | // License: GPL. For details, see LICENSE file.
|
---|
2 | package org.openstreetmap.josm.data.validation;
|
---|
3 |
|
---|
4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
5 | import static org.openstreetmap.josm.tools.I18n.trn;
|
---|
6 |
|
---|
7 | import java.io.File;
|
---|
8 | import java.io.IOException;
|
---|
9 | import java.io.InputStream;
|
---|
10 | import java.io.OutputStream;
|
---|
11 | import java.nio.charset.StandardCharsets;
|
---|
12 | import java.nio.file.Files;
|
---|
13 | import java.nio.file.Paths;
|
---|
14 | import java.util.ArrayList;
|
---|
15 | import java.util.Arrays;
|
---|
16 | import java.util.Collection;
|
---|
17 | import java.util.Collections;
|
---|
18 | import java.util.HashMap;
|
---|
19 | import java.util.List;
|
---|
20 | import java.util.Locale;
|
---|
21 | import java.util.Map;
|
---|
22 | import java.util.Optional;
|
---|
23 | import java.util.concurrent.atomic.AtomicReference;
|
---|
24 | import java.util.function.Supplier;
|
---|
25 | import java.util.logging.Level;
|
---|
26 | import java.util.stream.Collectors;
|
---|
27 |
|
---|
28 | import org.apache.commons.compress.utils.FileNameUtils;
|
---|
29 | import org.openstreetmap.josm.actions.ExtensionFileFilter;
|
---|
30 | import org.openstreetmap.josm.cli.CLIModule;
|
---|
31 | import org.openstreetmap.josm.data.Preferences;
|
---|
32 | import org.openstreetmap.josm.data.osm.DataSet;
|
---|
33 | import org.openstreetmap.josm.data.preferences.JosmBaseDirectories;
|
---|
34 | import org.openstreetmap.josm.data.preferences.JosmUrls;
|
---|
35 | import org.openstreetmap.josm.data.projection.ProjectionRegistry;
|
---|
36 | import org.openstreetmap.josm.data.projection.Projections;
|
---|
37 | import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker;
|
---|
38 | import org.openstreetmap.josm.gui.MainApplication;
|
---|
39 | import org.openstreetmap.josm.gui.io.CustomConfigurator;
|
---|
40 | import org.openstreetmap.josm.gui.io.importexport.FileImporter;
|
---|
41 | import org.openstreetmap.josm.gui.layer.OsmDataLayer;
|
---|
42 | import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
|
---|
43 | import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
|
---|
44 | import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException;
|
---|
45 | import org.openstreetmap.josm.gui.progress.CLIProgressMonitor;
|
---|
46 | import org.openstreetmap.josm.gui.progress.ProgressMonitor;
|
---|
47 | import org.openstreetmap.josm.io.Compression;
|
---|
48 | import org.openstreetmap.josm.io.GeoJSONMapRouletteWriter;
|
---|
49 | import org.openstreetmap.josm.io.IllegalDataException;
|
---|
50 | import org.openstreetmap.josm.io.OsmChangeReader;
|
---|
51 | import org.openstreetmap.josm.spi.lifecycle.Lifecycle;
|
---|
52 | import org.openstreetmap.josm.spi.preferences.Config;
|
---|
53 | import org.openstreetmap.josm.spi.preferences.IPreferences;
|
---|
54 | import org.openstreetmap.josm.spi.preferences.MemoryPreferences;
|
---|
55 | import org.openstreetmap.josm.tools.Http1Client;
|
---|
56 | import org.openstreetmap.josm.tools.HttpClient;
|
---|
57 | import org.openstreetmap.josm.tools.I18n;
|
---|
58 | import org.openstreetmap.josm.tools.JosmRuntimeException;
|
---|
59 | import org.openstreetmap.josm.tools.Logging;
|
---|
60 | import org.openstreetmap.josm.tools.OptionParser;
|
---|
61 | import org.openstreetmap.josm.tools.Stopwatch;
|
---|
62 | import org.openstreetmap.josm.tools.Territories;
|
---|
63 | import org.openstreetmap.josm.tools.Utils;
|
---|
64 |
|
---|
65 | /**
|
---|
66 | * Add a validate command to the JOSM command line interface.
|
---|
67 | * @author Taylor Smock
|
---|
68 | * @since 18365
|
---|
69 | */
|
---|
70 | public class ValidatorCLI implements CLIModule {
|
---|
71 | /**
|
---|
72 | * The unique instance.
|
---|
73 | */
|
---|
74 | public static final ValidatorCLI INSTANCE = new ValidatorCLI();
|
---|
75 |
|
---|
76 | /** The input file(s) */
|
---|
77 | private final List<String> input = new ArrayList<>();
|
---|
78 | /** The change files. input file -> list of change files */
|
---|
79 | private final Map<String, List<String>> changeFiles = new HashMap<>();
|
---|
80 | /** The output file(s). If {@code null}, use input filename as base (replace extension with geojson). input -> output */
|
---|
81 | private final Map<String, String> output = new HashMap<>();
|
---|
82 |
|
---|
83 | private static final Supplier<ProgressMonitor> progressMonitorFactory = CLIProgressMonitor::new;
|
---|
84 |
|
---|
85 | /** The log level */
|
---|
86 | private Level logLevel;
|
---|
87 |
|
---|
88 | private enum Option {
|
---|
89 | /** --help Show the help for validate */
|
---|
90 | HELP(false, 'h'),
|
---|
91 | /** --input=<input-file> Set the current input file */
|
---|
92 | INPUT(true, 'i', OptionParser.OptionCount.MULTIPLE),
|
---|
93 | /** --output=<output-file> Set the output file for the current input file */
|
---|
94 | OUTPUT(true, 'o', OptionParser.OptionCount.MULTIPLE),
|
---|
95 | /** --change-file=<change-file> Add a change file */
|
---|
96 | CHANGE_FILE(true, 'c', OptionParser.OptionCount.MULTIPLE),
|
---|
97 | /** --debug Set logging level to debug */
|
---|
98 | DEBUG(false, '*'),
|
---|
99 | /** --trace Set logging level to trace */
|
---|
100 | TRACE(false, '*'),
|
---|
101 | /** --language=<language> Set the language */
|
---|
102 | LANGUAGE(true, 'l'),
|
---|
103 | /** --load-preferences=<url-to-xml> Changes preferences according to the XML file */
|
---|
104 | LOAD_PREFERENCES(true, 'p'),
|
---|
105 | /** --set=<key>=<value> Set preference key to value */
|
---|
106 | SET(true, 's');
|
---|
107 |
|
---|
108 | private final String name;
|
---|
109 | private final boolean requiresArgument;
|
---|
110 | private final char shortOption;
|
---|
111 | private final OptionParser.OptionCount optionCount;
|
---|
112 | Option(final boolean requiresArgument, final char shortOption) {
|
---|
113 | this(requiresArgument, shortOption, OptionParser.OptionCount.OPTIONAL);
|
---|
114 | }
|
---|
115 |
|
---|
116 | Option(final boolean requiresArgument, final char shortOption, final OptionParser.OptionCount optionCount) {
|
---|
117 | this.name = name().toLowerCase(Locale.ROOT).replace('_', '-');
|
---|
118 | this.requiresArgument = requiresArgument;
|
---|
119 | this.shortOption = shortOption;
|
---|
120 | this.optionCount = optionCount;
|
---|
121 | }
|
---|
122 |
|
---|
123 | /**
|
---|
124 | * Replies the option name
|
---|
125 | * @return The option name, in lowercase
|
---|
126 | */
|
---|
127 | public String getName() {
|
---|
128 | return this.name;
|
---|
129 | }
|
---|
130 |
|
---|
131 | /**
|
---|
132 | * Get the number of times this option should be seen
|
---|
133 | * @return The option count
|
---|
134 | */
|
---|
135 | public OptionParser.OptionCount getOptionCount() {
|
---|
136 | return this.optionCount;
|
---|
137 | }
|
---|
138 |
|
---|
139 | /**
|
---|
140 | * Replies the short option (single letter) associated with this option.
|
---|
141 | * @return the short option or '*' if there is no short option
|
---|
142 | */
|
---|
143 | public char getShortOption() {
|
---|
144 | return this.shortOption;
|
---|
145 | }
|
---|
146 |
|
---|
147 | /**
|
---|
148 | * Determines if this option requires an argument.
|
---|
149 | * @return {@code true} if this option requires an argument, {@code false} otherwise
|
---|
150 | */
|
---|
151 | public boolean requiresArgument() {
|
---|
152 | return this.requiresArgument;
|
---|
153 | }
|
---|
154 |
|
---|
155 | }
|
---|
156 |
|
---|
157 | @Override
|
---|
158 | public String getActionKeyword() {
|
---|
159 | return "validate";
|
---|
160 | }
|
---|
161 |
|
---|
162 | @Override
|
---|
163 | public void processArguments(final String[] argArray) {
|
---|
164 | try {
|
---|
165 | // Ensure that preferences are only in memory
|
---|
166 | Config.setPreferencesInstance(new MemoryPreferences());
|
---|
167 | Logging.setLogLevel(Level.INFO);
|
---|
168 | this.parseArguments(argArray);
|
---|
169 | if (this.input.isEmpty()) {
|
---|
170 | throw new IllegalArgumentException(tr("Missing argument - input data file ({0})", "--input|-i"));
|
---|
171 | }
|
---|
172 | this.initialize();
|
---|
173 | final ProgressMonitor fileMonitor = progressMonitorFactory.get();
|
---|
174 | fileMonitor.beginTask(tr("Processing files..."), this.input.size());
|
---|
175 | for (String inputFile : this.input) {
|
---|
176 | if (inputFile.endsWith(".validator.mapcss")) {
|
---|
177 | this.processValidatorFile(inputFile);
|
---|
178 | } else if (inputFile.endsWith(".mapcss")) {
|
---|
179 | this.processMapcssFile(inputFile);
|
---|
180 | } else {
|
---|
181 | this.processFile(inputFile);
|
---|
182 | }
|
---|
183 | fileMonitor.worked(1);
|
---|
184 | }
|
---|
185 | fileMonitor.finishTask();
|
---|
186 | } catch (Exception e) {
|
---|
187 | Logging.info(e);
|
---|
188 | Lifecycle.exitJosm(true, 1);
|
---|
189 | }
|
---|
190 | Lifecycle.exitJosm(true, 0);
|
---|
191 | }
|
---|
192 |
|
---|
193 | /**
|
---|
194 | * Process a standard mapcss file
|
---|
195 | * @param inputFile The mapcss file to validate
|
---|
196 | * @throws ParseException if the file does not match the mapcss syntax
|
---|
197 | */
|
---|
198 | private void processMapcssFile(final String inputFile) throws ParseException {
|
---|
199 | final MapCSSStyleSource styleSource = new MapCSSStyleSource(new File(inputFile).toURI().getPath(), inputFile, inputFile);
|
---|
200 | styleSource.loadStyleSource();
|
---|
201 | if (!styleSource.getErrors().isEmpty()) {
|
---|
202 | throw new ParseException(trn("{0} had {1} error", "{0} had {1} errors", styleSource.getErrors().size(),
|
---|
203 | inputFile, styleSource.getErrors().size()));
|
---|
204 | } else {
|
---|
205 | Logging.info(tr("{0} had no errors", inputFile));
|
---|
206 | }
|
---|
207 | }
|
---|
208 |
|
---|
209 | /**
|
---|
210 | * Process a validator file
|
---|
211 | * @param inputFile The file to check
|
---|
212 | * @throws IOException if there is a problem reading the file
|
---|
213 | * @throws ParseException if the file does not match the validator mapcss syntax
|
---|
214 | */
|
---|
215 | private void processValidatorFile(final String inputFile) throws ParseException, IOException {
|
---|
216 | // Check asserts
|
---|
217 | Config.getPref().putBoolean("validator.check_assert_local_rules", true);
|
---|
218 | final MapCSSTagChecker mapCSSTagChecker = new MapCSSTagChecker();
|
---|
219 | final Collection<String> assertionErrors = new ArrayList<>();
|
---|
220 | final MapCSSTagChecker.ParseResult result = mapCSSTagChecker.addMapCSS(new File(inputFile).toURI().getPath(),
|
---|
221 | assertionErrors::add);
|
---|
222 | if (!result.parseErrors.isEmpty() || !assertionErrors.isEmpty()) {
|
---|
223 | for (Throwable throwable : result.parseErrors) {
|
---|
224 | Logging.error(throwable);
|
---|
225 | }
|
---|
226 | for (String error : assertionErrors) {
|
---|
227 | Logging.error(error);
|
---|
228 | }
|
---|
229 | throw new ParseException(trn("{0} had {1} error", "{0} had {1} errors", result.parseErrors.size() + assertionErrors.size(),
|
---|
230 | inputFile, result.parseErrors.size() + assertionErrors.size()));
|
---|
231 | } else {
|
---|
232 | Logging.info(tr("{0} had no errors"), inputFile);
|
---|
233 | }
|
---|
234 | }
|
---|
235 |
|
---|
236 | /**
|
---|
237 | * Process an OSM file
|
---|
238 | * @param inputFile The input filename
|
---|
239 | * @throws IllegalArgumentException If an argument is not valid
|
---|
240 | * @throws IllegalDataException If there is bad data
|
---|
241 | * @throws IOException If a file could not be read or written
|
---|
242 | */
|
---|
243 | private void processFile(final String inputFile) throws IllegalDataException, IOException {
|
---|
244 | final File inputFileFile = new File(inputFile);
|
---|
245 | final List<FileImporter> inputFileImporters = ExtensionFileFilter.getImporters().stream()
|
---|
246 | .filter(importer -> importer.acceptFile(inputFileFile)).collect(Collectors.toList());
|
---|
247 | final Stopwatch stopwatch = Stopwatch.createStarted();
|
---|
248 | if (inputFileImporters.stream().noneMatch(fileImporter ->
|
---|
249 | fileImporter.importDataHandleExceptions(inputFileFile, progressMonitorFactory.get()))) {
|
---|
250 | throw new IOException(tr("Could not load input file: {0}", inputFile));
|
---|
251 | }
|
---|
252 | final String outputFile = Optional.ofNullable(this.output.get(inputFile)).orElseGet(() -> getDefaultOutputName(inputFile));
|
---|
253 | final String task = tr("Validating {0}, saving output to {1}", inputFile, outputFile);
|
---|
254 | OsmDataLayer dataLayer = null;
|
---|
255 | try {
|
---|
256 | Logging.info(task);
|
---|
257 | OsmValidator.initializeTests();
|
---|
258 | dataLayer = MainApplication.getLayerManager().getLayersOfType(OsmDataLayer.class)
|
---|
259 | .stream().filter(layer -> inputFileFile.equals(layer.getAssociatedFile()))
|
---|
260 | .findFirst().orElseThrow(() -> new JosmRuntimeException(tr("Could not find a layer for {0}", inputFile)));
|
---|
261 | final DataSet dataSet = dataLayer.getDataSet();
|
---|
262 | if (this.changeFiles.containsKey(inputFile)) {
|
---|
263 | ProgressMonitor changeFilesMonitor = progressMonitorFactory.get();
|
---|
264 | for (String changeFile : this.changeFiles.getOrDefault(inputFile, Collections.emptyList())) {
|
---|
265 | try (InputStream changeStream = Compression.getUncompressedFileInputStream(Paths.get(changeFile))) {
|
---|
266 | dataSet.mergeFrom(OsmChangeReader.parseDataSet(changeStream, changeFilesMonitor));
|
---|
267 | }
|
---|
268 | }
|
---|
269 | }
|
---|
270 | Collection<Test> tests = OsmValidator.getEnabledTests(false);
|
---|
271 | if (Files.isRegularFile(Paths.get(outputFile)) && !Files.deleteIfExists(Paths.get(outputFile))) {
|
---|
272 | Logging.error("Could not delete {0}, attempting to append", outputFile);
|
---|
273 | }
|
---|
274 | GeoJSONMapRouletteWriter geoJSONMapRouletteWriter = new GeoJSONMapRouletteWriter(dataSet);
|
---|
275 | try (OutputStream fileOutputStream = Files.newOutputStream(Paths.get(outputFile))) {
|
---|
276 | tests.parallelStream().forEach(test -> runTest(test, geoJSONMapRouletteWriter, fileOutputStream, dataSet));
|
---|
277 | }
|
---|
278 | } finally {
|
---|
279 | if (dataLayer != null) {
|
---|
280 | MainApplication.getLayerManager().removeLayer(dataLayer);
|
---|
281 | }
|
---|
282 | Logging.info(stopwatch.toString(task));
|
---|
283 | }
|
---|
284 | }
|
---|
285 |
|
---|
286 | /**
|
---|
287 | * Get the default output name
|
---|
288 | * @param inputString The input file
|
---|
289 | * @return The default output name for the input file (extension stripped, ".geojson" added)
|
---|
290 | */
|
---|
291 | private static String getDefaultOutputName(final String inputString) {
|
---|
292 | final String extension = FileNameUtils.getExtension(inputString);
|
---|
293 | if (!Arrays.asList("zip", "bz", "xz", "geojson").contains(extension)) {
|
---|
294 | return FileNameUtils.getBaseName(inputString) + ".geojson";
|
---|
295 | } else if ("geojson".equals(extension)) {
|
---|
296 | // Account for geojson input files
|
---|
297 | return FileNameUtils.getBaseName(inputString) + ".validated.geojson";
|
---|
298 | }
|
---|
299 | return FileNameUtils.getBaseName(FileNameUtils.getBaseName(inputString)) + ".geojson";
|
---|
300 | }
|
---|
301 |
|
---|
302 | /**
|
---|
303 | * Run a test
|
---|
304 | * @param test The test to run
|
---|
305 | * @param geoJSONMapRouletteWriter The object to use to create challenges
|
---|
306 | * @param fileOutputStream The location to write data to
|
---|
307 | * @param dataSet The dataset to check
|
---|
308 | */
|
---|
309 | private void runTest(final Test test, final GeoJSONMapRouletteWriter geoJSONMapRouletteWriter,
|
---|
310 | final OutputStream fileOutputStream, DataSet dataSet) {
|
---|
311 | test.startTest(progressMonitorFactory.get());
|
---|
312 | test.visit(dataSet.allPrimitives());
|
---|
313 | test.endTest();
|
---|
314 | test.getErrors().stream().map(geoJSONMapRouletteWriter::write)
|
---|
315 | .filter(Optional::isPresent).map(Optional::get)
|
---|
316 | .map(jsonObject -> jsonObject.toString().getBytes(StandardCharsets.UTF_8)).forEach(bytes -> {
|
---|
317 | try {
|
---|
318 | writeToFile(fileOutputStream, bytes);
|
---|
319 | } catch (IOException e) {
|
---|
320 | throw new JosmRuntimeException(e);
|
---|
321 | }
|
---|
322 | });
|
---|
323 | test.clear();
|
---|
324 | }
|
---|
325 |
|
---|
326 | /**
|
---|
327 | * Write to a file. Synchronized to avoid writing to the same file in different threads.
|
---|
328 | *
|
---|
329 | * @param fileOutputStream The file output stream to read
|
---|
330 | * @param bytes The bytes to write (surrounded by RS and LF)
|
---|
331 | * @throws IOException If we couldn't write to file
|
---|
332 | */
|
---|
333 | private synchronized void writeToFile(final OutputStream fileOutputStream, final byte[] bytes)
|
---|
334 | throws IOException {
|
---|
335 | // Write the ASCII Record Separator character
|
---|
336 | fileOutputStream.write(0x1e);
|
---|
337 | fileOutputStream.write(bytes);
|
---|
338 | // Write the ASCII Line Feed character
|
---|
339 | fileOutputStream.write(0x0a);
|
---|
340 | }
|
---|
341 |
|
---|
342 | /**
|
---|
343 | * Initialize everything that might be needed
|
---|
344 | *
|
---|
345 | * Arguments may need to be parsed first.
|
---|
346 | */
|
---|
347 | void initialize() {
|
---|
348 | Logging.setLogLevel(this.logLevel);
|
---|
349 | HttpClient.setFactory(Http1Client::new);
|
---|
350 | Config.setBaseDirectoriesProvider(JosmBaseDirectories.getInstance()); // for right-left-hand traffic cache file
|
---|
351 | Config.setUrlsProvider(JosmUrls.getInstance());
|
---|
352 | ProjectionRegistry.setProjection(Projections.getProjectionByCode("epsg:3857".toUpperCase(Locale.ROOT)));
|
---|
353 |
|
---|
354 | Territories.initializeInternalData();
|
---|
355 | OsmValidator.initialize();
|
---|
356 | MapPaintStyles.readFromPreferences();
|
---|
357 | }
|
---|
358 |
|
---|
359 | /**
|
---|
360 | * Parse command line arguments and do some low-level error checking.
|
---|
361 | * @param argArray the arguments array
|
---|
362 | */
|
---|
363 | void parseArguments(String[] argArray) {
|
---|
364 | Logging.setLogLevel(Level.INFO);
|
---|
365 |
|
---|
366 | OptionParser parser = new OptionParser("JOSM validate");
|
---|
367 | final AtomicReference<String> currentInput = new AtomicReference<>(null);
|
---|
368 | for (Option o : Option.values()) {
|
---|
369 | if (o.requiresArgument()) {
|
---|
370 | parser.addArgumentParameter(o.getName(),
|
---|
371 | o.getOptionCount(),
|
---|
372 | arg -> handleOption(currentInput.get(), o, arg).ifPresent(currentInput::set));
|
---|
373 | } else {
|
---|
374 | parser.addFlagParameter(o.getName(), () -> handleOption(o));
|
---|
375 | }
|
---|
376 | if (o.getShortOption() != '*') {
|
---|
377 | parser.addShortAlias(o.getName(), Character.toString(o.getShortOption()));
|
---|
378 | }
|
---|
379 | }
|
---|
380 | parser.parseOptionsOrExit(Arrays.asList(argArray));
|
---|
381 | }
|
---|
382 |
|
---|
383 | private void handleOption(final Option option) {
|
---|
384 | switch (option) {
|
---|
385 | case HELP:
|
---|
386 | showHelp();
|
---|
387 | System.exit(0);
|
---|
388 | break;
|
---|
389 | case DEBUG:
|
---|
390 | this.logLevel = Logging.LEVEL_DEBUG;
|
---|
391 | break;
|
---|
392 | case TRACE:
|
---|
393 | this.logLevel = Logging.LEVEL_TRACE;
|
---|
394 | break;
|
---|
395 | default:
|
---|
396 | throw new AssertionError("Unexpected option: " + option);
|
---|
397 | }
|
---|
398 | }
|
---|
399 |
|
---|
400 | /**
|
---|
401 | * Handle an option
|
---|
402 | * @param currentInput The current input file, if any. May be {@code null}.
|
---|
403 | * @param option The option to parse
|
---|
404 | * @param argument The argument for the option
|
---|
405 | * @return The new input file, if any.
|
---|
406 | */
|
---|
407 | private Optional<String> handleOption(final String currentInput, final Option option, final String argument) {
|
---|
408 | switch (option) {
|
---|
409 | case INPUT:
|
---|
410 | this.input.add(argument);
|
---|
411 | return Optional.of(argument);
|
---|
412 | case OUTPUT:
|
---|
413 | this.output.put(currentInput, argument);
|
---|
414 | break;
|
---|
415 | case CHANGE_FILE:
|
---|
416 | this.changeFiles.computeIfAbsent(currentInput, key -> new ArrayList<>()).add(argument);
|
---|
417 | break;
|
---|
418 | case LANGUAGE:
|
---|
419 | I18n.set(argument);
|
---|
420 | break;
|
---|
421 | case LOAD_PREFERENCES:
|
---|
422 | final Preferences tempPreferences = new Preferences();
|
---|
423 | tempPreferences.enableSaveOnPut(false);
|
---|
424 | CustomConfigurator.XMLCommandProcessor config = new CustomConfigurator.XMLCommandProcessor(tempPreferences);
|
---|
425 | try (InputStream is = Utils.openStream(new File(argument).toURI().toURL())) {
|
---|
426 | config.openAndReadXML(is);
|
---|
427 | } catch (IOException e) {
|
---|
428 | throw new JosmRuntimeException(e);
|
---|
429 | }
|
---|
430 | final IPreferences pref = Config.getPref();
|
---|
431 | if (pref instanceof MemoryPreferences) {
|
---|
432 | final MemoryPreferences memoryPreferences = (MemoryPreferences) pref;
|
---|
433 | tempPreferences.getAllSettings().entrySet().stream().filter(entry -> entry.getValue().isNew())
|
---|
434 | .forEach(entry -> memoryPreferences.putSetting(entry.getKey(), entry.getValue()));
|
---|
435 | } else {
|
---|
436 | throw new JosmRuntimeException(tr("Preferences are not the expected type"));
|
---|
437 | }
|
---|
438 | break;
|
---|
439 | case SET:
|
---|
440 |
|
---|
441 | default:
|
---|
442 | throw new AssertionError("Unexpected option: " + option);
|
---|
443 | }
|
---|
444 | return Optional.empty();
|
---|
445 | }
|
---|
446 |
|
---|
447 | private static void showHelp() {
|
---|
448 | System.out.println(getHelp());
|
---|
449 | }
|
---|
450 |
|
---|
451 | private static String getHelp() {
|
---|
452 | final String helpPadding = "\t ";
|
---|
453 | // CHECKSTYLE.OFF: SingleSpaceSeparator
|
---|
454 | return tr("JOSM Validation command line interface") + "\n\n" +
|
---|
455 | tr("Usage") + ":\n" +
|
---|
456 | "\tjava -jar josm.jar validate <options>\n\n" +
|
---|
457 | tr("Description") + ":\n" +
|
---|
458 | tr("Validates data and saves the result to a file.") + "\n\n"+
|
---|
459 | tr("Options") + ":\n" +
|
---|
460 | "\t--help|-h " + tr("Show this help") + "\n" +
|
---|
461 | "\t--input|-i <file> " + tr("Input data file name (.osm, .validator.mapcss, .mapcss).") + '\n' +
|
---|
462 | helpPadding + tr("OSM files can be specified multiple times. Required.") + '\n' +
|
---|
463 | helpPadding + tr(".validator.mapcss and .mapcss files will stop processing on first error.") + '\n' +
|
---|
464 | helpPadding + tr("Non-osm files do not use --output or --change-file") + '\n' +
|
---|
465 | "\t--output|-o <file> " + tr("Output data file name (.geojson, line-by-line delimited for MapRoulette). Optional.")
|
---|
466 | + '\n' +
|
---|
467 | "\t--change-file|-c <file> " + tr("Change file name (.osc). Can be specified multiple times per input.") + '\n' +
|
---|
468 | helpPadding + tr("Changes will be applied in the specified order. Optional.");
|
---|
469 | // CHECKSTYLE.ON: SingleSpaceSeparator
|
---|
470 | }
|
---|
471 | }
|
---|