Skip to content

Commit

Permalink
Provide new "cid" sub-command.
Browse files Browse the repository at this point in the history
License: MIT
Signed-off-by: Kevin Atkinson <[email protected]>
  • Loading branch information
kevina committed Aug 16, 2018
1 parent f6ba685 commit 1dc7a95
Show file tree
Hide file tree
Showing 3 changed files with 309 additions and 0 deletions.
1 change: 1 addition & 0 deletions cmd/ipfs/ipfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,5 @@ var cmdDetailsMap = map[string]cmdDetails{
"diag/cmds": {cannotRunOnClient: true},
"repo/fsck": {cannotRunOnDaemon: true},
"config/edit": {cannotRunOnDaemon: true, doesNotUseRepo: true},
"cid": {doesNotUseRepo: true},
}
306 changes: 306 additions & 0 deletions core/commands/cid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
package commands

import (
"fmt"
"io"
"sort"
"strings"
"text/tabwriter"
"unicode"

cmdkit "gx/ipfs/QmPVqQHEfLpqK7JLCsUkyam7rhuV3MAeZ9gueQQCrBwCta/go-ipfs-cmdkit"
mhash "gx/ipfs/QmPnFwZ2JXKnXgMw8CdBPxn7FWh6LLdjUjxV1fKHuJnkr8/go-multihash"
mbase "gx/ipfs/QmSbvata2WqNkqGtZNg8MR3SKwnB8iQ7vTPJgWqB8bC5kR/go-multibase"
cmds "gx/ipfs/QmUQb3xtNzkQCgTj2NjaqcJZNv2nfSSub2QAdy9DtQMRBT/go-ipfs-cmds"
cid "gx/ipfs/QmYjnkEL7i731PirfVH1sis89evN7jt4otSHw5D2xXXwUV/go-cid"
verifcid "gx/ipfs/QmfMirfpEKQFctVpBYTvETxxLoU5q4ZJWsAMrtwSSE2bkn/go-verifcid"
)

var CidCmd = &cmds.Command{
Helptext: cmdkit.HelpText{
Tagline: "Convert and discover properties of CIDs",
},
Subcommands: map[string]*cmds.Command{
"format": cidFmtCmd,
"base32": base32Cmd,
"bases": basesCmd,
"codecs": codecsCmd,
"hashes": hashesCmd,
},
}

var cidFmtCmd = &cmds.Command{
Helptext: cmdkit.HelpText{
Tagline: "Format and convert a CID in various useful ways.",
LongDescription: `
Format and converts <cid>'s in various useful ways.
The optional format string either "prefix" or or a printf style format string:
` + cid.FormatRef,
},
Arguments: []cmdkit.Argument{
cmdkit.StringArg("cid", true, true, "Cids to format."),
},
Options: []cmdkit.Option{
cmdkit.StringOption("f", "Format string."),
cmdkit.StringOption("v", "CID version to convert to."),
cmdkit.StringOption("b", "Multibase to display CID in."),
},
Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) {
fmtStr, _ := req.Options["f"].(string)
verStr, _ := req.Options["v"].(string)
baseStr, _ := req.Options["b"].(string)

opts := cidFormatOpts{}

switch fmtStr {
case "":
opts.fmtStr = "%s"
case "prefix":
opts.fmtStr = "%P"
default:
if strings.IndexByte(fmtStr, '%') == -1 {
resp.SetError(fmt.Errorf("invalid format string: %s", fmtStr), cmdkit.ErrNormal)
return
}
opts.fmtStr = fmtStr
}

switch verStr {
case "":
// noop
case "0":
opts.verConv = toCidV0
case "1":
opts.verConv = toCidV1
default:
resp.SetError(fmt.Errorf("invalid cid version: %s\n", verStr), cmdkit.ErrNormal)
return
}

if baseStr != "" {
encoder, err := mbase.EncoderByName(baseStr)
if err != nil {
resp.SetError(err, cmdkit.ErrNormal)
return
}
opts.newBase = encoder.Encoding()
} else {
opts.newBase = mbase.Encoding(-1)
}

res, err := formatCids(req.Arguments, opts)
if err != nil {
resp.SetError(err, cmdkit.ErrNormal)
return
}
cmds.EmitOnce(resp, res)
},
Encoders: cmds.EncoderMap{
cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w io.Writer, val interface{}) error {
for _, v := range val.([]string) {
fmt.Fprintf(w, "%s\n", v)
}
return nil
}),
},
Type: []string{},
}

var base32Cmd = &cmds.Command{
Helptext: cmdkit.HelpText{
Tagline: "Convert CIDs to Base32 CID version 1.",
},
Arguments: []cmdkit.Argument{
cmdkit.StringArg("cid", true, true, "Cids to convert."),
},
Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) {
opts := cidFormatOpts{
fmtStr: "%s",
newBase: mbase.Encoding(mbase.Base32),
verConv: toCidV1,
}
res, err := formatCids(req.Arguments, opts)
if err != nil {
resp.SetError(err, cmdkit.ErrNormal)
return
}

cmds.EmitOnce(resp, res)
},
Encoders: cidFmtCmd.Encoders,
Type: cidFmtCmd.Type,
}

type cidFormatOpts struct {
fmtStr string
newBase mbase.Encoding
verConv func(cid *cid.Cid) (*cid.Cid, error)
}

func formatCids(args []string, opts cidFormatOpts) ([]string, error) {
var res []string
for _, cidStr := range args {
c, err := cid.Decode(cidStr)
if err != nil {
return nil, fmt.Errorf("%s: %v", cidStr, err)
}
base := opts.newBase
if base == -1 {
base, _ = cid.ExtractEncoding(cidStr)
}
if opts.verConv != nil {
c, err = opts.verConv(c)
if err != nil {
return nil, fmt.Errorf("%s: %v", cidStr, err)
}
}
str, err := cid.Format(opts.fmtStr, base, c)
if _, ok := err.(cid.FormatStringError); ok {
return nil, err
} else if err != nil {
return nil, fmt.Errorf("%s: %v", cidStr, err)
}
res = append(res, str)
}
return res, nil
}

func toCidV0(c *cid.Cid) (*cid.Cid, error) {
if c.Type() != cid.DagProtobuf {
return nil, fmt.Errorf("can't convert non-protobuf nodes to cidv0")
}
return cid.NewCidV0(c.Hash()), nil
}

func toCidV1(c *cid.Cid) (*cid.Cid, error) {
return cid.NewCidV1(c.Type(), c.Hash()), nil
}

type CodeAndName struct {
Code int
Name string
}

var basesCmd = &cmds.Command{
Helptext: cmdkit.HelpText{
Tagline: "List available multibase encodings.",
},
Options: []cmdkit.Option{
cmdkit.BoolOption("prefix", "also include the single leter prefixes in addition to the code"),
cmdkit.BoolOption("numeric", "also include numeric codes"),
},
Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) {
var res []CodeAndName
// use EncodingToStr in case at some point there are multiple names for a given code
for code, name := range mbase.EncodingToStr {
res = append(res, CodeAndName{int(code), name})
}
cmds.EmitOnce(resp, res)
},
Encoders: cmds.EncoderMap{
cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w0 io.Writer, val0 interface{}) error {
w := tabwriter.NewWriter(w0, 0, 0, 2, ' ', 0)
prefixes, _ := req.Options["prefix"].(bool)
numeric, _ := req.Options["numeric"].(bool)
val := val0.([]CodeAndName)
sort.Sort(multibaseSorter{val})
for _, v := range val {
if prefixes && v.Code >= 32 && v.Code < 127 {
fmt.Fprintf(w, "%c\t", v.Code)
} else if prefixes {
// don't display non-printable prefixes
fmt.Fprintf(w, "\t")
}
if numeric {
fmt.Fprintf(w, "%d\t%s\n", v.Code, v.Name)
} else {
fmt.Fprintf(w, "%s\n", v.Name)
}
}
w.Flush()
return nil
}),
},
Type: []CodeAndName{},
}

var codecsCmd = &cmds.Command{
Helptext: cmdkit.HelpText{
Tagline: "List available CID codecs.",
},
Options: []cmdkit.Option{
cmdkit.BoolOption("numeric", "also include numeric codes"),
},
Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) {
var res []CodeAndName
// use CodecToStr as there are multiple names for a given code
for code, name := range cid.CodecToStr {
res = append(res, CodeAndName{int(code), name})
}
cmds.EmitOnce(resp, res)
},
Encoders: cmds.EncoderMap{
cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w0 io.Writer, val0 interface{}) error {
w := tabwriter.NewWriter(w0, 0, 0, 2, ' ', 0)
numeric, _ := req.Options["numeric"].(bool)
val := val0.([]CodeAndName)
sort.Sort(codeAndNameSorter{val})
for _, v := range val {
if numeric {
fmt.Fprintf(w, "%d\t%s\n", v.Code, v.Name)
} else {
fmt.Fprintf(w, "%s\n", v.Name)
}
}
w.Flush()
return nil
}),
},
Type: []CodeAndName{},
}

var hashesCmd = &cmds.Command{
Helptext: cmdkit.HelpText{
Tagline: "List available multihashes.",
},
Options: codecsCmd.Options,
Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) {
var res []CodeAndName
// use mhash.Codes in case at some point there are multiple names for a given code
for code, name := range mhash.Codes {
if !verifcid.IsGoodHash(code) {
continue
}
res = append(res, CodeAndName{int(code), name})
}
cmds.EmitOnce(resp, res)
},
Encoders: codecsCmd.Encoders,
Type: codecsCmd.Type,
}

type multibaseSorter struct {
data []CodeAndName
}

func (s multibaseSorter) Len() int { return len(s.data) }
func (s multibaseSorter) Swap(i, j int) { s.data[i], s.data[j] = s.data[j], s.data[i] }

func (s multibaseSorter) Less(i, j int) bool {
a := unicode.ToLower(rune(s.data[i].Code))
b := unicode.ToLower(rune(s.data[j].Code))
if a != b {
return a < b
}
// lowecase letters should come before uppercase
return s.data[i].Code > s.data[j].Code
}

type codeAndNameSorter struct {
data []CodeAndName
}

func (s codeAndNameSorter) Len() int { return len(s.data) }
func (s codeAndNameSorter) Swap(i, j int) { s.data[i], s.data[j] = s.data[j], s.data[i] }
func (s codeAndNameSorter) Less(i, j int) bool { return s.data[i].Code < s.data[j].Code }
2 changes: 2 additions & 0 deletions core/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ TOOL COMMANDS
version Show ipfs version information
update Download and apply go-ipfs updates
commands List all available commands
cid Convert and discover properties of CIDs
Use 'ipfs <command> --help' to learn more about each command.
Expand Down Expand Up @@ -139,6 +140,7 @@ var rootSubcommands = map[string]*cmds.Command{
"urlstore": urlStoreCmd,
"version": lgc.NewCommand(VersionCmd),
"shutdown": daemonShutdownCmd,
"cid": CidCmd,
}

// RootRO is the readonly version of Root
Expand Down

0 comments on commit 1dc7a95

Please sign in to comment.