Skip to content

Commit

Permalink
Add pygments plugins for Gerber code and PyGerber API
Browse files Browse the repository at this point in the history
  • Loading branch information
Argmaster committed Sep 30, 2024
1 parent 4d025ad commit f563cf9
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 12 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,9 @@ currently available ones:

- Image generator (PNG/JPEG)
- Code formatter
- Language server
- Language server (requires `language_server` extras)
([Visual Studio Code extension available](https://marketplace.visualstudio.com/items?itemName=argmaster.gerber-x3-x2-format-support))
- Pygments Gerber syntax lexer plugin (requires `pygments` extras)

### 🖮 PyGerber APIs

Expand Down
10 changes: 5 additions & 5 deletions docs/80_code_generation/10_gerber.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ method to create a new pad shape which can be added to the image with
[`add_pad()`](../reference/pygerber/builder/gerber.md#pygerber.builder.gerber.GerberX3Builder.add_pad)
method.

{{ include_code("test/examples/builder/gerber/_00_circle_pad.ex.py", "python", title="example.py", linenums="1") }}
{{ include_code("test/examples/builder/gerber/_00_circle_pad.ex.py", "docspygerberlexer", title="example.py", linenums="1") }}

{{ run_capture_stdout("python test/examples/builder/gerber/_00_circle_pad.ex.py", "python example.py", "gerber") }}

Expand All @@ -39,10 +39,10 @@ This is the rendered result of the example presented above:
You can also add traces to the image with
[`new_trace()`](../reference/pygerber/builder/gerber.md#pygerber.builder.gerber.GerberX3Builder.add_trace)
method and
[`add_arc_trace()`](<(../reference/pygerber/builder/gerber.md#pygerber.builder.gerber.GerberX3Builder.add_arc_trace)>)
[`add_arc_trace()`](../reference/pygerber/builder/gerber.md#pygerber.builder.gerber.GerberX3Builder.add_arc_trace)
method.

{{ include_code("test/examples/builder/gerber/_10_circle_pad_and_trace.ex.py", "python", title="example.py", linenums="1") }}
{{ include_code("test/examples/builder/gerber/_10_circle_pad_and_trace.ex.py", "docspygerberlexer", title="example.py", linenums="1") }}

{{ run_capture_stdout("python test/examples/builder/gerber/_10_circle_pad_and_trace.ex.py", "python example.py", "gerber") }}

Expand All @@ -61,7 +61,7 @@ Commands creating graphical elements, like `add_pad()` or `add_trace()` return s
for `new_pad()` or `new_trace()` methods. This way you don not have to retype
coordinates for draws which are connected to previous objects.

{{ include_code("test/examples/builder/gerber/_11_circle_pad_and_trace_with_reuse.ex.py", "python", title="example.py", linenums="1") }}
{{ include_code("test/examples/builder/gerber/_11_circle_pad_and_trace_with_reuse.ex.py", "docspygerberlexer", title="example.py", linenums="1") }}

{{ run_capture_stdout("python test/examples/builder/gerber/_11_circle_pad_and_trace_with_reuse.ex.py", "python example.py", "gerber") }}

Expand All @@ -86,7 +86,7 @@ After calling `create()`, you cannot and new elements to that custom pad.
Custom pad is used in the same way as any other pad, to add it to the image you should
use `add_pad()` method.

{{ include_code("test/examples/builder/gerber/_20_custom_pad.ex.py", "python", title="custom_pad_example.py", linenums="1") }}
{{ include_code("test/examples/builder/gerber/_20_custom_pad.ex.py", "docspygerberlexer", title="custom_pad_example.py", linenums="1") }}

{{ run_capture_stdout("python test/examples/builder/gerber/_20_custom_pad.ex.py", "python custom_pad_example.py", "gerber") }}

Expand Down
3 changes: 3 additions & 0 deletions docs/macros/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
from pygerber.gerber.api import GerberFile


os.environ["MKDOCS_MODE"] = "1"


def define_env(env: MacrosPlugin) -> None:
"""This is the hook for defining variables, macros and filters
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ markdown_extensions:
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
use_pygments: true
- pymdownx.inlinehilite
- pymdownx.snippets
- pymdownx.superfences:
Expand Down
5 changes: 3 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pygerber"
version = "3.0.0a0"
version = "3.0.0a2"
description = "Parsing, formatting and rendering toolkit for Gerber X3 file format"
authors = ["Krzysztof Wisniewski <[email protected]>"]
license = "MIT"
Expand Down Expand Up @@ -59,6 +59,7 @@ pygls = { version = "^1.0.2", optional = true }
lsprotocol = { version = "^2023.0.0a3", optional = true }
drawsvg = { version = "^2.3.0", optional = true }
typing-extensions = "^4.12.2"
pygments = { version = "^2.18.0", optional = true }

[tool.poetry.group.dev.dependencies]
mypy = "^1.6.1"
Expand Down Expand Up @@ -100,15 +101,19 @@ black = "^24.4.0"
[tool.poetry.extras]
language_server = ["pygls", "lsprotocol"]
svg = ["drawsvg"]
all = ["pygls", "lsprotocol", "drawsvg"]
pygments = ["pygments"]
all = ["pygls", "lsprotocol", "drawsvg", "pygments"]

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.poetry.scripts]
pygerber = "pygerber.__main__:main"
pygerber_language_server = "pygerber.gerber.language_server.__main__:main"

[tool.poetry.plugins."pygments.lexers"]
pygerber_docs_lexer = "pygerber.gerber.pygments:PyGerberDocsLexer"
gerber_lexer = "pygerber.gerber.pygments:GerberLexer"

[tool.poe.tasks]
# -------------------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion src/pygerber/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

from __future__ import annotations

__version__ = "3.0.0a1"
__version__ = "3.0.0a2"
149 changes: 149 additions & 0 deletions src/pygerber/gerber/pygments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
"""The `pygments` module provides Pygments lexer for Gerber files."""

from __future__ import annotations

import importlib.util
import os
from typing import Any, ClassVar, Dict, List, Optional

_IS_PYGMENTS_AVAILABLE: Optional[bool] = None


def is_pygments_available() -> bool:
"""Check if the language server feature is available."""
global _IS_PYGMENTS_AVAILABLE # noqa: PLW0603

if _IS_PYGMENTS_AVAILABLE is None:
try:
_spec_pygls = importlib.util.find_spec("pygls")
_spec_lsprotocol = importlib.util.find_spec("lsprotocol")

except (ImportError, ValueError):
return False

else:
_IS_PYGMENTS_AVAILABLE = (_spec_pygls is not None) and (
_spec_lsprotocol is not None
)

return _IS_PYGMENTS_AVAILABLE


if is_pygments_available():
from pygments.lexer import RegexLexer
from pygments.lexers.python import PythonLexer
from pygments.token import Keyword, Name, Number

class GerberLexer(RegexLexer):
"""The `GerberLexer` class is a Pygments lexer for Gerber files."""

name = "Gerber"
aliases: ClassVar[List[str]] = ["gerber", "gbr", "grb", "gerberx3"]
filenames: ClassVar[List[str]] = ["*.grb", "*.gbr"]

tokens: ClassVar[Dict[str, Any]] = {
"root": [
(r"X(?=[0-9]+)", Keyword),
(r"Y(?=[0-9]+)", Keyword),
(r"I(?=[0-9]+)", Keyword),
(r"J(?=[0-9]+)", Keyword),
(r"(?<=%)FS", Keyword),
(r"(?<=%FS)[LT][AI]", Keyword.Constant),
(r"(?<=%)TA", Keyword),
(r"(?<=%)TO", Keyword),
(r"(?<=%)TF", Keyword),
(r"(?<=%)TD", Keyword),
(r"(?<=%)MO(?=MM|IN)", Keyword),
(r"(?<=%MO)(MM|IN)", Keyword.Constant),
(r"(?<=%ADD[0-9])[._a-zA-Z$][._a-zA-Z0-9]*", Name.Class),
(r"(?<=%ADD[0-9][0-9])[._a-zA-Z$][._a-zA-Z0-9]*", Name.Class),
(r"(?<=%ADD[0-9][0-9][0-9])[._a-zA-Z$][._a-zA-Z0-9]*", Name.Class),
(r"(?<=%)AD", Keyword),
(r"(?<=%)AM", Keyword),
(r"(?<=%AM)[._a-zA-Z$][._a-zA-Z0-9]*", Name.Class),
(r"(?<=%)AB", Keyword),
(r"(?<=%)SR", Keyword),
(r"D0*1(?=\*)", Keyword),
(r"D0*2(?=\*)", Keyword),
(r"D0*3(?=\*)", Keyword),
(r"D[0-9]+", Name.Variable),
(r"G[0-9]+", Keyword),
(r"M[0-9]+", Keyword),
(r"[0-9]+\.[0-9]+", Number),
(r"[0-9]+", Number),
]
}

class PyGerberDocsLexer(PythonLexer):
"""The `PyGerberDocsLexer` class is a Pygments lexer for Gerber files."""

name = "PyGerber Python Docs Lexer"
aliases: ClassVar[List[str]] = ["docspygerberlexer"]
filenames: ClassVar[List[str]] = []

EXTRA_SYMBOLS: ClassVar[dict[str, Any]] = {
**{ # noqa: PIE800
"GerberX3Builder": Name.Class,
"new_pad": Name.Function,
"add_pad": Name.Function,
"add_cutout_pad": Name.Function,
"add_trace": Name.Function,
"get_code": Name.Function,
"set_standard_attributes": Name.Function,
},
**{ # noqa: PIE800
"GerberX3Builder": Name.Class,
"dump": Name.Function,
"dumps": Name.Function,
"raw": Name.Property,
},
**{ # noqa: PIE800
"PadCreator": Name.Class,
"circle": Name.Function,
"rectangle": Name.Function,
"rounded_rectangle": Name.Function,
"regular_polygon": Name.Function,
"custom": Name.Function,
},
**{ # noqa: PIE800
"Pad": Name.Class,
"set_aperture_function": Name.Function,
"set_drill_tolerance": Name.Function,
"set_custom_attribute": Name.Function,
},
**{ # noqa: PIE800
"CustomPadCreator": Name.Class,
"create": Name.Function,
"add_circle": Name.Function,
"cut_circle": Name.Function,
"add_vector_line": Name.Function,
"cut_vector_line": Name.Function,
"add_center_line": Name.Function,
"cut_center_line": Name.Function,
"add_outline": Name.Function,
"cut_outline": Name.Function,
"add_polygon": Name.Function,
"cut_polygon": Name.Function,
"add_thermal": Name.Function,
},
**{ # noqa: PIE800
"Draw": Name.Class,
},
**{ # noqa: PIE800
"PadDraw": Name.Class,
},
**{ # noqa: PIE800
"TraceDraw": Name.Class,
},
}

def get_tokens_unprocessed(self, text: Any) -> Any:
"""Get tokens from the text."""
if os.environ["MKDOCS_MODE"] == "1":
for index, token, value in super().get_tokens_unprocessed(text):
if token is Name and value in self.EXTRA_SYMBOLS:
yield index, self.EXTRA_SYMBOLS[value], value
else:
yield index, token, value
else:
yield from super().get_tokens_unprocessed(text)

0 comments on commit f563cf9

Please sign in to comment.