Initial Commit
This commit is contained in:
parent
b43acf84bc
commit
868945cff9
BIN
boltbrowser
Executable file
BIN
boltbrowser
Executable file
Binary file not shown.
112
boltbrowser.go
Normal file
112
boltbrowser.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/boltdb/bolt"
|
||||||
|
"github.com/nsf/termbox-go"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BoltBucket struct {
|
||||||
|
name string
|
||||||
|
pairs map[string]string
|
||||||
|
buckets []BoltBucket
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Database, basically a collection of buckets
|
||||||
|
type BoltDB struct {
|
||||||
|
buckets []BoltBucket
|
||||||
|
}
|
||||||
|
|
||||||
|
const PROGRAM_NAME = "boltbrowser"
|
||||||
|
|
||||||
|
var database_file string
|
||||||
|
var db *bolt.DB
|
||||||
|
var memBolt *BoltDB
|
||||||
|
|
||||||
|
func mainLoop(memBolt *BoltDB, style Style) {
|
||||||
|
screens := defaultScreensForData(memBolt)
|
||||||
|
display_screen := screens[ABOUT_SCREEN_INDEX]
|
||||||
|
layoutAndDrawScreen(display_screen, style)
|
||||||
|
for {
|
||||||
|
event := termbox.PollEvent()
|
||||||
|
if event.Type == termbox.EventKey {
|
||||||
|
if event.Key == termbox.KeyCtrlZ {
|
||||||
|
process, _ := os.FindProcess(os.Getpid())
|
||||||
|
termbox.Close()
|
||||||
|
process.Signal(syscall.SIGSTOP)
|
||||||
|
termbox.Init()
|
||||||
|
}
|
||||||
|
new_screen_index := display_screen.handleKeyEvent(event)
|
||||||
|
if new_screen_index < len(screens) {
|
||||||
|
display_screen = screens[new_screen_index]
|
||||||
|
layoutAndDrawScreen(display_screen, style)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if event.Type == termbox.EventResize {
|
||||||
|
layoutAndDrawScreen(display_screen, style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var err error
|
||||||
|
if len(os.Args) != 2 {
|
||||||
|
fmt.Printf("Usage: %s <filename>\n", PROGRAM_NAME)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
database_file := os.Args[1]
|
||||||
|
|
||||||
|
db, err = bolt.Open(database_file, 0600, nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error reading file: %q\n", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
//defer db.Close()
|
||||||
|
|
||||||
|
// First things first, load the database into memory
|
||||||
|
refreshDatabase()
|
||||||
|
|
||||||
|
err = termbox.Init()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer termbox.Close()
|
||||||
|
|
||||||
|
style := defaultStyle()
|
||||||
|
termbox.SetOutputMode(termbox.Output256)
|
||||||
|
|
||||||
|
mainLoop(memBolt, style)
|
||||||
|
}
|
||||||
|
|
||||||
|
func refreshDatabase() {
|
||||||
|
// Reload the database into memBolt
|
||||||
|
memBolt = new(BoltDB)
|
||||||
|
db.View(func(tx *bolt.Tx) error {
|
||||||
|
tx.ForEach(func(nm []byte, b *bolt.Bucket) error {
|
||||||
|
bb := readBucket(b)
|
||||||
|
bb.name = string(nm)
|
||||||
|
memBolt.buckets = append(memBolt.buckets, *bb)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func readBucket(b *bolt.Bucket) *BoltBucket {
|
||||||
|
bb := new(BoltBucket)
|
||||||
|
b.ForEach(func(k, v []byte) error {
|
||||||
|
if v == nil {
|
||||||
|
tb := readBucket(b.Bucket(k))
|
||||||
|
tb.name = string(k)
|
||||||
|
bb.buckets = append(bb.buckets, *tb)
|
||||||
|
} else {
|
||||||
|
bb.pairs[string(k)] = string(v)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return bb
|
||||||
|
}
|
40
screen.go
Normal file
40
screen.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/nsf/termbox-go"
|
||||||
|
|
||||||
|
type Screen interface {
|
||||||
|
handleKeyEvent(event termbox.Event) int
|
||||||
|
performLayout()
|
||||||
|
drawScreen(style Style)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
BROWSER_SCREEN_INDEX = iota
|
||||||
|
ABOUT_SCREEN_INDEX
|
||||||
|
EXIT_SCREEN_INDEX
|
||||||
|
)
|
||||||
|
|
||||||
|
func defaultScreensForData(memBolt *BoltDB) []Screen {
|
||||||
|
var view_port ViewPort
|
||||||
|
var cursor Cursor
|
||||||
|
|
||||||
|
browser_screen := BrowserScreen{*memBolt, cursor, view_port}
|
||||||
|
about_screen := AboutScreen(0)
|
||||||
|
screens := [...]Screen{
|
||||||
|
&browser_screen,
|
||||||
|
&about_screen,
|
||||||
|
}
|
||||||
|
|
||||||
|
return screens[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawBackground(bg termbox.Attribute) {
|
||||||
|
termbox.Clear(0, bg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func layoutAndDrawScreen(screen Screen, style Style) {
|
||||||
|
screen.performLayout()
|
||||||
|
drawBackground(style.default_bg)
|
||||||
|
screen.drawScreen(style)
|
||||||
|
termbox.Flush()
|
||||||
|
}
|
95
screen_about.go
Normal file
95
screen_about.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/nsf/termbox-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Command struct {
|
||||||
|
key string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AboutScreen int
|
||||||
|
|
||||||
|
func drawCommandsAtPoint(commands []Command, x int, y int, style Style) {
|
||||||
|
x_pos, y_pos := x, y
|
||||||
|
for index, cmd := range commands {
|
||||||
|
drawStringAtPoint(fmt.Sprintf("%6s", cmd.key), x_pos, y_pos, style.default_fg, style.default_bg)
|
||||||
|
drawStringAtPoint(cmd.description, x_pos+8, y_pos, style.default_fg, style.default_bg)
|
||||||
|
y_pos++
|
||||||
|
if index > 2 && index%2 == 1 {
|
||||||
|
y_pos++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (screen *AboutScreen) handleKeyEvent(event termbox.Event) int {
|
||||||
|
return BROWSER_SCREEN_INDEX
|
||||||
|
}
|
||||||
|
|
||||||
|
func (screen *AboutScreen) performLayout() {}
|
||||||
|
|
||||||
|
func (screen *AboutScreen) drawScreen(style Style) {
|
||||||
|
default_fg := style.default_fg
|
||||||
|
default_bg := style.default_bg
|
||||||
|
width, height := termbox.Size()
|
||||||
|
template := [...]string{
|
||||||
|
" _______ _______ ___ _______ _______ ______ _______ _ _ _______ _______ ______ ",
|
||||||
|
"| _ || || | | || _ || _ | | || | _ | || || || _ | ",
|
||||||
|
"| |_| || _ || | |_ _|| |_| || | || | _ || || || || _____|| ___|| | || ",
|
||||||
|
"| || | | || | | | | || |_||_ | | | || || |_____ | |___ | |_||_ ",
|
||||||
|
"| _ | | |_| || |___ | | | _ | | __ || |_| || ||_____ || ___|| __ |",
|
||||||
|
"| |_| || || || | | |_| || | | || || _ | _____| || |___ | | | |",
|
||||||
|
"|_______||_______||_______||___| |_______||___| |_||_______||__| |__||_______||_______||___| |_|",
|
||||||
|
}
|
||||||
|
first_line := template[0]
|
||||||
|
start_x := (width - len(first_line)) / 2
|
||||||
|
start_y := (height - 2*len(template)) / 2
|
||||||
|
x_pos := start_x
|
||||||
|
y_pos := start_y
|
||||||
|
for _, line := range template {
|
||||||
|
x_pos = start_x
|
||||||
|
for _, runeValue := range line {
|
||||||
|
bg := default_bg
|
||||||
|
displayRune := ' '
|
||||||
|
if runeValue != ' ' {
|
||||||
|
bg = termbox.Attribute(125)
|
||||||
|
displayRune = runeValue
|
||||||
|
termbox.SetCell(x_pos, y_pos, displayRune, default_fg, bg)
|
||||||
|
}
|
||||||
|
x_pos++
|
||||||
|
}
|
||||||
|
y_pos++
|
||||||
|
}
|
||||||
|
|
||||||
|
commands1 := [...]Command{
|
||||||
|
{"h", "close parent"},
|
||||||
|
{"j", "down"},
|
||||||
|
{"k", "up"},
|
||||||
|
{"l", "open item"},
|
||||||
|
|
||||||
|
{"g", "goto top"},
|
||||||
|
{"G", "goto bottom"},
|
||||||
|
|
||||||
|
{"ctrl-e", "scroll down"},
|
||||||
|
{"ctrl-y", "scroll up"},
|
||||||
|
|
||||||
|
{"ctrl-f", "page down"},
|
||||||
|
{"ctrl-b", "page up"},
|
||||||
|
}
|
||||||
|
|
||||||
|
commands2 := [...]Command{
|
||||||
|
{"p", "create pair"},
|
||||||
|
{"b", "create bucket"},
|
||||||
|
{"d", "delete item"},
|
||||||
|
|
||||||
|
{"?", "this screen"},
|
||||||
|
{"q", "quit program"},
|
||||||
|
}
|
||||||
|
x_pos = start_x + 3
|
||||||
|
y_pos++
|
||||||
|
|
||||||
|
drawCommandsAtPoint(commands1[:], x_pos, y_pos+1, style)
|
||||||
|
drawCommandsAtPoint(commands2[:], x_pos+20, y_pos+1, style)
|
||||||
|
}
|
72
screen_browser.go
Normal file
72
screen_browser.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nsf/termbox-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ViewPort struct {
|
||||||
|
bytes_per_row int
|
||||||
|
number_of_rows int
|
||||||
|
first_row int
|
||||||
|
}
|
||||||
|
|
||||||
|
type BrowserScreen struct {
|
||||||
|
memBolt BoltDB
|
||||||
|
cursor Cursor
|
||||||
|
view_port ViewPort
|
||||||
|
}
|
||||||
|
|
||||||
|
func (screen *BrowserScreen) handleKeyEvent(event termbox.Event) int {
|
||||||
|
if event.Ch == '?' { // About
|
||||||
|
return ABOUT_SCREEN_INDEX
|
||||||
|
} else if event.Ch == 'q' || event.Key == termbox.KeyEsc || event.Key == termbox.KeyCtrlC {
|
||||||
|
return EXIT_SCREEN_INDEX
|
||||||
|
}
|
||||||
|
|
||||||
|
return BROWSER_SCREEN_INDEX
|
||||||
|
}
|
||||||
|
|
||||||
|
func (screen *BrowserScreen) performLayout() {
|
||||||
|
/*
|
||||||
|
width, height := termbox.Size()
|
||||||
|
legend_height := heightOfWidgets()
|
||||||
|
line_height := 3
|
||||||
|
cursor := screen.cursor
|
||||||
|
cursor_row_within_view_port := 0
|
||||||
|
|
||||||
|
var new_view_port ViewPort
|
||||||
|
new_view_port.bytes_per_row = (width - 3) / 3
|
||||||
|
new_view_port.number_of_rows = (height - 1 - legend_height) / line_height
|
||||||
|
new_view_port.first_row = screen.view_port.first_row
|
||||||
|
|
||||||
|
if new_view_port.first_row < 0 {
|
||||||
|
new_view_port.first_row = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
screen.view_port = new_view_port
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
func (screen *BrowserScreen) drawScreen(style Style) {
|
||||||
|
x, y := 2, 1
|
||||||
|
// x_pad := 2
|
||||||
|
//line_height := 1
|
||||||
|
// width, height := termbox.Size()
|
||||||
|
// widget_width, widget_height := drawWidgets(screen.cursor, style)
|
||||||
|
|
||||||
|
// cursor := screen.cursor
|
||||||
|
//view_port := screen.view_port
|
||||||
|
|
||||||
|
//last_y := y + view_port.number_of_rows*line_height - 1
|
||||||
|
//last_x := x + view_port.bytes_per_row*3 - 1
|
||||||
|
|
||||||
|
start_x := x
|
||||||
|
for _, bkt := range screen.memBolt.buckets {
|
||||||
|
bucket_fg := style.default_fg
|
||||||
|
bucket_bg := style.default_bg
|
||||||
|
termbox.SetCell(x, y, '+', bucket_fg, bucket_bg)
|
||||||
|
x = drawStringAtPoint(bkt.name, x+1, y, bucket_fg, bucket_bg)
|
||||||
|
y = y + 1
|
||||||
|
x = start_x
|
||||||
|
}
|
||||||
|
}
|
16
style.go
Normal file
16
style.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/nsf/termbox-go"
|
||||||
|
|
||||||
|
type Style struct {
|
||||||
|
default_bg termbox.Attribute
|
||||||
|
default_fg termbox.Attribute
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultStyle() Style {
|
||||||
|
var style Style
|
||||||
|
style.default_bg = termbox.Attribute(1)
|
||||||
|
style.default_fg = termbox.Attribute(256)
|
||||||
|
|
||||||
|
return style
|
||||||
|
}
|
12
util.go
Normal file
12
util.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/nsf/termbox-go"
|
||||||
|
|
||||||
|
func drawStringAtPoint(str string, x int, y int, fg termbox.Attribute, bg termbox.Attribute) int {
|
||||||
|
x_pos := x
|
||||||
|
for _, runeValue := range str {
|
||||||
|
termbox.SetCell(x_pos, y, runeValue, fg, bg)
|
||||||
|
x_pos++
|
||||||
|
}
|
||||||
|
return x_pos - x
|
||||||
|
}
|
90
widget.go
Normal file
90
widget.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/nsf/termbox-go"
|
||||||
|
|
||||||
|
type Widget interface {
|
||||||
|
layoutUnderPressure(pressure int) (int, int)
|
||||||
|
drawAtPoint(cursor Cursor, x int, y int, pressure int, style Style) (int, int)
|
||||||
|
}
|
||||||
|
|
||||||
|
type WidgetSlice []Widget
|
||||||
|
|
||||||
|
func (widgets WidgetSlice) sizeAtPressure(pressure int) (int, int) {
|
||||||
|
total_widget_width := 0
|
||||||
|
max_widget_height := 0
|
||||||
|
for _, widget := range widgets {
|
||||||
|
widget_width, widget_height := widget.layoutUnderPressure(pressure)
|
||||||
|
total_widget_width += widget_width
|
||||||
|
if widget_height > max_widget_height {
|
||||||
|
max_widget_height = widget_height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total_widget_width, max_widget_height
|
||||||
|
}
|
||||||
|
|
||||||
|
func (widgets WidgetSlice) numberVisibleAtPressure(pressure int) int {
|
||||||
|
count := 0
|
||||||
|
for _, widget := range widgets {
|
||||||
|
widget_width, _ := widget.layoutUnderPressure(pressure)
|
||||||
|
if widget_width > 0 {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
func (widgets WidgetSlice) layout() (int, int) {
|
||||||
|
width, _ := termbox.Size()
|
||||||
|
pressure := 0
|
||||||
|
spacing := 4
|
||||||
|
padding := 2
|
||||||
|
for ; pressure < 10; pressure++ {
|
||||||
|
spacing = 4
|
||||||
|
total_widget_width, _ := widgets.sizeAtPressure(pressure)
|
||||||
|
num_spaces := widgets.numberVisibleAtPressure(pressure) - 1
|
||||||
|
for ; total_widget_width+num_spaces*spacing > (width-2*padding) && spacing > 2; spacing-- {
|
||||||
|
}
|
||||||
|
if total_widget_width+num_spaces*spacing <= (width - 2*padding) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pressure, spacing
|
||||||
|
}
|
||||||
|
|
||||||
|
func listOfWidgets() WidgetSlice {
|
||||||
|
all_widgets := [...]Widget{
|
||||||
|
NavigationWidget(0),
|
||||||
|
//CursorWidget(0),
|
||||||
|
//OffsetWidget(0),
|
||||||
|
}
|
||||||
|
return all_widgets[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func heightOfWidgets() int {
|
||||||
|
widgets := listOfWidgets()
|
||||||
|
pressure, _ := widgets.layout()
|
||||||
|
_, max_widget_height := widgets.sizeAtPressure(pressure)
|
||||||
|
return max_widget_height
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawWidgets(cursor Cursor, style Style) (int, int) {
|
||||||
|
widgets := listOfWidgets()
|
||||||
|
|
||||||
|
width, height := termbox.Size()
|
||||||
|
spacing := 4
|
||||||
|
padding := 2
|
||||||
|
pressure, spacing := widgets.layout()
|
||||||
|
total_widget_width, max_widget_height := widgets.sizeAtPressure(pressure)
|
||||||
|
num_spaces := widgets.numberVisibleAtPressure(pressure) - 1
|
||||||
|
start_x, start_y := (width-2*padding-(total_widget_width+num_spaces*spacing))/2+padding, height-max_widget_height
|
||||||
|
x, y := start_x, start_y
|
||||||
|
for _, widget := range widgets {
|
||||||
|
widget_width, _ := widget.drawAtPoint(cursor, x, y, pressure, style)
|
||||||
|
x += widget_width
|
||||||
|
if widget_width > 0 {
|
||||||
|
x += spacing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return total_widget_width + num_spaces*spacing, max_widget_height
|
||||||
|
}
|
22
widget_nav.go
Normal file
22
widget_nav.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
type NavigationWidget int
|
||||||
|
|
||||||
|
func (widget NavigationWidget) layoutUnderPressure(pressure int) (int, int) {
|
||||||
|
layouts := map[int]string{
|
||||||
|
0: "Navigate: ↓j ↑k",
|
||||||
|
}
|
||||||
|
runeCount := 0
|
||||||
|
for _, _ = range layouts[0] {
|
||||||
|
runeCount++
|
||||||
|
}
|
||||||
|
return runeCount, 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (widget NavigationWidget) drawAtPoint(cursor Cursor, x int, y int, pressure int, style Style) (int, int) {
|
||||||
|
fg := style.default_fg
|
||||||
|
bg := style.default_bg
|
||||||
|
x_pos := x
|
||||||
|
x_pos += drawStringAtPoint("Navigate: ↓j ↑k", x_pos, y, fg, bg)
|
||||||
|
return x_pos - x, 2
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user