Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
paul-gauthier committed Jun 18, 2024
2 parents aa3dbac + e479f98 commit 5748a57
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 14 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
repos:
- repo: https://github.com/pycqa/isort
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
Expand Down
20 changes: 18 additions & 2 deletions aider/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@
from .dump import dump # noqa: F401


def default_env_file(git_root):
return os.path.join(git_root, ".env") if git_root else ".env"


def get_preparser(git_root):
parser = configargparse.ArgumentParser(add_help=False)
parser.add_argument(
"--env-file",
metavar="ENV_FILE",
default=default_env_file(git_root),
help="Specify the .env file to load (default: .env in git root)",
)
return parser


def get_parser(default_config_files, git_root):
parser = configargparse.ArgumentParser(
description="aider is GPT powered coding in your terminal",
Expand Down Expand Up @@ -184,11 +199,12 @@ def get_parser(default_config_files, git_root):
" max_chat_history_tokens."
),
)
default_env_file = os.path.join(git_root, ".env") if git_root else ".env"
# This is a duplicate of the argument in the preparser and is a no-op by this time of
# argument parsing, but it's here so that the help is displayed as expected.
group.add_argument(
"--env-file",
metavar="ENV_FILE",
default=default_env_file,
default=default_env_file(git_root),
help="Specify the .env file to load (default: .env in git root)",
)

Expand Down
19 changes: 14 additions & 5 deletions aider/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from streamlit.web import cli

from aider import __version__, models, utils
from aider.args import get_parser
from aider.args import get_parser, get_preparser
from aider.coders import Coder
from aider.commands import SwitchModel
from aider.io import InputOutput
Expand Down Expand Up @@ -124,12 +124,18 @@ def check_gitignore(git_root, io, ask=True):

def format_settings(parser, args):
show = scrub_sensitive_info(args, parser.format_values())
# clean up the headings for consistency w/ new lines
heading_env = "Environment Variables:"
heading_defaults = "Defaults:"
if heading_env in show:
show = show.replace(heading_env, "\n" + heading_env)
show = show.replace(heading_defaults, "\n" + heading_defaults)
show += "\n"
show += "Option settings:\n"
for arg, val in sorted(vars(args).items()):
if val:
val = scrub_sensitive_info(args, str(val))
show += f" - {arg}: {val}\n"
show += f" - {arg}: {val}\n" # noqa: E221
return show


Expand Down Expand Up @@ -225,6 +231,12 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
default_config_files.append(Path.home() / conf_fname) # homedir
default_config_files = list(map(str, default_config_files))

preparser = get_preparser(git_root)
pre_args, _ = preparser.parse_known_args(argv)

# Load the .env file specified in the arguments
load_dotenv(pre_args.env_file)

parser = get_parser(default_config_files, git_root)
args = parser.parse_args(argv)

Expand Down Expand Up @@ -320,9 +332,6 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
cmd_line = scrub_sensitive_info(args, cmd_line)
io.tool_output(cmd_line, log_only=True)

if args.env_file:
load_dotenv(args.env_file)

if args.anthropic_api_key:
os.environ["ANTHROPIC_API_KEY"] = args.anthropic_api_key

Expand Down
93 changes: 88 additions & 5 deletions aider/tests/test_main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
import shutil
import subprocess
import tempfile
from io import StringIO
from pathlib import Path
from unittest import TestCase
from unittest.mock import MagicMock, patch
Expand All @@ -13,24 +13,28 @@
from aider.dump import dump # noqa: F401
from aider.io import InputOutput
from aider.main import check_gitignore, main, setup_git
from aider.utils import GitTemporaryDirectory, make_repo
from aider.utils import GitTemporaryDirectory, IgnorantTemporaryDirectory, make_repo


class TestMain(TestCase):
def setUp(self):
self.original_env = os.environ.copy()
os.environ["OPENAI_API_KEY"] = "deadbeef"
self.original_cwd = os.getcwd()
self.tempdir = tempfile.mkdtemp()
self.tempdir_obj = IgnorantTemporaryDirectory()
self.tempdir = self.tempdir_obj.name
os.chdir(self.tempdir)

def tearDown(self):
os.chdir(self.original_cwd)
shutil.rmtree(self.tempdir, ignore_errors=True)
self.tempdir_obj.cleanup()
os.environ.clear()
os.environ.update(self.original_env)

def test_main_with_empty_dir_no_files_on_command(self):
main(["--no-git"], input=DummyInput(), output=DummyOutput())

