-
Notifications
You must be signed in to change notification settings - Fork 0
/
args.go
153 lines (139 loc) · 5.16 KB
/
args.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package main
import (
"fmt"
"io"
"os"
"strings"
"github.com/alecthomas/kingpin"
"github.com/xo/usql/text"
)
// CommandOrFile is a special type to deal with interspersed -c, -f,
// command-line options, to ensure proper order execution.
type CommandOrFile struct {
Command bool
Value string
}
// Args are the command line arguments.
type Args struct {
DSN string
CommandOrFiles []CommandOrFile
Out string
ForcePassword bool
NoPassword bool
NoRC bool
SingleTransaction bool
Variables []string
PVariables []string
}
func (args *Args) Next() (string, bool, error) {
if len(args.CommandOrFiles) == 0 {
return "", false, io.EOF
}
cmd := args.CommandOrFiles[0]
args.CommandOrFiles = args.CommandOrFiles[1:]
return cmd.Value, cmd.Command, nil
}
type commandOrFile struct {
args *Args
command bool
}
func (c commandOrFile) Set(value string) error {
c.args.CommandOrFiles = append(c.args.CommandOrFiles, CommandOrFile{
Command: c.command,
Value: value,
})
return nil
}
func (c commandOrFile) String() string {
return ""
}
func (c commandOrFile) IsCumulative() bool {
return true
}
// for populating args.PVariables with user-specified options
type pset struct {
args *Args
vals []string
}
func (p pset) Set(value string) error {
for i, v := range p.vals {
if strings.ContainsRune(v, '%') {
p.vals[i] = fmt.Sprintf(v, value)
}
}
p.args.PVariables = append(p.args.PVariables, p.vals...)
return nil
}
func (p pset) String() string {
return ""
}
func (p pset) IsCumulative() bool {
return true
}
func NewArgs() *Args {
args := &Args{}
// set usage template
kingpin.UsageTemplate(text.UsageTemplate())
kingpin.Arg("dsn", "database url").StringVar(&args.DSN)
// command / file flags
kingpin.Flag("command", "run only single command (SQL or internal) and exit").Short('c').SetValue(commandOrFile{args, true})
kingpin.Flag("file", "execute commands from file and exit").Short('f').SetValue(commandOrFile{args, false})
// general flags
kingpin.Flag("no-password", "never prompt for password").Short('w').BoolVar(&args.NoPassword)
kingpin.Flag("no-rc", "do not read start up file").Short('X').BoolVar(&args.NoRC)
kingpin.Flag("out", "output file").Short('o').StringVar(&args.Out)
kingpin.Flag("password", "force password prompt (should happen automatically)").Short('W').BoolVar(&args.ForcePassword)
kingpin.Flag("single-transaction", "execute as a single transaction (if non-interactive)").Short('1').BoolVar(&args.SingleTransaction)
kingpin.Flag("set", "set variable NAME to VALUE").Short('v').PlaceHolder(", --variable=NAME=VALUE").StringsVar(&args.Variables)
// pset
kingpin.Flag("pset", `set printing option VAR to ARG (see \pset command)`).Short('P').PlaceHolder("VAR[=ARG]").StringsVar(&args.PVariables)
// pset flags
kingpin.Flag("field-separator", `field separator for unaligned and CSV output (default "|" and ",")`).Short('F').SetValue(pset{args, []string{"fieldsep=%q", "csv_fieldsep=%q"}})
kingpin.Flag("record-separator", `record separator for unaligned and CSV output (default \n)`).Short('R').SetValue(pset{args, []string{"recordsep=%q"}})
kingpin.Flag("table-attr", "set HTML table tag attributes (e.g., width, border)").Short('T').SetValue(pset{args, []string{"tableattr=%q"}})
type psetconfig struct {
long string
short rune
help string
vals []string
}
pc := func(long string, r rune, help string, vals ...string) psetconfig {
return psetconfig{long, r, help, vals}
}
for _, c := range []psetconfig{
pc("no-align", 'A', "unaligned table output mode", "format=unaligned"),
pc("html", 'H', "HTML table output mode", "format=html"),
pc("tuples-only", 't', "print rows only", "tuples_only=on"),
pc("expanded", 'x', "turn on expanded table output", "expanded=on"),
pc("field-separator-zero", 'z', "set field separator for unaligned and CSV output to zero byte", "fieldsep_zero=on"),
pc("record-separator-zero", '0', "set record separator for unaligned and CSV output to zero byte", "recordsep_zero=on"),
pc("json", 'J', "JSON output mode", "format=json"),
pc("csv", 'C', "CSV output mode", "format=csv"),
pc("vertical", 'G', "vertical output mode", "format=vertical"),
} {
// make copy of values for the callback closure (see https://stackoverflow.com/q/26692844)
vals := make([]string, len(c.vals))
copy(vals, c.vals)
kingpin.Flag(c.long, c.help).Short(c.short).PlaceHolder("TEXT").PreAction(func(*kingpin.ParseContext) error {
args.PVariables = append(args.PVariables, vals...)
return nil
}).Bool()
}
kingpin.Flag("quiet", "run quietly (no messages, only query output)").Short('q').PreAction(func(*kingpin.ParseContext) error {
args.Variables = append(args.Variables, "QUIET=on")
return nil
}).Bool()
// add --set as a hidden alias for --variable
kingpin.Flag("variable", "set variable NAME to VALUE").Hidden().StringsVar(&args.Variables)
// add --version flag
kingpin.Flag("version", "display version and exit").PreAction(func(*kingpin.ParseContext) error {
fmt.Fprintln(os.Stdout, text.CommandName, text.CommandVersion)
os.Exit(0)
return nil
}).Bool()
// hide help flag
kingpin.HelpFlag.Short('h').Hidden()
// parse
kingpin.Parse()
return args
}