The tutorial, part 2 of 3
A command-line tool
A word counter with options and help, from a Struct's attributes, reading the files it's given.
This part builds words, a tool that counts the words in the files it’s given and lists the most
common ones. It reads its command line with std.os’s Args, its files with std.io, and its words
with std.text and a Map.
Step 1: the command line
A Struct with attributes describes the command line: @Positional arguments, @Options that take
a value, and @Flags. Args.Parse<T>() fills one from the program’s arguments, and with -h or
a mistake prints help or an error made from the same attributes, and exits.
Import std.core
Import std.os
@Program(Name: "words", About: "Counts the words in files.", Version: "0.1.0")
Struct Options
@Positional(Help: "The files to read", Name: "file")
Field Files: String[]
@Option(Help: "How many of the most common words to list", Short: "t")
Field Top: Int = 10
@Flag(Help: "Counts Word and word as one", Short: "i")
Field IgnoreCase: Bool
End Struct
Local options = Args.Parse<Options>()
Print("files: {options.Files}")
Print("top {options.Top}, ignoring case: {options.IgnoreCase}")files: [notes.txt, todo.txt] top 3, ignoring case: True
The output’s label shows the command line this ran with: fb run passes what follows -- to the
program.
Step 2: counting
File.ReadText reads a file, and it can throw an IOError, so the program says what to do when
one can’t be read: here, say so on stderr and go on with the rest. A Map<String, Int> counts each
word.
The rain in the night,
the wind in the trees,
and the night in the rain.Import std.core
Import std.os
Import std.io
Import std.text
Import std.collections
@Program(Name: "words", About: "Counts the words in files.", Version: "0.1.0")
Struct Options
@Positional(Help: "The files to read", Name: "file")
Field Files: String[]
@Option(Help: "How many of the most common words to list", Short: "t")
Field Top: Int = 10
@Flag(Help: "Counts Word and word as one", Short: "i")
Field IgnoreCase: Bool
End Struct
Function WordsOf(text: String): String[]
Local words: String[] = []
Local current = StringBuilder()
For c In text.Chars
If c.IsLetter()
current.Append(c)
ElseIf current.Length > 0
words.Add(current.ToString())
current.Clear()
End If
End For
If current.Length > 0 Then words.Add(current.ToString())
Return words
End Function
Local options = Args.Parse<Options>()
Local counts: Map<String, Int> = {}
Local total = 0
For path In options.Files
Try
For word In WordsOf(Try File.ReadText(path))
Local key = options.IgnoreCase ? word.ToLower() : word
counts[key] = (counts.Get(key) ?? 0) + 1
total += 1
End For
Catch e As IOError
PrintError("words: can't read {path}: {e.Kind}")
End Try
End For
Local common = counts.Keys()
common.SortByDescending(Function(word: String) => counts[word])
Print("{total} words, {counts.Count} different")
For word In common.View(0, Min(options.Top, common.Count))
Print("{counts[word]:>4} {word}")
End For16 words, 7 different 6 the 3 in 2 rain words: can't read missing.txt: NOT_FOUND
A run’s output shows what went to stderr after what went to stdout. SortByDescending keeps
words with the same count in the order they were first seen, so the list is the same every run.
Step 3: shipping it
fb build --releasebuild/words, or build\words.exe on Windows, is the whole tool: copy it anywhere on your PATH.
words -h prints its help, from the same attributes, and words --version its version.
Next, a web service.
Report an issue with this page or with FutureBlitz on GitHub.