/

Building an SEO Friendly Command Line App with Go: lolcat

Building an SEO Friendly Command Line App with Go: lolcat

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:

1
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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:

1
fortune | gololcat | cowsay

And here’s an example of using it with cowsay and the “meow” option:

1
cowsay -f meow | gololcat

Finally, to make this a system-wide command, you can build and install it using the following commands:

1
2
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