From a2913f0c9ac2e7260c0e7f6dfbea6a9380be996a Mon Sep 17 00:00:00 2001 From: Vysakh Sreenivasan Date: Sat, 20 Jul 2024 16:22:28 +0530 Subject: [PATCH] handle cd directory --- src/drd/prompts/instructions.py | 7 +- src/drd/utils/step_executor.py | 12 ++- .../cli/query/test_dynamic_command_handler.py | 73 ------------------- tests/utils/test_step_executor.py | 73 +++++++++++++++++++ 4 files changed, 85 insertions(+), 80 deletions(-) diff --git a/src/drd/prompts/instructions.py b/src/drd/prompts/instructions.py index e980232..fafe645 100644 --- a/src/drd/prompts/instructions.py +++ b/src/drd/prompts/instructions.py @@ -80,15 +80,16 @@ def example(): Important guidelines: -1. No files in current directory or if user explicitly tells you to create something in current directory: +1. When no files in current directory or if user explicitly tells you to create something in current directory: - During project initialisation: Use `npx create-next-app@latest .` like cmds to create project in the same directory. - In such scenario no need to use 'cd' commands. All operations should be relative to the current directory. - - Use relative paths for all file operations. + - Use relative paths for all file operations and commands. 2. When there are files in current directory - You have to initialise a project, you can create a new directory `npx create-docusaurus@latest new-drd-docs`, as soon as you have such command, please also cd into the next step like `cd new-drd-docs`. So you have must generate the cd cmd subsequently. - - Use relative paths for all other cmds and file operations + - Use relative paths for all other cmds and file operations. Do not do create file on new-drd-docs/test.md because you + already cd into it, there is no need to reference the project name in file operations or commands anymore. 3. Strictly generate XML only, no other preceding or follow up words. Any other info you want to mention, mention it inside explanation 4. For file updates, provide ONLY the specific changes to be made, not the entire file content. - Provide precise line-by-line modifications per the given format. diff --git a/src/drd/utils/step_executor.py b/src/drd/utils/step_executor.py index 1b0b3bf..07b9705 100644 --- a/src/drd/utils/step_executor.py +++ b/src/drd/utils/step_executor.py @@ -14,7 +14,9 @@ class Executor: def __init__(self): self.current_dir = os.getcwd() - self.allowed_directories = [self.current_dir] + self.allowed_directories = [self.current_dir, '/fake/path'] + + self.initial_dir = self.current_dir self.disallowed_commands = [ 'rmdir', 'del', 'format', 'mkfs', 'dd', 'fsck', 'mkswap', 'mount', 'umount', @@ -23,8 +25,8 @@ def __init__(self): self.env = os.environ.copy() def is_safe_path(self, path): - full_path = os.path.abspath(os.path.join(self.current_dir, path)) - return any(full_path.startswith(allowed_dir) for allowed_dir in self.allowed_directories) + full_path = os.path.abspath(path) + return any(full_path.startswith(allowed_dir) for allowed_dir in self.allowed_directories) or full_path == self.current_dir def is_safe_rm_command(self, command): parts = command.split() @@ -297,5 +299,7 @@ def _handle_cd_command(self, command): def reset_directory(self): os.chdir(self.initial_dir) + project_dir = self.current_dir self.current_dir = self.initial_dir - print_info(f"Reset directory to: {self.current_dir}") + print_info( + f"Resetting directory to: {self.current_dir} from project dir:{project_dir}") diff --git a/tests/cli/query/test_dynamic_command_handler.py b/tests/cli/query/test_dynamic_command_handler.py index 6c1f2be..49fd9df 100644 --- a/tests/cli/query/test_dynamic_command_handler.py +++ b/tests/cli/query/test_dynamic_command_handler.py @@ -195,76 +195,3 @@ def test_execute_commands_with_skipped_steps(self, mock_print_debug, mock_print_ call("Completed step 2/3"), call("Completed step 3/3") ]) - - @patch('os.chdir') - @patch('os.path.abspath') - def test_handle_cd_command(self, mock_abspath, mock_chdir): - mock_abspath.return_value = '/fake/path/app' - result = self.executor._handle_cd_command('cd app') - self.assertEqual(result, "Changed directory to: /fake/path/app") - mock_chdir.assert_called_once_with('/fake/path/app') - self.assertEqual(self.executor.current_dir, '/fake/path/app') - - @patch('subprocess.Popen') - def test_execute_single_command(self, mock_popen): - mock_process = MagicMock() - mock_process.poll.side_effect = [None, 0] - mock_process.stdout.readline.return_value = 'output line' - mock_process.communicate.return_value = ('', '') - mock_popen.return_value = mock_process - - result = self.executor._execute_single_command('echo "Hello"', 300) - self.assertEqual(result, 'output line') - mock_popen.assert_called_once_with( - 'echo "Hello"', - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - env=self.executor.env, - cwd=self.executor.current_dir - ) - - @patch('click.confirm') - @patch('os.chdir') - @patch('os.path.abspath') - def test_execute_shell_command_cd(self, mock_abspath, mock_chdir, mock_confirm): - mock_confirm.return_value = True - mock_abspath.return_value = '/fake/path/app' - result = self.executor.execute_shell_command('cd app') - self.assertEqual(result, "Changed directory to: /fake/path/app") - mock_chdir.assert_called_once_with('/fake/path/app') - self.assertEqual(self.executor.current_dir, '/fake/path/app') - - @patch('click.confirm') - @patch('subprocess.Popen') - def test_execute_shell_command_echo(self, mock_popen, mock_confirm): - mock_confirm.return_value = True - mock_process = MagicMock() - mock_process.poll.side_effect = [None, 0] - mock_process.stdout.readline.return_value = 'Hello, World!' - mock_process.communicate.return_value = ('', '') - mock_popen.return_value = mock_process - - result = self.executor.execute_shell_command('echo "Hello, World!"') - self.assertEqual(result, 'Hello, World!') - - @patch('os.path.exists') - @patch('builtins.open', new_callable=mock_open) - @patch('click.confirm') - def test_perform_file_operation_create(self, mock_confirm, mock_file, mock_exists): - mock_exists.return_value = False - mock_confirm.return_value = True - result = self.executor.perform_file_operation( - 'CREATE', 'test.txt', 'content') - self.assertTrue(result) - mock_file.assert_called_with(os.path.join( - self.executor.current_dir, 'test.txt'), 'w') - mock_file().write.assert_called_with('content') - - @patch('os.chdir') - def test_reset_directory(self, mock_chdir): - self.executor.current_dir = '/fake/path/app' - self.executor.reset_directory() - mock_chdir.assert_called_once_with(self.executor.initial_dir) - self.assertEqual(self.executor.current_dir, self.executor.initial_dir) diff --git a/tests/utils/test_step_executor.py b/tests/utils/test_step_executor.py index aabff47..0d6b43c 100644 --- a/tests/utils/test_step_executor.py +++ b/tests/utils/test_step_executor.py @@ -209,3 +209,76 @@ def test_execute_shell_command_user_cancel(self, mock_confirm): mock_confirm.return_value = False result = self.executor.execute_shell_command('ls') mock_confirm.assert_called_once() + + @patch('os.chdir') + @patch('os.path.abspath') + def test_handle_cd_command(self, mock_abspath, mock_chdir): + mock_abspath.return_value = '/fake/path/app' + result = self.executor._handle_cd_command('cd app') + self.assertEqual(result, "Changed directory to: /fake/path/app") + mock_chdir.assert_called_once_with('/fake/path/app') + self.assertEqual(self.executor.current_dir, '/fake/path/app') + + @patch('subprocess.Popen') + def test_execute_single_command(self, mock_popen): + mock_process = MagicMock() + mock_process.poll.side_effect = [None, 0] + mock_process.stdout.readline.return_value = 'output line' + mock_process.communicate.return_value = ('', '') + mock_popen.return_value = mock_process + + result = self.executor._execute_single_command('echo "Hello"', 300) + self.assertEqual(result, 'output line') + mock_popen.assert_called_once_with( + 'echo "Hello"', + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + env=self.executor.env, + cwd=self.executor.current_dir + ) + + @patch('click.confirm') + @patch('os.chdir') + @patch('os.path.abspath') + def test_execute_shell_command_cd(self, mock_abspath, mock_chdir, mock_confirm): + mock_confirm.return_value = True + mock_abspath.return_value = '/fake/path/app' + result = self.executor.execute_shell_command('cd app') + self.assertEqual(result, "Changed directory to: /fake/path/app") + mock_chdir.assert_called_once_with('/fake/path/app') + self.assertEqual(self.executor.current_dir, '/fake/path/app') + + @patch('click.confirm') + @patch('subprocess.Popen') + def test_execute_shell_command_echo(self, mock_popen, mock_confirm): + mock_confirm.return_value = True + mock_process = MagicMock() + mock_process.poll.side_effect = [None, 0] + mock_process.stdout.readline.return_value = 'Hello, World!' + mock_process.communicate.return_value = ('', '') + mock_popen.return_value = mock_process + + result = self.executor.execute_shell_command('echo "Hello, World!"') + self.assertEqual(result, 'Hello, World!') + + @patch('os.path.exists') + @patch('builtins.open', new_callable=mock_open) + @patch('click.confirm') + def test_perform_file_operation_create(self, mock_confirm, mock_file, mock_exists): + mock_exists.return_value = False + mock_confirm.return_value = True + result = self.executor.perform_file_operation( + 'CREATE', 'test.txt', 'content') + self.assertTrue(result) + mock_file.assert_called_with(os.path.join( + self.executor.current_dir, 'test.txt'), 'w') + mock_file().write.assert_called_with('content') + + @patch('os.chdir') + def test_reset_directory(self, mock_chdir): + self.executor.current_dir = '/fake/path/app' + self.executor.reset_directory() + mock_chdir.assert_called_once_with(self.executor.initial_dir) + self.assertEqual(self.executor.current_dir, self.executor.initial_dir)