push done with git instead of github

This commit is contained in:
pseusys
2023-03-11 20:15:28 +01:00
parent 7771515605
commit 8eb0910c70
6 changed files with 111 additions and 96 deletions

3
.gitignore vendored
View File

@@ -4,6 +4,9 @@
# Generated graph images: # Generated graph images:
assets/ assets/
# Cloned repo path:
repo/
# Library roots: # Library roots:
venv/ venv/

View File

@@ -47,6 +47,7 @@ lint: venv
clean: clean:
@ # Clean all build files, including: libraries, package manager configs, docker images and containers @ # Clean all build files, including: libraries, package manager configs, docker images and containers
rm -rf venv rm -rf venv
rm -rf repo
rm -rf assets rm -rf assets
rm -f package*.json rm -f package*.json
docker rm -f waka-readme-stats 2>/dev/null || true docker rm -f waka-readme-stats 2>/dev/null || true

View File

@@ -5,10 +5,11 @@ import matplotlib.patches as mpatches
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from manager_download import DownloadManager as DM from manager_download import DownloadManager as DM
from manager_file import FileManager as FM
MAX_LANGUAGES = 5 # Number of top languages to add to chart, for each year quarter MAX_LANGUAGES = 5 # Number of top languages to add to chart, for each year quarter
GRAPH_PATH = "assets/bar_graph.png" # Chart saving path. GRAPH_PATH = f"{FM.ASSETS_DIR}/bar_graph.png" # Chart saving path.
async def create_loc_graph(yearly_data: Dict, save_path: str): async def create_loc_graph(yearly_data: Dict, save_path: str):

View File

@@ -163,7 +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)
stats += GHM.update_chart(GRAPH_PATH) stats += f"**{FM.t('Timeline')}**\n\n{GHM.update_chart('Lines of Code', GRAPH_PATH)}"
if EM.SHOW_UPDATED_DATE: if EM.SHOW_UPDATED_DATE:
DBM.i("Adding last updated time...") DBM.i("Adding last updated time...")
@@ -185,11 +185,10 @@ async def main():
stats = await get_stats() stats = await get_stats()
if not EM.DEBUG_RUN: if not EM.DEBUG_RUN:
if GHM.update_readme(stats): GHM.update_readme(stats)
DBM.g("Readme updated!") GHM.commit_update()
else: else:
if GHM.set_github_output(stats): 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

@@ -20,6 +20,7 @@ class FileManager:
Stores localization in dictionary. Stores localization in dictionary.
""" """
ASSETS_DIR = "assets"
_LOCALIZATION: Dict[str, str] = dict() _LOCALIZATION: Dict[str, str] = dict()
@staticmethod @staticmethod
@@ -53,7 +54,7 @@ class FileManager:
:param append: True for appending to file, false for rewriting. :param append: True for appending to file, false for rewriting.
:param assets: True for saving to 'assets' directory, false otherwise. :param assets: True for saving to 'assets' directory, false otherwise.
""" """
name = join("assets", name) if assets else name name = join(FileManager.ASSETS_DIR, name) if assets else name
with open(name, "a" if append else "w", encoding="utf-8") as file: with open(name, "a" if append else "w", encoding="utf-8") as file:
file.write(content) file.write(content)
@@ -67,7 +68,7 @@ class FileManager:
:param assets: True for saving to 'assets' directory, false otherwise. :param assets: True for saving to 'assets' directory, false otherwise.
:returns: File cache contents if content is None, None otherwise. :returns: File cache contents if content is None, None otherwise.
""" """
name = join("assets", name) if assets else name name = join(FileManager.ASSETS_DIR, name) if assets else name
if content is None and not isfile(name): if content is None and not isfile(name):
return None return None

View File

