Skip to content

Commit b46e3d2

Browse files
committed
Provide new "cid" sub-command.
License: MIT Signed-off-by: Kevin Atkinson <[email protected]>
1 parent 30fe06c commit b46e3d2

File tree

3 files changed

+310
-0
lines changed

3 files changed

+310
-0
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: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
cmds "gx/ipfs/QmPTfgFTo9PFr1PvPKyKoeMgBvYPh6cX3aDP7DHKVbnCbi/go-ipfs-cmds"
12+
mhash "gx/ipfs/QmPnFwZ2JXKnXgMw8CdBPxn7FWh6LLdjUjxV1fKHuJnkr8/go-multihash"
13+
cidutil "gx/ipfs/QmPyxJ2QS7L5FhGkNYkNcXHGjDhvGHueJ4auqAstFHYxy5/go-cidutil"
14+
cmdkit "gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit"
15+
mbase "gx/ipfs/QmSbvata2WqNkqGtZNg8MR3SKwnB8iQ7vTPJgWqB8bC5kR/go-multibase"
16+
verifcid "gx/ipfs/QmVUhfewLZpSaAiBYCpw2krYMaiVmFuhr2iurQLuRoU6sD/go-verifcid"
17+
cid "gx/ipfs/QmZFbDTY9jfSBms2MchvYM9oYRbAF19K7Pby47yDBfpPrb/go-cid"
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) {
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+
resp.SetError(fmt.Errorf("invalid format string: %s", fmtStr), cmdkit.ErrNormal)
65+
return
66+
}
67+
opts.fmtStr = fmtStr
68+
}
69+
70+
switch verStr {
71+
case "":
72+
// noop
73+
case "0":
74+
opts.verConv = toCidV0
75+
case "1":
76+
opts.verConv = toCidV1
77+
default:
78+
resp.SetError(fmt.Errorf("invalid cid version: %s\n", verStr), cmdkit.ErrNormal)
79+
return
80+
}
81+
82+
if baseStr != "" {
83+
encoder, err := mbase.EncoderByName(baseStr)
84+
if err != nil {
85+
resp.SetError(err, cmdkit.ErrNormal)
86+
return
87+
}
88+
opts.newBase = encoder.Encoding()
89+
} else {
90+
opts.newBase = mbase.Encoding(-1)
91+
}
92+
93+
res, err := formatCids(req.Arguments, opts)
94+
if err != nil {
95+
resp.SetError(err, cmdkit.ErrNormal)
96+
return
97+
}
98+
cmds.EmitOnce(resp, res)
99+
},
100+
Encoders: cmds.EncoderMap{
101+
cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w io.Writer, val interface{}) error {
102+
for _, v := range val.([]string) {
103+
fmt.Fprintf(w, "%s\n", v)
104+
}
105+
return nil
106+
}),
107+
},
108+
Type: []string{},
109+
}
110+
111+
var base32Cmd = &cmds.Command{
112+
Helptext: cmdkit.HelpText{
113+
Tagline: "Convert CIDs to Base32 CID version 1.",
114+
},
115+
Arguments: []cmdkit.Argument{
116+
cmdkit.StringArg("cid", true, true, "Cids to convert."),
117+
},
118+
Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) {
119+
opts := cidFormatOpts{
120+
fmtStr: "%s",
121+
newBase: mbase.Encoding(mbase.Base32),
122+
verConv: toCidV1,
123+
}
124+
res, err := formatCids(req.Arguments, opts)
125+
if err != nil {
126+
resp.SetError(err, cmdkit.ErrNormal)
127+
return
128+
}
129+
130+
cmds.EmitOnce(resp, res)
131+
},
132+
Encoders: cidFmtCmd.Encoders,
133+
Type: cidFmtCmd.Type,
134+
}
135+
136+
type cidFormatOpts struct {
137+
fmtStr string
138+
newBase mbase.Encoding
139+
verConv func(cid *cid.Cid) (*cid.Cid, error)
140+
}
141+
142+
func formatCids(args []string, opts cidFormatOpts) ([]string, error) {
143+
var res []string
144+
for _, cidStr := range args {
145+
c, err := cid.Decode(cidStr)
146+
if err != nil {
147+
return nil, fmt.Errorf("%s: %v", cidStr, err)
148+
}
149+
base := opts.newBase
150+
if base == -1 {
151+
base, _ = cid.ExtractEncoding(cidStr)
152+
}
153+
if opts.verConv != nil {
154+
c, err = opts.verConv(c)
155+
if err != nil {
156+
return nil, fmt.Errorf("%s: %v", cidStr, err)
157+
}
158+
}
159+
str, err := cidutil.Format(opts.fmtStr, base, c)
160+
if _, ok := err.(cidutil.FormatStringError); ok {
161+
return nil, err
162+
} else if err != nil {
163+
return nil, fmt.Errorf("%s: %v", cidStr, err)
164+
}
165+
res = append(res, str)
166+
}
167+
return res, nil
168+
}
169+
170+
func toCidV0(c *cid.Cid) (*cid.Cid, error) {
171+
if c.Type() != cid.DagProtobuf {
172+
return nil, fmt.Errorf("can't convert non-protobuf nodes to cidv0")
173+
}
174+
return cid.NewCidV0(c.Hash()), nil
175+
}
176+
177+
func toCidV1(c *cid.Cid) (*cid.Cid, error) {
178+
return cid.NewCidV1(c.Type(), c.Hash()), nil
179+
}
180+
181+
type CodeAndName struct {
182+
Code int
183+
Name string
184+
}
185+
186+
var basesCmd = &cmds.Command{
187+
Helptext: cmdkit.HelpText{
188+
Tagline: "List available multibase encodings.",
189+
},
190+
Options: []cmdkit.Option{
191+
cmdkit.BoolOption("prefix", "also include the single leter prefixes in addition to the code"),
192+
cmdkit.BoolOption("numeric", "also include numeric codes"),
193+
},
194+
Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) {
195+
var res []CodeAndName
196+
// use EncodingToStr in case at some point there are multiple names for a given code
197+
for code, name := range mbase.EncodingToStr {
198+
res = append(res, CodeAndName{int(code), name})
199+
}
200+
cmds.EmitOnce(resp, res)
201+
},
202+
Encoders: cmds.EncoderMap{
203+
cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w0 io.Writer, val0 interface{}) error {
204+
w := tabwriter.NewWriter(w0, 0, 0, 2, ' ', 0)
205+
prefixes, _ := req.Options["prefix"].(bool)
206+
numeric, _ := req.Options["numeric"].(bool)
207+
val := val0.([]CodeAndName)
208+
sort.Sort(multibaseSorter{val})
209+
for _, v := range val {
210+
if prefixes && v.Code >= 32 && v.Code < 127 {
211+
fmt.Fprintf(w, "%c\t", v.Code)
212+
} else if prefixes {
213+
// don't display non-printable prefixes
214+
fmt.Fprintf(w, "\t")
215+
}
216+
if numeric {
217+
fmt.Fprintf(w, "%d\t%s\n", v.Code, v.Name)
218+
} else {
219+
fmt.Fprintf(w, "%s\n", v.Name)
220+
}
221+
}
222+
w.Flush()
223+
return nil
224+
}),
225+
},
226+
Type: []CodeAndName{},
227+
}
228+
229+
var codecsCmd = &cmds.Command{
230+
Helptext: cmdkit.HelpText{
231+
Tagline: "List available CID codecs.",
232+
},
233+
Options: []cmdkit.Option{
234+
cmdkit.BoolOption("numeric", "also include numeric codes"),
235+
},
236+
Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) {
237+
var res []CodeAndName
238+
// use CodecToStr as there are multiple names for a given code
239+
for code, name := range cid.CodecToStr {
240+
res = append(res, CodeAndName{int(code), name})
241+
}
242+
cmds.EmitOnce(resp, res)
243+
},
244+
Encoders: cmds.EncoderMap{
245+
cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w0 io.Writer, val0 interface{}) error {
246+
w := tabwriter.NewWriter(w0, 0, 0, 2, ' ', 0)
247+
numeric, _ := req.Options["numeric"].(bool)
248+
val := val0.([]CodeAndName)
249+
sort.Sort(codeAndNameSorter{val})
250+
for _, v := range val {
251+
if numeric {
252+
fmt.Fprintf(w, "%d\t%s\n", v.Code, v.Name)
253+
} else {
254+
fmt.Fprintf(w, "%s\n", v.Name)
255+
}
256+
}
257+
w.Flush()
258+
return nil
259+
}),
260+
},
261+
Type: []CodeAndName{},
262+
}
263+
264+
var hashesCmd = &cmds.Command{
265+
Helptext: cmdkit.HelpText{
266+
Tagline: "List available multihashes.",
267+
},
268+
Options: codecsCmd.Options,
269+
Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) {
270+
var res []CodeAndName
271+
// use mhash.Codes in case at some point there are multiple names for a given code
272+
for code, name := range mhash.Codes {
273+
if !verifcid.IsGoodHash(code) {
274+
continue
275+
}
276+
res = append(res, CodeAndName{int(code), name})
277+
}
278+
cmds.EmitOnce(resp, res)
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/root.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ TOOL COMMANDS
6767
version Show ipfs version information
6868
update Download and apply go-ipfs updates
6969
commands List all available commands
70+
cid Convert and discover properties of CIDs
7071
7172
Use 'ipfs <command> --help' to learn more about each command.
7273
@@ -139,6 +140,7 @@ var rootSubcommands = map[string]*cmds.Command{
139140
"urlstore": urlStoreCmd,
140141
"version": lgc.NewCommand(VersionCmd),
141142
"shutdown": daemonShutdownCmd,
143+
"cid": CidCmd,
142144
}
143145

144146
// RootRO is the readonly version of Root

0 commit comments

Comments
 (0)