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

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

13 14 15
	"github.com/ipfs/go-ipfs/commands/files"
	"github.com/ipfs/go-ipfs/core"
	"github.com/ipfs/go-ipfs/repo/config"
16
	u "gx/ipfs/QmZNVWh8LLjAavuQ2JXuFmuYH3C11xo988vSgp7UQrTRj1/go-ipfs-util"
Jeromy's avatar
Jeromy committed
17
	context "gx/ipfs/QmZy2y8t9zQH2a1b8q2ZSLKp17ATuJoCNxxyMFG5qFExpt/go-net/context"
18 19
)

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

22
type Context struct {
23
	Online     bool
24
	ConfigRoot string
25
	ReqLog     *ReqLog
26 27 28 29

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

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

// 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 {
40
			return nil, errors.New("nil LoadConfig function")
41 42 43 44 45 46 47
		}
		c.config, err = c.LoadConfig(c.ConfigRoot)
	}
	return c.config, err
}

// GetNode returns the node of the current Command exection
rht's avatar
rht committed
48
// context. It may construct it with the provided function.
49 50 51 52
func (c *Context) GetNode() (*core.IpfsNode, error) {
	var err error
	if c.node == nil {
		if c.ConstructNode == nil {
53
			return nil, errors.New("nil ConstructNode function")
54 55 56 57 58 59 60 61 62 63
		}
		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
64 65
}

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

86
	ConvertOptions() error
87 88 89
}

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

102 103
// Path returns the command path of this request
func (r *request) Path() []string {
104 105 106
	return r.path
}

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

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

123
	return &OptionValue{option.DefaultVal(), false, option}
Matt Bell's avatar
Matt Bell committed
124 125
}

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

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

	r.rctx = ctx
	return nil
}

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

162
	r.options[name] = val
163 164
}

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

171
// Arguments returns the arguments slice
172
func (r *request) Arguments() []string {
173 174 175 176 177 178 179 180 181 182
	if r.haveVarArgsFromStdin() {
		err := r.VarArgs(func(s string) error {
			r.arguments = append(r.arguments, s)
			return nil
		})
		if err != nil && err != io.EOF {
			log.Error(err)
		}
	}

Matt Bell's avatar
Matt Bell committed
183
	return r.arguments
184
}
Matt Bell's avatar
Matt Bell committed
185

186
func (r *request) SetArguments(args []string) {
187 188 189
	r.arguments = args
}

190
func (r *request) Files() files.File {
Matt Bell's avatar
Matt Bell committed
191 192 193
	return r.files
}

194
func (r *request) SetFiles(f files.File) {
195 196 197
	r.files = f
}

Jeromy's avatar
Jeromy committed
198 199 200 201
func (r *request) Context() context.Context {
	return r.rctx
}

202 203 204 205 206 207 208 209 210 211 212 213
func (r *request) haveVarArgsFromStdin() bool {
	// we expect varargs if we have a variadic required argument and no arguments to
	// fill it
	if len(r.cmd.Arguments) == 0 {
		return false
	}

	last := r.cmd.Arguments[len(r.cmd.Arguments)-1]
	return last.SupportsStdin && last.Type == ArgString &&
		len(r.arguments) < len(r.cmd.Arguments)
}

Jeromy's avatar
docs  
Jeromy committed
214 215
// VarArgs can be used when you want string arguments as input
// and also want to be able to handle them in a streaming fashion
Jeromy's avatar
Jeromy committed
216 217 218
func (r *request) VarArgs(f func(string) error) error {
	var i int
	for i = 0; i < len(r.cmd.Arguments); i++ {
219
		if r.cmd.Arguments[i].Variadic || r.cmd.Arguments[i].SupportsStdin {
Jeromy's avatar
Jeromy committed
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
			break
		}
	}

	args := r.arguments[i:]
	if len(args) > 0 {
		for _, arg := range args {
			err := f(arg)
			if err != nil {
				return err
			}
		}

		return nil
	} else {
235 236
		if r.files != nil {
			fi, err := r.files.NextFile()
Jeromy's avatar
Jeromy committed
237 238 239
			if err != nil {
				return err
			}
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254

			if fi.FileName() == "*stdin*" {
				fmt.Fprintln(os.Stderr, "ipfs: Reading from stdin; send Ctrl-d to stop.")
			}

			scan := bufio.NewScanner(fi)
			for scan.Scan() {
				err := f(scan.Text())
				if err != nil {
					return err
				}
			}
			return nil
		} else {
			return fmt.Errorf("expected more arguments from stdin")
Jeromy's avatar
Jeromy committed
255 256 257 258
		}
	}
}

