auto-coder 0.1.398__py3-none-any.whl → 0.1.400__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of auto-coder might be problematic. Click here for more details.

Files changed (86) hide show
  1. auto_coder-0.1.400.dist-info/METADATA +396 -0
  2. {auto_coder-0.1.398.dist-info → auto_coder-0.1.400.dist-info}/RECORD +82 -29
  3. {auto_coder-0.1.398.dist-info → auto_coder-0.1.400.dist-info}/WHEEL +1 -1
  4. {auto_coder-0.1.398.dist-info → auto_coder-0.1.400.dist-info}/entry_points.txt +2 -0
  5. autocoder/agent/base_agentic/base_agent.py +2 -2
  6. autocoder/agent/base_agentic/tools/replace_in_file_tool_resolver.py +1 -1
  7. autocoder/agent/entry_command_agent/__init__.py +29 -0
  8. autocoder/agent/entry_command_agent/auto_tool.py +61 -0
  9. autocoder/agent/entry_command_agent/chat.py +475 -0
  10. autocoder/agent/entry_command_agent/designer.py +53 -0
  11. autocoder/agent/entry_command_agent/generate_command.py +50 -0
  12. autocoder/agent/entry_command_agent/project_reader.py +58 -0
  13. autocoder/agent/entry_command_agent/voice2text.py +71 -0
  14. autocoder/auto_coder.py +23 -548
  15. autocoder/auto_coder_runner.py +511 -8
  16. autocoder/chat/rules_command.py +1 -1
  17. autocoder/chat_auto_coder.py +6 -1
  18. autocoder/common/ac_style_command_parser/__init__.py +15 -0
  19. autocoder/common/ac_style_command_parser/example.py +7 -0
  20. autocoder/{command_parser.py → common/ac_style_command_parser/parser.py} +28 -45
  21. autocoder/common/ac_style_command_parser/test_parser.py +516 -0
  22. autocoder/common/auto_coder_lang.py +78 -0
  23. autocoder/common/command_completer_v2.py +1 -1
  24. autocoder/common/command_file_manager/examples.py +22 -8
  25. autocoder/common/command_file_manager/manager.py +37 -6
  26. autocoder/common/conversations/get_conversation_manager.py +143 -0
  27. autocoder/common/conversations/manager.py +122 -11
  28. autocoder/common/conversations/storage/index_manager.py +89 -0
  29. autocoder/common/pull_requests/__init__.py +256 -0
  30. autocoder/common/pull_requests/base_provider.py +191 -0
  31. autocoder/common/pull_requests/config.py +66 -0
  32. autocoder/common/pull_requests/example.py +1 -0
  33. autocoder/common/pull_requests/exceptions.py +46 -0
  34. autocoder/common/pull_requests/manager.py +201 -0
  35. autocoder/common/pull_requests/models.py +164 -0
  36. autocoder/common/pull_requests/providers/__init__.py +23 -0
  37. autocoder/common/pull_requests/providers/gitcode_provider.py +19 -0
  38. autocoder/common/pull_requests/providers/gitee_provider.py +20 -0
  39. autocoder/common/pull_requests/providers/github_provider.py +214 -0
  40. autocoder/common/pull_requests/providers/gitlab_provider.py +29 -0
  41. autocoder/common/pull_requests/test_module.py +1 -0
  42. autocoder/common/pull_requests/utils.py +344 -0
  43. autocoder/common/tokens/__init__.py +62 -0
  44. autocoder/common/tokens/counter.py +211 -0
  45. autocoder/common/tokens/file_detector.py +105 -0
  46. autocoder/common/tokens/filters.py +111 -0
  47. autocoder/common/tokens/models.py +28 -0
  48. autocoder/common/v2/agent/agentic_edit.py +312 -85
  49. autocoder/common/v2/agent/agentic_edit_types.py +11 -0
  50. autocoder/common/v2/code_auto_generate_editblock.py +10 -2
  51. autocoder/dispacher/__init__.py +10 -0
  52. autocoder/rags.py +0 -27
  53. autocoder/run_context.py +1 -0
  54. autocoder/sdk/__init__.py +188 -0
  55. autocoder/sdk/cli/__init__.py +15 -0
  56. autocoder/sdk/cli/__main__.py +26 -0
  57. autocoder/sdk/cli/completion_wrapper.py +38 -0
  58. autocoder/sdk/cli/formatters.py +211 -0
  59. autocoder/sdk/cli/handlers.py +175 -0
  60. autocoder/sdk/cli/install_completion.py +301 -0
  61. autocoder/sdk/cli/main.py +286 -0
  62. autocoder/sdk/cli/options.py +73 -0
  63. autocoder/sdk/constants.py +102 -0
  64. autocoder/sdk/core/__init__.py +20 -0
  65. autocoder/sdk/core/auto_coder_core.py +880 -0
  66. autocoder/sdk/core/bridge.py +500 -0
  67. autocoder/sdk/example.py +0 -0
  68. autocoder/sdk/exceptions.py +72 -0
  69. autocoder/sdk/models/__init__.py +19 -0
  70. autocoder/sdk/models/messages.py +209 -0
  71. autocoder/sdk/models/options.py +196 -0
  72. autocoder/sdk/models/responses.py +311 -0
  73. autocoder/sdk/session/__init__.py +32 -0
  74. autocoder/sdk/session/session.py +106 -0
  75. autocoder/sdk/session/session_manager.py +56 -0
  76. autocoder/sdk/utils/__init__.py +24 -0
  77. autocoder/sdk/utils/formatters.py +216 -0
  78. autocoder/sdk/utils/io_utils.py +302 -0
  79. autocoder/sdk/utils/validators.py +287 -0
  80. autocoder/version.py +2 -1
  81. auto_coder-0.1.398.dist-info/METADATA +0 -111
  82. autocoder/common/conversations/compatibility.py +0 -303
  83. autocoder/common/conversations/conversation_manager.py +0 -502
  84. autocoder/common/conversations/example.py +0 -152
  85. {auto_coder-0.1.398.dist-info → auto_coder-0.1.400.dist-info/licenses}/LICENSE +0 -0
  86. {auto_coder-0.1.398.dist-info → auto_coder-0.1.400.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
1
- from typing import Dict, List, Tuple, Any, Optional
2
1
  import re
2
+ from typing import Dict, List, Tuple, Any, Optional
3
3
 
4
4
 
5
5
  class CommandParser:
@@ -22,8 +22,8 @@ class CommandParser:
22
22
  # 匹配命令的正则表达式 - 必须是以/开头,后跟单词字符,且不能后跟/或.
23
23
  # (?<!\S) 确保命令前是字符串开头或空白字符
24
24
  self.command_pattern = r'(?<!\S)/(\w+)(?!/|\.)'
25
- # 匹配键值对参数的正则表达式,支持带引号的值
26
- self.key_value_pattern = r'(\w+)=(?:"([^"]*?)"|\'([^\']*?)\'|([^\s"\']+))(?:\s|$)'
25
+ # 匹配键值对参数的正则表达式,支持带引号的值(包括三重引号)
26
+ self.key_value_pattern = r'(\w+)=(?:\'\'\'([^\']*?)\'\'\'|"""([^"]*?)"""|"([^"]*?)"|\'([^\']*?)\'|([^\s"\']*))(?:\s|$)'
27
27
  # 匹配路径模式的正则表达式
28
28
  self.path_pattern = r'/\w+(?:/[^/\s]+)+'
29
29
 
@@ -119,8 +119,9 @@ class CommandParser:
119
119
  if key_value_pairs:
120
120
  for match in key_value_pairs:
121
121
  key = match[0]
122
- # 值可能在三个捕获组中的一个,取非空的那个
123
- value = match[1] or match[2] or match[3]
122
+ # 值可能在六个捕获组中的一个,取非空的那个
123
+ # match[1]: 三重单引号, match[2]: 三重双引号, match[3]: 双引号, match[4]: 单引号, match[5]: 无引号
124
+ value = match[1] or match[2] or match[3] or match[4] or match[5]
124
125
  kwargs[key] = value.strip()
125
126
 
126
127
  # 替换带引号的键值对
@@ -131,15 +132,22 @@ class CommandParser:
131
132
 
132
133
  # 现在 processed_params_str 中应该只剩下位置参数
133
134
 
134
- # 处理带引号的位置参数
135
- quote_pattern = r'(?:"([^"]*?)"|\'([^\']*?)\')'
135
+ # 处理带引号的位置参数(包括三重引号)
136
+ quote_pattern = r'(?:\'\'\'([^\']*?)\'\'\'|"""([^"]*?)"""|"([^"]*?)"|\'([^\']*?)\')'
136
137
  quoted_args = re.findall(quote_pattern, processed_params_str)
137
138
  for quoted_arg in quoted_args:
138
139
  # 取非空的那个捕获组
139
- arg = quoted_arg[0] or quoted_arg[1]
140
+ arg = quoted_arg[0] or quoted_arg[1] or quoted_arg[2] or quoted_arg[3]
140
141
  args.append(arg)
141
142
  # 从参数字符串中移除这个带引号的参数
142
- quoted_pattern = f'"{arg}"' if quoted_arg[0] else f"'{arg}'"
143
+ if quoted_arg[0]: # 三重单引号
144
+ quoted_pattern = f"'''{arg}'''"
145
+ elif quoted_arg[1]: # 三重双引号
146
+ quoted_pattern = f'"""{arg}"""'
147
+ elif quoted_arg[2]: # 双引号
148
+ quoted_pattern = f'"{arg}"'
149
+ else: # 单引号
150
+ quoted_pattern = f"'{arg}'"
143
151
  processed_params_str = processed_params_str.replace(quoted_pattern, "", 1).strip()
144
152
 
145
153
  # 分割剩余的位置参数(不带引号的)
@@ -148,17 +156,24 @@ class CommandParser:
148
156
  else:
149
157
  # 如果没有键值对,处理所有参数作为位置参数
150
158
 
151
- # 处理带引号的位置参数
152
- quote_pattern = r'(?:"([^"]*?)"|\'([^\']*?)\')'
159
+ # 处理带引号的位置参数(包括三重引号)
160
+ quote_pattern = r'(?:\'\'\'([^\']*?)\'\'\'|"""([^"]*?)"""|"([^"]*?)"|\'([^\']*?)\')'
153
161
  quoted_args = re.findall(quote_pattern, params_str)
154
162
  processed_params_str = params_str
155
163
 
156
164
  for quoted_arg in quoted_args:
157
165
  # 取非空的那个捕获组
158
- arg = quoted_arg[0] or quoted_arg[1]
166
+ arg = quoted_arg[0] or quoted_arg[1] or quoted_arg[2] or quoted_arg[3]
159
167
  args.append(arg)
160
168
  # 从参数字符串中移除这个带引号的参数
161
- quoted_pattern = f'"{arg}"' if quoted_arg[0] else f"'{arg}'"
169
+ if quoted_arg[0]: # 三重单引号
170
+ quoted_pattern = f"'''{arg}'''"
171
+ elif quoted_arg[1]: # 三重双引号
172
+ quoted_pattern = f'"""{arg}"""'
173
+ elif quoted_arg[2]: # 双引号
174
+ quoted_pattern = f'"{arg}"'
175
+ else: # 单引号
176
+ quoted_pattern = f"'{arg}'"
162
177
  processed_params_str = processed_params_str.replace(quoted_pattern, "", 1).strip()
163
178
 
164
179
  # 分割剩余的位置参数(不带引号的)
@@ -246,35 +261,3 @@ def get_command_kwargs(query: str, command: str) -> Dict[str, str]:
246
261
  if command_info:
247
262
  return command_info['kwargs']
248
263
  return {}
249
-
250
-
251
- # 示例用法
252
- if __name__ == "__main__":
253
- # 测试各种格式的查询
254
- test_queries = [
255
- "/learn hello world /commit 123456",
256
- "/learn /commit 123456",
257
- "/learn /commit commit_id=123456",
258
- "/learn msg=hello /commit commit_id=123456",
259
- "/learn hello key=value /commit",
260
- # 带引号的值
261
- '/learn msg="hello world" /commit message="Fix bug #123"',
262
- "/learn 'quoted arg' key='value with spaces' /commit",
263
- # 路径参数测试
264
- "/learn /path/to/file.txt",
265
- "/commit message='Added /path/to/file.txt'",
266
- "Check /path/to/file.txt and also /another/path/file.md",
267
- "/clone /path/to/repo /checkout branch",
268
- "Use the file at /usr/local/bin/python with /learn"
269
- ]
270
-
271
- for query in test_queries:
272
- print(f"\nQuery: {query}")
273
- result = parse_query(query)
274
- print(f"Parsed: {result}")
275
-
276
- if has_command(query, "commit"):
277
- args = get_command_args(query, "commit")
278
- kwargs = get_command_kwargs(query, "commit")
279
- print(f"Commit args: {args}")
280
- print(f"Commit kwargs: {kwargs}")
@@ -0,0 +1,516 @@
1
+
2
+ import pytest
3
+ from typing import Dict, List, Any
4
+
5
+ # 导入被测模块
6
+ from .parser import (
7
+ CommandParser,
8
+ parse_query,
9
+ has_command,
10
+ get_command_args,
11
+ get_command_kwargs
12
+ )
13
+
14
+
15
+ class TestCommandParser:
16
+ """CommandParser类的单元测试"""
17
+
18
+ @pytest.fixture
19
+ def parser(self):
20
+ """提供CommandParser实例"""
21
+ return CommandParser()
22
+
23
+ def test_init(self, parser):
24
+ """测试CommandParser初始化"""
25
+ assert parser is not None
26
+ assert hasattr(parser, 'command_pattern')
27
+ assert hasattr(parser, 'key_value_pattern')
28
+ assert hasattr(parser, 'path_pattern')
29
+
30
+ def test_empty_query(self, parser):
31
+ """测试空查询字符串"""
32
+ assert parser.parse("") == {}
33
+ assert parser.parse(" ") == {}
34
+ assert parser.parse(None) == {}
35
+
36
+ def test_single_command_no_args(self, parser):
37
+ """测试单个命令无参数"""
38
+ result = parser.parse("/help")
39
+ expected = {
40
+ "help": {
41
+ "args": [],
42
+ "kwargs": {}
43
+ }
44
+ }
45
+ assert result == expected
46
+
47
+ def test_single_command_with_args(self, parser):
48
+ """测试单个命令带位置参数"""
49
+ result = parser.parse("/add file1.txt file2.py")
50
+ expected = {
51
+ "add": {
52
+ "args": ["file1.txt", "file2.py"],
53
+ "kwargs": {}
54
+ }
55
+ }
56
+ assert result == expected
57
+
58
+ def test_single_command_with_kwargs(self, parser):
59
+ """测试单个命令带键值对参数"""
60
+ result = parser.parse("/config model=gpt-4 temperature=0.7")
61
+ expected = {
62
+ "config": {
63
+ "args": [],
64
+ "kwargs": {
65
+ "model": "gpt-4",
66
+ "temperature": "0.7"
67
+ }
68
+ }
69
+ }
70
+ assert result == expected
71
+
72
+ def test_single_command_mixed_params(self, parser):
73
+ """测试单个命令混合参数"""
74
+ result = parser.parse("/deploy myapp env=prod version=1.0")
75
+ expected = {
76
+ "deploy": {
77
+ "args": ["myapp"],
78
+ "kwargs": {
79
+ "env": "prod",
80
+ "version": "1.0"
81
+ }
82
+ }
83
+ }
84
+ assert result == expected
85
+
86
+ def test_quoted_args_double_quotes(self, parser):
87
+ """测试双引号参数"""
88
+ result = parser.parse('/say "hello world" "this is a test"')
89
+ expected = {
90
+ "say": {
91
+ "args": ["hello world", "this is a test"],
92
+ "kwargs": {}
93
+ }
94
+ }
95
+ assert result == expected
96
+
97
+ def test_quoted_args_single_quotes(self, parser):
98
+ """测试单引号参数"""
99
+ result = parser.parse("/say 'hello world' 'this is a test'")
100
+ expected = {
101
+ "say": {
102
+ "args": ["hello world", "this is a test"],
103
+ "kwargs": {}
104
+ }
105
+ }
106
+ assert result == expected
107
+
108
+ def test_quoted_kwargs(self, parser):
109
+ """测试带引号的键值对参数"""
110
+ result = parser.parse('/config message="hello world" path=\'/tmp/test dir\'')
111
+ expected = {
112
+ "config": {
113
+ "args": [],
114
+ "kwargs": {
115
+ "message": "hello world",
116
+ "path": "/tmp/test dir"
117
+ }
118
+ }
119
+ }
120
+ assert result == expected
121
+
122
+ def test_multiple_commands(self, parser):
123
+ """测试多个命令"""
124
+ result = parser.parse("/add file1.txt /remove file2.txt")
125
+ expected = {
126
+ "add": {
127
+ "args": ["file1.txt"],
128
+ "kwargs": {}
129
+ },
130
+ "remove": {
131
+ "args": ["file2.txt"],
132
+ "kwargs": {}
133
+ }
134
+ }
135
+ assert result == expected
136
+
137
+ def test_multiple_commands_with_mixed_params(self, parser):
138
+ """测试多个命令带混合参数"""
139
+ result = parser.parse("/deploy app1 env=prod /config model=gpt-4 /status")
140
+ expected = {
141
+ "deploy": {
142
+ "args": ["app1"],
143
+ "kwargs": {"env": "prod"}
144
+ },
145
+ "config": {
146
+ "args": [],
147
+ "kwargs": {"model": "gpt-4"}
148
+ },
149
+ "status": {
150
+ "args": [],
151
+ "kwargs": {}
152
+ }
153
+ }
154
+ assert result == expected
155
+
156
+ def test_path_not_recognized_as_command(self, parser):
157
+ """测试路径不被识别为命令"""
158
+ result = parser.parse("/add /path/to/file.txt /config model=gpt-4")
159
+ expected = {
160
+ "add": {
161
+ "args": ["/path/to/file.txt"],
162
+ "kwargs": {}
163
+ },
164
+ "config": {
165
+ "args": [],
166
+ "kwargs": {"model": "gpt-4"}
167
+ }
168
+ }
169
+ assert result == expected
170
+
171
+ def test_complex_path_handling(self, parser):
172
+ """测试复杂路径处理"""
173
+ result = parser.parse("/analyze /home/user/project/src/main.py /output /tmp/results")
174
+ expected = {
175
+ "analyze": {
176
+ "args": ["/home/user/project/src/main.py"],
177
+ "kwargs": {}
178
+ },
179
+ "output": {
180
+ "args": ["/tmp/results"],
181
+ "kwargs": {}
182
+ }
183
+ }
184
+ assert result == expected
185
+
186
+ def test_command_with_dots_not_recognized(self, parser):
187
+ """测试带点的字符串不被识别为命令"""
188
+ result = parser.parse("check /config.json /setup.py")
189
+ # 不以/开头,所以没有命令被识别
190
+ assert result == {}
191
+
192
+ def test_parse_command_existing(self, parser):
193
+ """测试解析特定存在的命令"""
194
+ query = "/add file1.txt /remove file2.txt mode=force"
195
+ result = parser.parse_command(query, "remove")
196
+ expected = {
197
+ "args": ["file2.txt"],
198
+ "kwargs": {"mode": "force"}
199
+ }
200
+ assert result == expected
201
+
202
+ def test_parse_command_nonexistent(self, parser):
203
+ """测试解析不存在的命令"""
204
+ query = "/add file1.txt /remove file2.txt"
205
+ result = parser.parse_command(query, "deploy")
206
+ assert result is None
207
+
208
+ def test_edge_case_empty_kwargs_value(self, parser):
209
+ """测试空的键值对值"""
210
+ result = parser.parse("/config key1= key2=value")
211
+ expected = {
212
+ "config": {
213
+ "args": [],
214
+ "kwargs": {
215
+ "key1": "",
216
+ "key2": "value"
217
+ }
218
+ }
219
+ }
220
+ assert result == expected
221
+
222
+ def test_edge_case_special_characters_in_values(self, parser):
223
+ """测试值中包含特殊字符"""
224
+ result = parser.parse("/config url=https://api.example.com/v1 pattern='*.py'")
225
+ expected = {
226
+ "config": {
227
+ "args": [],
228
+ "kwargs": {
229
+ "url": "https://api.example.com/v1",
230
+ "pattern": "*.py"
231
+ }
232
+ }
233
+ }
234
+ assert result == expected
235
+
236
+ def test_command_at_end_of_string(self, parser):
237
+ """测试字符串末尾的命令"""
238
+ result = parser.parse("some text /help")
239
+ expected = {
240
+ "help": {
241
+ "args": [],
242
+ "kwargs": {}
243
+ }
244
+ }
245
+ assert result == expected
246
+
247
+
248
+ class TestConvenienceFunctions:
249
+ """测试便捷函数"""
250
+
251
+ def test_parse_query_function(self):
252
+ """测试parse_query函数"""
253
+ result = parse_query("/add file1.txt mode=fast")
254
+ expected = {
255
+ "add": {
256
+ "args": ["file1.txt"],
257
+ "kwargs": {"mode": "fast"}
258
+ }
259
+ }
260
+ assert result == expected
261
+
262
+ def test_has_command_true(self):
263
+ """测试has_command函数 - 命令存在"""
264
+ assert has_command("/add file1.txt /remove file2.txt", "add") == True
265
+ assert has_command("/add file1.txt /remove file2.txt", "remove") == True
266
+
267
+ def test_has_command_false(self):
268
+ """测试has_command函数 - 命令不存在"""
269
+ assert has_command("/add file1.txt /remove file2.txt", "deploy") == False
270
+ assert has_command("no commands here", "add") == False
271
+ assert has_command("", "add") == False
272
+
273
+ def test_get_command_args_existing(self):
274
+ """测试get_command_args函数 - 命令存在"""
275
+ query = "/add file1.txt file2.py /remove file3.txt"
276
+ assert get_command_args(query, "add") == ["file1.txt", "file2.py"]
277
+ assert get_command_args(query, "remove") == ["file3.txt"]
278
+
279
+ def test_get_command_args_nonexistent(self):
280
+ """测试get_command_args函数 - 命令不存在"""
281
+ query = "/add file1.txt file2.py"
282
+ assert get_command_args(query, "remove") == []
283
+ assert get_command_args("", "add") == []
284
+
285
+ def test_get_command_kwargs_existing(self):
286
+ """测试get_command_kwargs函数 - 命令存在"""
287
+ query = "/config model=gpt-4 temp=0.7 /deploy env=prod"
288
+ assert get_command_kwargs(query, "config") == {"model": "gpt-4", "temp": "0.7"}
289
+ assert get_command_kwargs(query, "deploy") == {"env": "prod"}
290
+
291
+ def test_get_command_kwargs_nonexistent(self):
292
+ """测试get_command_kwargs函数 - 命令不存在"""
293
+ query = "/config model=gpt-4"
294
+ assert get_command_kwargs(query, "deploy") == {}
295
+ assert get_command_kwargs("", "config") == {}
296
+
297
+
298
+ class TestComplexScenarios:
299
+ """测试复杂场景"""
300
+
301
+ def test_real_world_scenario_1(self):
302
+ """测试真实场景1:文件操作命令"""
303
+ query = '/add "src/main.py" "tests/test_main.py" /config model="gpt-4" temperature=0.7 /deploy env=production version="1.2.3"'
304
+ result = parse_query(query)
305
+
306
+ expected = {
307
+ "add": {
308
+ "args": ["src/main.py", "tests/test_main.py"],
309
+ "kwargs": {}
310
+ },
311
+ "config": {
312
+ "args": [],
313
+ "kwargs": {
314
+ "model": "gpt-4",
315
+ "temperature": "0.7"
316
+ }
317
+ },
318
+ "deploy": {
319
+ "args": [],
320
+ "kwargs": {
321
+ "env": "production",
322
+ "version": "1.2.3"
323
+ }
324
+ }
325
+ }
326
+ assert result == expected
327
+
328
+ def test_real_world_scenario_2(self):
329
+ """测试真实场景2:包含路径的复杂命令"""
330
+ query = "/analyze /home/user/project/src /output /tmp/analysis.json format=json verbose=true"
331
+ result = parse_query(query)
332
+
333
+ expected = {
334
+ "analyze": {
335
+ "args": ["/home/user/project/src"],
336
+ "kwargs": {}
337
+ },
338
+ "output": {
339
+ "args": ["/tmp/analysis.json"],
340
+ "kwargs": {
341
+ "format": "json",
342
+ "verbose": "true"
343
+ }
344
+ }
345
+ }
346
+ assert result == expected
347
+
348
+ def test_real_world_scenario_3(self):
349
+ """测试真实场景3:混合引号和特殊字符"""
350
+ query = """/search pattern="*.py" path='/home/user/My Documents/project' /filter exclude="__pycache__" """
351
+ result = parse_query(query)
352
+
353
+ expected = {
354
+ "search": {
355
+ "args": [],
356
+ "kwargs": {
357
+ "pattern": "*.py",
358
+ "path": "/home/user/My Documents/project"
359
+ }
360
+ },
361
+ "filter": {
362
+ "args": [],
363
+ "kwargs": {
364
+ "exclude": "__pycache__"
365
+ }
366
+ }
367
+ }
368
+ assert result == expected
369
+
370
+ def test_edge_case_consecutive_commands(self):
371
+ """测试连续命令"""
372
+ query = "/start /stop /restart mode=fast"
373
+ result = parse_query(query)
374
+
375
+ expected = {
376
+ "start": {
377
+ "args": [],
378
+ "kwargs": {}
379
+ },
380
+ "stop": {
381
+ "args": [],
382
+ "kwargs": {}
383
+ },
384
+ "restart": {
385
+ "args": [],
386
+ "kwargs": {"mode": "fast"}
387
+ }
388
+ }
389
+ assert result == expected
390
+
391
+ def test_edge_case_command_with_numbers_and_underscores(self):
392
+ """测试包含数字和下划线的命令"""
393
+ query = "/test_case_1 /deploy_v2 app_name=test123"
394
+ result = parse_query(query)
395
+
396
+ expected = {
397
+ "test_case_1": {
398
+ "args": [],
399
+ "kwargs": {}
400
+ },
401
+ "deploy_v2": {
402
+ "args": [],
403
+ "kwargs": {"app_name": "test123"}
404
+ }
405
+ }
406
+ assert result == expected
407
+
408
+
409
+ class TestErrorHandling:
410
+ """测试错误处理和边界情况"""
411
+
412
+ def test_malformed_quotes(self):
413
+ """测试格式错误的引号"""
414
+ # 这些应该仍能部分解析,不会抛出异常
415
+ parser = CommandParser()
416
+
417
+ # 未闭合的引号 - 应该被当作普通参数处理
418
+ result = parser.parse('/test "unclosed quote arg')
419
+ assert "test" in result
420
+
421
+ # 混合引号
422
+ result = parser.parse('/test "mixed\' quotes"')
423
+ assert "test" in result
424
+
425
+ def test_special_characters_in_commands(self):
426
+ """测试命令中的特殊字符"""
427
+ parser = CommandParser()
428
+
429
+ # 命令名只能包含单词字符,所以这些不会被识别为命令
430
+ result = parser.parse("/test-command /test.command")
431
+ assert result == {}
432
+
433
+ def test_whitespace_handling(self):
434
+ """测试空白字符处理"""
435
+ parser = CommandParser()
436
+
437
+ query = " /add file1.txt file2.py key=value "
438
+ result = parser.parse(query)
439
+
440
+ expected = {
441
+ "add": {
442
+ "args": ["file1.txt", "file2.py"],
443
+ "kwargs": {"key": "value"}
444
+ }
445
+ }
446
+ assert result == expected
447
+
448
+ def test_unicode_characters(self):
449
+ """测试Unicode字符"""
450
+ parser = CommandParser()
451
+
452
+ query = '/test "中文参数" key="中文值"'
453
+ result = parser.parse(query)
454
+
455
+ expected = {
456
+ "test": {
457
+ "args": ["中文参数"],
458
+ "kwargs": {"key": "中文值"}
459
+ }
460
+ }
461
+ assert result == expected
462
+
463
+ def test__command_with_path(self, parser):
464
+ """测试单个命令混合参数"""
465
+ result = parser.parse('/command "tdd/hello.md" name="威廉"')
466
+ expected = {
467
+ "command": {
468
+ "args": ["tdd/hello.md"],
469
+ "kwargs": {
470
+ "name": "威廉"
471
+ }
472
+ }
473
+ }
474
+ assert result == expected
475
+
476
+
477
+ # 参数化测试用例
478
+ @pytest.mark.parametrize("query,expected_commands", [
479
+ ("/help", ["help"]),
480
+ ("/add /remove", ["add", "remove"]),
481
+ ("/config model=gpt-4", ["config"]),
482
+ ("no commands", []),
483
+ ("/start /stop /restart", ["start", "stop", "restart"]),
484
+ ])
485
+ def test_command_detection_parametrized(query, expected_commands):
486
+ """参数化测试命令检测"""
487
+ result = parse_query(query)
488
+ actual_commands = list(result.keys())
489
+ assert actual_commands == expected_commands
490
+
491
+
492
+ @pytest.mark.parametrize("query,command,expected_args", [
493
+ ("/add file1 file2", "add", ["file1", "file2"]),
494
+ ("/remove", "remove", []),
495
+ ("/test arg1 'arg with spaces'", "test", ["arg1", "arg with spaces"]),
496
+ ("", "any", []),
497
+ ])
498
+ def test_get_args_parametrized(query, command, expected_args):
499
+ """参数化测试获取参数"""
500
+ assert get_command_args(query, command) == expected_args
501
+
502
+
503
+ @pytest.mark.parametrize("query,command,expected_kwargs", [
504
+ ("/config model=gpt-4 temp=0.7", "config", {"model": "gpt-4", "temp": "0.7"}),
505
+ ("/deploy", "deploy", {}),
506
+ ("/test key='value with spaces'", "test", {"key": "value with spaces"}),
507
+ ("", "any", {}),
508
+ ])
509
+ def test_get_kwargs_parametrized(query, command, expected_kwargs):
510
+ """参数化测试获取键值对参数"""
511
+ assert get_command_kwargs(query, command) == expected_kwargs
512
+
513
+
514
+ if __name__ == "__main__":
515
+ # 可以直接运行此文件进行测试
516
+ pytest.main([__file__, "-v"])