import datetime as dt from html import escape from src.constants import ( C_ACCENT, C_BAR, C_ERROR, C_LABEL, C_LINK, C_MUTED, C_TEXT, FONT_FAMILY, FONT_SIZE, HISTOGRAM_HEIGHT, LANGUAGE_BAR_WIDTH, MIN_LANGUAGE_SHARE, PROGRESS_WIDTH, ) _BLOCKS: str = " ▁▂▃▄▅▆▇█" def _c(text: str, color: str, bold: bool = False, italic: bool = False) -> str: style = f"color:{color}" if bold: style += ";font-weight:bold" if italic: style += ";font-style:italic" return f'{text}' def _pre(text: str, tight: bool = False) -> str: lh = "1" if tight else "1.6" style = ( f"font-family:{FONT_FAMILY};" f"font-size:{FONT_SIZE};line-height:{lh};color:{C_TEXT};background:transparent;" "padding:0;border:none;margin:0;white-space:pre-wrap;word-break:break-word" ) return f'
{text}
' def _pad(text: str, visual_len: int, width: int) -> str: return text + " " * max(0, width - visual_len) def _bucket_histogram(counts: list[int], width: int) -> list[int]: n = len(counts) if n == 0 or width <= 0: return [] if n >= width: return [sum(counts[j * n // width : (j + 1) * n // width]) for j in range(width)] return [counts[j * n // width] for j in range(width)] def _ts_to_iso(ts: int) -> str: return (dt.datetime(1970, 1, 1) + dt.timedelta(seconds=ts)).date().isoformat() def _render_histogram_block(histogram: list[tuple[int, int]], width: int) -> str: if not histogram or width <= 0: return "" nonzero = [i for i, (_, c) in enumerate(histogram) if c > 0] if not nonzero: return "" trimmed = histogram[nonzero[0] : nonzero[-1] + 1] counts = [c for _, c in trimmed] buckets = _bucket_histogram(counts, width) max_count = max(buckets) if buckets else 0 if max_count == 0: return "" total_eighths = HISTOGRAM_HEIGHT * 8 heights = [round(b / max_count * total_eighths) for b in buckets] first_iso = _ts_to_iso(trimmed[0][0]) mid_iso = _ts_to_iso((trimmed[0][0] + trimmed[-1][0]) // 2) last_iso = _ts_to_iso(trimmed[-1][0]) title = "commit history " top_dashes = max(0, width - len(title)) lines: list[str] = [ "", "", " " + _c(title + "─" * top_dashes, C_MUTED), ] for row in range(HISTOGRAM_HEIGHT - 1, -1, -1): chars = "".join(_BLOCKS[max(0, min(8, h - row * 8))] for h in heights) lines.append(" " + _c(chars, C_BAR)) total_gap = max(2, width - len(first_iso) - len(mid_iso) - len(last_iso)) left_gap = total_gap // 2 right_gap = total_gap - left_gap lines.append("") lines.append( " " + _c(f"{first_iso}{' ' * left_gap}{mid_iso}{' ' * right_gap}{last_iso}", C_MUTED) ) return _pre("\n".join(lines), tight=True) def render_html( languages: dict[str, int], owner: str = "", repo: str = "", completed: int = 0, total: int = 0, commits: int | None = None, branches: int | None = None, histogram: list[tuple[int, int]] | None = None, ) -> str: sep = _c("//", C_MUTED) g1, g2, g3 = " ", " ", " " total_loc = sum(languages.values()) has_langs = bool(languages) and total_loc > 0 sorted_langs: list[tuple[str, int]] = [] if has_langs: main = {k: v for k, v in languages.items() if v / total_loc >= MIN_LANGUAGE_SHARE} other = total_loc - sum(main.values()) if other > 0: main["Other"] = other sorted_langs = sorted(main.items(), key=lambda x: (x[0] == "Other", -x[1])) lw = max(len(k) for k in main) nw = max(len(f"{v:,}") for v in main.values()) else: lw, nw = 10, 7 row_width = lw + len(g1) + LANGUAGE_BAR_WIDTH + len(g2) + 7 + len(g3) + nw lines: list[str] = ["", ""] stats_title = "stats " lines.append(" " + _c(stats_title + "─" * max(0, row_width - len(stats_title)), C_MUTED)) if owner and repo: repo_url = f"https://github.com/{escape(owner)}/{escape(repo)}" repo_link = ( f'{repo_url}' ) lines.append(f" URL:{repo_link}") if total: done = completed >= total pct = 1.0 if done else completed / total filled = PROGRESS_WIDTH if done else round(pct * PROGRESS_WIDTH) pbar = _c("#" * filled, C_BAR) + _c("." * (PROGRESS_WIDTH - filled), C_MUTED) pct_str = _c("100%" if done else f"{pct:.0%}", C_ACCENT) lines.append(f" [{pbar}] {pct_str}") if not has_langs: if owner or total: return _pre("\n".join(lines)) return _error_html("No code files found in this repository.") stats_parts: list[str] = [""] if branches is not None: stats_parts.append(_c(f"{branches:,} branches", C_TEXT)) if commits is not None: stats_parts.append(_c(f"{commits:,} commits", C_TEXT)) if total: stats_parts.append(_c(f"{total:,} files", C_TEXT)) stats_parts.append(_c(f"{total_loc:,} lines of code", C_TEXT)) lines += [ f" {sep} ".join(stats_parts), "", ] dashes = max(0, row_width - len("language") - len("lines") - 2) hdr = _c(f"language {'─' * dashes} lines", C_MUTED) lines.append(" " + hdr) for lang, loc in sorted_langs: pct = loc / total_loc filled = round(pct * LANGUAGE_BAR_WIDTH) bar = _c("#" * filled, C_BAR) + _c("." * (LANGUAGE_BAR_WIDTH - filled), C_MUTED) lang_col = _pad(_c(escape(lang), C_LABEL), len(lang), lw) pct_str = _c(f"{pct:>7.2%}", C_ACCENT) loc_str = _c(f"{loc:>{nw},}", C_TEXT) lines.append(f" {lang_col}{g1}{bar}{g2}{pct_str}{g3}{loc_str}") if histogram: lines.append("\n") chart_html = _pre("\n".join(lines)) return chart_html + _render_histogram_block(histogram, row_width) return _pre("\n".join(lines)) def _error_html(message: str) -> str: lines = [ f"{_c('error:', C_ERROR, bold=True)} {_c(escape(message), C_TEXT)}", ] return _pre("\n".join(lines))