All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.carne.util.cmdline.CmdLineProcessor Maven / Gradle / Ivy

/*
 * Copyright (c) 2016-2017 Holger de Carne and contributors, All Rights Reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package de.carne.util.cmdline;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.regex.Pattern;

import de.carne.check.Nullable;

/**
 * Utility class for command line processing.
 * 

* An instance of this class wraps a single command line. *

* Actions can be added to define how the encountered arguments are processed. The following action types are supported: *

    *
  • switch (e.g. {@code --switch} or {@code -s}; see {@link #onSwitch(Consumer)})
  • *
  • option (e.g. {@code --option value} or {@code -o value}; see {@link #onOption(BiConsumer)})
  • *
  • unnamed (e.g. {@code file.txt)}; see {@link #onUnnamedOption(Consumer)})
  • *
  • unknown (default processing; see {@link #onUnknownArg(Consumer)}) *
* Invoking the {@link #process()} function processes all arguments and invokes the corresponding actions. */ public final class CmdLineProcessor { private static final Pattern ACTION_ARG_PATTERN = Pattern.compile("(-[^-\\s])|(--[^-\\s]+)"); private final String cmd; private final Iterable args; private final List switchActions = new ArrayList<>(); private final List optionActions = new ArrayList<>(); @Nullable private Consumer unnamedAction = null; @Nullable private Consumer unknownAction = null; /** * Construct {@link CmdLineProcessor}. * * @param cmd The command executing the command line (used by {@link #toString()} to build a complete command line * string). * @param args The command line to process. */ public CmdLineProcessor(String cmd, String[] args) { this(cmd, Arrays.asList(args)); } /** * Construct {@link CmdLineProcessor}. * * @param cmd The command executing the command line. * @param args The command line to process. */ public CmdLineProcessor(String cmd, Iterable args) { this.cmd = cmd; this.args = args; } /** * Check whether an argument string is a valid action argument. *

* An action argument must be of the form single '-' and character (e.g. {@code -a}) or double '-' and a name (e.g. * {@code --argument}). * * @param arg The argument string to check. * @return {@code true} if the argument string is a valid action argument. */ public static boolean isActionArg(String arg) { return ACTION_ARG_PATTERN.matcher(arg).matches(); } /** * Process the command line and invoke the correspond actions. * * @throws CmdLineException if the command line contains an error. * @see #onSwitch(Consumer) * @see #onOption(BiConsumer) * @see #onUnnamedOption(Consumer) * @see #onUnknownArg(Consumer) */ public void process() throws CmdLineException { ProcessingContext context = new ProcessingContext(); for (String arg : this.args) { if (!context.processPendingOptionAction(arg) && !context.processOptionAction(arg, this.optionActions) && !context.processSwitchAction(arg, this.switchActions)) { // No action found so far. Invoke the corresponding default action. Consumer defaultAction = (isActionArg(arg) ? this.unknownAction : this.unnamedAction); if (defaultAction != null) { defaultAction.accept(arg); } else { throw new CmdLineException(this, arg); } } } context.verifyNoPendingOptionAction(); } private class ProcessingContext { @Nullable private OptionCmdLineAction pendingOptionAction = null; @Nullable private String pendingArg = null; ProcessingContext() { // Nothing to do, just to make it accessible for out class } public boolean processPendingOptionAction(String option) throws CmdLineException { // Check whether there is a pending named option waiting for completion. // If this is the case, check and invoke the action. OptionCmdLineAction optionAction = this.pendingOptionAction; String arg = this.pendingArg; boolean processed = false; if (optionAction != null && arg != null) { if (isActionArg(option)) { throw new CmdLineException(CmdLineProcessor.this, arg); } optionAction.accept(this.pendingArg, option); this.pendingOptionAction = null; this.pendingArg = null; processed = true; } return processed; } public void verifyNoPendingOptionAction() throws CmdLineException { OptionCmdLineAction optionAction = this.pendingOptionAction; String arg = this.pendingArg; if (optionAction != null && arg != null) { throw new CmdLineException(CmdLineProcessor.this, arg); } } public boolean processOptionAction(String arg, List actions) { // Check whether the argument is a known option argument. // If this is the case, remember it as pending and continue (processing will be done above). Optional optOptionAction = actions.stream().filter(action -> action.contains(arg)) .findFirst(); boolean processed = false; if (optOptionAction.isPresent()) { this.pendingOptionAction = optOptionAction.get(); this.pendingArg = arg; processed = true; } return processed; } public boolean processSwitchAction(String arg, List actions) { // Check whether the argument is a known switch argument. // If this is the case, invoke it. Optional optSwitchAction = actions.stream().filter(action -> action.contains(arg)) .findFirst(); boolean processed = false; if (optSwitchAction.isPresent()) { optSwitchAction.get().accept(arg); processed = true; } return processed; } } /** * Add a {@link CmdLineAction} for switch argument ({@code e.g. --switch}) processing. * * @param action The {@link Consumer} to invoke with the argument string. * @return The created {@link CmdLineAction}. */ public CmdLineAction onSwitch(Consumer action) { SwitchCmdLineAction switchAction = new SwitchCmdLineAction(action); this.switchActions.add(switchAction); return switchAction; } /** * Add a {@link CmdLineAction} for option argument ({@code e.g. --option value}) processing. * * @param action The {@link BiConsumer} to invoke with the argument and option string. * @return The created {@link CmdLineAction}. */ public CmdLineAction onOption(BiConsumer action) { OptionCmdLineAction optionAction = new OptionCmdLineAction(action); this.optionActions.add(optionAction); return optionAction; } /** * Add an action for unnamed options (e.g. {@code file.txt}). *

* If no action is defined for unnamed options the command line processing will fail in case an unnamed option is * encountered. * * @param action The {@link Consumer} to invoke with the option string. */ public void onUnnamedOption(Consumer action) { this.unnamedAction = action; } /** * Add an action for unknown arguments (e.g. {@code --unknown}). *

* If no action is defined for unknown arguments the command line processing will fail in case an unknown arguments * is encountered. * * @param action The {@link Consumer} to invoke with the argument string. */ public void onUnknownArg(Consumer action) { this.unknownAction = action; } @Override public String toString() { StringBuilder buffer = new StringBuilder(); buffer.append(this.cmd); for (String arg : this.args) { buffer.append(" "); buffer.append(arg); } return buffer.toString(); } private class SwitchCmdLineAction extends CmdLineAction implements Consumer { private final Consumer action; SwitchCmdLineAction(Consumer action) { this.action = action; } @Override public void accept(@Nullable String t) { this.action.accept(t); } } private class OptionCmdLineAction extends CmdLineAction implements BiConsumer { private final BiConsumer action; OptionCmdLineAction(BiConsumer action) { this.action = action; } @Override public void accept(@Nullable String t, @Nullable String u) { this.action.accept(t, u); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy