csvql.*csvql.Run   B
last analyzed

Complexity

Conditions 6

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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