16 Commits

Author SHA1 Message Date
Jovial Joe Jayarson
c34fb893a6 Merge pull request #131 from joe733/workshop
maint: monthly updates for June '23
2023-06-14 17:30:31 +05:30
Jovial Joe Jayarson
1fc26a4121 feat: update dependencies; bump version 2023-06-14 14:13:38 +05:30
Jovial Joe Jayarson
82c9408d6d maint: follows google pydocstyle 2023-06-13 19:40:52 +05:30
Jovial Joe Jayarson
9bee9ba11b maint: migrate to black formatting 2023-06-13 19:17:26 +05:30
Jovial Joe Jayarson
b42a071671 Merge pull request #129 from athul/dependabot/pip/cryptography-41.0.0
chore(deps): bump cryptography from 40.0.2 to 41.0.0
2023-06-03 05:39:40 +05:30
dependabot[bot]
e8541dbe4e chore(deps): bump cryptography from 40.0.2 to 41.0.0
Bumps [cryptography](https://github.com/pyca/cryptography) from 40.0.2 to 41.0.0.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/40.0.2...41.0.0)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-02 20:31:09 +00:00
Jisan
da0f4f1847 feat: Add language syntax support for color code (#128)
- feat: text highlight
- fix: wrong env, typo
2023-06-02 11:17:41 +05:30
Jovial Joe Jayarson
d26ed33e7a Merge pull request #124 from athul/dependabot/pip/requests-2.31.0
chore(deps): bump requests from 2.29.0 to 2.31.0
2023-05-23 12:31:51 +05:30
dependabot[bot]
a50019231d chore(deps): bump requests from 2.29.0 to 2.31.0
Bumps [requests](https://github.com/psf/requests) from 2.29.0 to 2.31.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.29.0...v2.31.0)

---
updated-dependencies:
- dependency-name: requests
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-23 06:51:25 +00:00
Jovial Joe Jayarson
080a8c9b63 Merge pull request #122 from joe733/workshop
fix: adds granularity to show more languages
2023-05-05 16:07:45 +05:30
Jovial Joe Jayarson
fbc9196645 fix: adds granularity to show more languages
- adds option `STOP_AT_OTHER` to stop retrieval at lang marked `Other`
- updates dependencies
- makes dev dependencies optional
- bumps project version
- updates readme

**Related Items**

*Issues*

- Closes #121
2023-05-04 17:15:14 +05:30
Jovial Joe Jayarson
59f35b046b Merge pull request #120 from joe733/workshop
feat: bumped version to 0.1.9
2023-03-25 12:52:13 +05:30
Jovial Joe Jayarson
60fa45f3f0 feat: bumped version to 0.1.9
- updates dependencies
- prefer type inference with function return
- fix typos / minor improvements
- ignore all ``*.env` files
- bumped package version to 0.1.9
2023-03-25 10:05:49 +05:30
Jovial Joe Jayarson
60fe3b9f48 Merge pull request #119 from joe733/workshop
fix: adds check to find placeholder in old readme
2023-02-28 12:36:09 +05:30
Jovial Joe Jayarson
29dba6dd79 fix: adds check to find placeholder in old readme 2023-02-27 12:36:25 +05:30
Jovial Joe Jayarson
ac0bb21462 feat: bump version, update deps 2023-02-09 07:25:07 +05:30
11 changed files with 789 additions and 705 deletions

6
.gitignore vendored
View File

@@ -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

View File

@@ -1,6 +1,6 @@
# Contributing # Contributing
![python_ver](https://img.shields.io/badge/python-%5E3.10-blue.svg) ![python_ver](https://img.shields.io/badge/Python-%5E3.11-blue.svg)
> 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
``` ```

View File

@@ -4,7 +4,7 @@
</center> </center>
# Dev Metrics in Readme [![Unit Tests](https://github.com/athul/waka-readme/actions/workflows/testing.yml/badge.svg?branch=master)](https://github.com/athul/waka-readme/actions/workflows/testing.yml) ![Python Version](https://img.shields.io/badge/python-v3.11-blue) # Dev Metrics in Readme [![Unit Tests](https://github.com/athul/waka-readme/actions/workflows/testing.yml/badge.svg?branch=master)](https://github.com/athul/waka-readme/actions/workflows/testing.yml) ![Python Version](https://img.shields.io/badge/Python-^3.11-blue)
[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

View File

@@ -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"

View File

@@ -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

View File

@@ -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
View File

@@ -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

File diff suppressed because it is too large Load Diff

4
poetry.toml Normal file
View File

@@ -0,0 +1,4 @@
[virtualenvs]
prefer-active-python = true
in-project = true
path = ".venv"

View File

@@ -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"

View File

@@ -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: