gitee.com/mirrors/gauge@v1.0.6/parser/specparser.go (about) 1 // Copyright 2015 ThoughtWorks, Inc. 2 3 // This file is part of Gauge. 4 5 // Gauge is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 10 // Gauge is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 15 // You should have received a copy of the GNU General Public License 16 // along with Gauge. If not, see <http://www.gnu.org/licenses/>. 17 18 package parser 19 20 import ( 21 "bufio" 22 "strings" 23 24 "github.com/getgauge/gauge/gauge" 25 ) 26 27 // SpecParser is responsible for parsing a Specification. It delegates to respective processors composed sub-entities 28 type SpecParser struct { 29 scanner *bufio.Scanner 30 lineNo int 31 tokens []*Token 32 currentState int 33 processors map[gauge.TokenKind]func(*SpecParser, *Token) ([]error, bool) 34 conceptDictionary *gauge.ConceptDictionary 35 } 36 37 // Parse generates tokens for the given spec text and creates the specification. 38 func (parser *SpecParser) Parse(specText string, conceptDictionary *gauge.ConceptDictionary, specFile string) (*gauge.Specification, *ParseResult, error) { 39 tokens, errs := parser.GenerateTokens(specText, specFile) 40 spec, res, err := parser.CreateSpecification(tokens, conceptDictionary, specFile) 41 if err != nil { 42 return nil, nil, err 43 } 44 res.FileName = specFile 45 if len(errs) > 0 { 46 res.Ok = false 47 } 48 res.ParseErrors = append(errs, res.ParseErrors...) 49 return spec, res, nil 50 } 51 52 // ParseSpecText without validating and replacing concepts. 53 func (parser *SpecParser) ParseSpecText(specText string, specFile string) (*gauge.Specification, *ParseResult) { 54 tokens, errs := parser.GenerateTokens(specText, specFile) 55 spec, res := parser.createSpecification(tokens, specFile) 56 res.FileName = specFile 57 if len(errs) > 0 { 58 res.Ok = false 59 } 60 res.ParseErrors = append(errs, res.ParseErrors...) 61 return spec, res 62 } 63 64 // CreateSpecification creates specification from the given set of tokens. 65 func (parser *SpecParser) CreateSpecification(tokens []*Token, conceptDictionary *gauge.ConceptDictionary, specFile string) (*gauge.Specification, *ParseResult, error) { 66 parser.conceptDictionary = conceptDictionary 67 specification, finalResult := parser.createSpecification(tokens, specFile) 68 if err := specification.ProcessConceptStepsFrom(conceptDictionary); err != nil { 69 return nil, nil, err 70 } 71 err := parser.validateSpec(specification) 72 if err != nil { 73 finalResult.Ok = false 74 finalResult.ParseErrors = append([]ParseError{err.(ParseError)}, finalResult.ParseErrors...) 75 } 76 return specification, finalResult, nil 77 } 78 79 func (parser *SpecParser) createSpecification(tokens []*Token, specFile string) (*gauge.Specification, *ParseResult) { 80 finalResult := &ParseResult{ParseErrors: make([]ParseError, 0), Ok: true} 81 converters := parser.initializeConverters() 82 specification := &gauge.Specification{FileName: specFile} 83 state := initial 84 for _, token := range tokens { 85 for _, converter := range converters { 86 result := converter(token, &state, specification) 87 if !result.Ok { 88 if result.ParseErrors != nil { 89 finalResult.Ok = false 90 finalResult.ParseErrors = append(finalResult.ParseErrors, result.ParseErrors...) 91 } 92 } 93 if result.Warnings != nil { 94 if finalResult.Warnings == nil { 95 finalResult.Warnings = make([]*Warning, 0) 96 } 97 finalResult.Warnings = append(finalResult.Warnings, result.Warnings...) 98 } 99 } 100 } 101 if len(specification.Scenarios) > 0 { 102 specification.LatestScenario().Span.End = tokens[len(tokens)-1].LineNo 103 } 104 return specification, finalResult 105 } 106 107 func (parser *SpecParser) validateSpec(specification *gauge.Specification) error { 108 if len(specification.Items) == 0 { 109 specification.AddHeading(&gauge.Heading{}) 110 return ParseError{FileName: specification.FileName, LineNo: 1, Message: "Spec does not have any elements"} 111 } 112 if specification.Heading == nil { 113 specification.AddHeading(&gauge.Heading{}) 114 return ParseError{FileName: specification.FileName, LineNo: 1, Message: "Spec heading not found"} 115 } 116 if len(strings.TrimSpace(specification.Heading.Value)) < 1 { 117 return ParseError{FileName: specification.FileName, LineNo: specification.Heading.LineNo, Message: "Spec heading should have at least one character"} 118 } 119 120 dataTable := specification.DataTable.Table 121 if dataTable.IsInitialized() && dataTable.GetRowCount() == 0 { 122 return ParseError{FileName: specification.FileName, LineNo: dataTable.LineNo, Message: "Data table should have at least 1 data row"} 123 } 124 if len(specification.Scenarios) == 0 { 125 return ParseError{FileName: specification.FileName, LineNo: specification.Heading.LineNo, Message: "Spec should have atleast one scenario"} 126 } 127 for _, sce := range specification.Scenarios { 128 if len(sce.Steps) == 0 { 129 return ParseError{FileName: specification.FileName, LineNo: sce.Heading.LineNo, Message: "Scenario should have atleast one step"} 130 } 131 } 132 return nil 133 } 134 135 func createStep(spec *gauge.Specification, scn *gauge.Scenario, stepToken *Token) (*gauge.Step, *ParseResult) { 136 tables := []*gauge.Table{&spec.DataTable.Table} 137 if scn != nil { 138 tables = append(tables, &scn.DataTable.Table) 139 } 140 dataTableLookup := new(gauge.ArgLookup).FromDataTables(tables...) 141 stepToAdd, parseDetails := CreateStepUsingLookup(stepToken, dataTableLookup, spec.FileName) 142 if stepToAdd != nil { 143 stepToAdd.Suffix = stepToken.Suffix 144 } 145 return stepToAdd, parseDetails 146 } 147 148 // CreateStepUsingLookup generates gauge steps from step token and args lookup. 149 func CreateStepUsingLookup(stepToken *Token, lookup *gauge.ArgLookup, specFileName string) (*gauge.Step, *ParseResult) { 150 stepValue, argsType := extractStepValueAndParameterTypes(stepToken.Value) 151 if argsType != nil && len(argsType) != len(stepToken.Args) { 152 return nil, &ParseResult{ParseErrors: []ParseError{ParseError{specFileName, stepToken.LineNo, "Step text should not have '{static}' or '{dynamic}' or '{special}'", stepToken.LineText}}, Warnings: nil} 153 } 154 step := &gauge.Step{FileName: specFileName, LineNo: stepToken.LineNo, Value: stepValue, LineText: strings.TrimSpace(stepToken.LineText)} 155 arguments := make([]*gauge.StepArg, 0) 156 var errors []ParseError 157 var warnings []*Warning 158 for i, argType := range argsType { 159 argument, parseDetails := createStepArg(stepToken.Args[i], argType, stepToken, lookup, specFileName) 160 if parseDetails != nil && len(parseDetails.ParseErrors) > 0 { 161 errors = append(errors, parseDetails.ParseErrors...) 162 } 163 arguments = append(arguments, argument) 164 if parseDetails != nil && parseDetails.Warnings != nil { 165 for _, warn := range parseDetails.Warnings { 166 warnings = append(warnings, warn) 167 } 168 } 169 } 170 step.AddArgs(arguments...) 171 return step, &ParseResult{ParseErrors: errors, Warnings: warnings} 172 }