request.go 7.93 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 {
Matt Bell's avatar
Matt Bell committed
173
	return r.arguments
174
}
Matt Bell's avatar
Matt Bell committed
175

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

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

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

Jeromy's avatar
Jeromy committed
188 189 190 191
func (r *request) Context() context.Context {
	return r.rctx
}

Jeromy's avatar
Jeromy committed
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
func (r *request) VarArgs(f func(string) error) error {
	var i int
	for i = 0; i < len(r.cmd.Arguments); i++ {
		if r.cmd.Arguments[i].Variadic {
			break
		}
	}

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

		return nil
	} else {
		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
			}
		}
		return nil
	}
}

Jeromy's avatar
Jeromy committed
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
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 {
250 251 252
	return &r.ctx
}

Jeromy's avatar
Jeromy committed
253
func (r *request) SetInvocContext(ctx Context) {
254 255 256
	r.ctx = ctx
}

257 258 259 260
func (r *request) Command() *Command {
	return r.cmd
}

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

263
var converters = map[reflect.Kind]converter{
Matt Bell's avatar
Matt Bell committed
264
	Bool: func(v string) (interface{}, error) {
265 266 267 268 269
		if v == "" {
			return true, nil
		}
		return strconv.ParseBool(v)
	},
Matt Bell's avatar
Matt Bell committed
270
	Int: func(v string) (interface{}, error) {
271 272 273 274 275
		val, err := strconv.ParseInt(v, 0, 32)
		if err != nil {
			return nil, err
		}
		return int(val), err
276
	},
Matt Bell's avatar
Matt Bell committed
277
	Uint: func(v string) (interface{}, error) {
278 279 280 281 282
		val, err := strconv.ParseUint(v, 0, 32)
		if err != nil {
			return nil, err
		}
		return int(val), err
283
	},
Matt Bell's avatar
Matt Bell committed
284
	Float: func(v string) (interface{}, error) {
285 286 287 288
		return strconv.ParseFloat(v, 64)
	},
}

289 290 291 292
func (r *request) Values() map[string]interface{} {
	return r.values
}

293 294 295 296
func (r *request) Stdin() io.Reader {
	return r.stdin
}

297
func (r *request) ConvertOptions() error {
298
	for k, v := range r.options {
299
		opt, ok := r.optionDefs[k]
300
		if !ok {
301
			continue
302 303 304
		}

		kind := reflect.TypeOf(v).Kind()
305
		if kind != opt.Type() {
306
			if kind == String {
307
				convert := converters[opt.Type()]
308 309
				str, ok := v.(string)
				if !ok {
310
					return u.ErrCast()
311 312
				}
				val, err := convert(str)
313
				if err != nil {
314 315 316 317 318
					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')",
319
						value, opt.Type().String(), k)
320
				}
321
				r.options[k] = val
322 323 324

			} else {
				return fmt.Errorf("Option '%s' should be type '%s', but got type '%s'",
325
					k, opt.Type().String(), kind.String())
326 327
			}
		} else {
328
			r.options[k] = v
329 330
		}

331
		for _, name := range opt.Names() {
332 333 334 335 336 337 338 339 340 341
			if _, ok := r.options[name]; name != k && ok {
				return fmt.Errorf("Duplicate command options were provided ('%s' and '%s')",
					k, name)
			}
		}
	}

	return nil
}

342
// NewEmptyRequest initializes an empty request
343
func NewEmptyRequest() (Request, error) {
Matt Bell's avatar
Matt Bell committed
344
	return NewRequest(nil, nil, nil, nil, nil, nil)
Matt Bell's avatar
Matt Bell committed
345 346
}

347
// NewRequest returns a request initialized with given arguments
348
// An non-nil error will be returned if the provided option values are invalid
Henry's avatar
Henry committed
349
func NewRequest(path []string, opts OptMap, args []string, file files.File, cmd *Command, optDefs map[string]Option) (Request, error) {
350
	if opts == nil {
Henry's avatar
Henry committed
351
		opts = make(OptMap)
352
	}
353 354 355
	if optDefs == nil {
		optDefs = make(map[string]Option)
	}
356

Jeromy's avatar
Jeromy committed
357
	ctx := Context{}
358
	values := make(map[string]interface{})
Jeromy's avatar
Jeromy committed
359 360 361 362 363 364 365 366 367 368 369
	req := &request{
		path:       path,
		options:    opts,
		arguments:  args,
		files:      file,
		cmd:        cmd,
		ctx:        ctx,
		optionDefs: optDefs,
		values:     values,
		stdin:      os.Stdin,
	}
370 371 372 373
	err := req.ConvertOptions()
	if err != nil {
		return nil, err
	}
374

375
	return req, nil
Matt Bell's avatar
Matt Bell committed
376
}