Files
nicholaswilde_recipes/scripts/hyperlink_ingredient.py
2026-07-02 20:55:35 -07:00

135 lines
5.0 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
################################################################################
#
# hyperlink_ingredient.py
# ----------------
# Hyperlink a specific ingredient in a recipe file
#
# @author nιcнolaѕ wιlde, 0x08b7d7a3
# @date 12 Jun 2026
# @version 0.1.0
#
################################################################################
import os
import re
import sys
import argparse
def slugify(text):
# Convert to lowercase, replace spaces/special chars with hyphens
text = text.lower()
text = re.sub(r'[\s_\-]+', '-', text)
text = re.sub(r'[^\w\-]', '', text)
return text.strip('-')
def find_ingredient_file(ingredient_name, docs_dir):
# Try finding by H1 title first
slug = slugify(ingredient_name)
best_match = None
for root, dirs, files in os.walk(docs_dir):
for file in files:
if not file.endswith(".md"):
continue
filepath = os.path.join(root, file)
# Check filename slug match
file_slug = slugify(os.path.splitext(file)[0])
if file_slug == slug:
return filepath
# Fallback: parse H1 title
try:
with open(filepath, "r", encoding="utf-8") as f:
first_lines = [f.readline() for _ in range(20)]
for line in first_lines:
# Match H1 title e.g. "# :egg: Sambal Oelek"
h1_match = re.match(r'^#\s*(?:\:[a-z_]+\:\s*)?(.+)$', line.strip())
if h1_match:
title = h1_match.group(1).strip()
if title.lower() == ingredient_name.lower():
return filepath
except Exception:
continue
return None
def add_hyperlink(target_path, ingredient_name, ingredient_file):
# Calculate relative path
target_dir = os.path.dirname(os.path.abspath(target_path))
ing_abs_path = os.path.abspath(ingredient_file)
rel_path = os.path.relpath(ing_abs_path, target_dir)
# Read target content
with open(target_path, "r", encoding="utf-8") as f:
content = f.read()
# Regex to find ingredient name NOT inside markdown link syntax
# We want to match 'ingredient_name' but NOT if preceded by '[' or followed by ']'
# Using negative lookbehind/lookahead is tricky for multi-word, so we can use a simpler pattern
# Let's match: [something](link) OR [something] OR (ingredient)
# Actually, we can use a regex replacement function that skips matches inside markdown links.
# The pattern matches markdown links: \[([^\]]+)\]\([^\)]+\) OR \[([^\]]+)\]
# And we also match our raw ingredient name.
# Match skip targets:
# 1. Reference definition: ^\[[^\]]+\]:\s*.*$
# 2. Angle bracket links: <[^>]+>
# 3. Standard Markdown link: \[[^\]]+\]\([^\)]+\)
# 4. Reference link: \[[^\]]+\]\[[^\]]*\]
skip_def = r'(^\[[^\]]+\]:\s*.*$)'
skip_angle = r'(<[^>]+>)'
skip_link = r'(\[[^\]]+\]\([^\)]+\))'
skip_ref = r'(\[[^\]]+\]\[[^\]]*\])'
ing_pattern = r'\b(' + re.escape(ingredient_name) + r')\b'
combined_pattern = f'{skip_def}|{skip_angle}|{skip_link}|{skip_ref}|{ing_pattern}'
replacements_made = 0
def replace_fn(match):
nonlocal replacements_made
# Check if any skip group matched
if match.group(1) or match.group(2) or match.group(3) or match.group(4):
return match.group(0)
else:
replacements_made += 1
return f'[{ingredient_name}]({rel_path})'
new_content = re.sub(combined_pattern, replace_fn, content, flags=re.IGNORECASE | re.MULTILINE)
if replacements_made > 0:
with open(target_path, "w", encoding="utf-8") as f:
f.write(new_content)
print(f"Successfully hyperlinked '{ingredient_name}' to {rel_path} ({replacements_made} occurrences replaced).")
return True
else:
print(f"No unlinked occurrences of '{ingredient_name}' found in {target_path}.")
return False
def main():
parser = argparse.ArgumentParser(description="Hyperlink an ingredient in a recipe markdown file.")
parser.add_argument("--target", required=True, help="Path to the recipe file to modify")
parser.add_argument("--ingredient", required=True, help="Name of the ingredient to hyperlink")
args = parser.parse_args()
docs_dir = "/home/nicholas/git/nicholaswilde/recipes/docs"
if not os.path.exists(args.target):
print(f"Error: Target file not found: {args.target}", file=sys.stderr)
sys.exit(1)
ing_file = find_ingredient_file(args.ingredient, docs_dir)
if not ing_file:
print(f"Error: Could not find ingredient file for '{args.ingredient}' in {docs_dir}", file=sys.stderr)
sys.exit(1)
success = add_hyperlink(args.target, args.ingredient, ing_file)
if not success:
sys.exit(2)
if __name__ == "__main__":
main()