main.go 6.1 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
func usage() {
16
	fmt.Fprintf(os.Stderr, "usage: %s [-b multibase-code] [-v cid-version] <fmt-str> <cid> ...\n\n", os.Args[0])
Kevin Atkinson's avatar
Kevin Atkinson committed
17
	fmt.Fprintf(os.Stderr, "<fmt-str> is either 'prefix' or a printf style format string:\n%s", fmtRef)
18
	os.Exit(2)
Kevin Atkinson's avatar
Kevin Atkinson committed
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
}

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
		usage()
	}
	newBase := mb.Encoding(-1)
49
	var verConv func(cid *c.Cid) (*c.Cid, error)
Kevin Atkinson's avatar
Kevin Atkinson committed
50
	args := os.Args[1:]
51 52 53 54 55 56 57 58 59
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])
60
				os.Exit(2)
61 62 63 64 65 66 67 68 69 70 71 72 73 74
			}
			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])
75
				os.Exit(2)
76 77 78 79
			}
			args = args[2:]
		default:
			break outer
Kevin Atkinson's avatar
Kevin Atkinson committed
80 81 82 83
		}
	}
	if len(args) < 2 {
		usage()
84
	}
Kevin Atkinson's avatar
Kevin Atkinson committed
85 86
	fmtStr := args[0]
	switch fmtStr {
87
	case "prefix":
Kevin Atkinson's avatar
Kevin Atkinson committed
88 89 90 91
		fmtStr = "%P"
	default:
		if strings.IndexByte(fmtStr, '%') == -1 {
			fmt.Fprintf(os.Stderr, "Error: Invalid format string: %s\n", fmtStr)
92
			os.Exit(2)
Kevin Atkinson's avatar
Kevin Atkinson committed
93 94 95 96
		}
	}
	for _, cidStr := range args[1:] {
		base, cid, err := decode(cidStr)
97
		if err != nil {
Kevin Atkinson's avatar
Kevin Atkinson committed
98
			fmt.Fprintf(os.Stdout, "!INVALID_CID!\n")
99
			errorMsg("%s: %v", cidStr, err)
Kevin Atkinson's avatar
Kevin Atkinson committed
100 101 102
			// Don't abort on a bad cid
			continue
		}
103 104 105 106 107 108
		if newBase != -1 {
			base = newBase
		}
		if verConv != nil {
			cid, err = verConv(cid)
			if err != nil {
109 110
				fmt.Fprintf(os.Stdout, "!ERROR!\n")
				errorMsg("%s: %v", cidStr, err)
111 112 113 114
				// Don't abort on a bad conversion
				continue
			}
		}
Kevin Atkinson's avatar
Kevin Atkinson committed
115 116 117 118
		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
119
			os.Exit(2)
120
		}
Kevin Atkinson's avatar
Kevin Atkinson committed
121
		fmt.Fprintf(os.Stdout, "%s\n", str)
122
	}
123 124 125 126 127 128 129 130 131 132
	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
133 134
}

Kevin Atkinson's avatar
Kevin Atkinson committed
135 136 137 138 139 140 141
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)
142
		if err != nil {
Kevin Atkinson's avatar
Kevin Atkinson committed
143
			return 0, nil, err
144
		}
Kevin Atkinson's avatar
Kevin Atkinson committed
145 146

		return mb.Base58BTC, c.NewCidV0(hash), nil
147 148
	}

Kevin Atkinson's avatar
Kevin Atkinson committed
149
	base, data, err := mb.Decode(v)
150
	if err != nil {
Kevin Atkinson's avatar
Kevin Atkinson committed
151
		return 0, nil, err
152
	}
Kevin Atkinson's avatar
Kevin Atkinson committed
153 154 155 156 157 158 159 160 161

	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) {
162
	p := cid.Prefix()
Kevin Atkinson's avatar
Kevin Atkinson committed
163
	out := new(bytes.Buffer)
164
	var err error
Kevin Atkinson's avatar
Kevin Atkinson committed
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
	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)
201
				errorMsg("%v", err)
Kevin Atkinson's avatar
Kevin Atkinson committed
202 203 204 205 206 207 208
				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)
209
				errorMsg("%v", err)
Kevin Atkinson's avatar
Kevin Atkinson committed
210 211 212 213 214 215 216 217 218 219 220 221 222
				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:
223
			return "", fmt.Errorf("unrecognized specifier in format string: %c", fmtStr[i])
Kevin Atkinson's avatar
Kevin Atkinson committed
224 225 226
		}

	}
227
	return out.String(), err
228 229
}

Kevin Atkinson's avatar
Kevin Atkinson committed
230
func baseToString(base mb.Encoding) string {
231 232
	baseStr, ok := mb.EncodingToStr[base]
	if !ok {
Kevin Atkinson's avatar
Kevin Atkinson committed
233 234
		return fmt.Sprintf("base?%c", base)
	}
235
	return baseStr
Kevin Atkinson's avatar
Kevin Atkinson committed
236 237 238
}

func codecToString(num uint64) string {
239 240
	name, ok := c.CodecToStr[num]
	if !ok {
Kevin Atkinson's avatar
Kevin Atkinson committed
241
		return fmt.Sprintf("codec?%d", num)
242 243 244 245
	}
	return name
}

Kevin Atkinson's avatar
Kevin Atkinson committed
246
func hashToString(num uint64) string {
247 248
	name, ok := mh.Codes[num]
	if !ok {
Kevin Atkinson's avatar
Kevin Atkinson committed
249
		return fmt.Sprintf("hash?%d", num)
250 251 252
	}
	return name
}
Kevin Atkinson's avatar
Kevin Atkinson committed
253 254 255 256

func encode(base mb.Encoding, data []byte, strip bool) string {
	str, err := mb.Encode(base, data)
	if err != nil {
257
		errorMsg("%v", err)
Kevin Atkinson's avatar
Kevin Atkinson committed
258 259 260 261 262 263 264
		return ERR_STR
	}
	if strip {
		return str[1:]
	}
	return str
}
265 266 267 268 269 270 271 272 273 274 275

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
}