update_configuration_doc.py
Go to the documentation of this file.
1 #!/usr/bin/python3
2 # -*- coding: utf-8 -*-
3 
4 # Copyright 2016 The Cartographer Authors
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 """A dumb configuration.rst generator that relies on source comments."""
18 
19 import io
20 import os
21 
22 TARGET = 'docs/source/configuration.rst'
23 ROOT = 'cartographer'
24 PREFIX = """.. Copyright 2016 The Cartographer Authors
25 
26 .. Licensed under the Apache License, Version 2.0 (the "License");
27  you may not use this file except in compliance with the License.
28  You may obtain a copy of the License at
29 
30 .. http://www.apache.org/licenses/LICENSE-2.0
31 
32 .. Unless required by applicable law or agreed to in writing, software
33  distributed under the License is distributed on an "AS IS" BASIS,
34  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
35  See the License for the specific language governing permissions and
36  limitations under the License.
37 
38 =============
39 Configuration
40 =============
41 
42 .. DO NOT EDIT! This documentation is AUTOGENERATED, please edit .proto files as
43 .. needed and run scripts/update_configuration_doc.py.
44 
45 """
46 SUFFIX = """
47 """
48 NODOC = 'Not yet documented.'
49 
50 
51 class Message(object):
52  def __init__(self, name, package, preceding_comments):
53  self.name = name
54  self.package = package
55  self.preceding_comments = preceding_comments
56  self.trailing_comments = None
57  self.options = []
58 
59  def AddTrailingComments(self, comments):
60  self.trailing_comments = comments
61 
62  def AddOption(self, option_type, name, comments):
63  self.options.append((option_type, name, comments))
64 
65 
66 def ParseProtoFile(proto_file):
67  """Computes the list of Message objects of the option messages in a file."""
68  line_iter = iter(proto_file)
69 
70  # We ignore the license header and search for the 'package' line.
71  for line in line_iter:
72  line = line.strip()
73  if line.startswith('package'):
74  assert line[-1] == ';'
75  package = line[7:-1].strip()
76  break
77  else:
78  assert '}' not in line
79 
80  message_list = []
81  while True:
82  # Search for the next options message and capture preceding comments.
83  message_comments = []
84  for line in line_iter:
85  line = line.strip()
86  if '}' in line:
87  # The preceding comments were for a different message it seems.
88  message_comments = []
89  elif line.startswith('//'):
90  # We keep comments preceding an options message.
91  comment = line[2:].strip()
92  if not comment.startswith('NEXT ID:'):
93  message_comments.append(comment)
94  elif line.startswith('message') and line.endswith('Options {'):
95  message_name = package + '.' + line[7:-1].strip()
96  break
97  else:
98  # We reached the end of file.
99  break
100  print(" Found '%s'." % message_name)
101  message = Message(message_name, package, message_comments)
102  message_list.append(message)
103 
104  # We capture the contents of this message.
105  option_comments = []
106  multiline = ''
107  for line in line_iter:
108  line = line.strip()
109  if '}' in line:
110  # We reached the end of this message.
111  message.AddTrailingComments(option_comments)
112  break
113  elif line.startswith('//'):
114  comment = line[2:].strip()
115  if not comment.startswith('NEXT ID:'):
116  option_comments.append(comment)
117  else:
118  assert not line.startswith('required')
119  multiline += ' ' + line
120  if not multiline.endswith(';'):
121  continue
122  assert len(multiline) < 200
123  option = multiline[:-1].strip().rstrip('0123456789').strip()
124  assert option.endswith('=')
125  if option.startswith('repeated'):
126  option = option[8:]
127  option_type, option_name = option[:-1].strip().split();
128  print(" Option '%s'." % option_name)
129  multiline = ''
130  message.AddOption(option_type, option_name, option_comments)
131  option_comments = []
132 
133  return message_list
134 
135 
137  """Recursively parses all proto files into a list of Message objects."""
138  message_list = []
139  for dirpath, dirnames, filenames in os.walk(root):
140  for name in filenames:
141  if name.endswith('.proto'):
142  path = os.path.join(dirpath, name)
143  print("Found '%s'..." % path)
144  assert not os.path.islink(path)
145  message_list.extend(ParseProtoFile(io.open(path, encoding='UTF-8')))
146  return message_list
147 
148 
149 class ResolutionError(Exception):
150  """Raised when resolving a message name fails."""
151 
152 
153 class Resolver(object):
154  def __init__(self, name_set):
155  self.name_set = set(iter(name_set))
156 
157  def Resolve(self, message_name, package_name):
158  if message_name in ('bool', 'double', 'float', 'int32'):
159  return message_name
160  if message_name.startswith('.'):
161  return message_name[1:]
162  package = package_name.split('.')
163  for levels in range(len(package), -1, -1):
164  candidate = '.'.join(package[0:levels]) + '.' + message_name
165  if candidate in self.name_set:
166  return candidate
167  raise ResolutionError(
168  'Resolving %s in %s failed.' % (message_name, package_name))
169 
170 
171 def GenerateDocumentation(output_file, root):
172  """Recursively generates documentation, sorts and writes it."""
173  message_list = ParseProtoFilesRecursively(root)
174  resolver = Resolver(message.name for message in message_list)
175 
176  output_dict = {}
177  for message in message_list:
178  content = [message.name, '=' * len(message.name), '']
179  assert message.name not in output_dict
180  output_dict[message.name] = content
181  if message.preceding_comments:
182  content.extend(preceding_comments)
183  content.append('')
184  for option_type, option_name, option_comments in message.options:
185  # TODO(whess): For now we exclude InitialTrajectoryPose from the
186  # documentation. It is documented itself (since it has no Options suffix)
187  # and is not parsed from the Lua files.
188  if option_type in ('InitialTrajectoryPose',):
189  continue
190  content.append(
191  resolver.Resolve(option_type, message.package) + ' ' + option_name)
192  if not option_comments:
193  option_comments.append(NODOC)
194  for comment in option_comments:
195  content.append(' ' + comment)
196  content.append('')
197  if message.trailing_comments:
198  content.extend(message.trailing_comments)
199  content.append('')
200 
201  output = ['\n'.join(doc) for key, doc in sorted(list(output_dict.items()))]
202  print('\n\n'.join(output), file=output_file)
203 
204 
205 def main():
206  assert not os.path.islink(TARGET) and os.path.isfile(TARGET)
207  assert not os.path.islink(ROOT) and os.path.isdir(ROOT)
208  output_file = io.open(TARGET, mode='w', encoding='UTF-8', newline='\n')
209  output_file.write(PREFIX)
210  GenerateDocumentation(output_file, ROOT)
211  output_file.write(SUFFIX)
212  output_file.close()
213 
214 
215 if __name__ == "__main__":
216  main()
def Resolve(self, message_name, package_name)
def GenerateDocumentation(output_file, root)
def AddOption(self, option_type, name, comments)
def __init__(self, name, package, preceding_comments)


cartographer
Author(s): The Cartographer Authors
autogenerated on Mon Feb 28 2022 22:00:59