lupin-danquin 0.0.1.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.
- lupin-danquin-0.0.1.dev0/LICENCE +19 -0
- lupin-danquin-0.0.1.dev0/PKG-INFO +15 -0
- lupin-danquin-0.0.1.dev0/README.md +38 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin/__init__.py +1 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin/check_coding_rules.py +31 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin/core/__init__.py +0 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin/core/application.py +126 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin/core/external_resources.py +18 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin/core/global_variable.py +52 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin/core/line_ended.py +2 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin/core/local_variable.py +35 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin/core/parameter.py +42 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin/core/program.py +135 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin/core/rules.py +19 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin/core/tools/__init__.py +0 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin/core/tools/utils.py +37 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin/core/val3_doc_generator/__init__.py +0 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin/core/val3_doc_generator/val3_doc_generator.py +45 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin/documentation_extraction.py +54 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin.egg-info/PKG-INFO +15 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin.egg-info/SOURCES.txt +26 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin.egg-info/dependency_links.txt +1 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin.egg-info/not-zip-safe +1 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin.egg-info/requires.txt +7 -0
- lupin-danquin-0.0.1.dev0/lupin_danquin.egg-info/top_level.txt +1 -0
- lupin-danquin-0.0.1.dev0/setup.cfg +37 -0
- lupin-danquin-0.0.1.dev0/setup.py +10 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2018 The Python Packaging Authority
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: lupin-danquin
|
|
3
|
+
Version: 0.0.1.dev0
|
|
4
|
+
Summary: Testing - Documentation VAL3 from Code
|
|
5
|
+
License: MIT License
|
|
6
|
+
Keywords: documentation,testing
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Information Technology
|
|
9
|
+
Classifier: Topic :: Software Development :: Testing
|
|
10
|
+
Classifier: Topic :: Documentation
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Provides-Extra: test
|
|
15
|
+
License-File: LICENCE
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Documentation extraction
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
"documentation_extraction.py" Python script allow to create SDD documentation from source code.
|
|
6
|
+
|
|
7
|
+
The goal is to replace the hand written wiki page accessible here: https://gitlab.com/lupindental/arsene/robotics/production/lupin.robot.staubli/-/wikis/Software-Detailed-Design-Robot-Application-unit
|
|
8
|
+
|
|
9
|
+
## Run it
|
|
10
|
+
|
|
11
|
+
1. Go to current directory.
|
|
12
|
+
2. Execute
|
|
13
|
+
> python documentation_extraction.py
|
|
14
|
+
3. Result is available in "./val3_documentation.md"
|
|
15
|
+
|
|
16
|
+
## Add documentation
|
|
17
|
+
|
|
18
|
+
### For a program (pgx file)
|
|
19
|
+
|
|
20
|
+
Edit your program and add one or more lines of comments the line after "begin" keyword.
|
|
21
|
+
|
|
22
|
+
### For an application
|
|
23
|
+
|
|
24
|
+
Create a text file named "README.md" at the same level as "pjx" file. And add one or more lines of description.
|
|
25
|
+
|
|
26
|
+
## Improvements oportunities
|
|
27
|
+
|
|
28
|
+
- Improve table for sockets
|
|
29
|
+
- Allow to end docstring with 10 * "/"
|
|
30
|
+
|
|
31
|
+
## Other ideas
|
|
32
|
+
|
|
33
|
+
To create a sanitizer:
|
|
34
|
+
|
|
35
|
+
- detect unused variables (private, public?): already done for local variables and parameters (not global variables)
|
|
36
|
+
- detect wrongly named variables (l_, x_, s/b/n/st, t, Out): DONE
|
|
37
|
+
- validate plantuml diagram from pjx files
|
|
38
|
+
- code coverage ? find if all return values are tested
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.1.dev0"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from core.application import Application
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
if __name__ == "__main__":
|
|
7
|
+
base_program_path = (
|
|
8
|
+
os.path.abspath(os.path.dirname(__file__) + r"/../../danquin/val3/usrapp/")
|
|
9
|
+
+ "/"
|
|
10
|
+
)
|
|
11
|
+
application_names = [
|
|
12
|
+
"TaskManager",
|
|
13
|
+
]
|
|
14
|
+
result = []
|
|
15
|
+
for application_name in application_names:
|
|
16
|
+
program_path = base_program_path + application_name
|
|
17
|
+
app = Application(program_path, application_name)
|
|
18
|
+
result += app.check_coding_rules()
|
|
19
|
+
|
|
20
|
+
if len(result) > 0:
|
|
21
|
+
print(f"{len(result)} errors found while checking coding rules:")
|
|
22
|
+
print()
|
|
23
|
+
print(
|
|
24
|
+
"""
|
|
25
|
+
""".join(
|
|
26
|
+
result
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
else:
|
|
30
|
+
print("No error found while checking coding rules.")
|
|
31
|
+
exit(len(result))
|
|
File without changes
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
from xml.dom import minidom
|
|
3
|
+
|
|
4
|
+
from .global_variable import GlobalVariable
|
|
5
|
+
from .program import Program
|
|
6
|
+
from .line_ended import LINE_ENDED
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_program_list(
|
|
10
|
+
program_path,
|
|
11
|
+
application_name,
|
|
12
|
+
exclude=["UT.pgx", "start.pgx", "stop.pgx", "test.pgx"],
|
|
13
|
+
) -> List[Program]:
|
|
14
|
+
file_path = program_path + "/" + application_name + ".pjx"
|
|
15
|
+
doc = minidom.parse(file_path)
|
|
16
|
+
progs = doc.getElementsByTagName("Program")
|
|
17
|
+
return_progs = []
|
|
18
|
+
for prog in progs:
|
|
19
|
+
name = prog.getAttribute("file")
|
|
20
|
+
should_exclude = False
|
|
21
|
+
for e in exclude:
|
|
22
|
+
if e in name:
|
|
23
|
+
should_exclude = True
|
|
24
|
+
if not should_exclude:
|
|
25
|
+
return_progs.append(Program(program_path, name))
|
|
26
|
+
return return_progs
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_dependencies(program_path, application_name, exclude=["Logger"]) -> List[str]:
|
|
30
|
+
file_path = program_path + "/" + application_name + ".pjx"
|
|
31
|
+
doc = minidom.parse(file_path)
|
|
32
|
+
deps = doc.getElementsByTagName("Library")
|
|
33
|
+
return_deps = []
|
|
34
|
+
for dep in deps:
|
|
35
|
+
name = dep.getAttribute("alias")
|
|
36
|
+
should_exclude = False
|
|
37
|
+
for e in exclude:
|
|
38
|
+
if e in name:
|
|
39
|
+
should_exclude = True
|
|
40
|
+
if not should_exclude:
|
|
41
|
+
return_deps.append(name)
|
|
42
|
+
return return_deps
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_type_dependencies(
|
|
46
|
+
program_path, application_name, exclude=["Logger"]
|
|
47
|
+
) -> List[str]:
|
|
48
|
+
file_path = program_path + "/" + application_name + ".pjx"
|
|
49
|
+
doc = minidom.parse(file_path)
|
|
50
|
+
deps = doc.getElementsByTagName("Type")
|
|
51
|
+
return_deps = []
|
|
52
|
+
for dep in deps:
|
|
53
|
+
name = dep.getAttribute("name")
|
|
54
|
+
should_exclude = False
|
|
55
|
+
for e in exclude:
|
|
56
|
+
if e in name:
|
|
57
|
+
should_exclude = True
|
|
58
|
+
if not should_exclude:
|
|
59
|
+
return_deps.append(name)
|
|
60
|
+
return return_deps
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_global_variables(
|
|
64
|
+
program_path, application_name, exclude=["UT"]
|
|
65
|
+
) -> List[GlobalVariable]:
|
|
66
|
+
file_path = program_path + "/" + application_name + ".dtx"
|
|
67
|
+
doc = minidom.parse(file_path)
|
|
68
|
+
vars = doc.getElementsByTagName("Data")
|
|
69
|
+
return_vars = []
|
|
70
|
+
for var in vars:
|
|
71
|
+
name = var.getAttribute("name")
|
|
72
|
+
should_exclude = False
|
|
73
|
+
for e in exclude:
|
|
74
|
+
if e in name:
|
|
75
|
+
should_exclude = True
|
|
76
|
+
if not should_exclude:
|
|
77
|
+
xsi = var.getAttribute("xsi:type")
|
|
78
|
+
access = var.getAttribute("access")
|
|
79
|
+
type_ = var.getAttribute("type")
|
|
80
|
+
size = var.getAttribute("size")
|
|
81
|
+
return_vars.append(
|
|
82
|
+
GlobalVariable(name, access, type_, xsi, size, file_path)
|
|
83
|
+
)
|
|
84
|
+
return return_vars
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_application_description(program_path) -> str:
|
|
88
|
+
file_path = program_path + "/README.md"
|
|
89
|
+
lines = []
|
|
90
|
+
try:
|
|
91
|
+
with open(file_path, "r") as f:
|
|
92
|
+
lines = f.readlines()
|
|
93
|
+
except FileNotFoundError:
|
|
94
|
+
print("Missing documentation file " + file_path)
|
|
95
|
+
return LINE_ENDED.join(lines)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Application:
|
|
99
|
+
def __init__(self, program_path: str, application_name: str):
|
|
100
|
+
self.application_name = application_name
|
|
101
|
+
self.dependencies = get_dependencies(program_path, application_name)
|
|
102
|
+
self.type_dependencies = get_type_dependencies(program_path, application_name)
|
|
103
|
+
self.program_list = get_program_list(program_path, application_name)
|
|
104
|
+
self.global_variables = get_global_variables(program_path, application_name)
|
|
105
|
+
self.application_description = get_application_description(program_path)
|
|
106
|
+
|
|
107
|
+
def check_coding_rules(self) -> List[str]:
|
|
108
|
+
result = []
|
|
109
|
+
for p in self.program_list:
|
|
110
|
+
result += p.check_coding_rules()
|
|
111
|
+
for gv in self.global_variables:
|
|
112
|
+
result += gv.check_coding_rules()
|
|
113
|
+
return result
|
|
114
|
+
|
|
115
|
+
def get_all_information_from_app(self) -> Dict[str, Any]:
|
|
116
|
+
"""Return all information from the application in a dict for the documentation generation"""
|
|
117
|
+
all_information = {
|
|
118
|
+
"name": self.application_name,
|
|
119
|
+
"description": self.application_description,
|
|
120
|
+
"dependencies": self.dependencies,
|
|
121
|
+
"type_dependencies": self.type_dependencies,
|
|
122
|
+
"global_variables": self.global_variables,
|
|
123
|
+
"private_program_list": [p for p in self.program_list if not p.public],
|
|
124
|
+
"public_program_list": [p for p in self.program_list if p.public],
|
|
125
|
+
}
|
|
126
|
+
return all_information
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import codecs
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def read_begining_of_file() -> str:
|
|
6
|
+
result = ""
|
|
7
|
+
path = os.path.dirname(__file__) + os.sep + ".." + os.sep + "assets" + os.sep
|
|
8
|
+
with codecs.open(path + "BeginingOfFile.md", encoding="utf-8") as f:
|
|
9
|
+
result = f.read()
|
|
10
|
+
return result
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def read_end_of_file() -> str:
|
|
14
|
+
result = ""
|
|
15
|
+
path = os.path.dirname(__file__) + os.sep + ".." + os.sep + "assets" + os.sep
|
|
16
|
+
with codecs.open(path + "EndOfFile.md", encoding="utf-8") as f:
|
|
17
|
+
result = f.read()
|
|
18
|
+
return result
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from .rules import OTHER_TYPES, TYPE_RULES
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GlobalVariable:
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
name: str,
|
|
10
|
+
access: str,
|
|
11
|
+
type_data: str,
|
|
12
|
+
xsi: str,
|
|
13
|
+
size: str,
|
|
14
|
+
context: str = "",
|
|
15
|
+
):
|
|
16
|
+
self.name = name
|
|
17
|
+
self.type_data = type_data
|
|
18
|
+
self.xsi = xsi
|
|
19
|
+
self.size = size
|
|
20
|
+
self.access = access
|
|
21
|
+
self.context = context
|
|
22
|
+
|
|
23
|
+
def check_coding_rules(self) -> List:
|
|
24
|
+
result = []
|
|
25
|
+
if self.access == "private" and not self.name.startswith("_"):
|
|
26
|
+
result += [
|
|
27
|
+
f"Global private variable {self.name} should begin by '_'. {self.context}"
|
|
28
|
+
]
|
|
29
|
+
if self.access != "private" and self.name.startswith("_"):
|
|
30
|
+
result += [
|
|
31
|
+
f"Global public variable {self.name} should not begin by '_'. {self.context}"
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
name = self.name[1:] if self.name.startswith("_") else self.name
|
|
35
|
+
if self.type_data in TYPE_RULES.keys():
|
|
36
|
+
rule = TYPE_RULES.get(self.type_data)
|
|
37
|
+
if not name.startswith(rule):
|
|
38
|
+
result += [
|
|
39
|
+
(
|
|
40
|
+
f"Global variable {self.name} of type {self.type_data} should begin by "
|
|
41
|
+
f"'{'_' if self.access == 'private' else ''}{rule}'. {self.context}"
|
|
42
|
+
)
|
|
43
|
+
]
|
|
44
|
+
else:
|
|
45
|
+
if not name.startswith(OTHER_TYPES):
|
|
46
|
+
result += [
|
|
47
|
+
(
|
|
48
|
+
f"Global variable {self.name} of type CUSTOM should begin by "
|
|
49
|
+
f"'{'_' if self.access == 'private' else ''}{OTHER_TYPES}'. {self.context}"
|
|
50
|
+
)
|
|
51
|
+
]
|
|
52
|
+
return result
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from .rules import OTHER_TYPES, TYPE_RULES
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LocalVariable:
|
|
7
|
+
def __init__(
|
|
8
|
+
self, name: str, type_data: str, xsi: str, size: str, context: str = ""
|
|
9
|
+
):
|
|
10
|
+
self.name = name
|
|
11
|
+
self.type_data = type_data
|
|
12
|
+
self.xsi = xsi
|
|
13
|
+
self.size = size
|
|
14
|
+
self.context = context
|
|
15
|
+
|
|
16
|
+
def check_coding_rules(self) -> List:
|
|
17
|
+
result = []
|
|
18
|
+
if not self.name.startswith("l_"):
|
|
19
|
+
result += [
|
|
20
|
+
f"Local variable {self.name} should begin by 'l_'. {self.context}"
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
if self.type_data in TYPE_RULES.keys():
|
|
24
|
+
rule = TYPE_RULES.get(self.type_data)
|
|
25
|
+
if not self.name.startswith("l_" + rule):
|
|
26
|
+
result += [
|
|
27
|
+
f"Local variable {self.name} of type {self.type_data} should begin by 'l_{rule}'. {self.context}"
|
|
28
|
+
]
|
|
29
|
+
else:
|
|
30
|
+
if not self.name.startswith("l_" + OTHER_TYPES):
|
|
31
|
+
result += [
|
|
32
|
+
f"Local variable {self.name} of type CUSTOM should begin by 'l_{OTHER_TYPES}'. {self.context}"
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
return result
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from .rules import OTHER_TYPES, TYPE_RULES
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Parameter:
|
|
7
|
+
def __init__(
|
|
8
|
+
self, name: str, type_data: str, xsi: str, use: str, context: str = ""
|
|
9
|
+
):
|
|
10
|
+
self.name = name
|
|
11
|
+
self.type_data = type_data
|
|
12
|
+
self.xsi = xsi
|
|
13
|
+
self.use = use
|
|
14
|
+
if len(use) == 0:
|
|
15
|
+
self.use = "value"
|
|
16
|
+
self.context = context
|
|
17
|
+
|
|
18
|
+
def check_coding_rules(self) -> List:
|
|
19
|
+
result = []
|
|
20
|
+
if not self.name.startswith("x_"):
|
|
21
|
+
result += [f"Parameter {self.name} should begin by 'x_'. {self.context}"]
|
|
22
|
+
if (
|
|
23
|
+
self.use != "value"
|
|
24
|
+
and self.xsi == "element"
|
|
25
|
+
and not self.name.endswith("Out")
|
|
26
|
+
):
|
|
27
|
+
result += [
|
|
28
|
+
f"Output parameter {self.name} should ends by 'Out'. {self.context}"
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
if self.type_data in TYPE_RULES.keys():
|
|
32
|
+
rule = TYPE_RULES.get(self.type_data)
|
|
33
|
+
if not self.name.startswith("x_" + rule):
|
|
34
|
+
result += [
|
|
35
|
+
f"Parameter {self.name} of type {self.type_data} should begin by 'x_{rule}'. {self.context}"
|
|
36
|
+
]
|
|
37
|
+
else:
|
|
38
|
+
if not self.name.startswith("x_" + OTHER_TYPES):
|
|
39
|
+
result += [
|
|
40
|
+
f"Parameter {self.name} of type CUSTOM should begin by 'x_{OTHER_TYPES}'. {self.context}"
|
|
41
|
+
]
|
|
42
|
+
return result
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from xml.dom import minidom
|
|
3
|
+
|
|
4
|
+
from .parameter import Parameter
|
|
5
|
+
from .local_variable import LocalVariable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_parameters(program_path: str, program_file: str) -> List[Parameter]:
|
|
9
|
+
file_path = program_path + "/" + program_file
|
|
10
|
+
doc = minidom.parse(file_path)
|
|
11
|
+
params = doc.getElementsByTagName("Parameter")
|
|
12
|
+
returned_params = []
|
|
13
|
+
for param in params:
|
|
14
|
+
returned_params.append(
|
|
15
|
+
Parameter(
|
|
16
|
+
param.getAttribute("name"),
|
|
17
|
+
param.getAttribute("type"),
|
|
18
|
+
param.getAttribute("xsi:type"),
|
|
19
|
+
param.getAttribute("use"),
|
|
20
|
+
file_path,
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
return returned_params
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_program_description(program_path: str, program_file: str) -> str:
|
|
27
|
+
file_path = program_path + "/" + program_file
|
|
28
|
+
input_txt = []
|
|
29
|
+
with open(file_path, "r") as f_input:
|
|
30
|
+
input_txt = f_input.readlines()
|
|
31
|
+
comment_txt = r"//"
|
|
32
|
+
doc = ""
|
|
33
|
+
start_doc = False
|
|
34
|
+
for line in input_txt:
|
|
35
|
+
if start_doc:
|
|
36
|
+
if comment_txt in line:
|
|
37
|
+
doc += line.split(comment_txt)[-1]
|
|
38
|
+
else:
|
|
39
|
+
break
|
|
40
|
+
# first code line
|
|
41
|
+
if "<Code><![CDATA[begin" in line:
|
|
42
|
+
start_doc = True
|
|
43
|
+
# last code line
|
|
44
|
+
if "end]]></Code>" in line:
|
|
45
|
+
break
|
|
46
|
+
if len(doc) == 0:
|
|
47
|
+
print("Missing description for program " + file_path)
|
|
48
|
+
return doc
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_code(program_path: str, program_file: str) -> str:
|
|
52
|
+
file_path = program_path + "/" + program_file
|
|
53
|
+
input_txt = ""
|
|
54
|
+
with open(file_path, "r") as f_input:
|
|
55
|
+
input_txt = f_input.read()
|
|
56
|
+
return input_txt.split("<Code><![CDATA[begin")[-1].split("end]]></Code>")[0]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def is_public(program_path: str, program_file: str) -> bool:
|
|
60
|
+
file_path = program_path + "/" + program_file
|
|
61
|
+
doc = minidom.parse(file_path)
|
|
62
|
+
prog = doc.getElementsByTagName("Program")
|
|
63
|
+
for p in prog:
|
|
64
|
+
return p.getAttribute("access") == "public"
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_local_variables(program_path: str, program_file: str) -> List[LocalVariable]:
|
|
69
|
+
file_path = program_path + "/" + program_file
|
|
70
|
+
doc = minidom.parse(file_path)
|
|
71
|
+
vars = doc.getElementsByTagName("Local")
|
|
72
|
+
returned_vars = []
|
|
73
|
+
for var in vars:
|
|
74
|
+
returned_vars.append(
|
|
75
|
+
LocalVariable(
|
|
76
|
+
var.getAttribute("name"),
|
|
77
|
+
var.getAttribute("type"),
|
|
78
|
+
var.getAttribute("xsi:type"),
|
|
79
|
+
var.getAttribute("size"),
|
|
80
|
+
file_path,
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
return returned_vars
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_program_signature(program_name: str, parameters: List[Parameter]) -> str:
|
|
87
|
+
return (
|
|
88
|
+
program_name.removesuffix(".pgx")
|
|
89
|
+
+ "("
|
|
90
|
+
+ ", ".join(
|
|
91
|
+
[
|
|
92
|
+
param.type_data
|
|
93
|
+
+ ("& " if (param.use == "reference") else " ")
|
|
94
|
+
+ param.name
|
|
95
|
+
for param in parameters
|
|
96
|
+
]
|
|
97
|
+
)
|
|
98
|
+
+ ")"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class Program:
|
|
103
|
+
def __init__(self, program_path: str, program_name: str):
|
|
104
|
+
self.program_path = program_path
|
|
105
|
+
self.program_name = program_name
|
|
106
|
+
self.param_list = get_parameters(program_path, program_name)
|
|
107
|
+
self.variable_list = get_local_variables(program_path, program_name)
|
|
108
|
+
self.description = get_program_description(program_path, program_name)
|
|
109
|
+
self.signature = get_program_signature(program_name, self.param_list)
|
|
110
|
+
self.public = is_public(program_path, program_name)
|
|
111
|
+
self.code = get_code(program_path, program_name)
|
|
112
|
+
self.check_has_unused_parameter()
|
|
113
|
+
self.check_has_unused_variable()
|
|
114
|
+
|
|
115
|
+
def check_coding_rules(self) -> List[str]:
|
|
116
|
+
return self.check_has_unused_parameter() + self.check_has_unused_variable()
|
|
117
|
+
|
|
118
|
+
def check_has_unused_parameter(self) -> List[str]:
|
|
119
|
+
result = []
|
|
120
|
+
for param in self.param_list:
|
|
121
|
+
if param.name not in self.code:
|
|
122
|
+
result += [
|
|
123
|
+
f"Unused parameter {param.name} in program {self.program_path}/{self.program_name}"
|
|
124
|
+
]
|
|
125
|
+
result += param.check_coding_rules()
|
|
126
|
+
return result
|
|
127
|
+
|
|
128
|
+
def check_has_unused_variable(self) -> List[str]:
|
|
129
|
+
result = []
|
|
130
|
+
for var in self.variable_list:
|
|
131
|
+
if var.name not in self.code:
|
|
132
|
+
result += [
|
|
133
|
+
f"Unused local variable {var.name} in program {self.program_path}/{self.program_name}"
|
|
134
|
+
]
|
|
135
|
+
return result
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
TYPE_RULES = {
|
|
2
|
+
"aio": "aio",
|
|
3
|
+
"bool": "b",
|
|
4
|
+
"configRs": "c",
|
|
5
|
+
"configRx": "c",
|
|
6
|
+
"dio": "dio",
|
|
7
|
+
"frame": "f",
|
|
8
|
+
"jointRs": "j",
|
|
9
|
+
"jointRx": "j",
|
|
10
|
+
"mdesc": "m",
|
|
11
|
+
"num": "n",
|
|
12
|
+
"pointRs": "p",
|
|
13
|
+
"pointRx": "p",
|
|
14
|
+
"sio": "sio",
|
|
15
|
+
"string": "s",
|
|
16
|
+
"tool": "t",
|
|
17
|
+
"trsf": "trsf",
|
|
18
|
+
}
|
|
19
|
+
OTHER_TYPES = "st"
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def configure_logging():
|
|
7
|
+
logging.basicConfig(
|
|
8
|
+
format="%(asctime)s %(levelname)s: %(message)s", level=logging.INFO
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def die(msg: str) -> None:
|
|
13
|
+
logging.error(msg)
|
|
14
|
+
sys.exit(1)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_version():
|
|
18
|
+
"""get version from setup.cfg file and
|
|
19
|
+
update __version__ in lupin_danquin.__init__.py
|
|
20
|
+
"""
|
|
21
|
+
with open("setup.cfg", "r", encoding="utf-8") as f:
|
|
22
|
+
setup_cfg = f.read()
|
|
23
|
+
print(setup_cfg)
|
|
24
|
+
_version = re.search(
|
|
25
|
+
r"(^version = )(\d{1,2}\.\d{1,2}\.\d{1,2})(\.[a-z]{1,})?(\d{1,2})?",
|
|
26
|
+
setup_cfg,
|
|
27
|
+
re.MULTILINE,
|
|
28
|
+
)
|
|
29
|
+
version = ""
|
|
30
|
+
for group in _version.group(2, 3, 4):
|
|
31
|
+
if group is not None:
|
|
32
|
+
version = version + str(group)
|
|
33
|
+
content = f'__version__ = "{version}"\n'
|
|
34
|
+
|
|
35
|
+
with open("lupin_danquin/__init__.py", "w", encoding="utf-8") as outfile:
|
|
36
|
+
outfile.write(content)
|
|
37
|
+
return version
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import codecs
|
|
2
|
+
|
|
3
|
+
from jinja2 import (
|
|
4
|
+
Environment,
|
|
5
|
+
FileSystemLoader,
|
|
6
|
+
select_autoescape,
|
|
7
|
+
Template,
|
|
8
|
+
TemplateNotFound,
|
|
9
|
+
TemplateError,
|
|
10
|
+
TemplateRuntimeError,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from core.tools.utils import die
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Val3Documentation:
|
|
17
|
+
def _get_local_template(self) -> Template:
|
|
18
|
+
try:
|
|
19
|
+
# Create jinja2 environment
|
|
20
|
+
env = Environment(
|
|
21
|
+
loader=FileSystemLoader("lupin_danquin/templates"),
|
|
22
|
+
autoescape=select_autoescape(),
|
|
23
|
+
trim_blocks=True,
|
|
24
|
+
lstrip_blocks=True,
|
|
25
|
+
)
|
|
26
|
+
# Charge template
|
|
27
|
+
template = env.get_template("val3_documentation_md.j2")
|
|
28
|
+
return template
|
|
29
|
+
except TemplateNotFound:
|
|
30
|
+
print(
|
|
31
|
+
"Template 'lupin_danquin/templates/val3_documentation_md.j2 not found"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def generate_markdown(self, context={}) -> None:
|
|
35
|
+
"""Generate the documentation"""
|
|
36
|
+
template = self._get_local_template()
|
|
37
|
+
try:
|
|
38
|
+
content = template.render(context)
|
|
39
|
+
except (TemplateError, TemplateRuntimeError) as e:
|
|
40
|
+
die(msg=f"Error rendering Jinja2 template: {e}")
|
|
41
|
+
|
|
42
|
+
with codecs.open(
|
|
43
|
+
"lupin_danquin/val3_documentation.md", "w", encoding="utf-8"
|
|
44
|
+
) as f:
|
|
45
|
+
f.write(content)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from core.application import Application
|
|
4
|
+
from core.external_resources import read_begining_of_file, read_end_of_file
|
|
5
|
+
from core.tools.utils import configure_logging, die
|
|
6
|
+
from core.val3_doc_generator.val3_doc_generator import Val3Documentation
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
APPLICATION_NAMES = [
|
|
10
|
+
"TaskManager",
|
|
11
|
+
"Watchdog",
|
|
12
|
+
]
|
|
13
|
+
USR_APP_DIR = "usrapp"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def find_usrapp_dir() -> str:
|
|
17
|
+
"""Find the usrapp directory"""
|
|
18
|
+
for dirpath, dirnames, filenames in os.walk(os.getcwd()):
|
|
19
|
+
if USR_APP_DIR in dirnames:
|
|
20
|
+
return os.path.join(dirpath, USR_APP_DIR)
|
|
21
|
+
die(msg=f"'{USR_APP_DIR}' directory not found")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_informations_from_applications() -> list:
|
|
25
|
+
"""Get all informations from applications"""
|
|
26
|
+
|
|
27
|
+
apps_information = []
|
|
28
|
+
base_program_path = find_usrapp_dir() + os.sep
|
|
29
|
+
|
|
30
|
+
for application_name in APPLICATION_NAMES:
|
|
31
|
+
program_path = base_program_path + application_name
|
|
32
|
+
apps_information.append(
|
|
33
|
+
Application(program_path, application_name).get_all_information_from_app()
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return apps_information
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def main() -> None:
|
|
40
|
+
"""Main function"""
|
|
41
|
+
|
|
42
|
+
configure_logging()
|
|
43
|
+
|
|
44
|
+
context = {
|
|
45
|
+
"begining_of_file": read_begining_of_file(),
|
|
46
|
+
"end_of_file": read_end_of_file(),
|
|
47
|
+
"applications": get_informations_from_applications(),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Val3Documentation().generate_markdown(context=context)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
main()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: lupin-danquin
|
|
3
|
+
Version: 0.0.1.dev0
|
|
4
|
+
Summary: Testing - Documentation VAL3 from Code
|
|
5
|
+
License: MIT License
|
|
6
|
+
Keywords: documentation,testing
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Information Technology
|
|
9
|
+
Classifier: Topic :: Software Development :: Testing
|
|
10
|
+
Classifier: Topic :: Documentation
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Provides-Extra: test
|
|
15
|
+
License-File: LICENCE
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
LICENCE
|
|
2
|
+
README.md
|
|
3
|
+
setup.cfg
|
|
4
|
+
setup.py
|
|
5
|
+
lupin_danquin/__init__.py
|
|
6
|
+
lupin_danquin/check_coding_rules.py
|
|
7
|
+
lupin_danquin/documentation_extraction.py
|
|
8
|
+
lupin_danquin.egg-info/PKG-INFO
|
|
9
|
+
lupin_danquin.egg-info/SOURCES.txt
|
|
10
|
+
lupin_danquin.egg-info/dependency_links.txt
|
|
11
|
+
lupin_danquin.egg-info/not-zip-safe
|
|
12
|
+
lupin_danquin.egg-info/requires.txt
|
|
13
|
+
lupin_danquin.egg-info/top_level.txt
|
|
14
|
+
lupin_danquin/core/__init__.py
|
|
15
|
+
lupin_danquin/core/application.py
|
|
16
|
+
lupin_danquin/core/external_resources.py
|
|
17
|
+
lupin_danquin/core/global_variable.py
|
|
18
|
+
lupin_danquin/core/line_ended.py
|
|
19
|
+
lupin_danquin/core/local_variable.py
|
|
20
|
+
lupin_danquin/core/parameter.py
|
|
21
|
+
lupin_danquin/core/program.py
|
|
22
|
+
lupin_danquin/core/rules.py
|
|
23
|
+
lupin_danquin/core/tools/__init__.py
|
|
24
|
+
lupin_danquin/core/tools/utils.py
|
|
25
|
+
lupin_danquin/core/val3_doc_generator/__init__.py
|
|
26
|
+
lupin_danquin/core/val3_doc_generator/val3_doc_generator.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
lupin_danquin
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[metadata]
|
|
2
|
+
name = lupin-danquin
|
|
3
|
+
version = 0.0.1.dev0
|
|
4
|
+
description = Testing - Documentation VAL3 from Code
|
|
5
|
+
keywords = documentation, testing
|
|
6
|
+
license = MIT License
|
|
7
|
+
classifiers =
|
|
8
|
+
Development Status :: 3 - Alpha
|
|
9
|
+
Intended Audience :: Information Technology
|
|
10
|
+
Topic :: Software Development :: Testing
|
|
11
|
+
Topic :: Documentation
|
|
12
|
+
Programming Language :: Python :: 3.10
|
|
13
|
+
License :: OSI Approved :: MIT License
|
|
14
|
+
|
|
15
|
+
[options]
|
|
16
|
+
zip_safe = False
|
|
17
|
+
include_package_data = True
|
|
18
|
+
packages = find:
|
|
19
|
+
python_requires = >=3.10
|
|
20
|
+
install_requires =
|
|
21
|
+
typer[all]==0.7.0
|
|
22
|
+
python-dotenv==1.0.0
|
|
23
|
+
Jinja2==3.1.2
|
|
24
|
+
|
|
25
|
+
[options.extras_require]
|
|
26
|
+
test =
|
|
27
|
+
pytest==7.2.2
|
|
28
|
+
flake8==6.0.0
|
|
29
|
+
|
|
30
|
+
[options.packages.find]
|
|
31
|
+
exclude =
|
|
32
|
+
lupin_danquin.tests*
|
|
33
|
+
|
|
34
|
+
[egg_info]
|
|
35
|
+
tag_build =
|
|
36
|
+
tag_date = 0
|
|
37
|
+
|