From b9199f476ddc6d2494539d2046cfd08c6c8d4a97 Mon Sep 17 00:00:00 2001 From: Piotr Tabor Date: Wed, 7 Dec 2022 15:28:25 -0500 Subject: [PATCH 1/4] Add support for --format flag for 'bbolt keys' command. This allows to print keys as 'hex', especially useful in context of etcd revisions: ./bbolt keys --format=hex ./foo/db key Signed-off-by: Piotr Tabor --- cmd/bbolt/main.go | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/cmd/bbolt/main.go b/cmd/bbolt/main.go index bec49be3d..2729039f1 100644 --- a/cmd/bbolt/main.go +++ b/cmd/bbolt/main.go @@ -527,8 +527,9 @@ func (cmd *PageItemCommand) leafPageElement(pageBytes []byte, index uint16) (*le return p.leafPageElement(index), nil } + // writeBytes writes the byte to the writer. Supported formats: ascii-encoded, hex, bytes. -func (cmd *PageItemCommand) writeBytes(w io.Writer, b []byte, format string) error { +func writeBytes(w io.Writer, b []byte, format string) error { switch format { case "ascii-encoded": _, err := fmt.Fprintf(w, "%q", b) @@ -558,7 +559,7 @@ func (cmd *PageItemCommand) PrintLeafItemKey(w io.Writer, pageBytes []byte, inde if err != nil { return err } - return cmd.writeBytes(w, e.key(), format) + return writeBytes(w, e.key(), format) } // PrintLeafItemKey writes the bytes of a leaf element's value. @@ -567,7 +568,7 @@ func (cmd *PageItemCommand) PrintLeafItemValue(w io.Writer, pageBytes []byte, in if err != nil { return err } - return cmd.writeBytes(w, e.value(), format) + return writeBytes(w, e.value(), format) } // Usage returns the help message. @@ -1144,6 +1145,7 @@ func newKeysCommand(m *Main) *KeysCommand { func (cmd *KeysCommand) Run(args ...string) error { // Parse flags. fs := flag.NewFlagSet("", flag.ContinueOnError) + optionsFormat:=fs.String("format", "bytes", "Output format. One of: ascii-encoded|hex|bytes (default: bytes)") help := fs.Bool("h", false, "") if err := fs.Parse(args); err != nil { return err @@ -1186,18 +1188,31 @@ func (cmd *KeysCommand) Run(args ...string) error { // Iterate over each key. return lastbucket.ForEach(func(key, _ []byte) error { - fmt.Fprintln(cmd.Stdout, string(key)) + writeBytes(cmd.Stdout, key, *optionsFormat) + if *optionsFormat=="bytes" { + // Preserving the legacy behavior of separation with '\n' + fmt.Fprintln(cmd.Stdout) + } return nil }) }) } // Usage returns the help message. +// TODO: Use https://pkg.go.dev/flag#FlagSet.PrintDefaults to print supported +// flags. func (cmd *KeysCommand) Usage() string { return strings.TrimLeft(` usage: bolt keys PATH [BUCKET...] Print a list of keys in the given (sub)bucket. +======= + +Options: + --format + Output format. One of: ascii-encoded|hex|bytes (default=bytes) + +Print a list of keys in the given bucket. `, "\n") } @@ -1266,7 +1281,7 @@ func (cmd *GetCommand) Run(args ...string) error { // Find value for given key. val := lastbucket.Get([]byte(key)) if val == nil { - return ErrKeyNotFound + return fmt.Errorf("Error %w for key: %q hex: \"%x\"", ErrKeyNotFound, key, string(key)); } fmt.Fprintln(cmd.Stdout, string(val)) From ed3e67c69b432567681c149d045042936c5fe81d Mon Sep 17 00:00:00 2001 From: Piotr Tabor Date: Wed, 7 Dec 2022 22:05:14 +0100 Subject: [PATCH 2/4] writelnBytes method has cleaner contract Signed-off-by: Piotr Tabor --- cmd/bbolt/main.go | 53 ++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/cmd/bbolt/main.go b/cmd/bbolt/main.go index 2729039f1..d0b6e78db 100644 --- a/cmd/bbolt/main.go +++ b/cmd/bbolt/main.go @@ -527,30 +527,30 @@ func (cmd *PageItemCommand) leafPageElement(pageBytes []byte, index uint16) (*le return p.leafPageElement(index), nil } - -// writeBytes writes the byte to the writer. Supported formats: ascii-encoded, hex, bytes. -func writeBytes(w io.Writer, b []byte, format string) error { +// formatBytes converts bytes into string according to format. +// Supported formats: ascii-encoded, hex, bytes. +func formatBytes(b []byte, format string) (string, error) { switch format { case "ascii-encoded": - _, err := fmt.Fprintf(w, "%q", b) - if err != nil { - return err - } - _, err = fmt.Fprintf(w, "\n") - return err + return fmt.Sprintf("%q", b), nil case "hex": - _, err := fmt.Fprintf(w, "%x", b) - if err != nil { - return err - } - _, err = fmt.Fprintf(w, "\n") - return err + return fmt.Sprintf("%q", b), nil case "bytes": - _, err := w.Write(b) - return err + return fmt.Sprintf("%s", b), nil default: - return fmt.Errorf("writeBytes: unsupported format: %s", format) + return "", fmt.Errorf("formatBytes: unsupported format: %s", format) + } +} + +// writelnBytes writes the byte to the writer. Supported formats: ascii-encoded, hex, bytes. +// Terminates the write with a new line symbol; +func writelnBytes(w io.Writer, b []byte, format string) error { + str, err := formatBytes(b, format) + if err != nil { + return err } + _, err = fmt.Fprintln(w, str) + return err } // PrintLeafItemKey writes the bytes of a leaf element's key. @@ -559,7 +559,7 @@ func (cmd *PageItemCommand) PrintLeafItemKey(w io.Writer, pageBytes []byte, inde if err != nil { return err } - return writeBytes(w, e.key(), format) + return writelnBytes(w, e.key(), format) } // PrintLeafItemKey writes the bytes of a leaf element's value. @@ -568,7 +568,7 @@ func (cmd *PageItemCommand) PrintLeafItemValue(w io.Writer, pageBytes []byte, in if err != nil { return err } - return writeBytes(w, e.value(), format) + return writelnBytes(w, e.value(), format) } // Usage returns the help message. @@ -1145,7 +1145,7 @@ func newKeysCommand(m *Main) *KeysCommand { func (cmd *KeysCommand) Run(args ...string) error { // Parse flags. fs := flag.NewFlagSet("", flag.ContinueOnError) - optionsFormat:=fs.String("format", "bytes", "Output format. One of: ascii-encoded|hex|bytes (default: bytes)") + optionsFormat := fs.String("format", "bytes", "Output format. One of: ascii-encoded|hex|bytes (default: bytes)") help := fs.Bool("h", false, "") if err := fs.Parse(args); err != nil { return err @@ -1188,11 +1188,7 @@ func (cmd *KeysCommand) Run(args ...string) error { // Iterate over each key. return lastbucket.ForEach(func(key, _ []byte) error { - writeBytes(cmd.Stdout, key, *optionsFormat) - if *optionsFormat=="bytes" { - // Preserving the legacy behavior of separation with '\n' - fmt.Fprintln(cmd.Stdout) - } + writelnBytes(cmd.Stdout, key, *optionsFormat) return nil }) }) @@ -1200,7 +1196,8 @@ func (cmd *KeysCommand) Run(args ...string) error { // Usage returns the help message. // TODO: Use https://pkg.go.dev/flag#FlagSet.PrintDefaults to print supported -// flags. +// +// flags. func (cmd *KeysCommand) Usage() string { return strings.TrimLeft(` usage: bolt keys PATH [BUCKET...] @@ -1281,7 +1278,7 @@ func (cmd *GetCommand) Run(args ...string) error { // Find value for given key. val := lastbucket.Get([]byte(key)) if val == nil { - return fmt.Errorf("Error %w for key: %q hex: \"%x\"", ErrKeyNotFound, key, string(key)); + return fmt.Errorf("Error %w for key: %q hex: \"%x\"", ErrKeyNotFound, key, string(key)) } fmt.Fprintln(cmd.Stdout, string(val)) From 5495c633ad68bb573bf521aab7c320406e167284 Mon Sep 17 00:00:00 2001 From: Piotr Tabor Date: Wed, 7 Dec 2022 22:31:41 +0100 Subject: [PATCH 3/4] bbolt get support --parseFormat and --format flags. Signed-off-by: Piotr Tabor --- cmd/bbolt/main.go | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/cmd/bbolt/main.go b/cmd/bbolt/main.go index d0b6e78db..6cd588eea 100644 --- a/cmd/bbolt/main.go +++ b/cmd/bbolt/main.go @@ -3,6 +3,7 @@ package main import ( "bytes" "encoding/binary" + "encoding/hex" "errors" "flag" "fmt" @@ -534,7 +535,7 @@ func formatBytes(b []byte, format string) (string, error) { case "ascii-encoded": return fmt.Sprintf("%q", b), nil case "hex": - return fmt.Sprintf("%q", b), nil + return fmt.Sprintf("%x", b), nil case "bytes": return fmt.Sprintf("%s", b), nil default: @@ -542,6 +543,17 @@ func formatBytes(b []byte, format string) (string, error) { } } +func parseBytes(str string, format string) ([]byte, error) { + switch format { + case "ascii-encoded": + return []byte(str), nil + case "hex": + return hex.DecodeString(str) + default: + return nil, fmt.Errorf("parseBytes: unsupported format: %s", format) + } +} + // writelnBytes writes the byte to the writer. Supported formats: ascii-encoded, hex, bytes. // Terminates the write with a new line symbol; func writelnBytes(w io.Writer, b []byte, format string) error { @@ -1233,6 +1245,10 @@ func newGetCommand(m *Main) *GetCommand { func (cmd *GetCommand) Run(args ...string) error { // Parse flags. fs := flag.NewFlagSet("", flag.ContinueOnError) + var parseFormat string + var format string + fs.StringVar(&parseFormat, "parse-format", "ascii-encoded", "Output format. One of: ascii-encoded|hex") + fs.StringVar(&format, "format", "bytes", "Output format. One of: ascii-encoded|hex|bytes (default: bytes)") help := fs.Bool("h", false, "") if err := fs.Parse(args); err != nil { return err @@ -1243,14 +1259,18 @@ func (cmd *GetCommand) Run(args ...string) error { // Require database path, bucket and key. relevantArgs := fs.Args() - path, buckets, key := relevantArgs[0], relevantArgs[1:len(relevantArgs)-1], relevantArgs[len(relevantArgs)-1] + path, buckets := relevantArgs[0], relevantArgs[1:len(relevantArgs)-1] + key, err := parseBytes(relevantArgs[len(relevantArgs)-1], parseFormat) + if err != nil { + return err + } if path == "" { return ErrPathRequired } else if _, err := os.Stat(path); os.IsNotExist(err) { return ErrFileNotFound } else if len(buckets) == 0 { return ErrBucketRequired - } else if key == "" { + } else if len(key) == 0 { return ErrKeyRequired } @@ -1276,13 +1296,13 @@ func (cmd *GetCommand) Run(args ...string) error { } // Find value for given key. - val := lastbucket.Get([]byte(key)) + val := lastbucket.Get(key) if val == nil { return fmt.Errorf("Error %w for key: %q hex: \"%x\"", ErrKeyNotFound, key, string(key)) } - fmt.Fprintln(cmd.Stdout, string(val)) - return nil + // TODO: In this particular case, it would be better to not terminate with '\n' + return writelnBytes(cmd.Stdout, val, format) }) } From 4acf9d93a5cfeedf3e5499b35e9e3d499f562833 Mon Sep 17 00:00:00 2001 From: Piotr Tabor Date: Thu, 8 Dec 2022 10:01:19 +0100 Subject: [PATCH 4/4] Fixing golangci warnings. Signed-off-by: Piotr Tabor --- cmd/bbolt/main.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/cmd/bbolt/main.go b/cmd/bbolt/main.go index 6cd588eea..dd583ab54 100644 --- a/cmd/bbolt/main.go +++ b/cmd/bbolt/main.go @@ -537,7 +537,7 @@ func formatBytes(b []byte, format string) (string, error) { case "hex": return fmt.Sprintf("%x", b), nil case "bytes": - return fmt.Sprintf("%s", b), nil + return string(b), nil default: return "", fmt.Errorf("formatBytes: unsupported format: %s", format) } @@ -1200,16 +1200,13 @@ func (cmd *KeysCommand) Run(args ...string) error { // Iterate over each key. return lastbucket.ForEach(func(key, _ []byte) error { - writelnBytes(cmd.Stdout, key, *optionsFormat) - return nil + return writelnBytes(cmd.Stdout, key, *optionsFormat) }) }) } // Usage returns the help message. -// TODO: Use https://pkg.go.dev/flag#FlagSet.PrintDefaults to print supported -// -// flags. +// TODO: Use https://pkg.go.dev/flag#FlagSet.PrintDefaults to print supported flags. func (cmd *KeysCommand) Usage() string { return strings.TrimLeft(` usage: bolt keys PATH [BUCKET...] @@ -1217,7 +1214,8 @@ usage: bolt keys PATH [BUCKET...] Print a list of keys in the given (sub)bucket. ======= -Options: +Additional options include: + --format Output format. One of: ascii-encoded|hex|bytes (default=bytes) @@ -1247,7 +1245,7 @@ func (cmd *GetCommand) Run(args ...string) error { fs := flag.NewFlagSet("", flag.ContinueOnError) var parseFormat string var format string - fs.StringVar(&parseFormat, "parse-format", "ascii-encoded", "Output format. One of: ascii-encoded|hex") + fs.StringVar(&parseFormat, "parse-format", "ascii-encoded", "Input format. One of: ascii-encoded|hex (default: ascii-encoded)") fs.StringVar(&format, "format", "bytes", "Output format. One of: ascii-encoded|hex|bytes (default: bytes)") help := fs.Bool("h", false, "") if err := fs.Parse(args); err != nil { @@ -1312,6 +1310,13 @@ func (cmd *GetCommand) Usage() string { usage: bolt get PATH [BUCKET..] KEY Print the value of the given key in the given (sub)bucket. + +Additional options include: + + --format + Output format. One of: ascii-encoded|hex|bytes (default=bytes) + --parse-format + Input format (of key). One of: ascii-encoded|hex (default=ascii-encoded)" `, "\n") }