private void SetCreateLogDirectory(bool value) { ParameterItem logParameter = new ParameterItem() { LongParameter = "log-directory", ShortParameter = "l", Description = "directory for the log file. the log file name will be based on the script name", VariableName = "logDirectory", Default = "\"./\"", RequiresInputString = true, RequiredParameter = false, ValueIfSet = "$2" }; AddOptionParameter(logParameter, value); }
/// <summary> /// make sure we don't add exactly the same error twice /// </summary> /// <param name="findOrAdd"></param> private void AddValidationError(ParameterItem item, ParseErrorInfo findOrAdd) { foreach (var error in ParseErrors) { if (error.ErrorLevel == findOrAdd.ErrorLevel && error.Message == findOrAdd.Message) { return; } } ParseErrors.Add(findOrAdd); if (item != null) { findOrAdd.Tag = item; } }
private void SetAcceptsInputFile(bool newValue) { // i is the short name and input-file is the long name for the ParameterItem param = new ParameterItem() { ShortParameter = "i", LongParameter = "input-file", VariableName = "inputFile", Description = "filename that contains the JSON values to drive the script. command line overrides file", RequiresInputString = true, Default = "", // this needs to be empty because if it is set, the script will try to find the file... RequiredParameter = false, ValueIfSet = "$2" }; AddOptionParameter(param, newValue); }
private void AddOptionParameter(ParameterItem item, bool add) { ParameterItem param = null; foreach (ParameterItem p in Parameters) { if (p.VariableName == item.VariableName) { param = p; break; } } // // need to have the right parameter for long line to work correctly -- make sure it is there, and if not, add it. if (add && param == null) { Parameters.Add(item); } else if (!add && param != null) { Parameters.Remove(param); } }
/// <summary> /// Given a bash file, create a ScriptData object. This is the "parse a bash script" function /// </summary> /// <param name="bash"></param> public static ScriptData FromBash(string input) { ScriptData scriptData = new ScriptData(); bool oldGenerateBashScript = scriptData.GenerateBashScript; try { scriptData.ClearParseErrors(); scriptData.UpdateOnPropertyChanged = false; // this flag stops the NotifyPropertyChanged events from firing scriptData.GenerateBashScript = false; // this flag tells everything that we are in the process of parsing scriptData.BashScript = input; // // make sure that we deal with the case of getting a file with EOL == \n\r. we only want \n // I've also had scenarios where I get only \r...fix those too. if (input.IndexOf("\n") != -1) { // // we have some new lines, just kill the \r if (input.IndexOf("\r") != -1) { input = input.Replace("\r", string.Empty); } } else if (input.IndexOf("\r") != -1) { // no \n, but we have \r input = input.Replace("\r", "\n"); } else { // no \r and no \n scriptData.ParseErrors.Add(new ParseErrorInfo(ErrorLevel.Fatal, noNewLines)); return(scriptData); } // // make sure the file doesn't have GIT merge conflicts if (input.IndexOf("<<<<<<< HEAD") != -1) { scriptData.ParseErrors.Add(new ParseErrorInfo(ErrorLevel.Fatal, unMergedGitFile)); return(scriptData); } /* * The general layout of the file is * #!/bin/bash # bashWizard version <version> # <BashWizard Functions> # --- BEGIN USER CODE --- # # --- END USER CODE --- # <optional BashWizard code> # # the general parse strategy is to separate the user code and then to parse the Bash Wizard Functions to find all the parameter information # we *never* want to touch the user code # */ string[] userComments = new string[] { "# --- BEGIN USER CODE ---", "# --- END USER CODE ---" }; string[] sections = input.Split(userComments, StringSplitOptions.RemoveEmptyEntries); string bashWizardCode = ""; switch (sections.Length) { case 0: // // this means we couldn't find any of the comments -- treat this as a non-BW file scriptData.UserCode = input.Trim(); // make it all user code scriptData.ParseErrors.Add(new ParseErrorInfo(ErrorLevel.Warning, missingComments)); scriptData.ParseErrors.Add(new ParseErrorInfo(ErrorLevel.Warning, addingComments)); return(scriptData); case 1: scriptData.ParseErrors.Add(new ParseErrorInfo(ErrorLevel.Fatal, missingOneUserComment)); scriptData.ParseErrors.Add(new ParseErrorInfo(ErrorLevel.Fatal, pleaseFix)); return(scriptData); case 2: case 3: bashWizardCode = sections[0]; scriptData.UserCode = sections[1].Trim(); // ignore section[2], it is code after the "# --- END USER CODE ---" that will be regenerated break; default: scriptData.ParseErrors.Add(new ParseErrorInfo(ErrorLevel.Fatal, tooManyUserComments)); return(scriptData); } // // first look for the bash version string versionLine = "# bashWizard version "; int index = bashWizardCode.IndexOf(versionLine); double userBashVersion = 0.1; string[] lines = null; string line = ""; bool foundDescription = false; if (index > 0) { bool ret = double.TryParse(bashWizardCode.Substring(index + versionLine.Length, 5), out userBashVersion); if (!ret) { ret = double.TryParse(bashWizardCode.Substring(index + versionLine.Length, 3), out userBashVersion); // 0.9 is a version i have out there... if (!ret) { scriptData.ParseErrors.Add(new ParseErrorInfo(ErrorLevel.Warning, missingVersionInfo)); } } } scriptData.EchoInput = (bashWizardCode.IndexOf(" echoInput") > 0); // // find the usage() function and parse it out - this gives us the 4 properties in the ParameterItem below if (scriptData.GetStringBetween(bashWizardCode, "usage() {", "}", out string bashFragment) == false) { scriptData.ParseErrors.Add(new ParseErrorInfo(ErrorLevel.Fatal, bashFragment)); } else { bashFragment = bashFragment.Replace("echoWarning", "echo"); bashFragment = bashFragment.Replace("\n", ""); lines = bashFragment.Split(new string[] { "echo ", "\"" }, StringSplitOptions.RemoveEmptyEntries); line = ""; foreach (string l in lines) { line = l.Trim(); if (line == "") { continue; } if (line == "exit 1") { break; } if (!foundDescription) { /* * it look like: * * function usage() { * echoWarning * echo "<description>" * ... * * } * * but the echoWarning isn't always there -- only if the --input-file option was specified. * */ if (line.StartsWith("Parameters can be passed in the command line or in the input file.")) { continue; } // // if the description is black, the next line echo's the usage -- so if we do NOT find the Usage statement // we have found the description. and in any case, if the Description isn't there by now, it isn't there // so always set the flag saying we found it. if (!line.StartsWith("Usage: $0")) { scriptData.Description = line.TrimEnd(); } foundDescription = true; continue; } if (line.Substring(0, 1) == "-") // we have a parameter! { string[] paramTokens = line.Split(new string[] { " ", "|" }, StringSplitOptions.RemoveEmptyEntries); string description = ""; for (int i = 3; i < paramTokens.Length; i++) { description += paramTokens[i] + " "; } description = description.Trim(); ParameterItem parameterItem = new ParameterItem() { ShortParameter = paramTokens[0].Trim(), LongParameter = paramTokens[1].Trim(), RequiredParameter = (paramTokens[2].Trim() == "Required") ? true : false, Description = description }; scriptData.Parameters.Add(parameterItem); } } } // // parse the echoInput() function to get script name - don't fail parsing on this one bashFragment = ""; if (scriptData.GetStringBetween(bashWizardCode, "echoInput() {", "parseInput()", out bashFragment)) { lines = bashFragment.Split('\n'); foreach (string l in lines) { line = l.Trim(); if (line == "") { continue; } // // the line is in the form of: "echo "<scriptName>:" if (scriptData.GetStringBetween(line, "echo \"", ":", out string name)) { scriptData.ScriptName = name; } break; } } // // next parse out the "parseInput" function to get "valueWhenSet" and the "VariableName" bashFragment = ""; if (scriptData.GetStringBetween(bashWizardCode, "eval set -- \"$PARSED\"", "--)", out bashFragment) == false) { scriptData.ParseErrors.Add(new ParseErrorInfo(ErrorLevel.Warning, bashFragment)); } else { lines = bashFragment.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); for (index = 0; index < lines.Length; index++) { line = lines[index].Trim(); if (line == "") { continue; } if (line.Substring(0, 1) == "-") // we have a parameter! { string[] paramTokens = lines[index + 1].Trim().Split(new char[] { '=' }); if (paramTokens.Length != 2) { scriptData.ParseErrors.Add(new ParseErrorInfo(ErrorLevel.Warning, $"When parsing the parseInput() function to get the variable names, encountered the line {lines[index + 1].Trim()} which doesn't parse. It should look like varName=$2 or the like.")); } string[] nameTokens = line.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); if (nameTokens.Length != 2) // the first is the short param, second long param, and third is empty { scriptData.ParseErrors.Add(new ParseErrorInfo(ErrorLevel.Warning, $"When parsing the parseInput() function to get the variable names, encountered the line {lines[index].Trim()} which doesn't parse. It should look like \"-l | --long-name)\" or the like.")); } // nameTokens[1] looks like "--long-param) string longParam = nameTokens[1].Substring(3, nameTokens[1].Length - 4); ParameterItem param = scriptData.FindParameterByLongName(longParam); if (param == null) { scriptData.ParseErrors.Add(new ParseErrorInfo(ErrorLevel.Warning, $"When parsing the parseInput() function to get the variable names, found a long parameter named {longParam} which was not found in the usage() function")); } else { param.VariableName = paramTokens[0].Trim(); param.ValueIfSet = paramTokens[1].Trim(); if (lines[index + 2].Trim() == "shift 1") { param.RequiresInputString = false; } else if (lines[index + 2].Trim() == "shift 2") { param.RequiresInputString = true; } else { scriptData.ParseErrors.Add(new ParseErrorInfo(ErrorLevel.Warning, $"When parsing the parseInput() function to see if {param.VariableName} requires input, found this line: {lines[index + 1]} which does not parse. it should either be \"shift 1\" or \"shift 2\"")); } } index += 2; } } } // the last bit of info to figure out is the default value -- find these with a comment if (scriptData.GetStringBetween(bashWizardCode, "# input variables", "parseInput", out bashFragment) == false) { scriptData.ParseErrors.Add(new ParseErrorInfo(ErrorLevel.Fatal, bashFragment)); } else { // throw away the "declare " bashFragment = bashFragment.Replace("declare ", ""); lines = bashFragment.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (string l in lines) { line = l.Trim(); if (line == "") { continue; } if (line.StartsWith("#")) { continue; } string[] varTokens = line.Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries); if (varTokens.Length == 0 || varTokens.Length > 2) { scriptData.ParseErrors.Add(new ParseErrorInfo(ErrorLevel.Warning, $"When parsing the variable declarations between the \"# input variables\" comment and the \"parseInput\" calls, the line {line} was encountered that didn't parse. it should be in the form of varName=Default")); } string varName = varTokens[0].Trim(); ParameterItem param = scriptData.FindParameterByVarName(varName); if (param == null) { scriptData.ParseErrors.Add(new ParseErrorInfo(ErrorLevel.Warning, $"When parsing the variable declarations between the \"# input variables\" comment and the \"parseInput\" calls, found a variable named {varName} which was not found in the usage() function")); scriptData.ParseErrors.Add(new ParseErrorInfo(ErrorLevel.Information, $"\"{line}\" will be removed from the script. If you want to declare it, put the declaration inside the user code comments")); } else { param.Default = varTokens.Length == 2 ? varTokens[1].Trim() : ""; // in bash "varName=" is a valid declaration } } } return(scriptData); } finally { // // need to update everything that might have been changed by the parse scriptData.UpdateOnPropertyChanged = true; // force events to fire scriptData.NotifyPropertyChanged("Description"); scriptData.NotifyPropertyChanged("ScriptName"); // "BashScript" also updates the ToggleButtons scriptData.GenerateBashScript = oldGenerateBashScript; scriptData.NotifyPropertyChanged("BashScript"); // // now go from Parameters back to bash script, unless there are fatal errors // if there are, it will stay as the input set at the top of the FromBash() function if (!scriptData.HasFatalErrors) { scriptData.ToBash(); } } }
private (bool ret, string msg) SetCreateVerifyDelete(bool addParameter) { string msg = ""; if (!addParameter) // deselecting { string[] functions = new string[] { "onVerify", "onCreate", "onDelete" }; string err = ""; foreach (string f in functions) { if (FunctionExists(UserCode, f)) { err += f + "\n"; } } if (err != "") { msg = $"You can unselected the Create, Verify, Delete pattern, but you have the following functions implemented:\n{err}\n\nManually fix the user code to not need these functions before removing this option."; return(ret : false, msg : msg); } } ParameterItem param = new ParameterItem() { LongParameter = "create", ShortParameter = "c", VariableName = "create", Description = "creates the resource", RequiresInputString = false, Default = "false", RequiredParameter = false, ValueIfSet = "true" }; AddOptionParameter(param, addParameter); param = new ParameterItem() { LongParameter = "verify", ShortParameter = "v", VariableName = "verify", Description = "verifies the script ran correctly", RequiresInputString = false, Default = "false", RequiredParameter = false, ValueIfSet = "true" }; AddOptionParameter(param, addParameter); param = new ParameterItem() { LongParameter = "delete", ShortParameter = "d", VariableName = "delete", Description = "deletes whatever the script created", RequiresInputString = false, Default = "false", RequiredParameter = false, ValueIfSet = "true" }; AddOptionParameter(param, addParameter); return(ret : true, msg : ""); }
/// <summary> /// this is like compiler errors/warnings when generating a bash script /// </summary> /// <returns></returns> public bool ValidateParameters(bool allowBlankParameters = true) { //verify short names are unique Dictionary <string, ParameterItem> nameDictionary = new Dictionary <string, ParameterItem>(); Dictionary <string, ParameterItem> variableDictionary = new Dictionary <string, ParameterItem>(); ClearValidationErrors(); ParameterItem item = null; foreach (var param in Parameters) { if (allowBlankParameters) { if (param.ShortParameter == "" && param.LongParameter == "") { continue; // probably just getting started } } else { if (param.ShortParameter == "" || param.LongParameter == "" || param.VariableName == "") { AddValidationError(param, new ParseErrorInfo(ErrorLevel.Validation, $"Parameter[{Parameters.IndexOf(param)}]: All Long Names, Short Names, and Variable Names must be non-empty.")); } } if (nameDictionary.TryGetValue(param.ShortParameter, out item)) { var otherIndex = Parameters.IndexOf(item); AddValidationError(param, new ParseErrorInfo(ErrorLevel.Validation, $"Parameter[{Parameters.IndexOf(param)}]: The name \"{param.ShortParameter}\" already exists for the parameter with index={otherIndex}. All Long Names and Short Names must be unique.")); } else { nameDictionary[param.ShortParameter] = param; } if (nameDictionary.TryGetValue(param.LongParameter, out item)) { var otherIndex = Parameters.IndexOf(item); AddValidationError(param, new ParseErrorInfo(ErrorLevel.Validation, $"Parameter[{Parameters.IndexOf(param)}]: The name \"{param.LongParameter}\" already exists for the parameter with index={otherIndex}. All Long Names and Short Names must be unique.")); } else { nameDictionary[param.LongParameter] = param; } if (variableDictionary.TryGetValue(param.VariableName, out item)) { var otherIndex = Parameters.IndexOf(item); AddValidationError(param, new ParseErrorInfo(ErrorLevel.Validation, $"Parameter[{Parameters.IndexOf(param)}]: The variable \"{param.VariableName}\" already exists for the parameter with index={otherIndex}. All Variable Names must be unique.")); } else { variableDictionary[param.VariableName] = param; } if (!param.RequiresInputString && param.ValueIfSet.Trim() == "$2") { AddValidationError(param, new ParseErrorInfo(ErrorLevel.Validation, $"Parameter[{Parameters.IndexOf(param)}]: {param.LongParameter} has \"Require Input String\" set to False and the \"Value if Set\" to \"$2\". \nThis combination is not allowed.")); } } // // I'm taking out these chars because they are "special" in JSON. I found that the ":" messed up JQ processing // and it seems a small price to pay to not take any risks with the names. Note that we always Trim() the names // in the ParameterOrScriptData_PropertyChanged method // string[] illegalNameChars = new string[] { ":", "{", "}", "[", "]", "\\", "'", "\"" }; if (ScriptName != "") { foreach (string c in illegalNameChars) { if (ScriptName.Contains(c)) { AddValidationError(null, new ParseErrorInfo(ErrorLevel.Validation, "The following characters are illegal in the Script Name: :{}[]\"\'")); break; // we only print one error anyway } } } if (Description != "") { foreach (string c in illegalNameChars) { if (Description.Contains(c)) { AddValidationError(null, new ParseErrorInfo(ErrorLevel.Validation, "The following characters are illegal in the Description: :{}[]\"\'")); break; // we only print one error anyway } } } return(ParseErrors.Count == 0); }