package org.jetbrains.ether; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Created by IntelliJ IDEA. * User: db * Date: 21.11.10 * Time: 14:33 * To change this template use File | Settings | File Templates. */ public class Options { public enum ArgumentSpecifier { MANDATORY, OPTIONAL, NONE; } public abstract interface Argument { } public static class Switch implements Argument { } private static Switch SWITCH = new Switch(); public static class Value implements Argument { private final String myValue; Value (final String s) { myValue = s; } public String get () { return myValue; } } private class Cursor { int myPos; final int myLength; final String[] myArgs; Cursor (final String[] args) { myArgs = args; myPos = 0; myLength = myArgs.length; } public String look () { if (myPos >= myLength) return null; return myArgs [myPos]; } public void shift () { myPos++; } public String lookahead () { final String arg = myArgs[myPos+1]; myPos += 2; return arg; } public boolean nextIsArg () { return (myPos < myLength - 1 && ! myArgs[myPos+1].startsWith("-")); } public boolean endOf () { return myPos >= myLength; } } private interface Callback { void update (String key, Argument value); void addFree (String value); void report (String note); } public static class Descriptor { private final ArgumentSpecifier myArgumentSpecifier; private final String myKey; private final String myLong; private final String myShort; private final String myMemo; private final Pattern myLongPattern; Descriptor (final String key, final String longForm, final String shortForm, final ArgumentSpecifier as, final String memo) { myArgumentSpecifier = as; myKey = key; myLong = longForm; myShort = shortForm; myMemo = memo; myLongPattern = myLong == null ? null : Pattern.compile("^--" + myLong + "(=(.*))?$"); } public String memo () { final StringBuilder buf = new StringBuilder(); String longArg = "", shortArg = ""; switch (myArgumentSpecifier) { case OPTIONAL: longArg = "[=<argument>]"; shortArg = " [<argument>]"; break; case NONE: longArg = ""; shortArg = ""; break; case MANDATORY: longArg = "=<argument>"; shortArg = " <argument>"; break; } buf.append(" --" + myLong + longArg + ", -" + myShort + shortArg + "\n"); buf.append(" " + myMemo + "\n"); return buf.toString(); } public boolean proceed (final Cursor cursor, final Callback callback) { if (cursor.endOf()) return false; final String arg = cursor.look(); if (! arg.startsWith("-")) { callback.addFree(arg); cursor.shift(); return true; } if (myShort != null && arg.equals("-" + myShort)) { switch (myArgumentSpecifier) { case NONE: callback.update(myKey, SWITCH); break; default: if (cursor.nextIsArg()) callback.update(myKey, new Value(cursor.lookahead())); else switch (myArgumentSpecifier) { case OPTIONAL: callback.update(myKey, SWITCH); break; case MANDATORY: callback.report("option \"" + arg + "\" requires an argument, discarding."); } }; cursor.shift(); return true; } final Matcher m = myLongPattern.matcher(arg); if (myLong != null && m.matches()) { final String prm = m.group(2); switch (myArgumentSpecifier) { case MANDATORY: if (prm == null) callback.report("option \"" + arg + "\" requires an argument, discarding."); else callback.update(myKey, new Value (prm)); break; case NONE: if (prm == null) callback.update(myKey, SWITCH); else callback.report("option \"" + arg + "\" does not take an argument, omitting."); break; case OPTIONAL: callback.update(myKey, prm == null ? SWITCH : new Value(prm)); break; } cursor.shift(); return true; } return false; } } private Map<String, Argument> myOptions = new HashMap<String, Argument> (); private List<String> myFree; private final Descriptor[] myDescriptors; Options (final Descriptor[] descrs) { myDescriptors = descrs; } public List<String> parse (final String[] args) { myOptions.clear(); myFree = new ArrayList<String> (); final List<String> notes = new ArrayList<String> (); final Callback cb = new Callback() { public void addFree (final String value) { myFree.add(value); } public void update (final String key, final Argument value) { myOptions.put(key, value); } public void report (final String note) { notes.add(note); } }; final Cursor cursor = new Cursor(args); while (!cursor.endOf()) { boolean recognized = false; for (int i = 0; i<myDescriptors.length; i++) recognized |= myDescriptors[i].proceed(cursor, cb); if (!recognized) { cb.report("unrecognized option \"" + cursor.look() + "\" omitted."); cursor.shift(); } } return notes; } public Argument get (final String name) { return myOptions.get(name); } public List<String> getFree () { return myFree; } public String memo () { final StringBuilder buf = new StringBuilder(); for (final Descriptor descriptor : myDescriptors) { buf.append(descriptor.memo()).append("\n"); } return buf.toString(); } }