How do I do cursor-up in Go? - go

How do I do “cursor-up” in Go? (Clear-to-end-of-line would also be good to know). (All platforms).
To elaborate and show the context, I’m writing a test program in Go that requires the input of some parameters (via console) that are stored in a text file and used as defaults for the next usage. I want to have some very rudimentary console “editing” features.
Currently it is fairly primitive because I don’t want to go deeply into console editing, I just want something fairly basic but also not too basic.
In the example below from my test program, the String variable “sPrompt” contains the prompt for the input, and to the right it shows the default and then there are backspace characters to position the cursor so that the default is not overwritten – like I said, very basic.
When the operator enters the input, if an error, I'd like to display an error message, and then in either case move the cursor up to the line just displayed/entered and if an error, then display the original line, or if correct, display just the prompt and the new parameter.
I did read somewhere that ReadLine() should be avoided, but it appears to do the job.
Example:
func fInputString(sPrompt string, asValid []string, sDefault string)
(sInput string, tEnd bool) {
oBufReader := bufio.NewReader(os.Stdin)
for {
print("\n" + sPrompt)
vLine, _, _ := oBufReader.ReadLine()
sInput = strings.ToLower(string(vLine))
if sInput == "end" {
return "", true
}
if sInput == "" {
sInput = sDefault
}
// check for valid input //
for _, sVal := range asValid {
if sInput == sVal {
return sInput, false
}
}
}
}
This is how sPrompt is constructed (not meant to be optimized):
if sDefault != "" {
for len(sPrompt) < 67 {
sPrompt += " "
}
sPrompt += sDefault
for iBack := 20 + len(sDefault); iBack > 0; iBack-- {
sPrompt += "\b"
}
}

With tput strings that control the cursor around the screen:
tput sc to save the cursor position
tput rc to restore the cursor position
Then using these strings in the Go function:
package main
import (
"fmt"
"time"
)
const sc = "\u001B7"
const rc = "\u001B8"
func main() {
fmt.Print(sc)
for i := 1; i <= 10; i++ {
fmt.Print(rc + sc)
fmt.Println(i, "one")
fmt.Println(i, "two")
fmt.Println(i, "three")
fmt.Println(i, "four")
fmt.Println(i, "five")
time.Sleep(time.Second)
}
}
Should work in most terminals.

Make your own little shell
You should not reinvent the wheel and use a library which does exactly what you want.
A popular option is the readline library which is apparently available for Windows
as well. This is used, for example, by bash and ZSH. There are some Go wrappers for it:
https://github.com/shavac/readline
https://github.com/bobappleyard/readline
I personally would recommend bobappleyard/readline as it is better documented
and has a nicer API (less C-like). There does not seem to be a special build tag for
Windows, so you might have to write it for yourself but that should be not that hard.
termbox and its (pure) Go implementation termbox-go which was already pointed out by #bgp does not seem to be good for simply reading a line as it seems to be more intended
for full screen console applications. Also, you would have to code the up/down matching
and history yourself.
.Readline()
The doc is right by saying that you should not use this as it does not handle anything for
you. For example, reading from a stream that emits partial data, you have no guarantee that you will get a full line from Readline. Use ReadSlice('\n') for that.

Related

Issue with ANSI cursor movement in goroutine

