Skip to content

Instantly share code, notes, and snippets.

@janbalaz
Created May 4, 2025 08:08
Show Gist options
  • Save janbalaz/c28ad40828789d8a520c32be5c48a31e to your computer and use it in GitHub Desktop.
Save janbalaz/c28ad40828789d8a520c32be5c48a31e to your computer and use it in GitHub Desktop.
MealMaster v7.07 to Tandoor recipes format
"""
Vibe-coded version of formatter from MealMaster v7.07 to Tandoor recipes format.
"""
import re
import json
import os
import time
import zipfile
from datetime import datetime
from typing import List, Dict, Any
from pathlib import Path
def parse_meal_master_file(filename: str) -> List[str]:
"""Read the file and split it into individual recipes."""
with open(filename, 'r', encoding='utf-8') as f:
content = f.read()
# Split content into individual recipes
recipes = content.split('---------- Recipe via Meal-Master (tm) v7.07')
# Remove empty entries
return [r.strip() for r in recipes if r.strip()]
def parse_recipe_header(recipe_text: str) -> Dict[str, Any]:
"""Parse the recipe header (title, categories, servings)."""
title_match = re.search(r'Title:\s*(.*?)\n', recipe_text)
categories_match = re.search(r'Categories:\s*(.*?)\n', recipe_text)
servings_match = re.search(r'Servings:\s*(\d+)', recipe_text)
title = title_match.group(1).strip() if title_match else ""
categories = [cat.strip() for cat in categories_match.group(1).split(',')] if categories_match else []
servings = int(servings_match.group(1)) if servings_match else 1
return {
"title": title,
"categories": categories,
"servings": servings
}
def parse_ingredients(recipe_text: str) -> List[Dict[str, Any]]:
"""Parse the ingredients section."""
ingredients = []
# Find the ingredients section (between header and instructions)
lines = recipe_text.split('\n')
ingredient_lines = []
for line in lines:
if re.match(r'\s+\d+(/\d+)?\s+[a-zA-Z]', line):
ingredient_lines.append(line)
for line in ingredient_lines:
# Parse amount, unit, and ingredient name
match = re.match(r'\s*(\d+(/\d+)?)\s+([a-zA-Z]+)\s+(.*)', line)
if match:
amount_str, unit, name = match.group(1), match.group(3), match.group(4)
# Convert fractions to decimal
if '/' in amount_str:
num, denom = map(int, amount_str.split('/'))
amount = num / denom
else:
amount = float(amount_str)
ingredients.append({
"food": {
"name": name.lower().strip(),
"plural_name": None,
"ignore_shopping": False,
"supermarket_category": None
},
"unit": {
"name": unit.lower(),
"plural_name": None,
"description": None
},
"amount": amount,
"note": "",
"order": 0,
"is_header": False,
"no_amount": False,
"always_use_plural_unit": False,
"always_use_plural_food": False
})
return ingredients
def parse_instructions(recipe_text: str) -> str:
"""Parse the instructions section."""
# Find the first line that doesn't match ingredient pattern
lines = recipe_text.split('\n')
start_idx = 0
for i, line in enumerate(lines):
if not re.match(r'\s*\d+(/\d+)?\s+[a-zA-Z]', line) and line.strip():
start_idx = i
break
return '\n'.join(lines[start_idx:]).strip()
def create_tandoor_recipe(recipe_text: str) -> Dict[str, Any]:
"""Convert a single recipe to Tandoor format."""
header = parse_recipe_header(recipe_text)
ingredients = parse_ingredients(recipe_text)
instructions = parse_instructions(recipe_text)
# Create keywords from categories
keywords = []
timestamp = datetime.now().isoformat()
for category in header["categories"]:
keywords.append({
"name": category.strip(),
"description": "",
"created_at": timestamp,
"updated_at": timestamp
})
return {
"name": header["title"],
"description": None,
"keywords": keywords,
"steps": [{
"name": "",
"instruction": instructions,
"ingredients": ingredients,
"time": 0,
"order": 0,
"show_as_header": True,
"show_ingredients_table": True
}],
"working_time": 0,
"waiting_time": 0,
"internal": True,
"nutrition": None,
"servings": header["servings"],
"servings_text": "",
"source_url": None
}
def create_export_structure(recipes: List[Dict[str, Any]]) -> str:
"""
Create export directory structure and save recipes as individual zip files.
Returns the export directory name.
"""
# Create export directory with current date
current_date = datetime.now().strftime('%Y-%m-%d')
export_dir = f"export_{current_date}"
# Create directory if it doesn't exist
os.makedirs(export_dir, exist_ok=True)
# Save each recipe as a separate zip file
for recipe in recipes:
# Create timestamp for zip file name
timestamp = int(time.time())
zip_filename = f"{timestamp}.zip"
zip_path = os.path.join(export_dir, zip_filename)
# Create zip file containing recipe.json
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zf:
# Convert recipe to JSON string
recipe_json = json.dumps(recipe, indent=4)
# Add recipe.json to zip file
zf.writestr('recipe.json', recipe_json)
# Small delay to ensure unique timestamps
time.sleep(1)
return export_dir
def create_final_archive(export_dir: str):
"""Create a zip archive of the export directory."""
with zipfile.ZipFile(f"{export_dir}.zip", 'w', zipfile.ZIP_DEFLATED) as zf:
# Walk through the export directory
for root, _, files in os.walk(export_dir):
for file in files:
file_path = os.path.join(root, file)
# Add file to zip with relative path
arcname = os.path.relpath(file_path, start=os.path.dirname(export_dir))
zf.write(file_path, arcname)
def convert_recipes(input_file: str):
"""Convert all recipes from input file to individual Tandoor format files."""
# Read and parse recipes
recipe_texts = parse_meal_master_file(input_file)
# Convert each recipe to Tandoor format
tandoor_recipes = []
for recipe_text in recipe_texts:
tandoor_recipe = create_tandoor_recipe(recipe_text)
tandoor_recipes.append(tandoor_recipe)
# Create export structure
export_dir = create_export_structure(tandoor_recipes)
# Create final archive
create_final_archive(export_dir)
print(f"Export completed. Files are in {export_dir}/ and {export_dir}.zip")
if __name__ == "__main__":
convert_recipes("input.mmf")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment