import json import sys from pathlib import Path from datetime import timedelta import re from pathvalidate import validate_filename import os include_title = False output_dir = "recipes-md" def valid_duration(duration): if not duration: return False if duration == "0": return False return True def parse_duration(duration): if not duration: return "" match = re.match(r"PT(?:(\d+)H)?(?:(\d+)M)?", duration) if not match: return duration hours, minutes = match.groups() hours = f"{int(hours)}h " if hours else "" minutes = f"{int(minutes)}m" if minutes else "" return f"{hours}{minutes}".strip() def format_recipe_to_markdown(recipe): md = [] # Title (may be implicit by filename) if include_title: md.append(f"# {recipe.get('name', 'Untitled Recipe')}") # Description if 'description' in recipe: md.append(f"\n_{recipe['description']}_\n") # Details details_parts = [] if valid_duration(recipe.get('prepTime')): details_parts.append(f"Prep time: {parse_duration(recipe['prepTime'])}") if valid_duration(recipe.get('cookTime')): details_parts.append(f"Cook time: {parse_duration(recipe['cookTime'])}") if valid_duration(recipe.get('totalTime')): details_parts.append(f"Total time: {parse_duration(recipe['totalTime'])}") if 'recipeYield' in recipe: details_parts.append(f"Portionen: {recipe['recipeYield']}") if details_parts: for p in details_parts: md.append(f"* {p}\n") # Ingredients if 'recipeIngredient' in recipe: md.append("\n## Zutaten") for ingredient in recipe['recipeIngredient']: md.append(f"- {ingredient}") # Instructions if 'recipeInstructions' in recipe: md.append("\n## Zubereitung") instructions = recipe['recipeInstructions'] if isinstance(instructions, list): for i, step in enumerate(instructions, 1): if isinstance(step, dict): text = step.get('text', '').strip() else: text = str(step).strip() md.append(f"{i}. {text.replace(chr(10), ' ')}") else: for i, line in enumerate(instructions.strip().split('\n'), 1): if line.strip(): md.append(f"{i}. {line.strip()}") # Nutrition if 'nutrition' in recipe: md.append("\n## Nutrition") for key, value in recipe['nutrition'].items(): if key != "@type": md.append(f"- **{key.replace('_', ' ').capitalize()}**: {value}") return '\n'.join(md) def main(json_file): with open(json_file, 'r', encoding='utf-8') as f: data = json.load(f) # If the JSON-LD is embedded in @graph or a list # if isinstance(data, list): # recipe = next((item for item in data if item.get('@type') == 'Recipe'), data[0]) # elif '@graph' in data: # recipe = next((item for item in data['@graph'] if item.get('@type') == 'Recipe'), data['@graph'][0]) if isinstance(data, list) and data[0].get('@type') == 'Recipe': recipes = data elif data.get('@type') == 'Recipe': recipes = [data] else: print("No Recipe object found in JSON.") return for recipe in recipes: markdown_fn = f"{recipe.get('name', 'Untitled Recipe')}.md" validate_filename(markdown_fn) # XXX does this check directory traversal? markdown = format_recipe_to_markdown(recipe) os.makedirs(output_dir, exist_ok=True) with open(os.path.join(output_dir, markdown_fn), "w") as f: f.write(markdown) if __name__ == "__main__": if len(sys.argv) != 2: print("Usage: python recipe_to_md.py ") else: main(sys.argv[1])