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+
114import datetime
215import math
316import os
1023from github import Github
1124from github .InputFileContent import InputFileContent
1225
13- TitleAndValue = namedtuple ("TitleAndValue " , "title value" )
26+ LabelAndValue = namedtuple ("LabelAndValue " , "title value" )
1427
28+ # Type of stats
1529STATS_TYPE_LEVEL = "level-xp"
1630STATS_TYPE_RECENT_XP = "recent-xp"
1731STATS_TYPE_XP = "xp"
2135 STATS_TYPE_LEVEL ,
2236]
2337DEFAULT_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
3039VALUE_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}
3544GIST_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"
4054NO_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
4762ENV_VAR_GIST_ID = "GIST_ID"
4863ENV_VAR_GITHUB_TOKEN = "GH_TOKEN"
4964ENV_VAR_CODE_STATS_USERNAME = "CODE_STATS_USERNAME"
5368 ENV_VAR_GITHUB_TOKEN ,
5469 ENV_VAR_CODE_STATS_USERNAME ,
5570]
56-
71+ # Code stats API constants
5772CODE_STATS_URL_FORMAT = "https://codestats.net/api/users/{user}"
5873CODE_STATS_DATE_KEY = "dates"
5974CODE_STATS_TOTAL_XP_KEY = "total_xp"
6075CODE_STATS_TOTAL_NEW_XP_KEY = "new_xp"
6176CODE_STATS_LANGUAGES_KEY = "languages"
6277CODE_STATS_LANGUAGES_XP_KEY = "xps"
6378CODE_STATS_LANGUAGES_NEW_XP_KEY = "new_xps"
64-
6579XP_TO_LEVEL = lambda xp : math .floor (0.025 * math .sqrt (xp ))
6680
6781
6882def 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
89104def 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
93109def __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
110127def 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
121142def __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
130155def 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
167204def 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
192238def 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
205251if __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