Skip to content

Commit

Permalink
Merge pull request #20 from nao1215/feat-dump-tsv-ltsv-json
Browse files Browse the repository at this point in the history
Feat dump tsv ltsv json
  • Loading branch information
nao1215 authored Nov 13, 2022
2 parents 2ea9e0b + 4ca5c6e commit 3dafb16
Show file tree
Hide file tree
Showing 16 changed files with 167 additions and 40 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,13 @@ If the --sql option is not specified, the sqly shell is started. When you execut
The command beginning with a dot is the sqly helper command; I plan to add more features in the future to make the sqly shell run more comfortably.
```
$ sqly
sqly v0.3.0 (work in progress)
sqly v0.5.0 (work in progress)
enter "SQL query" or "sqly command that beginning with a dot".
.help print usage, .exit exit sqly.
sqly> .help
.dump: dump db table to csv file
.dump: dump db table to file in a format according to output mode (default: csv)
.exit: exit sqly
.header: print table header
.help: print help message
Expand Down Expand Up @@ -109,7 +109,7 @@ $ sqly --sql "SELECT * FROM user" testdata/user.csv --output=test.csv
-h, --help print help message
-j, --json change output format to json (default: table)
-l, --ltsv change output format to ltsv (default: table)
-m, --markdown change output format to markdown (default: table)
-m, --markdown change output format to markdown table(default: table)
-o, --output string destination path for SQL results specified in --sql option
-s, --sql string sql query you want to execute
-t, --tsv change output format to tsv (default: table)
Expand All @@ -119,13 +119,13 @@ $ sqly --sql "SELECT * FROM user" testdata/user.csv --output=test.csv
# Features to be added
- [x] import json
- [x] print json format
- [ ] dump json file
- [x] dump json file
- [x] import tsv
- [x] ptint tsv format
- [ ] dump tsv file
- [x] dump tsv file
- [x] import ltsv
- [x] print ltsv format
- [ ] dump ltsv file
- [x] dump ltsv file
- [ ] import swagger
- [x] print markdown format
- [ ] ignore csv header option
Expand Down
2 changes: 1 addition & 1 deletion config/argument.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func NewArg() (*Arg, error) {
pflag.BoolVarP(&outputFlag.tsv, "tsv", "t", false, "change output format to tsv (default: table)")
pflag.BoolVarP(&outputFlag.ltsv, "ltsv", "l", false, "change output format to ltsv (default: table)")
pflag.BoolVarP(&outputFlag.json, "json", "j", false, "change output format to json (default: table)")
pflag.BoolVarP(&outputFlag.markdown, "markdown", "m", false, "change output format to markdown table(default: table)")
pflag.BoolVarP(&outputFlag.markdown, "markdown", "m", false, "change output format to markdown table (default: table)")
pflag.BoolVarP(&arg.HelpFlag, "help", "h", false, "print help message")
pflag.BoolVarP(&arg.VersionFlag, "version", "v", false, "print help message")
pflag.Parse()
Expand Down
3 changes: 2 additions & 1 deletion domain/model/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ func (t *Table) printTSV(out *os.File) {
// Print print all record with header; output format is ltsv
func (t *Table) printLTSV(out *os.File) {
for _, v := range t.Records {
r := []string{}
r := Record{}
for i, data := range v {
r = append(r, t.Header[i]+":"+data)
}
Expand All @@ -192,6 +192,7 @@ func (t *Table) printJSON(out *os.File) {
b, err := json.MarshalIndent(data, "", " ")
if err != nil {
fmt.Fprintf(os.Stderr, "json marshal error: "+err.Error())
return
}
fmt.Fprintln(out, string(b))
}
4 changes: 4 additions & 0 deletions domain/repository/json.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package repository

import (
"os"

"github.com/nao1215/sqly/domain/model"
)

Expand All @@ -10,4 +12,6 @@ import (
type JSONRepository interface {
// List get csv all data with header.
List(jsonFilePath string) (*model.JSON, error)
// Dump write contents of DB table to JSON file
Dump(f *os.File, table *model.Table) error
}
2 changes: 2 additions & 0 deletions domain/repository/ltsv.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ import (
type LTSVRepository interface {
// List get ltsv all data with label.
List(ltsv *os.File) (*model.LTSV, error)
// Dump write contents of DB table to LTSV file
Dump(ltsv *os.File, table *model.Table) error
}
2 changes: 2 additions & 0 deletions domain/repository/tsv.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ import (
type TSVRepository interface {
// List get tsv all data with header.
List(tsv *os.File) (*model.TSV, error)
// Dump write contents of DB table to TSV file
Dump(tsv *os.File, table *model.Table) error
}
19 changes: 19 additions & 0 deletions infrastructure/persistence/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,22 @@ func (r *jsonRepository) List(jsonFilePath string) (*model.JSON, error) {
}
return &j, nil
}

// Dump write contents of DB table to JSON file
func (r *jsonRepository) Dump(f *os.File, table *model.Table) error {
data := make([]map[string]interface{}, 0)

for _, v := range table.Records {
d := make(map[string]interface{}, 0)
for i, r := range v {
d[table.Header[i]] = r
}
data = append(data, d)
}
b, err := json.MarshalIndent(data, "", " ")
if err != nil {
return err
}
_, err = f.Write(b)
return err
}
16 changes: 16 additions & 0 deletions infrastructure/persistence/ltsv.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,22 @@ func (lr *ltsvRepository) List(f *os.File) (*model.LTSV, error) {
return &t, nil
}

// Dump write contents of DB table to LTSV file
func (lr *ltsvRepository) Dump(f *os.File, table *model.Table) error {
w := csv.NewWriter(f)
w.Comma = '\t'

records := [][]string{}
for _, v := range table.Records {
r := model.Record{}
for i, data := range v {
r = append(r, table.Header[i]+":"+data)
}
records = append(records, r)
}
return w.WriteAll(records)
}

func (lr *ltsvRepository) labelAndData(s string) (string, string, error) {
idx := strings.Index(s, ":")
if idx == -1 || idx == 0 {
Expand Down
14 changes: 14 additions & 0 deletions infrastructure/persistence/tsv.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,17 @@ func (tr *tsvRepository) List(f *os.File) (*model.TSV, error) {
}
return &t, nil
}

// Dump write contents of DB table to TSV file
func (tr *tsvRepository) Dump(f *os.File, table *model.Table) error {
w := csv.NewWriter(f)
w.Comma = '\t'

records := [][]string{
table.Header,
}
for _, v := range table.Records {
records = append(records, v)
}
return w.WriteAll(records)
}
2 changes: 1 addition & 1 deletion shell/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type CommandList map[string]command
// NewCommands return *CommandList that set sqly helper commands.
func NewCommands() CommandList {
c := CommandList{}
c[".dump"] = command{execute: c.dumpCommand, name: ".dump", description: "dump db table to csv file"}
c[".dump"] = command{execute: c.dumpCommand, name: ".dump", description: "dump db table to file in a format according to output mode (default: csv)"}
c[".exit"] = command{execute: c.exitCommand, name: ".exit", description: "exit sqly"}
c[".header"] = command{execute: c.headerCommand, name: ".header", description: "print table header"}
c[".help"] = command{execute: c.helpCommand, name: ".help", description: "print help message"}
Expand Down
36 changes: 33 additions & 3 deletions shell/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import (
"fmt"

"github.com/fatih/color"
"github.com/nao1215/sqly/domain/model"
)

// dumpCommand dump specified table to csv file
func (c CommandList) dumpCommand(s *Shell, argv []string) error {
if len(argv) != 2 {
fmt.Fprintln(Stdout, "[Usage]")
fmt.Fprintln(Stdout, " .dump TABLE_NAME CSV_FILE_PATH")
fmt.Fprintln(Stdout, " .dump TABLE_NAME FILE_PATH")
fmt.Fprintln(Stdout, "[Note]")
fmt.Fprintln(Stdout, " Output will be in the format specified in .mode.")
fmt.Fprintln(Stdout, " table mode is not available in .dump. If mode is table, .dump output CSV file.")
return nil
}

Expand All @@ -19,10 +23,36 @@ func (c CommandList) dumpCommand(s *Shell, argv []string) error {
return err
}

if err := s.csvInteractor.Dump(argv[1], table); err != nil {
if err := dumpToFile(s, argv[1], table); err != nil {
return err
}
fmt.Fprintf(Stdout, "dump `%s` table to %s\n", color.CyanString(argv[0]), color.HiCyanString(argv[1]))
fmt.Fprintf(Stdout, "dump `%s` table to %s (mode=%s)\n",
color.CyanString(argv[0]), color.HiCyanString(argv[1]), dumpMode(s.argument.Output.Mode))

return nil
}

func dumpToFile(s *Shell, filePath string, table *model.Table) error {
var err error
switch s.argument.Output.Mode {
case model.PrintModeCSV:
err = s.csvInteractor.Dump(filePath, table)
case model.PrintModeTSV:
err = s.tsvInteractor.Dump(filePath, table)
case model.PrintModeLTSV:
err = s.ltsvInteractor.Dump(filePath, table)
case model.PrintModeJSON:
err = s.jsonInteractor.Dump(filePath, table)
default:
err = s.csvInteractor.Dump(filePath, table)
}
return err
}

func dumpMode(m model.PrintMode) string {
switch m {
case model.PrintModeTable:
return "csv"
}
return m.String()
}
52 changes: 28 additions & 24 deletions shell/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,27 +161,33 @@ func (s *Shell) prompt(ctx context.Context) (string, error) {

func (s *Shell) completer(d prompt.Document) []prompt.Suggest {
suggest := []prompt.Suggest{
{Text: "SELECT", Description: "get records from table"},
{Text: "INSERT INTO", Description: "creates one or more new records in an existing table"},
{Text: "UPDATE", Description: "update one or more records"},
{Text: "AS", Description: "set alias name"},
{Text: "FROM", Description: "specify the table"},
{Text: "WHERE", Description: "search condition"},
{Text: "GROUP BY", Description: "groping records"},
{Text: "HAVING", Description: "extraction conditions for records after grouping"},
{Text: "ORDER BY", Description: "sort result"},
{Text: "VALUES", Description: "specify values to be inserted or updated"},
{Text: "SET", Description: "specify values to be updated"},
{Text: "DELETE FROM", Description: "specify tables to be deleted"},
{Text: "IN", Description: "condition grouping"},
{Text: "INNER JOIN", Description: "inner join tables"},
{Text: "LIMIT", Description: "upper Limit of records"},
{Text: "SELECT", Description: "SQL: get records from table"},
{Text: "INSERT INTO", Description: "SQL: creates one or more new records in an existing table"},
{Text: "UPDATE", Description: "SQL: update one or more records"},
{Text: "AS", Description: "SQL: set alias name"},
{Text: "FROM", Description: "SQL: specify the table"},
{Text: "WHERE", Description: "SQL: search condition"},
{Text: "GROUP BY", Description: "SQL: groping records"},
{Text: "HAVING", Description: "SQL: extraction conditions for records after grouping"},
{Text: "ORDER BY", Description: "SQL: sort result"},
{Text: "VALUES", Description: "SQL: specify values to be inserted or updated"},
{Text: "SET", Description: "SQL: specify values to be updated"},
{Text: "DELETE FROM", Description: "SQL: specify tables to be deleted"},
{Text: "IN", Description: "SQL: condition grouping"},
{Text: "INNER JOIN", Description: "SQL: inner join tables"},
{Text: "LIMIT", Description: "SQL: upper Limit of records"},
{Text: "table", Description: "sqly command argument: table output format"},
{Text: "markdown", Description: "sqly command argument: markdown table output format"},
{Text: "csv", Description: "sqly command argument: csv output format"},
{Text: "tsv", Description: "sqly command argument: tsv output format"},
{Text: "ltsv", Description: "sqly command argument: ltsv output format"},
{Text: "json", Description: "sqly command argument: json output format"},
}

for _, v := range s.commands {
suggest = append(suggest, prompt.Suggest{
Text: v.name,
Description: v.description,
Description: "sqly command: " + v.description,
})
}

Expand All @@ -192,7 +198,7 @@ func (s *Shell) completer(d prompt.Document) []prompt.Suggest {
for _, v := range tableNames {
suggest = append(suggest, prompt.Suggest{
Text: v.Name,
Description: v.Name + " table",
Description: "table: " + v.Name,
})

table, err := s.sqlite3Interactor.Header(s.Ctx, v.Name)
Expand All @@ -202,7 +208,7 @@ func (s *Shell) completer(d prompt.Document) []prompt.Suggest {
for _, h := range table.Header {
suggest = append(suggest, prompt.Suggest{
Text: h,
Description: "header in " + v.Name + " table",
Description: "header: " + h + " column in " + v.Name + " table",
})
}
}
Expand Down Expand Up @@ -238,7 +244,7 @@ func (s *Shell) exec(request string) error {

func (s *Shell) execSQL(req string) error {
req = strings.TrimRight(req, ";")
table, affectedRows, err := s.sqlite3Interactor.ExecSQL(s.Ctx, req, s.argument.Output.Mode)
table, affectedRows, err := s.sqlite3Interactor.ExecSQL(s.Ctx, req)
if err != nil {
return err
}
Expand All @@ -249,13 +255,11 @@ func (s *Shell) execSQL(req string) error {

// use --sql option and user want to output table data to file.
if s.argument.NeedsOutputToFile() {
// I am having difficulty in designing. Therefore, I do not save files in TABLE format.
// Also, only Windows users need to save files with the --output option, and Linux users (it's me)
// can save data in table format with redirection.
if err := s.csvInteractor.Dump(s.argument.Output.FilePath, table); err != nil {
if err := dumpToFile(s, s.argument.Output.FilePath, table); err != nil {
return err
}
fmt.Fprintf(Stdout, "Output sql result to %s\n", color.HiCyanString(s.argument.Output.FilePath))
fmt.Fprintf(Stdout, "Output sql result to %s (output mode=%s)\n",
color.HiCyanString(s.argument.Output.FilePath), dumpMode(s.argument.Output.Mode))
return nil
}

Expand Down
13 changes: 13 additions & 0 deletions usecase/json.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package usecase

import (
"os"

"github.com/nao1215/sqly/domain/model"
"github.com/nao1215/sqly/domain/repository"
)
Expand All @@ -19,3 +21,14 @@ func NewJSONInteractor(r repository.JSONRepository) *JSONInteractor {
func (i *JSONInteractor) List(jsonFilePath string) (*model.JSON, error) {
return i.Repository.List(jsonFilePath)
}

// Dump write contents of DB table to JSON file
func (i *JSONInteractor) Dump(jsonFilePath string, table *model.Table) error {
f, err := os.OpenFile(jsonFilePath, os.O_RDWR|os.O_CREATE, 0664)
if err != nil {
return err
}
defer f.Close()

return i.Repository.Dump(f, table)
}
17 changes: 14 additions & 3 deletions usecase/ltsv.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,27 @@ func NewLTSVInteractor(r repository.LTSVRepository) *LTSVInteractor {
}

// List get LTSV data.
func (ti *LTSVInteractor) List(LTSVFilePath string) (*model.LTSV, error) {
f, err := os.Open(LTSVFilePath)
func (li *LTSVInteractor) List(ltsvFilePath string) (*model.LTSV, error) {
f, err := os.Open(ltsvFilePath)
if err != nil {
return nil, err
}
defer f.Close()

TSV, err := ti.Repository.List(f)
TSV, err := li.Repository.List(f)
if err != nil {
return nil, err
}
return TSV, nil
}

// Dump write contents of DB table to LTSV file
func (li *LTSVInteractor) Dump(ltsvFilePath string, table *model.Table) error {
f, err := os.OpenFile(ltsvFilePath, os.O_RDWR|os.O_CREATE, 0664)
if err != nil {
return err
}
defer f.Close()

return li.Repository.Dump(f, table)
}
2 changes: 1 addition & 1 deletion usecase/sqlite3.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (si *SQLite3Interactor) Exec(ctx context.Context, statement string) (int64,
}

// ExecSQL execute "SELECT/EXPLAIN"query or "INSERT/UPDATE/DELETE" statement
func (si *SQLite3Interactor) ExecSQL(ctx context.Context, statement string, mode model.PrintMode) (*model.Table, int64, error) {
func (si *SQLite3Interactor) ExecSQL(ctx context.Context, statement string) (*model.Table, int64, error) {
argv := strings.Split(trimWordGaps(statement), " ")

// NOTE: SQLY uses SQLite3. There is some SQL that can be changed from non-support
Expand Down
Loading

0 comments on commit 3dafb16

Please sign in to comment.