// md-fmt takes a csv or tsv input file and outputs a formatted markdown table // with the data. package main import ( "encoding/csv" "flag" "fmt" "log" "os" "strings" ) var ( sourcePath = flag.String("source", "", "path to the input file") separator = flag.String("sep", ",", "separator character to use when reading the csv file") lazyQuotes = flag.Bool("lazy-quotes", false, "controls the lazy-quotes setting on the csv reader") ) func main() { flag.Parse() fd, err := os.Open(*sourcePath) if err != nil { log.Fatalf("failed to open source file %s: %s\n", *sourcePath, err) } sep := []rune(*separator)[0] read := csv.NewReader(fd) read.Comma = sep read.TrimLeadingSpace = true read.LazyQuotes = *lazyQuotes records, err := read.ReadAll() if err != nil { log.Fatalf("failed to load records in memory: %s\n", err) } widths := make([]int, len(records[0])) for _, row := range records { for i, col := range row { widths[i] = max(widths[i], len(col)) } } c := make([]string, len(records[0])) log.Println(records[0], len(c)) for i := 0; i < len(c); i++ { c[i] = " %-*s " } pattern := fmt.Sprintf("|%s|\n", strings.Join(c, "|")) log.Println(pattern) sb := strings.Builder{} // Format header row curr := make([]any, 0, 2*len(widths)) for i := range widths { curr = append(curr, widths[i], records[0][i]) } sb.WriteString(fmt.Sprintf(pattern, curr...)) // Format header separator row curr = curr[:0] // empty slice but preserve capacity for i := range widths { curr = append(curr, widths[i], strings.Repeat("-", widths[i])) } sb.WriteString(fmt.Sprintf(pattern, curr...)) // Format rest of records for i := 1; i < len(records); i++ { curr = curr[:0] for j := range widths { curr = append(curr, widths[j], records[i][j]) } sb.WriteString(fmt.Sprintf(pattern, curr...)) } fmt.Print(sb.String()) } func max(a, b int) int { if a > b { return a } return b }