diff --git a/PICable.py b/PICable.py index d233233..b7e3c3c 100644 --- a/PICable.py +++ b/PICable.py @@ -10,6 +10,7 @@ LIMIT_STARS = 200 LIMIT_NLOC = 100000 MARGIN = 0.1 CLONE_DIR = "~/temp/PICable" +EXCLUDED_LANGUAGES = ["CSV", "diff", "INI", "JSON", "Markdown", "reStructuredText", "SVG", "Text", "YAML"] def get_stars_info(owner, repository, request_headers): @@ -47,28 +48,29 @@ def get_commits_last_30_days(owner, repository, request_headers): def get_lines_of_code(owner, repository): - repo_url = f"https://github.com/{owner}/{repository}.git" - repository_clone_dir = os.path.expanduser(f"{CLONE_DIR}/{repository}") + repository_url = f"https://github.com/{owner}/{repository}.git" + parent_dir = os.path.expanduser(f"{CLONE_DIR}/{owner}") + repository_clone_dir = os.path.expanduser(f"{parent_dir}/{repository}") try: subprocess.run( - ["git", "clone", repo_url, repository_clone_dir], + ["git", "clone", "--depth", "1", repository_url, repository_clone_dir], check=True, stdout=sys.stdout, stderr=sys.stderr, ) + result = subprocess.run( + ["cloc", f"--exclude-lang={','.join(EXCLUDED_LANGUAGES)}", repository_clone_dir], + capture_output=True, + text=True, + check=True, + ) + number_of_lines_of_code = int(result.stdout.split("\n")[-3].split()[-1]) except Exception: return -1 - - result = subprocess.run(["sloccount", repository_clone_dir], capture_output=True, text=True, check=True) - for line in result.stdout.splitlines(): - if "Total Physical Source Lines of Code (SLOC)" in line: - loc = int(line.split()[8].replace(",", "")) - break - else: - loc = 0 - subprocess.run(["rm", "-rf", repository_clone_dir], check=True) - return loc + finally: + subprocess.run(["rm", "-rf", parent_dir], check=True) + return number_of_lines_of_code def is_valid_repository(owner, repository, request_headers): @@ -80,7 +82,7 @@ def is_valid_repository(owner, repository, request_headers): def PICable(owner, repository, token): margin_percentage = round(MARGIN * 100) request_headers = {"Authorization": f"token {token}"} - picable_report = "" + picable_report = f"Analysis complete for the repository {owner}/{repository}.\n\n" repository_url = f"https://www.github.com/{owner}/{repository}" if not is_valid_repository(owner, repository, request_headers): @@ -134,4 +136,5 @@ def PICable(owner, repository, token): else: picable_report += f"The repository {repository_url} is almost PICable. :warning:\nThere is at least one requirement which is below the requirement, but by less than {margin_percentage}%. Consult with a professor before proceeding.\n" + print("Analysis complete.") return picable_report diff --git a/main.py b/main.py index 682912c..3406e1f 100644 --- a/main.py +++ b/main.py @@ -16,15 +16,7 @@ intents = discord.Intents.default() intents.message_content = True client = commands.Bot(command_prefix="/", intents=intents) - -async def reply_message_async(interaction: discord.Interaction, owner: str, repo: str): - try: - result = await asyncio.to_thread(PICable.PICable, owner, repo, github_token) - await interaction.followup.send(result) - except Exception as e: - print(f"Error processing PICable check: {e}") - await interaction.followup.send("An error occurred while processing your request. Please try again later.") - return +current_repositories = set() @client.event @@ -38,10 +30,43 @@ async def on_ready(): print(f"Failed to sync commands: {e}") +@client.tree.command(name="ping", description="Check the bot's latency.") +async def ping(interaction: discord.Interaction): + latency = client.latency * 1000 + await interaction.response.send_message(f"Pong! Latency: {latency:.2f} ms") + + @client.tree.command(name="picable", description="Check if a Github repository is eligible for PIC.") @discord.app_commands.describe(owner="The owner of the repository", repository="The name of the repository") async def picable(interaction: discord.Interaction, owner: str, repository: str): - await interaction.response.defer(ephemeral=False, thinking=True) - asyncio.create_task(reply_message_async(interaction, owner, repository)) + await interaction.response.defer(thinking=True) + + repository_full_name = f"{owner}/{repository}" + if repository_full_name in current_repositories: + await interaction.followup.send( + f"The repository {repository_full_name} is already being analyzed. Please wait for the current analysis to complete. :warning:" + ) + return + + current_repositories.add(repository_full_name) + try: + result_future = asyncio.get_running_loop().run_in_executor( + None, PICable.PICable, owner, repository, github_token + ) + try: + result = await asyncio.wait_for(asyncio.shield(result_future), timeout=600) + await interaction.followup.send(result) + except asyncio.TimeoutError: + await interaction.followup.send( + f"The analysis for {repository_full_name} is still taking place. The results will be posted here once the analysis is complete. :clock4:" + ) + result = await result_future + await interaction.channel.send(result) + except Exception as e: + print(f"Error processing PICable check: {e}") + await interaction.channel.send("An error occurred while processing your request. Please try again later.") + finally: + current_repositories.remove(repository_full_name) + client.run(discord_token)