From d56945a68f7e5e5805b692fbe99ca4b87c404a79 Mon Sep 17 00:00:00 2001 From: SowinskiBraeden Date: Sat, 26 Apr 2025 12:40:24 -0700 Subject: [PATCH] slacker 2.0 --- .gitignore | 5 +- insights.py | 212 ++++++++++++----- leaderboard.py | 415 +++++++++++++++------------------- links.py | 47 ---- parse.py | 76 ------- requirements.txt | 21 -- slacker.py | 36 +-- static/styles/index.css | 91 ++++++++ static/styles/leaderboard.css | 168 ++++++++++++++ templates/board.html | 81 +++++++ templates/index.html | 114 ++-------- update.py | 16 ++ wsgi.py | 0 13 files changed, 728 insertions(+), 554 deletions(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 insights.py mode change 100644 => 100755 leaderboard.py delete mode 100644 links.py delete mode 100644 parse.py delete mode 100644 requirements.txt mode change 100644 => 100755 slacker.py create mode 100755 static/styles/index.css create mode 100755 static/styles/leaderboard.css create mode 100755 templates/board.html mode change 100644 => 100755 templates/index.html create mode 100755 update.py mode change 100644 => 100755 wsgi.py diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 71e6d41..57e9a3b --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ __pycache__/* -teams.json -key.py boards/* venv/* +repos/* +urls.json +teams.json diff --git a/insights.py b/insights.py old mode 100644 new mode 100755 index 2c60d01..e35742d --- a/insights.py +++ b/insights.py @@ -1,100 +1,188 @@ #!/usr/bin/env python3 -from links import urls from typing import List, Dict +from json import JSONDecodeError import json import asyncio -import aiohttp -import time -from tqdm import tqdm +import os +import subprocess +import shutil -from parse import parse -from key import token +IGNORED: List[str] = [ + "node_modules/", + ".min.", + "bootstrap/", + ".history/", +] -MAX_RETRY: int = 5 +urls: Dict[str, str] = {} +with open("urls.json", "r") as file: + urls = json.load(file) -async def getInsights(repoURL: str, attempt: int = 0) -> Dict[str, str|None] | None: - owner: str = repoURL.split("/")[3] - repo: str = repoURL.split("/")[4] +def setupDir(directory: str) -> None: + folder = f'./{directory}' + if not os.path.exists(folder): + os.makedirs(folder) - query: str = f"https://api.github.com/repos/{owner}/{repo}/stats/contributors" - headers: Dict[str, str] = { - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {token}", - "X-GitHub-Api-Version": "2022-11-28" + for filename in os.listdir(folder): + file_path = os.path.join(folder, filename) + try: + if os.path.isfile(file_path) or os.path.islink(file_path): + os.unlink(file_path) + elif os.path.isdir(file_path): + shutil.rmtree(file_path) + except Exception as e: + print('Failed to delete %s. Reason: %s' % (file_path, e)) + + +async def getInsights(repoURL: str, teamID: str) -> Dict[str, str|List]: + repo: str = repoURL.split("/")[4].split(".git")[0] + + execDir: str = f"{os.getcwd()}/repos" + repoDir: str = f"{execDir}/{repo}" + + clear: List[str] = ["rm", "-rf", repo] + clone: List[str] = ["git", "clone", repoURL] + audit: List[str] = ["git", "log", "--stat"] + + subprocess.run(clear, cwd=execDir) + subprocess.run(clone, cwd=execDir) + + with open(f"{repoDir}/audit.txt", "w") as auditFile: + subprocess.run(audit, cwd=repoDir, stdout=auditFile, text=True) + + team: Dict[str, str|List] = { + "team": teamID, + "contributors": [] } - async with aiohttp.ClientSession() as session: - async with session.get(query, headers=headers, timeout=5) as resp: - if resp.status > 400: - return None - if resp.status == 202 and attempt <= MAX_RETRY: - time.sleep(15) - attempt += 1 - team = await getInsights(repoURL, attempt) - return team - elif attempt > MAX_RETRY: return None + with open(f"{repoDir}/audit.txt", "r") as auditFile: + lines = auditFile.readlines() - team: Dict[str, str|List] = { - "repo": repoURL, - "team": repo, - "contributors": [] - } - - data: List[Dict[str, any]] = await resp.json() + authorStats: Dict[str, str|int] | None = None - for contributor in data: - newContributor: Dict[str, str|int] = { - "author": contributor["author"]["login"], - "commits": contributor["total"], - "added": 0, - "deleted": 0 - } + for i in range(len(lines)): + line: str = lines[i].replace("\n", "") + if "Author: " in line: + author = line.split("Author: ")[1].split(" <")[0] + email = line.split("<")[1].split(">")[0].lower() - for week in contributor["weeks"]: - newContributor["added"] += week["a"] - newContributor["deleted"] += week["d"] + found: bool = False + for authorStat in team["contributors"]: + if found: break + if authorStat["author"] == author or authorStat["email"] == email: + authorStats = authorStat + team["contributors"].remove(authorStat) + found = True - team["contributors"].append(newContributor) + if not found and "@users.noreply.github.com" in email: continue + + if not found: + authorStats = { + "email": email, + "author": author, + "commits": 0, + "added": 0, + "deleted": 0 + } + + if "Merge: " in lines[i - 1]: + authorStats["commits"] += 1 + team["contributors"].append(authorStats) + continue + + offset: int = i + 5 + done: bool = False + + while not done: + if "files changed," in lines[offset] or "file changed," in lines[offset]: + done = True + break + + ignore: bool = False + for IGNORE in IGNORED: + if IGNORE in lines[offset]: + ignore = True + break + + if ignore: + offset += 1 + continue + + if " | " not in lines[offset]: + offset += 1 + continue + + if "Bin 0 -> " in lines[offset]: + offset += 1 + continue + + fileDiffNum: int = 0 + try: + fileDiffNum = int(list(filter(None, lines[offset].split("|")[1].split(" ")))[0]) + except ValueError: + offset += 1 + continue + + if fileDiffNum == 0: + offset += 1 + continue + + ratioString: str = list(filter(None, lines[offset].split("|")[1].split(" ")))[1].replace("\n", "") + + numAdd: int = ratioString.count("+") + numDel: int = ratioString.count("-") + + added: int = round((numAdd / len(ratioString)) * fileDiffNum) + deleted: int = round((numDel / len(ratioString)) * fileDiffNum) + + authorStats["added"] += added + authorStats["deleted"] += deleted + + offset += 1 + + authorStats["commits"] += 1 + team["contributors"].append(authorStats) return team async def main() -> None: - teamsOriginal: List[Dict] = [] + teamsOrigin: List[Dict] = [] try: with open("teams.json", "r") as file: - teamsOriginal = json.load(file) - except FileNotFoundError: + teamsOrigin = json.load(file) + except (FileNotFoundError, JSONDecodeError): pass - + teams: List[Dict] = [] - for i in tqdm(range(len(urls)), - desc="Reading Insights", - ascii=False, ncols=75): - url = urls[i] - team: Dict[str, str|List] = await getInsights(url) + for teamID in urls: + if urls[teamID] == "": continue + team: Dict[str, str|List] | None = None + try: + team = await getInsights(urls[teamID], teamID) + except Exception: + pass if team is not None: teams.append(team) - time.sleep(5) print(f"Completed {len(teams)}/{len(urls)}") - # Parse gets Campus, Set, and Team ID - teams = parse(teams) - - for originalTeam in teamsOriginal: - originalTeamID = originalTeam["id"] + # If failed to get new team data, populate with old team data + for original in teamsOrigin: + originalID = original["team"] exists: bool = False for team in teams: - if team["id"] == originalTeamID: + if team["team"] == originalID: exists = True break - + if not exists: - teams.append(originalTeam) + teams.append(original) with open("teams.json", "w") as file: json.dump(teams, file, indent=2) if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file + setupDir("repos") + setupDir("boards") + asyncio.run(main()) diff --git a/leaderboard.py b/leaderboard.py old mode 100644 new mode 100755 index 9ae9e59..608ef93 --- a/leaderboard.py +++ b/leaderboard.py @@ -1,267 +1,222 @@ #!/usr/bin/env python3 from typing import List, Dict import json -from prettytable import PrettyTable -import os, shutil from datetime import datetime -def pretty_table(dct: Dict, title: str, add: str="") -> None: - table = PrettyTable() - - for c in dct.keys(): - table.add_column(c, []) - - table.add_row(['\n'.join(dct[c]) for c in dct.keys()]) - table.align = "l" - - with open(f"./boards/{title}.txt", "a") as file: - if add != "": - file.write(f"{add}\n") - - file.write(table.__str__()) - file.write("\n\n") - -def setupDir() -> None: - if not os.path.exists("./boards"): - os.makedirs("./boards") - - folder = './boards' - for filename in os.listdir(folder): - file_path = os.path.join(folder, filename) - try: - if os.path.isfile(file_path) or os.path.islink(file_path): - os.unlink(file_path) - elif os.path.isdir(file_path): - shutil.rmtree(file_path) - except Exception as e: - print('Failed to delete %s. Reason: %s' % (file_path, e)) - -def main() -> None: +# This is sooooo poorly written with this dumb structure but oh well +def generateLeaderboards(teams: List[Dict]) -> None: date = datetime.today().strftime('%Y-%m-%d %H:%M:%S') - teams: List[Dict[str, str|List]] = {} - with open("teams.json", "r") as file: - teams = json.load(file) + # Empty leaderboard structures + projects_per_campus: Dict[str, List] = { + "title": "Largest Project per Campus", + "boards": [ + { + "title": "Burnaby Campus", + "headers": [ + "Rank", + "Team", + "Size", + "Commits" + ], + "entries": [] + }, + { + "title": "Downtown Campus", + "headers": [ + "Rank", + "Team", + "Size", + "Commits" + ], + "entries": [] + } + ], + "updatedAt": date + } + projects_all_time: Dict[str, List] = { + "title": "Largets Project of All", + "boards": [ + { + "title": "", + "headers": [ + "Rank", + "Team", + "Size", + "Commits" + ], + "entries": [] + } + ], + "updatedAt": date + } + contributors_per_campus: Dict[str, List] = { + "title": "Top Contributor per Campus", + "boards": [ + { + "title": "Burnaby Campus", + "headers": [ + "Rank", + "Author", + "Added", + "Deleted", + "Actual", + "Commits" + ], + "entries": [] + }, + { + "title": "Downtown Campus", + "headers": [ + "Rank", + "Author", + "Added", + "Deleted", + "Actual", + "Commits" + ], + "entries": [] + } + ], + "updatedAt": date + } + contributors_per_project: Dict[str, List] = { + "title": "Top Contributor per Project", + "boards": [], # Generated later + "updatedAt": date + } + tracked_teams: int = 0 + teams_in_project: Dict[str, int] = {} # keep track of teams added to contributors_per_project + contributors_all_time: Dict[str, List] = { + "title": "Top Contributor of All", + "boards": [ + { + "title": "", + "headers": [ + "Rank", + "Author", + "Added", + "Deleted", + "Actual", + "Commits" + ], + "entries": [] + } + ], + "updatedAt": date + } - projects_per_campus = { - "Burnaby": [], - "Downtown": [] - } - projects_per_set = { - "A": [], - "B": [], - "C": [], - "D": [], - "E": [], - "F": [] - } - projects_all_time = [] - contributors_per_campus = { - "Burnaby": [], - "Downtown": [] - } - contributors_per_set = { - "A": [], - "B": [], - "C": [], - "D": [], - "E": [], - "F": [] - } - contributors_per_project = {} - contributors_all_time = [] + # === Populate leaderboards === # for team in teams: added, deleted, commits = 0, 0, 0 for author in team["contributors"]: - added += author["added"] + added += author["added"] deleted += author["deleted"] commits += author["commits"] - author["contributed"] = author["added"] - author["deleted"] + author["actual"] = author["added"] - author["deleted"] + + authorEntry: List[any] = [ + None, author["author"], author["added"], author["deleted"], author["actual"], author["commits"] + ] + + campus: int = 0 if "BBY" in team["team"] else 1 + contributors_per_campus["boards"][campus]["entries"].append(authorEntry) - contributors_per_campus[team["campus"]].append(author) - contributors_per_set[team["set"]].append(author) + if team["team"] not in teams_in_project: + teams_in_project[team["team"]] = tracked_teams + tracked_teams += 1 + contributors_per_project["boards"].append({ + "title": team["team"], + "headers": [ + "Rank", + "Author", + "Added", + "Deleted", + "Actual", + "Commits" + ], + "entries": [] + }) - if team["id"] not in contributors_per_project: - contributors_per_project[team["id"]] = [] - contributors_per_project[team["id"]].append(author) + contributors_per_project["boards"][teams_in_project[team["team"]]]["entries"].append(authorEntry) - contributors_all_time.append(author) + contributors_all_time["boards"][0]["entries"].append(authorEntry) size = added - deleted - project = { - "team": team["id"], - "size": size, - "added": added, - "deleted": deleted, - "commits": commits - } - - projects_per_campus[team["campus"]].append(project) - projects_per_set[team["set"]].append(project) - projects_all_time.append(project) - - ####################################################################### - - # Largest project per campus - for campus in projects_per_campus: - filtered = sorted(projects_per_campus[campus], key=lambda d: d['size'], reverse=True) + projectEntry: List[str|int] = [ + None, + team["team"], + size, + commits + ] - data = { - "Rank": [], - "Team": [], - "Size": [] - } + campus: int = 0 if "BBY" in team["team"] else 1 + projects_per_campus["boards"][campus]["entries"].append(projectEntry) + projects_all_time["boards"][0]["entries"].append(projectEntry) - rank = 0 - for project in filtered: - rank += 1 - data["Rank"].append(str(rank)) - data["Team"].append(project["team"]) - data["Size"].append(str(project["size"])) - pretty_table(data, "LargestProjectsPerCampus", f"{campus} Campus - Updated at {date}") + # === Sort leaderboards and write to JSON === # - # Largest project per set - for set in projects_per_set: - filtered = sorted(projects_per_set[set], key=lambda d: d['size'], reverse=True) + # Project per Campus + for campus in range(len(projects_per_campus["boards"])): + filtered = sorted(projects_per_campus["boards"][campus]["entries"], key=lambda e: e[2], reverse=True) + for i in range(len(filtered)): + filtered[i][0] = i + 1 + + projects_per_campus["boards"][campus]["entries"] = filtered + + with open("boards/LargestProjectsPerCampus.json", "w") as file: + json.dump(projects_per_campus, file, indent=2) + + # Projects all time + filtered = sorted(projects_all_time["boards"][0]["entries"], key=lambda e: e[2], reverse=True) + for i in range(len(filtered)): + filtered[i][0] = i + 1 + + projects_all_time["boards"][0]["entries"] = filtered + + with open("boards/LargestProjectAllTeams.json", "w") as file: + json.dump(projects_all_time, file, indent=2) + + # Contributor per Campus + for campus in range(len(contributors_per_campus["boards"])): + filtered = sorted(contributors_per_campus["boards"][campus]["entries"], key=lambda e: e[2], reverse=True) + for i in range(len(filtered)): + filtered[i][0] = i + 1 - data = { - "Rank": [], - "Team": [], - "Size": [] - } + contributors_per_campus["boards"][campus]["entries"] = filtered - rank = 0 - for project in filtered: - rank += 1 - data["Rank"].append(str(rank)) - data["Team"].append(project["team"]) - data["Size"].append(str(project["size"])) + with open("boards/TopContributorPerCampus.json", "w") as file: + json.dump(contributors_per_campus, file, indent=2) - pretty_table(data, "LargestProjectPerSet", f"Set {set} - Updated at {date}") + # Contributor per Project + for project in range(len(contributors_per_project["boards"])): + filtered = sorted(contributors_per_project["boards"][project]["entries"], key=lambda e: e[2], reverse=True) + for i in range(len(filtered)): + filtered[i][0] = i + 1 - # Largest project of all - filtered = sorted(projects_all_time, key=lambda d: d['size'], reverse=True) - - data = { - "Rank": [], - "Team": [], - "Size": [] - } + contributors_per_project["boards"][project]["entries"] = filtered - rank = 0 - for project in filtered: - rank += 1 - data["Rank"].append(str(rank)) - data["Team"].append(project["team"]) - data["Size"].append(str(project["size"])) + with open("boards/TopContributorsPerTeam.json", "w") as file: + json.dump(contributors_per_project, file, indent=2) - pretty_table(data, "LargestProjectAllTeams", f"Updated at {date}") + # Contributor all time + filtered = sorted(contributors_all_time["boards"][0]["entries"], key=lambda e: e[2], reverse=True) + for i in range(len(filtered)): + filtered[i][0] = i + 1 + contributors_all_time["boards"][0]["entries"] = filtered - # Contributors ranker per campus - for campus in contributors_per_campus: - filtered = sorted(contributors_per_campus[campus], key=lambda d: d['added'], reverse=True) - - data = { - "Rank": [], - "Author": [], - "Added": [], - "Deleted": [], - "Actual": [], - "Commits": [], - } + with open("boards/TopContributorsAllTime.json", "w") as file: + json.dump(contributors_all_time, file, indent=2) - rank = 0 - for author in filtered: - rank += 1 - data["Rank"].append(str(rank)) - data["Author"].append(author['author']) - data["Commits"].append(str(author['commits'])) - data["Added"].append(f"+{author['added']}") - data["Deleted"].append(f"-{author['deleted']}") - data["Actual"].append(str(author['contributed'])) +def main() -> None: + with open("teams.json", "r") as file: + teams: List[Dict] = json.load(file) - pretty_table(data, "TopContributorPerCampus", f"{campus} Campus - Updated at {date}") - - - # Contributors ranked per set - for set in contributors_per_set: - filtered = sorted(contributors_per_set[set], key=lambda d: d['added'], reverse=True) - - data = { - "Rank": [], - "Author": [], - "Added": [], - "Deleted": [], - "Actual": [], - "Commits": [], - } - - rank = 0 - for author in filtered: - rank += 1 - data["Rank"].append(str(rank)) - data["Author"].append(author['author']) - data["Commits"].append(str(author['commits'])) - data["Added"].append(f"+{author['added']}") - data["Deleted"].append(f"-{author['deleted']}") - data["Actual"].append(str(author['contributed'])) - - pretty_table(data, "TopContributorPerSet", f"Set {set} - Updated at {date}") - - - # Contributors ranked per Team - for project in contributors_per_project: - filtered = sorted(contributors_per_project[project], key=lambda d: d['added'], reverse=True) - - data = { - "Rank": [], - "Author": [], - "Added": [], - "Deleted": [], - "Actual": [], - "Commits": [], - } - - rank = 0 - for author in filtered: - rank += 1 - data["Rank"].append(str(rank)) - data["Author"].append(author['author']) - data["Commits"].append(str(author['commits'])) - data["Added"].append(f"+{author['added']}") - data["Deleted"].append(f"-{author['deleted']}") - data["Actual"].append(str(author['contributed'])) - - pretty_table(data, "TopContributorsPerTeam", f"{project} - Updated at {date}"); - - # Contributors ranked all time - contributors_all_time = sorted(contributors_all_time, key=lambda d: d['added'], reverse=True) - - data = { - "Rank": [], - "Author": [], - "Added": [], - "Deleted": [], - "Actual": [], - "Commits": [], - } - - rank = 0 - for author in contributors_all_time: - rank += 1 - data["Rank"].append(str(rank)) - data["Author"].append(author['author']) - data["Actual"].append(str(author["contributed"])) - data["Added"].append(f"+{author['added']}") - data["Deleted"].append(f"-{author['deleted']}") - data["Commits"].append(str(author['commits'])) - - pretty_table(data, "TopContributorsAllTime", f"Updated at {date}") + generateLeaderboards(teams) if __name__ == "__main__": - setupDir() main() diff --git a/links.py b/links.py deleted file mode 100644 index c0da486..0000000 --- a/links.py +++ /dev/null @@ -1,47 +0,0 @@ -from typing import List - -urls: List[str] = [ - "https://github.com/ZihengZhaoJerry/1800_202510_BBY17", - "https://github.com/mskim9097/1800_202510_BBY18", - "https://github.com/dodaniel006/1800_202510_BBY19", - "https://github.com/NikR-Os/1800_202501_BBY20", - "https://github.com/cubeo77/1800_202510_BBY21", - "https://github.com/RayenBMoussa/1800_202510_BBY22", - "https://github.com/Likeemapples/1800_2025_BBY23", - "https://github.com/westudioss/1800_202510_BBY01", - "https://github.com/RodrickVy/1800_202510_BBY02", - "https://github.com/kevarome/1800_202510_BBY03", - "https://github.com/arshiaadamian/1800_202510_Team4", - "https://github.com/adilascio/1800_202510_bby5", - "https://github.com/dnlsvdr/1800_202430_BBY06", - "https://github.com/Dartleader1/1800_202510_BBY07", - "https://github.com/sukhrajsandhar/1800_202510_BBY8", - "https://github.com/Glonkz/CleanPath_BBY09", - "https://github.com/TaylorHillier/1800_202510_BBY11", - "https://github.com/cnguyen50/1800_202510_BBY12", - "https://github.com/emphs/1800_202510_BBY13", - "https://github.com/useluss-dev/1800_202510_BBY14", - "https://github.com/Jacob-Lebl-BCIT/1800_202510_BBY15", - "https://github.com/annoyingcoder174/2025_01_Group16", - "https://github.com/ShivaunBartoo/1800_202510_BBY25", - "https://github.com/ffloras/1800_202510_BBY26", - "https://github.com/Tlpadilla18/1800_202510_BBY27", - "https://github.com/anajnsilva/1800_202510_BBY28", - "https://github.com/amangrewal10/1800_202510_BBY_29", - "https://github.com/TirathJaggee/1800_202510_30", - "https://github.com/SowinskiBraeden/1800_202510_BBY32", - "https://github.com/klockwork3/1800_202430_DTC01", - "https://github.com/lawr-lau16/1800_202510_DTC02", - "https://github.com/tushitgrg/1800_202510_DTC03", - "https://github.com/ttyw24/1800_202510_DTC04", - "https://github.com/achepakovich/1800_202510_DTC05", - "https://github.com/ttrinh0/1800_202510_DTC06", - "https://github.com/dcao1205/1800_202510_DTC07", - "https://github.com/ivanekavalashvili/1800_202510_DTC09", - "https://github.com/wandereryiu/1800_202510_DTC10", - "https://github.com/Choudoufuhezi/1800_202510_DTC11", - "https://github.com/anthonyherradura/1800_202510_DTC12", - "https://github.com/andacg1/1800_202510_DTC13", - "https://github.com/senukzzzzz/1800_202510_DTC15", - "https://github.com/knighthawk4227/1800_202510_31" -] diff --git a/parse.py b/parse.py deleted file mode 100644 index f3f5201..0000000 --- a/parse.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python3 -from typing import List, Dict - -# Only ever really needs to be run one time - -def getSet(team: str) -> str: - team = team.upper() - campus = "DTC" if "DTC" in team else "BBY" - num = "" - if "BBY" in team: - num = team.split("BBY")[1] - elif "DTC" in team: - num = team.split("DTC")[1] - elif "TEAM" in team: - num = team.split("TEAM")[1] - elif "GROUP" in team: - num = team.split("GROUP")[1] - else: - num = team - - num = int(num) - if campus == "BBY": - if 17 <= num <= 24: return "A" - if 1 <= num <= 8: return "B" - if 9 <= num <= 16: return "C" - if 25 <= num <= 32: return "D" - else: - print(team, num) - return "X" - elif campus == "DTC": - if 1 <= num <= 8: return "E" - if 9 <= num <= 15: return "F" - else: - print(team) - return "X" - else: - print(team) - return "Z" - -def getCampus(team: str) -> str: - team = team.upper() - campus = "Downtown" if "DTC" in team else "Burnaby" - return campus - -def createTeamID(team: str) -> str: - team = team.upper() - campus = "DTC" if "DTC" in team else "BBY" - num = "" - if "BBY" in team: - num = team.split("BBY")[1] - elif "DTC" in team: - num = team.split("DTC")[1] - elif "TEAM" in team: - num = team.split("TEAM")[1] - elif "GROUP" in team: - num = team.split("GROUP")[1] - else: - num = team - - num = int(num) - return f"{campus}-{'0' if num < 10 else ''}{num}" - -def parse(teams: List[Dict[str, str|List]]) -> List[Dict[str, str|List]]: - - for team in teams: - teamID = team["repo"].split("_")[len(team["repo"].split("_")) - 1] - team["set"] = getSet(teamID) - team["campus"] = getCampus(teamID) - team["id"] = createTeamID(teamID) - - return teams - # with open("teams.json", "w") as file: - # json.dump(teams, file, indent=2) - -if __name__ == "__main__": - parse() diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index b4e2105..0000000 --- a/requirements.txt +++ /dev/null @@ -1,21 +0,0 @@ -aiohappyeyeballs==2.6.1 -aiohttp==3.11.13 -aiosignal==1.3.2 -attrs==25.3.0 -blinker==1.9.0 -click==8.1.8 -Flask==3.1.0 -frozenlist==1.5.0 -gunicorn==23.0.0 -idna==3.10 -itsdangerous==2.2.0 -Jinja2==3.1.6 -MarkupSafe==3.0.2 -multidict==6.1.0 -packaging==24.2 -prettytable==3.15.1 -propcache==0.3.0 -tqdm==4.67.1 -wcwidth==0.2.13 -Werkzeug==3.1.3 -yarl==1.18.3 diff --git a/slacker.py b/slacker.py old mode 100644 new mode 100755 index 15eb072..29af7fe --- a/slacker.py +++ b/slacker.py @@ -1,28 +1,34 @@ #!/usr/bin/env python3 -from flask import Flask, Response -from flask import render_template -import os +from flask import Flask +from flask import render_template, redirect, url_for +from typing import Dict, List +import json app = Flask(__name__) -def root_dir(): - return os.path.abspath(os.path.dirname(__file__)) - -def get_file(filename): - try: - src = os.path.join(root_dir(), filename) - return open(src).read() - except IOError as exc: - return str(exc) - @app.route("/", methods=["GET"]) def index() -> str: return render_template("index.html") @app.route("/boards/", methods=["GET"]) def loadBoard(boardID: str) -> str: - path = os.path.join(root_dir(), f"boards/{boardID}.txt") - return Response(get_file(path), mimetype="text/plain") + leaderboard: Dict[str, List] = {} + try: + with open(f"boards/{boardID}.json", "r") as file: + leaderboard = json.load(file) + except FileNotFoundError: + return redirect(url_for('404')) + + return render_template( + "board.html", + leaderboards=leaderboard["boards"], + title=leaderboard["title"], + updatedAt=leaderboard["updatedAt"] + ) + +@app.route("/404", methods=["GET"]) +def notFound() -> str: + return render_template("not_found.html") if __name__ == "__main__": app.run(host="0.0.0.0") diff --git a/static/styles/index.css b/static/styles/index.css new file mode 100755 index 0000000..f04b3f6 --- /dev/null +++ b/static/styles/index.css @@ -0,0 +1,91 @@ +@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;700;800&display=swap"); + +body { + font-family: -apple-system, BlinkMacSystemFont, segoe ui, Roboto, Oxygen, Ubuntu, Cantarell, open sans, helvetica neue, sans-serif; + padding: 0pt; + margin: 0pt; +} + +.title { + font-size: 40px; +} + +.main { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 25%; + height: auto; + padding: 24pt; + border-radius: 24px; + justify-content: center; + text-align: left; +} + +ul li { + margin-bottom: 12pt; +} + +.link { + font-size: 30px; + font-weight: 300; +} + +.flex-container-c { + display: flex; + flex-direction: column; + justify-content: center; +} + +.flex-container-r { + display: flex; + flex-direction: row; + justify-content: space-evenly; +} + +footer { + width: 100vw; + position: fixed; + bottom: 0%; + left: 0%; + background-color: #aaaaaa; + display: flex; + justify-content: space-between; + transition: .8s; + padding: 8pt; +} + +footer a { + font-size: 20px; + font-weight: 600; + font-style: normal; + text-decoration: underline; + color: black +} + +footer p { + margin-top: 16pt; +} + +.social-icons { + padding: 12pt 0; +} + +.social-icons a:not(:last-of-type) { + margin-inline-end: 12pt; +} + +.social-icons a svg { + height: 26pt; + width: 26pt; +} + +.social-icons a { + color: var(--primary); + text-decoration: none; +} + +.icon { + transition: 0.8s; +} \ No newline at end of file diff --git a/static/styles/leaderboard.css b/static/styles/leaderboard.css new file mode 100755 index 0000000..ef29c14 --- /dev/null +++ b/static/styles/leaderboard.css @@ -0,0 +1,168 @@ +body { + font-family: Arial, sans-serif; + background-color: #f8f9fa; + margin: 0; + padding: 0; +} +.grid-container { + display: grid; + grid-template-areas: + "header header" + "content content" + "footer footer"; +} +/* header { + grid-area: header; +} */ +.back-btn { + background-color: #007BFF; + border: none; + color: white; + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + border-radius: 4px; + margin: 16pt; +} +.footer { + grid-area: footer; +} +.container { + grid-area: content; + width: 90%; + max-width: 1200px; + margin: 40px auto; + background: #fff; + padding: 30px; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} +h1 { + text-align: center; + color: #333; +} +.leaderboard, .score-table { + margin-top: 30px; +} +.leaderboard { + text-align: center; + background-color: #f1f1f1; + padding: 20px; + border-radius: 8px; +} +.leaderboard h2 { + color: #007BFF; +} +.leaderboard ul { + list-style-type: none; + padding: 0; +} +.leaderboard li { + padding: 8px; + background: #fff; + margin: 10px auto; + width: 80%; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} +.leaderboard li span { + font-weight: bold; + color: #007BFF; +} +.filters { + margin-top: 20px; + text-align: center; +} +.filters select, .filters input { + padding: 10px; + margin: 5px; + border: 1px solid #ddd; + border-radius: 5px; +} +.score-table { + margin-top: 20px; +} +table { + width: 100%; + border-collapse: collapse; + margin-top: 20px; +} +table th, table td { + padding: 12px; + text-align: left; + border: 1px solid #ddd; +} +table th { + background-color: #007BFF; + color: white; +} +.btn { + padding: 8px 12px; + background-color: #007BFF; + color: #fff; + border: none; + border-radius: 5px; + cursor: pointer; + text-decoration: none; + font-size: 14px; +} +.btn:hover { + background-color: #0056b3; +} + +.flex-container-c { + display: flex; + flex-direction: column; + justify-content: center; +} + +.flex-container-r { + display: flex; + flex-direction: row; + justify-content: space-evenly; +} + +footer { + width: 100vw; + background-color: #aaaaaa; + display: flex; + justify-content: space-between; + transition: .8s; + padding: 8pt; +} + +footer a { + font-size: 20px; + font-weight: 600; + font-style: normal; + text-decoration: underline; + color: black +} + +footer p { + margin-top: 16pt; +} + +.social-icons { + padding: 12pt 0; +} + +.social-icons a:not(:last-of-type) { + margin-inline-end: 12pt; +} + +.social-icons a svg { + height: 26pt; + width: 26pt; +} + +.social-icons a { + color: var(--primary); + text-decoration: none; +} + +.icon { + transition: 0.8s; +} \ No newline at end of file diff --git a/templates/board.html b/templates/board.html new file mode 100755 index 0000000..091a01b --- /dev/null +++ b/templates/board.html @@ -0,0 +1,81 @@ + + + + COMP 2800 - Slacker + + + +
+ +
+ Back +
+ +
+

{{ title }}

+

Last Updated: {{ updatedAt }}

+ + + + + + {% for leaderboard in leaderboards %} +
+

{{ leaderboard.title }}

+ + + + {% for header in leaderboard.headers %} + + {% endfor %} + + + + {% for entry in leaderboard.entries %} + + {% for attr in entry %} + + {% endfor %} + + {% endfor %} + +
{{ header }}
{{ attr }}
+
+ {% endfor %} +
+ +
+ + diff --git a/templates/index.html b/templates/index.html old mode 100644 new mode 100755 index d2da998..a60a921 --- a/templates/index.html +++ b/templates/index.html @@ -1,118 +1,30 @@ - COMP 1800 - Slacker - - + COMP 2800 - Slacker +
-

COMP 1800 Slacker

+

COMP 2800 Slacker

See Leaderboards

-

PS: Some projects & users may not be up to date with others.

-

Also, I am lazy and don't want to make a pretty table so you just get .txt files

-

Check back for daily updates.

+

+ Disclaimer: This reads commits from git log and may not match + commits from Github insights. +

+

+ Additionally, any commits made through the Github web interface are ignored. +

+

+ Files with matching patterns may be ignored in your commits. E.g. node_module files. +

diff --git a/update.py b/update.py new file mode 100755 index 0000000..e978005 --- /dev/null +++ b/update.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +from insights import setupDir, main +from leaderboard import generateLeaderboards +import asyncio +import json +from typing import List, Dict + +if __name__ == "__main__": + setupDir("repos") + setupDir("boards") + asyncio.run(main()) + + with open("teams.json", "r") as file: + teams: List[Dict] = json.load(file) + + generateLeaderboards(teams) diff --git a/wsgi.py b/wsgi.py old mode 100644 new mode 100755