Skip to content

Commit 2ff213e

Browse files
committed
Change the Get command to cobra style
Signed-off-by: Lavish Pal <[email protected]>
1 parent 1373eb3 commit 2ff213e

File tree

5 files changed

+199
-162
lines changed

5 files changed

+199
-162
lines changed

cmd/bbolt/command_get.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"os"
7+
"strings"
8+
9+
bolt "go.etcd.io/bbolt"
10+
)
11+
12+
// getCommand represents the "get" command execution.
13+
type getCommand struct {
14+
baseCommand
15+
}
16+
17+
// newGetCommand returns a getCommand.
18+
func newGetCommand(m *Main) *getCommand {
19+
c := &getCommand{}
20+
c.baseCommand = m.baseCommand
21+
return c
22+
}
23+
24+
// Run executes the command.
25+
func (cmd *getCommand) Run(args ...string) error {
26+
// Parse CLI flags.
27+
fs := flag.NewFlagSet("", flag.ContinueOnError)
28+
var parseFormat string
29+
var format string
30+
fs.StringVar(&parseFormat, "parse-format", "ascii-encoded", "Input format. One of: ascii-encoded|hex (default: ascii-encoded)")
31+
fs.StringVar(&format, "format", "auto", "Output format. One of: "+FORMAT_MODES+" (default: auto)")
32+
help := fs.Bool("h", false, "")
33+
if err := fs.Parse(args); err != nil {
34+
return err
35+
} else if *help {
36+
fmt.Fprintln(cmd.Stderr, cmd.Usage())
37+
return ErrUsage
38+
}
39+
40+
// Validate arguments.
41+
relevantArgs := fs.Args()
42+
if len(relevantArgs) < 3 {
43+
return ErrNotEnoughArgs
44+
}
45+
path := relevantArgs[0]
46+
buckets := relevantArgs[1 : len(relevantArgs)-1]
47+
keyStr := relevantArgs[len(relevantArgs)-1]
48+
49+
key, err := parseBytes(keyStr, parseFormat)
50+
if err != nil {
51+
return err
52+
}
53+
if path == "" {
54+
return ErrPathRequired
55+
} else if _, err := os.Stat(path); os.IsNotExist(err) {
56+
return ErrFileNotFound
57+
} else if len(buckets) == 0 {
58+
return ErrBucketRequired
59+
} else if len(key) == 0 {
60+
} else if len(key) == 0 {
61+
return fmt.Errorf("key required")
62+
}
63+
// Open database.
64+
db, err := bolt.Open(path, 0600, &bolt.Options{ReadOnly: true})
65+
if err != nil {
66+
return err
67+
}
68+
defer db.Close()
69+
70+
// Fetch value.
71+
return db.View(func(tx *bolt.Tx) error {
72+
lastBucket, err := findLastBucket(tx, buckets)
73+
if err != nil {
74+
return err
75+
}
76+
val := lastBucket.Get(key)
77+
if val == nil {
78+
return fmt.Errorf("Error %w for key: %q hex: \"%x\"", ErrKeyNotFound, key, key)
79+
}
80+
return writelnBytes(cmd.Stdout, val, format)
81+
})
82+
}
83+
84+
// Usage returns the help message.
85+
func (cmd *getCommand) Usage() string {
86+
return strings.TrimLeft(`
87+
usage: bolt get PATH [BUCKET..] KEY
88+
89+
Print the value of the given key in the given (sub)bucket.
90+
91+
Additional options include:
92+
93+
--format
94+
Output format. One of: `+FORMAT_MODES+` (default=auto)
95+
--parse-format
96+
Input format (of key). One of: ascii-encoded|hex (default=ascii-encoded)
97+
`, "\n")
98+
}

