github.com/ezbuy/gauge@v0.9.4-0.20171013092048-7ac5bd3931cd/parser/parse.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 "strings" 22 23 "regexp" 24 "strconv" 25 26 "github.com/getgauge/common" 27 "github.com/getgauge/gauge/filter" 28 "github.com/getgauge/gauge/gauge" 29 "github.com/getgauge/gauge/logger" 30 "github.com/getgauge/gauge/order" 31 "github.com/getgauge/gauge/util" 32 ) 33 34 // TODO: Use single channel instead of one for spec and another for result, so that mapping is consistent 35 func ParseSpecFiles(specFiles []string, conceptDictionary *gauge.ConceptDictionary, buildErrors *gauge.BuildErrors) ([]*gauge.Specification, []*ParseResult) { 36 parseResultsChan := make(chan *ParseResult, len(specFiles)) 37 specsChan := make(chan *gauge.Specification, len(specFiles)) 38 var parseResults []*ParseResult 39 var specs []*gauge.Specification 40 41 for _, specFile := range specFiles { 42 go parseSpec(specFile, conceptDictionary, specsChan, parseResultsChan) 43 } 44 for range specFiles { 45 parseRes := <-parseResultsChan 46 spec := <-specsChan 47 if spec != nil { 48 specs = append(specs, spec) 49 var parseErrs []error 50 for _, e := range parseRes.ParseErrors { 51 parseErrs = append(parseErrs, e) 52 } 53 if len(parseErrs) != 0 { 54 buildErrors.SpecErrs[spec] = parseErrs 55 } 56 } 57 parseResults = append(parseResults, parseRes) 58 } 59 return specs, parseResults 60 } 61 62 func ParseSpecs(args []string, conceptsDictionary *gauge.ConceptDictionary, buildErrors *gauge.BuildErrors) ([]*gauge.Specification, bool) { 63 specs, failed := parseSpecsInDirs(conceptsDictionary, args, buildErrors) 64 specsToExecute := order.Sort(filter.FilterSpecs(specs)) 65 return specsToExecute, failed 66 } 67 68 func ParseConcepts() (*gauge.ConceptDictionary, *ParseResult) { 69 conceptsDictionary, conceptParseResult := CreateConceptsDictionary() 70 HandleParseResult(conceptParseResult) 71 return conceptsDictionary, conceptParseResult 72 } 73 74 func parseSpec(specFile string, conceptDictionary *gauge.ConceptDictionary, specChannel chan *gauge.Specification, parseResultChan chan *ParseResult) { 75 specFileContent, err := common.ReadFileContents(specFile) 76 if err != nil { 77 specChannel <- nil 78 parseResultChan <- &ParseResult{ParseErrors: []ParseError{ParseError{FileName: specFile, Message: err.Error()}}, Ok: false} 79 return 80 } 81 spec, parseResult := new(SpecParser).Parse(specFileContent, conceptDictionary, specFile) 82 specChannel <- spec 83 parseResultChan <- parseResult 84 } 85 86 type specFile struct { 87 filePath string 88 indices []int 89 } 90 91 // parseSpecsInDirs parses all the specs in list of dirs given. 92 // It also de-duplicates all specs passed through `specDirs` before parsing specs. 93 func parseSpecsInDirs(conceptDictionary *gauge.ConceptDictionary, specDirs []string, buildErrors *gauge.BuildErrors) ([]*gauge.Specification, bool) { 94 passed := true 95 givenSpecs, specFiles := getAllSpecFiles(specDirs) 96 var specs []*gauge.Specification 97 var specParseResults []*ParseResult 98 allSpecs := make([]*gauge.Specification, len(specFiles)) 99 specs, specParseResults = ParseSpecFiles(givenSpecs, conceptDictionary, buildErrors) 100 passed = !HandleParseResult(specParseResults...) && passed 101 for _, spec := range specs { 102 i, _ := getIndexFor(specFiles, spec.FileName) 103 specFile := specFiles[i] 104 if len(specFile.indices) > 0 { 105 spec.Filter(filter.NewScenarioFilterBasedOnSpan(specFile.indices)) 106 } 107 allSpecs[i] = spec 108 } 109 return allSpecs, !passed 110 } 111 112 func getAllSpecFiles(specDirs []string) (givenSpecs []string, specFiles []*specFile) { 113 for _, specSource := range specDirs { 114 if isIndexedSpec(specSource) { 115 var specName string 116 specName, index := getIndexedSpecName(specSource) 117 files := util.GetSpecFiles(specName) 118 if len(files) < 1 { 119 continue 120 } 121 specificationFile, created := addSpecFile(&specFiles, files[0]) 122 if created || len(specificationFile.indices) > 0 { 123 specificationFile.indices = append(specificationFile.indices, index) 124 } 125 givenSpecs = append(givenSpecs, files[0]) 126 } else { 127 files := util.GetSpecFiles(specSource) 128 for _, file := range files { 129 specificationFile, _ := addSpecFile(&specFiles, file) 130 specificationFile.indices = specificationFile.indices[0:0] 131 } 132 givenSpecs = append(givenSpecs, files...) 133 } 134 } 135 return 136 } 137 138 func addSpecFile(specFiles *[]*specFile, file string) (*specFile, bool) { 139 i, exists := getIndexFor(*specFiles, file) 140 if !exists { 141 specificationFile := &specFile{filePath: file} 142 *specFiles = append(*specFiles, specificationFile) 143 return specificationFile, true 144 } 145 return (*specFiles)[i], false 146 } 147 148 func getIndexFor(files []*specFile, file string) (int, bool) { 149 for index, f := range files { 150 if f.filePath == file { 151 return index, true 152 } 153 } 154 return -1, false 155 } 156 157 func isIndexedSpec(specSource string) bool { 158 return getIndex(specSource) != 0 159 } 160 161 func getIndexedSpecName(indexedSpec string) (string, int) { 162 index := getIndex(indexedSpec) 163 specName := indexedSpec[:index] 164 scenarioNum := indexedSpec[index+1:] 165 scenarioNumber, _ := strconv.Atoi(scenarioNum) 166 return specName, scenarioNumber 167 } 168 169 func getIndex(specSource string) int { 170 re, _ := regexp.Compile(":[0-9]+$") 171 index := re.FindStringSubmatchIndex(specSource) 172 if index != nil { 173 return index[0] 174 } 175 return 0 176 } 177 178 func ExtractStepValueAndParams(stepText string, hasInlineTable bool) (*gauge.StepValue, error) { 179 stepValueWithPlaceHolders, args, err := processStepText(stepText) 180 if err != nil { 181 return nil, err 182 } 183 184 extractedStepValue, _ := extractStepValueAndParameterTypes(stepValueWithPlaceHolders) 185 if hasInlineTable { 186 extractedStepValue += " " + gauge.ParameterPlaceholder 187 args = append(args, string(gauge.TableArg)) 188 } 189 parameterizedStepValue := getParameterizeStepValue(extractedStepValue, args) 190 191 return &gauge.StepValue{args, extractedStepValue, parameterizedStepValue}, nil 192 193 } 194 195 func CreateStepValue(step *gauge.Step) gauge.StepValue { 196 stepValue := gauge.StepValue{StepValue: step.Value} 197 args := make([]string, 0) 198 for _, arg := range step.Args { 199 args = append(args, arg.ArgValue()) 200 } 201 stepValue.Args = args 202 stepValue.ParameterizedStepValue = getParameterizeStepValue(stepValue.StepValue, args) 203 return stepValue 204 } 205 206 func getParameterizeStepValue(stepValue string, params []string) string { 207 for _, param := range params { 208 stepValue = strings.Replace(stepValue, gauge.ParameterPlaceholder, "<"+param+">", 1) 209 } 210 return stepValue 211 } 212 213 func HandleParseResult(results ...*ParseResult) bool { 214 var failed = false 215 for _, result := range results { 216 if !result.Ok { 217 for _, err := range result.Errors() { 218 logger.Errorf(err) 219 } 220 failed = true 221 } 222 if result.Warnings != nil { 223 for _, warning := range result.Warnings { 224 logger.Warningf("[ParseWarning] %s", warning) 225 } 226 } 227 } 228 return failed 229 }