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  }