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 = None
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  if multiline is None:
120  if line.startswith('optional'):
121  multiline = line
122  else:
123  continue
124  else:
125  multiline += ' ' + line
126  if not multiline.endswith(';'):
127  continue
128  assert len(multiline) < 200
129  option = multiline[8:-1].strip().rstrip('0123456789').strip()
130  assert option.endswith('=')
131  option_type, option_name = option[:-1].strip().split();
132  print(" Option '%s'." % option_name)
133  multiline = None
134  message.AddOption(option_type, option_name, option_comments)
135  option_comments = []
136 
137  return message_list
138 
139 
141  """Recursively parses all proto files into a list of Message objects."""
142  message_list = []
143  for dirpath, dirnames, filenames in os.walk(root):
144  for name in filenames:
145  if name.endswith('.proto'):
146  path = os.path.join(dirpath, name)
147  print("Found '%s'..." % path)
148  assert not os.path.islink(path)
149  message_list.extend(ParseProtoFile(io.open(path, encoding='UTF-8')))
150  return message_list
151 
152 
153 class ResolutionError(Exception):
154  """Raised when resolving a message name fails."""
155 
156 
157 class Resolver(object):
158  def __init__(self, name_set):
159  self.name_set = set(iter(name_set))
160 
161  def Resolve(self, message_name, package_name):
162  if message_name in ('bool', 'double', 'float', 'int32'):
163  return message_name
164  if message_name.startswith('.'):
165  return message_name[1:]
166  package = package_name.split('.')
167  for levels in range(len(package), -1, -1):
168  candidate = '.'.join(package[0:levels]) + '.' + message_name
169  if candidate in self.name_set:
170  return candidate
171  raise ResolutionError(
172  'Resolving %s in %s failed.' % (message_name, package_name))
173 
174 
175 def GenerateDocumentation(output_file, root):
176  """Recursively generates documentation, sorts and writes it."""
177  message_list = ParseProtoFilesRecursively(root)
178  resolver = Resolver(message.name for message in message_list)
179 
180  output_dict = {}
181  for message in message_list:
182  content = [message.name, '=' * len(message.name), '']
183  assert message.name not in output_dict
184  output_dict[message.name] = content
185  if message.preceding_comments:
186  content.extend(preceding_comments)
187  content.append('')
188  for option_type, option_name, option_comments in message.options:
189  content.append(
190  resolver.Resolve(option_type, message.package) + ' ' + option_name)
191  if not option_comments:
192  option_comments.append(NODOC)
193  for comment in option_comments:
194  content.append(' ' + comment)
195  content.append('')
196  if message.trailing_comments:
197  content.extend(message.trailing_comments)
198  content.append('')
199 
200  output = ['\n'.join(doc) for key, doc in sorted(list(output_dict.items()))]
201  print('\n\n'.join(output), file=output_file)
202 
203 
204 def main():
205  assert not os.path.islink(TARGET) and os.path.isfile(TARGET)
206  assert not os.path.islink(ROOT) and os.path.isdir(ROOT)
207  output_file = io.open(TARGET, mode='w', encoding='UTF-8', newline='\n')
208  output_file.write(PREFIX)
209  GenerateDocumentation(output_file, ROOT)
210  output_file.write(SUFFIX)
211  output_file.close()
212 
213 
214 if __name__ == "__main__":
215  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):
autogenerated on Wed Jun 5 2019 21:57:59