-
Subcommands
-
Commands should register their own subcommands
Something along the lines of:
// cmd/bitswap.go bitswapCmd := &commands.Command{...} // a command that does bitswap stuff strategyCmd := &commands.Command{...} // a command that controls the bitswap strategy bitswapCmd.Register("strategy", strategyCmd) // add `strategy` as a subcommand of the bitswap cmd // cmd/ipfs.go ipfsCmd := &commands.Command{...} // root command ipfsCmd.Register("bitswap", bitswapCmd) // register the bitswap command as a subcommand
-
Names resolved on any transport naming scheme
Example:
- CLI:
ipfs bitswap strategy set
- HTTP:
POST /bitswap/strategy
- CLI:
-
-
Same options/args across transports
- Example:
- CLI:
ipfs beep [-f=foo] <boop> <bop>
- HTTP:
GET /beep?f=foo&args=boop,bop
- CLI:
- Register options by both short and long names (
ipfs add -c ./config
equivalent toipfs add --config=./config
)
- Example:
-
Allow transport-specific output (e.g. nicely formatted CLI text)
-
Zero or minimal boilerplate for RPC-call commands
- Most commands from command line simply send an RPC request, would be nice for these to be registered succinctly and/or automatically
-
Make it easy to provide command documentation
- Agnostic to type of documentation, e.g. CLI help text or HTML docs
- Prefer explicitly created output for optimal UX, e.g. hand-formatted strings
- Could support markdown, then we could have 1 copy of text for everywhere. Sidenote: We could make a cool markdown -> ANSI terminal output renderer for really nice looking CLI output.
-
Preferably let devs implement new commands in a single file
- Provides ease of maintenance, codebase simplicity
-
Make command output encoding-agnostic
- Encoding chosen by user (JSON, XML, protobuf, CSV, etc.)
- Specified in options, something like
GET /peers?enc=json
oripfs ls foo -e=xml
- Easy implementation: commands return a
interface{}
for easy marshalling via golang stdlib encoders. Commands can each create a struct to specify output format.
Options are values specified by the consumer (as command line flags, HTTP querystring params, etc.). We can do things like programmatically checking to make sure an option is supported, so we can warn the user if they used an unrecognized flag. By giving options a datatype, we can make sure transports unmarshal values correctly and error if the user provided the wrong type.
// Option is used to specify a field that will be provided by a consumer
type Option struct {
Long string // long name (e.g. "config")
Short string // short name (e.g. "c")
Type reflect.Kind // value must be this type
// (we could use our own consts instead of the ones from `reflect`)
Default interface{} // the default value
}
A request is a call to a command (e.g. a HTTP request, or a command line call). Commands can use them to read the options or arguments that were specified wihtout worrying about how they unmarshalled (the transport does that part).
// Request represents a call to a command from a consumer
type Request struct {
opts map[string]interface{}
args []interface{}
transport *Transport
}
cmd := commands.Command{
help: `
# ipfs
### global versioned p2p merkledag file system
### Basic commands:
* `add <path> Add an object to ipfs.`
* `cat <ref> Show ipfs object data.`
* `ls <ref> List links from an object.`
* `refs <ref> List link hashes from an object.`
### Tool commands:
* `config Manage configuration.`
* `version Show ipfs version information.`
...
`,
options: [
commands.Option{ "config", "c", commands.String, "~/.go-ipf/config" },
],
f: func(req Request) interface{}, error {
...
},
}
Forking is hard... But I want to make sure that flags registered to a command can be accessed by the subcommands.