Compare commits

...

24 Commits
2.0 ... master

Author SHA1 Message Date
Brian Buller fcc13603da Merge branch 'master' of github.com:br0xen/boltbrowser 2023-05-31 09:37:31 -05:00
Brian Buller 305329c943
Merge pull request #62 from zhaojizhuang/master
change bolt vesion to go.etcd.io/bbolt 1.3.7
2023-05-31 09:36:31 -05:00
zhaojizhuang 54338be381 change bolt vesion to go.etcd.io/bbolt 1.3.7 2023-05-24 22:57:14 +08:00
Brian Buller b0aec79fcd Merge branch 'master' of ssh://git.bullercodeworks.com:2200/brian/boltbrowser 2023-05-24 07:37:35 -05:00
Brian Buller 1f901154b9 Add build directory to gitignore 2023-05-24 07:37:22 -05:00
Brian Buller 7a5543df99
Merge pull request #60 from Jamstah/buildtags
Update build tag to latest format
2023-05-15 07:49:37 -05:00
Brian Buller dc87af7195
Merge pull request #59 from Jamstah/import-value-from-file
Import value from file
2023-05-15 07:48:43 -05:00
James Hewitt e1b2bfab4e
Update build tag to latest format 2023-05-12 14:32:28 +01:00
James Hewitt 8a508a45ee
Add support to importing of value from a file 2023-05-12 14:27:40 +01:00
James Hewitt ab21f99955
Don't add a newline when exporting values 2023-05-12 14:22:33 +01:00
Brian Buller 184b3ef7cb Update links to screenshots & releases 2023-03-30 07:28:51 -05:00
Brian Buller 4d4716e825 Go mod tidy 2023-03-30 07:25:05 -05:00
Brian Buller ebb9186bbe Update go version 2023-03-30 07:24:40 -05:00
Brian Buller ab62644960 Update Dependencies 2023-03-30 07:12:16 -05:00
Brian Buller 7f10a81cec Don't duplicate root values when root is bucket. 2021-05-31 10:03:53 -05:00
Brian Buller 7718e0f038 Update links to binaries for v2.1 2021-05-31 09:39:19 -05:00
Brian Buller bbe5e92c9c Read/Edit DBs with key/value pairs in root
Not sure how 'good' this is, but apparently some people do it, so
we may as well support it.
2021-05-31 09:29:09 -05:00
Brian Buller 14bd1cdd41
Merge pull request #43 from sp0cket/master
fix drawHeader strings.Repeat crash
2020-04-08 15:29:24 -05:00
Aaron 10e63ac072 fix drawHeader strings.Repeat crash 2020-04-08 16:09:44 +08:00
Brian Buller 19bf4d25d2
Merge pull request #41 from knqyf263/filter
add filter
2020-03-30 16:24:09 -05:00
Brian Buller eacd685531
Merge pull request #39 from knqyf263/no_value
add -no-value option
2020-03-30 16:20:48 -05:00
knqyf263 12b395ae2a add filter 2019-12-23 16:57:25 +02:00
knqyf263 59a4e49fa4 add -no-value option 2019-12-23 13:26:45 +02:00
Brian Buller 2970b1c912
Update README.md
Add 2.0 Release Binaries
2019-10-17 11:56:24 -05:00
12 changed files with 299 additions and 100 deletions

6
.gitignore vendored
View File

@ -22,10 +22,16 @@ _cgo_export.*
_testmain.go _testmain.go
# Build Directory
build
# Binaries # Binaries
*.exe *.exe
boltbrowser boltbrowser
boltbrowser.*
# Test Database # Test Database
test.db test.db
*.db *.db

View File

