/* * Copyright © 2014 Cask Data, Inc. * * 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 co.cask.cdap.cli.util; import co.cask.cdap.cli.ProgramIdArgument; import co.cask.common.cli.util.Parser; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import static co.cask.common.cli.util.Parser.MANDATORY_ARG_BEGINNING; import static co.cask.common.cli.util.Parser.MANDATORY_ARG_ENDING; import static co.cask.common.cli.util.Parser.OPTIONAL_PART_BEGINNING; import static co.cask.common.cli.util.Parser.OPTIONAL_PART_ENDING; /** * Utility class for parsing arguments from user input based on command pattern. */ public class ArgumentParser { /** * Parses a map in the format: "key1=a key2=b .." * * @param mapString {@link String} representation of the map * @return the map */ public static Map<String, String> parseMap(String mapString) { if (mapString == null || mapString.isEmpty()) { return ImmutableMap.of(); } ImmutableMap.Builder<String, String> result = ImmutableMap.builder(); List<String> tokens = Parser.parseInput(mapString); for (String token : tokens) { int firstEquals = token.indexOf('='); if (firstEquals > 0) { String key = token.substring(0, firstEquals); String value = token.substring(firstEquals + 1, token.length()); result.put(extractValue(key), extractValue(value)); } } return result.build(); } /** * @param value string that may be surrounded by quotes * @return unquoted string if quoted, otherwise the original string */ private static String extractValue(String value) { if ((value.startsWith("'") && value.endsWith("'")) || (value.startsWith("\"") && value.endsWith("\""))) { return value.substring(1, value.length() - 1); } return value; } /** * Parses program id from user input. * * @param programId the program id * @return ProgramIdArgument */ public static ProgramIdArgument parseProgramId(String programId) { if (programId == null) { return null; } String[] split = programId.split("\\."); if (split.length != 2) { return null; } return new ProgramIdArgument(split[0], split[1]); } /** * Parses params from user input. * * @param input user input to be parsed * @param pattern pattern by which to parse * @return parsed params */ public static Map<String, String> getArguments(String input, String pattern) { if (input == null || pattern == null) { return Collections.emptyMap(); } input = cutNotFullyEnteredToken(input); Map<String, String> arguments = Maps.newHashMap(); List<String> patternTokens = Parser.parsePattern(pattern); List<String> inputTokens = Parser.parseInput(input); while (!patternTokens.isEmpty() && !inputTokens.isEmpty()) { String patternPart = patternTokens.get(0); String inputPart = inputTokens.get(0); if (patternPart.startsWith((Character.toString(OPTIONAL_PART_BEGINNING))) && patternPart.endsWith((Character.toString(OPTIONAL_PART_ENDING)))) { arguments.putAll(parseOptional(inputTokens, getEntry(patternPart))); } else { if (patternPart.startsWith((Character.toString(MANDATORY_ARG_BEGINNING))) && patternPart.endsWith((Character.toString(MANDATORY_ARG_ENDING)))) { arguments.put(getEntry(patternPart), tryGetInputEntry(inputPart)); } else if (!patternPart.equals(inputPart)) { return Collections.emptyMap(); } inputTokens.remove(0); } patternTokens.remove(0); } return arguments; } /** * Parses params from optional part. * * @param splitInput optional part to be parsed * @param pattern pattern by which to parse * @return parsed params */ private static Map<String, String> parseOptional(List<String> splitInput, String pattern) { ImmutableMap.Builder<String, String> args = ImmutableMap.builder(); List<String> copyInput = new ArrayList<>(splitInput); List<String> splitPattern = Parser.parsePattern(pattern); while (!splitPattern.isEmpty()) { if (copyInput.isEmpty()) { return Collections.emptyMap(); } String patternPart = splitPattern.get(0); String inputPart = tryGetInputEntry(copyInput.get(0)); if (patternPart.startsWith((Character.toString(MANDATORY_ARG_BEGINNING))) && patternPart.endsWith((Character.toString(MANDATORY_ARG_ENDING)))) { args.put(getEntry(patternPart), inputPart); } else if (patternPart.startsWith((Character.toString(OPTIONAL_PART_BEGINNING))) && patternPart.endsWith((Character.toString(OPTIONAL_PART_ENDING)))) { args.putAll(parseOptional(copyInput, getEntry(patternPart))); } else if (!patternPart.equals(inputPart)) { return Collections.emptyMap(); } splitPattern.remove(0); copyInput.remove(0); } splitInput.clear(); splitInput.addAll(copyInput); return args.build(); } /** * Cuts last not fully entered token, * where token is a word or some expression in quotes or double quotes. * * @param input the input to cut * @return cutted input */ private static String cutNotFullyEnteredToken(String input) { int index = input.lastIndexOf(" "); return input.substring(0, index == -1 ? input.length() : index); } /** * Retrieves entry from input {@link String}. * * @param input the input * @return entry {@link String} */ private static String tryGetInputEntry(String input) { if (input.startsWith("'") && input.endsWith("'") || input.startsWith("\"") && input.endsWith("\"")) { return getEntry(input); } return input; } /** * Retrieves entry from input {@link String}. * For example, for input "<some input>" returns "some input". * * @param input the input * @return entry {@link String} */ private static String getEntry(String input) { return input.substring(1, input.length() - 1); } }