3 # Chocolate Doom self-documentation tool. This works similar to javadoc
4 # or doxygen, but documents command line parameters and configuration
5 # file values, generating documentation in Unix manpage, wikitext and
8 # Comments are read from the source code in the following form:
11 # // @arg <extra arguments>
12 # // @category Category
13 # // @platform <some platform that the parameter is specific to>
15 # // Long description of the parameter
18 # something_involving = M_CheckParm("-param");
20 # For configuration file values:
22 # //! @begin_config_file myconfig.cfg
25 # // Description of the configuration file value.
28 # CONFIG_VARIABLE_INT(my_variable, c_variable),
37 # Find the maximum width of a list of parameters (for plain text output)
39 def parameter_list_width(params
):
54 def __init__(self
, filename
):
55 self
.filename
= filename
58 def add_variable(self
, variable
):
59 self
.variables
.append(variable
)
61 def manpage_output(self
):
62 result
= ".SH CONFIGURATION VARIABLES\n"
64 for v
in self
.variables
:
66 result
+= v
.manpage_output()
70 def plaintext_output(self
):
73 w
= parameter_list_width(self
.variables
)
75 for p
in self
.variables
:
76 result
+= p
.plaintext_output(w
)
78 result
= result
.rstrip() + "\n"
83 def __init__(self
, description
):
84 self
.description
= description
87 def add_param(self
, param
):
88 self
.params
.append(param
)
92 def plaintext_output(self
):
93 result
= "=== %s ===\n\n" % self
.description
97 w
= parameter_list_width(self
.params
)
101 result
+= p
.plaintext_output(w
)
103 result
= result
.rstrip() + "\n"
107 def manpage_output(self
):
108 result
= ".SH " + self
.description
.upper() + "\n"
112 for p
in self
.params
:
115 result
+= p
.manpage_output()
119 def wiki_output(self
):
120 result
= "=== %s ===\n" % self
.description
124 for p
in self
.params
:
126 result
+= "; " + p
.wiki_output() + "\n"
128 # Escape special HTML characters
130 result
= result
.replace("&", "&")
131 result
= result
.replace("<", "<")
132 result
= result
.replace(">", ">")
137 None: Category("General options"),
138 "video": Category("Display options"),
139 "demo": Category("Demo options"),
140 "net": Category("Networking options"),
141 "mod": Category("Dehacked and WAD merging"),
142 "compat": Category("Compatibility"),
148 # Show options that are in Vanilla Doom? Or only new options?
150 show_vanilla_options
= True
153 def __cmp__(self
, other
):
154 if self
.name
< other
.name
:
165 self
.vanilla_option
= False
167 def should_show(self
):
168 return not self
.vanilla_option
or show_vanilla_options
170 def add_text(self
, text
):
174 match
= re
.match('@(\S+)\s*(.*)', text
)
177 raise "Malformed option line: %s" % text
179 option_type
= match
.group(1)
180 data
= match
.group(2)
182 if option_type
== "arg":
184 elif option_type
== "platform":
186 elif option_type
== "category":
188 elif option_type
== "vanilla":
189 self
.vanilla_option
= True
191 raise "Unknown option type '%s'" % option_type
194 self
.text
+= text
+ " "
196 def manpage_output(self
):
200 result
+= " " + self
.args
202 result
= '\\fB' + result
+ '\\fR'
207 result
+= "[%s only] " % self
.platform
209 escaped
= re
.sub('\\\\', '\\\\\\\\', self
.text
)
211 result
+= escaped
+ "\n"
215 def wiki_output(self
):
219 result
+= " " + self
.args
223 result
+= add_wiki_links(self
.text
)
226 result
+= "'''(%s only)'''" % self
.platform
230 def plaintext_output(self
, w
):
232 # Build the first line, with the argument on
234 line
= " " + self
.name
236 line
+= " " + self
.args
238 # pad up to the plaintext width
240 line
+= " " * (w
- len(line
))
242 # Build the description text
244 description
= self
.text
247 description
+= " (%s only)" % self
.platform
249 # Build the complete text for the argument
250 # Split the description into words and add a word at a time
253 for word
in re
.split('\s+', description
):
255 # Break onto the next line?
257 if len(line
) + len(word
) + 1 > 75:
258 result
+= line
+ "\n"
265 result
+= line
+ "\n\n"
269 # Read list of wiki pages
271 def read_wikipages():
272 f
= open("wikipages")
278 line
= re
.sub('\#.*$', '', line
)
280 if not re
.match('^\s*$', line
):
281 wikipages
.append(line
)
285 # Add wiki page links
287 def add_wiki_links(text
):
288 for pagename
in wikipages
:
289 page_re
= re
.compile('(%s)' % pagename
, re
.IGNORECASE
)
290 # text = page_re.sub("SHOES", text)
291 text
= page_re
.sub('[[\\1]]', text
)
295 def add_parameter(param
, line
, config_file
):
297 # Is this documenting a command line parameter?
299 match
= re
.search('M_CheckParm\s*\(\s*"(.*?)"\s*\)', line
)
302 param
.name
= match
.group(1)
303 categories
[param
.category
].add_param(param
)
306 # Documenting a configuration file variable?
308 match
= re
.search('CONFIG_VARIABLE_\S+\s*\(\s*(\S+?),', line
)
311 param
.name
= match
.group(1)
312 config_file
.add_variable(param
)
315 raise Exception(param
.text
)
317 def process_file(file):
319 current_config_file
= None
325 waiting_for_checkparm
= False
332 if re
.match('\s*$', line
):
335 # Currently reading a doc comment?
340 if not re
.match('\s*//', line
):
341 waiting_for_checkparm
= True
343 # The first non-empty line after the documentation comment
344 # ends must contain the thing being documented.
346 if waiting_for_checkparm
:
347 add_parameter(param
, line
, current_config_file
)
350 # More documentation text
352 munged_line
= re
.sub('\s*\/\/\s*', '', line
, 1)
353 munged_line
= re
.sub('\s*$', '', munged_line
)
354 param
.add_text(munged_line
)
356 # Check for start of a doc comment
358 if re
.search("//!", line
):
359 match
= re
.search("@begin_config_file\s*(\S+)", line
)
362 # Beginning a configuration file
363 filename
= match
.group(1)
364 current_config_file
= ConfigFile(filename
)
365 config_files
[filename
] = current_config_file
367 # Start of a normal comment
369 waiting_for_checkparm
= False
373 def process_files(dir):
374 # Process all C source files.
376 if os
.path
.isdir(dir):
377 files
= glob
.glob(dir + "/*.c")
382 # Special case to allow a single file to be specified as a target
386 def print_template(template_file
, content
):
387 f
= open(template_file
)
391 line
= line
.replace("@content", content
)
397 def manpage_output(targets
, template_file
):
402 content
+= t
.manpage_output() + "\n"
404 print_template(template_file
, content
)
406 def wiki_output(targets
, template
):
410 print t
.wiki_output()
412 def plaintext_output(targets
, template_file
):
417 content
+= t
.plaintext_output() + "\n"
419 print_template(template_file
, content
)
422 print "Usage: %s [-V] [-c filename ]( -m | -w | -p ) <directory>" \
424 print " -c : Provide documentation for the specified configuration file"
425 print " -m : Manpage output"
426 print " -w : Wikitext output"
427 print " -p : Plaintext output"
428 print " -V : Don't show Vanilla Doom options"
433 opts
, args
= getopt
.getopt(sys
.argv
[1:], "m:wp:c:V")
435 output_function
= None
437 doc_config_file
= None
441 output_function
= manpage_output
444 output_function
= wiki_output
446 output_function
= plaintext_output
449 show_vanilla_options
= False
451 doc_config_file
= opt
[1]
453 if output_function
== None or len(args
) != 1:
457 # Process specified files
459 process_files(args
[0])
461 # Build a list of things to document
463 documentation_targets
= []
466 documentation_targets
.append(config_files
[doc_config_file
])
468 documentation_targets
.append(categories
[None])
472 documentation_targets
.append(categories
[c
])
474 # Generate the output
476 output_function(documentation_targets
, template
)