add markdown-to-csv option

otelq
William Perron 1 year ago
parent 2db3183288
commit d4d9394a1f
No known key found for this signature in database
GPG Key ID: D1815C43C9BA3DE1

@ -3,11 +3,11 @@
package main package main
import ( import (
"bufio"
"encoding/csv" "encoding/csv"
"flag" "flag"
"fmt" "fmt"
"io" "io"
"log"
"os" "os"
"strings" "strings"
) )
@ -16,6 +16,7 @@ var (
sourcePath = flag.String("source", "", "path to the input file") sourcePath = flag.String("source", "", "path to the input file")
separator = flag.String("sep", ",", "separator character to use when reading the csv 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") lazyQuotes = flag.Bool("lazy-quotes", false, "controls the lazy-quotes setting on the csv reader")
toCSV = flag.Bool("to-csv", false, "parses markdown table and encodes it to csv (opposite operation)")
) )
func main() { func main() {
@ -23,18 +24,27 @@ func main() {
fd, err := os.Open(*sourcePath) fd, err := os.Open(*sourcePath)
if err != nil { if err != nil {
log.Fatalf("failed to open source file %s: %s\n", *sourcePath, err) fmt.Fprintf(os.Stderr, "failed to open source file %s: %s\n", *sourcePath, err)
} }
if *toCSV {
markdownToCSV(fd, os.Stdout)
} else {
csvToMarkdown(fd, os.Stdout)
}
}
func csvToMarkdown(r io.ReadSeeker, w io.Writer) {
sep := []rune(*separator)[0] sep := []rune(*separator)[0]
read := csv.NewReader(fd) read := csv.NewReader(r)
read.Comma = sep read.Comma = sep
read.TrimLeadingSpace = true read.TrimLeadingSpace = true
read.LazyQuotes = *lazyQuotes read.LazyQuotes = *lazyQuotes
rec, err := read.Read() rec, err := read.Read()
if err != nil { if err != nil {
log.Fatalf("error reading from csv file: %s", err) fmt.Fprintf(os.Stderr, "error reading from csv file: %s\n", err)
return
} }
widths := make([]int, len(rec)) widths := make([]int, len(rec))
@ -47,7 +57,8 @@ func main() {
break break
} }
if err != nil { if err != nil {
log.Fatalf("error reading from csv file: %s", err) fmt.Fprintf(os.Stderr, "error reading from csv file: %s", err)
return
} }
for i, col := range rec { for i, col := range rec {
@ -62,8 +73,8 @@ func main() {
pattern := fmt.Sprintf("|%s|\n", strings.Join(c, "|")) pattern := fmt.Sprintf("|%s|\n", strings.Join(c, "|"))
// Reset file descriptor cursor and take new CSV reader from it // Reset file descriptor cursor and take new CSV reader from it
fd.Seek(0, 0) r.Seek(0, 0)
read = csv.NewReader(fd) read = csv.NewReader(r)
read.Comma = sep read.Comma = sep
read.TrimLeadingSpace = true read.TrimLeadingSpace = true
read.LazyQuotes = *lazyQuotes read.LazyQuotes = *lazyQuotes
@ -71,21 +82,22 @@ func main() {
// Format header row // Format header row
rec, err = read.Read() rec, err = read.Read()
if err != nil { if err != nil {
log.Fatalf("failed to read next csv record: %s", err) fmt.Fprintf(os.Stderr, "failed to read next csv record: %s", err)
return
} }
curr := make([]any, 0, 2*len(widths)) curr := make([]any, 0, 2*len(widths))
for i := range widths { for i := range widths {
curr = append(curr, widths[i], rec[i]) curr = append(curr, widths[i], rec[i])
} }
fmt.Printf(pattern, curr...) fmt.Fprintf(w, pattern, curr...)
// Format header separator row // Format header separator row
curr = curr[:0] // empty slice but preserve capacity curr = curr[:0] // empty slice but preserve capacity
for i := range widths { for i := range widths {
curr = append(curr, widths[i], strings.Repeat("-", widths[i])) curr = append(curr, widths[i], strings.Repeat("-", widths[i]))
} }
fmt.Printf(pattern, curr...) fmt.Fprintf(w, pattern, curr...)
for { for {
rec, err := read.Read() rec, err := read.Read()
@ -93,14 +105,65 @@ func main() {
break break
} }
if err != nil { if err != nil {
log.Fatalf("error reading from csv file: %s", err) fmt.Fprintf(os.Stderr, "error reading from csv file: %s", err)
return
} }
curr = curr[:0] curr = curr[:0]
for i := range widths { for i := range widths {
curr = append(curr, widths[i], rec[i]) curr = append(curr, widths[i], rec[i])
} }
fmt.Printf(pattern, curr...) fmt.Fprintf(w, pattern, curr...)
}
}
func markdownToCSV(r io.ReadSeeker, w io.Writer) {
cw := csv.NewWriter(w)
defer func() {
cw.Flush()
if err := cw.Error(); err != nil {
fmt.Fprintf(os.Stderr, "error occured during scan: %s", err)
}
}()
// for now we're assuming there's only one table in a markdown file, this
// flag keeps track of whether or not we've seen a table already, and is
// used to exit the loop early once we're done reading the table, ignoring
// subsequent lines in the file.
var seen bool
scanner := bufio.NewScanner(r)
for scanner.Scan() {
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading standard input:", err)
}
line := strings.TrimSpace(scanner.Text())
if !(strings.HasPrefix(line, "|") && strings.HasSuffix(line, "|")) {
if seen {
break // exit early if we've seen a table already
}
continue // keep going until we see a table
}
// the rest of the body only applies to lines that are part of a table
// i.e. that start and end with a pipe character (`|`)
seen = true
line = strings.Trim(line, "|")
parts := strings.Split(line, "|")
for i := 0; i < len(parts); i++ {
parts[i] = strings.TrimSpace(parts[i])
}
// ignore separator line
if parts[0] == strings.Repeat("-", len(parts[0])) {
continue
}
if err := cw.Write(parts); err != nil {
fmt.Fprintf(os.Stderr, "failed to write to output: %s\n", err)
return
}
} }
} }

Loading…
Cancel
Save