11
22
3- __all__ = ['term ' , 'logfile ' , 'github_auth_device ' , 'limit_cb ' , 'api ' , 'Events ' , 'print_event ' , 'tail_events ' ,
4- 'watch_users' , 'quad_logs' , 'simple' , 'main' ]
3+ __all__ = ['get_sparklines ' , 'ETYPES ' , 'term ' , 'tdim ' , 'github_auth_device ' , 'limit_cb ' , 'Events ' , 'print_event ' ,
4+ 'pct_comp' , 'tail_events' , ' watch_users' , 'quad_logs' , 'simple' , 'main' ]
55
66
77import sys , signal , shutil , os , json , emoji , enlighten
1414from fastcore .foundation import *
1515from fastcore .script import *
1616from ghapi .all import *
17+ from .richext import *
18+ from .all_rich import (Console , Color , FixedPanel , box , Segments , Live ,
19+ grid , ConsoleOptions , Progress , BarColumn , Spinner , Table )
20+
21+
22+
23+ ETYPES = PushEvent ,PullRequestEvent ,IssuesEvent ,ReleaseEvent
24+
25+ def get_sparklines ():
26+ s1 = ESpark ('Push' , 'magenta' , [PushEvent ], mx = 30 )
27+ s2 = ESpark ('PR' , 'yellow' , [PullRequestEvent , PullRequestReviewCommentEvent , PullRequestReviewEvent ], mx = 8 )
28+ s3 = ESpark ('Issues' , 'green' , [IssueCommentEvent ,IssuesEvent ], mx = 6 )
29+ s4 = ESpark ('Releases' , 'blue' , [ReleaseEvent ], mx = 0.4 )
30+ s5 = ESpark ('All Events' , 'orange' , mx = 45 )
31+
32+ return Stats ([s1 ,s2 ,s3 ,s4 ,s5 ], store = 5 , span = 5 , spn_lbl = '5/s' , show_freq = True )
1733
1834
1935term = Terminal ()
20- logfile = Path ("log.txt" )
36+
37+ tdim = L (os .popen ('stty size' , 'r' ).read ().split ())
38+ if not tdim : theight ,twidth = 15 ,15
39+ else : theight ,twidth = tdim .map (lambda x : max (int (x )- 3 , 15 ))
2140
2241
2342def github_auth_device (wb = '' , n_polls = 9999 ):
@@ -46,18 +65,6 @@ def limit_cb(rem,quota):
4665 if rem < 1000 : print (f"{ w } \n Remaining calls: { rem } out of { quota } \n { w } " , file = sys .stderr )
4766
4867
49- def _get_api ():
50- path = Path .home ()/ ".ghtop_token"
51- if path .is_file ():
52- try : token = path .read_text ().strip ()
53- except : _exit ("Error reading token" )
54- else : token = github_auth_device ()
55- path .write_text (token )
56- return GhApi (limit_cb = limit_cb , token = token )
57-
58- api = _get_api ()
59-
60-
6168Events = dict (
6269 IssuesEvent_closed = ('⭐' , 'closed' , noop ),
6370 IssuesEvent_opened = ('📫' , 'opened' , noop ),
@@ -78,79 +85,120 @@ def _to_log(e):
7885 elif e .type == "ReleaseEvent" : return f'🚀 { login } released { e .payload .release .tag_name } of { repo } '
7986
8087
81- def print_event (e , commits_counter ):
88+ def print_event (e , counter ):
8289 res = _to_log (e )
8390 if res : print (res )
84- elif e .type == "PushEvent" : [commits_counter .update () for c in e .payload .commits ]
91+ elif counter and e .type == "PushEvent" : [counter .update () for c in e .payload .commits ]
8592 elif e .type == "SecurityAdvisoryEvent" : print (term .blink ("SECURITY ADVISORY" ))
8693
8794
88- def tail_events (evt ):
89- "Print events from `fetch_events` along with a counter of push events"
90- manager = enlighten .get_manager ()
91- commits = manager .counter (desc = 'Commits' , unit = 'commits' , color = 'green' )
92- for ev in evt : print_event (ev , commits )
95+ def pct_comp (api ): return int (((5000 - int (api .limit_rem )) / 5000 ) * 100 )
9396
9497
95- def _pr_row (* its ): print (f"{ its [0 ]: <30} { its [1 ]: <6} { its [2 ]: <5} { its [3 ]: <6} { its [4 ]: <7} " )
96- def watch_users (evts ):
98+ def tail_events (evt , api ):
99+ "Print events from `fetch_events` along with a counter of push events"
100+ p = FixedPanel (theight , box = box .HORIZONTALS , title = 'ghtop' )
101+ s = get_sparklines ()
102+ g = grid ([[s ], [p ]])
103+ with Live (g ):
104+ for e in evt :
105+ s .add_events (e )
106+ s .update_prog (pct_comp (api ))
107+ p .append (e )
108+ g = grid ([[s ], [p ]])
109+
110+
111+ def _user_grid ():
112+ g = Table .grid (expand = True )
113+ g .add_column (justify = "left" )
114+ for i in range (4 ): g .add_column (justify = "center" )
115+ g .add_row ("" , "" , "" , "" , "" )
116+ g .add_row ("User" , "Events" , "PRs" , "Issues" , "Pushes" )
117+ return g
118+
119+
120+ def watch_users (evts , api ):
97121 "Print a table of the users with the most events"
98122 users ,users_events = defaultdict (int ),defaultdict (lambda : defaultdict (int ))
99- while True :
100- for x in islice (evts , 10 ):
101- users [x .actor .login ] += 1
102- users_events [x .actor .login ][x .type ] += 1
103123
104- print (term .clear ())
105- _pr_row ("User" , "Events" , "PRs" , "Issues" , "Pushes" )
106- sorted_users = sorted (users .items (), key = lambda o : (o [1 ],o [0 ]), reverse = True )
107- for u in sorted_users [:20 ]:
108- _pr_row (* u , * itemgetter ('PullRequestEvent' ,'IssuesEvent' ,'PushEvent' )(users_events [u [0 ]]))
124+ with Live () as live :
125+ s = get_sparklines ()
126+ while True :
127+ for x in islice (evts , 10 ):
128+ users [x .actor .login ] += 1
129+ users_events [x .actor .login ][x .type ] += 1
130+ s .add_events (x )
109131
132+ ig = _user_grid ()
133+ sorted_users = sorted (users .items (), key = lambda o : (o [1 ],o [0 ]), reverse = True )
134+ for u in sorted_users [:theight ]:
135+ data = (* u , * itemgetter ('PullRequestEvent' ,'IssuesEvent' ,'PushEvent' )(users_events [u [0 ]]))
136+ ig .add_row (* L (data ).map (str ))
110137
111- def _push_to_log (e ): return f"{ e .actor .login } pushed { len (e .payload .commits )} commits to repo { e .repo .name } "
112- def _logwin (title ,color ): return Log (title = title ,border_color = 2 ,color = color )
113-
114- def quad_logs (evts ):
115- "Print 4 panels, showing most recent issues, commits, PRs, and releases"
116- term .enter_fullscreen ()
117- ui = HSplit (VSplit (_logwin ('Issues' , color = 7 ), _logwin ('Commits' , color = 3 )),
118- VSplit (_logwin ('Pull Requests' , color = 4 ), _logwin ('Releases' , color = 5 )))
138+ s .update_prog (pct_comp (api ))
139+ g = grid ([[s ], [ig ]])
140+ live .update (g )
119141
120- issues ,commits ,prs ,releases = all_items = ui .items [0 ].items + ui .items [1 ].items
121- for o in all_items : o .append (" " )
122142
123- d = dict (PushEvent = commits , IssuesEvent = issues , IssueCommentEvent = issues , PullRequestEvent = prs , ReleaseEvent = releases )
124- while True :
125- for x in islice (evts , 10 ):
126- f = [_to_log ,_push_to_log ][x .type == 'PushEvent' ]
127- if x .type in d : d [x .type ].append (f (x )[:95 ])
128- ui .display ()
143+ def _panelDict2Grid (pd ):
144+ ispush ,ispr ,isiss ,isrel = pd .values ()
145+ return grid ([[ispush ,ispr ],[isiss ,isrel ]], width = twidth )
129146
130147
131- def simple (evts ):
148+ def quad_logs (evts , api ):
149+ "Print 4 panels, showing most recent issues, commits, PRs, and releases"
150+ pd = {o :FixedPanel (height = (theight // 2 ),
151+ width = (twidth // 2 )- 1 ,
152+ box = box .HORIZONTALS ,
153+ title = camel2words (remove_suffix (o .__name__ ,'Event' ))) for o in ETYPES }
154+ p = _panelDict2Grid (pd )
155+ s = get_sparklines ()
156+ g = grid ([[s ], [p ]])
157+ with Live (g ):
158+ for e in evts :
159+ s .add_events (e )
160+ s .update_prog (pct_comp (api ))
161+ typ = type (e )
162+ if typ in pd : pd [typ ].append (e )
163+ p = _panelDict2Grid (pd )
164+ g = grid ([[s ], [p ]])
165+
166+
167+ def simple (evts , api ):
132168 for ev in evts : print (f"{ ev .actor .login } { ev .type } { ev .repo .name } " )
133169
134170
171+ def _get_token ():
172+ path = Path .home ()/ ".ghtop_token"
173+ if path .is_file ():
174+ try : return path .read_text ().strip ()
175+ except : _exit ("Error reading token" )
176+ else : token = github_auth_device ()
177+ path .write_text (token )
178+ return token
179+
180+
135181def _signal_handler (sig , frame ):
136182 if sig != signal .SIGINT : return
137183 print (term .exit_fullscreen (),term .clear (),term .normal )
138184 sys .exit (0 )
139185
140186_funcs = dict (tail = tail_events , quad = quad_logs , users = watch_users , simple = simple )
141- _filts = str_enum ('_filts' , 'user ' , 'repo' , 'org' )
187+ _filts = str_enum ('_filts' , 'users ' , 'repo' , 'org' )
142188_OpModes = str_enum ('_OpModes' , * _funcs )
143189
144190@call_parse
145191def main (mode : Param ("Operation mode to run" , _OpModes ),
146192 include_bots : Param ("Include bots (there's a lot of them!)" , store_true )= False ,
147193 types : Param ("Comma-separated types of event to include (e.g PushEvent)" , str )= '' ,
194+ pause : Param ("Number of seconds to pause between requests to the GitHub api" , float )= 0.4 ,
148195 filt : Param ("Filtering method" , _filts )= None ,
149196 filtval : Param ("Value to filter by (for `repo` use format `owner/repo`)" , str )= None ):
150197 signal .signal (signal .SIGINT , _signal_handler )
151198 types = types .split (',' ) if types else None
152199 if filt and not filtval : _exit ("Must pass `filter_value` if passing `filter_type`" )
153200 if filtval and not filt : _exit ("Must pass `filter_type` if passing `filter_value`" )
154201 kwargs = {filt :filtval } if filt else {}
155- evts = api .fetch_events (types = types , incl_bot = include_bots , ** kwargs )
156- _funcs [mode ](evts )
202+ api = GhApi (limit_cb = limit_cb , token = _get_token ())
203+ evts = api .fetch_events (types = types , incl_bot = include_bots , pause = float (pause ), ** kwargs )
204+ _funcs [mode ](evts , api )
0 commit comments