--- /dev/null
+// Package opts helps to write commands which may take multihash
+// options.
+package opts
+
+import (
+ "bytes"
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "sort"
+ "strings"
+
+ mh "github.com/multiformats/go-multihash"
+)
+
+// package errors
+var (
+ ErrMatch = errors.New("multihash checksums did not match")
+)
+
+// Options is a struct used to parse cli flags.
+type Options struct {
+ Encoding string
+ Algorithm string
+ AlgorithmCode uint64
+ Length int
+
+ fs *flag.FlagSet
+}
+
+// FlagValues are the values the various option flags can take.
+var FlagValues = struct {
+ Encodings []string
+ Algorithms []string
+}{
+ Encodings: []string{"raw", "hex", "base58", "base64"},
+ Algorithms: func() []string {
+ names := make([]string, 0, len(mh.Names))
+ for n := range mh.Names {
+ // There are too many of these for now.
+ // We can figure something better out later.
+ if strings.HasPrefix(n, "blake2") {
+ switch n {
+ case "blake2s-256":
+ case "blake2b-128":
+ case "blake2b-224":
+ case "blake2b-256":
+ case "blake2b-384":
+ case "blake2b-512":
+ default:
+ continue
+ }
+ }
+ names = append(names, n)
+ }
+ sort.Strings(names)
+ return names
+ }(),
+}
+
+// SetupFlags adds multihash related options to given flagset.
+func SetupFlags(f *flag.FlagSet) *Options {
+ // TODO: add arg for adding opt prefix and/or overriding opts
+
+ o := new(Options)
+ algoStr := "one of: " + strings.Join(FlagValues.Algorithms, ", ")
+ f.StringVar(&o.Algorithm, "algorithm", "sha2-256", algoStr)
+ f.StringVar(&o.Algorithm, "a", "sha2-256", algoStr+" (shorthand)")
+
+ encStr := "one of: " + strings.Join(FlagValues.Encodings, ", ")
+ f.StringVar(&o.Encoding, "encoding", "base58", encStr)
+ f.StringVar(&o.Encoding, "e", "base58", encStr+" (shorthand)")
+
+ lengthStr := "checksums length in bits (truncate). -1 is default"
+ f.IntVar(&o.Length, "length", -1, lengthStr)
+ f.IntVar(&o.Length, "l", -1, lengthStr+" (shorthand)")
+ return o
+}
+
+// Parse parses the values of flags from given argument slice.
+// It is equivalent to flags.Parse(args)
+func (o *Options) Parse(args []string) error {
+ if err := o.fs.Parse(args); err != nil {
+ return err
+ }
+ return o.ParseError()
+}
+
+// ParseError checks the parsed options for errors.
+func (o *Options) ParseError() error {
+ if !strIn(o.Encoding, FlagValues.Encodings) {
+ return fmt.Errorf("encoding '%s' not %s", o.Encoding, FlagValues.Encodings)
+ }
+
+ if !strIn(o.Algorithm, FlagValues.Algorithms) {
+ return fmt.Errorf("algorithm '%s' not %s", o.Algorithm, FlagValues.Algorithms)
+ }
+
+ var found bool
+ o.AlgorithmCode, found = mh.Names[o.Algorithm]
+ if !found {
+ return fmt.Errorf("algorithm '%s' not found (lib error, pls report).", o.Algorithm)
+ }
+
+ if o.Length >= 0 {
+ if o.Length%8 != 0 {
+ return fmt.Errorf("length must be multiple of 8")
+ }
+ o.Length = o.Length / 8
+
+ if o.Length > mh.DefaultLengths[o.AlgorithmCode] {
+ o.Length = mh.DefaultLengths[o.AlgorithmCode]
+ }
+ }
+ return nil
+}
+
+// strIn checks wither string a is in set.
+func strIn(a string, set []string) bool {
+ for _, s := range set {
+ if s == a {
+ return true
+ }
+ }
+ return false
+}
+
+// Check reads all the data in r, calculates its multihash,
+// and checks it matches h1
+func (o *Options) Check(r io.Reader, h1 mh.Multihash) error {
+ h2, err := o.Multihash(r)
+ if err != nil {
+ return err
+ }
+
+ if !bytes.Equal(h1, h2) {
+ return fmt.Errorf("computed checksum did not match")
+ }
+
+ return nil
+}
+
+// Multihash reads all the data in r and calculates its multihash.
+func (o *Options) Multihash(r io.Reader) (mh.Multihash, error) {
+ b, err := ioutil.ReadAll(r)
+ if err != nil {
+ return nil, err
+ }
+
+ return mh.Sum(b, o.AlgorithmCode, o.Length)
+}