Split into a Package
Every Go file starts with a package clause, and every directory is exactly one
package. That is the whole rule: a package is a directory, its name is the
package line its files share, and the way you reach it from elsewhere is an
import path built from the module.
This workspace is one module:
go.mod module gofinity/splitpackage
main.go package main
textkit/ package textkit
The module line in go.mod names the module, and an import path is that name
plus the directory below it:
import "gofinity/splitpackage/textkit"
The last element of the path is the directory, not necessarily the package
name, though keeping the two the same is the convention and the kindest thing
you can do for a reader. After the import, the package name is what you type at
the call site: textkit.Title(...).
What crosses the boundary
Only the capitalised names. Title, Slug and WordCount are exported and
callable from main; isWordChar is not, and textkit.isWordChar does not
compile. Concept 15's second challenge is about designing that line - here just
notice that it exists.
Two kinds of test file
textkit/textkit_test.go declares package textkit, so it is inside the
package and can call isWordChar directly. main_test.go declares
package main, so it sees only what main sees: the exported names it imported
and the functions in its own package.
go test ./... walks the module and runs both.
Imports are grouped, not sorted by hand
gofmt sorts within a group; the convention is
standard library first, then a blank line, then everything else:
import (
"fmt"
"gofinity/splitpackage/textkit"
)
An unused import is a compile error, not a warning. Go would rather you delete it than let the build carry weight nobody reads.
Task
Fill in textkit.Title, textkit.Slug and textkit.WordCount, then use them
from main to write Headline and Summary.
Hints
strings.Fieldssplits on any run of whitespace and never returns an empty field, which handles the leading, trailing and doubled spaces for free.- Upper-casing the first rune means
[]rune(s)andunicode.ToUpper, nots[0]: a byte is not a letter once the text stops being ASCII. - Build the slug with a
strings.Builder. Remember whether you have skipped a character since the last one you kept, and write the-lazily when the next word character arrives - that way no separator is ever left dangling at either end. Sluglower-cases first, soisWordCharonly ever sees lower-case letters and digits.Summaryhas three shapes and no arithmetic: aswitchon the count reads better than nestedifs.