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