1
- from collections import namedtuple
2
1
import datetime
3
2
import math
4
3
import os
5
4
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
8
8
9
9
import requests
10
10
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
11
24
12
25
TOP_LANGUAGES_COUNT = 10
26
+ MAX_LINE_LENGTH = 54
13
27
WIDTH_JUSTIFICATION_SEPARATOR = ":"
14
28
RECENT_STATS_SEPARATOR = " + "
15
29
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
+ ]
21
46
22
47
ENV_VAR_GIST_ID = "GIST_ID"
23
48
ENV_VAR_GITHUB_TOKEN = "GH_TOKEN"
29
54
ENV_VAR_CODE_STATS_USERNAME ,
30
55
]
31
56
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
-
42
57
CODE_STATS_URL_FORMAT = "https://codestats.net/api/users/{user}"
43
58
CODE_STATS_DATE_KEY = "dates"
44
59
CODE_STATS_TOTAL_XP_KEY = "total_xp"
60
+ CODE_STATS_TOTAL_NEW_XP_KEY = "new_xp"
45
61
CODE_STATS_LANGUAGES_KEY = "languages"
46
62
CODE_STATS_LANGUAGES_XP_KEY = "xps"
47
63
CODE_STATS_LANGUAGES_NEW_XP_KEY = "new_xps"
48
64
49
65
XP_TO_LEVEL = lambda xp : math .floor (0.025 * math .sqrt (xp ))
50
66
51
- TitleAndValue = namedtuple ("TitleAndValue" , "title value" )
52
-
53
67
54
68
def validate_and_init () -> bool :
55
69
env_vars_absent = [
@@ -72,86 +86,89 @@ def validate_and_init() -> bool:
72
86
return True
73
87
74
88
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
-
83
89
def get_code_stats_response (user : str ) -> Dict [str , Any ]:
84
90
return requests .get (CODE_STATS_URL_FORMAT .format (user = user )).json ()
85
91
86
92
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
+
87
110
def get_total_xp_line (
88
111
code_stats_response : Dict [str , Any ], stats_type : str
89
112
) -> 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
- )
100
113
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 )
115
119
116
120
117
121
def __get_language_xp_line (
118
122
language : str , language_stats : Dict [str , int ], stats_type : str
119
123
) -> TitleAndValue :
120
124
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 )
134
128
135
129
136
130
def get_language_xp_lines (
137
131
code_stats_response : Dict [str , Any ], stats_type : str
138
132
) -> 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 ]
144
153
return [
145
154
__get_language_xp_line (language , stats , stats_type )
146
155
for language , stats in top_languages
147
156
]
148
157
149
158
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
+
150
167
def update_gist (title : str , content : str ) -> bool :
151
168
access_token = os .environ [ENV_VAR_GITHUB_TOKEN ]
152
169
gist_id = os .environ [ENV_VAR_GIST_ID ]
153
170
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?
155
172
old_title = list (gist .files .keys ())[0 ]
156
173
gist .edit (title , {old_title : InputFileContent (content , title )})
157
174
print (f"{ title } \n { content } " )
@@ -179,8 +196,10 @@ def main():
179
196
"Validations failed! See the messages above for more information"
180
197
)
181
198
199
+ stats_type = os .environ [ENV_VAR_STATS_TYPE ]
200
+ title = GIST_TITLE [stats_type ]
182
201
content = get_content ()
183
- update_gist (GIST_TITLE , content )
202
+ update_gist (title , content )
184
203
185
204
186
205
if __name__ == "__main__" :
0 commit comments