main.go 6.16 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 231 232 233 234 235 236 237 238 239 240
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 {
241 242
	name, ok := c.CodecToStr[num]
	if !ok {
Kevin Atkinson's avatar
Kevin Atkinson committed
243
		return fmt.Sprintf("codec?%d", num)
244 245 246 247
	}
	return name
}

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

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

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
}