slacker 2.0

This commit is contained in:
SowinskiBraeden committed 2025-04-26 12:40:24 -07:00
1 parent 9eb4ffce7f
commit d56945a68f
13 files changed
+718 -544

No files matched your search

Regular → Executable
+3 -2
View File
@@ -1,5 +1,6 @@
__pycache__/* __pycache__/*
teams.json
key.py
boards/* boards/*
venv/* venv/*
repos/*
urls.json
teams.json
Regular → Executable
+146 -58
View File
@@ -1,100 +1,188 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from links import urls
from typing import List, Dict from typing import List, Dict
from json import JSONDecodeError
import json import json
import asyncio import asyncio
import aiohttp import os
import time import subprocess
from tqdm import tqdm import shutil
from parse import parse IGNORED: List[str] = [
from key import token "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: def setupDir(directory: str) -> None:
owner: str = repoURL.split("/")[3] folder = f'./{directory}'
repo: str = repoURL.split("/")[4] if not os.path.exists(folder):
os.makedirs(folder)
query: str = f"https://api.github.com/repos/{owner}/{repo}/stats/contributors" for filename in os.listdir(folder):
headers: Dict[str, str] = { file_path = os.path.join(folder, filename)
"Accept": "application/vnd.github+json", try:
"Authorization": f"Bearer {token}", if os.path.isfile(file_path) or os.path.islink(file_path):
"X-GitHub-Api-Version": "2022-11-28" 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: with open(f"{repoDir}/audit.txt", "r") as auditFile:
async with session.get(query, headers=headers, timeout=5) as resp: lines = auditFile.readlines()
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
team: Dict[str, str|List] = { authorStats: Dict[str, str|int] | None = None
"repo": repoURL,
"team": repo,
"contributors": []
}
data: List[Dict[str, any]] = await resp.json() 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 contributor in data: found: bool = False
newContributor: Dict[str, str|int] = { for authorStat in team["contributors"]:
"author": contributor["author"]["login"], if found: break
"commits": contributor["total"], if authorStat["author"] == author or authorStat["email"] == email:
"added": 0, authorStats = authorStat
"deleted": 0 team["contributors"].remove(authorStat)
} found = True
for week in contributor["weeks"]: if not found and "@users.noreply.github.com" in email: continue
newContributor["added"] += week["a"]
newContributor["deleted"] += week["d"]
team["contributors"].append(newContributor) 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 return team
async def main() -> None: async def main() -> None:
teamsOriginal: List[Dict] = [] teamsOrigin: List[Dict] = []
try: try:
with open("teams.json", "r") as file: with open("teams.json", "r") as file:
teamsOriginal = json.load(file) teamsOrigin = json.load(file)
except FileNotFoundError: except (FileNotFoundError, JSONDecodeError):
pass pass
teams: List[Dict] = [] teams: List[Dict] = []
for i in tqdm(range(len(urls)), for teamID in urls:
desc="Reading Insights", if urls[teamID] == "": continue
ascii=False, ncols=75): team: Dict[str, str|List] | None = None
url = urls[i] try:
team: Dict[str, str|List] = await getInsights(url) team = await getInsights(urls[teamID], teamID)
except Exception:
pass
if team is not None: if team is not None:
teams.append(team) teams.append(team)
time.sleep(5)
print(f"Completed {len(teams)}/{len(urls)}") print(f"Completed {len(teams)}/{len(urls)}")
# Parse gets Campus, Set, and Team ID # If failed to get new team data, populate with old team data
teams = parse(teams) for original in teamsOrigin:
originalID = original["team"]
for originalTeam in teamsOriginal:
originalTeamID = originalTeam["id"]
exists: bool = False exists: bool = False
for team in teams: for team in teams:
if team["id"] == originalTeamID: if team["team"] == originalID:
exists = True exists = True
break break
if not exists: if not exists:
teams.append(originalTeam) teams.append(original)
with open("teams.json", "w") as file: with open("teams.json", "w") as file:
json.dump(teams, file, indent=2) json.dump(teams, file, indent=2)
if __name__ == "__main__": if __name__ == "__main__":
setupDir("repos")
setupDir("boards")
asyncio.run(main()) asyncio.run(main())
Regular → Executable
+179 -224
View File
@@ -1,267 +1,222 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from typing import List, Dict from typing import List, Dict
import json import json
from prettytable import PrettyTable
import os, shutil
from datetime import datetime from datetime import datetime
def pretty_table(dct: Dict, title: str, add: str="") -> None: # This is sooooo poorly written with this dumb structure but oh well
table = PrettyTable() def generateLeaderboards(teams: List[Dict]) -> None:
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:
date = datetime.today().strftime('%Y-%m-%d %H:%M:%S') date = datetime.today().strftime('%Y-%m-%d %H:%M:%S')
teams: List[Dict[str, str|List]] = {} # Empty leaderboard structures
with open("teams.json", "r") as file: projects_per_campus: Dict[str, List] = {
teams = json.load(file) "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 = { # === Populate leaderboards === #
"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 = []
for team in teams: for team in teams:
added, deleted, commits = 0, 0, 0 added, deleted, commits = 0, 0, 0
for author in team["contributors"]: for author in team["contributors"]:
added += author["added"] added += author["added"]
deleted += author["deleted"] deleted += author["deleted"]
commits += author["commits"] commits += author["commits"]
author["contributed"] = author["added"] - author["deleted"] author["actual"] = author["added"] - author["deleted"]
contributors_per_campus[team["campus"]].append(author) authorEntry: List[any] = [
contributors_per_set[team["set"]].append(author) None, author["author"], author["added"], author["deleted"], author["actual"], author["commits"]
]
if team["id"] not in contributors_per_project: campus: int = 0 if "BBY" in team["team"] else 1
contributors_per_project[team["id"]] = [] contributors_per_campus["boards"][campus]["entries"].append(authorEntry)
contributors_per_project[team["id"]].append(author)
contributors_all_time.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": []
})
contributors_per_project["boards"][teams_in_project[team["team"]]]["entries"].append(authorEntry)
contributors_all_time["boards"][0]["entries"].append(authorEntry)
size = added - deleted size = added - deleted
project = { projectEntry: List[str|int] = [
"team": team["id"], None,
"size": size, team["team"],
"added": added, size,
"deleted": deleted, commits
"commits": commits ]
}
projects_per_campus[team["campus"]].append(project) campus: int = 0 if "BBY" in team["team"] else 1
projects_per_set[team["set"]].append(project) projects_per_campus["boards"][campus]["entries"].append(projectEntry)
projects_all_time.append(project) projects_all_time["boards"][0]["entries"].append(projectEntry)
#######################################################################
# Largest project per campus
for campus in projects_per_campus:
filtered = sorted(projects_per_campus[campus], key=lambda d: d['size'], reverse=True)
data = {
"Rank": [],
"Team": [],
"Size": []
}
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}")
# Largest project per set
for set in projects_per_set:
filtered = sorted(projects_per_set[set], key=lambda d: d['size'], reverse=True)
data = {
"Rank": [],
"Team": [],
"Size": []
}
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, "LargestProjectPerSet", f"Set {set} - Updated at {date}")
# Largest project of all
filtered = sorted(projects_all_time, key=lambda d: d['size'], reverse=True)
data = {
"Rank": [],
"Team": [],
"Size": []
}
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, "LargestProjectAllTeams", f"Updated at {date}")
# Contributors ranker per campus # === Sort leaderboards and write to JSON === #
for campus in contributors_per_campus:
filtered = sorted(contributors_per_campus[campus], key=lambda d: d['added'], reverse=True)
data = { # Project per Campus
"Rank": [], for campus in range(len(projects_per_campus["boards"])):
"Author": [], filtered = sorted(projects_per_campus["boards"][campus]["entries"], key=lambda e: e[2], reverse=True)
"Added": [], for i in range(len(filtered)):
"Deleted": [], filtered[i][0] = i + 1
"Actual": [],
"Commits": [],
}
rank = 0 projects_per_campus["boards"][campus]["entries"] = filtered
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, "TopContributorPerCampus", f"{campus} Campus - Updated at {date}") 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
# Contributors ranked per set projects_all_time["boards"][0]["entries"] = filtered
for set in contributors_per_set:
filtered = sorted(contributors_per_set[set], key=lambda d: d['added'], reverse=True)
data = { with open("boards/LargestProjectAllTeams.json", "w") as file:
"Rank": [], json.dump(projects_all_time, file, indent=2)
"Author": [],
"Added": [],
"Deleted": [],
"Actual": [],
"Commits": [],
}
rank = 0 # Contributor per Campus
for author in filtered: for campus in range(len(contributors_per_campus["boards"])):
rank += 1 filtered = sorted(contributors_per_campus["boards"][campus]["entries"], key=lambda e: e[2], reverse=True)
data["Rank"].append(str(rank)) for i in range(len(filtered)):
data["Author"].append(author['author']) filtered[i][0] = i + 1
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_per_campus["boards"][campus]["entries"] = filtered
with open("boards/TopContributorPerCampus.json", "w") as file:
json.dump(contributors_per_campus, file, indent=2)
# Contributors ranked per Team # Contributor per Project
for project in contributors_per_project: for project in range(len(contributors_per_project["boards"])):
filtered = sorted(contributors_per_project[project], key=lambda d: d['added'], reverse=True) 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
data = { contributors_per_project["boards"][project]["entries"] = filtered
"Rank": [],
"Author": [],
"Added": [],
"Deleted": [],
"Actual": [],
"Commits": [],
}
rank = 0 with open("boards/TopContributorsPerTeam.json", "w") as file:
for author in filtered: json.dump(contributors_per_project, file, indent=2)
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}"); # 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 ranked all time contributors_all_time["boards"][0]["entries"] = filtered
contributors_all_time = sorted(contributors_all_time, key=lambda d: d['added'], reverse=True)
data = { with open("boards/TopContributorsAllTime.json", "w") as file:
"Rank": [], json.dump(contributors_all_time, file, indent=2)
"Author": [],
"Added": [],
"Deleted": [],
"Actual": [],
"Commits": [],
}
rank = 0 def main() -> None:
for author in contributors_all_time: with open("teams.json", "r") as file:
rank += 1 teams: List[Dict] = json.load(file)
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__": if __name__ == "__main__":
setupDir()
main() main()
-47
View File
@@ -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"
]
-76
View File
@@ -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()
-21
View File
@@ -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
Regular → Executable
+21 -15
View File
@@ -1,28 +1,34 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from flask import Flask, Response from flask import Flask
from flask import render_template from flask import render_template, redirect, url_for
import os from typing import Dict, List
import json
app = Flask(__name__) 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"]) @app.route("/", methods=["GET"])
def index() -> str: def index() -> str:
return render_template("index.html") return render_template("index.html")
@app.route("/boards/<boardID>", methods=["GET"]) @app.route("/boards/<boardID>", methods=["GET"])
def loadBoard(boardID: str) -> str: def loadBoard(boardID: str) -> str:
path = os.path.join(root_dir(), f"boards/{boardID}.txt") leaderboard: Dict[str, List] = {}
return Response(get_file(path), mimetype="text/plain") 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__": if __name__ == "__main__":
app.run(host="0.0.0.0") app.run(host="0.0.0.0")
+91
View File
@@ -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;
}
+168
View File
@@ -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;
}
+81
View File
@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>COMP 2800 - Slacker</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles/leaderboard.css') }}">
</head>
<body>
<div class="grid-container">
<header>
<a class="back-btn" href="/">Back</a>
</header>
<div class="container">
<h1>{{ title }}</h1>
<p><small>Last Updated: {{ updatedAt }}</small></p>
<!-- Filters Section -->
<!-- <div class="filters">
<select>
<option value="all">All Categories</option>
<option value="quizzes">Quizzes</option>
<option value="projects">Projects</option>
<option value="exams">Exams</option>
</select>
<input type="text" placeholder="Search by name or category">
<button class="btn">Apply</button>
</div> -->
<!-- Leaderboard Section -->
{% for leaderboard in leaderboards %}
<div class="score-table">
<h2>{{ leaderboard.title }}</h2>
<table>
<thead>
<tr>
{% for header in leaderboard.headers %}
<th>{{ header }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for entry in leaderboard.entries %}
<tr>
{% for attr in entry %}
<td>{{ attr }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endfor %}
</div>
<footer class="footer flex-container-c">
<div class="flex-container-r">
<div>
<p><a href="https://sowinski.dev">sowinski.dev</a></p>
</div>
<div class="social-icons">
<a class="icon" href="https://www.linkedin.com/in/SowinskiBraeden" target=_blank rel="noopener noreferrer me"
title=LinkedIn>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-linkedin"
viewBox="0 0 16 16">
<path
d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z" />
</svg>
</a>
<a class="icon" href=https://github.com/SowinskiBraeden/slacker target=_blank rel="noopener noreferrer me" title=Github>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentcolor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<path
d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37.0 00-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44.0 0020 4.77 5.07 5.07.0 0019.91 1S18.73.65 16 2.48a13.38 13.38.0 00-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07.0 005 4.77 5.44 5.44.0 003.5 8.55c0 5.42 3.3 6.61 6.44 7A3.37 3.37.0 009 18.13V22" />
</svg>
</a>
</div>
</div>
</div>
</body>
</html>
Regular → Executable
+13 -101
View File
@@ -1,118 +1,30 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>COMP 1800 - Slacker</title> <title>COMP 2800 - Slacker</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles/index.css') }}">
<style>
@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;
}
</style>
</head> </head>
<body> <body>
<section class="main"> <section class="main">
<div> <div>
<h1 class="title">COMP 1800 Slacker</h1> <h1 class="title">COMP 2800 Slacker</h1>
<h3>See Leaderboards</h3> <h3>See Leaderboards</h3>
<p>PS: Some projects & users may not be up to date with others.</p> <p>
<p>Also, I am lazy and don't want to make a pretty table so you just get .txt files</p> Disclaimer: This reads commits from git log and may not match
<p>Check back for daily updates.</p> commits from Github insights.
</p>
<p>
Additionally, any commits made through the Github web interface are ignored.
</p>
<p>
Files with matching patterns may be ignored in your commits. E.g. node_module files.
</p>
<ul> <ul>
<li><a class="link" href="/boards/TopContributorsAllTime">Top Contributor of All</a></li> <li><a class="link" href="/boards/TopContributorsAllTime">Top Contributor of All</a></li>
<li><a class="link" href="/boards/TopContributorPerCampus">Top Contributor per Campus</a></li> <li><a class="link" href="/boards/TopContributorPerCampus">Top Contributor per Campus</a></li>
<li><a class="link" href="/boards/TopContributorPerSet">Top Contributor per Set</a></li>
<li><a class="link" href="/boards/TopContributorsPerTeam">Top Contributor per Team</a></li> <li><a class="link" href="/boards/TopContributorsPerTeam">Top Contributor per Team</a></li>
<li><a class="link" href="/boards/LargestProjectAllTeams">Largest Project of All</a></li> <li><a class="link" href="/boards/LargestProjectAllTeams">Largest Project of All</a></li>
<li><a class="link" href="/boards/LargestProjectsPerCampus">Largest Project per Campus</a></li> <li><a class="link" href="/boards/LargestProjectsPerCampus">Largest Project per Campus</a></li>
<li><a class="link" href="/boards/LargestProjectPerSet">Largest Project per Set</a></li>
</ul> </ul>
</div> </div>
</section> </section>
Executable
+16
View File
@@ -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)
Regular → Executable
View File
File mode changed.