main.go — the menu loop and real input
The big moment: we join for, switch and input — the program starts to run. But input is a trap for beginners, so we'll do it right from the start.
Tokens vs a whole line — the real Go lesson. fmt.Scanln reads tokens: it stops at the first space. If the user types the title "Buy milk", Scanln reads only "Buy", and "milk" stays in the buffer and scrambles the next read. Every student's task tracker silently mangles input this way in the first minute.
So we read the whole line with a small helper:
var scanner = bufio.NewScanner(os.Stdin)
func readLine() string {
scanner.Scan() // reads one whole line
return strings.TrimSpace(scanner.Text()) // Text() — the line without "\n"
}
Create the scanner ONCE, at package level. Notice scanner is declared outside main, and readLine reuses it. This matters: a bufio.Scanner reads ahead into its own buffer. If you wrote bufio.NewScanner(os.Stdin) inside readLine, each call would build a new scanner that swallows the next lines — so the 2nd and 3rd inputs would silently come back empty. One scanner, reused, is the whole trick.
Why bufio.Scanner and not bufio.NewReader? We picked the simpler tool on purpose. Scanner is two calls — Scan() then Text() — with nothing to explain. NewReader's ReadString('\n') owes you three explanations first: what the '\n' rune delimiter is (you meet runes in lesson 7), an err you must handle immediately, and a trap — ReadString keeps the newline, so if choice == "1" silently never matches because choice is actually "1\n". For a first program, Scanner is the right amount of machinery. (One caveat for later: Scanner has a 64 KB default line limit — irrelevant for a task tracker, but the reason if a piped-in huge file ever truncates.)
A retry loop for bad input. When you expect a number and the user types "abc", don't crash and don't kick them to the menu — ask again. That's an inner for loop:
func readInt(prompt string) int {
for {
fmt.Print(prompt)
n, err := strconv.Atoi(readLine()) // Atoi: text → number (more in lesson 6)
if err == nil {
return n // good — return it
}
fmt.Println("Please enter a whole number.") // bad — ask again
}
}
Verify — enter a multi-word title and deliberately a bad number:
> 2
New title: Ąžuolas ir uosis
Title set to: Ąžuolas ir uosis
> 3
Pages: abc
Please enter a whole number.
Pages: 250
Pages set to: 250
The title is read in full (with spaces), and "abc" didn't break the program — it just asked again.
Two ways to get stuck: (1) forget the q branch or the return — the menu spins forever; Ctrl-C in the terminal stops it. (2) Read multi-word text with fmt.Scanln(&title) — you get only "Ąžuolas", and the rest scrambles the next read. So always read a line with readLine, not tokens.