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-print==5.0.0
flake8-tuple==0.4.1 flake8-tuple==0.4.1
djlint==1.19.2 djlint==1.19.2
types-pyyaml==6.0.12.9

View file

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

View file

@ -22,4 +22,4 @@ $code-padding: 0;
$code: inherit; $code: inherit;
$pre: unset; $pre: unset;
$content-heading-weight: $weight-medium; $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 typing import Iterator
from django.utils.functional import cached_property
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from pygments import highlight from pygments import highlight
from pygments.formatters.html import HtmlFormatter from pygments.formatters.html import HtmlFormatter
from pygments.lexers import find_lexer_class, get_all_lexers from pygments.lexers import find_lexer_class, get_all_lexers
from pygments.lexers.python import PythonConsoleLexer, PythonLexer from pygments.lexers.python import PythonConsoleLexer, PythonLexer
from pygments.lexers.shell import BashLexer, BashSessionLexer
from pygments.lexers.special import TextLexer from pygments.lexers.special import TextLexer
from wagtail.blocks import CharBlock, ChoiceBlock, StructBlock, StructValue, TextBlock from wagtail.blocks import CharBlock, ChoiceBlock, StructBlock, StructValue, TextBlock
from .utils import get_linguist_colours
def get_language_choices() -> Iterator[tuple[str, str]]: def get_language_choices() -> Iterator[tuple[str, str]]:
for name, _, _, _ in sorted(get_all_lexers()): for name, _, _, _ in sorted(get_all_lexers()):
@ -17,6 +21,8 @@ def get_language_choices() -> Iterator[tuple[str, str]]:
class CodeStructValue(StructValue): class CodeStructValue(StructValue):
LANGUAGE_DISPLAY_MAPPING = {PythonConsoleLexer.name: PythonLexer.name} LANGUAGE_DISPLAY_MAPPING = {PythonConsoleLexer.name: PythonLexer.name}
LINGUIST_MAPPING = {BashLexer.name: "Shell", BashSessionLexer.name: "Shell"}
def code(self) -> str: def code(self) -> str:
lexer = find_lexer_class(self["language"])() lexer = find_lexer_class(self["language"])()
formatter = HtmlFormatter( formatter = HtmlFormatter(
@ -39,6 +45,25 @@ class CodeStructValue(StructValue):
""" """
return self.LANGUAGE_DISPLAY_MAPPING.get(self["language"], self["language"]) 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): class CodeBlock(StructBlock):
filename = CharBlock(max_length=128, required=False) filename = CharBlock(max_length=128, required=False)

View file

@ -1,6 +1,10 @@
{% if value.header_text %} {% if value.header_text %}
<div class="code-header tags"> <div class="code-header">
{% if value.header_text %}<span class="tag ml-auto mr-5 language-tag" title="{{ value.language }}">{{ value.header_text }}</span>{% endif %} {% 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"> <span class="tag code-copy-tag">
<i class="fa-regular fa-clipboard is-clickable code-copy" title="Copy to clipboard"></i> <i class="fa-regular fa-clipboard is-clickable code-copy" title="Copy to clipboard"></i>
</span> </span>

View file

@ -1,4 +1,25 @@
from importlib.metadata import version from importlib.metadata import version
import requests
import yaml
from django_cache_decorator import django_cache_decorator
PYGMENTS_VERSION = version("pygments") PYGMENTS_VERSION = version("pygments")
PYGMENTS_VERSION_SLUG = PYGMENTS_VERSION.replace(".", "-") 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")
}