Création d'une commande CLI avec Go: cowsay

Vous aimez les applications CLI?Ne manquez pas lelol chattutoriel aussi!

Cowsayest l'une de ces applications dont vous ne pouvez pas vous passer.

Il génère essentiellement des images ASCII d'une vache avec n'importe quel message auquel vous le transmettez, dans la capture d'écran ci-dessus en utilisantfortunepour le générer. Mais ce n'est pas limité au domaine de la vache, il peut imprimer des pingouins, des orignaux et de nombreux autres animaux.

Sonne comme une application utile pour porter sur Go!

De plus, j'aime la licence en anglais qui y est attachée:

==============
cowsay License
==============

cowsay is distributed under the same licensing terms as Perl: the Artistic License or the GNU General Public License. If you don’t want to track down these licenses and read them for yourself, use the parts that I’d prefer:

(0) I wrote it and you didn’t.

(1) Give credit where credit is due if you borrow the code for some other purpose.

(2) If you have any bugfixes or suggestions, please notify me so that I may incorporate them.

(3) If you try to make money off of cowsay, you suck.

Commençons par définir le problème. Nous voulons accepter les commentaires par le biais d'un tuyau et demander à notre vache de le dire.

La première itération lit l'entrée utilisateur à partir du tube et la réimprime. Pas trop compliqué.

package main

import ( “bufio” “fmt” “io” “os” )

func main() { info, _ := os.Stdin.Stat()

<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">info</span>.<span style="color:#a6e22e">Mode</span>()<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">ModeCharDevice</span> <span style="color:#f92672">!=</span> <span style="color:#ae81ff">0</span> {
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">"The command is intended to work with pipes."</span>)
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">"Usage: fortune | gocowsay"</span>)
	<span style="color:#66d9ef">return</span>
}

<span style="color:#a6e22e">reader</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bufio</span>.<span style="color:#a6e22e">NewReader</span>(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stdin</span>)
<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">output</span> []<span style="color:#66d9ef">rune</span>

<span style="color:#66d9ef">for</span> {
	<span style="color:#a6e22e">input</span>, <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">reader</span>.<span style="color:#a6e22e">ReadRune</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:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">io</span>.<span style="color:#a6e22e">EOF</span> {
		<span style="color:#66d9ef">break</span>
	}
	<span style="color:#a6e22e">output</span> = append(<span style="color:#a6e22e">output</span>, <span style="color:#a6e22e">input</span>)
}

<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">j</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">j</span> &lt; len(<span style="color:#a6e22e">output</span>); <span style="color:#a6e22e">j</span><span style="color:#f92672">++</span> {
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">"%c"</span>, <span style="color:#a6e22e">output</span>[<span style="color:#a6e22e">j</span>])
}

}

Il nous manque la vache, et nous devons également envelopper le message dans un ballon, joliment formaté.

Voici la première itération de notre programme:

package main

import ( “bufio” “fmt” “io” “os” “strings” “unicode/utf8” )

// buildBalloon takes a slice of strings of max width maxwidth // prepends/appends margins on first and last line, and at start/end of each line // and returns a string with the contents of the balloon func buildBalloon(lines []string, maxwidth int) string { var borders []string count := len(lines) var ret []string

<span style="color:#a6e22e">borders</span> = []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">"/"</span>, <span style="color:#e6db74">"\\"</span>, <span style="color:#e6db74">"\\"</span>, <span style="color:#e6db74">"/"</span>, <span style="color:#e6db74">"|"</span>, <span style="color:#e6db74">"&lt;"</span>, <span style="color:#e6db74">"&gt;"</span>}

<span style="color:#a6e22e">top</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">" "</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Repeat</span>(<span style="color:#e6db74">"_"</span>, <span style="color:#a6e22e">maxwidth</span><span style="color:#f92672">+</span><span style="color:#ae81ff">2</span>)
<span style="color:#a6e22e">bottom</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">" "</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Repeat</span>(<span style="color:#e6db74">"-"</span>, <span style="color:#a6e22e">maxwidth</span><span style="color:#f92672">+</span><span style="color:#ae81ff">2</span>)

