Compare commits
149 Commits
Author | SHA1 | Date | |
---|---|---|---|
c1b28d7f43 | |||
512d27739c | |||
ad3a3b38e6 | |||
1d9eeae447 | |||
c0345cb5d9 | |||
632b13121d | |||
e9ab3581aa | |||
8b0ab3bd76 | |||
7a6d95d436 | |||
fd26197e70 | |||
99b6b3f887 | |||
c2f0b9de48 | |||
95c7f84451 | |||
d1558ad6db | |||
76d3c7249a | |||
450ba61d22 | |||
3d3239a2e3 | |||
830c76c07a | |||
6125862010 | |||
b88da2af7d | |||
b55e75a74d | |||
3bc9d82ee4 | |||
980f869999 | |||
108a90abce | |||
|
5dc349128a | ||
|
d2e7ba7208 | ||
|
2dd14d3fd8 | ||
|
e644272268 | ||
|
2aab71918a | ||
|
3f2f9d748e | ||
|
54c3312012 | ||
|
dd576f44c6 | ||
|
b2363ac606 | ||
|
519a95b477 | ||
|
ac8e6017f5 | ||
|
ba3aa17f4e | ||
|
9630bc0a2b | ||
|
78ac94ef4f | ||
|
a3e45eb050 | ||
|
7d29cf150e | ||
|
cefd952b71 | ||
|
77ba5f6a96 | ||
|
c34fb893a6 | ||
|
1fc26a4121 | ||
|
82c9408d6d | ||
|
9bee9ba11b | ||
|
b42a071671 | ||
|
e8541dbe4e | ||
|
da0f4f1847 | ||
|
d26ed33e7a | ||
|
a50019231d | ||
|
080a8c9b63 | ||
|
fbc9196645 | ||
|
59f35b046b | ||
|
60fa45f3f0 | ||
|
60fe3b9f48 | ||
|
29dba6dd79 | ||
|
ac0bb21462 | ||
|
413150be53 | ||
|
b2db3c3280 | ||
|
6e66f34e5a | ||
|
de673c4749 | ||
|
ce472c9c93 | ||
|
8514942821 | ||
|
d2c91885c3 | ||
|
bd7707fc5a | ||
|
72af24c8af | ||
|
8ffb95d479 | ||
|
a80f7247c2 | ||
|
f106d3b9cc | ||
|
c2075190e1 | ||
|
44f2fac0d4 | ||
|
3f32dda864 | ||
|
8ba2186686 | ||
|
6a37da6353 | ||
|
6c57b99980 | ||
|
4451606530 | ||
|
30c19dde74 | ||
|
4518aa2e35 | ||
|
9a92d7a3c8 | ||
|
9704223eb6 | ||
|
8d3bca6e36 | ||
|
b317bba454 | ||
|
9915bc091b | ||
|
88979b6b3c | ||
|
ec7c2a0b9c | ||
|
9486a673b8 | ||
|
c1064d56ae | ||
|
1b31f85a36 | ||
|
645f70e0c2 | ||
|
bc6f093055 | ||
|
2cb70368d3 | ||
|
29044e8e52 | ||
|
c1c945d84e | ||
|
dbc6d0363d | ||
|
07e44d1423 | ||
|
2bd9ca56a0 | ||
|
86e8f3cec9 | ||
|
b1d19d0944 | ||
|
10fe464b3f | ||
|
a4b0e4f26b | ||
|
6e63c24c52 | ||
|
271c8a29af | ||
|
9837d10946 | ||
|
da68592788 | ||
|
4424eb05d9 | ||
|
6da2f0445f | ||
|
98f62d0aa9 | ||
|
d69a99729e | ||
|
1104abbc61 | ||
|
4aeea1e94b | ||
|
30896489cb | ||
|
283ed2d09d | ||
|
49dcb5d3d7 | ||
|
65a5365b94 | ||
|
49db3cb845 | ||
|
22a8495563 | ||
|
4ae860d6e3 | ||
|
25201232d8 | ||
|
b822de0992 | ||
|
a6b91c890b | ||
|
25316e4f7f | ||
|
0e61c1620f | ||
|
8c16fb865f | ||
|
070586d4f7 | ||
|
e39d1ac61d | ||
|
501c9d2136 | ||
|
44d837a362 | ||
|
1f2d0f1334 | ||
|
08467f1de5 | ||
|
2e05fe3bf2 | ||
|
c5aaa5ad5d | ||
|
ec79c6dacb | ||
|
edf6e1715e | ||
|
8605a9760e | ||
|
27fc6f7586 | ||
|
8a3ea94195 | ||
|
aaff45b1d4 | ||
|
b37bede506 | ||
|
215367a2ad | ||
|
d827cb172d | ||
|
73c1050f48 | ||
|
d0c740a1a1 | ||
|
6f4f9c9933 | ||
|
442c0dae4d | ||
|
fa74a6bca2 | ||
|
55aaf85a2f | ||
|
59c5854cd1 | ||
|
21e5b9c804 |
28
.env.template
Executable file
28
.env.template
Executable file
@ -0,0 +1,28 @@
|
||||
# Do NOT edit this file, make
|
||||
# a copy and rename as `.env`
|
||||
|
||||
INPUT_GH_TOKEN=
|
||||
INPUT_WAKATIME_API_KEY=
|
||||
# meta
|
||||
INPUT_API_BASE_URL=
|
||||
INPUT_REPOSITORY=
|
||||
# content
|
||||
INPUT_SHOW_TITLE=
|
||||
INPUT_SECTION_NAME=
|
||||
INPUT_BLOCKS=
|
||||
INPUT_CODE_LANG=
|
||||
INPUT_TIME_RANGE=
|
||||
INPUT_LANG_COUNT=
|
||||
INPUT_SHOW_TIME=
|
||||
INPUT_SHOW_TOTAL=
|
||||
INPUT_SHOW_MASKED_TIME=
|
||||
INPUT_STOP_AT_OTHER=
|
||||
INPUT_IGNORED_LANGUAGES=
|
||||
# commit
|
||||
INPUT_COMMIT_MESSAGE=
|
||||
INPUT_TARGET_BRANCH=
|
||||
INPUT_TARGET_PATH=
|
||||
INPUT_COMMITTER_NAME=
|
||||
INPUT_COMMITTER_EMAIL=
|
||||
INPUT_AUTHOR_NAME=
|
||||
INPUT_AUTHOR_EMAIL=
|
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
ko_fi: athulcyriac
|
||||
custom: https://www.buymeacoffee.com/athulca
|
24
.github/workflows/testing.yml
vendored
Normal file
24
.github/workflows/testing.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
name: WakaReadme CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
UnitTests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build docker image
|
||||
run: |
|
||||
# Clear existing cache
|
||||
docker builder prune --force
|
||||
|
||||
# Build and run container (executes unit tests)
|
||||
docker compose -p waka-readme -f ./compose.yml up --no-color --pull always --build --force-recreate
|
||||
|
||||
# Cleanup
|
||||
docker compose -p waka-readme -f ./compose.yml down --rmi all
|
171
.gitignore
vendored
Normal file
171
.gitignore
vendored
Normal file
@ -0,0 +1,171 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to poetry.lock, it is generally recommended to include pdm.lock in version control.
|
||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||
# However, this project does not rely on pdm for production.
|
||||
pdm.lock
|
||||
.pdm-python
|
||||
.pdm-build
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
*.env
|
||||
env.sh
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
||||
|
||||
# asdf/rtx
|
||||
.tool-versions
|
||||
.rtx.toml
|
||||
|
||||
# ruff
|
||||
.ruff_cache
|
66
CONTRIBUTING.md
Normal file
66
CONTRIBUTING.md
Normal file
@ -0,0 +1,66 @@
|
||||
# Contributing
|
||||
|
||||

|
||||
|
||||
> First off, thank you! Please follow along.
|
||||
|
||||
**You need to _`fork`_ this repository & _`clone`_ it onto your system.** Inside the cloned folder, create a `.env` file with the following contents (without `# comments`):
|
||||
|
||||
```ini
|
||||
INPUT_GH_TOKEN=EXAMPLE_GITHUB_PAT # required (for development)
|
||||
INPUT_WAKATIME_API_KEY=EXAMPLE-WAKATIME-API-KEY # required
|
||||
INPUT_API_BASE_URL=https://wakatime.com/api # required
|
||||
INPUT_REPOSITORY=GITHUB_USERNAME/REPOSITORY_NAME # required
|
||||
INPUT_COMMIT_MESSAGE=Updated WakaReadme graph with new metrics
|
||||
INPUT_SHOW_TITLE=true
|
||||
INPUT_SECTION_NAME=waka
|
||||
INPUT_BLOCKS=->
|
||||
INPUT_SHOW_TIME=true
|
||||
INPUT_SHOW_TOTAL=true
|
||||
INPUT_TIME_RANGE=last_7_days
|
||||
INPUT_SHOW_MASKED_TIME=false
|
||||
INPUT_LANG_COUNT=0
|
||||
INPUT_STOP_AT_OTHER=true
|
||||
INPUT_IGNORED_LANGUAGES=
|
||||
```
|
||||
|
||||
**NEVER commit this `.env` file!**
|
||||
|
||||
## Using containers (recommended)
|
||||
|
||||
> Assumes that you already have latest version of either [`podman`](https://podman.io/) or [`docker`](https://www.docker.com/) (with [`compose`](https://docs.docker.com/compose/)) installed & configured.
|
||||
>
|
||||
> Replace `podman` with `docker` everywhere, if you're using the latter.
|
||||
|
||||
```sh
|
||||
# Build and watch logs
|
||||
$ podman-compose -p waka-readme -f ./docker-compose.yml up
|
||||
# Cleanup
|
||||
$ podman-compose -p waka-readme -f ./docker-compose.yml down
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Using virtual environments
|
||||
|
||||
> Assumes you've already installed & configured latest version of [python](https://www.python.org/).
|
||||
|
||||
1. Inside the cloned folder run the following commands to install dependencies
|
||||
|
||||
```sh
|
||||
$ python -m venv .venv
|
||||
$ . ./.venv/bin/activate
|
||||
$ python -m pip install .
|
||||
# ... install decencies ...
|
||||
```
|
||||
|
||||
to activate virtual environment & install dependencies.
|
||||
|
||||
2. To test or execute the program in development, run:
|
||||
|
||||
```sh
|
||||
(.venv)$ python -m unittest discover # run tests
|
||||
(.venv)$ python -m main --dev # execute program in dev mode
|
||||
```
|
||||
|
||||
> You can use any other virtual environment & dependency manager as well.
|
@ -1,8 +0,0 @@
|
||||
FROM python:latest
|
||||
|
||||
# Install dependencies.
|
||||
ADD requirements.txt /requirements.txt
|
||||
ADD main.py /main.py
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
CMD ["python", "/main.py"]
|
25
LICENSE
25
LICENSE
@ -1,20 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 ATHUL CYRIAC AJAY
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
13
Pipfile
13
Pipfile
@ -1,13 +0,0 @@
|
||||
[[source]]
|
||||
name = "pypi"
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[packages]
|
||||
requests = "*"
|
||||
pygithub = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.7"
|
85
Pipfile.lock
generated
85
Pipfile.lock
generated
@ -1,85 +0,0 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "ee0dbc310e64a4cc599d6b5e02bb22e3f33eb97ead8e5f1e315cf355fbef9367"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.7"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
|
||||
"sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
|
||||
],
|
||||
"version": "==2020.6.20"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"deprecated": {
|
||||
"hashes": [
|
||||
"sha256:525ba66fb5f90b07169fdd48b6373c18f1ee12728ca277ca44567a367d9d7f74",
|
||||
"sha256:a766c1dccb30c5f6eb2b203f87edd1d8588847709c78589e1521d769addc8218"
|
||||
],
|
||||
"version": "==1.2.10"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
|
||||
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
|
||||
],
|
||||
"version": "==2.10"
|
||||
},
|
||||
"pygithub": {
|
||||
"hashes": [
|
||||
"sha256:8375a058ec651cc0774244a3bc7395cf93617298735934cdd59e5bcd9a1df96e",
|
||||
"sha256:d2d17d1e3f4474e070353f201164685a95b5a92f5ee0897442504e399c7bc249"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.51"
|
||||
},
|
||||
"pyjwt": {
|
||||
"hashes": [
|
||||
"sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e",
|
||||
"sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96"
|
||||
],
|
||||
"version": "==1.7.1"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
|
||||
"sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.24.0"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527",
|
||||
"sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"
|
||||
],
|
||||
"version": "==1.25.9"
|
||||
},
|
||||
"wrapt": {
|
||||
"hashes": [
|
||||
"sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"
|
||||
],
|
||||
"version": "==1.12.1"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
}
|
251
README.md
251
README.md
@ -1,136 +1,191 @@
|
||||
# Dev Metrics in Readme
|
||||
|
||||

|
||||
fork from [waka-readme](https://github/athul/waka-readme)
|
||||
edit for Gitea use.
|
||||
|
||||
----
|
||||
[WakaTime](https://wakatime.com) coding metrics on your profile readme.
|
||||
|
||||
[WakaTime](https://wakatime.com) Weekly Metrics on your Profile Readme:
|
||||
## New to WakaTime?
|
||||
|
||||
> Nope? Skip to [#Prep work](#prep-work).
|
||||
|
||||
WakaTime gives you an idea of the time you spent on coding.
|
||||
This helps you boost your productivity and competitive edge (aka _flex_ :muscle:).
|
||||
|
||||
1. Head over to <https://wakatime.com/> and create an account.
|
||||
2. After logging in get your WakaTime API Key from <https://wakatime.com/api-key/>.
|
||||
3. Install [WakaTime plugin][waka_plugins] in your favorite editor / IDE.
|
||||
4. Paste in your API key to start telemetry.
|
||||
|
||||
:information_source: **Info** | You can read [WakaTime help][waka_help] to know more about configurations.
|
||||
Alternatively, you can fetch data from WakaTime compatible services such as [Wakapi][wakapi] or [Hakatime][hakatime].
|
||||
|
||||
## Prep Work
|
||||
|
||||
1. You need to update the markdown file(.md) with 2 comments. You can refer [here](#update-your-readme) for updating it.
|
||||
2. You'll need a WakaTime API Key. You can get that from your WakaTime Account Settings
|
||||
- You can refer [here](#new-to-wakatime), if you're new to WakaTime
|
||||
3. **Optional** You'll need a GitHub API Token with `repo` scope from [here](https://github.com/settings/tokens) if you're running the action not in your Profile Repository
|
||||
- You can use [this](#other-repository-not-profile) example to work it out
|
||||
4. You need to save the WakaTime API Key (and the GitHub API Token, if you need it) in the repository secrets. You can find that in the Settings of your Repository.Be sure to save those as the following.
|
||||
- WakaTime-api-key as `WAKATIME_API_KEY = <your wakatime API Key>`and
|
||||
- The GitHub Access Token as `GH_TOKEN=<your github access token>`
|
||||
5. You can follow either of the Two Examples according to your needs to get started with.
|
||||
A Gitea repository and a `README.md` file is required. We'll be making use of readme in the [profile repository][profile_readme].
|
||||
|
||||
> I strongly suggest you to run the Action in your Profile Repo since you won't be needing a GitHub Access Token
|
||||
|
||||
This Action will run everyday at 00.00 UTC
|
||||
|
||||
## Update your Readme
|
||||
|
||||
Add a comment to your `README.md` like this:
|
||||
|
||||
```md
|
||||
<!--START_SECTION:waka-->
|
||||
<!--END_SECTION:waka-->
|
||||
```
|
||||
|
||||
These lines will be our entry-points for the dev metrics.
|
||||
|
||||
## New to WakaTime
|
||||
|
||||
WakaTime gives you an idea of the time you really spent on coding. This helps you boost your productivity and competitive edge.
|
||||
|
||||
- Head over to <https://wakatime.com> and create an account.
|
||||
- Get your WakaTime API Key from your [Account Settings in WakaTime](https://wakatime.com/settings/account).
|
||||
- Install the [WakaTime plugin](https://wakatime.com/plugins) in your favourite editor / IDE.
|
||||
- Paste in your API key to start the analysis.
|
||||
|
||||
### Profile Repository
|
||||
|
||||
*If you're executing the workflow on your Profile Repository (`<username>/<username>`)*
|
||||
|
||||
> You wouldn't need an GitHub Access Token since GitHub Actions already makes one for you.
|
||||
|
||||
Please follow the steps below:
|
||||
|
||||
1. Go to your `<username>/<username>/actions`, hit `New workflow`, `set up a workflow yourself`, delete all the default content github made for you.
|
||||
2. Copy the following code and paste it to your new workflow you created at step 1:
|
||||
```yml
|
||||
name: Waka Readme
|
||||
|
||||
on:
|
||||
workflow_dispatch
|
||||
schedule:
|
||||
# Runs at 12am UTC
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
update-readme:
|
||||
name: Update this repo's README
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: athul/waka-readme@master
|
||||
with:
|
||||
WAKATIME_API_KEY: ${{ secrets.WAKATIME_API_KEY }}
|
||||
```
|
||||
3. Go to your repo secrets by hitting `Settings => Secrets` tab in your profile repo. You can also enter the url https://github.com/USERNAME/USERNAME/settings/secrets . Please replace the `USERNAME` with your own username.
|
||||
4. Create a new `Secret`. `Name`: `WAKATIME_API_KEY`, `Value`: Paste the Wakatime API key here. If you don't know what is the key, please go to [Account Settings in WakaTime](https://wakatime.com/settings/account) to find your API Key there.
|
||||
5. Add a comment to your `README.md` like this:
|
||||
- Save the `README.md` file after copy-pasting the following special comments. Your dev-metics will show up in between.
|
||||
|
||||
```md
|
||||
<!--START_SECTION:waka-->
|
||||
<!--END_SECTION:waka-->
|
||||
```
|
||||
6. Go to Workflows menu (mentioned in step 1), click `Waka Readme`, click `Run workflow`.
|
||||
7. Go to your profile page. you will be able to see it.
|
||||
|
||||
### Other Repository (not Profile)
|
||||
`<!--START_SECTION: -->` and `<!--END_SECTION: -->` are placeholders and must be retained as is. Whereas "`waka`" can be replaced by any alphanumeric string. See [#Tweaks](#tweaks) section for more.
|
||||
|
||||
- Navigate to your repo's `Settings`:
|
||||
- Go to `Secrets` (at `https://your_gitea_url/username/.profile/settings/actions/secrets` by replacing the `USERNAME` with your own username) and add a new secret "_Named_" `WAKATIME_API_KEY` with your API key as it's "_Secret_".
|
||||
|
||||
- Create a new workflow file named `waka-readme.yml` inside `.gitea/workflows/` folder of your profile repository.
|
||||
- Clear all existing contents, add following lines and save the file.
|
||||
|
||||
*If you're executing the workflow on another repo other than `<username>/<username>`*
|
||||
```yml
|
||||
name: Waka Readme
|
||||
|
||||
You'll need to get a [GitHub Access Token](https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token) with a `repo` scope and save it in the Repo Secrets `GH_TOKEN = <Your GitHub Access Token>`
|
||||
on:
|
||||
# for manual workflow trigger
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# runs at 12 AM UTC (5:30 AM IST)
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
Here is Sample Workflow File for running it:
|
||||
jobs:
|
||||
update-readme:
|
||||
name: WakaReadme DevMetrics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: https://git.mamahaha.work/sangge/waka-readme@master # this action name
|
||||
with:
|
||||
WAKATIME_API_KEY: ${{ secrets.WAKATIME_API_KEY }}
|
||||
```
|
||||
|
||||
Refer [#Example](#example) section for a full blown workflow file.
|
||||
|
||||
## Tweaks
|
||||
|
||||
There are many flags that you can modify as you see fit.
|
||||
|
||||
### Meta Tweaks
|
||||
|
||||
| Environment flag | Options (`Default`, `Other`, ...) | Description |
|
||||
| ---------------- | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
||||
| `API_BASE_URL` | `https://wakatime.com/api`, `https://wakapi.dev/api`, `https://hakatime.mtx-dev.xyz/api` | Use WakaTime compatible services like [Wakapi][wakapi] & [Hakatime][hakatime] |
|
||||
| `REPOSITORY` | `<gh_username>/<gh_username>`, `<gh_username>/<repo_name>` | Waka-readme stats will appear on the provided repository |
|
||||
|
||||
### Content Tweaks
|
||||
|
||||
| Environment flag | Options (`Default`, `Other`, ...) | Description |
|
||||
| ------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
|
||||
| `SHOW_TITLE` | `false`, `true` | Add title to waka-readme stats blob |
|
||||
| `SECTION_NAME` | `waka`, any alphanumeric string | The generator will look for section name to fill up the readme. |
|
||||
| `BLOCKS` | `░▒▓█`, `⣀⣄⣤⣦⣶⣷⣿`, `-#`, `=>`, you can be creative | Ascii art used to build stats graph |
|
||||
| `CODE_LANG` | `txt`, `python` `ruby` `json` , you can use other languages also | Language syntax based highlighted text |
|
||||
| `TIME_RANGE` | `last_7_days`, `last_30_days`, `last_6_months`, `last_year`, `all_time` | String representing a dispensation from which stats are aggregated |
|
||||
| `LANG_COUNT` | `5`, any plausible number | Number of languages to be displayed |
|
||||
| `SHOW_TIME` | `true`, `false` | Displays the amount of time spent for each language |
|
||||
| `SHOW_TOTAL` | `false`, `true` | Show total coding time |
|
||||
| `SHOW_MASKED_TIME` | `false`, `true` | Adds total coding time including unclassified languages (overrides: `SHOW_TOTAL`) |
|
||||
| `STOP_AT_OTHER` | `false`, `true` | Stop when language marked as `Other` is retrieved (overrides: `LANG_COUNT`) |
|
||||
| `IGNORED_LANGUAGES` | <code> </code>, `Binary YAML JSON TOML` | Hide languages from your stats |
|
||||
|
||||
### Commit Tweaks
|
||||
|
||||
| Environment flag | Options (`Default`, `Other`, ...) |
|
||||
| ----------------- | -------------------------------------------------------------------- |
|
||||
| `COMMIT_MESSAGE` | `Updated waka-readme graph with new metrics`, any reasonable message |
|
||||
| `TARGET_BRANCH` | `NOT_SET`, target branch name |
|
||||
| `TARGET_PATH` | `NOT_SET`, `/path/to/target/file` |
|
||||
| `COMMITTER_NAME` | `NOT_SET`, committer name |
|
||||
| `COMMITTER_EMAIL` | `NOT_SET`, committer email |
|
||||
| `AUTHOR_NAME` | `NOT_SET`, author name |
|
||||
| `AUTHOR_EMAIL` | `NOT_SET`, author email |
|
||||
|
||||
The first option is the _default_ value of the _flag_, subsequent options are valid values available for the _flag_.
|
||||
|
||||
## Example
|
||||
|
||||
**`waka-readme.yml`**
|
||||
|
||||
```yml
|
||||
name: Waka Readme
|
||||
|
||||
on:
|
||||
# for manual workflow trigger
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# Runs at 12am UTC
|
||||
- cron: '0 0 * * *'
|
||||
# runs at 12 AM UTC (5:30 AM IST)
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
update-readme:
|
||||
name: Update Readme with Metrics
|
||||
name: WakaReadme DevMetrics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: athul/waka-readme@master
|
||||
# this action name
|
||||
- uses: https://git.mamahaha.work/sangge/waka-readme@master # do NOT replace with anything else
|
||||
with:
|
||||
WAKATIME_API_KEY: ${{ secrets.WAKATIME_API_KEY }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
USERNAME: <username> # optional, it will automatically use the username of the owner of the repository who's executing the workflow.
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }} # optional if on profile readme
|
||||
WAKATIME_API_KEY: ${{ secrets.WAKATIME_API_KEY }} # required
|
||||
### meta
|
||||
API_BASE_URL: https://wakatime.com/api # optional
|
||||
REPOSITORY: YOUR_GITEA_USERNAME/YOUR_REPOSITORY_NAME # optional
|
||||
### content
|
||||
SHOW_TITLE: true # optional
|
||||
SECTION_NAME: waka # optional
|
||||
BLOCKS: -> # optional
|
||||
CODE_LANG: rust # optional
|
||||
TIME_RANGE: all_time # optional
|
||||
LANG_COUNT: 10 # optional
|
||||
SHOW_TIME: true # optional
|
||||
SHOW_TOTAL: true # optional
|
||||
SHOW_MASKED_TIME: false # optional
|
||||
STOP_AT_OTHER: true # optional
|
||||
IGNORED_LANGUAGES: YAML JSON TOML # optional
|
||||
### commit
|
||||
COMMIT_MESSAGE: Updated waka-readme graph with new metrics # optional
|
||||
TARGET_BRANCH: master # optional
|
||||
TARGET_PATH: README.md # optional
|
||||
COMMITTER_NAME: GitHubActionBot # optional
|
||||
COMMITTER_EMAIL: action-bot@github.com # optional
|
||||
AUTHOR_NAME: YOUR_NAME # optional
|
||||
AUTHOR_EMAIL: YOUR@EMAIL.com # optional
|
||||
# you can populate email-id with secretes instead
|
||||
```
|
||||
|
||||
## Extras
|
||||
_Rendered `markdown`:_
|
||||
|
||||
1. If you want to add the week in the Header of your stats, you can add `HEAD_FLAG: true` in your workflow file like this
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
```yml
|
||||
- uses: athul/waka-readme@master
|
||||
with:
|
||||
WAKATIME_API_KEY: ${{ secrets.WAKATIME_API_KEY }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
USERNAME: <username>
|
||||
SHOW_TITLE: true
|
||||
```rust
|
||||
From: 10 July 2020 - To: 06 August 2022
|
||||
|
||||
Total Time: 1,464 hrs 54 mins
|
||||
|
||||
Python 859 hrs 29 mins >>>>>>>>>>>>>>----------- 54.68 %
|
||||
Markdown 132 hrs 33 mins >>----------------------- 08.43 %
|
||||
TeX 103 hrs 52 mins >>----------------------- 06.61 %
|
||||
HTML 94 hrs 48 mins >>----------------------- 06.03 %
|
||||
Nim 64 hrs 31 mins >------------------------ 04.11 %
|
||||
Other 47 hrs 58 mins >------------------------ 03.05 %
|
||||
```
|
||||
|
||||
`SHOW_TITLE` flag can be set to true if you want to display the week number and days in the readme, by default it will be false. Here is an example output with `SHOW_TITLE` set to true.
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
```text
|
||||
Week: 10 July, 2020 - 17 July, 2020
|
||||
Python 8 hrs 52 mins ███████████████████░░░░░░ 75.87 %
|
||||
Go 1 hr 15 mins ██░░░░░░░░░░░░░░░░░░░░░░░ 10.79 %
|
||||
Markdown 52 mins █░░░░░░░░░░░░░░░░░░░░░░░░ 07.43 %
|
||||
Docker 16 mins ░░░░░░░░░░░░░░░░░░░░░░░░░ 02.32 %
|
||||
YAML 7 mins ░░░░░░░░░░░░░░░░░░░░░░░░░ 01.07 %
|
||||
```
|
||||
## Notes
|
||||
|
||||
- Flags `REPOSITORY` and `GH_TOKEN` are required ONLY if, you are NOT using [profile readme][profile_readme].
|
||||
- If you are using `GH_TOKEN`, make sure set the [fine grained token](https://github.com/settings/tokens?type=beta) scope to repository contents with `read-and-write` access. See [#141 (comment)](https://github.com/athul/waka-readme/issues/141#issuecomment-1679831949).
|
||||
- `WAKATIME_API_KEY` is a **required** secret. All other environment variables are optional.
|
||||
- The above example does NOT show proper default values, refer [#Tweaks](#tweaks) for the same.
|
||||
- `IGNORED_LANGUAGES` is suggested for [.NET](https://dotnet.microsoft.com) users, as WakaTime assumes you're working with `Binary`, while debugging.
|
||||
|
||||
## Why only the language stats (and not other data) from the API?
|
||||
|
||||
I am a fan of minimal designs and the profile readme is a great way to show off your skills and interests. The WakaTime API, gets us a **lot of data** about a person's **coding activity including the editors and Operating Systems you used and the projects you worked on**. Some of these projects maybe secretive and should not be shown out to the public. Using up more data via the Wakatime API will clutter the profile readme and hinder your chances on displaying what you provide **value to the community** like the pinned Repositories. I believe that **Coding Stats is nerdiest of all** since you can tell the community that you are **_exercising these languages or learning a new language_**, this will also show that you spend some amount of time to learn and exercise your development skills. That's what matters in the end :heart:
|
||||
|
||||
[//]: #(Links)
|
||||
[wakapi]: https://wakapi.dev
|
||||
[hakatime]: https://github.com/mujx/hakatime
|
||||
[waka_plugins]: https://wakatime.com/plugins
|
||||
[waka_help]: https://wakatime.com/help/editors
|
||||
[profile_readme]: https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/customizing-your-profile/managing-your-profile-readme
|
||||
|
106
action.yml
106
action.yml
@ -1,32 +1,106 @@
|
||||
name: 'Waka - Readme'
|
||||
author: Athil Cyriac Ajay
|
||||
description: 'Add a Wakatime Coding Activity graph in your Readme'
|
||||
name: "Waka - Readme"
|
||||
author: "Athul Cyriac Ajay"
|
||||
description: "WakaTime coding activity graph in your profile readme"
|
||||
|
||||
inputs:
|
||||
GH_TOKEN:
|
||||
description: 'GitHub access token with Repo scope'
|
||||
required: true
|
||||
GITEA_TOKEN:
|
||||
description: "GitHub access token with Repo scope"
|
||||
default: ${{ github.token }}
|
||||
|
||||
required: true
|
||||
WAKATIME_API_KEY:
|
||||
description: 'Your Wakatime API Key'
|
||||
description: "Your Wakatime/Wakapi/Hakatime API Key"
|
||||
required: true
|
||||
|
||||
USERNAME:
|
||||
description: 'Your GitHub username'
|
||||
default: ${{ github.repository_owner }}
|
||||
# meta tweaks
|
||||
API_BASE_URL:
|
||||
description: "Alternative API base URL when using a third-party WakaTime-ish backend"
|
||||
default: "https://wakatime.com/api"
|
||||
required: false
|
||||
REPOSITORY:
|
||||
description: "Your GitHub repository"
|
||||
default: ${{ github.repository }}
|
||||
required: false
|
||||
|
||||
# content tweaks
|
||||
SHOW_TITLE:
|
||||
description: "Displays the week number and days in Readme as title"
|
||||
default: false
|
||||
default: "false"
|
||||
required: false
|
||||
SECTION_NAME:
|
||||
description: "Section name for data to appear in readme"
|
||||
required: false
|
||||
default: "waka"
|
||||
BLOCKS:
|
||||
description: "Add the progress blocks of your choice"
|
||||
default: "░▒▓█"
|
||||
required: false
|
||||
CODE_LANG:
|
||||
description: "Add syntax formatter for generated code"
|
||||
default: "txt"
|
||||
required: false
|
||||
TIME_RANGE:
|
||||
description: "Time range of the queried statistics"
|
||||
default: "last_7_days"
|
||||
required: false
|
||||
LANG_COUNT:
|
||||
description: "Maximum number of languages to be shown"
|
||||
default: "5"
|
||||
required: false
|
||||
SHOW_TIME:
|
||||
description: "Displays the amount of time spent for each language"
|
||||
default: "true"
|
||||
required: false
|
||||
SHOW_TOTAL:
|
||||
description: "Displays total coding time"
|
||||
default: "false"
|
||||
required: false
|
||||
SHOW_MASKED_TIME:
|
||||
description: "Displays total coding time including unclassified languages"
|
||||
default: "false"
|
||||
required: false
|
||||
STOP_AT_OTHER:
|
||||
description: "Stop data retrieval when language marked 'Other' is reached"
|
||||
default: "false"
|
||||
required: false
|
||||
IGNORED_LANGUAGES:
|
||||
description: "Ignore space separated, listed languages"
|
||||
default: ""
|
||||
required: false
|
||||
|
||||
# commit tweaks
|
||||
COMMIT_MESSAGE:
|
||||
description: "Add a commit message of your choice"
|
||||
default: "Updated waka-readme graph with new metrics"
|
||||
required: false
|
||||
TARGET_BRANCH:
|
||||
description: "Target branch"
|
||||
default: "NOT_SET"
|
||||
required: false
|
||||
TARGET_PATH:
|
||||
description: "Target file path"
|
||||
default: "NOT_SET"
|
||||
required: false
|
||||
COMMITTER_NAME:
|
||||
description: "Committer name"
|
||||
default: "NOT_SET"
|
||||
required: false
|
||||
COMMITTER_EMAIL:
|
||||
description: "Committer email"
|
||||
default: "NOT_SET"
|
||||
required: false
|
||||
AUTHOR_NAME:
|
||||
description: "Author name"
|
||||
default: "NOT_SET"
|
||||
required: false
|
||||
AUTHOR_EMAIL:
|
||||
description: "Author email"
|
||||
default: "NOT_SET"
|
||||
required: false
|
||||
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'Dockerfile'
|
||||
using: "docker"
|
||||
image: "dockerfile"
|
||||
|
||||
branding:
|
||||
icon: 'info'
|
||||
color: 'blue'
|
||||
icon: "info"
|
||||
color: "blue"
|
||||
|
10
compose.yml
Normal file
10
compose.yml
Normal file
@ -0,0 +1,10 @@
|
||||
# for CI testing
|
||||
services:
|
||||
waka-readme:
|
||||
env_file:
|
||||
- .env.template
|
||||
build:
|
||||
context: .
|
||||
dockerfile: containerfile
|
||||
image: waka-readme:testing
|
||||
container_name: WakaReadmeTesting
|
51
containerfile
Normal file
51
containerfile
Normal file
@ -0,0 +1,51 @@
|
||||
FROM docker.io/python:3-slim
|
||||
|
||||
ENV INPUT_GITEA_TOKEN \
|
||||
INPUT_WAKATIME_API_KEY \
|
||||
# meta
|
||||
INPUT_API_BASE_URL \
|
||||
INPUT_REPOSITORY \
|
||||
# content
|
||||
INPUT_SHOW_TITLE \
|
||||
INPUT_SECTION_NAME \
|
||||
INPUT_BLOCKS \
|
||||
INPUT_CODE_LANG \
|
||||
INPUT_TIME_RANGE \
|
||||
INPUT_LANG_COUNT \
|
||||
INPUT_SHOW_TIME \
|
||||
INPUT_SHOW_TOTAL \
|
||||
INPUT_SHOW_MASKED_TIME \
|
||||
INPUT_STOP_AT_OTHER \
|
||||
INPUT_IGNORED_LANGUAGES \
|
||||
# commit
|
||||
INPUT_COMMIT_MESSAGE \
|
||||
INPUT_TARGET_BRANCH \
|
||||
INPUT_TARGET_PATH \
|
||||
INPUT_COMMITTER_NAME \
|
||||
INPUT_COMMITTER_EMAIL \
|
||||
INPUT_AUTHOR_NAME \
|
||||
INPUT_AUTHOR_EMAIL
|
||||
|
||||
|
||||
ENV PATH="${PATH}:/root/.local/bin" \
|
||||
# python
|
||||
PYTHONFAULTHANDLER=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PYTHONHASHSEED=random \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
# pip
|
||||
PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
||||
PIP_NO_CACHE_DIR=1 \
|
||||
PIP_DEFAULT_TIMEOUT=100
|
||||
|
||||
# copy project files
|
||||
COPY --chown=root:root pyproject.toml main.py /app/
|
||||
|
||||
# install dependencies
|
||||
RUN python -m pip install /app/ -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
|
||||
# copy tests
|
||||
COPY --chown=root:root tests /app/tests/
|
||||
|
||||
# run tests
|
||||
CMD python -m unittest discover /app/
|
9
docker-compose.yml
Normal file
9
docker-compose.yml
Normal file
@ -0,0 +1,9 @@
|
||||
services:
|
||||
waka-readme:
|
||||
env_file:
|
||||
- .env
|
||||
build:
|
||||
context: .
|
||||
dockerfile: dockerfile
|
||||
image: waka-readme:dev
|
||||
container_name: WakaReadmeDev
|
48
dockerfile
Normal file
48
dockerfile
Normal file
@ -0,0 +1,48 @@
|
||||
FROM docker.io/python:3-slim
|
||||
|
||||
ENV INPUT_GH_TOKEN \
|
||||
INPUT_WAKATIME_API_KEY \
|
||||
# meta
|
||||
INPUT_API_BASE_URL \
|
||||
INPUT_REPOSITORY \
|
||||
# content
|
||||
INPUT_SHOW_TITLE \
|
||||
INPUT_SECTION_NAME \
|
||||
INPUT_BLOCKS \
|
||||
INPUT_CODE_LANG \
|
||||
INPUT_TIME_RANGE \
|
||||
INPUT_LANG_COUNT \
|
||||
INPUT_SHOW_TIME \
|
||||
INPUT_SHOW_TOTAL \
|
||||
INPUT_SHOW_MASKED_TIME \
|
||||
INPUT_STOP_AT_OTHER \
|
||||
INPUT_IGNORED_LANGUAGES \
|
||||
# commit
|
||||
INPUT_COMMIT_MESSAGE \
|
||||
INPUT_TARGET_BRANCH \
|
||||
INPUT_TARGET_PATH \
|
||||
INPUT_COMMITTER_NAME \
|
||||
INPUT_COMMITTER_EMAIL \
|
||||
INPUT_AUTHOR_NAME \
|
||||
INPUT_AUTHOR_EMAIL
|
||||
|
||||
|
||||
ENV PATH="${PATH}:/root/.local/bin" \
|
||||
# python
|
||||
PYTHONFAULTHANDLER=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PYTHONHASHSEED=random \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
# pip
|
||||
PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
||||
PIP_NO_CACHE_DIR=1 \
|
||||
PIP_DEFAULT_TIMEOUT=100
|
||||
|
||||
# copy project files
|
||||
COPY --chown=root:root pyproject.toml main.py /app/
|
||||
|
||||
# install dependencies
|
||||
RUN python -m pip install /app/ -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
|
||||
# execute program
|
||||
CMD python /app/main.py
|
613
main.py
613
main.py
@ -1,97 +1,558 @@
|
||||
'''
|
||||
WakaTime progress visualizer
|
||||
'''
|
||||
"""WakaReadme : WakaTime progress visualizer.
|
||||
|
||||
import re
|
||||
Wakatime Metrics on your Profile Readme.
|
||||
|
||||
Title:
|
||||
|
||||
```txt
|
||||
From: 15 February, 2022 - To: 22 February, 2022
|
||||
````
|
||||
|
||||
Byline:
|
||||
|
||||
```txt
|
||||
Total: 34 hrs 43 mins
|
||||
```
|
||||
|
||||
Body:
|
||||
|
||||
```txt
|
||||
Python 27 hrs 29 mins ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣀⣀⣀⣀⣀ 77.83 %
|
||||
YAML 2 hrs 14 mins ⣿⣦⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ 06.33 %
|
||||
Markdown 1 hr 54 mins ⣿⣤⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ 05.39 %
|
||||
TOML 1 hr 48 mins ⣿⣤⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ 05.11 %
|
||||
Other 35 mins ⣦⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ 01.68 %
|
||||
```
|
||||
|
||||
Contents := Title + Byline + Body
|
||||
"""
|
||||
|
||||
# standard
|
||||
from base64 import b64encode, b64decode
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from functools import partial
|
||||
import logging as logger
|
||||
import os
|
||||
import base64
|
||||
from random import SystemRandom
|
||||
import re
|
||||
import sys
|
||||
import datetime
|
||||
import requests
|
||||
from github import Github, GithubException
|
||||
from time import sleep
|
||||
from typing import Any
|
||||
|
||||
START_COMMENT = '<!--START_SECTION:waka-->'
|
||||
END_COMMENT = '<!--END_SECTION:waka-->'
|
||||
listReg = f"{START_COMMENT}[\\s\\S]+{END_COMMENT}"
|
||||
# external
|
||||
from faker import Faker
|
||||
|
||||
user = os.getenv('INPUT_USERNAME')
|
||||
waka_key = os.getenv('INPUT_WAKATIME_API_KEY')
|
||||
ghtoken = os.getenv('INPUT_GH_TOKEN')
|
||||
show_title = os.getenv("INPUT_SHOW_TITLE")
|
||||
# from github import ContentFile, Github, GithubException, InputGitAuthor, Repository
|
||||
import gitea
|
||||
from requests import get as rq_get
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
################### setup ###################
|
||||
|
||||
print()
|
||||
# hush existing loggers
|
||||
for lgr_name in logger.root.manager.loggerDict:
|
||||
# to disable log propagation completely set '.propagate = False'
|
||||
logger.getLogger(lgr_name).setLevel(logger.WARNING)
|
||||
# somehow github.Requester gets missed out from loggerDict
|
||||
logger.getLogger("github.Requester").setLevel(logger.WARNING)
|
||||
# configure logger
|
||||
logger.basicConfig(
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
format="[%(asctime)s] ln. %(lineno)-3d %(levelname)-8s %(message)s",
|
||||
level=logger.DEBUG,
|
||||
)
|
||||
try:
|
||||
if len(sys.argv) == 2 and sys.argv[1] == "--dev":
|
||||
# get env-vars from .env file for development
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# comment this out to disable colored logging
|
||||
from loguru import logger
|
||||
|
||||
logger.debug("loguru loaded")
|
||||
|
||||
# load from .env before class def gets parsed
|
||||
load_dotenv()
|
||||
logger.debug("dotenv loaded")
|
||||
waka_key: str | None = os.getenv("INPUT_WAKATIME_API_KEY")
|
||||
except ImportError as im_err:
|
||||
logger.warning(im_err)
|
||||
|
||||
|
||||
def this_week() -> str:
|
||||
'''Returns a week streak'''
|
||||
week_end = datetime.datetime.today() - datetime.timedelta(days=1)
|
||||
week_start = week_end - datetime.timedelta(days=7)
|
||||
print("Week header created")
|
||||
return f"Week: {week_start.strftime('%d %B, %Y')} - {week_end.strftime('%d %B, %Y')}"
|
||||
################### lib-func ###################
|
||||
|
||||
|
||||
def make_graph(percent: float) -> str:
|
||||
'''Make progress graph from API graph'''
|
||||
done_block = '█'
|
||||
empty_block = '░'
|
||||
pc_rnd = round(percent)
|
||||
return f"{done_block*int(pc_rnd/4)}{empty_block*int(25-int(pc_rnd/4))}"
|
||||
def strtobool(val: str | bool):
|
||||
"""Strtobool.
|
||||
|
||||
PEP 632 https://www.python.org/dev/peps/pep-0632/ is depreciating distutils.
|
||||
This is from the official source code with slight modifications.
|
||||
|
||||
Converts a string representation of truth to `True` or `False`.
|
||||
|
||||
Args:
|
||||
val:
|
||||
Value to be converted to bool.
|
||||
|
||||
Returns:
|
||||
(Literal[True]):
|
||||
If `val` is any of 'y', 'yes', 't', 'true', 'on', or '1'.
|
||||
(Literal[False]):
|
||||
If `val` is any of 'n', 'no', 'f', 'false', 'off', and '0'.
|
||||
|
||||
Raises:
|
||||
ValueError: If `val` is anything else.
|
||||
"""
|
||||
if isinstance(val, bool):
|
||||
return val
|
||||
|
||||
val = val.lower()
|
||||
|
||||
if val in {"y", "yes", "t", "true", "on", "1"}:
|
||||
return True
|
||||
|
||||
if val in {"n", "no", "f", "false", "off", "0"}:
|
||||
return False
|
||||
|
||||
raise ValueError(f"invalid truth value for {val}")
|
||||
|
||||
|
||||
def get_stats() -> str:
|
||||
'''Gets API data and returns markdown progress'''
|
||||
data = requests.get(
|
||||
f"https://wakatime.com/api/v1/users/current/stats/last_7_days?api_key={waka_key}").json()
|
||||
################### data ###################
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class WakaInput:
|
||||
"""WakaReadme Input Env Variables."""
|
||||
|
||||
# constants
|
||||
prefix_length: int = 16
|
||||
graph_length: int = 25
|
||||
|
||||
# mapped environment variables
|
||||
# # required
|
||||
gitea_token: str | None = os.getenv("INPUT_GITEA_TOKEN")
|
||||
gitea_url: str | None = os.getenv("GITHUB_SERVER_URL", "https://gitea.com")
|
||||
waka_key: str | None = os.getenv("INPUT_WAKATIME_API_KEY")
|
||||
api_base_url: str | None = os.getenv("INPUT_API_BASE_URL", "https://wakatime.com/api")
|
||||
repository: str | None = os.getenv("INPUT_REPOSITORY", ".profile")
|
||||
# # depends
|
||||
commit_message: str = os.getenv(
|
||||
"INPUT_COMMIT_MESSAGE", "Updated WakaReadme graph with new metrics"
|
||||
)
|
||||
code_lang: str = os.getenv("INPUT_CODE_LANG", "txt")
|
||||
_section_name: str = os.getenv("INPUT_SECTION_NAME", "waka")
|
||||
start_comment: str = f"<!--START_SECTION:{_section_name}-->"
|
||||
end_comment: str = f"<!--END_SECTION:{_section_name}-->"
|
||||
waka_block_pattern: str = f"{start_comment}[\\s\\S]+{end_comment}"
|
||||
# # optional
|
||||
show_title: str | bool = os.getenv("INPUT_SHOW_TITLE") or False
|
||||
block_style: str = os.getenv("INPUT_BLOCKS", "░▒▓█")
|
||||
time_range: str = os.getenv("INPUT_TIME_RANGE", "last_7_days")
|
||||
show_time: str | bool = os.getenv("INPUT_SHOW_TIME") or False
|
||||
show_total_time: str | bool = os.getenv("INPUT_SHOW_TOTAL") or False
|
||||
show_masked_time: str | bool = os.getenv("INPUT_SHOW_MASKED_TIME") or False
|
||||
language_count: str | int = os.getenv("INPUT_LANG_COUNT") or 5
|
||||
stop_at_other: str | bool = os.getenv("INPUT_STOP_AT_OTHER") or False
|
||||
ignored_languages: str = os.getenv("INPUT_IGNORED_LANGUAGES", "")
|
||||
# # optional meta
|
||||
target_branch: str = os.getenv("INPUT_TARGET_BRANCH", "NOT_SET")
|
||||
target_path: str = os.getenv("INPUT_TARGET_PATH", "NOT_SET")
|
||||
committer_name: str = os.getenv("INPUT_COMMITTER_NAME", "NOT_SET")
|
||||
committer_email: str = os.getenv("INPUT_COMMITTER_EMAIL", "NOT_SET")
|
||||
author_name: str = os.getenv("INPUT_AUTHOR_NAME", "NOT_SET")
|
||||
author_email: str = os.getenv("INPUT_AUTHOR_EMAIL", "NOT_SET")
|
||||
|
||||
def validate_input(self):
|
||||
"""Validate Input Env Variables."""
|
||||
logger.debug("Validating input variables")
|
||||
if (
|
||||
not self.gitea_token
|
||||
or not self.waka_key
|
||||
or not self.api_base_url
|
||||
or not self.repository
|
||||
):
|
||||
logger.error("Invalid inputs")
|
||||
logger.info("Refer https://github.com/athul/waka-readme")
|
||||
return False
|
||||
|
||||
if len(self.commit_message) < 1:
|
||||
logger.error("Commit message length must be greater than 1 character long")
|
||||
return False
|
||||
|
||||
try:
|
||||
self.show_title = strtobool(self.show_title)
|
||||
self.show_time = strtobool(self.show_time)
|
||||
self.show_total_time = strtobool(self.show_total_time)
|
||||
self.show_masked_time = strtobool(self.show_masked_time)
|
||||
self.stop_at_other = strtobool(self.stop_at_other)
|
||||
except (ValueError, AttributeError) as err:
|
||||
logger.error(err)
|
||||
return False
|
||||
|
||||
if not self._section_name.isalnum():
|
||||
logger.warning("Section name must be in any of [[a-z][A-Z][0-9]]")
|
||||
logger.debug("Using default section name: waka")
|
||||
self._section_name = "waka"
|
||||
self.start_comment = f"<!--START_SECTION:{self._section_name}-->"
|
||||
self.end_comment = f"<!--END_SECTION:{self._section_name}-->"
|
||||
self.waka_block_pattern = f"{self.start_comment}[\\s\\S]+{self.end_comment}"
|
||||
|
||||
if len(self.block_style) < 2:
|
||||
logger.warning("Graph block must be longer than 2 characters")
|
||||
logger.debug("Using default blocks: ░▒▓█")
|
||||
self.block_style = "░▒▓█"
|
||||
|
||||
if self.time_range not in {
|
||||
"last_7_days",
|
||||
"last_30_days",
|
||||
"last_6_months",
|
||||
"last_year",
|
||||
"all_time",
|
||||
}: # "all_time" is un-documented, should it be used?
|
||||
logger.warning("Invalid time range")
|
||||
logger.debug("Using default time range: last_7_days")
|
||||
self.time_range = "last_7_days"
|
||||
|
||||
try:
|
||||
self.language_count = int(self.language_count)
|
||||
if self.language_count < -1:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
logger.warning("Invalid language count")
|
||||
logger.debug("Using default language count: 5")
|
||||
self.language_count = 5
|
||||
|
||||
for option in (
|
||||
"target_branch",
|
||||
"target_path",
|
||||
"committer_name",
|
||||
"committer_email",
|
||||
"author_name",
|
||||
"author_email",
|
||||
):
|
||||
if not getattr(self, option):
|
||||
logger.warning(f"Improper '{option}' configuration")
|
||||
logger.debug(f"Using default '{option}'")
|
||||
setattr(self, option, "NOT_SET")
|
||||
|
||||
logger.debug("Input validation complete\n")
|
||||
return True
|
||||
|
||||
|
||||
################### logic ###################
|
||||
|
||||
|
||||
def make_title(dawn: str | None, dusk: str | None, /):
|
||||
"""WakaReadme Title.
|
||||
|
||||
Makes title for WakaReadme.
|
||||
"""
|
||||
logger.debug("Making title")
|
||||
if not dawn or not dusk:
|
||||
logger.error("Cannot find start/end date\n")
|
||||
sys.exit(1)
|
||||
api_dfm, msg_dfm = "%Y-%m-%dT%H:%M:%SZ", "%d %B %Y"
|
||||
try:
|
||||
lang_data = data['data']['languages']
|
||||
except KeyError:
|
||||
print("Please Add your WakaTime API Key to the Repository Secrets")
|
||||
start_date = datetime.strptime(dawn, api_dfm).strftime(msg_dfm)
|
||||
end_date = datetime.strptime(dusk, api_dfm).strftime(msg_dfm)
|
||||
except ValueError as err:
|
||||
logger.error(f"{err}\n")
|
||||
sys.exit(1)
|
||||
|
||||
data_list = []
|
||||
try:
|
||||
pad = len(max([l['name'] for l in lang_data[:5]], key=len))
|
||||
except ValueError:
|
||||
print("The Data seems to be empty. Please wait for a day for the data to be filled in.")
|
||||
return '```text\nNo Activity tracked this Week\n```'
|
||||
for lang in lang_data[:5]:
|
||||
lth = len(lang['name'])
|
||||
ln_text = len(lang['text'])
|
||||
# following line provides a neat finish
|
||||
fmt_percent = format(lang['percent'], '0.2f').zfill(5)
|
||||
data_list.append(
|
||||
f"{lang['name']}{' '*(pad + 3 - lth)}{lang['text']}{' '*(16 - ln_text)}{make_graph(lang['percent'])} {fmt_percent} %")
|
||||
print("Graph Generated")
|
||||
data = ' \n'.join(data_list)
|
||||
if show_title == 'true':
|
||||
print("Stats with Weeks in Title Generated")
|
||||
return '```text\n'+this_week()+'\n\n'+data+'\n```'
|
||||
else:
|
||||
print("Usual Stats Generated")
|
||||
return '```text\n'+data+'\n```'
|
||||
logger.debug("Title was made\n")
|
||||
return f"From: {start_date} - To: {end_date}"
|
||||
|
||||
|
||||
def decode_readme(data: str) -> str:
|
||||
'''Decode the contets of old readme'''
|
||||
decoded_bytes = base64.b64decode(data)
|
||||
return str(decoded_bytes, 'utf-8')
|
||||
def make_graph(block_style: str, percent: float, gr_len: int, lg_nm: str = "", /):
|
||||
"""WakaReadme Graph.
|
||||
|
||||
Makes time graph from the API's data.
|
||||
"""
|
||||
logger.debug(f"Generating graph for '{lg_nm or '...'}'")
|
||||
markers = len(block_style) - 1
|
||||
proportion = percent / 100 * gr_len
|
||||
graph_bar = block_style[-1] * int(proportion + 0.5 / markers)
|
||||
remainder_block = int((proportion - len(graph_bar)) * markers + 0.5)
|
||||
graph_bar += block_style[remainder_block] if remainder_block > 0 else ""
|
||||
graph_bar += block_style[0] * (gr_len - len(graph_bar))
|
||||
|
||||
logger.debug(f"'{lg_nm or '...'}' graph generated")
|
||||
return graph_bar
|
||||
|
||||
|
||||
def generate_new_readme(stats: str, readme: str) -> str:
|
||||
'''Generate a new Readme.md'''
|
||||
stats_in_readme = f"{START_COMMENT}\n{stats}\n{END_COMMENT}"
|
||||
return re.sub(listReg, stats_in_readme, readme)
|
||||
def prep_content(stats: dict[str, Any], /):
|
||||
"""WakaReadme Prepare Markdown.
|
||||
|
||||
Prepared markdown content from the fetched statistics.
|
||||
```
|
||||
"""
|
||||
logger.debug("Making contents")
|
||||
contents = ""
|
||||
|
||||
# make title
|
||||
if wk_i.show_title:
|
||||
contents += make_title(stats.get("start"), stats.get("end")) + "\n\n"
|
||||
|
||||
# make byline
|
||||
if wk_i.show_masked_time and (
|
||||
total_time := stats.get("human_readable_total_including_other_language")
|
||||
):
|
||||
# overrides "human_readable_total"
|
||||
contents += f"Total Time: {total_time}\n\n"
|
||||
elif wk_i.show_total_time and (total_time := stats.get("human_readable_total")):
|
||||
contents += f"Total Time: {total_time}\n\n"
|
||||
|
||||
lang_info: list[dict[str, int | float | str]] | None = []
|
||||
|
||||
# Check if any language data exists
|
||||
if not (lang_info := stats.get("languages")):
|
||||
logger.debug("The API data seems to be empty, please wait for a day")
|
||||
contents += "No activity tracked"
|
||||
return contents.rstrip("\n")
|
||||
|
||||
# make lang content
|
||||
pad_len = len(
|
||||
# comment if it feels way computationally expensive
|
||||
max((str(lng["name"]) for lng in lang_info), key=len)
|
||||
# and then do not for get to set `pad_len` to say 13 :)
|
||||
)
|
||||
language_count, stop_at_other = int(wk_i.language_count), bool(wk_i.stop_at_other)
|
||||
if language_count == 0 and not wk_i.stop_at_other:
|
||||
logger.debug(
|
||||
"Set INPUT_LANG_COUNT to -1 to retrieve all language"
|
||||
+ " or specify a positive number (ie. above 0)"
|
||||
)
|
||||
return contents.rstrip("\n")
|
||||
|
||||
ignored_languages = set[str](igl.lower() for igl in wk_i.ignored_languages.strip().split())
|
||||
for idx, lang in enumerate(lang_info):
|
||||
lang_name = str(lang["name"])
|
||||
if ignored_languages and lang_name.lower() in ignored_languages:
|
||||
continue
|
||||
lang_time = str(lang["text"]) if wk_i.show_time else ""
|
||||
lang_ratio = float(lang["percent"])
|
||||
lang_bar = make_graph(wk_i.block_style, lang_ratio, wk_i.graph_length, lang_name)
|
||||
contents += (
|
||||
f"{lang_name.ljust(pad_len)} "
|
||||
+ f"{lang_time: <16}{lang_bar} "
|
||||
+ f"{lang_ratio:.2f}".zfill(5)
|
||||
+ " %\n"
|
||||
)
|
||||
if language_count == -1:
|
||||
continue
|
||||
if stop_at_other and (lang_name == "Other"):
|
||||
break
|
||||
if idx + 1 >= language_count > 0: # idx starts at 0
|
||||
break
|
||||
|
||||
logger.debug("Contents were made\n")
|
||||
return contents.rstrip("\n")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
g = Github(ghtoken)
|
||||
try:
|
||||
repo = g.get_repo(f"{user}/{user}")
|
||||
except GithubException:
|
||||
print("Authentication Error. Try saving a GitHub Token in your Repo Secrets or Use the GitHub Actions Token, which is automatically used by the action.")
|
||||
def fetch_stats():
|
||||
"""WakaReadme Fetch Stats.
|
||||
|
||||
Returns statistics as JSON string.
|
||||
"""
|
||||
attempts = 4
|
||||
statistic: dict[str, dict[str, Any]] = {}
|
||||
encoded_key = str(b64encode(bytes(str(wk_i.waka_key), "utf-8")), "utf-8")
|
||||
logger.debug(f"Pulling WakaTime stats from {' '.join(wk_i.time_range.split('_'))}")
|
||||
while attempts > 0:
|
||||
resp_message, fake_ua = "", cryptogenic.choice([str(fake.user_agent()) for _ in range(5)])
|
||||
# making a request
|
||||
if (
|
||||
resp := rq_get(
|
||||
url=f"{str(wk_i.api_base_url).rstrip('/')}/v1/users/current/stats/{wk_i.time_range}",
|
||||
headers={
|
||||
"Authorization": f"Basic {encoded_key}",
|
||||
"User-Agent": fake_ua,
|
||||
},
|
||||
timeout=(30.0 * (5 - attempts)),
|
||||
)
|
||||
).status_code != 200:
|
||||
resp_message += f" • {conn_info}" if (conn_info := resp.json().get("message")) else ""
|
||||
logger.debug(
|
||||
f"API response #{5 - attempts}: {resp.status_code} •" + f" {resp.reason}{resp_message}"
|
||||
)
|
||||
if resp.status_code == 200 and (statistic := resp.json()):
|
||||
logger.debug("Fetched WakaTime statistics")
|
||||
break
|
||||
logger.debug(f"Retrying in {30 * (5 - attempts )}s ...")
|
||||
sleep(30 * (5 - attempts))
|
||||
attempts -= 1
|
||||
|
||||
if err := (statistic.get("error") or statistic.get("errors")):
|
||||
logger.error(f"{err}\n")
|
||||
sys.exit(1)
|
||||
contents = repo.get_readme()
|
||||
waka_stats = get_stats()
|
||||
rdmd = decode_readme(contents.content)
|
||||
new_readme = generate_new_readme(stats=waka_stats, readme=rdmd)
|
||||
if new_readme != rdmd:
|
||||
repo.update_file(path=contents.path, message='Updated with Dev Metrics',
|
||||
content=new_readme, sha=contents.sha, branch='master')
|
||||
|
||||
print()
|
||||
return statistic.get("data")
|
||||
|
||||
|
||||
def churn(old_readme: str, /):
|
||||
"""WakaReadme Churn.
|
||||
|
||||
Composes WakaTime stats within markdown code snippet.
|
||||
"""
|
||||
# check if placeholder pattern exists in readme
|
||||
if not re.findall(wk_i.waka_block_pattern, old_readme):
|
||||
logger.warning(f"Cannot find `{wk_i.waka_block_pattern}` pattern in readme")
|
||||
return None
|
||||
# getting contents
|
||||
if not (waka_stats := fetch_stats()):
|
||||
logger.error("Unable to fetch data, please rerun workflow\n")
|
||||
sys.exit(1)
|
||||
# preparing contents
|
||||
try:
|
||||
generated_content = prep_content(waka_stats)
|
||||
except (AttributeError, KeyError, ValueError) as err:
|
||||
logger.error(f"Unable to read API data | {err}\n")
|
||||
sys.exit(1)
|
||||
print(generated_content, "\n", sep="")
|
||||
# substituting old contents
|
||||
new_readme = re.sub(
|
||||
pattern=wk_i.waka_block_pattern,
|
||||
repl=f"{wk_i.start_comment}\n\n```{wk_i.code_lang}\n{generated_content}\n```\n\n{wk_i.end_comment}",
|
||||
string=old_readme,
|
||||
)
|
||||
logger.debug(new_readme)
|
||||
if len(sys.argv) == 2 and sys.argv[1] == "--dev":
|
||||
logger.debug("Detected run in `dev` mode.")
|
||||
# to avoid accidentally writing back to Github
|
||||
# when developing or testing waka-readme
|
||||
return None
|
||||
return None if new_readme == old_readme else new_readme
|
||||
|
||||
|
||||
# def qualify_target(gitea_repo: Repository.Repository):
|
||||
def qualify_target(gitea_repo: gitea.Repository, gitea_connect: gitea.Gitea):
|
||||
"""Qualify target repository defaults."""
|
||||
|
||||
@dataclass
|
||||
class TargetRepository:
|
||||
# this: ContentFile.ContentFile
|
||||
this: gitea.Content
|
||||
path: str
|
||||
commit_message: str
|
||||
sha: str
|
||||
branch: str
|
||||
# committer: InputGitAuthor | None
|
||||
# author: InputGitAuthor | None
|
||||
committer: None
|
||||
author: None
|
||||
|
||||
gitea_branch = gitea_repo.get_branches()[0].name
|
||||
# if wk_i.target_branch != "NOT_SET":
|
||||
# gitea_branch = gitea_repo.get_branch(wk_i.target_branch)
|
||||
|
||||
readme_content = gitea.Content(gitea_connect)
|
||||
readme_content.path = "README.md"
|
||||
readme_content.type = gitea.Content.FILE
|
||||
target = gitea_repo.get_file_content(readme_content) # base64 encoded
|
||||
# target = gitea_repo.get_readme()
|
||||
|
||||
# if wk_i.target_path != "NOT_SET":
|
||||
# target = gitea_repo.get_contents(
|
||||
# path=wk_i.target_path,
|
||||
# ref=gitea_branch if isinstance(gitea_branch, str) else gitea_branch.commit.sha,
|
||||
# )
|
||||
if isinstance(target, list):
|
||||
raise RuntimeError("Cannot handle multiple files.")
|
||||
|
||||
committer, author = None, None
|
||||
# if wk_i.committer_name != "NOT_SET" and wk_i.committer_email != "NOT_SET":
|
||||
# committer = InputGitAuthor(name=wk_i.committer_name, email=wk_i.committer_email)
|
||||
# if wk_i.author_name != "NOT_SET" and wk_i.author_email != "NOT_SET":
|
||||
# author = InputGitAuthor(name=wk_i.author_name, email=wk_i.author_email)
|
||||
sha = ""
|
||||
|
||||
return TargetRepository(
|
||||
this=target,
|
||||
path="README.md",
|
||||
commit_message=wk_i.commit_message,
|
||||
sha=sha,
|
||||
branch=gitea_branch if isinstance(gitea_branch, str) else gitea_branch.name,
|
||||
committer=committer,
|
||||
author=author,
|
||||
)
|
||||
|
||||
|
||||
def genesis():
|
||||
"""Run Program."""
|
||||
logger.debug("Connecting to Gitea")
|
||||
gitea_connect = gitea.Gitea(wk_i.gitea_url, wk_i.gitea_token)
|
||||
logger.debug("logged into gitea\n")
|
||||
|
||||
# since a validator is being used earlier, casting
|
||||
# `wk_i.ENV_VARIABLE` to a string here, is okay
|
||||
# gitea_repo = gitea_connect.get_repo(str(wk_i.repository))
|
||||
|
||||
owner = "sangge"
|
||||
repo_name = ".profile"
|
||||
gitea_repo = gitea.Repository.request(gitea_connect, owner, repo_name)
|
||||
if len(sys.argv) == 2 and sys.argv[1] == "--dev":
|
||||
logger.debug("Detected run in `dev` mode.,fetched repo \n")
|
||||
target = qualify_target(gitea_repo, gitea_connect)
|
||||
logger.debug("Decoding readme contents\n")
|
||||
|
||||
readme_contents = str(b64decode(target.this), encoding="utf-8")
|
||||
if not (new_content := churn(readme_contents)):
|
||||
logger.info("WakaReadme was not updated")
|
||||
return
|
||||
|
||||
logger.debug("WakaReadme stats has changed")
|
||||
# update_metric = partial(
|
||||
# gitea_repo.update_file,
|
||||
# path=target.path,
|
||||
# message=target.commit_message,
|
||||
# content=new_content,
|
||||
# sha=target.sha,
|
||||
# branch=target.branch,
|
||||
# )
|
||||
# if target.committer:
|
||||
# update_metric = partial(update_metric, committer=target.committer)
|
||||
# if target.author:
|
||||
# update_metric = partial(update_metric, author=target.author)
|
||||
# update_metric()
|
||||
b64_new_content = b64encode(bytes(new_content, "utf-8"))
|
||||
repo_content = gitea_repo.get_git_content()
|
||||
readmes = [c for c in repo_content if c.name == "README.md"]
|
||||
str_new_content = b64_new_content.decode("utf-8")
|
||||
gitea_repo.change_file(readmes[0].name, readmes[0].sha, str_new_content)
|
||||
logger.info("Stats updated successfully")
|
||||
return
|
||||
|
||||
|
||||
################### driver ###################
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# faker data preparation
|
||||
fake = Faker()
|
||||
Faker.seed(0)
|
||||
cryptogenic = SystemRandom()
|
||||
|
||||
# initial waka-readme setup
|
||||
logger.debug("Initialize WakaReadme")
|
||||
wk_i = WakaInput()
|
||||
if not wk_i.validate_input():
|
||||
logger.error("Environment variables are misconfigured\n")
|
||||
sys.exit(1)
|
||||
|
||||
# run
|
||||
try:
|
||||
genesis()
|
||||
except KeyboardInterrupt:
|
||||
print("\r", end=" ")
|
||||
logger.error("Interrupt signal received\n")
|
||||
sys.exit(1)
|
||||
except RuntimeError as err:
|
||||
logger.error(f"{type(err).__name__}: {err}\n")
|
||||
sys.exit(1)
|
||||
# except (GithubException, RequestException) as rq_exp:
|
||||
# logger.critical(f"{rq_exp}\n")
|
||||
# sys.exit(1)
|
||||
print("\nThanks for using WakaReadme!\n")
|
||||
|
86
pyproject.toml
Normal file
86
pyproject.toml
Normal file
@ -0,0 +1,86 @@
|
||||
####################
|
||||
# Metadata #
|
||||
####################
|
||||
|
||||
[project]
|
||||
name = "waka-readme"
|
||||
version = "0.3.0"
|
||||
description = "Wakatime Weekly Metrics on your Profile Readme."
|
||||
authors = [{ name = "Athul Cyriac Ajay", email = "athul8720@gmail.com" }]
|
||||
license = { text = "MIT" }
|
||||
readme = "README.md"
|
||||
keywords = ["readme", "profile-page", "wakatime"]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Programming Language :: Python",
|
||||
"Typing :: Typed",
|
||||
]
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["faker>=21.0.0", "pygithub>=2.1.1", "requests>=2.31.0", "py-gitea>=0.2.6"]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/athul/waka-readme"
|
||||
Documentation = "https://github.com/athul/waka-readme#readme"
|
||||
Repository = "https://github.com/athul/waka-readme"
|
||||
Changelog = "https://github.com/athul/waka-readme/commits/master"
|
||||
|
||||
#############################
|
||||
# Optional Dependencies #
|
||||
#############################
|
||||
|
||||
[project.optional-dependencies]
|
||||
extra = ["loguru>=0.7.2", "python-dotenv>=1.0.0"]
|
||||
|
||||
#############################
|
||||
# Development Dependencies #
|
||||
#############################
|
||||
|
||||
[tool.pdm.dev-dependencies]
|
||||
tooling = ["bandit>=1.7.6", "black>=23.12.1", "ruff>=0.1.9", "pyright>=1.1.342"]
|
||||
|
||||
####################
|
||||
# Configurations #
|
||||
####################
|
||||
|
||||
[tool.bandit]
|
||||
exclude_dirs = [".github", "tests", ".venv", ".vscode"]
|
||||
|
||||
[tool.black]
|
||||
line-length = 100
|
||||
target-version = ["py312"]
|
||||
|
||||
[tool.pyright]
|
||||
exclude = ["**/__pycache__", ".venv/"]
|
||||
pythonVersion = "3.12"
|
||||
pythonPlatform = "All"
|
||||
typeCheckingMode = "strict"
|
||||
|
||||
[tool.ruff]
|
||||
select = [
|
||||
# Pyflakes
|
||||
"F",
|
||||
# pycodestyle
|
||||
"W",
|
||||
"E",
|
||||
# mccabe
|
||||
# C90
|
||||
# isort
|
||||
"I",
|
||||
# pep8-naming
|
||||
"N",
|
||||
# pydocstyle
|
||||
"D",
|
||||
]
|
||||
line-length = 100
|
||||
target-version = "py312"
|
||||
extend-exclude = ["**/__pycache__"]
|
||||
|
||||
[tool.ruff.isort]
|
||||
# case-sensitive = true
|
||||
combine-as-imports = true
|
||||
force-sort-within-sections = true
|
||||
force-wrap-aliases = true
|
||||
relative-imports-order = "closest-to-furthest"
|
||||
|
||||
[tool.ruff.pydocstyle]
|
||||
convention = "google"
|
@ -1,9 +0,0 @@
|
||||
certifi==2020.6.20
|
||||
chardet==3.0.4
|
||||
Deprecated==1.2.10
|
||||
idna==2.10
|
||||
PyGithub==1.51
|
||||
PyJWT==1.7.1
|
||||
requests==2.24.0
|
||||
urllib3==1.25.9
|
||||
wrapt==1.12.1
|
7
tests/__init__.py
Normal file
7
tests/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
"""Initialize test module."""
|
||||
|
||||
# standard
|
||||
import logging
|
||||
|
||||
# comment to enable logging w/ tests
|
||||
logging.disable(logging.CRITICAL)
|
11346
tests/sample_data.json
Normal file
11346
tests/sample_data.json
Normal file
File diff suppressed because it is too large
Load Diff
148
tests/test_main.py
Normal file
148
tests/test_main.py
Normal file
@ -0,0 +1,148 @@
|
||||
"""Unit Tests."""
|
||||
|
||||
# standard
|
||||
from dataclasses import dataclass # , field
|
||||
from importlib import import_module
|
||||
from itertools import product
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
# from pathlib import Path
|
||||
# from inspect import cleandoc
|
||||
# from typing import Any
|
||||
# from json import load
|
||||
|
||||
try:
|
||||
prime = import_module("main")
|
||||
# works when running as
|
||||
# python -m unittest discover
|
||||
except ImportError as err:
|
||||
print(err)
|
||||
# sys.exit(1)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TestData:
|
||||
"""Test Data."""
|
||||
|
||||
# for future tests
|
||||
# waka_json: dict[str, dict[str, Any]] = field(
|
||||
# default_factory=lambda: {}
|
||||
# )
|
||||
bar_percent: tuple[int | float, ...] | None = None
|
||||
graph_blocks: tuple[str, ...] | None = None
|
||||
waka_graphs: tuple[list[str], ...] | None = None
|
||||
dummy_readme: str = ""
|
||||
|
||||
def populate(self) -> None:
|
||||
"""Populate Test Data."""
|
||||
# for future tests
|
||||
# with open(
|
||||
# file=Path(__file__).parent / "sample_data.json",
|
||||
# encoding="utf-8",
|
||||
# mode="rt",
|
||||
# ) as wkf:
|
||||
# self.waka_json = load(wkf)
|
||||
|
||||
self.bar_percent = (0, 100, 49.999, 50, 25, 75, 3.14, 9.901, 87.334, 87.333, 4.666, 4.667)
|
||||
|
||||
self.graph_blocks = ("░▒▓█", "⚪⚫", "⓪①②③④⑤⑥⑦⑧⑨⑩")
|
||||
|
||||
self.waka_graphs = (
|
||||
[
|
||||
"░░░░░░░░░░░░░░░░░░░░░░░░░",
|
||||
"█████████████████████████",
|
||||
"████████████▒░░░░░░░░░░░░",
|
||||
"████████████▓░░░░░░░░░░░░",
|
||||
"██████▒░░░░░░░░░░░░░░░░░░",
|
||||
"██████████████████▓░░░░░░",
|
||||
"▓░░░░░░░░░░░░░░░░░░░░░░░░",
|
||||
"██▒░░░░░░░░░░░░░░░░░░░░░░",
|
||||
"██████████████████████░░░",
|
||||
"█████████████████████▓░░░",
|
||||
"█░░░░░░░░░░░░░░░░░░░░░░░░",
|
||||
"█▒░░░░░░░░░░░░░░░░░░░░░░░",
|
||||
],
|
||||
[
|
||||
"⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
||||
"⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫",
|
||||
"⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
||||
"⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
||||
"⚫⚫⚫⚫⚫⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
||||
"⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚪⚪⚪⚪⚪⚪",
|
||||
"⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
||||
"⚫⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
||||
"⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚪⚪⚪",
|
||||
"⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚪⚪⚪",
|
||||
"⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
||||
"⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
||||
],
|
||||
[
|
||||
"⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
||||
"⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩",
|
||||
"⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑤⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
||||
"⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑤⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
||||
"⑩⑩⑩⑩⑩⑩③⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
||||
"⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑧⓪⓪⓪⓪⓪⓪",
|
||||
"⑧⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
||||
"⑩⑩⑤⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
||||
"⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑧⓪⓪⓪",
|
||||
"⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑧⓪⓪⓪",
|
||||
"⑩②⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
||||
"⑩②⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
||||
],
|
||||
)
|
||||
|
||||
# self.dummy_readme = cleandoc("""
|
||||
# My Test Readme Start
|
||||
# <!--START_SECTION:waka-->
|
||||
# <!--END_SECTION:waka-->
|
||||
# My Test Readme End
|
||||
# """)
|
||||
|
||||
|
||||
class TestMain(unittest.TestCase):
|
||||
"""Testing Main Module."""
|
||||
|
||||
def test_make_graph(self) -> None:
|
||||
"""Test graph maker."""
|
||||
if not tds.graph_blocks or not tds.waka_graphs or not tds.bar_percent:
|
||||
raise AssertionError("Data population failed")
|
||||
|
||||
for (idx, grb), (jdy, bpc) in product(
|
||||
enumerate(tds.graph_blocks), enumerate(tds.bar_percent)
|
||||
):
|
||||
self.assertEqual(prime.make_graph(grb, bpc, 25), tds.waka_graphs[idx][jdy])
|
||||
|
||||
def test_make_title(self) -> None:
|
||||
"""Test title maker."""
|
||||
self.assertRegex(
|
||||
prime.make_title("2022-01-11T23:18:19Z", "2021-12-09T10:22:06Z"),
|
||||
r"From: \d{2} \w{3,9} \d{4} - To: \d{2} \w{3,9} \d{4}",
|
||||
)
|
||||
|
||||
def test_strtobool(self) -> None:
|
||||
"""Test string to bool."""
|
||||
self.assertTrue(prime.strtobool("Yes"))
|
||||
self.assertFalse(prime.strtobool("nO"))
|
||||
self.assertTrue(prime.strtobool(True))
|
||||
self.assertRaises(AttributeError, prime.strtobool, None)
|
||||
self.assertRaises(ValueError, prime.strtobool, "yo!")
|
||||
self.assertRaises(AttributeError, prime.strtobool, 20.5)
|
||||
|
||||
|
||||
tds = TestData()
|
||||
tds.populate()
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
import main as prime
|
||||
|
||||
# works when running as
|
||||
# python tests/test_main.py
|
||||
except ImportError as im_er:
|
||||
print(im_er)
|
||||
sys.exit(1)
|
||||
unittest.main()
|
Loading…
x
Reference in New Issue
Block a user