Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4468c85
Fix bug where `cfg` is taken as a filename.
Aathish04 Aug 6, 2020
5a7820c
Extended cfg write so that it can write all entries in cfg.
Aathish04 Aug 7, 2020
d87172f
Add cfg entry to choose presence of timestamps.
Aathish04 Aug 7, 2020
b7e455e
Disable timestamps for logging tests.
Aathish04 Aug 7, 2020
05b8875
Test on this branch.
Aathish04 Aug 7, 2020
7fe73f0
Reformat for black
Aathish04 Aug 7, 2020
fa718a4
Added some docstrings for added methods.
Aathish04 Aug 7, 2020
8b2c13e
Use getboolean to get value of log.timestamps.
Aathish04 Aug 10, 2020
1576a4f
Merge master into extend_subcmds
Aathish04 Aug 10, 2020
a3331d4
Merge branch 'master' into extend_subcmds
Aathish04 Aug 10, 2020
e083597
Format as per black.
Aathish04 Aug 10, 2020
32ccc50
Merge branch 'extend_subcmds' of https://github.com/Aathish04/manim i…
Aathish04 Aug 10, 2020
54a3a3e
Increase compatibility with scene-caching's tests.
Aathish04 Aug 10, 2020
6e5ec58
Edit a docstring.
Aathish04 Aug 10, 2020
aada59a
Apply suggestions from @pgbiel 's code review
Aathish04 Aug 11, 2020
4c3c53a
Apply suggestions from @pgbiel 's code review
Aathish04 Aug 11, 2020
3d4c23a
Modify how a default parameter is set.
Aathish04 Aug 11, 2020
138ae17
Raise a more detailed exception for cfg write test.
Aathish04 Aug 11, 2020
541f186
Change a docstring to be more accurate.
Aathish04 Aug 11, 2020
5ac0759
Improve Exception Statement.
Aathish04 Aug 12, 2020
9584dfe
Merge branch 'extend_subcmds' of https://github.com/Aathish04/manim i…
Aathish04 Aug 12, 2020
338c67d
Merge branch 'master' into extend_subcmds
Aathish04 Aug 12, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions manim/default.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ log_message =
log_path = dim
log_width = -1
log_height = -1
log_timestamps = True

[ffmpeg]
# Uncomment the following line to manually set the loglevel for ffmpeg. See ffmpeg manpage for accepted values
Expand Down
9 changes: 7 additions & 2 deletions manim/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,14 @@ def parse_theme(fp):
theme["log.height"] = (
None if theme["log.height"] == "-1" else int(theme["log.height"])
)
theme["log.timestamps"] = config_parser["logger"].getboolean("log.timestamps")
try:
customTheme = Theme(
{k: v for k, v in theme.items() if k not in ["log.width", "log.height"]}
{
k: v
for k, v in theme.items()
if k not in ["log.width", "log.height", "log.timestamps"]
}
)
except (color.ColorParseError, errors.StyleSyntaxError):
customTheme = None
Expand Down Expand Up @@ -80,7 +85,7 @@ def parse_theme(fp):
level="NOTSET",
format="%(message)s",
datefmt="[%X]",
handlers=[RichHandler(console=console)],
handlers=[RichHandler(console=console, show_time=themedict["log.timestamps"])],
)

logger = logging.getLogger("rich")
132 changes: 88 additions & 44 deletions manim/utils/cfg_subcmds.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
"""
import os
import configparser
from ast import literal_eval

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
from rich.style import Style
from rich.errors import StyleSyntaxError

Expand All @@ -22,10 +22,54 @@
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]"""

RICH_NON_STYLE_ENTRIES = ["log.width", "log.height", "log.timestamps"]
console = Console()


def value_from_string(value):
"""Extracts the literal of proper datatype from a string.
Parameters
----------
value : :class:`str`
The value to check get the literal from.

Returns
-------
Union[:class:`str`, :class:`int`, :class:`bool`]
Returns the literal of appropriate datatype.
"""
try:
value = literal_eval(value)
except (SyntaxError, ValueError):
pass
return value


def _is_expected_datatype(value, expected, style=False):
"""Checks whether `value` is the same datatype as `expected`,
and checks if it is a valid `style` if `style` is true.

Parameters
----------
value : :class:`str`
The string of the value to check (obtained from reading the user input).
expected : :class:`str`
The string of the literal datatype must be matched by `value`. Obtained from
reading the cfg file.
style : :class:`bool`, optional
Whether or not to confirm if `value` is a style, by default False

Returns
-------
:class:`bool`
Whether or not `value` matches the datatype of `expected`.
"""
value = value_from_string(value)
expected = type(value_from_string(expected))

return isinstance(value, expected) and (is_valid_style(value) if style else True)


