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 }