Skip to content

Commit a56dbee

Browse files
committed
feat(ifdata): Add shell completion for flags and network interfaces
1 parent ba4134b commit a56dbee

File tree

9 files changed

+468
-164
lines changed

9 files changed

+468
-164
lines changed

cmd/ifdata/cmd.go

Lines changed: 58 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -11,49 +11,20 @@ import (
1111
"github.com/spf13/cobra"
1212
)
1313

14-
const (
15-
Name = "ifdata"
16-
FlagExists = "e"
17-
FlagPrint = "p"
18-
FlagPrintExists = "pe"
19-
FlagAddress = "pa"
20-
FlagNetmask = "pn"
21-
FlagNetworkAddress = "pN"
22-
FlagBroadcastAddress = "pb"
23-
FlagMTU = "pm"
24-
FlagFlags = "pf"
25-
FlagHardwareAddress = "ph"
26-
27-
FlagInputStatistics = "si"
28-
FlagInputPackets = "sip"
29-
FlagInputBytes = "sib"
30-
FlagInputErrors = "sie"
31-
FlagInputDropped = "sid"
32-
FlagInputFIFO = "sif"
33-
FlagInputCompressed = "sic"
34-
FlagInputMulticast = "sim"
35-
FlagInputBytesSecond = "bips"
36-
37-
FlagOutputStatistics = "so"
38-
FlagOutputPackets = "sop"
39-
FlagOutputBytes = "sob"
40-
FlagOutputErrors = "soe"
41-
FlagOutputDropped = "sod"
42-
FlagOutputFIFO = "sof"
43-
FlagOutputCollisions = "sox"
44-
FlagOutputCarrierLosses = "soc"
45-
FlagOutputMulticast = "som"
46-
FlagOutputBytesSecond = "bops"
47-
)
14+
const Name = "ifdata"
4815

