Skip to content

Commit 1cf5ee7

Browse files
committed
Initial commit
0 parents  commit 1cf5ee7

7 files changed

+268
-0
lines changed

README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Pygments plugin examples
2+
3+
This repository contains an example of the project scaffolding needed to write a
4+
plugin lexer/formatter/style/filter for the [Pygments](https://pygments.org)
5+
syntax highlighter.
6+
7+
Roadmap:
8+
9+
```
10+
.
11+
├── example_filter.py # An example filter
12+
├── example_formatter.py # An example formatter
13+
├── example_lexer.py # An example lexer
14+
├── example_style.py # An example style
15+
├── pyproject.toml # The Python package metadata file
16+
├── README.md # You are here
17+
└── test.exmpl # A test file in the mockup language of the example lexer
18+
```
19+
20+
To be usable, plugins must be installed. If you are running the `pygmentize`
21+
command, you probably want to use a
22+
[virtual environment](https://docs.python.org/3/library/venv.html)
23+
to avoid installing packages globally. For example, here is how to create
24+
a virtual environment and install this set of plugins into it:
25+
26+
```
27+
python -m venv venv/
28+
venv/bin/pip install . # install the project in current directory into the virtual environment
29+
venv/bin/pygmentize ... # use the pygmentize command from the virtual environment
30+
```
31+
32+
Alternatively, since this example uses the [Hatch](https://hatch.pypa.io)
33+
tool, you may use
34+
35+
```
36+
hatch run pygmentize ...
37+
```
38+
39+
If you are using Pygments from Python, possibly through a tool like Sphinx,
40+
mkdocs, etc., then you just need to install the plugin in the same environment
41+
as the one where you installed Pygments.
42+
43+
If you want to distribute your plugin on PyPI, you should read the
44+
[packaging user guide](https://packaging.python.org/en/latest/tutorials/packaging-projects).
45+
46+
47+
#### License for this template
48+
49+
There isn't much copyrightable content here, but if you are worried about reuse:
50+
51+
Copyright (C) 2023 by Jean Abou Samra <jean@abou-samra.fr>
52+
53+
Permission to use, copy, modify, and/or distribute this software for any purpose
54+
with or without fee is hereby granted.
55+
56+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
57+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
58+
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
59+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
60+
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
61+
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
62+
THIS SOFTWARE.

example_filter.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"""An example filter for Pygments."""
2+
3+
from pygments.filters import Filter
4+
from pygments.token import Token
5+
6+
class ExampleFilter(Filter):
7+
# This filter replaces all tabs with "<tab>".
8+
def filter(self, lexer, stream):
9+
for ttype, value in stream:
10+
parts = value.split("\t")
11+
yield ttype, "<tab>".join(parts)

example_formatter.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""An example plugin formatter for Pygments."""
2+
3+
from pygments.formatter import Formatter
4+
5+
class ExampleFormatter(Formatter):
6+
# This should be the human-readable name of the format.
7+
name = "Pygments Plugin Example Format"
8+
9+
# This is a list of names that can be used to select the formatter.
10+
# In this example, we can call the pygmentize command as
11+
#
12+
# pygmentize -f example-format
13+
#
14+
# Also, doing
15+
#
16+
# pygments.formatters.find_formatter_class("example-format")
17+
#
18+
# in Python will find the formatter class.
19+
aliases = ["example-format"]
20+
21+
# This is a list of file patterns that the formatter will
22+
# typically be used to produce. In this example, calling
23+
#
24+
# pygmentize -o out.exmplfmt
25+
#
26+
# will automatically select this formatter based on the output
27+
# file name. Similarly,
28+
#
29+
# pygments.formatters.get_formatter_for_filename("out.exmplfmt")
30+
#
31+
# will return an instance of this formatter class.
32+
filenames = ["*.exmplfmt"]
33+
34+
def format_unencoded(self, tokensource, out):
35+
# This formatter writes each token as [<color>]<string> .
36+
for ttype, value in tokensource:
37+
while not self.style.styles_token(ttype):
38+
ttype = ttype.parent
39+
color = self.style.style_for_token(ttype)['color']
40+
out.write("[" + (color or "black") + "]")
41+
out.write(value)

example_lexer.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""An example plugin lexer for Pygments."""
2+
3+
from pygments.lexer import RegexLexer
4+
from pygments.token import Keyword, Text
5+
6+
class ExampleLexer(RegexLexer):
7+
# This should be the human-readable name of the language. In this example,
8+
# doing
9+
#
10+
# pygments.lexers.find_lexer_class("Pygments Plugin Example Language")
11+
#
12+
# will return the ExampleLexer class.
13+
name = "Pygments Plugin Example Language"
14+
15+
# This is a list of names that can be used to select the language.
16+
# In this example, we can call the pygmentize command as
17+
#
18+
# pygmentize -l example-lang
19+
#
20+
# Also, doing
21+
#
22+
# pygments.lexers.find_lexer_class_by_name("example-lang")
23+
#
24+
# in Python will find the lexer class. This is what many documentation or
25+
# site generation tools implicitly do with code blocks, e.g., this in Sphinx
26+
# reST:
27+
#
28+
# .. code-block:: example-lang
29+
#
30+
# your code here
31+
#
32+
# or this in Markdown:
33+
#
34+
# ```example-lang
35+
# your code here
36+
# ```
37+
#
38+
aliases = ["example-lang"]
39+
40+
# This is a list of file patterns that will automatically be highlighted
41+
# with this lexer. In this example, calling
42+
#
43+
# pygmentize file.exmpl
44+
#
45+
# will automatically highlight file.exmpl with this lexer, without needing
46+
# to pass `-l example-lang`. Similarly,
47+
#
48+
# pygments.lexers.find_lexer_class_for_filename("file.exmpl")
49+
#
50+
# will return the ExampleLexer class.
51+
filenames = ["*.exmpl"]
52+
53+
# This is a list of MIME types for the language. In this example,
54+
#
55+
# pygments.lexers.get_lexer_for_mimetype("text/x-exmpl")
56+
#
57+
# will return the ExampleLexer class.
58+
mimetypes = ["text/x-exmpl"]
59+
60+
# This lexer highlights lines that read "foo".
61+
tokens = {
62+
'root': [
63+
(r'foo\n', Keyword),
64+
(r'.*\n', Text),
65+
]
66+
}

example_style.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""An example plugin style for Pygments."""
2+
3+
from pygments.style import Style
4+
from pygments.token import Keyword
5+
6+
class ExampleStyle(Style):
7+
styles = {
8+
# This style merely highlights keywords in red.
9+
Keyword: "#f00",
10+
}

pyproject.toml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
[build-system]
2+
requires = ["hatchling"]
3+
build-backend = "hatchling.build"
4+
5+
[project]
6+
name = "pygments-plugin-scaffolding" # change this to a package name for your plugin
7+
version = "0.0.1"
8+
dependencies = ["pygments"]
9+
readme = "README.md"
10+
11+
12+
# Declare plugin lexers in this table. The key (like `example_lexer` below) are
13+
# not significant. The value has the form `module_name:ClassName`. The lexer
14+
# class will be imported using `from module_name import ClassName`.
15+
16+
# See the attributes declared in example_lexer.py for how to configure the
17+
# language names and file patterns.
18+
19+
[project.entry-points."pygments.lexers"]
20+
example_lexer = "example_lexer:ExampleLexer"
21+
22+
23+
# Declare plugin formatters in this table. The key is not significant and the
24+
# value has the same format as for lexers. See example_formatter.py for
25+
# configuration options.
26+
27+
[project.entry-points."pygments.formatters"]
28+
example_formatter = "example_formatter:ExampleFormatter"
29+
30+
31+
# Declare plugin styles in this table. The key *is* significant: it is the name
32+
# the style will be recognized as. In this example, we can use
33+
#
34+
# pygmentize -Ostyle=example-style"
35+
#
36+
# on the command line. Also,
37+
#
38+
# pygments.styles.get_style_by_name("example-style")
39+
#
40+
# will return the style class.
41+
42+
[project.entry-points."pygments.styles"]
43+
example-style = "example_style:ExampleStyle"
44+
45+
46+
# Declare plugin filters in this table. The key *is* significant: it is the name
47+
# the filter will be recognized as. In this example, we can use
48+
#
49+
# pygmentize -F example-filter
50+
#
51+
# on the command line. Also,
52+
#
53+
# pygments.filters.find_filter_class("example-filter")
54+
#
55+
# will return the filter class.
56+
57+
[project.entry-points."pygments.filters"]
58+
example-filter = "example_filter:ExampleFilter"
59+
60+
61+
# This is a test command. Running it should print:
62+
#
63+
# [ff0000]foo
64+
# [black]<tab><tab>bar # tabs here
65+
#
66+
# - Our custom lexer highlights "foo" as keyword,
67+
# - Our custom style uses red (ff0000) for keywords,
68+
# - Our custom formatter prints colors in brackets,
69+
# - Our custom filter replaces tabs with "<tab>".
70+
#
71+
# Run with
72+
#
73+
# hatch run test
74+
75+
[tool.hatch.envs.default.scripts]
76+
test = "pygmentize -l example-lang -f example-format -F example-filter -O style=example-style test.exmpl"

test.exmpl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
foo
2+
bar # tabs here

0 commit comments

Comments
 (0)