|
| 1 | +import os |
| 2 | +from pathlib import Path |
| 3 | + |
| 4 | +from dotenv import load_dotenv |
| 5 | +from pathspec import PathSpec, GitIgnorePattern |
| 6 | + |
| 7 | +from biz.utils.code_reviewer import CodeBaseReviewer |
| 8 | +from biz.utils.dir_util import get_directory_tree |
| 9 | +from biz.utils.i18n import get_translator |
| 10 | +from biz.utils.log import logger |
| 11 | +from biz.utils.token_util import count_tokens, truncate_text_by_tokens |
| 12 | + |
| 13 | +_ = get_translator() |
| 14 | + |
| 15 | +SUPPORTED_LANGUAGES = ["python", "java", "php", "vue"] |
| 16 | + |
| 17 | + |
| 18 | +def validate_language_choice(choice): |
| 19 | + """ |
| 20 | + 验证用户输入的数字是否有效。 |
| 21 | + :param choice: 用户输入的数字 |
| 22 | + :return: 如果有效返回 True,否则返回 False |
| 23 | + """ |
| 24 | + return choice.isdigit() and 1 <= int(choice) <= len(SUPPORTED_LANGUAGES) |
| 25 | + |
| 26 | + |
| 27 | +def validate_directory(directory): |
| 28 | + """ |
| 29 | + 验证用户输入的目录是否存在。 |
| 30 | + :param directory: 用户输入的目录路径 |
| 31 | + :return: 如果目录存在返回 True,否则返回 False |
| 32 | + """ |
| 33 | + return Path(directory).is_dir() |
| 34 | + |
| 35 | + |
| 36 | +def get_user_input(prompt: str, default=None, input_type=str): |
| 37 | + """ |
| 38 | + 获取用户输入,支持默认值和类型转换。 |
| 39 | +
|
| 40 | + Args: |
| 41 | + prompt (str): 提示信息。 |
| 42 | + default: 默认值。 |
| 43 | + input_type: 输入值的类型(如 int, str, bool 等)。 |
| 44 | +
|
| 45 | + Returns: |
| 46 | + 用户输入的值或默认值。 |
| 47 | + """ |
| 48 | + user_input = input(f"{prompt} (默认: {default}): ").strip() |
| 49 | + if not user_input: |
| 50 | + return default |
| 51 | + try: |
| 52 | + return input_type(user_input) |
| 53 | + except ValueError: |
| 54 | + print(_("输入无效,请输入 {} 类型的值。").format(input_type.__name__)) |
| 55 | + return get_user_input(prompt, default, input_type) |
| 56 | + |
| 57 | + |
| 58 | +def parse_arguments(): |
| 59 | + """ |
| 60 | + 使用交互方式获取用户输入,并返回语言和目录。 |
| 61 | + :return: 语言和目录的元组 (language, directory) |
| 62 | + """ |
| 63 | + # 显示语言选项 |
| 64 | + print(_("请选择开发语言:")) |
| 65 | + for index, language in enumerate(SUPPORTED_LANGUAGES, start=1): |
| 66 | + print(f"{index}. {language}") |
| 67 | + |
| 68 | + # 获取开发语言 |
| 69 | + while True: |
| 70 | + choice = input(_("请输入数字 (1-{}): ").format(len(SUPPORTED_LANGUAGES)).strip()) |
| 71 | + if validate_language_choice(choice): |
| 72 | + language = SUPPORTED_LANGUAGES[int(choice) - 1] # 获取对应的语言 |
| 73 | + break |
| 74 | + print(_("❌ 无效的选择,请输入 1 到 {} 之间的数字").format(len(SUPPORTED_LANGUAGES))) |
| 75 | + |
| 76 | + # 获取项目目录 |
| 77 | + while True: |
| 78 | + directory = input(_("请输入代码项目的根目录路径: ")).strip() |
| 79 | + if validate_directory(directory): |
| 80 | + break |
| 81 | + print(_("❌ 目录不存在,请输入有效路径")) |
| 82 | + |
| 83 | + max_depth = get_user_input(_("请输入目录树的最大深度"), default=3, input_type=int) |
| 84 | + only_dirs = get_user_input(_("是否仅获取目录?(y/n)"), default="y").lower() in ["y", "yes"] |
| 85 | + |
| 86 | + return language, directory, max_depth, only_dirs |
| 87 | + |
| 88 | + |
| 89 | +def load_gitignore_patterns(directory): |
| 90 | + """加载 .gitignore 规则""" |
| 91 | + gitignore_path = os.path.join(directory, ".gitignore") |
| 92 | + |
| 93 | + if not os.path.exists(gitignore_path): |
| 94 | + return None # 没有 .gitignore 文件,则不做忽略处理 |
| 95 | + |
| 96 | + with open(gitignore_path, "r", encoding="utf-8") as f: |
| 97 | + patterns = f.readlines() |
| 98 | + |
| 99 | + return PathSpec.from_lines(GitIgnorePattern, patterns) |
| 100 | + |
| 101 | + |
| 102 | +def review_code(text: str) -> str: |
| 103 | + # 如果超长,取前REVIEW_MAX_TOKENS个token |
| 104 | + review_max_tokens = int(os.getenv('REVIEW_MAX_TOKENS', 10000)) |
| 105 | + # 如果changes为空,打印日志 |
| 106 | + if not text: |
| 107 | + logger.info(_('代码为空, diffs_text = {}').format(str(text))) |
| 108 | + return _('代码为空') |
| 109 | + |
| 110 | + # 计算tokens数量,如果超过REVIEW_MAX_TOKENS,截断changes_text |
| 111 | + tokens_count = count_tokens(text) |
| 112 | + if tokens_count > review_max_tokens: |
| 113 | + text = truncate_text_by_tokens(text, review_max_tokens) |
| 114 | + |
| 115 | + review_result = CodeBaseReviewer().review_code(text).strip() |
| 116 | + if review_result.startswith("```markdown") and review_result.endswith("```"): |
| 117 | + return review_result[11:-3].strip() |
| 118 | + return review_result |
| 119 | + |
| 120 | + |
| 121 | +def confirm_action(prompt: str) -> bool: |
| 122 | + while True: |
| 123 | + user_input = input(prompt).strip().lower() |
| 124 | + if user_input in ["y", "yes"]: |
| 125 | + return True |
| 126 | + elif user_input in ["n", "no"]: |
| 127 | + return False |
| 128 | + else: |
| 129 | + print(_("请输入 'y' 或 'n' 确认。")) |
| 130 | + |
| 131 | + |
| 132 | +if __name__ == "__main__": |
| 133 | + load_dotenv("conf/.env") |
| 134 | + |
| 135 | + language, directory, max_depth, only_dirs = parse_arguments() |
| 136 | + ignore_spec = load_gitignore_patterns(directory) |
| 137 | + directory_structure = get_directory_tree(directory, ignore_spec, max_depth=max_depth, only_dirs=only_dirs) |
| 138 | + print(_("目录结构:\n"), directory_structure) |
| 139 | + |
| 140 | + # 用户确认 |
| 141 | + if confirm_action(_("是否确认发送 Review 请求?(y/n): ")): |
| 142 | + # 初始化 CodeBaseReviewer |
| 143 | + reviewer = CodeBaseReviewer() |
| 144 | + |
| 145 | + # 执行 CodeReview |
| 146 | + result = reviewer.review_code(language, directory_structure) |
| 147 | + print(_("Review 结果:\n"), result) |
| 148 | + else: |
| 149 | + print(_("用户取消操作,退出程序。")) |
0 commit comments