Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c34fb893a6 | ||
|
|
1fc26a4121 | ||
|
|
82c9408d6d | ||
|
|
9bee9ba11b | ||
|
|
b42a071671 | ||
|
|
e8541dbe4e | ||
|
|
da0f4f1847 | ||
|
|
d26ed33e7a | ||
|
|
a50019231d | ||
|
|
080a8c9b63 | ||
|
|
fbc9196645 | ||
|
|
59f35b046b | ||
|
|
60fa45f3f0 | ||
|
|
60fe3b9f48 | ||
|
|
29dba6dd79 | ||
|
|
ac0bb21462 |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -112,7 +112,8 @@ celerybeat.pid
|
|||||||
*.sage.py
|
*.sage.py
|
||||||
|
|
||||||
# Environments
|
# Environments
|
||||||
.env
|
*.env
|
||||||
|
env.sh
|
||||||
.venv
|
.venv
|
||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
@@ -156,3 +157,6 @@ cython_debug/
|
|||||||
|
|
||||||
# asdf
|
# asdf
|
||||||
.tool-versions
|
.tool-versions
|
||||||
|
|
||||||
|
# ruff
|
||||||
|
.ruff_cache
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
> First off, thank you! Please follow along.
|
> First off, thank you! Please follow along.
|
||||||
|
|
||||||
@@ -71,8 +71,8 @@
|
|||||||
```console
|
```console
|
||||||
# poetry shell
|
# poetry shell
|
||||||
# set -a && . ./.env && set +a # optional
|
# set -a && . ./.env && set +a # optional
|
||||||
(venv)# python -m main --dev
|
(waka-readme-py3_11)# python -m main --dev
|
||||||
(venv)# python -m unittest discover # run tests
|
(waka-readme-py3_11)# python -m unittest discover # run tests
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Later, to remove stop and remove the container:
|
5. Later, to remove stop and remove the container:
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
|
|
||||||
```console
|
```console
|
||||||
$ poetry shell
|
$ poetry shell
|
||||||
(venv)$ poetry install
|
(waka-readme-py3_11)$ poetry install
|
||||||
```
|
```
|
||||||
|
|
||||||
to create and activate a virtual environnement and install dependencies.
|
to create and activate a virtual environnement and install dependencies.
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
3. Execute program in development mode with:
|
3. Execute program in development mode with:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ set -a && . ./.env && set +a # optional
|
(waka-readme-py3_11)$ set -a && . ./.env && set +a # optional
|
||||||
(venv)$ python -m main --dev
|
(waka-readme-py3_11)$ python -m main --dev
|
||||||
(venv)$ python -m unittest discover # run tests
|
(waka-readme-py3_11)$ python -m unittest discover # run tests
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
</center>
|
</center>
|
||||||
|
|
||||||
# Dev Metrics in Readme [](https://github.com/athul/waka-readme/actions/workflows/testing.yml) 
|
# Dev Metrics in Readme [](https://github.com/athul/waka-readme/actions/workflows/testing.yml) 
|
||||||
|
|
||||||
[WakaTime](https://wakatime.com) weekly metrics on your profile readme.
|
[WakaTime](https://wakatime.com) weekly metrics on your profile readme.
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ A GitHub repository and a `README.md` file is required. We'll be making use of r
|
|||||||
<!--END_SECTION:waka-->
|
<!--END_SECTION:waka-->
|
||||||
```
|
```
|
||||||
|
|
||||||
"`waka`" can be replaced by any alphanumeric string with the `SECTION_NAME` environment variable. See the [#tweaks](#tweaks) section for more.
|
`<!--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 > Secrets` and add a new secret _named_ `WAKATIME_API_KEY` with your API key as it's _value_.
|
- Navigate to your repo's `Settings > Secrets` and add a new secret _named_ `WAKATIME_API_KEY` with your API key as it's _value_.
|
||||||
|
|
||||||
@@ -82,6 +82,7 @@ There are many flags that you can tweak to suit your taste!
|
|||||||
| `REPOSITORY` | `<gh_username>/<gh_username>` | `<gh_username>/<repo_name>` | Waka-readme stats will appear on the provided repository |
|
| `REPOSITORY` | `<gh_username>/<gh_username>` | `<gh_username>/<repo_name>` | Waka-readme stats will appear on the provided repository |
|
||||||
| `SECTION_NAME` | `waka` | Any alphanumeric string | The generator will look for this section to fill up the readme. |
|
| `SECTION_NAME` | `waka` | Any alphanumeric string | The generator will look for this section to fill up the readme. |
|
||||||
| `COMMIT_MESSAGE` | `Updated waka-readme graph with new metrics` | Any string | Messaged used when committing updated stats |
|
| `COMMIT_MESSAGE` | `Updated waka-readme graph with new metrics` | Any string | Messaged used when committing updated stats |
|
||||||
|
| `CODE_LANG` | `txt` | `python` `ruby` `json` , you can use other languages also | Language syntax to format the generated text, to get colored text. |
|
||||||
| `SHOW_TITLE` | `false` | `false`, `true` | Add title to waka-readme stats blob |
|
| `SHOW_TITLE` | `false` | `false`, `true` | Add title to waka-readme stats blob |
|
||||||
| `BLOCKS` | `░▒▓█` | `░▒▓█`, `⣀⣄⣤⣦⣶⣷⣿`, `-#`, you can be creative! | Ascii art used to build stats graph |
|
| `BLOCKS` | `░▒▓█` | `░▒▓█`, `⣀⣄⣤⣦⣶⣷⣿`, `-#`, you can be creative! | Ascii art used to build stats graph |
|
||||||
| `TIME_RANGE` | `last_7_days` | `last_7_days`, `last_30_days`, `last_6_months`, `last_year`, `all_time` | String representing a dispensation from which stats are aggregated |
|
| `TIME_RANGE` | `last_7_days` | `last_7_days`, `last_30_days`, `last_6_months`, `last_year`, `all_time` | String representing a dispensation from which stats are aggregated |
|
||||||
@@ -89,6 +90,7 @@ There are many flags that you can tweak to suit your taste!
|
|||||||
| `SHOW_TOTAL` | `false` | `false`, `true` | Show total coding time |
|
| `SHOW_TOTAL` | `false` | `false`, `true` | Show total coding time |
|
||||||
| `SHOW_MASKED_TIME` | `false` | `false`, `true` | Adds total coding time including unclassified languages (overrides: `SHOW_TOTAL`) |
|
| `SHOW_MASKED_TIME` | `false` | `false`, `true` | Adds total coding time including unclassified languages (overrides: `SHOW_TOTAL`) |
|
||||||
| `LANG_COUNT` | `5` | Any reasonable number | Number of languages to be displayed |
|
| `LANG_COUNT` | `5` | Any reasonable number | Number of languages to be displayed |
|
||||||
|
| `STOP_AT_OTHER` | `false` | `false`, `true` | Stop when language marked as `Other` is retrieved (overrides: `LANG_COUNT`) |
|
||||||
|
|
||||||
# Example
|
# Example
|
||||||
|
|
||||||
|
|||||||
10
action.yml
10
action.yml
@@ -44,6 +44,11 @@ inputs:
|
|||||||
default: "░▒▓█"
|
default: "░▒▓█"
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
|
CODE_LANG:
|
||||||
|
description: "Add syntax formatter for generated code"
|
||||||
|
default: "txt"
|
||||||
|
required: false
|
||||||
|
|
||||||
TIME_RANGE:
|
TIME_RANGE:
|
||||||
description: "Time range of the queried statistics"
|
description: "Time range of the queried statistics"
|
||||||
default: "last_7_days"
|
default: "last_7_days"
|
||||||
@@ -68,6 +73,11 @@ inputs:
|
|||||||
description: "Displays total coding time including unclassified languages"
|
description: "Displays total coding time including unclassified languages"
|
||||||
default: "false"
|
default: "false"
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
|
STOP_AT_OTHER:
|
||||||
|
description: "Stop data retrieval when language marked 'Other' is reached"
|
||||||
|
default: "false"
|
||||||
|
required: false
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "docker"
|
using: "docker"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ ENV PYTHONFAULTHANDLER=1 \
|
|||||||
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
||||||
PIP_DEFAULT_TIMEOUT=100 \
|
PIP_DEFAULT_TIMEOUT=100 \
|
||||||
# poetry:
|
# poetry:
|
||||||
# POETRY_VERSION=1.1.14 \
|
# POETRY_VERSION= \
|
||||||
POETRY_NO_INTERACTION=1 \
|
POETRY_NO_INTERACTION=1 \
|
||||||
POETRY_CACHE_DIR=/var/cache/pypoetry \
|
POETRY_CACHE_DIR=/var/cache/pypoetry \
|
||||||
PATH=${PATH}:/root/.local/bin
|
PATH=${PATH}:/root/.local/bin
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ ENV PYTHONFAULTHANDLER=1 \
|
|||||||
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
||||||
PIP_DEFAULT_TIMEOUT=100 \
|
PIP_DEFAULT_TIMEOUT=100 \
|
||||||
# poetry:
|
# poetry:
|
||||||
# POETRY_VERSION=1.1.14 \
|
# POETRY_VERSION= \
|
||||||
POETRY_NO_INTERACTION=1 \
|
POETRY_NO_INTERACTION=1 \
|
||||||
POETRY_CACHE_DIR=/var/cache/pypoetry \
|
POETRY_CACHE_DIR=/var/cache/pypoetry \
|
||||||
PATH=${PATH}:/root/.local/bin
|
PATH=${PATH}:/root/.local/bin
|
||||||
|
|||||||
391
main.py
391
main.py
@@ -1,25 +1,20 @@
|
|||||||
"""
|
"""WakaReadme : WakaTime progress visualizer.
|
||||||
WakaReadme : WakaTime progress visualizer
|
|
||||||
=========================================
|
|
||||||
|
|
||||||
Wakatime Metrics on your Profile Readme.
|
Wakatime Metrics on your Profile Readme.
|
||||||
|
|
||||||
Title:
|
Title:
|
||||||
------
|
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
From: 15 February, 2022 - To: 22 February, 2022
|
From: 15 February, 2022 - To: 22 February, 2022
|
||||||
````
|
````
|
||||||
|
|
||||||
Byline:
|
Byline:
|
||||||
-------
|
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
Total: 34 hrs 43 mins
|
Total: 34 hrs 43 mins
|
||||||
```
|
```
|
||||||
|
|
||||||
Body:
|
Body:
|
||||||
-----
|
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
Python 27 hrs 29 mins ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣀⣀⣀⣀⣀ 77.83 %
|
Python 27 hrs 29 mins ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣀⣀⣀⣀⣀ 77.83 %
|
||||||
@@ -29,7 +24,7 @@ TOML 1 hr 48 mins ⣿⣤⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀
|
|||||||
Other 35 mins ⣦⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ 01.68 %
|
Other 35 mins ⣦⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ 01.68 %
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Contents = Title + Byline + Body
|
Contents := Title + Byline + Body
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# standard
|
# standard
|
||||||
@@ -45,42 +40,36 @@ import re
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
# external
|
# external
|
||||||
# # requests
|
|
||||||
from requests.exceptions import RequestException
|
from requests.exceptions import RequestException
|
||||||
from requests import get as rq_get
|
from requests import get as rq_get
|
||||||
# # github
|
|
||||||
from github import GithubException, Github
|
from github import GithubException, Github
|
||||||
# # faker
|
|
||||||
from faker import Faker
|
from faker import Faker
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable = logging-fstring-interpolation
|
|
||||||
|
|
||||||
|
|
||||||
################### setup ###################
|
################### setup ###################
|
||||||
|
|
||||||
|
|
||||||
print()
|
print()
|
||||||
# hush existing loggers
|
# hush existing loggers
|
||||||
# pylint: disable = no-member # see: https://stackoverflow.com/q/20965287
|
|
||||||
for lgr_name in logger.root.manager.loggerDict:
|
for lgr_name in logger.root.manager.loggerDict:
|
||||||
# to disable log propagation completely set '.propagate = False'
|
# to disable log propagation completely set '.propagate = False'
|
||||||
logger.getLogger(lgr_name).setLevel(logger.WARNING)
|
logger.getLogger(lgr_name).setLevel(logger.WARNING)
|
||||||
# pylint: enable = no-member
|
|
||||||
# somehow github.Requester gets missed out from loggerDict
|
# somehow github.Requester gets missed out from loggerDict
|
||||||
logger.getLogger('github.Requester').setLevel(logger.WARNING)
|
logger.getLogger("github.Requester").setLevel(logger.WARNING)
|
||||||
# configure logger
|
# configure logger
|
||||||
logger.basicConfig(
|
logger.basicConfig(
|
||||||
datefmt='%Y-%m-%d %H:%M:%S',
|
datefmt="%Y-%m-%d %H:%M:%S",
|
||||||
format='[%(asctime)s] ln. %(lineno)-3d %(levelname)-8s %(message)s',
|
format="[%(asctime)s] ln. %(lineno)-3d %(levelname)-8s %(message)s",
|
||||||
level=logger.DEBUG
|
level=logger.DEBUG,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
if len(sys.argv) == 2 and sys.argv[1] == '--dev':
|
if len(sys.argv) == 2 and sys.argv[1] == "--dev":
|
||||||
# get env-vars from .env file for development
|
# get env-vars from .env file for development
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
# comment this out to disable colored logging
|
# comment this out to disable colored logging
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
# load from .env before class def gets parsed
|
# load from .env before class def gets parsed
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
except ImportError as im_err:
|
except ImportError as im_err:
|
||||||
@@ -90,32 +79,39 @@ except ImportError as im_err:
|
|||||||
################### lib-func ###################
|
################### lib-func ###################
|
||||||
|
|
||||||
|
|
||||||
def strtobool(val: str | bool) -> bool:
|
def strtobool(val: str | bool):
|
||||||
"""
|
"""Strtobool.
|
||||||
strtobool
|
|
||||||
---------
|
|
||||||
|
|
||||||
PEP 632 https://www.python.org/dev/peps/pep-0632/ is depreciating distutils.
|
PEP 632 https://www.python.org/dev/peps/pep-0632/ is depreciating distutils.
|
||||||
This is from the official source code with slight modifications.
|
This is from the official source code with slight modifications.
|
||||||
|
|
||||||
Converts a string representation of truth to True or False.
|
Converts a string representation of truth to `True` or `False`.
|
||||||
|
|
||||||
- True values are `'y', 'yes', 't', 'true', 'on', and '1'`
|
Args:
|
||||||
- False values are `'n', 'no', 'f', 'false', 'off', and '0'`
|
val:
|
||||||
- Raises `ValueError` if `val` is anything else.
|
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):
|
if isinstance(val, bool):
|
||||||
return val
|
return val
|
||||||
|
|
||||||
val = val.lower()
|
val = val.lower()
|
||||||
|
|
||||||
if val in {'y', 'yes', 't', 'true', 'on', '1'}:
|
if val in {"y", "yes", "t", "true", "on", "1"}:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if val in {'n', 'no', 'f', 'false', 'off', '0'}:
|
if val in {"n", "no", "f", "false", "off", "0"}:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
raise ValueError(f'invalid truth value for {val}')
|
raise ValueError(f"invalid truth value for {val}")
|
||||||
|
|
||||||
|
|
||||||
################### data ###################
|
################### data ###################
|
||||||
@@ -123,54 +119,47 @@ def strtobool(val: str | bool) -> bool:
|
|||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
class WakaInput:
|
class WakaInput:
|
||||||
"""
|
"""WakaReadme Input Env Variables."""
|
||||||
WakaReadme Input Env Variables
|
|
||||||
------------------------------
|
|
||||||
"""
|
|
||||||
# constants
|
# constants
|
||||||
prefix_length: int = 16
|
prefix_length: int = 16
|
||||||
graph_length: int = 25
|
graph_length: int = 25
|
||||||
|
|
||||||
# mapped environment variables
|
# mapped environment variables
|
||||||
# # required
|
# # required
|
||||||
gh_token: str | None = os.getenv('INPUT_GH_TOKEN')
|
gh_token: str | None = os.getenv("INPUT_GH_TOKEN")
|
||||||
waka_key: str | None = os.getenv('INPUT_WAKATIME_API_KEY')
|
waka_key: str | None = os.getenv("INPUT_WAKATIME_API_KEY")
|
||||||
api_base_url: str | None = os.getenv(
|
api_base_url: str | None = os.getenv("INPUT_API_BASE_URL", "https://wakatime.com/api")
|
||||||
'INPUT_API_BASE_URL', 'https://wakatime.com/api'
|
repository: str | None = os.getenv("INPUT_REPOSITORY")
|
||||||
)
|
|
||||||
repository: str | None = os.getenv('INPUT_REPOSITORY')
|
|
||||||
# # depends
|
# # depends
|
||||||
commit_message: str = os.getenv(
|
commit_message: str = os.getenv(
|
||||||
'INPUT_COMMIT_MESSAGE', 'Updated WakaReadme graph with new metrics'
|
"INPUT_COMMIT_MESSAGE", "Updated WakaReadme graph with new metrics"
|
||||||
)
|
)
|
||||||
_section_name: str = os.getenv('INPUT_SECTION_NAME', 'waka')
|
code_lang: str = os.getenv("INPUT_CODE_LANG", "txt")
|
||||||
start_comment: str = f'<!--START_SECTION:{_section_name}-->'
|
_section_name: str = os.getenv("INPUT_SECTION_NAME", "waka")
|
||||||
end_comment: str = f'<!--END_SECTION:{_section_name}-->'
|
start_comment: str = f"<!--START_SECTION:{_section_name}-->"
|
||||||
waka_block_pattern: str = f'{start_comment}[\\s\\S]+{end_comment}'
|
end_comment: str = f"<!--END_SECTION:{_section_name}-->"
|
||||||
|
waka_block_pattern: str = f"{start_comment}[\\s\\S]+{end_comment}"
|
||||||
# # optional
|
# # optional
|
||||||
show_title: str | bool = os.getenv('INPUT_SHOW_TITLE') or False
|
show_title: str | bool = os.getenv("INPUT_SHOW_TITLE") or False
|
||||||
block_style: str = os.getenv('INPUT_BLOCKS', '░▒▓█')
|
block_style: str = os.getenv("INPUT_BLOCKS", "░▒▓█")
|
||||||
time_range: str = os.getenv('INPUT_TIME_RANGE', 'last_7_days')
|
time_range: str = os.getenv("INPUT_TIME_RANGE", "last_7_days")
|
||||||
show_time: str | bool = os.getenv('INPUT_SHOW_TIME') or False
|
show_time: str | bool = os.getenv("INPUT_SHOW_TIME") or False
|
||||||
show_total_time: str | bool = os.getenv('INPUT_SHOW_TOTAL') 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
|
show_masked_time: str | bool = os.getenv("INPUT_SHOW_MASKED_TIME") or False
|
||||||
language_count: str | int = os.getenv('INPUT_LANG_COUNT') or 5
|
language_count: str | int = os.getenv("INPUT_LANG_COUNT") or 5
|
||||||
|
stop_at_other: str | bool = os.getenv("INPUT_STOP_AT_OTHER") or False
|
||||||
|
|
||||||
def validate_input(self) -> bool:
|
def validate_input(self):
|
||||||
"""
|
"""Validate Input Env Variables."""
|
||||||
Validate Input Env Variables
|
logger.debug("Validating input variables")
|
||||||
----------------------------
|
|
||||||
"""
|
|
||||||
logger.debug('Validating input variables')
|
|
||||||
if not self.gh_token or not self.waka_key or not self.api_base_url or not self.repository:
|
if not self.gh_token or not self.waka_key or not self.api_base_url or not self.repository:
|
||||||
logger.error('Invalid inputs')
|
logger.error("Invalid inputs")
|
||||||
logger.info('Refer https://github.com/athul/waka-readme')
|
logger.info("Refer https://github.com/athul/waka-readme")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if len(self.commit_message) < 1:
|
if len(self.commit_message) < 1:
|
||||||
logger.error(
|
logger.error("Commit message length must be greater than 1 character long")
|
||||||
'Commit message length must be greater than 1 character long'
|
|
||||||
)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -178,221 +167,227 @@ class WakaInput:
|
|||||||
self.show_time = strtobool(self.show_time)
|
self.show_time = strtobool(self.show_time)
|
||||||
self.show_total_time = strtobool(self.show_total_time)
|
self.show_total_time = strtobool(self.show_total_time)
|
||||||
self.show_masked_time = strtobool(self.show_masked_time)
|
self.show_masked_time = strtobool(self.show_masked_time)
|
||||||
|
self.stop_at_other = strtobool(self.stop_at_other)
|
||||||
except (ValueError, AttributeError) as err:
|
except (ValueError, AttributeError) as err:
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not self._section_name.isalnum():
|
if not self._section_name.isalnum():
|
||||||
logger.warning('Section name must be in any of [[a-z][A-Z][0-9]]')
|
logger.warning("Section name must be in any of [[a-z][A-Z][0-9]]")
|
||||||
logger.debug('Using default section name: waka')
|
logger.debug("Using default section name: waka")
|
||||||
self._section_name = 'waka'
|
self._section_name = "waka"
|
||||||
self.start_comment = f'<!--START_SECTION:{self._section_name}-->'
|
self.start_comment = f"<!--START_SECTION:{self._section_name}-->"
|
||||||
self.end_comment = f'<!--END_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}'
|
self.waka_block_pattern = f"{self.start_comment}[\\s\\S]+{self.end_comment}"
|
||||||
|
|
||||||
if len(self.block_style) < 2:
|
if len(self.block_style) < 2:
|
||||||
logger.warning('Graph block must be longer than 2 characters')
|
logger.warning("Graph block must be longer than 2 characters")
|
||||||
logger.debug('Using default blocks: ░▒▓█')
|
logger.debug("Using default blocks: ░▒▓█")
|
||||||
self.block_style = '░▒▓█'
|
self.block_style = "░▒▓█"
|
||||||
|
|
||||||
if self.time_range not in {
|
if self.time_range not in {
|
||||||
'last_7_days', 'last_30_days', 'last_6_months', 'last_year', 'all_time'
|
"last_7_days",
|
||||||
}: # 'all_time' is un-documented, should it be used?
|
"last_30_days",
|
||||||
logger.warning('Invalid time range')
|
"last_6_months",
|
||||||
logger.debug('Using default time range: last_7_days')
|
"last_year",
|
||||||
self.time_range = 'last_7_days'
|
"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"
|
||||||
|
|
||||||
if not str(self.language_count).isnumeric():
|
try:
|
||||||
logger.warning('Invalid language count')
|
self.language_count = int(self.language_count)
|
||||||
logger.debug('Using default language count: 5')
|
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
|
self.language_count = 5
|
||||||
|
|
||||||
logger.debug('Input validation complete\n')
|
logger.debug("Input validation complete\n")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
################### logic ###################
|
################### logic ###################
|
||||||
|
|
||||||
|
|
||||||
def make_title(dawn: str | None, dusk: str | None, /) -> str:
|
def make_title(dawn: str | None, dusk: str | None, /):
|
||||||
"""
|
"""WakaReadme Title.
|
||||||
WakaReadme Title
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Makes title for WakaReadme.
|
Makes title for WakaReadme.
|
||||||
"""
|
"""
|
||||||
logger.debug('Making title')
|
logger.debug("Making title")
|
||||||
if not dawn or not dusk:
|
if not dawn or not dusk:
|
||||||
logger.error('Cannot find start/end date\n')
|
logger.error("Cannot find start/end date\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
api_dfm, msg_dfm = '%Y-%m-%dT%H:%M:%SZ', '%d %B %Y'
|
api_dfm, msg_dfm = "%Y-%m-%dT%H:%M:%SZ", "%d %B %Y"
|
||||||
try:
|
try:
|
||||||
start_date = datetime.strptime(dawn, api_dfm).strftime(msg_dfm)
|
start_date = datetime.strptime(dawn, api_dfm).strftime(msg_dfm)
|
||||||
end_date = datetime.strptime(dusk, api_dfm).strftime(msg_dfm)
|
end_date = datetime.strptime(dusk, api_dfm).strftime(msg_dfm)
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
logger.error(f'{err}\n')
|
logger.error(f"{err}\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
logger.debug('Title was made\n')
|
logger.debug("Title was made\n")
|
||||||
return f'From: {start_date} - To: {end_date}'
|
return f"From: {start_date} - To: {end_date}"
|
||||||
|
|
||||||
|
|
||||||
def make_graph(block_style: str, percent: float, gr_len: int, lg_nm: str = '', /) -> str:
|
def make_graph(block_style: str, percent: float, gr_len: int, lg_nm: str = "", /):
|
||||||
"""
|
"""WakaReadme Graph.
|
||||||
WakaReadme Graph
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Makes time graph from the API's data.
|
Makes time graph from the API's data.
|
||||||
"""
|
"""
|
||||||
logger.debug(f'Generating graph for "{lg_nm or "..."}"')
|
logger.debug(f"Generating graph for '{lg_nm or '...'}'")
|
||||||
markers = len(block_style) - 1
|
markers = len(block_style) - 1
|
||||||
proportion = percent / 100 * gr_len
|
proportion = percent / 100 * gr_len
|
||||||
graph_bar = block_style[-1] * int(proportion + 0.5 / markers)
|
graph_bar = block_style[-1] * int(proportion + 0.5 / markers)
|
||||||
remainder_block = int(
|
remainder_block = int((proportion - len(graph_bar)) * markers + 0.5)
|
||||||
(proportion - len(graph_bar)) * markers + 0.5
|
graph_bar += block_style[remainder_block] if remainder_block > 0 else ""
|
||||||
)
|
|
||||||
graph_bar += block_style[remainder_block] if remainder_block > 0 else ''
|
|
||||||
graph_bar += block_style[0] * (gr_len - len(graph_bar))
|
graph_bar += block_style[0] * (gr_len - len(graph_bar))
|
||||||
|
|
||||||
logger.debug(f'"{lg_nm or "..."}" graph generated')
|
logger.debug(f"'{lg_nm or '...'}' graph generated")
|
||||||
return graph_bar
|
return graph_bar
|
||||||
|
|
||||||
|
|
||||||
def prep_content(stats: dict[str, Any], language_count: int = 5, /) -> str:
|
def prep_content(stats: dict[str, Any], language_count: int = 5, stop_at_other: bool = False, /):
|
||||||
"""
|
"""WakaReadme Prepare Markdown.
|
||||||
WakaReadme Prepare Markdown
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
Prepared markdown content from the fetched statistics
|
Prepared markdown content from the fetched statistics.
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
logger.debug('Making contents')
|
logger.debug("Making contents")
|
||||||
contents = ''
|
contents = ""
|
||||||
|
|
||||||
# make title
|
# make title
|
||||||
if wk_i.show_title:
|
if wk_i.show_title:
|
||||||
contents += make_title(stats.get('start'), stats.get('end')) + '\n\n'
|
contents += make_title(stats.get("start"), stats.get("end")) + "\n\n"
|
||||||
|
|
||||||
# make byline
|
# make byline
|
||||||
if wk_i.show_masked_time and (
|
if wk_i.show_masked_time and (
|
||||||
total_time := stats.get('human_readable_total_including_other_language')
|
total_time := stats.get("human_readable_total_including_other_language")
|
||||||
):
|
):
|
||||||
# overrides 'human_readable_total'
|
# overrides "human_readable_total"
|
||||||
contents += f'Total Time: {total_time}\n\n'
|
contents += f"Total Time: {total_time}\n\n"
|
||||||
elif wk_i.show_total_time and (
|
elif wk_i.show_total_time and (total_time := stats.get("human_readable_total")):
|
||||||
total_time := stats.get('human_readable_total')
|
contents += f"Total Time: {total_time}\n\n"
|
||||||
):
|
|
||||||
contents += f'Total Time: {total_time}\n\n'
|
|
||||||
|
|
||||||
lang_info: list[dict[str, int | float | str]] | None = []
|
lang_info: list[dict[str, int | float | str]] | None = []
|
||||||
|
|
||||||
# Check if any language data exists
|
# Check if any language data exists
|
||||||
if not (lang_info := stats.get('languages')):
|
if not (lang_info := stats.get("languages")):
|
||||||
logger.debug('The API data seems to be empty, please wait for a day')
|
logger.debug("The API data seems to be empty, please wait for a day")
|
||||||
contents += 'No activity tracked'
|
contents += "No activity tracked"
|
||||||
return contents
|
return contents.rstrip("\n")
|
||||||
|
|
||||||
# make lang content
|
# make lang content
|
||||||
pad_len = len(
|
pad_len = len(
|
||||||
# comment if it feels way computationally expensive
|
# comment if it feels way computationally expensive
|
||||||
max((str(lng['name']) for lng in lang_info), key=len)
|
max((str(lng["name"]) for lng in lang_info), key=len)
|
||||||
# and then don't for get to set pad_len to say 13 :)
|
# and then don't for get to set pad_len to say 13 :)
|
||||||
)
|
)
|
||||||
|
if language_count == 0 and not 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")
|
||||||
|
|
||||||
for idx, lang in enumerate(lang_info):
|
for idx, lang in enumerate(lang_info):
|
||||||
lang_name = str(lang['name'])
|
lang_name = str(lang["name"])
|
||||||
# >>> add languages to filter here <<<
|
# >>> add languages to filter here <<<
|
||||||
# if lang_name in {...}: continue
|
# if lang_name in {...}: continue
|
||||||
lang_time = str(lang['text']) if wk_i.show_time else ''
|
lang_time = str(lang["text"]) if wk_i.show_time else ""
|
||||||
lang_ratio = float(lang['percent'])
|
lang_ratio = float(lang["percent"])
|
||||||
lang_bar = make_graph(
|
lang_bar = make_graph(wk_i.block_style, lang_ratio, wk_i.graph_length, lang_name)
|
||||||
wk_i.block_style, lang_ratio, wk_i.graph_length, lang_name
|
|
||||||
)
|
|
||||||
contents += (
|
contents += (
|
||||||
f'{lang_name.ljust(pad_len)} ' +
|
f"{lang_name.ljust(pad_len)} "
|
||||||
f'{lang_time: <16}{lang_bar} ' +
|
+ f"{lang_time: <16}{lang_bar} "
|
||||||
f'{lang_ratio:.2f}'.zfill(5) + ' %\n'
|
+ f"{lang_ratio:.2f}".zfill(5)
|
||||||
|
+ " %\n"
|
||||||
)
|
)
|
||||||
if idx >= language_count or lang_name == 'Other':
|
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
|
break
|
||||||
|
|
||||||
logger.debug('Contents were made\n')
|
logger.debug("Contents were made\n")
|
||||||
return contents.rstrip('\n')
|
return contents.rstrip("\n")
|
||||||
|
|
||||||
|
|
||||||
def fetch_stats() -> dict[str, Any] | None:
|
def fetch_stats():
|
||||||
"""
|
"""WakaReadme Fetch Stats.
|
||||||
WakaReadme Fetch Stats
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
Returns statistics as JSON string
|
Returns statistics as JSON string.
|
||||||
"""
|
"""
|
||||||
attempts = 4
|
attempts = 4
|
||||||
statistic: dict[str, dict[str, Any]] = {}
|
statistic: dict[str, dict[str, Any]] = {}
|
||||||
encoded_key = str(
|
encoded_key = str(b64encode(bytes(str(wk_i.waka_key), "utf-8")), "utf-8")
|
||||||
b64encode(bytes(str(wk_i.waka_key), 'utf-8')), 'utf-8'
|
logger.debug(f"Pulling WakaTime stats from {' '.join(wk_i.time_range.split('_'))}")
|
||||||
)
|
|
||||||
logger.debug(
|
|
||||||
f'Pulling WakaTime stats from {" ".join(wk_i.time_range.split("_"))}'
|
|
||||||
)
|
|
||||||
while attempts > 0:
|
while attempts > 0:
|
||||||
resp_message, fake_ua = '', cryptogenic.choice(
|
resp_message, fake_ua = "", cryptogenic.choice([str(fake.user_agent()) for _ in range(5)])
|
||||||
[str(fake.user_agent()) for _ in range(5)]
|
|
||||||
)
|
|
||||||
# making a request
|
# making a request
|
||||||
if (resp := rq_get(
|
if (
|
||||||
url=f'{str(wk_i.api_base_url).rstrip("/")}/v1/users/current/stats/{wk_i.time_range}',
|
resp := rq_get(
|
||||||
headers={
|
url=f"{str(wk_i.api_base_url).rstrip('/')}/v1/users/current/stats/{wk_i.time_range}",
|
||||||
'Authorization': f'Basic {encoded_key}',
|
headers={
|
||||||
'User-Agent': fake_ua,
|
"Authorization": f"Basic {encoded_key}",
|
||||||
},
|
"User-Agent": fake_ua,
|
||||||
timeout=30 * (5 - attempts)
|
},
|
||||||
)).status_code != 200:
|
timeout=(30.0 * (5 - attempts)),
|
||||||
resp_message += f' • {conn_info}' if (
|
)
|
||||||
conn_info := resp.json().get('message')
|
).status_code != 200:
|
||||||
) else ''
|
resp_message += f" • {conn_info}" if (conn_info := resp.json().get("message")) else ""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f'API response #{5 - attempts}: {resp.status_code} • {resp.reason}{resp_message}'
|
f"API response #{5 - attempts}: {resp.status_code} •" + f" {resp.reason}{resp_message}"
|
||||||
)
|
)
|
||||||
if resp.status_code == 200 and (statistic := resp.json()):
|
if resp.status_code == 200 and (statistic := resp.json()):
|
||||||
logger.debug('Fetched WakaTime statistics')
|
logger.debug("Fetched WakaTime statistics")
|
||||||
break
|
break
|
||||||
logger.debug(f'Retrying in {30 * (5 - attempts )}s ...')
|
logger.debug(f"Retrying in {30 * (5 - attempts )}s ...")
|
||||||
sleep(30 * (5 - attempts))
|
sleep(30 * (5 - attempts))
|
||||||
attempts -= 1
|
attempts -= 1
|
||||||
|
|
||||||
if err := (statistic.get('error') or statistic.get('errors')):
|
if err := (statistic.get("error") or statistic.get("errors")):
|
||||||
logger.error(f'{err}\n')
|
logger.error(f"{err}\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
print()
|
print()
|
||||||
return statistic.get('data')
|
return statistic.get("data")
|
||||||
|
|
||||||
|
|
||||||
def churn(old_readme: str, /) -> str | None:
|
def churn(old_readme: str, /):
|
||||||
|
"""WakaReadme Churn.
|
||||||
|
|
||||||
|
Composes WakaTime stats within markdown code snippet.
|
||||||
"""
|
"""
|
||||||
WakaReadme Churn
|
# check if placeholder pattern exists in readme
|
||||||
----------------
|
if not re.findall(wk_i.waka_block_pattern, old_readme):
|
||||||
|
logger.warning(f"Can't find `{wk_i.waka_block_pattern}` pattern in readme")
|
||||||
Composes WakaTime stats within markdown code snippet
|
return None
|
||||||
"""
|
# getting contents
|
||||||
# getting content
|
|
||||||
if not (waka_stats := fetch_stats()):
|
if not (waka_stats := fetch_stats()):
|
||||||
logger.error('Unable to fetch data, please rerun workflow\n')
|
logger.error("Unable to fetch data, please rerun workflow\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
# processing content
|
# preparing contents
|
||||||
try:
|
try:
|
||||||
generated_content = prep_content(waka_stats, int(wk_i.language_count))
|
generated_content = prep_content(
|
||||||
|
waka_stats, int(wk_i.language_count), bool(wk_i.stop_at_other)
|
||||||
|
)
|
||||||
except (AttributeError, KeyError, ValueError) as err:
|
except (AttributeError, KeyError, ValueError) as err:
|
||||||
logger.error(f'Unable to read API data | {err}\n')
|
logger.error(f"Unable to read API data | {err}\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
print(generated_content, '\n', sep='')
|
print(generated_content, "\n", sep="")
|
||||||
|
# substituting old contents
|
||||||
new_readme = re.sub(
|
new_readme = re.sub(
|
||||||
pattern=wk_i.waka_block_pattern,
|
pattern=wk_i.waka_block_pattern,
|
||||||
repl=f'{wk_i.start_comment}\n\n```text\n{generated_content}\n```\n\n{wk_i.end_comment}',
|
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
|
string=old_readme,
|
||||||
)
|
)
|
||||||
if len(sys.argv) == 2 and sys.argv[1] == '--dev':
|
if len(sys.argv) == 2 and sys.argv[1] == "--dev":
|
||||||
logger.debug('Detected run in `dev` mode.')
|
logger.debug("Detected run in `dev` mode.")
|
||||||
# to avoid accidentally writing back to Github
|
# to avoid accidentally writing back to Github
|
||||||
# when developing and testing WakaReadme
|
# when developing and testing WakaReadme
|
||||||
return None
|
return None
|
||||||
@@ -400,43 +395,43 @@ def churn(old_readme: str, /) -> str | None:
|
|||||||
return None if new_readme == old_readme else new_readme
|
return None if new_readme == old_readme else new_readme
|
||||||
|
|
||||||
|
|
||||||
def genesis() -> None:
|
def genesis():
|
||||||
"""Run Program"""
|
"""Run Program."""
|
||||||
logger.debug('Connecting to GitHub')
|
logger.debug("Connecting to GitHub")
|
||||||
gh_connect = Github(wk_i.gh_token)
|
gh_connect = Github(wk_i.gh_token)
|
||||||
# since a validator is being used casting to string here is okay
|
# since a validator is being used casting to string here is okay
|
||||||
gh_repo = gh_connect.get_repo(str(wk_i.repository))
|
gh_repo = gh_connect.get_repo(str(wk_i.repository))
|
||||||
readme_file = gh_repo.get_readme()
|
readme_file = gh_repo.get_readme()
|
||||||
logger.debug('Decoding readme contents\n')
|
logger.debug("Decoding readme contents\n")
|
||||||
readme_contents = str(readme_file.decoded_content, encoding='utf-8')
|
readme_contents = str(readme_file.decoded_content, encoding="utf-8")
|
||||||
if new_content := churn(readme_contents):
|
if new_content := churn(readme_contents):
|
||||||
logger.debug('WakaReadme stats has changed')
|
logger.debug("WakaReadme stats has changed")
|
||||||
gh_repo.update_file(
|
gh_repo.update_file(
|
||||||
path=readme_file.path,
|
path=readme_file.path,
|
||||||
message=wk_i.commit_message,
|
message=wk_i.commit_message,
|
||||||
content=new_content,
|
content=new_content,
|
||||||
sha=readme_file.sha
|
sha=readme_file.sha,
|
||||||
)
|
)
|
||||||
logger.info('Stats updated successfully')
|
logger.info("Stats updated successfully")
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info('WakaReadme was not updated')
|
logger.info("WakaReadme was not updated")
|
||||||
|
|
||||||
|
|
||||||
################### driver ###################
|
################### driver ###################
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
# faker data preparation
|
# faker data preparation
|
||||||
fake = Faker()
|
fake = Faker()
|
||||||
Faker.seed(0)
|
Faker.seed(0)
|
||||||
cryptogenic = SystemRandom()
|
cryptogenic = SystemRandom()
|
||||||
|
|
||||||
# initial waka-readme setup
|
# initial waka-readme setup
|
||||||
logger.debug('Initialize WakaReadme')
|
logger.debug("Initialize WakaReadme")
|
||||||
wk_i = WakaInput()
|
wk_i = WakaInput()
|
||||||
if not wk_i.validate_input():
|
if not wk_i.validate_input():
|
||||||
logger.error('Environment variables are misconfigured\n')
|
logger.error("Environment variables are misconfigured\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# run
|
# run
|
||||||
@@ -444,9 +439,9 @@ if __name__ == '__main__':
|
|||||||
genesis()
|
genesis()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print()
|
print()
|
||||||
logger.error('Interrupt signal received\n')
|
logger.error("Interrupt signal received\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
except (GithubException, RequestException) as rq_exp:
|
except (GithubException, RequestException) as rq_exp:
|
||||||
logger.critical(f'{rq_exp}\n')
|
logger.critical(f"{rq_exp}\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
print('\nThanks for using WakaReadme!\n')
|
print("\nThanks for using WakaReadme!\n")
|
||||||
|
|||||||
848
poetry.lock
generated
848
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
4
poetry.toml
Normal file
4
poetry.toml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[virtualenvs]
|
||||||
|
prefer-active-python = true
|
||||||
|
in-project = true
|
||||||
|
path = ".venv"
|
||||||
@@ -1,25 +1,68 @@
|
|||||||
|
####################
|
||||||
|
# Metadata #
|
||||||
|
####################
|
||||||
|
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "waka-readme"
|
name = "waka-readme"
|
||||||
version = "0.1.7"
|
version = "0.2.1"
|
||||||
description = "Wakatime Weekly Metrics on your Profile Readme"
|
description = "Wakatime Weekly Metrics on your Profile Readme."
|
||||||
authors = ["Athul Cyriac Ajay <athul8720@gmail.com>"]
|
authors = ["Athul Cyriac Ajay <athul8720@gmail.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
#packages = [{include = "waka_readme"}]
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# Dependencies #
|
||||||
|
####################
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.11"
|
python = "^3.11"
|
||||||
faker = "^16.6.1"
|
faker = "^18.10.1"
|
||||||
requests = "^2.28.2"
|
pygithub = "^1.58.2"
|
||||||
pygithub = "^1.57"
|
requests = "^2.31.0"
|
||||||
|
|
||||||
|
[tool.poetry.group.dev]
|
||||||
|
optional = true
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
autopep8 = "^2.0.1"
|
loguru = "^0.7.0"
|
||||||
pylint = "^2.16.1"
|
python-dotenv = "^1.0.0"
|
||||||
python-dotenv = "^0.21.1"
|
|
||||||
loguru = "^0.6.0"
|
[tool.poetry.group.tooling]
|
||||||
bandit = "^1.7.4"
|
optional = true
|
||||||
|
|
||||||
|
[tool.poetry.group.tooling.dependencies]
|
||||||
|
bandit = "^1.7.5"
|
||||||
|
black = "^23.3.0"
|
||||||
|
ruff = "^0.0.272"
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# Build System #
|
||||||
|
####################
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# Configurations #
|
||||||
|
####################
|
||||||
|
|
||||||
|
[tool.black]
|
||||||
|
line-length = 100
|
||||||
|
target-version = ["py311"]
|
||||||
|
|
||||||
|
[tool.bandit]
|
||||||
|
exclude_dirs = [".github", ".pytest_cache", ".tox", ".vscode", "site", "tests"]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 100
|
||||||
|
|
||||||
|
[tool.ruff.pydocstyle]
|
||||||
|
convention = "google"
|
||||||
|
|
||||||
|
[tool.ruff.isort]
|
||||||
|
force-sort-within-sections = true
|
||||||
|
relative-imports-order = "closest-to-furthest"
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
"""
|
"""Unit Tests."""
|
||||||
Tests for the main.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
# standard
|
# standard
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from dataclasses import dataclass # , field
|
from dataclasses import dataclass # , field
|
||||||
from itertools import product
|
from itertools import product
|
||||||
# from pathlib import Path
|
|
||||||
# from inspect import cleandoc
|
|
||||||
# from typing import Any
|
|
||||||
# from json import load
|
|
||||||
import unittest
|
import unittest
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
# from pathlib import Path
|
||||||
|
# from inspect import cleandoc
|
||||||
|
# from typing import Any
|
||||||
|
# from json import load
|
||||||
|
|
||||||
try:
|
try:
|
||||||
prime = import_module('main')
|
prime = import_module("main")
|
||||||
# works when running as
|
# works when running as
|
||||||
# python -m unittest discover
|
# python -m unittest discover
|
||||||
except ImportError as err:
|
except ImportError as err:
|
||||||
@@ -25,7 +24,8 @@ except ImportError as err:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TestData:
|
class TestData:
|
||||||
"""Test Data"""
|
"""Test Data."""
|
||||||
|
|
||||||
# for future tests
|
# for future tests
|
||||||
# waka_json: dict[str, dict[str, Any]] = field(
|
# waka_json: dict[str, dict[str, Any]] = field(
|
||||||
# default_factory=lambda: {}
|
# default_factory=lambda: {}
|
||||||
@@ -33,66 +33,66 @@ class TestData:
|
|||||||
bar_percent: tuple[int | float, ...] | None = None
|
bar_percent: tuple[int | float, ...] | None = None
|
||||||
graph_blocks: tuple[str, ...] | None = None
|
graph_blocks: tuple[str, ...] | None = None
|
||||||
waka_graphs: tuple[list[str], ...] | None = None
|
waka_graphs: tuple[list[str], ...] | None = None
|
||||||
dummy_readme: str = ''
|
dummy_readme: str = ""
|
||||||
|
|
||||||
def populate(self) -> None:
|
def populate(self) -> None:
|
||||||
"""Populate Test Data"""
|
"""Populate Test Data."""
|
||||||
# for future tests
|
# for future tests
|
||||||
# with open(
|
# with open(
|
||||||
# file=Path(__file__).parent / 'sample_data.json',
|
# file=Path(__file__).parent / "sample_data.json",
|
||||||
# encoding='utf-8',
|
# encoding="utf-8",
|
||||||
# mode='rt',
|
# mode="rt",
|
||||||
# ) as wkf:
|
# ) as wkf:
|
||||||
# self.waka_json = load(wkf)
|
# self.waka_json = load(wkf)
|
||||||
|
|
||||||
self.bar_percent = (
|
self.bar_percent = (0, 100, 49.999, 50, 25, 75, 3.14, 9.901, 87.334, 87.333, 4.666, 4.667)
|
||||||
0, 100, 49.999, 50, 25, 75, 3.14, 9.901, 87.334, 87.333, 4.666, 4.667
|
|
||||||
)
|
|
||||||
|
|
||||||
self.graph_blocks = ("░▒▓█", "⚪⚫", "⓪①②③④⑤⑥⑦⑧⑨⑩")
|
self.graph_blocks = ("░▒▓█", "⚪⚫", "⓪①②③④⑤⑥⑦⑧⑨⑩")
|
||||||
|
|
||||||
self.waka_graphs = ([
|
self.waka_graphs = (
|
||||||
"░░░░░░░░░░░░░░░░░░░░░░░░░",
|
|
||||||
"█████████████████████████",
|
|
||||||
"████████████▒░░░░░░░░░░░░",
|
|
||||||
"████████████▓░░░░░░░░░░░░",
|
|
||||||
"██████▒░░░░░░░░░░░░░░░░░░",
|
|
||||||
"██████████████████▓░░░░░░",
|
|
||||||
"▓░░░░░░░░░░░░░░░░░░░░░░░░",
|
|
||||||
"██▒░░░░░░░░░░░░░░░░░░░░░░",
|
|
||||||
"██████████████████████░░░",
|
|
||||||
"█████████████████████▓░░░",
|
|
||||||
"█░░░░░░░░░░░░░░░░░░░░░░░░",
|
|
||||||
"█▒░░░░░░░░░░░░░░░░░░░░░░░"
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
"⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
"░░░░░░░░░░░░░░░░░░░░░░░░░",
|
||||||
"⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫",
|
"█████████████████████████",
|
||||||
"⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
"████████████▒░░░░░░░░░░░░",
|
||||||
"⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
"████████████▓░░░░░░░░░░░░",
|
||||||
"⚫⚫⚫⚫⚫⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
"██████▒░░░░░░░░░░░░░░░░░░",
|
||||||
"⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚪⚪⚪⚪⚪⚪",
|
"██████████████████▓░░░░░░",
|
||||||
"⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
"▓░░░░░░░░░░░░░░░░░░░░░░░░",
|
||||||
"⚫⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
"██▒░░░░░░░░░░░░░░░░░░░░░░",
|
||||||
"⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚪⚪⚪",
|
"██████████████████████░░░",
|
||||||
"⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚪⚪⚪",
|
"█████████████████████▓░░░",
|
||||||
"⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
"█░░░░░░░░░░░░░░░░░░░░░░░░",
|
||||||
"⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪"
|
"█▒░░░░░░░░░░░░░░░░░░░░░░░",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
"⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
||||||
"⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩",
|
"⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫",
|
||||||
"⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑤⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
"⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
||||||
"⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑤⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
"⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
||||||
"⑩⑩⑩⑩⑩⑩③⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
"⚫⚫⚫⚫⚫⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
||||||
"⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑧⓪⓪⓪⓪⓪⓪",
|
"⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚪⚪⚪⚪⚪⚪",
|
||||||
"⑧⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
"⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
||||||
"⑩⑩⑤⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
"⚫⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
||||||
"⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑧⓪⓪⓪",
|
"⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚪⚪⚪",
|
||||||
"⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑧⓪⓪⓪",
|
"⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚫⚪⚪⚪",
|
||||||
"⑩②⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
"⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
||||||
"⑩②⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪"
|
"⚫⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪",
|
||||||
])
|
],
|
||||||
|
[
|
||||||
|
"⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
||||||
|
"⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩",
|
||||||
|
"⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑤⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
||||||
|
"⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑤⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
||||||
|
"⑩⑩⑩⑩⑩⑩③⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
||||||
|
"⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑧⓪⓪⓪⓪⓪⓪",
|
||||||
|
"⑧⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
||||||
|
"⑩⑩⑤⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
||||||
|
"⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑧⓪⓪⓪",
|
||||||
|
"⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑩⑧⓪⓪⓪",
|
||||||
|
"⑩②⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
||||||
|
"⑩②⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
# self.dummy_readme = cleandoc("""
|
# self.dummy_readme = cleandoc("""
|
||||||
# My Test Readme Start
|
# My Test Readme Start
|
||||||
@@ -103,47 +103,43 @@ class TestData:
|
|||||||
|
|
||||||
|
|
||||||
class TestMain(unittest.TestCase):
|
class TestMain(unittest.TestCase):
|
||||||
"""Testing Main Module"""
|
"""Testing Main Module."""
|
||||||
|
|
||||||
def test_make_graph(self) -> None:
|
def test_make_graph(self) -> None:
|
||||||
"""Test graph maker"""
|
"""Test graph maker."""
|
||||||
if not tds.graph_blocks or not tds.waka_graphs or not tds.bar_percent:
|
if not tds.graph_blocks or not tds.waka_graphs or not tds.bar_percent:
|
||||||
raise AssertionError('Data population failed')
|
raise AssertionError("Data population failed")
|
||||||
|
|
||||||
for (idx, grb), (jdy, bpc) in product(
|
for (idx, grb), (jdy, bpc) in product(
|
||||||
enumerate(tds.graph_blocks), enumerate(tds.bar_percent)
|
enumerate(tds.graph_blocks), enumerate(tds.bar_percent)
|
||||||
):
|
):
|
||||||
self.assertEqual(
|
self.assertEqual(prime.make_graph(grb, bpc, 25), tds.waka_graphs[idx][jdy])
|
||||||
prime.make_graph(grb, bpc, 25),
|
|
||||||
tds.waka_graphs[idx][jdy]
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_make_title(self) -> None:
|
def test_make_title(self) -> None:
|
||||||
"""Test title maker"""
|
"""Test title maker."""
|
||||||
self.assertRegex(
|
self.assertRegex(
|
||||||
prime.make_title('2022-01-11T23:18:19Z', '2021-12-09T10:22:06Z'),
|
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}'
|
r"From: \d{2} \w{3,9} \d{4} - To: \d{2} \w{3,9} \d{4}",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_strtobool(self) -> None:
|
def test_strtobool(self) -> None:
|
||||||
"""Test string to bool"""
|
"""Test string to bool."""
|
||||||
self.assertTrue(prime.strtobool('Yes'))
|
self.assertTrue(prime.strtobool("Yes"))
|
||||||
self.assertFalse(prime.strtobool('nO'))
|
self.assertFalse(prime.strtobool("nO"))
|
||||||
self.assertTrue(prime.strtobool(True))
|
self.assertTrue(prime.strtobool(True))
|
||||||
self.assertRaises(AttributeError, prime.strtobool, None)
|
self.assertRaises(AttributeError, prime.strtobool, None)
|
||||||
self.assertRaises(ValueError, prime.strtobool, 'yo!')
|
self.assertRaises(ValueError, prime.strtobool, "yo!")
|
||||||
self.assertRaises(AttributeError, prime.strtobool, 20.5)
|
self.assertRaises(AttributeError, prime.strtobool, 20.5)
|
||||||
|
|
||||||
|
|
||||||
tds = TestData()
|
tds = TestData()
|
||||||
tds.populate()
|
tds.populate()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
sys.path.insert(0, os.path.abspath(
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||||
os.path.join(os.path.dirname(__file__), '..')
|
|
||||||
))
|
|
||||||
import main as prime
|
import main as prime
|
||||||
|
|
||||||
# works when running as
|
# works when running as
|
||||||
# python tests/test_main.py
|
# python tests/test_main.py
|
||||||
except ImportError as im_er:
|
except ImportError as im_er:
|
||||||
|
|||||||
Reference in New Issue
Block a user