Skip to content

Commit da46f72

Browse files
committed
Update behavior of recent-xp stats type ⬆
Minor refactoring making addition of stats types easier More consistent formats for types Closes #2
1 parent 8dc3276 commit da46f72

File tree

2 files changed

+103
-81
lines changed

2 files changed

+103
-81
lines changed

README.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,25 +34,28 @@
3434

3535
- **STATS_TYPE:** (Optional) Type of stats, supported values:
3636

37-
- `level-xp`: (Default) Show total and language XPs with level:
37+
- `level-xp`: (Default) Shows total and language XPs with level, sorted by XP:
3838

3939
```none
40-
Total XP :::::::::::::::::::::: lvl 25 [1,020,821 XP]
41-
Java :::::::::::::::::::::::::: lvl 17 [ 512,366 XP]
40+
💻 My Code::Stats XP (Top Languages)
41+
Total XP :::::::::::::::::::::: lvl 26 (1,104,152 XP)
42+
Java :::::::::::::::::::::::::: lvl 19 ( 580,523 XP)
4243
```
4344
44-
- `recent-xp`: Shows the past week in total as well as language wise recent XP:
45+
- `recent-xp`: Shows total and language XPs with level and recent xp, sorted by recent XP:
4546
4647
```none
47-
Total XP :::::::::::::::: 998,967 + 21,854 (past week)
48-
Java :::::::::::::::::::::::::: 511,969 + 397 (new xp)
48+
💻 My Code::Stats XP (Recent Languages)
49+
Total XP ::::::::::::: lvl 26 (1,104,152 XP) (+1,874)
50+
Python ::::::::::::::: lvl 7 ( 82,719 XP) (+1,789)
4951
```
5052
51-
- `xp`: Skips the recent stats (the parts after the ` +`) and shows just the aggregate total and language XPs:
53+
- `xp`: Shows total and language XPs, sorted by XP:
5254
5355
```none
54-
Total XP ::::::::::::::::::::::::::::::::::: 1,020,821
55-
Java ::::::::::::::::::::::::::::::::::::::::: 512,366
56+
💻 My Code::Stats XP (Top Languages)
57+
Total XP :::::::::::::::::::::::::::::::: 1,104,152 XP
58+
Java :::::::::::::::::::::::::::::::::::: 580,523 XP
5659
```
5760
5861
(This can also be put directly in `.github/workflows/codestats.yml`)

codestats_box.py

Lines changed: 91 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,48 @@
1-
from collections import namedtuple
21
import datetime
32
import math
43
import os
54
import sys
6-
from typing import Any, Dict, List
7-
from github.InputFileContent import InputFileContent
5+
from collections import namedtuple
6+
from itertools import takewhile
7+
from typing import Any, Callable, Dict, List
88

99
import requests
1010
from github import Github
11+
from github.InputFileContent import InputFileContent
12+
13+
TitleAndValue = namedtuple("TitleAndValue", "title value")
14+
15+
STATS_TYPE_LEVEL = "level-xp"
16+
STATS_TYPE_RECENT_XP = "recent-xp"
17+
STATS_TYPE_XP = "xp"
18+
ALLOWED_STATS_TYPES = [
19+
STATS_TYPE_RECENT_XP,
20+
STATS_TYPE_XP,
21+
STATS_TYPE_LEVEL,
22+
]
23+
DEFAULT_STATS_TYPE = STATS_TYPE_LEVEL
1124

1225
TOP_LANGUAGES_COUNT = 10
26+
MAX_LINE_LENGTH = 54
1327
WIDTH_JUSTIFICATION_SEPARATOR = ":"
1428
RECENT_STATS_SEPARATOR = " + "
1529
TOTAL_XP_TITLE = "Total XP"
16-
PAST_WEEK_SUFFIX_STRING = " (past week)"
17-
NEW_XP_SUFFIX_STRING = " (new xp)"
18-
LEVEL_STRING_FORMAT = "lvl {level:>3} [{xp:>9,} XP]"
19-
GIST_TITLE = "💻 My Code::Stats XP"
20-
MAX_LINE_LENGTH = 54
30+
VALUE_FORMAT = {
31+
STATS_TYPE_LEVEL: "lvl {level:>3} ({xp:>9,} XP)",
32+
STATS_TYPE_RECENT_XP: "lvl {level:>3} ({xp:>9,} XP) (+{recent_xp:>5,})",
33+
STATS_TYPE_XP: "{xp:>9,} XP",
34+
}
35+
GIST_TITLE = {
36+
STATS_TYPE_LEVEL: "💻 My Code::Stats XP (Top Languages)",
37+
STATS_TYPE_RECENT_XP: "💻 My Code::Stats XP (Recent Languages)",
38+
STATS_TYPE_XP: "💻 My Code::Stats XP (Top Languages)",
39+
}
40+
NO_RECENT_XP_LINES = [
41+
TitleAndValue("Not been coding recently", "🙈"),
42+
TitleAndValue("Probably busy with something else", "🗓"),
43+
TitleAndValue("Or just taking a break", "🌴"),
44+
TitleAndValue("But would be back to it soon!", "🤓"),
45+
]
2146

