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 }