Skip to content

Commit 96d9889

Browse files
travisjneumanclaude
andcommitted
feat: add step-by-step WALKTHROUGH.md files for 12 key beginner projects
Walkthroughs guide learners through the thinking process without giving away the complete solution. Each covers understanding the problem, planning, incremental steps with "predict before you scroll" prompts, common mistakes, testing guidance, and extension ideas. Level 00 (3 walkthroughs): shorter, simpler for absolute beginners Level 0 (3 walkthroughs): detailed with Mermaid diagrams Level 1 (3 walkthroughs): detailed with Mermaid pipeline diagrams Level 2 (3 walkthroughs): detailed with Mermaid diagrams and z-score explanation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4288fe4 commit 96d9889

12 files changed

Lines changed: 1997 additions & 0 deletions

File tree

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# Walkthrough: Terminal Hello Lab
2+
3+
> This guide walks through the **thinking process** for building this project.
4+
> It does NOT give you the complete solution. For that, see [SOLUTION.md](./SOLUTION.md).
5+
6+
## Before reading this
7+
8+
**Try the project yourself first.** Spend at least 20 minutes.
9+
If you have not tried yet, close this file and open the [project README](./README.md).
10+
11+
---
12+
13+
## Understanding the problem
14+
15+
You need to build a script that asks the user for their name and which day of their Python journey it is, then prints a decorated banner, a personalised greeting, a fun fact, and an info card. The key functions are `greet()`, `build_banner()`, `build_info_card()`, and `run_hello_lab()`.
16+
17+
This is your first project with **tests**. The functions must return values (not just print), so the tests can call them directly and check the output.
18+
19+
## Planning before code
20+
21+
```mermaid
22+
flowchart TD
23+
A[User types name and day] --> B[greet: build greeting string]
24+
A --> C[build_banner: create bordered title]
25+
B --> D[run_hello_lab: print everything]
26+
C --> D
27+
D --> E[build_info_card: collect summary dict]
28+
E --> F[Print info card]
29+
```
30+
31+
Break the project into three independent building blocks, then one function that ties them together:
32+
33+
1. **greet()** -- take a name, return a greeting string
34+
2. **build_banner()** -- take a title, return a bordered banner
35+
3. **build_info_card()** -- take name/language/day, return a dictionary
36+
4. **run_hello_lab()** -- call the above, print everything, return the info card
37+
38+
## Step 1: The greeting function
39+
40+
Start with the simplest piece. `greet()` takes a name and returns a string.
41+
42+
```python
43+
def greet(name: str) -> str:
44+
return f"Hello, {name}! Welcome to Python."
45+
```
46+
47+
The f-string `f"Hello, {name}!"` inserts the value of `name` into the text. This is cleaner than concatenation (`"Hello, " + name + "!"`).
48+
49+
### Predict before you scroll
50+
51+
What does `greet("Ada")` return? What about `greet("")`? Is an empty-name greeting a problem the tests will care about?
52+
53+
## Step 2: The banner function
54+
55+
The banner wraps a title in asterisks. You need:
56+
- A top border (a line of `*` characters)
57+
- The title, centred
58+
- A bottom border
59+
60+
```python
61+
def build_banner(title: str, width: int = 40) -> str:
62+
border = "*" * width
63+
centered_title = title.center(width)
64+
return f"{border}\n{centered_title}\n{border}"
65+
```
66+
67+
Two things to notice:
68+
- `"*" * width` repeats the `*` character `width` times. This is **string multiplication**.
69+
- `.center(width)` pads the string with spaces on both sides so it sits in the middle.
70+
71+
### Predict before you scroll
72+
73+
If `width` is 40 and `title` is `"HI"`, how many spaces will appear before `"HI"` in the centred line?
74+
75+
## Step 3: The info card
76+
77+
This function collects facts into a dictionary. Dictionaries let you label data with keys instead of relying on position.
78+
79+
```python
80+
def build_info_card(name: str, language: str, day: int) -> dict:
81+
return {
82+
"name": name,
83+
"language": language,
84+
"learning_day": day,
85+
"greeting": greet(name), # reuses greet()!
86+
}
87+
```
88+
89+
Notice that `build_info_card` calls `greet()` inside it. Functions can call other functions -- this is **composition**.
90+
91+
## Step 4: Tying it together
92+
93+
`run_hello_lab()` is the orchestrator. It calls the building blocks, prints output, and returns the info card.
94+
95+
```python
96+
def run_hello_lab(name: str, day: int) -> dict:
97+
print(build_banner("TERMINAL HELLO LAB"))
98+
print()
99+
print(greet(name))
100+
print(f"\tDay {day} of your Python journey.")
101+
# ...
102+
return build_info_card(name, "Python", day)
103+
```
104+
105+
The `\t` inside the f-string is a **tab character** -- it indents the text.
106+
107+
## Common mistakes
108+
109+
| Mistake | Why it happens | How to fix |
110+
|---------|---------------|------------|
111+
| `greet()` prints instead of returning | Confusing `print()` with `return` | `print()` shows text on screen; `return` sends data back to the caller. Tests need `return`. |
112+
| Banner width is wrong | Forgetting that `.center()` pads to a total width, not adds that much padding | `.center(40)` makes the whole string 40 characters wide |
113+
| `int(input(...))` crashes on non-numbers | The user typed letters instead of a number | Wrap in `try/except ValueError` or check `.isdigit()` first |
114+
| Forgetting `if __name__ == "__main__"` | Not understanding the guard | Without it, the `input()` calls run when tests import the file |
115+
116+
## Testing your solution
117+
118+
Run the tests from the project directory:
119+
120+
```bash
121+
pytest -q
122+
```
123+
124+
The five tests check:
125+
- `greet("Ada")` contains "Ada" and "Hello"
126+
- `greet()` works for multiple different names
127+
- `build_banner("MY TITLE")` contains the title and has 3 lines
128+
- `build_banner("HI", width=20)` produces borders exactly 20 characters wide
129+
- `build_info_card("Test", "Python", 5)` returns a dict with all expected keys
130+
131+
If a test fails, read the assertion error carefully -- it tells you what value it expected vs. what it got.
132+
133+
## What to explore next
134+
135+
1. Add a `build_greeting_box()` function that wraps the greeting in a box made of `+`, `-`, and `|` characters
136+
2. Add input validation: what should happen if the user types nothing for their name, or letters for the day number?
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# Walkthrough: First File Reader
2+
3+
> This guide walks through the **thinking process** for building this project.
4+
> It does NOT give you the complete solution. For that, see [SOLUTION.md](./SOLUTION.md).
5+
6+
## Before reading this
7+
8+
**Try the project yourself first.** Spend at least 20 minutes.
9+
If you have not tried yet, close this file and open the [project README](./README.md).
10+
11+
---
12+
13+
## Understanding the problem
14+
15+
You need to build a program that reads a text file, displays its contents with line numbers, and prints a summary (line count, word count, character count, non-empty lines). The program asks the user for a file path and handles the case where the file does not exist.
16+
17+
This is your first encounter with **file I/O** -- reading data from a file on disk instead of from user input.
18+
19+
## Planning before code
20+
21+
```mermaid
22+
flowchart TD
23+
A[User enters file path] --> B{File exists?}
24+
B -->|No| C[Print error message]
25+
B -->|Yes| D[read_file_lines: read all lines]
26+
D --> E[format_with_line_numbers: add numbers]
27+
D --> F[file_summary: count words/lines/chars]
28+
E --> G[Display numbered contents]
29+
F --> H[Display summary stats]
30+
```
31+
32+
Three independent functions, plus error handling:
33+
34+
1. **read_file_lines()** -- open a file, return a list of lines
35+
2. **format_with_line_numbers()** -- take lines, add line numbers
36+
3. **file_summary()** -- count lines, words, characters
37+
4. **Main block** -- ask for path, handle errors, display output
38+
39+
## Step 1: Reading lines from a file
40+
41+
The fundamental pattern for reading a file in Python:
42+
43+
```python
44+
def read_file_lines(filepath: str) -> list:
45+
with open(filepath, encoding="utf-8") as f:
46+
return f.read().splitlines()
47+
```
48+
49+
Two important things here:
50+
51+
- **`with open(...) as f`** opens the file and guarantees it gets closed when you are done, even if an error occurs. Always use `with` for files.
52+
- **`.splitlines()`** splits the text on newline characters and returns a list. Unlike `.split("\n")`, it does not leave an extra empty string at the end if the file ends with a newline.
53+
54+
### Predict before you scroll
55+
56+
If `sample_input.txt` contains three lines (one blank), how many items will the returned list have? Will the blank line be included or skipped?
57+
58+
## Step 2: Adding line numbers
59+
60+
You want output like:
61+
62+
```
63+
1 | Welcome to your first file reader!
64+
2 |
65+
3 | This file has several lines of text.
66+
```
67+
68+
The `enumerate()` function gives you both the index and the value as you loop:
69+
70+
```python
71+
def format_with_line_numbers(lines: list) -> str:
72+
if not lines:
73+
return "(empty file)"
74+
75+
width = len(str(len(lines))) # how many digits for the biggest line number
76+
77+
numbered = []
78+
for i, line in enumerate(lines, start=1):
79+
numbered.append(f" {i:>{width}} | {line}")
80+
81+
return "\n".join(numbered)
82+
```
83+
84+
The key trick is `f"{i:>{width}}"`. The `:>` means right-align, and `width` is how many characters wide to make the number. For a 100-line file, `width` would be 3, so line 1 displays as ` 1` and line 100 displays as `100`.
85+
86+
### Predict before you scroll
87+
88+
Why does the function handle the empty-lines case separately at the top? What would go wrong if `lines` were an empty list and you tried to calculate `width`?
89+
90+
## Step 3: Building the summary
91+
92+
The summary counts several things about the file:
93+
94+
```python
95+
def file_summary(filepath: str, lines: list) -> dict:
96+
text = "\n".join(lines)
97+
word_count = len(text.split())
98+
99+
name = filepath.replace("\\", "/").split("/")[-1]
100+
101+
return {
102+
"file_name": name,
103+
"lines": len(lines),
104+
"words": word_count,
105+
"characters": len(text),
106+
"non_empty_lines": sum(1 for line in lines if line.strip()),
107+
}
108+
```
109+
110+
Notice the last line: `sum(1 for line in lines if line.strip())`. This is a **generator expression** that counts only lines that are not blank. `line.strip()` removes whitespace -- if what is left is an empty string, it is falsy, so the condition filters it out.
111+
112+
## Step 4: Handling missing files
113+
114+
In the main block, wrap the file reading in `try/except`:
115+
116+
```python
117+
try:
118+
lines = read_file_lines(filepath)
119+
except FileNotFoundError:
120+
print(f" File not found: {filepath}")
121+
```
122+
123+
This prevents the program from crashing with an ugly traceback when the user types a wrong path.
124+
125+
## Common mistakes
126+
127+
| Mistake | Why it happens | How to fix |
128+
|---------|---------------|------------|
129+
| Using `f.readlines()` instead of `f.read().splitlines()` | Both read lines, but `readlines()` keeps the `\n` at the end of each line | Use `.read().splitlines()` for clean lines without trailing newlines |
130+
| Line numbers start at 0 | `enumerate()` defaults to `start=0` | Pass `start=1` to `enumerate()` |
131+
| Word count is wrong | Splitting on just `" "` misses tabs and multiple spaces | Use `.split()` with no argument -- it splits on any whitespace |
132+
| Crash on empty file | Dividing by zero or formatting empty data | Check `if not lines` at the start and return early |
133+
134+
## Testing your solution
135+
136+
Run the tests from the project directory:
137+
138+
```bash
139+
pytest -q
140+
```
141+
142+
The five tests check:
143+
- Reading a simple 3-line file returns the correct lines
144+
- Reading a missing file raises `FileNotFoundError`
145+
- `format_with_line_numbers()` adds correct line numbers
146+
- An empty file produces the `"(empty file)"` message
147+
- `file_summary()` returns correct line and word counts
148+
149+
## What to explore next
150+
151+
1. Add a feature that asks the user for start and end line numbers, then displays only that range
152+
2. After showing the summary, ask "Read another file? (y/n):" and loop if yes

0 commit comments

Comments
 (0)