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,77 @@
|
|
1
|
+
from typing import Dict, Type, ClassVar
|
2
|
+
from pydantic import BaseModel
|
3
|
+
from tatsu import compile as compile_grammar
|
4
|
+
from tatsu.model import ModelBuilder
|
5
|
+
from abc import ABC, abstractmethod
|
6
|
+
|
7
|
+
class GrammarBasedModel(BaseModel, ABC):
|
8
|
+
grammar: ClassVar[str]
|
9
|
+
start_token: ClassVar[str]
|
10
|
+
end_token: ClassVar[str]
|
11
|
+
field_mappings: ClassVar[Dict[str, str]]
|
12
|
+
|
13
|
+
@classmethod
|
14
|
+
@abstractmethod
|
15
|
+
def get_grammar(cls) -> str:
|
16
|
+
pass
|
17
|
+
|
18
|
+
@classmethod
|
19
|
+
def parse(cls, text: str) -> 'GrammarBasedModel':
|
20
|
+
parser = compile_grammar(cls.get_grammar())
|
21
|
+
ast = parser.parse(text, start='start')
|
22
|
+
model_dict = {field: getattr(ast, rule) for field, rule in cls.field_mappings.items()}
|
23
|
+
return cls(**model_dict)
|
24
|
+
|
25
|
+
def generate(self) -> str:
|
26
|
+
grammar = compile_grammar(self.get_grammar())
|
27
|
+
model_builder = ModelBuilder()
|
28
|
+
for field, rule in self.field_mappings.items():
|
29
|
+
setattr(model_builder, rule, getattr(self, field))
|
30
|
+
ast = model_builder.start()
|
31
|
+
return f"{self.start_token}\n{grammar.parse(str(ast), start='start')}\n{self.end_token}"
|
32
|
+
|
33
|
+
class PersonSpec(GrammarBasedModel):
|
34
|
+
name: str
|
35
|
+
age: int
|
36
|
+
city: str
|
37
|
+
|
38
|
+
grammar = """
|
39
|
+
start = name_line age_line city_line;
|
40
|
+
name_line = 'name:' /\s*/ name:/.+/ EOL;
|
41
|
+
age_line = 'age is' /\s*/ age:/\d+/ EOL;
|
42
|
+
city_line = 'lives in' /\s*/ city:/.+/ EOL;
|
43
|
+
EOL = /\r?\n/;
|
44
|
+
"""
|
45
|
+
start_token = "<spec>"
|
46
|
+
end_token = "</spec>"
|
47
|
+
field_mappings = {
|
48
|
+
"name": "name",
|
49
|
+
"age": "age",
|
50
|
+
"city": "city"
|
51
|
+
}
|
52
|
+
|
53
|
+
@classmethod
|
54
|
+
def get_grammar(cls):
|
55
|
+
return cls.grammar
|
56
|
+
|
57
|
+
if __name__ == "__main__":
|
58
|
+
# Test parsing
|
59
|
+
input_str = """<spec>
|
60
|
+
name: John Doe
|
61
|
+
age is 30
|
62
|
+
lives in New York
|
63
|
+
</spec>"""
|
64
|
+
person = PersonSpec.parse(input_str)
|
65
|
+
print("Parsed person:", person)
|
66
|
+
|
67
|
+
# Test generation
|
68
|
+
generated_str = person.generate()
|
69
|
+
print("\nGenerated string:")
|
70
|
+
print(generated_str)
|
71
|
+
|
72
|
+
# Test round-trip
|
73
|
+
round_trip_person = PersonSpec.parse(generated_str)
|
74
|
+
print("\nRound-trip parsed person:", round_trip_person)
|
75
|
+
|
76
|
+
assert person == round_trip_person, "Round-trip parsing failed"
|
77
|
+
print("\nRound-trip test passed!")
|
@@ -0,0 +1,135 @@
|
|
1
|
+
import pytest
|
2
|
+
from lark import Lark, Transformer, Visitor
|
3
|
+
|
4
|
+
from langroid.pydantic_v1 import BaseModel
|
5
|
+
|
6
|
+
|
7
|
+
class Person(BaseModel):
|
8
|
+
name: str
|
9
|
+
age: int
|
10
|
+
city: str
|
11
|
+
|
12
|
+
|
13
|
+
grammar = """
|
14
|
+
person: "Name:" name "Age:" age "City:" city
|
15
|
+
name: WORD
|
16
|
+
age: NUMBER
|
17
|
+
city: WORD
|
18
|
+
WORD: /\w+/
|
19
|
+
NUMBER: /\d+/
|
20
|
+
%import common.WS
|
21
|
+
%ignore WS
|
22
|
+
"""
|
23
|
+
|
24
|
+
parser = Lark(grammar, start="person", parser="earley")
|
25
|
+
|
26
|
+
|
27
|
+
class PersonTransformer(Transformer):
|
28
|
+
def person(self, items):
|
29
|
+
return Person(name=items[1], age=items[3], city=items[5])
|
30
|
+
|
31
|
+
def name(self, items):
|
32
|
+
return items[0].value
|
33
|
+
|
34
|
+
def age(self, items):
|
35
|
+
return int(items[0].value)
|
36
|
+
|
37
|
+
def city(self, items):
|
38
|
+
return items[0].value
|
39
|
+
|
40
|
+
|
41
|
+
class PersonToStringVisitor(Visitor):
|
42
|
+
def __init__(self):
|
43
|
+
self.result = []
|
44
|
+
|
45
|
+
def person(self, tree):
|
46
|
+
self.visit_children(tree)
|
47
|
+
return " ".join(self.result)
|
48
|
+
|
49
|
+
def name(self, tree):
|
50
|
+
self.result.extend(["Name:", str(tree.children[0])])
|
51
|
+
|
52
|
+
def age(self, tree):
|
53
|
+
self.result.extend(["Age:", str(tree.children[0])])
|
54
|
+
|
55
|
+
def city(self, tree):
|
56
|
+
self.result.extend(["City:", str(tree.children[0])])
|
57
|
+
|
58
|
+
|
59
|
+
transformer = PersonTransformer()
|
60
|
+
to_string_visitor = PersonToStringVisitor()
|
61
|
+
|
62
|
+
|
63
|
+
def from_string(cls, string):
|
64
|
+
tree = parser.parse(string)
|
65
|
+
return transformer.transform(tree)
|
66
|
+
|
67
|
+
|
68
|
+
def to_string(self):
|
69
|
+
from lark import Token
|
70
|
+
|
71
|
+
tree = parser.parse(
|
72
|
+
f"Name: {Token('WORD', self.name)} "
|
73
|
+
f"Age: {Token('NUMBER', str(self.age))} "
|
74
|
+
f"City: {Token('WORD', self.city)}"
|
75
|
+
)
|
76
|
+
return to_string_visitor.visit(tree)
|
77
|
+
|
78
|
+
|
79
|
+
Person.from_string = classmethod(from_string)
|
80
|
+
Person.to_string = to_string
|
81
|
+
|
82
|
+
# Test functions remain the same as in the previous version
|
83
|
+
|
84
|
+
|
85
|
+
# Test functions
|
86
|
+
def test_from_string():
|
87
|
+
person_str = "Name: John Age: 30 City: NewYork"
|
88
|
+
person = Person.from_string(person_str)
|
89
|
+
assert person.name == "John"
|
90
|
+
assert person.age == 30
|
91
|
+
assert person.city == "NewYork"
|
92
|
+
|
93
|
+
|
94
|
+
def test_to_string():
|
95
|
+
person = Person(name="Alice", age=25, city="London")
|
96
|
+
person_str = person.to_string()
|
97
|
+
assert person_str == "Name: Alice Age: 25 City: London"
|
98
|
+
|
99
|
+
|
100
|
+
def test_roundtrip():
|
101
|
+
original_str = "Name: Bob Age: 40 City: Paris"
|
102
|
+
person = Person.from_string(original_str)
|
103
|
+
regenerated_str = person.to_string()
|
104
|
+
assert original_str == regenerated_str
|
105
|
+
|
106
|
+
|
107
|
+
def test_different_values():
|
108
|
+
person = Person(name="Charlie", age=35, city="Berlin")
|
109
|
+
person_str = person.to_string()
|
110
|
+
assert person_str == "Name: Charlie Age: 35 City: Berlin"
|
111
|
+
|
112
|
+
|
113
|
+
def test_edge_cases():
|
114
|
+
# Test with minimum age
|
115
|
+
person = Person(name="Young", age=0, city="Baby")
|
116
|
+
person_str = person.to_string()
|
117
|
+
assert person_str == "Name: Young Age: 0 City: Baby"
|
118
|
+
|
119
|
+
# Test with very long name and city
|
120
|
+
long_name = "A" * 100
|
121
|
+
long_city = "B" * 100
|
122
|
+
person = Person(name=long_name, age=50, city=long_city)
|
123
|
+
person_str = person.to_string()
|
124
|
+
assert person_str == f"Name: {long_name} Age: 50 City: {long_city}"
|
125
|
+
|
126
|
+
|
127
|
+
def test_invalid_input():
|
128
|
+
with pytest.raises(
|
129
|
+
Exception
|
130
|
+
): # The exact exception may vary based on Lark's implementation
|
131
|
+
Person.from_string("Invalid: Input")
|
132
|
+
|
133
|
+
|
134
|
+
if __name__ == "__main__":
|
135
|
+
pytest.main([__file__])
|
@@ -0,0 +1,117 @@
|
|
1
|
+
import pytest
|
2
|
+
from pydantic import BaseModel
|
3
|
+
from lark import Lark, Transformer, Visitor
|
4
|
+
|
5
|
+
class Person(BaseModel):
|
6
|
+
name: str
|
7
|
+
age: int
|
8
|
+
city: str
|
9
|
+
|
10
|
+
grammar = """
|
11
|
+
person: "Name:" name "Age:" age "City:" city
|
12
|
+
name: WORD
|
13
|
+
age: NUMBER
|
14
|
+
city: WORD
|
15
|
+
WORD: /\w+/
|
16
|
+
NUMBER: /\d+/
|
17
|
+
%import common.WS
|
18
|
+
%ignore WS
|
19
|
+
"""
|
20
|
+
|
21
|
+
parser = Lark(grammar, start='person', parser='earley')
|
22
|
+
|
23
|
+
class PersonTransformer(Transformer):
|
24
|
+
def person(self, items):
|
25
|
+
return Person(name=items[1], age=items[3], city=items[5])
|
26
|
+
|
27
|
+
def name(self, items):
|
28
|
+
return items[0].value
|
29
|
+
|
30
|
+
def age(self, items):
|
31
|
+
return int(items[0].value)
|
32
|
+
|
33
|
+
def city(self, items):
|
34
|
+
return items[0].value
|
35
|
+
|
36
|
+
class PersonToStringVisitor(Visitor):
|
37
|
+
def __init__(self):
|
38
|
+
self.result = []
|
39
|
+
|
40
|
+
def person(self, tree):
|
41
|
+
self.visit_children(tree)
|
42
|
+
return " ".join(self.result)
|
43
|
+
|
44
|
+
def name(self, tree):
|
45
|
+
self.result.extend(["Name:", str(tree.children[0])])
|
46
|
+
|
47
|
+
def age(self, tree):
|
48
|
+
self.result.extend(["Age:", str(tree.children[0])])
|
49
|
+
|
50
|
+
def city(self, tree):
|
51
|
+
self.result.extend(["City:", str(tree.children[0])])
|
52
|
+
|
53
|
+
transformer = PersonTransformer()
|
54
|
+
to_string_visitor = PersonToStringVisitor()
|
55
|
+
|
56
|
+
def from_string(cls, string):
|
57
|
+
tree = parser.parse(string)
|
58
|
+
return transformer.transform(tree)
|
59
|
+
|
60
|
+
def to_string(self):
|
61
|
+
from lark import Token
|
62
|
+
tree = parser.parse(
|
63
|
+
f"Name: {Token('WORD', self.name)} "
|
64
|
+
f"Age: {Token('NUMBER', str(self.age))} "
|
65
|
+
f"City: {Token('WORD', self.city)}"
|
66
|
+
)
|
67
|
+
return to_string_visitor.visit(tree)
|
68
|
+
|
69
|
+
Person.from_string = classmethod(from_string)
|
70
|
+
Person.to_string = to_string
|
71
|
+
|
72
|
+
# Test functions remain the same as in the previous version
|
73
|
+
|
74
|
+
# Test functions
|
75
|
+
def test_from_string():
|
76
|
+
person_str = "Name: John Age: 30 City: NewYork"
|
77
|
+
person = Person.from_string(person_str)
|
78
|
+
assert person.name == "John"
|
79
|
+
assert person.age == 30
|
80
|
+
assert person.city == "NewYork"
|
81
|
+
|
82
|
+
def test_to_string():
|
83
|
+
person = Person(name="Alice", age=25, city="London")
|
84
|
+
person_str = person.to_string()
|
85
|
+
assert person_str == "Name: Alice Age: 25 City: London"
|
86
|
+
|
87
|
+
def test_roundtrip():
|
88
|
+
original_str = "Name: Bob Age: 40 City: Paris"
|
89
|
+
person = Person.from_string(original_str)
|
90
|
+
regenerated_str = person.to_string()
|
91
|
+
assert original_str == regenerated_str
|
92
|
+
|
93
|
+
def test_different_values():
|
94
|
+
person = Person(name="Charlie", age=35, city="Berlin")
|
95
|
+
person_str = person.to_string()
|
96
|
+
assert person_str == "Name: Charlie Age: 35 City: Berlin"
|
97
|
+
|
98
|
+
def test_edge_cases():
|
99
|
+
# Test with minimum age
|
100
|
+
person = Person(name="Young", age=0, city="Baby")
|
101
|
+
person_str = person.to_string()
|
102
|
+
assert person_str == "Name: Young Age: 0 City: Baby"
|
103
|
+
|
104
|
+
# Test with very long name and city
|
105
|
+
long_name = "A" * 100
|
106
|
+
long_city = "B" * 100
|
107
|
+
person = Person(name=long_name, age=50, city=long_city)
|
108
|
+
person_str = person.to_string()
|
109
|
+
assert person_str == f"Name: {long_name} Age: 50 City: {long_city}"
|
110
|
+
|
111
|
+
def test_invalid_input():
|
112
|
+
with pytest.raises(Exception): # The exact exception may vary based on Lark's implementation
|
113
|
+
Person.from_string("Invalid: Input")
|
114
|
+
|
115
|
+
if __name__ == "__main__":
|
116
|
+
pytest.main([__file__])
|
117
|
+
|
@@ -0,0 +1,72 @@
|
|
1
|
+
from typing import ClassVar
|
2
|
+
|
3
|
+
from lark import Lark, Transformer, Tree
|
4
|
+
|
5
|
+
from langroid.pydantic_v1 import BaseModel
|
6
|
+
|
7
|
+
|
8
|
+
class Person(BaseModel):
|
9
|
+
name: str
|
10
|
+
age: int
|
11
|
+
city: str
|
12
|
+
|
13
|
+
# Class variable to hold the grammar
|
14
|
+
grammar: ClassVar[
|
15
|
+
str
|
16
|
+
] = """
|
17
|
+
start: "START Person" "\n" field ("---" "\n" field)* "END Person"
|
18
|
+
field: NAME "=" VALUE
|
19
|
+
NAME: "name" | "age" | "city"
|
20
|
+
VALUE: /[^\n]+/
|
21
|
+
|
22
|
+
%import common.WS
|
23
|
+
%ignore WS
|
24
|
+
"""
|
25
|
+
|
26
|
+
@classmethod
|
27
|
+
def from_string(cls, string: str) -> "Person":
|
28
|
+
parser = Lark(cls.grammar, parser="lalr", transformer=PersonTransformer())
|
29
|
+
result = parser.parse(string)
|
30
|
+
return cls(**result)
|
31
|
+
|
32
|
+
def to_string(self) -> str:
|
33
|
+
parser = Lark(self.grammar, parser="lalr")
|
34
|
+
tree = Tree(
|
35
|
+
"start",
|
36
|
+
[
|
37
|
+
Tree("field", [Tree("NAME", [name]), Tree("VALUE", [str(value)])])
|
38
|
+
for name, value in self.dict().items()
|
39
|
+
],
|
40
|
+
)
|
41
|
+
return parser.serialize(tree)
|
42
|
+
|
43
|
+
|
44
|
+
class PersonTransformer(Transformer):
|
45
|
+
def start(self, items):
|
46
|
+
return dict(items)
|
47
|
+
|
48
|
+
def field(self, items):
|
49
|
+
name, value = items
|
50
|
+
if name == "age":
|
51
|
+
value = int(value)
|
52
|
+
return name, value
|
53
|
+
|
54
|
+
|
55
|
+
# Example usage:
|
56
|
+
if __name__ == "__main__":
|
57
|
+
# Create a Person instance
|
58
|
+
p = Person(name="John Doe", age=30, city="New York")
|
59
|
+
|
60
|
+
# Convert to string
|
61
|
+
s = p.to_string()
|
62
|
+
print("To string:")
|
63
|
+
print(s)
|
64
|
+
print()
|
65
|
+
|
66
|
+
# Parse from string
|
67
|
+
p2 = Person.from_string(s)
|
68
|
+
print("Parsed from string:")
|
69
|
+
print(p2)
|
70
|
+
|
71
|
+
# Demonstrate that they're equal
|
72
|
+
print("\nAre equal:", p == p2)
|
@@ -0,0 +1,76 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
|
3
|
+
from parse import compile
|
4
|
+
|
5
|
+
|
6
|
+
class PersonFormatter:
|
7
|
+
# Define the grammar/template as a class attribute
|
8
|
+
GRAMMAR = """
|
9
|
+
Name: {name}
|
10
|
+
Age: {age:d}
|
11
|
+
Birthday: {birthday}
|
12
|
+
Hobbies: {hobbies}
|
13
|
+
"""
|
14
|
+
|
15
|
+
@classmethod
|
16
|
+
def parse(cls, string):
|
17
|
+
parser = compile(cls.GRAMMAR)
|
18
|
+
result = parser.parse(string)
|
19
|
+
if result:
|
20
|
+
return {
|
21
|
+
"name": result["name"],
|
22
|
+
"age": result["age"],
|
23
|
+
"birthday": datetime.strptime(result["birthday"], "%Y-%m-%d").date(),
|
24
|
+
"hobbies": [hobby.strip() for hobby in result["hobbies"].split(",")],
|
25
|
+
}
|
26
|
+
return None
|
27
|
+
|
28
|
+
@classmethod
|
29
|
+
def format(cls, data):
|
30
|
+
hobbies_str = ", ".join(data["hobbies"])
|
31
|
+
return cls.GRAMMAR.format(
|
32
|
+
name=data["name"],
|
33
|
+
age=data["age"],
|
34
|
+
birthday=data["birthday"].strftime("%Y-%m-%d"),
|
35
|
+
hobbies=hobbies_str,
|
36
|
+
)
|
37
|
+
|
38
|
+
|
39
|
+
# Example usage
|
40
|
+
if __name__ == "__main__":
|
41
|
+
# Parsing
|
42
|
+
input_string = """
|
43
|
+
Name: John Doe
|
44
|
+
Age: 30
|
45
|
+
Birthday: 1993-05-15
|
46
|
+
Hobbies: reading, swimming, coding
|
47
|
+
"""
|
48
|
+
parsed_data = PersonFormatter.parse(input_string)
|
49
|
+
print("Parsed data:", parsed_data)
|
50
|
+
|
51
|
+
# Formatting
|
52
|
+
person_data = {
|
53
|
+
"name": "Jane Smith",
|
54
|
+
"age": 25,
|
55
|
+
"birthday": datetime(1998, 8, 22).date(),
|
56
|
+
"hobbies": ["painting", "yoga", "traveling"],
|
57
|
+
}
|
58
|
+
formatted_string = PersonFormatter.format(person_data)
|
59
|
+
print("\nFormatted string:")
|
60
|
+
print(formatted_string)
|
61
|
+
|
62
|
+
# Demonstrating bidirectional conversion
|
63
|
+
print("\nBidirectional conversion:")
|
64
|
+
original_string = """
|
65
|
+
Name: Alice Johnson
|
66
|
+
Age: 35
|
67
|
+
Birthday: 1988-12-01
|
68
|
+
Hobbies: gardening, photography, cooking
|
69
|
+
"""
|
70
|
+
print("Original:")
|
71
|
+
print(original_string)
|
72
|
+
parsed = PersonFormatter.parse(original_string)
|
73
|
+
print("Parsed:", parsed)
|
74
|
+
reformatted = PersonFormatter.format(parsed)
|
75
|
+
print("Reformatted:")
|
76
|
+
print(reformatted)
|
@@ -0,0 +1,87 @@
|
|
1
|
+
from parse import parse
|
2
|
+
|
3
|
+
# Define the format string
|
4
|
+
format_string = """
|
5
|
+
name:{:s}
|
6
|
+
{:d} is the age
|
7
|
+
lives in {:s}
|
8
|
+
"""
|
9
|
+
|
10
|
+
|
11
|
+
def parse_person_info(input_string):
|
12
|
+
# Remove leading/trailing whitespace and split into lines
|
13
|
+
lines = input_string.strip().split("\n")
|
14
|
+
|
15
|
+
# Join the lines with a single newline
|
16
|
+
normalized_input = "\n".join(line.strip() for line in lines if line.strip())
|
17
|
+
|
18
|
+
# Parse the normalized input
|
19
|
+
result = parse(format_string, normalized_input)
|
20
|
+
|
21
|
+
if result:
|
22
|
+
name, age, city = result
|
23
|
+
return f"Name: {name.strip()}, Age: {age}, City: {city.strip()}"
|
24
|
+
else:
|
25
|
+
return "Parsing failed"
|
26
|
+
|
27
|
+
|
28
|
+
# Test cases and assertions
|
29
|
+
if __name__ == "__main__":
|
30
|
+
# Test case 1: Standard format
|
31
|
+
input1 = """
|
32
|
+
name: Beck
|
33
|
+
30 is the age
|
34
|
+
lives in Tokyo
|
35
|
+
"""
|
36
|
+
assert (
|
37
|
+
parse_person_info(input1) == "Name: Beck, Age: 30, City: Tokyo"
|
38
|
+
), "Test case 1 failed"
|
39
|
+
|
40
|
+
# Test case 2: Extra whitespace
|
41
|
+
input2 = """
|
42
|
+
name: Beck
|
43
|
+
30 is the age
|
44
|
+
lives in Tokyo
|
45
|
+
"""
|
46
|
+
assert (
|
47
|
+
parse_person_info(input2) == "Name: Beck, Age: 30, City: Tokyo"
|
48
|
+
), "Test case 2 failed"
|
49
|
+
|
50
|
+
# Test case 3: Extra newlines
|
51
|
+
input3 = """
|
52
|
+
name:Beck
|
53
|
+
|
54
|
+
|
55
|
+
30 is the age
|
56
|
+
|
57
|
+
lives in Tokyo
|
58
|
+
"""
|
59
|
+
assert (
|
60
|
+
parse_person_info(input3) == "Name: Beck, Age: 30, City: Tokyo"
|
61
|
+
), "Test case 3 failed"
|
62
|
+
|
63
|
+
# Test case 4: Minimal whitespace
|
64
|
+
input4 = "name:John\n25 is the age\nlives in NewYork"
|
65
|
+
assert (
|
66
|
+
parse_person_info(input4) == "Name: John, Age: 25, City: NewYork"
|
67
|
+
), "Test case 4 failed"
|
68
|
+
|
69
|
+
# Test case 5: Different name, age, city
|
70
|
+
input5 = """
|
71
|
+
name: Alice Johnson
|
72
|
+
42 is the age
|
73
|
+
lives in San Francisco
|
74
|
+
"""
|
75
|
+
assert (
|
76
|
+
parse_person_info(input5) == "Name: Alice Johnson, Age: 42, City: San Francisco"
|
77
|
+
), "Test case 5 failed"
|
78
|
+
|
79
|
+
# Test case 6: Invalid format (should return "Parsing failed")
|
80
|
+
input6 = """
|
81
|
+
name: Invalid
|
82
|
+
not a number is the age
|
83
|
+
lives in Nowhere
|
84
|
+
"""
|
85
|
+
assert parse_person_info(input6) == "Parsing failed", "Test case 6 failed"
|
86
|
+
|
87
|
+
print("All tests passed successfully!")
|
@@ -0,0 +1,42 @@
|
|
1
|
+
from parse import parse
|
2
|
+
|
3
|
+
# Define the parse specification
|
4
|
+
parse_spec = """
|
5
|
+
<format>
|
6
|
+
name{:s}{:>}{}\n
|
7
|
+
age{:s}{:>}{}\n
|
8
|
+
</format>
|
9
|
+
"""
|
10
|
+
|
11
|
+
|
12
|
+
def parse_info(input_string):
|
13
|
+
print(f"Input string:\n{input_string}") # Debug print
|
14
|
+
result = parse(parse_spec, input_string, case_sensitive=False)
|
15
|
+
print(f"Parse result: {result}") # Debug print
|
16
|
+
if result:
|
17
|
+
name = result[1].strip() # The actual name value is in the second group
|
18
|
+
age = result[3].strip() # The actual age value is in the fourth group
|
19
|
+
return name, age
|
20
|
+
else:
|
21
|
+
return None
|
22
|
+
|
23
|
+
|
24
|
+
if __name__ == "__main__":
|
25
|
+
# Test case 1: Standard format
|
26
|
+
input_string1 = """
|
27
|
+
<format>
|
28
|
+
name beck
|
29
|
+
age 30
|
30
|
+
</format>
|
31
|
+
"""
|
32
|
+
print("\nTest case 1:")
|
33
|
+
result1 = parse_info(input_string1)
|
34
|
+
print(f"Test 1 result: {result1}")
|
35
|
+
assert result1 == (
|
36
|
+
"beck",
|
37
|
+
"30",
|
38
|
+
), f"Test 1 failed. Expected ('beck', '30'), got {result1}"
|
39
|
+
|
40
|
+
# Additional test cases...
|
41
|
+
|
42
|
+
print("All tests passed successfully!")
|