@@ -1,11 +1,13 @@
from base64 import b64decode, b64encode from base64 import b64encode
from os import environ from os import environ, makedirs
from os.path import dirname, join
from random import choice from random import choice
from re import sub from re import sub
from shutil import copy, rmtree
from string import ascii_letters from string import ascii_letters
from git import Repo from git import Repo, Actor
from github import Github, AuthenticatedUser, Repository, ContentFile, InputGitAuthor, UnknownObjectException from github import Github, AuthenticatedUser, Repository
from manager_environment import EnvironmentManager as EM from manager_environment import EnvironmentManager as EM
from manager_file import FileManager as FM from manager_file import FileManager as FM
@@ -18,17 +20,17 @@ def init_github_manager():
Current user, user readme repo and readme file are downloaded. Current user, user readme repo and readme file are downloaded.
""" """
GitHubManager.prepare_github_env() GitHubManager.prepare_github_env()
DBM.i(f"Current user: {GitHubManager.USER.login}") DBM.i(f"Current user: {GitHubManager.USER.login}.")
class GitHubManager: class GitHubManager:
USER: AuthenticatedUser USER: AuthenticatedUser
REPO: Repo REPO: Repo
REMOTE: Repository REMOTE: Repository
_README: ContentFile
_README_CONTENTS: str
_REPO_PATH = "repo" _REMOTE_NAME: str
_REMOTE_PATH: str
_START_COMMENT = f"<!--START_SECTION:{EM.SECTION_NAME}-->" _START_COMMENT = f"<!--START_SECTION:{EM.SECTION_NAME}-->"
_END_COMMENT = f"<!--END_SECTION:{EM.SECTION_NAME}-->" _END_COMMENT = f"<!--END_SECTION:{EM.SECTION_NAME}-->"
_README_REGEX = f"{_START_COMMENT}[\\s\\S]+{_END_COMMENT}" _README_REGEX = f"{_START_COMMENT}[\\s\\S]+{_END_COMMENT}"
@@ -39,29 +41,24 @@ class GitHubManager:
Download and store for future use: Download and store for future use:
- Current GitHub user. - Current GitHub user.
- Named repo of the user [username]/[username]. - Named repo of the user [username]/[username].
- README.md file of this repo. - Clone of the named repo.
- Parsed contents of the file.
""" """
github = Github(EM.GH_TOKEN) github = Github(EM.GH_TOKEN)
clone_path = "repo"
GitHubManager.USER = github.get_user() GitHubManager.USER = github.get_user()
GitHubManager.REMOTE = github.get_repo(f"{GitHubManager.USER.login}/{GitHubManager.USER.login}") rmtree(clone_path)
GitHubManager.REPO = Repo.clone_from(GitHubManager.REMOTE.clone_url, to_path=GitHubManager._REPO_PATH)
GitHubManager._README = GitHubManager.REMOTE.get_readme() GitHubManager._REMOTE_NAME = f"{GitHubManager.USER.login}/{GitHubManager.USER.login}"
GitHubManager._README_CONTENTS = str(b64decode(GitHubManager._README.content), "utf-8") GitHubManager._REPO_PATH = f"https://{EM.GH_TOKEN}@github.com/{GitHubManager._REMOTE_NAME}.git"
GitHubManager.REMOTE = github.get_repo(GitHubManager._REMOTE_NAME)
GitHubManager.REPO = Repo.clone_from(GitHubManager._REPO_PATH, to_path=clone_path)
GitHubManager.REPO.git.checkout(GitHubManager.branch())
# TODO: delete and recreate branch if single commit
@staticmethod @staticmethod
def _generate_new_readme(stats: str) -> str: def _get_author() -> Actor:
"""
Generates new README.md file, inserts its contents between start and end tags.
:param stats: contents to insert.
:returns: new README.md string.
"""
readme_stats = f"{GitHubManager._START_COMMENT}\n{stats}\n{GitHubManager._END_COMMENT}"
return sub(GitHubManager._README_REGEX, readme_stats, GitHubManager._README_CONTENTS)
@staticmethod
def _get_author() -> InputGitAuthor:
""" """
Gets GitHub commit author specified by environmental variables. Gets GitHub commit author specified by environmental variables.
It is the user himself or a 'readme-bot'. It is the user himself or a 'readme-bot'.
@@ -69,9 +66,9 @@ class GitHubManager:
:returns: Commit author. :returns: Commit author.
""" """
if EM.COMMIT_BY_ME: if EM.COMMIT_BY_ME:
return InputGitAuthor(GitHubManager.USER.login or EM.COMMIT_USERNAME, GitHubManager.USER.email or EM.COMMIT_EMAIL) return Actor(EM.COMMIT_USERNAME or GitHubManager.USER.login, EM.COMMIT_EMAIL or GitHubManager.USER.email)
else: else:
return InputGitAuthor(EM.COMMIT_USERNAME or "readme-bot", EM.COMMIT_EMAIL or "41898282+github-actions[bot]@users.noreply.github.com") return Actor(EM.COMMIT_USERNAME or "readme-bot", EM.COMMIT_EMAIL or "41898282+github-actions[bot]@users.noreply.github.com")
@staticmethod @staticmethod
def branch() -> str: def branch() -> str:
@@ -84,82 +81,95 @@ class GitHubManager:
return GitHubManager.REMOTE.default_branch if EM.BRANCH_NAME == "" else EM.BRANCH_NAME return GitHubManager.REMOTE.default_branch if EM.BRANCH_NAME == "" else EM.BRANCH_NAME
@staticmethod @staticmethod
def update_readme(stats: str) -> bool: def _copy_file_and_add_to_repo(src_path: str):
"""
Copies file to repository folder, creating path if needed and adds file to git.
The copied file relative to repository root path will be equal the source file relative to work directory path.
:param src_path: Source file path.
"""
dst_path = join(GitHubManager.REPO.working_tree_dir, src_path)
makedirs(dirname(src_path), exist_ok=True)
copy(dst_path, src_path)
GitHubManager.REPO.git.add(dst_path)
@staticmethod
def update_readme(stats: str):
""" """
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.
:returns: whether the README.md file was updated or not.
""" """
DBM.i("Updating README...") DBM.i("Updating README...")
new_readme = GitHubManager._generate_new_readme(stats) readme_path = join(GitHubManager.REPO.working_tree_dir, GitHubManager.REMOTE.get_readme().path)
if new_readme != GitHubManager._README_CONTENTS:
GitHubManager.REMOTE.update_file( with open(readme_path, "r") as readme_file:
path=GitHubManager._README.path, readme_contents = readme_file.read()
message=EM.COMMIT_MESSAGE, readme_stats = f"{GitHubManager._START_COMMENT}\n{stats}\n{GitHubManager._END_COMMENT}"
content=new_readme, new_readme = sub(GitHubManager._README_REGEX, readme_stats, readme_contents)
sha=GitHubManager._README.sha,
branch=GitHubManager.branch(), with open(readme_path, "w") as readme_file:
committer=GitHubManager._get_author(), readme_file.write(new_readme)
)
DBM.g("README updated!") GitHubManager.REPO.git.add(readme_path)
return True DBM.g("README updated!")
else:
DBM.w("README update not needed!")
return False
@staticmethod @staticmethod
def set_github_output(stats: str) -> bool: def update_chart(name: str, path: str) -> str:
""" """
Outputs readme data as current action output instead of committing it. Updates a chart.
Inlines data into readme if in debug mode, commits otherwise.
Uses commit author, commit message and branch name specified by environmental variables.
param stats: Readme stats to be outputted. :param name: Name of the chart to update.
:param path: Path of the chart to update.
:returns: String to add to README file.
"""
output = str()
DBM.i(f"Updating {name} chart...")
if not EM.DEBUG_RUN:
DBM.i("\tAdding chart to repo...")
GitHubManager._copy_file_and_add_to_repo(path)
chart_path = f"https://raw.githubusercontent.com/{GitHubManager._REMOTE_NAME}/{GitHubManager.branch()}/{path}"
output += f"![{name} chart]({chart_path})\n\n"
else:
DBM.i("\tInlining chart...")
hint = "You can use [this website](https://codebeautify.org/base64-to-image-converter) to view the generated base64 image."
with open(path, "rb") as input_file:
output += f"{hint}\n```\ndata:image/png;base64,{b64encode(input_file.read()).decode('utf-8')}\n```\n\n"
return output
@staticmethod
def commit_update():
"""
Commit update data to repository.
"""
actor = GitHubManager._get_author()
DBM.i("Committing files to repo...")
GitHubManager.REPO.index.commit(EM.COMMIT_MESSAGE, author=actor, committer=actor)
DBM.i("Pushing files to repo...")
headers = GitHubManager.REPO.remote(name="origin").push()
if len(headers) == 0:
DBM.i(f"Repository push error: {headers}!")
else:
DBM.i("Repository synchronized!")
@staticmethod
def set_github_output(stats: str):
"""
Output readme data as current action output instead of committing it.
:param stats: String representation of stats to output.
""" """
DBM.i("Setting README contents as action output...") DBM.i("Setting README contents as action output...")
if "GITHUB_OUTPUT" not in environ.keys(): if "GITHUB_OUTPUT" not in environ.keys():
DBM.p("Not in GitHub environment, not setting action output!") DBM.p("Not in GitHub environment, not setting action output!")
return False return
else:
DBM.i("Outputting readme contents, check the latest comment for the generated stats.")
prefix = "README stats current output:" prefix = "README stats current output:"
eol = "".join(choice(ascii_letters) for _ in range(10)) 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) 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!") DBM.g("Action output set!")
return True
@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()
if not EM.DEBUG_RUN:
DBM.i("Pushing chart to repo...")
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!")
chart_path = f"https://raw.githubusercontent.com/{GitHubManager.USER.login}/{GitHubManager.USER.login}/{GitHubManager.branch()}/{chart_path}"
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():
pass