From cbbfab104cf4a46fb744f55205d097503939da8b Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Wed, 22 Jul 2020 16:39:08 +0530 Subject: [PATCH 01/39] Added subparser for extra commands. --- manim/utils/config_utils.py | 86 ++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 29 deletions(-) diff --git a/manim/utils/config_utils.py b/manim/utils/config_utils.py index a6c7fb1875..3de3c4de6d 100644 --- a/manim/utils/config_utils.py +++ b/manim/utils/config_utils.py @@ -18,6 +18,7 @@ __all__ = ["_run_config", "_paths_config_file", "_from_command_line"] +NON_ANIM_UTILS=["cfg"] def _parse_file_writer_config(config_parser, args): """Parse config files and CLI arguments into a single dictionary.""" @@ -29,9 +30,11 @@ def _parse_file_writer_config(config_parser, args): # Handle input files and scenes. Note these cannot be set from # the .cfg files, only from CLI arguments - fw_config["input_file"] = args.file - fw_config["scene_names"] = args.scene_names if args.scene_names is not None else [] - fw_config["output_file"] = args.output_file + # Don't set these if the end user is invoking anything unrelated to rendering. + if not(any(sys.argv[1]==item for item in NON_ANIM_UTILS)): + fw_config["input_file"] = args.file + fw_config["scene_names"] = args.scene_names if args.scene_names is not None else [] + fw_config["output_file"] = args.output_file # Handle all options that are directly overridden by CLI # arguments. Note ConfigParser options are all strings and each @@ -131,22 +134,38 @@ def _parse_cli(arg_list, input=True): epilog="Made with <3 by the manim community devs", ) if input: - parser.add_argument( - "file", help="path to file holding the python code for the scene", - ) - parser.add_argument( - "scene_names", - nargs="*", - help="Name of the Scene class you want to see", - default=[""], - ) - parser.add_argument( - "-o", - "--output_file", - help="Specify the name of the output file, if " - "it should be different from the scene class name", - default="", - ) + subparsers = parser.add_subparsers() + cfg_related = subparsers.add_parser('cfg') + cfg_subparsers = cfg_related.add_subparsers() + + cfg_write = cfg_subparsers.add_parser('write') + cfg_write.add_argument( + "--level", + choices=["user", "cwd"], + default="cwd", + help="Specify if this config is for user or just the working directory." + ) + cfg_show = cfg_subparsers.add_parser('show') + + cfg_export = cfg_subparsers.add_parser("export") + cfg_export.add_argument("--dir",default=os.getcwd()) + if not(any(sys.argv[1] == item for item in NON_ANIM_UTILS)): + parser.add_argument( + "file", help="path to file holding the python code for the scene", + ) + parser.add_argument( + "scene_names", + nargs="*", + help="Name of the Scene class you want to see", + default=[""], + ) + parser.add_argument( + "-o", + "--output_file", + help="Specify the name of the output file, if " + "it should be different from the scene class name", + default="", + ) # The following use (action='store_const', const=True) instead of # the built-in (action='store_true'). This is because the latter @@ -328,7 +347,6 @@ def _init_dirs(config): if not os.path.exists(folder): os.makedirs(folder) - def _from_command_line(): """Determine if manim was called from the command line.""" # Manim can be called from the command line in three different @@ -373,17 +391,23 @@ def _run_config(): config_files = _paths_config_file() if _from_command_line(): args = _parse_cli(sys.argv[1:]) - if args.config_file is not None: - if os.path.exists(args.config_file): - config_files.append(args.config_file) + + if not(any(sys.argv[1]==item for item in NON_ANIM_UTILS)): + if args.config_file is not None: + if os.path.exists(args.config_file): + config_files.append(args.config_file) + else: + raise FileNotFoundError(f"Config file {args.config_file} doesn't exist") else: - raise FileNotFoundError(f"Config file {args.config_file} doesn't exist") + script_directory_file_config = os.path.join( + os.path.dirname(args.file), "manim.cfg" + ) + if os.path.exists(script_directory_file_config): + config_files.append(script_directory_file_config) else: - script_directory_file_config = os.path.join( - os.path.dirname(args.file), "manim.cfg" - ) - if os.path.exists(script_directory_file_config): - config_files.append(script_directory_file_config) + working_directory_file_config = os.path.join(os.getcwd(),"manim.cfg") + if os.path.exists(working_directory_file_config): + config_files.append(working_directory_file_config) else: # In this case, we still need an empty args object. @@ -397,3 +421,7 @@ def _run_config(): # this is for internal use when writing output files file_writer_config = _parse_file_writer_config(config_parser, args) return args, config_parser, file_writer_config, successfully_read_files + +def curr_config_dict(): + config=_run_config()[1] + return {section: dict(config[section]) for section in config.sections()} From 17a777f760b862eab2328e3d64f7eb89dcdb8d75 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Wed, 22 Jul 2020 16:48:35 +0530 Subject: [PATCH 02/39] Renamed main to write. Added show method to show config at cwd. Added export method to export config at cwd. --- .../utils/{cfgwriter.py => cfg_file_utils.py} | 56 ++++++++++++++----- 1 file changed, 41 insertions(+), 15 deletions(-) rename manim/utils/{cfgwriter.py => cfg_file_utils.py} (67%) diff --git a/manim/utils/cfgwriter.py b/manim/utils/cfg_file_utils.py similarity index 67% rename from manim/utils/cfgwriter.py rename to manim/utils/cfg_file_utils.py index 3b74dbeb21..ff5341f713 100644 --- a/manim/utils/cfgwriter.py +++ b/manim/utils/cfg_file_utils.py @@ -1,5 +1,5 @@ """ -cfgwriter.py +cfg_file_utils.py ------------ Inputs the configuration files while checking it is valid. Can be executed by `manim-cfg` command. @@ -8,7 +8,7 @@ import os import configparser -from .config_utils import _run_config, _paths_config_file +from .config_utils import _run_config, _paths_config_file, curr_config_dict from rich.console import Console from rich.progress import track @@ -23,6 +23,7 @@ [magenta] For a full list of styles, visit[/magenta] https://rich.readthedocs.io/en/latest/style.html""" TITLE_TEXT = "[yellow bold]Manim Configuration File Writer[/yellow bold]" +console = Console() def is_valid_style(style): """Checks whether the entered color is a valid color according to rich @@ -67,9 +68,8 @@ def replace_keys(default): return default -def main(): +def write(level=None): config = _run_config()[1] - console = Console() default = config["logger"] console.print(TITLE_TEXT, justify="center") console.print(INTRO_INSTRUCTIONS) @@ -86,20 +86,25 @@ def main(): default[key] = temp default = replace_keys(default) config["logger"] = default - console.print( - "Do you want to save this as the default for this User?(y/n)[[n]]", - style="dim purple", - end="", - ) - save_to_userpath = input() + + if level is None: + console.print( + "Do you want to save this as the default for this User?(y/n)[[n]]", + style="dim purple", + end="", + ) + save_to_userpath = input() + else: + save_to_userpath = "" + config_paths = _paths_config_file() + [os.path.abspath("manim.cfg")] - if save_to_userpath.lower() == "y": + if save_to_userpath.lower() == "y" or level=="user": if not os.path.exists(os.path.abspath(os.path.join(config_paths[1], ".."))): os.makedirs(os.path.abspath(os.path.join(config_paths[1], ".."))) with open(config_paths[1], "w") as fp: config.write(fp) console.print( - f"""A configuration file called [yellow]{config_paths[1]}[/yellow] has been created with your required changes. + f"""A configuration file at [yellow]{config_paths[1]}[/yellow] has been created with your required changes. This will be used when running the manim command. If you want to override this config, you will have to create a manim.cfg in the local directory, where you want those changes to be overridden.""" ) @@ -107,10 +112,31 @@ def main(): with open(config_paths[2], "w") as fp: config.write(fp) console.print( - f"""A configuration file called [yellow]{config_paths[2]}[/yellow] has been created. + f"""A configuration file at [yellow]{config_paths[2]}[/yellow] has been created. To save your theme please save that file and place it in your current working directory, from where you run the manim command.""" ) -if __name__ == "__main__": - main() +def show(): + current_config = curr_config_dict() + for category in current_config: + console.print(f"{category}",style="bold green underline") + for entry in current_config[category]: + if category=="logger": + console.print(f"{entry} :",end="") + console.print( + f" {current_config[category][entry]}", + style=current_config[category][entry] + ) + else: + console.print(f"{entry} : {current_config[category][entry]}") + console.print("\n") + +def export(path): + config = _run_config()[1] + with open(os.path.join(path,"manim.cfg"),"w") as outpath: + config.write(outpath) + from_path = os.path.join(os.getcwd(),'manim.cfg') + to_path = os.path.join(path,'manim.cfg') + console.print( + f"Exported Config at {from_path} to {to_path}.") From 744d0a1608d4065086a1666a398404342a84acd6 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Wed, 22 Jul 2020 16:50:07 +0530 Subject: [PATCH 03/39] Displayed all config files read, and not just the last. --- manim/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manim/config.py b/manim/config.py index 74e6ab7939..8cc05b8007 100644 --- a/manim/config.py +++ b/manim/config.py @@ -89,7 +89,7 @@ def _parse_config(config_parser, args): args, config_parser, file_writer_config, successfully_read_files = _run_config() if _from_command_line(): - logger.info(f"Read configuration files: {os.path.abspath(successfully_read_files[-1])}") + logger.info(f"Read configuration files: {[os.path.abspath(cfgfile) for cfgfile in successfully_read_files]}") _init_dirs(file_writer_config) config = _parse_config(config_parser, args) camera_config = config From 0ac65f3b9a1056418f3a974e1bbaf6974ce18ff4 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Wed, 22 Jul 2020 16:54:13 +0530 Subject: [PATCH 04/39] Removed manim-cfg command. Added actions for subcommands. --- manim/__main__.py | 49 +++++++++++++++++++++++++++++++---------------- setup.py | 1 - 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/manim/__main__.py b/manim/__main__.py index fed9b7827a..026179490b 100644 --- a/manim/__main__.py +++ b/manim/__main__.py @@ -8,7 +8,8 @@ import importlib.util import types -from .config import file_writer_config +from .config import file_writer_config,args +from .utils import cfg_file_utils from .scene.scene import Scene from .utils.sounds import play_error_sound from .utils.sounds import play_finish_sound @@ -150,23 +151,37 @@ def get_module(file_name): def main(): - module = get_module(file_writer_config["input_file"]) - all_scene_classes = get_scene_classes_from_module(module) - scene_classes_to_render = get_scenes_to_render(all_scene_classes) - sound_on = file_writer_config["sound"] - for SceneClass in scene_classes_to_render: + if sys.argv[1:][0]=="cfg": try: - # By invoking, this renders the full scene - scene = SceneClass() - open_file_if_needed(scene.file_writer) - if sound_on: - play_finish_sound() - except Exception: - print("\n\n") - traceback.print_exc() - print("\n\n") - if sound_on: - play_error_sound() + subcommand = sys.argv[1:][1] + except IndexError: + raise Exception("No subcommand provided. Type manim cfg -h for a list of commands.") + + if subcommand == "write": + cfg_file_utils.write(args.level) + elif subcommand == "show": + cfg_file_utils.show() + elif subcommand == "export": + cfg_file_utils.export(args.dir) + + else: + module = get_module(file_writer_config["input_file"]) + all_scene_classes = get_scene_classes_from_module(module) + scene_classes_to_render = get_scenes_to_render(all_scene_classes) + sound_on = file_writer_config["sound"] + for SceneClass in scene_classes_to_render: + try: + # By invoking, this renders the full scene + scene = SceneClass() + open_file_if_needed(scene.file_writer) + if sound_on: + play_finish_sound() + except Exception: + print("\n\n") + traceback.print_exc() + print("\n\n") + if sound_on: + play_error_sound() if __name__ == "__main__": diff --git a/setup.py b/setup.py index 2d081bb07e..af84f44ad7 100755 --- a/setup.py +++ b/setup.py @@ -11,7 +11,6 @@ "console_scripts": [ "manim=manim.__main__:main", "manimcm=manim.__main__:main", - "manim-cfg=manim.utils.cfgwriter:main", ] }, install_requires=[ From 558502ca7633f1c8d4185f38fb155bd6dcf33954 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Wed, 22 Jul 2020 22:03:06 +0530 Subject: [PATCH 05/39] Fix a bug where only using `manim` as command would throw an error. Set cfg write default value to None. This means that if no argument is provided, the configwriter will ask for location. --- manim/utils/config_utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/manim/utils/config_utils.py b/manim/utils/config_utils.py index 3de3c4de6d..506dda2e6b 100644 --- a/manim/utils/config_utils.py +++ b/manim/utils/config_utils.py @@ -142,14 +142,16 @@ def _parse_cli(arg_list, input=True): cfg_write.add_argument( "--level", choices=["user", "cwd"], - default="cwd", + default=None, help="Specify if this config is for user or just the working directory." ) cfg_show = cfg_subparsers.add_parser('show') cfg_export = cfg_subparsers.add_parser("export") cfg_export.add_argument("--dir",default=os.getcwd()) - if not(any(sys.argv[1] == item for item in NON_ANIM_UTILS)): + + # If the only command is manim, or if there are only rendering related commands + if len(sys.argv) < 2 or not(any(sys.argv[1] == item for item in NON_ANIM_UTILS)): parser.add_argument( "file", help="path to file holding the python code for the scene", ) From 237ea014df592ce920f96045012b498f6bf4989b Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Wed, 22 Jul 2020 22:03:59 +0530 Subject: [PATCH 06/39] Change what __all__ is. Make `export`'s log a bit clearer. --- manim/utils/cfg_file_utils.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/manim/utils/cfg_file_utils.py b/manim/utils/cfg_file_utils.py index ff5341f713..69c611aaa5 100644 --- a/manim/utils/cfg_file_utils.py +++ b/manim/utils/cfg_file_utils.py @@ -15,7 +15,7 @@ from rich.style import Style from rich.errors import StyleSyntaxError -__all__ = ["main"] +__all__ = ["write","show","export"] INVALID_STYLE_MSG = "[red bold]Your Style is not valid. Try again.[/red bold]" INTRO_INSTRUCTIONS = """[red]The default colour is used by the input statement. @@ -116,7 +116,6 @@ def write(level=None): To save your theme please save that file and place it in your current working directory, from where you run the manim command.""" ) - def show(): current_config = curr_config_dict() for category in current_config: @@ -138,5 +137,4 @@ def export(path): config.write(outpath) from_path = os.path.join(os.getcwd(),'manim.cfg') to_path = os.path.join(path,'manim.cfg') - console.print( - f"Exported Config at {from_path} to {to_path}.") + console.print(f"Exported final Config at {from_path} to {to_path}.") From 457e1519daa3e02630d2c4b62366aef9552b874c Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Wed, 22 Jul 2020 23:05:43 +0530 Subject: [PATCH 07/39] Check for number of arguments in multiple places. ( I should have done this before I opened the PR. Soz guys) --- manim/utils/config_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manim/utils/config_utils.py b/manim/utils/config_utils.py index 506dda2e6b..f1b58f8fa0 100644 --- a/manim/utils/config_utils.py +++ b/manim/utils/config_utils.py @@ -31,7 +31,7 @@ def _parse_file_writer_config(config_parser, args): # Handle input files and scenes. Note these cannot be set from # the .cfg files, only from CLI arguments # Don't set these if the end user is invoking anything unrelated to rendering. - if not(any(sys.argv[1]==item for item in NON_ANIM_UTILS)): + if len(sys.argv) < 2 or not(any(sys.argv[1] == item for item in NON_ANIM_UTILS)): fw_config["input_file"] = args.file fw_config["scene_names"] = args.scene_names if args.scene_names is not None else [] fw_config["output_file"] = args.output_file @@ -394,7 +394,7 @@ def _run_config(): if _from_command_line(): args = _parse_cli(sys.argv[1:]) - if not(any(sys.argv[1]==item for item in NON_ANIM_UTILS)): + if len(sys.argv) < 2 or not(any(sys.argv[1] == item for item in NON_ANIM_UTILS)): if args.config_file is not None: if os.path.exists(args.config_file): config_files.append(args.config_file) From 4a98e59cef9befcfb17d29d654d62abc4ddbf89b Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Wed, 22 Jul 2020 23:17:03 +0530 Subject: [PATCH 08/39] Change minimum arguments if called via python. --- manim/utils/config_utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/manim/utils/config_utils.py b/manim/utils/config_utils.py index f1b58f8fa0..23d5358503 100644 --- a/manim/utils/config_utils.py +++ b/manim/utils/config_utils.py @@ -31,7 +31,8 @@ def _parse_file_writer_config(config_parser, args): # Handle input files and scenes. Note these cannot be set from # the .cfg files, only from CLI arguments # Don't set these if the end user is invoking anything unrelated to rendering. - if len(sys.argv) < 2 or not(any(sys.argv[1] == item for item in NON_ANIM_UTILS)): + min_argvs = 2 if "py" in sys.argv[0] else 4 + if len(sys.argv) < min_argvs or not(any(sys.argv[min_argvs-1] == item for item in NON_ANIM_UTILS)): fw_config["input_file"] = args.file fw_config["scene_names"] = args.scene_names if args.scene_names is not None else [] fw_config["output_file"] = args.output_file @@ -393,8 +394,8 @@ def _run_config(): config_files = _paths_config_file() if _from_command_line(): args = _parse_cli(sys.argv[1:]) - - if len(sys.argv) < 2 or not(any(sys.argv[1] == item for item in NON_ANIM_UTILS)): + min_argvs = 2 if "py" in sys.argv[0] else 4 + if len(sys.argv) < min_argvs or not(any(sys.argv[min_argvs-1] == item for item in NON_ANIM_UTILS)): if args.config_file is not None: if os.path.exists(args.config_file): config_files.append(args.config_file) From 4938acb930ef2603d0b22954cdf0da66568c916b Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Wed, 22 Jul 2020 23:43:31 +0530 Subject: [PATCH 09/39] Only use subparser if a non-rendering related command appears. Fix the definition of min_argvs --- manim/utils/config_utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/manim/utils/config_utils.py b/manim/utils/config_utils.py index 23d5358503..a27f87c906 100644 --- a/manim/utils/config_utils.py +++ b/manim/utils/config_utils.py @@ -31,7 +31,7 @@ def _parse_file_writer_config(config_parser, args): # Handle input files and scenes. Note these cannot be set from # the .cfg files, only from CLI arguments # Don't set these if the end user is invoking anything unrelated to rendering. - min_argvs = 2 if "py" in sys.argv[0] else 4 + min_argvs = 4 if "py" in sys.argv[0] else 2 if len(sys.argv) < min_argvs or not(any(sys.argv[min_argvs-1] == item for item in NON_ANIM_UTILS)): fw_config["input_file"] = args.file fw_config["scene_names"] = args.scene_names if args.scene_names is not None else [] @@ -134,7 +134,8 @@ def _parse_cli(arg_list, input=True): description="Animation engine for explanatory math videos", epilog="Made with <3 by the manim community devs", ) - if input: + min_argvs = 4 if "py" in sys.argv[0] else 2 + if input and len(sys.argv) >= min_argvs and any(sys.argv[min_argvs-1] == item for item in NON_ANIM_UTILS): subparsers = parser.add_subparsers() cfg_related = subparsers.add_parser('cfg') cfg_subparsers = cfg_related.add_subparsers() @@ -151,6 +152,7 @@ def _parse_cli(arg_list, input=True): cfg_export = cfg_subparsers.add_parser("export") cfg_export.add_argument("--dir",default=os.getcwd()) + if input: # If the only command is manim, or if there are only rendering related commands if len(sys.argv) < 2 or not(any(sys.argv[1] == item for item in NON_ANIM_UTILS)): parser.add_argument( @@ -394,7 +396,7 @@ def _run_config(): config_files = _paths_config_file() if _from_command_line(): args = _parse_cli(sys.argv[1:]) - min_argvs = 2 if "py" in sys.argv[0] else 4 + min_argvs = 4 if "py" in sys.argv[0] else 2 if len(sys.argv) < min_argvs or not(any(sys.argv[min_argvs-1] == item for item in NON_ANIM_UTILS)): if args.config_file is not None: if os.path.exists(args.config_file): From 95dd479b3eec7a6ddb9195f7c18d4bc0640c2df7 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Thu, 23 Jul 2020 12:14:47 +0530 Subject: [PATCH 10/39] Make subcommand system less hacky. --- manim/__main__.py | 25 +++++++++++++------------ manim/utils/config_utils.py | 24 +++++++++++++++--------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/manim/__main__.py b/manim/__main__.py index 026179490b..a6dacd8f5f 100644 --- a/manim/__main__.py +++ b/manim/__main__.py @@ -151,18 +151,19 @@ def get_module(file_name): def main(): - if sys.argv[1:][0]=="cfg": - try: - subcommand = sys.argv[1:][1] - except IndexError: - raise Exception("No subcommand provided. Type manim cfg -h for a list of commands.") - - if subcommand == "write": - cfg_file_utils.write(args.level) - elif subcommand == "show": - cfg_file_utils.show() - elif subcommand == "export": - cfg_file_utils.export(args.dir) + if hasattr(args,"subcommands"): + if "cfg" in args.subcommands: + if args.cfg_subcommand is not None: + subcommand=args.cfg_subcommand + if subcommand == "write": + cfg_file_utils.write(args.level) + elif subcommand == "show": + cfg_file_utils.show() + elif subcommand == "export": + cfg_file_utils.export(args.dir) + else: + logger.error("NO ARGUMENT PROVIDED, exiting...") + else: module = get_module(file_writer_config["input_file"]) diff --git a/manim/utils/config_utils.py b/manim/utils/config_utils.py index a27f87c906..9347d7b534 100644 --- a/manim/utils/config_utils.py +++ b/manim/utils/config_utils.py @@ -30,9 +30,8 @@ def _parse_file_writer_config(config_parser, args): # Handle input files and scenes. Note these cannot be set from # the .cfg files, only from CLI arguments - # Don't set these if the end user is invoking anything unrelated to rendering. - min_argvs = 4 if "py" in sys.argv[0] else 2 - if len(sys.argv) < min_argvs or not(any(sys.argv[min_argvs-1] == item for item in NON_ANIM_UTILS)): + # Don't set these if the a subcommand is invoked + if not(hasattr(args,"subcommands")): fw_config["input_file"] = args.file fw_config["scene_names"] = args.scene_names if args.scene_names is not None else [] fw_config["output_file"] = args.output_file @@ -135,10 +134,12 @@ def _parse_cli(arg_list, input=True): epilog="Made with <3 by the manim community devs", ) min_argvs = 4 if "py" in sys.argv[0] else 2 - if input and len(sys.argv) >= min_argvs and any(sys.argv[min_argvs-1] == item for item in NON_ANIM_UTILS): - subparsers = parser.add_subparsers() + if input and (len(sys.argv) >= min_argvs-1 and # If "manim" is not the only command + any(a == item for a in sys.argv for item in NON_ANIM_UTILS)): # non-anim exists + + subparsers = parser.add_subparsers(dest="subcommands") cfg_related = subparsers.add_parser('cfg') - cfg_subparsers = cfg_related.add_subparsers() + cfg_subparsers = cfg_related.add_subparsers(dest="cfg_subcommand") cfg_write = cfg_subparsers.add_parser('write') cfg_write.add_argument( @@ -337,8 +338,14 @@ def _parse_cli(arg_list, input=True): parser.add_argument( "--config_file", help="Specify the configuration file", ) + parsed=parser.parse_args(arg_list) + if hasattr(parsed,"subcommands"): + setattr(parsed, "cfg_subcommand", + cfg_related.parse_args( + sys.argv[min_argvs:] + ).cfg_subcommand) - return parser.parse_args(arg_list) + return parsed def _init_dirs(config): @@ -396,8 +403,7 @@ def _run_config(): config_files = _paths_config_file() if _from_command_line(): args = _parse_cli(sys.argv[1:]) - min_argvs = 4 if "py" in sys.argv[0] else 2 - if len(sys.argv) < min_argvs or not(any(sys.argv[min_argvs-1] == item for item in NON_ANIM_UTILS)): + if not hasattr(args,"subcommands"): if args.config_file is not None: if os.path.exists(args.config_file): config_files.append(args.config_file) From 35add6f1b8295bac8b78a53a5d3409ca9c28b740 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Thu, 23 Jul 2020 18:31:15 +0530 Subject: [PATCH 11/39] Show cfg stuff in help commands as well. --- manim/utils/config_utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/manim/utils/config_utils.py b/manim/utils/config_utils.py index 9347d7b534..ed8890b698 100644 --- a/manim/utils/config_utils.py +++ b/manim/utils/config_utils.py @@ -18,7 +18,7 @@ __all__ = ["_run_config", "_paths_config_file", "_from_command_line"] -NON_ANIM_UTILS=["cfg"] +NON_ANIM_UTILS=["cfg","--help","-h"] def _parse_file_writer_config(config_parser, args): """Parse config files and CLI arguments into a single dictionary.""" @@ -134,8 +134,9 @@ def _parse_cli(arg_list, input=True): epilog="Made with <3 by the manim community devs", ) min_argvs = 4 if "py" in sys.argv[0] else 2 - if input and (len(sys.argv) >= min_argvs-1 and # If "manim" is not the only command - any(a == item for a in sys.argv for item in NON_ANIM_UTILS)): # non-anim exists + if input and (len(sys.argv) > min_argvs-1 # If "manim" is not the only command + and any(a == item for a in sys.argv for item in NON_ANIM_UTILS) #non-anim exists + or len(sys.argv) == min_argvs-1): subparsers = parser.add_subparsers(dest="subcommands") cfg_related = subparsers.add_parser('cfg') From 2b6bc73c0612a7bd501b38a362d28d917df5e901 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Fri, 24 Jul 2020 12:56:48 +0530 Subject: [PATCH 12/39] Made the export command check if inpath and outpath are same. Changed colour of link to green. --- manim/utils/cfg_file_utils.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/manim/utils/cfg_file_utils.py b/manim/utils/cfg_file_utils.py index 69c611aaa5..36dd5e6345 100644 --- a/manim/utils/cfg_file_utils.py +++ b/manim/utils/cfg_file_utils.py @@ -2,7 +2,8 @@ cfg_file_utils.py ------------ -Inputs the configuration files while checking it is valid. Can be executed by `manim-cfg` command. +General Config File Managing Utilities. +The functions below can be called via the `manim cfg` subcommand. """ import os @@ -20,7 +21,7 @@ INVALID_STYLE_MSG = "[red bold]Your Style is not valid. Try again.[/red bold]" INTRO_INSTRUCTIONS = """[red]The default colour is used by the input statement. If left empty, the default colour will be used.[/red] -[magenta] For a full list of styles, visit[/magenta] https://rich.readthedocs.io/en/latest/style.html""" +[magenta] For a full list of styles, visit[/magenta] [green]https://rich.readthedocs.io/en/latest/style.html[/green]""" TITLE_TEXT = "[yellow bold]Manim Configuration File Writer[/yellow bold]" console = Console() @@ -133,8 +134,24 @@ def show(): def export(path): config = _run_config()[1] - with open(os.path.join(path,"manim.cfg"),"w") as outpath: - config.write(outpath) - from_path = os.path.join(os.getcwd(),'manim.cfg') - to_path = os.path.join(path,'manim.cfg') - console.print(f"Exported final Config at {from_path} to {to_path}.") + if os.path.abspath(path) == os.path.abspath(os.getcwd()): + console.print( + """You are reading the config from the same directory you are exporting to. +This means that the exported config will overwrite the config for this directory. +Are you sure you want to continue?[y/n]""", + style="red bold", end="" + ) + proceed = True if input().lower()=="y" else False + else: + proceed = True + if proceed: + if not os.path.isdir(path): + console.print(f"Creating folder: {path}.",style="red bold") + os.mkdir(path) + with open(os.path.join(path,"manim.cfg"),"w") as outpath: + config.write(outpath) + from_path = os.path.join(os.getcwd(),'manim.cfg') + to_path = os.path.join(path,'manim.cfg') + console.print(f"Exported final Config at {from_path} to {to_path}.") + else: + console.print("Could NOT write config.", style="red bold") From 72afdbc573afb3117c18d010a8fe9b75fb263686 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Fri, 24 Jul 2020 12:57:07 +0530 Subject: [PATCH 13/39] Added curr_config_dict to __all__ --- manim/utils/config_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manim/utils/config_utils.py b/manim/utils/config_utils.py index ed8890b698..0c2726d5b5 100644 --- a/manim/utils/config_utils.py +++ b/manim/utils/config_utils.py @@ -16,7 +16,7 @@ from .. import constants from .tex import TexTemplate, TexTemplateFromFile -__all__ = ["_run_config", "_paths_config_file", "_from_command_line"] +__all__ = ["_run_config", "_paths_config_file", "_from_command_line", "curr_config_dict"] NON_ANIM_UTILS=["cfg","--help","-h"] From 5d6d69b458c25a5376fdf3ca1d865aa37fd91ee0 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Fri, 24 Jul 2020 23:53:03 +0530 Subject: [PATCH 14/39] Renamed cfg_file_utils.py to cfg_subcmd.py --- manim/utils/{cfg_file_utils.py => cfg_subcmds.py} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename manim/utils/{cfg_file_utils.py => cfg_subcmds.py} (97%) diff --git a/manim/utils/cfg_file_utils.py b/manim/utils/cfg_subcmds.py similarity index 97% rename from manim/utils/cfg_file_utils.py rename to manim/utils/cfg_subcmds.py index 36dd5e6345..03a9d28a23 100644 --- a/manim/utils/cfg_file_utils.py +++ b/manim/utils/cfg_subcmds.py @@ -1,5 +1,5 @@ """ -cfg_file_utils.py +cfg_subcmd.py ------------ General Config File Managing Utilities. @@ -9,7 +9,7 @@ import os import configparser -from .config_utils import _run_config, _paths_config_file, curr_config_dict +from .config_utils import _run_config, _paths_config_file, finalized_configs_dict from rich.console import Console from rich.progress import track @@ -118,7 +118,7 @@ def write(level=None): ) def show(): - current_config = curr_config_dict() + current_config = finalized_configs_dict() for category in current_config: console.print(f"{category}",style="bold green underline") for entry in current_config[category]: From 9270932538272aeb56e56029875450c74dd87358 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Fri, 24 Jul 2020 23:53:43 +0530 Subject: [PATCH 15/39] Stopped yelling at end user ;) --- manim/__main__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/manim/__main__.py b/manim/__main__.py index a6dacd8f5f..18f49b71b6 100644 --- a/manim/__main__.py +++ b/manim/__main__.py @@ -9,7 +9,7 @@ import types from .config import file_writer_config,args -from .utils import cfg_file_utils +from .utils import cfg_subcmds from .scene.scene import Scene from .utils.sounds import play_error_sound from .utils.sounds import play_finish_sound @@ -156,13 +156,13 @@ def main(): if args.cfg_subcommand is not None: subcommand=args.cfg_subcommand if subcommand == "write": - cfg_file_utils.write(args.level) + cfg_subcmds.write(args.level) elif subcommand == "show": - cfg_file_utils.show() + cfg_subcmds.show() elif subcommand == "export": - cfg_file_utils.export(args.dir) + cfg_subcmds.export(args.dir) else: - logger.error("NO ARGUMENT PROVIDED, exiting...") + logger.error("No argument provided; Exiting...") else: From 64626deb62818e0422c4b35cf87fc79424b7ea3b Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Fri, 24 Jul 2020 23:56:39 +0530 Subject: [PATCH 16/39] Refactored detection of subcommands and resultant control flow. Extracted conditional that checks for subcommands to an external function. Renamed subcommand argparsers to be clearer. Made code like, 10 times more readable. Renamed curr_config_dict to finalized_configs_dict. --- manim/utils/config_utils.py | 61 +++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/manim/utils/config_utils.py b/manim/utils/config_utils.py index 0c2726d5b5..04d6c223b1 100644 --- a/manim/utils/config_utils.py +++ b/manim/utils/config_utils.py @@ -16,9 +16,9 @@ from .. import constants from .tex import TexTemplate, TexTemplateFromFile -__all__ = ["_run_config", "_paths_config_file", "_from_command_line", "curr_config_dict"] +__all__ = ["_run_config", "_paths_config_file", "_from_command_line", "finalized_configs_dict"] -NON_ANIM_UTILS=["cfg","--help","-h"] +min_argvs = 4 if "py" in sys.argv[0] else 2 def _parse_file_writer_config(config_parser, args): """Parse config files and CLI arguments into a single dictionary.""" @@ -29,8 +29,9 @@ def _parse_file_writer_config(config_parser, args): fw_config = {} # Handle input files and scenes. Note these cannot be set from - # the .cfg files, only from CLI arguments - # Don't set these if the a subcommand is invoked + # the .cfg files, only from CLI arguments. + # If a subcommand is given, manim will not render a video and + # thus these specific input/output files are not needed. if not(hasattr(args,"subcommands")): fw_config["input_file"] = args.file fw_config["scene_names"] = args.scene_names if args.scene_names is not None else [] @@ -133,30 +134,28 @@ def _parse_cli(arg_list, input=True): description="Animation engine for explanatory math videos", epilog="Made with <3 by the manim community devs", ) - min_argvs = 4 if "py" in sys.argv[0] else 2 - if input and (len(sys.argv) > min_argvs-1 # If "manim" is not the only command - and any(a == item for a in sys.argv for item in NON_ANIM_UTILS) #non-anim exists - or len(sys.argv) == min_argvs-1): - - subparsers = parser.add_subparsers(dest="subcommands") - cfg_related = subparsers.add_parser('cfg') - cfg_subparsers = cfg_related.add_subparsers(dest="cfg_subcommand") - - cfg_write = cfg_subparsers.add_parser('write') - cfg_write.add_argument( - "--level", - choices=["user", "cwd"], - default=None, - help="Specify if this config is for user or just the working directory." - ) - cfg_show = cfg_subparsers.add_parser('show') + if input: + # If the only command is `manim`, we want both subcommands like `cfg` + # and mandatory positional arguments like `file` to show up in the help section. + if len(sys.argv) == min_argvs-1 or _subcommands_exist(): + subparsers = parser.add_subparsers(dest="subcommands") + cfg_related = subparsers.add_parser('cfg') + cfg_subparsers = cfg_related.add_subparsers(dest="cfg_subcommand") + + cfg_write_parser = cfg_subparsers.add_parser('write') + cfg_write_parser.add_argument( + "--level", + choices=["user", "cwd"], + default=None, + help="Specify if this config is for user or just the working directory." + ) + cfg_subparsers.add_parser('show') - cfg_export = cfg_subparsers.add_parser("export") - cfg_export.add_argument("--dir",default=os.getcwd()) + cfg_export_parser = cfg_subparsers.add_parser("export") + cfg_export_parser.add_argument("--dir",default=os.getcwd()) - if input: - # If the only command is manim, or if there are only rendering related commands - if len(sys.argv) < 2 or not(any(sys.argv[1] == item for item in NON_ANIM_UTILS)): + if (len(sys.argv) == min_argvs-1 or + not _subcommands_exist(ignore = ["--help","-h"])): parser.add_argument( "file", help="path to file holding the python code for the scene", ) @@ -434,6 +433,14 @@ def _run_config(): file_writer_config = _parse_file_writer_config(config_parser, args) return args, config_parser, file_writer_config, successfully_read_files -def curr_config_dict(): +def finalized_configs_dict(): config=_run_config()[1] return {section: dict(config[section]) for section in config.sections()} + +def _subcommands_exist(ignore = []): + NON_ANIM_UTILS = ["cfg","--help","-h"] + NON_ANIM_UTILS = [util for util in NON_ANIM_UTILS if util not in ignore] + + not_only_manim = len(sys.argv) > min_argvs-1 + sub_command_exists = any(a == item for a in sys.argv for item in NON_ANIM_UTILS) + return not_only_manim and sub_command_exists \ No newline at end of file From 7a6455103844d3043ef3825f6246df7230c8047d Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Sat, 25 Jul 2020 13:40:48 +0530 Subject: [PATCH 17/39] Added ability to modify all boolean config vars in cfg write. --- manim/utils/cfg_subcmds.py | 54 +++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/manim/utils/cfg_subcmds.py b/manim/utils/cfg_subcmds.py index 03a9d28a23..7d45713554 100644 --- a/manim/utils/cfg_subcmds.py +++ b/manim/utils/cfg_subcmds.py @@ -18,11 +18,9 @@ __all__ = ["write","show","export"] -INVALID_STYLE_MSG = "[red bold]Your Style is not valid. Try again.[/red bold]" -INTRO_INSTRUCTIONS = """[red]The default colour is used by the input statement. +RICH_COLOUR_INSTRUCTIONS = """[red]The default colour is used by the input statement. If left empty, the default colour will be used.[/red] [magenta] For a full list of styles, visit[/magenta] [green]https://rich.readthedocs.io/en/latest/style.html[/green]""" -TITLE_TEXT = "[yellow bold]Manim Configuration File Writer[/yellow bold]" console = Console() @@ -70,23 +68,43 @@ def replace_keys(default): def write(level=None): + console.print("[yellow bold]Manim Configuration File Writer[/yellow bold]", justify="center") config = _run_config()[1] - default = config["logger"] - console.print(TITLE_TEXT, justify="center") - console.print(INTRO_INSTRUCTIONS) - default = replace_keys(default) - for key in default: - console.print("Enter the Style for %s" % key + ":", style=key, end="") - temp = input() - if temp: - while not is_valid_style(temp): - console.print(INVALID_STYLE_MSG) - console.print("Enter the Style for %s" % key + ":", style=key, end="") + + for category in config: + console.print(f"{category}",style="bold green underline") + default = config[category] + if category == "logger": + console.print(RICH_COLOUR_INSTRUCTIONS) + default = replace_keys(default) + for key in default: + console.print(f"Enter the style for {key}:", style=key, end="") temp = input() - else: - default[key] = temp - default = replace_keys(default) - config["logger"] = default + if temp: + while not is_valid_style(temp): + console.print("[red bold]Invalid style. Try again.[/red bold]") + console.print(f"Enter the style for {key}:", style=key, end="") + temp = input() + else: + default[key] = temp + default = replace_keys(default) + + else: + for key in default: + if default[key] in ["True","False"]: + console.print( + f"Enter value for {key} (defaults to {default[key]}):", end="") + temp = input() + if temp: + while not temp.lower().capitalize() in ["True","False"]: + console.print( + "[red bold]Invalid value. Try again.[/red bold]") + console.print( + f"Enter the style for {key}:", style=key, end="") + temp = input() + else: + default[key] = temp + config[category] = dict(default) if level is None: console.print( From f400d3d48ab2ce809ab9724a05d7e2caf3d9da01 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Sat, 25 Jul 2020 18:06:47 +0530 Subject: [PATCH 18/39] Add newline at end to conform with formatting rules. --- manim/utils/config_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manim/utils/config_utils.py b/manim/utils/config_utils.py index 04d6c223b1..a7be775cb3 100644 --- a/manim/utils/config_utils.py +++ b/manim/utils/config_utils.py @@ -443,4 +443,4 @@ def _subcommands_exist(ignore = []): not_only_manim = len(sys.argv) > min_argvs-1 sub_command_exists = any(a == item for a in sys.argv for item in NON_ANIM_UTILS) - return not_only_manim and sub_command_exists \ No newline at end of file + return not_only_manim and sub_command_exists From 31e747965f6805c1b60bd511b30fe691eefa6afe Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Mon, 27 Jul 2020 00:47:07 +0530 Subject: [PATCH 19/39] Only Create Media Dir if not using subcommand. --- manim/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manim/config.py b/manim/config.py index 8cc05b8007..1db4dabd22 100644 --- a/manim/config.py +++ b/manim/config.py @@ -90,6 +90,7 @@ def _parse_config(config_parser, args): args, config_parser, file_writer_config, successfully_read_files = _run_config() if _from_command_line(): logger.info(f"Read configuration files: {[os.path.abspath(cfgfile) for cfgfile in successfully_read_files]}") - _init_dirs(file_writer_config) + if not(hasattr(args,"subcommands")): + _init_dirs(file_writer_config) config = _parse_config(config_parser, args) camera_config = config From 1f3091cccecc7732f93c5a9574fa6d2636b61722 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Mon, 27 Jul 2020 00:50:27 +0530 Subject: [PATCH 20/39] Add condition to check if _run_config() is being called from manim/__main__ (@leotrs please please please tell me if this'll screw something up.) Fixed number of minimum arguments to check for. Formatting changes. --- manim/utils/config_utils.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/manim/utils/config_utils.py b/manim/utils/config_utils.py index a7be775cb3..d2d8332de2 100644 --- a/manim/utils/config_utils.py +++ b/manim/utils/config_utils.py @@ -18,8 +18,7 @@ __all__ = ["_run_config", "_paths_config_file", "_from_command_line", "finalized_configs_dict"] -min_argvs = 4 if "py" in sys.argv[0] else 2 - +min_argvs = 3 if "-m" in sys.argv[0] else 2 def _parse_file_writer_config(config_parser, args): """Parse config files and CLI arguments into a single dictionary.""" # By default, use the CLI section of the digested .cfg files @@ -341,9 +340,8 @@ def _parse_cli(arg_list, input=True): parsed=parser.parse_args(arg_list) if hasattr(parsed,"subcommands"): setattr(parsed, "cfg_subcommand", - cfg_related.parse_args( - sys.argv[min_argvs:] - ).cfg_subcommand) + cfg_related.parse_args(sys.argv[min_argvs:]).cfg_subcommand + ) return parsed @@ -374,6 +372,11 @@ def _from_command_line(): return from_cli_command or from_python_m +def _from_dunder_main(): + dunder_main_path = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "__main__.py") + return sys.argv[0]==dunder_main_path def _paths_config_file(): library_wide = os.path.abspath( @@ -401,7 +404,7 @@ def _paths_config_file(): def _run_config(): # Config files to be parsed, in ascending priority config_files = _paths_config_file() - if _from_command_line(): + if _from_command_line() or _from_dunder_main(): args = _parse_cli(sys.argv[1:]) if not hasattr(args,"subcommands"): if args.config_file is not None: From cd425208854799aca2ffc09df7839f2bb01cda79 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Mon, 27 Jul 2020 00:51:51 +0530 Subject: [PATCH 21/39] Add test for current cfg subcommands. --- tests/test_cli/manim.cfg | 1 + tests/test_cli/test_cfg_subcmd.py | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 tests/test_cli/test_cfg_subcmd.py diff --git a/tests/test_cli/manim.cfg b/tests/test_cli/manim.cfg index b71cfe2352..1838b30c24 100644 --- a/tests/test_cli/manim.cfg +++ b/tests/test_cli/manim.cfg @@ -1,6 +1,7 @@ [CLI] movie_file_extension = .mp4 write_to_movie = True +sound = True # write_all = False save_last_frame = True # save_pngs = False diff --git a/tests/test_cli/test_cfg_subcmd.py b/tests/test_cli/test_cfg_subcmd.py new file mode 100644 index 0000000000..dfd3b73563 --- /dev/null +++ b/tests/test_cli/test_cfg_subcmd.py @@ -0,0 +1,27 @@ +import pytest +import os +import shutil + +from .test_cli import capture + +def test_cfg_help(python_version): + command = [python_version, "-m", "manim", "cfg", "--help"] + out, err, exitcode = capture(command) + assert exitcode == 0, f"The cfg subcommand is not working as intended." + +def test_cfg_show(python_version): + os.chdir(os.path.dirname(__file__)) + command = [python_version, "-m", "manim", "cfg", "show"] + out, err, exitcode = capture(command) + assert exitcode == 0 + assert f"{os.path.sep}tests{os.path.sep}".encode("utf-8") in out, err + +def test_cfg_export(python_version): + os.chdir(os.path.dirname(__file__)) + command = [python_version, "-m", "manim", "cfg", "export", "--dir", "temp"] + out, err, exitcode = capture(command) + assert exitcode == 0 + assert os.path.exists(os.path.join("temp","manim.cfg")) + with open(os.path.join("temp","manim.cfg"),"r") as writtencfg: + assert "sound = True" in writtencfg.read(), err + shutil.rmtree("temp") From f9febea0d1a4ec7eee4ef550c42dc38c3635c7a4 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Mon, 27 Jul 2020 00:56:04 +0530 Subject: [PATCH 22/39] Fix a relative import. --- tests/test_cli/test_cfg_subcmd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_cli/test_cfg_subcmd.py b/tests/test_cli/test_cfg_subcmd.py index dfd3b73563..fa6817878c 100644 --- a/tests/test_cli/test_cfg_subcmd.py +++ b/tests/test_cli/test_cfg_subcmd.py @@ -2,7 +2,7 @@ import os import shutil -from .test_cli import capture +from test_cli import capture def test_cfg_help(python_version): command = [python_version, "-m", "manim", "cfg", "--help"] From 9686500da5fd488e9b22cad7944c09225a7ed8e6 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Mon, 27 Jul 2020 02:46:26 +0530 Subject: [PATCH 23/39] Change a deprecated warning method. --- manim/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manim/__main__.py b/manim/__main__.py index 18f49b71b6..5fa555abe2 100644 --- a/manim/__main__.py +++ b/manim/__main__.py @@ -128,7 +128,7 @@ def get_module(file_name): logger.info("Enter the animation's code & end with an EOF (CTRL+D on Linux/Unix, CTRL+Z on Windows):") code = sys.stdin.read() if not code.startswith("from manim import"): - logger.warn("Didn't find an import statement for Manim. Importing automatically...") + logger.warning("Didn't find an import statement for Manim. Importing automatically...") code="from manim import *\n"+code logger.info("Rendering animation from typed code...") try: From 59d846c8d830b0481393555cea3404f4205bdf20 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Mon, 27 Jul 2020 02:47:29 +0530 Subject: [PATCH 24/39] Check if _run_config is called from manil/__main__.py Check for min_args again. --- manim/utils/config_utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/manim/utils/config_utils.py b/manim/utils/config_utils.py index d2d8332de2..03e9462979 100644 --- a/manim/utils/config_utils.py +++ b/manim/utils/config_utils.py @@ -340,8 +340,9 @@ def _parse_cli(arg_list, input=True): parsed=parser.parse_args(arg_list) if hasattr(parsed,"subcommands"): setattr(parsed, "cfg_subcommand", - cfg_related.parse_args(sys.argv[min_argvs:]).cfg_subcommand - ) + cfg_related.parse_args( + sys.argv[min_argvs -(0 if min_argvs == 2 else 1):] + ).cfg_subcommand) return parsed @@ -404,7 +405,7 @@ def _paths_config_file(): def _run_config(): # Config files to be parsed, in ascending priority config_files = _paths_config_file() - if _from_command_line() or _from_dunder_main(): + if _from_command_line() or _from_dunder_main(): args = _parse_cli(sys.argv[1:]) if not hasattr(args,"subcommands"): if args.config_file is not None: From e885280cc93d9584f2f8e9aee02b63016e256768 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Mon, 27 Jul 2020 02:47:52 +0530 Subject: [PATCH 25/39] Properly change cwd and change back as required. --- tests/test_cli/test_cfg_subcmd.py | 3 +-- tests/test_cli/test_cli.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_cli/test_cfg_subcmd.py b/tests/test_cli/test_cfg_subcmd.py index fa6817878c..3d55c27760 100644 --- a/tests/test_cli/test_cfg_subcmd.py +++ b/tests/test_cli/test_cfg_subcmd.py @@ -5,19 +5,18 @@ from test_cli import capture def test_cfg_help(python_version): + os.chdir(os.path.dirname(__file__)) command = [python_version, "-m", "manim", "cfg", "--help"] out, err, exitcode = capture(command) assert exitcode == 0, f"The cfg subcommand is not working as intended." def test_cfg_show(python_version): - os.chdir(os.path.dirname(__file__)) command = [python_version, "-m", "manim", "cfg", "show"] out, err, exitcode = capture(command) assert exitcode == 0 assert f"{os.path.sep}tests{os.path.sep}".encode("utf-8") in out, err def test_cfg_export(python_version): - os.chdir(os.path.dirname(__file__)) command = [python_version, "-m", "manim", "cfg", "export", "--dir", "temp"] out, err, exitcode = capture(command) assert exitcode == 0 diff --git a/tests/test_cli/test_cli.py b/tests/test_cli/test_cli.py index 0da7fa9bc4..ab0b7b06c0 100644 --- a/tests/test_cli/test_cli.py +++ b/tests/test_cli/test_cli.py @@ -15,6 +15,7 @@ def capture(command,instream=None): def test_help(python_version): + os.chdir(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) command = [python_version, "-m", "manim", "--help"] out, err, exitcode = capture(command) assert exitcode == 0, f"Manim has been installed incorrectly. Please refer to the troubleshooting section on the wiki. Error:\n{err.decode()}" From 5c2bc1f7619b311bebe0372eb40bab0963d209f7 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Mon, 27 Jul 2020 08:15:22 +0530 Subject: [PATCH 26/39] Add test for manim cfg write --- tests/test_cli/test_cfg_subcmd.py | 18 ++++++++++++++++ tests/test_cli/write_cfg_sbcmd_input.txt | 26 ++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 tests/test_cli/write_cfg_sbcmd_input.txt diff --git a/tests/test_cli/test_cfg_subcmd.py b/tests/test_cli/test_cfg_subcmd.py index 3d55c27760..5fe56907ed 100644 --- a/tests/test_cli/test_cfg_subcmd.py +++ b/tests/test_cli/test_cfg_subcmd.py @@ -5,18 +5,21 @@ from test_cli import capture def test_cfg_help(python_version): + """Test if Manim successfully adds configparsers when a subcommand is invoked.""" os.chdir(os.path.dirname(__file__)) command = [python_version, "-m", "manim", "cfg", "--help"] out, err, exitcode = capture(command) assert exitcode == 0, f"The cfg subcommand is not working as intended." def test_cfg_show(python_version): + """Test if the `manim cfg show` command works as intended.""" command = [python_version, "-m", "manim", "cfg", "show"] out, err, exitcode = capture(command) assert exitcode == 0 assert f"{os.path.sep}tests{os.path.sep}".encode("utf-8") in out, err def test_cfg_export(python_version): + """Test if the `manim cfg export` command works as intended.""" command = [python_version, "-m", "manim", "cfg", "export", "--dir", "temp"] out, err, exitcode = capture(command) assert exitcode == 0 @@ -24,3 +27,18 @@ def test_cfg_export(python_version): with open(os.path.join("temp","manim.cfg"),"r") as writtencfg: assert "sound = True" in writtencfg.read(), err shutil.rmtree("temp") + +def test_cfg_write(python_version): + """Simulate using the command `manim cfg write`""" + command = [python_version, "-m", "manim","cfg","write","--level","cwd"] + + """As the number of config values that `manim cfg write` increases, so must the + number of newlines and/or values written in write_cfg_sbcmd_input increase.""" + + out, err, exitcode = capture( + command, + open(os.path.join(os.path.dirname(__file__), "write_cfg_sbcmd_input.txt")) + ) + assert exitcode == 0, err + with open(os.path.join(os.path.dirname(__file__), "manim.cfg")) as cfgfile: + assert "sound = False" in cfgfile.read() diff --git a/tests/test_cli/write_cfg_sbcmd_input.txt b/tests/test_cli/write_cfg_sbcmd_input.txt new file mode 100644 index 0000000000..110779f7a8 --- /dev/null +++ b/tests/test_cli/write_cfg_sbcmd_input.txt @@ -0,0 +1,26 @@ + + + + + + + + +False + + + + + + + + + + + + + + + + + From 8060eebc2b783947c33ecf38ff0a9870ac637db3 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Mon, 27 Jul 2020 09:20:40 +0530 Subject: [PATCH 27/39] Clarify use of write_cfg_sbcmd_input --- tests/test_cli/test_cfg_subcmd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_cli/test_cfg_subcmd.py b/tests/test_cli/test_cfg_subcmd.py index 5fe56907ed..27346db489 100644 --- a/tests/test_cli/test_cfg_subcmd.py +++ b/tests/test_cli/test_cfg_subcmd.py @@ -32,8 +32,8 @@ def test_cfg_write(python_version): """Simulate using the command `manim cfg write`""" command = [python_version, "-m", "manim","cfg","write","--level","cwd"] - """As the number of config values that `manim cfg write` increases, so must the - number of newlines and/or values written in write_cfg_sbcmd_input increase.""" + """As the number of config values that `manim cfg write` can modify increases, so + must the number of newlines and/or values written in write_cfg_sbcmd_input increase.""" out, err, exitcode = capture( command, From 1bf8aefb5251e24d4a5310295051b3354dde6522 Mon Sep 17 00:00:00 2001 From: Aathish Date: Tue, 28 Jul 2020 08:36:02 +0530 Subject: [PATCH 28/39] Use os.pardir instead of ".." As suggested by @huguesdevimeux Co-authored-by: Hugues Devimeux <36239975+huguesdevimeux@users.noreply.github.com> --- manim/utils/cfg_subcmds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manim/utils/cfg_subcmds.py b/manim/utils/cfg_subcmds.py index 7d45713554..313ff8cf82 100644 --- a/manim/utils/cfg_subcmds.py +++ b/manim/utils/cfg_subcmds.py @@ -118,7 +118,7 @@ def write(level=None): config_paths = _paths_config_file() + [os.path.abspath("manim.cfg")] if save_to_userpath.lower() == "y" or level=="user": - if not os.path.exists(os.path.abspath(os.path.join(config_paths[1], ".."))): + if not os.path.exists(os.path.abspath(os.path.join(config_paths[1], os.pardir))): os.makedirs(os.path.abspath(os.path.join(config_paths[1], ".."))) with open(config_paths[1], "w") as fp: config.write(fp) From 7f4a88dd95f6a01a5781a71347ad4d3708f8f7d3 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Tue, 28 Jul 2020 10:50:28 +0530 Subject: [PATCH 29/39] Add cross-platform file opening method. --- manim/utils/file_ops.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/manim/utils/file_ops.py b/manim/utils/file_ops.py index 181cfb426b..76033d49fa 100644 --- a/manim/utils/file_ops.py +++ b/manim/utils/file_ops.py @@ -1,4 +1,6 @@ import os +import subprocess as sp +import platform import numpy as np @@ -56,4 +58,19 @@ def get_sorted_integer_files(directory, elif remove_non_integer_files: os.remove(full_path) indexed_files.sort(key=lambda p: p[0]) - return list(map(lambda p: os.path.join(directory, p[1]), indexed_files)) \ No newline at end of file + return list(map(lambda p: os.path.join(directory, p[1]), indexed_files)) + +def open_file(file_path): + current_os = platform.system() + if current_os == "Windows": + os.startfile(file_path) + else: + commands = [] + if current_os == "Linux": + commands.append("xdg-open") + elif current_os.startswith("CYGWIN"): + commands.append("cygstart") + else: # Assume macOS + commands.append("open") + commands.append(file_path) + sp.Popen(commands) \ No newline at end of file From 6ad00010f06af108432a5f5802fb9a8a5179664f Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Tue, 28 Jul 2020 10:51:11 +0530 Subject: [PATCH 30/39] Add --open flag for cfg write --- manim/__main__.py | 2 +- manim/utils/cfg_subcmds.py | 114 ++++++++++++++++++++---------------- manim/utils/config_utils.py | 6 ++ 3 files changed, 70 insertions(+), 52 deletions(-) diff --git a/manim/__main__.py b/manim/__main__.py index 5fa555abe2..a860105af0 100644 --- a/manim/__main__.py +++ b/manim/__main__.py @@ -156,7 +156,7 @@ def main(): if args.cfg_subcommand is not None: subcommand=args.cfg_subcommand if subcommand == "write": - cfg_subcmds.write(args.level) + cfg_subcmds.write(args.level,args.open) elif subcommand == "show": cfg_subcmds.show() elif subcommand == "export": diff --git a/manim/utils/cfg_subcmds.py b/manim/utils/cfg_subcmds.py index 313ff8cf82..08a0446ca5 100644 --- a/manim/utils/cfg_subcmds.py +++ b/manim/utils/cfg_subcmds.py @@ -10,6 +10,7 @@ import configparser from .config_utils import _run_config, _paths_config_file, finalized_configs_dict +from .file_ops import guarantee_existence, open_file from rich.console import Console from rich.progress import track @@ -67,73 +68,84 @@ def replace_keys(default): return default -def write(level=None): - console.print("[yellow bold]Manim Configuration File Writer[/yellow bold]", justify="center") +def write(level=None, openfile=False): config = _run_config()[1] + config_paths = _paths_config_file() + [os.path.abspath("manim.cfg")] + console.print("[yellow bold]Manim Configuration File Writer[/yellow bold]", justify="center") - for category in config: - console.print(f"{category}",style="bold green underline") - default = config[category] - if category == "logger": - console.print(RICH_COLOUR_INSTRUCTIONS) - default = replace_keys(default) - for key in default: - console.print(f"Enter the style for {key}:", style=key, end="") - temp = input() - if temp: - while not is_valid_style(temp): - console.print("[red bold]Invalid style. Try again.[/red bold]") - console.print(f"Enter the style for {key}:", style=key, end="") - temp = input() - else: - default[key] = temp - default = replace_keys(default) + USER_CONFIG_MSG = f"""A configuration file at [yellow]{config_paths[1]}[/yellow] has been created with your required changes. +This will be used when running the manim command. If you want to override this config, +you will have to create a manim.cfg in the local directory, where you want those changes to be overridden.""" - else: - for key in default: - if default[key] in ["True","False"]: - console.print( - f"Enter value for {key} (defaults to {default[key]}):", end="") + CWD_CONFIG_MSG = f"""A configuration file at [yellow]{config_paths[2]}[/yellow] has been created. +To save your theme please save that file and place it in your current working directory, from where you run the manim command.""" + + if not openfile: + action = "save this as" + + for category in config: + console.print(f"{category}",style="bold green underline") + default = config[category] + if category == "logger": + console.print(RICH_COLOUR_INSTRUCTIONS) + default = replace_keys(default) + for key in default: + console.print(f"Enter the style for {key}:", style=key, end="") temp = input() if temp: - while not temp.lower().capitalize() in ["True","False"]: - console.print( - "[red bold]Invalid value. Try again.[/red bold]") - console.print( - f"Enter the style for {key}:", style=key, end="") + while not is_valid_style(temp): + console.print("[red bold]Invalid style. Try again.[/red bold]") + console.print(f"Enter the style for {key}:", style=key, end="") temp = input() else: default[key] = temp - config[category] = dict(default) + default = replace_keys(default) + + else: + for key in default: + if default[key] in ["True","False"]: + console.print( + f"Enter value for {key} (defaults to {default[key]}):", end="") + temp = input() + if temp: + while not temp.lower().capitalize() in ["True","False"]: + console.print( + "[red bold]Invalid value. Try again.[/red bold]") + console.print( + f"Enter the style for {key}:", style=key, end="") + temp = input() + else: + default[key] = temp + config[category] = dict(default) + else: + action = "open" if level is None: console.print( - "Do you want to save this as the default for this User?(y/n)[[n]]", + f"Do you want to {action} the default config for this User?(y/n)[[n]]", style="dim purple", end="", ) - save_to_userpath = input() + action_to_userpath = input() else: - save_to_userpath = "" - - config_paths = _paths_config_file() + [os.path.abspath("manim.cfg")] - if save_to_userpath.lower() == "y" or level=="user": - if not os.path.exists(os.path.abspath(os.path.join(config_paths[1], os.pardir))): - os.makedirs(os.path.abspath(os.path.join(config_paths[1], ".."))) - with open(config_paths[1], "w") as fp: - config.write(fp) - console.print( - f"""A configuration file at [yellow]{config_paths[1]}[/yellow] has been created with your required changes. -This will be used when running the manim command. If you want to override this config, -you will have to create a manim.cfg in the local directory, where you want those changes to be overridden.""" - ) + action_to_userpath = "" + + if action_to_userpath.lower() == "y" or level=="user": + cfg_file_path = os.path.join( + guarantee_existence( + os.path.dirname(config_paths[1]) + ),"manim.cfg") + console.print(USER_CONFIG_MSG) else: - with open(config_paths[2], "w") as fp: - config.write(fp) - console.print( - f"""A configuration file at [yellow]{config_paths[2]}[/yellow] has been created. -To save your theme please save that file and place it in your current working directory, from where you run the manim command.""" - ) + cfg_file_path = os.path.join( + guarantee_existence( + os.path.dirname(config_paths[2]) + ),"manim.cfg") + console.print(CWD_CONFIG_MSG) + with open(cfg_file_path, "w") as fp: + config.write(fp) + if openfile: + open_file(cfg_file_path) def show(): current_config = finalized_configs_dict() diff --git a/manim/utils/config_utils.py b/manim/utils/config_utils.py index 03e9462979..9d8d4fcbae 100644 --- a/manim/utils/config_utils.py +++ b/manim/utils/config_utils.py @@ -148,6 +148,12 @@ def _parse_cli(arg_list, input=True): default=None, help="Specify if this config is for user or just the working directory." ) + cfg_write_parser.add_argument( + "--open", + action="store_const", + const=True, + default = False + ) cfg_subparsers.add_parser('show') cfg_export_parser = cfg_subparsers.add_parser("export") From 8d4ed877dbc5f22445bcaa142b3863349236bc82 Mon Sep 17 00:00:00 2001 From: Aathish Sivasubrahmanian Date: Tue, 28 Jul 2020 10:52:15 +0530 Subject: [PATCH 31/39] Stop changing cwd from mulitple files Change cwd and change back in test_cfg_subcmd itself. --- tests/test_cli/test_cfg_subcmd.py | 10 +++++++++- tests/test_cli/test_cli.py | 1 - 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/test_cli/test_cfg_subcmd.py b/tests/test_cli/test_cfg_subcmd.py index 27346db489..2d88f4b0dc 100644 --- a/tests/test_cli/test_cfg_subcmd.py +++ b/tests/test_cli/test_cfg_subcmd.py @@ -30,15 +30,23 @@ def test_cfg_export(python_version): def test_cfg_write(python_version): """Simulate using the command `manim cfg write`""" + cfgfilepath = os.path.join(os.path.dirname(__file__), "manim.cfg") command = [python_version, "-m", "manim","cfg","write","--level","cwd"] """As the number of config values that `manim cfg write` can modify increases, so must the number of newlines and/or values written in write_cfg_sbcmd_input increase.""" + with open(cfgfilepath) as cfgfile: + original = cfgfile.read() out, err, exitcode = capture( command, open(os.path.join(os.path.dirname(__file__), "write_cfg_sbcmd_input.txt")) ) assert exitcode == 0, err - with open(os.path.join(os.path.dirname(__file__), "manim.cfg")) as cfgfile: + + with open(cfgfilepath,"r") as cfgfile: assert "sound = False" in cfgfile.read() + + with open(cfgfilepath,"w") as cfgfile: + cfgfile.write(original) + os.chdir(os.path.abspath(os.path.join(__file__, os.pardir, os.pardir, os.pardir))) \ No newline at end of file diff --git a/tests/test_cli/test_cli.py b/tests/test_cli/test_cli.py index ab0b7b06c0..0da7fa9bc4 100644 --- a/tests/test_cli/test_cli.py +++ b/tests/test_cli/test_cli.py @@ -15,7 +15,6 @@ def capture(command,instream=None): def test_help(python_version): - os.chdir(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) command = [python_version, "-m", "manim", "--help"] out, err, exitcode = capture(command) assert exitcode == 0, f"Manim has been installed incorrectly. Please refer to the troubleshooting section on the wiki. Error:\n{err.decode()}" From 9018eaa02043342f3daf47ecb52fadecce06e59c Mon Sep 17 00:00:00 2001 From: Aathish Date: Tue, 28 Jul 2020 22:53:44 +0530 Subject: [PATCH 32/39] Change directory in subprocess instead of in test. --- tests/test_cli/test_cfg_subcmd.py | 28 ++++++++++++++-------------- tests/test_cli/test_cli.py | 7 ++++--- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/tests/test_cli/test_cfg_subcmd.py b/tests/test_cli/test_cfg_subcmd.py index 2d88f4b0dc..5c2b0672aa 100644 --- a/tests/test_cli/test_cfg_subcmd.py +++ b/tests/test_cli/test_cfg_subcmd.py @@ -3,35 +3,35 @@ import shutil from test_cli import capture +this_folder = os.path.dirname(__file__) def test_cfg_help(python_version): """Test if Manim successfully adds configparsers when a subcommand is invoked.""" - os.chdir(os.path.dirname(__file__)) - command = [python_version, "-m", "manim", "cfg", "--help"] - out, err, exitcode = capture(command) + command = f"cd {this_folder} && {python_version} -m manim cfg --help" + out, err, exitcode = capture(command, use_shell=True) assert exitcode == 0, f"The cfg subcommand is not working as intended." def test_cfg_show(python_version): """Test if the `manim cfg show` command works as intended.""" - command = [python_version, "-m", "manim", "cfg", "show"] - out, err, exitcode = capture(command) + command = f"cd {this_folder} && {python_version} -m manim cfg show" + out, err, exitcode = capture(command, use_shell=True) assert exitcode == 0 assert f"{os.path.sep}tests{os.path.sep}".encode("utf-8") in out, err def test_cfg_export(python_version): """Test if the `manim cfg export` command works as intended.""" - command = [python_version, "-m", "manim", "cfg", "export", "--dir", "temp"] - out, err, exitcode = capture(command) + command = f"cd {this_folder} && {python_version} -m manim cfg export --dir temp" + out, err, exitcode = capture(command, use_shell=True) assert exitcode == 0 - assert os.path.exists(os.path.join("temp","manim.cfg")) - with open(os.path.join("temp","manim.cfg"),"r") as writtencfg: + assert os.path.exists(os.path.join(this_folder,"temp","manim.cfg")) + with open(os.path.join(this_folder,"temp","manim.cfg"),"r") as writtencfg: assert "sound = True" in writtencfg.read(), err - shutil.rmtree("temp") + shutil.rmtree(os.path.join(this_folder,"temp")) def test_cfg_write(python_version): """Simulate using the command `manim cfg write`""" - cfgfilepath = os.path.join(os.path.dirname(__file__), "manim.cfg") - command = [python_version, "-m", "manim","cfg","write","--level","cwd"] + cfgfilepath = os.path.join(this_folder, "manim.cfg") + command = f"cd {this_folder} && {python_version} -m manim cfg write --level cwd" """As the number of config values that `manim cfg write` can modify increases, so must the number of newlines and/or values written in write_cfg_sbcmd_input increase.""" @@ -40,7 +40,8 @@ def test_cfg_write(python_version): out, err, exitcode = capture( command, - open(os.path.join(os.path.dirname(__file__), "write_cfg_sbcmd_input.txt")) + instream=open(os.path.join(this_folder, "write_cfg_sbcmd_input.txt")), + use_shell=True ) assert exitcode == 0, err @@ -49,4 +50,3 @@ def test_cfg_write(python_version): with open(cfgfilepath,"w") as cfgfile: cfgfile.write(original) - os.chdir(os.path.abspath(os.path.join(__file__, os.pardir, os.pardir, os.pardir))) \ No newline at end of file diff --git a/tests/test_cli/test_cli.py b/tests/test_cli/test_cli.py index 0da7fa9bc4..11a7e7c634 100644 --- a/tests/test_cli/test_cli.py +++ b/tests/test_cli/test_cli.py @@ -4,11 +4,12 @@ import pytest -def capture(command,instream=None): +def capture(command,instream=None, use_shell=False): proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - stdin=instream + stdin=instream, + shell = use_shell ) out, err = proc.communicate() return out, err, proc.returncode @@ -49,7 +50,7 @@ def test_dash_as_name(python_version): command = [python_version, "-m", "manim", "-", "-l", "--media_dir", path_output] out, err, exitcode = capture( command, - open(os.path.join(os.path.dirname(__file__), "dash_test_script.txt")) + instream = open(os.path.join(os.path.dirname(__file__), "dash_test_script.txt")) ) assert exitcode == 0, err assert os.path.exists(os.path.join( From 2244fa9b12c52f8d538e9dd5dc2127692045eb02 Mon Sep 17 00:00:00 2001 From: Aathish Date: Wed, 29 Jul 2020 09:56:07 +0530 Subject: [PATCH 33/39] Pass over full error message on error. Thank you @huguesdevimeux ! Co-authored-by: Hugues Devimeux <36239975+huguesdevimeux@users.noreply.github.com> --- tests/test_cli/test_cfg_subcmd.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_cli/test_cfg_subcmd.py b/tests/test_cli/test_cfg_subcmd.py index 5c2b0672aa..fd8e15b316 100644 --- a/tests/test_cli/test_cfg_subcmd.py +++ b/tests/test_cli/test_cfg_subcmd.py @@ -9,7 +9,7 @@ def test_cfg_help(python_version): """Test if Manim successfully adds configparsers when a subcommand is invoked.""" command = f"cd {this_folder} && {python_version} -m manim cfg --help" out, err, exitcode = capture(command, use_shell=True) - assert exitcode == 0, f"The cfg subcommand is not working as intended." + assert exitcode == 0, f"The cfg subcommand help is not working as intended.\nError : {err}" def test_cfg_show(python_version): """Test if the `manim cfg show` command works as intended.""" @@ -22,7 +22,7 @@ def test_cfg_export(python_version): """Test if the `manim cfg export` command works as intended.""" command = f"cd {this_folder} && {python_version} -m manim cfg export --dir temp" out, err, exitcode = capture(command, use_shell=True) - assert exitcode == 0 + assert exitcode == 0, f"The cfg subcommand export is not working as intended.\nError : {err}" assert os.path.exists(os.path.join(this_folder,"temp","manim.cfg")) with open(os.path.join(this_folder,"temp","manim.cfg"),"r") as writtencfg: assert "sound = True" in writtencfg.read(), err @@ -43,7 +43,8 @@ def test_cfg_write(python_version): instream=open(os.path.join(this_folder, "write_cfg_sbcmd_input.txt")), use_shell=True ) - assert exitcode == 0, err + assert exitcode == 0, f"The cfg subcommand write is not working as intended.\nError : {err}" +` with open(cfgfilepath,"r") as cfgfile: assert "sound = False" in cfgfile.read() From 0297f8444942b2d5e3ab7dfeb974abbe3d358c43 Mon Sep 17 00:00:00 2001 From: Aathish Date: Wed, 29 Jul 2020 09:59:25 +0530 Subject: [PATCH 34/39] Raised an exception if OS is unknown. Changed a misleading exit message. As suggested by @huguesdevimeux :) --- manim/utils/cfg_subcmds.py | 2 +- manim/utils/file_ops.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/manim/utils/cfg_subcmds.py b/manim/utils/cfg_subcmds.py index 08a0446ca5..cf042d6fd5 100644 --- a/manim/utils/cfg_subcmds.py +++ b/manim/utils/cfg_subcmds.py @@ -184,4 +184,4 @@ def export(path): to_path = os.path.join(path,'manim.cfg') console.print(f"Exported final Config at {from_path} to {to_path}.") else: - console.print("Could NOT write config.", style="red bold") + console.print("Aborted...", style="red bold") diff --git a/manim/utils/file_ops.py b/manim/utils/file_ops.py index 76033d49fa..008cf7686d 100644 --- a/manim/utils/file_ops.py +++ b/manim/utils/file_ops.py @@ -65,12 +65,13 @@ def open_file(file_path): if current_os == "Windows": os.startfile(file_path) else: - commands = [] if current_os == "Linux": - commands.append("xdg-open") + commands = ["xdg-open"] elif current_os.startswith("CYGWIN"): - commands.append("cygstart") - else: # Assume macOS - commands.append("open") + commands = ["cygstart"] + elif current_os == "Darwin": + commands = ["open"] + else: + raise OSError("Unable to identify your operating system...") commands.append(file_path) sp.Popen(commands) \ No newline at end of file From bc38eb9193a6ada949864ec6aa86332cab3475a9 Mon Sep 17 00:00:00 2001 From: Aathish Date: Wed, 29 Jul 2020 10:04:04 +0530 Subject: [PATCH 35/39] Remove a stray ` --- tests/test_cli/test_cfg_subcmd.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_cli/test_cfg_subcmd.py b/tests/test_cli/test_cfg_subcmd.py index fd8e15b316..b276ce5c08 100644 --- a/tests/test_cli/test_cfg_subcmd.py +++ b/tests/test_cli/test_cfg_subcmd.py @@ -44,7 +44,6 @@ def test_cfg_write(python_version): use_shell=True ) assert exitcode == 0, f"The cfg subcommand write is not working as intended.\nError : {err}" -` with open(cfgfilepath,"r") as cfgfile: assert "sound = False" in cfgfile.read() From b40bbe28c60360ef240e3e8580609a6f485b9a74 Mon Sep 17 00:00:00 2001 From: Aathish Date: Wed, 29 Jul 2020 10:35:11 +0530 Subject: [PATCH 36/39] Change square brackets to parentheses. Thank you @huguesdevimeux ! Co-authored-by: Hugues Devimeux <36239975+huguesdevimeux@users.noreply.github.com> --- manim/utils/cfg_subcmds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manim/utils/cfg_subcmds.py b/manim/utils/cfg_subcmds.py index cf042d6fd5..2992956750 100644 --- a/manim/utils/cfg_subcmds.py +++ b/manim/utils/cfg_subcmds.py @@ -168,7 +168,7 @@ def export(path): console.print( """You are reading the config from the same directory you are exporting to. This means that the exported config will overwrite the config for this directory. -Are you sure you want to continue?[y/n]""", +Are you sure you want to continue? (y/n)""", style="red bold", end="" ) proceed = True if input().lower()=="y" else False From 396f2fa1f3a2c8d7c471d72fbc47efb9f0be8ab8 Mon Sep 17 00:00:00 2001 From: Aathish Date: Wed, 29 Jul 2020 10:38:09 +0530 Subject: [PATCH 37/39] Change manim.cfg to original using pytest fixtures. As suggested by @huguesdevimeux --- tests/conftest.py | 9 +++++++++ tests/test_cli/test_cfg_subcmd.py | 7 +------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c07ad705c1..6d309522bb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -30,3 +30,12 @@ def pytest_collection_modifyitems(config, items): @pytest.fixture(scope="module") def python_version(): return "python3" if sys.platform == "darwin" else "python" + +@pytest.fixture +def reset_config(): + cfgfilepath = os.path.join(os.path.dirname(__file__),"test_cli", "manim.cfg") + with open(cfgfilepath) as cfgfile: + original = cfgfile.read() + yield + with open(cfgfilepath,"w") as cfgfile: + cfgfile.write(original) \ No newline at end of file diff --git a/tests/test_cli/test_cfg_subcmd.py b/tests/test_cli/test_cfg_subcmd.py index b276ce5c08..68a523265c 100644 --- a/tests/test_cli/test_cfg_subcmd.py +++ b/tests/test_cli/test_cfg_subcmd.py @@ -28,6 +28,7 @@ def test_cfg_export(python_version): assert "sound = True" in writtencfg.read(), err shutil.rmtree(os.path.join(this_folder,"temp")) +@pytest.mark.usefixtures("reset_config") def test_cfg_write(python_version): """Simulate using the command `manim cfg write`""" cfgfilepath = os.path.join(this_folder, "manim.cfg") @@ -35,9 +36,6 @@ def test_cfg_write(python_version): """As the number of config values that `manim cfg write` can modify increases, so must the number of newlines and/or values written in write_cfg_sbcmd_input increase.""" - with open(cfgfilepath) as cfgfile: - original = cfgfile.read() - out, err, exitcode = capture( command, instream=open(os.path.join(this_folder, "write_cfg_sbcmd_input.txt")), @@ -47,6 +45,3 @@ def test_cfg_write(python_version): with open(cfgfilepath,"r") as cfgfile: assert "sound = False" in cfgfile.read() - - with open(cfgfilepath,"w") as cfgfile: - cfgfile.write(original) From dc948279281fa4fb0d3685b43dd6a6c45c235b38 Mon Sep 17 00:00:00 2001 From: Aathish Date: Wed, 29 Jul 2020 10:49:13 +0530 Subject: [PATCH 38/39] Rename `reset_config` to `reset_cfg_file` for clarity. --- tests/conftest.py | 2 +- tests/test_cli/test_cfg_subcmd.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 6d309522bb..19f2c9640a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -32,7 +32,7 @@ def python_version(): return "python3" if sys.platform == "darwin" else "python" @pytest.fixture -def reset_config(): +def reset_cfg_file(): cfgfilepath = os.path.join(os.path.dirname(__file__),"test_cli", "manim.cfg") with open(cfgfilepath) as cfgfile: original = cfgfile.read() diff --git a/tests/test_cli/test_cfg_subcmd.py b/tests/test_cli/test_cfg_subcmd.py index 68a523265c..21dfcd51d8 100644 --- a/tests/test_cli/test_cfg_subcmd.py +++ b/tests/test_cli/test_cfg_subcmd.py @@ -28,7 +28,7 @@ def test_cfg_export(python_version): assert "sound = True" in writtencfg.read(), err shutil.rmtree(os.path.join(this_folder,"temp")) -@pytest.mark.usefixtures("reset_config") +@pytest.mark.usefixtures("reset_cfg_file") def test_cfg_write(python_version): """Simulate using the command `manim cfg write`""" cfgfilepath = os.path.join(this_folder, "manim.cfg") From da73e6074cd92cbbf0daef2283137fdfb936d41e Mon Sep 17 00:00:00 2001 From: Aathish Date: Fri, 31 Jul 2020 19:32:32 +0530 Subject: [PATCH 39/39] Apply Black's formatting. --- docs/source/conf.py | 17 +++-- logo/logo.py | 112 +++++++++++++++++------------- manim/__main__.py | 9 ++- manim/config.py | 6 +- manim/utils/cfg_subcmds.py | 70 +++++++++++-------- manim/utils/config_utils.py | 81 ++++++++++++--------- manim/utils/file_ops.py | 1 + scripts/pycairoinstall.py | 52 +++++++++----- setup.py | 7 +- tests/conftest.py | 7 +- tests/test_cli/test_cfg_subcmd.py | 29 +++++--- tests/test_cli/test_cli.py | 19 ++--- 12 files changed, 241 insertions(+), 169 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 8156771675..ec8e26948e 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -17,22 +17,21 @@ # -- Project information ----------------------------------------------------- -project = 'Manim' -copyright = '2019, EulerTour' -author = 'EulerTour' +project = "Manim" +copyright = "2019, EulerTour" +author = "EulerTour" # -- General configuration --------------------------------------------------- -master_doc = 'index' +master_doc = "index" # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [ -] +extensions = [] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -45,9 +44,9 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['assets'] +html_static_path = ["assets"] diff --git a/logo/logo.py b/logo/logo.py index a4b485fc47..4ff3e8c68e 100644 --- a/logo/logo.py +++ b/logo/logo.py @@ -2,6 +2,7 @@ NEW_BLUE = "#68a8e1" + class Thumbnail(GraphScene): CONFIG = { "y_max": 8, @@ -13,13 +14,15 @@ def construct(self): def show_function_graph(self): self.setup_axes(animate=False) + def func(x): - return 0.1 * (x + 3-5) * (x - 3-5) * (x-5) + 5 + return 0.1 * (x + 3 - 5) * (x - 3 - 5) * (x - 5) + 5 def rect(x): - return 2.775*(x-1.5)+3.862 - recta = self.get_graph(rect,x_min=-1,x_max=5) - graph = self.get_graph(func,x_min=0.2,x_max=9) + return 2.775 * (x - 1.5) + 3.862 + + recta = self.get_graph(rect, x_min=-1, x_max=5) + graph = self.get_graph(func, x_min=0.2, x_max=9) graph.set_color(NEW_BLUE) input_tracker_p1 = ValueTracker(1.5) input_tracker_p2 = ValueTracker(3.5) @@ -37,29 +40,40 @@ def get_y_point(input_tracker): return self.coords_to_point(0, get_y_value(input_tracker)) def get_graph_point(input_tracker): - return self.coords_to_point(get_x_value(input_tracker), get_y_value(input_tracker)) + return self.coords_to_point( + get_x_value(input_tracker), get_y_value(input_tracker) + ) def get_v_line(input_tracker): - return DashedLine(get_x_point(input_tracker), get_graph_point(input_tracker), stroke_width=2) + return DashedLine( + get_x_point(input_tracker), + get_graph_point(input_tracker), + stroke_width=2, + ) def get_h_line(input_tracker): - return DashedLine(get_graph_point(input_tracker), get_y_point(input_tracker), stroke_width=2) - # + return DashedLine( + get_graph_point(input_tracker), + get_y_point(input_tracker), + stroke_width=2, + ) + + # input_triangle_p1 = RegularPolygon(n=3, start_angle=TAU / 4) output_triangle_p1 = RegularPolygon(n=3, start_angle=0) for triangle in input_triangle_p1, output_triangle_p1: triangle.set_fill(WHITE, 1) triangle.set_stroke(width=0) triangle.scale(0.1) - # + # input_triangle_p2 = RegularPolygon(n=3, start_angle=TAU / 4) output_triangle_p2 = RegularPolygon(n=3, start_angle=0) for triangle in input_triangle_p2, output_triangle_p2: triangle.set_fill(WHITE, 1) triangle.set_stroke(width=0) triangle.scale(0.1) - - # + + # x_label_p1 = TexMobject("a") output_label_p1 = TexMobject("f(a)") x_label_p2 = TexMobject("b") @@ -83,11 +97,8 @@ def get_h_line(input_tracker): graph_dot_p1.move_to(get_graph_point(input_tracker_p1)) graph_dot_p2.move_to(get_graph_point(input_tracker_p2)) - # - self.play( - ShowCreation(graph), - ) + self.play(ShowCreation(graph),) # Animacion del punto a self.add_foreground_mobject(graph_dot_p1) self.add_foreground_mobject(graph_dot_p2) @@ -106,7 +117,7 @@ def get_h_line(input_tracker): ShowCreation(h_line_p2), Write(output_label_p2), DrawBorderThenFill(output_triangle_p2), - run_time=0.5 + run_time=0.5, ) self.add( input_triangle_p2, @@ -119,58 +130,61 @@ def get_h_line(input_tracker): ) ################### pendiente_recta = self.get_secant_slope_group( - 1.9, recta, dx = 1.4, - df_label = None, - dx_label = None, - dx_line_color = PURPLE, - df_line_color= ORANGE, - ) + 1.9, + recta, + dx=1.4, + df_label=None, + dx_label=None, + dx_line_color=PURPLE, + df_line_color=ORANGE, + ) grupo_secante = self.get_secant_slope_group( - 1.5, graph, dx = 2, - df_label = None, - dx_label = None, - dx_line_color = "#942357", - df_line_color= "#3f7d5c", - secant_line_color = RED, + 1.5, + graph, + dx=2, + df_label=None, + dx_label=None, + dx_line_color="#942357", + df_line_color="#3f7d5c", + secant_line_color=RED, ) - self.add( - input_triangle_p2, - graph_dot_p2, - v_line_p2, - h_line_p2, - output_triangle_p2, + input_triangle_p2, graph_dot_p2, v_line_p2, h_line_p2, output_triangle_p2, ) self.play(FadeIn(grupo_secante)) kwargs = { - "x_min" : 4, - "x_max" : 9, - "fill_opacity" : 0.75, - "stroke_width" : 0.25, + "x_min": 4, + "x_max": 9, + "fill_opacity": 0.75, + "stroke_width": 0.25, } - self.graph=graph - iteraciones=6 - + self.graph = graph + iteraciones = 6 self.rect_list = self.get_riemann_rectangles_list( - graph, iteraciones,start_color=PURPLE,end_color=ORANGE, **kwargs + graph, iteraciones, start_color=PURPLE, end_color=ORANGE, **kwargs ) flat_rects = self.get_riemann_rectangles( - self.get_graph(lambda x : 0), dx = 0.5,start_color=invert_color(PURPLE),end_color=invert_color(ORANGE),**kwargs + self.get_graph(lambda x: 0), + dx=0.5, + start_color=invert_color(PURPLE), + end_color=invert_color(ORANGE), + **kwargs ) rects = self.rect_list[0] self.transform_between_riemann_rects( - flat_rects, rects, - replace_mobject_with_target_in_scene = True, - run_time=0.9 + flat_rects, rects, replace_mobject_with_target_in_scene=True, run_time=0.9 ) # adding manim picture = Group(*self.mobjects) picture.scale(0.6).to_edge(LEFT, buff=SMALL_BUFF) - manim = TextMobject("Manim").set_height(1.5) \ - .next_to(picture, RIGHT) \ - .shift(DOWN * 0.7) + manim = ( + TextMobject("Manim") + .set_height(1.5) + .next_to(picture, RIGHT) + .shift(DOWN * 0.7) + ) self.add(manim) diff --git a/manim/__main__.py b/manim/__main__.py index 9ca010ae0c..f91b0a9996 100644 --- a/manim/__main__.py +++ b/manim/__main__.py @@ -8,7 +8,7 @@ import importlib.util import types -from .config import file_writer_config,args +from .config import file_writer_config, args from .utils import cfg_subcmds from .scene.scene import Scene from .utils.sounds import play_error_sound @@ -157,12 +157,12 @@ def get_module(file_name): def main(): - if hasattr(args,"subcommands"): + if hasattr(args, "subcommands"): if "cfg" in args.subcommands: if args.cfg_subcommand is not None: - subcommand=args.cfg_subcommand + subcommand = args.cfg_subcommand if subcommand == "write": - cfg_subcmds.write(args.level,args.open) + cfg_subcmds.write(args.level, args.open) elif subcommand == "show": cfg_subcmds.show() elif subcommand == "export": @@ -170,7 +170,6 @@ def main(): else: logger.error("No argument provided; Exiting...") - else: module = get_module(file_writer_config["input_file"]) all_scene_classes = get_scene_classes_from_module(module) diff --git a/manim/config.py b/manim/config.py index 1fcca6be1f..f6ab11112c 100644 --- a/manim/config.py +++ b/manim/config.py @@ -87,8 +87,10 @@ def _parse_config(config_parser, args): args, config_parser, file_writer_config, successfully_read_files = _run_config() if _from_command_line(): - logger.info(f"Read configuration files: {[os.path.abspath(cfgfile) for cfgfile in successfully_read_files]}") - if not(hasattr(args,"subcommands")): + logger.info( + f"Read configuration files: {[os.path.abspath(cfgfile) for cfgfile in successfully_read_files]}" + ) + if not (hasattr(args, "subcommands")): _init_dirs(file_writer_config) config = _parse_config(config_parser, args) camera_config = config diff --git a/manim/utils/cfg_subcmds.py b/manim/utils/cfg_subcmds.py index 2992956750..2b73cbc94e 100644 --- a/manim/utils/cfg_subcmds.py +++ b/manim/utils/cfg_subcmds.py @@ -17,7 +17,7 @@ from rich.style import Style from rich.errors import StyleSyntaxError -__all__ = ["write","show","export"] +__all__ = ["write", "show", "export"] RICH_COLOUR_INSTRUCTIONS = """[red]The default colour is used by the input statement. If left empty, the default colour will be used.[/red] @@ -25,6 +25,7 @@ console = Console() + def is_valid_style(style): """Checks whether the entered color is a valid color according to rich Parameters @@ -71,7 +72,9 @@ def replace_keys(default): def write(level=None, openfile=False): config = _run_config()[1] config_paths = _paths_config_file() + [os.path.abspath("manim.cfg")] - console.print("[yellow bold]Manim Configuration File Writer[/yellow bold]", justify="center") + console.print( + "[yellow bold]Manim Configuration File Writer[/yellow bold]", justify="center" + ) USER_CONFIG_MSG = f"""A configuration file at [yellow]{config_paths[1]}[/yellow] has been created with your required changes. This will be used when running the manim command. If you want to override this config, @@ -84,7 +87,7 @@ def write(level=None, openfile=False): action = "save this as" for category in config: - console.print(f"{category}",style="bold green underline") + console.print(f"{category}", style="bold green underline") default = config[category] if category == "logger": console.print(RICH_COLOUR_INSTRUCTIONS) @@ -94,8 +97,12 @@ def write(level=None, openfile=False): temp = input() if temp: while not is_valid_style(temp): - console.print("[red bold]Invalid style. Try again.[/red bold]") - console.print(f"Enter the style for {key}:", style=key, end="") + console.print( + "[red bold]Invalid style. Try again.[/red bold]" + ) + console.print( + f"Enter the style for {key}:", style=key, end="" + ) temp = input() else: default[key] = temp @@ -103,16 +110,20 @@ def write(level=None, openfile=False): else: for key in default: - if default[key] in ["True","False"]: + if default[key] in ["True", "False"]: console.print( - f"Enter value for {key} (defaults to {default[key]}):", end="") + f"Enter value for {key} (defaults to {default[key]}):", + end="", + ) temp = input() if temp: - while not temp.lower().capitalize() in ["True","False"]: + while not temp.lower().capitalize() in ["True", "False"]: console.print( - "[red bold]Invalid value. Try again.[/red bold]") + "[red bold]Invalid value. Try again.[/red bold]" + ) console.print( - f"Enter the style for {key}:", style=key, end="") + f"Enter the style for {key}:", style=key, end="" + ) temp = input() else: default[key] = temp @@ -130,38 +141,38 @@ def write(level=None, openfile=False): else: action_to_userpath = "" - if action_to_userpath.lower() == "y" or level=="user": + if action_to_userpath.lower() == "y" or level == "user": cfg_file_path = os.path.join( - guarantee_existence( - os.path.dirname(config_paths[1]) - ),"manim.cfg") + guarantee_existence(os.path.dirname(config_paths[1])), "manim.cfg" + ) console.print(USER_CONFIG_MSG) else: cfg_file_path = os.path.join( - guarantee_existence( - os.path.dirname(config_paths[2]) - ),"manim.cfg") + guarantee_existence(os.path.dirname(config_paths[2])), "manim.cfg" + ) console.print(CWD_CONFIG_MSG) with open(cfg_file_path, "w") as fp: config.write(fp) if openfile: open_file(cfg_file_path) + def show(): current_config = finalized_configs_dict() for category in current_config: - console.print(f"{category}",style="bold green underline") + console.print(f"{category}", style="bold green underline") for entry in current_config[category]: - if category=="logger": - console.print(f"{entry} :",end="") + if category == "logger": + console.print(f"{entry} :", end="") console.print( f" {current_config[category][entry]}", - style=current_config[category][entry] - ) + style=current_config[category][entry], + ) else: console.print(f"{entry} : {current_config[category][entry]}") console.print("\n") + def export(path): config = _run_config()[1] if os.path.abspath(path) == os.path.abspath(os.getcwd()): @@ -169,19 +180,20 @@ def export(path): """You are reading the config from the same directory you are exporting to. This means that the exported config will overwrite the config for this directory. Are you sure you want to continue? (y/n)""", - style="red bold", end="" - ) - proceed = True if input().lower()=="y" else False + style="red bold", + end="", + ) + proceed = True if input().lower() == "y" else False else: proceed = True if proceed: if not os.path.isdir(path): - console.print(f"Creating folder: {path}.",style="red bold") + console.print(f"Creating folder: {path}.", style="red bold") os.mkdir(path) - with open(os.path.join(path,"manim.cfg"),"w") as outpath: + with open(os.path.join(path, "manim.cfg"), "w") as outpath: config.write(outpath) - from_path = os.path.join(os.getcwd(),'manim.cfg') - to_path = os.path.join(path,'manim.cfg') + from_path = os.path.join(os.getcwd(), "manim.cfg") + to_path = os.path.join(path, "manim.cfg") console.print(f"Exported final Config at {from_path} to {to_path}.") else: console.print("Aborted...", style="red bold") diff --git a/manim/utils/config_utils.py b/manim/utils/config_utils.py index 119e1c2771..4d310edf69 100644 --- a/manim/utils/config_utils.py +++ b/manim/utils/config_utils.py @@ -16,9 +16,16 @@ from .. import constants from .tex import TexTemplate, TexTemplateFromFile -__all__ = ["_run_config", "_paths_config_file", "_from_command_line", "finalized_configs_dict"] +__all__ = [ + "_run_config", + "_paths_config_file", + "_from_command_line", + "finalized_configs_dict", +] min_argvs = 3 if "-m" in sys.argv[0] else 2 + + def _parse_file_writer_config(config_parser, args): """Parse config files and CLI arguments into a single dictionary.""" # By default, use the CLI section of the digested .cfg files @@ -31,9 +38,11 @@ def _parse_file_writer_config(config_parser, args): # the .cfg files, only from CLI arguments. # If a subcommand is given, manim will not render a video and # thus these specific input/output files are not needed. - if not(hasattr(args,"subcommands")): + if not (hasattr(args, "subcommands")): fw_config["input_file"] = args.file - fw_config["scene_names"] = args.scene_names if args.scene_names is not None else [] + fw_config["scene_names"] = ( + args.scene_names if args.scene_names is not None else [] + ) fw_config["output_file"] = args.output_file # Handle all options that are directly overridden by CLI @@ -136,31 +145,29 @@ def _parse_cli(arg_list, input=True): if input: # If the only command is `manim`, we want both subcommands like `cfg` # and mandatory positional arguments like `file` to show up in the help section. - if len(sys.argv) == min_argvs-1 or _subcommands_exist(): + if len(sys.argv) == min_argvs - 1 or _subcommands_exist(): subparsers = parser.add_subparsers(dest="subcommands") - cfg_related = subparsers.add_parser('cfg') + cfg_related = subparsers.add_parser("cfg") cfg_subparsers = cfg_related.add_subparsers(dest="cfg_subcommand") - cfg_write_parser = cfg_subparsers.add_parser('write') + cfg_write_parser = cfg_subparsers.add_parser("write") cfg_write_parser.add_argument( "--level", choices=["user", "cwd"], default=None, - help="Specify if this config is for user or just the working directory." - ) + help="Specify if this config is for user or just the working directory.", + ) cfg_write_parser.add_argument( - "--open", - action="store_const", - const=True, - default = False + "--open", action="store_const", const=True, default=False ) - cfg_subparsers.add_parser('show') + cfg_subparsers.add_parser("show") cfg_export_parser = cfg_subparsers.add_parser("export") - cfg_export_parser.add_argument("--dir",default=os.getcwd()) + cfg_export_parser.add_argument("--dir", default=os.getcwd()) - if (len(sys.argv) == min_argvs-1 or - not _subcommands_exist(ignore = ["--help","-h"])): + if len(sys.argv) == min_argvs - 1 or not _subcommands_exist( + ignore=["--help", "-h"] + ): parser.add_argument( "file", help="path to file holding the python code for the scene", ) @@ -343,12 +350,15 @@ def _parse_cli(arg_list, input=True): parser.add_argument( "--config_file", help="Specify the configuration file", ) - parsed=parser.parse_args(arg_list) - if hasattr(parsed,"subcommands"): - setattr(parsed, "cfg_subcommand", + parsed = parser.parse_args(arg_list) + if hasattr(parsed, "subcommands"): + setattr( + parsed, + "cfg_subcommand", cfg_related.parse_args( - sys.argv[min_argvs -(0 if min_argvs == 2 else 1):] - ).cfg_subcommand) + sys.argv[min_argvs - (0 if min_argvs == 2 else 1) :] + ).cfg_subcommand, + ) return parsed @@ -364,6 +374,7 @@ def _init_dirs(config): if not os.path.exists(folder): os.makedirs(folder) + def _from_command_line(): """Determine if manim was called from the command line.""" # Manim can be called from the command line in three different @@ -379,11 +390,13 @@ def _from_command_line(): return from_cli_command or from_python_m + def _from_dunder_main(): dunder_main_path = os.path.join( - os.path.dirname(os.path.dirname(__file__)), - "__main__.py") - return sys.argv[0]==dunder_main_path + os.path.dirname(os.path.dirname(__file__)), "__main__.py" + ) + return sys.argv[0] == dunder_main_path + def _paths_config_file(): library_wide = os.path.abspath( @@ -411,14 +424,16 @@ def _paths_config_file(): def _run_config(): # Config files to be parsed, in ascending priority config_files = _paths_config_file() - if _from_command_line() or _from_dunder_main(): + if _from_command_line() or _from_dunder_main(): args = _parse_cli(sys.argv[1:]) - if not hasattr(args,"subcommands"): + if not hasattr(args, "subcommands"): if args.config_file is not None: if os.path.exists(args.config_file): config_files.append(args.config_file) else: - raise FileNotFoundError(f"Config file {args.config_file} doesn't exist") + raise FileNotFoundError( + f"Config file {args.config_file} doesn't exist" + ) else: script_directory_file_config = os.path.join( os.path.dirname(args.file), "manim.cfg" @@ -426,7 +441,7 @@ def _run_config(): if os.path.exists(script_directory_file_config): config_files.append(script_directory_file_config) else: - working_directory_file_config = os.path.join(os.getcwd(),"manim.cfg") + working_directory_file_config = os.path.join(os.getcwd(), "manim.cfg") if os.path.exists(working_directory_file_config): config_files.append(working_directory_file_config) @@ -443,14 +458,16 @@ def _run_config(): file_writer_config = _parse_file_writer_config(config_parser, args) return args, config_parser, file_writer_config, successfully_read_files + def finalized_configs_dict(): - config=_run_config()[1] + config = _run_config()[1] return {section: dict(config[section]) for section in config.sections()} -def _subcommands_exist(ignore = []): - NON_ANIM_UTILS = ["cfg","--help","-h"] + +def _subcommands_exist(ignore=[]): + NON_ANIM_UTILS = ["cfg", "--help", "-h"] NON_ANIM_UTILS = [util for util in NON_ANIM_UTILS if util not in ignore] - not_only_manim = len(sys.argv) > min_argvs-1 + not_only_manim = len(sys.argv) > min_argvs - 1 sub_command_exists = any(a == item for a in sys.argv for item in NON_ANIM_UTILS) return not_only_manim and sub_command_exists diff --git a/manim/utils/file_ops.py b/manim/utils/file_ops.py index 186756821f..2e2dad80a3 100644 --- a/manim/utils/file_ops.py +++ b/manim/utils/file_ops.py @@ -61,6 +61,7 @@ def get_sorted_integer_files( indexed_files.sort(key=lambda p: p[0]) return list(map(lambda p: os.path.join(directory, p[1]), indexed_files)) + def open_file(file_path): current_os = platform.system() if current_os == "Windows": diff --git a/scripts/pycairoinstall.py b/scripts/pycairoinstall.py index 5cd7c8aeae..3176dfdfe8 100644 --- a/scripts/pycairoinstall.py +++ b/scripts/pycairoinstall.py @@ -3,49 +3,67 @@ import sys import urllib.request -if 'Windows' in platform.system(): +if "Windows" in platform.system(): # In case the python version is 3.6 and the system is 32-bit, try pycairo‑1.19.1‑cp37‑cp37m‑win32.whl version of cairo - if sys.version[:3]=='3.6' and platform.machine()=='x86': - urllib.request.urlretrieve("https://download.lfd.uci.edu/pythonlibs/w3jqiv8s/pycairo-1.19.1-cp36-cp36m-win32.whl", "pycairo-1.19.1-cp36-cp36m-win32.whl") + if sys.version[:3] == "3.6" and platform.machine() == "x86": + urllib.request.urlretrieve( + "https://download.lfd.uci.edu/pythonlibs/w3jqiv8s/pycairo-1.19.1-cp36-cp36m-win32.whl", + "pycairo-1.19.1-cp36-cp36m-win32.whl", + ) os.system("pip install pycairo-1.19.1-cp36-cp36m-win32.whl") os.remove("pycairo-1.19.1-cp37-cp37m-win32.whl") # In case the python version is 3.6 and the system is 64-bit, try pycairo‑1.19.1‑cp37‑cp37m‑win32.whl version of cairo - elif sys.version[:3]=='3.6' and platform.machine()=='AMD64': - urllib.request.urlretrieve("https://download.lfd.uci.edu/pythonlibs/w3jqiv8s/pycairo-1.19.1-cp36-cp36m-win_amd64.whl", "pycairo-1.19.1-cp36-cp36m-win_amd64.whl") + elif sys.version[:3] == "3.6" and platform.machine() == "AMD64": + urllib.request.urlretrieve( + "https://download.lfd.uci.edu/pythonlibs/w3jqiv8s/pycairo-1.19.1-cp36-cp36m-win_amd64.whl", + "pycairo-1.19.1-cp36-cp36m-win_amd64.whl", + ) print("Sucessfully downloaded Cairo for your system") print("Installing Cairo") os.system("pip install pycairo-1.19.1-cp36-cp36m-win_amd64.whl") os.remove("pycairo-1.19.1-cp36-cp36m-win_amd64.whl") - + # In case the python version is 3.7 and the system is 32-bit, try pycairo‑1.19.1‑cp37‑cp37m‑win32.whl version of cairo - elif sys.version[:3]=='3.7' and platform.machine()=='x86': - urllib.request.urlretrieve("https://download.lfd.uci.edu/pythonlibs/w3jqiv8s/pycairo-1.19.1-cp37-cp37m-win32.whl", "pycairo-1.19.1-cp37-cp37m-win32.whl") + elif sys.version[:3] == "3.7" and platform.machine() == "x86": + urllib.request.urlretrieve( + "https://download.lfd.uci.edu/pythonlibs/w3jqiv8s/pycairo-1.19.1-cp37-cp37m-win32.whl", + "pycairo-1.19.1-cp37-cp37m-win32.whl", + ) print("Sucessfully downloaded Cairo for your system") print("Installing Cairo") os.system("pip install pycairo-1.19.1-cp37-cp37m-win32.whl") os.remove("pycairo-1.19.1-cp37-cp37m-win32.whl") # In case the python version is 3.7 and the system is AMD64, try pycairo-1.19.1-cp37-cp37m-win_amd64.whl version of cairo - elif sys.version[:3]=='3.7' and platform.machine()=='AMD64': - urllib.request.urlretrieve("https://download.lfd.uci.edu/pythonlibs/w3jqiv8s/pycairo-1.19.1-cp37-cp37m-win_amd64.whl", "pycairo-1.19.1-cp37-cp37m-win_amd64.whl") + elif sys.version[:3] == "3.7" and platform.machine() == "AMD64": + urllib.request.urlretrieve( + "https://download.lfd.uci.edu/pythonlibs/w3jqiv8s/pycairo-1.19.1-cp37-cp37m-win_amd64.whl", + "pycairo-1.19.1-cp37-cp37m-win_amd64.whl", + ) print("Sucessfully downloaded Cairo for your system") print("Installing Cairo") os.system("pip install pycairo-1.19.1-cp37-cp37m-win_amd64.whl") os.remove("pycairo-1.19.1-cp37-cp37m-win_amd64.whl") - + # In case the python version is 3.8 and the system is 32-bit, try pycairo-1.19.1-cp38-cp38-win32.whl version of cairo - elif sys.version[:3]=='3.8' and platform.machine()=='x86': - urllib.request.urlretrieve("https://download.lfd.uci.edu/pythonlibs/w3jqiv8s/pycairo-1.19.1-cp38-cp38-win32.whl", "pycairo-1.19.1-cp38-cp38-win32.whl") + elif sys.version[:3] == "3.8" and platform.machine() == "x86": + urllib.request.urlretrieve( + "https://download.lfd.uci.edu/pythonlibs/w3jqiv8s/pycairo-1.19.1-cp38-cp38-win32.whl", + "pycairo-1.19.1-cp38-cp38-win32.whl", + ) print("Sucessfully downloaded Cairo for your system") print("Installing Cairo") os.system("pip install pycairo-1.19.1-cp38-cp38-win32.whl") os.remove("pycairo-1.19.1-cp38-cp38-win32.whl") - + # In case the python version is 3.8 and the system is AMD64, try pycairo-1.19.1-cp38-cp38-win_amd64.whl version of cairo - elif sys.version[:3]=='3.8' and platform.machine()=='AMD64': - urllib.request.urlretrieve("https://download.lfd.uci.edu/pythonlibs/w3jqiv8s/pycairo-1.19.1-cp38-cp38-win_amd64.whl", "pycairo-1.19.1-cp38-cp38-win_amd64.whl") + elif sys.version[:3] == "3.8" and platform.machine() == "AMD64": + urllib.request.urlretrieve( + "https://download.lfd.uci.edu/pythonlibs/w3jqiv8s/pycairo-1.19.1-cp38-cp38-win_amd64.whl", + "pycairo-1.19.1-cp38-cp38-win_amd64.whl", + ) print("Sucessfully downloaded Cairo for your system") print("Installing Cairo") os.system("pip install pycairo-1.19.1-cp38-cp38-win_amd64.whl") - os.remove("pycairo-1.19.1-cp38-cp38-win_amd64.whl") + os.remove("pycairo-1.19.1-cp38-cp38-win_amd64.whl") diff --git a/setup.py b/setup.py index af84f44ad7..af5078d670 100755 --- a/setup.py +++ b/setup.py @@ -6,12 +6,9 @@ description="Animation engine for explanatory math videos", license="MIT", packages=find_namespace_packages(), - package_data={ "manim": ["*.tex", "*.cfg"] }, + package_data={"manim": ["*.tex", "*.cfg"]}, entry_points={ - "console_scripts": [ - "manim=manim.__main__:main", - "manimcm=manim.__main__:main", - ] + "console_scripts": ["manim=manim.__main__:main", "manimcm=manim.__main__:main",] }, install_requires=[ "colour", diff --git a/tests/conftest.py b/tests/conftest.py index 7a270355d5..95f158842c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,11 +35,12 @@ def pytest_collection_modifyitems(config, items): def python_version(): return "python3" if sys.platform == "darwin" else "python" + @pytest.fixture def reset_cfg_file(): - cfgfilepath = os.path.join(os.path.dirname(__file__),"test_cli", "manim.cfg") + cfgfilepath = os.path.join(os.path.dirname(__file__), "test_cli", "manim.cfg") with open(cfgfilepath) as cfgfile: original = cfgfile.read() yield - with open(cfgfilepath,"w") as cfgfile: - cfgfile.write(original) \ No newline at end of file + with open(cfgfilepath, "w") as cfgfile: + cfgfile.write(original) diff --git a/tests/test_cli/test_cfg_subcmd.py b/tests/test_cli/test_cfg_subcmd.py index 21dfcd51d8..9995b0f576 100644 --- a/tests/test_cli/test_cfg_subcmd.py +++ b/tests/test_cli/test_cfg_subcmd.py @@ -3,13 +3,18 @@ import shutil from test_cli import capture + this_folder = os.path.dirname(__file__) + def test_cfg_help(python_version): """Test if Manim successfully adds configparsers when a subcommand is invoked.""" command = f"cd {this_folder} && {python_version} -m manim cfg --help" out, err, exitcode = capture(command, use_shell=True) - assert exitcode == 0, f"The cfg subcommand help is not working as intended.\nError : {err}" + assert ( + exitcode == 0 + ), f"The cfg subcommand help is not working as intended.\nError : {err}" + def test_cfg_show(python_version): """Test if the `manim cfg show` command works as intended.""" @@ -18,15 +23,19 @@ def test_cfg_show(python_version): assert exitcode == 0 assert f"{os.path.sep}tests{os.path.sep}".encode("utf-8") in out, err + def test_cfg_export(python_version): """Test if the `manim cfg export` command works as intended.""" command = f"cd {this_folder} && {python_version} -m manim cfg export --dir temp" out, err, exitcode = capture(command, use_shell=True) - assert exitcode == 0, f"The cfg subcommand export is not working as intended.\nError : {err}" - assert os.path.exists(os.path.join(this_folder,"temp","manim.cfg")) - with open(os.path.join(this_folder,"temp","manim.cfg"),"r") as writtencfg: + assert ( + exitcode == 0 + ), f"The cfg subcommand export is not working as intended.\nError : {err}" + assert os.path.exists(os.path.join(this_folder, "temp", "manim.cfg")) + with open(os.path.join(this_folder, "temp", "manim.cfg"), "r") as writtencfg: assert "sound = True" in writtencfg.read(), err - shutil.rmtree(os.path.join(this_folder,"temp")) + shutil.rmtree(os.path.join(this_folder, "temp")) + @pytest.mark.usefixtures("reset_cfg_file") def test_cfg_write(python_version): @@ -39,9 +48,11 @@ def test_cfg_write(python_version): out, err, exitcode = capture( command, instream=open(os.path.join(this_folder, "write_cfg_sbcmd_input.txt")), - use_shell=True - ) - assert exitcode == 0, f"The cfg subcommand write is not working as intended.\nError : {err}" + use_shell=True, + ) + assert ( + exitcode == 0 + ), f"The cfg subcommand write is not working as intended.\nError : {err}" - with open(cfgfilepath,"r") as cfgfile: + with open(cfgfilepath, "r") as cfgfile: assert "sound = False" in cfgfile.read() diff --git a/tests/test_cli/test_cli.py b/tests/test_cli/test_cli.py index b93c1fa88c..3318d457cf 100644 --- a/tests/test_cli/test_cli.py +++ b/tests/test_cli/test_cli.py @@ -4,13 +4,14 @@ import pytest -def capture(command,instream=None, use_shell=False): - proc = subprocess.Popen(command, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - stdin=instream, - shell = use_shell - ) +def capture(command, instream=None, use_shell=False): + proc = subprocess.Popen( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=instream, + shell=use_shell, + ) out, err = proc.communicate() return out, err, proc.returncode @@ -75,8 +76,8 @@ def test_dash_as_name(python_version): command = [python_version, "-m", "manim", "-", "-l", "--media_dir", path_output] out, err, exitcode = capture( command, - instream = open(os.path.join(os.path.dirname(__file__), "dash_test_script.txt")) - ) + instream=open(os.path.join(os.path.dirname(__file__), "dash_test_script.txt")), + ) assert exitcode == 0, err assert os.path.exists( os.path.join(path_output, "videos", "-", "480p15", "DashAsNameTest.mp4")