def test_main_with_empty_dir_new_file(self):
def test_main_with_emptqy_dir_new_file(self):
main(["foo.txt", "--yes", "--no-git"], input=DummyInput(), output=DummyOutput())
self.assertTrue(os.path.exists("foo.txt"))

Expand Down Expand Up @@ -237,3 +241,82 @@ def test_default_yes(self, mock_run, MockInputOutput):
main(["--message", test_message])
args, kwargs = MockInputOutput.call_args
self.assertEqual(args[1], None)

def test_dark_mode_sets_code_theme(self):
# Mock Coder.create to capture the configuration
with patch("aider.coders.Coder.create") as MockCoder:
main(["--dark-mode", "--no-git"], input=DummyInput(), output=DummyOutput())
# Ensure Coder.create was called
MockCoder.assert_called_once()
# Check if the code_theme setting is for dark mode
_, kwargs = MockCoder.call_args
self.assertEqual(kwargs["code_theme"], "monokai")

def test_light_mode_sets_code_theme(self):
# Mock Coder.create to capture the configuration
with patch("aider.coders.Coder.create") as MockCoder:
main(["--light-mode", "--no-git"], input=DummyInput(), output=DummyOutput())
# Ensure Coder.create was called
MockCoder.assert_called_once()
# Check if the code_theme setting is for light mode
_, kwargs = MockCoder.call_args
self.assertEqual(kwargs["code_theme"], "default")

def create_env_file(self, file_name, content):
env_file_path = Path(self.tempdir) / file_name
env_file_path.write_text(content)
return env_file_path

def test_env_file_flag_sets_automatic_variable(self):
env_file_path = self.create_env_file(".env.test", "AIDER_DARK_MODE=True")
with patch("aider.coders.Coder.create") as MockCoder:
main(
["--env-file", str(env_file_path), "--no-git"],
input=DummyInput(),
output=DummyOutput(),
)
MockCoder.assert_called_once()
# Check if the color settings are for dark mode
_, kwargs = MockCoder.call_args
self.assertEqual(kwargs["code_theme"], "monokai")

def test_default_env_file_sets_automatic_variable(self):
self.create_env_file(".env", "AIDER_DARK_MODE=True")
with patch("aider.coders.Coder.create") as MockCoder:
main(["--no-git"], input=DummyInput(), output=DummyOutput())
# Ensure Coder.create was called
MockCoder.assert_called_once()
# Check if the color settings are for dark mode
_, kwargs = MockCoder.call_args
self.assertEqual(kwargs["code_theme"], "monokai")

def test_false_vals_in_env_file(self):
self.create_env_file(".env", "AIDER_SHOW_DIFFS=off")
with patch("aider.coders.Coder.create") as MockCoder:
main(["--no-git"], input=DummyInput(), output=DummyOutput())
MockCoder.assert_called_once()
_, kwargs = MockCoder.call_args
self.assertEqual(kwargs["show_diffs"], False)

def test_true_vals_in_env_file(self):
self.create_env_file(".env", "AIDER_SHOW_DIFFS=on")
with patch("aider.coders.Coder.create") as MockCoder:
main(["--no-git"], input=DummyInput(), output=DummyOutput())
MockCoder.assert_called_once()
_, kwargs = MockCoder.call_args
self.assertEqual(kwargs["show_diffs"], True)

def test_verbose_mode_lists_env_vars(self):
self.create_env_file(".env", "AIDER_DARK_MODE=on")
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
main(["--no-git", "--verbose"], input=DummyInput(), output=DummyOutput())
output = mock_stdout.getvalue()
relevant_output = "\n".join(
line
for line in output.splitlines()
if "AIDER_DARK_MODE" in line or "dark_mode" in line
) # this bit just helps failing assertions to be easier to read
self.assertIn("AIDER_DARK_MODE", relevant_output)
self.assertIn("dark_mode", relevant_output)
self.assertRegex(relevant_output, r"AIDER_DARK_MODE:\s+on")
self.assertRegex(relevant_output, r"dark_mode:\s+True")
8 changes: 7 additions & 1 deletion aider/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@ def __enter__(self):
return self.temp_dir.__enter__()

def __exit__(self, exc_type, exc_val, exc_tb):
self.cleanup()

def cleanup(self):
try:
self.temp_dir.__exit__(exc_type, exc_val, exc_tb)
self.temp_dir.cleanup()
except (OSError, PermissionError):
pass # Ignore errors (Windows)

def __getattr__(self, item):
return getattr(self.temp_dir, item)


class ChdirTemporaryDirectory(IgnorantTemporaryDirectory):
def __init__(self):
Expand Down

0 comments on commit 5748a57

Please sign in to comment.