<span style="color:#a6e22e">ret</span> = append(<span style="color:#a6e22e">ret</span>, <span style="color:#a6e22e">top</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">count</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">1</span> {
	<span style="color:#a6e22e">s</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#e6db74">"%s %s %s"</span>, <span style="color:#a6e22e">borders</span>[<span style="color:#ae81ff">5</span>], <span style="color:#a6e22e">lines</span>[<span style="color:#ae81ff">0</span>], <span style="color:#a6e22e">borders</span>[<span style="color:#ae81ff">6</span>])
	<span style="color:#a6e22e">ret</span> = append(<span style="color:#a6e22e">ret</span>, <span style="color:#a6e22e">s</span>)
} <span style="color:#66d9ef">else</span> {
	<span style="color:#a6e22e">s</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#e6db74">`%s %s %s`</span>, <span style="color:#a6e22e">borders</span>[<span style="color:#ae81ff">0</span>], <span style="color:#a6e22e">lines</span>[<span style="color:#ae81ff">0</span>], <span style="color:#a6e22e">borders</span>[<span style="color:#ae81ff">1</span>])
	<span style="color:#a6e22e">ret</span> = append(<span style="color:#a6e22e">ret</span>, <span style="color:#a6e22e">s</span>)
	<span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">1</span>
	<span style="color:#66d9ef">for</span> ; <span style="color:#a6e22e">i</span> &lt; <span style="color:#a6e22e">count</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
		<span style="color:#a6e22e">s</span> = <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#e6db74">`%s %s %s`</span>, <span style="color:#a6e22e">borders</span>[<span style="color:#ae81ff">4</span>], <span style="color:#a6e22e">lines</span>[<span style="color:#a6e22e">i</span>], <span style="color:#a6e22e">borders</span>[<span style="color:#ae81ff">4</span>])
		<span style="color:#a6e22e">ret</span> = append(<span style="color:#a6e22e">ret</span>, <span style="color:#a6e22e">s</span>)
	}
	<span style="color:#a6e22e">s</span> = <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#e6db74">`%s %s %s`</span>, <span style="color:#a6e22e">borders</span>[<span style="color:#ae81ff">2</span>], <span style="color:#a6e22e">lines</span>[<span style="color:#a6e22e">i</span>], <span style="color:#a6e22e">borders</span>[<span style="color:#ae81ff">3</span>])
	<span style="color:#a6e22e">ret</span> = append(<span style="color:#a6e22e">ret</span>, <span style="color:#a6e22e">s</span>)
}

<span style="color:#a6e22e">ret</span> = append(<span style="color:#a6e22e">ret</span>, <span style="color:#a6e22e">bottom</span>)
<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Join</span>(<span style="color:#a6e22e">ret</span>, <span style="color:#e6db74">"\n"</span>)

}

// tabsToSpaces converts all tabs found in the strings // found in the lines slice to 4 spaces, to prevent misalignments in // counting the runes func tabsToSpaces(lines []string) []string { var ret []string for _, l := range lines { l = strings.Replace(l, “\t”, " ", -1) ret = append(ret, l) } return ret }

// calculatemaxwidth given a slice of strings returns the length of the // string with max length func calculateMaxWidth(lines []string) int { w := 0 for _, l := range lines { len := utf8.RuneCountInString(l) if len > w { w = len } }

<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">w</span>

}

// normalizeStringsLength takes a slice of strings and appends // to each one a number of spaces needed to have them all the same number // of runes func normalizeStringsLength(lines []string, maxwidth int) []string { var ret []string for _, l := range lines { s := l + strings.Repeat(" ", maxwidth-utf8.RuneCountInString(l)) ret = append(ret, s) } return ret }

func main() { info, _ := os.Stdin.Stat()

<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">info</span>.<span style="color:#a6e22e">Mode</span>()<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">ModeCharDevice</span> <span style="color:#f92672">!=</span> <span style="color:#ae81ff">0</span> {
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">"The command is intended to work with pipes."</span>)
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">"Usage: fortune | gocowsay"</span>)
	<span style="color:#66d9ef">return</span>
}

<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">lines</span> []<span style="color:#66d9ef">string</span>

