FIX: Pylint reduce
[csit.git] / resources / tools / doc_gen / gen_rst.py
1 # Copyright (c) 2021 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14
15 from os import walk, listdir
16 from os.path import isfile, isdir, join, getsize
17
18 # Temporary working directory. It is created and deleted by run_doc.sh
19 WORKING_DIR = u"tmp"
20
21 # Directory with resources to be documented.
22 RESOURCES_DIR = u"resources"
23
24 # Directory with libraries (python, robot) to be documented.
25 LIB_DIR = u"libraries"
26
27 # Directory with tests (func, perf) to be documented.
28 TESTS_DIR = u"tests"
29
30 PY_EXT = u".py"
31 RF_EXT = u".robot"
32
33 PATH_PY_LIBS = join(WORKING_DIR, RESOURCES_DIR, LIB_DIR, u"python")
34 PATH_RF_LIBS = join(WORKING_DIR, RESOURCES_DIR, LIB_DIR, u"robot")
35 PATH_TESTS = join(WORKING_DIR, TESTS_DIR)
36
37 # Sections in rst files
38 rst_toc = u"""
39 .. toctree::
40 """
41
42 rst_py_module = u"""
43 .. automodule:: {}.{}
44     :members:
45     :undoc-members:
46     :show-inheritance:
47 """
48
49 rst_rf_suite_setup = u"""
50 .. robot-settings::
51    :source: {}
52 """
53
54 rst_rf_variables = u"""
55 .. robot-variables::
56    :source: {}
57 """
58
59 rst_rf_keywords = u"""
60 .. robot-keywords::
61    :source: {}
62 """
63
64 rst_rf_tests = u"""
65 .. robot-tests::
66    :source: {}
67 """
68
69
70 def get_files(path, extension):
71     """Generates the list of files to process.
72
73     :param path: Path to files.
74     :param extension: Extension of files to process. If it is the empty string,
75     all files will be processed.
76     :type path: str
77     :type extension: str
78     :returns: List of files to process.
79     :rtype: list
80     """
81
82     file_list = list()
83     for root, dirs, files in walk(path):
84         for filename in files:
85             if extension:
86                 if filename.endswith(extension):
87                     file_list.append(join(root, filename))
88             else:
89                 file_list.append(join(root, filename))
90
91     return file_list
92
93
94 def create_file_name(path, start):
95     """Create the name of rst file.
96
97     Example:
98     tests.perf.rst
99
100     :param path: Path to a module to be documented.
101     :param start: The first directory in path which is used in the file name.
102     :type path: str
103     :type start: str
104     :returns: File name.
105     :rtype: str
106     """
107     dir_list = path.split(u"/")
108     start_index = dir_list.index(start)
109     return u".".join(dir_list[start_index:-1]) + u".rst"
110
111
112 def create_rst_file_names_set(files, start):
113     """Generate a set of unique rst file names.
114
115     :param files: List of all files to be documented with path beginning in the
116     working directory.
117     :param start: The first directory in path which is used in the file name.
118     :type files: list
119     :type start: str
120     :returns: Set of unique rst file names.
121     :rtype: set
122     """
123     file_names = set()
124     for file in files:
125         file_names.add(create_file_name(file, start))
126     return file_names
127
128
129 def scan_dir(path):
130     """Create a list of files and directories in the given directory.
131
132     :param path: Path to the directory.
133     :type path: str
134     :returns: List of directories and list of files sorted in alphabetical
135     order.
136     :rtype: tuple of two lists
137     """
138     files = list()
139     dirs = list()
140     items = listdir(path)
141     for item in items:
142         if isfile(join(path, item)) and u"__init__" not in item:
143             files.append(item)
144         elif isdir(join(path, item)):
145             dirs.append(item)
146     return sorted(dirs), sorted(files)
147
148
149 def write_toc(fh, path, dirs):
150     """Write a table of contents to given rst file.
151
152     :param fh: File handler of the rst file.
153     :param path: Path to package.
154     :param dirs: List of directories to be included in ToC.
155     :type fh: BinaryIO
156     :type path: str
157     :type dirs: list
158     """
159     fh.write(rst_toc)
160     for dir in dirs:
161         fh.write(f"    {u'.'.join(path)}.{dir}\n")
162
163
164 def write_module_title(fh, module_name):
165     """Write the module title to the given rst file. The title will be on the
166     second level.
167
168     :param fh: File handler of the rst file.
169     :param module_name: The name of module used for title.
170     :type fh: BinaryIO
171     :type module_name: str
172     """
173     title = f"{module_name} suite"
174     fh.write(f"\n{title}\n{u'-' * len(title)}")
175
176
177 def generate_py_rst_files():
178     """Generate all rst files for all python modules."""
179
180     dirs_ignore_list = [u"__pycache__", ]
181
182     py_libs = get_files(PATH_PY_LIBS, PY_EXT)
183     file_names = create_rst_file_names_set(py_libs, RESOURCES_DIR)
184
185     for file_name in file_names:
186         path = join(WORKING_DIR, *file_name.split(u".")[:-1])
187         dirs, files = scan_dir(path)
188
189         for item in dirs_ignore_list:
190             while True:
191                 try:
192                     dirs.remove(item)
193                 except ValueError:
194                     break
195
196         full_path = join(WORKING_DIR, file_name)
197         with open(full_path, mode="a") as fh:
198             if getsize(full_path) == 0:
199                 package = file_name.split(u".")[-2]
200                 fh.write(f"{package}\n")
201                 fh.write(u"=" * len(f"{package}"))
202             module_path = file_name.split(u".")[:-1]
203             if dirs:
204                 write_toc(fh, module_path, dirs)
205             for file in files:
206                 module_name = file.split(u".")[0]
207                 write_module_title(fh, module_name)
208                 fh.write(rst_py_module.format(
209                     u".".join(module_path), module_name)
210                 )
211
212
213 def generate_rf_rst_files(
214         file_names, incl_tests=True, incl_keywords=True, incl_suite_setup=False,
215         incl_variables=False):
216     """Generate rst files for the given robot modules.
217
218     :param file_names: List of file names to be included in the documentation
219     (rst files).
220     :param incl_tests: If True, tests will be included in the documentation.
221     :param incl_keywords: If True, keywords will be included in the
222     documentation.
223     :param incl_suite_setup: If True, the suite setup will be included in the
224     documentation.
225     :param incl_variables: If True, the variables will be included in the
226     documentation.
227     :type file_names: set
228     :type incl_tests: bool
229     :type incl_keywords: bool
230     :type incl_suite_setup: bool
231     :type incl_variables: bool
232     """
233
234     for file_name in file_names:
235         path = join(WORKING_DIR, *file_name.split(u".")[:-1])
236         dirs, files = scan_dir(path)
237
238         full_path = join(WORKING_DIR, file_name)
239         with open(full_path, mode="a") as fh:
240             if getsize(full_path) == 0:
241                 package = file_name.split(u".")[-2]
242                 fh.write(f"{package}\n")
243                 fh.write(u"=" * len(f"{package}") + u"\n")
244             module_path = file_name.split(u".")[:-1]
245             if dirs:
246                 write_toc(fh, module_path, dirs)
247             for file in files:
248                 module_name = file.split(u".")[0]
249                 write_module_title(fh, module_name)
250                 path = join(join(*module_path), module_name + RF_EXT)
251                 if incl_suite_setup:
252                     fh.write(rst_rf_suite_setup.format(path))
253                 if incl_variables:
254                     fh.write(rst_rf_variables.format(path))
255                 if incl_keywords:
256                     fh.write(rst_rf_keywords.format(path))
257                 if incl_tests:
258                     fh.write(rst_rf_tests.format(path))
259
260
261 def generate_kw_rst_files():
262     """Generate all rst files for all robot modules with keywords in libraries
263     directory (no tests)."""
264
265     rf_libs = get_files(PATH_RF_LIBS, RF_EXT)
266     file_names = create_rst_file_names_set(rf_libs, RESOURCES_DIR)
267
268     generate_rf_rst_files(file_names, incl_tests=False)
269
270
271 def generate_tests_rst_files():
272     """Generate all rst files for all robot modules with tests in tests
273     directory. Include also keywords defined in these modules."""
274
275     tests = get_files(PATH_TESTS, RF_EXT)
276     file_names = create_rst_file_names_set(tests, TESTS_DIR)
277
278     generate_rf_rst_files(
279         file_names, incl_suite_setup=True, incl_variables=True
280     )
281
282
283 if __name__ == u"__main__":
284
285     # Generate all rst files:
286     generate_py_rst_files()
287     generate_kw_rst_files()
288     generate_tests_rst_files()