4916
func New(opts ...cmdutil.Option) *cobra.Command {
5017
cmd := &cobra.Command{
5118
Use: Name + " [flags] interface",
5219
Short: "Get network interface info without parsing ifconfig output",
5320
RunE: run,
5421
GroupID: cmdutil.Applet,
22+
23+
ValidArgsFunction: validArgs,
5524
}
5625

26+
cmd.InitDefaultHelpFlag()
27+
5728
cmd.SetUsageFunc(usageFunc)
5829
cmd.DisableFlagsInUseLine = true
5930
cmd.DisableFlagParsing = true
@@ -64,6 +35,41 @@ func New(opts ...cmdutil.Option) *cobra.Command {
6435
return cmd
6536
}
6637

38+
func validArgs(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
39+
if len(args) == 0 {
40+
vals := formatterValues()
41+
strs := make([]string, 0, len(vals))
42+
for _, val := range vals {
43+
if val == fmtNone {
44+
continue
45+
}
46+
47+
if val.supported() {
48+
strs = append(strs, val.String()+"\t"+val.description())
49+
}
50+
}
51+
return strs, cobra.ShellCompDirectiveNoFileComp
52+
}
53+
54+
ifaces, err := net.Interfaces()
55+
if err != nil {
56+
return nil, cobra.ShellCompDirectiveError
57+
}
58+
59+
names := make([]string, 0, len(ifaces))
60+
for _, iface := range ifaces {
61+
addrs, _ := iface.Addrs()
62+
desc := make([]string, 0, len(addrs))
63+
for _, addr := range addrs {
64+
if addr, ok := addr.(*net.IPNet); ok && addr.IP.To4() != nil {
65+
desc = append(desc, addr.String())
66+
}
67+
}
68+
names = append(names, iface.Name+"\t"+strings.Join(desc, ","))
69+
}
70+
return names, cobra.ShellCompDirectiveNoFileComp
71+
}
72+
6773
var (
6874
ErrNoFormatter = errors.New("no formatter was provided")
6975
ErrUnknownFormatter = errors.New("unknown formatter")
@@ -77,7 +83,8 @@ func run(cmd *cobra.Command, args []string) error {
7783
return cmd.Usage()
7884
}
7985

80-
var op, name string
86+
var op formatter
87+
var name string
8188
for _, arg := range args {
8289
switch {
8390
case arg == "-h", arg == "--help":
@@ -92,14 +99,16 @@ func run(cmd *cobra.Command, args []string) error {
9299
return tmpl.Execute(cmd.OutOrStdout(), cmd)
93100
}
94101
case strings.HasPrefix(arg, "-"):
95-
op = strings.TrimPrefix(arg, "-")
102+
if err := op.UnmarshalText([]byte(arg)); err != nil {
103+
return err
104+
}
96105
default:
97106
name = arg
98107
}
99108
}
100109

101110
switch {
102-
case op == "":
111+
case op == fmtNone:
103112
return ErrNoFormatter
104113
case name == "":
105114
return ErrNoInterface
@@ -108,7 +117,7 @@ func run(cmd *cobra.Command, args []string) error {
108117

109118
iface, err := net.InterfaceByName(name)
110119

111-
if op == FlagPrintExists {
120+
if op == fmtPrintExists {
112121
if err != nil {
113122
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "no")
114123
} else {
@@ -121,20 +130,20 @@ func run(cmd *cobra.Command, args []string) error {
121130
return fmt.Errorf("%w: %s", err, name)
122131
}
123132

124-
if op == FlagExists {
133+
if op == fmtExists {
125134
return nil
126135
}
127136

128137
switch op {
129-
case FlagMTU:
138+
case fmtMTU:
130139
_, _ = fmt.Fprintln(cmd.OutOrStdout(), iface.MTU)
131-
case FlagFlags:
140+
case fmtFlags:
132141
for _, flag := range strings.Split(iface.Flags.String(), "|") {
133142
_, _ = fmt.Fprintln(cmd.OutOrStdout(), flag)
134143
}
135-
case FlagHardwareAddress:
144+
case fmtHardwareAddress:
136145
_, _ = fmt.Fprintln(cmd.OutOrStdout(), strings.ToUpper(iface.HardwareAddr.String()))
137-
case FlagAddress, FlagNetmask, FlagNetworkAddress, FlagBroadcastAddress, FlagPrint:
146+
case fmtAddress, fmtNetmask, fmtNetworkAddress, fmtBroadcastAddress, fmtPrint:
138147
addrs, err := iface.Addrs()
139148
if err != nil {
140149
return err
@@ -143,15 +152,15 @@ func run(cmd *cobra.Command, args []string) error {
143152
for _, addr := range addrs {
144153
if addr, ok := addr.(*net.IPNet); ok && addr.IP.To4() != nil {
145154
switch op {
146-
case FlagAddress:
155+
case fmtAddress:
147156
_, _ = fmt.Fprintln(cmd.OutOrStdout(), addr.IP)
148-
case FlagNetmask:
157+
case fmtNetmask:
149158
_, _ = fmt.Fprintln(cmd.OutOrStdout(), net.IP(addr.Mask))
150-
case FlagNetworkAddress:
159+
case fmtNetworkAddress:
151160
_, _ = fmt.Fprintln(cmd.OutOrStdout(), addr.IP.Mask(addr.Mask))
152-
case FlagBroadcastAddress:
161+
case fmtBroadcastAddress:
153162
_, _ = fmt.Fprintln(cmd.OutOrStdout(), getBroadcastAddr(addr))
154-
case FlagPrint:
163+
case fmtPrint:
155164
_, _ = fmt.Fprintf(cmd.OutOrStdout(),
156165
"%s %s %s %d\n",
157166
addr.IP,
@@ -168,7 +177,7 @@ func run(cmd *cobra.Command, args []string) error {
168177
}
169178

170179
cmd.SilenceUsage = false
171-
return fmt.Errorf("%w: -%s", ErrUnknownFormatter, op)
180+
return fmt.Errorf("%w: %s", ErrUnknownFormatter, op)
172181
}
173182

174183
return nil

cmd/ifdata/formatter.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package ifdata
2+
3+
//go:generate enumer -type formatter -linecomment -text -output formatter_string.go
4+
5+
type formatter uint8
6+
7+
const (
8+
fmtNone formatter = iota //
9+
10+
fmtExists // -e
11+
fmtPrint // -p
12+
fmtPrintExists // -pe
13+
fmtAddress // -pa
14+
fmtNetmask // -pn
15+
fmtNetworkAddress // -pN
16+
fmtBroadcastAddress // -pb
17+
fmtMTU // -pm
18+
fmtFlags // -pf
19+
fmtHardwareAddress // -ph
20+
21+
fmtInputStatistics // -si
22+
fmtInputPackets // -sip
23+
fmtInputBytes // -sib
24+
fmtInputErrors // -sie
25+
fmtInputDropped // -sid
26+
fmtInputFIFO // -sif
27+
fmtInputCompressed // -sic
28+
fmtInputMulticast // -sim
29+
30+
fmtOutputStatistics // -so
31+
fmtOutputPackets // -sop
32+
fmtOutputBytes // -sob
33+
fmtOutputErrors // -soe
34+
fmtOutputDropped // -sod
35+
fmtOutputFIFO // -sof
36+
fmtOutputCollisions // -sox
37+
fmtOutputCarrierLosses // -soc
38+
fmtOutputMulticast // -som
39+
40+
fmtInputBytesSecond // -bips
41+
fmtOutputBytesSecond // -bops
42+
)
43+
44+
func (f formatter) description() string {
45+
switch f {
46+
case fmtExists:
47+
return "Test to see if the interface exists, exit nonzero if it does not"
48+
case fmtPrint:
49+
return "Prints out the whole configuration of the interface"
50+
case fmtPrintExists:
51+
return `Prints "yes" or "no" if the interface exists or not`
52+
case fmtAddress:
53+
return "Prints the IP address of the interface"
54+
case fmtNetmask:
55+
return "Prints the netmask of the interface"
56+
case fmtNetworkAddress:
57+
return "Prints the network address of the interface"
58+
case fmtBroadcastAddress:
59+
return "Prints the broadcast address of the interface"
60+
case fmtMTU:
61+
return "Prints the MTU of the interface"
62+
case fmtFlags:
63+
return "Prints the hardware address of the interface. Exit with a failure exit code if there is not hardware address for the given network interface"
64+
case fmtHardwareAddress:
65+
return "Prints the flags of the interface"
66+
case fmtInputStatistics:
67+
return "Prints all input statistics of the interface"
68+
case fmtInputPackets:
69+
return "Prints the number of input packets"
70+
case fmtInputBytes:
71+
return "Prints the number of input bytes"
72+
case fmtInputErrors:
73+
return "Prints the number of input errors"
74+
case fmtInputDropped:
75+
return "Prints the number of dropped input packets"
76+
case fmtInputFIFO:
77+
return "Prints the number of input fifo overruns"
78+
case fmtInputCompressed:
79+
return "Prints the number of compressed input packets"
80+
case fmtInputMulticast:
81+
return "Prints the number of input multicast packets"
82+
case fmtInputBytesSecond:
83+
return "Prints all output statistics of the interface"
84+
case fmtOutputStatistics:
85+
return "Prints the number of output packets"
86+
case fmtOutputPackets:
87+
return "Prints the number of output bytes"
88+
case fmtOutputBytes:
89+
return "Prints the number of output errors"
90+
case fmtOutputErrors:
91+
return "Prints the number of dropped output packets"
92+
case fmtOutputDropped:
93+
return "Prints the number of output fifo overruns"
94+
case fmtOutputFIFO:
95+
return "Prints the number of output collisions"
96+
case fmtOutputCollisions:
97+
return "Prints the number of output carrier losses"
98+
case fmtOutputCarrierLosses:
99+
return "Prints the number of output multicast packets"
100+
case fmtOutputMulticast:
101+
return "Prints the number of bytes of incoming traffic measured in one second"
102+
case fmtOutputBytesSecond:
103+
return "Prints the number of bytes of outgoing traffic measured in one second"
104+
default:
105+
return ""
106+
}
107+
}
108+
109+
func (f formatter) supported() bool {
110+
return statisticsSupported || uint8(f) < uint8(fmtInputStatistics)
111+
}

0 commit comments

Comments
 (0)