001/*
002 * PermissionsEx
003 * Copyright (C) zml and PermissionsEx contributors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package ca.stellardrift.permissionsex.minecraft.command.argument;
018
019import ca.stellardrift.permissionsex.minecraft.command.CommandException;
020import cloud.commandframework.arguments.parser.ArgumentParseResult;
021import cloud.commandframework.arguments.parser.ArgumentParser;
022import cloud.commandframework.context.CommandContext;
023import cloud.commandframework.exceptions.parsing.NoInputProvidedException;
024import org.checkerframework.checker.nullness.qual.NonNull;
025import org.checkerframework.checker.nullness.qual.Nullable;
026
027import java.util.Queue;
028
029public final class OptionValueParser<C> implements ArgumentParser<C, String> {
030    private static final char ESCAPE = '\\';
031    private static final String FLAG_STARTER = "-";
032
033    OptionValueParser() {
034    }
035
036    @Override
037    public @NonNull ArgumentParseResult<@NonNull String> parse(
038        @NonNull final CommandContext<@NonNull C> commandContext,
039        @NonNull final Queue<@NonNull String> inputQueue
040    ) {
041        final @Nullable String input = inputQueue.peek();
042        if (input == null || input.startsWith(FLAG_STARTER)) {
043            return ArgumentParseResult.failure(new NoInputProvidedException(
044                PermissionValueParser.class,
045                commandContext
046            ));
047        }
048
049        final char startChar = input.charAt(0);
050
051        // If quoted string: read until a word ending with a quote
052        if (startChar == '\'' || startChar == '\"') {
053            final StringBuilder result = new StringBuilder();
054            result.append(inputQueue.remove(), 1, input.length());
055            @Nullable String next;
056            while ((next = inputQueue.peek()) != null) {
057                result.append(" ");
058                if (next.length() > 0 && next.charAt(next.length() - 1) == startChar) {
059                    // We've found the end of a quoted string, maybe
060                    // if escaped, append without escape then continue
061                    if (next.charAt(next.length() - 1) == ESCAPE) {
062                        result.append(inputQueue.remove(), 0, next.length() - 2)
063                            .append(startChar);
064                        continue;
065                    } else {
066                        result.append(inputQueue.remove(), 0, next.length() - 1);
067                        // then return our full quoted string
068                        return ArgumentParseResult.success(result.toString());
069                    }
070                }
071                result.append(inputQueue.remove());
072            }
073
074            // If we made it to the end without finding an end quote, throw an error
075            return ArgumentParseResult.failure(new CommandException(Messages.OPTION_VALUE_ERROR_QUOTE_UNTERMINATED.tr()));
076        } else { // otherwise, read until end of line, or a word starting with '-'
077            final StringBuilder result = new StringBuilder();
078            result.append(inputQueue.remove());
079            @Nullable String next;
080            while ((next = inputQueue.peek()) != null) {
081                if (next.startsWith(FLAG_STARTER)) {
082                    break;
083                }
084                result.append(" ").append(inputQueue.remove());
085            }
086
087            return ArgumentParseResult.success(result.toString());
088        }
089    }
090
091    @Override
092    public boolean isContextFree() {
093        return true;
094    }
095
096}