2247
ENV_VAR_GIST_ID = "GIST_ID"
2348
ENV_VAR_GITHUB_TOKEN = "GH_TOKEN"
@@ -29,27 +54,16 @@
2954
ENV_VAR_CODE_STATS_USERNAME,
3055
]
3156

32-
STATS_TYPE_RECENT_XP = "recent-xp"
33-
STATS_TYPE_XP = "xp"
34-
STATS_TYPE_LEVEL = "level-xp"
35-
ALLOWED_STATS_TYPES = [
36-
STATS_TYPE_RECENT_XP,
37-
STATS_TYPE_XP,
38-
STATS_TYPE_LEVEL,
39-
]
40-
DEFAULT_STATS_TYPE = STATS_TYPE_LEVEL
41-
4257
CODE_STATS_URL_FORMAT = "https://codestats.net/api/users/{user}"
4358
CODE_STATS_DATE_KEY = "dates"
4459
CODE_STATS_TOTAL_XP_KEY = "total_xp"
60+
CODE_STATS_TOTAL_NEW_XP_KEY = "new_xp"
4561
CODE_STATS_LANGUAGES_KEY = "languages"
4662
CODE_STATS_LANGUAGES_XP_KEY = "xps"
4763
CODE_STATS_LANGUAGES_NEW_XP_KEY = "new_xps"
4864

4965
XP_TO_LEVEL = lambda xp: math.floor(0.025 * math.sqrt(xp))
5066

51-
TitleAndValue = namedtuple("TitleAndValue", "title value")
52-
5367

