/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.flex.compiler.config; import java.util.Arrays; import java.util.List; import java.util.LinkedList; import java.util.TreeSet; import java.util.Set; import java.util.Iterator; import java.util.HashSet; import java.util.Map; import java.util.HashMap; import java.io.File; import com.google.common.base.Joiner; import org.apache.flex.compiler.Messages; import org.apache.flex.compiler.exceptions.ConfigurationException; import org.apache.flex.compiler.internal.config.localization.LocalizationManager; /** * A utility class, which is used to parse an array of command line args and * populate a ConfigurationBuffer. It also contains some associated methods like * brief() and usage(). A counterpart of FileConfigurator and * SystemPropertyConfigurator. */ public class CommandLineConfigurator { public static final String SOURCE_COMMAND_LINE = "command line"; /** * parse - buffer up configuration vals from the command line * * @param buffer the configuration buffer to hold the results * @param defaultvar the variable name where the trailing loose args go * @param args the command line */ public static void parse(final ConfigurationBuffer buffer, final String defaultvar, final String[] args) throws ConfigurationException { // "no-default-arg" means the application does not have a default var. assert defaultvar == null || buffer.isValidVar(defaultvar) || "no-default-arg".equals(defaultvar) : "coding error: config must provide default var " + defaultvar; Map<String, String> aliases = getAliases(buffer); final int START = 1; final int ARGS = 2; final int EXEC = 3; final int DONE = 4; int i = 0, iStart = 0, iEnd = 0; String var = null; int varArgCount = -2; List<String> argList = new LinkedList<String>(); Set<String> vars = new HashSet<String>(); boolean append = false; boolean dash = true; int mode = START; while (mode != DONE) { switch (mode) { case START: { iStart = i; if (args.length == i) { mode = DONE; break; } // expect -var, --, or the beginning of default args mode = ARGS; varArgCount = -2; if (args[i].equals("--")) { dash = false; if (defaultvar != null) var = defaultvar; else mode = START; ++i; } else if (dash && args[i].startsWith("+")) { String token = null; int c = (args[i].length() > 1 && args[i].charAt(1) == '+') ? 2 : 1; // gnu-style? int equals = args[i].indexOf('='); String rest = null; if (equals != -1) { rest = args[i].substring(equals + 1); token = args[i++].substring(c, equals); } else { token = args[i++].substring(c); } if (equals != -1) { iEnd = i; buffer.setToken(token, rest); buffer.addPosition(token, iStart, iEnd); } else { if (i == args.length) { throw new ConfigurationException.Token(ConfigurationException.Token.INSUFFICIENT_ARGS, token, var, source, -1); } rest = args[i++]; iEnd = i; buffer.setToken(token, rest); buffer.addPosition(token, iStart, iEnd); } mode = START; break; } else if (dash && isAnArgument(args[i])) { int c = (args[i].length() > 1 && args[i].charAt(1) == '-') ? 2 : 1; // gnu-style? int plusequals = args[i].indexOf("+="); int equals = args[i].indexOf('='); String rest = null; if (plusequals != -1) { rest = args[i].substring(plusequals + 2); var = args[i++].substring(c, plusequals); append = true; } else if (equals != -1) { rest = args[i].substring(equals + 1); var = args[i++].substring(c, equals); } else { var = args[i++].substring(c); } if (aliases.containsKey(var)) var = aliases.get(var); if (!buffer.isValidVar(var)) { throw new ConfigurationException.UnknownVariable(var, source, -1); } if (equals != -1) { if ((rest == null) || (rest.length() == 0)) { varArgCount = -1; mode = EXEC; } else { String seps = null; if (buffer.getInfo(var).isPath()) { seps = "[," + File.pathSeparatorChar + "]"; } else { seps = ","; } String[] tokens = rest.split(seps); argList.addAll(Arrays.asList(tokens)); varArgCount = buffer.getVarArgCount(var); mode = EXEC; } } } else { // asdoc sets default var as no-default-arg - it has no default vars if (defaultvar != null && !defaultvar.equals("no-default-arg")) { // don't increment i, let ARGS pick it up. var = defaultvar; } else { throw new ConfigurationException.UnexpectedDefaults(null, null, -1); } } break; } case ARGS: { if (varArgCount == -2) { if (isBoolean(buffer, var)) { varArgCount = 0; mode = EXEC; break; } else { varArgCount = buffer.getVarArgCount(var); } } assert varArgCount >= -1; // just in case the getVarArgCount author was insane. if (args.length == i) { mode = EXEC; break; } boolean greedy = buffer.getInfo(var).isGreedy(); // accumulating non-command arguments... // check for a terminator on our accumulated parameter list if (!greedy && dash && isAnArgument(args[i])) { if (varArgCount == -1) { // we were accumulating an unlimited set of args, a new var terminates that. mode = EXEC; break; } throw new ConfigurationException.IncorrectArgumentCount(varArgCount, argList.size(), var, source, -1); } argList.add(args[i++]); if (argList.size() == varArgCount) { mode = EXEC; } break; } case EXEC: { if ((varArgCount != -1) && (argList.size() != varArgCount)) { throw new ConfigurationException.IncorrectArgumentCount(varArgCount, argList.size(), var, source, -1); } if (varArgCount == 0) // boolean flag fakery... argList.add("true"); if (vars.contains(var)) { if ((defaultvar != null) && var.equals(defaultvar)) { // we could perhaps accumulate the defaults spread out through // the rest of the flags, but for now we'll call this illegal. throw new ConfigurationException.InterspersedDefaults(var, source, -1); } } iEnd = i; buffer.setVar(var, new LinkedList<String>(argList), source, -1, null, append); buffer.addPosition(var, iStart, iEnd); append = false; vars.add(var); argList.clear(); mode = START; break; } case DONE: { assert false; break; } } } } /** * Given a string like "-foo" or "-5" or "-123.mxml", this determines * whether the string is an argument or... not an argument (e.g. numeral) */ private static boolean isAnArgument(final String arg) { return (arg.startsWith("-") && // if the first character after a dash is numeric, this is not // an argument, it is a parameter (and therefore non-terminating) (arg.length() > 1) && !Character.isDigit(arg.charAt(1))); } private static Map<String, String> getAliases(ConfigurationBuffer buffer) { Map<String, String> aliases = new HashMap<String, String>(); aliases.putAll(buffer.getAliases()); for (final String varname : buffer.getVars()) { if (varname.indexOf('.') == -1) continue; String leafname = varname.substring(varname.lastIndexOf('.') + 1); if (aliases.containsKey(leafname)) continue; aliases.put(leafname, varname); } return aliases; } private static boolean isBoolean(ConfigurationBuffer buffer, String var) { ConfigurationInfo info = buffer.getInfo(var); if (info.getArgCount() > 1) return false; Class<?> c = info.getArgType(0); return ((c == boolean.class) || (c == Boolean.class)); } public static String brief(String program, String defaultvar, LocalizationManager l10n, String l10nPrefix) { Map<String, Object> params = new HashMap<String, Object>(); params.put("defaultVar", defaultvar); params.put("program", program); return l10n.getLocalizedTextString(l10nPrefix + ".Brief", params); } public static String usage(String program, String defaultVar, ConfigurationBuffer cfgbuf, Set<String> keywords, LocalizationManager lmgr, String l10nPrefix) { boolean isCompc = program.contains("compc"); Map<String, String> aliases = getAliases(cfgbuf); Map<String, String> sesaila = new HashMap<String, String>(); for (Iterator<Map.Entry<String, String>> it = aliases.entrySet().iterator(); it.hasNext();) { Map.Entry<String, String> e = it.next(); sesaila.put(e.getValue(), e.getKey()); } TreeSet<String> printSet = new TreeSet<String>(); boolean all = false; boolean advanced = false; boolean details = false; boolean syntax = false; boolean printaliases = false; // figure out behavior.. Set<String> newSet = new HashSet<String>(); for (Iterator<String> kit = keywords.iterator(); kit.hasNext();) { String keyword = kit.next(); if (keyword.equals("list")) { all = true; newSet.add("*"); } else if (keyword.equals("advanced")) { advanced = true; if (keywords.size() == 1) { all = true; newSet.add("*"); } } else if (keyword.equals("details")) { details = true; } else if (keyword.equals("syntax")) { syntax = true; } else if (keyword.equals("aliases")) { printaliases = true; } else { details = true; newSet.add(keyword); } } if (syntax) { final List<String> lines = ConfigurationBuffer.formatText(getSyntaxDescription(program, defaultVar, advanced, lmgr, l10nPrefix), 78); return Joiner.on("\n").join(lines); } keywords = newSet; // accumulate set to print for (Iterator<String> kit = keywords.iterator(); kit.hasNext();) { String keyword = kit.next().toLowerCase(); for (final String var : cfgbuf.getVars()) { ConfigurationInfo info = cfgbuf.getInfo(var); // If the client is not "compc", skip "compc-only" options. if (info.isCompcOnly && !isCompc) continue; String description = getDescription(cfgbuf, var, lmgr, l10nPrefix); if ((all || (var.indexOf(keyword) != -1) || ((description != null) && (description.toLowerCase().indexOf(keyword) != -1)) || (keyword.matches(var)) || ((sesaila.get(var) != null) && (sesaila.get(var)).indexOf(keyword) != -1)) && (!info.isHidden()) && (!info.isRemoved()) && (advanced || !info.isAdvanced())) { if (printaliases && sesaila.containsKey(var)) printSet.add(sesaila.get(var)); else printSet.add(var); } else { /* * for (int i = 0; i < info.getAliases().length; ++i) { * String alias = info.getAliases()[i]; if (alias.indexOf( * keyword ) != -1) { printSet.add( var ); } } */ } } } StringBuilder output = new StringBuilder(1024); if (printSet.size() == 0) { String nkm = lmgr.getLocalizedTextString(l10nPrefix + ".NoKeywordsMatched"); output.append(nkm); output.append("\n"); } else for (Iterator<String> it = printSet.iterator(); it.hasNext();) { String avar = it.next(); String var = avar; if (aliases.containsKey(avar)) var = aliases.get(avar); ConfigurationInfo info = cfgbuf.getInfo(var); assert info != null; output.append("-"); output.append(avar); int count = cfgbuf.getVarArgCount(var); if ((count >= 1) && (!isBoolean(cfgbuf, var))) { for (int i = 0; i < count; ++i) { output.append(" <"); output.append(cfgbuf.getVarArgName(var, i)); output.append(">"); } } else if (count == -1) { String last = ""; for (int i = 0; i < 5; ++i) { String argname = cfgbuf.getVarArgName(var, i); if (!argname.equals(last)) { output.append(" ["); output.append(argname); output.append("]"); last = argname; } else { output.append(" [...]"); break; } } } output.append("\n"); if (details) { StringBuilder description = new StringBuilder(160); if (printaliases) { if (aliases.containsKey(avar)) { String fullname = lmgr.getLocalizedTextString(l10nPrefix + ".FullName"); description.append(fullname); description.append(" -"); description.append(aliases.get(avar)); description.append("\n"); } } else if (sesaila.containsKey(var)) { String alias = lmgr.getLocalizedTextString(l10nPrefix + ".Alias"); description.append(alias); description.append(" -"); description.append(sesaila.get(var)); description.append("\n"); } String d = getDescription(cfgbuf, var, lmgr, l10nPrefix); if (var.equals("help") && (printSet.size() > 2)) { String helpKeywords = lmgr.getLocalizedTextString(l10nPrefix + ".HelpKeywords"); description.append(helpKeywords); } else if (d != null) description.append(d); String flags = ""; if (info.isAdvanced()) { String advancedString = lmgr.getLocalizedTextString(l10nPrefix + ".Advanced"); flags += (((flags.length() == 0) ? " (" : ", ") + advancedString); } if (info.allowMultiple()) { String repeatableString = lmgr.getLocalizedTextString(l10nPrefix + ".Repeatable"); flags += (((flags.length() == 0) ? " (" : ", ") + repeatableString); } if ((defaultVar != null) && var.equals(defaultVar)) { String defaultString = lmgr.getLocalizedTextString(l10nPrefix + ".Default"); flags += (((flags.length() == 0) ? " (" : ", ") + defaultString); } if (info.isFlexOnly()) { String flexOnlylString = Messages.getString("FlexOnly"); flags += (((flags.length() == 0) ? " (" : ", ") + flexOnlylString); } if (flags.length() != 0) { flags += ")"; } description.append(flags); List<String> descriptionLines = ConfigurationBuffer.formatText(description.toString(), 70); for (final String next : descriptionLines) { output.append(" "); output.append(next); output.append("\n"); } } } return output.toString(); } public static String getDescription(ConfigurationBuffer buffer, String var, LocalizationManager l10n, String l10nPrefix) { String key = (l10nPrefix == null) ? var : (l10nPrefix + "." + var); String description = l10n.getLocalizedTextString(key, null); return description; } public static String getSyntaxDescription(String program, String defaultVar, boolean advanced, LocalizationManager l10n, String l10nPrefix) { Map<String, Object> params = new HashMap<String, Object>(); params.put("defaultVar", defaultVar); params.put("program", program); String key = l10nPrefix + "." + (advanced ? "AdvancedSyntax" : "Syntax"); String text = l10n.getLocalizedTextString(key, params); if (text == null) { text = "No syntax help available, try '-help list' to list available configuration variables."; assert false : "Localized text for syntax description not found!"; } return text; } public static final String source = SOURCE_COMMAND_LINE; }