// 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" "io" "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 rec, err := read.Read() if err != nil { log.Fatalf("error reading from csv file: %s", err) } widths := make([]int, len(rec)) for i, col := range rec { widths[i] = max(widths[i], len(col)) } for { rec, err := read.Read() if err == io.EOF { break } if err != nil { log.Fatalf("error reading from csv file: %s", err) } for i, col := range rec { widths[i] = max(widths[i], len(col)) } } c := make([]string, len(widths)) for i := 0; i < len(c); i++ { c[i] = " %-*s " } pattern := fmt.Sprintf("|%s|\n", strings.Join(c, "|")) // Reset file descriptor cursor and take new CSV reader from it fd.Seek(0, 0) read = csv.NewReader(fd) read.Comma = sep read.TrimLeadingSpace = true read.LazyQuotes = *lazyQuotes // Format header row rec, err = read.Read() if err != nil { log.Fatalf("failed to read next csv record: %s", err) } curr := make([]any, 0, 2*len(widths)) for i := range widths { curr = append(curr, widths[i], rec[i]) } fmt.Printf(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])) } fmt.Printf(pattern, curr...) for { rec, err := read.Read() if err == io.EOF { break } if err != nil { log.Fatalf("error reading from csv file: %s", err) } curr = curr[:0] for i := range widths { curr = append(curr, widths[i], rec[i]) } fmt.Printf(pattern, curr...) } } func max(a, b int) int { if a > b { return a } return b }