Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dff4ff6757 | ||
|
|
6ea59bad51 | ||
|
|
4b1e1eb84c | ||
|
|
518188288e | ||
|
|
15547b2796 | ||
|
|
9f3df4899f | ||
|
|
bc1ee87dfe | ||
|
|
0d61bbf7f5 |
57
git.py
57
git.py
@ -6,6 +6,7 @@ command line tool directly, so please be careful with using untrusted
|
||||
parameters.
|
||||
"""
|
||||
|
||||
import functools
|
||||
import sys
|
||||
import io
|
||||
import subprocess
|
||||
@ -199,7 +200,8 @@ class Repo:
|
||||
"""Returns a GitCommand() on our path."""
|
||||
return GitCommand(self.path, cmd)
|
||||
|
||||
def for_each_ref(self, pattern=None, sort=None, count=None):
|
||||
@functools.lru_cache
|
||||
def _for_each_ref(self, pattern=None, sort=None, count=None):
|
||||
"""Returns a list of references."""
|
||||
cmd = self.cmd("for-each-ref")
|
||||
if sort:
|
||||
@ -209,26 +211,25 @@ class Repo:
|
||||
if pattern:
|
||||
cmd.arg(pattern)
|
||||
|
||||
refs = []
|
||||
for l in cmd.run():
|
||||
obj_id, obj_type, ref = l.split()
|
||||
yield obj_id, obj_type, ref
|
||||
|
||||
def branches(self, sort="-authordate"):
|
||||
"""Get the (name, obj_id) of the branches."""
|
||||
refs = self.for_each_ref(pattern="refs/heads/", sort=sort)
|
||||
for obj_id, _, ref in refs:
|
||||
yield ref[len("refs/heads/") :], obj_id
|
||||
refs.append((obj_id, obj_type, ref))
|
||||
return refs
|
||||
|
||||
@functools.cache
|
||||
def branch_names(self):
|
||||
"""Get the names of the branches."""
|
||||
return (name for name, _ in self.branches())
|
||||
refs = self._for_each_ref(pattern="refs/heads/", sort="-authordate")
|
||||
return [ref[len("refs/heads/") :] for _, _, ref in refs]
|
||||
|
||||
@functools.cache
|
||||
def tags(self, sort="-taggerdate"):
|
||||
"""Get the (name, obj_id) of the tags."""
|
||||
refs = self.for_each_ref(pattern="refs/tags/", sort=sort)
|
||||
for obj_id, _, ref in refs:
|
||||
yield ref[len("refs/tags/") :], obj_id
|
||||
refs = self._for_each_ref(pattern="refs/tags/", sort=sort)
|
||||
return [(ref[len("refs/tags/") :], obj_id) for obj_id, _, ref in refs]
|
||||
|
||||
@functools.lru_cache
|
||||
def commit_ids(self, ref, limit=None):
|
||||
"""Generate commit ids."""
|
||||
cmd = self.cmd("rev-list")
|
||||
@ -238,9 +239,9 @@ class Repo:
|
||||
cmd.arg(ref)
|
||||
cmd.arg("--")
|
||||
|
||||
for l in cmd.run():
|
||||
yield l.rstrip("\n")
|
||||
return [l.rstrip("\n") for l in cmd.run()]
|
||||
|
||||
@functools.lru_cache
|
||||
def commit(self, commit_id):
|
||||
"""Return a single commit."""
|
||||
cs = list(self.commits(commit_id, limit=1))
|
||||
@ -248,11 +249,11 @@ class Repo:
|
||||
return None
|
||||
return cs[0]
|
||||
|
||||
def commits(self, ref, limit=None, offset=0):
|
||||
@functools.lru_cache
|
||||
def commits(self, ref, limit, offset=0):
|
||||
"""Generate commit objects for the ref."""
|
||||
cmd = self.cmd("rev-list")
|
||||
if limit:
|
||||
cmd.max_count = limit + offset
|
||||
cmd.max_count = limit + offset
|
||||
|
||||
cmd.header = None
|
||||
|
||||
@ -261,6 +262,7 @@ class Repo:
|
||||
|
||||
info_buffer = ""
|
||||
count = 0
|
||||
commits = []
|
||||
for l in cmd.run():
|
||||
if "\0" in l:
|
||||
pre, post = l.split("\0", 1)
|
||||
@ -268,7 +270,7 @@ class Repo:
|
||||
|
||||
count += 1
|
||||
if count > offset:
|
||||
yield Commit.from_str(self, info_buffer)
|
||||
commits.append(Commit.from_str(self, info_buffer))
|
||||
|
||||
# Start over.
|
||||
info_buffer = post
|
||||
@ -278,8 +280,11 @@ class Repo:
|
||||
if info_buffer:
|
||||
count += 1
|
||||
if count > offset:
|
||||
yield Commit.from_str(self, info_buffer)
|
||||
commits.append(Commit.from_str(self, info_buffer))
|
||||
|
||||
return commits
|
||||
|
||||
@functools.lru_cache
|
||||
def diff(self, ref):
|
||||
"""Return a Diff object for the ref."""
|
||||
cmd = self.cmd("diff-tree")
|
||||
@ -295,6 +300,7 @@ class Repo:
|
||||
|
||||
return Diff.from_str(cmd.run())
|
||||
|
||||
@functools.lru_cache
|
||||
def refs(self):
|
||||
"""Return a dict of obj_id -> ref."""
|
||||
cmd = self.cmd("show-ref")
|
||||
@ -308,10 +314,12 @@ class Repo:
|
||||
|
||||
return r
|
||||
|
||||
@functools.lru_cache
|
||||
def tree(self, ref):
|
||||
"""Returns a Tree instance for the given ref."""
|
||||
return Tree(self, ref)
|
||||
|
||||
@functools.lru_cache
|
||||
def blob(self, path, ref):
|
||||
"""Returns a Blob instance for the given path."""
|
||||
cmd = self.cmd("cat-file")
|
||||
@ -329,9 +337,10 @@ class Repo:
|
||||
|
||||
return Blob(out.read()[: int(head)])
|
||||
|
||||
@functools.cache
|
||||
def last_commit_timestamp(self):
|
||||
"""Return the timestamp of the last commit."""
|
||||
refs = self.for_each_ref(
|
||||
refs = self._for_each_ref(
|
||||
pattern="refs/heads/", sort="-committerdate", count=1
|
||||
)
|
||||
for obj_id, _, _ in refs:
|
||||
@ -515,12 +524,13 @@ class Diff:
|
||||
|
||||
|
||||
class Tree:
|
||||
""" A git tree."""
|
||||
"""A git tree."""
|
||||
|
||||
def __init__(self, repo: Repo, ref: str):
|
||||
self.repo = repo
|
||||
self.ref = ref
|
||||
|
||||
@functools.lru_cache
|
||||
def ls(
|
||||
self, path, recursive=False
|
||||
) -> Iterable[Tuple[str, smstr, Optional[int]]]:
|
||||
@ -537,6 +547,7 @@ class Tree:
|
||||
else:
|
||||
cmd.arg(path)
|
||||
|
||||
files = []
|
||||
for l in cmd.run():
|
||||
_mode, otype, _oid, size, name = l.split(None, 4)
|
||||
if size == "-":
|
||||
@ -553,7 +564,9 @@ class Tree:
|
||||
|
||||
# We use a smart string for the name, as it's often tricky to
|
||||
# manipulate otherwise.
|
||||
yield otype, smstr(name), size
|
||||
files.append((otype, smstr(name), size))
|
||||
|
||||
return files
|
||||
|
||||
|
||||
class Blob:
|
||||
|
||||
@ -1,15 +1,53 @@
|
||||
|
||||
/*
|
||||
* git-arr style sheet
|
||||
*/
|
||||
:root {
|
||||
--body-bg: white;
|
||||
--text-fg: black;
|
||||
--h1-bg: #ddd;
|
||||
--hr-bg: #e3e3e3;
|
||||
--text-lowcontrast-fg: grey;
|
||||
--a-fg: #800;
|
||||
--a-explicit-fg: #038;
|
||||
--table-hover-bg: #eee;
|
||||
--head-bg: #88ff88;
|
||||
--tag-bg: #ffff88;
|
||||
--age-fg0: darkgreen;
|
||||
--age-fg1: green;
|
||||
--age-fg2: seagreen;
|
||||
--diff-added-fg: green;
|
||||
--diff-deleted-fg: red;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--body-bg: #121212;
|
||||
--text-fg: #c9d1d9;
|
||||
--h1-bg: #2f2f2f;
|
||||
--hr-bg: #e3e3e3;
|
||||
--text-lowcontrast-fg: grey;
|
||||
--a-fg: #d4b263;
|
||||
--a-explicit-fg: #44b4ec;
|
||||
--table-hover-bg: #313131;
|
||||
--head-bg: #020;
|
||||
--tag-bg: #333000;
|
||||
--age-fg0: #51a552;
|
||||
--age-fg1: #468646;
|
||||
--age-fg2: #2f722f;
|
||||
--diff-added-fg: #00A000;
|
||||
--diff-deleted-fg: #A00000;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
padding: 0 1em 1em 1em;
|
||||
color: var(--text-fg);
|
||||
background: var(--body-bg);
|
||||
}
|
||||
|
||||
h1 {
|
||||
background: #ddd;
|
||||
background: var(--h1-bg);
|
||||
padding: 0.3em;
|
||||
}
|
||||
|
||||
@ -21,26 +59,29 @@ h2, h3 {
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
background-color: #e3e3e3;
|
||||
background-color: var(--hr-bg);
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
|
||||
/* By default, use implied links, more discrete for increased readability. */
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
color: var(--text-fg);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
color: #800;
|
||||
color: var(--a-fg);
|
||||
}
|
||||
|
||||
|
||||
/* Explicit links */
|
||||
a.explicit {
|
||||
color: #038;
|
||||
color: var(--a-explicit-fg);
|
||||
}
|
||||
|
||||
a.explicit:hover, a.explicit:active {
|
||||
color: #880000;
|
||||
color: var(--a-fg);
|
||||
}
|
||||
|
||||
|
||||
@ -48,22 +89,27 @@ a.explicit:hover, a.explicit:active {
|
||||
table.nice {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
table.nice td {
|
||||
padding: 0.15em 0.5em;
|
||||
}
|
||||
|
||||
table.nice td.links {
|
||||
}
|
||||
|
||||
table.nice td.main {
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
table.nice tr:hover {
|
||||
background: #eee;
|
||||
background: var(--table-hover-bg);
|
||||
}
|
||||
|
||||
|
||||
/* Table for commits. */
|
||||
table.commits td.date {
|
||||
font-style: italic;
|
||||
color: gray;
|
||||
color: var(--text-lowcontrast-fg);
|
||||
}
|
||||
|
||||
@media (min-width: 600px) {
|
||||
@ -71,106 +117,134 @@ table.commits td.date {
|
||||
min-width: 32em;
|
||||
}
|
||||
}
|
||||
|
||||
table.commits td.author {
|
||||
color: gray;
|
||||
color: var(--text-lowcontrast-fg);
|
||||
}
|
||||
|
||||
|
||||
/* Table for commit information. */
|
||||
table.commit-info tr:hover {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
table.commit-info td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
table.commit-info span.date, span.email {
|
||||
color: gray;
|
||||
color: var(--text-lowcontrast-fg);
|
||||
}
|
||||
|
||||
|
||||
/* Reference annotations. */
|
||||
span.refs {
|
||||
margin: 0px 0.5em;
|
||||
padding: 0px 0.25em;
|
||||
border: solid 1px gray;
|
||||
border: solid 1px var(--text-lowcontrast-fg);
|
||||
}
|
||||
|
||||
span.head {
|
||||
background-color: #88ff88;
|
||||
background-color: var(--head-bg);
|
||||
}
|
||||
|
||||
span.tag {
|
||||
background-color: #ffff88;
|
||||
background-color: var(--tag-bg);
|
||||
}
|
||||
|
||||
|
||||
/* Projects table */
|
||||
table.projects td.name a {
|
||||
color: #038;
|
||||
color: var(--a-explicit-fg);
|
||||
}
|
||||
|
||||
|
||||
/* Age of an object.
|
||||
* Note this is hidden by default as we rely on javascript to show it. */
|
||||
span.age {
|
||||
display: none;
|
||||
color: gray;
|
||||
color: var(--text-lowcontrast-fg);
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
span.age-band0 {
|
||||
color: darkgreen;
|
||||
color: var(--age-fg0);
|
||||
}
|
||||
|
||||
span.age-band1 {
|
||||
color: green;
|
||||
color: var(--age-fg1);
|
||||
}
|
||||
|
||||
span.age-band2 {
|
||||
color: seagreen;
|
||||
color: var(--age-fg2);
|
||||
}
|
||||
|
||||
|
||||
/* Toggable titles */
|
||||
div.toggable-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
|
||||
pre {
|
||||
/* Sometimes, <pre> elements (commit messages, diffs, blobs) have very
|
||||
* long lines. In those case, use automatic overflow, which will
|
||||
* introduce a horizontal scroll bar for this element only (more
|
||||
* comfortable than stretching the page, which is the default). */
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
||||
/* Commit message and diff. */
|
||||
pre.commit-message {
|
||||
font-size: large;
|
||||
padding: 0.2em 2em;
|
||||
padding: 0.2em 0.5em;
|
||||
}
|
||||
|
||||
pre.diff-body {
|
||||
/* Note this is only used as a fallback if pygments is not available. */
|
||||
}
|
||||
|
||||
table.changed-files {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
table.changed-files span.lines-added {
|
||||
color: green;
|
||||
color: var(--diff-added-fg);
|
||||
}
|
||||
|
||||
table.changed-files span.lines-deleted {
|
||||
color: red;
|
||||
color: var(--diff-deleted-fg);
|
||||
}
|
||||
|
||||
|
||||
/* Pagination. */
|
||||
div.paginate {
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
div.paginate span.inactive {
|
||||
color: gray;
|
||||
color: var(--text-lowcontrast-fg);
|
||||
}
|
||||
|
||||
|
||||
/* Directory listing. */
|
||||
@media (min-width: 600px) {
|
||||
table.ls td.name {
|
||||
min-width: 20em;
|
||||
}
|
||||
}
|
||||
|
||||
table.ls {
|
||||
font-family: monospace;
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
table.ls tr.blob td.size {
|
||||
color: gray;
|
||||
color: var(--text-lowcontrast-fg);
|
||||
}
|
||||
|
||||
|
||||
/* Blob. */
|
||||
pre.blob-body {
|
||||
/* Note this is only used as a fallback if pygments is not available. */
|
||||
@ -184,60 +258,79 @@ table.blob-binary pre {
|
||||
table.blob-binary .offset {
|
||||
text-align: right;
|
||||
font-size: x-small;
|
||||
color: darkgray;
|
||||
border-right: 1px solid #eee;
|
||||
color: var(--text-lowcontrast-fg);
|
||||
border-right: 1px solid var(--text-lowcontrast-fg);
|
||||
}
|
||||
|
||||
table.blob-binary tr.etc {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
/* Pygments overrides. */
|
||||
div.linenodiv {
|
||||
padding-right: 0.5em;
|
||||
font-size: larger; /* must match div.source_code */
|
||||
}
|
||||
div.linenodiv a {
|
||||
color: gray;
|
||||
}
|
||||
div.source_code {
|
||||
background: inherit;
|
||||
div.colorized-src {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
div.colorized-src .source_code {
|
||||
/* Ignore pygments style's background. */
|
||||
background: var(--body-bg);
|
||||
}
|
||||
|
||||
td.code > div.source_code {
|
||||
/* This is a workaround, in pygments 2.11 there's a bug where the wrapper
|
||||
* div is inside the table, so we need to override the descendant (because
|
||||
* the style sets it on ".source_code" and the most specific value wins).
|
||||
* Once we no longer support 2.11, we can remove this. */
|
||||
background: var(--body-bg);
|
||||
}
|
||||
|
||||
div.linenodiv {
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
|
||||
div.linenodiv a {
|
||||
color: var(--text-lowcontrast-fg);
|
||||
}
|
||||
|
||||
|
||||
/* Repository information table. */
|
||||
table.repo_info tr:hover {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
table.repo_info td.category {
|
||||
font-weight: bold;
|
||||
/* So we can copy-paste rows and preserve spaces, useful for the row:
|
||||
* git clone | url
|
||||
*/
|
||||
* git clone | url */
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
table.repo_info td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
span.ctrlchr {
|
||||
color: gray;
|
||||
color: var(--text-lowcontrast-fg);
|
||||
padding: 0 0.2ex 0 0.1ex;
|
||||
margin: 0 0.2ex 0 0.1ex;
|
||||
margin: 0 0.2ex 0 0.1ex;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Markdown overrides
|
||||
*/
|
||||
|
||||
/* Colored links (same as explicit links above) */
|
||||
div.markdown a {
|
||||
color: #038;
|
||||
color: var(--a-explicit-fg);
|
||||
}
|
||||
|
||||
div.markdown a:hover, div.markdown a:active {
|
||||
color: #880000;
|
||||
color: var(--a-fg);
|
||||
}
|
||||
|
||||
|
||||
/* Restrict max width for readability */
|
||||
div.markdown {
|
||||
max-width: 55em;
|
||||
|
||||
@ -1,30 +1,37 @@
|
||||
|
||||
/* CSS for syntax highlighting.
|
||||
* Generated by pygments (what we use for syntax highlighting):
|
||||
* Generated by pygments (what we use for syntax highlighting).
|
||||
*
|
||||
* $ pygmentize -S default -f html -a .source_code
|
||||
* Light mode: pygmentize -S default -f html -a .source_code
|
||||
*/
|
||||
|
||||
pre { line-height: 125%; }
|
||||
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
|
||||
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
|
||||
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
|
||||
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
|
||||
.source_code .hll { background-color: #ffffcc }
|
||||
.source_code { background: #f8f8f8; }
|
||||
.source_code .c { color: #408080; font-style: italic } /* Comment */
|
||||
.source_code { background: #f8f8f8; }
|
||||
.source_code .c { color: #3D7B7B; font-style: italic } /* Comment */
|
||||
.source_code .err { border: 1px solid #FF0000 } /* Error */
|
||||
.source_code .k { color: #008000; font-weight: bold } /* Keyword */
|
||||
.source_code .o { color: #666666 } /* Operator */
|
||||
.source_code .cm { color: #408080; font-style: italic } /* Comment.Multiline */
|
||||
.source_code .cp { color: #BC7A00 } /* Comment.Preproc */
|
||||
.source_code .c1 { color: #408080; font-style: italic } /* Comment.Single */
|
||||
.source_code .cs { color: #408080; font-style: italic } /* Comment.Special */
|
||||
.source_code .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */
|
||||
.source_code .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */
|
||||
.source_code .cp { color: #9C6500 } /* Comment.Preproc */
|
||||
.source_code .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */
|
||||
.source_code .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */
|
||||
.source_code .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */
|
||||
.source_code .gd { color: #A00000 } /* Generic.Deleted */
|
||||
.source_code .ge { font-style: italic } /* Generic.Emph */
|
||||
.source_code .gr { color: #FF0000 } /* Generic.Error */
|
||||
.source_code .gr { color: #E40000 } /* Generic.Error */
|
||||
.source_code .gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
||||
.source_code .gi { color: #00A000 } /* Generic.Inserted */
|
||||
.source_code .go { color: #808080 } /* Generic.Output */
|
||||
.source_code .gi { color: #008400 } /* Generic.Inserted */
|
||||
.source_code .go { color: #717171 } /* Generic.Output */
|
||||
.source_code .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
|
||||
.source_code .gs { font-weight: bold } /* Generic.Strong */
|
||||
.source_code .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
|
||||
.source_code .gt { color: #0040D0 } /* Generic.Traceback */
|
||||
.source_code .gt { color: #0044DD } /* Generic.Traceback */
|
||||
.source_code .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
|
||||
.source_code .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
|
||||
.source_code .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
|
||||
@ -33,38 +40,139 @@
|
||||
.source_code .kt { color: #B00040 } /* Keyword.Type */
|
||||
.source_code .m { color: #666666 } /* Literal.Number */
|
||||
.source_code .s { color: #BA2121 } /* Literal.String */
|
||||
.source_code .na { color: #7D9029 } /* Name.Attribute */
|
||||
.source_code .na { color: #687822 } /* Name.Attribute */
|
||||
.source_code .nb { color: #008000 } /* Name.Builtin */
|
||||
.source_code .nc { color: #0000FF; font-weight: bold } /* Name.Class */
|
||||
.source_code .no { color: #880000 } /* Name.Constant */
|
||||
.source_code .nd { color: #AA22FF } /* Name.Decorator */
|
||||
.source_code .ni { color: #999999; font-weight: bold } /* Name.Entity */
|
||||
.source_code .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
|
||||
.source_code .ni { color: #717171; font-weight: bold } /* Name.Entity */
|
||||
.source_code .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */
|
||||
.source_code .nf { color: #0000FF } /* Name.Function */
|
||||
.source_code .nl { color: #A0A000 } /* Name.Label */
|
||||
.source_code .nl { color: #767600 } /* Name.Label */
|
||||
.source_code .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
|
||||
.source_code .nt { color: #008000; font-weight: bold } /* Name.Tag */
|
||||
.source_code .nv { color: #19177C } /* Name.Variable */
|
||||
.source_code .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
|
||||
.source_code .w { color: #bbbbbb } /* Text.Whitespace */
|
||||
.source_code .mb { color: #666666 } /* Literal.Number.Bin */
|
||||
.source_code .mf { color: #666666 } /* Literal.Number.Float */
|
||||
.source_code .mh { color: #666666 } /* Literal.Number.Hex */
|
||||
.source_code .mi { color: #666666 } /* Literal.Number.Integer */
|
||||
.source_code .mo { color: #666666 } /* Literal.Number.Oct */
|
||||
.source_code .sa { color: #BA2121 } /* Literal.String.Affix */
|
||||
.source_code .sb { color: #BA2121 } /* Literal.String.Backtick */
|
||||
.source_code .sc { color: #BA2121 } /* Literal.String.Char */
|
||||
.source_code .dl { color: #BA2121 } /* Literal.String.Delimiter */
|
||||
.source_code .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
|
||||
.source_code .s2 { color: #BA2121 } /* Literal.String.Double */
|
||||
.source_code .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
|
||||
.source_code .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */
|
||||
.source_code .sh { color: #BA2121 } /* Literal.String.Heredoc */
|
||||
.source_code .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
|
||||
.source_code .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */
|
||||
.source_code .sx { color: #008000 } /* Literal.String.Other */
|
||||
.source_code .sr { color: #BB6688 } /* Literal.String.Regex */
|
||||
.source_code .sr { color: #A45A77 } /* Literal.String.Regex */
|
||||
.source_code .s1 { color: #BA2121 } /* Literal.String.Single */
|
||||
.source_code .ss { color: #19177C } /* Literal.String.Symbol */
|
||||
.source_code .bp { color: #008000 } /* Name.Builtin.Pseudo */
|
||||
.source_code .fm { color: #0000FF } /* Name.Function.Magic */
|
||||
.source_code .vc { color: #19177C } /* Name.Variable.Class */
|
||||
.source_code .vg { color: #19177C } /* Name.Variable.Global */
|
||||
.source_code .vi { color: #19177C } /* Name.Variable.Instance */
|
||||
.source_code .vm { color: #19177C } /* Name.Variable.Magic */
|
||||
.source_code .il { color: #666666 } /* Literal.Number.Integer.Long */
|
||||
|
||||
/*
|
||||
* Dark mode: pygmentize -S native -f html -a .source_code
|
||||
*/
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
|
||||
pre { line-height: 125%; }
|
||||
td.linenos .normal { color: #aaaaaa; background-color: transparent; padding-left: 5px; padding-right: 5px; }
|
||||
span.linenos { color: #aaaaaa; background-color: transparent; padding-left: 5px; padding-right: 5px; }
|
||||
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
|
||||
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
|
||||
.source_code .hll { background-color: #404040 }
|
||||
.source_code { background: #202020; color: #d0d0d0 }
|
||||
.source_code .c { color: #ababab; font-style: italic } /* Comment */
|
||||
.source_code .err { color: #a61717; background-color: #e3d2d2 } /* Error */
|
||||
.source_code .esc { color: #d0d0d0 } /* Escape */
|
||||
.source_code .g { color: #d0d0d0 } /* Generic */
|
||||
.source_code .k { color: #6ebf26; font-weight: bold } /* Keyword */
|
||||
.source_code .l { color: #d0d0d0 } /* Literal */
|
||||
.source_code .n { color: #d0d0d0 } /* Name */
|
||||
.source_code .o { color: #d0d0d0 } /* Operator */
|
||||
.source_code .x { color: #d0d0d0 } /* Other */
|
||||
.source_code .p { color: #d0d0d0 } /* Punctuation */
|
||||
.source_code .ch { color: #ababab; font-style: italic } /* Comment.Hashbang */
|
||||
.source_code .cm { color: #ababab; font-style: italic } /* Comment.Multiline */
|
||||
.source_code .cp { color: #cd2828; font-weight: bold } /* Comment.Preproc */
|
||||
.source_code .cpf { color: #ababab; font-style: italic } /* Comment.PreprocFile */
|
||||
.source_code .c1 { color: #ababab; font-style: italic } /* Comment.Single */
|
||||
.source_code .cs { color: #e50808; font-weight: bold; background-color: #520000 } /* Comment.Special */
|
||||
.source_code .gd { color: #d22323 } /* Generic.Deleted */
|
||||
.source_code .ge { color: #d0d0d0; font-style: italic } /* Generic.Emph */
|
||||
.source_code .gr { color: #d22323 } /* Generic.Error */
|
||||
.source_code .gh { color: #ffffff; font-weight: bold } /* Generic.Heading */
|
||||
.source_code .gi { color: #589819 } /* Generic.Inserted */
|
||||
.source_code .go { color: #cccccc } /* Generic.Output */
|
||||
.source_code .gp { color: #aaaaaa } /* Generic.Prompt */
|
||||
.source_code .gs { color: #d0d0d0; font-weight: bold } /* Generic.Strong */
|
||||
.source_code .gu { color: #ffffff; text-decoration: underline } /* Generic.Subheading */
|
||||
.source_code .gt { color: #d22323 } /* Generic.Traceback */
|
||||
.source_code .kc { color: #6ebf26; font-weight: bold } /* Keyword.Constant */
|
||||
.source_code .kd { color: #6ebf26; font-weight: bold } /* Keyword.Declaration */
|
||||
.source_code .kn { color: #6ebf26; font-weight: bold } /* Keyword.Namespace */
|
||||
.source_code .kp { color: #6ebf26 } /* Keyword.Pseudo */
|
||||
.source_code .kr { color: #6ebf26; font-weight: bold } /* Keyword.Reserved */
|
||||
.source_code .kt { color: #6ebf26; font-weight: bold } /* Keyword.Type */
|
||||
.source_code .ld { color: #d0d0d0 } /* Literal.Date */
|
||||
.source_code .m { color: #51b2fd } /* Literal.Number */
|
||||
.source_code .s { color: #ed9d13 } /* Literal.String */
|
||||
.source_code .na { color: #bbbbbb } /* Name.Attribute */
|
||||
.source_code .nb { color: #2fbccd } /* Name.Builtin */
|
||||
.source_code .nc { color: #71adff; text-decoration: underline } /* Name.Class */
|
||||
.source_code .no { color: #40ffff } /* Name.Constant */
|
||||
.source_code .nd { color: #ffa500 } /* Name.Decorator */
|
||||
.source_code .ni { color: #d0d0d0 } /* Name.Entity */
|
||||
.source_code .ne { color: #bbbbbb } /* Name.Exception */
|
||||
.source_code .nf { color: #71adff } /* Name.Function */
|
||||
.source_code .nl { color: #d0d0d0 } /* Name.Label */
|
||||
.source_code .nn { color: #71adff; text-decoration: underline } /* Name.Namespace */
|
||||
.source_code .nx { color: #d0d0d0 } /* Name.Other */
|
||||
.source_code .py { color: #d0d0d0 } /* Name.Property */
|
||||
.source_code .nt { color: #6ebf26; font-weight: bold } /* Name.Tag */
|
||||
.source_code .nv { color: #40ffff } /* Name.Variable */
|
||||
.source_code .ow { color: #6ebf26; font-weight: bold } /* Operator.Word */
|
||||
.source_code .w { color: #666666 } /* Text.Whitespace */
|
||||
.source_code .mb { color: #51b2fd } /* Literal.Number.Bin */
|
||||
.source_code .mf { color: #51b2fd } /* Literal.Number.Float */
|
||||
.source_code .mh { color: #51b2fd } /* Literal.Number.Hex */
|
||||
.source_code .mi { color: #51b2fd } /* Literal.Number.Integer */
|
||||
.source_code .mo { color: #51b2fd } /* Literal.Number.Oct */
|
||||
.source_code .sa { color: #ed9d13 } /* Literal.String.Affix */
|
||||
.source_code .sb { color: #ed9d13 } /* Literal.String.Backtick */
|
||||
.source_code .sc { color: #ed9d13 } /* Literal.String.Char */
|
||||
.source_code .dl { color: #ed9d13 } /* Literal.String.Delimiter */
|
||||
.source_code .sd { color: #ed9d13 } /* Literal.String.Doc */
|
||||
.source_code .s2 { color: #ed9d13 } /* Literal.String.Double */
|
||||
.source_code .se { color: #ed9d13 } /* Literal.String.Escape */
|
||||
.source_code .sh { color: #ed9d13 } /* Literal.String.Heredoc */
|
||||
.source_code .si { color: #ed9d13 } /* Literal.String.Interpol */
|
||||
.source_code .sx { color: #ffa500 } /* Literal.String.Other */
|
||||
.source_code .sr { color: #ed9d13 } /* Literal.String.Regex */
|
||||
.source_code .s1 { color: #ed9d13 } /* Literal.String.Single */
|
||||
.source_code .ss { color: #ed9d13 } /* Literal.String.Symbol */
|
||||
.source_code .bp { color: #2fbccd } /* Name.Builtin.Pseudo */
|
||||
.source_code .fm { color: #71adff } /* Name.Function.Magic */
|
||||
.source_code .vc { color: #40ffff } /* Name.Variable.Class */
|
||||
.source_code .vg { color: #40ffff } /* Name.Variable.Global */
|
||||
.source_code .vi { color: #40ffff } /* Name.Variable.Instance */
|
||||
.source_code .vm { color: #40ffff } /* Name.Variable.Magic */
|
||||
.source_code .il { color: #51b2fd } /* Literal.Number.Integer.Long */
|
||||
|
||||
/* Dark mode - my overrides, because the defaults are too bright. */
|
||||
|
||||
.source_code .gh { color: rgb(189, 193, 198); }
|
||||
.source_code .gu { color: rgb(189, 193, 198); }
|
||||
}
|
||||
|
||||
|
||||
55
utils.py
55
utils.py
@ -9,6 +9,14 @@ try:
|
||||
from pygments import highlight # type: ignore
|
||||
from pygments import lexers # type: ignore
|
||||
from pygments.formatters import HtmlFormatter # type: ignore
|
||||
|
||||
_html_formatter = HtmlFormatter(
|
||||
encoding="utf-8",
|
||||
cssclass="source_code",
|
||||
linenos="table",
|
||||
anchorlinenos=True,
|
||||
lineanchors="line",
|
||||
)
|
||||
except ImportError:
|
||||
pygments = None
|
||||
|
||||
@ -19,6 +27,7 @@ except ImportError:
|
||||
markdown = None
|
||||
|
||||
import base64
|
||||
import functools
|
||||
import mimetypes
|
||||
import string
|
||||
import os.path
|
||||
@ -32,6 +41,7 @@ def shorten(s: str, width=60):
|
||||
return s[:57] + "..."
|
||||
|
||||
|
||||
@functools.lru_cache
|
||||
def can_colorize(s: str):
|
||||
"""True if we can colorize the string, False otherwise."""
|
||||
if pygments is None:
|
||||
@ -77,6 +87,7 @@ def can_embed_image(repo, fname):
|
||||
)
|
||||
|
||||
|
||||
@functools.lru_cache
|
||||
def colorize_diff(s: str) -> str:
|
||||
lexer = lexers.DiffLexer(encoding="utf-8")
|
||||
formatter = HtmlFormatter(encoding="utf-8", cssclass="source_code")
|
||||
@ -84,6 +95,7 @@ def colorize_diff(s: str) -> str:
|
||||
return highlight(s, lexer, formatter)
|
||||
|
||||
|
||||
@functools.lru_cache
|
||||
def colorize_blob(fname, s: str) -> str:
|
||||
try:
|
||||
lexer = lexers.guess_lexer_for_filename(fname, s, encoding="utf-8")
|
||||
@ -98,24 +110,7 @@ def colorize_blob(fname, s: str) -> str:
|
||||
except lexers.ClassNotFound:
|
||||
pass
|
||||
|
||||
formatter = HtmlFormatter(
|
||||
encoding="utf-8",
|
||||
cssclass="source_code",
|
||||
linenos="table",
|
||||
anchorlinenos=True,
|
||||
lineanchors="line",
|
||||
)
|
||||
|
||||
return highlight(s, lexer, formatter)
|
||||
|
||||
|
||||
def markdown_blob(s: str) -> str:
|
||||
extensions = [
|
||||
"markdown.extensions.fenced_code",
|
||||
"markdown.extensions.tables",
|
||||
RewriteLocalLinksExtension(),
|
||||
]
|
||||
return markdown.markdown(s, extensions=extensions)
|
||||
return highlight(s, lexer, _html_formatter)
|
||||
|
||||
|
||||
def embed_image_blob(fname: str, image_data: bytes) -> str:
|
||||
@ -126,11 +121,13 @@ def embed_image_blob(fname: str, image_data: bytes) -> str:
|
||||
)
|
||||
|
||||
|
||||
@functools.lru_cache
|
||||
def is_binary(b: bytes):
|
||||
# Git considers a blob binary if NUL in first ~8KB, so do the same.
|
||||
return b"\0" in b[:8192]
|
||||
|
||||
|
||||
@functools.lru_cache
|
||||
def hexdump(s: bytes):
|
||||
graph = string.ascii_letters + string.digits + string.punctuation + " "
|
||||
b = s.decode("latin1")
|
||||
@ -177,7 +174,23 @@ if markdown:
|
||||
tag.set("href", new_target)
|
||||
|
||||
class RewriteLocalLinksExtension(markdown.Extension):
|
||||
def extendMarkdown(self, md, md_globals):
|
||||
md.treeprocessors.add(
|
||||
"RewriteLocalLinks", RewriteLocalLinks(), "_end"
|
||||
def extendMarkdown(self, md):
|
||||
md.treeprocessors.register(
|
||||
RewriteLocalLinks(), "RewriteLocalLinks", 1000
|
||||
)
|
||||
|
||||
_md_extensions = [
|
||||
"markdown.extensions.fenced_code",
|
||||
"markdown.extensions.tables",
|
||||
RewriteLocalLinksExtension(),
|
||||
]
|
||||
|
||||
@functools.lru_cache
|
||||
def markdown_blob(s: str) -> str:
|
||||
return markdown.markdown(s, extensions=_md_extensions)
|
||||
|
||||
else:
|
||||
|
||||
@functools.lru_cache
|
||||
def markdown_blob(s: str) -> str:
|
||||
raise RuntimeError("markdown_blob() called without markdown support")
|
||||
|
||||
@ -77,7 +77,9 @@
|
||||
{{!markdown_blob(blob.utf8_content)}}
|
||||
</div>
|
||||
% elif can_colorize(blob.utf8_content):
|
||||
<div class="colorized-src">
|
||||
{{!colorize_blob(fname.raw, blob.utf8_content)}}
|
||||
</div>
|
||||
% else:
|
||||
<pre class="blob-body">
|
||||
{{blob.utf8_content}}
|
||||
|
||||
@ -56,7 +56,9 @@
|
||||
<hr/>
|
||||
|
||||
% if can_colorize(c.diff.body):
|
||||
<div class="colorized-src">
|
||||
{{!colorize_diff(c.diff.body)}}
|
||||
</div>
|
||||
% else:
|
||||
<pre class="diff-body">
|
||||
{{c.diff.body}}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user