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

org.jboss.aesh.cl.parser.AeshCommandLineParser Maven / Gradle / Ivy

There is a newer version: 9.1.7.Final
Show newest version
/*
 * JBoss, Home of Professional Open Source
 * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
 * as indicated by the @authors tag. All rights reserved.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * Licensed 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.jboss.aesh.cl.parser;

import org.jboss.aesh.cl.CommandLine;
import org.jboss.aesh.cl.internal.OptionType;
import org.jboss.aesh.cl.internal.ProcessedCommand;
import org.jboss.aesh.cl.internal.ProcessedOption;
import org.jboss.aesh.cl.populator.CommandPopulator;
import org.jboss.aesh.console.Config;
import org.jboss.aesh.console.command.Command;
import org.jboss.aesh.parser.AeshLine;
import org.jboss.aesh.parser.Parser;
import org.jboss.aesh.parser.ParserStatus;

import java.util.ArrayList;
import java.util.List;

/**
 * A simple command line parser.
 * It parses a given string based on the Command given and
 * returns a {@link org.jboss.aesh.cl.CommandLine}
 *
 * It can also print a formatted usage/help information.
 *
 * @author Ståle W. Pedersen
 */
public class AeshCommandLineParser implements CommandLineParser {

    private final ProcessedCommand processedCommand;
    private static final String EQUALS = "=";
    private List> childParsers;
    private boolean isChild = false;

    public AeshCommandLineParser(ProcessedCommand processedCommand) {
        this.processedCommand = processedCommand;
    }

    @Override
    public void addChildParser(CommandLineParser commandLineParser) {
        if(childParsers == null)
            childParsers = new ArrayList<>();
        commandLineParser.setChild(true);
        childParsers.add(commandLineParser);
    }

    @Override
    public void setChild(boolean child) {
        isChild = child;
    }

    @Override
    public List getAllNames() {
        if(isGroupCommand()) {
            List names = new ArrayList<>(childParsers.size());
            for(CommandLineParser child : childParsers) {
                names.add(processedCommand.getName()+" "+child.getProcessedCommand().getName());
            }
            return names;
        }
        else {
            List names = new ArrayList<>(1);
            names.add(processedCommand.getName());
            return names;
        }
    }

    public boolean isChild() {
        return isChild;
    }

    @Override
    public CommandLineParser getChildParser(String name) {
        if(!isGroupCommand())
            return null;
        for(CommandLineParser clp : childParsers) {
            if(clp.getProcessedCommand().getName().equals(name))
                return clp;
        }
        return null;
    }

    @Override
    public List> getAllChildParsers() {
        if(isGroupCommand())
            return childParsers;
        else
           return new ArrayList<>();
    }

    @Override
    public ProcessedCommand getProcessedCommand() {
        return processedCommand;
    }

    @Override
    public C getCommand() {
        return processedCommand.getCommand();
    }

    @Override
    public CommandLineCompletionParser getCompletionParser() {
        return new AeshCommandLineCompletionParser(this);
    }

    @Override
    public CommandPopulator getCommandPopulator() {
        return processedCommand.getCommandPopulator();
    }

    /**
     * Returns a usage String based on the defined command and options.
     * Useful when printing "help" info etc.
     */
    @Override
    public String printHelp() {
        if(childParsers != null && childParsers.size() > 0) {
            StringBuilder sb = new StringBuilder();
            sb.append(processedCommand.printHelp())
                    .append(Config.getLineSeparator())
                    .append(processedCommand.getName())
                    .append(" commands:")
                    .append(Config.getLineSeparator());
            for(CommandLineParser child : childParsers)
                sb.append("    ").append(child.getProcessedCommand().getName()).append(Config.getLineSeparator());

            return sb.toString();
        }
        else
            return processedCommand.printHelp();
    }

    /**
     * Parse a command line with the defined command as base of the rules.
     * If any options are found, but not defined in the command object an
     * CommandLineParserException will be thrown.
     * Also, if a required option is not found or options specified with value,
     * but is not given any value an OptionParserException will be thrown.
     *
     * The options found will be returned as a {@link org.jboss.aesh.cl.CommandLine} object where
     * they can be queried after.
     *
     * @param line input
     * @return CommandLine
     */
    @Override
    public CommandLine parse(String line) {
        return parse(line, false);
    }

