1+ import shutil
2+
13from .constants import BOLD , GREY , RESET , PR_STATE_COLORS , STATE_COLORS
24from .render_common import age_t , days_ago , group_by_repo , plasma_rgb , pr_state
35
6+ # Visible chars on the advisory line excluding title:
7+ # 4(connector) + 3(badge) + 2 + 9(state) + 2 = 20
8+ _FIXED_OVERHEAD = 20
9+ _NVD_URL = "https://nvd.nist.gov/vuln/detail/{}"
10+
411
512def _link (text , target ):
613 return f"\033 [4m\033 ]8;;{ target } \033 \\ { text } \033 ]8;;\033 \\ \033 [24m"
@@ -10,6 +17,22 @@ def _color(text, c):
1017 return f"{ c } { text } { RESET } "
1118
1219
20+ def _dim (text ):
21+ return _color (text , GREY )
22+
23+
24+ def _fmt_age (days ):
25+ if days < 7 :
26+ s = f"{ days } d"
27+ elif days < 30 :
28+ s = f"{ days // 7 } w"
29+ elif days < 365 :
30+ s = f"{ days // 30 } m"
31+ else :
32+ s = f"{ days // 365 } y"
33+ return s .rjust (3 )
34+
35+
1336def _age_badge (date_str , text ):
1437 t = age_t (date_str ) if date_str else 0.0
1538 r , g , b = plasma_rgb (t )
@@ -20,42 +43,64 @@ def _age_badge(date_str, text):
2043
2144
2245def render_org (org , states , results_by_key , pull_results = None , redact = False ):
46+ term_w = shutil .get_terminal_size (fallback = (120 , 24 )).columns
47+ title_w = max (10 , term_w - _FIXED_OVERHEAD )
2348 advisories = []
2449 for state in states :
2550 advisories .extend (results_by_key .get ((org , state ), []))
2651
2752 lines = [f"\n { BOLD } === { org if not redact else 'REDACTED ORG' } ==={ RESET } " ]
2853
2954 if not advisories :
30- if not redact :
31- lines .append (" (no advisories)" )
32- return "\n " .join (lines )
55+ return ""
3356
57+ first_repo = True
3458 for repo , items in group_by_repo (advisories ):
3559 items = sorted (items , key = lambda a : a .get ("updated_at" , "" ), reverse = True )
60+ if not items :
61+ continue
62+ if not first_repo :
63+ lines .append ("" )
64+ first_repo = False
65+
3666 lines .append (f"{ BOLD } { repo if not redact else 'REDACTED REPO' } { RESET } " )
37- for advisory in items :
38- id_ = advisory .get ("ghsa_id" , "" ) if not redact else "GHSA-xxxx-yyyy-zzzz"
67+ for i , advisory in enumerate (items ):
68+ is_last = i == len (items ) - 1
69+ connector = _dim ("└── " if is_last else "├── " )
70+ # prefix for sub-lines: continue the vertical bar only if more advisories follow
71+ sub_prefix = " " if is_last else "│ "
72+
3973 url = advisory .get ("html_url" , "" )
4074 date = advisory .get ("updated_at" , "" )
41- title = (advisory .get ("summary" ) or "" )[:40 ].ljust (40 )
4275 state_val = advisory .get ("state" ) or "?"
4376 state_str = _color (state_val .ljust (9 ), STATE_COLORS .get (state_val , "" ))
4477 cve = advisory .get ("cve_id" ) or ""
45- cve_str = _color (cve [:15 ].ljust (15 ), GREY )
4678 fork = advisory .get ("private_fork" )
4779 fork_html_url = fork .get ("html_url" ) if fork else None
48- first_pull = (pull_results or {}).get (fork_html_url ) if fork_html_url else None
49- ps = pr_state (first_pull )
50- pull_str = (
51- _color (_link ("PR " + ps .ljust (6 ), first_pull ["html_url" ]), PR_STATE_COLORS .get (ps , GREY ))
52- if first_pull else " " * 9
53- )
80+ pulls = (pull_results or {}).get (fork_html_url , []) if fork_html_url else []
81+ title = (advisory .get ("summary" ) or "" )[:title_w ]
5482 d = days_ago (date )
55- badge = _age_badge (date , (str (d ) + "d" ).rjust (5 ))
83+ badge = _age_badge (date , _fmt_age (d ))
84+
5685 if redact :
57- lines .append (f" { badge } \t { id_ } { state_str } " )
86+ lines .append (f"{ connector } { badge } { state_str } " )
5887 else :
59- lines .append (f" { badge } \t { state_str } { _color (_link (id_ , url ), GREY )} { title } { cve_str } { pull_str } " )
88+ lines .append (f"{ connector } { badge } { state_str } { _link (title , url )} " )
89+
90+ sub_items = []
91+ if cve :
92+ sub_items .append (_link (cve , _NVD_URL .format (cve )))
93+ if pulls :
94+ pr_items = []
95+ for p in pulls :
96+ ps = pr_state (p )
97+ label = "opened" if ps == "open" else ps
98+ pr_items .append (_color (_link (f"#{ p ['number' ]} ({ label } )" , p ["html_url" ]), PR_STATE_COLORS .get (ps , GREY )))
99+ sub_items .append ("PRs: " + ", " .join (pr_items ))
100+
101+ for j , sub_item in enumerate (sub_items ):
102+ is_last_sub = j == len (sub_items ) - 1
103+ sub_connector = _dim (sub_prefix + ("└── " if is_last_sub else "├── " ))
104+ lines .append (f"{ sub_connector } { sub_item } " )
60105
61106 return "\n " .join (lines )
0 commit comments