public static TplFunction Create(string query, out IReadOnlyList <LogMessage> errors) { var grammar = new TplGrammar(); var tplLangData = new LanguageData(grammar); var tplParser = new Parser(tplLangData); var parseTree = tplParser.Parse(query); if (parseTree.HasErrors()) { errors = parseTree.ParserMessages; return(null); } else { errors = new List <LogMessage>(0); var functionNodes = parseTree.Root.ChildNodes.Select(node => node.ChildNodes); TplFunction rootFunction = null; foreach (var funcNode in functionNodes) { TplFunction currentFunction; var funcName = funcNode[0].FindToken().Text; switch (funcName.ToLower()) { case "between": currentFunction = new TplBetween() { TargetField = GetOptionalVariable(funcNode[1]), StartValue = funcNode[2].FindToken().ValueString, EndValue = funcNode[3].FindToken().ValueString, Inclusive = GetSwitchValue(funcNode[4], "inclusive"), }; break; case "dedup": currentFunction = new TplDedup() { TargetFields = GetOptionalVariableList(funcNode[1]), Mode = GetSwitchValue(funcNode[2], "all") ? TplDedup.DedupMode.All : TplDedup.DedupMode.Consecutive, }; break; case "delete": { var fields = GetOptionalVariableList(funcNode[1]); if (fields.Count < 1) { throw funcNode[0].GetException("At least one field name is required in Delete function"); } currentFunction = new TplDelete() { SelectedFields = fields }; } break; case "eval": var expTree = GetAsExpressionTree(funcNode[3], null); currentFunction = new TplEval(funcNode[3].GetAsExpressionTree(null)) { NewFieldName = funcNode[1].FindTokenAndGetValue <string>(), }; break; case "first": currentFunction = new TplFirst() { First = funcNode[1].FindTokenAndGetValue <int>(), }; break; case "last": currentFunction = new TplLast() { Last = funcNode[1].FindTokenAndGetValue <int>(), }; break; case "group": currentFunction = new TplGroup() { TargetField = GetOptionalVariable(funcNode[1]), StartRegex = funcNode[2].FindToken().GetTokenAsRegex(), EndRegex = funcNode[3].FindToken().GetTokenAsRegex(), }; break; case "kv": currentFunction = new TplKeyValue() { TargetFields = GetOptionalVariableList(funcNode[1], TplResult.DEFAULT_FIELD), KeyValueRegex = GetTokenAsRegex(funcNode[2].FindToken()) }; break; case "rex": ValidateArgumentList(funcNode[3], "passthru"); currentFunction = new TplRegex() { TargetField = GetOptionalVariable(funcNode[1]), Rex = GetTokenAsRegex(funcNode[2].FindToken()), PassThru = GetNamedArgumentValue(funcNode[3], "passthru", false), }; break; case "select": currentFunction = new TplSelect() { SelectedFields = GetOptionalVariableList(funcNode[1], TplResult.DEFAULT_FIELD) }; break; case "sort": currentFunction = new TplSort( funcNode[1].ChildNodes .Select(sortNode => new TplSortField(sortNode.ChildNodes[0].FindToken().ValueString, (sortNode.ChildNodes.Count > 1 ? (sortNode.ChildNodes[1].FindToken().ValueString == "desc") : false))) .ToList() ); break; case "stats": { ValidateArgumentList(funcNode[3], "count", "sum", "avg"); if (!funcNode[3].ChildNodes.Any()) { throw funcNode[0].GetException("Expected 1 or more of the following arguments: 'count', 'sum', or 'avg' in 'stats' function"); } List <string> byFields; if (funcNode[2].ChildNodes.Any()) // if the keyword 'by' is present, look for by fields { byFields = GetOptionalVariableList(funcNode[2].ChildNodes[1]); if (!byFields.Any()) { throw funcNode[2].GetException("Expected 1 or more variables in 'stats' function after 'by'"); } } else { byFields = new List <string>(0); } currentFunction = new TplStats() { TargetFields = GetOptionalVariableList(funcNode[1], TplResult.DEFAULT_FIELD), ByFields = byFields, Avg = GetNamedArgumentValue(funcNode[3], "avg", false), Count = GetNamedArgumentValue(funcNode[3], "count", false), Sum = GetNamedArgumentValue(funcNode[3], "sum", false), }; } break; case "where": currentFunction = new TplWhere(funcNode[1].GetAsExpressionTree(null)); break; case "tolower": case "toupper": ValidateArgumentList(funcNode[2], "rex", "group"); { var rex = GetNamedArgumentValue <Regex>(funcNode[2], "rex", null); var group = GetNamedArgumentValue <string>(funcNode[2], "group", null); if (rex == null && group != null) { throw new InvalidOperationException($"Invalid argument 'group' in {funcName}. A group cannot be specified unless the 'rex' argument is also specified"); } else if (rex != null && group != null && !rex.GetNamedCaptureGroupNames().Contains(group)) { throw new InvalidOperationException($"Invalid value '{group}' for 'group' argument in {funcName}. The capture group does not exist in the specified regex"); } currentFunction = new TplChangeCase() { TargetFields = GetOptionalVariableList(funcNode[1], TplResult.DEFAULT_FIELD), ToUpper = funcName.ToLower() == "toupper", TargetGroup = group, MatchingRegex = rex }; } break; case "replace": ValidateArgumentList(funcNode[4], "as", "rex", "group", "case"); { var rex = new TplReplace() { TargetFields = GetOptionalVariableList(funcNode[1], TplResult.DEFAULT_FIELD), Replace = funcNode[3].FindTokenAndGetValue <string>(), AsField = GetNamedArgumentValue <string>(funcNode[4], "as", null), TargetGroup = GetNamedArgumentValue <string>(funcNode[4], "group", null), CaseSensitive = GetNamedArgumentValue <bool>(funcNode[4], "case", false), RegexMode = GetNamedArgumentValue <bool>(funcNode[4], "rex", false), }; rex.Find = funcNode[2].FindTokenAndGetValue <string>(); currentFunction = rex; } break; case "splice": { ValidateArgumentList(funcNode[3], "as"); var targetField = GetOptionalVariable(funcNode[1]); currentFunction = new TplSplice(funcNode[2].FindTokenAndGetValue <string>()) { TargetField = targetField, AsField = GetNamedArgumentValue(funcNode[3], "as", targetField) }; } break; case "concat": { var concatValues = funcNode[3].ChildNodes .Select(value => value.FindToken()) .Select(t => new ConcatValue(t.ValueString, t.Text[0] == '$')) .ToList(); currentFunction = new TplStringConcat(concatValues, funcNode[1].FindToken().ValueString); } break; case "padleft": case "padright": { ValidateArgumentList(funcNode[3], "char"); currentFunction = new TplStringPad() { TargetFields = GetOptionalVariableList(funcNode[1], TplResult.DEFAULT_FIELD), TotalLength = funcNode[2].FindTokenAndGetValue <int>(), PaddingChar = GetNamedArgumentValue <char>(funcNode[3], "char", ' '), PadRight = funcName == "padright", }; } break; case "substr": { ValidateArgumentList(funcNode[4], "as"); var target = GetOptionalVariable(funcNode[1]); currentFunction = new TplSubstring() { TargetField = target, StartIndex = funcNode[2].FindTokenAndGetValue <int>(), MaxLength = funcNode[3].FindTokenAndGetValue <int>(-1), AsField = GetNamedArgumentValue <string>(funcNode[4], "as", target), }; } break; case "split": { currentFunction = new TplSplit() { TargetField = GetOptionalVariable(funcNode[1]), SplitOn = funcNode[2].FindTokenAndGetValue <string>(), }; } break; case "readlines": { ValidateArgumentList(funcNode[2], "recurse"); currentFunction = new TplReadLines() { FilePath = funcNode[1].FindTokenAndGetValue <string>(), Recurse = GetNamedArgumentValue(funcNode[2], "recurse", false), }; } break; default: throw new InvalidOperationException($"Invalid function name '{funcName}'"); } if (rootFunction == null) { rootFunction = currentFunction; } else { rootFunction.AddToPipeline(currentFunction); } } return(rootFunction); } }
public void Constructor_Testing() { //Count var tplStat = new TplStats(new ParsableString("stats count")); Assert.IsFalse(tplStat._sum, "Sum was true"); Assert.IsTrue(tplStat._count, "Count was false"); //Count sum tplStat = new TplStats(new ParsableString("stats count sum price")); Assert.IsTrue(tplStat._sum, "Sum was false"); Assert.IsTrue(tplStat._count, "Count was false"); Assert.IsTrue(tplStat.TargetFields.Count == 1); Assert.IsTrue(tplStat.TargetFields.Contains("price")); Assert.IsTrue(tplStat.ByFields == null); tplStat = new TplStats(new ParsableString("stats count sum price, tax")); Assert.IsTrue(tplStat._sum, "Sum was false"); Assert.IsTrue(tplStat._count, "Count was false"); Assert.IsTrue(tplStat.TargetFields.Count == 2); Assert.IsTrue(tplStat.TargetFields.Contains("price")); Assert.IsTrue(tplStat.TargetFields.Contains("tax")); Assert.IsTrue(tplStat.ByFields == null); //Sum tplStat = new TplStats(new ParsableString("stats sum field1")); Assert.IsTrue(tplStat._sum, "Sum was false"); Assert.IsFalse(tplStat._count, "Count was true"); Assert.IsTrue(tplStat.TargetFields.Count == 1); Assert.IsTrue(tplStat.TargetFields.Contains("field1")); //Sum Count tplStat = new TplStats(new ParsableString("stats sum count price")); Assert.IsTrue(tplStat._sum, "Sum was false"); Assert.IsTrue(tplStat._count, "Count was false"); Assert.IsTrue(tplStat.TargetFields.Count == 1); Assert.IsTrue(tplStat.TargetFields.Contains("price")); Assert.IsTrue(tplStat.ByFields == null); tplStat = new TplStats(new ParsableString("stats sum count price, tax")); Assert.IsTrue(tplStat._sum, "Sum was false"); Assert.IsTrue(tplStat._count, "Count was false"); Assert.IsTrue(tplStat.TargetFields.Count == 2); Assert.IsTrue(tplStat.TargetFields.Contains("price")); Assert.IsTrue(tplStat.TargetFields.Contains("tax")); Assert.IsTrue(tplStat.ByFields == null); //Sum not field fail check try { tplStat = new TplStats(new ParsableString("stats sum")); Assert.IsTrue(false, "Constructor allowed sum without field(s)"); } catch { Assert.IsTrue(true); } //All of the above with by clauses //Count tplStat = new TplStats(new ParsableString("stats count by field1")); Assert.IsFalse(tplStat._sum, "Sum was true"); Assert.IsTrue(tplStat._count, "Count was false"); Assert.IsTrue(tplStat.ByFields.Contains("field1"), "by clause field name did not match expected"); //Count sum tplStat = new TplStats(new ParsableString("stats count sum price by field1 field2")); Assert.IsTrue(tplStat._sum, "Sum was false"); Assert.IsTrue(tplStat._count, "Count was false"); Assert.IsTrue(tplStat.TargetFields.Count == 1); Assert.IsTrue(tplStat.TargetFields.Contains("price")); Assert.IsTrue(tplStat.ByFields.Contains("field1"), "by clause field name did not match expected"); Assert.IsTrue(tplStat.ByFields.Contains("field2"), "by clause field name did not match expected"); tplStat = new TplStats(new ParsableString("stats count sum price, tax by field1, field2")); Assert.IsTrue(tplStat._sum, "Sum was false"); Assert.IsTrue(tplStat._count, "Count was false"); Assert.IsTrue(tplStat.TargetFields.Count == 2); Assert.IsTrue(tplStat.TargetFields.Contains("price")); Assert.IsTrue(tplStat.TargetFields.Contains("tax")); Assert.IsTrue(tplStat.ByFields.Contains("field1"), "by clause field name did not match expected"); Assert.IsTrue(tplStat.ByFields.Contains("field2"), "by clause field name did not match expected"); //Sum tplStat = new TplStats(new ParsableString("stats sum field1 by day")); Assert.IsTrue(tplStat._sum, "Sum was false"); Assert.IsFalse(tplStat._count, "Count was true"); Assert.IsTrue(tplStat.TargetFields.Count == 1); Assert.IsTrue(tplStat.TargetFields.Contains("field1")); Assert.IsTrue(tplStat.ByFields.Contains("day"), "by clause field name did not match expected"); //Sum Count tplStat = new TplStats(new ParsableString("stats sum count price by store, day")); Assert.IsTrue(tplStat._sum, "Sum was false"); Assert.IsTrue(tplStat._count, "Count was false"); Assert.IsTrue(tplStat.TargetFields.Count == 1); Assert.IsTrue(tplStat.TargetFields.Contains("price")); Assert.IsTrue(tplStat.ByFields.Contains("store"), "by clause field name did not match expected"); Assert.IsTrue(tplStat.ByFields.Contains("day"), "by clause field name did not match expected"); tplStat = new TplStats(new ParsableString("stats sum count price, tax by store day")); Assert.IsTrue(tplStat._sum, "Sum was false"); Assert.IsTrue(tplStat._count, "Count was false"); Assert.IsTrue(tplStat.TargetFields.Count == 2); Assert.IsTrue(tplStat.TargetFields.Contains("price")); Assert.IsTrue(tplStat.TargetFields.Contains("tax")); Assert.IsTrue(tplStat.ByFields.Contains("store"), "by clause field name did not match expected"); Assert.IsTrue(tplStat.ByFields.Contains("day"), "by clause field name did not match expected"); //Sum not field fail check try { tplStat = new TplStats(new ParsableString("stats sum by day")); Assert.IsTrue(false, "Constructor allowed sum without field(s)"); } catch { Assert.IsTrue(true); } }