    @Override
    public CommandLine parse(AeshLine line, boolean ignoreRequirements) {
        if(line.getWords().size() > 0) {
            if(processedCommand.getName().equals(line.getWords().get(0))) {
                if(isGroupCommand() && line.getWords().size() > 1) {
                   CommandLineParser clp = getChildParser(line.getWords().get(1));
                    if(clp == null)
                        return parse(line.getWords(), ignoreRequirements);
                    //we have a group command
                    else
                        return clp.parse(line.getWords(), ignoreRequirements);
                }
                else
                    return parse(line.getWords(), ignoreRequirements);
            }
        }
        else if(line.getStatus() != ParserStatus.OK)
            return new CommandLine<>(new CommandLineParserException(line.getErrorMessage()));

        return new CommandLine<>(new CommandLineParserException("Command:"+ processedCommand +", not found in: "+line));
    }
    /**
     * Parse a command line with the defined command as base of the rules.
     * If any options are found, but not defined in the command object an
     * CommandLineParserException will be thrown.
     * Also, if a required option is not found or options specified with value,
     * but is not given any value an CommandLineParserException will be thrown.
     *
     * The options found will be returned as a {@link CommandLine} object where
     * they can be queried after.
     *
     * @param line input
     * @param ignoreRequirements if we should ignore
     * @return CommandLine
     */
    @Override
    public CommandLine parse(String line, boolean ignoreRequirements) {
        return parse(Parser.findAllWords(line), ignoreRequirements);
    }

