|
| 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 } |
0 commit comments