Tutoriel Go CLI: Clone de fortune

J'ai écrit deux didacticiels d'application CLI pour créerGololcatetgocowsay. Dans les deux j'ai utiliséfortunecomme générateur d'entrée.

Dans cet article, je terminerai la triologie des tuyaux avecgofortune.

Qu'est-ce quefortune, première?Comme le dit Wikipedia, Fortune est un programme simple qui affiche un message pseudo-aléatoire à partir d'une base de données de citations.

Fondamentalement, un générateur de devis aléatoires.

Il a une très longue histoire qui remonte à Unix version 7 (1979). Ça va toujours fort. De nombreuses distributions Linux le préinstallent, et sur OSX, vous pouvez l'installer en utilisantbrew install fortune.

Sur certains systèmes, il est utilisé comme message d'accueil ou de séparation lors de l'utilisation de shells.

Wikipédia dit aussi

Beaucoup de gens choisissent de faire fortunevachecommande, pour ajouter plus d'humour à la boîte de dialogue.

C'est moi! Sauf que j'utilise mongocowsaycommander.

Assez avec l'intro,construisons un clone de fortune avec Go.

Voici un aperçu de ce que fera notre programme.

L'emplacement du dossier fortunes dépend du système et de la distribution, étant un indicateur de construction. Je pourrais le coder en dur ou utiliser une variable d'environnement, mais comme exercice, je vais faire une chose sale et demanderfortunedirectement, en l'exécutant avec le-fflag, qui génère:

La première ligne de la sortie contient le chemin du dossier fortunes.

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>))

}

Cet extrait de code reproduit la sortie exactement telle que je l'ai obtenue. Il paraît quefortune -fécrit la sortie sur stderr, c'est pourquoi j'ai utiliséCombinedOutput, pour obtenir les deuxstdoutetstderr.

Mais je veux juste la première ligne. Comment faire? Ceci imprime toute la sortie destderrligne par ligne:

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()) } }

Pour obtenir uniquement la première ligne, je supprime la boucle for et scanne simplement la première ligne:

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()) }

Maintenant, choisissons cette ligne et extrayons le chemin.

Sur mon système, la première ligne de la sortie est100.00% /usr/local/Cellar/fortune/9708/share/games/fortunes. Faisons une sous-chaîne à partir de la première occurrence du/carboniser:

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

Maintenant, j'ai le chemin des fortunes. Je peux indexer les fichiers qui s'y trouvent. Il y a.datfichiers binaires et fichiers texte brut. Je vais supprimer les fichiers binaires, et leoff/dossier tout à fait, qui contient des fortunes offensives.

Indexons d'abord les fichiers. Je utilise lepath/filepathpaquetWalkméthode pour parcourir l'arborescence des fichiers à partir deroot. Je l'utilise au lieu deioutil.ReadDir()parce que nous pourrions avoir des dossiers imbriqués de fortunes. Dans leWalkFunc visitJe supprime les fichiers .dat en utilisantfilepath.Ext(), Je supprime les fichiers du dossier (par exemple/off, mais pas les fichiers dans les sous-dossiers) et toutes les fortunes offensives, idéalement situées sous/off, et j'imprime la valeur de chaque fichier restant.

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>)
}

}

Mettons ces valeurs dans une tranche, afin que je puisse plus tard en choisir une au hasard: je définis unfilestranche de chaînes et j'ajoute à cela dans levisit()fonction. Au bout dumain()J'imprime le nombre de fichiers que j'ai.

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>))

}

J'utilise maintenant leAller au générateur de nombres aléatoiresfonctionnalité pour choisir un élément aléatoire dans le tableau:

// 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) }

Notre programme imprime maintenant un nom de fichier fortune aléatoire à chaque exécution.

Ce qui me manque maintenant, c'est de numériser les fortunes dans un fichier et d'en imprimer un au hasard. Dans chaque fichier, les guillemets sont séparés par un%assis sur une ligne tout seul. Je peux facilement détecter ce modèle et scanner chaque devis dans un tableau:

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])

Ce n'est pas vraiment efficace, car je scanne l'intégralité du fichier fortune dans une tranche, puis je choisis un élément aléatoire, mais cela fonctionne:

Alors, voici la version finale de notre très basiquefortunecloner. Il manque beaucoup de l'originalfortunecommande, mais c'est un début.

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>])

}

En terminant, je bougevisiten tant qu'argument de fonction en ligne defilepath.Walket bougefilesêtre une variable locale à l'intérieurmain()au lieu d'une variable de fichier globale:

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>])

}

je peux maintenantgo build; go installet la triologiegofortune gocowsayetgololcatest terminé:


Plus de tutoriels go: