public bool ParseAndContinue(string[] args) { string err = ""; try { object[] attribs = GetType().GetCustomAttributes(typeof(ParserUsageAttribute), true); ParserUsageAttribute parser = (ParserUsageAttribute)(attribs.Length != 0 ? attribs[0] : null); //TODO relying on exception being generated last (i.e. after values have been read and assigned) should pass param //Load defaults from the environmental variable if (parser != null) { if ("" != parser.EnvironmentDefaults) { try{ Parse(Environment.GetEnvironmentVariable(parser.EnvironmentDefaults)); }catch {} } } //-------------------------------------------------------- //Parse the command line Parse(args); } catch (Exception e) { err = e.Message; } return(Continue(err)); }
void Parse(string[] args, bool ignoreFirstArg) { object[] attribs = GetType().GetCustomAttributes(typeof(ParserUsageAttribute), true); ParserUsageAttribute parser = (ParserUsageAttribute)(attribs.Length != 0 ? attribs[0] : null); bool allowArgFile = (parser != null ? parser.AllowArgumentFile : true); MemberInfoUsage[] members = GetMembers(); for (int i = (ignoreFirstArg ? 1 : 0); i != args.Length; ++i) { string arg = args[i]; Debug.WriteLine("Processing arg: " + arg); MemberInfoUsage member = null; bool isFlag = false; // It's a flag if ((arg.Length > 1) && ((arg[0] == '/') || (arg[0] == '-'))) { bool hasOnOff; string flagName; // Flags can have a '+' or '-' suffix. If this arg has a prefix remove it, // else just remove the '/' or '-' hasOnOff = arg.EndsWith("-") || arg.EndsWith("+"); if (hasOnOff) { flagName = arg.Substring(1, arg.Length - 2); } else { flagName = arg.Substring(1); } // Flags can be passed in one of two ways // a) With a param name in 1 arg and a value in the next. e.g. "/flag myValue" // b) Using a delimiter, passed as a single argument. e.g. "/flag:myValue" // If using the single arg (a) method extract the arg name int delimiterPos = FindValueInlineDeliminter(flagName); //default delimiter. A space which is not a delimiter (space delimted values are split into seperate args) // will thus have a delimiter value of '\0' char delimiter = '\0'; if (delimiterPos != -1) { delimiter = flagName[delimiterPos]; flagName = flagName.Substring(0, delimiterPos); } // Find the argument by name member = FindFlag(flagName); if (member != null) { isFlag = true; //OnOff toggle only allowed if: member.usage is FlagUsage and FlagUsage.AllowOnOff is true if (hasOnOff) { FlagUsageAttribute flag = member.usage as FlagUsageAttribute; if (null == flag) { throw new UsageException(arg, "Only flags support on/off toggles"); } else if (!flag.AllowOnOff) { throw new UsageException(arg, "Flag does not allow on/off toggle"); } } //Check that only value args have a delimiter and that the correct delimiter has been used if (member.usage is ValueUsageAttribute) { ValueUsageAttribute val = (ValueUsageAttribute)member.usage; switch (delimiter) { case ':': if (0 == (val.Delimiters & ValueDelimiters.Colon)) { throw new UsageException(arg, "This value param does not support a ':' delimiter"); } break; case '=': if (0 == (val.Delimiters & ValueDelimiters.Equals)) { throw new UsageException(arg, "This value param does not support a '=' delimiter"); } break; case '\0': if (0 == (val.Delimiters & ValueDelimiters.Space)) { throw new UsageException(arg, "This value param does not support a ' ' delimiter"); } break; default: throw new UsageException(arg, "Unknown delimiter"); } } else if ('\0' != delimiter) { throw new UsageException(arg, "Only value params support delimiters"); } } } // It's a file name to process parameters from else if ((arg.Length > 1) && (arg[0] == '@') && allowArgFile) { ParseFromFile(arg.Substring(1)); continue; } // It's a parameter else { // Find the argument by offset member = GetNextParam(); } if (member == null) { throw new UsageException(arg, "Unrecognized argument"); } // Argument with a value, e.g. /foo bar if (member.usage.ExpectsValue) { string value; // If the arg has an inline delimiter (e.g. "/flag:myValue") // then read the value from the current arg else get the value from the next arg int delimiterPos = FindValueInlineDeliminter(arg); if (delimiterPos == -1) { if (isFlag && (++i == args.Length)) { throw new UsageException(arg, "Argument expects a parameter"); } value = args[i]; } else { if ((arg.Length - 1) == delimiterPos) { throw new UsageException(arg, "Argument expects a paramter"); } value = arg.Substring(delimiterPos + 1); } member.usage.ConsumeValue(value, this, member.info); } // Argument w/o a value, e.g. /foo else { string value; if (isFlag && arg.EndsWith("-")) { value = "false"; } else { value = "true"; } member.usage.ConsumeValue(value, this, member.info); } } // Check for missing required arguments foreach (MemberInfoUsage member in members) { if (!member.usage.Optional && !member.usage.IsFound) { throw new UsageException(member.info.Name, "Required argument not found"); } } }
public string GetUsage(string err) { StringBuilder usage = new StringBuilder(); // Logo string logo = GetLogo(true); if (logo.Length != 0) { usage.Append(logo).Append(Environment.NewLine); } // Parser prefs, e.g. preferred prefix object[] attribs = GetType().GetCustomAttributes(typeof(ParserUsageAttribute), true); ParserUsageAttribute parser = (ParserUsageAttribute)(attribs.Length != 0 ? attribs[0] : null); string preferredPrefix = (parser != null ? parser.PreferredPrefix : "/"); bool allowArgFile = (parser != null ? parser.AllowArgumentFile : true); // Error string if (err.Length != 0) { usage.Append(err).Append(Environment.NewLine).Append(Environment.NewLine); } // Short (name and value name only) StringBuilder shortUsage = new StringBuilder(); shortUsage.Append("Usage: ").Append(GetModuleName()).Append(" "); // Long (name and description only) StringBuilder longUsage = new StringBuilder(); // TODO there must be a better way of doing this than looping through the MemberInfo... // Find the right-most tab char. int maxTabPos = 0; foreach (MemberInfoUsage member in GetMembers()) { string prefix = (member.usage.MatchPosition ? "" : preferredPrefix); string tmpLongUsage = member.usage.GetLongUsage(prefix, member.info.Name, this, member.info); maxTabPos = Math.Max(maxTabPos, tmpLongUsage.IndexOf("\t")); } // There should be 2 chars after the longest usage, before its description maxTabPos += 2; // Max length for the descriptions int maxLen = ((null != parser) ? parser.MaxHelpLineLength : 79) - maxTabPos; if (allowArgFile) { shortUsage.Append("[@argfile]"); //TODO format line using FormatSingleLineMaxChars longUsage.AppendFormat("{0,-" + maxTabPos + ":S}{1}", "@argfile", "Read arguments from a file.").Append(Environment.NewLine); } //Regex to split line into usage and description Regex regexUsage = new Regex(@"(?s)^(?<usage>[^\t]+)(\t)(?<desc>.*)$"); string lastCategory = ""; foreach (MemberInfoUsage member in GetMembers()) { // NOTE: When matching by position, only the value will be present // on the commandline, e.g. "fooness" string prefix = (member.usage.MatchPosition ? "" : preferredPrefix); shortUsage.Append(" ").Append(member.usage.GetShortUsage(prefix, member.info.Name, this, member.info)); // Get the long usage, which must be of the format usage\tdescription // Where // 1) usage may not contain any tabs // 2) description can be any length and may contain \n or \r\n chars to indicate a newline string tmpLongUsage = member.usage.GetLongUsage(prefix, member.info.Name, this, member.info); // Replace all \r chars Match m = regexUsage.Match(tmpLongUsage); if (null != m) { // If categories are being displayed and the category has changed then display the category // An empty category is not concidered to be a category change if ((null != parser) && (parser.ShowCategories) && (0 != member.usage.Category.Length) && (member.usage.Category != lastCategory)) { longUsage.Append(Environment.NewLine).AppendFormat("{0,-" + maxTabPos + ":S}- {1} -", "", member.usage.Category).Append(Environment.NewLine); lastCategory = member.usage.Category; } // Usage longUsage.AppendFormat("{0,-" + maxTabPos + ":S}", m.Groups["usage"].Value); // Format the string to fit the max line length string[] formattedLines = FormatMultiLineMaxChars(maxLen, m.Groups["desc"].Value); if (formattedLines.Length > 0) { longUsage.Append(formattedLines[0]).Append(Environment.NewLine); } else { longUsage.Append(Environment.NewLine); } for (int g = 1; g < formattedLines.Length; ++g) { longUsage.AppendFormat("{0,-" + maxTabPos + ":S}{1}", "", formattedLines[g]).Append(Environment.NewLine); } } } //Format the short usage string[] shortLines = FormatMultiLineMaxChars(((null != parser) ? parser.MaxHelpLineLength : 79), shortUsage.ToString()); shortUsage.Length = 0; if (shortLines.Length > 0) { shortUsage.Append(shortLines[0]).Append(Environment.NewLine); } else { shortUsage.Append(Environment.NewLine); } //subsequent short usage lines to align up under "Usage: " string for (int g = 1; g < shortLines.Length; ++g) { shortUsage.AppendFormat("{0,-7:S}{1}", "", shortLines[g]).Append(Environment.NewLine); } usage.Append(shortUsage).Append(Environment.NewLine).Append(Environment.NewLine).Append(longUsage).Append(Environment.NewLine); if ((null != parser) && ("" != parser.EnvironmentDefaults)) { usage.Append(Environment.NewLine); usage.AppendFormat("Switches may be preset in the {0} environment variable.", parser.EnvironmentDefaults).Append(Environment.NewLine); usage.Append("Override preset switches by prefixing switches with a - (hyphen)").Append(Environment.NewLine); usage.Append(" for example, /W-").Append(Environment.NewLine); } return(usage.ToString()); }
protected virtual string GetLogo(bool includeDescription) { // TODO: Refactor this ugly code! StringBuilder logo = new StringBuilder(); string nl = Environment.NewLine; object[] attribs = null; Assembly assem = Assembly.GetEntryAssembly(); if (null == assem) { assem = System.Reflection.Assembly.GetExecutingAssembly(); } AssemblyName assemName = assem.GetName(); // Title: try AssemblyTitle first, use module name if AssemblyTitle is missing attribs = assem.GetCustomAttributes(typeof(AssemblyTitleAttribute), true); string title = (attribs.Length != 0 ? ((AssemblyTitleAttribute)attribs[0]).Title : ""); if (title.Length == 0) { title = GetModuleName(); } // Version string version = assem.GetName().Version.ToString(); // Copyright attribs = assem.GetCustomAttributes(typeof(AssemblyCopyrightAttribute), true); string copyright = (attribs.Length != 0 ? ((AssemblyCopyrightAttribute)attribs[0]).Copyright : ""); // Description: Try ParserUsage.Description first, use AssemblyDescription otherwise string description = ""; attribs = GetType().GetCustomAttributes(typeof(ParserUsageAttribute), true); ParserUsageAttribute parser = (ParserUsageAttribute)(attribs.Length != 0 ? attribs[0] : null); if ((parser != null) && (parser.Description != null) && (parser.Description.Length != 0)) { description = parser.Description; } else { attribs = assem.GetCustomAttributes(typeof(AssemblyDescriptionAttribute), true); description = (attribs.Length != 0 ? ((AssemblyDescriptionAttribute)attribs[0]).Description : ""); } // Layout logo.Append(title).Append(" v").Append(version).Append(nl); if (copyright.Length != 0) { logo.Append(copyright).Append(nl); } if (!string.IsNullOrEmpty(description) && includeDescription) { logo.Append(description).Append(nl); } return(logo.ToString()); }