diff --git a/.github/workflows/build_image.yml b/.github/workflows/build_image.yml index 338f66e..80b5ebb 100644 --- a/.github/workflows/build_image.yml +++ b/.github/workflows/build_image.yml @@ -16,6 +16,9 @@ jobs: - name: Checkout ๐Ÿ›Ž๏ธ uses: actions/checkout@v3 + - name: Set up Docker Buildx ๐Ÿ‹ + uses: docker/setup-buildx-action@v2 + - name: Log in to the container registry ๐Ÿšช uses: docker/login-action@v2 with: @@ -39,3 +42,5 @@ jobs: push: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/releases') }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..57455e6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -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 }} diff --git a/README.md b/README.md index 458609e..31da994 100644 --- a/README.md +++ b/README.md @@ -108,11 +108,11 @@ jobs: ```yml - uses: anmol098/waka-readme-stats@master - with: - WAKATIME_API_KEY: ${{ secrets.WAKATIME_API_KEY }} - GH_TOKEN: ${{ secrets.GH_TOKEN }} - SHOW_OS: "False" - SHOW_PROJECTS: "False" + with: + WAKATIME_API_KEY: ${{ secrets.WAKATIME_API_KEY }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} + SHOW_OS: "False" + SHOW_PROJECTS: "False" ``` ### Flags Available @@ -288,14 +288,15 @@ Contributions are welcome! โ™ฅ! Please share any features, and add unit tests! U # Selected Contributors 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. [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. [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. [Pedro Torres](https://github.com/Corfucinas): For Pull Request [#29](https://github.com/anmol098/waka-readme-stats/pull/29) -5. [Aaron Meese](https://github.com/ajmeese7): For Pull Request [#45](https://github.com/anmol098/waka-readme-stats/pull/45) -6. [Arnav Jindal](https://github.com/Daggy1234): For Pull Request [#48](https://github.com/anmol098/waka-readme-stats/pull/48) -7. [Daniel Rowe](https://github.com/DanRowe): For Pull Request [#57](https://github.com/anmol098/waka-readme-stats/pull/57) -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) +2. [Alexander Sergeev](https://github.com/pseusys): Maintainer +3. [Aravind V. Nair](https://github.com/aravindvnair99): Maintainer +4. [Prabhat Singh](https://github.com/prabhatdev): For code timeline graph [#18](https://github.com/anmol098/waka-readme-stats/pull/18) +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) +6. [Pedro Torres](https://github.com/Corfucinas): For Pull Request [#29](https://github.com/anmol098/waka-readme-stats/pull/29) +7. [Aaron Meese](https://github.com/ajmeese7): For Pull Request [#45](https://github.com/anmol098/waka-readme-stats/pull/45) +8. [Arnav Jindal](https://github.com/Daggy1234): For Pull Request [#48](https://github.com/anmol098/waka-readme-stats/pull/48) +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)
Special mention for those who are currently making their profile readme more awesome :smile: :tada: @@ -420,6 +421,10 @@ Contributions are welcome! โ™ฅ! Please share any features, and add unit tests! U - [Muhammad Bilal](https://github.com/BilalJaved15) + - [Wyatt Walsh](https://www.github.com/wyattowalsh) + + - [Nithin Balaji](https://github.com/thenithinbalaji) +
diff --git a/sources/graphics_list_formatter.py b/sources/graphics_list_formatter.py index 10f4862..fd06eb5 100644 --- a/sources/graphics_list_formatter.py +++ b/sources/graphics_list_formatter.py @@ -7,7 +7,7 @@ from pytz import timezone, utc from manager_download import DownloadManager as DM from manager_environment import EnvironmentManager as EM 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. @@ -109,17 +109,17 @@ async def make_commit_day_time_list(time_zone: str) -> str: sum_week = sum(week_days) 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_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" 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_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" 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] 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" diff --git a/sources/main.py b/sources/main.py index c899f35..55b000c 100644 --- a/sources/main.py +++ b/sources/main.py @@ -10,7 +10,7 @@ from humanize import intword, naturalsize, intcomma from manager_download import init_download_manager, DownloadManager as DM from manager_environment import EnvironmentManager as EM 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 graphics_chart_drawer import create_loc_graph, GRAPH_PATH 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" 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") - stats += f"๐Ÿ“Š **{LM.t('This Week I Spend My Time On')}** \n\n```text\n" + no_activity = FM.t("No Activity Tracked This Week") + stats += f"๐Ÿ“Š **{FM.t('This Week I Spend My Time On')}** \n\n```text\n" if EM.SHOW_TIMEZONE: DBM.i("Adding user timezone info...") 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: DBM.i("Adding user top languages info...") 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: DBM.i("Adding user editors info...") 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: DBM.i("Adding user projects info...") 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: 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"]) - 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" @@ -75,20 +75,20 @@ async def get_short_github_info() -> str: :returns: String representation of the 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...") 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!") 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" data = await DM.get_remote_json("github_stats") DBM.i("Adding contributions info...") 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" else: DBM.p("GitHub contributions data unavailable!") @@ -96,23 +96,23 @@ async def get_short_github_info() -> str: DBM.i("Adding opted for hire info...") opted_to_hire = GHM.USER.hireable if opted_to_hire: - stats += f"> ๐Ÿ’ผ {LM.t('Opted to Hire')}\n > \n" + stats += f"> ๐Ÿ’ผ {FM.t('Opted to Hire')}\n > \n" 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...") public_repo = GHM.USER.public_repos 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: - 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...") private_repo = GHM.USER.owned_private_repos if GHM.USER.owned_private_repos is not None else 0 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: - 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!") return stats @@ -144,13 +144,13 @@ async def get_stats() -> str: if EM.SHOW_PROFILE_VIEWS: DBM.i("Adding profile views info...") data = GHM.REMOTE.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: DBM.i("Adding lines of code info...") total_loc = sum([yearly_data[y][q][d] for y in yearly_data.keys() for q in yearly_data[y].keys() for d in yearly_data[y][q].keys()]) - data = f"{intword(total_loc)} {LM.t('Lines of code')}" - stats += f"![Lines of code](https://img.shields.io/badge/{quote(LM.t('From Hello World I have written'))}-{quote(data)}-blue)\n\n" + data = f"{intword(total_loc)} {FM.t('Lines of code')}" + stats += f"![Lines of code](https://img.shields.io/badge/{quote(FM.t('From Hello World I have written'))}-{quote(data)}-blue)\n\n" if EM.SHOW_SHORT_INFO: stats += await get_short_github_info() @@ -163,9 +163,7 @@ async def get_stats() -> str: if EM.SHOW_LOC_CHART: await create_loc_graph(yearly_data, GRAPH_PATH) - 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" + stats += GHM.update_chart(GRAPH_PATH) if EM.SHOW_UPDATED_DATE: 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. """ init_github_manager() - await init_download_manager() + await init_download_manager(GHM.USER.login) init_localization_manager() DBM.i("Managers initialized.") - if GHM.update_readme(await get_stats()): - DBM.g("Readme updated!") + stats = await get_stats() + if not EM.DEBUG_RUN: + if GHM.update_readme(stats): + 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() diff --git a/sources/manager_download.py b/sources/manager_download.py index 0505644..71db5d9 100644 --- a/sources/manager_download.py +++ b/sources/manager_download.py @@ -8,10 +8,8 @@ from httpx import AsyncClient from yaml import safe_load from manager_environment import EnvironmentManager as EM -from manager_github import GitHubManager as GHM from manager_debug import DebugManager as DBM - GITHUB_API_QUERIES = { # 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). @@ -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: - Setup headers for GitHub GraphQL requests. - Launch static queries in background. + + :param user_login: GitHub user login. """ await DownloadManager.load_remote_resources( - { - "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_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/{GHM.USER.login}", - }, - {"Authorization": f"Bearer {EM.GH_TOKEN}"}, + 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_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}", ) @@ -157,15 +161,13 @@ class DownloadManager: _REMOTE_RESOURCES_CACHE = dict() @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. - :param resources: Dictionary of static queries, "IDENTIFIER": "URL". - :param github_headers: Dictionary of headers for GitHub API queries. + :param resources: Static queries, formatted like "IDENTIFIER"="URL". """ for resource, url in resources.items(): DownloadManager._REMOTE_RESOURCES_CACHE[resource] = DownloadManager._client.get(url) - DownloadManager._client.headers = github_headers @staticmethod async def close_remote_resources(): @@ -229,10 +231,14 @@ class DownloadManager: """ Execute GitHub GraphQL API simple query. :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. :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: return res.json() else: @@ -268,13 +274,15 @@ class DownloadManager: Queries 100 new results each time until no more results are left. Merges result list into single query, clears pagination-related info. :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. :return: Response JSON dictionary. """ 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) 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) page_list += new_page_list _, page_info = DownloadManager._find_pagination_and_data_list(initial_query_response) diff --git a/sources/manager_localization.py b/sources/manager_file.py similarity index 51% rename from sources/manager_localization.py rename to sources/manager_file.py index 497ec2e..741b681 100644 --- a/sources/manager_localization.py +++ b/sources/manager_file.py @@ -1,5 +1,5 @@ from json import load -from os.path import join, dirname +from os.path import join from typing import Dict from manager_environment import EnvironmentManager as EM @@ -10,10 +10,10 @@ def init_localization_manager(): Initialize localization manager. 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). Stores localization in dictionary. @@ -28,9 +28,9 @@ class LocalizationManager: :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) - LocalizationManager._LOCALIZATION = data[EM.LOCALE] + FileManager._LOCALIZATION = data[EM.LOCALE] @staticmethod def t(key: str) -> str: @@ -40,4 +40,18 @@ class LocalizationManager: :param key: Localization key. :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) diff --git a/sources/manager_github.py b/sources/manager_github.py index 14eae5d..c420401 100644 --- a/sources/manager_github.py +++ b/sources/manager_github.py @@ -1,10 +1,14 @@ -from base64 import b64decode +from base64 import b64decode, b64encode +from os import environ +from random import choice from re import sub +from string import ascii_letters from git import Repo from github import Github, AuthenticatedUser, Repository, ContentFile, InputGitAuthor, UnknownObjectException from manager_environment import EnvironmentManager as EM +from manager_file import FileManager as FM from manager_debug import DebugManager as DBM @@ -105,23 +109,53 @@ class GitHubManager: return False @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. + Inlines data into readme if in debug mode, commits otherwise. Uses commit author, commit message and branch name specified by environmental variables. :param chart_path: path to saved lines of code chart. + :returns: string to add to README file. """ DBM.i("Updating lines of code chart...") with open(chart_path, "rb") as input_file: data = input_file.read() - try: - contents = GitHubManager.REMOTE.get_contents(chart_path) - GitHubManager.REMOTE.update_file(contents.path, "Charts Updated", data, contents.sha, committer=GitHubManager._get_author()) - DBM.g("Lines of code chart updated!") - except UnknownObjectException: - GitHubManager.REMOTE.create_file(chart_path, "Charts Added", data, committer=GitHubManager._get_author()) - DBM.g("Lines of code chart created!") + + 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: + contents = GitHubManager.REMOTE.get_contents(chart_path) + GitHubManager.REMOTE.update_file(contents.path, "Charts Updated", data, contents.sha, committer=GitHubManager._get_author()) + DBM.g("Lines of code chart updated!") + except UnknownObjectException: + GitHubManager.REMOTE.create_file(chart_path, "Charts Added", data, committer=GitHubManager._get_author()) + 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" @staticmethod def commit_repo(): diff --git a/sources/yearly_commit_calculator.py b/sources/yearly_commit_calculator.py index 49bce72..cc52f40 100644 --- a/sources/yearly_commit_calculator.py +++ b/sources/yearly_commit_calculator.py @@ -1,3 +1,4 @@ +from json import dumps from re import search from datetime import datetime from typing import Dict @@ -5,6 +6,7 @@ from typing import Dict from manager_download import DownloadManager as DM from manager_environment import EnvironmentManager as EM from manager_github import GitHubManager as GHM +from manager_file import FileManager as FM 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...") yearly_data = dict() total = len(repositories["data"]["user"]["repositories"]["nodes"]) + for ind, repo in enumerate(repositories["data"]["user"]["repositories"]["nodes"]): if repo["name"] not in EM.IGNORED_REPOS: repo_name = "private" if repo["isPrivate"] else f"{repo['owner']['login']}/{repo['name']}" DBM.i(f"\t{ind + 1}/{total} Retrieving repo: {repo_name}") await update_yearly_data_with_commit_stats(repo, yearly_data) DBM.g("Yearly commit data calculated!") + + if EM.DEBUG_RUN: + FM.write_file("yearly_data.json", dumps(yearly_data), assets=True) return yearly_data