package org.dcache.util; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultiset; import com.google.common.collect.Iterables; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimaps; import java.io.Serializable; import java.util.Map; import java.util.NoSuchElementException; import static com.google.common.base.Predicates.in; import static com.google.common.base.Predicates.not; import static java.util.Arrays.asList; /** * Argument parser. * * Supports single and multi character options, although both start * with a single hyphen. Options may have an optional value using the * equal sign to separate key and value. * * Supports single and double quoted valus and uses backslash as an * escape symbol. * * An optional double hyphen argument separator separates options from * arguments. Any argument following the separator will not be * interpreted as an option. * * Option and argument order is preserved with some limitations: * Option and argument interleaving is not preserved. Repeated options * are preserved, including the order of their values, however all * values appear at the place of the first use of that option. */ public class Args implements Serializable { private static final long serialVersionUID = 4389995682226525641L; private final ImmutableListMultimap<String,String> _options; private final String _oneChar; private ImmutableList<String> _arguments; public Args(CharSequence args) { Scanner scanner = new Scanner(); scanner.scan(args, false); _options = scanner.options.build(); _arguments = scanner.arguments.build(); _oneChar = scanner.oneChar.toString(); } public Args(String[] args) { this(asList(args)); } public Args(Iterable<String> args) { Scanner scanner = new Scanner(); for (String arg : args) { if (arg.isEmpty()) { scanner.arguments.add(""); } else { scanner.scan(arg, true); } } _options = scanner.options.build(); _arguments = scanner.arguments.build(); _oneChar = scanner.oneChar.toString(); } public Args(Args in) { _arguments = in._arguments; _options = in._options; _oneChar = in._oneChar; } private Args(ImmutableList<String> arguments, ImmutableListMultimap<String,String> options, String oneChar) { _arguments = arguments; _options = options; _oneChar = oneChar; } public static CharSequence quote(String raw) { if (raw.isEmpty()) { return "''"; } else { StringBuilder sb = new StringBuilder(); quote(raw, sb); return sb; } } public Args removeOptions(String... names) { ListMultimap<String,String> view = Multimaps.filterKeys(_options, not(in(asList(names)))); return new Args(_arguments, ImmutableListMultimap.copyOf(view), _oneChar); } public boolean isOneCharOption(char c) { return _oneChar.indexOf(c) > -1; } public int argc() { return _arguments.size(); } public int optc() { return _options.size(); } public String getOpt(String name) { return getOption(name); } public double getDoubleOption(String name) { String option = getOption(name); if (option == null) { throw new NoSuchElementException("Option " + name + " does not exist."); } return Double.parseDouble(option); } public double getDoubleOption(String name, double defaultValue) { String option = getOption(name); if (option == null) { return defaultValue; } else if (option.isEmpty()) { throw new IllegalArgumentException("Option " + name + " does not have a value."); } else { return Double.parseDouble(option); } } public int getIntOption(String name) { String option = getOption(name); if (option == null) { throw new NoSuchElementException("Option " + name + " does not exist."); } return Integer.parseInt(option); } public int getIntOption(String name, int defaultValue) { String option = getOption(name); if (option == null) { return defaultValue; } else if (option.isEmpty()) { throw new IllegalArgumentException("Option " + name + " does not have a value."); } else { return Integer.parseInt(option); } } public long getLongOption(String name) { String option = getOption(name); if (option == null) { throw new NoSuchElementException("Option " + name + " does not exist."); } return Long.parseLong(option); } public long getLongOption(String name, long defaultValue) { String option = getOption(name); if (option == null) { return defaultValue; } else if (option.isEmpty()) { throw new IllegalArgumentException("Option " + name + " does not have a value."); } else { return Long.parseLong(option); } } public boolean getBooleanOption(String name) { return getBooleanOption(name, false); } public boolean getBooleanOption(String name, boolean defaultValue) { String option = getOption(name); return (option == null) ? defaultValue : (option.isEmpty() || Boolean.parseBoolean(option)); } public String getOption(String name) { ImmutableList<String> values = _options.get(name); return values.isEmpty() ? null : values.get(values.size() - 1); } public String getOption(String name, String defaultValue) { ImmutableList<String> values = _options.get(name); return values.isEmpty() ? defaultValue : values.get(values.size() - 1); } public boolean hasOption(String name) { return !_options.get(name).isEmpty(); } public ImmutableList<String> getOptions(String name) { return _options.get(name); } public String argv(int i) { return (i < _arguments.size()) ? _arguments.get(i) : null; } public ImmutableList<String> getArguments() { return _arguments; } public String optv(int i) { ImmutableMultiset<String> keys = _options.keys(); return (i < keys.size()) ? keys.asList().get(i) : null; } public void shift() { if (!_arguments.isEmpty()) { _arguments = _arguments.subList(1, _arguments.size()); } } public void shift(int n) { _arguments = _arguments.subList(Math.min(n, _arguments.size()), _arguments.size()); } public ImmutableListMultimap<String,String> options() { return _options; } public ImmutableMap<String,String> optionsAsMap() { ImmutableMap.Builder<String,String> builder = ImmutableMap.builder(); for (Map.Entry<String,String> e: _options.entries()) { builder.put(e.getKey(), e.getValue()); } return builder.build(); } @Override public boolean equals(Object other) { if (other == this) { return true; } if (!(other instanceof Args)) { return false; } Args args = (Args) other; return args._options.equals(_options) && args._arguments.equals(_arguments) && args._oneChar.equals(_oneChar); } @Override public int hashCode() { return Objects.hashCode(_options, _arguments); } private static void quote(String in, StringBuilder out) { for (int i = 0; i < in.length(); i++) { switch (in.charAt(i)) { case '\\': out.append("\\\\"); break; case '"': out.append("\\\""); break; case '\'': out.append("\\'"); break; case '=': out.append("\\="); break; case '-': out.append("\\-"); break; case ' ': out.append("\\ "); break; default: out.append(in.charAt(i)); break; } } } @Override public String toString() { StringBuilder s = new StringBuilder(); for (Map.Entry<String,String> e: _options.entries()) { String key = e.getKey(); String value = e.getValue(); s.append('-'); quote(key, s); if (!value.isEmpty()) { s.append('='); quote(value, s); } s.append(' '); } if (s.length() > 0) { s.append("-- "); } Joiner.on(' ').appendTo(s, Iterables.transform(_arguments, new Function<String, CharSequence>() { @Override public CharSequence apply(String s) { return quote(s); } })); return s.toString(); } public String getInfo() { StringBuilder sb = new StringBuilder(); sb.append("Positional :\n"); for (int i = 0; i < _arguments.size(); i++) { sb.append(i).append(" -> ").append(_arguments.get(i)).append('\n'); } sb.append("Options :\n"); for (Map.Entry<String,String> option: _options.entries()) { sb.append(option.getKey()); if (option.getValue() != null) { sb.append(" -> ").append(option.getValue()); } sb.append('\n'); } return sb.toString(); } public static void main( String [] args ) { if( args.length < 1 ){ System.err.println( "Usage : ... <parseString>" ) ; System.exit(4); } Args lineArgs; if( args.length == 1 ) { lineArgs = new Args(args[0]); } else { lineArgs = new Args(args); } System.out.print( lineArgs.getInfo() ) ; System.out.println( "pvr="+lineArgs.getOpt( "pvr" ) ) ; } /** * Scanner for parsing strings of white space separated * words. Characters may be escaped with a backslash and character * sequences may be quoted. Options begin with an unescaped dash. * A -- signals the end of options and disables further option * processing. Any arguments after the -- are treated as regular * arguments. */ private static class Scanner { final ImmutableListMultimap.Builder<String,String> options = new ImmutableListMultimap.Builder<>(); final ImmutableList.Builder<String> arguments = new ImmutableList.Builder<>(); final StringBuilder oneChar = new StringBuilder(); private CharSequence line; private int position; private boolean isAtEndOfOptions; private boolean shouldIgnoreWhitespace; public Scanner() { } private char peek() { return isEof() ? (char) 0 : line.charAt(position); } private char readChar() { char c = peek(); position++; return c; } private boolean isEof() { return (position >= line.length()); } private boolean isWhitespace() { return !shouldIgnoreWhitespace && Character.isWhitespace(peek()); } private void scanWhitespace() { while (isWhitespace()) { readChar(); } } public void scan(CharSequence line, boolean shouldIgnoreWhitespace) { this.line = line; this.shouldIgnoreWhitespace = shouldIgnoreWhitespace; position = 0; scanWhitespace(); while (!isEof()) { if (!isAtEndOfOptions && peek() == '-') { readChar(); String key = scanKey(); if (key.isEmpty()) { arguments.add("-"); } else if (peek() == '=') { readChar(); options.put(key, scanWord()); } else if (key.equals("-")) { isAtEndOfOptions = true; } else { options.put(key, ""); oneChar.append(key); } } else { arguments.add(scanWord()); } scanWhitespace(); } } /** * Scans an option key. An option key is terminated by an * unescaped white space character or - for non-empty keys - * by an unescaped equal sign. */ private String scanKey() { StringBuilder key = new StringBuilder(); do { scanWordElement(key); } while (!isEof() && !isWhitespace() && peek() != '='); return key.toString(); } /** * Scans the next word. A word is a sequence of non-white * space characters and escaped or quoted white space * characters. The unescaped and unquoted word is returned. */ private String scanWord() { StringBuilder word = new StringBuilder(); while (!isEof() && !isWhitespace()) { scanWordElement(word); } return word.toString(); } /** * Scans the next element of a word. Elements of a word are * non-white space characters, escaped characters and quoted * strings. The unescaped and unquoted element is added to word. */ private void scanWordElement(StringBuilder word) { if (!isEof() && !isWhitespace()) { switch (peek()) { case '\'': scanSingleQuotedString(word); break; case '"': scanDoubleQuotedString(word); break; case '\\': scanEscapedCharacter(word); break; default: word.append(readChar()); break; } } } /** * Scans a single quoted string. Escaped characters are not * recognized. The unquoted string is added to word. */ private void scanSingleQuotedString(StringBuilder word) { if (readChar() != '\'') { throw new IllegalStateException("Parse failure"); } while (!isEof()) { char c = readChar(); switch (c) { case '\'': return; default: word.append(c); break; } } } /** * Scans a double quoted string. Escaped characters are * recognized. The unquoted and unescaped string is added to * word. */ private void scanDoubleQuotedString(StringBuilder word) { if (readChar() != '"') { throw new IllegalStateException("Parse failure"); } while (!isEof()) { switch (peek()) { case '\\': scanEscapedCharacter(word); break; case '"': readChar(); return; default: word.append(readChar()); break; } } } /** * Scans a backslash escaped character. The escaped character * without the escape symbol is added to word. */ private void scanEscapedCharacter(StringBuilder word) { if (readChar() != '\\') { throw new IllegalStateException("Parse failure"); } if (!isEof()) { char c = readChar(); switch (c) { case '\'': case '"': case ' ': case '-': case '=': case '\\': word.append(c); break; default: word.append('\\').append(c); break; } } } } }