commands.go 4.28 KB
Newer Older
1
// Package commands implements the ipfs command interface
2 3 4 5
//
// Using github.com/ipfs/go-ipfs/commands to define the command line and HTTP
// APIs.  This is the interface available to folks using IPFS from outside of
// the Go language.
6 7 8
package commands

import (
Jan Winkelmann's avatar
Jan Winkelmann committed
9
	"fmt"
10
	"io"
11
	"os"
12
	"sort"
13
	"strings"
14

Jan Winkelmann's avatar
Jan Winkelmann committed
15 16
	e "github.com/ipfs/go-ipfs/core/commands/e"

17
	"gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit"
Steven Allen's avatar
Steven Allen committed
18
	cmds "gx/ipfs/QmXTmUCBtDUrzDYVzASogLiNph7EBuYqEgPL7QoHNMzUnz/go-ipfs-cmds"
19 20
)

Jan Winkelmann's avatar
Jan Winkelmann committed
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
type commandEncoder struct {
	w io.Writer
}

func (e *commandEncoder) Encode(v interface{}) error {
	var (
		cmd *Command
		ok  bool
	)

	if cmd, ok = v.(*Command); !ok {
		return fmt.Errorf(`core/commands: uenxpected type %T, expected *"core/commands".Command`, v)
	}

	for _, s := range cmdPathStrings(cmd, cmd.showOpts) {
		_, err := e.w.Write([]byte(s + "\n"))
		if err != nil {
			return err
		}
	}

	return nil
}

45 46 47
type Command struct {
	Name        string
	Subcommands []Command
48
	Options     []Option
Jan Winkelmann's avatar
Jan Winkelmann committed
49 50

	showOpts bool
51 52 53 54
}

type Option struct {
	Names []string
55 56
}

57
const (
58
	flagsOptionName = "flags"
59 60
)

61 62 63 64
// CommandsCmd takes in a root command,
// and returns a command that lists the subcommands in that root
func CommandsCmd(root *cmds.Command) *cmds.Command {
	return &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
65
		Helptext: cmdkit.HelpText{
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
66 67 68
			Tagline:          "List all available commands.",
			ShortDescription: `Lists all available commands (and subcommands) and exits.`,
		},
Jan Winkelmann's avatar
Jan Winkelmann committed
69
		Options: []cmdkit.Option{
70
			cmdkit.BoolOption(flagsOptionName, "f", "Show command flags"),
71
		},
keks's avatar
keks committed
72
		Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
73
			rootCmd := cmd2outputCmd("ipfs", root)
74
			rootCmd.showOpts, _ = req.Options[flagsOptionName].(bool)
keks's avatar
keks committed
75
			return cmds.EmitOnce(res, &rootCmd)
76
		},
Jan Winkelmann's avatar
Jan Winkelmann committed
77
		Encoders: cmds.EncoderMap{
78
			cmds.Text: func(req *cmds.Request) func(io.Writer) cmds.Encoder {
Jan Winkelmann's avatar
Jan Winkelmann committed
79
				return func(w io.Writer) cmds.Encoder { return &commandEncoder{w} }
80
			},
81
		},
82
		Type: Command{},
83
	}
84 85
}

86 87 88 89 90 91
func cmd2outputCmd(name string, cmd *cmds.Command) Command {
	opts := make([]Option, len(cmd.Options))
	for i, opt := range cmd.Options {
		opts[i] = Option{opt.Names()}
	}

92 93
	output := Command{
		Name:        name,
94
		Subcommands: make([]Command, 0, len(cmd.Subcommands)),
95
		Options:     opts,
96 97
	}

Jan Winkelmann's avatar
Jan Winkelmann committed
98
	for name, sub := range cmd.Subcommands {
99
		output.Subcommands = append(output.Subcommands, cmd2outputCmd(name, sub))
Jan Winkelmann's avatar
Jan Winkelmann committed
100 101
	}

102 103 104
	return output
}

105
func cmdPathStrings(cmd *Command, showOptions bool) []string {
106
	var cmds []string
107

108 109
	var recurse func(prefix string, cmd *Command)
	recurse = func(prefix string, cmd *Command) {
rht's avatar
rht committed
110 111
		newPrefix := prefix + cmd.Name
		cmds = append(cmds, newPrefix)
112 113
		if prefix != "" && showOptions {
			for _, options := range cmd.Options {
114
				var cmdOpts []string
115
				for _, flag := range options.Names {
116 117 118 119 120 121
					if len(flag) == 1 {
						flag = "-" + flag
					} else {
						flag = "--" + flag
					}
					cmdOpts = append(cmdOpts, newPrefix+" "+flag)
rht's avatar
rht committed
122
				}
123
				cmds = append(cmds, strings.Join(cmdOpts, " / "))
rht's avatar
rht committed
124 125
			}
		}
126
		for _, sub := range cmd.Subcommands {
rht's avatar
rht committed
127
			recurse(newPrefix+" ", &sub)
128
		}
129 130
	}

131 132 133
	recurse("", cmd)
	sort.Sort(sort.StringSlice(cmds))
	return cmds
134
}
Jan Winkelmann's avatar
Jan Winkelmann committed
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152

// changes here will also need to be applied at
// - ./dag/dag.go
// - ./object/object.go
// - ./files/files.go
// - ./unixfs/unixfs.go
func unwrapOutput(i interface{}) (interface{}, error) {
	var (
		ch <-chan interface{}
		ok bool
	)

	if ch, ok = i.(<-chan interface{}); !ok {
		return nil, e.TypeErr(ch, i)
	}

	return <-ch, nil
}
153 154 155

type nonFatalError string

Kevin Atkinson's avatar
Kevin Atkinson committed
156 157 158 159
// streamResults is a helper function to stream results that possibly
// contain non-fatal errors.  The helper function is allowed to panic
// on internal errors.
func streamResults(procVal func(interface{}, io.Writer) nonFatalError) func(cmds.Response, cmds.ResponseEmitter) error {
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
	return func(res cmds.Response, re cmds.ResponseEmitter) (err error) {
		defer func() {
			if r := recover(); r != nil {
				err = fmt.Errorf("internal error: %v", r)
			}
			re.Close()
		}()

		var errors bool
		for {
			v, err := res.Next()
			if err != nil {
				if err == io.EOF {
					break
				}
				return err
			}

			errorMsg := procVal(v, os.Stdout)

			if errorMsg != "" {
				errors = true
				fmt.Fprintf(os.Stderr, "%s\n", errorMsg)
			}
		}

		if errors {
			return fmt.Errorf("errors while displaying some entries")
		}
		return nil
	}
}