Merge pull request #20 from heyitsanthony/readd-cli-commands

Add "buckets", "keys" and "get" CLI commands
pull/27/head
Anthony Romano 2017-08-11 11:23:52 -07:00 committed by GitHub
commit 3efc7f7ed5
2 changed files with 329 additions and 0 deletions

View File

@ -48,6 +48,18 @@ var (
// ErrPageIDRequired is returned when a required page id is not specified.
ErrPageIDRequired = errors.New("page id required")
// ErrBucketRequired is returned when a bucket is not specified.
ErrBucketRequired = errors.New("bucket required")
// ErrBucketNotFound is returned when a bucket is not found.
ErrBucketNotFound = errors.New("bucket not found")
// ErrKeyRequired is returned when a key is not specified.
ErrKeyRequired = errors.New("key required")
// ErrKeyNotFound is returned when a key is not found.
ErrKeyNotFound = errors.New("key not found")
)
// PageHeaderSize represents the size of the bolt.page header.
@ -94,14 +106,20 @@ func (m *Main) Run(args ...string) error {
return ErrUsage
case "bench":
return newBenchCommand(m).Run(args[1:]...)
case "buckets":
return newBucketsCommand(m).Run(args[1:]...)
case "check":
return newCheckCommand(m).Run(args[1:]...)
case "compact":
return newCompactCommand(m).Run(args[1:]...)
case "dump":
return newDumpCommand(m).Run(args[1:]...)
case "get":
return newGetCommand(m).Run(args[1:]...)
case "info":
return newInfoCommand(m).Run(args[1:]...)
case "keys":
return newKeysCommand(m).Run(args[1:]...)
case "page":
return newPageCommand(m).Run(args[1:]...)
case "pages":
@ -125,10 +143,15 @@ Usage:
The commands are:
bench run synthetic benchmark against bolt
buckets print a list of buckets
check verifies integrity of bolt database
compact copies a bolt database, compacting it in the process
dump print a hexidecimal dump of a single page
get print the value of a key in a bucket
info print basic info
keys print a list of keys in a bucket
help print this screen
page print one or more pages in human readable format
pages print list of pages with their types
stats iterate over all pages and generate usage stats
@ -865,6 +888,212 @@ experience corruption, please submit a ticket to the Bolt project page:
`, "\n")
}
// BucketsCommand represents the "buckets" command execution.
type BucketsCommand struct {
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
}
// NewBucketsCommand returns a BucketsCommand.
func newBucketsCommand(m *Main) *BucketsCommand {
return &BucketsCommand{
Stdin: m.Stdin,
Stdout: m.Stdout,
Stderr: m.Stderr,
}
}
// Run executes the command.
func (cmd *BucketsCommand) Run(args ...string) error {
// Parse flags.
fs := flag.NewFlagSet("", flag.ContinueOnError)
help := fs.Bool("h", false, "")
if err := fs.Parse(args); err != nil {
return err
} else if *help {
fmt.Fprintln(cmd.Stderr, cmd.Usage())
return ErrUsage
}
// Require database path.
path := fs.Arg(0)
if path == "" {
return ErrPathRequired
} else if _, err := os.Stat(path); os.IsNotExist(err) {
return ErrFileNotFound
}
// Open database.
db, err := bolt.Open(path, 0666, nil)
if err != nil {
return err
}
defer db.Close()
// Print buckets.
return db.View(func(tx *bolt.Tx) error {
return tx.ForEach(func(name []byte, _ *bolt.Bucket) error {
fmt.Fprintln(cmd.Stdout, string(name))
return nil
})
})
}
// Usage returns the help message.
func (cmd *BucketsCommand) Usage() string {
return strings.TrimLeft(`
usage: bolt buckets PATH
Print a list of buckets.
`, "\n")
}
// KeysCommand represents the "keys" command execution.
type KeysCommand struct {
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
}
// NewKeysCommand returns a KeysCommand.
func newKeysCommand(m *Main) *KeysCommand {
return &KeysCommand{
Stdin: m.Stdin,
Stdout: m.Stdout,
Stderr: m.Stderr,
}
}
// Run executes the command.
func (cmd *KeysCommand) Run(args ...string) error {
// Parse flags.
fs := flag.NewFlagSet("", flag.ContinueOnError)
help := fs.Bool("h", false, "")
if err := fs.Parse(args); err != nil {
return err
} else if *help {
fmt.Fprintln(cmd.Stderr, cmd.Usage())
return ErrUsage
}
// Require database path and bucket.
path, bucket := fs.Arg(0), fs.Arg(1)
if path == "" {
return ErrPathRequired
} else if _, err := os.Stat(path); os.IsNotExist(err) {
return ErrFileNotFound
} else if bucket == "" {
return ErrBucketRequired
}
// Open database.
db, err := bolt.Open(path, 0666, nil)
if err != nil {
return err
}
defer db.Close()
// Print keys.
return db.View(func(tx *bolt.Tx) error {
// Find bucket.
b := tx.Bucket([]byte(bucket))
if b == nil {
return ErrBucketNotFound
}
// Iterate over each key.
return b.ForEach(func(key, _ []byte) error {
fmt.Fprintln(cmd.Stdout, string(key))
return nil
})
})
}
// Usage returns the help message.
func (cmd *KeysCommand) Usage() string {
return strings.TrimLeft(`
usage: bolt keys PATH BUCKET
Print a list of keys in the given bucket.
`, "\n")
}
// GetCommand represents the "get" command execution.
type GetCommand struct {
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
}
// NewGetCommand returns a GetCommand.
func newGetCommand(m *Main) *GetCommand {
return &GetCommand{
Stdin: m.Stdin,
Stdout: m.Stdout,
Stderr: m.Stderr,
}
}
// Run executes the command.
func (cmd *GetCommand) Run(args ...string) error {
// Parse flags.
fs := flag.NewFlagSet("", flag.ContinueOnError)
help := fs.Bool("h", false, "")
if err := fs.Parse(args); err != nil {
return err
} else if *help {
fmt.Fprintln(cmd.Stderr, cmd.Usage())
return ErrUsage
}
// Require database path, bucket and key.
path, bucket, key := fs.Arg(0), fs.Arg(1), fs.Arg(2)
if path == "" {
return ErrPathRequired
} else if _, err := os.Stat(path); os.IsNotExist(err) {
return ErrFileNotFound
} else if bucket == "" {
return ErrBucketRequired
} else if key == "" {
return ErrKeyRequired
}
// Open database.
db, err := bolt.Open(path, 0666, nil)
if err != nil {
return err
}
defer db.Close()
// Print value.
return db.View(func(tx *bolt.Tx) error {
// Find bucket.
b := tx.Bucket([]byte(bucket))
if b == nil {
return ErrBucketNotFound
}
// Find value for given key.
val := b.Get([]byte(key))
if val == nil {
return ErrKeyNotFound
}
fmt.Fprintln(cmd.Stdout, string(val))
return nil
})
}
// Usage returns the help message.
func (cmd *GetCommand) Usage() string {
return strings.TrimLeft(`
usage: bolt get PATH BUCKET KEY
Print the value of the given key in the given bucket.
`, "\n")
}
var benchBucketName = []byte("bench")
// BenchCommand represents the "bench" command execution.

View File

@ -146,6 +146,106 @@ func TestStatsCommand_Run(t *testing.T) {
}
}
// Ensure the "buckets" command can print a list of buckets.
func TestBucketsCommand_Run(t *testing.T) {
db := MustOpen(0666, nil)
defer db.Close()
if err := db.Update(func(tx *bolt.Tx) error {
for _, name := range []string{"foo", "bar", "baz"} {
_, err := tx.CreateBucket([]byte(name))
if err != nil {
return err
}
}
return nil
}); err != nil {
t.Fatal(err)
}
db.DB.Close()
expected := "bar\nbaz\nfoo\n"
// Run the command.
m := NewMain()
if err := m.Run("buckets", db.Path); err != nil {
t.Fatal(err)
} else if actual := m.Stdout.String(); actual != expected {
t.Fatalf("unexpected stdout:\n\n%s", actual)
}
}
// Ensure the "keys" command can print a list of keys for a bucket.
func TestKeysCommand_Run(t *testing.T) {
db := MustOpen(0666, nil)
defer db.Close()
if err := db.Update(func(tx *bolt.Tx) error {
for _, name := range []string{"foo", "bar"} {
b, err := tx.CreateBucket([]byte(name))
if err != nil {
return err
}
for i := 0; i < 3; i++ {
key := fmt.Sprintf("%s-%d", name, i)
if err := b.Put([]byte(key), []byte{0}); err != nil {
return err
}
}
}
return nil
}); err != nil {
t.Fatal(err)
}
db.DB.Close()
expected := "foo-0\nfoo-1\nfoo-2\n"
// Run the command.
m := NewMain()
if err := m.Run("keys", db.Path, "foo"); err != nil {
t.Fatal(err)
} else if actual := m.Stdout.String(); actual != expected {
t.Fatalf("unexpected stdout:\n\n%s", actual)
}
}
// Ensure the "get" command can print the value of a key in a bucket.
func TestGetCommand_Run(t *testing.T) {
db := MustOpen(0666, nil)
defer db.Close()
if err := db.Update(func(tx *bolt.Tx) error {
for _, name := range []string{"foo", "bar"} {
b, err := tx.CreateBucket([]byte(name))
if err != nil {
return err
}
for i := 0; i < 3; i++ {
key := fmt.Sprintf("%s-%d", name, i)
val := fmt.Sprintf("val-%s-%d", name, i)
if err := b.Put([]byte(key), []byte(val)); err != nil {
return err
}
}
}
return nil
}); err != nil {
t.Fatal(err)
}
db.DB.Close()
expected := "val-foo-1\n"
// Run the command.
m := NewMain()
if err := m.Run("get", db.Path, "foo", "foo-1"); err != nil {
t.Fatal(err)
} else if actual := m.Stdout.String(); actual != expected {
t.Fatalf("unexpected stdout:\n\n%s", actual)
}
}
// Main represents a test wrapper for main.Main that records output.
type Main struct {
*main.Main