<span style="color:#a6e22e">reader</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bufio</span>.<span style="color:#a6e22e">NewReader</span>(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stdin</span>)

<span style="color:#66d9ef">for</span> {
	<span style="color:#a6e22e">line</span>, <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">reader</span>.<span style="color:#a6e22e">ReadLine</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:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">io</span>.<span style="color:#a6e22e">EOF</span> {
		<span style="color:#66d9ef">break</span>
	}
	<span style="color:#a6e22e">lines</span> = append(<span style="color:#a6e22e">lines</span>, string(<span style="color:#a6e22e">line</span>))
}

<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">cow</span> = <span style="color:#e6db74">`         \  ^__^

\ (oo)_______ (__)\ )/
||----w | || || `

<span style="color:#a6e22e">lines</span> = <span style="color:#a6e22e">tabsToSpaces</span>(<span style="color:#a6e22e">lines</span>)
<span style="color:#a6e22e">maxwidth</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">calculateMaxWidth</span>(<span style="color:#a6e22e">lines</span>)
<span style="color:#a6e22e">messages</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">normalizeStringsLength</span>(<span style="color:#a6e22e">lines</span>, <span style="color:#a6e22e">maxwidth</span>)
<span style="color:#a6e22e">balloon</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">buildBalloon</span>(<span style="color:#a6e22e">messages</span>, <span style="color:#a6e22e">maxwidth</span>)
<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">balloon</span>)
<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">cow</span>)
<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>()

}

Rendons maintenant la figure configurable, en ajoutant unstégosaure

L'application d'origine utilise le-fdrapeau pour accepter une figure personnalisée. Alors faisons la même chose entraitement d'un indicateur de ligne de commande.

Je change brièvement le programme précédent pour introduireprintFigure()

// printFigure given a figure name prints it.
// Currently accepts `cow` and `stegosaurus`.
func printFigure(name string) {
<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">cow</span> = <span style="color:#e6db74">`         \  ^__^

\ (oo)_______ (__)\ )/
||----w | || || `

<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">stegosaurus</span> = <span style="color:#e6db74">`         \                      .       .

\ / </span> <span style="color:#f92672">+</span> <span style="color:#e6db74">"" + . .' " </span><span style="color:#e6db74"> \ .---. &lt; &gt; &lt; &gt; .---. </span><span style="color:#e6db74"> \ | \ \ - ~ ~ - / / | </span><span style="color:#e6db74"> _____ ..-~ ~-..-~ </span><span style="color:#e6db74"> | | \~~~\\.' + ""</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">./~/ --------- _/ _/ .’ O \ / / \ " (_____, </span> <span style="color:#f92672">+</span> <span style="color:#e6db74">"" + ._.' | } \/~~~/ </span><span style="color:#e6db74"> + ""</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">----. / } | / __/ </span> <span style="color:#f92672">+</span> <span style="color:#e6db74">"" + -. | / | / + ""</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">. ,| ~-.| /_ - ~ ^| /- _ </span> <span style="color:#f92672">+</span> <span style="color:#e6db74">"" + ..-' </span><span style="color:#e6db74"> | / | / ~-. + ""</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">-. _ _ _ || |__| ~ - . _ _ _ _ _> `

<span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">name</span> {
<span style="color:#66d9ef">case</span> <span style="color:#e6db74">"cow"</span>:
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">cow</span>)
<span style="color:#66d9ef">case</span> <span style="color:#e6db74">"stegosaurus"</span>:
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">stegosaurus</span>)
<span style="color:#66d9ef">default</span>:
	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">"Unknown figure"</span>)
}

}

et changermain()d'accepter un drapeau et de le passer àprintFigure():

func main() {
	//...

	var figure string
	flag.StringVar(&figure, "f", "cow", "the figure name. Valid values are `cow` and `stegosaurus`")
	flag.Parse()
<span style="color:#75715e">//...

printFigure(figure) fmt.Println() }

jouer

Je pense que nous sommes à un bon point. Je veux juste rendre ce système utilisable, sans courirgo run main.go, donc je vais juste tapergo buildetgo install.

Je peux maintenant passer la journée avecGololcatet gocowsay


Plus de tutoriels go: