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 (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"git.qowevisa.me/Qowevisa/tuimenu/simple"
|
||||
)
|
||||
|
@ -9,9 +11,21 @@ import (
|
|||
func main() {
|
||||
m := simple.CreateMenu(
|
||||
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 {
|
||||
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
|
||||
})
|
||||
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 (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
@ -31,14 +32,16 @@ func (c *Command) Print() {
|
|||
}
|
||||
|
||||
type MenuConfig struct {
|
||||
Title string
|
||||
BackKey string
|
||||
Title string
|
||||
BackKey string
|
||||
UsingEscapeCodes bool
|
||||
}
|
||||
|
||||
func getDefConfig() *MenuConfig {
|
||||
return &MenuConfig{
|
||||
Title: "Default Menu Title",
|
||||
BackKey: "<",
|
||||
Title: "Default Menu Title",
|
||||
BackKey: "<",
|
||||
UsingEscapeCodes: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,9 +87,20 @@ func createCommandTree() *commandTree {
|
|||
type Menu struct {
|
||||
Title string
|
||||
BackKey string
|
||||
// Escape Code part
|
||||
//
|
||||
usingEscapeCodes bool
|
||||
lineCounter uint
|
||||
//
|
||||
Log *BufferedLogger
|
||||
//
|
||||
counterForIDs uint
|
||||
cmdTree *commandTree
|
||||
//
|
||||
interrupt chan func(m *Menu)
|
||||
resumeSignal chan struct{}
|
||||
inputChan chan string
|
||||
errorChan chan error
|
||||
}
|
||||
|
||||
func CreateMenu(options ...SimpleMenuOption) *Menu {
|
||||
|
@ -97,10 +111,17 @@ func CreateMenu(options ...SimpleMenuOption) *Menu {
|
|||
}
|
||||
|
||||
return &Menu{
|
||||
Title: conf.Title,
|
||||
BackKey: conf.BackKey,
|
||||
counterForIDs: 1,
|
||||
cmdTree: createCommandTree(),
|
||||
Title: conf.Title,
|
||||
BackKey: conf.BackKey,
|
||||
usingEscapeCodes: conf.UsingEscapeCodes,
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
m.lineCounter++
|
||||
path := ""
|
||||
for node := m.cmdTree.Pointer; node != nil; node = node.Parent {
|
||||
if node.Parent == nil {
|
||||
|
@ -161,6 +230,7 @@ func (m *Menu) iteration() {
|
|||
}
|
||||
}
|
||||
fmt.Printf("At %s\n", path)
|
||||
m.lineCounter++
|
||||
for _, node := range m.cmdTree.Pointer.Children {
|
||||
cmd := node.Val
|
||||
if cmd == nil {
|
||||
|
@ -168,33 +238,70 @@ func (m *Menu) iteration() {
|
|||
continue
|
||||
}
|
||||
cmd.Print()
|
||||
m.lineCounter++
|
||||
}
|
||||
if m.cmdTree.Pointer != m.cmdTree.Root {
|
||||
fmt.Printf("%s: Go back one layer\n", m.BackKey)
|
||||
}
|
||||
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
|
||||
m.lineCounter++
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
go m.readInput()
|
||||
for {
|
||||
m.iteration()
|
||||
}
|
||||
|
|
|
@ -13,3 +13,9 @@ func WithCustomBackKey(back string) SimpleMenuOption {
|
|||
conf.BackKey = back
|
||||
}
|
||||
}
|
||||
|
||||
func WithUsageOfEscapeCodes() SimpleMenuOption {
|
||||
return func(conf *MenuConfig) {
|
||||
conf.UsingEscapeCodes = true
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user