@ -3,9 +3,9 @@ boltbrowser
A CLI Browser for BoltDB Files A CLI Browser for BoltDB Files
![Image of About Screen](http://bullercodeworks.com/boltbrowser/ss2.png) ![Image of About Screen](https://git.bullercodeworks.com/brian/boltbrowser/raw/branch/master/build/aboutscreen.png)
![Image of Main Browser](http://bullercodeworks.com/boltbrowser/ss1.png) ![Image of Main Browser](https://git.bullercodeworks.com/brian/boltbrowser/raw/branch/master/build/mainscreen.png)
Installing Installing
---------- ----------
@ -20,13 +20,7 @@ Then you'll have `boltbrowser` in your path.
Pre-built Binaries Pre-built Binaries
------------------ ------------------
Here are pre-built binaries: Pre-build binaries are available on the [Releases Page](https://github.com/br0xen/boltbrowser/releases).
* [Linux 64-bit](https://bullercodeworks.com/downloads/boltbrowser/boltbrowser.linux64)
* [Linux 32-bit](https://bullercodeworks.com/downloads/boltbrowser/boltbrowser.linux386)
* [Linux Arm](https://bullercodeworks.com/downloads/boltbrowser/boltbrowser.linuxarm)
* [Windows 64-bit](https://bullercodeworks.com/downloads/boltbrowser/boltbrowser.win64.exe)
* [Windows 32-bit](https://bullercodeworks.com/downloads/boltbrowser/boltbrowser.win386.exe)
* [Mac OS](https://bullercodeworks.com/downloads/boltbrowser/boltbrowser.darwin64)
Usage Usage
----- -----

View File

@ -6,7 +6,7 @@ import (
"os" "os"
"strings" "strings"
"github.com/boltdb/bolt" "go.etcd.io/bbolt"
) )
/* /*
@ -26,6 +26,7 @@ type BoltBucket struct {
parent *BoltBucket parent *BoltBucket
expanded bool expanded bool
errorFlag bool errorFlag bool
isRoot bool
} }
/* /*
@ -123,12 +124,12 @@ func (bd *BoltDB) getVisibleItemCount(path []string) (int, error) {
return vis, retErr return vis, retErr
} }
func (bd *BoltDB) buildVisiblePathSlice() ([][]string, error) { func (bd *BoltDB) buildVisiblePathSlice(filter string) ([][]string, error) {
var retSlice [][]string var retSlice [][]string
var retErr error var retErr error
// The root path, recurse for root buckets // The root path, recurse for root buckets
for i := range bd.buckets { for i := range bd.buckets {
bktS, bktErr := bd.buckets[i].buildVisiblePathSlice([]string{}) bktS, bktErr := bd.buckets[i].buildVisiblePathSlice([]string{}, filter)
if bktErr == nil { if bktErr == nil {
retSlice = append(retSlice, bktS...) retSlice = append(retSlice, bktS...)
} else { } else {
@ -139,8 +140,30 @@ func (bd *BoltDB) buildVisiblePathSlice() ([][]string, error) {
return retSlice, retErr return retSlice, retErr
} }
func (bd *BoltDB) getPrevVisiblePath(path []string) []string { func (bd *BoltDB) isVisiblePath(path []string, filter string) bool {
visPaths, err := bd.buildVisiblePathSlice() visPaths, err := bd.buildVisiblePathSlice(filter)
if err != nil {
return false
}
for _, pth := range visPaths {
if len(pth) != len(path) {
continue
}
isVisible := true
for i := range path {
if path[i] != pth[i] {
isVisible = false
break
}
}
if isVisible {
return true
}
}
return false
}
func (bd *BoltDB) getPrevVisiblePath(path []string, filter string) []string {
visPaths, err := bd.buildVisiblePathSlice(filter)
if path == nil { if path == nil {
if len(visPaths) > 0 { if len(visPaths) > 0 {
return visPaths[len(visPaths)-1] return visPaths[len(visPaths)-1]
@ -163,8 +186,8 @@ func (bd *BoltDB) getPrevVisiblePath(path []string) []string {
} }
return nil return nil
} }
func (bd *BoltDB) getNextVisiblePath(path []string) []string { func (bd *BoltDB) getNextVisiblePath(path []string, filter string) []string {
visPaths, err := bd.buildVisiblePathSlice() visPaths, err := bd.buildVisiblePathSlice(filter)
if path == nil { if path == nil {
if len(visPaths) > 0 { if len(visPaths) > 0 {
return visPaths[0] return visPaths[0]
@ -245,8 +268,8 @@ func (bd *BoltDB) syncOpenBuckets(shadow *BoltDB) {
func (bd *BoltDB) refreshDatabase() *BoltDB { func (bd *BoltDB) refreshDatabase() *BoltDB {
// Reload the database into memBolt // Reload the database into memBolt
memBolt = new(BoltDB) memBolt = new(BoltDB)
db.View(func(tx *bolt.Tx) error { db.View(func(tx *bbolt.Tx) error {
return tx.ForEach(func(nm []byte, b *bolt.Bucket) error { err := tx.ForEach(func(nm []byte, b *bbolt.Bucket) error {
bb, err := readBucket(b) bb, err := readBucket(b)
if err == nil { if err == nil {
bb.name = string(nm) bb.name = string(nm)
@ -256,6 +279,19 @@ func (bd *BoltDB) refreshDatabase() *BoltDB {
} }
return err return err
}) })
if err != nil {
memBolt = new(BoltDB)
// Check if there are key/values directly in the root
bb, err := readBucket(tx.Cursor().Bucket())
if err == nil {
bb.isRoot = true
bb.expanded = true
memBolt.buckets = append(memBolt.buckets, *bb)
return nil
}
return err
}
return err
}) })
return memBolt return memBolt
} }
@ -274,14 +310,14 @@ func (b *BoltBucket) GetPath() []string {
buildVisiblePathSlice builds a slice of string slices containing all visible paths in this bucket buildVisiblePathSlice builds a slice of string slices containing all visible paths in this bucket
The passed prefix is the path leading to the current bucket The passed prefix is the path leading to the current bucket
*/ */
func (b *BoltBucket) buildVisiblePathSlice(prefix []string) ([][]string, error) { func (b *BoltBucket) buildVisiblePathSlice(prefix []string, filter string) ([][]string, error) {
var retSlice [][]string var retSlice [][]string
var retErr error var retErr error
retSlice = append(retSlice, append(prefix, b.name)) retSlice = append(retSlice, append(prefix, b.name))
if b.expanded { if b.expanded {
// Add subbuckets // Add subbuckets
for i := range b.buckets { for i := range b.buckets {
bktS, bktErr := b.buckets[i].buildVisiblePathSlice(append(prefix, b.name)) bktS, bktErr := b.buckets[i].buildVisiblePathSlice(append(prefix, b.name), filter)
if bktErr != nil { if bktErr != nil {
return retSlice, bktErr return retSlice, bktErr
} }
@ -289,6 +325,9 @@ func (b *BoltBucket) buildVisiblePathSlice(prefix []string) ([][]string, error)
} }
// Add pairs // Add pairs
for i := range b.pairs { for i := range b.pairs {
if filter != "" && !strings.Contains(b.pairs[i].key, filter) {
continue
}
retSlice = append(retSlice, append(append(prefix, b.name), b.pairs[i].key)) retSlice = append(retSlice, append(append(prefix, b.name), b.pairs[i].key))
} }
} }
@ -367,7 +406,7 @@ func deleteKey(path []string) error {
if AppArgs.ReadOnly { if AppArgs.ReadOnly {
return errors.New("DB is in Read-Only Mode") return errors.New("DB is in Read-Only Mode")
} }
err := db.Update(func(tx *bolt.Tx) error { err := db.Update(func(tx *bbolt.Tx) error {
// len(b.path)-1 is the key we need to delete, // len(b.path)-1 is the key we need to delete,
// the rest are buckets leading to that key // the rest are buckets leading to that key
if len(path) == 1 { if len(path) == 1 {
@ -375,6 +414,10 @@ func deleteKey(path []string) error {
return tx.DeleteBucket([]byte(path[0])) return tx.DeleteBucket([]byte(path[0]))
} }
b := tx.Bucket([]byte(path[0])) b := tx.Bucket([]byte(path[0]))
if b == nil {
// Invalid path, try for the root bucket
b = tx.Cursor().Bucket()
}
if b != nil { if b != nil {
if len(path) > 1 { if len(path) > 1 {
for i := range path[1 : len(path)-1] { for i := range path[1 : len(path)-1] {
@ -399,8 +442,11 @@ func deleteKey(path []string) error {
return err return err
} }
func readBucket(b *bolt.Bucket) (*BoltBucket, error) { func readBucket(b *bbolt.Bucket) (*BoltBucket, error) {
bb := new(BoltBucket) bb := new(BoltBucket)
if b == nil {
return nil, errors.New("No bucket passed")
}
b.ForEach(func(k, v []byte) error { b.ForEach(func(k, v []byte) error {
if v == nil { if v == nil {
tb, err := readBucket(b.Bucket(k)) tb, err := readBucket(b.Bucket(k))
@ -425,13 +471,17 @@ func renameBucket(path []string, name string) error {
return nil return nil
} }
var bb *BoltBucket // For caching the current bucket var bb *BoltBucket // For caching the current bucket
err := db.View(func(tx *bolt.Tx) error { err := db.View(func(tx *bbolt.Tx) error {
// len(b.path)-1 is the key we need to delete, // len(b.path)-1 is the key we need to delete,
// the rest are buckets leading to that key // the rest are buckets leading to that key
b := tx.Bucket([]byte(path[0])) b := tx.Bucket([]byte(path[0]))
if b == nil {
// Invalid path, try for the root bucket
b = tx.Cursor().Bucket()
}
if b != nil { if b != nil {
if len(path) > 1 { if len(path) > 1 {
for i := range path[1:len(path)] { for i := range path[1:] {
b = b.Bucket([]byte(path[i+1])) b = b.Bucket([]byte(path[i+1]))
if b == nil { if b == nil {
return errors.New("renameBucket: Invalid Path") return errors.New("renameBucket: Invalid Path")
@ -475,10 +525,14 @@ func updatePairKey(path []string, k string) error {
if AppArgs.ReadOnly { if AppArgs.ReadOnly {
return errors.New("DB is in Read-Only Mode") return errors.New("DB is in Read-Only Mode")
} }
err := db.Update(func(tx *bolt.Tx) error { err := db.Update(func(tx *bbolt.Tx) error {
// len(b.path)-1 is the key for the pair we're updating, // len(b.path)-1 is the key for the pair we're updating,
// the rest are buckets leading to that key // the rest are buckets leading to that key
b := tx.Bucket([]byte(path[0])) b := tx.Bucket([]byte(path[0]))
if b == nil {
// Invalid path, try for the root bucket
b = tx.Cursor().Bucket()
}
if b != nil { if b != nil {
if len(path) > 0 { if len(path) > 0 {
for i := range path[1 : len(path)-1] { for i := range path[1 : len(path)-1] {
@ -507,10 +561,14 @@ func updatePairValue(path []string, v string) error {
if AppArgs.ReadOnly { if AppArgs.ReadOnly {
return errors.New("DB is in Read-Only Mode") return errors.New("DB is in Read-Only Mode")
} }
err := db.Update(func(tx *bolt.Tx) error { err := db.Update(func(tx *bbolt.Tx) error {
// len(b.GetPath())-1 is the key for the pair we're updating, // len(b.GetPath())-1 is the key for the pair we're updating,
// the rest are buckets leading to that key // the rest are buckets leading to that key
b := tx.Bucket([]byte(path[0])) b := tx.Bucket([]byte(path[0]))
if b == nil {
// Invalid path, try for the root bucket
b = tx.Cursor().Bucket()
}
if b != nil { if b != nil {
if len(path) > 0 { if len(path) > 0 {
for i := range path[1 : len(path)-1] { for i := range path[1 : len(path)-1] {
@ -534,8 +592,8 @@ func insertBucket(path []string, n string) error {
return errors.New("DB is in Read-Only Mode") return errors.New("DB is in Read-Only Mode")
} }
// Inserts a new bucket named 'n' at 'path' // Inserts a new bucket named 'n' at 'path'
err := db.Update(func(tx *bolt.Tx) error { err := db.Update(func(tx *bbolt.Tx) error {
if len(path) == 0 { if len(path) == 0 || path[0] == "" {
// insert at root // insert at root
_, err := tx.CreateBucket([]byte(n)) _, err := tx.CreateBucket([]byte(n))
if err != nil { if err != nil {
@ -574,13 +632,17 @@ func insertPair(path []string, k string, v string) error {
return errors.New("DB is in Read-Only Mode") return errors.New("DB is in Read-Only Mode")
} }
// Insert a new pair k => v at path // Insert a new pair k => v at path
err := db.Update(func(tx *bolt.Tx) error { err := db.Update(func(tx *bbolt.Tx) error {
if len(path) == 0 { if len(path) == 0 {
// We cannot insert a pair at root // We cannot insert a pair at root
return errors.New("insertPair: Cannot insert pair at root") return errors.New("insertPair: Cannot insert pair at root")
} }
var err error var err error
b := tx.Bucket([]byte(path[0])) b := tx.Bucket([]byte(path[0]))
if b == nil {
// Invalid path, try for the root bucket
b = tx.Cursor().Bucket()
}
if b != nil { if b != nil {
if len(path) > 0 { if len(path) > 0 {
for i := 1; i < len(path); i++ { for i := 1; i < len(path); i++ {
@ -601,10 +663,14 @@ func insertPair(path []string, k string, v string) error {
} }
func exportValue(path []string, fName string) error { func exportValue(path []string, fName string) error {
return db.View(func(tx *bolt.Tx) error { return db.View(func(tx *bbolt.Tx) error {
// len(b.path)-1 is the key whose value we want to export // len(b.path)-1 is the key whose value we want to export
// the rest are buckets leading to that key // the rest are buckets leading to that key
b := tx.Bucket([]byte(path[0])) b := tx.Bucket([]byte(path[0]))
if b == nil {
// Invalid path, try for the root bucket
b = tx.Cursor().Bucket()
}
if b != nil { if b != nil {
if len(path) > 1 { if len(path) > 1 {
for i := range path[1 : len(path)-1] { for i := range path[1 : len(path)-1] {
@ -616,17 +682,21 @@ func exportValue(path []string, fName string) error {
} }
bk := []byte(path[len(path)-1]) bk := []byte(path[len(path)-1])
v := b.Get(bk) v := b.Get(bk)
return writeToFile(fName, string(v)+"\n", os.O_CREATE|os.O_WRONLY|os.O_TRUNC) return writeToFile(fName, string(v), os.O_CREATE|os.O_WRONLY|os.O_TRUNC)
} }
return errors.New("exportValue: Invalid Bucket") return errors.New("exportValue: Invalid Bucket")
}) })
} }
func exportJSON(path []string, fName string) error { func exportJSON(path []string, fName string) error {
return db.View(func(tx *bolt.Tx) error { return db.View(func(tx *bbolt.Tx) error {
// len(b.path)-1 is the key whose value we want to export // len(b.path)-1 is the key whose value we want to export
// the rest are buckets leading to that key // the rest are buckets leading to that key
b := tx.Bucket([]byte(path[0])) b := tx.Bucket([]byte(path[0]))
if b == nil {
// Invalid path, try for the root bucket
b = tx.Cursor().Bucket()
}
if b != nil { if b != nil {
if len(path) > 1 { if len(path) > 1 {
for i := range path[1 : len(path)-1] { for i := range path[1 : len(path)-1] {
@ -649,7 +719,7 @@ func exportJSON(path []string, fName string) error {
}) })
} }
func genJSONString(b *bolt.Bucket) string { func genJSONString(b *bbolt.Bucket) string {
ret := "{" ret := "{"
b.ForEach(func(k, v []byte) error { b.ForEach(func(k, v []byte) error {
ret = fmt.Sprintf("%s\"%s\":", ret, string(k)) ret = fmt.Sprintf("%s\"%s\":", ret, string(k))
@ -686,3 +756,36 @@ func writeToFile(fn, s string, mode int) error {
} }
return nil return nil
} }
func importValue(path []string, fName string) error {
if AppArgs.ReadOnly {
return errors.New("DB is in Read-Only Mode")
}
return db.Update(func(tx *bbolt.Tx) error {
// len(b.GetPath())-1 is the key for the pair we're updating,
// the rest are buckets leading to that key
b := tx.Bucket([]byte(path[0]))
if b == nil {
// Invalid path, try for the root bucket
b = tx.Cursor().Bucket()
}
if b != nil {
if len(path) > 0 {
for i := range path[1 : len(path)-1] {
b = b.Bucket([]byte(path[i+1]))
if b == nil {
return errors.New("updatePairValue: Invalid Path")
}
}
}
// Now update the last key in the path
bk := []byte(path[len(path)-1])
v, err := os.ReadFile(fName)
if err != nil {
return err
}
return b.Put(bk, v)
}
return errors.New("importValue: Invalid Bucket")
})
}

BIN
build/aboutscreen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
build/mainscreen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

13
go.mod
View File

@ -1,11 +1,14 @@
module github.com/br0xen/boltbrowser module github.com/br0xen/boltbrowser
require ( require (
github.com/boltdb/bolt v1.3.1
github.com/br0xen/termbox-util v0.0.0-20170904143325-de1d4c83380e github.com/br0xen/termbox-util v0.0.0-20170904143325-de1d4c83380e
github.com/mattn/go-runewidth v0.0.4 // indirect github.com/nsf/termbox-go v1.1.1
github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e go.etcd.io/bbolt v1.3.7
golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed // indirect
) )
go 1.13 require (
github.com/mattn/go-runewidth v0.0.9 // indirect
golang.org/x/sys v0.8.0 // indirect
)
go 1.20

20
go.sum
View File

@ -1,10 +1,14 @@
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/br0xen/termbox-util v0.0.0-20170904143325-de1d4c83380e h1:PF4gYXcZfTbAoAk5DPZcvjmq8gyg4gpcmWdT8W+0X1c= github.com/br0xen/termbox-util v0.0.0-20170904143325-de1d4c83380e h1:PF4gYXcZfTbAoAk5DPZcvjmq8gyg4gpcmWdT8W+0X1c=
github.com/br0xen/termbox-util v0.0.0-20170904143325-de1d4c83380e/go.mod h1:x9wJlgOj74OFTOBwXOuO8pBguW37EgYNx51Dbjkfzo4= github.com/br0xen/termbox-util v0.0.0-20170904143325-de1d4c83380e/go.mod h1:x9wJlgOj74OFTOBwXOuO8pBguW37EgYNx51Dbjkfzo4=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e h1:fvw0uluMptljaRKSU8459cJ4bmi3qUYyMs5kzpic2fY= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY=
golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed h1:5TJcLJn2a55mJjzYk0yOoqN8X1OdvBDUnaZaKKyQtkY= github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=
golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

16
main.go
View File

@ -7,15 +7,15 @@ import (
"strings" "strings"
"time" "time"
"github.com/boltdb/bolt"
"github.com/nsf/termbox-go" "github.com/nsf/termbox-go"
"go.etcd.io/bbolt"
) )
var ProgramName = "boltbrowser" var ProgramName = "boltbrowser"
var VersionNum = 2.0 var VersionNum = 2.0
var databaseFiles []string var databaseFiles []string
var db *bolt.DB var db *bbolt.DB
var memBolt *BoltDB var memBolt *BoltDB
var currentFilename string var currentFilename string
@ -25,6 +25,7 @@ const DefaultDBOpenTimeout = time.Second
var AppArgs struct { var AppArgs struct {
DBOpenTimeout time.Duration DBOpenTimeout time.Duration
ReadOnly bool ReadOnly bool
NoValue bool
} }
func init() { func init() {
@ -63,6 +64,10 @@ func parseArgs() {
if val == "true" { if val == "true" {
AppArgs.ReadOnly = true AppArgs.ReadOnly = true
} }
case "-no-value":
if val == "true" {
AppArgs.NoValue = true
}
case "-help": case "-help":
printUsage(nil) printUsage(nil)
default: default:
@ -73,6 +78,8 @@ func parseArgs() {
switch parms[i] { switch parms[i] {
case "-readonly", "-ro": case "-readonly", "-ro":
AppArgs.ReadOnly = true AppArgs.ReadOnly = true
case "-no-value":
AppArgs.NoValue = true
case "-help": case "-help":
printUsage(nil) printUsage(nil)
default: default:
@ -89,6 +96,7 @@ func printUsage(err error) {
fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] <filename(s)>\nOptions:\n", ProgramName) fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] <filename(s)>\nOptions:\n", ProgramName)
fmt.Fprintf(os.Stderr, " -timeout=duration\n DB file open timeout (default 1s)\n") fmt.Fprintf(os.Stderr, " -timeout=duration\n DB file open timeout (default 1s)\n")
fmt.Fprintf(os.Stderr, " -ro, -readonly \n Open the DB in read-only mode\n") fmt.Fprintf(os.Stderr, " -ro, -readonly \n Open the DB in read-only mode\n")
fmt.Fprintf(os.Stderr, " -no-value \n Do not display a value in left pane\n")
} }
func main() { func main() {
@ -106,8 +114,8 @@ func main() {
for _, databaseFile := range databaseFiles { for _, databaseFile := range databaseFiles {
currentFilename = databaseFile currentFilename = databaseFile
db, err = bolt.Open(databaseFile, 0600, &bolt.Options{Timeout: AppArgs.DBOpenTimeout}) db, err = bbolt.Open(databaseFile, 0600, &bbolt.Options{Timeout: AppArgs.DBOpenTimeout})
if err == bolt.ErrTimeout { if err == bbolt.ErrTimeout {
termbox.Close() termbox.Close()
fmt.Printf("File %s is locked. Make sure it's not used by another app and try again\n", databaseFile) fmt.Printf("File %s is locked. Make sure it's not used by another app and try again\n", databaseFile)
os.Exit(1) os.Exit(1)

View File

@ -1,3 +1,4 @@
//go:build !windows
// +build !windows // +build !windows
package main package main

View File

@ -1,4 +1,5 @@
// +build windows //go:build windows
package main package main
// Windows doesn't support process backgrounding like *nix. // Windows doesn't support process backgrounding like *nix.

View File

@ -3,7 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"github.com/br0xen/termbox-util" termboxUtil "github.com/br0xen/termbox-util"
"github.com/nsf/termbox-go" "github.com/nsf/termbox-go"
) )
@ -111,9 +111,9 @@ func (screen *AboutScreen) drawScreen(style Style) {
{"e", "edit value of pair"}, {"e", "edit value of pair"},
{"r", "rename pair/bucket"}, {"r", "rename pair/bucket"},
{"", ""}, {"", ""},
{"", ""},
{"D", "delete item"}, {"D", "delete item"},
{"x,X", "export as string/json to file"}, {"x,X", "export as string/json to file"},
{"i", "import file to value of pair"},
{"", ""}, {"", ""},
{"?", "this screen"}, {"?", "this screen"},
{"q", "quit program"}, {"q", "quit program"},

View File

@ -3,6 +3,7 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"path/filepath"
"strings" "strings"
"time" "time"
@ -31,6 +32,7 @@ type BrowserScreen struct {
currentPath []string currentPath []string
currentType int currentType int
message string message string
filter string
mode BrowserMode mode BrowserMode
inputModal *termboxUtil.InputModal inputModal *termboxUtil.InputModal
confirmModal *termboxUtil.ConfirmModal confirmModal *termboxUtil.ConfirmModal
@ -51,6 +53,7 @@ const (
modeChange = 32 // 0000 0010 0000 modeChange = 32 // 0000 0010 0000
modeChangeKey = 33 // 0000 0010 0001 modeChangeKey = 33 // 0000 0010 0001
modeChangeVal = 34 // 0000 0010 0010 modeChangeVal = 34 // 0000 0010 0010
modeFilter = 35 // 0100 0010 0011
modeInsert = 64 // 0000 0100 0000 modeInsert = 64 // 0000 0100 0000
modeInsertBucket = 65 // 0000 0100 0001 modeInsertBucket = 65 // 0000 0100 0001
modeInsertPair = 68 // 0000 0100 0100 modeInsertPair = 68 // 0000 0100 0100
@ -58,9 +61,10 @@ const (
modeInsertPairVal = 70 // 0000 0100 0110 modeInsertPairVal = 70 // 0000 0100 0110
modeDelete = 256 // 0001 0000 0000 modeDelete = 256 // 0001 0000 0000
modeModToParent = 8 // 0000 0000 1000 modeModToParent = 8 // 0000 0000 1000
modeExport = 512 // 0010 0000 0000 modeIO = 512 // 0010 0000 0000
modeExportValue = 513 // 0010 0000 0001 modeIOExportValue = 513 // 0010 0000 0001
modeExportJSON = 514 // 0010 0000 0010 modeIOExportJSON = 514 // 0010 0000 0010
modeIOImportValue = 516 // 0010 0000 0100
) )
/* /*
@ -85,8 +89,8 @@ func (screen *BrowserScreen) handleKeyEvent(event termbox.Event) int {
return screen.handleInsertKeyEvent(event) return screen.handleInsertKeyEvent(event)
} else if screen.mode == modeDelete { } else if screen.mode == modeDelete {
return screen.handleDeleteKeyEvent(event) return screen.handleDeleteKeyEvent(event)
} else if screen.mode&modeExport == modeExport { } else if screen.mode&modeIO == modeIO {
return screen.handleExportKeyEvent(event) return screen.handleIOKeyEvent(event)
} }
return BrowserScreenIndex return BrowserScreenIndex
} }
@ -102,11 +106,11 @@ func (screen *BrowserScreen) handleBrowseKeyEvent(event termbox.Event) int {
} else if event.Ch == 'g' { } else if event.Ch == 'g' {
// Jump to Beginning // Jump to Beginning
screen.currentPath = screen.db.getNextVisiblePath(nil) screen.currentPath = screen.db.getNextVisiblePath(nil, screen.filter)
} else if event.Ch == 'G' { } else if event.Ch == 'G' {
// Jump to End // Jump to End
screen.currentPath = screen.db.getPrevVisiblePath(nil) screen.currentPath = screen.db.getPrevVisiblePath(nil, screen.filter)
} else if event.Key == termbox.KeyCtrlR { } else if event.Key == termbox.KeyCtrlR {
screen.refreshDatabase() screen.refreshDatabase()
@ -155,6 +159,8 @@ func (screen *BrowserScreen) handleBrowseKeyEvent(event termbox.Event) int {
} else if p != nil { } else if p != nil {
screen.startEditItem() screen.startEditItem()
} }
} else if event.Ch == '/' {
screen.startFilter()
} else if event.Ch == 'r' { } else if event.Ch == 'r' {
screen.startRenameItem() screen.startRenameItem()
@ -199,11 +205,14 @@ func (screen *BrowserScreen) handleBrowseKeyEvent(event termbox.Event) int {
} else if event.Ch == 'D' { } else if event.Ch == 'D' {
screen.startDeleteItem() screen.startDeleteItem()
} else if event.Ch == 'x' { } else if event.Ch == 'x' {
// Export Value // Export Value to a file
screen.startExportValue() screen.startExportValue()
} else if event.Ch == 'X' { } else if event.Ch == 'X' {
// Export Key/Value (or Bucket) as JSON // Export Key/Value (or Bucket) as JSON
screen.startExportJSON() screen.startExportJSON()
} else if event.Ch == 'i' {
// Import value from a file
screen.startImportValue()
} }
return BrowserScreenIndex return BrowserScreenIndex
} }
@ -216,6 +225,12 @@ func (screen *BrowserScreen) handleInputKeyEvent(event termbox.Event) int {
screen.inputModal.HandleEvent(event) screen.inputModal.HandleEvent(event)
if screen.inputModal.IsDone() { if screen.inputModal.IsDone() {
b, p, _ := screen.db.getGenericFromPath(screen.currentPath) b, p, _ := screen.db.getGenericFromPath(screen.currentPath)
if screen.mode == modeFilter {
screen.filter = screen.inputModal.GetValue()
if !screen.db.isVisiblePath(screen.currentPath, screen.filter) {
screen.currentPath = screen.currentPath[:len(screen.currentPath)-1]
}
}
if b != nil { if b != nil {
if screen.mode == modeChangeKey { if screen.mode == modeChangeKey {
newName := screen.inputModal.GetValue() newName := screen.inputModal.GetValue()
@ -231,7 +246,7 @@ func (screen *BrowserScreen) handleInputKeyEvent(event termbox.Event) int {
} else if p != nil { } else if p != nil {
if screen.mode == modeChangeKey { if screen.mode == modeChangeKey {
newKey := screen.inputModal.GetValue() newKey := screen.inputModal.GetValue()
if updatePairKey(screen.currentPath, newKey) != nil { if err := updatePairKey(screen.currentPath, newKey); err != nil {
screen.setMessage("Error occurred updating Pair.") screen.setMessage("Error occurred updating Pair.")
} else { } else {
p.key = newKey p.key = newKey
@ -241,7 +256,7 @@ func (screen *BrowserScreen) handleInputKeyEvent(event termbox.Event) int {
} }
} else if screen.mode == modeChangeVal { } else if screen.mode == modeChangeVal {
newVal := screen.inputModal.GetValue() newVal := screen.inputModal.GetValue()
if updatePairValue(screen.currentPath, newVal) != nil { if err := updatePairValue(screen.currentPath, newVal); err != nil {
screen.setMessage("Error occurred updating Pair.") screen.setMessage("Error occurred updating Pair.")
} else { } else {
p.val = newVal p.val = newVal
@ -261,8 +276,8 @@ func (screen *BrowserScreen) handleDeleteKeyEvent(event termbox.Event) int {
screen.confirmModal.HandleEvent(event) screen.confirmModal.HandleEvent(event)
if screen.confirmModal.IsDone() { if screen.confirmModal.IsDone() {
if screen.confirmModal.IsAccepted() { if screen.confirmModal.IsAccepted() {
holdNextPath := screen.db.getNextVisiblePath(screen.currentPath) holdNextPath := screen.db.getNextVisiblePath(screen.currentPath, screen.filter)
holdPrevPath := screen.db.getPrevVisiblePath(screen.currentPath) holdPrevPath := screen.db.getPrevVisiblePath(screen.currentPath, screen.filter)
if deleteKey(screen.currentPath) == nil { if deleteKey(screen.currentPath) == nil {
screen.refreshDatabase() screen.refreshDatabase()
// Move the current path endpoint appropriately // Move the current path endpoint appropriately
@ -363,7 +378,7 @@ func (screen *BrowserScreen) handleInsertKeyEvent(event termbox.Event) int {
return BrowserScreenIndex return BrowserScreenIndex
} }
func (screen *BrowserScreen) handleExportKeyEvent(event termbox.Event) int { func (screen *BrowserScreen) handleIOKeyEvent(event termbox.Event) int {
if event.Key == termbox.KeyEsc { if event.Key == termbox.KeyEsc {
screen.mode = modeBrowse screen.mode = modeBrowse
screen.inputModal.Clear() screen.inputModal.Clear()
@ -372,24 +387,33 @@ func (screen *BrowserScreen) handleExportKeyEvent(event termbox.Event) int {
if screen.inputModal.IsDone() { if screen.inputModal.IsDone() {
b, p, _ := screen.db.getGenericFromPath(screen.currentPath) b, p, _ := screen.db.getGenericFromPath(screen.currentPath)
fileName := screen.inputModal.GetValue() fileName := screen.inputModal.GetValue()
if screen.mode&modeExportValue == modeExportValue { if screen.mode&modeIOExportValue == modeIOExportValue {
// Exporting the value // Exporting the value
if p != nil { if p != nil {
if err := exportValue(screen.currentPath, fileName); err != nil { if err := exportValue(screen.currentPath, fileName); err != nil {
//screen.setMessage("Error Exporting to file " + fileName + ".") //screen.setMessage("Error exporting to file " + fileName + ".")
screen.setMessage(err.Error()) screen.setMessage(err.Error())
} else { } else {
screen.setMessage("Value exported to file: " + fileName) screen.setMessage("Value exported to file: " + fileName)
} }
} }
} else if screen.mode&modeExportJSON == modeExportJSON { } else if screen.mode&modeIOExportJSON == modeIOExportJSON {
if b != nil || p != nil { if b != nil || p != nil {
if exportJSON(screen.currentPath, fileName) != nil { if exportJSON(screen.currentPath, fileName) != nil {
screen.setMessage("Error Exporting to file " + fileName + ".") screen.setMessage("Error exporting to file " + fileName + ".")
} else { } else {
screen.setMessage("Value exported to file: " + fileName) screen.setMessage("Value exported to file: " + fileName)
} }
} }
} else if screen.mode&modeIOImportValue == modeIOImportValue {
if p != nil {
if err := importValue(screen.currentPath, fileName); err != nil {
screen.setMessage(err.Error())
} else {
screen.setMessage("Value imported from file: " + fileName)
screen.refreshDatabase()
}
}
} }
screen.mode = modeBrowse screen.mode = modeBrowse
screen.inputModal.Clear() screen.inputModal.Clear()
@ -400,7 +424,7 @@ func (screen *BrowserScreen) handleExportKeyEvent(event termbox.Event) int {
func (screen *BrowserScreen) jumpCursorUp(distance int) bool { func (screen *BrowserScreen) jumpCursorUp(distance int) bool {
// Jump up 'distance' lines // Jump up 'distance' lines
visPaths, err := screen.db.buildVisiblePathSlice() visPaths, err := screen.db.buildVisiblePathSlice(screen.filter)
if err == nil { if err == nil {
findPath := screen.currentPath findPath := screen.currentPath
for idx, pth := range visPaths { for idx, pth := range visPaths {
@ -426,13 +450,13 @@ func (screen *BrowserScreen) jumpCursorUp(distance int) bool {
} }
} }
if isCurPath { if isCurPath {
screen.currentPath = screen.db.getNextVisiblePath(nil) screen.currentPath = screen.db.getNextVisiblePath(nil, screen.filter)
} }
} }
return true return true
} }
func (screen *BrowserScreen) jumpCursorDown(distance int) bool { func (screen *BrowserScreen) jumpCursorDown(distance int) bool {
visPaths, err := screen.db.buildVisiblePathSlice() visPaths, err := screen.db.buildVisiblePathSlice(screen.filter)
if err == nil { if err == nil {
findPath := screen.currentPath findPath := screen.currentPath
for idx, pth := range visPaths { for idx, pth := range visPaths {
@ -459,14 +483,14 @@ func (screen *BrowserScreen) jumpCursorDown(distance int) bool {
} }
} }
if isCurPath { if isCurPath {
screen.currentPath = screen.db.getNextVisiblePath(nil) screen.currentPath = screen.db.getNextVisiblePath(nil, screen.filter)
} }
} }
return true return true
} }
func (screen *BrowserScreen) moveCursorUp() bool { func (screen *BrowserScreen) moveCursorUp() bool {
newPath := screen.db.getPrevVisiblePath(screen.currentPath) newPath := screen.db.getPrevVisiblePath(screen.currentPath, screen.filter)
if newPath != nil { if newPath != nil {
screen.currentPath = newPath screen.currentPath = newPath
return true return true
@ -474,7 +498,7 @@ func (screen *BrowserScreen) moveCursorUp() bool {
return false return false
} }
func (screen *BrowserScreen) moveCursorDown() bool { func (screen *BrowserScreen) moveCursorDown() bool {
newPath := screen.db.getNextVisiblePath(screen.currentPath) newPath := screen.db.getNextVisiblePath(screen.currentPath, screen.filter)
if newPath != nil { if newPath != nil {
screen.currentPath = newPath screen.currentPath = newPath
return true return true
@ -524,10 +548,22 @@ func (screen *BrowserScreen) drawScreen(style Style) {
func (screen *BrowserScreen) drawHeader(style Style) { func (screen *BrowserScreen) drawHeader(style Style) {
width, _ := termbox.Size() width, _ := termbox.Size()
headerString := ProgramName + ": " + currentFilename headerStringLen := func(fileName string) int {
spaces := strings.Repeat(" ", ((width-len(headerString))/2)+1) return len(ProgramName) + len(fileName) + 1
}
headerFileName := currentFilename
if headerStringLen(headerFileName) > width {
headerFileName = filepath.Base(headerFileName)
}
headerString := ProgramName + ": " + headerFileName
count := ((width - len(headerString)) / 2) + 1
if count < 0 {
count = 0
}
spaces := strings.Repeat(" ", count)
termboxUtil.DrawStringAtPoint(fmt.Sprintf("%s%s%s", spaces, headerString, spaces), 0, 0, style.titleFg, style.titleBg) termboxUtil.DrawStringAtPoint(fmt.Sprintf("%s%s%s", spaces, headerString, spaces), 0, 0, style.titleFg, style.titleBg)
} }
func (screen *BrowserScreen) drawFooter(style Style) { func (screen *BrowserScreen) drawFooter(style Style) {
if screen.messageTimeout > 0 && time.Since(screen.messageTime) > screen.messageTimeout { if screen.messageTimeout > 0 && time.Since(screen.messageTime) > screen.messageTimeout {
screen.clearMessage() screen.clearMessage()
@ -539,18 +575,18 @@ func (screen *BrowserScreen) drawFooter(style Style) {
func (screen *BrowserScreen) buildLeftPane(style Style) { func (screen *BrowserScreen) buildLeftPane(style Style) {
screen.leftPaneBuffer = nil screen.leftPaneBuffer = nil
if len(screen.currentPath) == 0 { if len(screen.currentPath) == 0 {
screen.currentPath = screen.db.getNextVisiblePath(nil) screen.currentPath = screen.db.getNextVisiblePath(nil, screen.filter)
} }
for i := range screen.db.buckets { for i := range screen.db.buckets {
screen.leftPaneBuffer = append(screen.leftPaneBuffer, screen.bucketToLines(&screen.db.buckets[i], style)...) screen.leftPaneBuffer = append(screen.leftPaneBuffer, screen.bucketToLines(&screen.db.buckets[i], style)...)
} }
// Find the cursor in the leftPane // Find the cursor in the leftPane
for k, v := range screen.leftPaneBuffer { for k, v := range screen.leftPaneBuffer {
if v.Fg == style.cursorFg { if v.Fg == style.cursorFg {
screen.leftViewPort.scrollRow = k screen.leftViewPort.scrollRow = k
break break
} }
} }
} }
func (screen *BrowserScreen) drawLeftPane(style Style) { func (screen *BrowserScreen) drawLeftPane(style Style) {
@ -562,19 +598,19 @@ func (screen *BrowserScreen) drawLeftPane(style Style) {
screen.leftViewPort.bytesPerRow = w screen.leftViewPort.bytesPerRow = w
screen.leftViewPort.numberOfRows = h - 2 screen.leftViewPort.numberOfRows = h - 2
termboxUtil.FillWithChar('=', 0, 1, w, 1, style.defaultFg, style.defaultBg) termboxUtil.FillWithChar('=', 0, 1, w, 1, style.defaultFg, style.defaultBg)
startX, startY := 0, 3 startX, startY := 0, 3
screen.leftViewPort.firstRow = startY screen.leftViewPort.firstRow = startY
treeOffset := 0 treeOffset := 0
maxCursor := screen.leftViewPort.numberOfRows * 2 / 3 maxCursor := screen.leftViewPort.numberOfRows * 2 / 3
if screen.leftViewPort.scrollRow > maxCursor { if screen.leftViewPort.scrollRow > maxCursor {
treeOffset = screen.leftViewPort.scrollRow - maxCursor treeOffset = screen.leftViewPort.scrollRow - maxCursor
} }
if len(screen.leftPaneBuffer) > 0 { if len(screen.leftPaneBuffer) > 0 {
for k, v := range screen.leftPaneBuffer[treeOffset:] { for k, v := range screen.leftPaneBuffer[treeOffset:] {
termboxUtil.DrawStringAtPoint(v.Text, startX, (startY + k - 1), v.Fg, v.Bg) termboxUtil.DrawStringAtPoint(v.Text, startX, (startY + k - 1), v.Fg, v.Bg)
} }
} }
} }
func (screen *BrowserScreen) buildRightPane(style Style) { func (screen *BrowserScreen) buildRightPane(style Style) {
@ -679,12 +715,20 @@ func (screen *BrowserScreen) bucketToLines(bkt *BoltBucket, style Style) []Line
ret = append(ret, screen.bucketToLines(&bkt.buckets[i], style)...) ret = append(ret, screen.bucketToLines(&bkt.buckets[i], style)...)
} }
for _, bp := range bkt.pairs { for _, bp := range bkt.pairs {
if screen.filter != "" && !strings.Contains(bp.key, screen.filter) {
continue
}
pfg, pbg := style.defaultFg, style.defaultBg pfg, pbg := style.defaultFg, style.defaultBg
if comparePaths(screen.currentPath, bp.GetPath()) { if comparePaths(screen.currentPath, bp.GetPath()) {
pfg, pbg = style.cursorFg, style.cursorBg pfg, pbg = style.cursorFg, style.cursorBg
} }
prPrefix := strings.Repeat(" ", len(bp.GetPath())*2) prPrefix := strings.Repeat(" ", len(bp.GetPath())*2)
pairString := fmt.Sprintf("%s%s: %s", prPrefix, stringify([]byte(bp.key)), stringify([]byte(bp.val))) var pairString string
if AppArgs.NoValue {
pairString = fmt.Sprintf("%s%s", prPrefix, stringify([]byte(bp.key)))
} else {
pairString = fmt.Sprintf("%s%s: %s", prPrefix, stringify([]byte(bp.key)), stringify([]byte(bp.val)))
}
ret = append(ret, Line{pairString, pfg, pbg}) ret = append(ret, Line{pairString, pfg, pbg})
} }
} else { } else {
@ -714,6 +758,23 @@ func (screen *BrowserScreen) startDeleteItem() bool {
return false return false
} }
func (screen *BrowserScreen) startFilter() bool {
_, _, e := screen.db.getGenericFromPath(screen.currentPath)
if e == nil {
w, h := termbox.Size()
inpW, inpH := (w / 2), 6
inpX, inpY := ((w / 2) - (inpW / 2)), ((h / 2) - inpH)
mod := termboxUtil.CreateInputModal("", inpX, inpY, inpW, inpH, termbox.ColorWhite, termbox.ColorBlack)
mod.SetTitle(termboxUtil.AlignText("Filter", inpW, termboxUtil.AlignCenter))
mod.SetValue(screen.filter)
mod.Show()
screen.inputModal = mod
screen.mode = modeFilter
return true
}
return false
}
func (screen *BrowserScreen) startEditItem() bool { func (screen *BrowserScreen) startEditItem() bool {
_, p, e := screen.db.getGenericFromPath(screen.currentPath) _, p, e := screen.db.getGenericFromPath(screen.currentPath)
if e == nil { if e == nil {
@ -859,7 +920,7 @@ func (screen *BrowserScreen) startExportValue() bool {
mod.SetValue("") mod.SetValue("")
mod.Show() mod.Show()
screen.inputModal = mod screen.inputModal = mod
screen.mode = modeExportValue screen.mode = modeIOExportValue
return true return true
} }
screen.setMessage("Couldn't do string export on " + screen.currentPath[len(screen.currentPath)-1] + "(did you mean 'X'?)") screen.setMessage("Couldn't do string export on " + screen.currentPath[len(screen.currentPath)-1] + "(did you mean 'X'?)")
@ -882,12 +943,30 @@ func (screen *BrowserScreen) startExportJSON() bool {
} }
mod.Show() mod.Show()
screen.inputModal = mod screen.inputModal = mod
screen.mode = modeExportJSON screen.mode = modeIOExportJSON
return true return true
} }
return false return false
} }
func (screen *BrowserScreen) startImportValue() bool {
_, p, e := screen.db.getGenericFromPath(screen.currentPath)
if e == nil && p != nil {
w, h := termbox.Size()
inpW, inpH := (w / 2), 6
inpX, inpY := ((w / 2) - (inpW / 2)), ((h / 2) - inpH)
mod := termboxUtil.CreateInputModal("", inpX, inpY, inpW, inpH, termbox.ColorWhite, termbox.ColorBlack)
mod.SetTitle(termboxUtil.AlignText(fmt.Sprintf("Import value of '%s' from:", p.key), inpW, termboxUtil.AlignCenter))
mod.SetValue("")
mod.Show()
screen.inputModal = mod
screen.mode = modeIOImportValue
return true
}
screen.setMessage("Couldn't do import on " + screen.currentPath[len(screen.currentPath)-1] + ", must be a pair.")
return false
}
func (screen *BrowserScreen) setMessage(msg string) { func (screen *BrowserScreen) setMessage(msg string) {
screen.message = msg screen.message = msg
screen.messageTime = time.Now() screen.messageTime = time.Now()