slacker 2.0
This commit is contained in:
13 files changed
+728
-554
No files matched your search
Regular → Executable
+3
-2
@@ -1,5 +1,6 @@
|
|||||||
__pycache__/*
|
__pycache__/*
|
||||||
teams.json
|
|
||||||
key.py
|
|
||||||
boards/*
|
boards/*
|
||||||
venv/*
|
venv/*
|
||||||
|
repos/*
|
||||||
|
urls.json
|
||||||
|
teams.json
|
||||||
Regular → Executable
+150
-62
@@ -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 contributor in data:
|
for i in range(len(lines)):
|
||||||
newContributor: Dict[str, str|int] = {
|
line: str = lines[i].replace("\n", "")
|
||||||
"author": contributor["author"]["login"],
|
if "Author: " in line:
|
||||||
"commits": contributor["total"],
|
author = line.split("Author: ")[1].split(" <")[0]
|
||||||
"added": 0,
|
email = line.split("<")[1].split(">")[0].lower()
|
||||||
"deleted": 0
|
|
||||||
}
|
|
||||||
|
|
||||||
for week in contributor["weeks"]:
|
found: bool = False
|
||||||
newContributor["added"] += week["a"]
|
for authorStat in team["contributors"]:
|
||||||
newContributor["deleted"] += week["d"]
|
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
|
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__":
|
||||||
asyncio.run(main())
|
setupDir("repos")
|
||||||
|
setupDir("boards")
|
||||||
|
asyncio.run(main())
|
||||||
Regular → Executable
+185
-230
@@ -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"]
|
||||||
|
|
||||||
|
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)
|
if team["team"] not in teams_in_project:
|
||||||
contributors_per_set[team["set"]].append(author)
|
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["boards"][teams_in_project[team["team"]]]["entries"].append(authorEntry)
|
||||||
contributors_per_project[team["id"]] = []
|
|
||||||
contributors_per_project[team["id"]].append(author)
|
|
||||||
|
|
||||||
contributors_all_time.append(author)
|
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)
|
|
||||||
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 = {
|
campus: int = 0 if "BBY" in team["team"] else 1
|
||||||
"Rank": [],
|
projects_per_campus["boards"][campus]["entries"].append(projectEntry)
|
||||||
"Team": [],
|
projects_all_time["boards"][0]["entries"].append(projectEntry)
|
||||||
"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}")
|
# === Sort leaderboards and write to JSON === #
|
||||||
|
|
||||||
# Largest project per set
|
# Project per Campus
|
||||||
for set in projects_per_set:
|
for campus in range(len(projects_per_campus["boards"])):
|
||||||
filtered = sorted(projects_per_set[set], key=lambda d: d['size'], reverse=True)
|
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 = {
|
contributors_per_campus["boards"][campus]["entries"] = filtered
|
||||||
"Rank": [],
|
|
||||||
"Team": [],
|
|
||||||
"Size": []
|
|
||||||
}
|
|
||||||
|
|
||||||
rank = 0
|
with open("boards/TopContributorPerCampus.json", "w") as file:
|
||||||
for project in filtered:
|
json.dump(contributors_per_campus, file, indent=2)
|
||||||
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}")
|
# 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
|
contributors_per_project["boards"][project]["entries"] = filtered
|
||||||
filtered = sorted(projects_all_time, key=lambda d: d['size'], reverse=True)
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"Rank": [],
|
|
||||||
"Team": [],
|
|
||||||
"Size": []
|
|
||||||
}
|
|
||||||
|
|
||||||
rank = 0
|
with open("boards/TopContributorsPerTeam.json", "w") as file:
|
||||||
for project in filtered:
|
json.dump(contributors_per_project, file, indent=2)
|
||||||
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}")
|
# 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
|
with open("boards/TopContributorsAllTime.json", "w") as file:
|
||||||
for campus in contributors_per_campus:
|
json.dump(contributors_all_time, file, indent=2)
|
||||||
filtered = sorted(contributors_per_campus[campus], key=lambda d: d['added'], reverse=True)
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"Rank": [],
|
|
||||||
"Author": [],
|
|
||||||
"Added": [],
|
|
||||||
"Deleted": [],
|
|
||||||
"Actual": [],
|
|
||||||
"Commits": [],
|
|
||||||
}
|
|
||||||
|
|
||||||
rank = 0
|
def main() -> None:
|
||||||
for author in filtered:
|
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["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}")
|
generateLeaderboards(teams)
|
||||||
|
|
||||||
|
|
||||||
# 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}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
setupDir()
|
|
||||||
main()
|
main()
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
@@ -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()
|
|
||||||
@@ -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
@@ -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")
|
||||||
Executable
+91
@@ -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;
|
||||||
|
}
|
||||||
Executable
+168
@@ -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;
|
||||||
|
}
|
||||||
Executable
+81
@@ -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
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
||||||
Reference in new issue
Block a user