278 lines
6.2 KiB
Go
278 lines
6.2 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
c "github.com/ipfs/go-cid"
|
|
|
|
mb "github.com/multiformats/go-multibase"
|
|
mh "github.com/multiformats/go-multihash"
|
|
)
|
|
|
|
func usage() {
|
|
fmt.Fprintf(os.Stderr, "usage: %s [-b multibase-code] [-v cid-version] <fmt-str> <cid> ...\n\n", os.Args[0])
|
|
fmt.Fprintf(os.Stderr, "<fmt-str> is either 'prefix' or a printf style format string:\n%s", fmtRef)
|
|
os.Exit(2)
|
|
}
|
|
|
|
const fmtRef = `
|
|
%% literal %
|
|
%b multibase name
|
|
%B multibase code
|
|
%v version string
|
|
%V version number
|
|
%c codec name
|
|
%C codec code
|
|
%h multihash name
|
|
%H multihash code
|
|
%L hash digest length
|
|
%m multihash encoded in base %b (with multibase prefix)
|
|
%M multihash encoded in base %b without multibase prefix
|
|
%d hash digest encoded in base %b (with multibase prefix)
|
|
%D hash digest encoded in base %b without multibase prefix
|
|
%s cid string encoded in base %b (1)
|
|
%s cid string encoded in base %b without multibase prefix
|
|
%P cid prefix: %v-%c-%h-%L
|
|
|
|
(1) For CID version 0 the multibase must be base58btc and no prefix is
|
|
used. For Cid version 1 the multibase prefix is included.
|
|
`
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
usage()
|
|
}
|
|
newBase := mb.Encoding(-1)
|
|
var verConv func(cid *c.Cid) (*c.Cid, error)
|
|
args := os.Args[1:]
|
|
outer:
|
|
for {
|
|
switch args[0] {
|
|
case "-b":
|
|
if len(args) < 2 {
|
|
usage()
|
|
}
|
|
if len(args[1]) != 1 {
|
|
fmt.Fprintf(os.Stderr, "Error: Invalid multibase code: %s\n", args[1])
|
|
os.Exit(2)
|
|
}
|
|
newBase = mb.Encoding(args[1][0])
|
|
args = args[2:]
|
|
case "-v":
|
|
if len(args) < 2 {
|
|
usage()
|
|
}
|
|
switch args[1] {
|
|
case "0":
|
|
verConv = toCidV0
|
|
case "1":
|
|
verConv = toCidV1
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "Error: Invalid cid version: %s\n", args[1])
|
|
os.Exit(2)
|
|
}
|
|
args = args[2:]
|
|
default:
|
|
break outer
|
|
}
|
|
}
|
|
if len(args) < 2 {
|
|
usage()
|
|
}
|
|
fmtStr := args[0]
|
|
switch fmtStr {
|
|
case "prefix":
|
|
fmtStr = "%P"
|
|
default:
|
|
if strings.IndexByte(fmtStr, '%') == -1 {
|
|
fmt.Fprintf(os.Stderr, "Error: Invalid format string: %s\n", fmtStr)
|
|
os.Exit(2)
|
|
}
|
|
}
|
|
for _, cidStr := range args[1:] {
|
|
base, cid, err := decode(cidStr)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stdout, "!INVALID_CID!\n")
|
|
errorMsg("%s: %v", cidStr, err)
|
|
// Don't abort on a bad cid
|
|
continue
|
|
}
|
|
if newBase != -1 {
|
|
base = newBase
|
|
}
|
|
if verConv != nil {
|
|
cid, err = verConv(cid)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stdout, "!ERROR!\n")
|
|
errorMsg("%s: %v", cidStr, err)
|
|
// Don't abort on a bad conversion
|
|
continue
|
|
}
|
|
}
|
|
str, err := fmtCid(fmtStr, base, cid)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
// An error here means a bad format string, no point in continuing
|
|
os.Exit(2)
|
|
}
|
|
fmt.Fprintf(os.Stdout, "%s\n", str)
|
|
}
|
|
os.Exit(exitCode)
|
|
}
|
|
|
|
var exitCode = 0
|
|
|
|
func errorMsg(fmtStr string, a ...interface{}) {
|
|
fmt.Fprintf(os.Stderr, "Error: ")
|
|
fmt.Fprintf(os.Stderr, fmtStr, a...)
|
|
fmt.Fprintf(os.Stderr, "\n")
|
|
exitCode = 1
|
|
}
|
|
|
|
func decode(v string) (mb.Encoding, *c.Cid, error) {
|
|
if len(v) < 2 {
|
|
return 0, nil, c.ErrCidTooShort
|
|
}
|
|
|
|
if len(v) == 46 && v[:2] == "Qm" {
|
|
hash, err := mh.FromB58String(v)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
return mb.Base58BTC, c.NewCidV0(hash), nil
|
|
}
|
|
|
|
base, data, err := mb.Decode(v)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
cid, err := c.Cast(data)
|
|
|
|
return base, cid, err
|
|
}
|
|
|
|
const ERR_STR = "!ERROR!"
|
|
|
|
func fmtCid(fmtStr string, base mb.Encoding, cid *c.Cid) (string, error) {
|
|
p := cid.Prefix()
|
|
out := new(bytes.Buffer)
|
|
var err error
|
|
for i := 0; i < len(fmtStr); i++ {
|
|
if fmtStr[i] != '%' {
|
|
out.WriteByte(fmtStr[i])
|
|
continue
|
|
}
|
|
i++
|
|
if i >= len(fmtStr) {
|
|
return "", fmt.Errorf("premature end of format string")
|
|
}
|
|
switch fmtStr[i] {
|
|
case '%':
|
|
out.WriteByte('%')
|
|
case 'b': // base name
|
|
out.WriteString(baseToString(base))
|
|
case 'B': // base code
|
|
out.WriteByte(byte(base))
|
|
case 'v': // version string
|
|
fmt.Fprintf(out, "cidv%d", p.Version)
|
|
case 'V': // version num
|
|
fmt.Fprintf(out, "%d", p.Version)
|
|
case 'c': // codec name
|
|
out.WriteString(codecToString(p.Codec))
|
|
case 'C': // codec code
|
|
fmt.Fprintf(out, "%d", p.Codec)
|
|
case 'h': // hash fun name
|
|
out.WriteString(hashToString(p.MhType))
|
|
case 'H': // hash fun code
|
|
fmt.Fprintf(out, "%d", p.MhType)
|
|
case 'L': // hash length
|
|
fmt.Fprintf(out, "%d", p.MhLength)
|
|
case 'm', 'M': // multihash encoded in base %b
|
|
out.WriteString(encode(base, cid.Hash(), fmtStr[i] == 'M'))
|
|
case 'd', 'D': // hash digest encoded in base %b
|
|
dec, err := mh.Decode(cid.Hash())
|
|
if err != nil {
|
|
out.WriteString(ERR_STR)
|
|
errorMsg("%v", err)
|
|
continue
|
|
}
|
|
out.WriteString(encode(base, dec.Digest, fmtStr[i] == 'D'))
|
|
case 's': // cid string encoded in base %b
|
|
str, err := cid.StringOfBase(base)
|
|
if err != nil {
|
|
out.WriteString(ERR_STR)
|
|
errorMsg("%v", err)
|
|
continue
|
|
}
|
|
out.WriteString(str)
|
|
case 'S': // cid string without base prefix
|
|
out.WriteString(encode(base, cid.Bytes(), true))
|
|
case 'P': // prefix
|
|
fmt.Fprintf(out, "cidv%d-%s-%s-%d",
|
|
p.Version,
|
|
codecToString(p.Codec),
|
|
hashToString(p.MhType),
|
|
p.MhLength,
|
|
)
|
|
default:
|
|
return "", fmt.Errorf("unrecognized specifier in format string: %c", fmtStr[i])
|
|
}
|
|
|
|
}
|
|
return out.String(), err
|
|
}
|
|
|
|
func baseToString(base mb.Encoding) string {
|
|
// FIXME: Use lookup tables when they are added to go-multibase
|
|
switch base {
|
|
case mb.Base58BTC:
|
|
return "base58btc"
|
|
default:
|
|
return fmt.Sprintf("base?%c", base)
|
|
}
|
|
}
|
|
|
|
func codecToString(num uint64) string {
|
|
name, ok := c.CodecToStr[num]
|
|
if !ok {
|
|
return fmt.Sprintf("codec?%d", num)
|
|
}
|
|
return name
|
|
}
|
|
|
|
func hashToString(num uint64) string {
|
|
name, ok := mh.Codes[num]
|
|
if !ok {
|
|
return fmt.Sprintf("hash?%d", num)
|
|
}
|
|
return name
|
|
}
|
|
|
|
func encode(base mb.Encoding, data []byte, strip bool) string {
|
|
str, err := mb.Encode(base, data)
|
|
if err != nil {
|
|
errorMsg("%v", err)
|
|
return ERR_STR
|
|
}
|
|
if strip {
|
|
return str[1:]
|
|
}
|
|
return str
|
|
}
|
|
|
|
func toCidV0(cid *c.Cid) (*c.Cid, error) {
|
|
if cid.Type() != c.DagProtobuf {
|
|
return nil, fmt.Errorf("can't convert non-protobuf nodes to cidv0")
|
|
}
|
|
return c.NewCidV0(cid.Hash()), nil
|
|
}
|
|
|
|
func toCidV1(cid *c.Cid) (*c.Cid, error) {
|
|
return c.NewCidV1(cid.Type(), cid.Hash()), nil
|
|
}
|