Colour language tag using GitHub's linguist colours

This commit is contained in:
Jake Howard 2023-04-21 21:22:59 +01:00
parent e504da0bcf
commit d85065d914
Signed by: jake
GPG key ID: 57AFB45680EDD477
6 changed files with 67 additions and 10 deletions

View file

@ -17,3 +17,4 @@ flake8-mutable==1.2.0
flake8-print==5.0.0
flake8-tuple==0.4.1
djlint==1.19.2
types-pyyaml==6.0.12.9

View file

@ -80,6 +80,14 @@ div.block-tangent {
div.block-code {
position: relative;
.highlight {
pre,
span,
& {
@include dark-mode;
}
}
.code-header {
position: absolute;
font-family: $family-code;
@ -103,19 +111,17 @@ div.block-code {
.language-tag {
background-color: $primary-light;
font-weight: $weight-medium;
@include dark-mode {
background-color: $primary-dark;
color: $white;
}
}
}
.highlight {
pre,
span,
& {
@include dark-mode;
&.linguist-color span {
// HACK: Naive inverse threshold of the colour for optimum contrast
filter: contrast(1000) grayscale(100) invert(1);
}
}
}
}

View file

@ -22,4 +22,4 @@ $code-padding: 0;
$code: inherit;
$pre: unset;
$content-heading-weight: $weight-medium;
$content-pre-padding: 1.5em 1.25em 1.25em;
$content-pre-padding: 1.5em;

View file

@ -1,13 +1,17 @@
from typing import Iterator
from django.utils.functional import cached_property
from django.utils.safestring import mark_safe
from pygments import highlight
from pygments.formatters.html import HtmlFormatter
from pygments.lexers import find_lexer_class, get_all_lexers
from pygments.lexers.python import PythonConsoleLexer, PythonLexer
from pygments.lexers.shell import BashLexer, BashSessionLexer
from pygments.lexers.special import TextLexer
from wagtail.blocks import CharBlock, ChoiceBlock, StructBlock, StructValue, TextBlock
from .utils import get_linguist_colours
def get_language_choices() -> Iterator[tuple[str, str]]:
for name, _, _, _ in sorted(get_all_lexers()):
@ -17,6 +21,8 @@ def get_language_choices() -> Iterator[tuple[str, str]]:
class CodeStructValue(StructValue):
LANGUAGE_DISPLAY_MAPPING = {PythonConsoleLexer.name: PythonLexer.name}
LINGUIST_MAPPING = {BashLexer.name: "Shell", BashSessionLexer.name: "Shell"}
def code(self) -> str:
lexer = find_lexer_class(self["language"])()
formatter = HtmlFormatter(
@ -39,6 +45,25 @@ class CodeStructValue(StructValue):
"""
return self.LANGUAGE_DISPLAY_MAPPING.get(self["language"], self["language"])
@cached_property
def colour(self) -> str | None:
"""
Expose the colour denoted by GitHub's linguist.
"""
linguist_colours = get_linguist_colours()
language = self.LINGUIST_MAPPING.get(self["language"], self["language"])
if exact_match := linguist_colours.get(language.lower()):
return exact_match
if language_display_match := linguist_colours.get(
self.LANGUAGE_DISPLAY_MAPPING.get(language, "").lower()
):
return language_display_match
return None
class CodeBlock(StructBlock):
filename = CharBlock(max_length=128, required=False)

View file

@ -1,6 +1,10 @@
{% if value.header_text %}
<div class="code-header tags">
{% if value.header_text %}<span class="tag ml-auto mr-5 language-tag" title="{{ value.language }}">{{ value.header_text }}</span>{% endif %}
<div class="code-header">
{% if value.header_text %}
<span class="tag ml-auto mr-5 language-tag {% if value.colour %}linguist-color{% endif %}" title="{{ value.language }}" {% if value.colour %}style="background-color: {{ value.colour }}"{% endif %}>
<span {% if value.colour %}style="color: {{ value.colour }}"{% endif %}>{{ value.header_text }}</span>
</span>
{% endif %}
<span class="tag code-copy-tag">
<i class="fa-regular fa-clipboard is-clickable code-copy" title="Copy to clipboard"></i>
</span>

View file

@ -1,4 +1,25 @@
from importlib.metadata import version
import requests
import yaml
from django_cache_decorator import django_cache_decorator
PYGMENTS_VERSION = version("pygments")
PYGMENTS_VERSION_SLUG = PYGMENTS_VERSION.replace(".", "-")
LINGUIST_DATA_URL = "https://raw.githubusercontent.com/github/linguist/master/lib/linguist/languages.yml"
@django_cache_decorator(time=21600)
def get_linguist_colours() -> dict[str, str]:
response = requests.get(LINGUIST_DATA_URL)
response.raise_for_status()
linguist_data = yaml.safe_load(response.text)
return {
language.lower(): l["color"]
for language, l in linguist_data.items()
if l.get("color")
}