Skip to content

Commit 021fc9f

Browse files
authored
Merge pull request #5385 from ipfs/kevina/cid-cmd
Provide new "cid" sub-command.
2 parents 5c8580d + 54904a7 commit 021fc9f

File tree

7 files changed

+633
-30
lines changed

7 files changed

+633
-30
lines changed

cmd/ipfs/ipfs.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,5 @@ var cmdDetailsMap = map[string]cmdDetails{
9292
"diag/cmds": {cannotRunOnClient: true},
9393
"repo/fsck": {cannotRunOnDaemon: true},
9494
"config/edit": {cannotRunOnDaemon: true, doesNotUseRepo: true},
95+
"cid": {doesNotUseRepo: true},
9596
}

core/commands/cid.go

Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
package commands
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"sort"
7+
"strings"
8+
"unicode"
9+
10+
"github.com/ipfs/go-ipfs/core/commands/e"
11+
12+
cid "gx/ipfs/QmPSQnBKM9g7BaUcZCvswUJVscQ1ipjmwxN5PXCjkp9EQ7/go-cid"
13+
mhash "gx/ipfs/QmPnFwZ2JXKnXgMw8CdBPxn7FWh6LLdjUjxV1fKHuJnkr8/go-multihash"
14+
cidutil "gx/ipfs/QmQJSeE3CX4zos9qeaG8EhecEK9zvrTEfTG84J8C5NVRwt/go-cidutil"
15+
cmdkit "gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit"
16+
verifcid "gx/ipfs/QmVkMRSkXrpjqrroEXWuYBvDBnXCdMMY6gsKicBGVGUqKT/go-verifcid"
17+
cmds "gx/ipfs/QmXTmUCBtDUrzDYVzASogLiNph7EBuYqEgPL7QoHNMzUnz/go-ipfs-cmds"
18+
mbase "gx/ipfs/QmekxXDhCxCJRNuzmHreuaT3BsuJcsjcXWNrtV9C8DRHtd/go-multibase"
19+
)
20+
21+
var CidCmd = &cmds.Command{
22+
Helptext: cmdkit.HelpText{
23+
Tagline: "Convert and discover properties of CIDs",
24+
},
25+
Subcommands: map[string]*cmds.Command{
26+
"format": cidFmtCmd,
27+
"base32": base32Cmd,
28+
"bases": basesCmd,
29+
"codecs": codecsCmd,
30+
"hashes": hashesCmd,
31+
},
32+
}
33+
34+
var cidFmtCmd = &cmds.Command{
35+
Helptext: cmdkit.HelpText{
36+
Tagline: "Format and convert a CID in various useful ways.",
37+
LongDescription: `
38+
Format and converts <cid>'s in various useful ways.
39+
40+
The optional format string is a printf style format string:
41+
` + cidutil.FormatRef,
42+
},
43+
Arguments: []cmdkit.Argument{
44+
cmdkit.StringArg("cid", true, true, "Cids to format.").EnableStdin(),
45+
},
46+
Options: []cmdkit.Option{
47+
cmdkit.StringOption("f", "Printf style format string.").WithDefault("%s"),
48+
cmdkit.StringOption("v", "CID version to convert to."),
49+
cmdkit.StringOption("b", "Multibase to display CID in."),
50+
},
51+
Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) error {
52+
fmtStr, _ := req.Options["f"].(string)
53+
verStr, _ := req.Options["v"].(string)
54+
baseStr, _ := req.Options["b"].(string)
55+
56+
opts := cidFormatOpts{}
57+
58+
if strings.IndexByte(fmtStr, '%') == -1 {
59+
return fmt.Errorf("invalid format string: %s", fmtStr)
60+
}
61+
opts.fmtStr = fmtStr
62+
63+
switch verStr {
64+
case "":
65+
// noop
66+
case "0":
67+
opts.verConv = toCidV0
68+
case "1":
69+
opts.verConv = toCidV1
70+
default:
71+
return fmt.Errorf("invalid cid version: %s", verStr)
72+
}
73+
74+
if baseStr != "" {
75+
encoder, err := mbase.EncoderByName(baseStr)
76+
if err != nil {
77+
return err
78+
}
79+
opts.newBase = encoder.Encoding()
80+
} else {
81+
opts.newBase = mbase.Encoding(-1)
82+
}
83+
84+
return emitCids(req, resp, opts)
85+
},
86+
PostRun: cmds.PostRunMap{
87+
cmds.CLI: streamResult(func(v interface{}, out io.Writer) nonFatalError {
88+
r := v.(*CidFormatRes)
89+
if r.ErrorMsg != "" {
90+
return nonFatalError(fmt.Sprintf("%s: %s", r.CidStr, r.ErrorMsg))
91+
}
92+
fmt.Fprintf(out, "%s\n", r.Formatted)
93+
return ""
94+
}),
95+
},
96+
Type: CidFormatRes{},
97+
}
98+
99+
type CidFormatRes struct {
100+
CidStr string // Original Cid String passed in
101+
Formatted string // Formated Result
102+
ErrorMsg string // Error
103+
}
104+
105+
var base32Cmd = &cmds.Command{
106+
Helptext: cmdkit.HelpText{
107+
Tagline: "Convert CIDs to Base32 CID version 1.",
108+
},
109+
Arguments: []cmdkit.Argument{
110+
cmdkit.StringArg("cid", true, true, "Cids to convert.").EnableStdin(),
111+
},
112+
Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) error {
113+
opts := cidFormatOpts{
114+
fmtStr: "%s",
115+
newBase: mbase.Encoding(mbase.Base32),
116+
verConv: toCidV1,
117+
}
118+
return emitCids(req, resp, opts)
119+
},
120+
PostRun: cidFmtCmd.PostRun,
121+
Type: cidFmtCmd.Type,
122+
}
123+
124+
type cidFormatOpts struct {
125+
fmtStr string
126+
newBase mbase.Encoding
127+
verConv func(cid cid.Cid) (cid.Cid, error)
128+
}
129+
130+
type argumentIterator struct {
131+
args []string
132+
body cmds.StdinArguments
133+
}
134+
135+
func (i *argumentIterator) next() (string, bool) {
136+
if len(i.args) > 0 {
137+
arg := i.args[0]
138+
i.args = i.args[1:]
139+
return arg, true
140+
}
141+
if i.body == nil || !i.body.Scan() {
142+
return "", false
143+
}
144+
return strings.TrimSpace(i.body.Argument()), true
145+
}
146+
147+
func (i *argumentIterator) err() error {
148+
if i.body == nil {
149+
return nil
150+
}
151+
return i.body.Err()
152+
}
153+
154+
func emitCids(req *cmds.Request, resp cmds.ResponseEmitter, opts cidFormatOpts) error {
155+
itr := argumentIterator{req.Arguments, req.BodyArgs()}
156+
var emitErr error
157+
for emitErr == nil {
158+
cidStr, ok := itr.next()
159+
if !ok {
160+
break
161+
}
162+
res := &CidFormatRes{CidStr: cidStr}
163+
c, err := cid.Decode(cidStr)
164+
if err != nil {
165+
res.ErrorMsg = err.Error()
166+
emitErr = resp.Emit(res)
167+
continue
168+
}
169+
base := opts.newBase
170+
if base == -1 {
171+
base, _ = cid.ExtractEncoding(cidStr)
172+
}
173+
if opts.verConv != nil {
174+
c, err = opts.verConv(c)
175+
if err != nil {
176+
res.ErrorMsg = err.Error()
177+
emitErr = resp.Emit(res)
178+
continue
179+
}
180+
}
181+
str, err := cidutil.Format(opts.fmtStr, base, c)
182+
if _, ok := err.(cidutil.FormatStringError); ok {
183+
// no point in continuing if there is a problem with the format string
184+
return err
185+
}
186+
if err != nil {
187+
res.ErrorMsg = err.Error()
188+
} else {
189+
res.Formatted = str
190+
}
191+
emitErr = resp.Emit(res)
192+
}
193+
if emitErr != nil {
194+
return emitErr
195+
}
196+
err := itr.err()
197+
if err != nil {
198+
return err
199+
}
200+
return nil
201+
}
202+
203+
func toCidV0(c cid.Cid) (cid.Cid, error) {
204+
if c.Type() != cid.DagProtobuf {
205+
return cid.Cid{}, fmt.Errorf("can't convert non-protobuf nodes to cidv0")
206+
}
207+
return cid.NewCidV0(c.Hash()), nil
208+
}
209+
210+
func toCidV1(c cid.Cid) (cid.Cid, error) {
211+
return cid.NewCidV1(c.Type(), c.Hash()), nil
212+
}
213+
214+
type CodeAndName struct {
215+
Code int
216+
Name string
217+
}
218+
219+
var basesCmd = &cmds.Command{
220+
Helptext: cmdkit.HelpText{
221+
Tagline: "List available multibase encodings.",
222+
},
223+
Options: []cmdkit.Option{
224+
cmdkit.BoolOption("prefix", "also include the single leter prefixes in addition to the code"),
225+
cmdkit.BoolOption("numeric", "also include numeric codes"),
226+
},
227+
Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) error {
228+
var res []CodeAndName
229+
// use EncodingToStr in case at some point there are multiple names for a given code
230+
for code, name := range mbase.EncodingToStr {
231+
res = append(res, CodeAndName{int(code), name})
232+
}
233+
cmds.EmitOnce(resp, res)
234+
return nil
235+
},
236+
Encoders: cmds.EncoderMap{
237+
cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w io.Writer, val0 interface{}) error {
238+
prefixes, _ := req.Options["prefix"].(bool)
239+
numeric, _ := req.Options["numeric"].(bool)
240+
val, ok := val0.([]CodeAndName)
241+
if !ok {
242+
return e.TypeErr(val, val0)
243+
}
244+
sort.Sort(multibaseSorter{val})
245+
for _, v := range val {
246+
code := v.Code
247+
if code < 32 || code >= 127 {
248+
// don't display non-printable prefixes
249+
code = ' '
250+
}
251+
switch {
252+
case prefixes && numeric:
253+
fmt.Fprintf(w, "%c %5d %s\n", code, v.Code, v.Name)
254+
case prefixes:
255+
fmt.Fprintf(w, "%c %s\n", code, v.Name)
256+
case numeric:
257+
fmt.Fprintf(w, "%5d %s\n", v.Code, v.Name)
258+
default:
259+
fmt.Fprintf(w, "%s\n", v.Name)
260+
}
261+
}
262+
return nil
263+
}),
264+
},
265+
Type: []CodeAndName{},
266+
}
267+
268+
var codecsCmd = &cmds.Command{
269+
Helptext: cmdkit.HelpText{
270+
Tagline: "List available CID codecs.",
271+
},
272+
Options: []cmdkit.Option{
273+
cmdkit.BoolOption("numeric", "also include numeric codes"),
274+
},
275+
Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) error {
276+
var res []CodeAndName
277+
// use CodecToStr as there are multiple names for a given code
278+
for code, name := range cid.CodecToStr {
279+
res = append(res, CodeAndName{int(code), name})
280+
}
281+
cmds.EmitOnce(resp, res)
282+
return nil
283+
},
284+
Encoders: cmds.EncoderMap{
285+
cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w io.Writer, val0 interface{}) error {
286+
numeric, _ := req.Options["numeric"].(bool)
287+
val, ok := val0.([]CodeAndName)
288+
if !ok {
289+
return e.TypeErr(val, val0)
290+
}
291+
sort.Sort(codeAndNameSorter{val})
292+
for _, v := range val {
293+
if numeric {
294+
fmt.Fprintf(w, "%5d %s\n", v.Code, v.Name)
295+
} else {
296+
fmt.Fprintf(w, "%s\n", v.Name)
297+
}
298+
}
299+
return nil
300+
}),
301+
},
302+
Type: []CodeAndName{},
303+
}
304+
305+
var hashesCmd = &cmds.Command{
306+
Helptext: cmdkit.HelpText{
307+
Tagline: "List available multihashes.",
308+
},
309+
Options: codecsCmd.Options,
310+
Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) error {
311+
var res []CodeAndName
312+
// use mhash.Codes in case at some point there are multiple names for a given code
313+
for code, name := range mhash.Codes {
314+
if !verifcid.IsGoodHash(code) {
315+
continue
316+
}
317+
res = append(res, CodeAndName{int(code), name})
318+
}
319+
cmds.EmitOnce(resp, res)
320+
return nil
321+
},
322+
Encoders: codecsCmd.Encoders,
323+
Type: codecsCmd.Type,
324+
}
325+
326+
type multibaseSorter struct {
327+
data []CodeAndName
328+
}
329+
330+
func (s multibaseSorter) Len() int { return len(s.data) }
331+
func (s multibaseSorter) Swap(i, j int) { s.data[i], s.data[j] = s.data[j], s.data[i] }
332+
333+
func (s multibaseSorter) Less(i, j int) bool {
334+
a := unicode.ToLower(rune(s.data[i].Code))
335+
b := unicode.ToLower(rune(s.data[j].Code))
336+
if a != b {
337+
return a < b
338+
}
339+
// lowecase letters should come before uppercase
340+
return s.data[i].Code > s.data[j].Code
341+
}
342+
343+
type codeAndNameSorter struct {
344+
data []CodeAndName
345+
}
346+
347+
func (s codeAndNameSorter) Len() int { return len(s.data) }
348+
func (s codeAndNameSorter) Swap(i, j int) { s.data[i], s.data[j] = s.data[j], s.data[i] }
349+
func (s codeAndNameSorter) Less(i, j int) bool { return s.data[i].Code < s.data[j].Code }

0 commit comments

Comments
 (0)