feat(tour) show list of topics when user tries to view topic that doesn't exist
eg. ``` ipfs2 (cmd-ref-part2) λ. go build ./...; ./ipfs2 tour 0 Tour 0 - Hello Mars Hello Mars ipfs2 (cmd-ref-part2) λ. go build ./...; ./ipfs2 tour 10 ERROR no topic with id: 10 TOPICS 0 - Hello Mars 0.1 - Hello Mars 2 ```
Brian Tiger Chow committed
Nov 13, 2014 at 15:20 UTC
5c4fa5a7834e4742b4d8f42732200ab1738a64db
2 files changed
+142
-26
cmd/ipfs2/tour.go
+93
-24
@@ -11,8 +11,12 @@ import (
11
config "github.com/jbenet/go-ipfs/config"
12
internal "github.com/jbenet/go-ipfs/core/commands2/internal"
13
tour "github.com/jbenet/go-ipfs/tour"
14
+ "github.com/jbenet/go-ipfs/util"
15
)
16
17
+// TODO the parent function now uses tourOutput. Migrate the children to also
18
+// use the tourOutput struct
19
+
20
var tourCmd = &cmds.Command{
21
Helptext: cmds.HelpText{
22
Tagline: "An introduction to IPFS",
@@ -33,38 +37,101 @@ IPFS very quickly. To start, run:
37
"next": cmdIpfsTourNext,
38
"restart": cmdIpfsTourRestart,
39
},
36
- Run: func(req cmds.Request) (interface{}, error) {
40
+ Run: tourRunFunc,
41
+ Marshalers: cmds.MarshalerMap{
42
+ cmds.Text: tourTextMarshaler,
43
+ },
44
+ Type: &tourOutput{},
45
+}
46
38
- out := new(bytes.Buffer)
39
- cfg, err := req.Context().GetConfig()
40
- if err != nil {
41
- return nil, err
42
- }
47
+// tourOutput is a union type. It either contains a Topic or it contains the
48
+// list of Topics and an Error.
49
+type tourOutput struct {
50
+ Topic *tour.Topic
51
44
- strs, err := internal.CastToStrings(req.Arguments())
45
- if err != nil {
46
- return nil, err
47
- }
52
+ Topics []tour.Topic
53
+ Error error
54
+}
55
49
- id := tour.TopicID(cfg.Tour.Last)
50
- if len(strs) > 0 {
51
- id = tour.TopicID(strs[0])
52
- }
56
+func tourTextMarshaler(r cmds.Response) ([]byte, error) {
57
+ output, ok := r.Output().(*tourOutput)
58
+ if !ok {
59
+ return nil, util.ErrCast()
60
+ }
61
+ // can be listing when error
62
+ var buf bytes.Buffer
63
+ err := printTourOutput(&buf, output)
64
+ if err != nil {
65
+ return nil, err
66
+ }
67
+ return buf.Bytes(), nil
68
+}
69
54
- t, err := tourGet(id)
55
- if err != nil {
56
- return nil, cmds.ClientError(err.Error())
57
- }
70
+func printTourOutput(w io.Writer, output *tourOutput) error {
71
+ tmpl := `{{ if .Error }}
72
+ERROR
73
+ {{ .Error }}
74
+TOPICS
75
+ {{ range $topic := .Topics }}
76
+ {{ $topic.ID }} - {{ $topic.Title }} {{ end }}
77
+{{ else if .Topic }}
78
+Tour {{ .Topic.ID }} - {{ .Topic.Title }}
79
59
- err = tourShow(out, t)
60
- if err != nil {
61
- return nil, err
80
+{{ .Topic.Text }}
81
+{{ end }}
82
+`
83
+ tourTmpl, err := template.New("tour").Parse(tmpl)
84
+ if err != nil {
85
+ return err
86
+ }
87
+ return tourTmpl.Execute(w, output)
88
+}
89
+
90
+func tourRunFunc(req cmds.Request) (interface{}, error) {
91
+
92
+ cfg, err := req.Context().GetConfig()
93
+ if err != nil {
94
+ return nil, err
95
+ }
96
+
97
+ strs, err := internal.CastToStrings(req.Arguments())
98
+ if err != nil {
99
+ return nil, err
100
+ }
101
+
102
+ id := tour.TopicID(cfg.Tour.Last)
103
+ if len(strs) > 0 {
104
+ id = tour.TopicID(strs[0])
105
+ }
106
+
107
+ t, err := tourGet(id)
108
+ if err != nil {
109
+
110
+ // If no topic exists for this id, we handle this error right here.
111
+ // To help the user achieve the task, we construct a response
112
+ // comprised of...
113
+ // 1) a simple error message
114
+ // 2) the full list of topics
115
+
116
+ output := &tourOutput{
117
+ Error: err,
118
+ }
119
+ for _, id := range tour.IDs {
120
+ t, ok := tour.Topics[id]
121
+ if !ok {
122
+ return nil, err
123
+ }
124
+ output.Topics = append(output.Topics, t)
125
}
126
64
- return out, nil
65
- },
127
+ return output, nil
128
+ // return nil, cmds.ClientError(err.Error())
129
+ }
130
+
131
+ return &tourOutput{Topic: t}, nil
132
}
133
134
+// TODO use tourOutput like parent command
135
var cmdIpfsTourNext = &cmds.Command{
136
Helptext: cmds.HelpText{
137
Tagline: "Show the next IPFS Tour topic",
@@ -97,7 +164,7 @@ var cmdIpfsTourNext = &cmds.Command{
164
}
165
166
w.WriteTo(os.Stdout) // TODO write to res.SetValue
100
- return nil, nil
167
+ return w, nil
168
},
169
}
170
@@ -122,6 +189,7 @@ var cmdIpfsTourRestart = &cmds.Command{
189
},
190
}
191
192
+// TODO use tourOutput like parent command
193
var cmdIpfsTourList = &cmds.Command{
194
Helptext: cmds.HelpText{
195
Tagline: "Show a list of IPFS Tour topics",
@@ -171,6 +239,7 @@ Tour {{ .ID }} - {{ .Title }}
239
return ttempl.Execute(w, t)
240
}
241
242
+// tourGet returns an error if topic does not exist
243
func tourGet(id tour.ID) (*tour.Topic, error) {
244
t, found := tour.Topics[id]
245
if !found {
cmd/ipfs2/tour_test.go
+49
-2
@@ -2,6 +2,7 @@ package main
2
3
import (
4
"bytes"
5
+ "errors"
6
"testing"
7
8
"github.com/jbenet/go-ipfs/tour"
@@ -11,8 +12,9 @@ func TestParseTourTemplate(t *testing.T) {
12
topic := &tour.Topic{
13
ID: "42",
14
Title: "IPFS CLI test files",
14
- Text: `Welcome to the IPFS test files
15
- This is where we test our beautiful command line interfaces
15
+ Text: `
16
+Welcome to the IPFS test files
17
+This is where we test our beautiful command line interfaces
18
`,
19
}
20
var buf bytes.Buffer
@@ -22,3 +24,48 @@ func TestParseTourTemplate(t *testing.T) {
24
}
25
t.Log(buf.String())
26
}
27
+
28
+func TestRenderTourOutputList(t *testing.T) {
29
+
30
+ t.Log(`Ensure we can successfully print the tour output when there's an
31
+ error and list of tour topics`)
32
+ listOutput := &tourOutput{
33
+ Error: errors.New("Topic 42 does not exist"),
34
+ Topics: []tour.Topic{
35
+ tour.Topic{
36
+ ID: "41",
37
+ Title: "Being one shy of the mark",
38
+ Text: "Poor thing.",
39
+ },
40
+ tour.Topic{
41
+ ID: "44",
42
+ Title: "Two shy of the mark",
43
+ Text: "Oh no.",
44
+ },
45
+ },
46
+ }
47
+
48
+ var list bytes.Buffer
49
+ if err := printTourOutput(&list, listOutput); err != nil {
50
+ t.Fatal(err)
51
+ }
52
+ t.Log(list.String())
53
+}
54
+
55
+func TestRenderTourOutputSingle(t *testing.T) {
56
+ t.Log(`
57
+ When there's just a single topic in the output, ensure we can render the
58
+ template`)
59
+ singleOutput := &tourOutput{
60
+ Topic: &tour.Topic{
61
+ ID: "42",
62
+ Title: "Informative!",
63
+ Text: "Compelling!",
64
+ },
65
+ }
66
+ var single bytes.Buffer
67
+ if err := printTourOutput(&single, singleOutput); err != nil {
68
+ t.Fatal(err)
69
+ }
70
+ t.Log(single.String())
71
+}