toolplane-python-client 0.1.0__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.
- toolplane/__init__.py +106 -0
- toolplane/common/__init__.py +93 -0
- toolplane/common/base_config.py +129 -0
- toolplane/common/base_connection_manager.py +171 -0
- toolplane/common/base_session_manager.py +321 -0
- toolplane/common/base_tool_manager.py +347 -0
- toolplane/common/constants.py +47 -0
- toolplane/common/utils.py +310 -0
- toolplane/core/__init__.py +67 -0
- toolplane/core/config.py +107 -0
- toolplane/core/connection.py +285 -0
- toolplane/core/errors.py +298 -0
- toolplane/core/machine.py +480 -0
- toolplane/core/request.py +775 -0
- toolplane/core/session.py +332 -0
- toolplane/core/session_context.py +514 -0
- toolplane/core/task.py +130 -0
- toolplane/core/tool.py +329 -0
- toolplane/http_core/__init__.py +37 -0
- toolplane/http_core/http_config.py +97 -0
- toolplane/http_core/http_connection.py +409 -0
- toolplane/http_core/http_machine.py +298 -0
- toolplane/http_core/http_request.py +748 -0
- toolplane/http_core/http_session.py +348 -0
- toolplane/http_core/http_session_context.py +491 -0
- toolplane/http_core/http_task.py +101 -0
- toolplane/http_core/http_tool.py +400 -0
- toolplane/interfaces/__init__.py +27 -0
- toolplane/interfaces/client_interface.py +122 -0
- toolplane/interfaces/connection_interface.py +193 -0
- toolplane/interfaces/event_interface.py +290 -0
- toolplane/interfaces/request_interface.py +439 -0
- toolplane/interfaces/session_interface.py +288 -0
- toolplane/interfaces/tool_interface.py +441 -0
- toolplane/proto/__init__.py +0 -0
- toolplane/proto/service_pb2.py +315 -0
- toolplane/proto/service_pb2_grpc.py +2240 -0
- toolplane/provider_cli.py +268 -0
- toolplane/provider_registry.py +77 -0
- toolplane/provider_runtime.py +302 -0
- toolplane/toolkits/__init__.py +0 -0
- toolplane/toolkits/standalone_tools/__init__.py +0 -0
- toolplane/toolkits/standalone_tools/create_directory.py +94 -0
- toolplane/toolkits/standalone_tools/create_file.py +124 -0
- toolplane/toolkits/standalone_tools/file_search.py +229 -0
- toolplane/toolkits/standalone_tools/grep_search.py +372 -0
- toolplane/toolkits/standalone_tools/launcher.py +146 -0
- toolplane/toolkits/standalone_tools/list_dir.py +395 -0
- toolplane/toolkits/standalone_tools/read_file.py +346 -0
- toolplane/toolkits/standalone_tools/replace_string_in_file.py +407 -0
- toolplane/toolkits/standalone_tools/run_tests.py +66 -0
- toolplane/toolkits/standalone_tools/semantic_search.py +485 -0
- toolplane/toolkits/standalone_tools/standalone_toolkit.py +979 -0
- toolplane/toolkits/standalone_tools/test_failure_analysis.py +618 -0
- toolplane/toolkits/standalone_tools/test_standalone_toolkit.py +517 -0
- toolplane/toolkits/swe/__init__.py +35 -0
- toolplane/toolkits/swe/create_directory.py +15 -0
- toolplane/toolkits/swe/create_file.py +15 -0
- toolplane/toolkits/swe/descriptions.py +273 -0
- toolplane/toolkits/swe/execute_bash.py +93 -0
- toolplane/toolkits/swe/file_editor.py +775 -0
- toolplane/toolkits/swe/file_search.py +16 -0
- toolplane/toolkits/swe/finish.py +50 -0
- toolplane/toolkits/swe/grep_search.py +19 -0
- toolplane/toolkits/swe/list_dir.py +407 -0
- toolplane/toolkits/swe/read_file.py +18 -0
- toolplane/toolkits/swe/replace_string_in_file.py +17 -0
- toolplane/toolkits/swe/search.py +260 -0
- toolplane/toolkits/swe/semantic_search.py +20 -0
- toolplane/toolkits/swe/str_replace_editor.py +647 -0
- toolplane/toolkits/swe/submit.py +29 -0
- toolplane/toolkits/swe/swe_toolkit.py +1296 -0
- toolplane/toolplane_client.py +686 -0
- toolplane/toolplane_http_client.py +681 -0
- toolplane/utils/__init__.py +3 -0
- toolplane/utils/schema.py +146 -0
- toolplane_python_client-0.1.0.dist-info/METADATA +543 -0
- toolplane_python_client-0.1.0.dist-info/RECORD +81 -0
- toolplane_python_client-0.1.0.dist-info/WHEEL +5 -0
- toolplane_python_client-0.1.0.dist-info/entry_points.txt +2 -0
- toolplane_python_client-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Description: List directory contents with enhanced features.
|
|
4
|
+
|
|
5
|
+
This tool lists the contents of a directory with various display options,
|
|
6
|
+
sorting capabilities, and filtering features.
|
|
7
|
+
|
|
8
|
+
Parameters:
|
|
9
|
+
path (string, required): The absolute path to the directory to list.
|
|
10
|
+
show_hidden (boolean, optional): Show hidden files and directories (default: False).
|
|
11
|
+
show_details (boolean, optional): Show detailed information like size and permissions (default: False).
|
|
12
|
+
sort_by (string, optional): Sort by 'name', 'size', 'modified', 'type' (default: 'name').
|
|
13
|
+
reverse_sort (boolean, optional): Reverse the sort order (default: False).
|
|
14
|
+
recursive (boolean, optional): List contents recursively (default: False).
|
|
15
|
+
max_depth (integer, optional): Maximum depth for recursive listing (default: 3).
|
|
16
|
+
file_filter (string, optional): Filter files by extension (e.g., '.py', '.txt').
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import os
|
|
21
|
+
import stat
|
|
22
|
+
import sys
|
|
23
|
+
import time
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any, Dict, List
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def list_dir(
|
|
29
|
+
path: str,
|
|
30
|
+
show_hidden: bool = False,
|
|
31
|
+
show_details: bool = False,
|
|
32
|
+
sort_by: str = "name",
|
|
33
|
+
reverse_sort: bool = False,
|
|
34
|
+
recursive: bool = False,
|
|
35
|
+
max_depth: int = 3,
|
|
36
|
+
file_filter: str = None,
|
|
37
|
+
) -> List[Dict[str, Any]]:
|
|
38
|
+
"""
|
|
39
|
+
List directory contents with enhanced features.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
path: The directory path to list
|
|
43
|
+
show_hidden: Show hidden files and directories
|
|
44
|
+
show_details: Show detailed information
|
|
45
|
+
sort_by: Sort by 'name', 'size', 'modified', 'type'
|
|
46
|
+
reverse_sort: Reverse the sort order
|
|
47
|
+
recursive: List contents recursively
|
|
48
|
+
max_depth: Maximum depth for recursive listing
|
|
49
|
+
file_filter: Filter files by extension
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
List of dictionaries containing file/directory information
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
dir_path = Path(path)
|
|
56
|
+
|
|
57
|
+
if not dir_path.exists():
|
|
58
|
+
print(f"Error: Directory '{path}' does not exist.", file=sys.stderr)
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
if not dir_path.is_dir():
|
|
62
|
+
print(f"Error: '{path}' is not a directory.", file=sys.stderr)
|
|
63
|
+
return []
|
|
64
|
+
|
|
65
|
+
results = []
|
|
66
|
+
|
|
67
|
+
if recursive:
|
|
68
|
+
results = _list_recursive(
|
|
69
|
+
dir_path, show_hidden, max_depth, file_filter, current_depth=0
|
|
70
|
+
)
|
|
71
|
+
else:
|
|
72
|
+
results = _list_single_dir(dir_path, show_hidden, file_filter)
|
|
73
|
+
|
|
74
|
+
# Add detailed information if requested
|
|
75
|
+
if show_details:
|
|
76
|
+
for result in results:
|
|
77
|
+
_add_detailed_info(result)
|
|
78
|
+
|
|
79
|
+
# Sort results
|
|
80
|
+
results = _sort_results(results, sort_by, reverse_sort)
|
|
81
|
+
|
|
82
|
+
return results
|
|
83
|
+
|
|
84
|
+
except PermissionError:
|
|
85
|
+
print(f"Error: Permission denied accessing '{path}'.", file=sys.stderr)
|
|
86
|
+
return []
|
|
87
|
+
except Exception as e:
|
|
88
|
+
print(f"Error listing directory '{path}': {e}", file=sys.stderr)
|
|
89
|
+
return []
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _list_single_dir(
|
|
93
|
+
dir_path: Path, show_hidden: bool, file_filter: str
|
|
94
|
+
) -> List[Dict[str, Any]]:
|
|
95
|
+
"""List contents of a single directory."""
|
|
96
|
+
results = []
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
for item in dir_path.iterdir():
|
|
100
|
+
# Skip hidden files if not requested
|
|
101
|
+
if not show_hidden and item.name.startswith("."):
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
# Apply file filter
|
|
105
|
+
if file_filter and item.is_file() and not item.name.endswith(file_filter):
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
stat_info = item.stat()
|
|
110
|
+
|
|
111
|
+
result = {
|
|
112
|
+
"name": item.name,
|
|
113
|
+
"path": str(item.resolve()),
|
|
114
|
+
"relative_path": str(item.relative_to(dir_path.parent)),
|
|
115
|
+
"is_file": item.is_file(),
|
|
116
|
+
"is_dir": item.is_dir(),
|
|
117
|
+
"is_symlink": item.is_symlink(),
|
|
118
|
+
"size": stat_info.st_size if item.is_file() else 0,
|
|
119
|
+
"modified": stat_info.st_mtime,
|
|
120
|
+
"modified_readable": time.strftime(
|
|
121
|
+
"%Y-%m-%d %H:%M:%S", time.localtime(stat_info.st_mtime)
|
|
122
|
+
),
|
|
123
|
+
"permissions": stat.filemode(stat_info.st_mode),
|
|
124
|
+
"depth": 0,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
results.append(result)
|
|
128
|
+
|
|
129
|
+
except (OSError, PermissionError):
|
|
130
|
+
# Skip items that can't be accessed
|
|
131
|
+
continue
|
|
132
|
+
|
|
133
|
+
except PermissionError:
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
return results
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _list_recursive(
|
|
140
|
+
dir_path: Path,
|
|
141
|
+
show_hidden: bool,
|
|
142
|
+
max_depth: int,
|
|
143
|
+
file_filter: str,
|
|
144
|
+
current_depth: int = 0,
|
|
145
|
+
) -> List[Dict[str, Any]]:
|
|
146
|
+
"""List directory contents recursively."""
|
|
147
|
+
results = []
|
|
148
|
+
|
|
149
|
+
if current_depth >= max_depth:
|
|
150
|
+
return results
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
for item in dir_path.iterdir():
|
|
154
|
+
# Skip hidden files if not requested
|
|
155
|
+
if not show_hidden and item.name.startswith("."):
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
# Apply file filter
|
|
159
|
+
if file_filter and item.is_file() and not item.name.endswith(file_filter):
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
stat_info = item.stat()
|
|
164
|
+
|
|
165
|
+
result = {
|
|
166
|
+
"name": item.name,
|
|
167
|
+
"path": str(item.resolve()),
|
|
168
|
+
"relative_path": str(item.relative_to(dir_path.parent)),
|
|
169
|
+
"is_file": item.is_file(),
|
|
170
|
+
"is_dir": item.is_dir(),
|
|
171
|
+
"is_symlink": item.is_symlink(),
|
|
172
|
+
"size": stat_info.st_size if item.is_file() else 0,
|
|
173
|
+
"modified": stat_info.st_mtime,
|
|
174
|
+
"modified_readable": time.strftime(
|
|
175
|
+
"%Y-%m-%d %H:%M:%S", time.localtime(stat_info.st_mtime)
|
|
176
|
+
),
|
|
177
|
+
"permissions": stat.filemode(stat_info.st_mode),
|
|
178
|
+
"depth": current_depth,
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
results.append(result)
|
|
182
|
+
|
|
183
|
+
# Recurse into subdirectories
|
|
184
|
+
if item.is_dir() and not item.is_symlink():
|
|
185
|
+
sub_results = _list_recursive(
|
|
186
|
+
item, show_hidden, max_depth, file_filter, current_depth + 1
|
|
187
|
+
)
|
|
188
|
+
results.extend(sub_results)
|
|
189
|
+
|
|
190
|
+
except (OSError, PermissionError):
|
|
191
|
+
# Skip items that can't be accessed
|
|
192
|
+
continue
|
|
193
|
+
|
|
194
|
+
except PermissionError:
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
return results
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _add_detailed_info(result: Dict[str, Any]) -> None:
|
|
201
|
+
"""Add detailed information to a result entry."""
|
|
202
|
+
try:
|
|
203
|
+
path = Path(result["path"])
|
|
204
|
+
stat_info = path.stat()
|
|
205
|
+
|
|
206
|
+
result.update(
|
|
207
|
+
{
|
|
208
|
+
"owner_readable": bool(stat_info.st_mode & stat.S_IRUSR),
|
|
209
|
+
"owner_writable": bool(stat_info.st_mode & stat.S_IWUSR),
|
|
210
|
+
"owner_executable": bool(stat_info.st_mode & stat.S_IXUSR),
|
|
211
|
+
"group_readable": bool(stat_info.st_mode & stat.S_IRGRP),
|
|
212
|
+
"group_writable": bool(stat_info.st_mode & stat.S_IWGRP),
|
|
213
|
+
"group_executable": bool(stat_info.st_mode & stat.S_IXGRP),
|
|
214
|
+
"other_readable": bool(stat_info.st_mode & stat.S_IROTH),
|
|
215
|
+
"other_writable": bool(stat_info.st_mode & stat.S_IWOTH),
|
|
216
|
+
"other_executable": bool(stat_info.st_mode & stat.S_IXOTH),
|
|
217
|
+
"inode": stat_info.st_ino,
|
|
218
|
+
"device": stat_info.st_dev,
|
|
219
|
+
"nlink": stat_info.st_nlink,
|
|
220
|
+
"uid": stat_info.st_uid,
|
|
221
|
+
"gid": stat_info.st_gid,
|
|
222
|
+
"accessed": stat_info.st_atime,
|
|
223
|
+
"accessed_readable": time.strftime(
|
|
224
|
+
"%Y-%m-%d %H:%M:%S", time.localtime(stat_info.st_atime)
|
|
225
|
+
),
|
|
226
|
+
"created": getattr(stat_info, "st_birthtime", stat_info.st_ctime),
|
|
227
|
+
"created_readable": time.strftime(
|
|
228
|
+
"%Y-%m-%d %H:%M:%S",
|
|
229
|
+
time.localtime(
|
|
230
|
+
getattr(stat_info, "st_birthtime", stat_info.st_ctime)
|
|
231
|
+
),
|
|
232
|
+
),
|
|
233
|
+
}
|
|
234
|
+
)
|
|
235
|
+
except (OSError, PermissionError):
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _sort_results(
|
|
240
|
+
results: List[Dict[str, Any]], sort_by: str, reverse_sort: bool
|
|
241
|
+
) -> List[Dict[str, Any]]:
|
|
242
|
+
"""Sort results by the specified criteria."""
|
|
243
|
+
if sort_by == "name":
|
|
244
|
+
results.sort(key=lambda x: x["name"].lower(), reverse=reverse_sort)
|
|
245
|
+
elif sort_by == "size":
|
|
246
|
+
results.sort(key=lambda x: x["size"], reverse=reverse_sort)
|
|
247
|
+
elif sort_by == "modified":
|
|
248
|
+
results.sort(key=lambda x: x["modified"], reverse=reverse_sort)
|
|
249
|
+
elif sort_by == "type":
|
|
250
|
+
# Sort by type (directories first, then files)
|
|
251
|
+
results.sort(
|
|
252
|
+
key=lambda x: (not x["is_dir"], x["name"].lower()), reverse=reverse_sort
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
return results
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def format_size(size_bytes: int) -> str:
|
|
259
|
+
"""Format file size in human-readable format."""
|
|
260
|
+
if size_bytes == 0:
|
|
261
|
+
return "0 B"
|
|
262
|
+
|
|
263
|
+
units = ["B", "KB", "MB", "GB", "TB"]
|
|
264
|
+
i = 0
|
|
265
|
+
while size_bytes >= 1024 and i < len(units) - 1:
|
|
266
|
+
size_bytes /= 1024
|
|
267
|
+
i += 1
|
|
268
|
+
|
|
269
|
+
return f"{size_bytes:.1f} {units[i]}"
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def main():
|
|
273
|
+
parser = argparse.ArgumentParser(
|
|
274
|
+
description="List directory contents with enhanced features."
|
|
275
|
+
)
|
|
276
|
+
parser.add_argument(
|
|
277
|
+
"path", type=str, help="The absolute path to the directory to list"
|
|
278
|
+
)
|
|
279
|
+
parser.add_argument(
|
|
280
|
+
"--show_hidden",
|
|
281
|
+
action="store_true",
|
|
282
|
+
default=False,
|
|
283
|
+
help="Show hidden files and directories (default: False)",
|
|
284
|
+
)
|
|
285
|
+
parser.add_argument(
|
|
286
|
+
"--show_details",
|
|
287
|
+
action="store_true",
|
|
288
|
+
default=False,
|
|
289
|
+
help="Show detailed information like size and permissions (default: False)",
|
|
290
|
+
)
|
|
291
|
+
parser.add_argument(
|
|
292
|
+
"--sort_by",
|
|
293
|
+
choices=["name", "size", "modified", "type"],
|
|
294
|
+
default="name",
|
|
295
|
+
help="Sort by 'name', 'size', 'modified', or 'type' (default: name)",
|
|
296
|
+
)
|
|
297
|
+
parser.add_argument(
|
|
298
|
+
"--reverse_sort",
|
|
299
|
+
action="store_true",
|
|
300
|
+
default=False,
|
|
301
|
+
help="Reverse the sort order (default: False)",
|
|
302
|
+
)
|
|
303
|
+
parser.add_argument(
|
|
304
|
+
"--recursive",
|
|
305
|
+
action="store_true",
|
|
306
|
+
default=False,
|
|
307
|
+
help="List contents recursively (default: False)",
|
|
308
|
+
)
|
|
309
|
+
parser.add_argument(
|
|
310
|
+
"--max_depth",
|
|
311
|
+
type=int,
|
|
312
|
+
default=3,
|
|
313
|
+
help="Maximum depth for recursive listing (default: 3)",
|
|
314
|
+
)
|
|
315
|
+
parser.add_argument(
|
|
316
|
+
"--file_filter",
|
|
317
|
+
type=str,
|
|
318
|
+
help="Filter files by extension (e.g., '.py', '.txt')",
|
|
319
|
+
)
|
|
320
|
+
parser.add_argument(
|
|
321
|
+
"--output_format",
|
|
322
|
+
choices=["list", "table", "json", "tree"],
|
|
323
|
+
default="list",
|
|
324
|
+
help="Output format (default: list)",
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
args = parser.parse_args()
|
|
328
|
+
|
|
329
|
+
results = list_dir(
|
|
330
|
+
args.path,
|
|
331
|
+
show_hidden=args.show_hidden,
|
|
332
|
+
show_details=args.show_details,
|
|
333
|
+
sort_by=args.sort_by,
|
|
334
|
+
reverse_sort=args.reverse_sort,
|
|
335
|
+
recursive=args.recursive,
|
|
336
|
+
max_depth=args.max_depth,
|
|
337
|
+
file_filter=args.file_filter,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
if not results:
|
|
341
|
+
sys.exit(1)
|
|
342
|
+
|
|
343
|
+
if args.output_format == "json":
|
|
344
|
+
import json
|
|
345
|
+
|
|
346
|
+
print(json.dumps(results, indent=2))
|
|
347
|
+
|
|
348
|
+
elif args.output_format == "table":
|
|
349
|
+
if args.show_details:
|
|
350
|
+
print(
|
|
351
|
+
f"{'Name':<30} {'Type':<5} {'Size':<10} {'Permissions':<12} {'Modified':<20}"
|
|
352
|
+
)
|
|
353
|
+
print("-" * 90)
|
|
354
|
+
|
|
355
|
+
for result in results:
|
|
356
|
+
file_type = "DIR" if result["is_dir"] else "FILE"
|
|
357
|
+
if result["is_symlink"]:
|
|
358
|
+
file_type = "LINK"
|
|
359
|
+
|
|
360
|
+
size_str = format_size(result["size"]) if result["is_file"] else "-"
|
|
361
|
+
indent = " " * result.get("depth", 0)
|
|
362
|
+
|
|
363
|
+
print(
|
|
364
|
+
f"{indent}{result['name']:<30} {file_type:<5} {size_str:<10} {result['permissions']:<12} {result['modified_readable']:<20}"
|
|
365
|
+
)
|
|
366
|
+
else:
|
|
367
|
+
print(f"{'Name':<40} {'Type':<5} {'Size':<10}")
|
|
368
|
+
print("-" * 60)
|
|
369
|
+
|
|
370
|
+
for result in results:
|
|
371
|
+
file_type = "DIR" if result["is_dir"] else "FILE"
|
|
372
|
+
if result["is_symlink"]:
|
|
373
|
+
file_type = "LINK"
|
|
374
|
+
|
|
375
|
+
size_str = format_size(result["size"]) if result["is_file"] else "-"
|
|
376
|
+
indent = " " * result.get("depth", 0)
|
|
377
|
+
|
|
378
|
+
print(f"{indent}{result['name']:<40} {file_type:<5} {size_str:<10}")
|
|
379
|
+
|
|
380
|
+
elif args.output_format == "tree":
|
|
381
|
+
for result in results:
|
|
382
|
+
indent = " " * result.get("depth", 0)
|
|
383
|
+
marker = "├── " if result.get("depth", 0) > 0 else ""
|
|
384
|
+
suffix = "/" if result["is_dir"] else ""
|
|
385
|
+
|
|
386
|
+
print(f"{indent}{marker}{result['name']}{suffix}")
|
|
387
|
+
|
|
388
|
+
else: # list format
|
|
389
|
+
for result in results:
|
|
390
|
+
suffix = "/" if result["is_dir"] else ""
|
|
391
|
+
print(f"{result['name']}{suffix}")
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
if __name__ == "__main__":
|
|
395
|
+
main()
|