Closes #20013: Syntax highlighting on Data Files

Add syntax highlighting on the details page for Data Files.
This commit is contained in:
Tobias Genannt 2025-08-14 19:53:06 +02:00
parent 9da777d667
commit 80f42c8b2a
10 changed files with 628 additions and 1 deletions

View File

@ -125,6 +125,10 @@ Pillow
# https://github.com/psycopg/psycopg/blob/master/docs/news.rst
psycopg[c,pool]
# Pygments syntax highlighting
# https://github.com/pygments/pygments/blob/master/CHANGES
Pygments
# YAML rendering library
# https://github.com/yaml/pyyaml/blob/master/CHANGES
PyYAML

View File

View File

@ -0,0 +1,30 @@
from django import template
from django.utils.safestring import mark_safe
from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import get_lexer_for_filename
from pygments.util import ClassNotFound
register = template.Library()
@register.simple_tag
def highlight_code(value, filename: str):
"""
Highlight code using Pygments.
"""
if not value:
return ''
try:
lexer = get_lexer_for_filename(filename)
except ClassNotFound:
return mark_safe(f"<pre>{value}</pre>") # Fallback to plain text if no lexer was found
return mark_safe(
highlight(
value,
lexer,
HtmlFormatter(
linenos="inline", classprefix="pygments-", style="solarized-light"
),
)
)

Binary file not shown.

View File

@ -28,3 +28,7 @@
@import 'custom/misc';
@import 'custom/notifications';
@import 'custom/racks';
// Pygments styles
@import 'pygments/dark';
@import 'pygments/light';

View File

@ -0,0 +1,33 @@
## Pygments style for NetBox
The style are based on the `Pygments` themes `solarized-light` and `solarized-dark`.
To generated the scss files in this directory execute the following code in a Python environment where `Pygments` is installed:
```python
from pygments.formatters import HtmlFormatter
h = HtmlFormatter(linenos="inline", classprefix="pygments-", style="solarized-dark")
print(h.get_style_defs())
h = HtmlFormatter(linenos="inline", classprefix="pygments-", style="solarized-light")
print(h.get_style_defs())
```
To get the correct theme for dark and light modes wrap the resulting CSS in the following selectors:
```scss
// _dark.scss
body[data-bs-theme='dark'] {
// Insert generated CSS for dark theme here
}
// _light.scss
body[data-bs-theme='light'] {
// Insert generated CSS for light theme here
}
```
The run the formatter:
```bash
yarn run format:styles
```

View File

