langfun 0.1.2.dev202501080804__py3-none-any.whl → 0.1.2.dev202501240804__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.
- langfun/core/__init__.py +1 -6
- langfun/core/coding/python/__init__.py +5 -11
- langfun/core/coding/python/correction.py +4 -7
- langfun/core/coding/python/correction_test.py +2 -3
- langfun/core/coding/python/execution.py +22 -211
- langfun/core/coding/python/execution_test.py +11 -90
- langfun/core/coding/python/generation.py +3 -2
- langfun/core/coding/python/generation_test.py +2 -2
- langfun/core/coding/python/parsing.py +108 -194
- langfun/core/coding/python/parsing_test.py +2 -105
- langfun/core/component.py +11 -273
- langfun/core/component_test.py +2 -29
- langfun/core/concurrent.py +187 -82
- langfun/core/concurrent_test.py +28 -19
- langfun/core/console.py +7 -3
- langfun/core/eval/base.py +2 -3
- langfun/core/eval/v2/evaluation.py +3 -1
- langfun/core/eval/v2/reporting.py +8 -4
- langfun/core/language_model.py +84 -8
- langfun/core/language_model_test.py +84 -29
- langfun/core/llms/__init__.py +46 -11
- langfun/core/llms/anthropic.py +1 -123
- langfun/core/llms/anthropic_test.py +0 -48
- langfun/core/llms/deepseek.py +117 -0
- langfun/core/llms/deepseek_test.py +61 -0
- langfun/core/llms/gemini.py +1 -1
- langfun/core/llms/groq.py +12 -99
- langfun/core/llms/groq_test.py +31 -137
- langfun/core/llms/llama_cpp.py +17 -54
- langfun/core/llms/llama_cpp_test.py +2 -34
- langfun/core/llms/openai.py +9 -147
- langfun/core/llms/openai_compatible.py +179 -0
- langfun/core/llms/openai_compatible_test.py +495 -0
- langfun/core/llms/openai_test.py +13 -423
- langfun/core/llms/rest_test.py +1 -1
- langfun/core/llms/vertexai.py +387 -18
- langfun/core/llms/vertexai_test.py +52 -0
- langfun/core/message_test.py +3 -3
- langfun/core/modalities/mime.py +8 -0
- langfun/core/modalities/mime_test.py +19 -4
- langfun/core/modality_test.py +0 -1
- langfun/core/structured/mapping.py +13 -13
- langfun/core/structured/mapping_test.py +2 -2
- langfun/core/structured/schema.py +16 -8
- langfun/core/structured/schema_generation.py +1 -1
- {langfun-0.1.2.dev202501080804.dist-info → langfun-0.1.2.dev202501240804.dist-info}/METADATA +13 -2
- {langfun-0.1.2.dev202501080804.dist-info → langfun-0.1.2.dev202501240804.dist-info}/RECORD +50 -52
- {langfun-0.1.2.dev202501080804.dist-info → langfun-0.1.2.dev202501240804.dist-info}/WHEEL +1 -1
- langfun/core/coding/python/errors.py +0 -108
- langfun/core/coding/python/errors_test.py +0 -99
- langfun/core/coding/python/permissions.py +0 -90
- langfun/core/coding/python/permissions_test.py +0 -86
- langfun/core/text_formatting.py +0 -168
- langfun/core/text_formatting_test.py +0 -65
- {langfun-0.1.2.dev202501080804.dist-info → langfun-0.1.2.dev202501240804.dist-info}/LICENSE +0 -0
- {langfun-0.1.2.dev202501080804.dist-info → langfun-0.1.2.dev202501240804.dist-info}/top_level.txt +0 -0
@@ -1,108 +0,0 @@
|
|
1
|
-
# Copyright 2023 The Langfun Authors
|
2
|
-
#
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
-
# you may not use this file except in compliance with the License.
|
5
|
-
# You may obtain a copy of the License at
|
6
|
-
#
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
-
#
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
-
# See the License for the specific language governing permissions and
|
13
|
-
# limitations under the License.
|
14
|
-
"""Python code errors."""
|
15
|
-
|
16
|
-
import io
|
17
|
-
import sys
|
18
|
-
import textwrap
|
19
|
-
import traceback
|
20
|
-
import langfun.core as lf
|
21
|
-
|
22
|
-
|
23
|
-
class CodeError(RuntimeError):
|
24
|
-
"""Python code error."""
|
25
|
-
|
26
|
-
def __init__(
|
27
|
-
self,
|
28
|
-
code: str,
|
29
|
-
cause: Exception,
|
30
|
-
):
|
31
|
-
self.code = code
|
32
|
-
self.cause = cause
|
33
|
-
|
34
|
-
# Figure out the starting and ending line numbers of the erratic code.
|
35
|
-
lineno = None
|
36
|
-
end_lineno = None
|
37
|
-
if isinstance(cause, SyntaxError):
|
38
|
-
lineno = cause.lineno
|
39
|
-
end_lineno = cause.end_lineno
|
40
|
-
elif not isinstance(cause, TimeoutError):
|
41
|
-
tb = sys.exc_info()[2]
|
42
|
-
frames = traceback.extract_tb(tb, limit=5)
|
43
|
-
for f in frames:
|
44
|
-
if not f.filename or f.filename == '<string>':
|
45
|
-
lineno = f.lineno
|
46
|
-
end_lineno = lineno
|
47
|
-
break
|
48
|
-
self.lineno = lineno
|
49
|
-
self.end_lineno = end_lineno
|
50
|
-
|
51
|
-
def __str__(self):
|
52
|
-
return self.format(include_complete_code=True)
|
53
|
-
|
54
|
-
def code_lines(self, start_line: int, end_line: int):
|
55
|
-
"""Returns code lines ."""
|
56
|
-
return '\n'.join(self.code.split('\n')[start_line:end_line])
|
57
|
-
|
58
|
-
def format(self, include_complete_code: bool = True):
|
59
|
-
"""Formats the code error."""
|
60
|
-
r = io.StringIO()
|
61
|
-
error_message = str(self.cause).rstrip()
|
62
|
-
if 'line' not in error_message and self.lineno is not None:
|
63
|
-
error_message += f' (<unknown>, line {self.lineno})'
|
64
|
-
r.write(
|
65
|
-
lf.colored(
|
66
|
-
f'{self.cause.__class__.__name__}: {error_message}', 'magenta'))
|
67
|
-
|
68
|
-
if self.lineno is not None:
|
69
|
-
r.write('\n\n')
|
70
|
-
r.write(textwrap.indent(
|
71
|
-
lf.colored(
|
72
|
-
self.code_lines(self.lineno - 1, self.end_lineno), 'magenta'),
|
73
|
-
' ' * 2
|
74
|
-
))
|
75
|
-
r.write('\n')
|
76
|
-
|
77
|
-
if include_complete_code:
|
78
|
-
r.write('\n')
|
79
|
-
r.write(lf.colored('[Generated Code]', 'green', styles=['bold']))
|
80
|
-
r.write('\n\n')
|
81
|
-
r.write(lf.colored(' ```python\n', 'green'))
|
82
|
-
r.write(textwrap.indent(
|
83
|
-
lf.colored(self.code, 'green'),
|
84
|
-
' ' * 2
|
85
|
-
))
|
86
|
-
r.write(lf.colored('\n ```\n', 'green'))
|
87
|
-
return r.getvalue()
|
88
|
-
|
89
|
-
|
90
|
-
class SerializationError(RuntimeError):
|
91
|
-
"""Object serialization error."""
|
92
|
-
|
93
|
-
def __init__(self, message: str | None, cause: Exception):
|
94
|
-
self.message = message
|
95
|
-
self.cause = cause
|
96
|
-
|
97
|
-
def __str__(self):
|
98
|
-
r = io.StringIO()
|
99
|
-
cause_message = str(self.cause).rstrip()
|
100
|
-
if self.message:
|
101
|
-
r.write(lf.colored(self.message, 'magenta'))
|
102
|
-
r.write('\n\n')
|
103
|
-
r.write(
|
104
|
-
lf.colored(
|
105
|
-
f'{self.cause.__class__.__name__}: {cause_message}', 'magenta'
|
106
|
-
)
|
107
|
-
)
|
108
|
-
return r.getvalue()
|
@@ -1,99 +0,0 @@
|
|
1
|
-
# Copyright 2023 The Langfun Authors
|
2
|
-
#
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
-
# you may not use this file except in compliance with the License.
|
5
|
-
# You may obtain a copy of the License at
|
6
|
-
#
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
-
#
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
-
# See the License for the specific language governing permissions and
|
13
|
-
# limitations under the License.
|
14
|
-
"""Tests for code errors."""
|
15
|
-
|
16
|
-
import unittest
|
17
|
-
|
18
|
-
from langfun.core.coding.python import errors
|
19
|
-
from langfun.core.coding.python import execution
|
20
|
-
|
21
|
-
|
22
|
-
def code_error(code: str) -> errors.CodeError | None:
|
23
|
-
try:
|
24
|
-
execution.run(code, timeout=2)
|
25
|
-
return None
|
26
|
-
except errors.CodeError as e:
|
27
|
-
return e
|
28
|
-
|
29
|
-
|
30
|
-
class CodeErrorsTest(unittest.TestCase):
|
31
|
-
|
32
|
-
def test_format(self):
|
33
|
-
e = code_error(
|
34
|
-
"""
|
35
|
-
x = y + 1
|
36
|
-
"""
|
37
|
-
)
|
38
|
-
self.assertIn('[Generated Code]', str(e))
|
39
|
-
self.assertNotIn(
|
40
|
-
'[Generated Code]', e.format(include_complete_code=False))
|
41
|
-
|
42
|
-
def test_lineno(self):
|
43
|
-
self.assertEqual(
|
44
|
-
code_error(
|
45
|
-
"""
|
46
|
-
x = y + 1
|
47
|
-
"""
|
48
|
-
).lineno, 1)
|
49
|
-
self.assertEqual(
|
50
|
-
code_error(
|
51
|
-
"""
|
52
|
-
x = 1
|
53
|
-
for i of x:
|
54
|
-
y = i
|
55
|
-
"""
|
56
|
-
).lineno, 2)
|
57
|
-
self.assertEqual(
|
58
|
-
code_error(
|
59
|
-
"""
|
60
|
-
x = 1
|
61
|
-
y = 2
|
62
|
-
raise ValueError
|
63
|
-
"""
|
64
|
-
).lineno, 3)
|
65
|
-
|
66
|
-
def test_lineno_in_error_message(self):
|
67
|
-
def assert_lineno(code):
|
68
|
-
e = code_error(code)
|
69
|
-
self.assertIn('line', e.format(include_complete_code=False))
|
70
|
-
|
71
|
-
assert_lineno(
|
72
|
-
"""
|
73
|
-
x = y + 1
|
74
|
-
"""
|
75
|
-
)
|
76
|
-
assert_lineno(
|
77
|
-
"""
|
78
|
-
x = 1
|
79
|
-
y = 2
|
80
|
-
"""
|
81
|
-
)
|
82
|
-
assert_lineno(
|
83
|
-
"""
|
84
|
-
raise ValueError()
|
85
|
-
"""
|
86
|
-
)
|
87
|
-
|
88
|
-
|
89
|
-
class SerializationErrorTest(unittest.TestCase):
|
90
|
-
|
91
|
-
def test_str(self):
|
92
|
-
e = errors.SerializationError(
|
93
|
-
'Output cannot be serialized.', ValueError('abc'))
|
94
|
-
self.assertIn('Output cannot be serialized', str(e))
|
95
|
-
self.assertIn('ValueError: abc', str(e))
|
96
|
-
|
97
|
-
|
98
|
-
if __name__ == '__main__':
|
99
|
-
unittest.main()
|
@@ -1,90 +0,0 @@
|
|
1
|
-
# Copyright 2023 The Langfun Authors
|
2
|
-
#
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
-
# you may not use this file except in compliance with the License.
|
5
|
-
# You may obtain a copy of the License at
|
6
|
-
#
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
-
#
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
-
# See the License for the specific language governing permissions and
|
13
|
-
# limitations under the License.
|
14
|
-
"""Python code permissions."""
|
15
|
-
|
16
|
-
import contextlib
|
17
|
-
import enum
|
18
|
-
import pyglove as pg
|
19
|
-
|
20
|
-
|
21
|
-
class CodePermission(enum.Flag):
|
22
|
-
"""Permissions for code execution."""
|
23
|
-
|
24
|
-
# Allows basic Python code: Creating objects, assignment, operations.
|
25
|
-
BASIC = enum.auto()
|
26
|
-
|
27
|
-
# Allows conditions.
|
28
|
-
CONDITION = enum.auto()
|
29
|
-
|
30
|
-
# Allows loops.
|
31
|
-
LOOP = enum.auto()
|
32
|
-
|
33
|
-
# Allows exception.
|
34
|
-
EXCEPTION = enum.auto()
|
35
|
-
|
36
|
-
# Allows class definitions.
|
37
|
-
CLASS_DEFINITION = enum.auto()
|
38
|
-
|
39
|
-
# Allows function definitions.
|
40
|
-
FUNCTION_DEFINITION = enum.auto()
|
41
|
-
|
42
|
-
# Allows import.
|
43
|
-
IMPORT = enum.auto()
|
44
|
-
|
45
|
-
@classmethod
|
46
|
-
@property
|
47
|
-
def ALL(cls) -> 'CodePermission': # pylint: disable=invalid-name
|
48
|
-
"""Returns all permissions."""
|
49
|
-
return (
|
50
|
-
CodePermission.BASIC | CodePermission.CONDITION | CodePermission.LOOP |
|
51
|
-
CodePermission.EXCEPTION | CodePermission.CLASS_DEFINITION |
|
52
|
-
CodePermission.FUNCTION_DEFINITION | CodePermission.IMPORT)
|
53
|
-
|
54
|
-
|
55
|
-
_TLS_CODE_RUN_PERMISSION = '__code_run_permission__'
|
56
|
-
|
57
|
-
|
58
|
-
@contextlib.contextmanager
|
59
|
-
def permission(perm: CodePermission):
|
60
|
-
"""Context manager for controling the permission for code execution.
|
61
|
-
|
62
|
-
When the `permission` context manager is nested, the outtermost permission
|
63
|
-
will be used. This design allows users to control permission at the top level.
|
64
|
-
|
65
|
-
Args:
|
66
|
-
perm: Code execution permission.
|
67
|
-
|
68
|
-
Yields:
|
69
|
-
Actual permission applied.
|
70
|
-
"""
|
71
|
-
|
72
|
-
outter_perm = pg.object_utils.thread_local_get(_TLS_CODE_RUN_PERMISSION, None)
|
73
|
-
|
74
|
-
# Use the top-level permission as the actual permission
|
75
|
-
if outter_perm is not None:
|
76
|
-
perm = outter_perm
|
77
|
-
|
78
|
-
pg.object_utils.thread_local_set(_TLS_CODE_RUN_PERMISSION, perm)
|
79
|
-
|
80
|
-
try:
|
81
|
-
yield perm
|
82
|
-
finally:
|
83
|
-
if outter_perm is None:
|
84
|
-
pg.object_utils.thread_local_del(_TLS_CODE_RUN_PERMISSION)
|
85
|
-
|
86
|
-
|
87
|
-
def get_permission() -> CodePermission:
|
88
|
-
"""Gets the current permission for code execution."""
|
89
|
-
return pg.object_utils.thread_local_get(
|
90
|
-
_TLS_CODE_RUN_PERMISSION, CodePermission.ALL)
|
@@ -1,86 +0,0 @@
|
|
1
|
-
# Copyright 2023 The Langfun Authors
|
2
|
-
#
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
-
# you may not use this file except in compliance with the License.
|
5
|
-
# You may obtain a copy of the License at
|
6
|
-
#
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
-
#
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
-
# See the License for the specific language governing permissions and
|
13
|
-
# limitations under the License.
|
14
|
-
"""Tests for Python code permissions."""
|
15
|
-
|
16
|
-
import unittest
|
17
|
-
from langfun.core.coding.python import permissions
|
18
|
-
|
19
|
-
|
20
|
-
class CodePermissionTest(unittest.TestCase):
|
21
|
-
|
22
|
-
def assert_set(
|
23
|
-
self,
|
24
|
-
permission: permissions.CodePermission,
|
25
|
-
flag: permissions.CodePermission,
|
26
|
-
):
|
27
|
-
self.assertEqual(permission & flag, flag)
|
28
|
-
|
29
|
-
def assert_not_set(
|
30
|
-
self,
|
31
|
-
permission: permissions.CodePermission,
|
32
|
-
flag: permissions.CodePermission,
|
33
|
-
):
|
34
|
-
self.assertFalse(permission & flag)
|
35
|
-
|
36
|
-
def test_all(self):
|
37
|
-
self.assert_set(
|
38
|
-
permissions.CodePermission.ALL, permissions.CodePermission.BASIC
|
39
|
-
)
|
40
|
-
self.assert_set(
|
41
|
-
permissions.CodePermission.ALL, permissions.CodePermission.CONDITION
|
42
|
-
)
|
43
|
-
self.assert_set(
|
44
|
-
permissions.CodePermission.ALL, permissions.CodePermission.LOOP
|
45
|
-
)
|
46
|
-
self.assert_set(
|
47
|
-
permissions.CodePermission.ALL, permissions.CodePermission.EXCEPTION
|
48
|
-
)
|
49
|
-
self.assert_set(
|
50
|
-
permissions.CodePermission.ALL,
|
51
|
-
permissions.CodePermission.CLASS_DEFINITION,
|
52
|
-
)
|
53
|
-
self.assert_set(
|
54
|
-
permissions.CodePermission.ALL,
|
55
|
-
permissions.CodePermission.FUNCTION_DEFINITION,
|
56
|
-
)
|
57
|
-
self.assert_set(
|
58
|
-
permissions.CodePermission.ALL, permissions.CodePermission.IMPORT
|
59
|
-
)
|
60
|
-
|
61
|
-
def test_xor(self):
|
62
|
-
self.assert_not_set(
|
63
|
-
permissions.CodePermission.ALL ^ permissions.CodePermission.BASIC,
|
64
|
-
permissions.CodePermission.BASIC,
|
65
|
-
)
|
66
|
-
self.assert_set(
|
67
|
-
permissions.CodePermission.ALL ^ permissions.CodePermission.BASIC,
|
68
|
-
permissions.CodePermission.CONDITION,
|
69
|
-
)
|
70
|
-
|
71
|
-
def test_permission_control(self):
|
72
|
-
self.assertEqual(
|
73
|
-
permissions.get_permission(), permissions.CodePermission.ALL
|
74
|
-
)
|
75
|
-
with permissions.permission(permissions.CodePermission.BASIC):
|
76
|
-
self.assertEqual(
|
77
|
-
permissions.get_permission(), permissions.CodePermission.BASIC
|
78
|
-
)
|
79
|
-
with permissions.permission(permissions.CodePermission.ALL):
|
80
|
-
self.assertEqual(
|
81
|
-
permissions.get_permission(), permissions.CodePermission.BASIC
|
82
|
-
)
|
83
|
-
|
84
|
-
|
85
|
-
if __name__ == '__main__':
|
86
|
-
unittest.main()
|
langfun/core/text_formatting.py
DELETED
@@ -1,168 +0,0 @@
|
|
1
|
-
# Copyright 2023 The Langfun Authors
|
2
|
-
#
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
-
# you may not use this file except in compliance with the License.
|
5
|
-
# You may obtain a copy of the License at
|
6
|
-
#
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
-
#
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
-
# See the License for the specific language governing permissions and
|
13
|
-
# limitations under the License.
|
14
|
-
"""Utility library for LM input/output formatting."""
|
15
|
-
|
16
|
-
import io
|
17
|
-
import re
|
18
|
-
from typing import Any
|
19
|
-
|
20
|
-
try:
|
21
|
-
import termcolor # pylint: disable=g-import-not-at-top
|
22
|
-
except ImportError:
|
23
|
-
termcolor = None
|
24
|
-
|
25
|
-
|
26
|
-
# Regular expression for ANSI color characters.
|
27
|
-
_ANSI_COLOR_REGEX = re.compile(r'\x1b\[[0-9;]*m')
|
28
|
-
|
29
|
-
|
30
|
-
def decolored(text: str) -> str:
|
31
|
-
"""Return the de-colored string that may contains ANSI color characters."""
|
32
|
-
return re.sub(_ANSI_COLOR_REGEX, '', text)
|
33
|
-
|
34
|
-
|
35
|
-
def colored(
|
36
|
-
text: str,
|
37
|
-
color: str | None = None,
|
38
|
-
background: str | None = None,
|
39
|
-
styles: list[str] | None = None
|
40
|
-
) -> str:
|
41
|
-
"""Returns the colored text with ANSI color characters.
|
42
|
-
|
43
|
-
Args:
|
44
|
-
text: A string that may or may not already has ANSI color characters.
|
45
|
-
color: A string for text colors. Applicable values are:
|
46
|
-
'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'.
|
47
|
-
background: A string for background colors. Applicable values are:
|
48
|
-
'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'.
|
49
|
-
styles: A list of strings for applying styles on the text.
|
50
|
-
Applicable values are:
|
51
|
-
'bold', 'dark', 'underline', 'blink', 'reverse', 'concealed'.
|
52
|
-
|
53
|
-
Returns:
|
54
|
-
A string with ANSI color characters embracing the entire text.
|
55
|
-
"""
|
56
|
-
if not termcolor:
|
57
|
-
return text
|
58
|
-
return termcolor.colored(
|
59
|
-
text,
|
60
|
-
color=color,
|
61
|
-
on_color=('on_' + background) if background else None,
|
62
|
-
attrs=styles)
|
63
|
-
|
64
|
-
|
65
|
-
def colored_template(
|
66
|
-
text: str,
|
67
|
-
expression_color: str | None = 'white',
|
68
|
-
expression_background: str | None = 'blue',
|
69
|
-
expression_styles: list[str] | None = None,
|
70
|
-
statement_color: str | None = 'red',
|
71
|
-
statement_background: str | None = None,
|
72
|
-
statement_styles: list[str] | None = None,
|
73
|
-
comment_color: str | None = 'green',
|
74
|
-
comment_background: str | None = None,
|
75
|
-
comment_styles: list[str] | None = None,
|
76
|
-
) -> str:
|
77
|
-
"""Returns colored (maybe) Jinja2 template string."""
|
78
|
-
text = color_text_blocks(
|
79
|
-
text, '{{', '}}',
|
80
|
-
color=expression_color,
|
81
|
-
background=expression_background,
|
82
|
-
styles=expression_styles)
|
83
|
-
|
84
|
-
text = color_text_blocks(
|
85
|
-
text, '{%', '%}',
|
86
|
-
color=statement_color,
|
87
|
-
background=statement_background,
|
88
|
-
styles=statement_styles)
|
89
|
-
|
90
|
-
text = color_text_blocks(
|
91
|
-
text, '{#', '#}',
|
92
|
-
color=comment_color,
|
93
|
-
background=comment_background,
|
94
|
-
styles=comment_styles)
|
95
|
-
|
96
|
-
return text
|
97
|
-
|
98
|
-
|
99
|
-
def color_text_blocks(
|
100
|
-
text: str,
|
101
|
-
block_start: str,
|
102
|
-
block_end: str,
|
103
|
-
color: str | None = None,
|
104
|
-
background: str | None = None,
|
105
|
-
styles: list[str] | None = None
|
106
|
-
) -> str:
|
107
|
-
"""Apply colors to text blocks.
|
108
|
-
|
109
|
-
Args:
|
110
|
-
text: A string that may or may not already has ANSI color characters.
|
111
|
-
block_start: A string that signals the start of a block. E.g. '{{'
|
112
|
-
block_end: A string that signals the end of a block. E.g. '}}'.
|
113
|
-
color: A string for text colors. Applicable values are:
|
114
|
-
'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'.
|
115
|
-
background: A string for background colors. Applicable values are:
|
116
|
-
'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'.
|
117
|
-
styles: A list of strings for applying styles on the text.
|
118
|
-
Applicable values are:
|
119
|
-
'bold', 'dark', 'underline', 'blink', 'reverse', 'concealed'.
|
120
|
-
|
121
|
-
Returns:
|
122
|
-
A string with ANSI color characters embracing the matched text blocks.
|
123
|
-
"""
|
124
|
-
if not color and not background and not styles:
|
125
|
-
return text
|
126
|
-
|
127
|
-
string_buffer = io.StringIO()
|
128
|
-
start_index = 0
|
129
|
-
end_index = 0
|
130
|
-
previous_color = None
|
131
|
-
|
132
|
-
def write_nonblock_text(text: str, previous_color: str | None):
|
133
|
-
if previous_color:
|
134
|
-
string_buffer.write(previous_color)
|
135
|
-
string_buffer.write(text)
|
136
|
-
|
137
|
-
while start_index < len(text):
|
138
|
-
start_index = text.find(block_start, end_index)
|
139
|
-
if start_index == -1:
|
140
|
-
write_nonblock_text(text[end_index:], previous_color)
|
141
|
-
break
|
142
|
-
|
143
|
-
# Deal with text since last block.
|
144
|
-
since_last_block = text[end_index:start_index]
|
145
|
-
write_nonblock_text(since_last_block, previous_color)
|
146
|
-
colors = re.findall(_ANSI_COLOR_REGEX, since_last_block)
|
147
|
-
if colors:
|
148
|
-
previous_color = colors[-1]
|
149
|
-
|
150
|
-
# Match block.
|
151
|
-
end_index = text.find(block_end, start_index + len(block_start))
|
152
|
-
if end_index == -1:
|
153
|
-
write_nonblock_text(text[start_index:], previous_color)
|
154
|
-
break
|
155
|
-
end_index += len(block_end)
|
156
|
-
|
157
|
-
# Write block text.
|
158
|
-
block = text[start_index:end_index]
|
159
|
-
colored_block = colored(
|
160
|
-
block, color=color, background=background, styles=styles)
|
161
|
-
string_buffer.write(colored_block)
|
162
|
-
return string_buffer.getvalue()
|
163
|
-
|
164
|
-
|
165
|
-
def colored_print(value: Any):
|
166
|
-
"""Prints text with color."""
|
167
|
-
print(colored_template(str(value)))
|
168
|
-
|
@@ -1,65 +0,0 @@
|
|
1
|
-
# Copyright 2023 The Langfun Authors
|
2
|
-
#
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
-
# you may not use this file except in compliance with the License.
|
5
|
-
# You may obtain a copy of the License at
|
6
|
-
#
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
-
#
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
-
# See the License for the specific language governing permissions and
|
13
|
-
# limitations under the License.
|
14
|
-
"""Tests for text formatting."""
|
15
|
-
|
16
|
-
import inspect
|
17
|
-
import unittest
|
18
|
-
from langfun.core import text_formatting
|
19
|
-
|
20
|
-
|
21
|
-
class TextFormattingTest(unittest.TestCase):
|
22
|
-
|
23
|
-
def test_colored_template(self):
|
24
|
-
original_text = inspect.cleandoc("""
|
25
|
-
Hi {{ foo }}
|
26
|
-
{# print x if x is present #}
|
27
|
-
{% if x %}
|
28
|
-
{{ x }}
|
29
|
-
{% endif %}
|
30
|
-
""")
|
31
|
-
|
32
|
-
colored_text = text_formatting.colored_template(
|
33
|
-
text_formatting.colored(original_text, color='blue')
|
34
|
-
)
|
35
|
-
self.assertEqual(
|
36
|
-
colored_text,
|
37
|
-
'\x1b[34mHi \x1b[44m\x1b[37m{{ foo }}\x1b[0m\x1b[34m\n'
|
38
|
-
'\x1b[32m{# print x if x is present #}\x1b[0m\x1b[34m\n'
|
39
|
-
'\x1b[31m{% if x %}\x1b[0m\x1b[34m\n'
|
40
|
-
'\x1b[44m\x1b[37m{{ x }}\x1b[0m\x1b[34m\n'
|
41
|
-
'\x1b[31m{% endif %}\x1b[0m\x1b[34m\x1b[0m'
|
42
|
-
)
|
43
|
-
self.assertEqual(text_formatting.decolored(colored_text), original_text)
|
44
|
-
|
45
|
-
def test_colored_without_termcolor(self):
|
46
|
-
termcolor = text_formatting.termcolor
|
47
|
-
text_formatting.termcolor = None
|
48
|
-
original_text = inspect.cleandoc("""
|
49
|
-
Hi {{ foo }}
|
50
|
-
{# print x if x is present #}
|
51
|
-
{% if x %}
|
52
|
-
{{ x }}
|
53
|
-
{% endif %}
|
54
|
-
""")
|
55
|
-
|
56
|
-
colored_text = text_formatting.colored_template(
|
57
|
-
text_formatting.colored(original_text, color='blue')
|
58
|
-
)
|
59
|
-
self.assertEqual(colored_text, original_text)
|
60
|
-
self.assertEqual(text_formatting.decolored(colored_text), original_text)
|
61
|
-
text_formatting.termcolor = termcolor
|
62
|
-
|
63
|
-
|
64
|
-
if __name__ == '__main__':
|
65
|
-
unittest.main()
|
File without changes
|
{langfun-0.1.2.dev202501080804.dist-info → langfun-0.1.2.dev202501240804.dist-info}/top_level.txt
RENAMED
File without changes
|