Compare commits
10 Commits
e07cdfdfde
...
ddaf609d0c
Author | SHA1 | Date | |
---|---|---|---|
ddaf609d0c | |||
37b00556e7 | |||
6cd080fc72 | |||
c3d2f34f09 | |||
3adb36e336 | |||
1a4d39fd1d | |||
faaa13fb2c | |||
65b0d132c1 | |||
e505fb654b | |||
c84bed67cf |
|
@ -2,6 +2,8 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.qowevisa.me/Qowevisa/tuimenu/simple"
|
"git.qowevisa.me/Qowevisa/tuimenu/simple"
|
||||||
)
|
)
|
||||||
|
@ -9,9 +11,21 @@ import (
|
||||||
func main() {
|
func main() {
|
||||||
m := simple.CreateMenu(
|
m := simple.CreateMenu(
|
||||||
simple.WithCustomTitle("My Custom Title"),
|
simple.WithCustomTitle("My Custom Title"),
|
||||||
|
simple.WithUsageOfEscapeCodes(),
|
||||||
)
|
)
|
||||||
|
// Using this function will redirect every log.PrintX func in THIS file
|
||||||
|
// to m.Log buffer that will Flush everything at the start of next iteration
|
||||||
|
m.RedirectLogOutputToBufferedLogger()
|
||||||
nameName, err := m.AddCommand("0", "NameName", func(m *simple.Menu) error {
|
nameName, err := m.AddCommand("0", "NameName", func(m *simple.Menu) error {
|
||||||
fmt.Printf("hello, world\n")
|
log.Printf("hello, world\n")
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
m.Log.Logf("some data = %d\n", 42)
|
||||||
|
time.Sleep(time.Second * 1)
|
||||||
|
m.Log.Logf("some data = %d\n", 43)
|
||||||
|
time.Sleep(time.Second * 1)
|
||||||
|
m.Log.Logf("some data = %d\n", 44)
|
||||||
|
log.Printf("asdas")
|
||||||
|
time.Sleep(time.Second * 1)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
47
simple/logger.go
Normal file
47
simple/logger.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package simple
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BufferedLogger structure buffer log messages
|
||||||
|
type BufferedLogger struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
buffer bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logf buffers the log message with a formatted string
|
||||||
|
// To print it instantly call Flush
|
||||||
|
func (bl *BufferedLogger) Logf(format string, args ...interface{}) {
|
||||||
|
bl.mu.Lock()
|
||||||
|
defer bl.mu.Unlock()
|
||||||
|
|
||||||
|
// Create log entry with a timestamp
|
||||||
|
timestamp := time.Now().Format(time.RFC3339)
|
||||||
|
entry := fmt.Sprintf("[%s] %s", timestamp, fmt.Sprintf(format, args...))
|
||||||
|
|
||||||
|
if !(strings.Contains(entry, "\n") && entry[len(entry)-1] == '\n') {
|
||||||
|
entry += "\n"
|
||||||
|
}
|
||||||
|
bl.buffer.WriteString(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush outputs all buffered log messages
|
||||||
|
func (bl *BufferedLogger) Flush() {
|
||||||
|
bl.mu.Lock()
|
||||||
|
defer bl.mu.Unlock()
|
||||||
|
|
||||||
|
if bl.buffer.Len() > 0 {
|
||||||
|
fmt.Print(bl.buffer.String())
|
||||||
|
bl.buffer.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Menu) RedirectLogOutputToBufferedLogger() {
|
||||||
|
log.SetOutput(&m.Log.buffer)
|
||||||
|
}
|
165
simple/menu.go
165
simple/menu.go
|
@ -3,6 +3,7 @@ package simple
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -31,14 +32,16 @@ func (c *Command) Print() {
|
||||||
}
|
}
|
||||||
|
|
||||||
type MenuConfig struct {
|
type MenuConfig struct {
|
||||||
Title string
|
Title string
|
||||||
BackKey string
|
BackKey string
|
||||||
|
UsingEscapeCodes bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefConfig() *MenuConfig {
|
func getDefConfig() *MenuConfig {
|
||||||
return &MenuConfig{
|
return &MenuConfig{
|
||||||
Title: "Default Menu Title",
|
Title: "Default Menu Title",
|
||||||
BackKey: "<",
|
BackKey: "<",
|
||||||
|
UsingEscapeCodes: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,9 +87,20 @@ func createCommandTree() *commandTree {
|
||||||
type Menu struct {
|
type Menu struct {
|
||||||
Title string
|
Title string
|
||||||
BackKey string
|
BackKey string
|
||||||
|
// Escape Code part
|
||||||
|
//
|
||||||
|
usingEscapeCodes bool
|
||||||
|
lineCounter uint
|
||||||
|
//
|
||||||
|
Log *BufferedLogger
|
||||||
//
|
//
|
||||||
counterForIDs uint
|
counterForIDs uint
|
||||||
cmdTree *commandTree
|
cmdTree *commandTree
|
||||||
|
//
|
||||||
|
interrupt chan func(m *Menu)
|
||||||
|
resumeSignal chan struct{}
|
||||||
|
inputChan chan string
|
||||||
|
errorChan chan error
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateMenu(options ...SimpleMenuOption) *Menu {
|
func CreateMenu(options ...SimpleMenuOption) *Menu {
|
||||||
|
@ -97,10 +111,17 @@ func CreateMenu(options ...SimpleMenuOption) *Menu {
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Menu{
|
return &Menu{
|
||||||
Title: conf.Title,
|
Title: conf.Title,
|
||||||
BackKey: conf.BackKey,
|
BackKey: conf.BackKey,
|
||||||
counterForIDs: 1,
|
usingEscapeCodes: conf.UsingEscapeCodes,
|
||||||
cmdTree: createCommandTree(),
|
Log: &BufferedLogger{},
|
||||||
|
lineCounter: 0,
|
||||||
|
counterForIDs: 1,
|
||||||
|
cmdTree: createCommandTree(),
|
||||||
|
interrupt: make(chan func(m *Menu)),
|
||||||
|
resumeSignal: make(chan struct{}),
|
||||||
|
inputChan: make(chan string),
|
||||||
|
errorChan: make(chan error),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,8 +171,56 @@ func (m *Menu) AddCommand(key, name string, action CommandAction) (*commandNode,
|
||||||
return m.cmdTree.Root.AddCommand(key, name, action), nil
|
return m.cmdTree.Root.AddCommand(key, name, action), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Menu) iteration() {
|
func (m *Menu) clearLines() {
|
||||||
|
for i := 0; i < int(m.lineCounter); i++ {
|
||||||
|
fmt.Printf("\033[F\033[K")
|
||||||
|
}
|
||||||
|
m.lineCounter = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Menu) handleInput(input string) {
|
||||||
|
var preHandler func()
|
||||||
|
var action CommandAction
|
||||||
|
var afterHandler func()
|
||||||
|
if m.usingEscapeCodes {
|
||||||
|
preHandler = func() {
|
||||||
|
m.clearLines()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, node := range m.cmdTree.Pointer.Children {
|
||||||
|
cmd := node.Val
|
||||||
|
if cmd == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if cmd.Key == input {
|
||||||
|
action = cmd.Action
|
||||||
|
if cmd.MoveTo {
|
||||||
|
afterHandler = func() {
|
||||||
|
m.cmdTree.Pointer = node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if m.cmdTree.Pointer != m.cmdTree.Root && m.BackKey == input {
|
||||||
|
afterHandler = func() {
|
||||||
|
m.cmdTree.Pointer = m.cmdTree.Pointer.Parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if preHandler != nil {
|
||||||
|
preHandler()
|
||||||
|
}
|
||||||
|
if action != nil {
|
||||||
|
action(m)
|
||||||
|
}
|
||||||
|
if afterHandler != nil {
|
||||||
|
afterHandler()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Menu) printMenu() {
|
||||||
fmt.Printf("%s\n", m.Title)
|
fmt.Printf("%s\n", m.Title)
|
||||||
|
m.lineCounter++
|
||||||
path := ""
|
path := ""
|
||||||
for node := m.cmdTree.Pointer; node != nil; node = node.Parent {
|
for node := m.cmdTree.Pointer; node != nil; node = node.Parent {
|
||||||
if node.Parent == nil {
|
if node.Parent == nil {
|
||||||
|
@ -161,6 +230,7 @@ func (m *Menu) iteration() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Printf("At %s\n", path)
|
fmt.Printf("At %s\n", path)
|
||||||
|
m.lineCounter++
|
||||||
for _, node := range m.cmdTree.Pointer.Children {
|
for _, node := range m.cmdTree.Pointer.Children {
|
||||||
cmd := node.Val
|
cmd := node.Val
|
||||||
if cmd == nil {
|
if cmd == nil {
|
||||||
|
@ -168,33 +238,70 @@ func (m *Menu) iteration() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cmd.Print()
|
cmd.Print()
|
||||||
|
m.lineCounter++
|
||||||
}
|
}
|
||||||
if m.cmdTree.Pointer != m.cmdTree.Root {
|
if m.cmdTree.Pointer != m.cmdTree.Root {
|
||||||
fmt.Printf("%s: Go back one layer\n", m.BackKey)
|
fmt.Printf("%s: Go back one layer\n", m.BackKey)
|
||||||
}
|
m.lineCounter++
|
||||||
stdinReader := bufio.NewReader(os.Stdin)
|
|
||||||
msg, err := stdinReader.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error: ReadString: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
msg = strings.TrimRight(msg, "\n")
|
|
||||||
for _, node := range m.cmdTree.Pointer.Children {
|
|
||||||
cmd := node.Val
|
|
||||||
if cmd == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if cmd.Key == msg {
|
|
||||||
cmd.Action(m)
|
|
||||||
m.cmdTree.Pointer = node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if m.BackKey == msg {
|
|
||||||
m.cmdTree.Pointer = m.cmdTree.Pointer.Parent
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Menu) readInput() {
|
||||||
|
stdinReader := bufio.NewReader(os.Stdin)
|
||||||
|
for {
|
||||||
|
msg, err := stdinReader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
// fmt.Printf("Error: ReadString: %v\n", err)
|
||||||
|
m.errorChan <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.lineCounter++
|
||||||
|
m.inputChan <- msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Menu) GetInput(prompt string) string {
|
||||||
|
nlCount := strings.Count(prompt, "\n")
|
||||||
|
m.lineCounter += uint(nlCount)
|
||||||
|
stdinReader := bufio.NewReader(os.Stdin)
|
||||||
|
rawMsg, err := stdinReader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
// fmt.Printf("Error: ReadString: %v\n", err)
|
||||||
|
m.errorChan <- err
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
m.lineCounter++
|
||||||
|
msg := strings.TrimRight(rawMsg, "\n")
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Menu) iteration() {
|
||||||
|
m.Log.Flush()
|
||||||
|
m.printMenu()
|
||||||
|
|
||||||
|
// for {
|
||||||
|
select {
|
||||||
|
case msg := <-m.inputChan:
|
||||||
|
msg = strings.TrimRight(msg, "\n")
|
||||||
|
m.handleInput(msg)
|
||||||
|
case err := <-m.errorChan:
|
||||||
|
m.Log.Logf("err: %v", err)
|
||||||
|
case f := <-m.interrupt:
|
||||||
|
if m.usingEscapeCodes {
|
||||||
|
m.clearLines()
|
||||||
|
}
|
||||||
|
log.Printf("Interrupt")
|
||||||
|
f(m)
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Menu) SendInterrupt(f func(m *Menu)) {
|
||||||
|
m.interrupt <- f
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Menu) Start() {
|
func (m *Menu) Start() {
|
||||||
|
go m.readInput()
|
||||||
for {
|
for {
|
||||||
m.iteration()
|
m.iteration()
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,3 +13,9 @@ func WithCustomBackKey(back string) SimpleMenuOption {
|
||||||
conf.BackKey = back
|
conf.BackKey = back
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithUsageOfEscapeCodes() SimpleMenuOption {
|
||||||
|
return func(conf *MenuConfig) {
|
||||||
|
conf.UsingEscapeCodes = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user