@cryptotaxi247 / kubo / commits / 5c4fa5a78

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 +}