cyberark-tpc-plugin-parser 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. cyberark_tpc_plugin_parser-0.1.0/LICENSE +21 -0
  2. cyberark_tpc_plugin_parser-0.1.0/PKG-INFO +72 -0
  3. cyberark_tpc_plugin_parser-0.1.0/README.md +34 -0
  4. cyberark_tpc_plugin_parser-0.1.0/cyberark_tpc_plugin_parser.egg-info/PKG-INFO +72 -0
  5. cyberark_tpc_plugin_parser-0.1.0/cyberark_tpc_plugin_parser.egg-info/SOURCES.txt +25 -0
  6. cyberark_tpc_plugin_parser-0.1.0/cyberark_tpc_plugin_parser.egg-info/dependency_links.txt +1 -0
  7. cyberark_tpc_plugin_parser-0.1.0/cyberark_tpc_plugin_parser.egg-info/top_level.txt +1 -0
  8. cyberark_tpc_plugin_parser-0.1.0/pyproject.toml +34 -0
  9. cyberark_tpc_plugin_parser-0.1.0/setup.cfg +4 -0
  10. cyberark_tpc_plugin_parser-0.1.0/tests/test_lexer.py +263 -0
  11. cyberark_tpc_plugin_parser-0.1.0/tests/test_parser.py +44 -0
  12. cyberark_tpc_plugin_parser-0.1.0/tpc_plugin_parser/__init__..py +1 -0
  13. cyberark_tpc_plugin_parser-0.1.0/tpc_plugin_parser/lexer/__init__.py +1 -0
  14. cyberark_tpc_plugin_parser-0.1.0/tpc_plugin_parser/lexer/lexer.py +238 -0
  15. cyberark_tpc_plugin_parser-0.1.0/tpc_plugin_parser/lexer/tokens/__init__.py +1 -0
  16. cyberark_tpc_plugin_parser-0.1.0/tpc_plugin_parser/lexer/tokens/assignment.py +16 -0
  17. cyberark_tpc_plugin_parser-0.1.0/tpc_plugin_parser/lexer/tokens/comment.py +14 -0
  18. cyberark_tpc_plugin_parser-0.1.0/tpc_plugin_parser/lexer/tokens/cpm_parameter_validation.py +17 -0
  19. cyberark_tpc_plugin_parser-0.1.0/tpc_plugin_parser/lexer/tokens/fail_state.py +16 -0
  20. cyberark_tpc_plugin_parser-0.1.0/tpc_plugin_parser/lexer/tokens/parse_error.py +14 -0
  21. cyberark_tpc_plugin_parser-0.1.0/tpc_plugin_parser/lexer/tokens/section_header.py +14 -0
  22. cyberark_tpc_plugin_parser-0.1.0/tpc_plugin_parser/lexer/tokens/transition.py +16 -0
  23. cyberark_tpc_plugin_parser-0.1.0/tpc_plugin_parser/lexer/utilities/__init__.py +1 -0
  24. cyberark_tpc_plugin_parser-0.1.0/tpc_plugin_parser/lexer/utilities/regex.py +10 -0
  25. cyberark_tpc_plugin_parser-0.1.0/tpc_plugin_parser/lexer/utilities/token_name.py +15 -0
  26. cyberark_tpc_plugin_parser-0.1.0/tpc_plugin_parser/lexer/utilities/types.py +23 -0
  27. cyberark_tpc_plugin_parser-0.1.0/tpc_plugin_parser/parser.py +89 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 (Peter McDonald)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.4
