request.go 8.72 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
	if r.files == nil {
234 235
		log.Warning("expected more arguments from stdin")
		return nil
Jeromy's avatar
cleanup  
Jeromy committed
236
	}
237

Jeromy's avatar
cleanup  
Jeromy committed
238 239 240 241 242 243 244 245 246 247
	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
248 249
		}
	}
Jeromy's avatar
cleanup  
Jeromy committed
250
	return nil
Jeromy's avatar
Jeromy committed
251 252
}

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

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

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

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

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

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

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

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

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

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

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

	return nil
}

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

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

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

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