com.github.rvesse.airline.parser.AbstractCommandParser.java Source code

Java tutorial

Introduction

Here is the source code for com.github.rvesse.airline.parser.AbstractCommandParser.java

Source

/**
 * Copyright (C) 2010-16 the original author or authors.
 *
 * 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 com.github.rvesse.airline.parser;

import com.github.rvesse.airline.Context;
import com.github.rvesse.airline.model.ArgumentsMetadata;
import com.github.rvesse.airline.model.CommandGroupMetadata;
import com.github.rvesse.airline.model.CommandMetadata;
import com.github.rvesse.airline.model.GlobalMetadata;
import com.github.rvesse.airline.model.OptionMetadata;
import com.github.rvesse.airline.model.ParserMetadata;
import com.github.rvesse.airline.parser.aliases.AliasResolver;
import com.github.rvesse.airline.parser.options.OptionParser;
import com.github.rvesse.airline.utils.AirlineUtils;
import com.github.rvesse.airline.utils.predicates.parser.AbbreviatedCommandFinder;
import com.github.rvesse.airline.utils.predicates.parser.AbbreviatedGroupFinder;
import com.github.rvesse.airline.utils.predicates.parser.CommandFinder;
import com.github.rvesse.airline.utils.predicates.parser.GroupFinder;

import java.util.List;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Predicate;
import org.apache.commons.collections4.iterators.PeekingIterator;

/**
 * Abstract implementation of a parser for commands that can cope with both CLI
 * and Single Command parsing
 *
 * @param <T>
 *            Command type
 */
public abstract class AbstractCommandParser<T> extends AbstractParser<T> {

    /**
     * Tries to parse the arguments
     * 
     * @param metadata
     *            Global Metadata
     * @param args
     *            Arguments
     * @return Parser State
     */
    protected ParseState<T> tryParse(GlobalMetadata<T> metadata, String... args) {
        return tryParse(metadata, AirlineUtils.unmodifiableListCopy(args));
    }

    /**
     * Tries to parse the arguments
     * 
     * @param metadata
     *            Global Metadata
     * @param args
     *            Arguments
     * @return Parser State
     */
    protected ParseState<T> tryParse(GlobalMetadata<T> metadata, Iterable<String> args) {
        PeekingIterator<String> tokens = new PeekingIterator<String>(args.iterator());

        //@formatter:off
        ParseState<T> state = ParseState.<T>newInstance().pushContext(Context.GLOBAL).withGlobal(metadata);
        //@formatter:on

        // Parse global options
        state = parseOptions(tokens, state, metadata.getOptions());

        // Apply aliases
        tokens = applyAliases(tokens, state);

        // Parse group
        state = parseGroup(tokens, state);

        // parse command
        state = parseCommand(tokens, state);

        return state;
    }

    protected PeekingIterator<String> applyAliases(PeekingIterator<String> tokens, ParseState<T> state) {
        AliasResolver<T> resolver = new AliasResolver<T>();
        return resolver.resolveAliases(tokens, state);
    }

    /**
     * Tries to parse the arguments
     * 
     * @param parserConfig
     *            Parser Configuration
     * @param command
     *            Command meta-data
     * @param args
     *            Arguments
     * @return Parser State
     */
    protected ParseState<T> tryParse(ParserMetadata<T> parserConfig, CommandMetadata command,
            Iterable<String> args) {
        PeekingIterator<String> tokens = new PeekingIterator<String>(args.iterator());
        //@formatter:off
        ParseState<T> state = ParseState.<T>newInstance().pushContext(Context.GLOBAL)
                .withConfiguration(parserConfig).withCommand(command).pushContext(Context.COMMAND);
        //@formatter:off

        state = parseCommandOptionsAndArguments(tokens, state, command);
        return state;
    }

    protected ParseState<T> parseCommand(PeekingIterator<String> tokens, ParseState<T> state) {
        Predicate<CommandMetadata> findCommandPredicate;
        List<CommandMetadata> expectedCommands = state.getGlobal().getDefaultGroupCommands();
        if (state.getGroup() != null) {
            expectedCommands = state.getGroup().getCommands();
        }

        if (tokens.hasNext()) {
            //@formatter:off
            findCommandPredicate = state.getParserConfiguration().allowsAbbreviatedCommands()
                    ? new AbbreviatedCommandFinder(tokens.peek(), expectedCommands)
                    : new CommandFinder(tokens.peek());
            //@formatter:on
            CommandMetadata command = AirlineUtils.find(expectedCommands, findCommandPredicate,
                    state.getGroup() != null ? state.getGroup().getDefaultCommand() : null);

            boolean usingDefault = false;
            if (command == null && state.getGroup() == null && state.getGlobal().getDefaultCommand() != null) {
                usingDefault = true;
                command = state.getGlobal().getDefaultCommand();
            }

            if (command == null) {
                while (tokens.hasNext()) {
                    state = state.withUnparsedInput(tokens.next());
                }
            } else {
                if (tokens.peek().equals(command.getName())
                        || (!usingDefault && state.getParserConfiguration().allowsAbbreviatedCommands())) {
                    tokens.next();
                }

                state = state.withCommand(command).pushContext(Context.COMMAND);

                state = parseCommandOptionsAndArguments(tokens, state, command);
            }
        }
        return state;
    }

