Skip to content

Commit e30b861

Browse files
committed
Documentation! 📓
Update screenshot Add requirement of codestats account to instructions
1 parent da46f72 commit e30b861

File tree

3 files changed

+76
-29
lines changed

3 files changed

+76
-29
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
1414
## 🎒 Prep Work
1515

16+
0. Make sure you have a Code::Stats account! (Get one at https://codestats.net/ if you don't!)
1617
1. Create a new public GitHub Gist (https://gist.github.com/)
1718
2. Create a token with the `gist` scope and copy it. (https://github.com/settings/tokens/new)
1819
3. Copy the `API token`

art/codestats-box.png

1.49 KB
Loading

codestats_box.py

Lines changed: 75 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
"""
2+
Script to update a GitHub Gist with stats for a Code::Stats user.
3+
4+
Uses code stats api to fetch stats.
5+
Supports various stats types, all resulting in different content for the gist.
6+
Meant for Gists to be pinned on GitHub profile.
7+
8+
Test with
9+
python codestats_box.py test <codestats-user> <stats-type>
10+
to only print content. To also test gist update, use:
11+
python codestats_box.py test <codestats-user> <stats-type> <gist-id> <github-token>
12+
"""
13+
114
import datetime
215
import math
316
import os
@@ -10,8 +23,9 @@
1023
from github import Github
1124
from github.InputFileContent import InputFileContent
1225

13-
TitleAndValue = namedtuple("TitleAndValue", "title value")
26+
LabelAndValue = namedtuple("LabelAndValue", "title value")
1427

28+
# Type of stats
1529
STATS_TYPE_LEVEL = "level-xp"
1630
STATS_TYPE_RECENT_XP = "recent-xp"
1731
STATS_TYPE_XP = "xp"
@@ -21,29 +35,30 @@
2135
STATS_TYPE_LEVEL,
2236
]
2337
DEFAULT_STATS_TYPE = STATS_TYPE_LEVEL
24-
25-
TOP_LANGUAGES_COUNT = 10
26-
MAX_LINE_LENGTH = 54
27-
WIDTH_JUSTIFICATION_SEPARATOR = ":"
28-
RECENT_STATS_SEPARATOR = " + "
29-
TOTAL_XP_TITLE = "Total XP"
38+
# Dicts for stats type dependent values
3039
VALUE_FORMAT = {
3140
STATS_TYPE_LEVEL: "lvl {level:>3} ({xp:>9,} XP)",
32-
STATS_TYPE_RECENT_XP: "lvl {level:>3} ({xp:>9,} XP) (+{recent_xp:>5,})",
41+
STATS_TYPE_RECENT_XP: "lvl {level:>3} ({xp:>9,} XP) (+{recent_xp:>6,})",
3342
STATS_TYPE_XP: "{xp:>9,} XP",
3443
}
3544
GIST_TITLE = {
3645
STATS_TYPE_LEVEL: "💻 My Code::Stats XP (Top Languages)",
3746
STATS_TYPE_RECENT_XP: "💻 My Code::Stats XP (Recent Languages)",
3847
STATS_TYPE_XP: "💻 My Code::Stats XP (Top Languages)",
3948
}
49+
# Other configurable values
50+
TOP_LANGUAGES_COUNT = 10
51+
WIDTH_JUSTIFICATION_SEPARATOR = ":"
52+
RECENT_STATS_SEPARATOR = " + "
53+
TOTAL_XP_TITLE = "Total XP"
4054
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!", "🤓"),
55+
LabelAndValue("Not been coding recently", "🙈"),
56+
LabelAndValue("Probably busy with something else", "🗓"),
57+
LabelAndValue("Or just taking a break", "🌴"),
58+
LabelAndValue("But would be back to it soon!", "🤓"),
4559
]
46-
60+
# Internal constants
61+
MAX_LINE_LENGTH = 54
4762
ENV_VAR_GIST_ID = "GIST_ID"
4863
ENV_VAR_GITHUB_TOKEN = "GH_TOKEN"
4964
ENV_VAR_CODE_STATS_USERNAME = "CODE_STATS_USERNAME"
@@ -53,19 +68,19 @@
5368
ENV_VAR_GITHUB_TOKEN,
5469
ENV_VAR_CODE_STATS_USERNAME,
5570
]
56-
71+
# Code stats API constants
5772
CODE_STATS_URL_FORMAT = "https://codestats.net/api/users/{user}"
5873
CODE_STATS_DATE_KEY = "dates"
5974
CODE_STATS_TOTAL_XP_KEY = "total_xp"
6075
CODE_STATS_TOTAL_NEW_XP_KEY = "new_xp"
6176
CODE_STATS_LANGUAGES_KEY = "languages"
6277
CODE_STATS_LANGUAGES_XP_KEY = "xps"
6378
CODE_STATS_LANGUAGES_NEW_XP_KEY = "new_xps"
64-
6579
XP_TO_LEVEL = lambda xp: math.floor(0.025 * math.sqrt(xp))
6680

6781

