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
+712 -538

No files matched your search

Regular → Executable
+3 -2
View File
@@ -1,5 +1,6 @@
__pycache__/*
teams.json
key.py
boards/*
venv/*
repos/*
urls.json
teams.json
Regular → Executable
+141 -53
View File
@@ -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 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
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] = {
"repo": repoURL,
"team": repo,
"team": teamID,
"contributors": []
}
data: List[Dict[str, any]] = await resp.json()
with open(f"{repoDir}/audit.txt", "r") as auditFile:
lines = auditFile.readlines()
for contributor in data:
newContributor: Dict[str, str|int] = {
"author": contributor["author"]["login"],
"commits": contributor["total"],
authorStats: Dict[str, str|int] | None = None
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()
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
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
}
for week in contributor["weeks"]:
newContributor["added"] += week["a"]
newContributor["deleted"] += week["d"]
if "Merge: " in lines[i - 1]:
authorStats["commits"] += 1
team["contributors"].append(authorStats)
continue
team["contributors"].append(newContributor)
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__":
setupDir("repos")
setupDir("boards")
asyncio.run(main())
Regular → Executable
+178 -223
View File
@@ -1,75 +1,112 @@
#!/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"]:
@@ -77,191 +114,109 @@ def main() -> None:
deleted += author["deleted"]
commits += author["commits"]
author["contributed"] = author["added"] - author["deleted"]
author["actual"] = author["added"] - author["deleted"]
contributors_per_campus[team["campus"]].append(author)
contributors_per_set[team["set"]].append(author)
authorEntry: List[any] = [
None, author["author"], author["added"], author["deleted"], author["actual"], author["commits"]
]
if team["id"] not in contributors_per_project:
contributors_per_project[team["id"]] = []
contributors_per_project[team["id"]].append(author)
campus: int = 0 if "BBY" in team["team"] else 1
contributors_per_campus["boards"][campus]["entries"].append(authorEntry)
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
project = {
"team": team["id"],
"size": size,
"added": added,
"deleted": deleted,
"commits": commits
}
projectEntry: List[str|int] = [
None,
team["team"],
size,
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)
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}")
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)
# Contributors ranker per campus
for campus in contributors_per_campus:
filtered = sorted(contributors_per_campus[campus], key=lambda d: d['added'], reverse=True)
# === Sort leaderboards and write to JSON === #
data = {
"Rank": [],
"Author": [],
"Added": [],
"Deleted": [],
"Actual": [],
"Commits": [],
}
# 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
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']))
projects_per_campus["boards"][campus]["entries"] = filtered
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
for set in contributors_per_set:
filtered = sorted(contributors_per_set[set], key=lambda d: d['added'], reverse=True)
projects_all_time["boards"][0]["entries"] = filtered
data = {
"Rank": [],
"Author": [],
"Added": [],
"Deleted": [],
"Actual": [],
"Commits": [],
}
with open("boards/LargestProjectAllTeams.json", "w") as file:
json.dump(projects_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']))
# 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
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
for project in contributors_per_project:
filtered = sorted(contributors_per_project[project], key=lambda d: d['added'], reverse=True)
# 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
data = {
"Rank": [],
"Author": [],
"Added": [],
"Deleted": [],
"Actual": [],
"Commits": [],
}
contributors_per_project["boards"][project]["entries"] = filtered
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']))
with open("boards/TopContributorsPerTeam.json", "w") as file:
json.dump(contributors_per_project, file, indent=2)
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 = sorted(contributors_all_time, key=lambda d: d['added'], reverse=True)
contributors_all_time["boards"][0]["entries"] = filtered
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 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']))
def main() -> None:
with open("teams.json", "r") as file:
teams: List[Dict] = json.load(file)
pretty_table(data, "TopContributorsAllTime", f"Updated at {date}")
generateLeaderboards(teams)
if __name__ == "__main__":
setupDir()
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
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/<boardID>", 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")
+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>
<html lang="en">
<head>
<title>COMP 1800 - Slacker</title>
<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>
<title>COMP 2800 - Slacker</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles/index.css') }}">
</head>
<body>
<section class="main">
<div>
<h1 class="title">COMP 1800 Slacker</h1>
<h1 class="title">COMP 2800 Slacker</h1>
<h3>See Leaderboards</h3>
<p>PS: Some projects & users may not be up to date with others.</p>
<p>Also, I am lazy and don't want to make a pretty table so you just get .txt files</p>
<p>Check back for daily updates.</p>
<p>
Disclaimer: This reads commits from git log and may not match
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>
<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/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/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/LargestProjectPerSet">Largest Project per Set</a></li>
</ul>
</div>
</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.