    protected ParseState<T> parseCommandOptionsAndArguments(PeekingIterator<String> tokens, ParseState<T> state,
            CommandMetadata command) {
        while (tokens.hasNext()) {
            state = parseOptions(tokens, state, command.getCommandOptions());

            state = parseArgs(state, tokens, command.getArguments(), command.getDefaultOption());
        }
        return state;
    }

    protected ParseState<T> parseGroup(PeekingIterator<String> tokens, ParseState<T> state) {
        Predicate<CommandGroupMetadata> findGroupPredicate;
        if (tokens.hasNext()) {
            //@formatter:off
            findGroupPredicate = state.getParserConfiguration().allowsAbbreviatedCommands()
                    ? new AbbreviatedGroupFinder(tokens.peek(), state.getGlobal().getCommandGroups())
                    : new GroupFinder(tokens.peek());
            //@formatter:on
            CommandGroupMetadata group = CollectionUtils.find(state.getGlobal().getCommandGroups(),
                    findGroupPredicate);
            if (group != null) {
                tokens.next();
                state = state.withGroup(group).pushContext(Context.GROUP);
                state = parseOptions(tokens, state, state.getGroup().getOptions());

                // Possibly may have sub-groups specified
                while (tokens.hasNext() && state.getGroup().getSubGroups().size() > 0) {
                    //@formatter:off
                    findGroupPredicate = state.getParserConfiguration().allowsAbbreviatedCommands()
                            ? new AbbreviatedGroupFinder(tokens.peek(), state.getGroup().getSubGroups())
                            : new GroupFinder(tokens.peek());
                    //@formatter:on
                    group = CollectionUtils.find(state.getGroup().getSubGroups(), findGroupPredicate);
                    if (group != null) {
                        tokens.next();
                        state = state.withGroup(group).pushContext(Context.GROUP);
                        state = parseOptions(tokens, state, state.getGroup().getOptions());
                    } else {
                        // Either a group that has a mixture of sub-groups and
                        // commands in which case we need to break out of this
                        // loop and flow will go onto to try the token as a
                        // command name instead or actually invalid input and
                        // we'll hit an error
                        break;
                    }
                }
            }
        }
        return state;
    }

    private ParseState<T> parseOptions(PeekingIterator<String> tokens, ParseState<T> state,
            List<OptionMetadata> allowedOptions) {

        // Get the option parsers in use
        List<OptionParser<T>> optionParsers = state.getParserConfiguration().getOptionParsers();

        while (tokens.hasNext()) {
            // Try to parse next option(s) using different styles. If code
            // matches it returns the next parser state, otherwise it returns
            // null.

            // Try each option parser in turn
            boolean matched = false;
            for (OptionParser<T> optionParser : optionParsers) {
                ParseState<T> nextState = optionParser.parseOptions(tokens, state, allowedOptions);

                if (nextState != null) {
                    // If the current parser matched an option this token is
                    // processed and we don't need to consider other parsers
                    state = nextState;
                    matched = true;
                    break;
                }
            }

            // Parsed an option so continue parsing options
            if (matched)
                continue;

            // Otherwise did not match an option so parse no further options
            break;
        }

        return state;
    }

    private ParseState<T> parseArgs(ParseState<T> state, PeekingIterator<String> tokens,
            ArgumentsMetadata arguments, OptionMetadata defaultOption) {
        String sep = state.getParserConfiguration().getArgumentsSeparator();

        if (tokens.hasNext()) {
            if (tokens.peek().equals(sep)) {
                state = state.pushContext(Context.ARGS);
                tokens.next();

                // Consume all remaining tokens as arguments
                // Default option can't possibly apply at this point because we
                // saw the arguments separator
                while (tokens.hasNext()) {
                    state = parseArg(state, tokens, arguments, null);
                }
            } else {
                state = parseArg(state, tokens, arguments, defaultOption);
            }
        }

        return state;
    }

    private ParseState<T> parseArg(ParseState<T> state, PeekingIterator<String> tokens, ArgumentsMetadata arguments,
            OptionMetadata defaultOption) {
        if (arguments != null) {
            // Argument
            state = state.withArgument(arguments, tokens.next());
        } else if (defaultOption != null) {
            // Default Option
            state = state.pushContext(Context.OPTION).withOption(defaultOption);
            state = state.withOptionValue(defaultOption, tokens.next()).popContext();
        } else {
            // Unparsed input
            state = state.withUnparsedInput(tokens.next());
        }
        return state;
    }
}