def test_empty(self, tmp_path: Path) -> None: empty_dir = tmp_path / "empty_dir" empty_dir.mkdir() assert enumerate_recipe_directory(empty_dir) == RecipeDirectoryListing( title="Empty dir", description_html=None, description_source=None, subdirectories=[], recipes=[], )
def test_recipes(self, tmp_path: Path) -> None: path = tmp_path / "test_dir" path.mkdir() (path / "foo.md").open("w").write("# Foo for 2") (path / "bar.md").open("w").write("# Bar for 3") d = enumerate_recipe_directory(path) assert len(d.recipes) == 2 assert set(d.recipes) == {path / "foo.md", path / "bar.md"}
def test_readme(self, tmp_path: Path, readme_filename: str) -> None: path = tmp_path / "test_dir" path.mkdir() readme = path / readme_filename readme.open("w").write("# A Directory\nTa-da!") assert enumerate_recipe_directory(path) == RecipeDirectoryListing( title="A Directory", description_html="<p>Ta-da!</p>\n", description_source=readme, subdirectories=[], recipes=[], )
def from_root_directory( cls, root_directory: Path, max_servings: int = 10, ) -> "HomePage": """ Create a complete hierarchy of pages from a root directory. Parameters ========== root_directory: Path The root directory of the recipe website sources. max_servings: int The maximum number of servings to scale recipes to. Must be at least as high as the largest number of servings a recipe is scaled for. """ root = enumerate_recipe_directory(root_directory) homepage = cls( title=root.title, parent=None, source_root_directory=root_directory, welcome_message_html=root.description_html, welcome_message_source=root.description_source, ) recipe_pages: MutableMapping[ Path, MutableMapping[Optional[int], RecipePage] ] = {} homepage.scaled_categories = { servings: CategoryPage.from_directory( servings=servings, directory_path=root_directory, parent=homepage, recipe_pages=recipe_pages, ) for servings in range(1, max_servings + 1) } homepage.unscaled_categories = CategoryPage.from_directory( servings=None, directory_path=root_directory, parent=homepage, recipe_pages=recipe_pages, ) return homepage
def from_directory( cls, servings: Optional[int], directory_path: Path, parent: Union[HomePage, "CategoryPage"], recipe_pages: MutableMapping[Path, MutableMapping[Optional[int], "RecipePage"]], ) -> "CategoryPage": """ Create a category listing page. Parameters ========== servings : int or None The number of servings selected. If None, select the native number of servings for recipes. directory_path : Path The directory containing the recipes/subcategories at this level. parent: HomePage or CategoryPage The parent page in the hierarchy. recipe_pages: {recipe_source_path: {servings: MarkdownRecipe, ...}, ...} A dictionary which maps from recipe markdown source filenames and serving counts (where 'None' is for unscaled recipes with no serving count defined) to :py:class:`RecipePage` objects. When scaled category pages are generated (i.e. where servings is not None), this dictionary will be populated with RecipePage objects for the recipes within that category and the given scale. When unscaled category pages are generated (i.e. when servings is None), this dictionary is used to find the natively scaled RecipePage to refer to. As a consequence of the above, scaled category pages must be created before unscaled ones. """ directory = enumerate_recipe_directory(directory_path) root_category = isinstance(parent, HomePage) category_page = cls( title=( directory.title if not root_category else f"Recipes for {servings}" if servings is not None else "Categories" ), parent=parent, servings=servings, description_html=( directory.description_html if not root_category else None ), description_source=directory.description_source if not root_category else None, source_directory=directory_path, ) category_page.subcategories = sorted( ( CategoryPage.from_directory( servings=servings, directory_path=subdirectory, parent=category_page, recipe_pages=recipe_pages, ) for subdirectory in directory.subdirectories ), key=lambda p: p.title, ) if servings is not None: for recipe_source in directory.recipes: scaled_recipe_pages = recipe_pages.setdefault(recipe_source, {}) recipe_page = RecipePage.from_recipe_source( servings=servings, recipe_source=recipe_source, parent=category_page, other_scalings=scaled_recipe_pages, ) category_page.recipes.append(recipe_page) else: for recipe_source in directory.recipes: this_recipe_pages = recipe_pages[recipe_source] if 1 in this_recipe_pages: # Scalable recipe recipe_page = this_recipe_pages[1] else: # Unscalable recipe recipe_page = this_recipe_pages[None] native_servings = recipe_page.native_servings try: recipe_page = recipe_pages[recipe_source][native_servings] except KeyError: raise MaxServingsLowerThanLargestRecipeError( f"The maximum number of servings must be at least {native_servings}" ) category_page.recipes.append(recipe_page) # Bodge: For unscaled recipes, the native number of servings # will be None. Over-write the parent with this (unscaled) # categories page. if native_servings is None: recipe_page.parent = category_page category_page.recipes.sort(key=lambda recipe_page: recipe_page.title) return category_page
def test_multiple_index_files(self, tmp_path: Path) -> None: (tmp_path / "index.md").open("w").write("# Hello") (tmp_path / "readme.md").open("w").write("# Hello") with pytest.raises(MultipleReadmeError): enumerate_recipe_directory(tmp_path)
def test_not_a_directory(self, tmp_path: Path) -> None: path = tmp_path / "not_exists" path.open("w").write("") with pytest.raises(NotADirectoryError): enumerate_recipe_directory(path)
def test_not_exists(self, tmp_path: Path) -> None: path = tmp_path / "not_exists" with pytest.raises(NotADirectoryError): enumerate_recipe_directory(path)