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