Learn Python in 30 Days — Day 21 Week 3 Challenge: Daily Journal CLI App
Day 21 - Challenge: Daily Journal CLI App
Welcome to Day 21 of the Learn Python in 30 Days series!
By now you’ve used:
-
Functions (Day 15–16)
-
Error handling with
try/except(Day 17) -
File handling (Day 18)
-
Modules (Day 19–20)
Today, we’re going to glue all of that together and build a proper little project:
A Daily Journal CLI App that’s split into:
journal_core.py→ your self-made module with all the logic
journal_cli.py→ a tiny command-line interface that imports and uses it
The goal isn’t just “make a journal”, it’s to think like a developer: split code into sensible pieces, give each function a job, and wire it all together.
All example files for this series are available on my GitHub: Learn-Python-in-30-Days
What You’ll Build
journal_core.pyWhich handles:
getting today’s date
adding entries
viewing all entries
viewing entries for a specific date
file + error handling
journal_cli.pyWhich handles:
showing the menu
reading user choices
calling functions from
journal_core
Let’s build this step by step.
Plan the Journal Format
Before any code, decide how you’ll store entries.
We’ll keep it simple: one text file called journal.txt.
Each entry looks like this:
Rules:
-
Each entry starts with a header line:
=== YYYY-MM-DD === -
The lines after that are the entry text
-
A blank line separates entries
Everything we build in journal_core.py will read and write this format.
Create journal_core.py
Create a new file called:
Add this at the top:
What’s going on?
-
The docstring at the top explains what this file is for.
This is really helpful when your project grows. -
from datetime import dateimports thedateclass so we can get today’s date later. -
JOURNAL_FILE = "journal.txt"is a module-level constant.
Every function that needs the journal file uses this same name, so if you ever change it, you do it in one place.
Step 1 – get_today_str() - Getting todays date
Now we need a standard way to get today’s date as a string.
Add this to journal_core.py:
What’s going on?
-
date.today()gives you today’s date as adateobject. -
.isoformat()turns it into"YYYY-MM-DD", which is perfect for your header lines. -
Wrapping this in a function means:
-
You don’t repeat this logic everywhere.
-
If you ever want a different format, you change it in one place.
-
We’ll use get_today_str() in the next function.
Step 2 – add_entry_for_today() — Writing New Entries
Now to add functions for writing a new entry.
def add_entry_for_today():
What’s going on?
1. Getting today’s date
-
Keeps the date format consistent everywhere.
2. Collecting multi-line input
-
The user types lines of text.
-
When they press Enter on an empty line, the loop stops.
-
All non-empty lines get stored in
lines.
3. Handling “no entry”
-
If the user just presses Enter straight away, we don’t write an empty entry to the file.
4. Joining lines into a single block of text
-
Turns
["Line 1", "Line 2"]into:
5. Writing to the file with error handling
-
"a"= append mode, so we don’t overwrite old entries. -
We write:
-
The header line with the date
-
The entry text
-
A blank line afterwards
-
-
If anything goes wrong (e.g. permission issue, disk problem),
OSErroris caught and we print a friendly error.
Step 3 – view_all_entries() — Reading the Entire Journal
Next, we want to see everything we’ve ever written.
def view_all_entries():
What’s going on?
1. Simple heading
-
Just gives a clear title before we show content.
2. Reading the file safely
-
"r"= read mode. -
f.read()loads the entire file into a single string.
3. Handling file-related errors
-
FileNotFoundError→ This is normal if you haven’t written any entries yet. -
OSError→ Catches other I/O issues and shows a message.
4. Handling an empty file
-
content.strip()removes whitespace.
If the result is empty, the file contains nothing meaningful yet.
Step 4 – view_entries_for_date() — Filtering by Date
Now a slightly more advanced function: show entries for a specific date.
def view_entries_for_date(target_date):
What’s going on?
1. Build the header we’re looking for
-
If
target_dateis"2025-11-21", the header becomes:
2. Read the file as lines
-
We read line by line because we need to figure out which lines belong to which entry.
3. Variables for tracking state
-
matching_entries— list of full entries for the specified date. -
current_entry_lines— lines for the entry we’re currently reading. -
in_matching_entry—Truewhen we’re inside an entry with the target date.
4. Loop through each line to detect entry blocks
-
If a line looks like a header (
=== something ===):-
We’re starting a new entry.
-
If we were just inside a matching entry, we save the lines we collected so far.
-
Then we check if the new header matches our target date.
-
-
If it’s not a header and
in_matching_entryisTrue:-
The line belongs to the current entry and is added to
current_entry_lines.
-
5. Catch the last entry
-
After the loop, the last entry won’t have been saved yet, so we add it now if needed.
6. Print results
-
If nothing matched: friendly message.
-
If there are matches: print each entry with a separator line.
Build journal_cli.py
Now that journal_core.py has all the logic, we make a tiny script to run it.
Create a new file:
Add this code:
What’s going on?
1. Importing your own module
-
journal_coreis your self-made module. -
You import the functions you need, just like you do with
mathorrandom.
2. show_menu() – UI only, no logic
-
This function just prints the menu.
-
It doesn’t talk to files or dates — that’s
journal_core’s job.
3. main() – the main loop
-
Shows the menu.
-
Reads the user’s choice.
-
Calls the relevant function from
journal_core. -
Keeps looping until the user chooses
"4"to quit.
4. The usual “run this file directly” guard
-
This means
main()only runs if you do: -
If you imported
journal_clifrom somewhere else, it wouldn’t auto-run.
Run the Journal App
Reusing Your Module Elsewhere
The nice part about this setup?
journal_core is now a reusable module. You can use it in other scripts:
You could write a completely different interface but still call the same core functions.
Next Up — Day 22 – Lists & Dictionary Comprehensions
Tomorrow is Day 22 — Week 4, where you’ll begin structuring programs, tie all skills together and complete one final project
All example files for this series are available on my GitHub: Learn-Python-in-30-Days
You can see the full series here Learn Python in 30 Days series!
Hope you have enjoyed this post, thanks Matty

Comments
Post a Comment