From b3eb8ffa60be381d3ffd07da0c8bde68e1ba2543 Mon Sep 17 00:00:00 2001 From: Charles Corbett Date: Thu, 17 Aug 2023 23:14:12 -0700 Subject: [PATCH] Overhauled user IO --- coloring.go | 34 +++++++++++++ examples/README.md | 2 +- examples/examples.go | 113 +++++++++++++++++++++++++++++++++++++++++++ examples/prints.go | 39 --------------- hobocode.go | 19 +++++++- userio.go | 80 +++++++++++++++++++++++++----- 6 files changed, 232 insertions(+), 55 deletions(-) create mode 100644 examples/examples.go delete mode 100644 examples/prints.go diff --git a/coloring.go b/coloring.go index 78e6e7f..0f3ad00 100644 --- a/coloring.go +++ b/coloring.go @@ -7,8 +7,13 @@ import ( "github.com/jedib0t/go-pretty/v6/text" ) +/* + TODO: Should possibly check terminal width and try to nicely wrap to the indent if width is going to exceed +*/ + // Icolor prints the given string at the given indent // to the given os.File with the given text.Color, if colorable +// with newline func Icolor(indent int, fd *os.File, color text.Color, message string) { id := Tindent(indent) if Colorable(fd) { @@ -22,6 +27,7 @@ func Icolor(indent int, fd *os.File, color text.Color, message string) { // Icolorf prints the given format at the given indent // to the given os.File with the given text.Color, if colorable +// with newline func Icolorf(indent int, fd *os.File, color text.Color, format string, a ...interface{}) { id := Tindent(indent) if Colorable(fd) { @@ -32,3 +38,31 @@ func Icolorf(indent int, fd *os.File, color text.Color, format string, a ...inte Stdlin(fd, fmt.Sprintf(format, a...)) } } + +// Ficolor prints the given string at the given indent +// to the given os.File with the given text.Color, if colorable +// and does not newline +func Ficolor(indent int, fd *os.File, color text.Color, format string, a ...interface{}) { + id := Tindent(indent) + if Colorable(fd) { + fmt.Fprint(fd, id) + Std(fd, color.Sprintf(format, a...)) + } else { + fmt.Fprint(fd, id) + Std(fd, fmt.Sprintf(format, a...)) + } +} + +// Ficolorf prints the given format at the given indent +// to the given os.File with the given text.Color, if colorable +// and does not newline +func Ficolorf(indent int, fd *os.File, color text.Color, format string, a ...interface{}) { + id := Tindent(indent) + if Colorable(fd) { + fmt.Fprint(fd, id) + Std(fd, color.Sprintf(format, a...)) + } else { + fmt.Fprint(fd, id) + Std(fd, fmt.Sprintf(format, a...)) + } +} diff --git a/examples/README.md b/examples/README.md index 05cac36..3b6c363 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,6 +1,6 @@ # Hobocode Examples ``` -go run prints.go +go run examples.go ``` diff --git a/examples/examples.go b/examples/examples.go new file mode 100644 index 0000000..5263cc0 --- /dev/null +++ b/examples/examples.go @@ -0,0 +1,113 @@ +package main + +import ( + "fmt" + "os" + "time" + + "github.com/asciifaceman/hobocode" +) + +func main() { + if len(os.Args) == 1 { + help() + } + + arg := os.Args[1] + + if arg == "prints" { + prints() + } else if arg == "io" { + userio() + } else { + help() + } +} + +func help() { + hobocode.Error("Please select an example to see") + hobocode.Error("prints | io") + os.Exit(0) +} + +func prints() { + hobocode.Header("Info") + hobocode.Info("This is an info message") + hobocode.Infof("This is a formatted info message: %v", time.Now().Format("2006/01/02")) + hobocode.Iinfo(1, "This is an info message indented once") + hobocode.Iinfo(2, "This is an info message indented twice") + hobocode.Iinfof(1, "This is a formatted info message indented once: %v", time.Now().Format("2006/01/02")) + hobocode.Iinfof(2, "This is a formatted info message indented twice: %v", time.Now().Format("2006/01/02")) + + hobocode.Header("Warn") + hobocode.Warn("This is a warn message") + hobocode.Warnf("This is a formatted warn message: %v", time.Now().Format("2006/01/02")) + + hobocode.Header("Error") + hobocode.Error("This is an error message") + hobocode.Errorf("This is a formatted error message: %v", fmt.Errorf("error occured at %v", time.Now().Format("2006/01/02"))) + + hobocode.Header("Success") + hobocode.Success("This is a success message!") + hobocode.Successf("This is a formatted successs message! %v", time.Now().Format("2006/01/02")) + + hobocode.Header("Debug") + hobocode.Debug("This is a debug message!") + hobocode.Debugf("This is a formatted debug message! %v", time.Now().Format("2006/01/02")) + + hobocode.HeaderLeft("A left justified header!?") + hobocode.HeaderLeft("Whoa!") + hobocode.HeaderLeft("That's cool") + +} + +func userio() { + in1 := hobocode.Inputd("nvim", "What is your preferred editor? ") + hobocode.Successf("You chose %s", in1) + + options := []string{ + "Continue seeing user IO examples", + "Quit seeing user IO examples", + "What does the owl say", + } + + resp := hobocode.Selection(options, "Please select from the list", "Your selection (empty is none): ") + if resp == -1 { + hobocode.Error("Error fetching input, exiting") + os.Exit(1) + } else if resp == 0 { + hobocode.Info("Continuing...") + } else if resp == 1 { + hobocode.Info("User exited") + os.Exit(0) + } else if resp == 2 { + hobocode.Success("Hoooo!") + } + + options = []string{ + "Continue (again!) seeing user IO examples", + "See curerent date and continue", + "Quit seeing user IO examples", + } + + resp = hobocode.Selectionr(options, 0, "Please select again from the list", "Your selection: ") + if resp == -1 { + hobocode.Error("Error fetching input, exiting") + os.Exit(1) + } else if resp == 0 { + hobocode.Info("Continuing...") + } else if resp == 1 { + hobocode.Info(time.Now().Format("2006-01-02")) + } else if resp == 2 { + hobocode.Info("User exited...") + os.Exit(0) + } + + conf := hobocode.Confirm("All good?") + if conf { + hobocode.Success("Yay!") + } else { + hobocode.Warn("Oh no :(") + } + +} diff --git a/examples/prints.go b/examples/prints.go deleted file mode 100644 index 32df428..0000000 --- a/examples/prints.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "fmt" - "time" - - "github.com/asciifaceman/hobocode" -) - -func main() { - hobocode.Header("Info") - hobocode.Info("This is an info message") - hobocode.Infof("This is a formatted info message: %v", time.Now().Format("2006/01/02")) - hobocode.Iinfo(1, "This is an info message indented once") - hobocode.Iinfo(2, "This is an info message indented twice") - hobocode.Iinfof(1, "This is a formatted info message indented once: %v", time.Now().Format("2006/01/02")) - hobocode.Iinfof(2, "This is a formatted info message indented twice: %v", time.Now().Format("2006/01/02")) - - hobocode.Header("Warn") - hobocode.Warn("This is a warn message") - hobocode.Warnf("This is a formatted warn message: %v", time.Now().Format("2006/01/02")) - - hobocode.Header("Error") - hobocode.Error("This is an error message") - hobocode.Errorf("This is a formatted error message: %v", fmt.Errorf("error occured at %v", time.Now().Format("2006/01/02"))) - - hobocode.Header("Success") - hobocode.Success("This is a success message!") - hobocode.Successf("This is a formatted successs message! %v", time.Now().Format("2006/01/02")) - - hobocode.Header("Debug") - hobocode.Debug("This is a debug message!") - hobocode.Debugf("This is a formatted debug message! %v", time.Now().Format("2006/01/02")) - - hobocode.HeaderLeft("A left justified header!?") - hobocode.HeaderLeft("Whoa!") - hobocode.HeaderLeft("That's cool") - -} diff --git a/hobocode.go b/hobocode.go index d8139f5..bb3c8bf 100644 --- a/hobocode.go +++ b/hobocode.go @@ -18,15 +18,30 @@ import ( // Stdoutln prints with newline to os.Stdout func Stdoutln(message string) { - fmt.Fprintln(os.Stdout, message) + Stdlin(os.Stdout, message) } // Stderrln prints with newline to os.Stderr func Stderrln(message string) { - fmt.Fprintln(os.Stderr, message) + Stdlin(os.Stderr, message) +} + +// Stdout prints without newline to os.Stdout +func Stdout(message string) { + Std(os.Stdout, message) +} + +// Stderr prints without newline to os.Stdout +func Stderr(message string) { + Std(os.Stderr, message) } // Stdlin prints with newline to *os.File the given message func Stdlin(fd *os.File, message string) { fmt.Fprintln(fd, message) } + +// Std prints without newline to *os.FIle +func Std(fd *os.File, message string) { + fmt.Fprint(fd, message) +} diff --git a/userio.go b/userio.go index 0e525b8..b70916e 100644 --- a/userio.go +++ b/userio.go @@ -2,37 +2,44 @@ package hobocode import ( "fmt" + "os" + "strconv" "strings" "github.com/jedib0t/go-pretty/v6/text" ) -/* - TODO: User IO stuff is rough and needs attention -*/ - // Input acquires user CLI input +func Input(message string) string { + Ficolor(0, out, text.FgHiYellow, message) + var ret string + fmt.Scanln(&ret) + return ret +} + +// Inputd acquires user CLI input with a default // defaultOption is presented as the default and used on nil entry -func Input(defaultOption string, message string) string { - fmt.Print(text.FgHiYellow.Sprintf("%s [%s]: ", message, defaultOption)) +func Inputd(defaultOption string, message string) string { + Ficolorf(0, out, text.FgHiYellow, "%s [%s]: ", message, defaultOption) fmt.Scanln(&defaultOption) return defaultOption } // Inputf acquires user CLI input using a formatted message // defaultOption is presented as the default and used on nil entry -func Inputf(defaultOption string, format string, a ...interface{}) string { - fmt.Print(text.FgHiYellow.Sprintf(format, a...)) - fmt.Scanln(&defaultOption) - return defaultOption +func Inputf(format string, a ...interface{}) string { + var ret string + Ficolorf(0, out, text.FgHiYellow, format, a...) + fmt.Scanln(&ret) + return ret } -// Confirm is a naive confirmation prompt in HiRed that won't break until an affirmative answer is given +// Confirm is a naive confirmation prompt in HiRed that won't break until an explicit answer is given func Confirm(message string) bool { var ret string for { - fmt.Print(text.FgHiRed.Sprintf("%s [(y)es / (n)o]: ", message)) + Ficolorf(0, os.Stdout, text.FgHiWhite, "%s (Y/n): ", message) fmt.Scanln(&ret) if strings.ToLower(ret) == "y" || strings.ToLower(ret) == "yes" { @@ -43,7 +50,6 @@ func Confirm(message string) bool { continue } } - } // Confirmf is a naive confirmation prompt in HiRed that won't break until an affirmative answer is given @@ -52,3 +58,51 @@ func Confirmf(format string, a ...interface{}) bool { msg := fmt.Sprintf(format, a...) return Confirm(msg) } + +// Select allows the user to select from a list of options +// the index of the options is used as the selection and the user +// selection is returned as that int +// a response of -1 indicates a failure to capture input +// message introduces the list selection +// prompt is the input line +func Selection(options []string, message string, prompt string) int { + Icolor(0, out, text.FgHiWhite, message) + for i, opt := range options { + Icolorf(1, out, text.FgHiWhite, "%d): %s", i, opt) + } + + resp := Input(prompt) + + respInt, err := strconv.Atoi(resp) + if err != nil { + return -1 + } + + return respInt +} + +// Selectr allows the user to select from a list of options with a recommended option +// the index of the options is used as the selection and the user +// selection is returned as that int +// a response of -1 indicates a failure to capture input +// message introduces the list selection +// prompt is the input line +func Selectionr(options []string, recommendedOption int, message string, prompt string) int { + Icolor(0, out, text.FgHiWhite, message) + for i, opt := range options { + if i == recommendedOption { + Icolorf(1, out, text.FgHiWhite, "%d): %s <--", i, opt) + } else { + Icolorf(1, out, text.FgHiWhite, "%d): %s", i, opt) + } + } + + resp := Inputd(fmt.Sprintf("%d", recommendedOption), prompt) + + respInt, err := strconv.Atoi(resp) + if err != nil { + return -1 + } + + return respInt +}