I keep my drafts as Markdown files with a short front matter block at the top. It holds the title, a one-line description that ends up as the meta description, and a few tags. When a piece is ready, the static site generator or the blogging platform reads those fields and builds the page from them.
The trouble is that front matter is the part I look at least. I spend hours on the body and about ten seconds on the block above it. So the mistakes pile up there: a title that grew while I was editing and is now far too long for a search result, a description I meant to write later, a tags line I left empty.
None of this is hard to fix. It's just easy to miss. So I wrote a short script that reads every draft in a folder and tells me what's missing or too long.
What it checks
I kept the rules deliberately small:
- Every file starts with a front matter block between two
---lines. -
title,descriptionandtagsare present and not empty. - The title is 60 characters or fewer.
- The description is 155 characters or fewer.
The two length limits are rough guides, not official numbers. Search engines cut titles and snippets by display width, not by a fixed character count, so treat them as a "probably too long" warning. Change them to whatever your platform or site uses.
A draft that passes looks like this:
---
title: How I name my draft files
description: A simple naming pattern that keeps a folder of drafts easy to search.
tags: [writing, workflow]
---
Body text starts here.
The script
It uses only the standard library, so there's nothing to install.
#!/usr/bin/env python3
"""Flag Markdown drafts with missing or overlong front matter fields."""
import sys
from pathlib import Path
REQUIRED = ["title", "description", "tags"]
MAX_TITLE = 60
MAX_DESCRIPTION = 155
def read_front_matter(text):
lines = text.splitlines()
if not lines or lines[0].strip() != "---":
return None
fields = {}
for line in lines[1:]:
if line.strip() == "---":
return fields
if ":" in line:
key, value = line.split(":", 1)
fields[key.strip()] = value.strip().strip('"').strip("'")
return None # no closing ---
def check(path):
problems = []
fields = read_front_matter(path.read_text(encoding="utf-8"))
if fields is None:
return ["no front matter block"]
for key in REQUIRED:
if not fields.get(key):
problems.append(f"missing {key}")
title = fields.get("title", "")
if len(title) > MAX_TITLE:
problems.append(f"title is {len(title)} characters (max {MAX_TITLE})")
description = fields.get("description", "")
if len(description) > MAX_DESCRIPTION:
problems.append(
f"description is {len(description)} characters (max {MAX_DESCRIPTION})"
)
return problems
def main():
folder = Path(sys.argv[1] if len(sys.argv) > 1 else ".")
found = 0
for path in sorted(folder.rglob("*.md")):
for problem in check(path):
print(f"{path}: {problem}")
found += 1
sys.exit(1 if found else 0)
if __name__ == "__main__":
main()
read_front_matter returns None when there's no opening --- or no closing one, and a dictionary of fields otherwise. check turns that into a list of plain-language problems. main walks the folder recursively with rglob, prints one line per problem, and exits with status 1 if anything was found.
Running it
Save it as check_front_matter.py and point it at your drafts folder:
python3 check_front_matter.py drafts
With one good file, one file with a long title and no description, and one file without front matter, the output looks like this:
drafts/long-title.md: missing description
drafts/long-title.md: title is 83 characters (max 60)
drafts/no-front-matter.md: no front matter block
Each line starts with the file path, so in most editors' terminals you can click straight through to the file. When everything passes, it prints nothing and exits with 0.
Making it hard to forget
If your drafts live in a git repository, the exit code means you can run the check before every commit. Put this in .git/hooks/pre-commit and make it executable with chmod +x .git/hooks/pre-commit:
#!/bin/sh
python3 check_front_matter.py drafts
If the script finds a problem, git stops the commit and shows the list. When I really do want to commit a half-finished draft, git commit --no-verify skips the hook for that one commit.
I don't always want that level of strictness. On some projects I just run the script by hand before I move a file into the "ready" pile, and that's enough.
What it doesn't do
This is not a real YAML parser. It reads simple key: value lines, one per field. If you write tags as a block list:
tags:
- writing
- workflow
the script sees an empty tags: line and reports it as missing. If that's how you write front matter, either switch to the inline [writing, workflow] style or install PyYAML and use yaml.safe_load on the block instead of the loop.
It also can't tell whether a description is any good. It only knows that one exists and isn't too long. Writing a description that makes someone want to read the piece is still my job. The script just makes sure I don't publish without one.
That's the part I like about small checks like this. They take the boring, forgettable items off my mind, so the attention I have left goes to the writing itself.
Top comments (0)