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  }