Skip to content

Commit a8ae38c

Browse files
authored
Merge pull request #61 from ipfs/kevina/cid-fmt-enhan
cid-fmt Enhancments
2 parents 23f03cb + 1c907db commit a8ae38c

File tree

6 files changed

+270
-237
lines changed

6 files changed

+270
-237
lines changed

cid-fmt/main.go

+19-168
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,21 @@
11
package main
22

33
import (
4-
"bytes"
54
"fmt"
65
"os"
76
"strings"
87

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

1110
mb "github.com/multiformats/go-multibase"
12-
mh "github.com/multiformats/go-multihash"
1311
)
1412

1513
func usage() {
1614
fmt.Fprintf(os.Stderr, "usage: %s [-b multibase-code] [-v cid-version] <fmt-str> <cid> ...\n\n", os.Args[0])
17-
fmt.Fprintf(os.Stderr, "<fmt-str> is either 'prefix' or a printf style format string:\n%s", fmtRef)
15+
fmt.Fprintf(os.Stderr, "<fmt-str> is either 'prefix' or a printf style format string:\n%s", c.FormatRef)
1816
os.Exit(2)
1917
}
2018

21-
const fmtRef = `
22-
%% literal %
23-
%b multibase name
24-
%B multibase code
25-
%v version string
26-
%V version number
27-
%c codec name
28-
%C codec code
29-
%h multihash name
30-
%H multihash code
31-
%L hash digest length
32-
%m multihash encoded in base %b (with multibase prefix)
33-
%M multihash encoded in base %b without multibase prefix
34-
%d hash digest encoded in base %b (with multibase prefix)
35-
%D hash digest encoded in base %b without multibase prefix
36-
%s cid string encoded in base %b (1)
37-
%s cid string encoded in base %b without multibase prefix
38-
%P cid prefix: %v-%c-%h-%L
39-
40-
(1) For CID version 0 the multibase must be base58btc and no prefix is
41-
used. For Cid version 1 the multibase prefix is included.
42-
`
43-
4419
func main() {
4520
if len(os.Args) < 2 {
4621
usage()
@@ -55,11 +30,12 @@ outer:
5530
if len(args) < 2 {
5631
usage()
5732
}
58-
if len(args[1]) != 1 {
59-
fmt.Fprintf(os.Stderr, "Error: Invalid multibase code: %s\n", args[1])
33+
encoder, err := mb.EncoderByName(args[1])
34+
if err != nil {
35+
fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
6036
os.Exit(2)
6137
}
62-
newBase = mb.Encoding(args[1][0])
38+
newBase = encoder.Encoding()
6339
args = args[2:]
6440
case "-v":
6541
if len(args) < 2 {
@@ -93,15 +69,16 @@ outer:
9369
}
9470
}
9571
for _, cidStr := range args[1:] {
96-
base, cid, err := decode(cidStr)
72+
cid, err := c.Decode(cidStr)
9773
if err != nil {
9874
fmt.Fprintf(os.Stdout, "!INVALID_CID!\n")
9975
errorMsg("%s: %v", cidStr, err)
10076
// Don't abort on a bad cid
10177
continue
10278
}
103-
if newBase != -1 {
104-
base = newBase
79+
base := newBase
80+
if newBase == -1 {
81+
base, _ = c.ExtractEncoding(cidStr)
10582
}
10683
if verConv != nil {
10784
cid, err = verConv(cid)
@@ -112,11 +89,18 @@ outer:
11289
continue
11390
}
11491
}
115-
str, err := fmtCid(fmtStr, base, cid)
116-
if err != nil {
92+
str, err := c.Format(fmtStr, base, cid)
93+
switch err.(type) {
94+
case c.FormatStringError:
11795
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
118-
// An error here means a bad format string, no point in continuing
11996
os.Exit(2)
97+
default:
98+
fmt.Fprintf(os.Stdout, "!ERROR!\n")
99+
errorMsg("%s: %v", cidStr, err)
100+
// Don't abort on cid specific errors
101+
continue
102+
case nil:
103+
// no error
120104
}
121105
fmt.Fprintf(os.Stdout, "%s\n", str)
122106
}
@@ -132,139 +116,6 @@ func errorMsg(fmtStr string, a ...interface{}) {
132116
exitCode = 1
133117
}
134118

135-
func decode(v string) (mb.Encoding, *c.Cid, error) {
136-
if len(v) < 2 {
137-
return 0, nil, c.ErrCidTooShort
138-
}
139-
140-
if len(v) == 46 && v[:2] == "Qm" {
141-
hash, err := mh.FromB58String(v)
142-
if err != nil {
143-
return 0, nil, err
144-
}
145-
146-
return mb.Base58BTC, c.NewCidV0(hash), nil
147-
}
148-
149-
base, data, err := mb.Decode(v)
150-
if err != nil {
151-
return 0, nil, err
152-
}
153-
154-
cid, err := c.Cast(data)
155-
156-
return base, cid, err
157-
}
158-
159-
const ERR_STR = "!ERROR!"
160-
161-
func fmtCid(fmtStr string, base mb.Encoding, cid *c.Cid) (string, error) {
162-
p := cid.Prefix()
163-
out := new(bytes.Buffer)
164-
var err error
165-
for i := 0; i < len(fmtStr); i++ {
166-
if fmtStr[i] != '%' {
167-
out.WriteByte(fmtStr[i])
168-
continue
169-
}
170-
i++
171-
if i >= len(fmtStr) {
172-
return "", fmt.Errorf("premature end of format string")
173-
}
174-
switch fmtStr[i] {
175-
case '%':
176-
out.WriteByte('%')
177-
case 'b': // base name
178-
out.WriteString(baseToString(base))
179-
case 'B': // base code
180-
out.WriteByte(byte(base))
181-
case 'v': // version string
182-
fmt.Fprintf(out, "cidv%d", p.Version)
183-
case 'V': // version num
184-
fmt.Fprintf(out, "%d", p.Version)
185-
case 'c': // codec name
186-
out.WriteString(codecToString(p.Codec))
187-
case 'C': // codec code
188-
fmt.Fprintf(out, "%d", p.Codec)
189-
case 'h': // hash fun name
190-
out.WriteString(hashToString(p.MhType))
191-
case 'H': // hash fun code
192-
fmt.Fprintf(out, "%d", p.MhType)
193-
case 'L': // hash length
194-
fmt.Fprintf(out, "%d", p.MhLength)
195-
case 'm', 'M': // multihash encoded in base %b
196-
out.WriteString(encode(base, cid.Hash(), fmtStr[i] == 'M'))
197-
case 'd', 'D': // hash digest encoded in base %b
198-
dec, err := mh.Decode(cid.Hash())
199-
if err != nil {
200-
out.WriteString(ERR_STR)
201-
errorMsg("%v", err)
202-
continue
203-
}
204-
out.WriteString(encode(base, dec.Digest, fmtStr[i] == 'D'))
205-
case 's': // cid string encoded in base %b
206-
str, err := cid.StringOfBase(base)
207-
if err != nil {
208-
out.WriteString(ERR_STR)
209-
errorMsg("%v", err)
210-
continue
211-
}
212-
out.WriteString(str)
213-
case 'S': // cid string without base prefix
214-
out.WriteString(encode(base, cid.Bytes(), true))
215-
case 'P': // prefix
216-
fmt.Fprintf(out, "cidv%d-%s-%s-%d",
217-
p.Version,
218-
codecToString(p.Codec),
219-
hashToString(p.MhType),
220-
p.MhLength,
221-
)
222-
default:
223-
return "", fmt.Errorf("unrecognized specifier in format string: %c", fmtStr[i])
224-
}
225-
226-
}
227-
return out.String(), err
228-
}
229-
230-
func baseToString(base mb.Encoding) string {
231-
// FIXME: Use lookup tables when they are added to go-multibase
232-
switch base {
233-
case mb.Base58BTC:
234-
return "base58btc"
235-
default:
236-
return fmt.Sprintf("base?%c", base)
237-
}
238-
}
239-
240-
func codecToString(num uint64) string {
241-
name, ok := c.CodecToStr[num]
242-
if !ok {
243-
return fmt.Sprintf("codec?%d", num)
244-
}
245-
return name
246-
}
247-
248-
func hashToString(num uint64) string {
249-
name, ok := mh.Codes[num]
250-
if !ok {
251-
return fmt.Sprintf("hash?%d", num)
252-
}
253-
return name
254-
}
255-
256-
func encode(base mb.Encoding, data []byte, strip bool) string {
257-
str, err := mb.Encode(base, data)
258-
if err != nil {
259-
errorMsg("%v", err)
260-
return ERR_STR
261-
}
262-
if strip {
263-
return str[1:]
264-
}
265-
return str
266-
}
267-
268119
func toCidV0(cid *c.Cid) (*c.Cid, error) {
269120
if cid.Type() != c.DagProtobuf {
270121
return nil, fmt.Errorf("can't convert non-protobuf nodes to cidv0")

cid-fmt/main_test.go

+3-67
Original file line numberDiff line numberDiff line change
@@ -4,77 +4,13 @@ import (
44
"fmt"
55
"testing"
66

7-
mb "github.com/multiformats/go-multibase"
7+
c "github.com/ipfs/go-cid"
88
)
99

10-
func TestFmt(t *testing.T) {
11-
cids := map[string]string{
12-
"cidv0": "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn",
13-
"cidv1": "zdj7WfLr9DhLrb1hsoSi4fSdjjxuZmeqgEtBPWxMLtPbDNbFD",
14-
}
15-
tests := []struct {
16-
cidId string
17-
newBase mb.Encoding
18-
fmtStr string
19-
result string
20-
}{
21-
{"cidv0", -1, "%P", "cidv0-protobuf-sha2-256-32"},
22-
{"cidv0", -1, "%b-%v-%c-%h-%L", "base58btc-cidv0-protobuf-sha2-256-32"},
23-
{"cidv0", -1, "%s", "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"},
24-
{"cidv0", -1, "%S", "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"},
25-
{"cidv0", -1, "ver#%V/#%C/#%H/%L", "ver#0/#112/#18/32"},
26-
{"cidv0", -1, "%m", "zQmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"},
27-
{"cidv0", -1, "%M", "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"},
28-
{"cidv0", -1, "%d", "z72gdmFAgRzYHkJzKiL8MgMMRW3BTSCGyDHroPxJbxMJn"},
29-
{"cidv0", -1, "%D", "72gdmFAgRzYHkJzKiL8MgMMRW3BTSCGyDHroPxJbxMJn"},
30-
{"cidv0", 'B', "%S", "CIQFTFEEHEDF6KLBT32BFAGLXEZL4UWFNWM4LFTLMXQBCERZ6CMLX3Y"},
31-
{"cidv0", 'B', "%B%S", "BCIQFTFEEHEDF6KLBT32BFAGLXEZL4UWFNWM4LFTLMXQBCERZ6CMLX3Y"},
32-
{"cidv1", -1, "%P", "cidv1-protobuf-sha2-256-32"},
33-
{"cidv1", -1, "%b-%v-%c-%h-%L", "base58btc-cidv1-protobuf-sha2-256-32"},
34-
{"cidv1", -1, "%s", "zdj7WfLr9DhLrb1hsoSi4fSdjjxuZmeqgEtBPWxMLtPbDNbFD"},
35-
{"cidv1", -1, "%S", "dj7WfLr9DhLrb1hsoSi4fSdjjxuZmeqgEtBPWxMLtPbDNbFD"},
36-
{"cidv1", -1, "ver#%V/#%C/#%H/%L", "ver#1/#112/#18/32"},
37-
{"cidv1", -1, "%m", "zQmYFbmndVP7QqAVWyKhpmMuQHMaD88pkK57RgYVimmoh5H"},
38-
{"cidv1", -1, "%M", "QmYFbmndVP7QqAVWyKhpmMuQHMaD88pkK57RgYVimmoh5H"},
39-
{"cidv1", -1, "%d", "zAux4gVVsLRMXtsZ9fd3tFEZN4jGYB6kP37fgoZNTc11H"},
40-
{"cidv1", -1, "%D", "Aux4gVVsLRMXtsZ9fd3tFEZN4jGYB6kP37fgoZNTc11H"},
41-
{"cidv1", 'B', "%s", "BAFYBEIETJGSRL3EQPQPCABV3G6IUBYTSIFVQ24XRRHD3JUETSKLTGQ7DJA"},
42-
{"cidv1", 'B', "%S", "AFYBEIETJGSRL3EQPQPCABV3G6IUBYTSIFVQ24XRRHD3JUETSKLTGQ7DJA"},
43-
{"cidv1", 'B', "%B%S", "BAFYBEIETJGSRL3EQPQPCABV3G6IUBYTSIFVQ24XRRHD3JUETSKLTGQ7DJA"},
44-
}
45-
for _, tc := range tests {
46-
name := fmt.Sprintf("%s/%s", tc.cidId, tc.fmtStr)
47-
if tc.newBase != -1 {
48-
name = fmt.Sprintf("%s/%c", name, tc.newBase)
49-
}
50-
cidStr := cids[tc.cidId]
51-
t.Run(name, func(t *testing.T) {
52-
testFmt(t, cidStr, tc.newBase, tc.fmtStr, tc.result)
53-
})
54-
}
55-
}
56-
57-
func testFmt(t *testing.T, cidStr string, newBase mb.Encoding, fmtStr string, result string) {
58-
base, cid, err := decode(cidStr)
59-
if newBase != -1 {
60-
base = newBase
61-
}
62-
if err != nil {
63-
t.Fatal(err)
64-
}
65-
str, err := fmtCid(fmtStr, base, cid)
66-
if err != nil {
67-
t.Fatal(err)
68-
}
69-
if str != result {
70-
t.Error(fmt.Sprintf("expected: %s; but got: %s", result, str))
71-
}
72-
}
73-
7410
func TestCidConv(t *testing.T) {
7511
cidv0 := "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"
7612
cidv1 := "zdj7WbTaiJT1fgatdet9Ei9iDB5hdCxkbVyhyh8YTUnXMiwYi"
77-
_, cid, err := decode(cidv0)
13+
cid, err := c.Decode(cidv0)
7814
if err != nil {
7915
t.Fatal(err)
8016
}
@@ -98,7 +34,7 @@ func TestCidConv(t *testing.T) {
9834
func TestBadCidConv(t *testing.T) {
9935
// this cid is a raw leaf and should not be able to convert to cidv0
10036
cidv1 := "zb2rhhzX7uSKrtQ2ZZXFAabKiKFYZrJqKY2KE1cJ8yre2GSWZ"
101-
_, cid, err := decode(cidv1)
37+
cid, err := c.Decode(cidv1)
10238
if err != nil {
10339
t.Fatal(err)
10440
}

cid.go

+22
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,28 @@ func Decode(v string) (*Cid, error) {
213213
return Cast(data)
214214
}
215215

216+
// Extract the encoding from a Cid. If Decode on the same string did
217+
// not return an error neither will this function.
218+
func ExtractEncoding(v string) (mbase.Encoding, error) {
219+
if len(v) < 2 {
220+
return -1, ErrCidTooShort
221+
}
222+
223+
if len(v) == 46 && v[:2] == "Qm" {
224+
return mbase.Base58BTC, nil
225+
}
226+
227+
encoding := mbase.Encoding(v[0])
228+
229+
// check encoding is valid
230+
_, err := mbase.NewEncoder(encoding)
231+
if err != nil {
232+
return -1, err
233+
}
234+
235+
return encoding, nil
236+
}
237+
216238
func uvError(read int) error {
217239
switch {
218240
case read == 0:

0 commit comments

Comments
 (0)