langroid 0.16.5__py3-none-any.whl → 0.16.7__py3-none-any.whl
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.
- langroid/agent/md_tool_message_grammar.py +455 -0
- langroid/agent/tools/code_file_tool_parse.py +150 -0
- langroid/agent/tools/code_file_tool_pyparsing.py +194 -0
- langroid/agent/tools/code_file_tool_pyparsing2.py +199 -0
- langroid/agent/tools/formatted_model_custom.py +150 -0
- langroid/agent/tools/formatted_model_custom2.py +168 -0
- langroid/agent/tools/formatted_model_custom3.py +279 -0
- langroid/agent/tools/formatted_model_custom4.py +395 -0
- langroid/agent/tools/formatted_model_jinja.py +133 -0
- langroid/agent/tools/formatted_model_jinja.py-e +122 -0
- langroid/agent/tools/formatted_model_jinja2.py +145 -0
- langroid/agent/tools/formatted_model_jinja2.py-e +135 -0
- langroid/agent/tools/formatted_model_lark.py +0 -0
- langroid/agent/tools/formatted_model_lark2.py +168 -0
- langroid/agent/tools/formatted_model_parse.py +105 -0
- langroid/agent/tools/formatted_model_parse.py-e +98 -0
- langroid/agent/tools/formatted_model_parse2.py +113 -0
- langroid/agent/tools/formatted_model_parse2.py-e +109 -0
- langroid/agent/tools/formatted_model_parse3.py +114 -0
- langroid/agent/tools/formatted_model_parse3.py-e +110 -0
- langroid/agent/tools/formatted_model_parsimon.py +194 -0
- langroid/agent/tools/formatted_model_parsimon.py-e +186 -0
- langroid/agent/tools/formatted_model_pyparsing.py +169 -0
- langroid/agent/tools/formatted_model_pyparsing.py-e +149 -0
- langroid/agent/tools/formatted_model_pyparsing2.py +159 -0
- langroid/agent/tools/formatted_model_pyparsing2.py-e +143 -0
- langroid/agent/tools/formatted_model_pyparsing3.py +133 -0
- langroid/agent/tools/formatted_model_pyparsing3.py-e +121 -0
- langroid/agent/tools/formatted_model_pyparsing4.py +213 -0
- langroid/agent/tools/formatted_model_pyparsing4.py-e +176 -0
- langroid/agent/tools/formatted_model_pyparsing5.py +173 -0
- langroid/agent/tools/formatted_model_pyparsing5.py-e +142 -0
- langroid/agent/tools/formatted_model_regex.py +246 -0
- langroid/agent/tools/formatted_model_regex.py-e +248 -0
- langroid/agent/tools/formatted_model_regex2.py +250 -0
- langroid/agent/tools/formatted_model_regex2.py-e +253 -0
- langroid/agent/tools/formatted_model_tatsu.py +172 -0
- langroid/agent/tools/formatted_model_tatsu.py-e +160 -0
- langroid/agent/tools/formatted_model_template.py +217 -0
- langroid/agent/tools/formatted_model_template.py-e +200 -0
- langroid/agent/tools/formatted_model_xml.py +178 -0
- langroid/agent/tools/formatted_model_xml2.py +178 -0
- langroid/agent/tools/formatted_model_xml3.py +132 -0
- langroid/agent/tools/formatted_model_xml4.py +130 -0
- langroid/agent/tools/formatted_model_xml5.py +130 -0
- langroid/agent/tools/formatted_model_xml6.py +113 -0
- langroid/agent/tools/formatted_model_xml7.py +117 -0
- langroid/agent/tools/formatted_model_xml8.py +164 -0
- langroid/agent/tools/generic_tool.py +165 -0
- langroid/agent/tools/generic_tool_tatsu.py +275 -0
- langroid/agent/tools/grammar_based_model.py +132 -0
- langroid/agent/tools/grammar_based_model.py-e +128 -0
- langroid/agent/tools/grammar_based_model_lark.py +156 -0
- langroid/agent/tools/grammar_based_model_lark.py-e +153 -0
- langroid/agent/tools/grammar_based_model_parse.py +86 -0
- langroid/agent/tools/grammar_based_model_parse.py-e +80 -0
- langroid/agent/tools/grammar_based_model_parsimonious.py +129 -0
- langroid/agent/tools/grammar_based_model_parsimonious.py-e +120 -0
- langroid/agent/tools/grammar_based_model_pyparsing.py +105 -0
- langroid/agent/tools/grammar_based_model_pyparsing.py-e +103 -0
- langroid/agent/tools/grammar_based_model_regex.py +139 -0
- langroid/agent/tools/grammar_based_model_regex.py-e +130 -0
- langroid/agent/tools/grammar_based_model_regex2.py +124 -0
- langroid/agent/tools/grammar_based_model_regex2.py-e +116 -0
- langroid/agent/tools/grammar_based_model_tatsu.py +80 -0
- langroid/agent/tools/grammar_based_model_tatsu.py-e +77 -0
- langroid/agent/tools/lark_earley_example.py +135 -0
- langroid/agent/tools/lark_earley_example.py-e +117 -0
- langroid/agent/tools/lark_example.py +72 -0
- langroid/agent/tools/parse_example.py +76 -0
- langroid/agent/tools/parse_example2.py +87 -0
- langroid/agent/tools/parse_example3.py +42 -0
- langroid/agent/tools/parse_test.py +791 -0
- langroid/agent/xml_tool_message.py +106 -0
- langroid/language_models/openai_gpt.py +6 -1
- {langroid-0.16.5.dist-info → langroid-0.16.7.dist-info}/METADATA +1 -1
- {langroid-0.16.5.dist-info → langroid-0.16.7.dist-info}/RECORD +80 -6
- pyproject.toml +1 -1
- {langroid-0.16.5.dist-info → langroid-0.16.7.dist-info}/LICENSE +0 -0
- {langroid-0.16.5.dist-info → langroid-0.16.7.dist-info}/WHEEL +0 -0
@@ -0,0 +1,213 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from typing import List, Tuple
|
3
|
+
|
4
|
+
from pyparsing import (
|
5
|
+
LineEnd,
|
6
|
+
Literal,
|
7
|
+
Optional,
|
8
|
+
SkipTo,
|
9
|
+
Suppress,
|
10
|
+
White,
|
11
|
+
)
|
12
|
+
|
13
|
+
from langroid.pydantic_v1 import BaseModel
|
14
|
+
|
15
|
+
|
16
|
+
class FormattingModel(BaseModel, ABC):
|
17
|
+
@classmethod
|
18
|
+
@abstractmethod
|
19
|
+
def spec(cls) -> List[Tuple[str, str, dict]]:
|
20
|
+
pass
|
21
|
+
|
22
|
+
@classmethod
|
23
|
+
def format_spec(cls):
|
24
|
+
format_str = ""
|
25
|
+
for literal, field, options in cls.spec():
|
26
|
+
if field:
|
27
|
+
if options.get("multiline", False):
|
28
|
+
format_str += f"{{{field}}}\n"
|
29
|
+
else:
|
30
|
+
format_str += f"{{{field}}}"
|
31
|
+
else:
|
32
|
+
format_str += literal
|
33
|
+
return format_str
|
34
|
+
|
35
|
+
@classmethod
|
36
|
+
def parse_spec(cls):
|
37
|
+
parser = None
|
38
|
+
for literal, field, options in cls.spec():
|
39
|
+
if literal:
|
40
|
+
element = Suppress(Optional(White())) + Literal(literal)
|
41
|
+
else:
|
42
|
+
if options.get("multiline", False):
|
43
|
+
end_marker = Suppress(Optional(White())) + Literal(
|
44
|
+
options.get("end_marker", "```")
|
45
|
+
)
|
46
|
+
element = SkipTo(end_marker).setParseAction(
|
47
|
+
lambda s, l, t: t[0].strip()
|
48
|
+
)
|
49
|
+
else:
|
50
|
+
element = SkipTo(LineEnd()).setParseAction(
|
51
|
+
lambda s, l, t: t[0].strip()
|
52
|
+
)
|
53
|
+
if field:
|
54
|
+
element = element.setResultsName(field)
|
55
|
+
if "parse_action" in options:
|
56
|
+
element = element.setParseAction(options["parse_action"])
|
57
|
+
parser = element if parser is None else parser + element
|
58
|
+
return parser
|
59
|
+
|
60
|
+
@classmethod
|
61
|
+
def start_token(cls) -> str:
|
62
|
+
return "<format>"
|
63
|
+
|
64
|
+
@classmethod
|
65
|
+
def end_token(cls) -> str:
|
66
|
+
return "</format>"
|
67
|
+
|
68
|
+
@classmethod
|
69
|
+
def format(cls, instance: "FormattingModel") -> str:
|
70
|
+
spec = cls.format_spec()
|
71
|
+
formatted = spec.format(**instance.dict())
|
72
|
+
return f"{cls.start_token()}\n{formatted.rstrip()}\n{cls.end_token()}"
|
73
|
+
|
74
|
+
@classmethod
|
75
|
+
def parse(cls, formatted_string: str) -> "FormattingModel":
|
76
|
+
lines = formatted_string.strip().split("\n")
|
77
|
+
if lines[0] != cls.start_token() or lines[-1] != cls.end_token():
|
78
|
+
raise ValueError("Invalid start or end token")
|
79
|
+
content = "\n".join(lines[1:-1])
|
80
|
+
|
81
|
+
spec = cls.parse_spec()
|
82
|
+
parsed = spec.parseString(content, parseAll=True)
|
83
|
+
return cls(**parsed.asDict())
|
84
|
+
|
85
|
+
|
86
|
+
class CodeFileModel(FormattingModel):
|
87
|
+
language: str
|
88
|
+
file_path: str
|
89
|
+
code: str
|
90
|
+
|
91
|
+
@classmethod
|
92
|
+
def spec(cls):
|
93
|
+
return [
|
94
|
+
("code_file_model\n", "", {}),
|
95
|
+
("", "file_path", {"single_line": True}),
|
96
|
+
("\n```", "", {}),
|
97
|
+
("", "language", {"single_line": True}),
|
98
|
+
("\n", "", {}),
|
99
|
+
(
|
100
|
+
"",
|
101
|
+
"code",
|
102
|
+
{
|
103
|
+
"multiline": True,
|
104
|
+
"end_marker": "```",
|
105
|
+
"parse_action": lambda s, l, t: t[0].rstrip(),
|
106
|
+
},
|
107
|
+
),
|
108
|
+
("\n```", "", {}),
|
109
|
+
]
|
110
|
+
|
111
|
+
|
112
|
+
if __name__ == "__main__":
|
113
|
+
# Test formatting
|
114
|
+
code_file = CodeFileModel(
|
115
|
+
language="Python",
|
116
|
+
file_path="src/main.py",
|
117
|
+
code="def hello():\n print('Hello, World!')",
|
118
|
+
)
|
119
|
+
formatted = CodeFileModel.format(code_file)
|
120
|
+
expected_format = """<format>
|
121
|
+
code_file_model
|
122
|
+
src/main.py
|
123
|
+
```Python
|
124
|
+
def hello():
|
125
|
+
print('Hello, World!')
|
126
|
+
```
|
127
|
+
</format>"""
|
128
|
+
|
129
|
+
# Compare ignoring whitespace
|
130
|
+
assert "".join(formatted.split()) == "".join(
|
131
|
+
expected_format.split()
|
132
|
+
), f"Formatting failed. Expected:\n{expected_format}\nGot:\n{formatted}"
|
133
|
+
print("Formatting test passed.")
|
134
|
+
|
135
|
+
# Test parsing
|
136
|
+
parsed = CodeFileModel.parse(formatted)
|
137
|
+
assert (
|
138
|
+
parsed == code_file
|
139
|
+
), f"Parsing failed. Expected:\n{code_file}\nGot:\n{parsed}"
|
140
|
+
print("Parsing test passed.")
|
141
|
+
|
142
|
+
# Test round-trip
|
143
|
+
round_trip = CodeFileModel.parse(CodeFileModel.format(code_file))
|
144
|
+
assert (
|
145
|
+
round_trip == code_file
|
146
|
+
), f"Round-trip failed. Expected:\n{code_file}\nGot:\n{round_trip}"
|
147
|
+
print("Round-trip test passed.")
|
148
|
+
|
149
|
+
# Test with different values
|
150
|
+
code_file2 = CodeFileModel(
|
151
|
+
language="JavaScript",
|
152
|
+
file_path="src/app.js",
|
153
|
+
code="function greet() {\n console.log('Hello, World!');\n}",
|
154
|
+
)
|
155
|
+
formatted2 = CodeFileModel.format(code_file2)
|
156
|
+
parsed2 = CodeFileModel.parse(formatted2)
|
157
|
+
assert (
|
158
|
+
parsed2 == code_file2
|
159
|
+
), f"Parsing failed for different values. Expected:\n{code_file2}\nGot:\n{parsed2}"
|
160
|
+
print("Different values test passed.")
|
161
|
+
|
162
|
+
print("All tests passed successfully!")
|
163
|
+
|
164
|
+
# Test cases
|
165
|
+
if __name__ == "__main__":
|
166
|
+
# Test formatting
|
167
|
+
code_file = CodeFileModel(
|
168
|
+
language="Python",
|
169
|
+
file_path="src/main.py",
|
170
|
+
code="def hello():\n print('Hello, World!')",
|
171
|
+
)
|
172
|
+
formatted = CodeFileModel.format(code_file)
|
173
|
+
expected_format = """<format>
|
174
|
+
code_file_model
|
175
|
+
src/main.py
|
176
|
+
```Python
|
177
|
+
def hello():
|
178
|
+
print('Hello, World!')
|
179
|
+
```
|
180
|
+
</format>"""
|
181
|
+
assert (
|
182
|
+
formatted == expected_format
|
183
|
+
), f"Formatting failed. Expected:\n{expected_format}\nGot:\n{formatted}"
|
184
|
+
print("Formatting test passed.")
|
185
|
+
|
186
|
+
# Test parsing
|
187
|
+
parsed = CodeFileModel.parse(formatted)
|
188
|
+
assert (
|
189
|
+
parsed == code_file
|
190
|
+
), f"Parsing failed. Expected:\n{code_file}\nGot:\n{parsed}"
|
191
|
+
print("Parsing test passed.")
|
192
|
+
|
193
|
+
# Test round-trip
|
194
|
+
round_trip = CodeFileModel.parse(CodeFileModel.format(code_file))
|
195
|
+
assert (
|
196
|
+
round_trip == code_file
|
197
|
+
), f"Round-trip failed. Expected:\n{code_file}\nGot:\n{round_trip}"
|
198
|
+
print("Round-trip test passed.")
|
199
|
+
|
200
|
+
# Test with different values
|
201
|
+
code_file2 = CodeFileModel(
|
202
|
+
language="JavaScript",
|
203
|
+
file_path="src/app.js",
|
204
|
+
code="function greet() {\n console.log('Hello, World!');\n}",
|
205
|
+
)
|
206
|
+
formatted2 = CodeFileModel.format(code_file2)
|
207
|
+
parsed2 = CodeFileModel.parse(formatted2)
|
208
|
+
assert (
|
209
|
+
parsed2 == code_file2
|
210
|
+
), f"Parsing failed for different values. Expected:\n{code_file2}\nGot:\n{parsed2}"
|
211
|
+
print("Different values test passed.")
|
212
|
+
|
213
|
+
print("All tests passed successfully!")
|
@@ -0,0 +1,176 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from pydantic import BaseModel
|
3
|
+
from pyparsing import (
|
4
|
+
Word, alphas, alphanums, Suppress, OneOrMore,
|
5
|
+
LineEnd, SkipTo, ParseResults, Optional, Literal, White
|
6
|
+
)
|
7
|
+
from typing import List, Tuple
|
8
|
+
|
9
|
+
class FormattingModel(BaseModel, ABC):
|
10
|
+
@classmethod
|
11
|
+
@abstractmethod
|
12
|
+
def spec(cls) -> List[Tuple[str, str, dict]]:
|
13
|
+
pass
|
14
|
+
|
15
|
+
@classmethod
|
16
|
+
def format_spec(cls):
|
17
|
+
format_str = ''
|
18
|
+
for literal, field, options in cls.spec():
|
19
|
+
if field:
|
20
|
+
if options.get('multiline', False):
|
21
|
+
format_str += f"{{{field}}}\n"
|
22
|
+
else:
|
23
|
+
format_str += f"{{{field}}}"
|
24
|
+
else:
|
25
|
+
format_str += literal
|
26
|
+
return format_str
|
27
|
+
|
28
|
+
@classmethod
|
29
|
+
def parse_spec(cls):
|
30
|
+
parser = None
|
31
|
+
for literal, field, options in cls.spec():
|
32
|
+
if literal:
|
33
|
+
element = Suppress(Optional(White())) + Literal(literal)
|
34
|
+
else:
|
35
|
+
if options.get('multiline', False):
|
36
|
+
end_marker = Suppress(Optional(White())) + Literal(options.get('end_marker', '```'))
|
37
|
+
element = SkipTo(end_marker).setParseAction(lambda s, l, t: t[0].strip())
|
38
|
+
else:
|
39
|
+
element = SkipTo(LineEnd()).setParseAction(lambda s, l, t: t[0].strip())
|
40
|
+
if field:
|
41
|
+
element = element.setResultsName(field)
|
42
|
+
if 'parse_action' in options:
|
43
|
+
element = element.setParseAction(options['parse_action'])
|
44
|
+
parser = element if parser is None else parser + element
|
45
|
+
return parser
|
46
|
+
|
47
|
+
@classmethod
|
48
|
+
def start_token(cls) -> str:
|
49
|
+
return '<format>'
|
50
|
+
|
51
|
+
@classmethod
|
52
|
+
def end_token(cls) -> str:
|
53
|
+
return '</format>'
|
54
|
+
|
55
|
+
@classmethod
|
56
|
+
def format(cls, instance: 'FormattingModel') -> str:
|
57
|
+
spec = cls.format_spec()
|
58
|
+
formatted = spec.format(**instance.dict())
|
59
|
+
return f"{cls.start_token()}\n{formatted.rstrip()}\n{cls.end_token()}"
|
60
|
+
|
61
|
+
@classmethod
|
62
|
+
def parse(cls, formatted_string: str) -> 'FormattingModel':
|
63
|
+
lines = formatted_string.strip().split('\n')
|
64
|
+
if lines[0] != cls.start_token() or lines[-1] != cls.end_token():
|
65
|
+
raise ValueError("Invalid start or end token")
|
66
|
+
content = '\n'.join(lines[1:-1])
|
67
|
+
|
68
|
+
spec = cls.parse_spec()
|
69
|
+
parsed = spec.parseString(content, parseAll=True)
|
70
|
+
return cls(**parsed.asDict())
|
71
|
+
|
72
|
+
class CodeFileModel(FormattingModel):
|
73
|
+
language: str
|
74
|
+
file_path: str
|
75
|
+
code: str
|
76
|
+
|
77
|
+
@classmethod
|
78
|
+
def spec(cls):
|
79
|
+
return [
|
80
|
+
("code_file_model\n", '', {}),
|
81
|
+
('', 'file_path', {'single_line': True}),
|
82
|
+
("\n```", '', {}),
|
83
|
+
('', 'language', {'single_line': True}),
|
84
|
+
('\n', '', {}),
|
85
|
+
('', 'code', {'multiline': True, 'end_marker': '```', 'parse_action': lambda s, l, t: t[0].rstrip()}),
|
86
|
+
('\n```', '', {})
|
87
|
+
]
|
88
|
+
|
89
|
+
if __name__ == "__main__":
|
90
|
+
# Test formatting
|
91
|
+
code_file = CodeFileModel(
|
92
|
+
language="Python",
|
93
|
+
file_path="src/main.py",
|
94
|
+
code="def hello():\n print('Hello, World!')"
|
95
|
+
)
|
96
|
+
formatted = CodeFileModel.format(code_file)
|
97
|
+
expected_format = """<format>
|
98
|
+
code_file_model
|
99
|
+
src/main.py
|
100
|
+
```Python
|
101
|
+
def hello():
|
102
|
+
print('Hello, World!')
|
103
|
+
```
|
104
|
+
</format>"""
|
105
|
+
|
106
|
+
# Compare ignoring whitespace
|
107
|
+
assert ''.join(formatted.split()) == ''.join(expected_format.split()), \
|
108
|
+
f"Formatting failed. Expected:\n{expected_format}\nGot:\n{formatted}"
|
109
|
+
print("Formatting test passed.")
|
110
|
+
|
111
|
+
# Test parsing
|
112
|
+
parsed = CodeFileModel.parse(formatted)
|
113
|
+
assert parsed == code_file, f"Parsing failed. Expected:\n{code_file}\nGot:\n{parsed}"
|
114
|
+
print("Parsing test passed.")
|
115
|
+
|
116
|
+
# Test round-trip
|
117
|
+
round_trip = CodeFileModel.parse(CodeFileModel.format(code_file))
|
118
|
+
assert round_trip == code_file, f"Round-trip failed. Expected:\n{code_file}\nGot:\n{round_trip}"
|
119
|
+
print("Round-trip test passed.")
|
120
|
+
|
121
|
+
# Test with different values
|
122
|
+
code_file2 = CodeFileModel(
|
123
|
+
language="JavaScript",
|
124
|
+
file_path="src/app.js",
|
125
|
+
code="function greet() {\n console.log('Hello, World!');\n}"
|
126
|
+
)
|
127
|
+
formatted2 = CodeFileModel.format(code_file2)
|
128
|
+
parsed2 = CodeFileModel.parse(formatted2)
|
129
|
+
assert parsed2 == code_file2, f"Parsing failed for different values. Expected:\n{code_file2}\nGot:\n{parsed2}"
|
130
|
+
print("Different values test passed.")
|
131
|
+
|
132
|
+
print("All tests passed successfully!")
|
133
|
+
|
134
|
+
# Test cases
|
135
|
+
if __name__ == "__main__":
|
136
|
+
# Test formatting
|
137
|
+
code_file = CodeFileModel(
|
138
|
+
language="Python",
|
139
|
+
file_path="src/main.py",
|
140
|
+
code="def hello():\n print('Hello, World!')"
|
141
|
+
)
|
142
|
+
formatted = CodeFileModel.format(code_file)
|
143
|
+
expected_format = """<format>
|
144
|
+
code_file_model
|
145
|
+
src/main.py
|
146
|
+
```Python
|
147
|
+
def hello():
|
148
|
+
print('Hello, World!')
|
149
|
+
```
|
150
|
+
</format>"""
|
151
|
+
assert formatted == expected_format, f"Formatting failed. Expected:\n{expected_format}\nGot:\n{formatted}"
|
152
|
+
print("Formatting test passed.")
|
153
|
+
|
154
|
+
# Test parsing
|
155
|
+
parsed = CodeFileModel.parse(formatted)
|
156
|
+
assert parsed == code_file, f"Parsing failed. Expected:\n{code_file}\nGot:\n{parsed}"
|
157
|
+
print("Parsing test passed.")
|
158
|
+
|
159
|
+
# Test round-trip
|
160
|
+
round_trip = CodeFileModel.parse(CodeFileModel.format(code_file))
|
161
|
+
assert round_trip == code_file, f"Round-trip failed. Expected:\n{code_file}\nGot:\n{round_trip}"
|
162
|
+
print("Round-trip test passed.")
|
163
|
+
|
164
|
+
# Test with different values
|
165
|
+
code_file2 = CodeFileModel(
|
166
|
+
language="JavaScript",
|
167
|
+
file_path="src/app.js",
|
168
|
+
code="function greet() {\n console.log('Hello, World!');\n}"
|
169
|
+
)
|
170
|
+
formatted2 = CodeFileModel.format(code_file2)
|
171
|
+
parsed2 = CodeFileModel.parse(formatted2)
|
172
|
+
assert parsed2 == code_file2, f"Parsing failed for different values. Expected:\n{code_file2}\nGot:\n{parsed2}"
|
173
|
+
print("Different values test passed.")
|
174
|
+
|
175
|
+
print("All tests passed successfully!")
|
176
|
+
|
@@ -0,0 +1,173 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
|
3
|
+
from pyparsing import (
|
4
|
+
LineEnd,
|
5
|
+
Optional,
|
6
|
+
SkipTo,
|
7
|
+
Suppress,
|
8
|
+
White,
|
9
|
+
Word,
|
10
|
+
ZeroOrMore,
|
11
|
+
alphas,
|
12
|
+
)
|
13
|
+
|
14
|
+
from langroid.pydantic_v1 import BaseModel
|
15
|
+
|
16
|
+
|
17
|
+
class FormattingModel(BaseModel, ABC):
|
18
|
+
@classmethod
|
19
|
+
@abstractmethod
|
20
|
+
def format_spec(cls):
|
21
|
+
pass
|
22
|
+
|
23
|
+
@classmethod
|
24
|
+
@abstractmethod
|
25
|
+
def parse_spec(cls):
|
26
|
+
pass
|
27
|
+
|
28
|
+
@classmethod
|
29
|
+
@abstractmethod
|
30
|
+
def start_token(cls) -> str:
|
31
|
+
pass
|
32
|
+
|
33
|
+
@classmethod
|
34
|
+
@abstractmethod
|
35
|
+
def end_token(cls) -> str:
|
36
|
+
pass
|
37
|
+
|
38
|
+
@classmethod
|
39
|
+
def format(cls, instance: "FormattingModel") -> str:
|
40
|
+
spec = cls.format_spec()
|
41
|
+
formatted = spec.format(**instance.dict())
|
42
|
+
return f"{cls.start_token()}\n{formatted}\n{cls.end_token()}"
|
43
|
+
|
44
|
+
@classmethod
|
45
|
+
def parse(cls, formatted_string: str) -> "FormattingModel":
|
46
|
+
lines = formatted_string.strip().split("\n")
|
47
|
+
if lines[0] != cls.start_token() or lines[-1] != cls.end_token():
|
48
|
+
raise ValueError("Invalid start or end token")
|
49
|
+
content = "\n".join(lines[1:-1])
|
50
|
+
|
51
|
+
spec = cls.parse_spec()
|
52
|
+
parsed = spec.parseString(content, parseAll=True)
|
53
|
+
return cls(**parsed.asDict())
|
54
|
+
|
55
|
+
|
56
|
+
class CodeFileModel(FormattingModel):
|
57
|
+
language: str
|
58
|
+
file_path: str
|
59
|
+
code: str
|
60
|
+
|
61
|
+
@classmethod
|
62
|
+
def format_spec(cls):
|
63
|
+
return "code_file_model\n{file_path}\n```{language}\n{code}\n```"
|
64
|
+
|
65
|
+
@classmethod
|
66
|
+
def parse_spec(cls):
|
67
|
+
header = Suppress("code_file_model") + ZeroOrMore(LineEnd())
|
68
|
+
file_path = (
|
69
|
+
SkipTo(LineEnd())
|
70
|
+
.setResultsName("file_path")
|
71
|
+
.setParseAction(lambda s, l, t: t[0].strip())
|
72
|
+
)
|
73
|
+
language = (
|
74
|
+
Suppress("```")
|
75
|
+
+ Optional(White())
|
76
|
+
+ Word(alphas).setResultsName("language")
|
77
|
+
+ LineEnd()
|
78
|
+
)
|
79
|
+
code = (
|
80
|
+
SkipTo("```")
|
81
|
+
.setResultsName("code")
|
82
|
+
.setParseAction(lambda s, l, t: t[0].strip())
|
83
|
+
)
|
84
|
+
|
85
|
+
return (
|
86
|
+
header
|
87
|
+
+ file_path
|
88
|
+
+ ZeroOrMore(LineEnd())
|
89
|
+
+ language
|
90
|
+
+ code
|
91
|
+
+ Suppress("```")
|
92
|
+
)
|
93
|
+
|
94
|
+
@classmethod
|
95
|
+
def start_token(cls):
|
96
|
+
return "<format>"
|
97
|
+
|
98
|
+
@classmethod
|
99
|
+
def end_token(cls):
|
100
|
+
return "</format>"
|
101
|
+
|
102
|
+
|
103
|
+
# Test cases
|
104
|
+
if __name__ == "__main__":
|
105
|
+
# Test formatting
|
106
|
+
code_file = CodeFileModel(
|
107
|
+
language="Python",
|
108
|
+
file_path="src/main.py",
|
109
|
+
code="def hello():\n print('Hello, World!')",
|
110
|
+
)
|
111
|
+
formatted = CodeFileModel.format(code_file)
|
112
|
+
expected_format = """<format>
|
113
|
+
code_file_model
|
114
|
+
src/main.py
|
115
|
+
```Python
|
116
|
+
def hello():
|
117
|
+
print('Hello, World!')
|
118
|
+
```
|
119
|
+
</format>"""
|
120
|
+
assert (
|
121
|
+
formatted == expected_format
|
122
|
+
), f"Formatting failed. Expected:\n{expected_format}\nGot:\n{formatted}"
|
123
|
+
print("Formatting test passed.")
|
124
|
+
|
125
|
+
# Test parsing
|
126
|
+
parsed = CodeFileModel.parse(formatted)
|
127
|
+
assert (
|
128
|
+
parsed == code_file
|
129
|
+
), f"Parsing failed. Expected:\n{code_file}\nGot:\n{parsed}"
|
130
|
+
print("Parsing test passed.")
|
131
|
+
|
132
|
+
# Test round-trip
|
133
|
+
round_trip = CodeFileModel.parse(CodeFileModel.format(code_file))
|
134
|
+
assert (
|
135
|
+
round_trip == code_file
|
136
|
+
), f"Round-trip failed. Expected:\n{code_file}\nGot:\n{round_trip}"
|
137
|
+
print("Round-trip test passed.")
|
138
|
+
|
139
|
+
# Test with different values
|
140
|
+
code_file2 = CodeFileModel(
|
141
|
+
language="JavaScript",
|
142
|
+
file_path="src/app.js",
|
143
|
+
code="function greet() {\n console.log('Hello, World!');\n}",
|
144
|
+
)
|
145
|
+
formatted2 = CodeFileModel.format(code_file2)
|
146
|
+
parsed2 = CodeFileModel.parse(formatted2)
|
147
|
+
assert (
|
148
|
+
parsed2 == code_file2
|
149
|
+
), f"Parsing failed for different values. Expected:\n{code_file2}\nGot:\n{parsed2}"
|
150
|
+
print("Different values test passed.")
|
151
|
+
|
152
|
+
# Test tolerant parsing
|
153
|
+
tolerant_input = """<format>
|
154
|
+
code_file_model
|
155
|
+
src/main.py
|
156
|
+
|
157
|
+
``` Python
|
158
|
+
def hello():
|
159
|
+
print('Hello, World!')
|
160
|
+
```
|
161
|
+
</format>"""
|
162
|
+
parsed_tolerant = CodeFileModel.parse(tolerant_input)
|
163
|
+
expected_tolerant = CodeFileModel(
|
164
|
+
language="Python",
|
165
|
+
file_path="src/main.py",
|
166
|
+
code="def hello():\n print('Hello, World!')",
|
167
|
+
)
|
168
|
+
assert (
|
169
|
+
parsed_tolerant == expected_tolerant
|
170
|
+
), f"Tolerant parsing failed. Expected:\n{expected_tolerant}\nGot:\n{parsed_tolerant}"
|
171
|
+
print("Tolerant parsing test passed.")
|
172
|
+
|
173
|
+
print("All tests passed successfully!")
|
@@ -0,0 +1,142 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from pydantic import BaseModel
|
3
|
+
from pyparsing import (
|
4
|
+
Word, alphas, alphanums, Suppress, OneOrMore,
|
5
|
+
LineEnd, SkipTo, ParseResults, Optional, Literal, White, ZeroOrMore
|
6
|
+
)
|
7
|
+
class FormattingModel(BaseModel, ABC):
|
8
|
+
@classmethod
|
9
|
+
@abstractmethod
|
10
|
+
def format_spec(cls):
|
11
|
+
pass
|
12
|
+
|
13
|
+
@classmethod
|
14
|
+
@abstractmethod
|
15
|
+
def parse_spec(cls):
|
16
|
+
pass
|
17
|
+
|
18
|
+
@classmethod
|
19
|
+
@abstractmethod
|
20
|
+
def start_token(cls) -> str:
|
21
|
+
pass
|
22
|
+
|
23
|
+
@classmethod
|
24
|
+
@abstractmethod
|
25
|
+
def end_token(cls) -> str:
|
26
|
+
pass
|
27
|
+
|
28
|
+
@classmethod
|
29
|
+
def format(cls, instance: 'FormattingModel') -> str:
|
30
|
+
spec = cls.format_spec()
|
31
|
+
formatted = spec.format(**instance.dict())
|
32
|
+
return f"{cls.start_token()}\n{formatted}\n{cls.end_token()}"
|
33
|
+
|
34
|
+
@classmethod
|
35
|
+
def parse(cls, formatted_string: str) -> 'FormattingModel':
|
36
|
+
lines = formatted_string.strip().split('\n')
|
37
|
+
if lines[0] != cls.start_token() or lines[-1] != cls.end_token():
|
38
|
+
raise ValueError("Invalid start or end token")
|
39
|
+
content = '\n'.join(lines[1:-1])
|
40
|
+
|
41
|
+
spec = cls.parse_spec()
|
42
|
+
parsed = spec.parseString(content, parseAll=True)
|
43
|
+
return cls(**parsed.asDict())
|
44
|
+
|
45
|
+
class CodeFileModel(FormattingModel):
|
46
|
+
language: str
|
47
|
+
file_path: str
|
48
|
+
code: str
|
49
|
+
|
50
|
+
@classmethod
|
51
|
+
def format_spec(cls):
|
52
|
+
return "code_file_model\n{file_path}\n```{language}\n{code}\n```"
|
53
|
+
|
54
|
+
@classmethod
|
55
|
+
def parse_spec(cls):
|
56
|
+
header = Suppress("code_file_model") + ZeroOrMore(LineEnd())
|
57
|
+
file_path = SkipTo(LineEnd()).setResultsName("file_path").setParseAction(lambda s, l, t: t[0].strip())
|
58
|
+
language = Suppress("```") + Optional(White()) + Word(alphas).setResultsName("language") + LineEnd()
|
59
|
+
code = SkipTo("```").setResultsName("code").setParseAction(lambda s, l, t: t[0].strip())
|
60
|
+
|
61
|
+
return (
|
62
|
+
header +
|
63
|
+
file_path +
|
64
|
+
ZeroOrMore(LineEnd()) +
|
65
|
+
language +
|
66
|
+
code +
|
67
|
+
Suppress("```")
|
68
|
+
)
|
69
|
+
|
70
|
+
|
71
|
+
@classmethod
|
72
|
+
def start_token(cls):
|
73
|
+
return '<format>'
|
74
|
+
|
75
|
+
@classmethod
|
76
|
+
def end_token(cls):
|
77
|
+
return '</format>'
|
78
|
+
|
79
|
+
# Test cases
|
80
|
+
if __name__ == "__main__":
|
81
|
+
# Test formatting
|
82
|
+
code_file = CodeFileModel(
|
83
|
+
language="Python",
|
84
|
+
file_path="src/main.py",
|
85
|
+
code="def hello():\n print('Hello, World!')"
|
86
|
+
)
|
87
|
+
formatted = CodeFileModel.format(code_file)
|
88
|
+
expected_format = """<format>
|
89
|
+
code_file_model
|
90
|
+
src/main.py
|
91
|
+
```Python
|
92
|
+
def hello():
|
93
|
+
print('Hello, World!')
|
94
|
+
```
|
95
|
+
</format>"""
|
96
|
+
assert formatted == expected_format, f"Formatting failed. Expected:\n{expected_format}\nGot:\n{formatted}"
|
97
|
+
print("Formatting test passed.")
|
98
|
+
|
99
|
+
# Test parsing
|
100
|
+
parsed = CodeFileModel.parse(formatted)
|
101
|
+
assert parsed == code_file, f"Parsing failed. Expected:\n{code_file}\nGot:\n{parsed}"
|
102
|
+
print("Parsing test passed.")
|
103
|
+
|
104
|
+
# Test round-trip
|
105
|
+
round_trip = CodeFileModel.parse(CodeFileModel.format(code_file))
|
106
|
+
assert round_trip == code_file, f"Round-trip failed. Expected:\n{code_file}\nGot:\n{round_trip}"
|
107
|
+
print("Round-trip test passed.")
|
108
|
+
|
109
|
+
# Test with different values
|
110
|
+
code_file2 = CodeFileModel(
|
111
|
+
language="JavaScript",
|
112
|
+
file_path="src/app.js",
|
113
|
+
code="function greet() {\n console.log('Hello, World!');\n}"
|
114
|
+
)
|
115
|
+
formatted2 = CodeFileModel.format(code_file2)
|
116
|
+
parsed2 = CodeFileModel.parse(formatted2)
|
117
|
+
assert parsed2 == code_file2, f"Parsing failed for different values. Expected:\n{code_file2}\nGot:\n{parsed2}"
|
118
|
+
print("Different values test passed.")
|
119
|
+
|
120
|
+
|
121
|
+
# Test tolerant parsing
|
122
|
+
tolerant_input = """<format>
|
123
|
+
code_file_model
|
124
|
+
src/main.py
|
125
|
+
|
126
|
+
``` Python
|
127
|
+
def hello():
|
128
|
+
print('Hello, World!')
|
129
|
+
```
|
130
|
+
</format>"""
|
131
|
+
parsed_tolerant = CodeFileModel.parse(tolerant_input)
|
132
|
+
expected_tolerant = CodeFileModel(
|
133
|
+
language="Python",
|
134
|
+
file_path="src/main.py",
|
135
|
+
code="def hello():\n print('Hello, World!')"
|
136
|
+
)
|
137
|
+
assert parsed_tolerant == expected_tolerant, f"Tolerant parsing failed. Expected:\n{expected_tolerant}\nGot:\n{parsed_tolerant}"
|
138
|
+
print("Tolerant parsing test passed.")
|
139
|
+
|
140
|
+
print("All tests passed successfully!")
|
141
|
+
|
142
|
+
|