Skip to content

Commit 9dd573e

Browse files
committed
Make pcpp more optional
1 parent 2a17b27 commit 9dd573e

File tree

2 files changed

+66
-31
lines changed

2 files changed

+66
-31
lines changed

cxxheaderparser/preprocessor.py

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,37 @@
88
import typing
99
from .options import PreprocessorFunction
1010

11-
from pcpp import Preprocessor, OutputDirective, Action
12-
1311

1412
class PreprocessorError(Exception):
1513
pass
1614

1715

18-
class _CustomPreprocessor(Preprocessor):
19-
def __init__(
20-
self,
21-
encoding: typing.Optional[str],
22-
passthru_includes: typing.Optional["re.Pattern"],
23-
):
24-
Preprocessor.__init__(self)
25-
self.errors: typing.List[str] = []
26-
self.assume_encoding = encoding
27-
self.passthru_includes = passthru_includes
16+
try:
17+
import pcpp
18+
from pcpp import Preprocessor, OutputDirective, Action
19+
20+
class _CustomPreprocessor(Preprocessor):
21+
def __init__(
22+
self,
23+
encoding: typing.Optional[str],
24+
passthru_includes: typing.Optional["re.Pattern"],
25+
):
26+
Preprocessor.__init__(self)
27+
self.errors: typing.List[str] = []
28+
self.assume_encoding = encoding
29+
self.passthru_includes = passthru_includes
2830

29-
def on_error(self, file, line, msg):
30-
self.errors.append(f"{file}:{line} error: {msg}")
31+
def on_error(self, file, line, msg):
32+
self.errors.append(f"{file}:{line} error: {msg}")
3133

32-
def on_include_not_found(self, *ignored):
33-
raise OutputDirective(Action.IgnoreAndPassThrough)
34+
def on_include_not_found(self, *ignored):
35+
raise OutputDirective(Action.IgnoreAndPassThrough)
3436

35-
def on_comment(self, *ignored):
36-
return True
37+
def on_comment(self, *ignored):
38+
return True
39+
40+
except ImportError:
41+
pcpp = None
3742

3843