@ -0,0 +1,277 @@
body[data-bs-theme='dark'] {
td.linenos .normal {
color: #586e75;
background-color: #073642;
padding-left: 5px;
padding-right: 5px;
}
span.linenos {
color: #586e75;
background-color: #073642;
padding-left: 5px;
padding-right: 5px;
margin-right: 10px;
}
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;
}
.hll {
background-color: #073642;
}
.pygments-c {
color: #586e75;
font-style: italic;
} /* Comment */
.pygments-err {
color: #839496;
background-color: #dc322f;
} /* Error */
.pygments-esc {
color: #839496;
} /* Escape */
.pygments-g {
color: #839496;
} /* Generic */
.pygments-k {
color: #859900;
} /* Keyword */
.pygments-l {
color: #839496;
} /* Literal */
.pygments-n {
color: #839496;
} /* Name */
.pygments-o {
color: #586e75;
} /* Operator */
.pygments-x {
color: #839496;
} /* Other */
.pygments-p {
color: #839496;
} /* Punctuation */
.pygments-ch {
color: #586e75;
font-style: italic;
} /* Comment.Hashbang */
.pygments-cm {
color: #586e75;
font-style: italic;
} /* Comment.Multiline */
.pygments-cp {
color: #d33682;
} /* Comment.Preproc */
.pygments-cpf {
color: #586e75;
} /* Comment.PreprocFile */
.pygments-c1 {
color: #586e75;
font-style: italic;
} /* Comment.Single */
.pygments-cs {
color: #586e75;
font-style: italic;
} /* Comment.Special */
.pygments-gd {
color: #dc322f;
} /* Generic.Deleted */
.pygments-ge {
color: #839496;
font-style: italic;
} /* Generic.Emph */
.pygments-ges {
color: #839496;
font-weight: bold;
font-style: italic;
} /* Generic.EmphStrong */
.pygments-gr {
color: #dc322f;
} /* Generic.Error */
.pygments-gh {
color: #839496;
font-weight: bold;
} /* Generic.Heading */
.pygments-gi {
color: #859900;
} /* Generic.Inserted */
.pygments-go {
color: #839496;
} /* Generic.Output */
.pygments-gp {
color: #268bd2;
font-weight: bold;
} /* Generic.Prompt */
.pygments-gs {
color: #839496;
font-weight: bold;
} /* Generic.Strong */
.pygments-gu {
color: #839496;
text-decoration: underline;
} /* Generic.Subheading */
.pygments-gt {
color: #268bd2;
} /* Generic.Traceback */
.pygments-kc {
color: #2aa198;
} /* Keyword.Constant */
.pygments-kd {
color: #2aa198;
} /* Keyword.Declaration */
.pygments-kn {
color: #cb4b16;
} /* Keyword.Namespace */
.pygments-kp {
color: #859900;
} /* Keyword.Pseudo */
.pygments-kr {
color: #859900;
} /* Keyword.Reserved */
.pygments-kt {
color: #b58900;
} /* Keyword.Type */
.pygments-ld {
color: #839496;
} /* Literal.Date */
.pygments-m {
color: #2aa198;
} /* Literal.Number */
.pygments-s {
color: #2aa198;
} /* Literal.String */
.pygments-na {
color: #839496;
} /* Name.Attribute */
.pygments-nb {
color: #268bd2;
} /* Name.Builtin */
.pygments-nc {
color: #268bd2;
} /* Name.Class */
.pygments-no {
color: #268bd2;
} /* Name.Constant */
.pygments-nd {
color: #268bd2;
} /* Name.Decorator */
.pygments-ni {
color: #268bd2;
} /* Name.Entity */
.pygments-ne {
color: #268bd2;
} /* Name.Exception */
.pygments-nf {
color: #268bd2;
} /* Name.Function */
.pygments-nl {
color: #268bd2;
} /* Name.Label */
.pygments-nn {
color: #268bd2;
} /* Name.Namespace */
.pygments-nx {
color: #839496;
} /* Name.Other */
.pygments-py {
color: #839496;
} /* Name.Property */
.pygments-nt {
color: #268bd2;
} /* Name.Tag */
.pygments-nv {
color: #268bd2;
} /* Name.Variable */
.pygments-ow {
color: #859900;
} /* Operator.Word */
.pygments-pm {
color: #839496;
} /* Punctuation.Marker */
.pygments-w {
color: #839496;
} /* Text.Whitespace */
.pygments-mb {
color: #2aa198;
} /* Literal.Number.Bin */
.pygments-mf {
color: #2aa198;
} /* Literal.Number.Float */
.pygments-mh {
color: #2aa198;
} /* Literal.Number.Hex */
.pygments-mi {
color: #2aa198;
} /* Literal.Number.Integer */
.pygments-mo {
color: #2aa198;
} /* Literal.Number.Oct */
.pygments-sa {
color: #2aa198;
} /* Literal.String.Affix */
.pygments-sb {
color: #2aa198;
} /* Literal.String.Backtick */
.pygments-sc {
color: #2aa198;
} /* Literal.String.Char */
.pygments-dl {
color: #2aa198;
} /* Literal.String.Delimiter */
.pygments-sd {
color: #586e75;
} /* Literal.String.Doc */
.pygments-s2 {
color: #2aa198;
} /* Literal.String.Double */
.pygments-se {
color: #2aa198;
} /* Literal.String.Escape */
.pygments-sh {
color: #2aa198;
} /* Literal.String.Heredoc */
.pygments-si {
color: #2aa198;
} /* Literal.String.Interpol */
.pygments-sx {
color: #2aa198;
} /* Literal.String.Other */
.pygments-sr {
color: #cb4b16;
} /* Literal.String.Regex */
.pygments-s1 {
color: #2aa198;
} /* Literal.String.Single */
.pygments-ss {
color: #2aa198;
} /* Literal.String.Symbol */
.pygments-bp {
color: #268bd2;
} /* Name.Builtin.Pseudo */
.pygments-fm {
color: #268bd2;
} /* Name.Function.Magic */
.pygments-vc {
color: #268bd2;
} /* Name.Variable.Class */
.pygments-vg {
color: #268bd2;
} /* Name.Variable.Global */
.pygments-vi {
color: #268bd2;
} /* Name.Variable.Instance */
.pygments-vm {
color: #268bd2;
} /* Name.Variable.Magic */
.pygments-il {
color: #2aa198;
} /* Literal.Number.Integer.Long */
}

View File

@ -0,0 +1,277 @@
body[data-bs-theme='light'] {
td.linenos .normal {
color: #93a1a1;
background-color: #eee8d5;
padding-left: 5px;
padding-right: 5px;
}
span.linenos {
color: #93a1a1;
background-color: #eee8d5;
padding-left: 5px;
padding-right: 5px;
margin-right: 10px;
}
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;
}
.hll {
background-color: #eee8d5;
}
.pygments-c {
color: #93a1a1;
font-style: italic;
} /* Comment */
.pygments-err {
color: #657b83;
background-color: #dc322f;
} /* Error */
.pygments-esc {
color: #657b83;
} /* Escape */
.pygments-g {
color: #657b83;
} /* Generic */
.pygments-k {
color: #859900;
} /* Keyword */
.pygments-l {
color: #657b83;
} /* Literal */
.pygments-n {
color: #657b83;
} /* Name */
.pygments-o {
color: #93a1a1;
} /* Operator */
.pygments-x {
color: #657b83;
} /* Other */
.pygments-p {
color: #657b83;
} /* Punctuation */
.pygments-ch {
color: #93a1a1;
font-style: italic;
} /* Comment.Hashbang */
.pygments-cm {
color: #93a1a1;
font-style: italic;
} /* Comment.Multiline */
.pygments-cp {
color: #d33682;
} /* Comment.Preproc */
.pygments-cpf {
color: #93a1a1;
} /* Comment.PreprocFile */
.pygments-c1 {
color: #93a1a1;
font-style: italic;
} /* Comment.Single */
.pygments-cs {
color: #93a1a1;
font-style: italic;
} /* Comment.Special */
.pygments-gd {
color: #dc322f;
} /* Generic.Deleted */
.pygments-ge {
color: #657b83;
font-style: italic;
} /* Generic.Emph */
.pygments-ges {
color: #657b83;
font-weight: bold;
font-style: italic;
} /* Generic.EmphStrong */
.pygments-gr {
color: #dc322f;
} /* Generic.Error */
.pygments-gh {
color: #657b83;
font-weight: bold;
} /* Generic.Heading */
.pygments-gi {
color: #859900;
} /* Generic.Inserted */
.pygments-go {
color: #657b83;
} /* Generic.Output */
.pygments-gp {
color: #268bd2;
font-weight: bold;
} /* Generic.Prompt */
.pygments-gs {
color: #657b83;
font-weight: bold;
} /* Generic.Strong */
.pygments-gu {
color: #657b83;
text-decoration: underline;
} /* Generic.Subheading */
.pygments-gt {
color: #268bd2;
} /* Generic.Traceback */
.pygments-kc {
color: #2aa198;
} /* Keyword.Constant */
.pygments-kd {
color: #2aa198;
} /* Keyword.Declaration */
.pygments-kn {
color: #cb4b16;
} /* Keyword.Namespace */
.pygments-kp {
color: #859900;
} /* Keyword.Pseudo */
.pygments-kr {
color: #859900;
} /* Keyword.Reserved */
.pygments-kt {
color: #b58900;
} /* Keyword.Type */
.pygments-ld {
color: #657b83;
} /* Literal.Date */
.pygments-m {
color: #2aa198;
} /* Literal.Number */
.pygments-s {
color: #2aa198;
} /* Literal.String */
.pygments-na {
color: #657b83;
} /* Name.Attribute */
.pygments-nb {
color: #268bd2;
} /* Name.Builtin */
.pygments-nc {
color: #268bd2;
} /* Name.Class */
.pygments-no {
color: #268bd2;
} /* Name.Constant */
.pygments-nd {
color: #268bd2;
} /* Name.Decorator */
.pygments-ni {
color: #268bd2;
} /* Name.Entity */
.pygments-ne {
color: #268bd2;
} /* Name.Exception */
.pygments-nf {
color: #268bd2;
} /* Name.Function */
.pygments-nl {
color: #268bd2;
} /* Name.Label */
.pygments-nn {
color: #268bd2;
} /* Name.Namespace */
.pygments-nx {
color: #657b83;
} /* Name.Other */
.pygments-py {
color: #657b83;
} /* Name.Property */
.pygments-nt {
color: #268bd2;
} /* Name.Tag */
.pygments-nv {
color: #268bd2;
} /* Name.Variable */
.pygments-ow {
color: #859900;
} /* Operator.Word */
.pygments-pm {
color: #657b83;
} /* Punctuation.Marker */
.pygments-w {
color: #657b83;
} /* Text.Whitespace */
.pygments-mb {
color: #2aa198;
} /* Literal.Number.Bin */
.pygments-mf {
color: #2aa198;
} /* Literal.Number.Float */
.pygments-mh {
color: #2aa198;
} /* Literal.Number.Hex */
.pygments-mi {
color: #2aa198;
} /* Literal.Number.Integer */
.pygments-mo {
color: #2aa198;
} /* Literal.Number.Oct */
.pygments-sa {
color: #2aa198;
} /* Literal.String.Affix */
.pygments-sb {
color: #2aa198;
} /* Literal.String.Backtick */
.pygments-sc {
color: #2aa198;
} /* Literal.String.Char */
.pygments-dl {
color: #2aa198;
} /* Literal.String.Delimiter */
.pygments-sd {
color: #93a1a1;
} /* Literal.String.Doc */
.pygments-s2 {
color: #2aa198;
} /* Literal.String.Double */
.pygments-se {
color: #2aa198;
} /* Literal.String.Escape */
.pygments-sh {
color: #2aa198;
} /* Literal.String.Heredoc */
.pygments-si {
color: #2aa198;
} /* Literal.String.Interpol */
.pygments-sx {
color: #2aa198;
} /* Literal.String.Other */
.pygments-sr {
color: #cb4b16;
} /* Literal.String.Regex */
.pygments-s1 {
color: #2aa198;
} /* Literal.String.Single */
.pygments-ss {
color: #2aa198;
} /* Literal.String.Symbol */
.pygments-bp {
color: #268bd2;
} /* Name.Builtin.Pseudo */
.pygments-fm {
color: #268bd2;
} /* Name.Function.Magic */
.pygments-vc {
color: #268bd2;
} /* Name.Variable.Class */
.pygments-vg {
color: #268bd2;
} /* Name.Variable.Global */
.pygments-vi {
color: #268bd2;
} /* Name.Variable.Instance */
.pygments-vm {
color: #268bd2;
} /* Name.Variable.Magic */
.pygments-il {
color: #2aa198;
} /* Literal.Number.Integer.Long */
}

View File

@ -4,6 +4,7 @@
{% load helpers %}
{% load perms %}
{% load plugins %}
{% load highlight_code %}
{% load i18n %}
{% block breadcrumbs %}
@ -48,7 +49,7 @@
<div class="card">
<h2 class="card-header">{% trans "Content" %}</h2>
<div class="card-body">
<pre>{{ object.data_as_string }}</pre>
{% highlight_code object.data_as_string object.path %}
</div>
</div>
{% plugin_left_page object %}

View File

@ -29,6 +29,7 @@ netaddr==1.3.0
nh3==0.3.0
Pillow==11.3.0
psycopg[c,pool]==3.2.9
pygments==2.19.2
PyYAML==6.0.2
requests==2.32.4
rq==2.4.1