github.com/ezbuy/gauge@v0.9.4-0.20171013092048-7ac5bd3931cd/conceptExtractor/conceptExtractor.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 conceptExtractor 19 20 import ( 21 "errors" 22 "fmt" 23 "os" 24 "regexp" 25 "strings" 26 27 "path" 28 29 "github.com/getgauge/common" 30 "github.com/getgauge/gauge/formatter" 31 "github.com/getgauge/gauge/gauge" 32 "github.com/getgauge/gauge/gauge_messages" 33 "github.com/getgauge/gauge/parser" 34 "github.com/getgauge/gauge/util" 35 ) 36 37 const ( 38 SPEC_HEADING_TEMPLATE = "# S\n\n" 39 TABLE = "table" 40 ) 41 42 type extractor struct { 43 conceptName string 44 conceptStep *gauge.Step 45 stepsToExtract []*gauge_messages.Step 46 stepsInConcept string 47 table *gauge.Table 48 fileContent string 49 dynamicArgs []string 50 errors []error 51 } 52 53 func ExtractConcept(conceptName *gauge_messages.Step, steps []*gauge_messages.Step, conceptFileName string, changeAcrossProject bool, selectedTextInfo *gauge_messages.TextInfo) (bool, error, []string) { 54 content := SPEC_HEADING_TEMPLATE 55 if util.IsSpec(selectedTextInfo.GetFileName()) { 56 content, _ = common.ReadFileContents(selectedTextInfo.GetFileName()) 57 } 58 concept, conceptUsageText, err := getExtractedConcept(conceptName, steps, content, selectedTextInfo.GetFileName()) 59 if err != nil { 60 return false, err, []string{} 61 } 62 writeConceptToFile(concept, conceptUsageText, conceptFileName, selectedTextInfo.GetFileName(), selectedTextInfo) 63 return true, errors.New(""), []string{conceptFileName, selectedTextInfo.GetFileName()} 64 } 65 66 func ReplaceExtractedStepsWithConcept(selectedTextInfo *gauge_messages.TextInfo, conceptText string) string { 67 content, _ := common.ReadFileContents(selectedTextInfo.GetFileName()) 68 return replaceText(content, selectedTextInfo, conceptText) 69 } 70 71 func replaceText(content string, info *gauge_messages.TextInfo, replacement string) string { 72 parts := regexp.MustCompile("\r\n|\n").Split(content, -1) 73 for i := info.GetStartingLineNo(); i < info.GetEndLineNo(); i++ { 74 parts = append(parts[:info.GetStartingLineNo()], parts[info.GetStartingLineNo()+1:]...) 75 } 76 parts[info.GetStartingLineNo()-1] = replacement 77 return strings.Join(parts, "\n") 78 } 79 80 func writeConceptToFile(concept string, conceptUsageText string, conceptFileName string, fileName string, info *gauge_messages.TextInfo) { 81 if _, err := os.Stat(conceptFileName); os.IsNotExist(err) { 82 basepath := path.Dir(conceptFileName) 83 if _, err := os.Stat(basepath); os.IsNotExist(err) { 84 os.MkdirAll(basepath, common.NewDirectoryPermissions) 85 } 86 os.Create(conceptFileName) 87 } 88 content, _ := common.ReadFileContents(conceptFileName) 89 util.SaveFile(conceptFileName, content+"\n"+concept, true) 90 text := ReplaceExtractedStepsWithConcept(info, conceptUsageText) 91 util.SaveFile(fileName, text, true) 92 } 93 94 func getExtractedConcept(conceptName *gauge_messages.Step, steps []*gauge_messages.Step, content string, cptFileName string) (string, string, error) { 95 tokens, _ := new(parser.SpecParser).GenerateTokens("* "+conceptName.GetName(), cptFileName) 96 conceptStep, _ := parser.CreateStepUsingLookup(tokens[0], nil, cptFileName) 97 cptDict, _ := parser.ParseConcepts() 98 if isDuplicateConcept(conceptStep, cptDict) { 99 return "", "", fmt.Errorf("Concept `%s` already present", conceptName.GetName()) 100 } 101 specText, err := getContentWithDataTable(content, cptFileName) 102 if err != nil { 103 return "", "", err 104 } 105 extractor := &extractor{conceptName: "* " + conceptName.GetName(), stepsInConcept: "", stepsToExtract: steps, conceptStep: conceptStep, table: &gauge.Table{}, fileContent: specText, errors: make([]error, 0)} 106 extractor.extractSteps(cptFileName) 107 if len(extractor.errors) != 0 { 108 return "", "", err 109 } 110 conceptStep.ReplaceArgsWithDynamic(conceptStep.Args) 111 addArgsFromTable(conceptStep, &extractor.conceptName, extractor.dynamicArgs) 112 if extractor.table.IsInitialized() { 113 extractor.conceptName += "\n" + formatter.FormatTable(extractor.table) 114 } 115 return strings.Replace(formatter.FormatStep(conceptStep), "* ", "# ", 1) + (extractor.stepsInConcept), extractor.conceptName, nil 116 } 117 118 func addArgsFromTable(concept *gauge.Step, conceptName *string, args []string) { 119 for _, arg := range args { 120 concept.Value += " {}" 121 concept.Args = append(concept.Args, &gauge.StepArg{Value: arg, ArgType: gauge.Dynamic, Name: arg}) 122 *conceptName += fmt.Sprintf(" <%s>", arg) 123 } 124 } 125 126 func getContentWithDataTable(content, cptFileName string) (string, error) { 127 spec, result := new(parser.SpecParser).Parse(content, &gauge.ConceptDictionary{}, cptFileName) 128 if !result.Ok { 129 return "", fmt.Errorf("Spec Parse failure: %s", result.ParseErrors) 130 } 131 newSpec := &gauge.Specification{Heading: &gauge.Heading{Value: "SPECHEADING"}} 132 if spec.DataTable.IsInitialized() { 133 newSpec = &gauge.Specification{Items: []gauge.Item{&spec.DataTable}, Heading: &gauge.Heading{Value: "SPECHEADING"}} 134 } 135 return formatter.FormatSpecification(newSpec) + "\n##hello \n* step \n", nil 136 } 137 138 func isDuplicateConcept(concept *gauge.Step, cptDict *gauge.ConceptDictionary) bool { 139 for _, cpt := range cptDict.ConceptsMap { 140 if strings.TrimSpace(cpt.ConceptStep.Value) == strings.TrimSpace(concept.Value) { 141 return true 142 } 143 } 144 return false 145 } 146 147 func (e *extractor) extractSteps(cptFileName string) { 148 for _, step := range e.stepsToExtract { 149 tokens, _ := new(parser.SpecParser).GenerateTokens("*"+step.GetName(), cptFileName) 150 stepInConcept, _ := parser.CreateStepUsingLookup(tokens[0], nil, cptFileName) 151 if step.GetTable() != "" { 152 e.handleTable(stepInConcept, step, cptFileName) 153 } 154 stepInConcept.ReplaceArgsWithDynamic(e.conceptStep.Args) 155 e.stepsInConcept += formatter.FormatStep(stepInConcept) 156 } 157 } 158 159 func (e *extractor) handleTable(stepInConcept *gauge.Step, step *gauge_messages.Step, cptFileName string) { 160 stepInConcept.Value += " {}" 161 specText := e.fileContent + step.GetTable() 162 spec, result := new(parser.SpecParser).Parse(specText, &gauge.ConceptDictionary{}, cptFileName) 163 if !result.Ok { 164 for _, err := range result.ParseErrors { 165 e.errors = append(e.errors, err) 166 } 167 return 168 } 169 stepArgs := []*gauge.StepArg{spec.Scenarios[0].Steps[0].Args[0]} 170 e.addTableAsParam(step, stepArgs) 171 stepInConcept.Args = append(stepInConcept.Args, stepArgs[0]) 172 } 173 174 func (e *extractor) addTableAsParam(step *gauge_messages.Step, args []*gauge.StepArg) { 175 if step.GetParamTableName() != "" { 176 e.conceptName = strings.Replace(e.conceptName, fmt.Sprintf("<%s>", step.GetParamTableName()), "", 1) 177 e.table = &args[0].Table 178 args[0] = &gauge.StepArg{Value: step.GetParamTableName(), ArgType: gauge.Dynamic} 179 } else { 180 e.dynamicArgs = append(e.dynamicArgs, (&args[0].Table).GetDynamicArgs()...) 181 } 182 }