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