piotr | 437f546 | 2014-02-04 17:57:25 +0100 | [diff] [blame] | 1 | # |
| 2 | # Copyright 2010,2011 Free Software Foundation, Inc. |
| 3 | # |
| 4 | # This file is part of GNU Radio |
| 5 | # |
| 6 | # GNU Radio is free software; you can redistribute it and/or modify |
| 7 | # it under the terms of the GNU General Public License as published by |
| 8 | # the Free Software Foundation; either version 3, or (at your option) |
| 9 | # any later version. |
| 10 | # |
| 11 | # GNU Radio is distributed in the hope that it will be useful, |
| 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | # GNU General Public License for more details. |
| 15 | # |
| 16 | # You should have received a copy of the GNU General Public License |
| 17 | # along with GNU Radio; see the file COPYING. If not, write to |
| 18 | # the Free Software Foundation, Inc., 51 Franklin Street, |
| 19 | # Boston, MA 02110-1301, USA. |
| 20 | # |
| 21 | """ |
| 22 | Creates the swig_doc.i SWIG interface file. |
| 23 | Execute using: python swig_doc.py xml_path outputfilename |
| 24 | |
| 25 | The file instructs SWIG to transfer the doxygen comments into the |
| 26 | python docstrings. |
| 27 | |
| 28 | """ |
| 29 | |
| 30 | import sys |
| 31 | |
| 32 | try: |
| 33 | from doxyxml import DoxyIndex, DoxyClass, DoxyFriend, DoxyFunction, DoxyFile, base |
| 34 | except ImportError: |
| 35 | from gnuradio.doxyxml import DoxyIndex, DoxyClass, DoxyFriend, DoxyFunction, DoxyFile, base |
| 36 | |
| 37 | |
| 38 | def py_name(name): |
| 39 | bits = name.split('_') |
| 40 | return '_'.join(bits[1:]) |
| 41 | |
| 42 | def make_name(name): |
| 43 | bits = name.split('_') |
| 44 | return bits[0] + '_make_' + '_'.join(bits[1:]) |
| 45 | |
| 46 | |
| 47 | class Block(object): |
| 48 | """ |
| 49 | Checks if doxyxml produced objects correspond to a gnuradio block. |
| 50 | """ |
| 51 | |
| 52 | @classmethod |
| 53 | def includes(cls, item): |
| 54 | if not isinstance(item, DoxyClass): |
| 55 | return False |
| 56 | # Check for a parsing error. |
| 57 | if item.error(): |
| 58 | return False |
| 59 | return item.has_member(make_name(item.name()), DoxyFriend) |
| 60 | |
| 61 | |
| 62 | def utoascii(text): |
| 63 | """ |
| 64 | Convert unicode text into ascii and escape quotes. |
| 65 | """ |
| 66 | if text is None: |
| 67 | return '' |
| 68 | out = text.encode('ascii', 'replace') |
| 69 | out = out.replace('"', '\\"') |
| 70 | return out |
| 71 | |
| 72 | |
| 73 | def combine_descriptions(obj): |
| 74 | """ |
| 75 | Combines the brief and detailed descriptions of an object together. |
| 76 | """ |
| 77 | description = [] |
| 78 | bd = obj.brief_description.strip() |
| 79 | dd = obj.detailed_description.strip() |
| 80 | if bd: |
| 81 | description.append(bd) |
| 82 | if dd: |
| 83 | description.append(dd) |
| 84 | return utoascii('\n\n'.join(description)).strip() |
| 85 | |
| 86 | |
| 87 | entry_templ = '%feature("docstring") {name} "{docstring}"' |
| 88 | def make_entry(obj, name=None, templ="{description}", description=None): |
| 89 | """ |
| 90 | Create a docstring entry for a swig interface file. |
| 91 | |
| 92 | obj - a doxyxml object from which documentation will be extracted. |
| 93 | name - the name of the C object (defaults to obj.name()) |
| 94 | templ - an optional template for the docstring containing only one |
| 95 | variable named 'description'. |
| 96 | description - if this optional variable is set then it's value is |
| 97 | used as the description instead of extracting it from obj. |
| 98 | """ |
| 99 | if name is None: |
| 100 | name=obj.name() |
| 101 | if "operator " in name: |
| 102 | return '' |
| 103 | if description is None: |
| 104 | description = combine_descriptions(obj) |
| 105 | docstring = templ.format(description=description) |
| 106 | if not docstring: |
| 107 | return '' |
| 108 | return entry_templ.format( |
| 109 | name=name, |
| 110 | docstring=docstring, |
| 111 | ) |
| 112 | |
| 113 | |
| 114 | def make_func_entry(func, name=None, description=None, params=None): |
| 115 | """ |
| 116 | Create a function docstring entry for a swig interface file. |
| 117 | |
| 118 | func - a doxyxml object from which documentation will be extracted. |
| 119 | name - the name of the C object (defaults to func.name()) |
| 120 | description - if this optional variable is set then it's value is |
| 121 | used as the description instead of extracting it from func. |
| 122 | params - a parameter list that overrides using func.params. |
| 123 | """ |
| 124 | if params is None: |
| 125 | params = func.params |
| 126 | params = [prm.declname for prm in params] |
| 127 | if params: |
| 128 | sig = "Params: (%s)" % ", ".join(params) |
| 129 | else: |
| 130 | sig = "Params: (NONE)" |
| 131 | templ = "{description}\n\n" + sig |
| 132 | return make_entry(func, name=name, templ=utoascii(templ), |
| 133 | description=description) |
| 134 | |
| 135 | |
| 136 | def make_class_entry(klass, description=None): |
| 137 | """ |
| 138 | Create a class docstring for a swig interface file. |
| 139 | """ |
| 140 | output = [] |
| 141 | output.append(make_entry(klass, description=description)) |
| 142 | for func in klass.in_category(DoxyFunction): |
| 143 | name = klass.name() + '::' + func.name() |
| 144 | output.append(make_func_entry(func, name=name)) |
| 145 | return "\n\n".join(output) |
| 146 | |
| 147 | |
| 148 | def make_block_entry(di, block): |
| 149 | """ |
| 150 | Create class and function docstrings of a gnuradio block for a |
| 151 | swig interface file. |
| 152 | """ |
| 153 | descriptions = [] |
| 154 | # Get the documentation associated with the class. |
| 155 | class_desc = combine_descriptions(block) |
| 156 | if class_desc: |
| 157 | descriptions.append(class_desc) |
| 158 | # Get the documentation associated with the make function |
| 159 | make_func = di.get_member(make_name(block.name()), DoxyFunction) |
| 160 | make_func_desc = combine_descriptions(make_func) |
| 161 | if make_func_desc: |
| 162 | descriptions.append(make_func_desc) |
| 163 | # Get the documentation associated with the file |
| 164 | try: |
| 165 | block_file = di.get_member(block.name() + ".h", DoxyFile) |
| 166 | file_desc = combine_descriptions(block_file) |
| 167 | if file_desc: |
| 168 | descriptions.append(file_desc) |
| 169 | except base.Base.NoSuchMember: |
| 170 | # Don't worry if we can't find a matching file. |
| 171 | pass |
| 172 | # And join them all together to make a super duper description. |
| 173 | super_description = "\n\n".join(descriptions) |
| 174 | # Associate the combined description with the class and |
| 175 | # the make function. |
| 176 | output = [] |
| 177 | output.append(make_class_entry(block, description=super_description)) |
| 178 | creator = block.get_member(block.name(), DoxyFunction) |
| 179 | output.append(make_func_entry(make_func, description=super_description, |
| 180 | params=creator.params)) |
| 181 | return "\n\n".join(output) |
| 182 | |
| 183 | |
| 184 | def make_swig_interface_file(di, swigdocfilename, custom_output=None): |
| 185 | |
| 186 | output = [""" |
| 187 | /* |
| 188 | * This file was automatically generated using swig_doc.py. |
| 189 | * |
| 190 | * Any changes to it will be lost next time it is regenerated. |
| 191 | */ |
| 192 | """] |
| 193 | |
| 194 | if custom_output is not None: |
| 195 | output.append(custom_output) |
| 196 | |
| 197 | # Create docstrings for the blocks. |
| 198 | blocks = di.in_category(Block) |
| 199 | make_funcs = set([]) |
| 200 | for block in blocks: |
| 201 | try: |
| 202 | make_func = di.get_member(make_name(block.name()), DoxyFunction) |
| 203 | make_funcs.add(make_func.name()) |
| 204 | output.append(make_block_entry(di, block)) |
| 205 | except block.ParsingError: |
| 206 | print('Parsing error for block %s' % block.name()) |
| 207 | |
| 208 | # Create docstrings for functions |
| 209 | # Don't include the make functions since they have already been dealt with. |
| 210 | funcs = [f for f in di.in_category(DoxyFunction) if f.name() not in make_funcs] |
| 211 | for f in funcs: |
| 212 | try: |
| 213 | output.append(make_func_entry(f)) |
| 214 | except f.ParsingError: |
| 215 | print('Parsing error for function %s' % f.name()) |
| 216 | |
| 217 | # Create docstrings for classes |
| 218 | block_names = [block.name() for block in blocks] |
| 219 | klasses = [k for k in di.in_category(DoxyClass) if k.name() not in block_names] |
| 220 | for k in klasses: |
| 221 | try: |
| 222 | output.append(make_class_entry(k)) |
| 223 | except k.ParsingError: |
| 224 | print('Parsing error for class %s' % k.name()) |
| 225 | |
| 226 | # Docstrings are not created for anything that is not a function or a class. |
| 227 | # If this excludes anything important please add it here. |
| 228 | |
| 229 | output = "\n\n".join(output) |
| 230 | |
| 231 | swig_doc = file(swigdocfilename, 'w') |
| 232 | swig_doc.write(output) |
| 233 | swig_doc.close() |
| 234 | |
| 235 | if __name__ == "__main__": |
| 236 | # Parse command line options and set up doxyxml. |
| 237 | err_msg = "Execute using: python swig_doc.py xml_path outputfilename" |
| 238 | if len(sys.argv) != 3: |
| 239 | raise StandardError(err_msg) |
| 240 | xml_path = sys.argv[1] |
| 241 | swigdocfilename = sys.argv[2] |
| 242 | di = DoxyIndex(xml_path) |
| 243 | |
| 244 | # gnuradio.gr.msq_queue.insert_tail and delete_head create errors unless docstrings are defined! |
| 245 | # This is presumably a bug in SWIG. |
| 246 | #msg_q = di.get_member(u'gr_msg_queue', DoxyClass) |
| 247 | #insert_tail = msg_q.get_member(u'insert_tail', DoxyFunction) |
| 248 | #delete_head = msg_q.get_member(u'delete_head', DoxyFunction) |
| 249 | output = [] |
| 250 | #output.append(make_func_entry(insert_tail, name='gr_py_msg_queue__insert_tail')) |
| 251 | #output.append(make_func_entry(delete_head, name='gr_py_msg_queue__delete_head')) |
| 252 | custom_output = "\n\n".join(output) |
| 253 | |
| 254 | # Generate the docstrings interface file. |
| 255 | make_swig_interface_file(di, swigdocfilename, custom_output=custom_output) |