Provide new "cid" sub-command.
License: MIT Signed-off-by: Kevin Atkinson <k@kevina.org>
Kevin Atkinson committed
Aug 15, 2018 at 20:02 UTC
57f96019f7b94f7f8b412b3e9a34dbcdfa19a95e
4 files changed
+316
cmd/ipfs/ipfs.go
+1
@@ -92,4 +92,5 @@ var cmdDetailsMap = map[string]cmdDetails{
92
"diag/cmds": {cannotRunOnClient: true},
93
"repo/fsck": {cannotRunOnDaemon: true},
94
"config/edit": {cannotRunOnDaemon: true, doesNotUseRepo: true},
95
+ "cid": {doesNotUseRepo: true},
96
}
core/commands/cid.go
new
+307
@@ -0,0 +1,307 @@
1
+package commands
2
+
3
+import (
4
+ "fmt"
5
+ "io"
6
+ "sort"
7
+ "strings"
8
+ "text/tabwriter"
9
+ "unicode"
10
+
11
+ cid "gx/ipfs/QmPSQnBKM9g7BaUcZCvswUJVscQ1ipjmwxN5PXCjkp9EQ7/go-cid"
12
+ mhash "gx/ipfs/QmPnFwZ2JXKnXgMw8CdBPxn7FWh6LLdjUjxV1fKHuJnkr8/go-multihash"
13
+ cidutil "gx/ipfs/QmQJSeE3CX4zos9qeaG8EhecEK9zvrTEfTG84J8C5NVRwt/go-cidutil"
14
+ cmdkit "gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit"
15
+ verifcid "gx/ipfs/QmVkMRSkXrpjqrroEXWuYBvDBnXCdMMY6gsKicBGVGUqKT/go-verifcid"
16
+ cmds "gx/ipfs/QmXTmUCBtDUrzDYVzASogLiNph7EBuYqEgPL7QoHNMzUnz/go-ipfs-cmds"
17
+ mbase "gx/ipfs/QmekxXDhCxCJRNuzmHreuaT3BsuJcsjcXWNrtV9C8DRHtd/go-multibase"
18
+)
19
+
20
+var CidCmd = &cmds.Command{
21
+ Helptext: cmdkit.HelpText{
22
+ Tagline: "Convert and discover properties of CIDs",
23
+ },
24
+ Subcommands: map[string]*cmds.Command{
25
+ "format": cidFmtCmd,
26
+ "base32": base32Cmd,
27
+ "bases": basesCmd,
28
+ "codecs": codecsCmd,
29
+ "hashes": hashesCmd,
30
+ },
31
+}
32
+
33
+var cidFmtCmd = &cmds.Command{
34
+ Helptext: cmdkit.HelpText{
35
+ Tagline: "Format and convert a CID in various useful ways.",
36
+ LongDescription: `
37
+Format and converts <cid>'s in various useful ways.
38
+
39
+The optional format string either "prefix" or or a printf style format string:
40
+` + cidutil.FormatRef,
41
+ },
42
+ Arguments: []cmdkit.Argument{
43
+ cmdkit.StringArg("cid", true, true, "Cids to format."),
44
+ },
45
+ Options: []cmdkit.Option{
46
+ cmdkit.StringOption("f", "Format string."),
47
+ cmdkit.StringOption("v", "CID version to convert to."),
48
+ cmdkit.StringOption("b", "Multibase to display CID in."),
49
+ },
50
+ Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) error {
51
+ fmtStr, _ := req.Options["f"].(string)
52
+ verStr, _ := req.Options["v"].(string)
53
+ baseStr, _ := req.Options["b"].(string)
54
+
55
+ opts := cidFormatOpts{}
56
+
57
+ switch fmtStr {
58
+ case "":
59
+ opts.fmtStr = "%s"
60
+ case "prefix":
61
+ opts.fmtStr = "%P"
62
+ default:
63
+ if strings.IndexByte(fmtStr, '%') == -1 {
64
+ return fmt.Errorf("invalid format string: %s", fmtStr)
65
+ }
66
+ opts.fmtStr = fmtStr
67
+ }
68
+
69
+ switch verStr {
70
+ case "":
71
+ // noop
72
+ case "0":
73
+ opts.verConv = toCidV0
74
+ case "1":
75
+ opts.verConv = toCidV1
76
+ default:
77
+ return fmt.Errorf("invalid cid version: %s\n", verStr)
78
+ }
79
+
80
+ if baseStr != "" {
81
+ encoder, err := mbase.EncoderByName(baseStr)
82
+ if err != nil {
83
+ return err
84
+ }
85
+ opts.newBase = encoder.Encoding()
86
+ } else {
87
+ opts.newBase = mbase.Encoding(-1)
88
+ }
89
+
90
+ res, err := formatCids(req.Arguments, opts)
91
+ if err != nil {
92
+ return err
93
+ }
94
+ cmds.EmitOnce(resp, res)
95
+ return nil
96
+ },
97
+ Encoders: cmds.EncoderMap{
98
+ cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w io.Writer, val interface{}) error {
99
+ for _, v := range val.([]string) {
100
+ fmt.Fprintf(w, "%s\n", v)
101
+ }
102
+ return nil
103
+ }),
104
+ },
105
+ Type: []string{},
106
+}
107
+
108
+var base32Cmd = &cmds.Command{
109
+ Helptext: cmdkit.HelpText{
110
+ Tagline: "Convert CIDs to Base32 CID version 1.",
111
+ },
112
+ Arguments: []cmdkit.Argument{
113
+ cmdkit.StringArg("cid", true, true, "Cids to convert."),
114
+ },
115
+ Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) error {
116
+ opts := cidFormatOpts{
117
+ fmtStr: "%s",
118
+ newBase: mbase.Encoding(mbase.Base32),
119
+ verConv: toCidV1,
120
+ }
121
+ res, err := formatCids(req.Arguments, opts)
122
+ if err != nil {
123
+ return err
124
+ }
125
+
126
+ cmds.EmitOnce(resp, res)
127
+ return nil
128
+ },
129
+ Encoders: cidFmtCmd.Encoders,
130
+ Type: cidFmtCmd.Type,
131
+}
132
+
133
+type cidFormatOpts struct {
134
+ fmtStr string
135
+ newBase mbase.Encoding
136
+ verConv func(cid cid.Cid) (cid.Cid, error)
137
+}
138
+
139
+func formatCids(args []string, opts cidFormatOpts) ([]string, error) {
140
+ var res []string
141
+ for _, cidStr := range args {
142
+ c, err := cid.Decode(cidStr)
143
+ if err != nil {
144
+ return nil, fmt.Errorf("%s: %v", cidStr, err)
145
+ }
146
+ base := opts.newBase
147
+ if base == -1 {
148
+ base, _ = cid.ExtractEncoding(cidStr)
149
+ }
150
+ if opts.verConv != nil {
151
+ c, err = opts.verConv(c)
152
+ if err != nil {
153
+ return nil, fmt.Errorf("%s: %v", cidStr, err)
154
+ }
155
+ }
156
+ str, err := cidutil.Format(opts.fmtStr, base, c)
157
+ if _, ok := err.(cidutil.FormatStringError); ok {
158
+ return nil, err
159
+ } else if err != nil {
160
+ return nil, fmt.Errorf("%s: %v", cidStr, err)
161
+ }
162
+ res = append(res, str)
163
+ }
164
+ return res, nil
165
+}
166
+
167
+func toCidV0(c cid.Cid) (cid.Cid, error) {
168
+ if c.Type() != cid.DagProtobuf {
169
+ return cid.Cid{}, fmt.Errorf("can't convert non-protobuf nodes to cidv0")
170
+ }
171
+ return cid.NewCidV0(c.Hash()), nil
172
+}
173
+
174
+func toCidV1(c cid.Cid) (cid.Cid, error) {
175
+ return cid.NewCidV1(c.Type(), c.Hash()), nil
176
+}
177
+
178
+type CodeAndName struct {
179
+ Code int
180
+ Name string
181
+}
182
+
183
+var basesCmd = &cmds.Command{
184
+ Helptext: cmdkit.HelpText{
185
+ Tagline: "List available multibase encodings.",
186
+ },
187
+ Options: []cmdkit.Option{
188
+ cmdkit.BoolOption("prefix", "also include the single leter prefixes in addition to the code"),
189
+ cmdkit.BoolOption("numeric", "also include numeric codes"),
190
+ },
191
+ Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) error {
192
+ var res []CodeAndName
193
+ // use EncodingToStr in case at some point there are multiple names for a given code
194
+ for code, name := range mbase.EncodingToStr {
195
+ res = append(res, CodeAndName{int(code), name})
196
+ }
197
+ cmds.EmitOnce(resp, res)
198
+ return nil
199
+ },
200
+ Encoders: cmds.EncoderMap{
201
+ cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w0 io.Writer, val0 interface{}) error {
202
+ w := tabwriter.NewWriter(w0, 0, 0, 2, ' ', 0)
203
+ prefixes, _ := req.Options["prefix"].(bool)
204
+ numeric, _ := req.Options["numeric"].(bool)
205
+ val := val0.([]CodeAndName)
206
+ sort.Sort(multibaseSorter{val})
207
+ for _, v := range val {
208
+ if prefixes && v.Code >= 32 && v.Code < 127 {
209
+ fmt.Fprintf(w, "%c\t", v.Code)
210
+ } else if prefixes {
211
+ // don't display non-printable prefixes
212
+ fmt.Fprintf(w, "\t")
213
+ }
214
+ if numeric {
215
+ fmt.Fprintf(w, "%d\t%s\n", v.Code, v.Name)
216
+ } else {
217
+ fmt.Fprintf(w, "%s\n", v.Name)
218
+ }
219
+ }
220
+ w.Flush()
221
+ return nil
222
+ }),
223
+ },
224
+ Type: []CodeAndName{},
225
+}
226
+
227
+var codecsCmd = &cmds.Command{
228
+ Helptext: cmdkit.HelpText{
229
+ Tagline: "List available CID codecs.",
230
+ },
231
+ Options: []cmdkit.Option{
232
+ cmdkit.BoolOption("numeric", "also include numeric codes"),
233
+ },
234
+ Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) error {
235
+ var res []CodeAndName
236
+ // use CodecToStr as there are multiple names for a given code
237
+ for code, name := range cid.CodecToStr {
238
+ res = append(res, CodeAndName{int(code), name})
239
+ }
240
+ cmds.EmitOnce(resp, res)
241
+ return nil
242
+ },
243
+ Encoders: cmds.EncoderMap{
244
+ cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w0 io.Writer, val0 interface{}) error {
245
+ w := tabwriter.NewWriter(w0, 0, 0, 2, ' ', 0)
246
+ numeric, _ := req.Options["numeric"].(bool)
247
+ val := val0.([]CodeAndName)
248
+ sort.Sort(codeAndNameSorter{val})
249
+ for _, v := range val {
250
+ if numeric {
251
+ fmt.Fprintf(w, "%d\t%s\n", v.Code, v.Name)
252
+ } else {
253
+ fmt.Fprintf(w, "%s\n", v.Name)
254
+ }
255
+ }
256
+ w.Flush()
257
+ return nil
258
+ }),
259
+ },
260
+ Type: []CodeAndName{},
261
+}
262
+
263
+var hashesCmd = &cmds.Command{
264
+ Helptext: cmdkit.HelpText{
265
+ Tagline: "List available multihashes.",
266
+ },
267
+ Options: codecsCmd.Options,
268
+ Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) error {
269
+ var res []CodeAndName
270
+ // use mhash.Codes in case at some point there are multiple names for a given code
271
+ for code, name := range mhash.Codes {
272
+ if !verifcid.IsGoodHash(code) {
273
+ continue
274
+ }
275
+ res = append(res, CodeAndName{int(code), name})
276
+ }
277
+ cmds.EmitOnce(resp, res)
278
+ return nil
279
+ },
280
+ Encoders: codecsCmd.Encoders,
281
+ Type: codecsCmd.Type,
282
+}
283
+
284
+type multibaseSorter struct {
285
+ data []CodeAndName
286
+}
287
+
288
+func (s multibaseSorter) Len() int { return len(s.data) }
289
+func (s multibaseSorter) Swap(i, j int) { s.data[i], s.data[j] = s.data[j], s.data[i] }
290
+
291
+func (s multibaseSorter) Less(i, j int) bool {
292
+ a := unicode.ToLower(rune(s.data[i].Code))
293
+ b := unicode.ToLower(rune(s.data[j].Code))
294
+ if a != b {
295
+ return a < b
296
+ }
297
+ // lowecase letters should come before uppercase
298
+ return s.data[i].Code > s.data[j].Code
299
+}
300
+
301
+type codeAndNameSorter struct {
302
+ data []CodeAndName
303
+}
304
+
305
+func (s codeAndNameSorter) Len() int { return len(s.data) }
306
+func (s codeAndNameSorter) Swap(i, j int) { s.data[i], s.data[j] = s.data[j], s.data[i] }
307
+func (s codeAndNameSorter) Less(i, j int) bool { return s.data[i].Code < s.data[j].Code }
core/commands/commands_test.go
+6
@@ -211,6 +211,12 @@ func TestCommands(t *testing.T) {
211
"/urlstore",
212
"/urlstore/add",
213
"/version",
214
+ "/cid",
215
+ "/cid/format",
216
+ "/cid/base32",
217
+ "/cid/codecs",
218
+ "/cid/bases",
219
+ "/cid/hashes",
220
}
221
222
cmdSet := make(map[string]struct{})
core/commands/root.go
+2
@@ -71,6 +71,7 @@ TOOL COMMANDS
71
version Show ipfs version information
72
update Download and apply go-ipfs updates
73
commands List all available commands
74
+ cid Convert and discover properties of CIDs
75
76
Use 'ipfs <command> --help' to learn more about each command.
77
@@ -143,6 +144,7 @@ var rootSubcommands = map[string]*cmds.Command{
144
"urlstore": urlStoreCmd,
145
"version": lgc.NewCommand(VersionCmd),
146
"shutdown": daemonShutdownCmd,
147
+ "cid": CidCmd,
148
}
149
150
// RootRO is the readonly version of Root