Compare commits

...

8 Commits
0.30 ... master

Author SHA1 Message Date
Alberto Bertogli
dff4ff6757 css: Work around Pygments' element layout changes
Pygments 2.12 changes the element layout slightly, adding a wrapper
<div> that was accidentally removed before:
https://github.com/pygments/pygments/issues/632.

This patch adds a workaround, so the styling is consistent on both 2.11
and 2.12.
2022-10-13 22:32:21 +01:00
Alberto Bertogli
6ea59bad51 css: Dark mode for Pygments' syntax highlight
This patch updates Pygments' syntax highlight CSS to support dark mode.
It uses two themes from Pygments: `default` for light (same as before),
and `native` for dark.
2022-10-13 22:32:21 +01:00
Alberto Bertogli
4b1e1eb84c css: Introduce dark mode
This patch extends our CSS to introduce dark mode, so the style shown
matches the user media preference.

It is very analogous to the previous one, only minor adjustments have
been made to make the contrast levels pass the accessibility standards.

No changes have been made to the pygments CSS. It works surprisingly
well as-is, but there are some minor changes that may be needed. Those
will be done in subsequent patches.
2022-10-13 22:32:21 +01:00
Alberto Bertogli
518188288e Cache some (possibly) expensive function calls
This patch memoizes some of the functions to help speed up execution.
The speedup is quite variable, but ~30% is normal when generating a
medium size repository, and the output is byte-for-byte identical.
2022-08-31 23:15:16 +01:00
Alberto Bertogli
15547b2796 utils: Update Markdown local links extension to the new API
The Markdown extension for rewriting local links was using an API that
is now deprecated, and as of python-markdown 3.4 it is no longer
available.

This patch adjusts the code to use the new API which should be available
from 3.0 onwards.
2022-08-31 21:07:53 +01:00
Alberto Bertogli
9f3df4899f css: Improve handling of text overflow in <pre>
When a <pre> section (commit message, blob, diff) has a very long line,
today it makes the entire page very wide, causing usability issues.

This patch makes <pre> have a horizontal scroll in those cases, which is
easier to use.
2021-05-15 01:24:58 +01:00
Alberto Bertogli
bc1ee87dfe css: Reduce commit-message left padding
The commit message has a very large left and right padding, but doesn't
improve readability and might make the commit message more difficult to
read on smaller screens.

This patch shortens the padding.
2021-05-15 00:58:11 +01:00
Alberto Bertogli
0d61bbf7f5 css: Auto-format git-arr.css
Auto-format static/git-arr.css with https://blitiri.com.ar/git/r/css3fmt/
for consistency.
2021-05-15 00:53:42 +01:00
7 changed files with 336 additions and 105 deletions

57
git.py
View File

@ -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:

View File

@ -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;

View File

@ -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); }
}

View File

@ -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")

View File

@ -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}}

View File

@ -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}}