main.go 5.15 KB
Newer Older
1 2 3
package main

import (
Kevin Atkinson's avatar
Kevin Atkinson committed
4
	"bytes"
5 6
	"fmt"
	"os"
Kevin Atkinson's avatar
Kevin Atkinson committed
7
	"strings"
8 9 10

	c "github.com/ipfs/go-cid"

Kevin Atkinson's avatar
Kevin Atkinson committed
11
	mb "github.com/multiformats/go-multibase"
12 13 14
	mh "github.com/multiformats/go-multihash"
)

Kevin Atkinson's avatar
Kevin Atkinson committed
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
func usage() {
	fmt.Fprintf(os.Stderr, "usage: %s [-b multibase-code] <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(1)
}

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

44 45
func main() {
	if len(os.Args) < 2 {
Kevin Atkinson's avatar
Kevin Atkinson committed
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
		usage()
	}
	newBase := mb.Encoding(-1)
	args := os.Args[1:]
	if args[0] == "-b" {
		if len(args) < 2 {
			usage()
		}
		if len(args[1]) != 1 {
			fmt.Fprintf(os.Stderr, "Error: Invalid multibase code: %s\n", args[1])
		}
		newBase = mb.Encoding(args[1][0])
		args = args[2:]
	}
	if len(args) < 2 {
		usage()
62
	}
Kevin Atkinson's avatar
Kevin Atkinson committed
63 64
	fmtStr := args[0]
	switch fmtStr {
65
	case "prefix":
Kevin Atkinson's avatar
Kevin Atkinson committed
66 67 68 69 70 71 72 73 74 75 76
		fmtStr = "%P"
	default:
		if strings.IndexByte(fmtStr, '%') == -1 {
			fmt.Fprintf(os.Stderr, "Error: Invalid format string: %s\n", fmtStr)
		}
	}
	for _, cidStr := range args[1:] {
		base, cid, err := decode(cidStr)
		if newBase != -1 {
			base = newBase
		}
77
		if err != nil {
Kevin Atkinson's avatar
Kevin Atkinson committed
78 79 80 81 82 83 84 85 86
			fmt.Fprintf(os.Stderr, "Error: %s: %v\n", cidStr, err)
			fmt.Fprintf(os.Stdout, "!INVALID_CID!\n")
			// Don't abort on a bad cid
			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
87 88
			os.Exit(1)
		}
Kevin Atkinson's avatar
Kevin Atkinson committed
89
		fmt.Fprintf(os.Stdout, "%s\n", str)
90 91 92
	}
}

Kevin Atkinson's avatar
Kevin Atkinson committed
93 94 95 96 97 98 99
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)
100
		if err != nil {
Kevin Atkinson's avatar
Kevin Atkinson committed
101
			return 0, nil, err
102
		}
Kevin Atkinson's avatar
Kevin Atkinson committed
103 104

		return mb.Base58BTC, c.NewCidV0(hash), nil
105 106
	}

Kevin Atkinson's avatar
Kevin Atkinson committed
107
	base, data, err := mb.Decode(v)
108
	if err != nil {
Kevin Atkinson's avatar
Kevin Atkinson committed
109
		return 0, nil, err
110
	}
Kevin Atkinson's avatar
Kevin Atkinson committed
111 112 113 114 115 116 117 118 119

	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) {
120
	p := cid.Prefix()
Kevin Atkinson's avatar
Kevin Atkinson committed
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
	out := new(bytes.Buffer)
	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)
				fmt.Fprintf(os.Stderr, "Error: %v\n", 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)
				fmt.Fprintf(os.Stderr, "Error: %v\n", 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\n", fmtStr[i])
		}

	}
	return out.String(), nil
185 186
}

Kevin Atkinson's avatar
Kevin Atkinson committed
187 188 189 190 191 192 193 194 195 196 197
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 {
198 199
	name, ok := c.CodecToStr[num]
	if !ok {
Kevin Atkinson's avatar
Kevin Atkinson committed
200
		return fmt.Sprintf("codec?%d", num)
201 202 203 204
	}
	return name
}

Kevin Atkinson's avatar
Kevin Atkinson committed
205
func hashToString(num uint64) string {
206 207
	name, ok := mh.Codes[num]
	if !ok {
Kevin Atkinson's avatar
Kevin Atkinson committed
208
		return fmt.Sprintf("hash?%d", num)
209 210 211
	}
	return name
}
Kevin Atkinson's avatar
Kevin Atkinson committed
212 213 214 215 216 217 218 219 220 221 222 223

func encode(base mb.Encoding, data []byte, strip bool) string {
	str, err := mb.Encode(base, data)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		return ERR_STR
	}
	if strip {
		return str[1:]
	}
	return str
}