format.go 3.74 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 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
package cid

import (
	"bytes"
	"fmt"

	mb "github.com/multiformats/go-multibase"
	mh "github.com/multiformats/go-multihash"
)

// FormatRef is a string documenting the format string for the Format function
const FormatRef = `
   %% 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.
`

// Format formats a cid according to the format specificer as
// documented in the FormatRef constant
func Format(fmtStr string, base mb.Encoding, cid *Cid) (string, error) {
	p := cid.Prefix()
	out := new(bytes.Buffer)
	var err error
	encoder, err := mb.NewEncoder(base)
	if err != nil {
		return "", err
	}
	for i := 0; i < len(fmtStr); i++ {
		if fmtStr[i] != '%' {
			out.WriteByte(fmtStr[i])
			continue
		}
		i++
		if i >= len(fmtStr) {
			return "", FormatStringError{"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(encoder, cid.Hash(), fmtStr[i] == 'M'))
		case 'd', 'D': // hash digest encoded in base %b
			dec, err := mh.Decode(cid.Hash())
			if err != nil {
				return "", err
			}
			out.WriteString(encode(encoder, dec.Digest, fmtStr[i] == 'D'))
		case 's': // cid string encoded in base %b
			str, err := cid.StringOfBase(base)
			if err != nil {
				return "", err
			}
			out.WriteString(str)
		case 'S': // cid string without base prefix
			out.WriteString(encode(encoder, 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 "", FormatStringError{"unrecognized specifier in format string", fmtStr[i-1 : i+1]}
		}

	}
	return out.String(), err
}

// FormatStringError is the error return from Format when the format
// string is ill formed
type FormatStringError struct {
	Message   string
	Specifier string
}

func (e FormatStringError) Error() string {
	if e.Specifier == "" {
		return e.Message
	} else {
		return fmt.Sprintf("%s: %s", e.Message, e.Specifier)
	}
}

func baseToString(base mb.Encoding) string {
	baseStr, ok := mb.EncodingToStr[base]
	if !ok {
		return fmt.Sprintf("base?%c", base)
	}
	return baseStr
}

func codecToString(num uint64) string {
	name, ok := 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.Encoder, data []byte, strip bool) string {
	str := base.Encode(data)
	if strip {
		return str[1:]
	}
	return str
}