    /**
     * Parse a command line with the defined command as base of the rules.
     * This method is useful when parsing a command line program thats not
     * in aesh, but rather a standalone command that want to parse input
     * parameters.
     *
     * @param lines input
     * @param ignoreRequirements if we should ignore
     * @return CommandLine
     */
    @Override
    public CommandLine parse(List lines, boolean ignoreRequirements) {
        clear();
        CommandLine commandLine = new CommandLine<>(this);
        if(processedCommand.hasArgument())
            commandLine.setArgument(processedCommand.getArgument());
        ProcessedOption active = null;
        boolean addedArgument = false;
        int startWord = 1;
        if(isChild)
            startWord = 2;
        //skip first entry since that's the name of the command
        for(int i=startWord; i < lines.size(); i++) {
            String parseLine = lines.get(i);
            ProcessedOption currentOption = null;

            // name
            if (parseLine.startsWith("--")) {
                currentOption = findLongOption(processedCommand, parseLine.substring(2));
                if (currentOption != null)
                    currentOption.setLongNameUsed(true);
            }
            else if (parseLine.startsWith("-")) {
                currentOption = findOption(processedCommand, parseLine.substring(1, 2));
                if (currentOption != null)
                    currentOption.setLongNameUsed(false);
            }

            // new option
            if (currentOption != null) {
                if (currentOption.isLongNameUsed()) {
                    // make sure that we dont have any "active" options lying around
                    if (active != null) {
                        if (active.getOptionType() == OptionType.LIST ||
                            active.getOptionType() == OptionType.GROUP) {
                            commandLine.addOption(active);
                            active = null;
                        }
                        else {
                            commandLine.setParserException(new OptionParserException("Option: " + active.getDisplayName() + " must be given a value"));
                            break;
                        }
                    }

                    active = currentOption;
                    if (active.isProperty()) {
                        if (parseLine.length() <= (2 + active.getName().length()) ||
                            !parseLine.contains(EQUALS))
                            commandLine.setParserException(new OptionParserException(
                                "Option " + active.getDisplayName() + ", must be part of a property"));
                        else {
                            String name =
                                parseLine.substring(2 + active.getName().length(),
                                    parseLine.indexOf(EQUALS));
                            String value = parseLine.substring(parseLine.indexOf(EQUALS) + 1);
                            if (value.length() < 1)
                                commandLine.setParserException(new OptionParserException("Option " + active.getDisplayName() + ", must have a value"));
                            else {
                                active.addProperty(name, value);
                                commandLine.addOption(active);
                                active = null;
                                if (addedArgument)
                                    commandLine.setParserException(new ArgumentParserException("An argument was given to an option that does not support it."));
                            }
                        }
                    }
                    else if (active.getValue() != null) {
                        if (!active.getEndsWithSeparator()) {
                            commandLine.addOption(active);
                            active = null;
                        }
                    }
                    else if (active.getOptionType().equals(OptionType.BOOLEAN) &&
                        (!active.hasValue() || active.getValue() != null)) {
                        active.addValue("true");
                        commandLine.addOption(active);
                        active = null;
                        if (addedArgument)
                            commandLine.setParserException(new ArgumentParserException("An argument was given to an option that does not support it."));
                    }
                    else if (active == null)
                        commandLine.setParserException(new OptionParserException("Option: " + parseLine + " is not a valid option for this command"));
                }
                // name
                else if (!currentOption.isLongNameUsed()) {
                    // make sure that we dont have any "active" options lying around
                    // except list and group
                    if (active != null) {
                        if (active.getOptionType() == OptionType.LIST ||
                            active.getOptionType() == OptionType.GROUP) {
                            commandLine.addOption(active);
                            active = null;
                        }
                        else {
                            commandLine.setParserException(new OptionParserException("Option: " + active.getDisplayName() + " must be given a value"));
                            break;
                        }
                    }
                    else if (parseLine.length() != 2 && !parseLine.contains("=")) {
                        // we might have two or more options in a group
                        // if so, we only allow options (boolean) without value
                        if (parseLine.length() > 2) {
                            for (char shortName : parseLine.substring(1).toCharArray()) {
                                active = findOption(processedCommand, String.valueOf(shortName));
                                if (active != null) {
                                    if (!active.hasValue()) {
                                        active.setLongNameUsed(false);
                                        active.addValue("true");
                                        commandLine.addOption(active);
                                    }
                                    else
                                        commandLine.setParserException(new OptionParserException("Option: -" + shortName +
                                            " can not be grouped with other options since it need to be given a value"));
                                }
                                else
                                    commandLine.setParserException(new OptionParserException("Option: -" + shortName + " was not found."));
                            }
                            // make sure to reset active
                            active = null;
                        }
                        else
                            commandLine.setParserException(new OptionParserException("Option: - must be followed by a valid operator"));
                    }
                    else {
                        active = findOption(processedCommand, parseLine.substring(1));
                        if (active != null)
                            active.setLongNameUsed(false);

                        if (active != null && active.isProperty()) {
                            if (parseLine.length() <= 2 ||
                                !parseLine.contains(EQUALS))
                                commandLine.setParserException(new OptionParserException(
                                    "Option " + active.getDisplayName() + ", must be part of a property"));
                            else {
                                String name =
                                    parseLine.substring(2, // 2+char.length
                                        parseLine.indexOf(EQUALS));
                                String value = parseLine.substring(parseLine.indexOf(EQUALS) + 1);
                                if (value.length() < 1)
                                    commandLine.setParserException(new OptionParserException("Option " + active.getDisplayName() + ", must have a value"));
                                else {
                                    active.addProperty(name, value);
                                    commandLine.addOption(active);
                                    active = null;
                                    if (addedArgument)
                                        commandLine.setParserException(new OptionParserException("An argument was given to an option that does not support it."));
                                }
                            }
                        }
                        else if (active.getValue() != null) {
                            if (!active.getEndsWithSeparator()) {
                                commandLine.addOption(active);
                                active = null;
                            }
                        }
                        else if (active != null && active.getOptionType().equals(OptionType.BOOLEAN) &&
                            (!active.hasValue() || active.getValue() != null)) {
                            active.addValue("true");
                            commandLine.addOption(active);
                            active = null;
                            if (addedArgument)
                                commandLine.setParserException(new OptionParserException("An argument was given to an option that does not support it."));
                        }
                        else if (active == null)
                            commandLine.setParserException(new OptionParserException("Option: " + parseLine + " is not a valid option for this command"));
                    }
                }
            }
            else if(active != null) {
                if(active.hasMultipleValues()) {
                    if(parseLine.contains(String.valueOf(active.getValueSeparator()))) {
                        for(String value : parseLine.split(String.valueOf(active.getValueSeparator()))) {
                            active.addValue(value.trim());
                        }
                        if(parseLine.endsWith(String.valueOf(active.getValueSeparator())))
                            active.setEndsWithSeparator(true);
                        commandLine.addOption(active);
                        active = null;
                    }
                    else
                        active.addValue(parseLine);
                }
                else
                    active.addValue(parseLine);

                if(active != null &&
                        (active.getOptionType() == OptionType.NORMAL ||
                                active.getOptionType() == OptionType.BOOLEAN)) {
                    commandLine.addOption(active);
                    active = null;
                }
                if(addedArgument)
                    commandLine.setParserException(new OptionParserException("An argument was given to an option that does not support it."));
            }
            //if no command is "active", it is a wrong option or we add it as an argument
            else {
                if (parseLine.startsWith("-")) {
                    commandLine.setParserException(new OptionParserException("Option: " + parseLine + " was not found."));
                }
                else if (processedCommand.getArgument() == null) {
                    commandLine.setParserException(new OptionParserException("An argument was given to a command that does not support it."));
                }
                else {
                    commandLine.addArgumentValue(parseLine);
                    addedArgument = true;
                }
            }
        }

        if(active != null && (ignoreRequirements ||
                (active.getOptionType() == OptionType.LIST || active.getOptionType() == OptionType.GROUP))) {
            commandLine.addOption(active);
        }

        //this will throw and CommandLineParserException if needed
        if(!ignoreRequirements) {
            RequiredOptionException re = checkForMissingRequiredOptions(processedCommand, commandLine);
            if(re != null)
                commandLine.setParserException(re);
        }

        return commandLine;
    }

