Passed
Push — main ( 25bc96...3a375b )
by Adriano
02:00
created

csvql.*csvql.initializePrompt   C

Complexity

Conditions 9

Size

Total Lines 39
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 25
nop 0
dl 0
loc 39
rs 6.6666
c 0
b 0
f 0
1
package csvql
2
3
import (
4
	"adrianolaselva.github.io/csvql/internal/exportdata"
5
	"adrianolaselva.github.io/csvql/pkg/filehandler"
6
	csvHandler "adrianolaselva.github.io/csvql/pkg/filehandler/csv"
7
	"adrianolaselva.github.io/csvql/pkg/storage"
8
	"adrianolaselva.github.io/csvql/pkg/storage/sqlite"
9
	"database/sql"
10
	"errors"
11
	"fmt"
12
	"github.com/chzyer/readline"
13
	"github.com/fatih/color"
14
	_ "github.com/mattn/go-sqlite3"
15
	"github.com/rodaine/table"
16
	"github.com/schollz/progressbar/v3"
17
	"io"
18
	"os"
19
	"strings"
20
)
21
22
const (
23
	cliPrompt          = "csvql> "
24
	cliInterruptPrompt = "^C"
25
	cliEOFPrompt       = "exit"
26
)
27
28
type Csvql interface {
29
	Run() error
30
	Close() error
31
}
32
33
type csvql struct {
34
	storage     storage.Storage
35
	bar         *progressbar.ProgressBar
36
	params      CsvqlParams
37
	fileHandler filehandler.FileHandler
38
}
39
40
func New(params CsvqlParams) (Csvql, error) {
41
	sqLiteStorage, err := sqlite.NewSqLiteStorage(params.DataSourceName)
42
	if err != nil {
43
		return nil, fmt.Errorf("failed to initialize storage: %w", err)
1 ignored issue
show
introduced by
unrecognized printf verb 'w'
Loading history...
44
	}
45
46
	bar := progressbar.NewOptions(0,
47
		progressbar.OptionSetWriter(os.Stdout),
48
		progressbar.OptionEnableColorCodes(true),
49
		progressbar.OptionShowBytes(true),
50
		progressbar.OptionFullWidth(),
51
		progressbar.OptionSetDescription("[cyan][1/1][reset] loading data..."),
52
		progressbar.OptionSetTheme(progressbar.Theme{
53
			Saucer:        "[green]=[reset]",
54
			SaucerHead:    "[green]>[reset]",
55
			SaucerPadding: " ",
56
			BarStart:      "[",
57
			BarEnd:        "]",
58
		}))
59
60
	impData := csvHandler.NewCsvHandler(params.FileInput, rune(params.Delimiter[0]), bar, sqLiteStorage, params.Lines)
61
62
	return &csvql{params: params, bar: bar, fileHandler: impData, storage: sqLiteStorage}, nil
63
}
64
65
// Run import file content and run command
66
func (c *csvql) Run() error {
67
	defer func(bar *progressbar.ProgressBar) {
68
		_ = bar.Clear()
69
	}(c.bar)
70
71
	if err := c.fileHandler.Import(); err != nil {
72
		return fmt.Errorf("failed to import data %w", err)
1 ignored issue
show
introduced by
unrecognized printf verb 'w'
Loading history...
73
	}
74
	defer func(fileHandler filehandler.FileHandler) {
75
		_ = fileHandler.Close()
76
	}(c.fileHandler)
77
78
	return c.execute()
79
}
80
81
// execute execution after data import
82
func (c *csvql) execute() error {
83
	switch {
84
	case c.params.Query != "" && c.params.Export == "":
85
		return c.executeQuery(c.params.Query)
86
	case c.params.Query != "" && c.params.Export != "":
87
		return c.executeQueryAndExport(c.params.Query)
88
	default:
89
		if err := c.initializePrompt(); err != nil {
90
			return err
91
		}
92
	}
93
94
	return nil
95
}
96
97
func (c *csvql) Close() error {
98
	defer func(fileHandler filehandler.FileHandler) {
99
		_ = fileHandler.Close()
100
	}(c.fileHandler)
101
102
	return nil
103
}
104
105
func (c *csvql) initializePrompt() error {
106
	l, err := readline.NewEx(&readline.Config{
107
		Prompt:          cliPrompt,
108
		InterruptPrompt: cliInterruptPrompt,
109
		EOFPrompt:       cliEOFPrompt,
110
		AutoComplete: readline.SegmentFunc(func(i [][]rune, i2 int) [][]rune {
111
			return nil
112
		}),
113
	})
114
	if err != nil {
115
		return fmt.Errorf("failed to initialize cli: %w", err)
1 ignored issue
show
introduced by
unrecognized printf verb 'w'
Loading history...
116
	}
117
	defer func(l *readline.Instance) {
118
		_ = l.Close()
119
	}(l)
120
121
	l.CaptureExitSignal()
122
123
	for {
124
		line, err := l.Readline()
125
		if errors.Is(err, readline.ErrInterrupt) {
126
			if len(line) == 0 {
127
				break
128
			}
129
130
			continue
131
		}
132
133
		if errors.Is(err, io.EOF) {
134
			break
135
		}
136
137
		line = strings.TrimSpace(line)
138
		if err := c.executeQuery(line); err != nil {
139
			fmt.Fprintf(os.Stderr, "%s\n", err.Error())
140
		}
141
	}
142
143
	return nil
144
}
145
146
// executeQueryAndExport execute query and export
147
func (c *csvql) executeQueryAndExport(line string) error {
148
	c.bar.Reset()
149
	c.bar.ChangeMax(c.fileHandler.Lines())
150
	defer func(bar *progressbar.ProgressBar) {
151
		_ = bar.Finish()
152
	}(c.bar)
153
154
	rows, err := c.storage.Query(line)
155
	if err != nil {
156
		return fmt.Errorf("failed to execute query: %w", err)
1 ignored issue
show
introduced by
unrecognized printf verb 'w'
Loading history...
157
	}
158
	defer func(rows *sql.Rows) {
159
		_ = rows.Close()
160
	}(rows)
161
162
	export, err := exportdata.NewExport(c.params.Type, rows, c.params.Export, c.bar)
163
	if err != nil {
164
		return fmt.Errorf("failed to export: %w", err)
1 ignored issue
show
introduced by
unrecognized printf verb 'w'
Loading history...
165
	}
166
167
	if err := export.Export(); err != nil {
168
		return fmt.Errorf("failed to export data: %w", err)
1 ignored issue
show
introduced by
unrecognized printf verb 'w'
Loading history...
169
	}
170
171
	c.bar.Clear()
172
173
	fmt.Printf("[%s] file successfully exported\n", c.params.Export)
174
175
	return nil
176
}
177
178
func (c *csvql) executeQuery(line string) error {
179
	rows, err := c.storage.Query(line)
180
	if err != nil {
181
		return fmt.Errorf("failed to execute query: %w", err)
1 ignored issue
show
introduced by
unrecognized printf verb 'w'
Loading history...
182
	}
183
	defer func(rows *sql.Rows) {
184
		_ = rows.Close()
185
	}(rows)
186
187
	return c.printResult(rows)
188
}
189
190
func (c *csvql) printResult(rows *sql.Rows) error {
191
	columns, err := rows.Columns()
192
	if err != nil {
193
		return fmt.Errorf("failed to load columns: %w", err)
1 ignored issue
show
introduced by
unrecognized printf verb 'w'
Loading history...
194
	}
195
196
	cols := make([]interface{}, 0)
197
	for _, c := range columns {
198
		cols = append(cols, c)
199
	}
200
201
	tbl := table.New(cols...).
202
		WithHeaderFormatter(color.New(color.FgGreen, color.Underline).SprintfFunc()).
203
		WithFirstColumnFormatter(color.New(color.FgYellow).SprintfFunc()).
204
		WithWriter(os.Stdout)
205
206
	for rows.Next() {
207
		values := make([]interface{}, len(columns))
208
		pointers := make([]interface{}, len(columns))
209
		for i := range values {
210
			pointers[i] = &values[i]
211
		}
212
213
		if err := rows.Scan(pointers...); err != nil {
214
			return fmt.Errorf("failed to read row: %w", err)
1 ignored issue
show
introduced by
unrecognized printf verb 'w'
Loading history...
215
		}
216
217
		tbl.AddRow(values...)
218
	}
219
220
	_ = c.bar.Clear()
221
	tbl.Print()
222
223
	return nil
224
}
225