@cryptotaxi247 / kubo / commits / 57f96019f

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