Skip to content

Commit 5eafdb5

Browse files
zeroshadeamoeba
andauthored
feat(info, search, install, uninstall): add json output option (#246)
Add `--json` options for output of info and search subcommands --------- Co-authored-by: Bryce Mecum <petridish@gmail.com>
1 parent 656e7da commit 5eafdb5

File tree

11 files changed

+251
-50
lines changed

11 files changed

+251
-50
lines changed

cmd/dbc/completions/dbc.bash

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ _dbc_install_completions() {
6666
esac
6767

6868
if [[ "$cur" == -* ]]; then
69-
COMPREPLY=($(compgen -W "--no-verify --level -l" -- "$cur"))
69+
COMPREPLY=($(compgen -W "--json --no-verify --level -l" -- "$cur"))
7070
return 0
7171
fi
7272

@@ -87,7 +87,7 @@ _dbc_uninstall_completions() {
8787
esac
8888

8989
if [[ "$cur" == -* ]]; then
90-
COMPREPLY=($(compgen -W "--level -l" -- "$cur"))
90+
COMPREPLY=($(compgen -W "--json --level -l" -- "$cur"))
9191
return 0
9292
fi
9393

@@ -172,7 +172,7 @@ _dbc_search_completions() {
172172
prev="${COMP_WORDS[COMP_CWORD-1]}"
173173

174174
if [[ "$cur" == -* ]]; then
175-
COMPREPLY=($(compgen -W "-h -v" -- "$cur"))
175+
COMPREPLY=($(compgen -W "-h -v --json" -- "$cur"))
176176
return 0
177177
fi
178178

@@ -181,6 +181,16 @@ _dbc_search_completions() {
181181
}
182182

183183
_dbc_info_completions() {
184+
local cur prev
185+
cur="${COMP_WORDS[COMP_CWORD]}"
186+
prev="${COMP_WORDS[COMP_CWORD-1]}"
187+
188+
if [[ "$cur" == -* ]]; then
189+
COMPREPLY=($(compgen -W "--json" -- "$cur"))
190+
return 0
191+
fi
192+
193+
# Driver name completion (no specific completion available)
184194
COMPREPLY=()
185195
}
186196

cmd/dbc/completions/dbc.fish

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,12 @@ complete -f -c dbc -n '__fish_dbc_needs_command' -a 'completion' -d 'Generate sh
4242
# install subcommand
4343
complete -f -c dbc -n '__fish_dbc_using_subcommand install' -s h -d 'Show Help'
4444
complete -f -c dbc -n '__fish_dbc_using_subcommand install' -l help -d 'Show Help'
45+
complete -f -c dbc -n '__fish_dbc_using_subcommand install' -l json -d 'Print output as JSON instead of plaintext'
4546
complete -f -c dbc -n '__fish_dbc_using_subcommand install' -l no-verify -d 'Do not verify the driver after installation'
4647
complete -f -c dbc -n '__fish_dbc_using_subcommand install' -l level -s l -d 'Installation level' -xa 'user system'
4748

4849
# uninstall subcommand
50+
complete -f -c dbc -n '__fish_dbc_using_subcommand uninstall' -l json -d 'Print output as JSON instead of plaintext'
4951
complete -f -c dbc -n '__fish_dbc_using_subcommand uninstall' -l level -s l -d 'Installation level' -xa 'user system'
5052

5153
# init subcommand
@@ -69,6 +71,7 @@ complete -f -c dbc -n '__fish_dbc_using_subcommand sync' -l no-verify -d 'Do not
6971
complete -f -c dbc -n '__fish_dbc_using_subcommand search' -s h -d 'Help'
7072
complete -f -c dbc -n '__fish_dbc_using_subcommand search' -l help -d 'Help'
7173
complete -f -c dbc -n '__fish_dbc_using_subcommand search' -s v -d 'Verbose'
74+
complete -f -c dbc -n '__fish_dbc_using_subcommand search' -l json -d 'Print output as JSON instead of plaintext'
7275

7376
# remove subcommand
7477
complete -f -c dbc -n '__fish_dbc_using_subcommand remove' -s h -d 'Help'
@@ -78,6 +81,7 @@ complete -c dbc -n '__fish_dbc_using_subcommand remove' -l path -s p -r -F -a '*
7881
# info subcommand
7982
complete -f -c dbc -n '__fish_dbc_using_subcommand info' -s h -d 'Help'
8083
complete -f -c dbc -n '__fish_dbc_using_subcommand info' -l help -d 'Help'
84+
complete -f -c dbc -n '__fish_dbc_using_subcommand info' -l json -d 'Print output as JSON instead of plaintext'
8185

8286
# docs subcommand
8387
complete -f -c dbc -n '__fish_dbc_using_subcommand docs' -s h -d 'Help'

cmd/dbc/completions/dbc.zsh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ function _dbc_install_completions {
7171
'(--help)-h[Help]' \
7272
'(-h)--help[Help]' \
7373
'--no-verify[do not verify the driver after installation]' \
74+
'--json[Print output as JSON instead of plaintext]' \
7475
'(-l)--level[installation level]: :(user system)' \
7576
'(--level)-l[installation level]: :(user system)' \
7677
':driver name: '
@@ -80,6 +81,7 @@ function _dbc_uninstall_completions {
8081
_arguments \
8182
'(-l)--level[installation level]: :(user system)' \
8283
'(--level)-l[installation level]: :(user system)' \
84+
'--json[Print output as JSON instead of plaintext]' \
8385
':driver name: '
8486
}
8587

@@ -115,13 +117,15 @@ function _dbc_search_completions {
115117
'(--help)-h[Help]' \
116118
'(-h)--help[Help]' \
117119
'-v[verbose]' \
120+
'--json[Print output as JSON instead of plaintext]' \
118121
':search term: '
119122
}
120123

121124
function _dbc_info_completions {
122125
_arguments \
123126
'(--help)-h[Help]' \
124127
'(-h)--help[Help]' \
128+
'--json[Print output as JSON instead of plaintext]' \
125129
':driver name: '
126130
}
127131

cmd/dbc/driver_list_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ func TestUnmarshalDriverList(t *testing.T) {
4141
{"less", "[drivers]\nflightsql = {version = '<=1.8.0'}", []dbc.PkgInfo{
4242
{Driver: dbc.Driver{Path: "flightsql"}, Version: semver.MustParse("1.8.0")},
4343
}, nil},
44-
{"greater", "[drivers]\nflightsql = {version = '>=1.8.0, <1.10.0'}", []dbc.PkgInfo{
45-
{Driver: dbc.Driver{Path: "flightsql"}, Version: semver.MustParse("1.9.0")},
44+
{"greater", "[drivers]\nflightsql = {version = '>=1.8.0, <=1.10.0'}", []dbc.PkgInfo{
45+
{Driver: dbc.Driver{Path: "flightsql"}, Version: semver.MustParse("1.10.0")},
4646
}, nil},
4747
}
4848

cmd/dbc/info.go

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package main
1616

1717
import (
18+
"encoding/json"
1819
"strings"
1920

2021
tea "github.com/charmbracelet/bubbletea"
@@ -23,12 +24,14 @@ import (
2324

2425
type InfoCmd struct {
2526
Driver string `arg:"positional,required" help:"Driver to get info about"`
27+
Json bool `help:"Print output as JSON instead of plaintext"`
2628
}
2729

2830
func (c InfoCmd) GetModelCustom(baseModel baseModel) tea.Model {
2931
return infoModel{
30-
baseModel: baseModel,
31-
driver: c.Driver,
32+
baseModel: baseModel,
33+
jsonOutput: c.Json,
34+
driver: c.Driver,
3235
}
3336
}
3437

@@ -42,8 +45,9 @@ func (c InfoCmd) GetModel() tea.Model {
4245
type infoModel struct {
4346
baseModel
4447

45-
driver string
46-
drv dbc.Driver
48+
driver string
49+
jsonOutput bool
50+
drv dbc.Driver
4751
}
4852

4953
func (m infoModel) Init() tea.Cmd {
@@ -83,6 +87,35 @@ func formatDriverInfo(drv dbc.Driver) string {
8387
return b.String()
8488
}
8589

90+
func driverInfoJSON(drv dbc.Driver) string {
91+
info := drv.MaxVersion()
92+
93+
var driverInfoOutput = struct {
94+
Driver string `json:"driver"`
95+
Version string `json:"version"`
96+
Title string `json:"title"`
97+
License string `json:"license"`
98+
Desc string `json:"description"`
99+
Packages []string `json:"packages"`
100+
}{
101+
Driver: drv.Path,
102+
Version: info.Version.String(),
103+
Title: drv.Title,
104+
License: drv.License,
105+
Desc: drv.Desc,
106+
}
107+
for _, pkg := range info.Packages {
108+
driverInfoOutput.Packages = append(driverInfoOutput.Packages, pkg.PlatformTuple)
109+
}
110+
111+
jsonBytes, err := json.Marshal(driverInfoOutput)
112+
if err != nil {
113+
return err.Error()
114+
}
115+
116+
return string(jsonBytes)
117+
}
118+
86119
func (m infoModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
87120
switch msg := msg.(type) {
88121
case dbc.Driver:
@@ -96,6 +129,9 @@ func (m infoModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
96129
}
97130

98131
func (m infoModel) FinalOutput() string {
132+
if m.jsonOutput {
133+
return driverInfoJSON(m.drv)
134+
}
99135
return formatDriverInfo(m.drv)
100136
}
101137

cmd/dbc/install.go

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package main
1616

1717
import (
18+
"encoding/json"
1819
"errors"
1920
"fmt"
2021
"io/fs"
@@ -51,18 +52,20 @@ type InstallCmd struct {
5152
// URI url.URL `arg:"-u" placeholder:"URL" help:"Base URL for fetching drivers"`
5253
Driver string `arg:"positional,required" help:"Driver to install"`
5354
Level config.ConfigLevel `arg:"-l" help:"Config level to install to (user, system)"`
55+
Json bool `arg:"--json" help:"Output JSON instead of plaintext"`
5456
NoVerify bool `arg:"--no-verify" help:"Allow installation of drivers without a signature file"`
5557
}
5658

5759
func (c InstallCmd) GetModelCustom(baseModel baseModel) tea.Model {
5860
s := spinner.New()
5961
s.Spinner = spinner.MiniDot
6062
return progressiveInstallModel{
61-
Driver: c.Driver,
62-
NoVerify: c.NoVerify,
63-
spinner: s,
64-
cfg: getConfig(c.Level),
65-
baseModel: baseModel,
63+
Driver: c.Driver,
64+
NoVerify: c.NoVerify,
65+
jsonOutput: c.Json,
66+
spinner: s,
67+
cfg: getConfig(c.Level),
68+
baseModel: baseModel,
6669
p: dbc.NewFileProgress(
6770
progress.WithDefaultGradient(),
6871
progress.WithWidth(20),
@@ -147,6 +150,7 @@ type progressiveInstallModel struct {
147150
Driver string
148151
VersionInput *semver.Version
149152
NoVerify bool
153+
jsonOutput bool
150154
cfg config.Config
151155

152156
DriverPackage dbc.PkgInfo
@@ -173,23 +177,55 @@ func (m progressiveInstallModel) Init() tea.Cmd {
173177
func (m progressiveInstallModel) FinalOutput() string {
174178
if m.conflictingInfo.ID != "" && m.conflictingInfo.Version != nil {
175179
if m.conflictingInfo.Version.Equal(m.DriverPackage.Version) {
180+
if m.jsonOutput {
181+
return fmt.Sprintf(`{"status":"already installed","driver":"%s","version":"%s","location":"%s"}`,
182+
m.conflictingInfo.ID, m.conflictingInfo.Version, filepath.SplitList(m.cfg.Location)[0])
183+
}
176184
return fmt.Sprintf("\nDriver %s %s already installed at %s\n",
177185
m.conflictingInfo.ID, m.conflictingInfo.Version, filepath.SplitList(m.cfg.Location)[0])
178186
}
179187
}
180188

181189
var b strings.Builder
182190
if m.state == stDone {
183-
if m.conflictingInfo.ID != "" && m.conflictingInfo.Version != nil {
184-
b.WriteString(fmt.Sprintf("\nRemoved conflicting driver: %s (version: %s)",
185-
m.conflictingInfo.ID, m.conflictingInfo.Version))
191+
var output struct {
192+
Status string `json:"status"`
193+
Driver string `json:"driver"`
194+
Version string `json:"version"`
195+
Location string `json:"location"`
196+
Message string `json:"message,omitempty"`
197+
Conflict string `json:"conflict,omitempty"`
186198
}
187199

188-
b.WriteString(fmt.Sprintf("\nInstalled %s %s to %s\n",
189-
m.Driver, m.DriverPackage.Version, filepath.SplitList(m.cfg.Location)[0]))
200+
output.Status = "installed"
201+
output.Driver = m.Driver
202+
output.Version = m.DriverPackage.Version.String()
203+
output.Location = filepath.SplitList(m.cfg.Location)[0]
204+
if m.conflictingInfo.ID != "" && m.conflictingInfo.Version != nil {
205+
output.Conflict = fmt.Sprintf("%s (version: %s)", m.conflictingInfo.ID, m.conflictingInfo.Version)
206+
}
190207

191208
if m.postInstallMessage != "" {
192-
b.WriteString("\n" + postMsgStyle.Render(m.postInstallMessage) + "\n")
209+
output.Message = m.postInstallMessage
210+
}
211+
212+
if m.jsonOutput {
213+
jsonOutput, err := json.Marshal(output)
214+
if err != nil {
215+
return fmt.Sprintf(`{"status":"error","error":"%s"}`, err.Error())
216+
}
217+
return string(jsonOutput)
218+
}
219+
220+
if output.Conflict != "" {
221+
fmt.Fprintf(&b, "\nRemoved conflicting driver: %s", output.Conflict)
222+
}
223+
224+
fmt.Fprintf(&b, "\nInstalled %s %s to %s\n",
225+
output.Driver, output.Version, output.Location)
226+
227+
if output.Message != "" {
228+
b.WriteString("\n" + postMsgStyle.Render(output.Message) + "\n")
193229
}
194230
}
195231
return b.String()
@@ -303,6 +339,10 @@ func (m progressiveInstallModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
303339
return m, tea.Sequence(func() tea.Msg {
304340
return config.CreateManifest(m.cfg, msg.DriverInfo)
305341
}, tea.Quit)
342+
case error:
343+
if m.jsonOutput {
344+
return m, tea.Sequence(tea.Println(fmt.Sprintf(`{"status":"error","error":"%s"}`, msg.Error())), tea.Quit)
345+
}
306346
}
307347

308348
base, cmd := m.baseModel.Update(msg)
@@ -320,7 +360,7 @@ func checkbox(label string, checked bool) string {
320360
var postMsgStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
321361

322362
func (m progressiveInstallModel) View() string {
323-
if m.status != 0 {
363+
if m.status != 0 || m.jsonOutput {
324364
return ""
325365
}
326366

@@ -333,7 +373,7 @@ func (m progressiveInstallModel) View() string {
333373
var b strings.Builder
334374
for s := range stDone {
335375
if s == m.state {
336-
b.WriteString(fmt.Sprintf("[%s] %s...", m.spinner.View(), s.String()))
376+
fmt.Fprintf(&b, "[%s] %s...", m.spinner.View(), s.String())
337377
if s == stDownloading {
338378
b.WriteString(" " + m.p.View())
339379
}

cmd/dbc/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ func main() {
227227

228228
if !args.Quiet {
229229
if fo, ok := m.(HasFinalOutput); ok {
230-
fmt.Print(fo.FinalOutput())
230+
fmt.Println(fo.FinalOutput())
231231
}
232232
}
233233

0 commit comments

Comments
 (0)