5468
def validate_and_init() -> bool:
5569
env_vars_absent = [
@@ -72,86 +86,89 @@ def validate_and_init() -> bool:
7286
return True
7387

7488

75-
def get_adjusted_line(title_and_value: TitleAndValue) -> str:
76-
separation = MAX_LINE_LENGTH - (
77-
len(title_and_value.title) + len(title_and_value.value) + 2
78-
)
79-
separator = f" {WIDTH_JUSTIFICATION_SEPARATOR * separation} "
80-
return title_and_value.title + separator + title_and_value.value
81-
82-
8389
def get_code_stats_response(user: str) -> Dict[str, Any]:
8490
return requests.get(CODE_STATS_URL_FORMAT.format(user=user)).json()
8591

8692

93+
def __get_formatted_value(
94+
xp: int, recent_xp_supplier: Callable[[], int], stats_type: str
95+
) -> str:
96+
value_format = VALUE_FORMAT[stats_type]
97+
if stats_type == STATS_TYPE_LEVEL:
98+
return value_format.format(level=XP_TO_LEVEL(xp), xp=xp)
99+
elif stats_type == STATS_TYPE_RECENT_XP:
100+
recent_xp = recent_xp_supplier()
101+
formatted_value = value_format.format(
102+
level=XP_TO_LEVEL(xp), xp=xp, recent_xp=recent_xp
103+
)
104+
return formatted_value if recent_xp > 0 else formatted_value[:-9]
105+
elif stats_type == STATS_TYPE_XP:
106+
return value_format.format(xp=xp)
107+
raise RuntimeError(f"Unknown stats type {stats_type}")
108+
109+
87110
def get_total_xp_line(
88111
code_stats_response: Dict[str, Any], stats_type: str
89112
) -> TitleAndValue:
90-
last_seven_days = [
91-
str(datetime.date.today() - datetime.timedelta(days=i)) for i in range(7)
92-
]
93-
last_seven_days_xp = sum(
94-
[
95-
code_stats_response[CODE_STATS_DATE_KEY][day]
96-
for day in last_seven_days
97-
if day in code_stats_response[CODE_STATS_DATE_KEY]
98-
]
99-
)
100113
total_xp = code_stats_response[CODE_STATS_TOTAL_XP_KEY]
101-
total_xp_value = ""
102-
if stats_type == STATS_TYPE_RECENT_XP:
103-
total_xp_value = f"{total_xp - last_seven_days_xp:,}" + (
104-
f"{RECENT_STATS_SEPARATOR}{last_seven_days_xp:,}{PAST_WEEK_SUFFIX_STRING}"
105-
if last_seven_days_xp > 0
106-
else ""
107-
)
108-
elif stats_type == STATS_TYPE_XP:
109-
total_xp_value = f"{total_xp:,}"
110-
elif stats_type == STATS_TYPE_LEVEL:
111-
total_xp_value = LEVEL_STRING_FORMAT.format(
112-
level=XP_TO_LEVEL(total_xp), xp=total_xp
113-
)
114-
return TitleAndValue(TOTAL_XP_TITLE, total_xp_value)
114+
recent_total_xp_supplier = lambda: code_stats_response[CODE_STATS_TOTAL_NEW_XP_KEY]
115+
formatted_value = __get_formatted_value(
116+
total_xp, recent_total_xp_supplier, stats_type
117+
)
118+
return TitleAndValue(TOTAL_XP_TITLE, formatted_value)
115119

116120

117121
def __get_language_xp_line(
118122
language: str, language_stats: Dict[str, int], stats_type: str
119123
) -> TitleAndValue:
120124
xp = language_stats[CODE_STATS_LANGUAGES_XP_KEY]
121-
recent_xp = language_stats[CODE_STATS_LANGUAGES_NEW_XP_KEY]
122-
language_xp_value = ""
123-
if stats_type == STATS_TYPE_RECENT_XP:
124-
language_xp_value = f"{xp - recent_xp:,}" + (
125-
f"{RECENT_STATS_SEPARATOR}{recent_xp:,}{NEW_XP_SUFFIX_STRING}"
126-
if recent_xp > 0
127-
else ""
128-
)
129-
elif stats_type == STATS_TYPE_XP:
130-
language_xp_value = f"{xp:,}"
131-
elif stats_type == STATS_TYPE_LEVEL:
132-
language_xp_value = LEVEL_STRING_FORMAT.format(level=XP_TO_LEVEL(xp), xp=xp)
133-
return TitleAndValue(language, language_xp_value)
125+
recent_xp_supplier = lambda: language_stats[CODE_STATS_LANGUAGES_NEW_XP_KEY]
126+
formatted_value = __get_formatted_value(xp, recent_xp_supplier, stats_type)
127+
return TitleAndValue(language, formatted_value)
134128

135129

136130
def get_language_xp_lines(
137131
code_stats_response: Dict[str, Any], stats_type: str
138132
) -> List[TitleAndValue]:
139-
top_languages = sorted(
140-
code_stats_response[CODE_STATS_LANGUAGES_KEY].items(),
141-
key=lambda t: t[1][CODE_STATS_LANGUAGES_XP_KEY],
142-
reverse=True,
143-
)[:TOP_LANGUAGES_COUNT]
133+
if stats_type == STATS_TYPE_RECENT_XP:
134+
# Only considering languages with recent xp
135+
top_languages = list(
136+
takewhile(
137+
lambda t: t[1][CODE_STATS_LANGUAGES_NEW_XP_KEY] > 0,
138+
sorted(
139+
code_stats_response[CODE_STATS_LANGUAGES_KEY].items(),
140+
key=lambda t: t[1][CODE_STATS_LANGUAGES_NEW_XP_KEY],
141+
reverse=True,
142+
)[:TOP_LANGUAGES_COUNT],
143+
),
144+
)
145+
if not top_languages:
146+
return NO_RECENT_XP_LINES
147+
else:
148+
top_languages = sorted(
149+
code_stats_response[CODE_STATS_LANGUAGES_KEY].items(),
150+
key=lambda t: t[1][CODE_STATS_LANGUAGES_XP_KEY],
151+
reverse=True,
152+
)[:TOP_LANGUAGES_COUNT]
144153
return [
145154
__get_language_xp_line(language, stats, stats_type)
146155
for language, stats in top_languages
147156
]
148157

149158

159+
def get_adjusted_line(title_and_value: TitleAndValue) -> str:
160+
separation = MAX_LINE_LENGTH - (
161+
len(title_and_value.title) + len(title_and_value.value) + 2
162+
)
163+
separator = f" {WIDTH_JUSTIFICATION_SEPARATOR * separation} "
164+
return title_and_value.title + separator + title_and_value.value
165+
166+
150167
def update_gist(title: str, content: str) -> bool:
151168
access_token = os.environ[ENV_VAR_GITHUB_TOKEN]
152169
gist_id = os.environ[ENV_VAR_GIST_ID]
153170
gist = Github(access_token).get_gist(gist_id)
154-
# Shouldn't necessarily work, keeping for case of single file made in hurry to get gist id.
171+
# Works only for single file. Should we clear all files and create new file?
155172
old_title = list(gist.files.keys())[0]
156173
gist.edit(title, {old_title: InputFileContent(content, title)})
157174
print(f"{title}\n{content}")
@@ -179,8 +196,10 @@ def main():
179196
"Validations failed! See the messages above for more information"
180197
)
181198

199+
stats_type = os.environ[ENV_VAR_STATS_TYPE]
200+
title = GIST_TITLE[stats_type]
182201
content = get_content()
183-
update_gist(GIST_TITLE, content)
202+
update_gist(title, content)
184203

185204

186205
if __name__ == "__main__":

0 commit comments

Comments
 (0)