コード例 #1
0
ファイル: ScriptDataAPI.cs プロジェクト: drewby/Bash-Wizard
        /// <summary>
        ///     given a JSON string and the user code, generate a bash file
        /// </summary>
        /// <param name="json"></param>
        /// <param name="userCode"></param>
        /// <returns></returns>
        public static ScriptData FromJson(string json, string userCode)
        {
            ScriptData scriptData            = new ScriptData();
            bool       oldGenerateBashScript = scriptData.GenerateBashScript;

            try
            {
                scriptData = JsonConvert.DeserializeObject <ScriptData>(json);
                //
                //  Serialize is OptIn, deserialize is not.  so these will be reset
                // scriptData.JSON = json;
                scriptData.UserCode                = userCode;
                scriptData.GenerateBashScript      = true;
                scriptData.UpdateOnPropertyChanged = false;
                scriptData.ToBash(); //this will set the JSON
                return(scriptData);
            }
            catch (Exception e)
            {
                //
                //  the user should expect to have the JSON still there and not lose their code
                //  when they type incorrect JSON - setting these here allows them to fix the error
                //  and get back to the state they were in.
                scriptData.JSON     = json;
                scriptData.UserCode = userCode;
                scriptData.ParseErrors.Add(new ParseErrorInfo(ErrorLevel.Fatal, "Exception caught when parsing JSON."));
                scriptData.ParseErrors.Add(new ParseErrorInfo(ErrorLevel.Fatal, $"ExceptionInfo: {e.Message}"));
                return(scriptData);
            }
            finally
            {
                scriptData.GenerateBashScript      = oldGenerateBashScript;
                scriptData.UpdateOnPropertyChanged = true;
            }
        }
コード例 #2
0
ファイル: ScriptDataAPI.cs プロジェクト: drewby/Bash-Wizard
        /// <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();
                }
            }
        }