Parsing structured metadata (git log example)
A huge amount of real work is turning structured text into records you can reason over — and git log output is a perfect, everyday example. A line from git log --oneline looks like "a1b2c3d Fix the login bug": a short hash, a space, then the message.
The parsing toolkit is small and you already have it:
str.split(sep, maxsplit)—line.split(" ", 1)splits on the *first* space only, giving["a1b2c3d", "Fix the login bug"]. Themaxsplit=1is what keeps the message (which has its own spaces) in one piece.str.splitlines()— break a multi-line block into a list of lines.str.strip()— trim whitespace; handy for skipping blank lines.- a list of dicts — turn each line into
{"hash": ..., "message": ...}so the data is structured and queryable.
Once the text is a list of dicts, reasoning over it is ordinary Python: filter with a list comprehension, count with sum(1 for ...), group, sort. Counting feat: vs fix: prefixes (the "conventional commits" style) or finding every commit mentioning a keyword is then a one-liner.
Syntax
# Split each line into a record, skip blanks, query the list:
line.split(" ", 1) # ["a1b2c3d", "Fix the login bug"]
log_text.splitlines() # list of lines
[l for l in lines if l.strip()] # drop blank lines
# reason over parsed records:
[c for c in commits if "fix" in c["message"].lower()]
sum(1 for c in commits if c["message"].startswith("feat:"))Worked examples
Parse one line into a record (split on first space)
def parse_log_line(line):
parts = line.split(" ", 1) # first space only
return {"hash": parts[0], "message": parts[1]}
print(parse_log_line("a1b2c3d Fix the login bug"))
# Output:
# {'hash': 'a1b2c3d', 'message': 'Fix the login bug'}Parse a whole log into a list of dicts (skip blanks)
SAMPLE_LOG = """a1b2c3d feat: add user login
b2c3d4e fix: handle null pointer in auth
c3d4e5f feat: add password reset flow
d4e5f6g chore: update dependencies"""
def parse_log(log_text):
lines = [l for l in log_text.splitlines() if l.strip()]
return [parse_log_line(l) for l in lines]
commits = parse_log(SAMPLE_LOG)
print(len(commits))
print(commits[0])
# Output:
# 4
# {'hash': 'a1b2c3d', 'message': 'feat: add user login'}Reason over the parsed metadata
def count_by_prefix(log_text, prefix):
commits = parse_log(log_text)
return sum(1 for c in commits
if c["message"].lower().startswith(prefix.lower()))
def find_by_keyword(log_text, keyword):
return [c for c in parse_log(log_text)
if keyword.lower() in c["message"].lower()]
print(count_by_prefix(SAMPLE_LOG, "feat:"))
print(count_by_prefix(SAMPLE_LOG, "fix:"))
print([c["hash"] for c in find_by_keyword(SAMPLE_LOG, "ADD")])
# Output:
# 2
# 1
# ['a1b2c3d', 'c3d4e5f']Common mistakes & gotchas
split() with no maxsplit shreds the message
Plain line.split(" ") splits on *every* space, so "a1b2c3d Fix the login bug" becomes five pieces and you lose the message structure. Use split(" ", 1) to take only the hash off the front and keep the rest whole.
Forgetting to skip blank lines
A trailing newline or blank line in the log produces an empty string. Running parse_log_line("") on it gives [""] with no parts[1] — an IndexError. Filter with if l.strip() before parsing each line.
Case-sensitive matching misses real hits
Searching for "fix" won't match "Fix:" or "FIX" unless you lower-case both sides. For human-written text, compare keyword.lower() in message.lower() (or .startswith(prefix.lower())) so case differences don't silently drop results.
Why it matters
Logs, CSVs, config files, command output — much of a developer's day is parsing semi-structured text into records and then querying them. The split → strip → list-of-dicts → comprehend pattern here is the same one you'll reach for constantly, and it's exactly how you'd pre-process metadata before feeding it to a model to reason about.