mirror of
https://github.com/anthonyaxenov/csv2md.git
synced 2024-11-22 21:35:49 +00:00
Compare commits
4 Commits
60b2f676bd
...
6996e4634f
Author | SHA1 | Date | |
---|---|---|---|
6996e4634f | |||
5dcccb9880 | |||
cf2b11fe1a | |||
a01edbac7d |
56
.vscode/launch.json
vendored
56
.vscode/launch.json
vendored
@ -5,7 +5,7 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug csv2md",
|
||||
"name": "csv",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
@ -15,15 +15,61 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Debug csv2md (tsv)",
|
||||
"name": "csv aligned",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "main.go",
|
||||
"args": [
|
||||
"-t",
|
||||
"-f=example.tsv",
|
||||
"-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",
|
||||
"-a"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tsv header",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "main.go",
|
||||
"args": [
|
||||
"-f=example.tsv",
|
||||
"-h=\"My Header\"",
|
||||
"-t"
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
|
16
Makefile
16
Makefile
@ -12,29 +12,35 @@ LINUX_FILE="${LINUX_PATH}/${BINARY_NAME}"
|
||||
WINDOWS_FILE="${WINDOWS_PATH}/${BINARY_NAME}.exe"
|
||||
DARWIN_FILE="${DARWIN_PATH}/${BINARY_NAME}"
|
||||
|
||||
## clean: Removes all compiled binaries
|
||||
## clean: Remove all compiled binaries
|
||||
clean:
|
||||
@go clean
|
||||
@rm -rf bin/
|
||||
|
||||
## linux: Builds new binaries for linux (x64)
|
||||
## linux: Build new binaries for linux (x64)
|
||||
linux:
|
||||
@rm -rf ${LINUX_PATH}
|
||||
@GOARCH=${ARCH} GOOS=linux go build -o ${LINUX_FILE} . && echo "Compiled: ${LINUX_FILE}"
|
||||
|
||||
## win: Builds new binaries for windows (x64)
|
||||
## win: Build new binaries for windows (x64)
|
||||
win:
|
||||
@rm -rf ${WINDOWS_PATH}
|
||||
@GOARCH=${ARCH} GOOS=windows go build -o ${WINDOWS_FILE} . && echo "Compiled: ${WINDOWS_FILE}"
|
||||
|
||||
## darwin: Builds new binaries for darwin (x64)
|
||||
## darwin: Build new binaries for darwin (x64)
|
||||
darwin:
|
||||
@rm -rf ${DARWIN_PATH}
|
||||
@GOARCH=${ARCH} GOOS=darwin go build -o ${DARWIN_FILE} . && echo "Compiled: ${DARWIN_FILE}"
|
||||
|
||||
## build: Builds new binaries for linux, windows and darwin (x64)
|
||||
## build: Build new binaries for linux, windows and darwin (x64)
|
||||
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
|
||||
help: Makefile
|
||||
@echo "Choose a command run:"
|
||||
|
51
README.md
51
README.md
@ -2,39 +2,39 @@
|
||||
|
||||
Stupidly simple tool to convert csv/tsv to [markdown](https://spec-md.com/) table.
|
||||
|
||||
Outputs result in stdout.
|
||||
Prints result in stdout.
|
||||
|
||||
## Usage
|
||||
|
||||
```shell
|
||||
csv2md [-help|--help] [-t] [-f <FILE>]
|
||||
```
|
||||
Usage:
|
||||
csv2md [-help|--help] [-f=<FILE>] [-h=<HEADER>] [-t] [-a]
|
||||
|
||||
Available arguments:
|
||||
* `-help` or `--help` - get help
|
||||
* `-f=<FILE>` or `-f <FILE>` - convert specified `FILE`
|
||||
* `-t` - convert input as tsv
|
||||
-help|--help - show this help
|
||||
-f=<FILE> - convert specified FILE
|
||||
-h=<HEADER> - add main header (h1) to result
|
||||
-t - convert input as tsv
|
||||
-a - align columns width
|
||||
|
||||
Available `FILE` formats:
|
||||
* csv (default);
|
||||
* tsv (with `-t` argument).
|
||||
FILE formats supported:
|
||||
- csv (default)
|
||||
- tsv (with -t flag)
|
||||
|
||||
Path to `FILE` may be presented as:
|
||||
* absolute path;
|
||||
* path relative to current working directory;
|
||||
* path relative to user home directory (~).
|
||||
Path to FILE may be presented as:
|
||||
- absolute
|
||||
- relative to current working directory
|
||||
- relative to user home directory (~)
|
||||
|
||||
Also if `PATH` contains whitespaces then you should double-quote it.
|
||||
Both HEADER and FILE path with whitespaces must be double-quoted.
|
||||
To save result as separate file you should use redirection operators (> or >>).
|
||||
```
|
||||
|
||||
To save result as separate file you can use piping.
|
||||
**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.
|
||||
|
||||
> **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
|
||||
## Examples
|
||||
|
||||
```
|
||||
csv2md - paste or type csv to stdin and then
|
||||
@ -47,8 +47,9 @@ 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
|
||||
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 | code -n - - convert csv from file and open result in vscode
|
||||
|
||||
...anything is possible with redirection and piping, e.g. grep, sed, awk, etc.
|
||||
...anything is possible with redirection and piping, inc. grep, sed, awk, etc.
|
||||
```
|
||||
|
||||
You can generate some examples here: [csv](https://onlinerandomtools.com/generate-random-csv), [tsv](https://onlinerandomtools.com/generate-random-tsv)
|
||||
|
106
main.go
106
main.go
@ -11,27 +11,31 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const VERSION = "1.2.0"
|
||||
const VERSION = "1.3.0"
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
filepath := flag.String("f", "", "File path")
|
||||
is_help := flag.Bool("help", false, "Get help")
|
||||
as_tsv := flag.Bool("t", false, "Parse as tsv")
|
||||
filepath := flag.String("f", "", "File path")
|
||||
header := flag.String("h", "", "Add main header (h1)")
|
||||
as_tsv := flag.Bool("t", false, "Parse input as tsv")
|
||||
aligned := flag.Bool("a", false, "Align columns width")
|
||||
flag.Parse()
|
||||
|
||||
// show help
|
||||
if *is_help {
|
||||
usage(os.Stdout)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if *filepath == "" {
|
||||
data, err := readRawCsv(*as_tsv)
|
||||
// if filepath is not provided then convert data from stdin
|
||||
if len(*filepath) == 0 {
|
||||
data, err := ReadStdin(*as_tsv)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
print(data)
|
||||
} else {
|
||||
Print(Convert(*header, data, *aligned))
|
||||
} else { // otherwise convert data from file
|
||||
src, err := ExpandPath(*filepath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@ -40,26 +44,26 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
data, err := readCsvFile(src, *as_tsv)
|
||||
data, err := ReadFile(src, *as_tsv)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
print(data)
|
||||
Print(Convert(*header, data, *aligned))
|
||||
}
|
||||
}
|
||||
|
||||
// print write converted data to stdout
|
||||
func print(data [][]string) {
|
||||
// Print writes converted data to stdout
|
||||
func Print(data []string) {
|
||||
if len(data) == 0 {
|
||||
usage(os.Stderr)
|
||||
}
|
||||
result := convert(data)
|
||||
for _, row := range result {
|
||||
|
||||
for _, row := range data {
|
||||
fmt.Println(row)
|
||||
}
|
||||
}
|
||||
|
||||
// ExpandPath return absolute path to file replacing ~ to user's home folder
|
||||
// ExpandPath returns absolute path to file replacing ~ to user's home folder
|
||||
func ExpandPath(path string) (string, error) {
|
||||
homepath, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
@ -69,8 +73,8 @@ func ExpandPath(path string) (string, error) {
|
||||
return newpath, err
|
||||
}
|
||||
|
||||
// readRawCsv read data from file
|
||||
func readCsvFile(filePath string, as_tsv bool) ([][]string, error) {
|
||||
// ReadFile reads data from file
|
||||
func ReadFile(filePath string, as_tsv bool) ([][]string, error) {
|
||||
f, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to open file '" + filePath + "': " + err.Error())
|
||||
@ -90,8 +94,8 @@ func readCsvFile(filePath string, as_tsv bool) ([][]string, error) {
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// readRawCsv read data from stdin
|
||||
func readRawCsv(as_tsv bool) ([][]string, error) {
|
||||
// ReadStdin reads data from stdin
|
||||
func ReadStdin(as_tsv bool) ([][]string, error) {
|
||||
csvReader := csv.NewReader(os.Stdin)
|
||||
if as_tsv {
|
||||
csvReader.Comma = '\t'
|
||||
@ -105,19 +109,54 @@ func readRawCsv(as_tsv bool) ([][]string, error) {
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// convert format data from file or stdin as markdown
|
||||
func convert(records [][]string) []string {
|
||||
// Convert formats data from file or stdin as markdown
|
||||
func Convert(header string, records [][]string, aligned bool) []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 := "| "
|
||||
for _, col := range row {
|
||||
str += col + " | "
|
||||
for col_idx, col := range row {
|
||||
if aligned {
|
||||
str += fmt.Sprintf("%-*s | ", widths[col_idx], col)
|
||||
} else {
|
||||
str += col + " | "
|
||||
}
|
||||
}
|
||||
result = append(result, str)
|
||||
if idx == 0 {
|
||||
|
||||
// content separator only after first row (header)
|
||||
if row_idx == 0 {
|
||||
str := "| "
|
||||
for range row {
|
||||
str += "--- | "
|
||||
for col_idx := range row {
|
||||
if !aligned || widths[col_idx] < 3 {
|
||||
str += "--- | "
|
||||
} else {
|
||||
str += strings.Repeat("-", widths[col_idx]) + " | "
|
||||
}
|
||||
}
|
||||
result = append(result, str)
|
||||
}
|
||||
@ -125,7 +164,7 @@ func convert(records [][]string) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
// usage print help into writer
|
||||
// usage Print help into writer
|
||||
func usage(writer *os.File) {
|
||||
usage := []string{
|
||||
"csv2md v" + VERSION,
|
||||
@ -133,12 +172,14 @@ func usage(writer *os.File) {
|
||||
"https://github.com/anthonyaxenov/csv2md",
|
||||
"",
|
||||
"Usage:",
|
||||
"\tcsv2md [-help|--help] [-t] [-f <FILE>]",
|
||||
"\tcsv2md [-help|--help] [-f=<FILE>] [-h=<HEADER>] [-t] [-a]",
|
||||
"",
|
||||
"Available arguments:",
|
||||
"\t-help|--help - get this help",
|
||||
"\t-f=<FILE>|-f <FILE> - convert specified FILE",
|
||||
"\t-t - convert input as TSV",
|
||||
"\t-help|--help - show this help",
|
||||
"\t-f=<FILE> - Convert specified FILE",
|
||||
"\t-h=<HEADER> - add main header (h1) to result",
|
||||
"\t-t - Convert input as tsv",
|
||||
"\t-a - align columns width",
|
||||
"",
|
||||
"FILE formats supported:",
|
||||
"\t- csv (default)",
|
||||
@ -148,6 +189,9 @@ func usage(writer *os.File) {
|
||||
"\t- absolute",
|
||||
"\t- relative to current working 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 {
|
||||
fmt.Fprintln(writer, str)
|
||||
|
Loading…
Reference in New Issue
Block a user