    private RequiredOptionException checkForMissingRequiredOptions(ProcessedCommand command,
                                                                   CommandLine commandLine) {
        for(ProcessedOption o : command.getOptions())
            if(o.isRequired()) {
                boolean found = false;
                for(ProcessedOption po : commandLine.getOptions()) {
                    if(po.getShortName() != null && o.getShortName() != null &&
                            po.getShortName().equals(o.getShortName()) ||
                            (po.getName() != null && po.getName().equals(o.getName()))) {
                        found = true;
                        break;
                    }
                    else if(po.doOverrideRequired()) {
                        found = true;
                        break;
                    }
                }
                if(!found)
                    return new RequiredOptionException("Option: "+o.getDisplayName()+" is required for this command.");
            }
        return null;
    }

    private ProcessedOption findOption(ProcessedCommand command, String line) {
        ProcessedOption option = command.findOption(line);
        //simplest case
        if(option != null)
            return option;

        option = command.startWithOption(line);
        //if its a property, we'll parse it later
        if(option != null && option.isProperty())
            return option;
        if(option != null) {
           String rest = line.substring(option.getShortName().length());
            if(rest.length() > 1 && rest.startsWith("=")) {
                if(option.getOptionType().equals(OptionType.LIST) &&
                        rest.indexOf(option.getValueSeparator()) > -1) {
                    for(String value : rest.substring(1).split(String.valueOf(option.getValueSeparator()))) {
                        option.addValue(value.trim());
                    }
                    if(rest.endsWith(String.valueOf(option.getValueSeparator())))
                        option.setEndsWithSeparator(true);
                }
                else
                    option.addValue(rest.substring(1));
                return option;
            }
        }

        return null;
    }

    private ProcessedOption findLongOption(ProcessedCommand command, String line) {
        ProcessedOption option = command.findLongOptionNoActivatorCheck(line);
        //simplest case
        if(option != null)
            return option;

        option = command.startWithLongOption(line);
        //if its a property, we'll parse it later
        if(option != null && option.isProperty())
            return option;
        if(option != null) {
            String rest = line.substring(option.getName().length());
            if(rest.length() > 1 && rest.startsWith("=")) {
                 if(option.getOptionType().equals(OptionType.LIST) &&
                        rest.indexOf(option.getValueSeparator()) > -1) {
                    for(String value : rest.substring(1).split(String.valueOf(option.getValueSeparator()))) {
                        option.addValue(value.trim());
                    }
                    if(rest.endsWith(String.valueOf(option.getValueSeparator())))
                        option.setEndsWithSeparator(true);
                }
                else
                     option.addValue(rest.substring(1));
                return option;
            }
        }

        return null;
    }

    @Override
    public void clear() {
        processedCommand.clear();
        if(isGroupCommand()) {
            for(CommandLineParser child : childParsers)
                child.getProcessedCommand().clear();
        }
    }

    @Override
    public boolean isGroupCommand() {
        return childParsers != null && childParsers.size() > 0;
    }

    @Override
    public String toString() {
        return "CommandLineParser{" +
                "processedCommand=" + processedCommand +
                "command=" + processedCommand.getCommand() +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof AeshCommandLineParser)) return false;

        AeshCommandLineParser that = (AeshCommandLineParser) o;

        return processedCommand.equals(that.processedCommand);

    }

    @Override
    public int hashCode() {
        return processedCommand.hashCode();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy