github.com/getgauge/gauge@v1.6.9/gauge/specification.go (about)

     1  /*----------------------------------------------------------------
     2   *  Copyright (c) ThoughtWorks, Inc.
     3   *  Licensed under the Apache License, Version 2.0
     4   *  See LICENSE in the project root for license information.
     5   *----------------------------------------------------------------*/
     6  
     7  package gauge
     8  
     9  import (
    10  	"reflect"
    11  )
    12  
    13  type HeadingType int
    14  
    15  const (
    16  	SpecHeading     = 0
    17  	ScenarioHeading = 1
    18  )
    19  
    20  type TokenKind int
    21  
    22  const (
    23  	SpecKind TokenKind = iota
    24  	TagKind
    25  	ScenarioKind
    26  	CommentKind
    27  	StepKind
    28  	TableHeader
    29  	TableRow
    30  	HeadingKind
    31  	TableKind
    32  	DataTableKind
    33  	TearDownKind
    34  )
    35  
    36  type Specification struct {
    37  	Heading       *Heading
    38  	Scenarios     []*Scenario
    39  	Comments      []*Comment
    40  	DataTable     DataTable
    41  	Contexts      []*Step
    42  	FileName      string
    43  	Tags          *Tags
    44  	Items         []Item
    45  	TearDownSteps []*Step
    46  }
    47  
    48  type Item interface {
    49  	Kind() TokenKind
    50  }
    51  
    52  func (spec *Specification) Kind() TokenKind {
    53  	return SpecKind
    54  }
    55  
    56  // Steps gives all the steps present in Specification
    57  func (spec *Specification) Steps() []*Step {
    58  	steps := spec.Contexts
    59  	for _, scen := range spec.Scenarios {
    60  		steps = append(steps, scen.Steps...)
    61  	}
    62  	return append(steps, spec.TearDownSteps...)
    63  }
    64  
    65  func (spec *Specification) ProcessConceptStepsFrom(conceptDictionary *ConceptDictionary) error {
    66  	for _, step := range spec.Contexts {
    67  		if err := spec.processConceptStep(step, conceptDictionary); err != nil {
    68  			return err
    69  		}
    70  	}
    71  	for _, scenario := range spec.Scenarios {
    72  		for _, step := range scenario.Steps {
    73  			if err := spec.processConceptStep(step, conceptDictionary); err != nil {
    74  				return err
    75  			}
    76  		}
    77  	}
    78  	for _, step := range spec.TearDownSteps {
    79  		if err := spec.processConceptStep(step, conceptDictionary); err != nil {
    80  			return err
    81  		}
    82  	}
    83  	return nil
    84  }
    85  
    86  func (spec *Specification) processConceptStep(step *Step, conceptDictionary *ConceptDictionary) error {
    87  	if conceptFromDictionary := conceptDictionary.Search(step.Value); conceptFromDictionary != nil {
    88  		return spec.createConceptStep(conceptFromDictionary.ConceptStep, step)
    89  	}
    90  	return nil
    91  }
    92  
    93  func (spec *Specification) createConceptStep(concept *Step, originalStep *Step) error {
    94  	stepCopy, err := concept.GetCopy()
    95  	if err != nil {
    96  		return err
    97  	}
    98  	originalArgs := originalStep.Args
    99  	originalStep.CopyFrom(stepCopy)
   100  	originalStep.Args = originalArgs
   101  
   102  	// set parent of all concept steps to be the current concept (referred as originalStep here)
   103  	// this is used to fetch from parent's lookup when nested
   104  	for _, conceptStep := range originalStep.ConceptSteps {
   105  		conceptStep.Parent = originalStep
   106  	}
   107  
   108  	return spec.PopulateConceptLookup(&originalStep.Lookup, concept.Args, originalStep.Args)
   109  }
   110  
   111  func (spec *Specification) AddItem(itemToAdd Item) {
   112  	if spec.Items == nil {
   113  		spec.Items = make([]Item, 0)
   114  	}
   115  
   116  	spec.Items = append(spec.Items, itemToAdd)
   117  }
   118  
   119  func (spec *Specification) AddHeading(heading *Heading) {
   120  	heading.HeadingType = SpecHeading
   121  	spec.Heading = heading
   122  }
   123  
   124  func (spec *Specification) AddScenario(scenario *Scenario) {
   125  	spec.Scenarios = append(spec.Scenarios, scenario)
   126  	spec.AddItem(scenario)
   127  }
   128  
   129  func (spec *Specification) AddContext(contextStep *Step) {
   130  	spec.Contexts = append(spec.Contexts, contextStep)
   131  	spec.AddItem(contextStep)
   132  }
   133  
   134  func (spec *Specification) AddComment(comment *Comment) {
   135  	spec.Comments = append(spec.Comments, comment)
   136  	spec.AddItem(comment)
   137  }
   138  
   139  func (spec *Specification) AddDataTable(table *Table) {
   140  	spec.DataTable.Table = table
   141  	spec.AddItem(&spec.DataTable)
   142  }
   143  
   144  func (spec *Specification) AddExternalDataTable(externalTable *DataTable) {
   145  	spec.DataTable = *externalTable
   146  	spec.AddItem(externalTable)
   147  }
   148  
   149  func (spec *Specification) AddTags(tags *Tags) {
   150  	spec.Tags = tags
   151  	spec.AddItem(spec.Tags)
   152  }
   153  
   154  func (spec *Specification) NTags() int {
   155  	if spec.Tags == nil {
   156  		return 0
   157  	}
   158  	return len(spec.Tags.Values())
   159  }
   160  
   161  func (spec *Specification) LatestScenario() *Scenario {
   162  	return spec.Scenarios[len(spec.Scenarios)-1]
   163  }
   164  
   165  func (spec *Specification) LatestContext() *Step {
   166  	return spec.Contexts[len(spec.Contexts)-1]
   167  }
   168  
   169  func (spec *Specification) LatestTeardown() *Step {
   170  	return spec.TearDownSteps[len(spec.TearDownSteps)-1]
   171  }
   172  
   173  func (spec *Specification) removeItem(itemIndex int) {
   174  	item := spec.Items[itemIndex]
   175  	items := make([]Item, len(spec.Items))
   176  	copy(items, spec.Items)
   177  	if len(spec.Items)-1 == itemIndex {
   178  		spec.Items = items[:itemIndex]
   179  	} else if 0 == itemIndex {
   180  		spec.Items = items[itemIndex+1:]
   181  	} else {
   182  		spec.Items = append(items[:itemIndex], items[itemIndex+1:]...)
   183  	}
   184  	if item.Kind() == ScenarioKind {
   185  		spec.removeScenario(item.(*Scenario))
   186  	}
   187  }
   188  
   189  func (spec *Specification) removeScenario(scenario *Scenario) {
   190  	index := getIndexFor(scenario, spec.Scenarios)
   191  	scenarios := make([]*Scenario, len(spec.Scenarios))
   192  	copy(scenarios, spec.Scenarios)
   193  	if len(spec.Scenarios)-1 == index {
   194  		spec.Scenarios = scenarios[:index]
   195  	} else if index == 0 {
   196  		spec.Scenarios = scenarios[index+1:]
   197  	} else {
   198  		spec.Scenarios = append(scenarios[:index], scenarios[index+1:]...)
   199  	}
   200  }
   201  
   202  func (spec *Specification) PopulateConceptLookup(lookup *ArgLookup, conceptArgs []*StepArg, stepArgs []*StepArg) error {
   203  	for i, arg := range stepArgs {
   204  		stepArg := StepArg{Value: arg.Value, ArgType: arg.ArgType, Table: arg.Table, Name: arg.Name}
   205  		if err := lookup.AddArgValue(conceptArgs[i].Value, &stepArg); err != nil {
   206  			return err
   207  		}
   208  	}
   209  	return nil
   210  }
   211  
   212  func (spec *Specification) RenameSteps(oldStep *Step, newStep *Step, orderMap map[int]int) ([]*StepDiff, bool) {
   213  	diffs, isRefactored := spec.rename(spec.Contexts, oldStep, newStep, false, orderMap)
   214  	for _, scenario := range spec.Scenarios {
   215  		scenStepDiffs, refactor := scenario.renameSteps(oldStep, newStep, orderMap)
   216  		diffs = append(diffs, scenStepDiffs...)
   217  		if refactor {
   218  			isRefactored = refactor
   219  		}
   220  	}
   221  	teardownStepdiffs, isRefactored := spec.rename(spec.TearDownSteps, oldStep, newStep, isRefactored, orderMap)
   222  	return append(diffs, teardownStepdiffs...), isRefactored
   223  }
   224  
   225  func (spec *Specification) rename(steps []*Step, oldStep *Step, newStep *Step, isRefactored bool, orderMap map[int]int) ([]*StepDiff, bool) {
   226  	diffs := []*StepDiff{}
   227  	isConcept := false
   228  	for _, step := range steps {
   229  		diff, refactor := step.Rename(oldStep, newStep, isRefactored, orderMap, &isConcept)
   230  		if diff != nil {
   231  			diffs = append(diffs, diff)
   232  		}
   233  		if refactor {
   234  			isRefactored = refactor
   235  		}
   236  	}
   237  	return diffs, isRefactored
   238  }
   239  
   240  func (spec *Specification) GetSpecItems() []Item {
   241  	specItems := make([]Item, 0)
   242  	for _, item := range spec.Items {
   243  		if item.Kind() != ScenarioKind {
   244  			specItems = append(specItems, item)
   245  		}
   246  	}
   247  	return specItems
   248  }
   249  
   250  func (spec *Specification) Traverse(processor ItemProcessor, queue *ItemQueue) {
   251  	processor.Specification(spec)
   252  	processor.Heading(spec.Heading)
   253  
   254  	for queue.Peek() != nil {
   255  		item := queue.Next()
   256  		switch item.Kind() {
   257  		case ScenarioKind:
   258  			processor.Heading(item.(*Scenario).Heading)
   259  			processor.Scenario(item.(*Scenario))
   260  		case StepKind:
   261  			processor.Step(item.(*Step))
   262  		case CommentKind:
   263  			processor.Comment(item.(*Comment))
   264  		case TableKind:
   265  			processor.Table(item.(*Table))
   266  		case TagKind:
   267  			processor.Tags(item.(*Tags))
   268  		case TearDownKind:
   269  			processor.TearDown(item.(*TearDown))
   270  		case DataTableKind:
   271  			processor.DataTable(item.(*DataTable))
   272  		}
   273  	}
   274  }
   275  
   276  func (spec *Specification) AllItems() (items []Item) {
   277  	for _, item := range spec.Items {
   278  		items = append(items, item)
   279  		if item.Kind() == ScenarioKind {
   280  			items = append(items, item.(*Scenario).Items...)
   281  		}
   282  	}
   283  	return
   284  }
   285  
   286  func (spec *Specification) UsesArgsInContextTeardown(args ...string) bool {
   287  	return UsesArgs(append(spec.Contexts, spec.TearDownSteps...), args...)
   288  }
   289  
   290  type SpecItemFilter interface {
   291  	Filter(Item) bool
   292  }
   293  
   294  func (spec *Specification) Filter(filter SpecItemFilter) (*Specification, *Specification) {
   295  	specWithFilteredItems := new(Specification)
   296  	specWithOtherItems := new(Specification)
   297  	*specWithFilteredItems, *specWithOtherItems = *spec, *spec
   298  	for i := 0; i < len(specWithFilteredItems.Items); i++ {
   299  		item := specWithFilteredItems.Items[i]
   300  		if item.Kind() == ScenarioKind && filter.Filter(item) {
   301  			specWithFilteredItems.removeItem(i)
   302  			i--
   303  		}
   304  	}
   305  	for i := 0; i < len(specWithOtherItems.Items); i++ {
   306  		item := specWithOtherItems.Items[i]
   307  		if item.Kind() == ScenarioKind && !filter.Filter(item) {
   308  			specWithOtherItems.removeItem(i)
   309  			i--
   310  		}
   311  	}
   312  	return specWithFilteredItems, specWithOtherItems
   313  }
   314  
   315  func getIndexFor(scenario *Scenario, scenarios []*Scenario) int {
   316  	for index, anItem := range scenarios {
   317  		if reflect.DeepEqual(scenario, anItem) {
   318  			return index
   319  		}
   320  	}
   321  	return -1
   322  }
   323  
   324  type Heading struct {
   325  	Value       string
   326  	LineNo      int
   327  	SpanEnd     int
   328  	HeadingType HeadingType
   329  }
   330  
   331  func (heading *Heading) Kind() TokenKind {
   332  	return HeadingKind
   333  }
   334  
   335  type Comment struct {
   336  	Value  string
   337  	LineNo int
   338  }
   339  
   340  func (comment *Comment) Kind() TokenKind {
   341  	return CommentKind
   342  }
   343  
   344  type TearDown struct {
   345  	LineNo int
   346  	Value  string
   347  }
   348  
   349  func (t *TearDown) Kind() TokenKind {
   350  	return TearDownKind
   351  }
   352  
   353  type Tags struct {
   354  	RawValues [][]string
   355  }
   356  
   357  func (tags *Tags) Add(values []string) {
   358  	tags.RawValues = append(tags.RawValues, values)
   359  }
   360  
   361  func (tags *Tags) Values() (val []string) {
   362  	for i := range tags.RawValues {
   363  		val = append(val, tags.RawValues[i]...)
   364  	}
   365  	return val
   366  }
   367  func (tags *Tags) Kind() TokenKind {
   368  	return TagKind
   369  }