//////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: Parameter.cs // // Note: /////////////////////////////////////////////////////////////////////////////// using System; using System.Text; using System.Collections; using Microsoft.SingSharp; using Microsoft.Singularity; using Microsoft.Singularity.Xml; using Microsoft.Singularity.V1.Services; using System.Globalization; namespace Microsoft.Singularity.Applications { [CLSCompliant(false)] public class ParameterProcessor { private Parameter[] parameterSet; private Parameter[] positionSet; private bool havePositional; private string appName; private int totalCount; private string helpMsg; private int boolCount, longCount, stringCount, stringArrayCount; public ParameterProcessor() { } private enum pType { boolType, longType, stringType, stringArrayType } private class Parameter { public string Name; public bool Mandatory; public string Default; public int Position; public int Index; public pType Kind; public bool Resolved; public string HelpMsg; public Parameter(pType kind) { Resolved = false; Name = null; Kind = kind; } } private string CodeToString(ParameterCode code) { switch (code) { case ParameterCode.Success: return "Success"; break; case ParameterCode.OutOfRange: return "OutOfRange"; break; case ParameterCode.NotSet: return "NotSet"; break; case ParameterCode.Retrieved: return "Retrieved"; break; case ParameterCode.Undefined: return "Undefined"; break; default: return "This should never happen"; break; } return null; } private bool StringToBool (string arg, out bool value) { value = false; bool ok = false; if (arg == null) return false; string s = arg.ToLower(); if (s == "true") { value = true; ok = true; } if (s == "false") { value = false; ok = true; } return ok; } private bool StringToLong (string arg, out long value) { value = -1; int start = 0; bool doHex = false; if (arg == null) return false; if (arg.StartsWith("0x") || arg.StartsWith("0X")) { if (arg.Length == 2) return false; doHex = true; start = 2; } else { for (int i=start; i < arg.Length; i++) { if (!Char.IsNumber(arg[i]) && arg[i] != '-' ) return false; } } try { if (doHex) value = System.Int64.Parse(arg, NumberStyles.AllowHexSpecifier); else value = System.Int64.Parse(arg); } catch (Exception e) { Console.WriteLine("Exception: converting {0} to Long. error={1}",arg,e.Message); return false; } return true; } private Parameter! GetXmlAttributes(XmlNode! p, pType t) { Parameter a = new Parameter(t); assert a != null; a.Position = p.GetAttribute("Position", -1); a.Index = p.GetAttribute("id", -1); a.Mandatory = p.GetAttribute("Mandatory", false); a.Name = p.GetAttribute("Name",null); a.Default = p.GetAttribute("Default",null); a.HelpMsg = p.GetAttribute("HelpMessage",null); //Console.WriteLine("name=({0}), Mandatory={1}, id={2}, position={3}", // a.Name, a.Mandatory, a.Index, a.Position); return a; } private static bool InSeparators(char c, char []! separators) { for (int i = 0; i < separators.Length; i++) { if (separators[i] == c) return true; } return false; } private static ArrayList! Tokenize(string! input, int last, char []! separators) { ArrayList tokens = new ArrayList(); for (int i = 0; i <= last;) { // Skip separators while (i <= last && InSeparators(input[i], separators)) { i++; } if (i > last) break; // Try to slurp word int start = i; while (i <= last && !InSeparators(input[i], separators) && input[i] != '\'') { i++; } if (i != start) { tokens.Add(input.Substring(start, i - start)); } // Skip separators while (i <= last && InSeparators(input[i], separators)) { i++; } if (i > last) break; // Try to quoted slurp word if (input[i] == '\'') { start = i; i++; while (i <= last && input[i] != '\'') { i++; } if (i <= last && input[i] == '\'') { tokens.Add(input.Substring(start + 1, i - start - 1)); i++; } else { tokens.Add(input.Substring(start, i - start)); i++; } } } // end for return tokens; } private Parameter FindParameter(string name) { if (parameterSet != null) { for (int i=0; i < parameterSet.Length; i++) { Parameter a = parameterSet[i]; if (a == null) continue; assert a.Name != null; if ( a.Name == name) return parameterSet[i]; } } return null; } private bool ParseAndSetParameter(Process! p, Parameter! a, string! value) { bool ok; ParameterCode code; bool boolValue; long longValue; switch (a.Kind) { case pType.boolType: ok = StringToBool(value, out boolValue); if (!ok) { Console.WriteLine("{0} for {1} is not a bool value.", value, a.Name); } else { code = p.SetStartupBoolArg(a.Index, boolValue); if (code != ParameterCode.Success){ Console.WriteLine("unable to set bool index {0} to {1} for arg {2}. error={3}", a.Index, boolValue, a.Name, CodeToString(code) ); return false; } a.Resolved = true; return true; } break; case pType.longType: ok = StringToLong(value, out longValue); if (!ok) { Console.WriteLine("{0} for {1} is not a integer value.", value, a.Name); } else { code = p.SetStartupLongArg(a.Index, longValue); if (code != ParameterCode.Success){ Console.WriteLine("unable to set string. index {0} to {1} for arg {2}. error={3}", a.Index, longValue, a.Name, CodeToString(code)); return false; } a.Resolved = true; return true; } break; case pType.stringType: code = p.SetStartupStringArg(a.Index, value); if (code != ParameterCode.Success){ Console.WriteLine("unable to set string. index {0} to {1} for arg {2}. error={3}", a.Index, value, a.Name, CodeToString(code)); return false; } a.Resolved = true; return true; break; } return false; } private string! KindToString(pType t) { switch (t) { case pType.boolType: return "bool"; case pType.longType: return "int"; case pType.stringType: return "string"; case pType.stringArrayType: return "strings"; default: return "???"; } } #if false public void Show(string action, Manifest! manifest, bool verbose) { if (action != null) { Console.Write("\n {0} @{1} ", appName, action); InitializeParameters(action, manifest); ShowArgs(action, manifest); return; } XmlNode catNode = manifest.GetCategoriesNode(); if (catNode == null ) { Console.WriteLine(" unable to find categories node!"); return; } ArrayList actions = new ArrayList(); assert catNode != null; foreach ( XmlNode n in catNode.Children) { assert n != null; string s = n.GetAttribute("Action", null); actions.Add(s); } if (actions.Count > 0 ) { Console.Write("\n {0} Actions: ", appName); for (int i=0; i < actions.Count; i++) { Console.Write("{0} ",actions[i]); } Console.WriteLine("\n"); } if (verbose) { for (int i=0; i < actions.Count; i++) { string a = (string) actions[i]; if (a != null ) Console.Write("\n Configuration Parameters for action @{0}",a); InitializeParameters(a, manifest); ShowArgs(action, manifest); } } else { // Show a reasonably-short list of commands. ShowCommandsSummary(manifest); } } #endif /// /// This method shows a list of all of the commands (actions) that an /// application provides, as defined in its manifest. The list simply /// shows the names of each command, and the helptext for each. /// void ShowCommandsSummary(Manifest! manifest) { XmlNode catNode = manifest.GetCategoriesNode(); if (catNode == null ) { Console.WriteLine(" unable to find categories node!"); return; } const string format = "{0,-16} {1}"; bool any = false; foreach ( XmlNode node in catNode.Children) { assert node != null; if (node.Name != "category") continue; string action; string actionWithAt; // Is it the default action? string value = node.GetAttribute("DefaultAction", null); bool isDefault = value != null && value.Length != 0; if (isDefault) { action = ""; actionWithAt = ""; } else { action = node.GetAttribute("Action", null); if (action == null) continue; actionWithAt = "@" + action; } string! help = GetHelpMessage(node); if (!any) { any = true; Console.WriteLine("The program supports the following actions:"); Console.WriteLine(); } actionWithAt = this.appName + " " + actionWithAt; const int max_help_length = 60; if (help.Length > max_help_length) { string[]! help_lines = BreakLineAtWords(help, max_help_length); bool first = true; foreach (string! help_line in help_lines) { if (first) { Console.WriteLine(format, actionWithAt, help_line); first = false; } else { Console.WriteLine(format, "", help_line); } } } else { Console.WriteLine(format, actionWithAt, help); } } if (!any) { Console.WriteLine("The program does not support any commands."); } else { Console.WriteLine(); // Console.WriteLine("Use -?? for verbose info on all actions."); Console.WriteLine("Use '@cmd -?' for detailed help on a particular command."); } } /// /// This method breaks a string into a list of strings, each of which is at most 'maxlength' /// in length, if possible. The method tries to break the string at whitespace boundaries. /// Note that it is possible for entries returned to be longer than 'maxlength', so don't /// rely on that behavior. /// string[]! BreakLineAtWords(string! line, int maxlength) { if (line.Length < maxlength) return new string[] { line }; ArrayList list = new ArrayList(); int pos = 0; for (;;) { if (pos >= line.Length) break; int remaining = line.Length - pos; if (remaining <= maxlength) { // No need to break anymore. list.Add(line.Substring(pos, remaining)); break; } int len = maxlength; assert len < remaining; // Move backward and find the first whitespace char. while (len > 0 && !Char.IsWhiteSpace(line[pos + len - 1])) len--; // Remove whitespace from the end of the current line. while (len > 0 && Char.IsWhiteSpace(line[pos + len - 1])) len--; if (len == 0) { // Uh oh. The current line is one big long non-breaking string. // Just break it at maxlength. Ugly. len = maxlength; } list.Add(line.Substring(pos, len)); pos += len; // Skip whitespace. while (pos < line.Length && Char.IsWhiteSpace(line[pos])) pos++; } Array obj_array = list.ToArray(typeof(string)); return (string[])obj_array; } string! GetHelpMessage(XmlNode! actionNode) { string value = actionNode.GetAttribute("HelpMessage", null); if (value != null) return value; else return ""; } /// /// This method shows a summary of all of the commands (actions) defined in /// an application manifest. /// void ShowCommandsSummaryWithArgs(Manifest! manifest) { // Show a reasonably-short list of commands. string[] actions = GetAllActions(manifest); if (actions.Length > 0 ) { foreach (string action in actions) { InitializeParameters(action, manifest); string! line = GetParameterSyntaxSummary(manifest, action); Console.WriteLine(line); if (this.helpMsg != null) { string! help = this.helpMsg; const int max_length = 70; if (help.Length > max_length) { string[]! help_lines = BreakLineAtWords(help, max_length); for (int i = 0; i < help_lines.Length; i++) { Console.WriteLine(" " + help_lines[i]); } } else { Console.WriteLine(" " + this.helpMsg); } Console.WriteLine(); } } } Console.WriteLine("Use '" + this.appName + " @cmd -?' for detailed help on a particular command."); Console.WriteLine("Use '" + this.appName + " -??' for detailed help on all commands."); } /// /// Builds a /// string! GetParameterSyntaxSummary(Manifest! manifest, string action) { StringBuilder buffer = new StringBuilder(); if (action != null) { buffer.Append(appName); buffer.Append(" @"); buffer.Append(action); } else { buffer.Append(appName); } // First pass, show positional arguments. if (positionSet != null) { foreach (Parameter p in positionSet) { if (p == null) continue; buffer.Append(' '); if (!p.Mandatory) buffer.Append('['); buffer.Append('<'); buffer.Append(p.Name); buffer.Append('>'); if (!p.Mandatory) buffer.Append(']'); } } // Next, show non-positional arguments (-foo=x) if (parameterSet != null) { foreach (Parameter p in parameterSet) { if (p == null) continue; // Positional parameters are apparently in both lists. // Screen them out. bool skip = false; if (positionSet != null) { foreach (Parameter positional in positionSet) { if (positional == p) { skip = true; break; } } } if (skip) continue; buffer.Append(' '); if (!p.Mandatory) buffer.Append('['); buffer.Append('-'); buffer.Append(p.Name); switch (p.Kind) { case pType.boolType: break; case pType.longType: buffer.Append("=nn"); break; default: buffer.Append("=..."); break; } if (!p.Mandatory) buffer.Append(']'); } } return buffer.ToString(); } /// /// Builds a list of all of the actions (commands) defined in a manifest. /// Note: This CAN return a null entry (an array containing a null entry), /// if the program has a default action, and most do. /// string[]! GetAllActions(Manifest! manifest) { XmlNode catNode = manifest.GetCategoriesNode(); if (catNode == null ) { Console.WriteLine(" unable to find categories node!"); return new string[0]; } ArrayList actions = new ArrayList(); foreach ( XmlNode n in catNode.Children) { assert n != null; string s = n.GetAttribute("Action", null); actions.Add(s); } Array actions_array = actions.ToArray(typeof(string)); return (string[])actions_array; } void ShowVerboseCommandHelpAll(Manifest! manifest) { string[]! actions = GetAllActions(manifest); foreach (string action in actions) { InitializeParameters(action, manifest); ShowVerboseCommandHelp(manifest, action); Console.WriteLine(); } } /** This method shows detailed help for a specific command. It shows the command syntax, and information on each parameter. */ void ShowVerboseCommandHelp(Manifest! manifest, string action) { Console.WriteLine("Help for command: " + action); InitializeParameters(action, manifest); if (this.helpMsg != null) { Console.WriteLine(); string[]! lines = BreakLineAtWords(this.helpMsg, 70); foreach (string line in lines) { Console.WriteLine(" " + line); } } Console.WriteLine(); Console.WriteLine("Usage:"); string! syntax_summary = GetParameterSyntaxSummary(manifest, action); Console.WriteLine(" " + syntax_summary); Console.WriteLine(); // Now show parameter details. if (parameterSet != null && parameterSet.Length != 0) { Console.WriteLine("Parameters:"); Console.WriteLine(); // 0 = parameter name // 1 = parameter type // 2 = mandatory? // 3 = help text const string parameter_format = "{0,-10} {1,-8} {2,-6} {3}"; Console.WriteLine(parameter_format, "Name", "Type", "Req'd?", "Description"); Console.WriteLine(parameter_format, "====", "====", "======", "==========="); if (parameterSet != null) { foreach (Parameter a in parameterSet) { if (a == null) continue; string! typestring = KindToString(a.Kind); const int max_length = 40; string! help = a.HelpMsg != null ? a.HelpMsg : ""; if (help.Length > max_length) { string[]! help_lines = BreakLineAtWords(help, max_length); for (int i = 0; i < help_lines.Length; i++) { if (i == 0) Console.WriteLine(parameter_format, a.Name, typestring, a.Mandatory ? "yes" : "no", help_lines[i]); else Console.WriteLine(parameter_format, "", "", "", help_lines[i]); } } else { Console.WriteLine(parameter_format, a.Name, typestring, a.Mandatory ? "yes" : "no", a.HelpMsg != null ? a.HelpMsg : ""); } } } } // Console.WriteLine("\nExternal Channels needed:"); // manifest.SetEndpoints(null, action, true); } private bool Parse(String[]! cmd, Manifest! manifest, out Process process, out string action) { string name; action = null; process = null; String[] args = cmd; // see if we have an action verb. If so determine if it is valid. // if it is valid prime the parameter set based on it. if (cmd.Length > 1 && cmd[1] != null) { assert cmd[1] != null; string! s = (!)cmd[1]; if ( s[0] == '@'){ // we have an action. action = s.Substring(1); //Console.WriteLine("Action encountered action={0}",action); if (!InitializeParameters(action, manifest)) { Console.WriteLine("action init failed"); return false; } args = new String[cmd.Length -1]; Array.Copy(cmd, 1, args, 0, cmd.Length-1); } else { if (!InitializeParameters(null, manifest)) { Console.WriteLine("parameter init failed"); return false; } } } else { if (!InitializeParameters(null, manifest)) { Console.WriteLine("parameter init failed"); return false; } } //if ( cmd.Length == 1 ) return false; appName = cmd[0]; assert appName != null; if (args.Length > 1 && args[1] != null && args[1] == "-?") { if (action != null) { ShowVerboseCommandHelp(manifest, action); } else { ShowCommandsSummaryWithArgs(manifest); } return false; } if (args.Length > 1 && args[1] != null && args[1] == "-??"){ ShowVerboseCommandHelpAll(manifest); return false; } // FIXFIX: should really parse and validate all the args and cache values // BEFORE creating the process. Need to change data structures //DebugStub.WriteLine("parameters.Parse creating new process"); process = new Process(appName, action, null); if (process == null) { Console.WriteLine("Unable to create process {0} with action={1}",appName,action); } ArrayList extras = new ArrayList(); bool seenPositional = false; int positionalBase = 0; Parameter a; for (int i=1; i < args.Length ; i++){ string p = args[i]; assert p != null; //Console.WriteLine("processing {0}", p); if (p[0] == '-') { // this is an optional arg p = p.Substring(1); if ( p== null || p == "") { Console.WriteLine("'-' with no parameter name is invalid"); return false; } ArrayList tokens = Tokenize(p, p.Length - 1, new char[] {'=', ':'}); name = (string) tokens[0]; a = FindParameter(name); if (a != null) { //Console.WriteLine("found a match name={0}, rest={1} ", // a.Name, tokens.Count >=2 ? tokens[1]: "empty"); if (tokens.Count == 1) { //this is potentially a bool parameter with no value if (a.Kind == pType.boolType) { // set to opposite of default string val = a.Default == "False" ? "True" : "False"; bool ok = ParseAndSetParameter(process, a, val); if (!ok) return false; //Console.WriteLine("Setting {0} to {1}", a.Name, val); } else { Console.WriteLine("invalid parameter assignment"); return false; } } if (tokens.Count == 2) { bool ok = ParseAndSetParameter(process, a, (string)(!) tokens[1]); if (!ok) return false; } } else { //unknown parameter Console.WriteLine("Unknown parameter not set"); return false; } } else { a = null; if (!seenPositional) { seenPositional = true; positionalBase = i; } assert i-positionalBase >= 0; if (positionSet != null && (i-positionalBase) < positionSet.Length ) { a = positionSet[i-positionalBase]; } if (a != null) { //Console.WriteLine("Positional match: for {0} at {1} arrayIdx={2}", // a.Name, a.Position, i); bool ok = ParseAndSetParameter(process, a, (string)(!) args[i]); if (!ok) return false; } else { if (stringArrayCount != 1) { Console.WriteLine("{0} is not a known parameter.", p); } // add it to the Array List // After processing all the tokens the list will be // converted to StringArray parameter if present extras.Add(args[i]); //return false; } } } // check to see if all positional args were resolved bool status = true; if (positionSet != null) { for (int i=0; i < positionSet.Length; i++ ){ Parameter p = positionSet[i]; if (p != null) { assert p.Name != null; if (p.Resolved != true){ if (p.Mandatory) { Console.WriteLine("Mandatory positional Parameter {0} at {1} not set.", p.Name, i); status = false; } } } } } // check to see if all mandatory args were resolved if (status == true) { if (parameterSet != null) { for (int i=0; i < parameterSet.Length; i++ ){ Parameter p = parameterSet[i]; assert p != null; assert p.Name != null; if (p.Mandatory == true){ if (p.Resolved != true){ Console.WriteLine("Mandatory Parameter {0} not set.", p.Name); status = false; } } } } } // Set default values if needed on non mandatory parameters if (status == true) { if (parameterSet != null) { for (int i=0; i < parameterSet.Length; i++ ){ Parameter p = parameterSet[i]; assert p != null; assert p.Name != null; if (p.Kind == pType.stringArrayType) { // ignore defaults for string type.. continue; } if (p.Resolved != true && p.Default != null){ //Console.WriteLine("setting default: {0}={1}", p.Name, p.Default); ParseAndSetParameter(process, p, p.Default); } } } } // see if there were any extra args. If so pass them on to the process // if the manifest defined a StringArrayParameter string[] strings = null; if (extras.Count > 0) { // is there 1 ArrayParameter defined in the manifest? //Console.WriteLine("extras={0}, arrayCount={1}", extras.Count, stringArrayCount); if (stringArrayCount == 1) { // Always send in if app requests is, even if 0 extra args strings = new string[extras.Count]; for (int i=0; i < extras.Count; i++) { strings[i] = (string) extras[i]; //Console.WriteLine(" strings[{0}]={1}",i,strings[i]); } Console.WriteLine("Setting Strings to {0}", strings); process.SetStartupStringArrayArg(0, strings); } else { Console.WriteLine("There is not exactly ONE StringArray parameter.\n " + "Unsure where to place strings.\n" + "#of String Array parameters={0}",stringArrayCount); return false; } } return status ; } public bool ProcessParameters( String[] commandLine, Manifest manifest, out Process p, out string action) { ParameterCode code; long longValue; string stringValue; bool boolValue; action = null; p = null; if (manifest == null) return false; if (commandLine == null) return false; return Parse( commandLine, manifest, out p, out action); } private bool SetPosition(Parameter! p) { bool result = true; if (p != null) { if (p.Position != -1) { if (p.Position < 0 || p.Position > totalCount-1) { Console.WriteLine("{0} position index out of range ({1})", p.Name, p.Position); result = false; } else { if (positionSet != null) { positionSet[p.Position] = p; } result = true; } } } return result; } public bool InitializeParameters(string actionName, Manifest! manifest) { XmlNode bools; XmlNode strings; XmlNode stringArrays; XmlNode longs; XmlNode ActionNode; int pIndex; longCount = 0; boolCount = 0; stringCount = 0; stringArrayCount = 0; bools = null; longs = null; strings = null; stringArrays = null; parameterSet = null; positionSet = null; // Use Action to determine which set of parameters are going to be used. // If action is null we need to ensure there is 1 Parameter configuration // and its DefaultAction parameter is set to true. XmlNode actionNode = manifest.GetCategoryNode(actionName); if (actionNode == null) { if (actionName != null) { Console.WriteLine("Unknown action {0} recognized", actionName); } else { Console.WriteLine("Unable to find default action"); } return false; } string categoryName = actionNode.GetAttribute("name",""); #if VERBOSE Console.WriteLine(" using category {0}, action={1}, default={2}", categoryName, actionNode.GetAttribute("Action",""), actionNode.GetAttribute("DefaultAction",false)); #endif if (categoryName != "console") { Console.WriteLine("This manifest's category is not 'console': ({0})", categoryName); return false; } bools = manifest.GetBoolParameterNode(actionNode); longs = manifest.GetLongParameterNode(actionNode); strings = manifest.GetStringParameterNode(actionNode); stringArrays = manifest.GetStringArrayParameterNode(actionNode); helpMsg = manifest.GetHelpMessage(actionNode); if ( bools == null && longs == null && strings == null && stringArrays == null) { parameterSet = new Parameter[0]; positionSet = new Parameter[0]; return true; } if ( bools != null ) { boolCount = bools.GetAttribute("length", 0); } if ( longs != null ) { longCount = longs.GetAttribute("length", 0); } if ( strings != null ) { stringCount = strings.GetAttribute("length", 0); } if ( stringArrays != null ) { stringArrayCount = stringArrays.GetAttribute("length", 0); } totalCount = stringCount + stringArrayCount + longCount + boolCount; parameterSet = new Parameter[totalCount]; positionSet = new Parameter[totalCount]; if (totalCount == 0 ) return true; assert positionSet != null; assert parameterSet != null; pIndex = 0; if ( bools != null ) { if (boolCount > 0 ) { foreach (XmlNode! bp in bools.Children) { if ( bp.Name == "BoolParameter") { Parameter p = GetXmlAttributes(bp, pType.boolType); if (!SetPosition(p)) return false; parameterSet[pIndex++] = p; } } } } if ( longs != null ) { if (longCount > 0 ) { foreach (XmlNode! lp in longs.Children) { if ( lp.Name == "LongParameter") { Parameter p = GetXmlAttributes(lp, pType.longType); if (!SetPosition(p)) return false; parameterSet[pIndex++] = p; } } } } if ( strings != null ) { if (stringCount > 0 ) { foreach (XmlNode! sp in strings.Children) { if ( sp.Name == "StringParameter") { Parameter p = GetXmlAttributes(sp, pType.stringType); if (!SetPosition(p)) return false; parameterSet[pIndex++] = p; } } } } if ( stringArrays != null ) { if (stringArrayCount > 0 ) { foreach (XmlNode! sp in stringArrays.Children) { if ( sp.Name == "StringArrayParameter") { Parameter p = GetXmlAttributes(sp, pType.stringArrayType); if (!SetPosition(p)) return false; parameterSet[pIndex++] = p; } } } } if ( positionSet != null && positionSet[0] != null) { havePositional = true; bool seenNull = false; for (int i=0; i < positionSet.Length; i++) { if ( positionSet[i] == null ) { seenNull = true; continue; } if (seenNull) { Console.Write("Positional indices not contiguous!"); for (int j=0; j < positionSet.Length; j++) { Parameter p = positionSet[j]; if ( p != null ) { Console.Write("{0}({1}) ",j, p.Name); } } Console.WriteLine(); return false; } } } return true; } } }