Go CLI Tutorial: Fortune Clone

I have written two CLI application tutorials to buildGorolkatwithCocosse. I usefortuneAs an input generator.

In this article, I will complete the pipeline trilogy throughgofortune.

what iswealth, The first one?As href="Encyclopedia said, Fortune is a simple program that displays pseudo-random messages from the quote database.

Basically, it is a random quote generator.

Its history can be traced back to Unix version 7 (1979). It is still very powerful. Many Linux distributions have it pre-installed, and on OSX, you can usebrew install fortune.

On some systems, when using the shell, it is used as a greeting or parting message.

href="Encyclopedia also says

Many people choose to use their wealthCowsayCommand to add more humor to the dialog.

that's me! Except I use minegocowsaycommand.

Enough introduction,Let's build a wealth clone with Go.

The following is a breakdown of the functions of our program.

The location of the wealth folder depends on the system and distribution, they are build markers. I can hardcode it or use environment variables, but as an exercise, I will do a dirty thing and askfortuneBy direct execution-fFlag, output:

The first line of output contains the path to the fortunes folder.

package main

import ( “fmt” “os/exec” )

func main() { out, err := exec.Command(“fortune”, “-f”).CombinedOutput() if err != nil { panic(err) }

<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(string(<span style="color:#a6e22e">out</span>))

}

This code snippet replicates the output exactly in my way. It looks likefortune -fWrite output to stderr, that's why I useCombinedOutputTo get bothstdoutwithstderr.

However, I only want the first row. How to do it? This will print all the outputstderrLine by line:

package main

import ( “bufio” “fmt” “os/exec” )

func main() { fortuneCommand := exec.Command(“fortune”, “-f”) pipe, err := fortuneCommand.StderrPipe() if err != nil { panic(err) } for outputStream.Scan() { fmt.Println(outputStream.Text()) } }

In order to get only the first row, I removed the for loop, and then scanned the first row:

package main

import ( “bufio” “fmt” “os/exec” )

func main() { fortuneCommand := exec.Command(“fortune”, “-f”) pipe, err := fortuneCommand.StderrPipe() if err != nil { panic(err) } fortuneCommand.Start() outputStream := bufio.NewScanner(pipe) outputStream.Scan() fmt.Println(outputStream.Text()) }

Now let's select that line and extract the path.

On my system, the first line of output is100.00% /usr/local/Cellar/fortune/9708/share/games/fortunes. Let's make a substring starting from the first occurrence/character:

line := outputStream.Text()
path := line[strings.Index(line, "/"):]

Now, I have a way to get rich. I can index the files found there. Have.datBinary files and plain text files. I will discard the binary file and thenoff/Folder, which contains annoying luck.

Let us first index the files. I usepath/filepathpackageWalkThe method to iterate the file tree from the following locationsroot. I use it insteadioutil.ReadDir()Because we may have nested destiny folders. insideWalkFunc visitI discarded the .dat file usingfilepath.Ext(), I discarded the folder file (e.g./off, But does not include files in subfolders) and all annoying fortunes (located in the following locations)/off, And then print the value of each remaining file.

func visit(path string, f os.FileInfo, err error) error {
	if strings.Contains(path, "/off/") {
		return nil
	}
	if filepath.Ext(path) == ".dat" {
		return nil
	}
	if f.IsDir() {
		return nil
	}
	files = append(files, path)
	return nil
}

func main() { fortuneCommand := exec.Command(“fortune”, “-f”) pipe, err := fortuneCommand.StderrPipe() if err != nil { panic(err) } fortuneCommand.Start() outputStream := bufio.NewScanner(pipe) outputStream.Scan() line := outputStream.Text() root := line[strings.Index(line, “/”):]

<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">filepath</span>.<span style="color:#a6e22e">Walk</span>(<span style="color:#a6e22e">root</span>, <span style="color:#a6e22e">visit</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
	panic(<span style="color:#a6e22e">err</span>)
}

}

Let's put these values in a slice so that we can choose a random value later: I defined afilesPart of the string, I append it tovisit()Features. At the end ofmain()I print out the number of files I got.

package main

import ( “bufio” “log” “os” “os/exec” “path/filepath” “strings” )

var files []string

