Repository: 3b1b/manim
File: manimlib/extract_scene.py (Python)

Code before:
            for member in inspect.getmembers(
                module,
                lambda x: is_child_scene(x, module)
            )
        ]


def get_indent(code_lines: list[str], line_number: int) -> str:
    """
    Find the indent associated with a given line of python code,
    as a string of spaces
    """
    # Find most recent non-empty line
    try:
        line = next(filter(lambda line: line.strip(), code_lines[line_number - 1::-1]))
    except StopIteration:
        return ""

    # Either return its leading spaces, or add for if it ends with colon
    n_spaces = len(line) - len(line.lstrip())
    if line.endswith(":"):
        n_spaces += 4
    return n_spaces * " "


def insert_embed_line_to_module(module: Module, run_config: Dict) -> None:
    """
    This is hacky, but convenient. When user includes the argument "-e", it will try
    to recreate a file that inserts the line `self.embed()` into the end of the scene's
    construct method. If there is an argument passed in, it will insert the line after
    the last line in the sourcefile which includes that string.
    """
    lines = inspect.getsource(module).splitlines()
    line_number = run_config.embed_line

    # Add the relevant embed line to the code
    indent = get_indent(lines, line_number)
    lines.insert(line_number, indent + "self.embed()")
    new_code = "\n".join(lines)

    # When the user executes the `-e <line_number>` command,
    # it should automatically identifies the nearest class
    # defined above `<line_number>` as 'scene_names'.
    classes = list(filter(lambda line: line.startswith("class"), lines[:line_number]))
    if classes:
        from re import search

        name_search = search(r"(\w+)\(", classes[-1])
        run_config.update(scene_names=[name_search.group(1)])
    else:
        log.error(f"No 'class' has been found above {line_number}!")

Review comment (suggestion):
Instead of passing `run_config` inside here, an alternative could be to return the scene names from this function and then update the `scene_names` of `run_config` from somewhere outside, e.g. in `get_module` that is calling `insert_embed_line_to_module)`.

Code after:
            for member in inspect.getmembers(
                module,
                lambda x: is_child_scene(x, module)
            )
        ]


def get_indent(code_lines: list[str], line_number: int) -> str:
    """
    Find the indent associated with a given line of python code,
    as a string of spaces
    """
    # Find most recent non-empty line
    try:
        line = next(filter(lambda line: line.strip(), code_lines[line_number - 1::-1]))
    except StopIteration:
        return ""

    # Either return its leading spaces, or add for if it ends with colon
    n_spaces = len(line) - len(line.lstrip())
    if line.endswith(":"):
        n_spaces += 4
    return n_spaces * " "


def insert_embed_line_to_module(module: Module, run_config: Dict) -> None:
    """
    This is hacky, but convenient. When user includes the argument "-e", it will try
    to recreate a file that inserts the line `self.embed()` into the end of the scene's
    construct method. If there is an argument passed in, it will insert the line after
    the last line in the sourcefile which includes that string.
    """
    lines = inspect.getsource(module).splitlines()
    line_number = run_config.embed_line

    # Add the relevant embed line to the code
    indent = get_indent(lines, line_number)
    lines.insert(line_number, indent + "self.embed()")
    new_code = "\n".join(lines)

    # When the user executes the `-e <line_number>` command
    # without specifying scene_names, the nearest class name above
    # `<line_number>` will be automatically used as 'scene_names'.

    if not run_config.scene_names:
        classes = list(filter(lambda line: line.startswith("class"), lines[:line_number]))
        if classes:
            from re import search

            scene_name = search(r"(\w+)\(", classes[-1])
            run_config.update(scene_names=[scene_name.group(1)])