ArchLens 0.2__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.
- archlens-0.2.data/data/config.schema.json +94 -0
- archlens-0.2.data/data/config.template.json +16 -0
- archlens-0.2.dist-info/METADATA +25 -0
- archlens-0.2.dist-info/RECORD +31 -0
- archlens-0.2.dist-info/WHEEL +5 -0
- archlens-0.2.dist-info/entry_points.txt +2 -0
- archlens-0.2.dist-info/top_level.txt +1 -0
- src/__init__.py +4 -0
- src/cli_interface.py +176 -0
- src/config.schema.json +94 -0
- src/config.template.json +16 -0
- src/core/__init__.py +0 -0
- src/core/bt_file.py +95 -0
- src/core/bt_graph.py +115 -0
- src/core/bt_module.py +99 -0
- src/git_integration/__init__.py +0 -0
- src/git_integration/fetch_git.py +11 -0
- src/main.py +2 -0
- src/providers/__init__.py +0 -0
- src/providers/json/__init__.py +0 -0
- src/providers/json/json_render.py +46 -0
- src/providers/plantuml/__init__.py +0 -0
- src/providers/plantuml/pu_render.py +62 -0
- src/utils/__init__.py +0 -0
- src/utils/config_manager_singleton.py +14 -0
- src/utils/functions.py +38 -0
- src/utils/path_manager_singleton.py +54 -0
- src/views/__init__.py +0 -0
- src/views/utils.py +8 -0
- src/views/view_entities.py +225 -0
- src/views/view_manager.py +255 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"required": ["name", "rootFolder", "views"],
|
|
5
|
+
"properties": {
|
|
6
|
+
"name": {
|
|
7
|
+
"type": "string",
|
|
8
|
+
"description": "The name of the project",
|
|
9
|
+
"minLength": 1
|
|
10
|
+
},
|
|
11
|
+
"rootFolder": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"description": "Point to the root package of the project",
|
|
14
|
+
"minLength": 1
|
|
15
|
+
},
|
|
16
|
+
"github": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"description": "Information about the GitHub repository for the project",
|
|
19
|
+
"required": ["url", "branch"],
|
|
20
|
+
"properties": {
|
|
21
|
+
"url": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "The URL of the GitHub repository for the project"
|
|
24
|
+
},
|
|
25
|
+
"branch": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"description": "The name of the branch to use for the GitHub repository"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"SaveLocation": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "The folder to which the diagrams should be saved in. If the folder does not exists it will be created",
|
|
34
|
+
"default": "./diagrams/"
|
|
35
|
+
},
|
|
36
|
+
"showDependencyCount": {
|
|
37
|
+
"type": "boolean",
|
|
38
|
+
"description": "If to showcase the number of dependencies between packages"
|
|
39
|
+
},
|
|
40
|
+
"packageColor": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"enum": ["#GoldenRod", "#Azure", ""]
|
|
43
|
+
},
|
|
44
|
+
"views": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"description": "Defines the views available",
|
|
47
|
+
"patternProperties": {
|
|
48
|
+
".*": {
|
|
49
|
+
"description": "The name of the view",
|
|
50
|
+
"type": "object",
|
|
51
|
+
"properties": {
|
|
52
|
+
"packages": {
|
|
53
|
+
"description": "The packages to include in the view. Leave empty to include all packages",
|
|
54
|
+
"type": "array",
|
|
55
|
+
"items": {
|
|
56
|
+
"oneOf": [
|
|
57
|
+
{
|
|
58
|
+
"type": "string",
|
|
59
|
+
"pattern": "^[A-Za-z0-9._-]+$"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"type": "object",
|
|
63
|
+
"properties": {
|
|
64
|
+
"path": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"pattern": "^[A-Za-z0-9._\\-]+|[*]$"
|
|
67
|
+
},
|
|
68
|
+
"depth": {
|
|
69
|
+
"type": "integer"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"required": ["path", "depth"]
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"ignorePackages": {
|
|
78
|
+
"type": "array",
|
|
79
|
+
"items": {
|
|
80
|
+
"type": "string",
|
|
81
|
+
"pattern": "^[A-Za-z0-9._\\-\\/*]+$"
|
|
82
|
+
},
|
|
83
|
+
"description": "Defines the packages to ignore"
|
|
84
|
+
},
|
|
85
|
+
"usePackagePathAsLabel": {
|
|
86
|
+
"type": "boolean",
|
|
87
|
+
"description": "If true, paths of each package will be displayed, if false only package name will be displayed"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/archlens/ArchLens/master/src/config.schema.json",
|
|
3
|
+
"name": "",
|
|
4
|
+
"rootFolder": "",
|
|
5
|
+
"github": {
|
|
6
|
+
"url": "",
|
|
7
|
+
"branch": "main"
|
|
8
|
+
},
|
|
9
|
+
"saveLocation": "./diagrams/",
|
|
10
|
+
"views": {
|
|
11
|
+
"completeView": {
|
|
12
|
+
"packages": [],
|
|
13
|
+
"ignorePackages": []
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ArchLens
|
|
3
|
+
Version: 0.2
|
|
4
|
+
Summary: Designed for visualizing package dependencies and highlighting differences between branches in GitHub pull requests. It offers customization options to tailor package views.
|
|
5
|
+
Home-page: https://github.com/archlens/ArchLens
|
|
6
|
+
Author: The ArchLens Team
|
|
7
|
+
Author-email: mlun@itu.dk
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Requires-Dist: plantuml
|
|
11
|
+
Requires-Dist: typer
|
|
12
|
+
Requires-Dist: astroid
|
|
13
|
+
Requires-Dist: six
|
|
14
|
+
Requires-Dist: requests
|
|
15
|
+
Requires-Dist: jsonschema
|
|
16
|
+
Requires-Dist: gitpython
|
|
17
|
+
Dynamic: author
|
|
18
|
+
Dynamic: author-email
|
|
19
|
+
Dynamic: classifier
|
|
20
|
+
Dynamic: description
|
|
21
|
+
Dynamic: home-page
|
|
22
|
+
Dynamic: requires-dist
|
|
23
|
+
Dynamic: summary
|
|
24
|
+
|
|
25
|
+
This is the long description
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
archlens-0.2.data/data/config.schema.json,sha256=WexuktJolnluIvoOE4YGWoxY4rRlU9lr3f0L2lKELWA,2901
|
|
2
|
+
archlens-0.2.data/data/config.template.json,sha256=mdJse5TRDy_V0KpN9UFaFRFo23ZbOQ32Y4hwJgDJj6c,318
|
|
3
|
+
src/__init__.py,sha256=-d7TWFKkXpRRhH9rz7n-2n623B_HqgfMd1rpbRfpXCM,64
|
|
4
|
+
src/cli_interface.py,sha256=NtjNKVw88HTwLnEbJCP893Ta-yOzHcQkUWzKdeYvbHA,5084
|
|
5
|
+
src/config.schema.json,sha256=WexuktJolnluIvoOE4YGWoxY4rRlU9lr3f0L2lKELWA,2901
|
|
6
|
+
src/config.template.json,sha256=mdJse5TRDy_V0KpN9UFaFRFo23ZbOQ32Y4hwJgDJj6c,318
|
|
7
|
+
src/main.py,sha256=YbJCHt-qZHNxtl9z1QYOesw8b8OLkQKsML2QmZTHNVA,30
|
|
8
|
+
src/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
src/core/bt_file.py,sha256=ugNwjTiiisqaXr0iQJ4IG9s2W3mpuv_wPT23eOU25Jg,2768
|
|
10
|
+
src/core/bt_graph.py,sha256=TfDEbMcDFjsBe1A2_g610FMKG7zM6Rf6i1U0D3SAiK0,3859
|
|
11
|
+
src/core/bt_module.py,sha256=cGtKiJxazojyjmPEoTHtk-zFSE_12lY_zLjXh3Tad3s,3147
|
|
12
|
+
src/git_integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
src/git_integration/fetch_git.py,sha256=yqDWi_fk-zxkdApmE3SKEmx0uXcBux1Fw3K0Mhh8w6g,244
|
|
14
|
+
src/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
src/providers/json/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
src/providers/json/json_render.py,sha256=iCPtE8thUFcOzDZNyET0VvU6E4uCcT-D9HmoWx3rono,1465
|
|
17
|
+
src/providers/plantuml/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
src/providers/plantuml/pu_render.py,sha256=WEozrq9AWUr4Cg7LqhyVoT04Vg4Z8QzOT_5nTYSEUSM,1820
|
|
19
|
+
src/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
src/utils/config_manager_singleton.py,sha256=n3Vp-JSaoXpD5ukBUgKxXdmTWIlapth8gZ53C0WEYsY,424
|
|
21
|
+
src/utils/functions.py,sha256=TbLO4o9qJ-iaioN1S0HDE70AAFm7SxQaHjGgd-nNrSo,1520
|
|
22
|
+
src/utils/path_manager_singleton.py,sha256=VhKzS82tRZ6QNJTq6tQKJmUSHyr29yrGmxZXdSmpJn0,1850
|
|
23
|
+
src/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
src/views/utils.py,sha256=BJi7RcURuA3oOLrl8g6DdROVIjVtrJLoq0ZuYydQU_4,329
|
|
25
|
+
src/views/view_entities.py,sha256=-LbDYMyaJr-QMPQNR17TiVd3kzbc6PbGlfn7N7tZsOg,7918
|
|
26
|
+
src/views/view_manager.py,sha256=-mwxDkYOMb23UiqcFNyB1dR0h_e61dsY8_sRf5NQIKI,10834
|
|
27
|
+
archlens-0.2.dist-info/METADATA,sha256=0ZBD6oQNweOw0x28StGsNWbAnNpzj6C-kDAT2h3ElKc,764
|
|
28
|
+
archlens-0.2.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
29
|
+
archlens-0.2.dist-info/entry_points.txt,sha256=eOn74xvh44lrsjdvJ89_1uzx7XB0DeAaMwYnWEjI748,52
|
|
30
|
+
archlens-0.2.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
|
|
31
|
+
archlens-0.2.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
src
|
src/__init__.py
ADDED
src/cli_interface.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import requests
|
|
5
|
+
import jsonschema
|
|
6
|
+
import tempfile
|
|
7
|
+
import shutil
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from src.providers.json.json_render import save_json, save_json_diff
|
|
10
|
+
from src.providers.plantuml.pu_render import save_plant_uml, save_plant_uml_diff
|
|
11
|
+
from src.utils.path_manager_singleton import PathManagerSingleton
|
|
12
|
+
|
|
13
|
+
# from src.utils.functions import verify_config_options
|
|
14
|
+
from src.utils.config_manager_singleton import ConfigManagerSingleton
|
|
15
|
+
|
|
16
|
+
from src.views.view_manager import render_views, render_diff_views
|
|
17
|
+
|
|
18
|
+
from src.core.bt_graph import BTGraph
|
|
19
|
+
|
|
20
|
+
from src.git_integration.fetch_git import fetch_git_repo
|
|
21
|
+
|
|
22
|
+
from astroid.manager import AstroidManager
|
|
23
|
+
|
|
24
|
+
import astroid
|
|
25
|
+
|
|
26
|
+
astroid.MANAGER = None
|
|
27
|
+
|
|
28
|
+
app = typer.Typer(add_completion=True)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.command()
|
|
32
|
+
def render(config_path: str = "archlens.json"):
|
|
33
|
+
config = read_config_file(config_path)
|
|
34
|
+
|
|
35
|
+
mt_path_manager = PathManagerSingleton()
|
|
36
|
+
mt_path_manager.setup(config)
|
|
37
|
+
|
|
38
|
+
am = _create_astroid()
|
|
39
|
+
g = BTGraph(am)
|
|
40
|
+
g.build_graph(config)
|
|
41
|
+
|
|
42
|
+
render_views(g, config, save_plant_uml)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.command()
|
|
46
|
+
def render_json(config_path: str = "archlens.json"):
|
|
47
|
+
config = read_config_file(config_path)
|
|
48
|
+
|
|
49
|
+
mt_path_manager = PathManagerSingleton()
|
|
50
|
+
mt_path_manager.setup(config)
|
|
51
|
+
|
|
52
|
+
am = _create_astroid()
|
|
53
|
+
g = BTGraph(am)
|
|
54
|
+
g.build_graph(config)
|
|
55
|
+
|
|
56
|
+
render_views(g, config, save_json)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _create_astroid():
|
|
60
|
+
am = AstroidManager()
|
|
61
|
+
am.brain["astroid_cache"] = {}
|
|
62
|
+
return am
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@app.command()
|
|
66
|
+
def render_diff(config_path: str = "archlens.json"):
|
|
67
|
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
68
|
+
print("Created temporary directory:", tmp_dir)
|
|
69
|
+
config = read_config_file(config_path)
|
|
70
|
+
|
|
71
|
+
fetch_git_repo(tmp_dir, config["github"]["url"], config["github"]["branch"])
|
|
72
|
+
|
|
73
|
+
shutil.copyfile(config_path, os.path.join(tmp_dir, "archlens.json"))
|
|
74
|
+
|
|
75
|
+
config_git = read_config_file(os.path.join(tmp_dir, "archlens.json"))
|
|
76
|
+
|
|
77
|
+
path_manager = PathManagerSingleton()
|
|
78
|
+
path_manager.setup(config, config_git)
|
|
79
|
+
|
|
80
|
+
local_am = _create_astroid()
|
|
81
|
+
local_graph = BTGraph(local_am)
|
|
82
|
+
local_graph.build_graph(config)
|
|
83
|
+
# verify_config_options(config, g)
|
|
84
|
+
|
|
85
|
+
remote_am = _create_astroid()
|
|
86
|
+
remote_graph = BTGraph(remote_am)
|
|
87
|
+
remote_graph.build_graph(config_git)
|
|
88
|
+
# verify_config_options(config_git, g_git)
|
|
89
|
+
|
|
90
|
+
render_diff_views(local_graph, remote_graph, config, save_plant_uml_diff)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@app.command()
|
|
94
|
+
def render_diff_json(config_path: str = "archlens.json"):
|
|
95
|
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
96
|
+
print("Created temporary directory:", tmp_dir)
|
|
97
|
+
config = read_config_file(config_path)
|
|
98
|
+
|
|
99
|
+
fetch_git_repo(tmp_dir, config["github"]["url"], config["github"]["branch"])
|
|
100
|
+
|
|
101
|
+
shutil.copyfile(config_path, os.path.join(tmp_dir, "archlens.json"))
|
|
102
|
+
|
|
103
|
+
config_git = read_config_file(os.path.join(tmp_dir, "archlens.json"))
|
|
104
|
+
|
|
105
|
+
path_manager = PathManagerSingleton()
|
|
106
|
+
path_manager.setup(config, config_git)
|
|
107
|
+
|
|
108
|
+
local_am = _create_astroid()
|
|
109
|
+
local_graph = BTGraph(local_am)
|
|
110
|
+
local_graph.build_graph(config)
|
|
111
|
+
# verify_config_options(config, g)
|
|
112
|
+
|
|
113
|
+
remote_am = _create_astroid()
|
|
114
|
+
remote_graph = BTGraph(remote_am)
|
|
115
|
+
remote_graph.build_graph(config_git)
|
|
116
|
+
# verify_config_options(config_git, g_git)
|
|
117
|
+
|
|
118
|
+
render_diff_views(local_graph, remote_graph, config, save_json_diff)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@app.command()
|
|
122
|
+
def init(config_path="./archlens.json"):
|
|
123
|
+
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
|
124
|
+
template_path = os.path.join(os.path.dirname(__file__), "config.template.json")
|
|
125
|
+
schema = None
|
|
126
|
+
with open(template_path, "r") as f:
|
|
127
|
+
schema = json.load(f)
|
|
128
|
+
|
|
129
|
+
schema["name"] = os.path.basename(os.getcwd())
|
|
130
|
+
with open(config_path, "w") as outfile:
|
|
131
|
+
json.dump(schema, outfile, indent=4)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@app.command()
|
|
135
|
+
def create_action():
|
|
136
|
+
action_url = "https://raw.githubusercontent.com/archlens/ArchLens/master/.github/workflows/render-diff-on-pr.yml"
|
|
137
|
+
action_path = Path(".github/workflows/render-diff-on-pr.yml")
|
|
138
|
+
typer.secho(f"Creating the action at {action_path}", fg="green")
|
|
139
|
+
action_path.parent.mkdir(parents=True, exist_ok=True)
|
|
140
|
+
action = requests.get(action_url).text
|
|
141
|
+
|
|
142
|
+
with open(action_path, "w") as f:
|
|
143
|
+
f.write(action)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def read_config_file(config_path):
|
|
147
|
+
config = None
|
|
148
|
+
with open(config_path, "r") as f:
|
|
149
|
+
config = json.load(f)
|
|
150
|
+
|
|
151
|
+
config_schema = None
|
|
152
|
+
schema_path = os.path.join(os.path.dirname(__file__), "config.schema.json")
|
|
153
|
+
with open(schema_path) as fp:
|
|
154
|
+
config_schema = json.load(fp)
|
|
155
|
+
|
|
156
|
+
if not os.getenv("MT_DEBUG"):
|
|
157
|
+
jsonschema.validate(instance=config, schema=config_schema)
|
|
158
|
+
|
|
159
|
+
config["_config_path"] = os.path.dirname(os.path.abspath(config_path))
|
|
160
|
+
|
|
161
|
+
config["saveLocation"] = os.path.normpath(
|
|
162
|
+
os.path.join(config["_config_path"], config["saveLocation"])
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
config_manager = ConfigManagerSingleton()
|
|
166
|
+
config_manager.setup(config)
|
|
167
|
+
|
|
168
|
+
return config
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def main():
|
|
172
|
+
app()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
if __name__ == "__main__":
|
|
176
|
+
main()
|
src/config.schema.json
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"required": ["name", "rootFolder", "views"],
|
|
5
|
+
"properties": {
|
|
6
|
+
"name": {
|
|
7
|
+
"type": "string",
|
|
8
|
+
"description": "The name of the project",
|
|
9
|
+
"minLength": 1
|
|
10
|
+
},
|
|
11
|
+
"rootFolder": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"description": "Point to the root package of the project",
|
|
14
|
+
"minLength": 1
|
|
15
|
+
},
|
|
16
|
+
"github": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"description": "Information about the GitHub repository for the project",
|
|
19
|
+
"required": ["url", "branch"],
|
|
20
|
+
"properties": {
|
|
21
|
+
"url": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "The URL of the GitHub repository for the project"
|
|
24
|
+
},
|
|
25
|
+
"branch": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"description": "The name of the branch to use for the GitHub repository"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"SaveLocation": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "The folder to which the diagrams should be saved in. If the folder does not exists it will be created",
|
|
34
|
+
"default": "./diagrams/"
|
|
35
|
+
},
|
|
36
|
+
"showDependencyCount": {
|
|
37
|
+
"type": "boolean",
|
|
38
|
+
"description": "If to showcase the number of dependencies between packages"
|
|
39
|
+
},
|
|
40
|
+
"packageColor": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"enum": ["#GoldenRod", "#Azure", ""]
|
|
43
|
+
},
|
|
44
|
+
"views": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"description": "Defines the views available",
|
|
47
|
+
"patternProperties": {
|
|
48
|
+
".*": {
|
|
49
|
+
"description": "The name of the view",
|
|
50
|
+
"type": "object",
|
|
51
|
+
"properties": {
|
|
52
|
+
"packages": {
|
|
53
|
+
"description": "The packages to include in the view. Leave empty to include all packages",
|
|
54
|
+
"type": "array",
|
|
55
|
+
"items": {
|
|
56
|
+
"oneOf": [
|
|
57
|
+
{
|
|
58
|
+
"type": "string",
|
|
59
|
+
"pattern": "^[A-Za-z0-9._-]+$"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"type": "object",
|
|
63
|
+
"properties": {
|
|
64
|
+
"path": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"pattern": "^[A-Za-z0-9._\\-]+|[*]$"
|
|
67
|
+
},
|
|
68
|
+
"depth": {
|
|
69
|
+
"type": "integer"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"required": ["path", "depth"]
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"ignorePackages": {
|
|
78
|
+
"type": "array",
|
|
79
|
+
"items": {
|
|
80
|
+
"type": "string",
|
|
81
|
+
"pattern": "^[A-Za-z0-9._\\-\\/*]+$"
|
|
82
|
+
},
|
|
83
|
+
"description": "Defines the packages to ignore"
|
|
84
|
+
},
|
|
85
|
+
"usePackagePathAsLabel": {
|
|
86
|
+
"type": "boolean",
|
|
87
|
+
"description": "If true, paths of each package will be displayed, if false only package name will be displayed"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
src/config.template.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/archlens/ArchLens/master/src/config.schema.json",
|
|
3
|
+
"name": "",
|
|
4
|
+
"rootFolder": "",
|
|
5
|
+
"github": {
|
|
6
|
+
"url": "",
|
|
7
|
+
"branch": "main"
|
|
8
|
+
},
|
|
9
|
+
"saveLocation": "./diagrams/",
|
|
10
|
+
"views": {
|
|
11
|
+
"completeView": {
|
|
12
|
+
"packages": [],
|
|
13
|
+
"ignorePackages": []
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
src/core/__init__.py
ADDED
|
File without changes
|
src/core/bt_file.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import astroid
|
|
2
|
+
from astroid.manager import AstroidManager
|
|
3
|
+
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from src.core.bt_module import BTModule
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BTFile:
|
|
11
|
+
label: str = ""
|
|
12
|
+
edge_to: list["BTFile"] = None
|
|
13
|
+
ast = None
|
|
14
|
+
module: "BTModule" = None
|
|
15
|
+
am: AstroidManager
|
|
16
|
+
|
|
17
|
+
def __init__(self, label: str, module, am: AstroidManager, code_path: str = None):
|
|
18
|
+
self.label = label
|
|
19
|
+
self.ast = None
|
|
20
|
+
self.am = am
|
|
21
|
+
|
|
22
|
+
if code_path is not None:
|
|
23
|
+
self.ast: astroid.Module = self.am.ast_from_module_name(code_path)
|
|
24
|
+
|
|
25
|
+
self.edge_to = []
|
|
26
|
+
self.module = module
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def file(self):
|
|
30
|
+
if self.ast:
|
|
31
|
+
return self.ast.file
|
|
32
|
+
return ""
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def uid(self):
|
|
36
|
+
if self.ast:
|
|
37
|
+
return self.ast.file
|
|
38
|
+
else:
|
|
39
|
+
return self.label
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def module_path(self) -> str:
|
|
43
|
+
if not self.ast:
|
|
44
|
+
return None
|
|
45
|
+
return "/".join(self.file.split("/")[:-1])
|
|
46
|
+
|
|
47
|
+
def __rshift__(self, other):
|
|
48
|
+
if isinstance(other, list):
|
|
49
|
+
existing_edges = set(
|
|
50
|
+
[edge.file for edge in self.edge_to if edge.file != ""]
|
|
51
|
+
)
|
|
52
|
+
new_node_list = filter(lambda e: e.file not in existing_edges, other)
|
|
53
|
+
self.edge_to.extend([node for node in new_node_list])
|
|
54
|
+
else:
|
|
55
|
+
edges = set([edge.file for edge in self.edge_to])
|
|
56
|
+
if other.file in edges:
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
self.edge_to.append(other)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_imported_modules(
|
|
63
|
+
ast: astroid.Module, root_location: str, am: AstroidManager
|
|
64
|
+
) -> list:
|
|
65
|
+
imported_modules = []
|
|
66
|
+
for sub_node in ast.body:
|
|
67
|
+
try:
|
|
68
|
+
if isinstance(sub_node, astroid.node_classes.ImportFrom):
|
|
69
|
+
sub_node: astroid.node_classes.ImportFrom = sub_node
|
|
70
|
+
|
|
71
|
+
module_node = am.ast_from_module_name(
|
|
72
|
+
sub_node.modname,
|
|
73
|
+
context_file=root_location,
|
|
74
|
+
)
|
|
75
|
+
imported_modules.append(module_node)
|
|
76
|
+
|
|
77
|
+
elif isinstance(sub_node, astroid.node_classes.Import):
|
|
78
|
+
for name, _ in sub_node.names:
|
|
79
|
+
try:
|
|
80
|
+
module_node = am.ast_from_module_name(
|
|
81
|
+
name,
|
|
82
|
+
context_file=root_location,
|
|
83
|
+
)
|
|
84
|
+
imported_modules.append(module_node)
|
|
85
|
+
except Exception:
|
|
86
|
+
continue
|
|
87
|
+
elif hasattr(sub_node, "body"):
|
|
88
|
+
imported_modules.extend(
|
|
89
|
+
get_imported_modules(sub_node, root_location, am)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
except astroid.AstroidImportError:
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
return imported_modules
|
src/core/bt_graph.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import astroid
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from src.core.bt_file import BTFile, get_imported_modules
|
|
8
|
+
from src.core.bt_module import BTModule
|
|
9
|
+
from astroid.manager import AstroidManager
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BTGraph:
|
|
13
|
+
DEFAULT_SETTINGS = {"diagram_name": "", "project": None}
|
|
14
|
+
root_module_location: str = None
|
|
15
|
+
target_project_base_location: str = None
|
|
16
|
+
root_module = None
|
|
17
|
+
base_module = None
|
|
18
|
+
am: AstroidManager = None
|
|
19
|
+
|
|
20
|
+
def __init__(self, am: AstroidManager) -> None:
|
|
21
|
+
self.am = am
|
|
22
|
+
|
|
23
|
+
def build_graph(self, config: dict):
|
|
24
|
+
config_path = config.get("_config_path")
|
|
25
|
+
self.root_module_location = os.path.join(config_path, config.get("rootFolder"))
|
|
26
|
+
self.target_project_base_location = config_path
|
|
27
|
+
|
|
28
|
+
sys.path.insert(0, config_path)
|
|
29
|
+
sys.path.insert(1, self.root_module_location)
|
|
30
|
+
|
|
31
|
+
bt_module_list: list[BTModule] = []
|
|
32
|
+
|
|
33
|
+
# Read all the python files within the project
|
|
34
|
+
file_list = self._get_files_recursive(self.root_module_location)
|
|
35
|
+
|
|
36
|
+
# Create modules and add them to module list (No relations between them yet)
|
|
37
|
+
for file in file_list:
|
|
38
|
+
try:
|
|
39
|
+
if not file.endswith("__init__.py"):
|
|
40
|
+
continue
|
|
41
|
+
bt_module = BTModule(file, self.am)
|
|
42
|
+
bt_module.add_files()
|
|
43
|
+
bt_module_list.append(bt_module)
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print(e)
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
# Add relations between the modules (parent and child nodes)
|
|
49
|
+
for module in bt_module_list:
|
|
50
|
+
for parent_module in bt_module_list:
|
|
51
|
+
if module == parent_module:
|
|
52
|
+
continue
|
|
53
|
+
if parent_module.path == os.path.dirname(module.path):
|
|
54
|
+
parent_module.child_module.append(module)
|
|
55
|
+
module.parent_module = parent_module
|
|
56
|
+
|
|
57
|
+
# Find the root node
|
|
58
|
+
self.root_module = next(
|
|
59
|
+
filter(lambda e: e.parent_module is None, bt_module_list)
|
|
60
|
+
)
|
|
61
|
+
self.base_module = self.root_module
|
|
62
|
+
|
|
63
|
+
# Add dependencies between all the files
|
|
64
|
+
btf_map = self.get_all_bt_files_map()
|
|
65
|
+
|
|
66
|
+
for bt_file in btf_map.values():
|
|
67
|
+
imported_modules = get_imported_modules(
|
|
68
|
+
bt_file.ast, self.target_project_base_location, self.am
|
|
69
|
+
)
|
|
70
|
+
bt_file >> [
|
|
71
|
+
btf_map[module.file]
|
|
72
|
+
for module in imported_modules
|
|
73
|
+
if module.file in btf_map
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
sys.path = sys.path[2:]
|
|
77
|
+
astroid.manager.AstroidManager().clear_cache()
|
|
78
|
+
self.am.clear_cache()
|
|
79
|
+
|
|
80
|
+
def get_bt_file(self, path: str) -> BTFile:
|
|
81
|
+
file_path = self.am.ast_from_module_name(path).file
|
|
82
|
+
bt_file = self.get_all_bt_files_map()[file_path]
|
|
83
|
+
return bt_file
|
|
84
|
+
|
|
85
|
+
def get_bt_module(self, path: str) -> BTModule:
|
|
86
|
+
path_list = path.split(".")[1:]
|
|
87
|
+
current_module = self.root_module
|
|
88
|
+
while path_list:
|
|
89
|
+
current_module = next(
|
|
90
|
+
filter(
|
|
91
|
+
lambda e: e.name == path_list[0],
|
|
92
|
+
current_module.child_module,
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
path_list.pop(0)
|
|
96
|
+
return current_module
|
|
97
|
+
|
|
98
|
+
def change_scope(self, path: str):
|
|
99
|
+
self.root_module = self.get_bt_module(path)
|
|
100
|
+
|
|
101
|
+
def get_all_bt_files_map(self) -> dict[str, BTFile]:
|
|
102
|
+
return {btf.file: btf for btf in self.root_module.get_files_recursive()}
|
|
103
|
+
|
|
104
|
+
def get_all_bt_modules_map(self) -> dict[str, BTModule]:
|
|
105
|
+
return {btm.path: btm for btm in self.root_module.get_submodules_recursive()}
|
|
106
|
+
|
|
107
|
+
def _get_files_recursive(self, path: str) -> list[str]:
|
|
108
|
+
file_list = []
|
|
109
|
+
t = list(os.walk(path))
|
|
110
|
+
for root, _, files in t:
|
|
111
|
+
for file in files:
|
|
112
|
+
file_list.append(os.path.join(root, file))
|
|
113
|
+
|
|
114
|
+
return file_list
|
|
115
|
+
|