request.go 5.9 KB
Newer Older
Matt Bell's avatar
Matt Bell committed
1 2
package commands

3 4
import (
	"fmt"
5
	"io"
6 7
	"reflect"
	"strconv"
8 9 10

	"github.com/jbenet/go-ipfs/config"
	"github.com/jbenet/go-ipfs/core"
11
	u "github.com/jbenet/go-ipfs/util"
12 13
)

14 15
type optMap map[string]interface{}

16 17
type Context struct {
	ConfigRoot string
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

	config     *config.Config
	LoadConfig func(path string) (*config.Config, error)

	node          *core.IpfsNode
	ConstructNode func() (*core.IpfsNode, error)
}

// GetConfig returns the config of the current Command exection
// context. It may load it with the providied function.
func (c *Context) GetConfig() (*config.Config, error) {
	var err error
	if c.config == nil {
		if c.LoadConfig == nil {
			panic("nil LoadConfig function")
		}
		c.config, err = c.LoadConfig(c.ConfigRoot)
	}
	return c.config, err
}

// GetNode returns the node of the current Command exection
// context. It may construct it with the providied function.
func (c *Context) GetNode() (*core.IpfsNode, error) {
	var err error
	if c.node == nil {
		if c.ConstructNode == nil {
			panic("nil ConstructNode function")
		}
		c.node, err = c.ConstructNode()
	}
	return c.node, err
}

// NodeWithoutConstructing returns the underlying node variable
// so that clients may close it.
func (c *Context) NodeWithoutConstructing() *core.IpfsNode {
	return c.node
56 57
}

Matt Bell's avatar
Matt Bell committed
58
// Request represents a call to a command from a consumer
59 60
type Request interface {
	Path() []string
61 62
	Option(name string) *OptionValue
	Options() optMap
63
	SetOption(name string, val interface{})
Brian Tiger Chow's avatar
Brian Tiger Chow committed
64 65 66 67 68 69

	// Arguments() returns user provided arguments as declared on the Command.
	//
	// NB: `io.Reader`s returned by Arguments() are owned by the library.
	// Readers are not guaranteed to remain open after the Command's Run
	// function returns.
70
	Arguments() []interface{} // TODO: make argument value type instead of using interface{}
71
	Context() *Context
72
	SetContext(Context)
73
	Command() *Command
74
	Cleanup() error
75

76
	ConvertOptions() error
77 78 79
}

type request struct {
80 81 82 83 84 85
	path       []string
	options    optMap
	arguments  []interface{}
	cmd        *Command
	ctx        Context
	optionDefs map[string]Option
Matt Bell's avatar
Matt Bell committed
86 87
}

88 89
// Path returns the command path of this request
func (r *request) Path() []string {
90 91 92
	return r.path
}

93
// Option returns the value of the option for given name.
94
func (r *request) Option(name string) *OptionValue {
95 96
	val, found := r.options[name]
	if found {
97
		return &OptionValue{val, found}
98 99 100 101 102 103
	}

	// if a value isn't defined for that name, we will try to look it up by its aliases

	// find the option with the specified name
	option, found := r.optionDefs[name]
104 105 106 107 108 109 110 111 112
	if !found {
		return nil
	}

	// try all the possible names, break if we find a value
	for _, n := range option.Names {
		val, found = r.options[n]
		if found {
			return &OptionValue{val, found}
113 114 115
		}
	}

116 117
	// MAYBE_TODO: use default value instead of nil
	return &OptionValue{nil, false}
Matt Bell's avatar
Matt Bell committed
118 119
}

120
// Options returns a copy of the option map
121
func (r *request) Options() optMap {
122 123 124 125 126 127 128
	output := make(optMap)
	for k, v := range r.options {
		output[k] = v
	}
	return output
}

129 130
// SetOption sets the value of the option for given name.
func (r *request) SetOption(name string, val interface{}) {
131 132 133 134 135 136 137 138
	// find the option with the specified name
	option, found := r.optionDefs[name]
	if !found {
		return
	}

	// try all the possible names, if we already have a value then set over it
	for _, n := range option.Names {
139
		_, found := r.options[n]
140 141 142 143 144 145
		if found {
			r.options[n] = val
			return
		}
	}

146
	r.options[name] = val
147 148
}

