ghoshell-common 0.4.0.dev0__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 (33) hide show
  1. ghoshell_common-0.4.0.dev0/LICENSE +21 -0
  2. ghoshell_common-0.4.0.dev0/PKG-INFO +19 -0
  3. ghoshell_common-0.4.0.dev0/README.md +3 -0
  4. ghoshell_common-0.4.0.dev0/pyproject.toml +40 -0
  5. ghoshell_common-0.4.0.dev0/src/ghoshell_common/__init__.py +0 -0
  6. ghoshell_common-0.4.0.dev0/src/ghoshell_common/entity.py +233 -0
  7. ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/__init__.py +55 -0
  8. ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/code_analyser.py +287 -0
  9. ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/coding.py +21 -0
  10. ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/dictionary.py +20 -0
  11. ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/files.py +163 -0
  12. ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/hashes.py +29 -0
  13. ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/io.py +19 -0
  14. ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/modules.py +245 -0
  15. ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/openai.py +3 -0
  16. ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/string.py +24 -0
  17. ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/timeutils.py +35 -0
  18. ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/toml.py +139 -0
  19. ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/trans.py +13 -0
  20. ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/tree_sitter.py +421 -0
  21. ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/yaml.py +24 -0
  22. ghoshell_common-0.4.0.dev0/src/ghoshell_common/identifier.py +171 -0
  23. ghoshell_common-0.4.0.dev0/src/ghoshell_common/prompter.py +385 -0
  24. ghoshell_common-0.4.0.dev0/tests/helpers/test_code_analyser.py +12 -0
  25. ghoshell_common-0.4.0.dev0/tests/helpers/test_files_helpers.py +22 -0
  26. ghoshell_common-0.4.0.dev0/tests/helpers/test_get_interface.py +114 -0
  27. ghoshell_common-0.4.0.dev0/tests/helpers/test_helpers.py +29 -0
  28. ghoshell_common-0.4.0.dev0/tests/helpers/test_modules_helper.py +48 -0
  29. ghoshell_common-0.4.0.dev0/tests/helpers/test_timeleft.py +8 -0
  30. ghoshell_common-0.4.0.dev0/tests/helpers/test_toml.py +62 -0
  31. ghoshell_common-0.4.0.dev0/tests/helpers/test_tree_sitter.py +39 -0
  32. ghoshell_common-0.4.0.dev0/tests/test_entity.py +38 -0
  33. ghoshell_common-0.4.0.dev0/tests/test_prompter.py +61 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 ghost-in-moss
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,19 @@
1
+ Metadata-Version: 2.1
2
+ Name: ghoshell-common
3
+ Version: 0.4.0.dev0
4
+ Summary: common library for GhostInShells project
5
+ Author-Email: thirdgerb <thirdgerb@gmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: ghoshell-container>=0.3.0.dev1
9
+ Requires-Dist: pydantic<3.0.0,>=2.7.0
10
+ Requires-Dist: pyyaml<7.0.0,>=6.0.1
11
+ Requires-Dist: tomlkit>=0.13.3
12
+ Requires-Dist: tree-sitter<=0.24.0,>=0.23.0
13
+ Requires-Dist: tree-sitter-languages>=1.10.0
14
+ Requires-Dist: tree-sitter-python>=0.23.6
15
+ Description-Content-Type: text/markdown
16
+
17
+ # ghoshell common lib
18
+
19
+ Ghoshell common class and functions. No need to pay attentions here.
@@ -0,0 +1,3 @@
1
+ # ghoshell common lib
2
+
3
+ Ghoshell common class and functions. No need to pay attentions here.
@@ -0,0 +1,40 @@
1
+ [project]
2
+ name = "ghoshell-common"
3
+ version = "0.4.0-dev"
4
+ description = "common library for GhostInShells project"
5
+ authors = [
6
+ { name = "thirdgerb", email = "thirdgerb@gmail.com" },
7
+ ]
8
+ readme = "README.md"
9
+ requires-python = ">=3.10"
10
+ dependencies = [
11
+ "ghoshell-container>=0.3.0.dev1",
12
+ "pydantic<3.0.0,>=2.7.0",
13
+ "pyyaml<7.0.0,>=6.0.1",
14
+ "tomlkit>=0.13.3",
15
+ "tree-sitter<=0.24.0,>=0.23.0",
16
+ "tree-sitter-languages>=1.10.0",
17
+ "tree-sitter-python>=0.23.6",
18
+ ]
19
+
20
+ [project.license]
21
+ text = "MIT"
22
+
23
+ [tool.setuptools]
24
+ packages = [
25
+ { include = "src" },
26
+ ]
27
+
28
+ [tool.pdm.build]
29
+ includes = []
30
+
31
+ [build-system]
32
+ requires = [
33
+ "pdm-backend",
34
+ ]
35
+ build-backend = "pdm.backend"
36
+
37
+ [dependency-groups]
38
+ dev = [
39
+ "pytest>=8.3.5",
40
+ ]
@@ -0,0 +1,233 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from abc import ABC, abstractmethod
5
+ from typing import Union, Any, TypeVar, Type, Optional, Protocol
6
+ from typing_extensions import Required, Self, TypedDict
7
+ from types import ModuleType
8
+ from pydantic import BaseModel
9
+ from ghoshell_common.helpers import generate_import_path, import_from_path, parse_import_path_module_and_attr_name
10
+ import inspect
11
+ import pickle
12
+ import base64
13
+ import yaml
14
+
15
+ __all__ = [
16
+
17
+ 'to_entity_meta', 'from_entity_meta', 'get_entity',
18
+ 'is_entity_type',
19
+ 'EntityMeta',
20
+ 'Entity', 'EntityType',
21
+ 'EntityClass', 'ModelEntity',
22
+
23
+ 'ModelEntityMeta',
24
+ 'to_entity_model_meta',
25
+ 'from_entity_model_meta',
26
+
27
+ ]
28
+
29
+ """
30
+ Experimental Feature
31
+ """
32
+
33
+
34
+ class Entity(Protocol):
35
+ """
36
+ Experimental interface
37
+
38
+ Protocol to define a class that generate transportable data (EntityMeta)
39
+ Instead of pickle, I want Entity is a light-weighted language independent protocol,
40
+ suite for JSON RPC or JSON API e.t.c.
41
+ """
42
+
43
+ @abstractmethod
44
+ def __to_entity_meta__(self) -> EntityMeta:
45
+ """
46
+ self-defined method to generate EntityMeta
47
+ """
48
+ pass
49
+
50
+ @classmethod
51
+ @abstractmethod
52
+ def __from_entity_meta__(cls, meta: EntityMeta) -> Self:
53
+ """
54
+ self-defined method to factory instance from EntityMeta
55
+ """
56
+ pass
57
+
58
+
59
+ class EntityClass(ABC):
60
+ """
61
+ Abstract Class for Entity
62
+
63
+ Experimental interface
64
+ """
65
+
66
+ @abstractmethod
67
+ def __to_entity_meta__(self) -> EntityMeta:
68
+ pass
69
+
70
+ @classmethod
71
+ @abstractmethod
72
+ def __from_entity_meta__(cls, meta: EntityMeta) -> Self:
73
+ pass
74
+
75
+
76
+ class ModelEntity(BaseModel, EntityClass, ABC):
77
+ """
78
+ pydantic Model that implements Entity interface
79
+ """
80
+
81
+ def __to_entity_meta__(self) -> EntityMeta:
82
+ return EntityMeta(
83
+ type=generate_import_path(self.__class__),
84
+ content=self.model_dump_json(exclude_defaults=True),
85
+ )
86
+
87
+ @classmethod
88
+ def __from_entity_meta__(cls, meta: EntityMeta) -> Self:
89
+ data = json.loads(meta['content'])
90
+ return cls(**data)
91
+
92
+
93
+ class EntityMeta(TypedDict):
94
+ """
95
+ Experimental feature
96
+
97
+ I want python has a light-weight way to marshal and unmarshal any instance and make it readable if allowed.
98
+ I found so many package-level implements like various kinds of Serializable etc.
99
+
100
+ So, I develop EntityMeta as a wrapper for any kind.
101
+ The EntityType will grow bigger with more marshaller, but do not affect who (me) is using the EntityMeta.
102
+ One day I can replace it with any better way inside the functions (but in-compatible)
103
+ """
104
+ type: Required[str]
105
+ content: Required[str]
106
+
107
+
108
+ class ModelEntityMeta(TypedDict):
109
+ """
110
+ Experimental feature
111
+
112
+ Data is dict, for pydantic.BaseModel
113
+ """
114
+ type: Required[str]
115
+ data: Required[dict]
116
+
117
+
118
+ EntityType = Union[Entity, EntityMeta, BaseModel]
119
+
120
+
121
+ def is_entity_type(value: Any) -> bool:
122
+ return hasattr(value, '__to_entity_meta__')
123
+
124
+
125
+ def to_entity_model_meta(value: BaseModel) -> ModelEntityMeta:
126
+ type_ = generate_import_path(type(value))
127
+ data = value.model_dump(exclude_defaults=True)
128
+ return ModelEntityMeta(type=type_, data=data)
129
+
130
+
131
+ def from_entity_model_meta(value: ModelEntityMeta) -> BaseModel:
132
+ cls = import_from_path(value['type'])
133
+ return cls(**value['data'])
134
+
135
+
136
+ def to_entity_meta(value: Union[EntityType, Any]) -> EntityMeta:
137
+ if value is None:
138
+ return EntityMeta(
139
+ type="None",
140
+ content="",
141
+ )
142
+ elif value is True or value is False:
143
+ return EntityMeta(type="bool", content=str(value))
144
+ elif isinstance(value, int):
145
+ return EntityMeta(type="int", content=str(value))
146
+ elif isinstance(value, str):
147
+ return EntityMeta(type="str", content=str(value))
148
+ elif isinstance(value, float):
149
+ return EntityMeta(type="float", content=str(value))
150
+ elif isinstance(value, list):
151
+ content = yaml.safe_dump(value)
152
+ return EntityMeta(type="list", content=content)
153
+ elif isinstance(value, dict):
154
+ content = yaml.safe_dump(value)
155
+ return EntityMeta(type="dict", content=content)
156
+ elif hasattr(value, '__to_entity_meta__'):
157
+ return getattr(value, '__to_entity_meta__')()
158
+ elif isinstance(value, BaseModel):
159
+ return EntityMeta(
160
+ type=generate_import_path(value.__class__),
161
+ content=value.model_dump_json(exclude_defaults=True),
162
+ )
163
+ elif inspect.isfunction(value):
164
+ return EntityMeta(
165
+ type=generate_import_path(value),
166
+ content="",
167
+ )
168
+ elif isinstance(value, BaseModel):
169
+ type_ = generate_import_path(value.__class__)
170
+ content = value.model_dump_json(exclude_defaults=True)
171
+ return EntityMeta(type=type_, content=content)
172
+ else:
173
+ content_bytes = pickle.dumps(value)
174
+ content = base64.encodebytes(content_bytes)
175
+ return EntityMeta(
176
+ type="pickle",
177
+ content=content.decode(),
178
+ )
179
+
180
+
181
+ T = TypeVar("T")
182
+
183
+
184
+ def get_entity(meta: EntityMeta, expect: Type[T]) -> T:
185
+ if meta is None:
186
+ raise ValueError("EntityMeta cannot be None")
187
+ entity = from_entity_meta(meta)
188
+ if not isinstance(entity, expect):
189
+ raise TypeError(f"Expected entity type {expect} but got {type(entity)}")
190
+ return entity
191
+
192
+
193
+ def from_entity_meta(meta: EntityMeta, module: Optional[ModuleType] = None) -> Any:
194
+ if meta is None:
195
+ return None
196
+ unmarshal_type = meta['type']
197
+ if unmarshal_type == "None":
198
+ return None
199
+ elif unmarshal_type == "int":
200
+ return int(meta['content'])
201
+ elif unmarshal_type == "str":
202
+ return str(meta['content'])
203
+ elif unmarshal_type == "bool":
204
+ return meta['content'] == "True"
205
+ elif unmarshal_type == "float":
206
+ return float(meta['content'])
207
+ elif unmarshal_type == "list" or unmarshal_type == "dict":
208
+ return yaml.safe_load(meta['content'])
209
+ elif unmarshal_type == 'pickle':
210
+ content = meta['content']
211
+ content_bytes = base64.decodebytes(content.encode())
212
+ return pickle.loads(content_bytes)
213
+
214
+ # raise if import error
215
+ cls = None
216
+ if module:
217
+ module_name, local_name = parse_import_path_module_and_attr_name(unmarshal_type)
218
+ if module_name == module.__name__:
219
+ cls = module.__dict__[local_name]
220
+ if cls is None:
221
+ cls = import_from_path(unmarshal_type)
222
+
223
+ if inspect.isfunction(cls):
224
+ return cls
225
+ # method is prior
226
+ elif hasattr(cls, "__from_entity_meta__"):
227
+ return getattr(cls, "__from_entity_meta__")(meta)
228
+
229
+ elif issubclass(cls, BaseModel):
230
+ data = json.loads(meta["content"])
231
+ return cls(**data)
232
+
233
+ raise TypeError(f"unsupported entity meta type: {unmarshal_type}")
@@ -0,0 +1,55 @@
1
+ from typing import TYPE_CHECKING
2
+ from ghoshell_common.helpers.dictionary import (dict_without_none, dict_without_zero)
3
+ from ghoshell_common.helpers.string import camel_to_snake
4
+ from ghoshell_common.helpers.yaml import yaml_pretty_dump, yaml_multiline_string_pipe
5
+ from ghoshell_common.helpers.modules import (
6
+ import_from_path,
7
+ import_class_from_path,
8
+ import_instance_from_path,
9
+ parse_import_path_module_and_attr_name,
10
+ join_import_module_and_spec,
11
+ get_module_attr,
12
+ generate_module_and_attr_name,
13
+ generate_import_path,
14
+ get_module_fullname_from_path,
15
+ Importer,
16
+ is_method_belongs_to_class,
17
+ get_calling_modulename,
18
+ rewrite_module,
19
+ rewrite_module_by_path,
20
+ create_module,
21
+ create_and_bind_module,
22
+ )
23
+ from ghoshell_common.helpers.io import BufferPrint
24
+ from ghoshell_common.helpers.timeutils import Timeleft, timestamp_datetime, timestamp, timestamp_ms
25
+ from ghoshell_common.helpers.hashes import md5, sha1, sha256
26
+ from ghoshell_common.helpers.trans import gettext, ngettext
27
+
28
+ from ghoshell_common.helpers.coding import reflect_module_code, unwrap
29
+ from ghoshell_common.helpers.openai import get_openai_key
30
+ from ghoshell_common.helpers.tree_sitter import tree_sitter_parse, code_syntax_check
31
+ from ghoshell_common.helpers.code_analyser import (
32
+ get_code_interface, get_code_interface_str,
33
+ get_attr_source_from_code, get_attr_interface_from_code,
34
+ )
35
+ from ghoshell_common.helpers.files import generate_directory_tree, list_dir, is_pathname_ignored
36
+
37
+ if TYPE_CHECKING:
38
+ from typing import Callable
39
+
40
+
41
+ # --- private methods --- #
42
+ def __uuid() -> str:
43
+ from uuid import uuid4
44
+ # keep uuid in 32 chars
45
+ return str(uuid4())
46
+
47
+
48
+ # --- facade --- #
49
+
50
+ uuid: "Callable[[], str]" = __uuid
51
+ """ patch this method to change global uuid generator"""
52
+
53
+
54
+ def uuid_md5() -> str:
55
+ return md5(uuid())
@@ -0,0 +1,287 @@
1
+ from typing import List, Union, Dict
2
+ from tree_sitter import (
3
+ Node as TreeSitterNode,
4
+ )
5
+ from ghoshell_common.helpers.tree_sitter import tree_sitter_parse
6
+ from functools import lru_cache
7
+
8
+ __all__ = ['get_code_interface', 'get_code_interface_str', 'get_attr_source_from_code', 'get_attr_interface_from_code']
9
+
10
+
11
+ @lru_cache(maxsize=256)
12
+ def get_code_interface_str(code: str) -> str:
13
+ return "\n\n".join(get_code_interface(code))
14
+
15
+
16
+ def get_code_interface(code: str) -> List[str]:
17
+ try:
18
+ # 解析代码,获取语法树
19
+ tree = tree_sitter_parse(code)
20
+ root_node = tree.root_node
21
+
22
+ # 用于存储解析后的抽象描述
23
+ interfaces = []
24
+ for child in root_node.children:
25
+ child_interface = _get_child_interface_in_depth(code, child, depth=0)
26
+ if child_interface:
27
+ interfaces.append(child_interface)
28
+
29
+ return interfaces
30
+
31
+ except Exception as e:
32
+ # 异常处理
33
+ raise
34
+
35
+
36
+ @lru_cache(maxsize=256)
37
+ def get_attr_source_from_code(code: str) -> Dict[str, str]:
38
+ """
39
+ get function or class attribute code.
40
+ """
41
+ try:
42
+ data = {}
43
+ # 解析代码,获取语法树
44
+ tree = tree_sitter_parse(code)
45
+ root_node = tree.root_node
46
+
47
+ for child in root_node.children:
48
+ name = _get_func_or_class_name(child)
49
+ if name is not None:
50
+ data[name] = child.text.decode()
51
+ return data
52
+ except Exception as e:
53
+ # 异常处理
54
+ raise
55
+
56
+
57
+ def get_attr_interface_from_code(code: str) -> Dict[str, str]:
58
+ """
59
+ get function or class interface map from the code.
60
+ """
61
+ data = {}
62
+ # 解析代码,获取语法树
63
+ tree = tree_sitter_parse(code)
64
+ root_node = tree.root_node
65
+
66
+ for child in root_node.children:
67
+ name = _get_func_or_class_name(child)
68
+ if name is not None:
69
+ interface = _get_child_interface(code, child)
70
+ if interface:
71
+ data[name] = interface
72
+ return data
73
+
74
+
75
+ def _get_func_or_class_name(child: TreeSitterNode) -> Union[str, None]:
76
+ if child.type == 'function_definition':
77
+ # 处理函数定义
78
+ func_name = child.child_by_field_name('name').text.decode()
79
+ return func_name
80
+ elif child.type == 'class_definition':
81
+ class_name = child.child_by_field_name('name').text.decode()
82
+ return class_name
83
+
84
+ elif child.type == 'decorated_definition':
85
+ # 处理装饰器
86
+ return _get_decorator_target_name(child)
87
+ return None
88
+
89
+
90
+ def _get_decorator_target_name(child: TreeSitterNode) -> str:
91
+ definition = child.children[1]
92
+ return _get_func_or_class_name(definition)
93
+
94
+
95
+ def _get_child_interface_in_depth(code: str, node: TreeSitterNode, depth: int) -> str:
96
+ interface = _get_child_interface(code, node)
97
+ if interface and depth > 0:
98
+ intend = ' ' * 4
99
+ splits = [intend + content for content in interface.split('\n')]
100
+ return '\n'.join(splits)
101
+ return interface
102
+
103
+
104
+ def _get_child_interface(code: str, child: TreeSitterNode) -> str:
105
+ if child.type == 'import_statement' or child.type == 'import_from_statement':
106
+ # 如果是引用语句,保持原样
107
+ return child.text.decode()
108
+
109
+ elif child.type == 'expression_statement':
110
+ # 处理表达式语句
111
+ return _get_assignment_interface(code, child)
112
+
113
+ elif child.type == 'function_definition':
114
+ # 处理函数定义
115
+ func_name = child.child_by_field_name('name').text.decode()
116
+ if func_name and not func_name.startswith('_'):
117
+ definition = _get_function_interface(child)
118
+ return definition
119
+ return ""
120
+
121
+ elif child.type == 'class_definition':
122
+ return _get_class_interface(code, child)
123
+
124
+ elif child.type == 'decorated_definition':
125
+ # 处理装饰器
126
+ return _get_decorator_interface(code, child)
127
+ else:
128
+ return ""
129
+
130
+
131
+ def _get_decorator_interface(code: str, child: TreeSitterNode) -> str:
132
+ decorator = child.children[0]
133
+ definition = child.children[1]
134
+ body = _get_child_interface_in_depth(code, definition, 0)
135
+ if body:
136
+ decorator_interfaces = [f"{decorator.text.decode()}", body]
137
+ return "\n".join(decorator_interfaces)
138
+ return ""
139
+
140
+
141
+ def _get_full_definition(node: TreeSitterNode, docstring: bool = True) -> str:
142
+ definition = node.children[0].text.decode() + ' '
143
+ for child in node.children[1:]:
144
+ if child.type != "block":
145
+ text = child.text.decode()
146
+ if child.type == 'identifier':
147
+ definition = definition.rstrip() + ' '
148
+
149
+ definition += text
150
+ else:
151
+ if len(child.children) > 0 and docstring:
152
+ doc_node = child.children[0]
153
+ if doc_node.type == 'expression_statement' and doc_node.children[0].type == "string":
154
+ doc_node = doc_node.children[0]
155
+ doc = "\n".join([line.lstrip() for line in doc_node.text.decode().splitlines()])
156
+ doc = insert_depth_to_code(doc, 1)
157
+ definition += "\n" + doc
158
+ break
159
+ return definition.lstrip()
160
+
161
+
162
+ def _get_assignment_interface(code: str, node: TreeSitterNode) -> str:
163
+ expression = node.children[0]
164
+ if expression.type == 'string':
165
+ return node.text.decode()
166
+
167
+ elif expression.type != 'assignment':
168
+ return ""
169
+
170
+ # 处理赋值语句
171
+ left = expression.children[0]
172
+ right = expression.children[2]
173
+ if left.type == 'identifier':
174
+ var_name = left.text.decode()
175
+ if var_name.startswith('__') and var_name.endswith('__'):
176
+ # 保持魔术变量和类型变量赋值原样
177
+ return node.text.decode()
178
+ elif var_name.startswith('_') or var_name.startswith('__'):
179
+ # 忽略私有变量赋值
180
+ return ""
181
+ else:
182
+ # 如果赋值语句长度小于50个字符,保留原样
183
+ assignment_code = node.text.decode()
184
+ if len(assignment_code) < 500:
185
+ return assignment_code
186
+ else:
187
+ # 折叠其它赋值
188
+ return f"{var_name} = ..."
189
+
190
+
191
+ def _get_class_interface(code, class_node: TreeSitterNode) -> str:
192
+ # 处理类定义
193
+ class_name = class_node.child_by_field_name('name').text.decode()
194
+ if class_name.startswith('_'):
195
+ return ""
196
+ class_definition = _get_full_definition(class_node, docstring=False)
197
+
198
+ class_body_interface = []
199
+ # 处理类中的公共方法和__init__中的公共属性赋值
200
+ property_count = 0
201
+ class_body = class_node.children[-1]
202
+ for class_child in class_body.children:
203
+ if class_child.type == 'decorated_definition':
204
+ # 处理装饰器
205
+ decorator_interface = _get_decorator_interface(code, class_child)
206
+ class_body_interface.append(decorator_interface)
207
+
208
+ elif class_child.type == 'function_definition':
209
+ method_name = class_child.child_by_field_name('name').text.decode()
210
+ if method_name == '__init__':
211
+ init_method_interface = _get_class_init_body_interface(code, class_child)
212
+ class_body_interface.append(init_method_interface)
213
+ elif not method_name.startswith('_'):
214
+ method_interface = _get_function_interface(class_child)
215
+ class_body_interface.append(method_interface)
216
+ property_count += 1
217
+ else:
218
+ interface = _get_child_interface_in_depth(code, class_child, 0)
219
+ if interface:
220
+ class_body_interface.append(interface)
221
+ property_count += 1
222
+
223
+ if property_count == 0:
224
+ class_body_interface.append(" pass")
225
+ class_body = "\n\n".join([itf for itf in class_body_interface if itf])
226
+ class_body = insert_depth_to_code(class_body, 1)
227
+ return class_definition + "\n" + class_body
228
+
229
+
230
+ def insert_depth_to_code(code: str, depth: int) -> str:
231
+ if depth < 1:
232
+ return code
233
+ indent = ' ' * 4 * depth
234
+ lines = []
235
+ for line in code.splitlines():
236
+ if line:
237
+ line = indent + line
238
+ lines.append(line)
239
+ return '\n'.join(lines)
240
+
241
+
242
+ def _get_function_interface(method_node: TreeSitterNode) -> str:
243
+ definition = _get_full_definition(method_node)
244
+ method_interface = [definition, " pass"]
245
+ return "\n".join(method_interface)
246
+
247
+
248
+ def _get_class_init_body_interface(code: str, method_node: TreeSitterNode) -> str:
249
+ # 处理__init__方法中的公共属性赋值
250
+ definition = _get_full_definition(method_node)
251
+ init_method_interface = [definition]
252
+ body_interface = []
253
+ for init_child in method_node.children:
254
+ if init_child.type == 'block':
255
+ for block_child in init_child.children:
256
+ if block_child.type == 'expression_statement':
257
+ expr = block_child.children[0]
258
+ if expr.type == 'assignment':
259
+ left = expr.children[0]
260
+ if left.type == 'attribute':
261
+ attr_name = left.text.decode()
262
+ if not (attr_name.startswith('_') or attr_name.startswith('self._')):
263
+ expr_line = expr.text.decode()
264
+ if len(expr_line) > 50:
265
+ expr_line = expr_line[:50] + "..."
266
+ body_interface.append(f" {expr_line}")
267
+ body = "\n".join(body_interface)
268
+ if body:
269
+ init_method_interface.append(body)
270
+ else:
271
+ init_method_interface.append(" pass")
272
+
273
+ return "\n".join(init_method_interface)
274
+
275
+
276
+ def _get_node_docstring(node: TreeSitterNode) -> str:
277
+ """
278
+ 获取函数或类的 docstring.
279
+ """
280
+ for child in node.children:
281
+ if child.type == 'block':
282
+ for block_child in child.children:
283
+ if block_child.type == 'expression_statement':
284
+ expr = block_child.children[0]
285
+ if expr.type == 'string':
286
+ return expr.text.decode().strip()
287
+ return ""
@@ -0,0 +1,21 @@
1
+ from typing import TypeVar, Union, Callable
2
+ from types import ModuleType
3
+
4
+ T = TypeVar("T")
5
+
6
+
7
+ def unwrap(value: Union[T, Callable[[], T]]) -> T:
8
+ """
9
+ unwrap T from value or a callable
10
+ """
11
+ if isinstance(value, Callable):
12
+ return value()
13
+ return value
14
+
15
+
16
+ def reflect_module_code(module: ModuleType) -> str:
17
+ """
18
+ get the module's code from file
19
+ """
20
+ with open(module.__file__) as f:
21
+ return f.read()