3944
def _filter_self(fname: str, fp: typing.TextIO) -> str:
@@ -82,6 +87,9 @@ def make_pcpp_preprocessor(
8287
8388
"""
8489

90+
if pcpp is None:
91+
raise PreprocessorError("pcpp is not installed")
92+
8593
def _preprocess_file(filename: str, content: str) -> str:
8694
pp = _CustomPreprocessor(encoding, passthru_includes)
8795
if include_paths:

tests/test_preprocessor.py

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import os
22
import pathlib
3+
import pytest
34
import re
5+
import shutil
6+
import subprocess
7+
import typing
48

5-
from cxxheaderparser.options import ParserOptions
6-
from cxxheaderparser.preprocessor import make_pcpp_preprocessor
9+
from cxxheaderparser.options import ParserOptions, PreprocessorFunction
10+
from cxxheaderparser import preprocessor
711
from cxxheaderparser.simple import (
812
NamespaceScope,
913
ParsedData,
@@ -22,12 +26,26 @@
2226
)
2327

2428

25-
def test_basic_preprocessor() -> None:
29+
@pytest.fixture(params=["pcpp"])
30+
def make_pp(request) -> typing.Callable[..., PreprocessorFunction]:
31+
param = request.param
32+
if param == "pcpp":
33+
if preprocessor.pcpp is None:
34+
pytest.skip("pcpp not installed")
35+
return preprocessor.make_pcpp_preprocessor
36+
else:
37+
assert False
38+
39+
40+
def test_basic_preprocessor(
41+
make_pp: typing.Callable[..., PreprocessorFunction]
42+
) -> None:
2643
content = """
2744
#define X 1
2845
int x = X;
2946
"""
30-
options = ParserOptions(preprocessor=make_pcpp_preprocessor())
47+
48+
options = ParserOptions(preprocessor=make_pp())
3149
data = parse_string(content, cleandoc=True, options=options)
3250

3351
assert data == ParsedData(
@@ -45,7 +63,10 @@ def test_basic_preprocessor() -> None:
4563
)
4664

4765

48-
def test_preprocessor_omit_content(tmp_path: pathlib.Path) -> None:
66+
def test_preprocessor_omit_content(
67+
make_pp: typing.Callable[..., PreprocessorFunction],
68+
tmp_path: pathlib.Path,
69+
) -> None:
4970
"""Ensure that content in other headers is omitted"""
5071
h_content = '#include "t2.h"' "\n" "int x = X;\n"
5172
h2_content = "#define X 2\n" "int omitted = 1;\n"
@@ -56,7 +77,7 @@ def test_preprocessor_omit_content(tmp_path: pathlib.Path) -> None:
5677
with open(tmp_path / "t2.h", "w") as fp:
5778
fp.write(h2_content)
5879

59-
options = ParserOptions(preprocessor=make_pcpp_preprocessor())
80+
options = ParserOptions(preprocessor=make_pp())
6081
data = parse_file(tmp_path / "t1.h", options=options)
6182

6283
assert data == ParsedData(
@@ -74,7 +95,10 @@ def test_preprocessor_omit_content(tmp_path: pathlib.Path) -> None:
7495
)
7596

7697

77-
def test_preprocessor_omit_content2(tmp_path: pathlib.Path) -> None:
98+
def test_preprocessor_omit_content2(
99+
make_pp: typing.Callable[..., PreprocessorFunction],
100+
tmp_path: pathlib.Path,
101+
) -> None:
78102
"""
79103
Ensure that content in other headers is omitted while handling pcpp
80104
relative path quirk
@@ -91,9 +115,7 @@ def test_preprocessor_omit_content2(tmp_path: pathlib.Path) -> None:
91115
with open(tmp_path2 / "t2.h", "w") as fp:
92116
fp.write(h2_content)
93117

94-
options = ParserOptions(
95-
preprocessor=make_pcpp_preprocessor(include_paths=[str(tmp_path)])
96-
)
118+
options = ParserOptions(preprocessor=make_pp(include_paths=[str(tmp_path)]))
97119

98120
# Weirdness happens here
99121
os.chdir(tmp_path)
@@ -114,7 +136,9 @@ def test_preprocessor_omit_content2(tmp_path: pathlib.Path) -> None:
114136
)
115137

116138

117-
def test_preprocessor_encoding(tmp_path: pathlib.Path) -> None:
139+
def test_preprocessor_encoding(
140+
make_pp: typing.Callable[..., PreprocessorFunction], tmp_path: pathlib.Path
141+
) -> None:
118142
"""Ensure we can handle alternate encodings"""
119143
h_content = b"// \xa9 2023 someone\n" b'#include "t2.h"' b"\n" b"int x = X;\n"
120144

@@ -126,7 +150,7 @@ def test_preprocessor_encoding(tmp_path: pathlib.Path) -> None:
126150
with open(tmp_path / "t2.h", "wb") as fp:
127151
fp.write(h2_content)
128152

129-
options = ParserOptions(preprocessor=make_pcpp_preprocessor(encoding="cp1252"))
153+
options = ParserOptions(preprocessor=make_pp(encoding="cp1252"))
130154
data = parse_file(tmp_path / "t1.h", options=options, encoding="cp1252")
131155

132156
assert data == ParsedData(
@@ -144,6 +168,7 @@ def test_preprocessor_encoding(tmp_path: pathlib.Path) -> None:
144168
)
145169

146170

171+
@pytest.mark.skipif(preprocessor.pcpp is None, reason="pcpp not installed")
147172
def test_preprocessor_passthru_includes(tmp_path: pathlib.Path) -> None:
148173
"""Ensure that all #include pass through"""
149174
h_content = '#include "t2.h"\n'
@@ -155,7 +180,9 @@ def test_preprocessor_passthru_includes(tmp_path: pathlib.Path) -> None:
155180
fp.write("")
156181

157182
options = ParserOptions(
158-
preprocessor=make_pcpp_preprocessor(passthru_includes=re.compile(".+"))
183+
preprocessor=preprocessor.make_pcpp_preprocessor(
184+
passthru_includes=re.compile(".+")
185+
)
159186
)
160187
data = parse_file(tmp_path / "t1.h", options=options)
161188

0 commit comments

Comments
 (0)