github.com/getgauge/gauge@v1.6.9/formatter/formatter.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 formatter
     8  
     9  import (
    10  	"bytes"
    11  	"fmt"
    12  	"os"
    13  	"sort"
    14  	"strings"
    15  
    16  	"github.com/getgauge/common"
    17  	"github.com/getgauge/gauge-proto/go/gauge_messages"
    18  	"github.com/getgauge/gauge/gauge"
    19  	"github.com/getgauge/gauge/logger"
    20  	"github.com/getgauge/gauge/parser"
    21  	"github.com/getgauge/gauge/util"
    22  )
    23  
    24  const (
    25  	tableLeftSpacing = 3
    26  )
    27  
    28  func FormatSpecFiles(specFiles ...string) []*parser.ParseResult {
    29  	specs, results := parser.ParseSpecFiles(specFiles, &gauge.ConceptDictionary{}, gauge.NewBuildErrors())
    30  	resultsMap := getParseResult(results)
    31  	filesSkipped := make([]string, 0)
    32  	for _, spec := range specs {
    33  		result := resultsMap[spec.FileName]
    34  		if !result.Ok {
    35  			filesSkipped = append(filesSkipped, spec.FileName)
    36  			continue
    37  		}
    38  		if err := formatAndSave(spec); err != nil {
    39  			result.ParseErrors = []parser.ParseError{parser.ParseError{Message: err.Error()}}
    40  		} else {
    41  			logger.Debugf(true, "Successfully formatted spec: %s", util.RelPathToProjectRoot(spec.FileName))
    42  		}
    43  	}
    44  	if len(filesSkipped) > 0 {
    45  		logger.Errorf(true, "Skipping %d file(s), due to following error(s):", len(filesSkipped))
    46  	}
    47  	return results
    48  }
    49  
    50  func getParseResult(results []*parser.ParseResult) map[string]*parser.ParseResult {
    51  	resultsMap := make(map[string]*parser.ParseResult)
    52  	for _, result := range results {
    53  		resultsMap[result.FileName] = result
    54  	}
    55  	return resultsMap
    56  }
    57  
    58  func FormatStep(step *gauge.Step) string {
    59  	text := step.Value
    60  	paramCount := strings.Count(text, gauge.ParameterPlaceholder)
    61  	for i := 0; i < paramCount; i++ {
    62  		argument := step.Args[i]
    63  		var formattedArg string
    64  		stripBeforeArg := ""
    65  		if argument.ArgType == gauge.TableArg {
    66  			formattedArg = fmt.Sprintf("\n%s", FormatTable(&argument.Table))
    67  			stripBeforeArg = " "
    68  		} else if argument.ArgType == gauge.Dynamic || argument.ArgType == gauge.SpecialString || argument.ArgType == gauge.SpecialTable {
    69  			formattedArg = fmt.Sprintf("<%s>", parser.GetUnescapedString(argument.Name))
    70  		} else {
    71  			formattedArg = fmt.Sprintf("\"%s\"", parser.GetUnescapedString(argument.Value))
    72  		}
    73  		text = strings.Replace(text, stripBeforeArg + gauge.ParameterPlaceholder, formattedArg, 1)
    74  	}
    75  	stepText := ""
    76  	if strings.HasSuffix(text, "\n") {
    77  		stepText = fmt.Sprintf("* %s", text)
    78  	} else {
    79  		stepText = fmt.Sprintf("* %s%s\n", text, step.Suffix)
    80  	}
    81  	return stepText
    82  }
    83  
    84  func FormatStepWithResolvedArgs(step *gauge.Step) string {
    85  	text := step.Value
    86  	paramCount := strings.Count(text, gauge.ParameterPlaceholder)
    87  	sf := make([]*gauge_messages.Fragment, 0)
    88  	for _, f := range step.GetFragments() {
    89  		if f.FragmentType == gauge_messages.Fragment_Parameter {
    90  			sf = append(sf, f)
    91  		}
    92  	}
    93  	for i := 0; i < paramCount; i++ {
    94  		a := step.Args[i]
    95  		var formattedArg string
    96  		if a.ArgType == gauge.TableArg && sf[i].Parameter.ParameterType == gauge_messages.Parameter_Table {
    97  			formattedArg = fmt.Sprintf("\n%s", FormatTable(&a.Table))
    98  		} else {
    99  			formattedArg = fmt.Sprintf("\"%s\"", sf[i].GetParameter().Value)
   100  		}
   101  		text = strings.Replace(text, gauge.ParameterPlaceholder, formattedArg, 1)
   102  	}
   103  	stepText := ""
   104  	if strings.HasSuffix(text, "\n") {
   105  		stepText = fmt.Sprintf("* %s", text)
   106  	} else {
   107  		stepText = fmt.Sprintf("* %s%s\n", text, step.Suffix)
   108  	}
   109  	return stepText
   110  }
   111  
   112  func FormatHeading(heading, headingChar string) string {
   113  	trimmedHeading := strings.TrimSpace(heading)
   114  	return fmt.Sprintf("%s %s\n", headingChar, trimmedHeading)
   115  }
   116  
   117  func FormatTable(table *gauge.Table) string {
   118  	columnToWidthMap := make(map[int]int)
   119  	for i, header := range table.Headers {
   120  		//table.get(header) returns a list of cells in that particular column
   121  		cells, _ := table.Get(header)
   122  		columnToWidthMap[i] = findLongestCellWidth(cells, len(header))
   123  	}
   124  
   125  	var tableStringBuffer bytes.Buffer
   126  
   127  	tableStringBuffer.WriteString("\n")
   128  
   129  	tableStringBuffer.WriteString(fmt.Sprintf("%s|", getRepeatedChars(" ", tableLeftSpacing)))
   130  	for i, header := range table.Headers {
   131  		width := columnToWidthMap[i]
   132  		tableStringBuffer.WriteString(fmt.Sprintf("%s|", addPaddingToCell(header, width)))
   133  	}
   134  
   135  	tableStringBuffer.WriteString("\n")
   136  	tableStringBuffer.WriteString(fmt.Sprintf("%s|", getRepeatedChars(" ", tableLeftSpacing)))
   137  	for i := range table.Headers {
   138  		width := columnToWidthMap[i]
   139  		cell := getRepeatedChars("-", width)
   140  		tableStringBuffer.WriteString(fmt.Sprintf("%s|", addPaddingToCell(cell, width)))
   141  	}
   142  
   143  	tableStringBuffer.WriteString("\n")
   144  	for _, row := range table.Rows() {
   145  		tableStringBuffer.WriteString(fmt.Sprintf("%s|", getRepeatedChars(" ", tableLeftSpacing)))
   146  		for i, cell := range row {
   147  			width := columnToWidthMap[i]
   148  			tableStringBuffer.WriteString(fmt.Sprintf("%s|", addPaddingToCell(cell, width)))
   149  		}
   150  		tableStringBuffer.WriteString("\n")
   151  	}
   152  
   153  	return tableStringBuffer.String()
   154  }
   155  
   156  func addPaddingToCell(cellValue string, width int) string {
   157  	cellRunes := []rune(cellValue)
   158  	padding := getRepeatedChars(" ", width-len(cellRunes))
   159  	return fmt.Sprintf("%s%s", string(cellRunes), padding)
   160  }
   161  
   162  func findLongestCellWidth(columnCells []gauge.TableCell, minValue int) int {
   163  	longestLength := minValue
   164  	for _, cellValue := range columnCells {
   165  		cellValueLen := len([]rune(cellValue.GetValue()))
   166  		if cellValueLen > longestLength {
   167  			longestLength = cellValueLen
   168  		}
   169  	}
   170  	return longestLength
   171  }
   172  
   173  func FormatComment(comment *gauge.Comment) string {
   174  	if comment.Value == "\n" {
   175  		return comment.Value
   176  	}
   177  	return fmt.Sprintf("%s\n", comment.Value)
   178  }
   179  
   180  func FormatTags(tags *gauge.Tags) string {
   181  	if tags == nil || len(tags.RawValues) == 0 {
   182  		return ""
   183  	}
   184  	var b bytes.Buffer
   185  	b.WriteString("tags: ")
   186  	for i, tag := range tags.RawValues {
   187  		for j, tagString := range tag {
   188  			b.WriteString(tagString)
   189  			if (i != len(tags.RawValues)-1) || (j != len(tag)-1) {
   190  				b.WriteString(", ")
   191  			}
   192  		}
   193  		b.WriteString("\n")
   194  		if i != len(tags.RawValues)-1 {
   195  			b.WriteString("      ")
   196  		}
   197  	}
   198  	return b.String()
   199  }
   200  
   201  func formatExternalDataTable(dataTable *gauge.DataTable) string {
   202  	if dataTable == nil || len(dataTable.Value) == 0 {
   203  		return ""
   204  	}
   205  	var b bytes.Buffer
   206  	b.WriteString(dataTable.Value)
   207  	b.WriteString("\n")
   208  	return b.String()
   209  }
   210  
   211  func formatAndSave(spec *gauge.Specification) error {
   212  	formatted := FormatSpecification(spec)
   213  	if err := common.SaveFile(spec.FileName, formatted, true); err != nil {
   214  		return err
   215  	}
   216  	return nil
   217  }
   218  
   219  func FormatSpecification(specification *gauge.Specification) string {
   220  	var formattedSpec bytes.Buffer
   221  	queue := &gauge.ItemQueue{Items: specification.AllItems()}
   222  	formatter := &formatter{buffer: formattedSpec, itemQueue: queue}
   223  	specification.Traverse(formatter, queue)
   224  	return formatter.buffer.String()
   225  }
   226  
   227  func sortConcepts(conceptDictionary *gauge.ConceptDictionary, conceptMap map[string]string) []*gauge.Concept {
   228  	var concepts []*gauge.Concept
   229  	for _, concept := range conceptDictionary.ConceptsMap {
   230  		conceptMap[concept.FileName] = ""
   231  		concepts = append(concepts, concept)
   232  	}
   233  	sort.Sort(gauge.ByLineNo(concepts))
   234  	return concepts
   235  }
   236  
   237  func formatConceptSteps(conceptMap map[string]string, concept *gauge.Concept) {
   238  	conceptMap[concept.FileName] += strings.TrimSpace(strings.Replace(FormatStep(concept.ConceptStep), "*", "#", 1)) + "\n"
   239  	for i := 1; i < len(concept.ConceptStep.Items); i++ {
   240  		conceptMap[concept.FileName] += formatItem(concept.ConceptStep.Items[i])
   241  	}
   242  }
   243  
   244  func FormatConcepts(conceptDictionary *gauge.ConceptDictionary) map[string]string {
   245  	conceptMap := make(map[string]string)
   246  	for _, concept := range sortConcepts(conceptDictionary, conceptMap) {
   247  		for _, comment := range concept.ConceptStep.PreComments {
   248  			conceptMap[concept.FileName] += FormatComment(comment)
   249  		}
   250  		formatConceptSteps(conceptMap, concept)
   251  	}
   252  	return conceptMap
   253  }
   254  
   255  func formatItem(item gauge.Item) string {
   256  	switch item.Kind() {
   257  	case gauge.CommentKind:
   258  		comment := item.(*gauge.Comment)
   259  		if comment.Value == "\n" {
   260  			return comment.Value
   261  		}
   262  		return fmt.Sprintf("%s\n", comment.Value)
   263  	case gauge.StepKind:
   264  		step := item.(*gauge.Step)
   265  		return FormatStep(step)
   266  	case gauge.DataTableKind:
   267  		dataTable := item.(*gauge.DataTable)
   268  		return FormatTable(dataTable.Table)
   269  	case gauge.TagKind:
   270  		tags := item.(*gauge.Tags)
   271  		return FormatTags(tags)
   272  	}
   273  	return ""
   274  }
   275  
   276  func getRepeatedChars(character string, repeatCount int) string {
   277  	formatted := ""
   278  	for i := 0; i < repeatCount; i++ {
   279  		formatted = fmt.Sprintf("%s%s", formatted, character)
   280  	}
   281  	return formatted
   282  }
   283  
   284  func FormatSpecFilesIn(filesLocation string) {
   285  	specFiles := util.GetSpecFiles([]string{filesLocation})
   286  	parseResults := FormatSpecFiles(specFiles...)
   287  	if parser.HandleParseResult(parseResults...) {
   288  		os.Exit(1)
   289  	}
   290  }