Do you enjoy working with Command Line Interface (CLI) applications? If so, then you’re going to love this tutorial on building a CLI app with Go called lolcat.
While searching for some terminal applications for inspiration, I stumbled upon lolcat. The original version can be found at https://github.com/busyloop/lolcat, but there are already several Go implementations available. Here are some examples:
Now, you might be thinking, why would anyone want to build something so useless? Well, sometimes it’s just fun to build something for the sake of it. So let’s get started!
First, let’s start by printing some values on the screen. Then, we’ll move on to coloring them, and finally, we’ll look into accepting user input to work as a pipe.
To generate some random output, I’ll be using the Faker library, which you can get by running the following command:
go get -u github.com/enodata/faker
The following Go code uses the Faker library to generate a number of phrases and prints them on the screen:
package main
import (
"fmt"
"strings"
"github.com/enodata/faker"
)
func main() {
var phrases []string
for i := 1; i < 3; i++ {
phrases = append(phrases, faker.Hacker().Phrases()...)
}
fmt.Println(strings.Join(phrases[:], "; "))
}
Unfortunately, the output is displayed in boring black and white. Let’s add some color to make it more interesting. We can do this by using escape character sequences in fmt.Printf
to print the strings in gold color (#FFD700), which has an RGB color code of (255, 215, 0):
package main
import (
"fmt"
"strings"
"github.com/enodata/faker"
)
func main() {
var phrases []string
for i := 1; i < 3; i++ {
phrases = append(phrases, faker.Hacker().Phrases()...)
}
output := strings.Join(phrases[:], "; ")
r, g, b := 255, 215, 0 // gold color
for j := 0; j < len(output); j++ {
fmt.Printf("\033[38;2;%d;%d;%dm%c\033[0m", r, g, b, output[j])
}
}
Now the output is displayed in a shiny gold color.
But why stop at just one color? Let’s introduce a rainbow effect to make it even more exciting. We can achieve this by modifying the code as follows:
package main
import (
"fmt"
"math"
"strings"
"github.com/enodata/faker"
)
func rgb(i int) (int, int, int) {
var f = 0.1
return int(math.Sin(f*float64(i)+0)*127+128),
int(math.Sin(f*float64(i)+2*math.Pi/3)*127+128),
int(math.Sin(f*float64(i)+4*math.Pi/3)*127+128)
}
func main() {
var phrases []string
for i := 1; i < 3; i++ {
phrases = append(phrases, faker.Hacker().Phrases()...)
}
output := strings.Join(phrases[:], "; ")
for j := 0; j < len(output); j++ {
r, g, b := rgb(j)
fmt.Printf("\033[38;2;%d;%d;%dm%c\033[0m", r, g, b, output[j])
}
fmt.Println()
}
Now the output is displayed in a beautiful rainbow of colors.
But what if we want our program to work as a pipe for other programs? Instead of providing its own output, we can modify the program to read content from os.Stdin
and apply the rainbow effect to it. Here’s an example of how to do that:
package main
import (
"bufio"
"fmt"
"io"
"math"
"os"
)
func rgb(i int) (int, int, int) {
var f = 0.1
return int(math.Sin(f*float64(i)+0)*127+128),
int(math.Sin(f*float64(i)+2*math.Pi/3)*127+128),
int(math.Sin(f*float64(i)+4*math.Pi/3)*127+128)
}
func print(output []rune) {
for j := 0; j < len(output); j++ {
r, g, b := rgb(j)
fmt.Printf("\033[38;2;%d;%d;%dm%c\033[0m", r, g, b, output[j])
}
fmt.Println()
}
func main() {
info, _ := os.Stdin.Stat()
var output []rune
if info.Mode()&os.ModeCharDevice != 0 {
fmt.Println("The command is intended to work with pipes.")
fmt.Println("Usage: fortune | gorainbow")
}
reader := bufio.NewReader(os.Stdin)
for {
input, _, err := reader.ReadRune()
if err != nil && err == io.EOF {
break
}
output = append(output, input)
}
print(output)
}
Now the program reads one rune at a time from os.Stdin
and applies the rainbow effect to it.
If you want a more efficient approach, you can apply the rainbow effect “just in time” as each rune is scanned, like this:
package main
import (
"bufio"
"fmt"
"io"
"math"
"os"
)
func rgb(i int) (int, int, int) {
var f = 0.1
return int(math.Sin(f*float64(i)+0)*127+128),
int(math.Sin(f*float64(i)+2*math.Pi/3)*127+128),
int(math.Sin(f*float64(i)+4*math.Pi/3)*127+128)
}
func main() {
info, _ := os.Stdin.Stat()
if info.Mode()&os.ModeCharDevice != 0 {
fmt.Println("The command is intended to work with pipes.")
fmt.Println("Usage: fortune | gorainbow")
}
reader := bufio.NewReader(os.Stdin)
j := 0
for {
input, _, err := reader.ReadRune()
if err != nil && err == io.EOF {
break
}
r, g, b := rgb(j)
fmt.Printf("\033[38;2;%d;%d;%dm%c\033[0m", r, g, b, input)
j++
}
}
It works the same way as before, but it applies the rainbow effect as each rune is read.
Now, let’s have some fun with fortune and cowsay. Here’s an example of how we can use lolcat as a pipe with these programs to generate colorful output:
fortune | gololcat | cowsay
And here’s an example of using it with cowsay and the “meow” option:
cowsay -f meow | gololcat
Finally, to make this a system-wide command, you can build and install it using the following commands:
go build
go install
The command will be accessible as gololcat
. Now you can enjoy your rainbow-colored terminal output everywhere.
Tags: Go, CLI, lolcat, terminal applications