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 }