iterative closest points
CEV ICP algorithm library
Loading...
Searching...
No Matches
icp_doc_builder.py
Go to the documentation of this file.
1# Copyright (C) 2024 Ethan Uppal. All rights reserved.
2
3import os, re, sys
4
5def change_extension(filename, new_extension):
6 name, _ = os.path.splitext(filename)
7 return f'{name}.{new_extension}'
8
9
11 """
12 A class used to build documentation for an ICP implementation.
13
14 ...
15
16 Attributes
17 ----------
18 search_dir : str
19 The directory to search for source files.
20 out_dir : str
21 The directory to write the output markdown files.
22 all_sources : set
23 A set of all sources found in the source files.
24
25 Methods
26 -------
27 extract(file, contents):
28 Extracts the documentation from the given file contents and writes it to a markdown file.
29 build():
30 Searches for source files in the search directory and extracts their documentation.
31 """
32
33 def __init__(self, search_dir, out_dir):
34 """
35 Constructs a new ICPDocumentationBuilder.
36
37 Parameters
38 ----------
39 search_dir : str
40 The directory to search for source files.
41 out_dir : str
42 The directory to write the output markdown files.
43 """
44 self.search_dir = search_dir
45 self.out_dir = out_dir
46 self.all_sources = set()
47
48 def extract(self, file, contents):
49 """
50 Extracts the documentation from the given file contents and writes it to a markdown file.
51
52 Parameters
53 ----------
54 file : str
55 The name of the file to extract documentation from.
56 contents : str
57 The contents of the file.
58 """
59 pattern = r'/\*\s*(#step|#name|#desc)(.*?)\*/'
60 comments = re.findall(pattern, contents, re.DOTALL)
61 if not comments:
62 return
63 md_filename = 'icp_' + change_extension(file, 'md')
64 made_description = False
65 with open(self.out_dir + '/' + md_filename, 'w') as md_file:
66 step_cnt = 1
67 for (kind, comment) in comments:
68 comment_parts = comment.strip().split('Sources:', 1)
69 comment_text = re.sub(r'\n *', '\n', comment_parts[0]).replace('\n*', '\n').replace('\n *', '\n')
70 sources = re.findall(r'https?://[^\s]+', comment_parts[1]) if len(comment_parts) > 1 else []
71 self.all_sources.update(sources)
72 if kind == '#name':
73 method_name = re.findall(r'register_method\‍("([^"]*)', contents)[0]
74 md_file.write(f"\page {comment_text.lower()}_icp {comment_text} ICP\n")
75 conf_pattern = r'/\*\s*#conf\s+"([^"]*)"\s+(.*?)\*/'
76 confs = re.findall(conf_pattern, contents, re.DOTALL)
77 if not confs:
78 md_file.write(f'\par Usage\nYou can construct a new instance of {comment_text} ICP with `icp::ICP::from_method("{method_name}")`.')
79 else:
80 md_file.write(f'\par Usage\nYou can construct a new instance of {comment_text} ICP with `icp::ICP::from_method("{method_name}", config)`. Supply the following parameters to `config` (via icp::ICP::Config::set):\n')
81 md_file.write('\nKey | Description\n--- | ---\n')
82 for (key, descr) in confs:
83 descr = descr.replace('\n', ' ')
84 md_file.write(f'`"{key}"` | {descr}\n')
85 elif kind == '#step':
86 if not made_description:
87 md_file.write('\n\par Description\n')
88 made_description = True
89 lines = comment_text.splitlines()
90 first_line_parts = lines[0].split(':', 1)
91 if len(first_line_parts) == 2:
92 lines[0] = f"**{first_line_parts[0]}**:{first_line_parts[1]}"
93 else:
94 lines[0] = f"**{first_line_parts[0]}**"
95 step_text = '\n'.join(' ' + line for line in lines)
96 md_file.write(f"\n{step_cnt}. {step_text}\n")
97 step_cnt += 1
98 if sources:
99 md_file.write(' Sources: \n')
100 for source in sources:
101 md_file.write(f" - {source}\n")
102 md_file.write('\n')
103 elif kind == '#desc':
104 md_file.write(f"\n\par Description\n{comment_text}\n")
105 made_description = True
106 md_file.write('\n\nRead \\ref icp_sources for a list of all resources used in this project.')
107 md_file.write(f"\nThis page was automatically generated from {file} with {os.path.basename(__file__)}.")
108
109 def build(self):
110 """
111 Searches for source files in the search directory and extracts their documentation.
112 """
113 for root, dirs, files in os.walk(self.search_dir):
114 for file in files:
115 if file.endswith('.cpp'):
116 with open(os.path.join(root, file), 'r') as f:
117 self.extract(file, f.read())
118 os.makedirs(self.out_dir + '/extra', exist_ok=True)
119 with open(self.out_dir + '/extra/sources.md', 'w') as md_file:
120 md_file.write('\page icp_sources ICP Sources\n\n')
121 md_file.write('This list contains all resources used in implementing ICP for this project in alphabetical order.\n\n')
122 for source in sorted(list(self.all_sources)):
123 md_file.write(f" - {source}\n")
124
125
126if __name__ == '__main__':
127 if len(sys.argv) != 3:
128 print(f'usage: python3 {os.path.basename(__file__)} search_dir out_dir')
129 sys.exit(1)
130 doc_builder = ICPDocumentationBuilder(sys.argv[1], sys.argv[2])
131 doc_builder.build()
__init__(self, search_dir, out_dir)
change_extension(filename, new_extension)