149
// Arguments returns the arguments slice
150
func (r *request) Arguments() []interface{} {
Matt Bell's avatar
Matt Bell committed
151
	return r.arguments
152
}
Matt Bell's avatar
Matt Bell committed
153

154 155 156 157
func (r *request) Context() *Context {
	return &r.ctx
}

158 159 160 161
func (r *request) SetContext(ctx Context) {
	r.ctx = ctx
}

162 163 164 165
func (r *request) Command() *Command {
	return r.cmd
}

166 167 168 169 170 171 172 173 174 175 176 177 178 179
func (r *request) Cleanup() error {
	for _, arg := range r.arguments {
		closer, ok := arg.(io.Closer)
		if ok {
			err := closer.Close()
			if err != nil {
				return err
			}
		}
	}

	return nil
}

Matt Bell's avatar
Matt Bell committed
180 181
type converter func(string) (interface{}, error)

182
var converters = map[reflect.Kind]converter{
Matt Bell's avatar
Matt Bell committed
183
	Bool: func(v string) (interface{}, error) {
184 185 186 187 188
		if v == "" {
			return true, nil
		}
		return strconv.ParseBool(v)
	},
Matt Bell's avatar
Matt Bell committed
189
	Int: func(v string) (interface{}, error) {
190 191 192 193 194
		val, err := strconv.ParseInt(v, 0, 32)
		if err != nil {
			return nil, err
		}
		return int(val), err
195
	},
Matt Bell's avatar
Matt Bell committed
196
	Uint: func(v string) (interface{}, error) {
197 198 199 200 201
		val, err := strconv.ParseUint(v, 0, 32)
		if err != nil {
			return nil, err
		}
		return int(val), err
202
	},
Matt Bell's avatar
Matt Bell committed
203
	Float: func(v string) (interface{}, error) {
204 205 206 207
		return strconv.ParseFloat(v, 64)
	},
}

208
func (r *request) ConvertOptions() error {
209
	for k, v := range r.options {
210
		opt, ok := r.optionDefs[k]
211
		if !ok {
212
			continue
213 214 215 216 217 218
		}

		kind := reflect.TypeOf(v).Kind()
		if kind != opt.Type {
			if kind == String {
				convert := converters[opt.Type]
219 220
				str, ok := v.(string)
				if !ok {
221
					return u.ErrCast()
222 223
				}
				val, err := convert(str)
224 225 226 227
				if err != nil {
					return fmt.Errorf("Could not convert string value '%s' to type '%s'",
						v, opt.Type.String())
				}
228
				r.options[k] = val
229 230 231 232 233 234

			} else {
				return fmt.Errorf("Option '%s' should be type '%s', but got type '%s'",
					k, opt.Type.String(), kind.String())
			}
		} else {
235
			r.options[k] = v
236 237 238 239 240 241 242 243 244 245 246 247 248
		}

		for _, name := range opt.Names {
			if _, ok := r.options[name]; name != k && ok {
				return fmt.Errorf("Duplicate command options were provided ('%s' and '%s')",
					k, name)
			}
		}
	}

	return nil
}

249 250
// NewEmptyRequest initializes an empty request
func NewEmptyRequest() Request {
251
	return NewRequest(nil, nil, nil, nil, nil)
Matt Bell's avatar
Matt Bell committed
252 253
}

254
// NewRequest returns a request initialized with given arguments
255
func NewRequest(path []string, opts optMap, args []interface{}, cmd *Command, optDefs map[string]Option) Request {
256
	if path == nil {
Matt Bell's avatar
Matt Bell committed
257
		path = make([]string, 0)
Matt Bell's avatar
Matt Bell committed
258
	}
259 260 261 262
	if opts == nil {
		opts = make(map[string]interface{})
	}
	if args == nil {
263
		args = make([]interface{}, 0)
264
	}
265 266 267
	if optDefs == nil {
		optDefs = make(map[string]Option)
	}
268 269 270 271 272

	req := &request{path, opts, args, cmd, Context{}, optDefs}
	req.ConvertOptions()

	return req
Matt Bell's avatar
Matt Bell committed
273
}