func visit(path string, f os.FileInfo, err error) error { if err != nil { log.Fatal(err) } if strings.Contains(path, “/off/”) { return nil } if filepath.Ext(path) == “.dat” { return nil } if f.IsDir() { return nil } files = append(files, path) return nil }

func main() { fortuneCommand := exec.Command(“fortune”, “-f”) pipe, err := fortuneCommand.StderrPipe() if err != nil { panic(err) } fortuneCommand.Start() outputStream := bufio.NewScanner(pipe) outputStream.Scan() line := outputStream.Text() root := line[strings.Index(line, “/”):]

<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">filepath</span>.<span style="color:#a6e22e">Walk</span>(<span style="color:#a6e22e">root</span>, <span style="color:#a6e22e">visit</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
	panic(<span style="color:#a6e22e">err</span>)
}

println(len(<span style="color:#a6e22e">files</span>))

}

I now useGo to random number generatorThe function to select random items from an array:

// Returns an int >= min, < max
func randomInt(min, max int) int {
	return min + rand.Intn(max-min)
}

func main() {

<span style="color:#75715e">//...

rand.Seed(time.Now().UnixNano()) i := randomInt(1, len(files)) randomFile := files[i] println(randomFile) }

Now, our program will print a random wealth file name every time it runs.

What I miss now is the luck in scanning the documents and then printing them randomly. In each file, quotes are used%Sit alone on a line. I can easily detect this pattern and scan every quote in the array:

file, err := os.Open(randomFile)
if err != nil {
    panic(err)
}
defer file.Close()

b, err := ioutil.ReadAll(file) if err != nil { panic(err) }

quotes := string(b)

quotesSlice := strings.Split(quotes, “%”) j := randomInt(1, len(quotesSlice))

fmt.Print(quotesSlice[j])

This is not really effective method, because I want to scan the entire wealth file in a slice, and then select a random item, but it works as follows:

So this is our very basic final versionfortuneclone. It misses a lot of originalfortuneOrder, but this is the beginning.

package main

import ( “bufio” “fmt” “io/ioutil” “log” “math/rand” “os” “os/exec” “path/filepath” “strings” “time” )

var files []string

// Returns an int >= min, < max func randomInt(min, max int) int { return min + rand.Intn(max-min) }

func visit(path string, f os.FileInfo, err error) error { if err != nil { log.Fatal(err) } if strings.Contains(path, “/off/”) { return nil } if filepath.Ext(path) == “.dat” { return nil } if f.IsDir() { return nil } files = append(files, path) return nil }

func main() { fortuneCommand := exec.Command(“fortune”, “-f”) pipe, err := fortuneCommand.StderrPipe() if err != nil { panic(err) } fortuneCommand.Start() outputStream := bufio.NewScanner(pipe) outputStream.Scan() line := outputStream.Text() root := line[strings.Index(line, “/”):]

<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">filepath</span>.<span style="color:#a6e22e">Walk</span>(<span style="color:#a6e22e">root</span>, <span style="color:#a6e22e">visit</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
	panic(<span style="color:#a6e22e">err</span>)
}

<span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">Seed</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>().<span style="color:#a6e22e">UnixNano</span>())
<span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">randomInt</span>(<span style="color:#ae81ff">1</span>, len(<span style="color:#a6e22e">files</span>))
<span style="color:#a6e22e">randomFile</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">files</span>[<span style="color:#a6e22e">i</span>]

<span style="color:#a6e22e">file</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Open</span>(<span style="color:#a6e22e">randomFile</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
	panic(<span style="color:#a6e22e">err</span>)
}
<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">file</span>.<span style="color:#a6e22e">Close</span>()

<span style="color:#a6e22e">b</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ioutil</span>.<span style="color:#a6e22e">ReadAll</span>(<span style="color:#a6e22e">file</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
	panic(<span style="color:#a6e22e">err</span>)
}

<span style="color:#a6e22e">quotes</span> <span style="color:#f92672">:=</span> string(<span style="color:#a6e22e">b</span>)

<span style="color:#a6e22e">quotesSlice</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Split</span>(<span style="color:#a6e22e">quotes</span>, <span style="color:#e6db74">"%"</span>)
<span style="color:#a6e22e">j</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">randomInt</span>(<span style="color:#ae81ff">1</span>, len(<span style="color:#a6e22e">quotesSlice</span>))

