From 3ea0040dc9e026a6cea936a001f593d4bd73cdf7 Mon Sep 17 00:00:00 2001 From: hamelsmu Date: Fri, 22 Jan 2021 13:32:42 -0800 Subject: [PATCH 1/3] add rich, clean up cli --- README.md | 27 +-- ghtop/all_rich.py | 84 ++++++++++ ghtop/ghtop.py | 161 ++++++++++-------- ghtop/richext.py | 131 +++++++++++++++ nbs/00_ghtop.ipynb | 392 -------------------------------------------- nbs/index.ipynb | 156 ------------------ nbs/rich_test.ipynb | 169 ------------------- 7 files changed, 325 insertions(+), 795 deletions(-) create mode 100644 ghtop/all_rich.py create mode 100644 ghtop/richext.py delete mode 100644 nbs/00_ghtop.ipynb delete mode 100644 nbs/index.ipynb delete mode 100644 nbs/rich_test.ipynb diff --git a/README.md b/README.md index c8016c2..f5f32e3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ `ghtop` provides a number of views of all current public activity from all users across the entire GitHub platform. (Note that GitHub delays all events by five minutes.) - + ## Install @@ -14,19 +14,19 @@ Either `pip install ghtop` or `conda install -c fastai ghtop`. Run `ghtop -h` to view the help: -```bash +``` $ ghtop -h -usage: ghtop [-h] [--include_bots] [--types TYPES] [--filt {user,repo,org}] [--filtval FILTVAL] - {tail,quad,users,simple} +usage: ghtop [-h] [--include_bots] [--types TYPES] [--pause PAUSE] [--filt {users,repo,org}] [--filtval FILTVAL] {tail,quad,users,simple} positional arguments: {tail,quad,users,simple} Operation mode to run optional arguments: -h, --help show this help message and exit - --include_bots Include bots (there is a lot of them!) (default: False) - --types TYPES Comma-separated types of event to include (e.g PushEvent) - --filt {user,repo,org} Filtering method + --include_bots Include bots (there's a lot of them!) (default: False) + --types TYPES Comma-separated types of event to include (e.g PushEvent) (default: ) + --pause PAUSE Number of seconds to pause between requests to the GitHub api (default: 0.4) + --filt {users,repo,org} Filtering method --filtval FILTVAL Value to filter by (for `repo` use format `owner/repo`) ``` @@ -34,6 +34,7 @@ There are 4 views you can choose: `ghtop simple`, `ghtop tail`, `ghtop quad`, or - `--include_bots`: By default events that appear to be from bots are excluded. Add this flag to include them - `--types TYPES`: Optional comma-separated list of event types to include (defaults to all types). For a full list of types, see the GitHub [event types docs](https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/github-event-types) +- `--pause PAUSE`: Number of seconds to pause between requests to the GitHub api (default: 0.4). It is helpful to adjust this number if you want to get events more or less frequently. For example, if you are filtering all events by an org, then you likely want to pause for a longer period of time than the default. - `--filt` and `--filtval`: Optionally filter events to just those from one of: `user`, `repo`, or `org`, depending on `filt`. `filtval` is the value to filter by. See the [GitHub docs](https://docs.github.com/en/free-pro-team@latest/rest/reference/activity#list-public-events) for details on the public event API calls used. **Important note**: while running, `ghtop` will make about 5 API calls per second. GitHub has a quota of 5000 calls per hour. When there are 1000 calls left, `ghtop` will show a warning on every call. @@ -42,25 +43,25 @@ There are 4 views you can choose: `ghtop simple`, `ghtop tail`, `ghtop quad`, or A simple dump to your console of all events as they happen. - + ### ghtop tail -Like `simple`, but removes most bots, and only includes releases, issues and PRs (open, close, and comment events). A summary of the frequency of push events is also shown at the bottom of the screen. +Like `simple`, but only includes releases, issues and PRs (open, close, and comment events). A summary of the frequency of different kind of events along with sparklines are shown at the top of the screen. - + ### ghtop quad -The same information as `tail`, but in a split window showing separately PRs, issues, pushes, and releases. This view does not remove bot activity. +The same information as `tail`, but in a split window showing separately PRs, issues, pushes, and releases. - + ### ghtop users A summary of activity for the most active current users. - + ---- diff --git a/ghtop/all_rich.py b/ghtop/all_rich.py new file mode 100644 index 0000000..1e0df69 --- /dev/null +++ b/ghtop/all_rich.py @@ -0,0 +1,84 @@ +from rich import print as pr +from rich.align import * +from rich.bar import * +from rich.color import * +from rich.columns import * +from rich.console import * +from rich.emoji import * +from rich.highlighter import * +from rich.live import * +from rich.logging import * +from rich.markdown import * +from rich.markup import * +from rich.measure import * +from rich.padding import * +from rich.panel import * +from rich.pretty import * +from rich.progress_bar import * +from rich.progress import * +from rich.prompt import * +from rich.protocol import * +from rich.rule import * +from rich.segment import * +from rich.spinner import * +from rich.status import * +from rich.style import * +from rich.styled import * +from rich.syntax import * +from rich.table import * +from rich.text import * +from rich.theme import * +from rich.traceback import * +from fastcore.all import * + +@delegates(Style) +def text(s, maxlen=None, **kwargs): + "Create a styled `Text` object" + if maxlen: s = truncstr(s, maxlen=maxlen) + return Text(s, style=Style(**kwargs)) + +@delegates(Style) +def segment(s, maxlen=None, space=' ', **kwargs): + "Create a styled `Segment` object" + if maxlen: s = truncstr(s, maxlen=maxlen, space=space) + return Segment(s, style=Style(**kwargs)) + +class Segments(list): + def __init__(self, options): self.w = options.max_width + + @property + def chars(self): return sum(o.cell_length for o in self) + def txtlen(self, pct): return min((self.w-self.chars)*pct, 999) + + @delegates(segment) + def add(self, x, maxlen=None, pct=None, **kwargs): + if pct: maxlen = math.ceil(self.txtlen(pct)) + self.append(segment(x, maxlen=maxlen, **kwargs)) + +@delegates(Table) +def _grid(box=None, padding=0, collapse_padding=True, pad_edge=False, expand=False, show_header=False, show_edge=False, **kwargs): + return Table(padding=padding, pad_edge=pad_edge, expand=expand, collapse_padding=collapse_padding, + box=box, show_header=show_header, show_edge=show_edge, **kwargs) + +@delegates(_grid) +def grid(items, expand=True, no_wrap=True, **kwargs): + g = _grid(expand=expand, **kwargs) + for c in items[0]: g.add_column(no_wrap=no_wrap, justify='center') + for i in items: g.add_row(*i) + return g + +Color = str_enum('Color', *ANSI_COLOR_NAMES) + +class Deque(deque): + def __rich__(self): return RenderGroup(*(filter(None, self))) + +@delegates() +class FixedPanel(Panel, GetAttr): + _default='renderable' + def __init__(self, height, **kwargs): + super().__init__(Deque([' ']*height, maxlen=height), **kwargs) + +@delegates(Style) +def add(self, s:str, **kwargs): + "Add styled `s` to panel" + self.append(text(s, **kwargs)) diff --git a/ghtop/ghtop.py b/ghtop/ghtop.py index addafeb..6bcff0b 100644 --- a/ghtop/ghtop.py +++ b/ghtop/ghtop.py @@ -1,6 +1,6 @@ -__all__ = ['term', 'logfile', 'github_auth_device', 'limit_cb', 'api', 'Events', 'print_event', 'tail_events', +__all__ = ['get_sparklines', 'ETYPES', 'term', 'tdim', 'limit_cb', 'Events', 'print_event', 'pct_comp', 'tail_events', 'watch_users', 'quad_logs', 'simple', 'main'] @@ -14,25 +14,27 @@ from fastcore.foundation import * from fastcore.script import * from ghapi.all import * +from .richext import * +from .all_rich import (Console, Color, FixedPanel, box, Segments, Live, + grid, ConsoleOptions, Progress, BarColumn, Spinner, Table) +ETYPES=PushEvent,PullRequestEvent,IssuesEvent,ReleaseEvent -term = Terminal() -logfile = Path("log.txt") +def get_sparklines(): + s1 = ESpark('Push', 'magenta', [PushEvent], mx=30) + s2 = ESpark('PR', 'yellow', [PullRequestEvent, PullRequestReviewCommentEvent, PullRequestReviewEvent], mx=8) + s3 = ESpark('Issues', 'green', [IssueCommentEvent,IssuesEvent], mx=6) + s4 = ESpark('Releases', 'blue', [ReleaseEvent], mx=0.4) + s5 = ESpark('All Events', 'orange', mx=45) + return Stats([s1,s2,s3,s4,s5], store=5, span=5, spn_lbl='5/s', show_freq=True) -def github_auth_device(wb='', n_polls=9999): - "Authenticate with GitHub, polling up to `n_polls` times to wait for completion" - auth = GhDeviceAuth() - print(f"First copy your one-time code: {term.yellow}{auth.user_code}{term.normal}") - print(f"Then visit {auth.verification_uri} in your browser, and paste the code when prompted.") - if not wb: wb = input("Shall we try to open the link for you? [y/n] ") - if wb[0].lower()=='y': auth.open_browser() - print("Waiting for authorization...", end='') - token = auth.wait(lambda: print('.', end=''), n_polls=n_polls) - if not token: return print('Authentication not complete!') - print("Authenticated with GitHub") - return token +term = Terminal() + +tdim = L(os.popen('stty size', 'r').read().split()) +if not tdim: theight,twidth = 15,15 +else: theight,twidth = tdim.map(lambda x: max(int(x)-4, 15)) def _exit(msg): @@ -46,18 +48,6 @@ def limit_cb(rem,quota): if rem < 1000: print(f"{w}\nRemaining calls: {rem} out of {quota}\n{w}", file=sys.stderr) -def _get_api(): - path = Path.home()/".ghtop_token" - if path.is_file(): - try: token = path.read_text().strip() - except: _exit("Error reading token") - else: token = github_auth_device() - path.write_text(token) - return GhApi(limit_cb=limit_cb, token=token) - -api = _get_api() - - Events = dict( IssuesEvent_closed=('โญ', 'closed', noop), IssuesEvent_opened=('๐Ÿ“ซ', 'opened', noop), @@ -78,73 +68,113 @@ def _to_log(e): elif e.type == "ReleaseEvent": return f'๐Ÿš€ {login} released {e.payload.release.tag_name} of {repo}' -def print_event(e, commits_counter): +def print_event(e, counter): res = _to_log(e) if res: print(res) - elif e.type == "PushEvent": [commits_counter.update() for c in e.payload.commits] + elif counter and e.type == "PushEvent": [counter.update() for c in e.payload.commits] elif e.type == "SecurityAdvisoryEvent": print(term.blink("SECURITY ADVISORY")) -def tail_events(evt): - "Print events from `fetch_events` along with a counter of push events" - manager = enlighten.get_manager() - commits = manager.counter(desc='Commits', unit='commits', color='green') - for ev in evt: print_event(ev, commits) +def pct_comp(api): return int(((5000-int(api.limit_rem)) / 5000) * 100) -def _pr_row(*its): print(f"{its[0]: <30} {its[1]: <6} {its[2]: <5} {its[3]: <6} {its[4]: <7}") -def watch_users(evts): +def tail_events(evt, api): + "Print events from `fetch_events` along with a counter of push events" + p = FixedPanel(theight, box=box.HORIZONTALS, title='ghtop') + s = get_sparklines() + g = grid([[s], [p]]) + with Live(g): + for e in evt: + s.add_events(e) + s.update_prog(pct_comp(api)) + p.append(e) + g = grid([[s], [p]]) + + +def _user_grid(): + g = Table.grid(expand=True) + g.add_column(justify="left") + for i in range(4): g.add_column(justify="center") + g.add_row("", "", "", "", "") + g.add_row("User", "Events", "PRs", "Issues", "Pushes") + return g + + +def watch_users(evts, api): "Print a table of the users with the most events" users,users_events = defaultdict(int),defaultdict(lambda: defaultdict(int)) - while True: - for x in islice(evts, 10): - users[x.actor.login] += 1 - users_events[x.actor.login][x.type] += 1 - - print(term.clear()) - _pr_row("User", "Events", "PRs", "Issues", "Pushes") - sorted_users = sorted(users.items(), key=lambda o: (o[1],o[0]), reverse=True) - for u in sorted_users[:20]: - _pr_row(*u, *itemgetter('PullRequestEvent','IssuesEvent','PushEvent')(users_events[u[0]])) + with Live() as live: + s = get_sparklines() + while True: + for x in islice(evts, 10): + users[x.actor.login] += 1 + users_events[x.actor.login][x.type] += 1 + s.add_events(x) -def _push_to_log(e): return f"{e.actor.login} pushed {len(e.payload.commits)} commits to repo {e.repo.name}" -def _logwin(title,color): return Log(title=title,border_color=2,color=color) + ig = _user_grid() + sorted_users = sorted(users.items(), key=lambda o: (o[1],o[0]), reverse=True) + for u in sorted_users[:theight]: + data = (*u, *itemgetter('PullRequestEvent','IssuesEvent','PushEvent')(users_events[u[0]])) + ig.add_row(*L(data).map(str)) -def quad_logs(evts): - "Print 4 panels, showing most recent issues, commits, PRs, and releases" - term.enter_fullscreen() - ui = HSplit(VSplit(_logwin('Issues', color=7), _logwin('Commits' , color=3)), - VSplit(_logwin('Pull Requests', color=4), _logwin('Releases', color=5))) + s.update_prog(pct_comp(api)) + g = grid([[s], [ig]]) + live.update(g) - issues,commits,prs,releases = all_items = ui.items[0].items+ui.items[1].items - for o in all_items: o.append(" ") - d = dict(PushEvent=commits, IssuesEvent=issues, IssueCommentEvent=issues, PullRequestEvent=prs, ReleaseEvent=releases) - while True: - for x in islice(evts, 10): - f = [_to_log,_push_to_log][x.type == 'PushEvent'] - if x.type in d: d[x.type].append(f(x)[:95]) - ui.display() +def _panelDict2Grid(pd): + ispush,ispr,isiss,isrel = pd.values() + return grid([[ispush,ispr],[isiss,isrel]], width=twidth) -def simple(evts): +def quad_logs(evts, api): + "Print 4 panels, showing most recent issues, commits, PRs, and releases" + pd = {o:FixedPanel(height=(theight//2)-1, + width=(twidth//2)-1, + box=box.HORIZONTALS, + title=camel2words(remove_suffix(o.__name__,'Event'))) for o in ETYPES} + p = _panelDict2Grid(pd) + s = get_sparklines() + g = grid([[s], [p]]) + with Live(g): + for e in evts: + s.add_events(e) + s.update_prog(pct_comp(api)) + typ = type(e) + if typ in pd: pd[typ].append(e) + p = _panelDict2Grid(pd) + g = grid([[s], [p]]) + + +def simple(evts, api): for ev in evts: print(f"{ev.actor.login} {ev.type} {ev.repo.name}") +def _get_token(): + path = Path.home()/".ghtop_token" + if path.is_file(): + try: return path.read_text().strip() + except: _exit("Error reading token") + else: token = github_auth_device() + path.write_text(token) + return token + + def _signal_handler(sig, frame): if sig != signal.SIGINT: return print(term.exit_fullscreen(),term.clear(),term.normal) sys.exit(0) _funcs = dict(tail=tail_events, quad=quad_logs, users=watch_users, simple=simple) -_filts = str_enum('_filts', 'user', 'repo', 'org') +_filts = str_enum('_filts', 'users', 'repo', 'org') _OpModes = str_enum('_OpModes', *_funcs) @call_parse def main(mode: Param("Operation mode to run", _OpModes), include_bots: Param("Include bots (there's a lot of them!)", store_true)=False, types: Param("Comma-separated types of event to include (e.g PushEvent)", str)='', + pause: Param("Number of seconds to pause between requests to the GitHub api", float)=0.4, filt: Param("Filtering method", _filts)=None, filtval: Param("Value to filter by (for `repo` use format `owner/repo`)", str)=None): signal.signal(signal.SIGINT, _signal_handler) @@ -152,5 +182,6 @@ def main(mode: Param("Operation mode to run", _OpModes), if filt and not filtval: _exit("Must pass `filter_value` if passing `filter_type`") if filtval and not filt: _exit("Must pass `filter_type` if passing `filter_value`") kwargs = {filt:filtval} if filt else {} - evts = api.fetch_events(types=types, incl_bot=include_bots, **kwargs) - _funcs[mode](evts) \ No newline at end of file + api = GhApi(limit_cb=limit_cb, token=_get_token()) + evts = api.fetch_events(types=types, incl_bot=include_bots, pause=float(pause), **kwargs) + _funcs[mode](evts, api) \ No newline at end of file diff --git a/ghtop/richext.py b/ghtop/richext.py new file mode 100644 index 0000000..627d033 --- /dev/null +++ b/ghtop/richext.py @@ -0,0 +1,131 @@ + + +__all__ = ['console', 'EProg', 'ESpark', 'SpkMap', 'Stats', 'colors', 'colors2'] + + +import time,random +from collections import defaultdict +from typing import List +from collections import deque, OrderedDict, namedtuple +from .all_rich import (Console, Color, FixedPanel, box, Segments, Live, + grid, ConsoleOptions, Progress, BarColumn, Spinner) +from ghapi.event import * +from fastcore.all import * +console = Console() + + + +class EProg: + "Progress bar with a heading `hdg`." + def __init__(self, hdg='Quota', width=10): + self.prog = Progress(BarColumn(bar_width=width), "[progress.percentage]{task.percentage:>3.0f}%") + self.task = self.prog.add_task("",total=100, visible=False) + store_attr() + def update(self, completed): self.prog.update(self.task, completed=completed) + def __rich_console__(self, console: Console, options: ConsoleOptions): + self.prog.update(self.task, visible=True) + yield grid([["Quota"], [self.prog.get_renderable()]], width=self.width+2, expand=False) + + + +class ESpark(EventTimer): + "An `EventTimer` that displays a sparkline with a heading `nm`." + def __init__(self, nm:str, color:str, ghevts=None, store=5, span=.2, mn=0, mx=None, stacked=True, show_freq=False): + super().__init__(store=store, span=span) + self.ghevts=L(ghevts) + store_attr('nm,color,store,span,mn,mx,stacked,show_freq') + + def _spark(self): + data = L(list(self.hist)+[self.freq] if self.show_freq else self.hist) + num = f'{self.freq:.1f}' if self.freq < 10 else f'{self.freq:.0f}' + return f"[{self.color}]{num} {sparkline(data, mn=self.mn, mx=self.mx)}[/]" + + def upd_hist(self, store, span): super().__init__(store=store, span=span) + + def _nm(self): return f"[{self.color}] {self.nm}[/]" + + def __rich_console__(self, console: Console, options: ConsoleOptions): + yield grid([[self._nm()], [self._spark()]]) if self.stacked else f'{self._nm()} {self._spark()}' + + def add_events(self, evts): + evts = L([evts]) if isinstance(evts, dict) else L(evts) + if self.ghevts: evts.map(lambda e: self.add(1) if type(e) in L(self.ghevts) else noop) + else: self.add(len(evts)) + + __repr__ = basic_repr('nm,color,ghevts,store,span,stacked,show_freq,ylim') + + +class SpkMap: + "A Group of `ESpark` instances." + def __init__(self, spks:List[ESpark]): store_attr() + + @property + def evcounts(self): return dict([(s.nm, s.events) for s in self.spks]) + + def update_params(self, store:int=None, span:float=None, stacked:bool=None, show_freq:bool=None): + for s in self.spks: + s.upd_hist(store=ifnone(store,s.store), span=ifnone(span,s.span)) + s.stacked = ifnone(stacked,s.stacked) + s.show_freq = ifnone(show_freq,s.show_freq) + + def add_events(self, evts:GhEvent): + "Update `SpkMap` sparkline historgrams with events." + evts = L([evts]) if isinstance(evts, dict) else L(evts) + for s in self.spks: s.add_events(evts) + + def __rich_console__(self, console: Console, options: ConsoleOptions): yield grid([self.spks]) + __repr__ = basic_repr('spks') + + + + +class Stats(SpkMap): + "Renders a group of `ESpark` along with a spinner and progress bar that are dynamically sized." + def __init__(self, spks:List[ESpark], store=None, span=None, stacked=None, show_freq=None, max_width=console.width-5, spin:str='earth', spn_lbl="/min"): + super().__init__(spks) + self.update_params(store=store, span=span, stacked=stacked, show_freq=show_freq) + store_attr() + self.spn = Spinner(spin) + self.slen = len(spks) * max(15, store*2) + self.plen = max(store, 10) # max(max_width-self.slen-15, 15) + self.progbar = EProg(width=self.plen) + + def get_spk(self): return grid([self.spks], width=min(console.width-15, self.slen), expand=False) + + def get_spinner(self): return grid([[self.spn], [self.spn_lbl]]) + + def update_prog(self, pct_complete:int=None): self.progbar.update(pct_complete) if pct_complete else noop() + + def __rich_console__(self, console: Console, options: ConsoleOptions): + yield grid([[self.get_spinner(), self.get_spk(), grid([[self.progbar]], width=self.plen+5) ]], width=self.max_width) + + + +@patch +def __rich_console__(self:GhEvent, console, options): + res = Segments(options) + kw = {'color': colors[self.type]} + res.add(f'{self.emoji} ') + res.add(self.actor.login, pct=0.25, bold=True, **kw) + res.add(self.description, pct=0.5, **kw) + res.add(self.repo.name, pct=0.5 if self.text else 1, space = ': ' if self.text else '', italic=True, **kw) + if self.text: + clean_text = self.text.replace('\n', ' ').replace('\n', ' ') + res.add (f'"{clean_text}"', pct=1, space='', **kw) + res.add('\n') + return res + + +colors = dict( + PushEvent=None, CreateEvent=Color.red, IssueCommentEvent=Color.green, WatchEvent=Color.yellow, + PullRequestEvent=Color.blue, PullRequestReviewEvent=Color.magenta, PullRequestReviewCommentEvent=Color.cyan, + DeleteEvent=Color.bright_red, ForkEvent=Color.bright_green, IssuesEvent=Color.bright_magenta, + ReleaseEvent=Color.bright_blue, MemberEvent=Color.bright_yellow, CommitCommentEvent=Color.bright_cyan, + GollumEvent=Color.white, PublicEvent=Color.turquoise4) + +colors2 = dict( + PushEvent=None, CreateEvent=Color.dodger_blue1, IssueCommentEvent=Color.tan, WatchEvent=Color.steel_blue1, + PullRequestEvent=Color.deep_pink1, PullRequestReviewEvent=Color.slate_blue1, PullRequestReviewCommentEvent=Color.tan, + DeleteEvent=Color.light_pink1, ForkEvent=Color.orange1, IssuesEvent=Color.medium_violet_red, + ReleaseEvent=Color.green1, MemberEvent=Color.orchid1, CommitCommentEvent=Color.tan, + GollumEvent=Color.sea_green1, PublicEvent=Color.magenta2) \ No newline at end of file diff --git a/nbs/00_ghtop.ipynb b/nbs/00_ghtop.ipynb deleted file mode 100644 index 5188bd4..0000000 --- a/nbs/00_ghtop.ipynb +++ /dev/null @@ -1,392 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#default_exp ghtop" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# ghtop API\n", - "\n", - "> API details" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#export\n", - "import sys, signal, shutil, os, json, emoji, enlighten\n", - "from dashing import *\n", - "from collections import defaultdict\n", - "from warnings import warn\n", - "from itertools import islice\n", - "\n", - "from fastcore.utils import *\n", - "from fastcore.foundation import *\n", - "from fastcore.script import *\n", - "from ghapi.all import *" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#export\n", - "term = Terminal()\n", - "logfile = Path(\"log.txt\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#export\n", - "def github_auth_device(wb='', n_polls=9999):\n", - " \"Authenticate with GitHub, polling up to `n_polls` times to wait for completion\"\n", - " auth = GhDeviceAuth()\n", - " print(f\"First copy your one-time code: {term.yellow}{auth.user_code}{term.normal}\")\n", - " print(f\"Then visit {auth.verification_uri} in your browser, and paste the code when prompted.\")\n", - " if not wb: wb = input(\"Shall we try to open the link for you? [y/n] \")\n", - " if wb[0].lower()=='y': auth.open_browser()\n", - "\n", - " print(\"Waiting for authorization...\", end='')\n", - " token = auth.wait(lambda: print('.', end=''), n_polls=n_polls)\n", - " if not token: return print('Authentication not complete!')\n", - " print(\"Authenticated with GitHub\")\n", - " return token" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When we run this we'll be shown a URL to visit and a code to enter in order to authenticate. Normally we'll be prompted to open a browser, and the function will wait for authentication to complete -- for demonstrating here we'll skip both of these steps:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "First copy your one-time code: \u001b[33m4647-152A\u001b[m\n", - "Then visit https://github.com/login/device in your browser, and paste the code when prompted.\n", - "Waiting for authorization...Authentication not complete!\n" - ] - } - ], - "source": [ - "github_auth_device('n',n_polls=0)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#export\n", - "def _exit(msg):\n", - " print(msg, file=sys.stderr)\n", - " sys.exit()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#exports\n", - "def limit_cb(rem,quota):\n", - " \"Callback to warn user when close to using up hourly quota\"\n", - " w='WARNING '*7\n", - " if rem < 1000: print(f\"{w}\\nRemaining calls: {rem} out of {quota}\\n{w}\", file=sys.stderr)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When creating `GhApi` we can pass a callback which will be called after each API operation. In this case, we use it to warn the user when their quota is getting low." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#export\n", - "def _get_api():\n", - " path = Path.home()/\".ghtop_token\"\n", - " if path.is_file():\n", - " try: token = path.read_text().strip()\n", - " except: _exit(\"Error reading token\")\n", - " else: token = github_auth_device()\n", - " path.write_text(token)\n", - " return GhApi(limit_cb=limit_cb, token=token)\n", - "\n", - "api = _get_api()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#export\n", - "Events = dict(\n", - " IssuesEvent_closed=('โญ', 'closed', noop),\n", - " IssuesEvent_opened=('๐Ÿ“ซ', 'opened', noop),\n", - " IssueCommentEvent=('๐Ÿ’ฌ', 'commented on', term.white),\n", - " PullRequestEvent_opened=('โœจ', 'opened a pull request', term.yellow),\n", - " PullRequestEvent_closed=('โœ”', 'closed a pull request', term.green),\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#export\n", - "def _to_log(e):\n", - " login,repo,pay = e.actor.login,e.repo.name,e.payload\n", - " typ = e.type + (f'_{pay.action}' if e.type in ('PullRequestEvent','IssuesEvent') else '')\n", - " emoji,msg,color = Events.get(typ, [0]*3)\n", - " if emoji:\n", - " xtra = '' if e.type == \"PullRequestEvent\" else f' issue # {pay.issue.number}'\n", - " d = try_attrs(pay, \"pull_request\", \"issue\")\n", - " return color(f'{emoji} {login} {msg}{xtra} on repo {repo[:20]} (\"{d.title[:50]}...\")')\n", - " elif e.type == \"ReleaseEvent\": return f'๐Ÿš€ {login} released {e.payload.release.tag_name} of {repo}'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#export\n", - "def print_event(e, commits_counter):\n", - " res = _to_log(e)\n", - " if res: print(res)\n", - " elif e.type == \"PushEvent\": [commits_counter.update() for c in e.payload.commits]\n", - " elif e.type == \"SecurityAdvisoryEvent\": print(term.blink(\"SECURITY ADVISORY\"))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can pretty print a selection of event types using `print_event`, e.g:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[32mโœ” eddiejaoude closed a pull request on repo MLH-Fellowship/batch (\"Add Gabriel Cruz (South America)...\")\u001b[m\n", - "๐Ÿ“ซ PennyJim opened issue # 4125 on repo inorichi/tachiyomi (\"[Bug] loading icon on individual manga doesn't dis...\")\n", - "โญ Sirorco closed issue # 1 on repo Sirorco/Isil-Data-Sc (\"Create Protocol...\")\n", - "โญ unlikelyzero closed issue # 6048 on repo ansible/awx (\"Some Installations of ui_next do not render anythi...\")\n", - "๐Ÿ“ซ fhfuu58 opened issue # 32639 on repo fhfuur26/eghgqzmurn (\"ๆ–—็‰›ๆ€Žไนˆๆด—็‰Œๅฏไปฅ่ฎฉๅบ„ๅฎถ็‰Œๅคง_ๆ‰‘ๅ…‹็‰Œ่ฏป็‰Œๅ™จ...\")\n", - "๐Ÿ“ซ 13260843847 opened issue # 592 on repo trailofbits/bisc (\"้‡ๅบ†ๅธ‚ๅคงๆธกๅฃๅŒบๅฆนๅญ็œŸๅฎžๆ‰พไธŠ้—จๆœๅŠกf...\")\n", - "๐Ÿ“ซ matbesancon opened issue # 16 on repo matbesancon/MathOptS (\"Trait for sets with an interior...\")\n", - "๐Ÿ“ซ 13260843847 opened issue # 593 on repo trailofbits/bisc (\"่Œ‚ๅๆฑฝ่ฝฆ็ซ™ๅ“ชๆœ‰็‰นๆฎŠๆœๅŠก็š„ๆด—ๆตดu...\")\n", - "๐Ÿ“ซ rjake opened issue # 69 on repo rjake/shinyobjects (\"Replace travis CI...\")\n", - "๐Ÿ“ซ jguui25 opened issue # 10034 on repo fdgdfgtdyuyd/xzjsmll (\"ๆˆ้ƒฝๆ‰‹ๆœบๆ‰ซๆๅˆ†ๆžไปช...\")\n", - "๐Ÿš€ kmova released v2.4.0 of openebs/external-storage\n", - "โญ AhmedAlaa23 closed issue # 1 on repo AhmedAlaa23/levity-m (\"feat: add typeCast for the json data type untill i...\")\n", - "๐Ÿš€ kmova released v2.4.0 of openebs/cstor-operators\n", - "โญ Filip62 closed issue # 1236 on repo baskerville/bspwm (\"Bspc quit does not terminate all processes spawned...\")\n", - "โญ pete911 closed issue # 2 on repo pete911/eks-cluster (\"using launch template is failing...\")\n", - "\u001b[33mโœจ Astem-Grey opened a pull request on repo Astem-Grey/Ascept (\"ะ”ะพะผะฐัˆะฝะตะต ะทะฐะดะฐะฝะธะต โ„–8...\")\u001b[m\n", - "\u001b[33mโœจ Kronos3 opened a pull request on repo AutoGentoo/AutoGento (\"Fix parser reduce conflicts...\")\u001b[m\n", - "๐Ÿ“ซ HFYFY2 opened issue # 4562 on repo fyhfuufu258/kfrkpywg (\"ไธดๆฒ‚็‰Œๅ…ทๅฎžไฝ“ๅบ—...\")\n", - "โญ Neo23x0 closed issue # 1303 on repo Neo23x0/sigma (\"Predefined tag \"c2\" in Wiki seems to have fallen o...\")\n", - "๐Ÿ“ซ JorgeLAB opened issue # 123 on repo OBC-HCKTON-GRP03/One (\"Add styles in treatment edit and new form...\")\n" - ] - } - ], - "source": [ - "gen = api.fetch_events(types=('IssuesEvent','ReleaseEvent','PullRequestEvent'))\n", - "for e in islice(gen, 20): print_event(e,None)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#export\n", - "def tail_events(evt):\n", - " \"Print events from `fetch_events` along with a counter of push events\"\n", - " manager = enlighten.get_manager()\n", - " commits = manager.counter(desc='Commits', unit='commits', color='green')\n", - " for ev in evt: print_event(ev, commits)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#export\n", - "def _pr_row(*its): print(f\"{its[0]: <30} {its[1]: <6} {its[2]: <5} {its[3]: <6} {its[4]: <7}\")\n", - "def watch_users(evts):\n", - " \"Print a table of the users with the most events\"\n", - " users,users_events = defaultdict(int),defaultdict(lambda: defaultdict(int))\n", - " while True:\n", - " for x in islice(evts, 10):\n", - " users[x.actor.login] += 1\n", - " users_events[x.actor.login][x.type] += 1\n", - "\n", - " print(term.clear())\n", - " _pr_row(\"User\", \"Events\", \"PRs\", \"Issues\", \"Pushes\")\n", - " sorted_users = sorted(users.items(), key=lambda o: (o[1],o[0]), reverse=True)\n", - " for u in sorted_users[:20]:\n", - " _pr_row(*u, *itemgetter('PullRequestEvent','IssuesEvent','PushEvent')(users_events[u[0]]))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#export\n", - "def _push_to_log(e): return f\"{e.actor.login} pushed {len(e.payload.commits)} commits to repo {e.repo.name}\"\n", - "def _logwin(title,color): return Log(title=title,border_color=2,color=color)\n", - "\n", - "def quad_logs(evts):\n", - " \"Print 4 panels, showing most recent issues, commits, PRs, and releases\"\n", - " term.enter_fullscreen()\n", - " ui = HSplit(VSplit(_logwin('Issues', color=7), _logwin('Commits' , color=3)),\n", - " VSplit(_logwin('Pull Requests', color=4), _logwin('Releases', color=5)))\n", - "\n", - " issues,commits,prs,releases = all_items = ui.items[0].items+ui.items[1].items\n", - " for o in all_items: o.append(\" \")\n", - "\n", - " d = dict(PushEvent=commits, IssuesEvent=issues, IssueCommentEvent=issues, PullRequestEvent=prs, ReleaseEvent=releases)\n", - " while True:\n", - " for x in islice(evts, 10):\n", - " f = [_to_log,_push_to_log][x.type == 'PushEvent']\n", - " if x.type in d: d[x.type].append(f(x)[:95])\n", - " ui.display()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#export\n", - "def simple(evts):\n", - " for ev in evts: print(f\"{ev.actor.login} {ev.type} {ev.repo.name}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#export\n", - "def _signal_handler(sig, frame):\n", - " if sig != signal.SIGINT: return\n", - " print(term.exit_fullscreen(),term.clear(),term.normal)\n", - " sys.exit(0)\n", - "\n", - "_funcs = dict(tail=tail_events, quad=quad_logs, users=watch_users, simple=simple)\n", - "_filts = str_enum('_filts', 'user', 'repo', 'org')\n", - "_OpModes = str_enum('_OpModes', *_funcs)\n", - "\n", - "@call_parse\n", - "def main(mode: Param(\"Operation mode to run\", _OpModes),\n", - " include_bots: Param(\"Include bots (there's a lot of them!)\", store_true)=False,\n", - " types: Param(\"Comma-separated types of event to include (e.g PushEvent)\", str)='',\n", - " filt: Param(\"Filtering method\", _filts)=None,\n", - " filtval: Param(\"Value to filter by (for `repo` use format `owner/repo`)\", str)=None):\n", - " signal.signal(signal.SIGINT, _signal_handler)\n", - " types = types.split(',') if types else None\n", - " if filt and not filtval: _exit(\"Must pass `filter_value` if passing `filter_type`\")\n", - " if filtval and not filt: _exit(\"Must pass `filter_type` if passing `filter_value`\")\n", - " kwargs = {filt:filtval} if filt else {}\n", - " evts = api.fetch_events(types=types, incl_bot=include_bots, **kwargs)\n", - " _funcs[mode](evts)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Export -" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Converted 00_ghtop.ipynb.\n", - "Converted index.ipynb.\n" - ] - } - ], - "source": [ - "#hide\n", - "from nbdev.export import notebook2script\n", - "notebook2script()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/nbs/index.ipynb b/nbs/index.ipynb deleted file mode 100644 index b4b8e22..0000000 --- a/nbs/index.ipynb +++ /dev/null @@ -1,156 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# ghtop\n", - "\n", - "> See what's happening on GitHub in real time (also helpful if you need to use up your API quota as quickly as possible)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`ghtop` provides a number of views of all current public activity from all users across the entire GitHub platform. (Note that GitHub delays all events by five minutes.)\n", - "\n", - "" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Install" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Either `pip install ghtop` or `conda install -c fastai ghtop`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## How to use" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Run `ghtop -h` to view the help:\n", - "\n", - "```bash\n", - "$ ghtop -h\n", - "usage: ghtop [-h] [--include_bots] [--types TYPES] [--filt {user,repo,org}] [--filtval FILTVAL]\n", - " {tail,quad,users,simple}\n", - "\n", - "positional arguments:\n", - " {tail,quad,users,simple} Operation mode to run\n", - "\n", - "optional arguments:\n", - " -h, --help show this help message and exit\n", - " --include_bots Include bots (there is a lot of them!) (default: False)\n", - " --types TYPES Comma-separated types of event to include (e.g PushEvent)\n", - " --filt {user,repo,org} Filtering method\n", - " --filtval FILTVAL Value to filter by (for `repo` use format `owner/repo`)\n", - "```\n", - "\n", - "There are 4 views you can choose: `ghtop simple`, `ghtop tail`, `ghtop quad`, or `ghtop users`. Each are shown and described below. All views have the following options:\n", - "\n", - "- `--include_bots`: By default events that appear to be from bots are excluded. Add this flag to include them\n", - "- `--types TYPES`: Optional comma-separated list of event types to include (defaults to all types). For a full list of types, see the GitHub [event types docs](https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/github-event-types)\n", - "- `--filt` and `--filtval`: Optionally filter events to just those from one of: `user`, `repo`, or `org`, depending on `filt`. `filtval` is the value to filter by. See the [GitHub docs](https://docs.github.com/en/free-pro-team@latest/rest/reference/activity#list-public-events) for details on the public event API calls used.\n", - "\n", - "**Important note**: while running, `ghtop` will make about 5 API calls per second. GitHub has a quota of 5000 calls per hour. When there are 1000 calls left, `ghtop` will show a warning on every call." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### ghtop simple" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A simple dump to your console of all events as they happen.\n", - "\n", - "" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### ghtop tail" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Like `simple`, but removes most bots, and only includes releases, issues and PRs (open, close, and comment events). A summary of the frequency of push events is also shown at the bottom of the screen.\n", - "\n", - "" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### ghtop quad" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The same information as `tail`, but in a split window showing separately PRs, issues, pushes, and releases. This view does not remove bot activity.\n", - "\n", - "" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### ghtop users" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A summary of activity for the most active current users.\n", - "\n", - "\n", - "\n", - "----" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Shared under the MIT license with โ™ฅ by @nat" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/nbs/rich_test.ipynb b/nbs/rich_test.ipynb deleted file mode 100644 index 82525bc..0000000 --- a/nbs/rich_test.ipynb +++ /dev/null @@ -1,169 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from rich import print as pr\n", - "from rich.panel import Panel\n", - "from rich.console import RenderGroup\n", - "from rich.table import Table\n", - "from rich.live import Live\n", - "\n", - "import time" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from collections import deque\n", - "import random\n", - "\n", - "class Deque(deque):\n", - " def __rich__(self): return '\\n'.join(map(str,self))\n", - "\n", - "def grid(*rows):\n", - " g = Table.grid(expand=True)\n", - " for c in rows[0]: g.add_column()\n", - " for r in rows: g.add_row(*r)\n", - " return g" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def quad(d):\n", - " ps = [Panel(v, title=k) for k,v in d.items()]\n", - " return RenderGroup(\n", - " grid(ps[:2], ps[2:]),\n", - " '# commits: ...'\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Issues โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎโ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ PRs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ\n",
-       "โ”‚ 20                                          โ”‚โ”‚ 33                                         โ”‚\n",
-       "โ”‚ 29                                          โ”‚โ”‚ 37                                         โ”‚\n",
-       "โ”‚ 31                                          โ”‚โ”‚ 40                                         โ”‚\n",
-       "โ”‚ 38                                          โ”‚โ”‚ 45                                         โ”‚\n",
-       "โ”‚ 49                                          โ”‚โ”‚ 48                                         โ”‚\n",
-       "โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏโ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ\n",
-       "โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Commits โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎโ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Releases โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ\n",
-       "โ”‚ 39                                          โ”‚โ”‚ 34                                         โ”‚\n",
-       "โ”‚ 41                                          โ”‚โ”‚ 35                                         โ”‚\n",
-       "โ”‚ 43                                          โ”‚โ”‚ 36                                         โ”‚\n",
-       "โ”‚ 46                                          โ”‚โ”‚ 42                                         โ”‚\n",
-       "โ”‚ 47                                          โ”‚โ”‚ 44                                         โ”‚\n",
-       "โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏโ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ\n",
-       "# commits: ...\n",
-       "
\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "types = 'Issues','PRs','Commits','Releases'\n", - "ds = {o:Deque(maxlen=5) for o in types}\n", - "for o in range(50): ds[types[random.randint(0,3)]].append(o)\n", - "pr(quad(ds))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "19da5a6dafcb45f88efe1c4b2a9cdd13", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Output()" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "ds = {o:Deque(maxlen=5) for o in types}\n", - "with Live(refresh_per_second=4) as live:\n", - " for o in range(40):\n", - " ds[types[random.randint(0,3)]].append(o)\n", - " time.sleep(0.1)\n", - " live.update(quad(ds))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "e354517320d84ef5baf8086459c0ffa3", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Output()" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from rich.progress import Progress\n", - "\n", - "with Progress() as progress:\n", - " task1 = progress.add_task(\"[red]Downloading...\", total=20)\n", - " task2 = progress.add_task(\"[green]Processing...\", total=20)\n", - " while not progress.finished:\n", - " progress.update(task1, advance=0.5, refresh=True)\n", - " progress.update(task2, advance=0.3, refresh=True)\n", - " time.sleep(0.02)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} From 114a7ff8ae4061fe5da3f6945f7a94cad674ced8 Mon Sep 17 00:00:00 2001 From: hamelsmu Date: Sat, 6 Feb 2021 18:21:06 -0800 Subject: [PATCH 2/3] removed deps --- ghtop/ghtop.py | 42 ++++++++---------------------------------- settings.ini | 2 +- 2 files changed, 9 insertions(+), 35 deletions(-) diff --git a/ghtop/ghtop.py b/ghtop/ghtop.py index 6bcff0b..e2fa5ed 100644 --- a/ghtop/ghtop.py +++ b/ghtop/ghtop.py @@ -1,15 +1,14 @@ -__all__ = ['get_sparklines', 'ETYPES', 'term', 'tdim', 'limit_cb', 'Events', 'print_event', 'pct_comp', 'tail_events', - 'watch_users', 'quad_logs', 'simple', 'main'] +__all__ = ['get_sparklines', 'ETYPES', 'term', 'tdim', 'limit_cb', 'pct_comp', 'tail_events', 'watch_users', + 'quad_logs', 'simple', 'main'] -import sys, signal, shutil, os, json, emoji, enlighten -from dashing import * +import sys, signal, shutil, os, json +# from dashing import * from collections import defaultdict from warnings import warn from itertools import islice - from fastcore.utils import * from fastcore.foundation import * from fastcore.script import * @@ -18,6 +17,8 @@ from .all_rich import (Console, Color, FixedPanel, box, Segments, Live, grid, ConsoleOptions, Progress, BarColumn, Spinner, Table) + + ETYPES=PushEvent,PullRequestEvent,IssuesEvent,ReleaseEvent def get_sparklines(): @@ -30,7 +31,7 @@ def get_sparklines(): return Stats([s1,s2,s3,s4,s5], store=5, span=5, spn_lbl='5/s', show_freq=True) -term = Terminal() +term = Console() tdim = L(os.popen('stty size', 'r').read().split()) if not tdim: theight,twidth = 15,15 @@ -48,33 +49,6 @@ def limit_cb(rem,quota): if rem < 1000: print(f"{w}\nRemaining calls: {rem} out of {quota}\n{w}", file=sys.stderr) -Events = dict( - IssuesEvent_closed=('โญ', 'closed', noop), - IssuesEvent_opened=('๐Ÿ“ซ', 'opened', noop), - IssueCommentEvent=('๐Ÿ’ฌ', 'commented on', term.white), - PullRequestEvent_opened=('โœจ', 'opened a pull request', term.yellow), - PullRequestEvent_closed=('โœ”', 'closed a pull request', term.green), -) - - -def _to_log(e): - login,repo,pay = e.actor.login,e.repo.name,e.payload - typ = e.type + (f'_{pay.action}' if e.type in ('PullRequestEvent','IssuesEvent') else '') - emoji,msg,color = Events.get(typ, [0]*3) - if emoji: - xtra = '' if e.type == "PullRequestEvent" else f' issue # {pay.issue.number}' - d = try_attrs(pay, "pull_request", "issue") - return color(f'{emoji} {login} {msg}{xtra} on repo {repo[:20]} ("{d.title[:50]}...")') - elif e.type == "ReleaseEvent": return f'๐Ÿš€ {login} released {e.payload.release.tag_name} of {repo}' - - -def print_event(e, counter): - res = _to_log(e) - if res: print(res) - elif counter and e.type == "PushEvent": [counter.update() for c in e.payload.commits] - elif e.type == "SecurityAdvisoryEvent": print(term.blink("SECURITY ADVISORY")) - - def pct_comp(api): return int(((5000-int(api.limit_rem)) / 5000) * 100) @@ -163,7 +137,7 @@ def _get_token(): def _signal_handler(sig, frame): if sig != signal.SIGINT: return - print(term.exit_fullscreen(),term.clear(),term.normal) + term.clear() sys.exit(0) _funcs = dict(tail=tail_events, quad=quad_logs, users=watch_users, simple=simple) diff --git a/settings.ini b/settings.ini index f5526eb..8c20a56 100644 --- a/settings.ini +++ b/settings.ini @@ -12,7 +12,7 @@ audience = Developers language = English license = mit status = 2 -requirements = emoji enlighten py-dashing fastcore ghapi>0.1.9 rich +requirements = fastcore ghapi>0.1.9 rich console_scripts = ghtop=ghtop.ghtop:main branch = master custom_sidebar = False From 7e31099820ce2dc06f63a0cb40d2cd9d93cbf2af Mon Sep 17 00:00:00 2001 From: hamelsmu Date: Sat, 6 Feb 2021 18:25:10 -0800 Subject: [PATCH 3/3] remove comment --- ghtop/ghtop.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ghtop/ghtop.py b/ghtop/ghtop.py index e2fa5ed..daf087e 100644 --- a/ghtop/ghtop.py +++ b/ghtop/ghtop.py @@ -5,7 +5,6 @@ import sys, signal, shutil, os, json -# from dashing import * from collections import defaultdict from warnings import warn from itertools import islice