Repository: ManimCommunity/manim

Issue:
ValueError when invoking cfg write -l user from the command line ( jupyter : media_width )
## Description of bug / unexpected behavior
<!-- Add a clear and concise description of the problem you encountered. -->

I was testing the command to write to a manim config file and found a bug caused by the percentage sign of the media_width value in the jupyter section.  I'm not sure if this occurs in other areas but I know for sure that when 'manim cfg write' is called directly it first calls make_config_parser() which parses the default file and removes the escapement on the percentage sign.  Then it tries to call the __setitem__ method further down with the value '60%'.  This causes a ValueError.  I think the issue is that there is a difference in the way configparser creates a value using the read_file method and the way it creates values on assignment.

read_file('60%%') -> '60%'
parser[category] = '60%%' -> '60'
parser[category] = '60%' -> ValueError

## Expected behavior
<!-- Add a clear and concise description of what you expected to happen. -->
It should update the config value without throwing an exception

## How to reproduce the issue
<!-- Provide a piece of code illustrating the undesired behavior. -->
Actual Case:
run ```manim cfg write -l user``` and keep hitting enter for defaults.  It will encounter a value error at the jupyter section
Minimal Test Case:
Create file called default.cfg with the contents:
```
[jupyter]
media_width = 60%%
```
<details><summary>Code for reproducing the problem</summary>

```py
import configparser
parser = configparser.ConfigParser()
with open('default.cfg') as file:
    parser.read_file(file)
for category in parser:
    parser[category]=dict(parser[category])
```

</details>

## Logs
<details><summary>Terminal output</summary>
<!-- Add "-v DEBUG" when calling manim to generate more detailed logs -->

```
(venv) λ manim cfg write -l user
Manim Community v0.12.0

                                             Manim Configuration File Writer
DEFAULT
CLI
Enter the value for notify_outdated_version (defaults to True) :
Enter the value for write_to_movie (defaults to True) :
Enter the value for format (defaults to 'mp4') :
Enter the value for save_last_frame (defaults to False) :
Enter the value for write_all (defaults to False) :
Enter the value for save_pngs (defaults to False) :
Enter the value for zero_pad (defaults to 4) :
Enter the value for save_as_gif (defaults to False) :
Enter the value for save_sections (defaults to False) :
Enter the value for preview (defaults to False) :
Enter the value for show_in_file_browser (defaults to False) :
Enter the value for verbosity (defaults to 'INFO') :
Enter the value for progress_bar (defaults to 'display') :
Enter the value for output_file (defaults to '') :
Enter the value for log_to_file (defaults to False) :
Enter the value for background_color (defaults to 'BLACK') :
Enter the value for background_opacity (defaults to 1) :
Enter the value for from_animation_number (defaults to 0) :
Enter the value for upto_animation_number (defaults to -1) :
Enter the value for media_dir (defaults to './media') :
Enter the value for log_dir (defaults to '{media_dir}/logs') :
Enter the value for assets_dir (defaults to './') :
Enter the value for video_dir (defaults to '{media_dir}/videos/{module_name}/{quality}') :
Enter the value for sections_dir (defaults to '{video_dir}/sections') :
Enter the value for images_dir (defaults to '{media_dir}/images/{module_name}') :
Enter the value for tex_dir (defaults to '{media_dir}/Tex') :
Enter the value for text_dir (defaults to '{media_dir}/texts') :
Enter the value for partial_movie_dir (defaults to '{video_dir}/partial_movie_files/{scene_name}') :
Enter the value for renderer (defaults to 'cairo') :
Enter the value for use_webgl_renderer (defaults to False) :
Enter the value for webgl_renderer_path (defaults to '') :
Enter the value for use_opengl_renderer (defaults to False) :
Enter the value for enable_gui (defaults to False) :
Enter the value for gui_location (defaults to 0,0) :
Enter the value for fullscreen (defaults to False) :
Enter the value for window_position (defaults to 'UR') :
Enter the value for window_size (defaults to 'default') :
Enter the value for window_monitor (defaults to 0) :
Enter the value for force_window (defaults to False) :
Enter the value for use_projection_fill_shaders (defaults to False) :
Enter the value for use_projection_stroke_shaders (defaults to False) :
Enter the value for movie_file_extension (defaults to '.mp4') :
Enter the value for frame_rate (defaults to 60) :
Enter the value for pixel_height (defaults to 1080) :
Enter the value for pixel_width (defaults to 1920) :
Enter the value for max_files_cached (defaults to 100) :
Enter the value for flush_cache (defaults to False) :
Enter the value for disable_caching (defaults to False) :
Enter the value for disable_caching_warning (defaults to False) :
Enter the value for enable_wireframe (defaults to False) :
Enter the value for dry_run (defaults to False) :
Enter the value for tex_template (defaults to '') :
Enter the value for plugins (defaults to '') :
custom_folders
Enter the value for media_dir (defaults to 'videos') :
Enter the value for video_dir (defaults to '{media_dir}') :
Enter the value for sections_dir (defaults to '{media_dir}') :
Enter the value for images_dir (defaults to '{media_dir}') :
Enter the value for text_dir (defaults to '{media_dir}/temp_files') :
Enter the value for tex_dir (defaults to '{media_dir}/temp_files') :
Enter the value for log_dir (defaults to '{media_dir}/temp_files') :
Enter the value for partial_movie_dir (defaults to '{media_dir}/partial_movie_files/{scene_name}') :
logger

The default colour is used by the input statement.
If left empty, the default colour will be used.
 For a full list of styles, visit https://rich.readthedocs.io/en/latest/style.html

Enter the style for logging.keyword
Enter the style for logging.level.notset
Enter the style for logging.level.debug
Enter the style for logging.level.info
Enter the style for logging.level.warning
Enter the style for logging.level.error
Enter the style for logging.level.critical
Enter the style for log.level
Enter the style for log.time
Enter the style for log.message
Enter the style for log.path
Enter the value for log.width (defaults to -1) :
Enter the value for log.height (defaults to -1) :
Enter the value for log.timestamps (defaults to True) :
Enter the style for repr.number
ffmpeg
Enter the value for loglevel (defaults to 'ERROR') :
jupyter
Enter the value for media_width (defaults to '60%') :
Traceback (most recent call last):
  File "C:\Users\John\AppData\Local\Programs\Python\Python39\lib\runpy.py", line 197, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "C:\Users\John\AppData\Local\Programs\Python\Python39\lib\runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "C:\Users\John\Desktop\manim_prg\venv\Scripts\manim.exe\__main__.py", line 7, in <module>
  File "c:\users\john\desktop\manim_prg\venv\lib\site-packages\click\core.py", line 1128, in __call__
    return self.main(*args, **kwargs)
  File "c:\users\john\desktop\manim_prg\venv\lib\site-packages\click\core.py", line 1053, in main
    rv = self.invoke(ctx)
  File "c:\users\john\desktop\manim_prg\venv\lib\site-packages\click\core.py", line 1659, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "c:\users\john\desktop\manim_prg\venv\lib\site-packages\click\core.py", line 1659, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "c:\users\john\desktop\manim_prg\venv\lib\site-packages\click\core.py", line 1395, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "c:\users\john\desktop\manim_prg\venv\lib\site-packages\click\core.py", line 754, in invoke
    return __callback(*args, **kwargs)
  File "c:\users\john\desktop\manim_prg\venv\lib\site-packages\manim\cli\cfg\group.py", line 208, in write
    parser[category] = dict(default)
  File "C:\Users\John\AppData\Local\Programs\Python\Python39\lib\configparser.py", line 975, in __setitem__
    self.read_dict({key: value})
  File "C:\Users\John\AppData\Local\Programs\Python\Python39\lib\configparser.py", line 754, in read_dict
    self.set(section, key, value)
  File "C:\Users\John\AppData\Local\Programs\Python\Python39\lib\configparser.py", line 1202, in set
    super().set(section, option, value)
  File "C:\Users\John\AppData\Local\Programs\Python\Python39\lib\configparser.py", line 894, in set
    value = self._interpolation.before_set(self, section, option,
  File "C:\Users\John\AppData\Local\Programs\Python\Python39\lib\configparser.py", line 402, in before_set
    raise ValueError("invalid interpolation syntax in %r at "
ValueError: invalid interpolation syntax in '60%' at position 2
'''


Discussion:
These changes to lines 205 to 208 of the manim\cli\cfg\group.py file fix the issue.

```
        default[key] = temp.replace("%","%%")

default = replace_keys(default) if category == "logger" else default
parser[category] = {i:v.replace("%","%%") for i,v in dict(default).items()}
```

Resolving patch:
diff --git a/manim/cli/cfg/group.py b/manim/cli/cfg/group.py
--- a/manim/cli/cfg/group.py
+++ b/manim/cli/cfg/group.py
@@ -201,11 +201,13 @@ def write(level: str = None, openfile: bool = False) -> None:
                         )
                         temp = input()
 
-                    default[key] = temp
+                    default[key] = temp.replace("%", "%%")
 
             default = replace_keys(default) if category == "logger" else default
 
-            parser[category] = dict(default)
+            parser[category] = {
+                i: v.replace("%", "%%") for i, v in dict(default).items()
+            }
 
     else:
         action = "open"


Test patch: