github.com/mattdotmatt/gauge@v0.3.2-0.20160421115137-425a4cdccb62/gauge/specification.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 gauge
    19  
    20  import "reflect"
    21  
    22  type HeadingType int
    23  
    24  const (
    25  	SpecHeading     = 0
    26  	ScenarioHeading = 1
    27  )
    28  
    29  type Specification struct {
    30  	Heading       *Heading
    31  	Scenarios     []*Scenario
    32  	Comments      []*Comment
    33  	DataTable     DataTable
    34  	Contexts      []*Step
    35  	FileName      string
    36  	Tags          *Tags
    37  	Items         []Item
    38  	TearDownSteps []*Step
    39  }
    40  
    41  type Item interface {
    42  	Kind() TokenKind
    43  }
    44  
    45  func (spec *Specification) ProcessConceptStepsFrom(conceptDictionary *ConceptDictionary) {
    46  	for _, step := range spec.Contexts {
    47  		spec.processConceptStep(step, conceptDictionary)
    48  	}
    49  	for _, scenario := range spec.Scenarios {
    50  		for _, step := range scenario.Steps {
    51  			spec.processConceptStep(step, conceptDictionary)
    52  		}
    53  	}
    54  	for _, step := range spec.TearDownSteps {
    55  		spec.processConceptStep(step, conceptDictionary)
    56  	}
    57  }
    58  
    59  func (spec *Specification) processConceptStep(step *Step, conceptDictionary *ConceptDictionary) {
    60  	if conceptFromDictionary := conceptDictionary.Search(step.Value); conceptFromDictionary != nil {
    61  		spec.createConceptStep(conceptFromDictionary.ConceptStep, step)
    62  	}
    63  }
    64  
    65  func (spec *Specification) createConceptStep(concept *Step, originalStep *Step) {
    66  	stepCopy := concept.GetCopy()
    67  	originalArgs := originalStep.Args
    68  	originalStep.CopyFrom(stepCopy)
    69  	originalStep.Args = originalArgs
    70  
    71  	// set parent of all concept steps to be the current concept (referred as originalStep here)
    72  	// this is used to fetch from parent's lookup when nested
    73  	for _, conceptStep := range originalStep.ConceptSteps {
    74  		conceptStep.Parent = originalStep
    75  	}
    76  
    77  	spec.PopulateConceptLookup(&originalStep.Lookup, concept.Args, originalStep.Args)
    78  }
    79  
    80  func (spec *Specification) AddItem(itemToAdd Item) {
    81  	if spec.Items == nil {
    82  		spec.Items = make([]Item, 0)
    83  	}
    84  
    85  	spec.Items = append(spec.Items, itemToAdd)
    86  }
    87  
    88  func (spec *Specification) AddHeading(heading *Heading) {
    89  	heading.HeadingType = SpecHeading
    90  	spec.Heading = heading
    91  }
    92  
    93  func (spec *Specification) AddScenario(scenario *Scenario) {
    94  	spec.Scenarios = append(spec.Scenarios, scenario)
    95  	spec.AddItem(scenario)
    96  }
    97  
    98  func (spec *Specification) AddContext(contextStep *Step) {
    99  	spec.Contexts = append(spec.Contexts, contextStep)
   100  	spec.AddItem(contextStep)
   101  }
   102  
   103  func (spec *Specification) AddComment(comment *Comment) {
   104  	spec.Comments = append(spec.Comments, comment)
   105  	spec.AddItem(comment)
   106  }
   107  
   108  func (spec *Specification) AddDataTable(table *Table) {
   109  	spec.DataTable.Table = *table
   110  	spec.AddItem(&spec.DataTable)
   111  }
   112  
   113  func (spec *Specification) AddExternalDataTable(externalTable *DataTable) {
   114  	spec.DataTable = *externalTable
   115  	spec.AddItem(externalTable)
   116  }
   117  
   118  func (spec *Specification) AddTags(tags *Tags) {
   119  	spec.Tags = tags
   120  	spec.AddItem(tags)
   121  }
   122  
   123  func (spec *Specification) LatestScenario() *Scenario {
   124  	return spec.Scenarios[len(spec.Scenarios)-1]
   125  }
   126  
   127  func (spec *Specification) LatestContext() *Step {
   128  	return spec.Contexts[len(spec.Contexts)-1]
   129  }
   130  
   131  func (spec *Specification) LatestTeardown() *Step {
   132  	return spec.TearDownSteps[len(spec.TearDownSteps)-1]
   133  }
   134  
   135  func (spec *Specification) removeItem(itemIndex int) {
   136  	item := spec.Items[itemIndex]
   137  	if len(spec.Items)-1 == itemIndex {
   138  		spec.Items = spec.Items[:itemIndex]
   139  	} else if 0 == itemIndex {
   140  		spec.Items = spec.Items[itemIndex+1:]
   141  	} else {
   142  		spec.Items = append(spec.Items[:itemIndex], spec.Items[itemIndex+1:]...)
   143  	}
   144  	if item.Kind() == ScenarioKind {
   145  		spec.removeScenario(item.(*Scenario))
   146  	}
   147  }
   148  
   149  func (spec *Specification) removeScenario(scenario *Scenario) {
   150  	index := getIndexFor(scenario, spec.Scenarios)
   151  	if len(spec.Scenarios)-1 == index {
   152  		spec.Scenarios = spec.Scenarios[:index]
   153  	} else if index == 0 {
   154  		spec.Scenarios = spec.Scenarios[index+1:]
   155  	} else {
   156  		spec.Scenarios = append(spec.Scenarios[:index], spec.Scenarios[index+1:]...)
   157  	}
   158  }
   159  
   160  func (spec *Specification) PopulateConceptLookup(lookup *ArgLookup, conceptArgs []*StepArg, stepArgs []*StepArg) {
   161  	for i, arg := range stepArgs {
   162  		lookup.AddArgValue(conceptArgs[i].Value, &StepArg{Value: arg.Value, ArgType: arg.ArgType, Table: arg.Table, Name: arg.Name})
   163  	}
   164  }
   165  
   166  func (spec *Specification) RenameSteps(oldStep Step, newStep Step, orderMap map[int]int) bool {
   167  	isRefactored := false
   168  	for _, step := range spec.Contexts {
   169  		isConcept := false
   170  		isRefactored = step.Rename(oldStep, newStep, isRefactored, orderMap, &isConcept)
   171  	}
   172  	for _, scenario := range spec.Scenarios {
   173  		refactor := scenario.renameSteps(oldStep, newStep, orderMap)
   174  		if refactor {
   175  			isRefactored = refactor
   176  		}
   177  	}
   178  	return isRefactored
   179  }
   180  
   181  func (spec *Specification) GetSpecItems() []Item {
   182  	specItems := make([]Item, 0)
   183  	for _, item := range spec.Items {
   184  		if item.Kind() != ScenarioKind {
   185  			specItems = append(specItems, item)
   186  		}
   187  		if item.Kind() == TearDownKind {
   188  			return specItems
   189  		}
   190  	}
   191  	return specItems
   192  }
   193  
   194  func (spec *Specification) Traverse(traverser SpecTraverser) {
   195  	traverser.SpecHeading(spec.Heading)
   196  	for _, item := range spec.Items {
   197  		switch item.Kind() {
   198  		case ScenarioKind:
   199  			item.(*Scenario).Traverse(traverser)
   200  			traverser.Scenario(item.(*Scenario))
   201  		case StepKind:
   202  			traverser.ContextStep(item.(*Step))
   203  		case CommentKind:
   204  			traverser.Comment(item.(*Comment))
   205  		case TableKind:
   206  			traverser.DataTable(item.(*Table))
   207  		case TagKind:
   208  			traverser.SpecTags(item.(*Tags))
   209  		case TearDownKind:
   210  			traverser.TearDown(item.(*TearDown))
   211  		case DataTableKind:
   212  			if !item.(*DataTable).IsExternal {
   213  				traverser.DataTable(&item.(*DataTable).Table)
   214  			} else {
   215  				traverser.ExternalDataTable(item.(*DataTable))
   216  			}
   217  		}
   218  	}
   219  }
   220  
   221  type SpecItemFilter interface {
   222  	Filter(Item) bool
   223  }
   224  
   225  func (spec *Specification) Filter(filter SpecItemFilter) {
   226  	for i := 0; i < len(spec.Items); i++ {
   227  		if filter.Filter(spec.Items[i]) {
   228  			spec.removeItem(i)
   229  			i--
   230  		}
   231  	}
   232  }
   233  
   234  func getIndexFor(scenario *Scenario, scenarios []*Scenario) int {
   235  	for index, anItem := range scenarios {
   236  		if reflect.DeepEqual(scenario, anItem) {
   237  			return index
   238  		}
   239  	}
   240  	return -1
   241  }
   242  
   243  type TokenKind int
   244  
   245  const (
   246  	SpecKind TokenKind = iota
   247  	TagKind
   248  	ScenarioKind
   249  	CommentKind
   250  	StepKind
   251  	TableHeader
   252  	TableRow
   253  	HeadingKind
   254  	TableKind
   255  	DataTableKind
   256  	TearDownKind
   257  )
   258  
   259  type Heading struct {
   260  	Value       string
   261  	LineNo      int
   262  	HeadingType HeadingType
   263  }
   264  
   265  func (heading *Heading) Kind() TokenKind {
   266  	return HeadingKind
   267  }
   268  
   269  type Comment struct {
   270  	Value  string
   271  	LineNo int
   272  }
   273  
   274  func (comment *Comment) Kind() TokenKind {
   275  	return CommentKind
   276  }
   277  
   278  type TearDown struct {
   279  	LineNo int
   280  	Value  string
   281  }
   282  
   283  func (t *TearDown) Kind() TokenKind {
   284  	return TearDownKind
   285  }
   286  
   287  type Tags struct {
   288  	Values []string
   289  }
   290  
   291  func (tags *Tags) Kind() TokenKind {
   292  	return TagKind
   293  }