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.
- ghoshell_common-0.4.0.dev0/LICENSE +21 -0
- ghoshell_common-0.4.0.dev0/PKG-INFO +19 -0
- ghoshell_common-0.4.0.dev0/README.md +3 -0
- ghoshell_common-0.4.0.dev0/pyproject.toml +40 -0
- ghoshell_common-0.4.0.dev0/src/ghoshell_common/__init__.py +0 -0
- ghoshell_common-0.4.0.dev0/src/ghoshell_common/entity.py +233 -0
- ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/__init__.py +55 -0
- ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/code_analyser.py +287 -0
- ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/coding.py +21 -0
- ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/dictionary.py +20 -0
- ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/files.py +163 -0
- ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/hashes.py +29 -0
- ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/io.py +19 -0
- ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/modules.py +245 -0
- ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/openai.py +3 -0
- ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/string.py +24 -0
- ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/timeutils.py +35 -0
- ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/toml.py +139 -0
- ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/trans.py +13 -0
- ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/tree_sitter.py +421 -0
- ghoshell_common-0.4.0.dev0/src/ghoshell_common/helpers/yaml.py +24 -0
- ghoshell_common-0.4.0.dev0/src/ghoshell_common/identifier.py +171 -0
- ghoshell_common-0.4.0.dev0/src/ghoshell_common/prompter.py +385 -0
- ghoshell_common-0.4.0.dev0/tests/helpers/test_code_analyser.py +12 -0
- ghoshell_common-0.4.0.dev0/tests/helpers/test_files_helpers.py +22 -0
- ghoshell_common-0.4.0.dev0/tests/helpers/test_get_interface.py +114 -0
- ghoshell_common-0.4.0.dev0/tests/helpers/test_helpers.py +29 -0
- ghoshell_common-0.4.0.dev0/tests/helpers/test_modules_helper.py +48 -0
- ghoshell_common-0.4.0.dev0/tests/helpers/test_timeleft.py +8 -0
- ghoshell_common-0.4.0.dev0/tests/helpers/test_toml.py +62 -0
- ghoshell_common-0.4.0.dev0/tests/helpers/test_tree_sitter.py +39 -0
- ghoshell_common-0.4.0.dev0/tests/test_entity.py +38 -0
- 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,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
|
+
]
|
|
File without changes
|
|
@@ -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()
|