Background
I'm trying to write a Go library for creating terminal task-lists, inspired by the Node library listr.
My library, golist, prints the task list out in a background goroutine and updates the text and status characters using ANSI escape sequences.
The Problem
There's an issue where the final print of the list will occasionally have extra spaces included, leading to some spaces or repeated lines. Here are two examples – one correct, one not – both from runs of the same exact code (here's a link to the code).
Example
Here's an example of what it should look like:
(Here's a gist of the raw text output for the correct output)
And here's an example of what it sometimes looks like:
(Here's a gist of the raw text output for the incorrect output)
If you look at lines 184 and 185 in the gist of the incorrect version, there are two blank lines that aren't in the correct version.
Why is this happening and why is it only happening sometimes?
Code
I'm printing the list to the terminal in the following loop:
go func() {
defer donePrinting() // Tell the Stop function that we're done printing
ts := l.getTaskStates()
l.print(ts)
for {
select {
case <-ctx.Done(): // Check if the print loop should stop
// Perform a final clear and an optional print depending on `ClearOnComplete`
ts := l.getTaskStates()
if l.ClearOnComplete {
l.clear(ts)
return
}
l.clearThenPrint(ts)
return
case s := <-l.printQ: // Check if there's a message to print
fmt.Fprintln(l.Writer, s)
default: // Otherwise, print the list
ts := l.getTaskStates()
l.clearThenPrint(ts)
l.StatusIndicator.Next()
time.Sleep(l.Delay)
}
}
}()
The list is formatted as a string and then printed. The following function formats the string:
// fmtPrint returns the formatted list of messages
// and statuses, using the supplied TaskStates
func (l *List) fmtPrint(ts []*TaskState) string {
s := make([]string, 0)
for _, t := range ts {
s = append(s, l.formatMessage(t))
}
return strings.Join(s, "\n")
}
and the following function builds the ANSI escape string to clear the lines:
// fmtClear returns a string of ANSI escape characters
// to clear the `n` lines previously printed.
func (l *List) fmtClear(n int) string {
s := "\033[1A" // Move up a line
s += "\033[K" // Clear the line
s += "\r" // Move back to the beginning of the line
return strings.Repeat(s, n)
}
I'm using this site as a reference for the ANSI codes.
Thanks in advance for any suggestions you might have about why this is happening!
Let me know if there's any other information I can add that can help.
I think the ANSI codes are just a red herring. I pulled down the library and tried running it locally, and found that the following section is what is creating this issue:
case s := <-l.printQ: // Check if there's a message to print
fmt.Fprintln(l.Writer, s)
When the printQ channel is getting closed, this case is sometimes running, which seems to be moving the cursor down even though nothing is getting printed. This behaviour went away when I moved the call to close the channel after l.printDone is called.
...
// Wait for the print loop to finish
<-l.printDone
if l.printQ != nil {
close(l.printQ)
}
...
This ensures that the loop is no longer running when the channel is closed, and thus the s := <-l.printQ case cannot run.

Writing a struct's fields and values of different types to a file in Go

I'm writing a simple program that takes in input from a form, populates an instance of a struct with the received data and the writes this received data to a file.
I'm a bit stuck at the moment with figuring out the best way to iterate over the populated struct and write its contents to the file.
The struct in question contains 3 different types of fields (ints, strings, []strings).
I can iterate over them but I am unable to get their actual type.
Inspecting my posted code below with print statements reveals that each of their types is coming back as structs rather than the aforementioned string, int etc.
The desired output format is be plain text.
For example:
field_1="value_1"
field_2=10
field_3=["a", "b", "c"]
Anyone have any ideas? Perhaps I'm going about this the wrong way entirely?
func (c *Config) writeConfigToFile(file *os.File) {
listVal := reflect.ValueOf(c)
element := listVal.Elem()
for i := 0; i < element.NumField(); i++ {
field := element.Field(i)
myType := reflect.TypeOf(field)
if myType.Kind() == reflect.Int {
file.Write(field.Bytes())
} else {
file.WriteString(field.String())
}
}
}
Instead of using the Bytes method on reflect.Value which does not work as you initially intended, you can use either the strconv package or the fmt to format you fields.
Here's an example using fmt:
var s string
switch fi.Kind() {
case reflect.String:
s = fmt.Sprintf("%q", fi.String())
case reflect.Int:
s = fmt.Sprintf("%d", fi.Int())
case reflect.Slice:
if fi.Type().Elem().Kind() != reflect.String {
continue
}
s = "["
for j := 0; j < fi.Len(); j++ {
s = fmt.Sprintf("%s%q, ", s, fi.Index(i).String())
}
s = strings.TrimRight(s, ", ") + "]"
default:
continue
}
sf := rv.Type().Field(i)
if _, err := fmt.Fprintf(file, "%s=%s\n", sf.Name, s); err!= nil {
panic(err)
}
Playground: https://play.golang.org/p/KQF3CicVzA
Why not use the built-in gob package to store your struct values?
I use it to store different structures, one per line, in files. During decoding, you can test the type conversion or provide a hint in a wrapper - whichever is faster for your given use case.
You'd treat each line as a buffer when Encoding and Decoding when reading back the line. You can even gzip/zlib/compress, encrypt/decrypt, etc the stream in real-time.
No point in re-inventing the wheel when you have a polished and armorall'd wheel already at your disposal.

Compare Go stdout to file contents with testing package [duplicate]

I have a simple function I want to test:
func (t *Thing) print(min_verbosity int, message string) {
if t.verbosity >= minv {
fmt.Print(message)
}
}
But how can I test what the function actually sends to standard output? Test::Output does what I want in Perl. I know I could write all my own boilerplate to do the same in Go (as described here):
orig = os.Stdout
r,w,_ = os.Pipe()
thing.print("Some message")
var buf bytes.Buffer
io.Copy(&buf, r)
w.Close()
os.Stdout = orig
if(buf.String() != "Some message") {
t.Error("Failure!")
}
But that's a lot of extra work for every single test. I'm hoping there's a more standard way, or perhaps an abstraction library to handle this.
One thing to also remember, there's nothing stopping you from writing functions to avoid the boilerplate.
For example I have a command line app that uses log and I wrote this function:
func captureOutput(f func()) string {
var buf bytes.Buffer
log.SetOutput(&buf)
f()
log.SetOutput(os.Stderr)
return buf.String()
}
Then used it like this:
output := captureOutput(func() {
client.RemoveCertificate("www.example.com")
})
assert.Equal(t, "removed certificate www.example.com\n", output)
Using this assert library: http://godoc.org/github.com/stretchr/testify/assert.
You can do one of three things. The first is to use Examples.
The package also runs and verifies example code. Example functions may include a concluding line comment that begins with "Output:" and is compared with the standard output of the function when the tests are run. (The comparison ignores leading and trailing space.) These are examples of an example:
func ExampleHello() {
fmt.Println("hello")
// Output: hello
}
The second (and more appropriate, IMO) is to use fake functions for your IO. In your code you do:
var myPrint = fmt.Print
func (t *Thing) print(min_verbosity int, message string) {
if t.verbosity >= minv {
myPrint(message) // N.B.
}
}
And in your tests:
func init() {
myPrint = fakePrint // fakePrint records everything it's supposed to print.
}
func Test...
The third is to use fmt.Fprintf with an io.Writer that is os.Stdout in production code, but bytes.Buffer in tests.
You could consider adding a return statement to your function to return the string that is actually printed out.
func (t *Thing) print(min_verbosity int, message string) string {
if t.verbosity >= minv {
fmt.Print(message)
return message
}
return ""
}
Now, your test could just check the returned string against an expected string (rather than the print out). Maybe a bit more in-line with Test Driven Development (TDD).
And, in your production code, nothing would need to change, since you don't have to assign the return value of a function if you don't need it.

Golang Flag gets interpreted as first os.Args argument

I would like to run my program like this:
go run launch.go http://example.com --m=2 --strat=par
"http://example.com" gets interpreted as the first command line argument, which is ok, but the flags are not parsed after that and stay at the default value. If I put it like this:
go run launch.go --m=2 --strat=par http://example.com
then "--m=2" is interpreted as the first argument (which should be the URL).
I could also just remove the os.Args completely, but then I would have only optional flags and I want one (the URL) to be mandatory.
Here's my code:
package main
import (
"fmt"
"webcrawler/crawler"
"webcrawler/model"
"webcrawler/urlutils"
"os"
"flag"
)
func main() {
if len(os.Args) < 2 {
log.Fatal("Url must be provided as first argument")
}
strategy := flag.String("strat", "par", "par for parallel OR seq for sequential crawling strategy")
routineMultiplier := flag.Int("m", 1, "Goroutine multiplier. Default 1x logical CPUs. Only works in parallel strategy")
page := model.NewBasePage(os.Args[1])
urlutils.BASE_URL = os.Args[1]
flag.Parse()
pages := crawler.Crawl(&page, *strategy, *routineMultiplier)
fmt.Printf("Crawled: %d\n", len(pages))
}
I am pretty sure that this should be possible, but I can't figure out how.
EDIT:
Thanks justinas for the hint with the flag.Args(). I now adapted it like this and it works:
...
flag.Parse()
args := flag.Args()
if len(args) != 1 {
log.Fatal("Only one argument (URL) allowed.")
}
page := model.NewBasePage(args[0])
...
os.Args doesn't really know anything about the flag package and contains all command-line arguments. Try flag.Args() (after calling flag.Parse(), of course).
As a followup, to parse flags that follow a command like
runme init -m thisis
You can create your own flagset to skip the first value like
var myValue string
mySet := flag.NewFlagSet("",flag.ExitOnError)
mySet.StringVar(&myValue,"m","mmmmm","something")
mySet.Parse(os.Args[2:])
This tripped me up too, and since I call flag.String/flag.Int64/etc in a couple of places in my app, I didn't want to have to pass around a new flag.FlagSet all over the place.
// If a commandline app works like this: ./app subcommand -flag -flag2
// `flag.Parse` won't parse anything after `subcommand`.
// To still be able to use `flag.String/flag.Int64` etc without creating
// a new `flag.FlagSet`, we need this hack to find the first arg that has a dash
// so we know when to start parsing
firstArgWithDash := 1
for i := 1; i < len(os.Args); i++ {
firstArgWithDash = i
if len(os.Args[i]) > 0 && os.Args[i][0] == '-' {
break
}
}
flag.CommandLine.Parse(os.Args[firstArgWithDash:])
The reason I went with this is because flag.Parse just calls flag.CommandLine.Parse(os.Args[1:]) under the hood anyway.
You can check if the Arg starts with "--" or "-" and avoid using that Arg in a loop.
For example:
for _, file := range os.Args[1:] {
if strings.HasPrefix(file, "--") {
continue
}
//do stuff
}

How to have an in-place string that updates on stdout

I want to output to stdout and have the output "overwrite" the previous output.
For example; if I output On 1/10, I want the next output On 2/10 to overwrite On 1/10. How can I do this?
stdout is a stream (io.Writer). You cannot modify what was already written to it. What can be changed is how that stream's represented in case it is printed to a terminal. Note that there's no good reason to assume this scenario. For example, a user could redirect stdout to a pipe or to a file at will.
So the proper approach is to first check:
if the stdout is going to a terminal
what is that terminal's procedure to overwrite a line/screen
Both of the above are out of this question's scope, but let's assume that a terminal is our device. Then usually, printing:
fmt.Printf("\rOn %d/10", i)
will overwrite the previous line in the terminal. \r stands for carriage return, implemented by many terminals as moving the cursor to the beginning of the current line, hence providing the "overwrite line" facility.
As an example of "other" terminal with a differently supported 'overwriting', here is an example at the playground.
Use this solution if you want to rewrite multiple lines to the output. For instance, I made a decent Conway's "Game of Life" output using this method.
DISCLAIMER: this only works on ANSI Terminals, and besides using fmt this isn't a Go-specific answer either.
fmt.Printf("\033[0;0H")
// put your other fmt.Printf(...) here
Brief Explanation: this is an escape sequence which tells the ANSI terminal to move the cursor to a particular spot on the screen. The \033[ is the so-called escape sequence, and the 0;0H is the type of code telling the terminal move the cursor to row 0, column 0 of the terminal.
Source: https://en.wikipedia.org/wiki/ANSI_escape_code#Sequence_elements
The solution for one string which will replace whole string
fmt.Printf("\033[2K\r%d", i)
For example, it correctly prints from 10 to 0:
for i:= 10; i>=0; i-- {
fmt.Printf("\033[2K\r%d", i)
time.Sleep(1 * time.Second)
}
fmt.Println()
which previous answers don't solve.
Found something worth sharing for problems like this.
Sharing for people who might be facing same problem in future
Check if output is being written to terminal. If so, use \r (carriage return) defined by terminal to move cursor to the beginning of line
package main
import (
"flag"
"fmt"
"os"
"time"
)
var spinChars = `|/-\`
type Spinner struct {
message string
i int
}
func NewSpinner(message string) *Spinner {
return &Spinner{message: message}
}
func (s *Spinner) Tick() {
fmt.Printf("%s %c \r", s.message, spinChars[s.i])
s.i = (s.i + 1) % len(spinChars)
}
func isTTY() bool {
fi, err := os.Stdout.Stat()
if err != nil {
return false
}
return fi.Mode()&os.ModeCharDevice != 0
}
func main() {
flag.Parse()
s := NewSpinner("working...")
isTTY := isTTY()
for i := 0; i < 100; i++ {
if isTTY {
s.Tick()
}
time.Sleep(100 * time.Millisecond)
}
}
Example code taken from

Resources