6882
def validate_and_init() -> bool:
83+
"""Check environment variables present and valid."""
6984
env_vars_absent = [
7085
env
7186
for env in REQUIRED_ENVS
@@ -87,12 +102,14 @@ def validate_and_init() -> bool:
87102

88103

89104
def get_code_stats_response(user: str) -> Dict[str, Any]:
105+
"""Get statistics from codestats for user."""
90106
return requests.get(CODE_STATS_URL_FORMAT.format(user=user)).json()
91107

92108

93109
def __get_formatted_value(
94110
xp: int, recent_xp_supplier: Callable[[], int], stats_type: str
95111
) -> str:
112+
"""Get formatted value with xp and/or recent xp depending on stats type."""
96113
value_format = VALUE_FORMAT[stats_type]
97114
if stats_type == STATS_TYPE_LEVEL:
98115
return value_format.format(level=XP_TO_LEVEL(xp), xp=xp)
@@ -109,27 +126,43 @@ def __get_formatted_value(
109126

110127
def get_total_xp_line(
111128
code_stats_response: Dict[str, Any], stats_type: str
112-
) -> TitleAndValue:
129+
) -> LabelAndValue:
130+
"""Get label and formatted value for total xp.
131+
132+
Something along the lines of ("Total XP", "lvl 26 (1,104,152 XP)")
133+
"""
113134
total_xp = code_stats_response[CODE_STATS_TOTAL_XP_KEY]
114135
recent_total_xp_supplier = lambda: code_stats_response[CODE_STATS_TOTAL_NEW_XP_KEY]
115136
formatted_value = __get_formatted_value(
116137
total_xp, recent_total_xp_supplier, stats_type
117138
)
118-
return TitleAndValue(TOTAL_XP_TITLE, formatted_value)
139+
return LabelAndValue(TOTAL_XP_TITLE, formatted_value)
119140

120141

121142
def __get_language_xp_line(
122143
language: str, language_stats: Dict[str, int], stats_type: str
123-
) -> TitleAndValue:
144+
) -> LabelAndValue:
145+
"""Get label and formatted value for language xp.
146+
147+
Something along the lines of ("Java", "lvl 19 ( 580,523 XP)")
148+
"""
124149
xp = language_stats[CODE_STATS_LANGUAGES_XP_KEY]
125150
recent_xp_supplier = lambda: language_stats[CODE_STATS_LANGUAGES_NEW_XP_KEY]
126151
formatted_value = __get_formatted_value(xp, recent_xp_supplier, stats_type)
127-
return TitleAndValue(language, formatted_value)
152+
return LabelAndValue(language, formatted_value)
128153

129154

130155
def get_language_xp_lines(
131156
code_stats_response: Dict[str, Any], stats_type: str
132-
) -> List[TitleAndValue]:
157+
) -> List[LabelAndValue]:
158+
"""Get list of labels and formatted values for languages.
159+
160+
Decide which languages to include and return something like:
161+
[
162+
("Java", "lvl 19 ( 580,523 XP)"),
163+
("Python", "lvl 7 ( 82,719 XP)"),
164+
]
165+
"""
133166
if stats_type == STATS_TYPE_RECENT_XP:
134167
# Only considering languages with recent xp
135168
top_languages = list(
@@ -156,7 +189,11 @@ def get_language_xp_lines(
156189
]
157190

158191

159-
def get_adjusted_line(title_and_value: TitleAndValue) -> str:
192+
def get_adjusted_line(title_and_value: LabelAndValue) -> str:
193+
"""Format given label and value to single string separated by configured separator.
194+
195+
Something like (label, value) -> "label ::::::::::::: value"
196+
"""
160197
separation = MAX_LINE_LENGTH - (
161198
len(title_and_value.title) + len(title_and_value.value) + 2
162199
)
@@ -165,6 +202,11 @@ def get_adjusted_line(title_and_value: TitleAndValue) -> str:
165202

166203

167204
def update_gist(title: str, content: str) -> bool:
205+
"""Update gist with provided title and content.
206+
207+
Use gist id and github token present in environment variables.
208+
Replace first file in the gist.
209+
"""
168210
access_token = os.environ[ENV_VAR_GITHUB_TOKEN]
169211
gist_id = os.environ[ENV_VAR_GIST_ID]
170212
gist = Github(access_token).get_gist(gist_id)
@@ -174,7 +216,11 @@ def update_gist(title: str, content: str) -> bool:
174216
print(f"{title}\n{content}")
175217

176218

177-
def get_content() -> str:
219+
def get_stats() -> str:
220+
"""Get stats for codestats user according to stats type...
221+
222+
...both extracted from environment variables.
223+
"""
178224
code_stats_user_name = os.environ[ENV_VAR_CODE_STATS_USERNAME]
179225
code_stats_response = get_code_stats_response(code_stats_user_name)
180226

@@ -190,36 +236,36 @@ def get_content() -> str:
190236

191237

192238
def main():
193-
239+
"""Validate prerequisites, get content and update gist."""
194240
if not validate_and_init():
195241
raise RuntimeError(
196242
"Validations failed! See the messages above for more information"
197243
)
198244

199245
stats_type = os.environ[ENV_VAR_STATS_TYPE]
200246
title = GIST_TITLE[stats_type]
201-
content = get_content()
247+
content = get_stats()
202248
update_gist(title, content)
203249

204250

205251
if __name__ == "__main__":
206252
import time
207253

208254
s = time.perf_counter()
209-
# test with
210-
# python codestats_box.py test <codestats-user> <stats-type>
211-
# to only print content. To also test gist update, use:
212-
# python codestats_box.py test <codestats-user> <stats-type> <gist-id> <github-token>
213255
if len(sys.argv) > 1:
256+
# Test run
214257
os.environ[ENV_VAR_CODE_STATS_USERNAME] = sys.argv[2]
215258
os.environ[ENV_VAR_STATS_TYPE] = sys.argv[3]
216259
if len(sys.argv) > 4:
260+
# Testing gist update too
217261
os.environ[ENV_VAR_GIST_ID] = sys.argv[4]
218262
os.environ[ENV_VAR_GITHUB_TOKEN] = sys.argv[5]
219263
main()
220264
else:
221-
print(get_content())
265+
# Testing stats content only
266+
print(get_stats())
222267
else:
268+
# Normal run
223269
main()
224270
elapsed = time.perf_counter() - s
225271
print(f"{__file__} executed in {elapsed:0.2f} seconds.")

0 commit comments

Comments
 (0)