1
0
mirror of https://github.com/anthonyaxenov/csv2md.git synced 2024-11-22 21:35:49 +00:00

Compare commits

..

No commits in common. "6996e4634f2bf8ac59ccaf105cd56a71d047a039" and "60b2f676bd9b36d8ea1fdf694a3515694700323c" have entirely different histories.

4 changed files with 64 additions and 161 deletions

52
.vscode/launch.json vendored
View File

@ -5,7 +5,7 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "csv", "name": "Debug csv2md",
"type": "go", "type": "go",
"request": "launch", "request": "launch",
"mode": "auto", "mode": "auto",
@ -15,61 +15,15 @@
] ]
}, },
{ {
"name": "csv aligned", "name": "Debug csv2md (tsv)",
"type": "go", "type": "go",
"request": "launch", "request": "launch",
"mode": "auto", "mode": "auto",
"program": "main.go", "program": "main.go",
"args": [ "args": [
"-f=example.csv",
"-a"
]
},
{
"name": "csv header",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "main.go",
"args": [
"-f=example.csv",
"-h=\"My Header\"",
]
},
{
"name": "tsv",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "main.go",
"args": [
"-f=example.tsv",
"-t"
]
},
{
"name": "tsv aligned",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "main.go",
"args": [
"-f=example.tsv",
"-t", "-t",
"-a"
]
},
{
"name": "tsv header",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "main.go",
"args": [
"-f=example.tsv", "-f=example.tsv",
"-h=\"My Header\"",
"-t"
] ]
}, }
] ]
} }

View File

@ -12,35 +12,29 @@ LINUX_FILE="${LINUX_PATH}/${BINARY_NAME}"
WINDOWS_FILE="${WINDOWS_PATH}/${BINARY_NAME}.exe" WINDOWS_FILE="${WINDOWS_PATH}/${BINARY_NAME}.exe"
DARWIN_FILE="${DARWIN_PATH}/${BINARY_NAME}" DARWIN_FILE="${DARWIN_PATH}/${BINARY_NAME}"
## clean: Remove all compiled binaries ## clean: Removes all compiled binaries
clean: clean:
@go clean @go clean
@rm -rf bin/ @rm -rf bin/
## linux: Build new binaries for linux (x64) ## linux: Builds new binaries for linux (x64)
linux: linux:
@rm -rf ${LINUX_PATH} @rm -rf ${LINUX_PATH}
@GOARCH=${ARCH} GOOS=linux go build -o ${LINUX_FILE} . && echo "Compiled: ${LINUX_FILE}" @GOARCH=${ARCH} GOOS=linux go build -o ${LINUX_FILE} . && echo "Compiled: ${LINUX_FILE}"
## win: Build new binaries for windows (x64) ## win: Builds new binaries for windows (x64)
win: win:
@rm -rf ${WINDOWS_PATH} @rm -rf ${WINDOWS_PATH}
@GOARCH=${ARCH} GOOS=windows go build -o ${WINDOWS_FILE} . && echo "Compiled: ${WINDOWS_FILE}" @GOARCH=${ARCH} GOOS=windows go build -o ${WINDOWS_FILE} . && echo "Compiled: ${WINDOWS_FILE}"
## darwin: Build new binaries for darwin (x64) ## darwin: Builds new binaries for darwin (x64)
darwin: darwin:
@rm -rf ${DARWIN_PATH} @rm -rf ${DARWIN_PATH}
@GOARCH=${ARCH} GOOS=darwin go build -o ${DARWIN_FILE} . && echo "Compiled: ${DARWIN_FILE}" @GOARCH=${ARCH} GOOS=darwin go build -o ${DARWIN_FILE} . && echo "Compiled: ${DARWIN_FILE}"
## build: Build new binaries for linux, windows and darwin (x64) ## build: Builds new binaries for linux, windows and darwin (x64)
all: clean linux win darwin all: clean linux win darwin
## release: Build all binaries and zip them
release: clean darwin linux win
@zip -j ${LINUX_PATH}.zip ${LINUX_FILE}
@zip -j ${DARWIN_PATH}.zip ${DARWIN_FILE}
@zip -j ${WINDOWS_PATH}.zip ${WINDOWS_FILE}
## compile: This message ## compile: This message
help: Makefile help: Makefile
@echo "Choose a command run:" @echo "Choose a command run:"

View File

@ -2,39 +2,39 @@
Stupidly simple tool to convert csv/tsv to [markdown](https://spec-md.com/) table. Stupidly simple tool to convert csv/tsv to [markdown](https://spec-md.com/) table.
Prints result in stdout. Outputs result in stdout.
## Usage
```shell
csv2md [-help|--help] [-t] [-f <FILE>]
``` ```
Usage:
csv2md [-help|--help] [-f=<FILE>] [-h=<HEADER>] [-t] [-a]
Available arguments: Available arguments:
-help|--help - show this help * `-help` or `--help` - get help
-f=<FILE> - convert specified FILE * `-f=<FILE>` or `-f <FILE>` - convert specified `FILE`
-h=<HEADER> - add main header (h1) to result * `-t` - convert input as tsv
-t - convert input as tsv
-a - align columns width
FILE formats supported: Available `FILE` formats:
- csv (default) * csv (default);
- tsv (with -t flag) * tsv (with `-t` argument).
Path to FILE may be presented as: Path to `FILE` may be presented as:
- absolute * absolute path;
- relative to current working directory * path relative to current working directory;
- relative to user home directory (~) * path relative to user home directory (~).
Both HEADER and FILE path with whitespaces must be double-quoted. Also if `PATH` contains whitespaces then you should double-quote it.
To save result as separate file you should use redirection operators (> or >>).
```
**IMPORTANT:** To save result as separate file you can use piping.
1. Input data must be valid csv/tsv
2. Whitespaces allowed only between double-quotes
3. Due to markdown spec first line of result table will always be presented as header.
So if your raw data hasn't one you'll should add it before conversion or edit later in ready md.
## Examples > **IMPORTANT:**
> 1. Input data must be valid csv/tsv
> 2. Whitespaces allowed only between double-quotes
> 3. Due to markdown spec first line of result table will always be presented as header.
> So if your raw data hasn't one you'll should add it before conversion or edit later in ready md.
### Examples
``` ```
csv2md - paste or type csv to stdin and then csv2md - paste or type csv to stdin and then
@ -47,9 +47,8 @@ csv2md -t < example.tsv > example.md - convert tsv from stdin and write result
cat example.csv | csv2md - convert csv from stdin and view result in stdout cat example.csv | csv2md - convert csv from stdin and view result in stdout
csv2md -t -f=example.tsv > example.md - convert tsv from file and write result in new file csv2md -t -f=example.tsv > example.md - convert tsv from file and write result in new file
csv2md -f example.csv | less - convert csv from file and view result in stdout using pager csv2md -f example.csv | less - convert csv from file and view result in stdout using pager
csv2md -f example.csv | code -n - - convert csv from file and open result in vscode
...anything is possible with redirection and piping, inc. grep, sed, awk, etc. ...anything is possible with redirection and piping, e.g. grep, sed, awk, etc.
``` ```
You can generate some examples here: [csv](https://onlinerandomtools.com/generate-random-csv), [tsv](https://onlinerandomtools.com/generate-random-tsv) You can generate some examples here: [csv](https://onlinerandomtools.com/generate-random-csv), [tsv](https://onlinerandomtools.com/generate-random-tsv)

106
main.go
View File

@ -11,31 +11,27 @@ import (
"strings" "strings"
) )
const VERSION = "1.3.0" const VERSION = "1.2.0"
func main() { func main() {
log.SetFlags(0) log.SetFlags(0)
is_help := flag.Bool("help", false, "Get help")
filepath := flag.String("f", "", "File path") filepath := flag.String("f", "", "File path")
header := flag.String("h", "", "Add main header (h1)") is_help := flag.Bool("help", false, "Get help")
as_tsv := flag.Bool("t", false, "Parse input as tsv") as_tsv := flag.Bool("t", false, "Parse as tsv")
aligned := flag.Bool("a", false, "Align columns width")
flag.Parse() flag.Parse()
// show help
if *is_help { if *is_help {
usage(os.Stdout) usage(os.Stdout)
os.Exit(0) os.Exit(0)
} }
// if filepath is not provided then convert data from stdin if *filepath == "" {
if len(*filepath) == 0 { data, err := readRawCsv(*as_tsv)
data, err := ReadStdin(*as_tsv)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
Print(Convert(*header, data, *aligned)) print(data)
} else { // otherwise convert data from file } else {
src, err := ExpandPath(*filepath) src, err := ExpandPath(*filepath)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -44,26 +40,26 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
data, err := ReadFile(src, *as_tsv) data, err := readCsvFile(src, *as_tsv)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
Print(Convert(*header, data, *aligned)) print(data)
} }
} }
// Print writes converted data to stdout // print write converted data to stdout
func Print(data []string) { func print(data [][]string) {
if len(data) == 0 { if len(data) == 0 {
usage(os.Stderr) usage(os.Stderr)
} }
result := convert(data)
for _, row := range data { for _, row := range result {
fmt.Println(row) fmt.Println(row)
} }
} }
// ExpandPath returns absolute path to file replacing ~ to user's home folder // ExpandPath return absolute path to file replacing ~ to user's home folder
func ExpandPath(path string) (string, error) { func ExpandPath(path string) (string, error) {
homepath, err := os.UserHomeDir() homepath, err := os.UserHomeDir()
if err != nil { if err != nil {
@ -73,8 +69,8 @@ func ExpandPath(path string) (string, error) {
return newpath, err return newpath, err
} }
// ReadFile reads data from file // readRawCsv read data from file
func ReadFile(filePath string, as_tsv bool) ([][]string, error) { func readCsvFile(filePath string, as_tsv bool) ([][]string, error) {
f, err := os.Open(filePath) f, err := os.Open(filePath)
if err != nil { if err != nil {
return nil, errors.New("Failed to open file '" + filePath + "': " + err.Error()) return nil, errors.New("Failed to open file '" + filePath + "': " + err.Error())
@ -94,8 +90,8 @@ func ReadFile(filePath string, as_tsv bool) ([][]string, error) {
return records, nil return records, nil
} }
// ReadStdin reads data from stdin // readRawCsv read data from stdin
func ReadStdin(as_tsv bool) ([][]string, error) { func readRawCsv(as_tsv bool) ([][]string, error) {
csvReader := csv.NewReader(os.Stdin) csvReader := csv.NewReader(os.Stdin)
if as_tsv { if as_tsv {
csvReader.Comma = '\t' csvReader.Comma = '\t'
@ -109,54 +105,19 @@ func ReadStdin(as_tsv bool) ([][]string, error) {
return records, nil return records, nil
} }
// Convert formats data from file or stdin as markdown // convert format data from file or stdin as markdown
func Convert(header string, records [][]string, aligned bool) []string { func convert(records [][]string) []string {
var result []string var result []string
for idx, row := range records {
// add h1 if passed
header = strings.Trim(header, "\t\r\n ")
if len(header) != 0 {
result = append(result, "# " + header)
result = append(result, "")
}
// if user wants aligned columns width then we
// count max length of every value in every column
widths := make(map[int]int)
if aligned {
for _, row := range records {
for col_idx, col := range row {
length := len(col)
if len(widths) == 0 || widths[col_idx] < length {
widths[col_idx] = length
}
}
}
}
// build markdown table
for row_idx, row := range records {
// table content
str := "| " str := "| "
for col_idx, col := range row { for _, col := range row {
if aligned { str += col + " | "
str += fmt.Sprintf("%-*s | ", widths[col_idx], col)
} else {
str += col + " | "
}
} }
result = append(result, str) result = append(result, str)
if idx == 0 {
// content separator only after first row (header)
if row_idx == 0 {
str := "| " str := "| "
for col_idx := range row { for range row {
if !aligned || widths[col_idx] < 3 { str += "--- | "
str += "--- | "
} else {
str += strings.Repeat("-", widths[col_idx]) + " | "
}
} }
result = append(result, str) result = append(result, str)
} }
@ -164,7 +125,7 @@ func Convert(header string, records [][]string, aligned bool) []string {
return result return result
} }
// usage Print help into writer // usage print help into writer
func usage(writer *os.File) { func usage(writer *os.File) {
usage := []string{ usage := []string{
"csv2md v" + VERSION, "csv2md v" + VERSION,
@ -172,14 +133,12 @@ func usage(writer *os.File) {
"https://github.com/anthonyaxenov/csv2md", "https://github.com/anthonyaxenov/csv2md",
"", "",
"Usage:", "Usage:",
"\tcsv2md [-help|--help] [-f=<FILE>] [-h=<HEADER>] [-t] [-a]", "\tcsv2md [-help|--help] [-t] [-f <FILE>]",
"", "",
"Available arguments:", "Available arguments:",
"\t-help|--help - show this help", "\t-help|--help - get this help",
"\t-f=<FILE> - Convert specified FILE", "\t-f=<FILE>|-f <FILE> - convert specified FILE",
"\t-h=<HEADER> - add main header (h1) to result", "\t-t - convert input as TSV",
"\t-t - Convert input as tsv",
"\t-a - align columns width",
"", "",
"FILE formats supported:", "FILE formats supported:",
"\t- csv (default)", "\t- csv (default)",
@ -189,9 +148,6 @@ func usage(writer *os.File) {
"\t- absolute", "\t- absolute",
"\t- relative to current working directory", "\t- relative to current working directory",
"\t- relative to user home directory (~)", "\t- relative to user home directory (~)",
"",
"Both HEADER and FILE path with whitespaces must be double-quoted.",
"To save result as separate file you should use redirection operators (> or >>).",
} }
for _, str := range usage { for _, str := range usage {
fmt.Fprintln(writer, str) fmt.Fprintln(writer, str)