cmd/bbolt/command_get_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package main_test
2+
3+
import (
4+
"bytes"
5+
"encoding/hex"
6+
"errors"
7+
"fmt"
8+
"io"
9+
"testing"
10+
11+
bolt "go.etcd.io/bbolt"
12+
main "go.etcd.io/bbolt/cmd/bbolt"
13+
"go.etcd.io/bbolt/internal/btesting"
14+
15+
"github.com/stretchr/testify/require"
16+
)
17+
18+
func TestGetCommand_Run(t *testing.T) {
19+
testCases := []struct {
20+
name string
21+
printable bool
22+
testBucket string
23+
testKey string
24+
expectedValue string
25+
}{
26+
{
27+
name: "printable key value",
28+
printable: true,
29+
testBucket: "foo",
30+
testKey: "foo-key",
31+
expectedValue: "value-foo-key\n",
32+
},
33+
{
34+
name: "non printable key value",
35+
printable: false,
36+
testBucket: "bar",
37+
testKey: "101",
38+
expectedValue: hex.EncodeToString(convertInt64IntoBytes(101)) + "\n",
39+
},
40+
}
41+
42+
for _, tc := range testCases {
43+
t.Run(tc.name, func(t *testing.T) {
44+
t.Logf("Creating test database for subtest '%s'", tc.name)
45+
db := btesting.MustCreateDB(t)
46+
47+
t.Log("Inserting test data")
48+
err := db.Update(func(tx *bolt.Tx) error {
49+
b, err := tx.CreateBucketIfNotExists([]byte(tc.testBucket))
50+
if err != nil {
51+
return fmt.Errorf("create bucket %q: %w", tc.testBucket, err)
52+
}
53+
if tc.printable {
54+
return b.Put([]byte(tc.testKey), []byte("value-"+tc.testKey))
55+
}
56+
key := []byte(tc.testKey)
57+
if k, err := parseBytes(tc.testKey, "ascii-encoded"); err == nil {
58+
key = k
59+
}
60+
return b.Put(key, convertInt64IntoBytes(101))
61+
})
62+
require.NoError(t, err)
63+
db.Close()
64+
defer requireDBNoChange(t, dbData(t, db.Path()), db.Path())
65+
66+
t.Log("Running get command")
67+
rootCmd := main.NewRootCommand()
68+
outputBuf := bytes.NewBufferString("")
69+
rootCmd.SetOut(outputBuf)
70+
rootCmd.SetArgs([]string{"get", db.Path(), tc.testBucket, tc.testKey})
71+
err = rootCmd.Execute()
72+
require.NoError(t, err)
73+
74+
t.Log("Checking output")
75+
output, err := io.ReadAll(outputBuf)
76+
require.NoError(t, err)
77+
require.Equalf(t, tc.expectedValue, string(output), "unexpected stdout:\n\n%s", string(output))
78+
})
79+
}
80+
}
81+
82+
func TestGetCommand_NoArgs(t *testing.T) {
83+
expErr := errors.New("requires at least 2 arg(s), only received 0")
84+
rootCmd := main.NewRootCommand()
85+
rootCmd.SetArgs([]string{"get"})
86+
err := rootCmd.Execute()
87+
require.ErrorContains(t, err, expErr.Error())
88+
}
89+
90+
// parseBytes tries to decode a string as a hex or ascii-encoded byte slice.
91+
func parseBytes(s string, encoding string) ([]byte, error) {
92+
switch encoding {
93+
case "hex":
94+
return hex.DecodeString(s)
95+
case "ascii-encoded":
96+
return []byte(s), nil
97+
default:
98+
return nil, fmt.Errorf("unsupported encoding: %s", encoding)
99+
}
100+
}

cmd/bbolt/command_root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ func NewRootCommand() *cobra.Command {
2222
newInspectCommand(),
2323
newCheckCommand(),
2424
newBucketsCommand(),
25+
newGetCommand(),
2526
)
2627

2728
return rootCmd

cmd/bbolt/main.go

