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

3
import (
4
	"errors"
5
	"fmt"
6 7
	"io"
	"os"
8 9
	"reflect"
	"strconv"
Jeromy's avatar
Jeromy committed
10
	"time"
11

12 13 14 15 16
	context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
	"github.com/ipfs/go-ipfs/commands/files"
	"github.com/ipfs/go-ipfs/core"
	"github.com/ipfs/go-ipfs/repo/config"
	u "github.com/ipfs/go-ipfs/util"
17 18
)

Henry's avatar
Henry committed
19
type OptMap map[string]interface{}
20

21
type Context struct {
22
	Online     bool
23
	ConfigRoot string
24 25 26 27

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

28 29
	node          *core.IpfsNode
	ConstructNode func() (*core.IpfsNode, error)
30 31 32 33 34 35 36 37
}

// 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 {
38
			return nil, errors.New("nil LoadConfig function")
39 40 41 42 43 44 45 46 47 48 49 50
		}
		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 {
51
			return nil, errors.New("nil ConstructNode function")
52 53 54 55 56 57 58 59 60 61
		}
		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
62 63
}

Matt Bell's avatar
Matt Bell committed
64
// Request represents a call to a command from a consumer
65 66
type Request interface {
	Path() []string
67
	Option(name string) *OptionValue
Henry's avatar
Henry committed
68
	Options() OptMap
69
	SetOption(name string, val interface{})
Henry's avatar
Henry committed
70
	SetOptions(opts OptMap) error
71 72
	Arguments() []string
	SetArguments([]string)
73 74
	Files() files.File
	SetFiles(files.File)
Jeromy's avatar
Jeromy committed
75 76 77 78
	Context() context.Context
	SetRootContext(context.Context) error
	InvocContext() *Context
	SetInvocContext(Context)
79
	Command() *Command
80
	Values() map[string]interface{}
81
	Stdin() io.Reader
82

83
	ConvertOptions() error
84 85 86
}

type request struct {
87
	path       []string
Henry's avatar
Henry committed
88
	options    OptMap
89
	arguments  []string
90
	files      files.File
91 92
	cmd        *Command
	ctx        Context
Jeromy's avatar
Jeromy committed
93
	rctx       context.Context
94
	optionDefs map[string]Option
95
	values     map[string]interface{}
96
	stdin      io.Reader
Matt Bell's avatar
Matt Bell committed
97 98
}

99 100
// Path returns the command path of this request
func (r *request) Path() []string {
101 102 103
	return r.path
}

104
// Option returns the value of the option for given name.
105
func (r *request) Option(name string) *OptionValue {
106 107
	// find the option with the specified name
	option, found := r.optionDefs[name]
108 109 110 111 112
	if !found {
		return nil
	}

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

120
	// MAYBE_TODO: use default value instead of nil
121
	return &OptionValue{nil, false, option}
Matt Bell's avatar
Matt Bell committed
122 123
}

124
// Options returns a copy of the option map
Henry's avatar
Henry committed
125 126
func (r *request) Options() OptMap {
	output := make(OptMap)
127 128 129 130 131 132
	for k, v := range r.options {
		output[k] = v
	}
	return output
}

Jeromy's avatar
Jeromy committed
133 134 135 136 137 138 139 140 141 142
func (r *request) SetRootContext(ctx context.Context) error {
	ctx, err := getContext(ctx, r)
	if err != nil {
		return err
	}

	r.rctx = ctx
	return nil
}

143 144
// SetOption sets the value of the option for given name.
func (r *request) SetOption(name string, val interface{}) {
145 146 147 148 149 150 151
	// 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
152
	for _, n := range option.Names() {
153
		_, found := r.options[n]
154 155 156 157 158 159
		if found {
			r.options[n] = val
			return
		}
	}

160
	r.options[name] = val
161 162
}

Matt Bell's avatar
Matt Bell committed
163
// SetOptions sets the option values, unsetting any values that were previously set
Henry's avatar
Henry committed
164
func (r *request) SetOptions(opts OptMap) error {
Matt Bell's avatar
Matt Bell committed
165 166 167 168
	r.options = opts
	return r.ConvertOptions()
}

169
// Arguments returns the arguments slice
170
func (r *request) Arguments() []string {
Matt Bell's avatar
Matt Bell committed
171
	return r.arguments
172
}
Matt Bell's avatar
Matt Bell committed
173

174
func (r *request) SetArguments(args []string) {
175 176 177
	r.arguments = args
}

178
func (r *request) Files() files.File {
Matt Bell's avatar
Matt Bell committed
179 180 181
	return r.files
}

182
func (r *request) SetFiles(f files.File) {
183 184 185
	r.files = f
}

Jeromy's avatar
Jeromy committed
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
func (r *request) Context() context.Context {
	return r.rctx
}

func getContext(base context.Context, req Request) (context.Context, error) {
	tout, found, err := req.Option("timeout").String()
	if err != nil {
		return nil, fmt.Errorf("error parsing timeout option: %s", err)
	}

	var ctx context.Context
	if found {
		duration, err := time.ParseDuration(tout)
		if err != nil {
			return nil, fmt.Errorf("error parsing timeout option: %s", err)
		}

		tctx, _ := context.WithTimeout(base, duration)
		ctx = tctx
	} else {
		cctx, _ := context.WithCancel(base)
		ctx = cctx
	}
	return ctx, nil
}

func (r *request) InvocContext() *Context {
213 214 215
	return &r.ctx
}

Jeromy's avatar
Jeromy committed
216
func (r *request) SetInvocContext(ctx Context) {
217 218 219
	r.ctx = ctx
}

220 221 222 223
func (r *request) Command() *Command {
	return r.cmd
}

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

226
var converters = map[reflect.Kind]converter{
Matt Bell's avatar
Matt Bell committed
227
	Bool: func(v string) (interface{}, error) {
228 229 230 231 232
		if v == "" {
			return true, nil
		}
		return strconv.ParseBool(v)
	},
Matt Bell's avatar
Matt Bell committed
233
	Int: func(v string) (interface{}, error) {
234 235 236 237 238
		val, err := strconv.ParseInt(v, 0, 32)
		if err != nil {
			return nil, err
		}
		return int(val), err
239
	},
Matt Bell's avatar
Matt Bell committed
240
	Uint: func(v string) (interface{}, error) {
241 242 243 244 245
		val, err := strconv.ParseUint(v, 0, 32)
		if err != nil {
			return nil, err
		}
		return int(val), err
246
	},
Matt Bell's avatar
Matt Bell committed
247
	Float: func(v string) (interface{}, error) {
248 249 250 251
		return strconv.ParseFloat(v, 64)
	},
}

252 253 254 255
func (r *request) Values() map[string]interface{} {
	return r.values
}

256 257 258 259
func (r *request) Stdin() io.Reader {
	return r.stdin
}

260
func (r *request) ConvertOptions() error {
261
	for k, v := range r.options {
262
		opt, ok := r.optionDefs[k]
263
		if !ok {
264
			continue
265 266 267
		}

		kind := reflect.TypeOf(v).Kind()
268
		if kind != opt.Type() {
269
			if kind == String {
270
				convert := converters[opt.Type()]
271 272
				str, ok := v.(string)
				if !ok {
273
					return u.ErrCast()
274 275
				}
				val, err := convert(str)
276
				if err != nil {
277 278 279 280 281
					value := fmt.Sprintf("value '%v'", v)
					if len(str) == 0 {
						value = "empty value"
					}
					return fmt.Errorf("Could not convert %s to type '%s' (for option '-%s')",
282
						value, opt.Type().String(), k)
283
				}
284
				r.options[k] = val
285 286 287

			} else {
				return fmt.Errorf("Option '%s' should be type '%s', but got type '%s'",
288
					k, opt.Type().String(), kind.String())
289 290
			}
		} else {
291
			r.options[k] = v
292 293
		}

294
		for _, name := range opt.Names() {
295 296 297 298 299 300 301 302 303 304
			if _, ok := r.options[name]; name != k && ok {
				return fmt.Errorf("Duplicate command options were provided ('%s' and '%s')",
					k, name)
			}
		}
	}

	return nil
}

305
// NewEmptyRequest initializes an empty request
306
func NewEmptyRequest() (Request, error) {
Matt Bell's avatar
Matt Bell committed
307
	return NewRequest(nil, nil, nil, nil, nil, nil)
Matt Bell's avatar
Matt Bell committed
308 309
}

310
// NewRequest returns a request initialized with given arguments
311
// An non-nil error will be returned if the provided option values are invalid
Henry's avatar
Henry committed
312
func NewRequest(path []string, opts OptMap, args []string, file files.File, cmd *Command, optDefs map[string]Option) (Request, error) {
313
	if opts == nil {
Henry's avatar
Henry committed
314
		opts = make(OptMap)
315
	}
316 317 318
	if optDefs == nil {
		optDefs = make(map[string]Option)
	}
319

Jeromy's avatar
Jeromy committed
320
	ctx := Context{}
321
	values := make(map[string]interface{})
Jeromy's avatar
Jeromy committed
322 323 324 325 326 327 328 329 330 331 332
	req := &request{
		path:       path,
		options:    opts,
		arguments:  args,
		files:      file,
		cmd:        cmd,
		ctx:        ctx,
		optionDefs: optDefs,
		values:     values,
		stdin:      os.Stdin,
	}
333 334 335 336
	err := req.ConvertOptions()
	if err != nil {
		return nil, err
	}
337

338
	return req, nil
Matt Bell's avatar
Matt Bell committed
339
}