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