Lines changed: 0 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,6 @@ func (m *Main) Run(args ...string) error {
128128
return newDumpCommand(m).Run(args[1:]...)
129129
case "page-item":
130130
return newPageItemCommand(m).Run(args[1:]...)
131-
case "get":
132-
return newGetCommand(m).Run(args[1:]...)
133131
case "info":
134132
return newInfoCommand(m).Run(args[1:]...)
135133
case "keys":
@@ -840,96 +838,6 @@ Print a list of keys in the given bucket.
840838
`, "\n")
841839
}
842840

843-
// getCommand represents the "get" command execution.
844-
type getCommand struct {
845-
baseCommand
846-
}
847-
848-
// newGetCommand returns a getCommand.
849-
func newGetCommand(m *Main) *getCommand {
850-
c := &getCommand{}
851-
c.baseCommand = m.baseCommand
852-
return c
853-
}
854-
855-
// Run executes the command.
856-
func (cmd *getCommand) Run(args ...string) error {
857-
// Parse flags.
858-
fs := flag.NewFlagSet("", flag.ContinueOnError)
859-
var parseFormat string
860-
var format string
861-
fs.StringVar(&parseFormat, "parse-format", "ascii-encoded", "Input format. One of: ascii-encoded|hex (default: ascii-encoded)")
862-
fs.StringVar(&format, "format", "auto", "Output format. One of: "+FORMAT_MODES+" (default: auto)")
863-
help := fs.Bool("h", false, "")
864-
if err := fs.Parse(args); err != nil {
865-
return err
866-
} else if *help {
867-
fmt.Fprintln(cmd.Stderr, cmd.Usage())
868-
return ErrUsage
869-
}
870-
871-
// Require database path, bucket and key.
872-
relevantArgs := fs.Args()
873-
if len(relevantArgs) < 3 {
874-
return ErrNotEnoughArgs
875-
}
876-
path, buckets := relevantArgs[0], relevantArgs[1:len(relevantArgs)-1]
877-
key, err := parseBytes(relevantArgs[len(relevantArgs)-1], parseFormat)
878-
if err != nil {
879-
return err
880-
}
881-
if path == "" {
882-
return ErrPathRequired
883-
} else if _, err := os.Stat(path); os.IsNotExist(err) {
884-
return ErrFileNotFound
885-
} else if len(buckets) == 0 {
886-
return ErrBucketRequired
887-
} else if len(key) == 0 {
888-
return berrors.ErrKeyRequired
889-
}
890-
891-
// Open database.
892-
db, err := bolt.Open(path, 0600, &bolt.Options{ReadOnly: true})
893-
if err != nil {
894-
return err
895-
}
896-
defer db.Close()
897-
898-
// Print value.
899-
return db.View(func(tx *bolt.Tx) error {
900-
// Find bucket.
901-
lastBucket, err := findLastBucket(tx, buckets)
902-
if err != nil {
903-
return err
904-
}
905-
906-
// Find value for given key.
907-
val := lastBucket.Get(key)
908-
if val == nil {
909-
return fmt.Errorf("Error %w for key: %q hex: \"%x\"", ErrKeyNotFound, key, string(key))
910-
}
911-
912-
// TODO: In this particular case, it would be better to not terminate with '\n'
913-
return writelnBytes(cmd.Stdout, val, format)
914-
})
915-
}
916-
917-
// Usage returns the help message.
918-
func (cmd *getCommand) Usage() string {
919-
return strings.TrimLeft(`
920-
usage: bolt get PATH [BUCKET..] KEY
921-
922-
Print the value of the given key in the given (sub)bucket.
923-
924-
Additional options include:
925-
926-
--format
927-
Output format. One of: `+FORMAT_MODES+` (default=auto)
928-
--parse-format
929-
Input format (of key). One of: ascii-encoded|hex (default=ascii-encoded)"
930-
`, "\n")
931-
}
932-
933841
var benchBucketName = []byte("bench")
934842

935843
// benchCommand represents the "bench" command execution.

cmd/bbolt/main_test.go

Lines changed: 0 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -339,71 +339,6 @@ func TestKeysCommand_Run(t *testing.T) {
339339
})
340340
}
341341
}
342-
343-
// Ensure the "get" command can print the value of a key in a bucket.
344-
func TestGetCommand_Run(t *testing.T) {
345-
testCases := []struct {
346-
name string
347-
printable bool
348-
testBucket string
349-
testKey string
350-
expectedValue string
351-
}{
352-
{
353-
name: "printable data",
354-
printable: true,
355-
testBucket: "foo",
356-
testKey: "foo-1",
357-
expectedValue: "val-foo-1\n",
358-
},
359-
{
360-
name: "non printable data",
361-
printable: false,
362-
testBucket: "bar",
363-
testKey: "100001",
364-
expectedValue: hex.EncodeToString(convertInt64IntoBytes(100001)) + "\n",
365-
},
366-
}
367-
368-
for _, tc := range testCases {
369-
t.Run(tc.name, func(t *testing.T) {
370-
db := btesting.MustCreateDB(t)
371-
372-
if err := db.Update(func(tx *bolt.Tx) error {
373-
b, err := tx.CreateBucket([]byte(tc.testBucket))
374-
if err != nil {
375-
return err
376-
}
377-
if tc.printable {
378-
val := fmt.Sprintf("val-%s", tc.testKey)
379-
if err := b.Put([]byte(tc.testKey), []byte(val)); err != nil {
380-
return err
381-
}
382-
} else {
383-
if err := b.Put([]byte(tc.testKey), convertInt64IntoBytes(100001)); err != nil {
384-
return err
385-
}
386-
}
387-
return nil
388-
}); err != nil {
389-
t.Fatal(err)
390-
}
391-
db.Close()
392-
393-
defer requireDBNoChange(t, dbData(t, db.Path()), db.Path())
394-
395-
// Run the command.
396-
m := NewMain()
397-
if err := m.Run("get", db.Path(), tc.testBucket, tc.testKey); err != nil {
398-
t.Fatal(err)
399-
}
400-
actual := m.Stdout.String()
401-
assert.Equal(t, tc.expectedValue, actual)
402-
})
403-
}
404-
}
405-
406-
// Ensure the "pages" command neither panic, nor change the db file.
407342
func TestPagesCommand_Run(t *testing.T) {
408343
db := btesting.MustCreateDB(t)
409344

@@ -606,11 +541,6 @@ func TestCommands_Run_NoArgs(t *testing.T) {
606541
cmd string
607542
expErr error
608543
}{
609-
{
610-
name: "get",
611-
cmd: "get",
612-
expErr: main.ErrNotEnoughArgs,
613-
},
614544
{
615545
name: "keys",
616546
cmd: "keys",

0 commit comments

Comments
 (0)