Skip to content

Commit 4e6c56e

Browse files
committed
Update script based on review feedback
1 parent 86776e8 commit 4e6c56e

File tree

1 file changed

+106
-76
lines changed

1 file changed

+106
-76
lines changed

convert.py

Lines changed: 106 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import re, collections, textwrap, sys, argparse, platform
22

3-
43
Field = collections.namedtuple('Field', ['name', 'contents'])
54

65
Header = collections.namedtuple('Header', ['module'])
76

87
Function = collections.namedtuple('Function',
9-
['pos', 'name', 'purpose', 'inputs', 'returns'])
8+
['name', 'purpose', 'inputs', 'returns'])
109

1110
Class = collections.namedtuple('Class', ['name', 'purpose'])
1211

@@ -23,18 +22,22 @@ def header_from_block(block):
2322

2423
def function_from_block(block):
2524
""" 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),
2726
block.fields.get('Purpose', None), block.fields.get('Inputs', None),
2827
block.fields.get('Outputs', None))
2928

3029

30+
def make_field(name, contents):
31+
return Field(name, contents if contents.strip() else None)
32+
33+
3134
def class_from_block(block):
3235
""" Create a Class structure from a parsed Block. """
3336
return Class(block.fields.get('Class', None),
3437
block.fields.get('Purpose', None))
3538

3639

37-
def parse_fields(block_contents, width):
40+
def parse_fields(block_contents):
3841
""" Extract the named fields of an old-style comment block. """
3942

4043
field_re = re.compile(
@@ -43,13 +46,13 @@ def parse_fields(block_contents, width):
4346
for m in field_re.finditer(block_contents):
4447
# If the field is a Purpose field
4548
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)))
4750
# If the field is any other field
4851
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)))
5053

5154

52-
Block = collections.namedtuple('Block', ['pos', 'fields'])
55+
Block = collections.namedtuple('Block', ['fields'])
5356

5457

5558
def has_field(block, field_name):
@@ -58,42 +61,52 @@ def has_field(block, field_name):
5861

5962

6063
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)
6466

6567

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)
6974

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 ''
7080

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 '')
7781

82+
class HeaderFormatter(GenericFormatter):
83+
def format_module(self, header):
84+
if not header.module:
85+
return None
7886

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)
8289

90+
def is_block_valid(self, block):
91+
return has_field(block, 'Module')
8392

84-
class FunctionFormatter:
93+
def convert_sections(self, block):
94+
return [self.format_module(block)]
95+
96+
97+
class FunctionFormatter(GenericFormatter):
8598
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)
90100
self.paragraph_re = re.compile(r'(.*?)^$(.*)', re.MULTILINE | re.DOTALL)
91101

92102
def format_purpose(self, function):
103+
if not function.purpose:
104+
return None
105+
93106
match = self.paragraph_re.match(function.purpose)
94107
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 ''
97110

98111
tail_paragraphs = (('\n' + match.group(2)) if match.group(2) else '')
99112
formatted_purpose = (self.text_wrapper.fill(first_paragraph) +
@@ -102,79 +115,90 @@ def format_purpose(self, function):
102115
return formatted_purpose.strip()
103116

104117
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+
105124
def param_replacement(match):
106125
return r'\param %s:' % match.group(1)
107126

108127
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)
110129
text, num_replacements = re.subn(r'^([a-zA-Z0-9_]+)\s+[:-]',
111130
param_replacement, text, flags=re.MULTILINE)
112131

113132
if num_replacements == 0:
114133
text = r'parameters: %s' % text
115134

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'))
117137
return text.strip()
118138

119139
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
122142

143+
subbed = self.whitespace_re.sub(' ', function.returns)
144+
return self.indented_wrapper.fill(r'\return %s' % subbed)
123145

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')
127148

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)]
129154

130-
if function.purpose and function.purpose.strip():
131-
sections.append(formatter.format_purpose(function))
132155

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)
138160

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
144164

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 ''
146169

170+
tail_paragraphs = (('\n' + match.group(2)) if match.group(2) else '')
171+
formatted_purpose = (self.text_wrapper.fill(first_paragraph) +
172+
tail_paragraphs)
147173

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()
151175

176+
def is_block_valid(self, block):
177+
return has_field(block, 'Class')
152178

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)]
159181

160182

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):
162189
"""
163190
Replace an old-style documentation block with the doxygen equivalent
164191
"""
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)})
168193

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))
171196

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))
175199

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))
178202

179203
warn('block in "%s" has unrecognised format:\n%s' %
180204
(file, block_contents))
@@ -187,15 +211,21 @@ def convert_file(file):
187211
with open(file) as f:
188212
contents = f.read()
189213

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)
191219

192220
block_re = re.compile(
193221
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))
199229

200230

201231
def main():

0 commit comments

Comments
 (0)