EasyDocPy 1.2.0__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.
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.4
2
+ Name: EasyDocPy
3
+ Version: 1.2.0
4
+ Summary: python documentation generator
5
+ Author: epsilonkn
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/epsilonkn/EasyDoc
8
+ Project-URL: Issues, https://github.com/epsilonkn/EasyDoc/issues
9
+ Requires-Python: >=3.12
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Dynamic: license-file
13
+
14
+ # Doc
15
+
16
+ **Welcome to the page of the EasyDoc project**
17
+
18
+ The idea of that package is to create a technical documentation of a python source file, from the docstrings and the commentary in the source code : yes, the more you comment your code, the more the module will scrap.
19
+
20
+ For more information, read the wiki : https://github.com/epsilonkn/EasyDoc/wiki
21
+
22
+ ## To use it :
23
+
24
+ ### To comment your code :
25
+
26
+ #### **declarations :**
27
+
28
+ Is accepted for classes
29
+
30
+ class obj(*parents):
31
+
32
+ or
33
+
34
+ class obj:
35
+
36
+ Is accepted for functions :
37
+
38
+ def foo(arg, agr2 = 10, arg 3 : str = "poo", *args, **kwargs) -> None :
39
+
40
+ or
41
+
42
+ def foo(arg,
43
+ agr2 = 10,
44
+ arg 3 : str = "poo",
45
+ *args,
46
+ **kwargs) -> None :
47
+
48
+ Note : the tabs before the "def" are obviously accepted, but the module will assume a function with tabs before is a method of a class.
49
+
50
+ #### **docstrings :**
51
+
52
+ The docstrings MUST be defined by 3 double quotes at the beginning and same at the end, otherwise it won't work.
53
+ \
54
+ You can place docstrings below your classes, methods and functions to detail them, they can be juste below the declaration, or some lines below. There can be 1 or multiple docstrings, but they will all get concatenated into one.
55
+
56
+ Examples :
57
+
58
+ class foo:
59
+ """
60
+ a detail
61
+ """
62
+
63
+ class foo:
64
+ """a detail"""
65
+
66
+ def foo(*args):
67
+ """
68
+ detail
69
+
70
+ args:
71
+ args : detail
72
+ """
73
+
74
+ def foo(*args):
75
+ """detail"""
76
+
77
+
78
+ #### **custom comment lines :**
79
+
80
+ To enhance your documentation, there are a few custom comments you can do :
81
+
82
+ **#/actual_version**
83
+ \
84
+ Define the version of the file
85
+
86
+ Use :
87
+ #/actual_version : V.1.9.25
88
+
89
+ **#/author :**
90
+ \
91
+ Define the author the file
92
+
93
+ Use :
94
+ #/author : epsilonkn
95
+
96
+ **#/creation_date :**
97
+ \
98
+ Date of creation of the file
99
+
100
+ Use :
101
+ #/creation_date : 01/01/1900
102
+
103
+ **#/last_release_date :**
104
+ \
105
+ Last date of release of the file
106
+
107
+ Use :
108
+ #/last_release_date : 02/01/1900
109
+
110
+ **#/TODO :**
111
+ \
112
+ List all the todo in the file
113
+
114
+ Use :
115
+ #/TODO Find time to write the doc
116
+ #/TODO Write the doc when dev is done
117
+ #/TODO Write the doc of this file
118
+
119
+ **#/planned :**
120
+ \
121
+ List all the planned future versions
122
+
123
+ Use :
124
+ #/planned V2 : rewrite the code for better scalability
125
+ #/planned V2.1 : patch the errors of the new code
126
+ #/planned V2.2 : ......
127
+
128
+ **#/file_intro :**
129
+ \
130
+ Marks the begin of the file intro. the file intro follows the same rules as the function's docstrings.
131
+
132
+ Use :
133
+ #/file_intro
134
+ """
135
+ this file is meant to provide the result of the operation 2+2
136
+ it takes as a paramter....
137
+ """
138
+
139
+ **#/const :**
140
+ \
141
+ Explains a constant in the code. for now, all the constants are written at the begining of the doc no matter if they're class's constants or file's constants.
142
+
143
+ Use :
144
+ #/const CONST defines the gravitation force for calculus purposes
145
+ CONST = 10
146
+ #/const DEFAULT defines the default values for the empty strings
147
+ DEFAULT = "VOID"
148
+
149
+
150
+ #### Generate the documentation :
151
+
152
+ ### In a terminal :
153
+
154
+ Here is the only way implemented to generate a documentation in command line :
155
+
156
+ easydoc file "/your/path/to/file.py"
157
+
158
+ Note 1 : your terminal must be in the directory where you want to see the documentation generated.
159
+
160
+ Note 2 : you can pass the path without double quotes, however it is better to keep them if your path got spaces in it.
161
+
162
+ ### In a python program :
163
+
164
+ Disclaimer : Running the module in a program by calling directly the classes can be possible, but this might also opens a pandora box of bugs.
165
+
166
+ The reason you would need to run the module manually in a python file is to get a better control over the process.
167
+ Unfortunately, in the actual state of the module, the control over the module is still very little, this will be improved in the next updates.
168
+
169
+ If you still wish to do it in a program rather than in command line, here is the base :
170
+
171
+ From easydoc import Parser, MarkdownGenerator
172
+
173
+ parser = Parser(path = "/path/to/file.py", automatic = False)
174
+ parse_list = parser.get_parse()
175
+ file_header = parser.get_intro()
176
+
177
+ MarkdownGenerator(parse_list, file_header, doc_file_name)
178
+
179
+
180
+
181
+ ## Next updates :
182
+
183
+ |Version | Improvement|
184
+ |-|-|
185
+ |V 1.2.0 | adding custom comment line to add info to the documentation|
186
+ |V 1.3.0| better control over the process when done in a program |
187
+ |V 1.4.0| adding doc generation for an entire directory |
188
+ |V 1.5.0| adding user configuration |
189
+ |......||
190
+
191
+ ## API :
192
+
193
+ For now there is no released API entry points in the module, you'll have to wait for the V1.3 and V1.5 for these parts
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ EasyDocPy.egg-info/PKG-INFO
5
+ EasyDocPy.egg-info/SOURCES.txt
6
+ EasyDocPy.egg-info/dependency_links.txt
7
+ EasyDocPy.egg-info/entry_points.txt
8
+ EasyDocPy.egg-info/top_level.txt
9
+ easydoc/__init__.py
10
+ easydoc/__main__.py
11
+ easydoc/analyser.py
12
+ easydoc/generator.py
13
+ easydoc/objects.py
14
+ easydoc/config/custom_comment_lines.json
15
+ easydoc/templates/template.md
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ easydoc = easydoc.cli:main
@@ -0,0 +1 @@
1
+ easydoc
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2026 Ywan Maxime GERARD
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.4
2
+ Name: EasyDocPy
3
+ Version: 1.2.0
4
+ Summary: python documentation generator
5
+ Author: epsilonkn
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/epsilonkn/EasyDoc
8
+ Project-URL: Issues, https://github.com/epsilonkn/EasyDoc/issues
9
+ Requires-Python: >=3.12
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Dynamic: license-file
13
+
14
+ # Doc
15
+
16
+ **Welcome to the page of the EasyDoc project**
17
+
18
+ The idea of that package is to create a technical documentation of a python source file, from the docstrings and the commentary in the source code : yes, the more you comment your code, the more the module will scrap.
19
+
20
+ For more information, read the wiki : https://github.com/epsilonkn/EasyDoc/wiki
21
+
22
+ ## To use it :
23
+
24
+ ### To comment your code :
25
+
26
+ #### **declarations :**
27
+
28
+ Is accepted for classes
29
+
30
+ class obj(*parents):
31
+
32
+ or
33
+
34
+ class obj:
35
+
36
+ Is accepted for functions :
37
+
38
+ def foo(arg, agr2 = 10, arg 3 : str = "poo", *args, **kwargs) -> None :
39
+
40
+ or
41
+
42
+ def foo(arg,
43
+ agr2 = 10,
44
+ arg 3 : str = "poo",
45
+ *args,
46
+ **kwargs) -> None :
47
+
48
+ Note : the tabs before the "def" are obviously accepted, but the module will assume a function with tabs before is a method of a class.
49
+
50
+ #### **docstrings :**
51
+
52
+ The docstrings MUST be defined by 3 double quotes at the beginning and same at the end, otherwise it won't work.
53
+ \
54
+ You can place docstrings below your classes, methods and functions to detail them, they can be juste below the declaration, or some lines below. There can be 1 or multiple docstrings, but they will all get concatenated into one.
55
+
56
+ Examples :
57
+
58
+ class foo:
59
+ """
60
+ a detail
61
+ """
62
+
63
+ class foo:
64
+ """a detail"""
65
+
66
+ def foo(*args):
67
+ """
68
+ detail
69
+
70
+ args:
71
+ args : detail
72
+ """
73
+
74
+ def foo(*args):
75
+ """detail"""
76
+
77
+
78
+ #### **custom comment lines :**
79
+
80
+ To enhance your documentation, there are a few custom comments you can do :
81
+
82
+ **#/actual_version**
83
+ \
84
+ Define the version of the file
85
+
86
+ Use :
87
+ #/actual_version : V.1.9.25
88
+
89
+ **#/author :**
90
+ \
91
+ Define the author the file
92
+
93
+ Use :
94
+ #/author : epsilonkn
95
+
96
+ **#/creation_date :**
97
+ \
98
+ Date of creation of the file
99
+
100
+ Use :
101
+ #/creation_date : 01/01/1900
102
+
103
+ **#/last_release_date :**
104
+ \
105
+ Last date of release of the file
106
+
107
+ Use :
108
+ #/last_release_date : 02/01/1900
109
+
110
+ **#/TODO :**
111
+ \
112
+ List all the todo in the file
113
+
114
+ Use :
115
+ #/TODO Find time to write the doc
116
+ #/TODO Write the doc when dev is done
117
+ #/TODO Write the doc of this file
118
+
119
+ **#/planned :**
120
+ \
121
+ List all the planned future versions
122
+
123
+ Use :
124
+ #/planned V2 : rewrite the code for better scalability
125
+ #/planned V2.1 : patch the errors of the new code
126
+ #/planned V2.2 : ......
127
+
128
+ **#/file_intro :**
129
+ \
130
+ Marks the begin of the file intro. the file intro follows the same rules as the function's docstrings.
131
+
132
+ Use :
133
+ #/file_intro
134
+ """
135
+ this file is meant to provide the result of the operation 2+2
136
+ it takes as a paramter....
137
+ """
138
+
139
+ **#/const :**
140
+ \
141
+ Explains a constant in the code. for now, all the constants are written at the begining of the doc no matter if they're class's constants or file's constants.
142
+
143
+ Use :
144
+ #/const CONST defines the gravitation force for calculus purposes
145
+ CONST = 10
146
+ #/const DEFAULT defines the default values for the empty strings
147
+ DEFAULT = "VOID"
148
+
149
+
150
+ #### Generate the documentation :
151
+
152
+ ### In a terminal :
153
+
154
+ Here is the only way implemented to generate a documentation in command line :
155
+
156
+ easydoc file "/your/path/to/file.py"
157
+
158
+ Note 1 : your terminal must be in the directory where you want to see the documentation generated.
159
+
160
+ Note 2 : you can pass the path without double quotes, however it is better to keep them if your path got spaces in it.
161
+
162
+ ### In a python program :
163
+
164
+ Disclaimer : Running the module in a program by calling directly the classes can be possible, but this might also opens a pandora box of bugs.
165
+
166
+ The reason you would need to run the module manually in a python file is to get a better control over the process.
167
+ Unfortunately, in the actual state of the module, the control over the module is still very little, this will be improved in the next updates.
168
+
169
+ If you still wish to do it in a program rather than in command line, here is the base :
170
+
171
+ From easydoc import Parser, MarkdownGenerator
172
+
173
+ parser = Parser(path = "/path/to/file.py", automatic = False)
174
+ parse_list = parser.get_parse()
175
+ file_header = parser.get_intro()
176
+
177
+ MarkdownGenerator(parse_list, file_header, doc_file_name)
178
+
179
+
180
+
181
+ ## Next updates :
182
+
183
+ |Version | Improvement|
184
+ |-|-|
185
+ |V 1.2.0 | adding custom comment line to add info to the documentation|
186
+ |V 1.3.0| better control over the process when done in a program |
187
+ |V 1.4.0| adding doc generation for an entire directory |
188
+ |V 1.5.0| adding user configuration |
189
+ |......||
190
+
191
+ ## API :
192
+
193
+ For now there is no released API entry points in the module, you'll have to wait for the V1.3 and V1.5 for these parts
@@ -0,0 +1,180 @@
1
+ # Doc
2
+
3
+ **Welcome to the page of the EasyDoc project**
4
+
5
+ The idea of that package is to create a technical documentation of a python source file, from the docstrings and the commentary in the source code : yes, the more you comment your code, the more the module will scrap.
6
+
7
+ For more information, read the wiki : https://github.com/epsilonkn/EasyDoc/wiki
8
+
9
+ ## To use it :
10
+
11
+ ### To comment your code :
12
+
13
+ #### **declarations :**
14
+
15
+ Is accepted for classes
16
+
17
+ class obj(*parents):
18
+
19
+ or
20
+
21
+ class obj:
22
+
23
+ Is accepted for functions :
24
+
25
+ def foo(arg, agr2 = 10, arg 3 : str = "poo", *args, **kwargs) -> None :
26
+
27
+ or
28
+
29
+ def foo(arg,
30
+ agr2 = 10,
31
+ arg 3 : str = "poo",
32
+ *args,
33
+ **kwargs) -> None :
34
+
35
+ Note : the tabs before the "def" are obviously accepted, but the module will assume a function with tabs before is a method of a class.
36
+
37
+ #### **docstrings :**
38
+
39
+ The docstrings MUST be defined by 3 double quotes at the beginning and same at the end, otherwise it won't work.
40
+ \
41
+ You can place docstrings below your classes, methods and functions to detail them, they can be juste below the declaration, or some lines below. There can be 1 or multiple docstrings, but they will all get concatenated into one.
42
+
43
+ Examples :
44
+
45
+ class foo:
46
+ """
47
+ a detail
48
+ """
49
+
50
+ class foo:
51
+ """a detail"""
52
+
53
+ def foo(*args):
54
+ """
55
+ detail
56
+
57
+ args:
58
+ args : detail
59
+ """
60
+
61
+ def foo(*args):
62
+ """detail"""
63
+
64
+
65
+ #### **custom comment lines :**
66
+
67
+ To enhance your documentation, there are a few custom comments you can do :
68
+
69
+ **#/actual_version**
70
+ \
71
+ Define the version of the file
72
+
73
+ Use :
74
+ #/actual_version : V.1.9.25
75
+
76
+ **#/author :**
77
+ \
78
+ Define the author the file
79
+
80
+ Use :
81
+ #/author : epsilonkn
82
+
83
+ **#/creation_date :**
84
+ \
85
+ Date of creation of the file
86
+
87
+ Use :
88
+ #/creation_date : 01/01/1900
89
+
90
+ **#/last_release_date :**
91
+ \
92
+ Last date of release of the file
93
+
94
+ Use :
95
+ #/last_release_date : 02/01/1900
96
+
97
+ **#/TODO :**
98
+ \
99
+ List all the todo in the file
100
+
101
+ Use :
102
+ #/TODO Find time to write the doc
103
+ #/TODO Write the doc when dev is done
104
+ #/TODO Write the doc of this file
105
+
106
+ **#/planned :**
107
+ \
108
+ List all the planned future versions
109
+
110
+ Use :
111
+ #/planned V2 : rewrite the code for better scalability
112
+ #/planned V2.1 : patch the errors of the new code
113
+ #/planned V2.2 : ......
114
+
115
+ **#/file_intro :**
116
+ \
117
+ Marks the begin of the file intro. the file intro follows the same rules as the function's docstrings.
118
+
119
+ Use :
120
+ #/file_intro
121
+ """
122
+ this file is meant to provide the result of the operation 2+2
123
+ it takes as a paramter....
124
+ """
125
+
126
+ **#/const :**
127
+ \
128
+ Explains a constant in the code. for now, all the constants are written at the begining of the doc no matter if they're class's constants or file's constants.
129
+
130
+ Use :
131
+ #/const CONST defines the gravitation force for calculus purposes
132
+ CONST = 10
133
+ #/const DEFAULT defines the default values for the empty strings
134
+ DEFAULT = "VOID"
135
+
136
+
137
+ #### Generate the documentation :
138
+
139
+ ### In a terminal :
140
+
141
+ Here is the only way implemented to generate a documentation in command line :
142
+
143
+ easydoc file "/your/path/to/file.py"
144
+
145
+ Note 1 : your terminal must be in the directory where you want to see the documentation generated.
146
+
147
+ Note 2 : you can pass the path without double quotes, however it is better to keep them if your path got spaces in it.
148
+
149
+ ### In a python program :
150
+
151
+ Disclaimer : Running the module in a program by calling directly the classes can be possible, but this might also opens a pandora box of bugs.
152
+
153
+ The reason you would need to run the module manually in a python file is to get a better control over the process.
154
+ Unfortunately, in the actual state of the module, the control over the module is still very little, this will be improved in the next updates.
155
+
156
+ If you still wish to do it in a program rather than in command line, here is the base :
157
+
158
+ From easydoc import Parser, MarkdownGenerator
159
+
160
+ parser = Parser(path = "/path/to/file.py", automatic = False)
161
+ parse_list = parser.get_parse()
162
+ file_header = parser.get_intro()
163
+
164
+ MarkdownGenerator(parse_list, file_header, doc_file_name)
165
+
166
+
167
+
168
+ ## Next updates :
169
+
170
+ |Version | Improvement|
171
+ |-|-|
172
+ |V 1.2.0 | adding custom comment line to add info to the documentation|
173
+ |V 1.3.0| better control over the process when done in a program |
174
+ |V 1.4.0| adding doc generation for an entire directory |
175
+ |V 1.5.0| adding user configuration |
176
+ |......||
177
+
178
+ ## API :
179
+
180
+ For now there is no released API entry points in the module, you'll have to wait for the V1.3 and V1.5 for these parts
@@ -0,0 +1,11 @@
1
+ from .objects import *
2
+ from .generator import MarkdownGenerator
3
+ from .analyser import Parser
4
+
5
+ #/TODO add dir treatment
6
+ #/TODO add API entry points
7
+
8
+ __all__ = [
9
+ "MarkdownGenerator",
10
+ "Parser"
11
+ ]
@@ -0,0 +1,29 @@
1
+ import sys
2
+ from .analyser import Parser
3
+ import pathlib
4
+
5
+ from importlib.metadata import version
6
+
7
+ if "--version" == sys.argv[1]:
8
+ print("PyEasyDoc", version("PyEasyDoc"))
9
+ exit(0)
10
+
11
+ types = ["file", "dir"]
12
+
13
+ if sys.argv[1] not in types :
14
+ raise ValueError(f"The parameter {sys.argv[1]} is invalid\neasydoc accepts only \"file\" and \"dir\"")
15
+
16
+ path = pathlib.Path(sys.argv[2])
17
+
18
+ if not path.exists() :
19
+ raise ValueError(f"The path {sys.argv[2]} is doesn't exists")
20
+
21
+ match sys.argv[1] :
22
+
23
+ case "file":
24
+ if path.suffix != ".py" :
25
+ raise ValueError(f"The path {sys.argv[2]} doesn't point to a python file")
26
+ Parser(path)
27
+ case "group":
28
+ if path.suffix != ".py" :
29
+ raise NotImplementedError("the directory documentation mode hasn't been developped yet.")
@@ -0,0 +1,359 @@
1
+ #/file_intro
2
+ """
3
+ Module d'analyse d'un fichier source python
4
+ contient les classes de données ainsi que la classe Analyse qui se charge du parsing
5
+
6
+ Raises:
7
+ IndexError: raise IndexError si la classe Analyse détecte une parenthèse non fermée
8
+ empêchant la détection de la fin d'une fonction
9
+ """
10
+
11
+
12
+ #/actual_version : 1.2.0
13
+ #/last_release_date : 20/02/2026
14
+ #/author : Ywan GERARD
15
+
16
+
17
+ from importlib.resources import files
18
+ import json
19
+ from pathlib import Path
20
+ import re
21
+ import sys
22
+ from .objects import *
23
+ from .generator import MarkdownGenerator
24
+
25
+
26
+
27
+ class Parser:
28
+ """
29
+ Cette classe parcours le fichier source, identifie les classes et fonctions
30
+ et identifie les docstrings présents pour chaque classe et fonction
31
+ """
32
+
33
+ def __init__(self, path, automatic : bool = True):
34
+ """
35
+ initialise les attributs de la classe :
36
+ -fpath contient le chemin vers le fichier source
37
+ -fname contient le nom du fichier source
38
+ -parse est une liste contenant les classes et fonctions indépendantes scrappées
39
+ -intro contient le docstring en en-tête du fichier source
40
+
41
+ Args:
42
+ path (str): chemin vers le fichier source.
43
+ automatic (bool): chemin vers le fichier source.
44
+ """
45
+ self.auto = automatic
46
+ self.fpath = Path(path)
47
+ self.fname = self.fpath.stem
48
+ self.parse : list[Parsed_class, Parsed_function] = []
49
+ self.customs : dict = self.open_custom_config()
50
+ self.file_data : list[Custom_comment] = []
51
+ self.pointer = 0
52
+ self.parse_source()
53
+ if self.auto :
54
+ MarkdownGenerator(self.parse, self.file_data, self.fname)
55
+
56
+
57
+
58
+ def get_parse(self):
59
+ return self.parse
60
+
61
+
62
+ def parse_source(self):
63
+ """
64
+ parcours le code à la recherche d'une déclaration de classe, de fonction
65
+ et recherche un docstring rattaché à aucune classe ni fonction
66
+ ce docstring indépendant est interprété comme une explication du fichier source
67
+ """
68
+ source : list[str]= self.get_source()
69
+ print("classes and functions found :")
70
+ while self.pointer < len(source):
71
+ if self.is_class(source[ self.pointer]):
72
+ print(source[ self.pointer])
73
+ self.pointer += self.class_parser(source[ self.pointer:])
74
+
75
+ elif self.is_function(source[ self.pointer:]):
76
+ print(source[ self.pointer])
77
+ self.pointer += self.function_parser(source[ self.pointer:])
78
+
79
+ elif custom:=self.is_custom(source[ self.pointer]) :
80
+ self.pointer += self.parse_custom(custom, source[self.pointer:])
81
+
82
+ else :
83
+ self.pointer += 1
84
+
85
+
86
+ def is_custom(self, line : str) -> bool:
87
+ for custom in self.customs:
88
+ if custom in line :
89
+ return custom
90
+ return False
91
+
92
+
93
+ def parse_custom(self, custom : str, lines : list[str]):
94
+ pointer = 0
95
+ content = ""
96
+ match custom :
97
+ case "#/file_intro" :
98
+ pointer += 1
99
+ if self.is_oneline_docstring(lines[pointer]) :
100
+
101
+ content = self.format_string(lines[pointer].replace('"""', ""))
102
+ pointer +=1
103
+
104
+ elif self.is_docstring(lines[pointer]) :
105
+ content = self.format_string(lines[pointer].replace('"""', ""))
106
+ pointer +=1
107
+ while not self.is_docstring(lines[pointer]):
108
+ content += self.format_string(lines[pointer].replace('"""', "")).replace("\t", "")
109
+ pointer +=1
110
+ pointer +=1
111
+
112
+ case _ :
113
+ content = lines[pointer].replace(custom, "").replace("\n", "")
114
+ pointer += 1
115
+ obj = Custom_comment(self.customs[custom]["type"], self.customs[custom]["ref"], self.customs[custom]["is_list"], content )
116
+ self.file_data.append(obj)
117
+ return pointer
118
+
119
+
120
+ def is_class(self, line: str) -> bool:
121
+ """
122
+ Vérifie si une ligne est une déclaration de classe
123
+
124
+ Args:
125
+ line (str): ligne à vérifier
126
+
127
+ Returns:
128
+ bool: retourne True si la ligne est une déclaration de classe, False sinon
129
+ """
130
+ return bool(re.search(r"^class\s.*:", line))
131
+
132
+
133
+ def is_function(self, lines : list[str], in_class = False) -> bool:
134
+ """
135
+ Vérifie si la ligne du pointer est une fonction, ou le début d'une fonction dont l'en-tête est sur plusieurs lignes :
136
+ ex :
137
+
138
+ def funct( param1 = "foo", param2 = ("poo", 1)) -> None:
139
+
140
+ ou
141
+
142
+ def funct(
143
+ param1 = "foo",
144
+ param2 = ("poo", 1)
145
+ ) -> None:
146
+
147
+ Args:
148
+ line (list[str]): lignes à vérifier
149
+ in_class (bool, optional): indique si il s'agit d'une méthode ou d'une fonction. Defaults to False.
150
+
151
+ Returns:
152
+ bool: retourne True si il s'agit d'une déclaration de fonction, False sinon
153
+ """
154
+ if not in_class : pat = r"^def\s+[a-zA-Z_]\w*\s*\(.*"
155
+ else : pat = r"^\s+def\s+[a-zA-Z_]\w*\s*\(.*"
156
+ pointer = 0
157
+
158
+ if re.search(pat, lines[0]) :
159
+ opened = lines.count("(") - lines.count(")")
160
+ while opened != 0 and pointer < len(lines):
161
+ pointer += 1
162
+ opened += lines.count("(") - lines.count(")")
163
+ if opened == 0:
164
+ return True
165
+ if not opened and pointer >= len(lines):
166
+ raise IndexError(f"a parenthesis was opened but never closed on line \n {lines[0]}\nFailed to parse the module")
167
+ else :
168
+ return False
169
+
170
+
171
+ def get_function_declaration(self, lines : list[str]) -> tuple[str, int]:
172
+ """
173
+ Récupère la déclaration de la fonction et la retoure
174
+ ex :
175
+
176
+ def funct( param1 = "foo", param2 = ("poo", 1)) -> None:
177
+
178
+ ou
179
+
180
+ def funct(
181
+ param1 = "foo",
182
+ param2 = ("poo", 1)
183
+ ) -> None:
184
+
185
+ Args:
186
+ line (list[str]): lignes où se trouvent la déclaration
187
+ in_class (bool, optional): indique si il s'agit d'une méthode ou d'une fonction. Defaults to False.
188
+
189
+ Returns:
190
+ str: retourne la déclaration de la fonction sous forme d'un string
191
+ """
192
+ decla = ""
193
+ pointer = 0
194
+
195
+ opened = lines[pointer].count("(") - lines[pointer].count(")")
196
+ decla = lines[0]
197
+ while opened != 0 and pointer < len(lines):
198
+ pointer += 1
199
+ opened += lines[pointer].count("(") - lines[pointer].count(")")
200
+ decla += lines[pointer]
201
+ if opened == 0:
202
+ return self.format_string(decla), pointer + 1
203
+ if not opened and pointer >= len(lines):
204
+ raise IndexError(f"a parenthesis was opened but never closed on line \n {lines[0]}\nFailed to parse the module")
205
+
206
+
207
+ def class_parser(self, sub_source : list[str]) -> int:
208
+ """
209
+ isole la déclaration d'une classe, son docstring éventuel,
210
+ puis parcours le code de la classe à la recherche de méthodes
211
+
212
+ Args:
213
+ sub_source (list[str]): code source commençant à partir de la déclaration de la classe
214
+
215
+ Returns:
216
+ int: retourne la taille de la classe en nombre de ligne, évite que le parseur principal repasse sur du code déjà parsé
217
+ """
218
+ obj = Parsed_class(sub_source[0], "")
219
+ pointer = 1
220
+ docstring = ""
221
+ while pointer < len(sub_source) and not self.is_class(sub_source[pointer]) and not self.is_function(sub_source[pointer:]) :
222
+
223
+ if self.is_oneline_docstring(sub_source[pointer]) :
224
+ docstring = self.format_string(sub_source[pointer].replace('"""', ""))
225
+ pointer +=1
226
+
227
+ elif self.is_docstring(sub_source[pointer]) :
228
+ docstring += self.format_string(sub_source[pointer].replace('"""', ""))
229
+ pointer +=1
230
+ while not self.is_docstring(sub_source[pointer]):
231
+ docstring += self.format_string(sub_source[pointer].replace('"""', ""))
232
+ pointer +=1
233
+ pointer +=1
234
+
235
+ elif self.is_function(sub_source[pointer:], in_class=True) :
236
+ print(sub_source[pointer])
237
+ pointer += self.function_parser(sub_source[pointer:], obj)
238
+
239
+ else :
240
+ pointer += 1
241
+ obj.docstring = docstring
242
+ self.parse.append(obj)
243
+ return pointer
244
+
245
+
246
+ def function_parser(self, sub_source : list[str], parent : Parsed_class = None) -> int:
247
+ """
248
+ isole la déclaration d'une fonction et récupère son docstring éventuel,
249
+
250
+ Args:
251
+ sub_source (list[str]): code source commençant à partir de la déclaration de la fonction
252
+ parent (Parsed_class, optional): si le paramètre est fourni, alors function_parser
253
+ considèrera que la fonction à scraper est une méthode appartenant à la classe "parent". Defaults to None.
254
+
255
+ Returns:
256
+ int: retourne la taille de la fonction en nombre de ligne, évite que le parseur principal repasse sur du code déjà parsé
257
+ """
258
+ declaration, pointer = self.get_function_declaration(sub_source)
259
+ docstring = ""
260
+ while pointer < len(sub_source) \
261
+ and not self.is_function(sub_source[pointer:], in_class=True) \
262
+ and not self.is_class(sub_source[pointer]) \
263
+ and not self.is_function(sub_source[pointer:]) :
264
+
265
+ if self.is_oneline_docstring(sub_source[pointer]) :
266
+
267
+ docstring = self.format_string(sub_source[pointer].replace('"""', ""))
268
+ pointer +=1
269
+
270
+ elif self.is_docstring(sub_source[pointer]) :
271
+ docstring += self.format_string(sub_source[pointer].replace('"""', ""))
272
+ pointer +=1
273
+ while not self.is_docstring(sub_source[pointer]):
274
+ docstring += self.format_string(sub_source[pointer].replace('"""', ""))
275
+ pointer +=1
276
+ pointer +=1
277
+ else :
278
+ pointer += 1
279
+ obj = Parsed_function(declaration, docstring)
280
+ if parent :
281
+ parent.add_method(obj)
282
+ else :
283
+ self.parse.append(obj)
284
+ return pointer
285
+
286
+
287
+ def is_docstring(self, line: str) -> bool:
288
+ """
289
+ Vérifie si une ligne est le début d'un docstring sur plusieurs lignes
290
+
291
+ Args:
292
+ line (str): ligne à vérifier
293
+
294
+ Returns:
295
+ bool: retourne True si il s'agit du début d'un docstring, False sinon
296
+ """
297
+ return bool(re.search(r"(?<!')\"{3}", line))
298
+
299
+
300
+ def is_oneline_docstring(self, line: str) -> bool:
301
+ """
302
+ Vérifie si une ligne est une doctring sur une seule ligne, par exemple :
303
+
304
+ '''doctring d'une fonction'''
305
+
306
+ Args:
307
+ line (str): ligne à vérifier
308
+
309
+ Returns:
310
+ bool: retourne True si il s'agit d'un docstring sur une ligne, False sinon
311
+ """
312
+ return line.count('"""') == 2
313
+
314
+
315
+ def format_string(self, string : str) -> str :
316
+ """
317
+ Formate le string passé en paramètre,
318
+ remplace les chaine de tabs supérieures à 3 par une double tab,
319
+ si aucune tab n'est présente dans la chaine, la fonction en ajoute une en début de chaine
320
+
321
+ Args:
322
+ string (str): string à traiter
323
+
324
+ Returns:
325
+ str: retourne la chaine traité selon l'algorithme défini plus haut
326
+ """
327
+ string = re.sub("\s{4}", "\t", string)
328
+ if "\t" in string :
329
+ nb_tab : str = "\t" * string.count("\t")
330
+
331
+ string = string.replace(nb_tab, "\t")
332
+ else :
333
+ string = "\t" + string
334
+ return string
335
+
336
+
337
+ def get_source(self) -> list[str]:
338
+ """
339
+ Ouvre et retourne le code source du fichier choisi
340
+
341
+ Returns:
342
+ list[str]: retourne une liste dont chaque élément est une ligne du fichier source à parser
343
+ """
344
+ with open(self.fpath, 'r', encoding='utf-8') as f:
345
+ return f.readlines()
346
+
347
+
348
+ @staticmethod
349
+ def open_custom_config() -> dict:
350
+ with open(files("easydoc.config").joinpath("custom_comment_lines.json"), 'r', encoding='utf-8') as f :
351
+ return json.load(f)
352
+
353
+
354
+ if __name__ == "__main__" :
355
+ if len(sys.argv) >= 2 :
356
+ path = sys.argv[1]
357
+ Parser(path)
358
+ else :
359
+ raise IndexError("Aucun fichier source fourni, arrêt du programme")
@@ -0,0 +1,10 @@
1
+ {
2
+ "#/actual_version" : {"ref" : "%version%", "type" : "Version", "is_list" : false},
3
+ "#/author" : {"ref" : "%author%", "type" : "Author", "is_list" : false},
4
+ "#/creation_date" : {"ref" : "%creation_date%", "type" : "Creation_date", "is_list" : false},
5
+ "#/last_release_date" : {"ref" : "%last_release_date%", "type" : "Last_release_date", "is_list" : false},
6
+ "#/TODO" : {"ref" : "%TODO%", "type" : "TODO", "is_list" : true},
7
+ "#/planned" : {"ref" : "%roadmap%", "type" : "Roadmap", "is_list" : true},
8
+ "#/file_intro" : {"ref" : "%intro%", "type" : "Sum-up", "is_list" : false},
9
+ "#/const" : {"ref" : "%const%", "type" : "Constants", "is_list" : true}
10
+ }
@@ -0,0 +1,155 @@
1
+ #-----------------------------------------------------------------------------------------
2
+ # Fichier : analyser.py
3
+ # Version : 1.2
4
+ # Dernier changement : 21/02/2026
5
+ # dernier éditeur : Ywan GERARD
6
+ # Créateur : Ywan GERARD
7
+ #
8
+ #-----------------------------------------------------------------------------------------
9
+
10
+ import json
11
+ import re
12
+
13
+ from .objects import Parsed_class, Parsed_function, Custom_comment
14
+ import os
15
+ from importlib.resources import files
16
+
17
+
18
+ class MarkdownGenerator:
19
+
20
+ def __init__(self, obj_list, custom_list : list[Custom_comment], fname):
21
+ body : str = self.open_pattern()
22
+ customs = self.open_custom_config()
23
+ custom_done = []
24
+ body = body.replace("%module_name%", fname)
25
+ for elt in custom_list :
26
+ if elt.type_ in custom_done:
27
+ continue
28
+ elif elt.is_list :
29
+ part = self.generate_custom_list(custom_list, elt.type_)
30
+ body = body.replace(elt.ref, part)
31
+ custom_done.append(elt.type_)
32
+ else :
33
+ body = body.replace(elt.ref, f"{self._custom_header_wrap(elt.type_.replace("_"," "))}{elt.content}\n\\")
34
+
35
+ custom_done.append(elt.type_)
36
+
37
+ for elt in customs :
38
+ if customs[elt]["type"] not in custom_done :
39
+ print(customs[elt]["ref"])
40
+ body = body.replace(f"{customs[elt]["ref"]}\n", "")
41
+
42
+ for elt in obj_list :
43
+ if isinstance(elt, Parsed_class):
44
+ body += self.generate_class(elt)
45
+
46
+ for elt in obj_list :
47
+ if isinstance(elt, Parsed_function):
48
+ body += self.generate_function(elt)
49
+
50
+ body = re.sub(r"\\\n[^a-zA-Z]*\n", "\n", body)
51
+
52
+ self.create_file(fname, body)
53
+
54
+
55
+ # --------------- default wrapper functions ------------------
56
+
57
+
58
+ @staticmethod
59
+ def _class_wrap(name) :
60
+ return f"\n### Classe {name} :\n---\n"
61
+ @staticmethod
62
+ def _method_wrap(name) :
63
+ return f"\n#### **Methode {name} :**\n"
64
+ @staticmethod
65
+ def _function_wrap(name) :
66
+ return f"\n### Fonction {name} :\n"
67
+ @staticmethod
68
+ def _main_name_wrap(name) :
69
+ return f"\n### Fonction {name} :\n"
70
+ @staticmethod
71
+ def _custom_list_wrap(name) :
72
+ return f"\n### {name} :\n"
73
+ @staticmethod
74
+ def _custom_header_wrap(name) :
75
+ return f"**{name}**\n"
76
+
77
+
78
+ # --------------- hook functions ------------------
79
+
80
+
81
+ @classmethod
82
+ def set_class_wrap(cls, wrapper) :
83
+ cls._class_wrap = wrapper
84
+ return wrapper
85
+
86
+ @classmethod
87
+ def set_method_wrap(cls, wrapper) :
88
+ cls._method_wrap = wrapper
89
+ return wrapper
90
+
91
+ @classmethod
92
+ def set_function_wrap(cls, wrapper) :
93
+ cls._function_wrap = wrapper
94
+ return wrapper
95
+
96
+ @classmethod
97
+ def set_main_name_wrap(cls, wrapper) :
98
+ cls._main_name_wrap = wrapper
99
+ return wrapper
100
+
101
+
102
+ # --------------- files related functions ------------------
103
+
104
+
105
+ @staticmethod
106
+ def open_pattern():
107
+ return files("easydoc.templates").joinpath("template.md").read_text()
108
+
109
+
110
+ @staticmethod
111
+ def open_custom_config() -> dict:
112
+ with open(files("easydoc.config").joinpath("custom_comment_lines.json"), 'r', encoding='utf-8') as f :
113
+ return json.load(f)
114
+
115
+
116
+ @staticmethod
117
+ def create_file(name, body):
118
+ with open(f"{os.getcwd()}/{name}_doc.md", 'w', encoding="utf-8") as f:
119
+ return f.write(body)
120
+
121
+
122
+ # --------------- generation functions ------------------
123
+
124
+
125
+ def generate_custom_list(self, customs : list[Custom_comment], type_):
126
+ md = self._custom_list_wrap(type_)
127
+ elem_list = [cust for cust in customs if cust.type_ == type_]
128
+ for elem in elem_list:
129
+ md += elem.content + "\n\\\n"
130
+ return md
131
+
132
+
133
+
134
+ def generate_class(self, classe : Parsed_class):
135
+ subbody = self._class_wrap(classe.name)
136
+ subbody += f"\nDéclaration :\n\n\t{classe.declaration}"
137
+ subbody += f"\nDescription :\n{classe.docstring}"
138
+ for func in classe.methods:
139
+ subbody += self.generate_function(func, True)
140
+
141
+ return subbody
142
+
143
+
144
+ def generate_function(self, func : Parsed_function, in_class : bool = False):
145
+ if in_class :
146
+ subbody = self._method_wrap(func.name)
147
+ else :
148
+ subbody = self._function_wrap(func.name)
149
+
150
+
151
+ subbody += f"\nDéclaration :\n\n{func.declaration}"
152
+ if func.docstring:
153
+ subbody += f"\nDescription :\n\n{func.docstring}"
154
+
155
+ return subbody
@@ -0,0 +1,44 @@
1
+ import re
2
+
3
+ class Parsed_function:
4
+
5
+ def __init__(self, declaration, docstring):
6
+ self.name : str = ""
7
+ self.declaration : str = ""
8
+ self.set_declaration(declaration)
9
+ self.docstring : str = docstring
10
+
11
+
12
+ def set_declaration(self, declaration):
13
+ self.declaration = declaration
14
+ self.name = re.search(r"^\s*(?:def|class)\s+([a-zA-Z_]\w*)", declaration).group(1)
15
+
16
+
17
+ class Custom_comment:
18
+
19
+ def __init__(self, type_, ref, is_list, content):
20
+ self.type_ : str = type_
21
+ self.ref : str = ref
22
+ self.is_list : bool = is_list
23
+ self.content : str = content
24
+
25
+
26
+
27
+ class Parsed_class:
28
+
29
+ def __init__(self, declaration, docstring):
30
+ self.name : str = ""
31
+ self.declaration : str = ""
32
+ self.set_declaration(declaration)
33
+ self.docstring : str = docstring
34
+ self.methods : list[Parsed_function] = []
35
+
36
+
37
+ def set_declaration(self, declaration):
38
+ self.declaration = declaration
39
+ self.name = re.search(r"^\s*(?:def|class)\s+([a-zA-Z_]\w*)", declaration).group(1)
40
+
41
+
42
+ def add_method(self, method : Parsed_function):
43
+ self.methods.append(method)
44
+
@@ -0,0 +1,20 @@
1
+ # %module_name%
2
+
3
+ ## Introduction
4
+
5
+ %version%
6
+ %author%
7
+ %creation_date%
8
+ %last_release_date%
9
+
10
+ %intro%
11
+
12
+ %roadmap%
13
+
14
+ %TODO%
15
+
16
+ ## Uses
17
+
18
+ ## Technical details
19
+
20
+ %const%
@@ -0,0 +1,24 @@
1
+ [project]
2
+ name = "EasyDocPy"
3
+ version = "1.2.0"
4
+ description = "python documentation generator"
5
+ dependencies = []
6
+ authors = [{ name="epsilonkn" }]
7
+ requires-python = ">=3.12"
8
+ readme = "README.md"
9
+ license = "MIT"
10
+ license-files = ["LICENSE"]
11
+
12
+ [build-system]
13
+ requires = ["setuptools>=61", "wheel"]
14
+ build-backend = "setuptools.build_meta"
15
+
16
+ [project.scripts]
17
+ easydoc = "easydoc.cli:main"
18
+
19
+ [tool.setuptools.package-data]
20
+ "easydoc" = ["templates/*.md", "templates/*.html", "config/*.json"]
21
+
22
+ [project.urls]
23
+ Homepage = "https://github.com/epsilonkn/EasyDoc"
24
+ Issues = "https://github.com/epsilonkn/EasyDoc/issues"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+