def test_scaled_value_expr_integers_stay_as_ints(self) -> None: compiled = compile_markdown("{5}") assert compiled.render(Fraction(1, 3)) == ( '<p><span class="rg-scaled-value">1 <sup>2</sup>⁄<sub>3</sub></span></p>\n' ) compiled = compile_markdown("{5.0}") assert compiled.render(Fraction( 1, 3)) == ('<p><span class="rg-scaled-value">1.67</span></p>\n')
def test_error_message_line_numbers(self, source: str) -> None: with pytest.raises(ParseError) as exc_info: compile_markdown(dedent(source).strip()) assert (str(exc_info.value) == dedent(""" At line 5 column 13: foo = fried() ^ Expected <action> or <ingredient> or <quantity> """).strip())
def test_finds_problems(self, recipe: str, exp_description: str) -> None: markdown_recipe = compile_markdown(f"```recipe\n{recipe}\n```") recipe_blocks = markdown_recipe.recipes[0] lint = list(check_for_unused_ingredients(recipe_blocks)) assert len(lint) == 1 assert lint[0].kind == LintKind.unused_ingredient assert lint[0].description == exp_description
def test_recipes_split_across_blocks(self) -> None: compiled = compile_markdown( dedent(""" A recipe in two parts. Part one: sauce = boil down(tomatoes, water) Part two: pour over(pasta, sauce) """).strip()) sr = SubRecipe( Step( SVS("boil down"), ( Ingredient(SVS("tomatoes")), Ingredient(SVS("water")), ), ), (SVS("sauce"), ), ) r1 = Recipe((sr, )) r2 = Recipe( (Step( SVS("pour over"), ( Ingredient(SVS("pasta")), Reference(sr), ), ), ), follows=r1, ) assert compiled.recipes == [[r1, r2]]
def test_title_parsing( self, source: str, exp_title: Optional[str], exp_servings: Optional[int], exp_html: str, exp_html_10: str, ) -> None: compiled = compile_markdown(dedent(source).strip()) assert compiled.title == exp_title assert compiled.servings == exp_servings assert compiled.render(1) == exp_html assert compiled.render(10) == exp_html_10
def test_separate_recipes(self) -> None: compiled = compile_markdown( dedent(""" Fried egg: ```recipe 1 egg ``` ```recipe fry(egg) ``` Boiled egg: ```new-recipe 2 egg ``` ```recipe boil(egg) ``` """).strip()) e1 = SubRecipe( Ingredient(SVS("egg"), Quantity(1)), (SVS("egg"), ), show_output_names=False, ) r1 = Recipe((e1, )) r2 = Recipe((Step(SVS("fry"), (Reference(e1), )), ), follows=r1) e2 = SubRecipe( Ingredient(SVS("egg"), Quantity(2)), (SVS("egg"), ), show_output_names=False, ) r3 = Recipe((e2, )) r4 = Recipe((Step(SVS("boil"), (Reference(e2), )), ), follows=r3) assert compiled.recipes == [[r1, r2], [r3, r4]] # Check anchor IDs are unique too html = compiled.render() assert 'id="recipe-egg"' in html assert 'href="#recipe-egg"' in html assert 'id="recipe2-egg"' in html assert 'href="#recipe2-egg"' in html
def main() -> None: parser = ArgumentParser(description=""" Check a recipe grid markdown file for possible mistakes. """, ) parser.add_argument( "recipe", type=Path, nargs="*", help=""" The filename of the recipe grid markdown file to compile. Pass multiple filenames to check multiple files. """, ) parser.add_argument( "--ignore", "-i", action="extend", default=[], nargs="+", choices=[k.name for k in LintKind], help=""" Ignore warnings of a certain types. """, ) args = parser.parse_args() failed = False for page in args.recipe: try: markdown_recipe = compile_markdown(page.open().read()) for recipe in markdown_recipe.recipes: for lint in check(recipe): if lint.kind.name not in args.ignore: failed = True print( f"{page}: Warning: {lint.description} [{lint.kind.name}]" ) except (ParseError, RecipeCompileError) as e: failed = True print(f"{page}: Error: {e}") sys.exit(1 if failed else 0)
def test_add_recipe_scaling_links() -> None: html = compile_markdown("# A recipe serving 3").render(Fraction(2, 3)) assert postprocess_html( html, False, [ partial( add_recipe_scaling_links, from_path="/serves2/foo/bar.html", scaled_paths={ 1: "/serves1/foo/bar.html", 2: "/serves2/foo/bar.html", 3: "/serves3/foo/bar.html", 4: "/serves4/foo/bar.html", }, native_servings=3, ) ], ) == ('<header><h1 class="rg-title-scalable">A recipe ' '<span class="rg-serving-count">' '<a href="#" class="rg-serving-count-current">' 'serving <span class="rg-scaled-value">2</span>' "</a>" "<ul>" '<li><a href="../../serves1/foo/bar.html">serving ' '<span class="rg-scaled-value">1</span></a></li>' '<li><a href="bar.html">serving ' '<span class="rg-scaled-value">2</span></a></li>' '<li><a href="../../serves3/foo/bar.html">serving ' '<span class="rg-scaled-value">3</span></a></li>' '<li><a href="../../serves4/foo/bar.html">serving ' '<span class="rg-scaled-value">4</span></a></li>' "</ul>" "</span></h1><p>Rescaled from " '<span class="rg-original-servings">' '<a href="../../serves3/foo/bar.html">3 servings</a></span>.</p>' "</header>\n")
def test_recipe_code_blocks_and_scaled_rendering(self, source: str) -> None: compiled = compile_markdown(dedent(source).strip()) assert compiled.title == "A recipe" assert compiled.servings == 2 assert compiled.recipes == [[ Recipe((Step( SVS("fry"), ( Ingredient(SVS("spam"), Quantity(100, "g")), Ingredient(SVS("eggs"), Quantity(2)), ), ), )), ]] assert (compiled.render(2) == dedent(""" <header><h1 class="rg-title-scalable">A recipe <span class="rg-serving-count">for <span class="rg-scaled-value">4</span></span></h1><p>Rescaled from <span class="rg-original-servings">2 servings</span>.</p></header> <div class="rg-recipe-block"> <table class="rg-table"> <tr> <td class="rg-ingredient rg-border-left-sub-recipe rg-border-top-sub-recipe"> <span class="rg-quantity-with-conversions rg-scaled-value" tabindex="0"> 200g<ul class="rg-quantity-conversions"> <li><sup>1</sup>⁄<sub>5</sub>kg</li> <li>0.441lb</li> <li>7.05oz</li> </ul> </span> spam </td> <td class="rg-step rg-border-right-sub-recipe rg-border-top-sub-recipe rg-border-bottom-sub-recipe" rowspan="2">fry</td> </tr> <tr><td class="rg-ingredient rg-border-left-sub-recipe rg-border-bottom-sub-recipe"><span class="rg-quantity-unitless rg-scaled-value">4</span> eggs</td></tr> </table> </div><p>Ta-da!</p> """ # noqa: E501 ).lstrip())
def test_quiet_when_everything_adds_up(self, recipe: str) -> None: markdown_recipe = compile_markdown(f"```recipe\n{recipe}\n```") recipe_blocks = markdown_recipe.recipes[0] assert list( check_sub_recipe_references_sum_to_whole(recipe_blocks)) == []
def test_quiet_when_nothing_unused(self, recipe: str) -> None: markdown_recipe = compile_markdown(f"```recipe\n{recipe}\n```") recipe_blocks = markdown_recipe.recipes[0] assert list(check_for_unused_ingredients(recipe_blocks)) == []
def test_finds_problems(self, recipe: str, exp_lint: List[Lint]) -> None: markdown_recipe = compile_markdown(f"```recipe\n{recipe}\n```") recipe_blocks = markdown_recipe.recipes[0] lint = list(check_sub_recipe_references_sum_to_whole(recipe_blocks)) assert len(lint) == len(exp_lint) assert set(lint) == set(exp_lint)
def test_scaled_value_expr(self, markdown: str, exp: str, exp10: str) -> None: compiled = compile_markdown(markdown) assert compiled.render(1) == exp assert compiled.render(10) == exp10
def test_no_recipes(self) -> None: assert compile_markdown("").render() == "" assert compile_markdown("Hello").render() == "<p>Hello</p>\n"
def test_non_recipe_fenced_block(self) -> None: assert (compile_markdown("~~~\nfoo\n~~~").render() == "<pre><code>foo\n</code></pre>\n")