Coverage Summary for Class: CliArgs (co.rsk.cli)

Class Method, % Line, %
CliArgs 33.3% (2/6) 69.2% (9/13)
CliArgs$Parser 0% (0/9) 0% (0/39)
Total 13.3% (2/15) 17.3% (9/52)


1 /* 2  * This file is part of RskJ 3  * Copyright (C) 2018 RSK Labs Ltd. 4  * 5  * This program is free software: you can redistribute it and/or modify 6  * it under the terms of the GNU Lesser General Public License as published by 7  * the Free Software Foundation, either version 3 of the License, or 8  * (at your option) any later version. 9  * 10  * This program is distributed in the hope that it will be useful, 11  * but WITHOUT ANY WARRANTY; without even the implied warranty of 12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13  * GNU Lesser General Public License for more details. 14  * 15  * You should have received a copy of the GNU Lesser General Public License 16  * along with this program. If not, see <http://www.gnu.org/licenses/>. 17  */ 18 package co.rsk.cli; 19  20 import java.util.*; 21 import java.util.stream.Collectors; 22  23 /** 24  * A simple representation of command line arguments, broken into "options", "flags" and "arguments". 25  */ 26 public class CliArgs<O, F> { 27  28  private final List<String> arguments; 29  private final Map<O, String> options; 30  private final Set<F> flags; 31  32  private CliArgs(List<String> arguments, Map<O, String> options, Set<F> flags) { 33  this.arguments = Collections.unmodifiableList(arguments); 34  this.options = Collections.unmodifiableMap(options); 35  this.flags = Collections.unmodifiableSet(flags); 36  } 37  38  public static <O, F> CliArgs<O, F> empty() { 39  return new CliArgs<>( 40  Collections.emptyList(), 41  Collections.emptyMap(), 42  Collections.emptySet() 43  ); 44  } 45  46  public List<String> getArguments() { 47  return arguments; 48  } 49  50  public Map<O, String> getOptions() { 51  return options; 52  } 53  54  public Set<F> getFlags() { 55  return flags; 56  } 57  58  /** 59  * Parses a {@code String[]} of command line arguments in order to populate a 60  * {@link CliArgs} object. 61  * 62  * <h3>Working with option arguments</h3> 63  * Option arguments must adhere to the exact syntax: 64  * <pre class="code">-optName optValue</pre> 65  * <pre class="code">--flagName</pre> 66  * That is, options must be prefixed with "{@code -}", and must specify a value, 67  * and flags must be prefixed with "{@code --}", and may not specify a value. 68  */ 69  public static class Parser<O extends Enum<O> & OptionalizableCliArg, F extends Enum<F> & CliArg> { 70  71  private final EnumSet<O> options; 72  private final EnumSet<F> flags; 73  74  public Parser(Class<O> optionsClass, Class<F> flagsClass) { 75  this.options = EnumSet.allOf(optionsClass); 76  this.flags = EnumSet.allOf(flagsClass); 77  } 78  79  public CliArgs<O, F> parse(String[] args) { 80  List<String> arguments = new LinkedList<>(); 81  Map<O, String> options = new HashMap<>(); 82  Set<F> flags = new HashSet<>(); 83  84  for (int i = 0; i < args.length; i++) { 85  switch (args[i].charAt(0)) { 86  case '-': 87  if (args[i].length() < 2) { 88  throw new IllegalArgumentException("You must provide an option name, e.g. -d"); 89  } 90  if (args[i].charAt(1) == '-') { 91  if (args[i].length() < 3) { 92  throw new IllegalArgumentException("You must provide a flag name, e.g. --quiet"); 93  } 94  flags.add(getFlagByName(args[i].substring(2, args[i].length()))); 95  } else { 96  if (args.length - 1 == i) { 97  throw new IllegalArgumentException( 98  String.format("A value must be provided after the option -%s", args[i]) 99  ); 100  } 101  options.put(getOptionByName(args[i].substring(1, args[i].length())), args[i + 1]); 102  i++; 103  } 104  break; 105  default: 106  arguments.add(args[i]); 107  break; 108  } 109  } 110  111  Set<O> missingOptions = this.options.stream() 112  .filter(arg -> !arg.isOptional()) 113  .collect(Collectors.toSet()); 114  missingOptions.removeAll(options.keySet()); 115  if (!missingOptions.isEmpty()) { 116  throw new IllegalArgumentException( 117  String.format("Missing configuration options: %s", missingOptions) 118  ); 119  } 120  121  return new CliArgs<>(arguments, options, flags); 122  } 123  124  private F getFlagByName(String flagName) { 125  return flags.stream() 126  .filter(flag -> flag.getName().equals(flagName)) 127  .findFirst() 128  .orElseThrow( 129  () -> new NoSuchElementException(String.format("--%s is not a valid flag", flagName)) 130  ); 131  } 132  133  private O getOptionByName(String optionName) { 134  return options.stream() 135  .filter(opt -> opt.getName().equals(optionName)) 136  .findFirst() 137  .orElseThrow( 138  () -> new NoSuchElementException(String.format("-%s is not a valid option", optionName)) 139  ); 140  } 141  } 142 }