def test_proportion_given_for_ingredient(self) -> None: with pytest.raises(ProportionGivenForIngredientError) as exc_info: compile(["1/2 * spam"]) assert (str(exc_info.value) == dedent(""" At line 1 column 1: 1/2 * spam ^ A proportion was given (implying a sub recipe is being referenced) but no sub recipe named spam exists. """ # noqa: E501 ).strip())
def test_ingredient_with_explicit_output_name_always_shown( self, output_name: str) -> None: # NB: output name always shown even if it matches what would be the inferred name assert compile([f"{output_name} = spam"]) == [ Recipe((SubRecipe(Ingredient(SVS("spam")), (SVS(output_name), ), True), )) ]
def test_reference_compilation(self) -> None: sub_recipe = SubRecipe( Step(SVS("open"), (Ingredient(SVS("spam")), )), (SVS("spam"), SVS("tin")), True, ) assert compile([ "spam, tin = open(spam)\nspam\n1/3*spam\n25% of the spam\nleft over spam\n2 'tin'\n50g spam" # noqa: E501 ]) == [ Recipe(( sub_recipe, # spam Reference(sub_recipe, 0, Proportion(1.0)), # 1/3*spam Reference(sub_recipe, 0, Proportion(Fraction(1, 3), preposition="*")), # 25% of the spam Reference( sub_recipe, 0, Proportion(0.25, percentage=True, preposition="% of the"), ), # remaining Reference(sub_recipe, 0, Proportion(None, remainder_wording="left over")), # 2 tin Reference(sub_recipe, 1, Quantity(2.0)), # 50g spam Reference(sub_recipe, 0, Quantity(50.0, "g")), )), ]
def test_reference_has_no_inferred_name(self) -> None: sub_recipe = SubRecipe(Ingredient(SVS("spam")), (SVS("spam"), ), False) assert compile(["spam\nspam\nspam"]) == [ Recipe(( sub_recipe, Reference(sub_recipe, 0), Reference(sub_recipe, 0), )), ]
def test_string_compilation(self) -> None: # Just a sanity check assert compile(["spam {3 eggs}"]) == [ Recipe((SubRecipe( Ingredient(SVS(("spam ", 3, " eggs"))), (SVS(["spam ", 3, " eggs"]), ), False, ), )) ]
def test_processed_ingredient_with_implied_output_name( self, syntax: str) -> None: assert compile([syntax]) == [ Recipe((SubRecipe( Step(SVS("fry"), (Ingredient(SVS("spam")), )), (SVS("spam"), ), False, ), )), ]
def test_compilation_of_steps(self) -> None: assert compile(["fry(slice(spam), eggs)"]) == [ Recipe((Step( SVS("fry"), ( Step(SVS("slice"), (Ingredient(SVS("spam")), )), Ingredient(SVS("eggs")), ), ), )), ]
def test_inlining_single_references_name_explicitly_required(self) -> None: assert compile(["meat := spam, sliced\nfry(meat, eggs)"]) == [ Recipe((Step( SVS("fry"), ( SubRecipe( Step(SVS("sliced"), (Ingredient(SVS("spam")), )), (SVS("meat"), ), ), Ingredient(SVS("eggs")), ), ), )) ]
def test_inlining_single_references(self, quantity_spec: str) -> None: assert compile( [f"meat = 10g spam, sliced\nfry({quantity_spec} meat, eggs)"]) == [ Recipe((Step( SVS("fry"), ( Step( SVS("sliced"), (Ingredient(SVS("spam"), Quantity(10, "g")), ), ), Ingredient(SVS("eggs")), ), ), )) ]
def test_inlines_within_inlines(self) -> None: recipe = Recipe((Step( SVS("boil"), ( SubRecipe( Step( SVS("fry"), (Ingredient(SVS("spam"), Quantity(100, "g")), ), ), (SVS("fried spam"), ), ), Ingredient(SVS("water")), ), ), )) assert compile([ "100g spam\nfried spam := fry(spam)\nboil(fried spam, water)" ]) == [recipe]
def render_document(self, element: Document) -> MarkdownRecipe: """Render the document and compile all recipe code blocks.""" # Render the markdown document self.output.html = super().render_children(element) # type: ignore # Compile captured recipe blocks markdown_source = element.text for recipe_source_blocks in self.independent_recipe_source_blocks: placeholders = list(recipe_source_blocks.keys()) sources = [ recipe_source_block.get_line_number_corrected_source(markdown_source) for recipe_source_block in recipe_source_blocks.values() ] recipes = compile(sources) for placeholder, recipe in zip(placeholders, recipes): self.output.recipe_placeholders[placeholder] = recipe return self.output
def test_dont_inline_multi_output_subrecipes(self) -> None: sub_recipe = SubRecipe( Ingredient(SVS("spam")), (SVS("meat"), SVS("tin")), True, ) assert compile(["meat, tin = spam\nfry(meat, eggs)"]) == [ Recipe(( sub_recipe, Step( SVS("fry"), ( Reference(sub_recipe, 0), Ingredient(SVS("eggs")), ), ), )) ]
def test_inlining_within_a_block(self) -> None: recipe0 = Recipe((SubRecipe(Ingredient(SVS("egg")), (SVS("egg"), ), show_output_names=False), )) recipe1 = Recipe( (Step(SVS("fry"), (Ingredient(SVS("spam"), Quantity(50, "g")), )), ), follows=recipe0, ) recipe2 = Recipe( (SubRecipe(Ingredient(SVS("potato")), (SVS("potato"), ), show_output_names=False), ), follows=recipe1, ) assert compile(["egg", "50g spam\nfry(spam)", "potato"]) == [ recipe0, recipe1, recipe2, ]
def test_dont_inline_partial_uses_of_a_subrecipe(self) -> None: sub_recipe = SubRecipe( Ingredient(SVS("spam"), Quantity(100, "g")), (SVS("spam"), ), False, ) assert compile(["100g spam\nfry(50g spam, eggs)"]) == [ Recipe(( sub_recipe, Step( SVS("fry"), ( Reference(sub_recipe, 0, Quantity(50, "g")), Ingredient(SVS("eggs")), ), ), )) ]
def test_dont_inline_definitions_from_earlier_blocks(self) -> None: sub_recipe = SubRecipe( Ingredient(SVS("spam"), Quantity(100, "g")), (SVS("spam"), ), False, ) recipe0 = Recipe((sub_recipe, )) recipe1 = Recipe( (Step( SVS("fry"), ( Reference(sub_recipe, 0, Quantity(50, "g")), Ingredient(SVS("eggs")), ), ), ), follows=recipe0, ) assert compile(["100g spam", "fry(50g spam, eggs)"]) == [recipe0, recipe1]
def test_ingredient_compilation(self) -> None: assert compile( ["500g spam\n2 eggs\n1 kg foo\n1 can of dog food\nheat"]) == [ Recipe(( # With unit SubRecipe( Ingredient(SVS("spam"), Quantity(500.0, "g")), (SVS("spam"), ), False, ), # No unit SubRecipe( Ingredient(SVS("eggs"), Quantity(2.0)), (SVS("eggs"), ), False, ), # With spacing between number and unit SubRecipe( Ingredient(SVS("foo"), Quantity(1.0, "kg", value_unit_spacing=" ")), (SVS("foo"), ), False, ), # With spacing between number and unit SubRecipe( Ingredient( SVS("dog food"), Quantity( 1, "can", value_unit_spacing=" ", preposition=" of", ), ), (SVS("dog food"), ), False, ), # No quantity SubRecipe(Ingredient(SVS("heat")), (SVS("heat"), ), False), )) ]
def compile_recipes(app: Sphinx, doctree: nodes.Node) -> None: """Parse and compile the recipes appearing on a page.""" env = app.env # Skip if no recipe sources found anywhere if not hasattr(env, "recipe_sources"): return docname = env.docname page_recipes = env.recipe_sources[docname] # type: ignore for i, page_recipe_blocks in enumerate(page_recipes): sources = [source for source, _node in page_recipe_blocks] try: recipes = compile(sources) except (ParseError, RecipeCompileError) as e: filename = env.doc2path(docname) raise SphinxRecipeCompileError( f"Recipe compile error in {filename}: {e}") for (_source, node), recipe in zip(page_recipe_blocks, recipes): node.id_prefix = f"sub-recipe-{i}-" node.recipe = recipe
def test_ingredient_with_implied_output_name(self) -> None: assert compile(["spam"]) == [ Recipe((SubRecipe(Ingredient(SVS("spam")), (SVS("spam"), ), False), )) ]
def test_multiple_outputs(self) -> None: assert compile(["foo, bar = spam"]) == [ Recipe((SubRecipe(Ingredient(SVS("spam")), (SVS("foo"), SVS("bar")), True), )) ]
def test_step_with_multiple_inputs_has_no_inferred_name(self) -> None: assert compile(["fry(spam, eggs)"]) == [ Recipe( (Step(SVS("fry"), (Ingredient(SVS("spam")), Ingredient(SVS("eggs")))), )), ]