Jeromy's avatar
Jeromy committed
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
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 {
282 283 284
	return &r.ctx
}

Jeromy's avatar
Jeromy committed
285
func (r *request) SetInvocContext(ctx Context) {
286 287 288
	r.ctx = ctx
}

289 290 291 292
func (r *request) Command() *Command {
	return r.cmd
}

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

295
var converters = map[reflect.Kind]converter{
Matt Bell's avatar
Matt Bell committed
296
	Bool: func(v string) (interface{}, error) {
297 298 299 300 301
		if v == "" {
			return true, nil
		}
		return strconv.ParseBool(v)
	},
Matt Bell's avatar
Matt Bell committed
302
	Int: func(v string) (interface{}, error) {
303 304 305 306 307
		val, err := strconv.ParseInt(v, 0, 32)
		if err != nil {
			return nil, err
		}
		return int(val), err
308
	},
Matt Bell's avatar
Matt Bell committed
309
	Uint: func(v string) (interface{}, error) {
310 311 312 313 314
		val, err := strconv.ParseUint(v, 0, 32)
		if err != nil {
			return nil, err
		}
		return int(val), err
315
	},
Matt Bell's avatar
Matt Bell committed
316
	Float: func(v string) (interface{}, error) {
317 318 319 320
		return strconv.ParseFloat(v, 64)
	},
}

321 322 323 324
func (r *request) Values() map[string]interface{} {
	return r.values
}

325 326 327 328
func (r *request) Stdin() io.Reader {
	return r.stdin
}

329
func (r *request) ConvertOptions() error {
330
	for k, v := range r.options {
331
		opt, ok := r.optionDefs[k]
332
		if !ok {
333
			continue
334 335 336
		}

		kind := reflect.TypeOf(v).Kind()
337
		if kind != opt.Type() {
338
			if kind == String {
339
				convert := converters[opt.Type()]
340 341
				str, ok := v.(string)
				if !ok {
342
					return u.ErrCast()
343 344
				}
				val, err := convert(str)
345
				if err != nil {
346 347 348 349 350
					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')",
351
						value, opt.Type().String(), k)
352
				}
353
				r.options[k] = val
354 355 356

			} else {
				return fmt.Errorf("Option '%s' should be type '%s', but got type '%s'",
357
					k, opt.Type().String(), kind.String())
358 359
			}
		} else {
360
			r.options[k] = v
361 362
		}

363
		for _, name := range opt.Names() {
364 365 366 367 368 369 370 371 372 373
			if _, ok := r.options[name]; name != k && ok {
				return fmt.Errorf("Duplicate command options were provided ('%s' and '%s')",
					k, name)
			}
		}
	}

	return nil
}

374
// NewEmptyRequest initializes an empty request
375
func NewEmptyRequest() (Request, error) {
Matt Bell's avatar
Matt Bell committed
376
	return NewRequest(nil, nil, nil, nil, nil, nil)
Matt Bell's avatar
Matt Bell committed
377 378
}

379
// NewRequest returns a request initialized with given arguments
380
// An non-nil error will be returned if the provided option values are invalid
Henry's avatar
Henry committed
381
func NewRequest(path []string, opts OptMap, args []string, file files.File, cmd *Command, optDefs map[string]Option) (Request, error) {
382
	if opts == nil {
Henry's avatar
Henry committed
383
		opts = make(OptMap)
384
	}
385 386 387
	if optDefs == nil {
		optDefs = make(map[string]Option)
	}
388

Jeromy's avatar
Jeromy committed
389
	ctx := Context{}
390
	values := make(map[string]interface{})
Jeromy's avatar
Jeromy committed
391 392 393 394 395 396 397 398 399 400 401
	req := &request{
		path:       path,
		options:    opts,
		arguments:  args,
		files:      file,
		cmd:        cmd,
		ctx:        ctx,
		optionDefs: optDefs,
		values:     values,
		stdin:      os.Stdin,
	}
402 403 404 405
	err := req.ConvertOptions()
	if err != nil {
		return nil, err
	}
406

407
	return req, nil
Matt Bell's avatar
Matt Bell committed
408
}