1
1
import re , collections , textwrap , sys , argparse , platform
2
2
3
-
4
3
Field = collections .namedtuple ('Field' , ['name' , 'contents' ])
5
4
6
5
Header = collections .namedtuple ('Header' , ['module' ])
7
6
8
7
Function = collections .namedtuple ('Function' ,
9
- ['pos' , ' name' , 'purpose' , 'inputs' , 'returns' ])
8
+ ['name' , 'purpose' , 'inputs' , 'returns' ])
10
9
11
10
Class = collections .namedtuple ('Class' , ['name' , 'purpose' ])
12
11
@@ -23,18 +22,22 @@ def header_from_block(block):
23
22
24
23
def function_from_block (block ):
25
24
""" Create a Function structure from a parsed Block. """
26
- return Function (block .pos , block . fields .get ('Function' , None ),
25
+ return Function (block .fields .get ('Function' , None ),
27
26
block .fields .get ('Purpose' , None ), block .fields .get ('Inputs' , None ),
28
27
block .fields .get ('Outputs' , None ))
29
28
30
29
30
+ def make_field (name , contents ):
31
+ return Field (name , contents if contents .strip () else None )
32
+
33
+
31
34
def class_from_block (block ):
32
35
""" Create a Class structure from a parsed Block. """
33
36
return Class (block .fields .get ('Class' , None ),
34
37
block .fields .get ('Purpose' , None ))
35
38
36
39
37
- def parse_fields (block_contents , width ):
40
+ def parse_fields (block_contents ):
38
41
""" Extract the named fields of an old-style comment block. """
39
42
40
43
field_re = re .compile (
@@ -43,13 +46,13 @@ def parse_fields(block_contents, width):
43
46
for m in field_re .finditer (block_contents ):
44
47
# If the field is a Purpose field
45
48
if m .lastindex == 2 :
46
- yield Field (m .group (1 ), textwrap .dedent (m .group (2 )))
49
+ yield make_field (m .group (1 ), textwrap .dedent (m .group (2 )))
47
50
# If the field is any other field
48
51
elif m .lastindex == 3 or m .lastindex == 4 :
49
- yield Field (m .group (3 ), textwrap .dedent (m .group (4 )))
52
+ yield make_field (m .group (3 ), textwrap .dedent (m .group (4 )))
50
53
51
54
52
- Block = collections .namedtuple ('Block' , ['pos' , ' fields' ])
55
+ Block = collections .namedtuple ('Block' , ['fields' ])
53
56
54
57
55
58
def has_field (block , field_name ):
@@ -58,42 +61,52 @@ def has_field(block, field_name):
58
61
59
62
60
63
def make_doxy_comment (text ):
61
- text , _ = re .subn (r'^(?!$)' , r'/// ' , text , flags = re .MULTILINE )
62
- text , _ = re .subn (r'^(?=$)' , r'///' , text , flags = re .MULTILINE )
63
- return text
64
+ text = re .sub (r'^(?!$)' , r'/// ' , text , flags = re .MULTILINE )
65
+ return re .sub (r'^(?=$)' , r'///' , text , flags = re .MULTILINE )
64
66
65
67
66
- def is_header_doc (block ):
67
- """ Return whether the block appears to be a file header. """
68
- return has_field (block , 'Module' )
68
+ class GenericFormatter (object ):
69
+ def __init__ (self , doc_width ):
70
+ self .text_wrapper = textwrap .TextWrapper (width = doc_width )
71
+ self .indented_wrapper = textwrap .TextWrapper (width = doc_width ,
72
+ subsequent_indent = r' ' )
73
+ self .whitespace_re = re .compile (r'\n\s*' , re .MULTILINE | re .DOTALL )
69
74
75
+ def convert (self , block ):
76
+ sections = filter (None , self .convert_sections (block ))
77
+ if sections :
78
+ return make_doxy_comment ('\n ' .join (sections )) + '\n '
79
+ return ''
70
80
71
- def convert_header_doc (header , doc_width ):
72
- """ Return a doxygen-style header string. """
73
- text_wrapper = textwrap .TextWrapper (width = doc_width )
74
- return (make_doxy_comment (
75
- text_wrapper .fill (r'\file %s' % header .module )) + '\n \n '
76
- if header .module .strip () else '' )
77
81
82
+ class HeaderFormatter (GenericFormatter ):
83
+ def format_module (self , header ):
84
+ if not header .module :
85
+ return None
78
86
79
- def is_function_doc (block ):
80
- """ Return whether the block appears to be a function descriptor. """
81
- return has_field (block , 'Function' )
87
+ subbed = self .whitespace_re .sub (' ' , header .module )
88
+ return self .indented_wrapper .fill (r'\file %s' % subbed )
82
89
90
+ def is_block_valid (self , block ):
91
+ return has_field (block , 'Module' )
83
92
84
- class FunctionFormatter :
93
+ def convert_sections (self , block ):
94
+ return [self .format_module (block )]
95
+
96
+
97
+ class FunctionFormatter (GenericFormatter ):
85
98
def __init__ (self , doc_width ):
86
- self .text_wrapper = textwrap .TextWrapper (width = doc_width )
87
- self .input_wrapper = textwrap .TextWrapper (width = doc_width ,
88
- subsequent_indent = r' ' )
89
- self .whitespace_re = re .compile (r'\n\s*' , re .MULTILINE | re .DOTALL )
99
+ super (FunctionFormatter , self ).__init__ (doc_width )
90
100
self .paragraph_re = re .compile (r'(.*?)^$(.*)' , re .MULTILINE | re .DOTALL )
91
101
92
102
def format_purpose (self , function ):
103
+ if not function .purpose :
104
+ return None
105
+
93
106
match = self .paragraph_re .match (function .purpose )
94
107
first_paragraph = match .group (1 )
95
- first_paragraph , _ = self .whitespace_re .subn (' ' ,
96
- first_paragraph ) if first_paragraph else ( '' , None )
108
+ first_paragraph = self .whitespace_re .sub (' ' ,
109
+ first_paragraph ) if first_paragraph else ''
97
110
98
111
tail_paragraphs = (('\n ' + match .group (2 )) if match .group (2 ) else '' )
99
112
formatted_purpose = (self .text_wrapper .fill (first_paragraph ) +
@@ -102,79 +115,90 @@ def format_purpose(self, function):
102
115
return formatted_purpose .strip ()
103
116
104
117
def format_inputs (self , function ):
118
+ if not function .inputs :
119
+ return None
120
+
121
+ if re .match (r'^\s*\S+\s*$' , function .inputs ):
122
+ return None
123
+
105
124
def param_replacement (match ):
106
125
return r'\param %s:' % match .group (1 )
107
126
108
127
dedented = textwrap .dedent (function .inputs )
109
- text , _ = re .subn (r'\n\s+' , ' ' , dedented , flags = re .MULTILINE )
128
+ text = re .sub (r'\n\s+' , ' ' , dedented , flags = re .MULTILINE )
110
129
text , num_replacements = re .subn (r'^([a-zA-Z0-9_]+)\s+[:-]' ,
111
130
param_replacement , text , flags = re .MULTILINE )
112
131
113
132
if num_replacements == 0 :
114
133
text = r'parameters: %s' % text
115
134
116
- text = '\n ' .join (self .input_wrapper .fill (t ) for t in text .split ('\n ' ))
135
+ text = '\n ' .join (
136
+ self .indented_wrapper .fill (t ) for t in text .split ('\n ' ))
117
137
return text .strip ()
118
138
119
139
def format_returns (self , function ):
120
- subbed , _ = self . whitespace_re . subn ( ' ' , function .returns )
121
- return self . input_wrapper . fill ( r'\returns %s' % subbed )
140
+ if not function .returns :
141
+ return None
122
142
143
+ subbed = self .whitespace_re .sub (' ' , function .returns )
144
+ return self .indented_wrapper .fill (r'\return %s' % subbed )
123
145
124
- def convert_function_doc (function , file , doc_width ):
125
- """ Return a doxygen-style doc string for the supplied Function. """
126
- formatter = FunctionFormatter (doc_width )
146
+ def is_block_valid (self , block ):
147
+ return has_field (block , 'Function' )
127
148
128
- sections = []
149
+ def convert_sections (self , block ):
150
+ return [
151
+ self .format_purpose (block ),
152
+ self .format_inputs (block ),
153
+ self .format_returns (block )]
129
154
130
- if function .purpose and function .purpose .strip ():
131
- sections .append (formatter .format_purpose (function ))
132
155
133
- if function .inputs and function .inputs .strip ():
134
- sections .append (formatter .format_inputs (function ))
135
-
136
- if function .returns and function .returns .strip ():
137
- sections .append (formatter .format_returns (function ))
156
+ class ClassFormatter (GenericFormatter ):
157
+ def __init__ (self , doc_width ):
158
+ super (ClassFormatter , self ).__init__ (doc_width )
159
+ self .paragraph_re = re .compile (r'(.*?)^$(.*)' , re .MULTILINE | re .DOTALL )
138
160
139
- if sections :
140
- text = '\n \n ' .join (sections )
141
- if text :
142
- text = make_doxy_comment (text )
143
- return text + '\n '
161
+ def format_purpose (self , klass ):
162
+ if not klass .purpose :
163
+ return None
144
164
145
- return ''
165
+ match = self .paragraph_re .match (klass .purpose )
166
+ first_paragraph = match .group (1 )
167
+ first_paragraph = self .whitespace_re .sub (' ' ,
168
+ first_paragraph ) if first_paragraph else ''
146
169
170
+ tail_paragraphs = (('\n ' + match .group (2 )) if match .group (2 ) else '' )
171
+ formatted_purpose = (self .text_wrapper .fill (first_paragraph ) +
172
+ tail_paragraphs )
147
173
148
- def is_class_doc (block ):
149
- """ Return whether the block appears to be a class doc block. """
150
- return has_field (block , 'Class' )
174
+ return formatted_purpose .strip ()
151
175
176
+ def is_block_valid (self , block ):
177
+ return has_field (block , 'Class' )
152
178
153
- def convert_class_doc (c , doc_width ):
154
- """ Return a doxygen-style class string. """
155
- text_wrapper = textwrap .TextWrapper (width = doc_width )
156
- stripped = c .purpose .strip ()
157
- return (make_doxy_comment (text_wrapper .fill (stripped )) + '\n '
158
- if stripped else '' )
179
+ def convert_sections (self , block ):
180
+ return [self .format_purpose (block )]
159
181
160
182
161
- def replace_block (start , block_contents , file , doc_width ):
183
+ def replace_block (
184
+ block_contents ,
185
+ file ,
186
+ header_formatter ,
187
+ class_formatter ,
188
+ function_formatter ):
162
189
"""
163
190
Replace an old-style documentation block with the doxygen equivalent
164
191
"""
165
- block = Block (start ,
166
- {f .name : f .contents
167
- for f in parse_fields (block_contents , doc_width )})
192
+ block = Block ({f .name : f .contents for f in parse_fields (block_contents )})
168
193
169
- if is_header_doc (block ):
170
- return convert_header_doc (header_from_block (block ), doc_width )
194
+ if header_formatter . is_block_valid (block ):
195
+ return header_formatter . convert (header_from_block (block ))
171
196
172
- if is_function_doc (block ):
173
- return convert_function_doc (
174
- function_from_block (block ), file , doc_width )
197
+ if class_formatter .is_block_valid (block ):
198
+ return class_formatter .convert (class_from_block (block ))
175
199
176
- if is_class_doc (block ):
177
- return convert_class_doc ( class_from_block (block ), doc_width )
200
+ if function_formatter . is_block_valid (block ):
201
+ return function_formatter . convert ( function_from_block (block ))
178
202
179
203
warn ('block in "%s" has unrecognised format:\n %s' %
180
204
(file , block_contents ))
@@ -187,15 +211,21 @@ def convert_file(file):
187
211
with open (file ) as f :
188
212
contents = f .read ()
189
213
190
- doc_width = 75
214
+ doc_width = 76
215
+
216
+ header_formatter = HeaderFormatter (doc_width )
217
+ class_formatter = ClassFormatter (doc_width )
218
+ function_formatter = FunctionFormatter (doc_width )
191
219
192
220
block_re = re .compile (
193
221
r'^/\*+\\$(.*?)^\\\*+/$\s*' , re .MULTILINE | re .DOTALL )
194
- contents , _ = block_re .subn (
195
- lambda match : replace_block (match .start (), match .group (1 ), file ,
196
- doc_width ), contents )
197
-
198
- sys .stdout .write (contents )
222
+ sys .stdout .write (block_re .sub (
223
+ lambda match : replace_block (
224
+ match .group (1 ),
225
+ file ,
226
+ header_formatter ,
227
+ class_formatter ,
228
+ function_formatter ), contents ))
199
229
200
230
201
231
def main ():
0 commit comments