1
1
package clibase
2
2
3
3
import (
4
+ "bytes"
5
+ "encoding/json"
4
6
"os"
5
7
"strings"
6
8
@@ -65,6 +67,20 @@ type Option struct {
65
67
ValueSource ValueSource `json:"value_source,omitempty"`
66
68
}
67
69
70
+ // optionNoMethods is just a wrapper around Option so we can defer to the
71
+ // default json.Unmarshaler behavior.
72
+ type optionNoMethods Option
73
+
74
+ func (o * Option ) UnmarshalJSON (data []byte ) error {
75
+ // If an option has no values, we have no idea how to unmarshal it.
76
+ // So just discard the json data.
77
+ if o .Value == nil {
78
+ o .Value = & DiscardValue
79
+ }
80
+
81
+ return json .Unmarshal (data , (* optionNoMethods )(o ))
82
+ }
83
+
68
84
func (o Option ) YAMLPath () string {
69
85
if o .YAML == "" {
70
86
return ""
@@ -79,6 +95,85 @@ func (o Option) YAMLPath() string {
79
95
// OptionSet is a group of options that can be applied to a command.
80
96
type OptionSet []Option
81
97
98
+ // UnmarshalJSON implements json.Unmarshaler for OptionSets. Options have an
99
+ // interface Value type that cannot handle unmarshalling because the types cannot
100
+ // be inferred. Since it is a slice, instantiating the Options first does not
101
+ // help.
102
+ //
103
+ // However, we typically do instantiate the slice to have the correct types.
104
+ // So this unmarshaller will attempt to find the named option in the existing
105
+ // set, if it cannot, the value is discarded. If the option exists, the value
106
+ // is unmarshalled into the existing option, and replaces the existing option.
107
+ //
108
+ // The value is discarded if it's type cannot be inferred. This behavior just
109
+ // feels "safer", although it should never happen if the correct option set
110
+ // is passed in.
111
+ func (os * OptionSet ) UnmarshalJSON (data []byte ) error {
112
+ dec := json .NewDecoder (bytes .NewBuffer (data ))
113
+ // Should be a json array, so consume the starting open bracket.
114
+ t , err := dec .Token ()
115
+ if err != nil {
116
+ return xerrors .Errorf ("read array open bracket: %w" , err )
117
+ }
118
+ if t != json .Delim ('[' ) {
119
+ return xerrors .Errorf ("expected array open bracket, got %q" , t )
120
+ }
121
+
122
+ // As long as json elements exist, consume them. The counter is used for
123
+ // better errors.
124
+ var i int
125
+ OptionSetDecodeLoop:
126
+ for dec .More () {
127
+ var opt Option
128
+ // jValue is a placeholder value that allows us to capture the
129
+ // raw json for the value to attempt to unmarshal later.
130
+ var jValue jsonValue
131
+ opt .Value = & jValue
132
+ err := dec .Decode (& opt )
133
+ if err != nil {
134
+ return xerrors .Errorf ("decode %d option: %w" , i , err )
135
+ }
136
+
137
+ // Try to see if the option already exists in the option set.
138
+ // If it does, just update the existing option.
139
+ for i , have := range * os {
140
+ if have .Name == opt .Name {
141
+ if jValue != nil {
142
+ err := json .Unmarshal (jValue , & (* os )[i ].Value )
143
+ if err != nil {
144
+ return xerrors .Errorf ("decode option %q value: %w" , have .Name , err )
145
+ }
146
+ // Set the opt's value
147
+ opt .Value = (* os )[i ].Value
148
+ } else {
149
+ // Discard the value if it's nil.
150
+ opt .Value = DiscardValue
151
+ }
152
+ // Override the existing.
153
+ (* os )[i ] = opt
154
+ // Go to the next option to decode.
155
+ continue OptionSetDecodeLoop
156
+ }
157
+ }
158
+
159
+ // If the option doesn't exist, the value will be discarded.
160
+ // We do this because we cannot infer the type of the value.
161
+ opt .Value = DiscardValue
162
+ * os = append (* os , opt )
163
+ i ++
164
+ }
165
+
166
+ t , err = dec .Token ()
167
+ if err != nil {
168
+ return xerrors .Errorf ("read array close bracket: %w" , err )
169
+ }
170
+ if t != json .Delim (']' ) {
171
+ return xerrors .Errorf ("expected array close bracket, got %q" , t )
172
+ }
173
+
174
+ return nil
175
+ }
176
+
82
177
// Add adds the given Options to the OptionSet.
83
178
func (s * OptionSet ) Add (opts ... Option ) {
84
179
* s = append (* s , opts ... )
0 commit comments