<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Print</span>(<span style="color:#a6e22e">quotesSlice</span>[<span style="color:#a6e22e">j</span>])

}

To summarize, I movevisitAs an inline function parameterfilepath.WalkAnd movefilesBecome an internal local variablemain()Instead of global file variables:

package main

import ( “bufio” “fmt” “io/ioutil” “log” “math/rand” “os” “os/exec” “path/filepath” “strings” “time” )

// Returns an int >= min, < max func randomInt(min, max int) int { return min + rand.Intn(max-min) }

func main() { var files []string

<span style="color:#a6e22e">fortuneCommand</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">exec</span>.<span style="color:#a6e22e">Command</span>(<span style="color:#e6db74">"fortune"</span>, <span style="color:#e6db74">"-f"</span>)
<span style="color:#a6e22e">pipe</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fortuneCommand</span>.<span style="color:#a6e22e">StderrPipe</span>()
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
	panic(<span style="color:#a6e22e">err</span>)
}
<span style="color:#a6e22e">fortuneCommand</span>.<span style="color:#a6e22e">Start</span>()
<span style="color:#a6e22e">outputStream</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bufio</span>.<span style="color:#a6e22e">NewScanner</span>(<span style="color:#a6e22e">pipe</span>)
<span style="color:#a6e22e">outputStream</span>.<span style="color:#a6e22e">Scan</span>()
<span style="color:#a6e22e">line</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">outputStream</span>.<span style="color:#a6e22e">Text</span>()
<span style="color:#a6e22e">root</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">line</span>[<span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Index</span>(<span style="color:#a6e22e">line</span>, <span style="color:#e6db74">"/"</span>):]

<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">filepath</span>.<span style="color:#a6e22e">Walk</span>(<span style="color:#a6e22e">root</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">path</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">f</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">FileInfo</span>, <span style="color:#a6e22e">err</span> <span style="color:#66d9ef">error</span>) <span style="color:#66d9ef">error</span> {
    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
        <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">err</span>)
    }
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Contains</span>(<span style="color:#a6e22e">path</span>, <span style="color:#e6db74">"/off/"</span>) {
		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
	}
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">filepath</span>.<span style="color:#a6e22e">Ext</span>(<span style="color:#a6e22e">path</span>) <span style="color:#f92672">==</span> <span style="color:#e6db74">".dat"</span> {
		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
	}
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">f</span>.<span style="color:#a6e22e">IsDir</span>() {
		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
	}
	<span style="color:#a6e22e">files</span> = append(<span style="color:#a6e22e">files</span>, <span style="color:#a6e22e">path</span>)
	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
})
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
	panic(<span style="color:#a6e22e">err</span>)
}

<span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">Seed</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>().<span style="color:#a6e22e">UnixNano</span>())
<span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">randomInt</span>(<span style="color:#ae81ff">1</span>, len(<span style="color:#a6e22e">files</span>))
<span style="color:#a6e22e">randomFile</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">files</span>[<span style="color:#a6e22e">i</span>]

<span style="color:#a6e22e">file</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Open</span>(<span style="color:#a6e22e">randomFile</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
	panic(<span style="color:#a6e22e">err</span>)
}
<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">file</span>.<span style="color:#a6e22e">Close</span>()

<span style="color:#a6e22e">b</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ioutil</span>.<span style="color:#a6e22e">ReadAll</span>(<span style="color:#a6e22e">file</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
	panic(<span style="color:#a6e22e">err</span>)
}

<span style="color:#a6e22e">quotes</span> <span style="color:#f92672">:=</span> string(<span style="color:#a6e22e">b</span>)

<span style="color:#a6e22e">quotesSlice</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Split</span>(<span style="color:#a6e22e">quotes</span>, <span style="color:#e6db74">"%"</span>)
<span style="color:#a6e22e">j</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">randomInt</span>(<span style="color:#ae81ff">1</span>, len(<span style="color:#a6e22e">quotesSlice</span>))

<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Print</span>(<span style="color:#a6e22e">quotesSlice</span>[<span style="color:#a6e22e">j</span>])

}

I can nowgo build; go installAnd trilogygofortune gocowsaywithgololcatfinished:


More tutorials: