diff --git a/cmd/md-fmt/README.md b/cmd/md-fmt/README.md new file mode 100644 index 0000000..11b0bc4 --- /dev/null +++ b/cmd/md-fmt/README.md @@ -0,0 +1,7 @@ +# CSV to Markdown Table Formatter + +Takes a csv-like file as input and outputs a formatted markdown table with it. + +## Usage + +Run `md-fmt -help` for the list of command line arguments. diff --git a/cmd/md-fmt/main.go b/cmd/md-fmt/main.go new file mode 100644 index 0000000..22f3dbc --- /dev/null +++ b/cmd/md-fmt/main.go @@ -0,0 +1,87 @@ +// 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 +}