Merge branch 'master' into feat/difference_between_additions_and_deletions

This commit is contained in:
Alexander Sergeev
2023-02-27 22:13:35 +01:00
committed by GitHub
9 changed files with 234 additions and 75 deletions

View File

@@ -16,6 +16,9 @@ jobs:
- name: Checkout 🛎️ - name: Checkout 🛎️
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Set up Docker Buildx 🐋
uses: docker/setup-buildx-action@v2
- name: Log in to the container registry 🚪 - name: Log in to the container registry 🚪
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
@@ -39,3 +42,5 @@ jobs:
push: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/releases') }} push: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/releases') }}
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

83
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,83 @@
name: CI
on:
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
ci:
name: Run Test and Review PR
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v3
- name: Setup Python 3.8 🐍
uses: actions/setup-python@v4
with:
python-version: 3.8
- name: Install Dependencies 📥
run: pip install -r requirements.txt
- name: Create Assets Folder 📥
run: mkdir assets
- name: Create Previous Comments 🫣
uses: int128/hide-comment-action@v1
with:
starts-with: "README stats current output:"
- name: Run Action Preview on Current Code 🧪
id: make-stats
env:
INPUT_GH_TOKEN: ${{ secrets.INPUT_GITHUB_TOKEN }}
INPUT_WAKATIME_API_KEY: ${{ secrets.WAKATIME_API_KEY }}
INPUT_SHOW_TIMEZONE: True
INPUT_SHOW_PROJECTS: True
INPUT_SHOW_EDITORS: True
INPUT_SHOW_OS: True
INPUT_SHOW_LANGUAGE: True
INPUT_SYMBOL_VERSION: 1
INPUT_SHOW_LINES_OF_CODE: True
INPUT_SHOW_LOC_CHART: True
INPUT_SHOW_PROFILE_VIEWS: True
INPUT_SHOW_TOTAL_CODE_TIME: True
INPUT_SHOW_SHORT_INFO: True
INPUT_SHOW_COMMIT: True
INPUT_SHOW_DAYS_OF_WEEK: True
INPUT_SHOW_LANGUAGE_PER_REPO: True
INPUT_SHOW_UPDATED_DATE: True
INPUT_COMMIT_BY_ME: True
INPUT_DEBUG_LOGGING: True # Not for prod
DEBUG_RUN: True # Not for prod
run: python3 sources/main.py
- name: Save Branch Name Without Slashes 📛
if: ${{ github.ref != 'refs/heads/master' }}
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
run: |
BRANCH_NAME=${{ env.BRANCH_NAME }}
BRANCH_NAME=${BRANCH_NAME////_}
echo BRANCH_NAME=${BRANCH_NAME} >> $GITHUB_ENV
- name: Upload Artifact 📦
uses: actions/upload-artifact@v3
if: ${{ github.ref != 'refs/heads/master' }}
with:
name: ${{ format('github-pages-for-branch-{0}', env.BRANCH_NAME) }}
path: assets
- name: Create Comment 💬
uses: jungwinter/comment@v1
with:
type: create
body: ${{ steps.make-stats.outputs.README_CONTENT }}
issue_number: ${{ github.event.number }}
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -288,14 +288,15 @@ Contributions are welcome! ♥! Please share any features, and add unit tests! U
# Selected Contributors # Selected Contributors
1. [Anmol Pratap Singh](https://github.com/anmol098): Maintainer 1. [Anmol Pratap Singh](https://github.com/anmol098): Maintainer
2. [Prabhat Singh](https://github.com/prabhatdev): For code timeline graph [#18](https://github.com/anmol098/waka-readme-stats/pull/18) 2. [Alexander Sergeev](https://github.com/pseusys): Maintainer
2. [Aravind V. Nair](https://github.com/aravindvnair99): Maintainer and For Pull Request [#188](https://github.com/anmol098/waka-readme-stats/pull/188) And Other improvements 3. [Aravind V. Nair](https://github.com/aravindvnair99): Maintainer
3. [Hedy Li](https://github.com/hedythedev): For Pull Request [#34](https://github.com/anmol098/waka-readme-stats/pull/34) and [#23](https://github.com/anmol098/waka-readme-stats/pull/23) 4. [Prabhat Singh](https://github.com/prabhatdev): For code timeline graph [#18](https://github.com/anmol098/waka-readme-stats/pull/18)
4. [Pedro Torres](https://github.com/Corfucinas): For Pull Request [#29](https://github.com/anmol098/waka-readme-stats/pull/29) 5. [Hedy Li](https://github.com/hedythedev): For Pull Request [#34](https://github.com/anmol098/waka-readme-stats/pull/34) and [#23](https://github.com/anmol098/waka-readme-stats/pull/23)
5. [Aaron Meese](https://github.com/ajmeese7): For Pull Request [#45](https://github.com/anmol098/waka-readme-stats/pull/45) 6. [Pedro Torres](https://github.com/Corfucinas): For Pull Request [#29](https://github.com/anmol098/waka-readme-stats/pull/29)
6. [Arnav Jindal](https://github.com/Daggy1234): For Pull Request [#48](https://github.com/anmol098/waka-readme-stats/pull/48) 7. [Aaron Meese](https://github.com/ajmeese7): For Pull Request [#45](https://github.com/anmol098/waka-readme-stats/pull/45)
7. [Daniel Rowe](https://github.com/DanRowe): For Pull Request [#57](https://github.com/anmol098/waka-readme-stats/pull/57) 8. [Arnav Jindal](https://github.com/Daggy1234): For Pull Request [#48](https://github.com/anmol098/waka-readme-stats/pull/48)
8. [Ss5h](https://github.com/tlatkdgus1): For adding support for natural sentence writing for translation [#136](https://github.com/anmol098/waka-readme-stats/pull/136) 9. [Daniel Rowe](https://github.com/DanRowe): For Pull Request [#57](https://github.com/anmol098/waka-readme-stats/pull/57)
10. [Ss5h](https://github.com/tlatkdgus1): For adding support for natural sentence writing for translation [#136](https://github.com/anmol098/waka-readme-stats/pull/136)
<details> <details>
<summary>Special mention for those who are currently making their profile readme more awesome :smile: :tada:</summary> <summary>Special mention for those who are currently making their profile readme more awesome :smile: :tada:</summary>
@@ -420,6 +421,10 @@ Contributions are welcome! ♥! Please share any features, and add unit tests! U
- [Muhammad Bilal](https://github.com/BilalJaved15) - [Muhammad Bilal](https://github.com/BilalJaved15)
- [Wyatt Walsh](https://www.github.com/wyattowalsh)
- [Nithin Balaji](https://github.com/thenithinbalaji)
</details> </details>

View File

@@ -7,7 +7,7 @@ from pytz import timezone, utc
from manager_download import DownloadManager as DM from manager_download import DownloadManager as DM
from manager_environment import EnvironmentManager as EM from manager_environment import EnvironmentManager as EM
from manager_github import GitHubManager as GHM from manager_github import GitHubManager as GHM
from manager_localization import LocalizationManager as LM from manager_file import FileManager as FM
DAY_TIME_EMOJI = ["🌞", "🌆", "🌃", "🌙"] # Emojis, representing different times of day. DAY_TIME_EMOJI = ["🌞", "🌆", "🌃", "🌙"] # Emojis, representing different times of day.
@@ -109,17 +109,17 @@ async def make_commit_day_time_list(time_zone: str) -> str:
sum_week = sum(week_days) sum_week = sum(week_days)
day_times = day_times[1:] + day_times[:1] day_times = day_times[1:] + day_times[:1]
dt_names = [f"{DAY_TIME_EMOJI[i]} {LM.t(DAY_TIME_NAMES[i])}" for i in range(len(day_times))] dt_names = [f"{DAY_TIME_EMOJI[i]} {FM.t(DAY_TIME_NAMES[i])}" for i in range(len(day_times))]
dt_texts = [f"{day_time} commits" for day_time in day_times] dt_texts = [f"{day_time} commits" for day_time in day_times]
dt_percents = [round((day_time / sum_day) * 100, 2) for day_time in day_times] dt_percents = [round((day_time / sum_day) * 100, 2) for day_time in day_times]
title = LM.t("I am an Early") if sum(day_times[0:2]) >= sum(day_times[2:4]) else LM.t("I am a Night") title = FM.t("I am an Early") if sum(day_times[0:2]) >= sum(day_times[2:4]) else FM.t("I am a Night")
stats += f"**{title}** \n\n```text\n{make_list(names=dt_names, texts=dt_texts, percents=dt_percents, top_num=7, sort=False)}\n```\n" stats += f"**{title}** \n\n```text\n{make_list(names=dt_names, texts=dt_texts, percents=dt_percents, top_num=7, sort=False)}\n```\n"
if EM.SHOW_DAYS_OF_WEEK: if EM.SHOW_DAYS_OF_WEEK:
wd_names = [LM.t(week_day) for week_day in WEEK_DAY_NAMES] wd_names = [FM.t(week_day) for week_day in WEEK_DAY_NAMES]
wd_texts = [f"{week_day} commits" for week_day in week_days] wd_texts = [f"{week_day} commits" for week_day in week_days]
wd_percents = [round((week_day / sum_week) * 100, 2) for week_day in week_days] wd_percents = [round((week_day / sum_week) * 100, 2) for week_day in week_days]
title = LM.t("I am Most Productive on") % wd_names[wd_percents.index(max(wd_percents))] title = FM.t("I am Most Productive on") % wd_names[wd_percents.index(max(wd_percents))]
stats += f"📅 **{title}** \n\n```text\n{make_list(names=wd_names, texts=wd_texts, percents=wd_percents, top_num=7, sort=False)}\n```\n" stats += f"📅 **{title}** \n\n```text\n{make_list(names=wd_names, texts=wd_texts, percents=wd_percents, top_num=7, sort=False)}\n```\n"
return stats return stats
@@ -144,5 +144,5 @@ def make_language_per_repo_list(repositories: Dict) -> str:
percents = [round(language_count[lang]["count"] / len(repos_with_language) * 100, 2) for lang in names] percents = [round(language_count[lang]["count"] / len(repos_with_language) * 100, 2) for lang in names]
top_language = max(list(language_count.keys()), key=lambda x: language_count[x]["count"]) top_language = max(list(language_count.keys()), key=lambda x: language_count[x]["count"])
title = f"**{LM.t('I Mostly Code in') % top_language}** \n\n" if len(repos_with_language) > 0 else "" title = f"**{FM.t('I Mostly Code in') % top_language}** \n\n" if len(repos_with_language) > 0 else ""
return f"{title}```text\n{make_list(names=names, texts=texts, percents=percents)}\n```\n\n" return f"{title}```text\n{make_list(names=names, texts=texts, percents=percents)}\n```\n\n"

View File

@@ -10,7 +10,7 @@ from humanize import intword, naturalsize, intcomma
from manager_download import init_download_manager, DownloadManager as DM from manager_download import init_download_manager, DownloadManager as DM
from manager_environment import EnvironmentManager as EM from manager_environment import EnvironmentManager as EM
from manager_github import init_github_manager, GitHubManager as GHM from manager_github import init_github_manager, GitHubManager as GHM
from manager_localization import init_localization_manager, LocalizationManager as LM from manager_file import init_localization_manager, FileManager as FM
from manager_debug import init_debug_manager, DebugManager as DBM from manager_debug import init_debug_manager, DebugManager as DBM
from graphics_chart_drawer import create_loc_graph, GRAPH_PATH from graphics_chart_drawer import create_loc_graph, GRAPH_PATH
from yearly_commit_calculator import calculate_yearly_commit_data from yearly_commit_calculator import calculate_yearly_commit_data
@@ -33,33 +33,33 @@ async def get_waka_time_stats() -> str:
stats += f"{await make_commit_day_time_list(data['data']['timezone'])}\n\n" stats += f"{await make_commit_day_time_list(data['data']['timezone'])}\n\n"
if EM.SHOW_TIMEZONE or EM.SHOW_LANGUAGE or EM.SHOW_EDITORS or EM.SHOW_PROJECTS or EM.SHOW_OS: if EM.SHOW_TIMEZONE or EM.SHOW_LANGUAGE or EM.SHOW_EDITORS or EM.SHOW_PROJECTS or EM.SHOW_OS:
no_activity = LM.t("No Activity Tracked This Week") no_activity = FM.t("No Activity Tracked This Week")
stats += f"📊 **{LM.t('This Week I Spend My Time On')}** \n\n```text\n" stats += f"📊 **{FM.t('This Week I Spend My Time On')}** \n\n```text\n"
if EM.SHOW_TIMEZONE: if EM.SHOW_TIMEZONE:
DBM.i("Adding user timezone info...") DBM.i("Adding user timezone info...")
time_zone = data["data"]["timezone"] time_zone = data["data"]["timezone"]
stats += f"🕑︎ {LM.t('Timezone')}: {time_zone}\n\n" stats += f"🕑︎ {FM.t('Timezone')}: {time_zone}\n\n"
if EM.SHOW_LANGUAGE: if EM.SHOW_LANGUAGE:
DBM.i("Adding user top languages info...") DBM.i("Adding user top languages info...")
lang_list = no_activity if len(data["data"]["languages"]) == 0 else make_list(data["data"]["languages"]) lang_list = no_activity if len(data["data"]["languages"]) == 0 else make_list(data["data"]["languages"])
stats += f"💬 {LM.t('Languages')}: \n{lang_list}\n\n" stats += f"💬 {FM.t('Languages')}: \n{lang_list}\n\n"
if EM.SHOW_EDITORS: if EM.SHOW_EDITORS:
DBM.i("Adding user editors info...") DBM.i("Adding user editors info...")
edit_list = no_activity if len(data["data"]["editors"]) == 0 else make_list(data["data"]["editors"]) edit_list = no_activity if len(data["data"]["editors"]) == 0 else make_list(data["data"]["editors"])
stats += f"🔥 {LM.t('Editors')}: \n{edit_list}\n\n" stats += f"🔥 {FM.t('Editors')}: \n{edit_list}\n\n"
if EM.SHOW_PROJECTS: if EM.SHOW_PROJECTS:
DBM.i("Adding user projects info...") DBM.i("Adding user projects info...")
project_list = no_activity if len(data["data"]["projects"]) == 0 else make_list(data["data"]["projects"]) project_list = no_activity if len(data["data"]["projects"]) == 0 else make_list(data["data"]["projects"])
stats += f"🐱‍💻 {LM.t('Projects')}: \n{project_list}\n\n" stats += f"🐱‍💻 {FM.t('Projects')}: \n{project_list}\n\n"
if EM.SHOW_OS: if EM.SHOW_OS:
DBM.i("Adding user operating systems info...") DBM.i("Adding user operating systems info...")
os_list = no_activity if len(data["data"]["operating_systems"]) == 0 else make_list(data["data"]["operating_systems"]) os_list = no_activity if len(data["data"]["operating_systems"]) == 0 else make_list(data["data"]["operating_systems"])
stats += f"💻 {LM.t('operating system')}: \n{os_list}\n\n" stats += f"💻 {FM.t('operating system')}: \n{os_list}\n\n"
stats = f"{stats[:-1]}```\n\n" stats = f"{stats[:-1]}```\n\n"
@@ -75,20 +75,20 @@ async def get_short_github_info() -> str:
:returns: String representation of the info. :returns: String representation of the info.
""" """
DBM.i("Adding short GitHub info...") DBM.i("Adding short GitHub info...")
stats = f"**🐱 {LM.t('My GitHub Data')}** \n\n" stats = f"**🐱 {FM.t('My GitHub Data')}** \n\n"
DBM.i("Adding user disk usage info...") DBM.i("Adding user disk usage info...")
if GHM.USER.disk_usage is None: if GHM.USER.disk_usage is None:
disk_usage = LM.t("Used in GitHub's Storage") % "?" disk_usage = FM.t("Used in GitHub's Storage") % "?"
DBM.p("Please add new github personal access token with user permission!") DBM.p("Please add new github personal access token with user permission!")
else: else:
disk_usage = LM.t("Used in GitHub's Storage") % naturalsize(GHM.USER.disk_usage) disk_usage = FM.t("Used in GitHub's Storage") % naturalsize(GHM.USER.disk_usage)
stats += f"> 📦 {disk_usage} \n > \n" stats += f"> 📦 {disk_usage} \n > \n"
data = await DM.get_remote_json("github_stats") data = await DM.get_remote_json("github_stats")
DBM.i("Adding contributions info...") DBM.i("Adding contributions info...")
if len(data["years"]) > 0: if len(data["years"]) > 0:
contributions = LM.t("Contributions in the year") % (intcomma(data["years"][0]["total"]), data["years"][0]["year"]) contributions = FM.t("Contributions in the year") % (intcomma(data["years"][0]["total"]), data["years"][0]["year"])
stats += f"> 🏆 {contributions}\n > \n" stats += f"> 🏆 {contributions}\n > \n"
else: else:
DBM.p("GitHub contributions data unavailable!") DBM.p("GitHub contributions data unavailable!")
@@ -96,23 +96,23 @@ async def get_short_github_info() -> str:
DBM.i("Adding opted for hire info...") DBM.i("Adding opted for hire info...")
opted_to_hire = GHM.USER.hireable opted_to_hire = GHM.USER.hireable
if opted_to_hire: if opted_to_hire:
stats += f"> 💼 {LM.t('Opted to Hire')}\n > \n" stats += f"> 💼 {FM.t('Opted to Hire')}\n > \n"
else: else:
stats += f"> 🚫 {LM.t('Not Opted to Hire')}\n > \n" stats += f"> 🚫 {FM.t('Not Opted to Hire')}\n > \n"
DBM.i("Adding public repositories info...") DBM.i("Adding public repositories info...")
public_repo = GHM.USER.public_repos public_repo = GHM.USER.public_repos
if public_repo != 1: if public_repo != 1:
stats += f"> 📜 {LM.t('public repositories') % public_repo} \n > \n" stats += f"> 📜 {FM.t('public repositories') % public_repo} \n > \n"
else: else:
stats += f"> 📜 {LM.t('public repository') % public_repo} \n > \n" stats += f"> 📜 {FM.t('public repository') % public_repo} \n > \n"
DBM.i("Adding private repositories info...") DBM.i("Adding private repositories info...")
private_repo = GHM.USER.owned_private_repos if GHM.USER.owned_private_repos is not None else 0 private_repo = GHM.USER.owned_private_repos if GHM.USER.owned_private_repos is not None else 0
if public_repo != 1: if public_repo != 1:
stats += f"> 🔑 {LM.t('private repositories') % private_repo} \n > \n" stats += f"> 🔑 {FM.t('private repositories') % private_repo} \n > \n"
else: else:
stats += f"> 🔑 {LM.t('private repository') % private_repo} \n > \n" stats += f"> 🔑 {FM.t('private repository') % private_repo} \n > \n"
DBM.g("Short GitHub info added!") DBM.g("Short GitHub info added!")
return stats return stats
@@ -144,7 +144,7 @@ async def get_stats() -> str:
if EM.SHOW_PROFILE_VIEWS: if EM.SHOW_PROFILE_VIEWS:
DBM.i("Adding profile views info...") DBM.i("Adding profile views info...")
data = GHM.REPO.get_views_traffic(per="week") data = GHM.REPO.get_views_traffic(per="week")
stats += f"![Profile Views](http://img.shields.io/badge/{quote(LM.t('Profile Views'))}-{data['count']}-blue)\n\n" stats += f"![Profile Views](http://img.shields.io/badge/{quote(FM.t('Profile Views'))}-{data['count']}-blue)\n\n"
if EM.SHOW_LINES_OF_CODE: if EM.SHOW_LINES_OF_CODE:
DBM.i("Adding lines of code info...") DBM.i("Adding lines of code info...")
@@ -163,9 +163,7 @@ async def get_stats() -> str:
if EM.SHOW_LOC_CHART: if EM.SHOW_LOC_CHART:
await create_loc_graph(yearly_data, GRAPH_PATH) await create_loc_graph(yearly_data, GRAPH_PATH)
GHM.update_chart(GRAPH_PATH) stats += GHM.update_chart(GRAPH_PATH)
chart_path = f"{GHM.USER.login}/{GHM.USER.login}/{GHM.branch()}/{GRAPH_PATH}"
stats += f"**{LM.t('Timeline')}**\n\n![Lines of Code chart](https://raw.githubusercontent.com/{chart_path})\n\n"
if EM.SHOW_UPDATED_DATE: if EM.SHOW_UPDATED_DATE:
DBM.i("Adding last updated time...") DBM.i("Adding last updated time...")
@@ -181,12 +179,17 @@ async def main():
Initializes all managers, collects user info and updates README.md if necessary. Initializes all managers, collects user info and updates README.md if necessary.
""" """
init_github_manager() init_github_manager()
await init_download_manager() await init_download_manager(GHM.USER.login)
init_localization_manager() init_localization_manager()
DBM.i("Managers initialized.") DBM.i("Managers initialized.")
if GHM.update_readme(await get_stats()): stats = await get_stats()
if not EM.DEBUG_RUN:
if GHM.update_readme(stats):
DBM.g("Readme updated!") DBM.g("Readme updated!")
else:
GHM.set_github_output(stats)
DBM.g("Debug run, readme not updated. Check the latest comment for the generated stats.")
await DM.close_remote_resources() await DM.close_remote_resources()

View File

@@ -8,10 +8,8 @@ from httpx import AsyncClient
from yaml import safe_load from yaml import safe_load
from manager_environment import EnvironmentManager as EM from manager_environment import EnvironmentManager as EM
from manager_github import GitHubManager as GHM
from manager_debug import DebugManager as DBM from manager_debug import DebugManager as DBM
GITHUB_API_QUERIES = { GITHUB_API_QUERIES = {
# Query to collect info about all user repositories, including: is it a fork, name and owner login. # Query to collect info about all user repositories, including: is it a fork, name and owner login.
# NB! Query includes information about recent repositories only (apparently, contributed within a year). # NB! Query includes information about recent repositories only (apparently, contributed within a year).
@@ -120,24 +118,30 @@ GITHUB_API_QUERIES = {
} }
} }
} }
""",
"hide_outdated_comment": """
mutation {
minimizeComment(input: {classifier:OUTDATED, subjectId: "$id"}) {
clientMutationId
}
}
""", """,
} }
async def init_download_manager(): async def init_download_manager(user_login: str):
""" """
Initialize download manager: Initialize download manager:
- Setup headers for GitHub GraphQL requests. - Setup headers for GitHub GraphQL requests.
- Launch static queries in background. - Launch static queries in background.
:param user_login: GitHub user login.
""" """
await DownloadManager.load_remote_resources( await DownloadManager.load_remote_resources(
{ linguist="https://cdn.jsdelivr.net/gh/github/linguist@master/lib/linguist/languages.yml",
"linguist": "https://cdn.jsdelivr.net/gh/github/linguist@master/lib/linguist/languages.yml", waka_latest=f"https://wakatime.com/api/v1/users/current/stats/last_7_days?api_key={EM.WAKATIME_API_KEY}",
"waka_latest": f"https://wakatime.com/api/v1/users/current/stats/last_7_days?api_key={EM.WAKATIME_API_KEY}", waka_all=f"https://wakatime.com/api/v1/users/current/all_time_since_today?api_key={EM.WAKATIME_API_KEY}",
"waka_all": f"https://wakatime.com/api/v1/users/current/all_time_since_today?api_key={EM.WAKATIME_API_KEY}", github_stats=f"https://github-contributions.vercel.app/api/v1/{user_login}",
"github_stats": f"https://github-contributions.vercel.app/api/v1/{GHM.USER.login}",
},
{"Authorization": f"Bearer {EM.GH_TOKEN}"},
) )
@@ -157,15 +161,13 @@ class DownloadManager:
_REMOTE_RESOURCES_CACHE = dict() _REMOTE_RESOURCES_CACHE = dict()
@staticmethod @staticmethod
async def load_remote_resources(resources: Dict[str, str], github_headers: Dict[str, str]): async def load_remote_resources(**resources: str):
""" """
Prepare DownloadManager to launch GitHub API queries and launch all static queries. Prepare DownloadManager to launch GitHub API queries and launch all static queries.
:param resources: Dictionary of static queries, "IDENTIFIER": "URL". :param resources: Static queries, formatted like "IDENTIFIER"="URL".
:param github_headers: Dictionary of headers for GitHub API queries.
""" """
for resource, url in resources.items(): for resource, url in resources.items():
DownloadManager._REMOTE_RESOURCES_CACHE[resource] = DownloadManager._client.get(url) DownloadManager._REMOTE_RESOURCES_CACHE[resource] = DownloadManager._client.get(url)
DownloadManager._client.headers = github_headers
@staticmethod @staticmethod
async def close_remote_resources(): async def close_remote_resources():
@@ -229,10 +231,14 @@ class DownloadManager:
""" """
Execute GitHub GraphQL API simple query. Execute GitHub GraphQL API simple query.
:param query: Dynamic query identifier. :param query: Dynamic query identifier.
:param use_github_action: Use GitHub actions bot auth token instead of current user.
:param kwargs: Parameters for substitution of variables in dynamic query. :param kwargs: Parameters for substitution of variables in dynamic query.
:return: Response JSON dictionary. :return: Response JSON dictionary.
""" """
res = await DownloadManager._client.post("https://api.github.com/graphql", json={"query": Template(GITHUB_API_QUERIES[query]).substitute(kwargs)}) headers = {"Authorization": f"Bearer {EM.GH_TOKEN}"}
res = await DownloadManager._client.post(
"https://api.github.com/graphql", json={"query": Template(GITHUB_API_QUERIES[query]).substitute(kwargs)}, headers=headers
)
if res.status_code == 200: if res.status_code == 200:
return res.json() return res.json()
else: else:
@@ -268,13 +274,15 @@ class DownloadManager:
Queries 100 new results each time until no more results are left. Queries 100 new results each time until no more results are left.
Merges result list into single query, clears pagination-related info. Merges result list into single query, clears pagination-related info.
:param query: Dynamic query identifier. :param query: Dynamic query identifier.
:param use_github_action: Use GitHub actions bot auth token instead of current user.
:param kwargs: Parameters for substitution of variables in dynamic query. :param kwargs: Parameters for substitution of variables in dynamic query.
:return: Response JSON dictionary. :return: Response JSON dictionary.
""" """
initial_query_response = await DownloadManager._fetch_graphql_query(query, **kwargs, pagination="first: 100") initial_query_response = await DownloadManager._fetch_graphql_query(query, **kwargs, pagination="first: 100")
page_list, page_info = DownloadManager._find_pagination_and_data_list(initial_query_response) page_list, page_info = DownloadManager._find_pagination_and_data_list(initial_query_response)
while page_info["hasNextPage"]: while page_info["hasNextPage"]:
query_response = await DownloadManager._fetch_graphql_query(query, **kwargs, pagination=f'first: 100, after: "{page_info["endCursor"]}"') pagination = f'first: 100, after: "{page_info["endCursor"]}"'
query_response = await DownloadManager._fetch_graphql_query(query, **kwargs, pagination=pagination)
new_page_list, page_info = DownloadManager._find_pagination_and_data_list(query_response) new_page_list, page_info = DownloadManager._find_pagination_and_data_list(query_response)
page_list += new_page_list page_list += new_page_list
_, page_info = DownloadManager._find_pagination_and_data_list(initial_query_response) _, page_info = DownloadManager._find_pagination_and_data_list(initial_query_response)

View File

@@ -1,5 +1,5 @@
from json import load from json import load
from os.path import join, dirname from os.path import join
from typing import Dict from typing import Dict
from manager_environment import EnvironmentManager as EM from manager_environment import EnvironmentManager as EM
@@ -10,10 +10,10 @@ def init_localization_manager():
Initialize localization manager. Initialize localization manager.
Load GUI translations JSON file. Load GUI translations JSON file.
""" """
LocalizationManager.load_localization("translation.json") FileManager.load_localization("translation.json")
class LocalizationManager: class FileManager:
""" """
Class for handling localization (and maybe other file IO in future). Class for handling localization (and maybe other file IO in future).
Stores localization in dictionary. Stores localization in dictionary.
@@ -28,9 +28,9 @@ class LocalizationManager:
:param file: Localization file path, related to current file (in sources root). :param file: Localization file path, related to current file (in sources root).
""" """
with open(join(dirname(__file__), file), encoding="utf-8") as config_file: with open(join("sources", file), encoding="utf-8") as config_file:
data = load(config_file) data = load(config_file)
LocalizationManager._LOCALIZATION = data[EM.LOCALE] FileManager._LOCALIZATION = data[EM.LOCALE]
@staticmethod @staticmethod
def t(key: str) -> str: def t(key: str) -> str:
@@ -40,4 +40,18 @@ class LocalizationManager:
:param key: Localization key. :param key: Localization key.
:returns: Translation string. :returns: Translation string.
""" """
return LocalizationManager._LOCALIZATION[key] return FileManager._LOCALIZATION[key]
@staticmethod
def write_file(name: str, content: str, append: bool = False, assets: bool = False):
"""
Save output file.
:param name: File name.
:param content: File content (utf-8 string).
:param append: True for appending to file, false for rewriting.
:param assets: True for saving to 'assets' directory, false otherwise.
"""
name = join("assets", name) if assets else name
with open(name, "a" if append else "w", encoding="utf-8") as file:
file.write(content)

View File

@@ -1,9 +1,13 @@
from base64 import b64decode from base64 import b64decode, b64encode
from os import environ
from random import choice
from re import sub from re import sub
from string import ascii_letters
from github import Github, AuthenticatedUser, Repository, ContentFile, InputGitAuthor, UnknownObjectException from github import Github, AuthenticatedUser, Repository, ContentFile, InputGitAuthor, UnknownObjectException
from manager_environment import EnvironmentManager as EM from manager_environment import EnvironmentManager as EM
from manager_file import FileManager as FM
from manager_debug import DebugManager as DBM from manager_debug import DebugManager as DBM
@@ -81,6 +85,7 @@ class GitHubManager:
Updates readme with given data if necessary. Updates readme with given data if necessary.
Uses commit author, commit message and branch name specified by environmental variables. Uses commit author, commit message and branch name specified by environmental variables.
:param stats: Readme stats to be pushed.
:returns: whether the README.md file was updated or not. :returns: whether the README.md file was updated or not.
""" """
DBM.i("Updating README...") DBM.i("Updating README...")
@@ -101,16 +106,40 @@ class GitHubManager:
return False return False
@staticmethod @staticmethod
def update_chart(chart_path: str): def set_github_output(stats: str):
"""
Outputs readme data as current action output instead of committing it.
param stats: Readme stats to be outputted.
"""
DBM.i("Setting README contents as action output...")
if "GITHUB_OUTPUT" not in environ.keys():
raise Exception("Not in GitHub environment ('GITHUB_OUTPUT' not defined)!")
prefix = "README stats current output:"
eol = "".join(choice(ascii_letters) for _ in range(10))
FM.write_file(environ["GITHUB_OUTPUT"], f"README_CONTENT<<{eol}\n{prefix}\n\n{stats}\n{eol}\n", append=True)
DBM.g("Action output set!")
@staticmethod
def update_chart(chart_path: str) -> str:
""" """
Updates lines of code chart. Updates lines of code chart.
Inlines data into readme if in debug mode, commits otherwise.
Uses commit author, commit message and branch name specified by environmental variables. Uses commit author, commit message and branch name specified by environmental variables.
:param chart_path: path to saved lines of code chart. :param chart_path: path to saved lines of code chart.
:returns: string to add to README file.
""" """
DBM.i("Updating lines of code chart...") DBM.i("Updating lines of code chart...")
with open(chart_path, "rb") as input_file: with open(chart_path, "rb") as input_file:
data = input_file.read() data = input_file.read()
if not EM.DEBUG_RUN:
DBM.i("Pushing chart to repo...")
chart_path = f"https://raw.githubusercontent.com/{GitHubManager.USER.login}/{GitHubManager.USER.login}/{GitHubManager.branch()}/{chart_path}"
try: try:
contents = GitHubManager.REPO.get_contents(chart_path) contents = GitHubManager.REPO.get_contents(chart_path)
GitHubManager.REPO.update_file(contents.path, "Charts Updated", data, contents.sha, committer=GitHubManager._get_author()) GitHubManager.REPO.update_file(contents.path, "Charts Updated", data, contents.sha, committer=GitHubManager._get_author())
@@ -118,3 +147,9 @@ class GitHubManager:
except UnknownObjectException: except UnknownObjectException:
GitHubManager.REPO.create_file(chart_path, "Charts Added", data, committer=GitHubManager._get_author()) GitHubManager.REPO.create_file(chart_path, "Charts Added", data, committer=GitHubManager._get_author())
DBM.g("Lines of code chart created!") DBM.g("Lines of code chart created!")
return f"**{FM.t('Timeline')}**\n\n![Lines of Code chart]({chart_path})\n\n"
else:
DBM.i("Inlining chart...")
hint = "You can use [this website](https://codebeautify.org/base64-to-image-converter) to view the generated base64 image."
return f"{hint}\n```\ndata:image/png;base64,{b64encode(data).decode('utf-8')}\n```\n\n"

View File

@@ -1,3 +1,4 @@
from json import dumps
from re import search from re import search
from datetime import datetime from datetime import datetime
from typing import Dict from typing import Dict
@@ -5,6 +6,7 @@ from typing import Dict
from manager_download import DownloadManager as DM from manager_download import DownloadManager as DM
from manager_environment import EnvironmentManager as EM from manager_environment import EnvironmentManager as EM
from manager_github import GitHubManager as GHM from manager_github import GitHubManager as GHM
from manager_file import FileManager as FM
from manager_debug import DebugManager as DBM from manager_debug import DebugManager as DBM
@@ -19,12 +21,16 @@ async def calculate_yearly_commit_data(repositories: Dict) -> Dict:
DBM.i("Calculating yearly commit data...") DBM.i("Calculating yearly commit data...")
yearly_data = dict() yearly_data = dict()
total = len(repositories["data"]["user"]["repositories"]["nodes"]) total = len(repositories["data"]["user"]["repositories"]["nodes"])
for ind, repo in enumerate(repositories["data"]["user"]["repositories"]["nodes"]): for ind, repo in enumerate(repositories["data"]["user"]["repositories"]["nodes"]):
if repo["name"] not in EM.IGNORED_REPOS: if repo["name"] not in EM.IGNORED_REPOS:
repo_name = "[private]" if repo["isPrivate"] else f"{repo['owner']['login']}/{repo['name']}" repo_name = "[private]" if repo["isPrivate"] else f"{repo['owner']['login']}/{repo['name']}"
DBM.i(f"\t{ind + 1}/{total} Retrieving repo: {repo_name}") DBM.i(f"\t{ind + 1}/{total} Retrieving repo: {repo_name}")
await update_yearly_data_with_commit_stats(repo, yearly_data) await update_yearly_data_with_commit_stats(repo, yearly_data)
DBM.g("Yearly commit data calculated!") DBM.g("Yearly commit data calculated!")
if EM.DEBUG_RUN:
FM.write_file("yearly_data.json", dumps(yearly_data), assets=True)
return yearly_data return yearly_data