request.go 8.71 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
	Arguments() []string
74
	StringArguments() []string
75
	SetArguments([]string)
76 77
	Files() files.File
	SetFiles(files.File)
Jeromy's avatar
Jeromy committed
78 79 80 81
	Context() context.Context
	SetRootContext(context.Context) error
	InvocContext() *Context
	SetInvocContext(Context)
82
	Command() *Command
83
	Values() map[string]interface{}
84
	Stdin() io.Reader
Jeromy's avatar
Jeromy committed
85
	VarArgs(func(string) error) error
86

87
	ConvertOptions() error
88 89 90
}

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

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

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

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

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

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

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

	r.rctx = ctx
	return nil
}

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

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

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

172 173 174 175
func (r *request) StringArguments() []string {
	return r.arguments
}

176
// Arguments returns the arguments slice
177
func (r *request) Arguments() []string {
178 179 180 181 182 183 184 185 186 187
	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
188
	return r.arguments
189
}
Matt Bell's avatar
Matt Bell committed
190

191
func (r *request) SetArguments(args []string) {
192 193 194
	r.arguments = args
}

195
func (r *request) Files() files.File {
Matt Bell's avatar
Matt Bell committed
196 197 198
	return r.files
}

199
func (r *request) SetFiles(f files.File) {
200 201 202
	r.files = f
}

Jeromy's avatar
Jeromy committed
203 204 205 206
func (r *request) Context() context.Context {
	return r.rctx
}

207
func (r *request) haveVarArgsFromStdin() bool {
Jeromy's avatar
cleanup  
Jeromy committed
208 209
	// we expect varargs if we have a string argument that supports stdin
	// and not arguments to satisfy it
210 211 212 213 214
	if len(r.cmd.Arguments) == 0 {
		return false
	}

	last := r.cmd.Arguments[len(r.cmd.Arguments)-1]
215
	return last.SupportsStdin && last.Type == ArgString && (last.Required || last.Variadic) &&
216 217 218
		len(r.arguments) < len(r.cmd.Arguments)
}

Jeromy's avatar
docs  
Jeromy committed
219 220
// 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
221
func (r *request) VarArgs(f func(string) error) error {
Jeromy's avatar
cleanup  
Jeromy committed
222 223
	if len(r.arguments) >= len(r.cmd.Arguments) {
		for _, arg := range r.arguments[len(r.cmd.Arguments)-1:] {
Jeromy's avatar
Jeromy committed
224 225 226 227 228 229 230
			err := f(arg)
			if err != nil {
				return err
			}
		}

		return nil
Jeromy's avatar
cleanup  
Jeromy committed
231
	}
232

Jeromy's avatar
cleanup  
Jeromy committed
233 234 235
	if r.files == nil {
		return fmt.Errorf("expected more arguments from stdin")
	}
236

Jeromy's avatar
cleanup  
Jeromy committed
237 238 239 240 241 242 243 244 245 246
	fi, err := r.files.NextFile()
	if err != nil {
		return err
	}

	scan := bufio.NewScanner(fi)
	for scan.Scan() {
		err := f(scan.Text())
		if err != nil {
			return err
Jeromy's avatar
Jeromy committed
247 248
		}
	}
Jeromy's avatar
cleanup  
Jeromy committed
249
	return nil
Jeromy's avatar
Jeromy committed
250 251
}

Jeromy's avatar
Jeromy committed
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
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 {
275 276 277
	return &r.ctx
}

Jeromy's avatar
Jeromy committed
278
func (r *request) SetInvocContext(ctx Context) {
279 280 281
	r.ctx = ctx
}

282 283 284 285
func (r *request) Command() *Command {
	return r.cmd
}

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

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

314 315 316 317
func (r *request) Values() map[string]interface{} {
	return r.values
}

318 319 320 321
func (r *request) Stdin() io.Reader {
	return r.stdin
}

322
func (r *request) ConvertOptions() error {
323
	for k, v := range r.options {
324
		opt, ok := r.optionDefs[k]
325
		if !ok {
326
			continue
327 328 329
		}

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

			} else {
				return fmt.Errorf("Option '%s' should be type '%s', but got type '%s'",
350
					k, opt.Type().String(), kind.String())
351 352
			}
		} else {
353
			r.options[k] = v
354 355
		}

356
		for _, name := range opt.Names() {
357 358 359 360 361 362 363 364 365 366
			if _, ok := r.options[name]; name != k && ok {
				return fmt.Errorf("Duplicate command options were provided ('%s' and '%s')",
					k, name)
			}
		}
	}

	return nil
}

367
// NewEmptyRequest initializes an empty request
368
func NewEmptyRequest() (Request, error) {
Matt Bell's avatar
Matt Bell committed
369
	return NewRequest(nil, nil, nil, nil, nil, nil)
Matt Bell's avatar
Matt Bell committed
370 371
}

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

Jeromy's avatar
Jeromy committed
382
	ctx := Context{}
383
	values := make(map[string]interface{})
Jeromy's avatar
Jeromy committed
384 385 386 387 388 389 390 391 392 393 394
	req := &request{
		path:       path,
		options:    opts,
		arguments:  args,
		files:      file,
		cmd:        cmd,
		ctx:        ctx,
		optionDefs: optDefs,
		values:     values,
		stdin:      os.Stdin,
	}
395 396 397 398
	err := req.ConvertOptions()
	if err != nil {
		return nil, err
	}
399

400
	return req, nil
Matt Bell's avatar
Matt Bell committed
401
}