def is_valid_style(style):
"""Checks whether the entered color is a valid color according to rich
Parameters
Expand Down Expand Up @@ -81,62 +125,61 @@ def write(level=None, openfile=False):
you will have to create a manim.cfg in the local directory, where you want those changes to be overridden."""

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."""
To save your config 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:
desc = (
"style" if key not in ["log.width", "log.height"] else "value"
)
style = key if key not in ["log.width", "log.height"] else None
cond = (
is_valid_style
if key not in ["log.width", "log.height"]
else lambda m: m.isdigit()

for key in default:
# All the cfg entries for logger need to be validated as styles,
# as long as they arent setting the log width or height etc
if category == "logger" and key not in RICH_NON_STYLE_ENTRIES:
desc = "style"
style = default[key]
else:
desc = "value"
style = None

console.print(f"Enter the {desc} for {key} ", style=style, end="")
if category != "logger" or key in RICH_NON_STYLE_ENTRIES:
defaultval = (
repr(default[key])
if isinstance(value_from_string(default[key]), str)
else default[key]
)
console.print(f"Enter the {desc} for {key}:", style=style, end="")
console.print(f"(defaults to {defaultval}) :", end="")
try:
temp = input()
if temp:
while not cond(temp):
console.print(
f"[red bold]Invalid {desc}. Try again.[/red bold]"
)
console.print(
f"Enter the {desc} for {key}:", style=style, end=""
)
temp = input()
else:
default[key] = temp
default = replace_keys(default)

else:
for key in default:
if default[key] in ["True", "False"]:
except EOFError:
raise Exception(
"""Not enough values in input.
You may have added a new entry to default.cfg, in which case you will have to
modify write_cfg_subcmd_input to account for it."""
)
if temp:
while temp and not _is_expected_datatype(
temp, default[key], bool(style)
):
console.print(
f"[red bold]Invalid {desc}. Try again.[/red bold]"
)
console.print(
f"Enter value for {key} (defaults to {default[key]}):",
end="",
f"Enter the {desc} for {key}:", style=style, 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
else:
default[key] = temp

default = replace_keys(default) if category == "logger" else default

config[category] = dict(default)

else:
action = "open"

Expand Down Expand Up @@ -168,10 +211,11 @@ def write(level=None, openfile=False):

def show():
current_config = finalized_configs_dict()
rich_non_style_entries = [a.replace(".", "_") for a in RICH_NON_STYLE_ENTRIES]
for category in current_config:
console.print(f"{category}", style="bold green underline")
for entry in current_config[category]:
if category == "logger" and entry not in ["log_width", "log_height"]:
if category == "logger" and entry not in rich_non_style_entries:
console.print(f"{entry} :", end="")
console.print(
f" {current_config[category][entry]}",
Expand Down
115 changes: 82 additions & 33 deletions manim/utils/config_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
"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."""
Expand Down Expand Up @@ -169,29 +167,19 @@ 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():
only_manim = len(sys.argv) == 1

if only_manim or _subcommand_name():
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_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")
cfg_export_parser.add_argument("--dir", default=os.getcwd())
# More subcommands can be added here, with elif statements.
# If a help command is passed, we still want subcommands to show
# up, so we check for help commands as well before adding the
# subcommand's subparser.
if only_manim or _subcommand_name() in ["cfg", "--help", "-h"]:
cfg_related = _init_cfg_subcmd(subparsers)

if len(sys.argv) == min_argvs - 1 or not _subcommands_exist(
ignore=["--help", "-h"]
):
if only_manim or not _subcommand_name(ignore=["--help", "-h"]):
parser.add_argument(
"file", help="path to file holding the python code for the scene",
)
Expand Down Expand Up @@ -420,13 +408,12 @@ def _str2bool(s):
)
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,
)
if _subcommand_name() == "cfg":
setattr(
parsed,
"cfg_subcommand",
cfg_related.parse_args(sys.argv[2:]).cfg_subcommand,
)

return parsed

Expand Down Expand Up @@ -537,10 +524,72 @@ def finalized_configs_dict():
return {section: dict(config[section]) for section in config.sections()}


def _subcommands_exist(ignore=[]):
def _subcommand_name(ignore=()):
"""Goes through sys.argv to check if any subcommand has been passed,
and returns the first such subcommand's name, if found.

Parameters
----------
ignore : Iterable[:class:`str`], optional
List of NON_ANIM_UTILS to ignore when searching for subcommands, by default []

Returns
-------
Optional[:class:`str`]
If a subcommand is found, returns the string of its name. Returns None if no
subcommand is found.
"""
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
# If a subcommand is found, break out of the inner loop, and hit the break of the outer loop
# on the way out, effectively breaking out of both loops. The value of arg will be the
# subcommand to be taken.
# If no subcommand is found, none of the breaks are hit, and the else clause of the outer loop
# is run, setting arg to None.

for item in NON_ANIM_UTILS:
for arg in sys.argv:
if arg == item:
break
else:
continue
break
else:
arg = None

return arg


def _init_cfg_subcmd(subparsers):
"""Initialises the subparser for the `cfg` subcommand.

Parameters
----------
subparsers : :class:`argparse._SubParsersAction`
The subparser object for which to add the sub-subparser for the cfg subcommand.

Returns
-------
:class:`argparse.ArgumentParser`
The parser that parser anything cfg subcommand related.
"""
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_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")
cfg_export_parser.add_argument("--dir", default=os.getcwd())

return cfg_related
Loading