def test_mismatched_shapes(self) -> None: with pytest.raises(MissingCellError): combine_tables( [ Table.from_dict({(0, 0): Cell(123, columns=2)}), Table.from_dict({(0, 0): Cell(123)}), ], axis=0, ) with pytest.raises(MissingCellError): combine_tables( [ Table.from_dict({(0, 0): Cell(123, rows=2)}), Table.from_dict({(0, 0): Cell(123)}), ], axis=1, )
def test_multiple_tables(self) -> None: t1 = Table.from_dict({(0, 0): Cell(123, columns=2)}) t2 = Table.from_dict({(0, 0): Cell(123), (0, 1): Cell(123)}) assert combine_tables([t1, t2], axis=0) == Table.from_dict({ (0, 0): Cell(123, columns=2), (1, 0): Cell(123), (1, 1): Cell(123) }) t3 = Table.from_dict({(0, 0): Cell(123, rows=2)}) t4 = Table.from_dict({(0, 0): Cell(123), (1, 0): Cell(123)}) assert combine_tables([t3, t4], axis=1) == Table.from_dict({ (0, 0): Cell(123, rows=2), (0, 1): Cell(123), (1, 1): Cell(123) })
def test_multiple_output_sub_recipe() -> None: ingredient_0 = Ingredient(SVS("spam")) ingredient_1 = Ingredient(SVS("eggs")) ingredient_2 = Ingredient(SVS("more spam")) step = Step(SVS("fry"), (ingredient_0, ingredient_1, ingredient_2)) sub_recipe = SubRecipe(step, (SVS("output 0"), SVS("output 1"))) assert recipe_tree_to_table(sub_recipe) == combine_tables( cast( List[Table[RecipeTreeNode]], [ set_border_around_table( Table.from_dict( cast( Mapping[Tuple[int, int], Cell[RecipeTreeNode]], { (0, 0): Cell(ingredient_0), (1, 0): Cell(ingredient_1), (2, 0): Cell(ingredient_2), (0, 1): Cell(step, rows=3), }, )), BorderType.sub_recipe, ), Table.from_dict({ (0, 0): Cell( sub_recipe, rows=3, border_top=BorderType.none, border_right=BorderType.none, border_bottom=BorderType.none, ) }), ], ), axis=1, )
def test_single_table(self) -> None: orig = Table.from_dict({(0, 0): Cell(123, columns=2)}) assert combine_tables([orig], axis=0) == orig assert combine_tables([orig], axis=1) == orig
def test_no_tables(self) -> None: with pytest.raises(EmptyTableError): combine_tables([], axis=0)
def recipe_tree_to_table( recipe_tree: RecipeTreeNode, _root: bool = True ) -> Table[RecipeTreeNode]: """ Convert a recipe tree into tabular form. To display the resulting :py:class:`~Table`, cell contents should be rendered as indicated below. Firstly, the obvious cases: * :py:class:`~Ingredient` is shown as the ingredient quantity and name etc. * :py:class:`~Reference` is shown as its label (and quantity/proportion etc.) * :py:class:`~Step` is shown as its description. The cells immediately to the left in the generated table will contain the inputs to this step. Finally, cells containing a :py:class:`~SubRecipe` may appear in the table for one of two purposes: * For single-output sub recipes, the cell containing the :py:class:`SubRecipe` will be located above the cells containing the body of the sub recipe. This cell should be rendered in the style of a heading and containing the output name as the text. * For multiple-output sub recipes, thec cell containing the :py:class:`SubRecipe` will be immediately to the right of the cells defining the sub-recipe and should be rendered as a list all of the output names for the sub recipe, for example as a bulleted list. .. note:: This cell will have all but its left border style set to be :py:attr:`BorderType.none` to give the appearance of the output list being located out to the right of the rest of the table. Unless otherwise stated, all cells will have all borders set to :py:attr:`BorderType.normal` with the exception of those borders of cells near the edges of a sub recipe block. These will have a style of :py:attr:`BorderType.sub_recipe`. .. note:: The ``_root`` argument is for internal use and must be left unspecified when called by external users. Internally, it indicates if the node passed to this function is the root node of its recipe tree. """ if isinstance(recipe_tree, (Ingredient, Reference)): table: Table[RecipeTreeNode] = Table([[Cell(recipe_tree)]]) if _root: return set_border_around_table(table, BorderType.sub_recipe) else: return table elif isinstance(recipe_tree, Step): input_tables = [ recipe_tree_to_table(input_tree, False) for input_tree in recipe_tree.inputs ] # Pad all input tables to same width and stack them up input_columns = max(table.columns for table in input_tables) input_tables = [right_pad_table(table, input_columns) for table in input_tables] combined_input_tables = combine_tables(input_tables, axis=0) # Add step to RHS of inputs table = combine_tables( [ combined_input_tables, Table.from_dict( {(0, 0): Cell(recipe_tree, rows=combined_input_tables.rows)} ), ], axis=1, ) if _root: return set_border_around_table(table, BorderType.sub_recipe) else: return table elif isinstance(recipe_tree, SubRecipe): sub_tree_table = recipe_tree_to_table(recipe_tree.sub_tree, False) if len(recipe_tree.output_names) == 1: if recipe_tree.show_output_names: return set_border_around_table( combine_tables( [ Table.from_dict( { (0, 0): Cell( cast(RecipeTreeNode, recipe_tree), columns=sub_tree_table.columns, ), } ), sub_tree_table, ], axis=0, ), BorderType.sub_recipe, ) else: return set_border_around_table(sub_tree_table, BorderType.sub_recipe) else: return combine_tables( [ set_border_around_table(sub_tree_table, BorderType.sub_recipe), Table.from_dict( { (0, 0): Cell( cast(RecipeTreeNode, recipe_tree), rows=sub_tree_table.rows, border_top=BorderType.none, border_right=BorderType.none, border_bottom=BorderType.none, ), } ), ], axis=1, ) else: raise NotImplementedError(type(recipe_tree))