Skip to content
FutureBlitz

    Install

    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}")
    $ fb run main.fb -- --top 3 -i notes.txt todo.txt
    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.

    poem.txt
    The rain in the night,
    the wind in the trees,
    and the night in the rain.
    main.fb
    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 For
    $ fb run main.fb -- --top 3 -i poem.txt missing.txt
    16 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 --release

    build/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.