2
+ Name: cyberark-tpc-plugin-parser
3
+ Version: 0.1.0
4
+ Summary: A package to help parse CyberArk TPC plugin prompts and process files.
5
+ Author-email: Peter McDonald <git@petermcdonald.co.uk>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 (Peter McDonald)
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Classifier: Programming Language :: Python
29
+ Classifier: Programming Language :: Python :: 3.11
30
+ Classifier: Programming Language :: Python :: 3.12
31
+ Classifier: Programming Language :: Python :: 3.13
32
+ Classifier: Programming Language :: Python :: 3.14
33
+ Classifier: Programming Language :: Python :: 3.15
34
+ Requires-Python: >=3.11
35
+ Description-Content-Type: text/markdown
36
+ License-File: LICENSE
37
+ Dynamic: license-file
38
+
39
+ # CyberArk TPC Plugin Parser
40
+
41
+ CyberArk TPC Plugin Parser is a package intended to make it easy to work with CyberArk process
42
+ and platform ini files.
43
+
44
+ The lexer and parser breaks down the files to their constituent parts so that they can be
45
+ further processed by other applications.
46
+
47
+ ## Installation
48
+
49
+ CyberArk TPC Plugin Parser can be installed using Pip:
50
+
51
+ ```bash
52
+ pip install cyberark-tpc-plugin-parser
53
+ ```
54
+
55
+ ## Usage
56
+
57
+ The main point of entry is the parser, this takes the content of the process and prompts
58
+ files as arguments:
59
+
60
+ ```python
61
+ from tpc_plugin_parser.parser import Parser
62
+
63
+ with open('/path/to/files/process.ini') as process_fh:
64
+ process_content: str = process_fh.read()
65
+
66
+ with open('/path/to/files/prompts.ini') as prompts_fh:
67
+ prompts_content: str = prompts_fh.read()
68
+
69
+ parser: Parser = Parser(process_file=process_content, prompts_file=prompts_content)
70
+ process_file_tokens = parser.process_file
71
+ prompt_file_tokens = parser.prompts_file
72
+ ```
@@ -0,0 +1,34 @@
1
+ # CyberArk TPC Plugin Parser
2
+
3
+ CyberArk TPC Plugin Parser is a package intended to make it easy to work with CyberArk process
4
+ and platform ini files.
5
+
6
+ The lexer and parser breaks down the files to their constituent parts so that they can be
7
+ further processed by other applications.
8
+
9
+ ## Installation
10
+
11
+ CyberArk TPC Plugin Parser can be installed using Pip:
12
+
13
+ ```bash
14
+ pip install cyberark-tpc-plugin-parser
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ The main point of entry is the parser, this takes the content of the process and prompts
20
+ files as arguments:
21
+
22
+ ```python
23
+ from tpc_plugin_parser.parser import Parser
24
+
25
+ with open('/path/to/files/process.ini') as process_fh:
26
+ process_content: str = process_fh.read()
27
+
28
+ with open('/path/to/files/prompts.ini') as prompts_fh:
29
+ prompts_content: str = prompts_fh.read()
30
+
31
+ parser: Parser = Parser(process_file=process_content, prompts_file=prompts_content)
32
+ process_file_tokens = parser.process_file
33
+ prompt_file_tokens = parser.prompts_file
34
+ ```
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.4
2
+ Name: cyberark-tpc-plugin-parser
3
+ Version: 0.1.0
4
+ Summary: A package to help parse CyberArk TPC plugin prompts and process files.
5
+ Author-email: Peter McDonald <git@petermcdonald.co.uk>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 (Peter McDonald)
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Classifier: Programming Language :: Python
29
+ Classifier: Programming Language :: Python :: 3.11
30
+ Classifier: Programming Language :: Python :: 3.12
31
+ Classifier: Programming Language :: Python :: 3.13
32
+ Classifier: Programming Language :: Python :: 3.14
33
+ Classifier: Programming Language :: Python :: 3.15
34
+ Requires-Python: >=3.11
35
+ Description-Content-Type: text/markdown
36
+ License-File: LICENSE
37
+ Dynamic: license-file
38
+
39
+ # CyberArk TPC Plugin Parser
40
+
41
+ CyberArk TPC Plugin Parser is a package intended to make it easy to work with CyberArk process
42
+ and platform ini files.
43
+
44
+ The lexer and parser breaks down the files to their constituent parts so that they can be
45
+ further processed by other applications.
46
+
47
+ ## Installation
48
+
49
+ CyberArk TPC Plugin Parser can be installed using Pip:
50
+
51
+ ```bash
52
+ pip install cyberark-tpc-plugin-parser
53
+ ```
54
+
55
+ ## Usage
56
+
57
+ The main point of entry is the parser, this takes the content of the process and prompts
58
+ files as arguments:
59
+
60
+ ```python
61
+ from tpc_plugin_parser.parser import Parser
62
+
63
+ with open('/path/to/files/process.ini') as process_fh:
64
+ process_content: str = process_fh.read()
65
+
66
+ with open('/path/to/files/prompts.ini') as prompts_fh:
67
+ prompts_content: str = prompts_fh.read()
68
+
69
+ parser: Parser = Parser(process_file=process_content, prompts_file=prompts_content)
70
+ process_file_tokens = parser.process_file
71
+ prompt_file_tokens = parser.prompts_file
72
+ ```
@@ -0,0 +1,25 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ cyberark_tpc_plugin_parser.egg-info/PKG-INFO
5
+ cyberark_tpc_plugin_parser.egg-info/SOURCES.txt
6
+ cyberark_tpc_plugin_parser.egg-info/dependency_links.txt
7
+ cyberark_tpc_plugin_parser.egg-info/top_level.txt
8
+ tests/test_lexer.py
9
+ tests/test_parser.py
10
+ tpc_plugin_parser/__init__..py
11
+ tpc_plugin_parser/parser.py
12
+ tpc_plugin_parser/lexer/__init__.py
13
+ tpc_plugin_parser/lexer/lexer.py
14
+ tpc_plugin_parser/lexer/tokens/__init__.py
15
+ tpc_plugin_parser/lexer/tokens/assignment.py
16
+ tpc_plugin_parser/lexer/tokens/comment.py
17
+ tpc_plugin_parser/lexer/tokens/cpm_parameter_validation.py
18
+ tpc_plugin_parser/lexer/tokens/fail_state.py
19
+ tpc_plugin_parser/lexer/tokens/parse_error.py
20
+ tpc_plugin_parser/lexer/tokens/section_header.py
21
+ tpc_plugin_parser/lexer/tokens/transition.py
22
+ tpc_plugin_parser/lexer/utilities/__init__.py
23
+ tpc_plugin_parser/lexer/utilities/regex.py
24
+ tpc_plugin_parser/lexer/utilities/token_name.py
25
+ tpc_plugin_parser/lexer/utilities/types.py
@@ -0,0 +1,34 @@
1
+ [project]
2
+ name = "cyberark-tpc-plugin-parser"
3
+ version = "0.1.0"
4
+ description = "A package to help parse CyberArk TPC plugin prompts and process files."
5
+ authors = [{ name = "Peter McDonald", email = "git@petermcdonald.co.uk"}]
6
+ readme = "README.md"
7
+ license = { file = "LICENSE" }
8
+ requires-python = ">=3.11"
9
+
10
+ classifiers = [
11
+ "Programming Language :: Python",
12
+ "Programming Language :: Python :: 3.11",
13
+ "Programming Language :: Python :: 3.12",
14
+ "Programming Language :: Python :: 3.13",
15
+ "Programming Language :: Python :: 3.14",
16
+ "Programming Language :: Python :: 3.15",
17
+ ]
18
+
19
+ dependencies = []
20
+
21
+ [dependency-groups]
22
+ dev = [
23
+ "pytest>=8.4.2",
24
+ "pytest-cov>=7.0.0",
25
+ "ruff>=0.13.0",
26
+ "troml>=0.4.1",
27
+ "ty>=0.0.1a20",
28
+ ]
29
+
30
+ [tool.pytest.ini_options]
31
+ addopts = "--cov=tpc_plugin_parser --cov-report term-missing --cov-report xml:coverage.xml"
32
+
33
+ [tool.ruff]
34
+ line-length = 120
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,263 @@
1
+ """Test the lexer."""
2
+
3
+ import pytest
4
+ from tpc_plugin_parser.lexer.lexer import Lexer
5
+ from tpc_plugin_parser.lexer.tokens.assignment import Assignment
6
+ from tpc_plugin_parser.lexer.tokens.comment import Comment
7
+ from tpc_plugin_parser.lexer.tokens.cpm_parameter_validation import CPMParameterValidation
8
+ from tpc_plugin_parser.lexer.tokens.fail_state import FailState
9
+ from tpc_plugin_parser.lexer.tokens.parse_error import ParseError
10
+ from tpc_plugin_parser.lexer.tokens.section_header import SectionHeader
11
+ from tpc_plugin_parser.lexer.tokens.transition import Transition
12
+ from tpc_plugin_parser.lexer.utilities.token_name import TokenName
13
+
14
+
15
+ class TestLexer(object):
16
+ """Test the lexer."""
17
+
18
+ @pytest.mark.parametrize(
19
+ "line,expected_token_list",
20
+ [
21
+ (
22
+ 'password, source=FILE, Mandatory=![string equal -nocase "<username>" ""], allowcharacters=abc',
23
+ [
24
+ (
25
+ TokenName.CPM_PARAMETER_VALIDATION,
26
+ CPMParameterValidation(
27
+ line_number=1,
28
+ name="password",
29
+ source="FILE",
30
+ mandatory='![string equal -nocase "<username>" ""]',
31
+ allow_characters="abc",
32
+ token_name=TokenName.CPM_PARAMETER_VALIDATION.value,
33
+ ),
34
+ ),
35
+ ],
36
+ ),
37
+ (
38
+ "TestVar",
39
+ [
40
+ (
41
+ TokenName.ASSIGNMENT,
42
+ Assignment(
43
+ line_number=1,
44
+ name="TestVar",
45
+ equals=None,
46
+ assigned=None,
47
+ ),
48
+ ),
49
+ ],
50
+ ),
51
+ (
52
+ "testvar = ",
53
+ [
54
+ (
55
+ TokenName.ASSIGNMENT,
56
+ Assignment(
57
+ line_number=1,
58
+ name="testvar",
59
+ equals="=",
60
+ assigned=None,
61
+ ),
62
+ ),
63
+ ],
64
+ ),
65
+ (
66
+ "test_var = 123",
67
+ [
68
+ (
69
+ TokenName.ASSIGNMENT,
70
+ Assignment(
71
+ line_number=1,
72
+ name="test_var",
73
+ equals="=",
74
+ assigned="123",
75
+ ),
76
+ ),
77
+ ],
78
+ ),
79
+ (
80
+ "# this is a standard comment ",
81
+ [
82
+ (
83
+ TokenName.COMMENT,
84
+ Comment(
85
+ line_number=1,
86
+ content="# this is a standard comment",
87
+ ),
88
+ ),
89
+ ],
90
+ ),
91
+ (
92
+ ";this is a standard comment",
93
+ [
94
+ (
95
+ TokenName.COMMENT,
96
+ Comment(
97
+ line_number=1,
98
+ content=";this is a standard comment",
99
+ ),
100
+ ),
101
+ ],
102
+ ),
103
+ (
104
+ "standard=FAIL('This is a standard fail state', 1234)",
105
+ [
106
+ (
107
+ TokenName.FAIL_STATE,
108
+ FailState(
109
+ name="standard",
110
+ line_number=1,
111
+ message="'This is a standard fail state'",
112
+ code=1234,
113
+ ),
114
+ ),
115
+ ],
116
+ ),
117
+ (
118
+ "standard = FAIL ('This is a standard fail state, isn't it?', 2468)",
119
+ [
120
+ (
121
+ TokenName.FAIL_STATE,
122
+ FailState(
123
+ name="standard",
124
+ line_number=1,
125
+ message="'This is a standard fail state, isn't it?'",
126
+ code=2468,
127
+ ),
128
+ ),
129
+ ],
130
+ ),
131
+ (
132
+ "standard = FAIL (This is a standard fail state, isn't it?, 2468)",
133
+ [
134
+ (
135
+ TokenName.FAIL_STATE,
136
+ FailState(
137
+ name="standard",
138
+ line_number=1,
139
+ message="This is a standard fail state, isn't it?",
140
+ code=2468,
141
+ ),
142
+ ),
143
+ ],
144
+ ),
145
+ (
146
+ "[Some Section Header]",
147
+ [
148
+ (
149
+ TokenName.SECTION_HEADER,
150
+ SectionHeader(
151
+ line_number=1,
152
+ name="Some Section Header",
153
+ ),
154
+ ),
155
+ ],
156
+ ),
157
+ (
158
+ " [Some Section Header] ",
159
+ [
160
+ (
161
+ TokenName.SECTION_HEADER,
162
+ SectionHeader(
163
+ line_number=1,
164
+ name="Some Section Header",
165
+ ),
166
+ ),
167
+ ],
168
+ ),
169
+ (
170
+ "state1,condition,state2",
171
+ [
172
+ (
173
+ TokenName.TRANSITION,
174
+ Transition(
175
+ line_number=1,
176
+ current_state="state1",
177
+ condition="condition",
178
+ next_state="state2",
179
+ ),
180
+ ),
181
+ ],
182
+ ),
183
+ (
184
+ " state_1 , condition2 , STATE2 ",
185
+ [
186
+ (
187
+ TokenName.TRANSITION,
188
+ Transition(
189
+ line_number=1,
190
+ current_state="state_1",
191
+ condition="condition2",
192
+ next_state="STATE2",
193
+ ),
194
+ ),
195
+ ],
196
+ ),
197
+ (
198
+ "[Some Section Header]\r\rTestVar\r",
199
+ [
200
+ (
201
+ TokenName.SECTION_HEADER,
202
+ SectionHeader(
203
+ line_number=1,
204
+ name="Some Section Header",
205
+ ),
206
+ ),
207
+ (
208
+ TokenName.ASSIGNMENT,
209
+ Assignment(
210
+ line_number=3,
211
+ name="TestVar",
212
+ equals=None,
213
+ assigned=None,
214
+ ),
215
+ ),
216
+ ],
217
+ ),
218
+ ],
219
+ )
220
+ def test_token(self, line: str, expected_token_list) -> None:
221
+ """
222
+ Test to ensure that a token parses ok.
223
+
224
+ :param line: The line to be parsed.
225
+ :param expected_token_list: A copy of the object we expect to receive back.
226
+ """
227
+ lexer = Lexer(source=line)
228
+ tokens = lexer.tokens
229
+
230
+ # The following 2 lines have been added to ensure we receive the same tokens after process running twice.
231
+ lexer.process()
232
+ tokens_post_process = lexer.tokens
233
+
234
+ assert tokens == expected_token_list
235
+ assert tokens_post_process == tokens
236
+
237
+ @pytest.mark.parametrize(
238
+ "line,expected_tokens",
239
+ [
240
+ (
241
+ "This line will not match.",
242
+ [
243
+ ParseError(
244
+ line_number=1,
245
+ content="This line will not match.",
246
+ )
247
+ ],
248
+ ),
249
+ ],
250
+ )
251
+ def test_unmatched_lines(self, line: str, expected_tokens: list[ParseError]) -> None:
252
+ """
253
+ Test to ensure that the lexer returns a ParseError token.
254
+
255
+ :param line: The line to be parsed.
256
+ :param expected_tokens: Parse error tokens.
257
+ """
258
+ lex: Lexer = Lexer(source=line)
259
+ lex.process()
260
+ found_tokens = lex.tokens
261
+ assert len(found_tokens) == len(expected_tokens)
262
+ assert found_tokens[0][1].content == expected_tokens[0].content
263
+ assert found_tokens[0][1].line_number == expected_tokens[0].line_number
@@ -0,0 +1,44 @@
1
+ """Test the parser."""
2
+
3
+ import pytest
4
+ from tpc_plugin_parser.parser import Parser
5
+
6
+
7
+ class TestParser(object):
8
+ """Test the lexer."""
9
+
10
+ @pytest.mark.parametrize(
11
+ "process_file,prompts_file",
12
+ [
13
+ (
14
+ "tests/data/process.ini",
15
+ "tests/data/prompts.ini",
16
+ ),
17
+ ],
18
+ )
19
+ def test_parser(self, process_file: str, prompts_file: str) -> None:
20
+ """
21
+ Test to ensure that a token parses ok.
22
+
23
+ :param process_file: Path to the process file.
24
+ :param prompts_file: Path to the prompts file.
25
+ """
26
+ with open(process_file, "r") as process_file:
27
+ process_content = process_file.read()
28
+
29
+ with open(prompts_file, "r") as prompts_file:
30
+ prompts_content = prompts_file.read()
31
+
32
+ parser = Parser(process_file=process_content, prompts_file=prompts_content)
33
+
34
+ assert len(parser.process_file) == 6
35
+ assert len(parser.process_file["default"]) == 6
36
+ assert len(parser.process_file["states"]) == 8
37
+ assert len(parser.process_file["transitions"]) == 7
38
+ assert len(parser.process_file["CPM Parameters Validation"]) == 5
39
+ assert len(parser.process_file["parameters"]) == 5
40
+ assert len(parser.process_file["Debug Information"]) == 7
41
+
42
+ assert len(parser.prompts_file) == 2
43
+ assert len(parser.prompts_file["default"]) == 6
44
+ assert len(parser.prompts_file["conditions"]) == 8
@@ -0,0 +1 @@
1
+ """Standard blank init file for the package."""
@@ -0,0 +1 @@
1
+ """Standard blank init file for the package."""
@@ -0,0 +1,238 @@
1
+ """Lexer module for reading and processing configuration files."""
2
+
3
+ import re
4
+
5
+ from tpc_plugin_parser.lexer.tokens.assignment import Assignment
6
+ from tpc_plugin_parser.lexer.tokens.comment import Comment
7
+ from tpc_plugin_parser.lexer.tokens.cpm_parameter_validation import (
8
+ CPMParameterValidation,
9
+ )
10
+ from tpc_plugin_parser.lexer.tokens.fail_state import FailState
11
+ from tpc_plugin_parser.lexer.tokens.parse_error import ParseError
12
+ from tpc_plugin_parser.lexer.tokens.section_header import SectionHeader
13
+ from tpc_plugin_parser.lexer.tokens.transition import Transition
14
+ from tpc_plugin_parser.lexer.utilities.regex import (
15
+ ASSIGNMENT,
16
+ COMMENT,
17
+ CPM_PARAMETER_VALIDATION,
18
+ FAIL_STATE,
19
+ SECTION_HEADER,
20
+ TRANSITION,
21
+ )
22
+ from tpc_plugin_parser.lexer.utilities.token_name import TokenName
23
+ from tpc_plugin_parser.lexer.utilities.types import ALL_TOKEN_TYPES, TokenSpecs
24
+
25
+
26
+ class Lexer(object):
27
+ """Object to handle processing the ini files."""
28
+
29
+ __slots__ = (
30
+ "_tokens",
31
+ "_source",
32
+ "_token_specs",
33
+ )
34
+
35
+ def __init__(self, source: str) -> None:
36
+ """Standard init for the Lexer object."""
37
+
38
+ self._tokens: list[
39
+ tuple[
40
+ TokenName,
41
+ ALL_TOKEN_TYPES,
42
+ ]
43
+ ] = []
44
+ self._source: str = source
45
+ self._token_specs: list[TokenSpecs] = [
46
+ {
47
+ "pattern": re.compile(ASSIGNMENT, re.IGNORECASE),
48
+ "token_name": TokenName.ASSIGNMENT,
49
+ "processor_method": "_process_assignment",
50
+ },
51
+ {
52
+ "pattern": re.compile(COMMENT, re.IGNORECASE),
53
+ "token_name": TokenName.COMMENT,
54
+ "processor_method": "_process_comment",
55
+ },
56
+ {
57
+ "pattern": re.compile(CPM_PARAMETER_VALIDATION, re.IGNORECASE),
58
+ "token_name": TokenName.CPM_PARAMETER_VALIDATION,
59
+ "processor_method": "_process_cpm_parameter_validation",
60
+ },
61
+ {
62
+ "pattern": re.compile(FAIL_STATE, re.IGNORECASE),
63
+ "token_name": TokenName.FAIL_STATE,
64
+ "processor_method": "_process_fail_state",
65
+ },
66
+ {
67
+ "pattern": re.compile(SECTION_HEADER, re.IGNORECASE),
68
+ "token_name": TokenName.SECTION_HEADER,
69
+ "processor_method": "_process_section_header",
70
+ },
71
+ {
72
+ "pattern": re.compile(TRANSITION, re.IGNORECASE),
73
+ "token_name": TokenName.TRANSITION,
74
+ "processor_method": "_process_transitions",
75
+ },
76
+ ]
77
+
78
+ def process(self) -> None:
79
+ """
80
+ Process the content of the file line by line.
81
+ """
82
+
83
+ if self._tokens:
84
+ # Returning as we have parsed the data already.
85
+ return
86
+
87
+ for line_number, line in enumerate(self._source.splitlines(), start=1):
88
+ for token_spec in self._token_specs:
89
+ if match := token_spec["pattern"].match(line):
90
+ getattr(self, token_spec["processor_method"])(match=match, line_number=line_number)
91
+ break
92
+ else:
93
+ if line.strip():
94
+ self._process_parse_error(line=line, line_number=line_number)
95
+
96
+ def _process_assignment(self, match: re.Match, line_number: int) -> None:
97
+ """
98
+ Process a variable assignment line
99
+
100
+ :param match: Regex match of the assignment.
101
+ """
102
+ name: str = str(match.group("name")).strip()
103
+ equals = str(match.group("equals")).strip() if match.groupdict().get("equals", None) else None
104
+ assigned_stripped = str(match.group("value")).strip() if match.groupdict().get("value", None) else None
105
+ assigned = assigned_stripped or None
106
+ self._tokens.append(
107
+ (
108
+ TokenName.ASSIGNMENT,
109
+ Assignment(
110
+ name=name,
111
+ equals=equals,
112
+ assigned=assigned,
113
+ line_number=line_number,
114
+ ),
115
+ )
116
+ )
117
+
118
+ def _process_comment(self, match: re.Match, line_number: int) -> None:
119
+ """
120
+ Process the provided comment.
121
+
122
+ :param match: Regex match of the comment.
123
+ """
124
+ self._tokens.append(
125
+ (
126
+ TokenName.COMMENT,
127
+ Comment(
128
+ content=str(match.group("comment")).strip(),
129
+ line_number=line_number,
130
+ ),
131
+ )
132
+ )
133
+
134
+ def _process_cpm_parameter_validation(self, match: re.Match, line_number: int) -> None:
135
+ """
136
+ Process the provided parameter validation.
137
+
138
+ :param match: Regex match of the parameter validation.
139
+ """
140
+ allow_characters: str | None = None
141
+ if match.group("allowcharacters"):
142
+ allow_characters = str(match.group("allowcharacters")).strip()
143
+
144
+ self._tokens.append(
145
+ (
146
+ TokenName.CPM_PARAMETER_VALIDATION,
147
+ CPMParameterValidation(
148
+ name=str(match.group("name")),
149
+ source=str(match.group("source")),
150
+ mandatory=str(match.group("mandatory")),
151
+ allow_characters=allow_characters,
152
+ line_number=line_number,
153
+ ),
154
+ )
155
+ )
156
+
157
+ def _process_fail_state(self, match: re.Match, line_number: int) -> None:
158
+ """
159
+ Process the provided fail state .
160
+
161
+ :param match: Regex match of the fail state.
162
+ """
163
+ self._tokens.append(
164
+ (
165
+ TokenName.FAIL_STATE,
166
+ FailState(
167
+ name=str(match.group("name")).strip(),
168
+ message=str(match.group("message")).strip(),
169
+ code=int(match.group("code")),
170
+ line_number=line_number,
171
+ ),
172
+ )
173
+ )
174
+
175
+ def _process_parse_error(self, line: str, line_number: int) -> None:
176
+ """
177
+ Process a line that has a parse error.
178
+
179
+ :param line: The line that has the parse error.
180
+ :param line_number: The line number of the line that has the parse error.
181
+ """
182
+ self._tokens.append(
183
+ (
184
+ TokenName.PARSE_ERROR,
185
+ ParseError(
186
+ content=line.strip(),
187
+ line_number=line_number,
188
+ ),
189
+ )
190
+ )
191
+
192
+ def _process_section_header(self, match: re.Match, line_number: int) -> None:
193
+ """
194
+ Process the provided section header.
195
+
196
+ :param match: Regex match of the section header.
197
+ """
198
+ self._tokens.append(
199
+ (
200
+ TokenName.SECTION_HEADER,
201
+ SectionHeader(
202
+ name=str(match.group("name").strip()),
203
+ line_number=line_number,
204
+ ),
205
+ )
206
+ )
207
+
208
+ def _process_transitions(self, match: re.Match, line_number: int) -> None:
209
+ """
210
+ Process the provided transitions.
211
+
212
+ :param match: Regex match of the transitions.
213
+ """
214
+ self._tokens.append(
215
+ (
216
+ TokenName.TRANSITION,
217
+ Transition(
218
+ current_state=str(match.group("current")).strip(),
219
+ condition=str(match.group("condition")).strip(),
220
+ next_state=str(match.group("next")).strip(),
221
+ line_number=line_number,
222
+ ),
223
+ )
224
+ )
225
+
226
+ @property
227
+ def tokens(
228
+ self,
229
+ ) -> list[
230
+ tuple[
231
+ TokenName,
232
+ ALL_TOKEN_TYPES,
233
+ ]
234
+ ]:
235
+ """A list of tokens found by the lexer."""
236
+ if not self._tokens:
237
+ self.process()
238
+ return self._tokens
@@ -0,0 +1 @@
1
+ """Standard blank init file for the package."""
@@ -0,0 +1,16 @@
1
+ """Class to hold the assignment token."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from tpc_plugin_parser.lexer.utilities.token_name import TokenName
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class Assignment(object):
10
+ """Dataclass to hold variable assignment details."""
11
+
12
+ line_number: int
13
+ name: str
14
+ equals: str | None = None
15
+ assigned: str | None = None
16
+ token_name: str = TokenName.ASSIGNMENT.value
@@ -0,0 +1,14 @@
1
+ """Class to hold the comment token."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from tpc_plugin_parser.lexer.utilities.token_name import TokenName
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class Comment(object):
10
+ """Dataclass to hold a comment."""
11
+
12
+ line_number: int
13
+ content: str
14
+ token_name: str = TokenName.COMMENT.value
@@ -0,0 +1,17 @@
1
+ """Class to hold the parameter validation token."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from tpc_plugin_parser.lexer.utilities.token_name import TokenName
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class CPMParameterValidation(object):
10
+ """Dataclass to hold variable cpm parameter validation details."""
11
+
12
+ line_number: int
13
+ name: str
14
+ source: str
15
+ mandatory: str
16
+ allow_characters: str | None = None
17
+ token_name: str = TokenName.CPM_PARAMETER_VALIDATION.value
@@ -0,0 +1,16 @@
1
+ """Class to hold the fail state token."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from tpc_plugin_parser.lexer.utilities.token_name import TokenName
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class FailState(object):
10
+ """Dataclass to hold a fail state."""
11
+
12
+ line_number: int
13
+ name: str
14
+ message: str
15
+ code: int
16
+ token_name: str = TokenName.FAIL_STATE.value
@@ -0,0 +1,14 @@
1
+ """Class to hold a parse error token."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from tpc_plugin_parser.lexer.utilities.token_name import TokenName
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class ParseError(object):
10
+ """Dataclass to hold sparse errors."""
11
+
12
+ line_number: int
13
+ content: str
14
+ token_name: str = TokenName.PARSE_ERROR.value
@@ -0,0 +1,14 @@
1
+ """Class to hold the section header token."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from tpc_plugin_parser.lexer.utilities.token_name import TokenName
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class SectionHeader(object):
10
+ """Dataclass to hold a section header name."""
11
+
12
+ line_number: int
13
+ name: str
14
+ token_name: str = TokenName.SECTION_HEADER.value
@@ -0,0 +1,16 @@
1
+ """Class to hold the state transition token."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from tpc_plugin_parser.lexer.utilities.token_name import TokenName
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class Transition(object):
10
+ """Dataclass to hold state transitions."""
11
+
12
+ line_number: int
13
+ current_state: str
14
+ condition: str
15
+ next_state: str
16
+ token_name: str = TokenName.TRANSITION.value
@@ -0,0 +1 @@
1
+ """Standard blank init file for the package."""
@@ -0,0 +1,10 @@
1
+ """List of regex the lexer uses."""
2
+
3
+ ASSIGNMENT = (
4
+ r"^(?:[\s]*)(?P<name>[\w]+)(?:(?:[\s]*)(?P<equals>=)(?:(?:[\s]*)(?P<value>(?:(?!\s*fail\s*\().)+))?)?(?:[\s]*)$"
5
+ )
6
+ COMMENT = r"^(?:[\s]*)(?P<comment>(?:[#;]+)(?:.*))(?:[\s]*)$"
7
+ CPM_PARAMETER_VALIDATION = r"^(?:[\s]*)(?P<name>[\w\\]+)(?:(?:[\s]*,[\s]*)(?:source)(?:[\s]*)=(?:[\s]*)(?P<source>[^, ]*))(?:(?:[\s]*,[\s]*)(?:mandatory)(?:[\s]*)=(?:[\s]*)(?P<mandatory>[^,]*))?(?:(?:[\s]*,[\s]*)(?:allowcharacters)(?:[\s]*)=(?:[\s]*)(?P<allowcharacters>.*))?$"
8
+ FAIL_STATE = r"^(?:[\s]*)(?P<name>[\w]+)(?:[\s]*)=(?:[\s]*)(?:[\s]*)(?:fail)(?:[\s]*)\((?:[\s]*)(?P<message>.*)(?:[\s])?,(?:[\s]*)(?P<code>[0-9]+)\)(?:[\s]*)$"
9
+ SECTION_HEADER = r"^(?:[\s]*)\[(?P<name>[\w]+(?:[\s]+[\w]+)*)](?:[\s]*)$"
10
+ TRANSITION = r"^(?:[\s]*)(?P<current>[\w]+)(?:[\s]*,[\s]*)(?P<condition>[\w]+)(?:[\s]*,[\s]*)(?P<next>[\w]+)(?:[\s]*)$"
@@ -0,0 +1,15 @@
1
+ """Enum to hold the allowed token names."""
2
+
3
+ from enum import Enum
4
+
5
+
6
+ class TokenName(Enum):
7
+ """CLass to hold the token name."""
8
+
9
+ ASSIGNMENT = "Assignment"
10
+ COMMENT = "Comment"
11
+ FAIL_STATE = "Fail State"
12
+ CPM_PARAMETER_VALIDATION = "CPM Parameter Validation"
13
+ PARSE_ERROR = "Parse Error"
14
+ SECTION_HEADER = "Section Header"
15
+ TRANSITION = "Transition"
@@ -0,0 +1,23 @@
1
+ """Module for aggregating all token types used in the lexer."""
2
+
3
+ from typing import TypedDict
4
+ import re
5
+
6
+ from tpc_plugin_parser.lexer.tokens.assignment import Assignment
7
+ from tpc_plugin_parser.lexer.tokens.comment import Comment
8
+ from tpc_plugin_parser.lexer.tokens.cpm_parameter_validation import (
9
+ CPMParameterValidation,
10
+ )
11
+ from tpc_plugin_parser.lexer.tokens.fail_state import FailState
12
+ from tpc_plugin_parser.lexer.tokens.parse_error import ParseError
13
+ from tpc_plugin_parser.lexer.tokens.section_header import SectionHeader
14
+ from tpc_plugin_parser.lexer.tokens.transition import Transition
15
+ from tpc_plugin_parser.lexer.utilities.token_name import TokenName
16
+
17
+ ALL_TOKEN_TYPES = Assignment | Comment | FailState | CPMParameterValidation | ParseError | SectionHeader | Transition
18
+
19
+
20
+ class TokenSpecs(TypedDict):
21
+ pattern: re.Pattern
22
+ token_name: TokenName
23
+ processor_method: str
@@ -0,0 +1,89 @@
1
+ """Parser module for reading and processing configuration files."""
2
+
3
+ from tpc_plugin_parser.lexer.lexer import Lexer
4
+ from tpc_plugin_parser.lexer.tokens.section_header import SectionHeader
5
+ from tpc_plugin_parser.lexer.utilities.token_name import TokenName
6
+ from tpc_plugin_parser.lexer.utilities.types import ALL_TOKEN_TYPES
7
+
8
+
9
+ class Parser(object):
10
+ """Object to handle parsing ini files."""
11
+
12
+ __slots__ = (
13
+ "_process_file",
14
+ "_prompts_file",
15
+ )
16
+
17
+ def __init__(self, process_file: str, prompts_file: str) -> None:
18
+ """
19
+ Initializes the Parser with the given process and prompts files.
20
+
21
+ :param process_file (str): Content of the process file.
22
+ :param prompts_file (str): Content of the prompts file.
23
+ """
24
+
25
+ process_lexer = Lexer(source=process_file)
26
+ self._prepare_process(lexed_process=process_lexer)
27
+
28
+ prompts_lexer = Lexer(source=prompts_file)
29
+ self._prepare_prompts(lexed_prompts=prompts_lexer)
30
+
31
+ def _prepare_process(self, lexed_process: Lexer) -> None:
32
+ """
33
+ Prepare the process file from the lexed result.
34
+
35
+ :param lexed_process: Result of lexing the process file.
36
+ """
37
+ self._process_file = self._process_lex(lexed_file=lexed_process)
38
+
39
+ def _prepare_prompts(self, lexed_prompts: Lexer) -> None:
40
+ """
41
+ Prepare the prompts file from the lexed result.
42
+
43
+ :param lexed_prompts: Result of lexing the prompts file.
44
+ """
45
+ self._prompts_file = self._process_lex(lexed_file=lexed_prompts)
46
+
47
+ @staticmethod
48
+ def _process_lex(lexed_file: Lexer):
49
+ """
50
+ Process a lex and return the results.
51
+
52
+ :param lexed_file: Result of lexing a file.
53
+
54
+ :return: Result of processing the lexed file.
55
+ """
56
+ current_section_name: str = "default"
57
+ section_entries: list[ALL_TOKEN_TYPES,] = []
58
+ sorted_lex = {}
59
+ for lexed_line in lexed_file.tokens:
60
+ if lexed_line[0] == TokenName.SECTION_HEADER:
61
+ sorted_lex[current_section_name] = section_entries
62
+ if isinstance(lexed_line[1], SectionHeader):
63
+ current_section_name = lexed_line[1].name
64
+ else:
65
+ # This should never be reached, this is here to satisfy typing.
66
+ current_section_name = "UNKNOWN"
67
+ section_entries = []
68
+ continue
69
+ section_entries.append(lexed_line[1])
70
+ sorted_lex[current_section_name] = section_entries
71
+ return sorted_lex
72
+
73
+ @property
74
+ def process_file(self):
75
+ """
76
+ Returns the parsed process configuration.
77
+
78
+ :return: List of tokens from the process file.
79
+ """
80
+ return self._process_file
81
+
82
+ @property
83
+ def prompts_file(self):
84
+ """
85
+ Returns the parsed prompts file.
86
+
87
+ :return: List of tokens from the prompts file.
88
+ """
89
+ return self._prompts_file