/**
* Documentation system for removing documentation from source files.
* <p>
* The documentation will be created in the directory /doc/autodoc. Extra
* documentation should be placed into the other help directories and will
* be accessed via the help system. The html documentation will be placed
* into the /www/autodoc directory. The files in theses directories will
* be named with the '/'s replaced with '.'s. ie: /obj/handlers/room_handler.c
* would be in the file /doc/autodoc/obj.handlers.room_handler.c and
* /www/autodoc/obj.handlers.room_handler.c.html. An index of the methods
* and object names will be generated from the input files and linked into the
* help system. An alphabetical index will also be produced that is linked
* to the html stuff.
* <p>
* The first comment which follows the comment format below and occurs right
* before a function will be used as the documentation for that function. The
* comment must start with two '*'s to distinguish it from a normal comment.
* A special tag at the start of one of the first comments in the file will
* signify that it is a comment for the class.
* <p>
* Only comments before a real function declaration will be used, comments
* before a predeclaration will be ignored.
* <p>
* Any function which is not declared as private and does not have the @ignore
* tag in its description will be placed into the resulting documentation
* file. This will be done even if no comment was found.
* <p>
* In the text part of the comment the main section is a free form
* documentation, it is merely a description of the whole function. After
* this several tags can be added to the comment which allows other things
* to be documented. All the text after the tag is taken as part of that
* tags information. The main tag should never actually be used, it
* is implied as being the tag associated with the first section of
* comment.
* <p>
* HTML tags will be used in the comments to allow certain things to
* happen. The tags 'p', 'b', 'i', 'code', 'ul', 'ol', 'dl', 'dd', 'dt', 'pre'
* and 'li' will be supported.
* <p>
* The sequences '\\/', '\\\\' and '\<' will be processed by the comment
* handling code so that they do not get placed into the output code.
* <p>
* White space is ignored... Both html and roff format files will be
* created from the documentation maker.
* <p>
* The supported tags are:
* <dl>
* <dt>@param
* <dd>Specify the parameter and what it does. This should be one short
* sentence only. There needs to be one of these for every
* parameter, the first word of the sentence is the name of the
* parameter and the rest is the description. There should be no full
* stop on the end of the sentance.
* <dt>@member
* <dd>This is used with classes and defines the members of the class. The
* definition is the same as for the @param element above.
* <dt>@return
* <dd>Specify what the return value is. This should be one short
* sentence only. Theres should only be one of these in every
* function. There should be no full stop on the end of the sentence.
* <dt>@see
* <dd>Specify an object or function to see. The format must be one of:
* <ul>
* <li> object_name<br><i>@see /global/new_parse</i>
* <li> function_name()<br><i>@see frog()</i>
* <li> object_name->function_name()<br><i>@see /global/new_parse->add_command()</i>
* <li> efun::name()<br><i>@see efun::sort_array()</i>
* <li> help::name<br><i>@see help::effects</i>
* </ul>
* <dt>@ignore
* <dd>Do not document this function, the whole comment is ignored.
* <dt>@main
* <dd>The main documentation section.
* <dt>@classification
* <dd>Used in the effects files to classify the effect. So a tree
* of the classifications can be derived.
* <dt>@index
* <dd>Adds in a new index reference on this name. This should be one
* word only.
* <dt>@change
* <dd>Placed in the class documentation to reference a change list.
* First line after the tag is the date and the author and
* then follows a description of the change.
* <dt>@example
* <dd>If you wish to embed an example in the code.
* <dt>@started
* <dd>When the file was started.
* <dt>@author
* <dd>The author of the file.
* </dl>
*
* The output format will look something like the API documentation for
* java. If you have not seen this before, go to http://www.javasoft.com
* and look in the JDK and then the API section.
* <p>
* Comment format:
* <pre>
* /\**
* * <text>
* * ..
* * <text>
* *
* *\/
* </pre>
*
* ie: it starts with a /\** and ends with a *\/ on a line by themselves and
* every intervening line has a * at the start. By using a \\ you can
* make characters escaped. This means they are not processed by
* the internal system, so to make a end comment appear inside a
* the text you go : *\\/
*
* @example
* inherit "/std/object";
*
* /\**
* * This class is a nice shade of blue and allows all other shades of
* * green and yellow to mix well.
* * @see yellow
* * @see blue
* * @change 12/3/97 Pinkfish
* * Pushed some of the yellow stuff down a hole and ate a bagel.
* *\/
*
* /\**
* * This method mixes the green in with our blue.
* * @param green the green object to mix.
* *\/
* void mixGreen(object green) {
* /\* Something wild and exciting! *\/
* } /\* mixGreen() *\/
*
* /\**
* * This method mixes the yellow in with our blue.
* * @param yellow the yellow object to mix.
* * @return returns a TRUE or FALSE depending on if the mix succeeded.
* * @example
* * mixYellow(12);
* *\/
* int mixYellow(object yellow) {
* } /\* mixYellow() *\/
*
* @index autodoc
* @see autodoc_file
* @see autodoc_nroff
* @see autodoc_html
* @author Pinkfish
* @started Tue Oct 28 13:25:09 EST 1997
*
*/
#include <autodoc.h>
#define AUTODOC_SAVE_FILE (AUTODOC_SAVE_DIR "main_rubbish")
string *files;
mapping help_map;
int file_pos, something_changed, last_created_index;
nosave int call_id;
nosave mapping summary_map;
nosave mixed extra_indexes;
private void create_nroff_file( object ob );
private void create_html_index( mixed ob );
private void after_thingy( int no_index );
private void start_processing();
private void do_parse_next_file();
private void create_index();
private void add_to_index( object ob );
private void load();
private void save();
string *query_files();
int remove_file( string fname );
void set_file_pos(int number) { file_pos = number; }
void create() {
seteuid(getuid());
file_pos = 0;
something_changed = 0;
files = ({ });
load();
if( !mapp( help_map ) )
help_map = ([ ]);
start_processing();
} /* create() */
/** @ignore yes */
private void create_nroff_file( object ob ) {
string fname;
/* Now we create the nroff file... */
fname = replace( ob->query_file_name(), "/", ".");
if( fname[0] == '.')
fname = fname[1..];
rm( NROFF_DOC_DIR + fname );
event( users(), "inform", "Creating "+NROFF_DOC_DIR + fname, "autodoc");
AUTODOC_NROFF->create_nroff_file( ob, NROFF_DOC_DIR + fname );
} /* create_nroff_file() */
/** @ignore yes */
private void create_html_index( mixed ob ) {
string fname, letter;
mixed stuff, tmp;
mapping chars;
/* Create an index file... */
stuff = map( query_files(), function( string name ) {
string *bits;
bits = explode(name, "/");
return ({ bits[sizeof(bits)-1], name, "", summary_map[name] });
} );
stuff = unique_array( ob + stuff, (: lower_case($1[0])[0] :));
chars = ([ ]);
foreach( tmp in stuff ) {
letter = tmp[0][0][0..0];
fname = "index_" + lower_case(letter) + ".html";
AUTODOC_HTML->create_html_index_file( tmp, letter,
HTML_DOC_DIR + fname );
reset_eval_cost();
chars[CAP(letter)] = fname;
}
/* The different sorts of indexes will be genertated in the html file */
fname = HTML_DOC_DIR;;
AUTODOC_HTML->create_main_index( chars, fname );
} /* create_html_file() */
/** @ignore yes */
private void after_thingy( int no_index ) {
if( member_array( PO->query_file_name(), files ) == -1 ) {
call_out( (: $1->dest_me() :), 20, PO );
return;
}
if( no_index )
event( users(), "inform", "Autodoc: finished recreating the "
"documentation for "+PO->query_file_name(), "autodoc");
if( PO->query_changed() )
something_changed = 1;
event( users(), "inform", "Autodoc: \""+PO->query_file_name()+"\" "+
( PO->query_num_failed_tries() ? "failed to parse correctly!" :
"parsed correctly." ), "autodoc");
// This means that the file has been deleted... Argh! Auto remove it.
// Delete PO->query_file_name(), not some file_pos - Sandoz.
if( PO->query_num_failed_tries() > 1 )
remove_file( PO->query_file_name() );
else if( PO->query_changed() || no_index )
call_out( (: create_nroff_file :), 0, PO );
call_out( (: $1->dest_me() :), 20, PO );
save();
} /* after_thingy() */
/**
* This method queries whether or not the documentation
* for the specified file has expired.
* @param name the file name to check
* @return 1 if an update is needed
*/
int query_update_needed( string name ) {
int cur_change;
string my_name, dotless;
// No such file or directory.
if( !sizeof( unguarded( (: stat($(name)) :) ) ) ) {
remove_file( name );
return 0;
}
// A directory - always update.
if( dir_exists(name) )
return 1;
my_name = replace_string( name, "/", ".");
dotless = ( my_name[0] == '.' ? my_name[1..] : my_name );
if( my_name[<2..] == ".c" )
my_name = my_name[0..<3];
// No save file.
if( !unguarded( (: file_exists($(AUTODOC_SAVE_DIR+my_name+".o")) :) ) )
return 1;
// No NROFF file.
if( !unguarded( (: file_exists($(NROFF_DOC_DIR+dotless)) :) ) )
return 1;
cur_change = unguarded( (: stat($(name)) :) )[1];
// The main file is outdated.
if( cur_change > unguarded( (: stat($(AUTODOC_SAVE_DIR+my_name+".o")) :) )[1] )
return 1;
// The nroff docs are outdated.
if( cur_change > unguarded( (: stat($(NROFF_DOC_DIR+dotless)) :) )[1] )
return 1;
return 0;
} /* query_update_needed() */
/** @ignore yes */
private void do_parse_next_file() {
/* Give it a minute to do this file... */
call_id = call_out( (: start_processing() :), 60 );
if( file_pos >= sizeof(files) ) {
file_pos = 0;
return;
}
if( query_update_needed(files[file_pos]) )
new(AUTODOC_FILE)->parse_file( files[file_pos], (: after_thingy(0) :) );
else
event( users(), "inform", "Autodoc: Docs for \""+
files[file_pos]+"\" already up to date", "autodoc");
file_pos++;
} /* do_parse_next_file() */
/** @ignore yes */
private void start_processing() {
if( !sizeof(files) )
return;
/* Ok, now we skip onto the next file and zoom it... */
if( last_created_index + SAVE_INDEX_DELAY < time() ) {
if( something_changed )
call_out( (: create_index() :), 2 );
something_changed = 0;
save();
}
call_id = call_out( (: do_parse_next_file() :), 180 );
} /* start_processing() */
/** @ignore yes */
private string query_short_args_def( mixed args ) {
string ret;
int i;
ret = "";
for( i = 0; i < sizeof(args); i += 2 ) {
if( i != 0 )
ret += ", ";
ret += implode( args[AUTO_ARGS_TYPE], " ");
}
return "(" + ret + ")";
} /* query_short_args_def() */
/**
* @ignore yes
* This will add it into the help array stuff too.
*/
private string *process_stuff( string name, string fname, string fn,
mapping docs ) {
string blue;
int i, end;
mixed ret;
mapping fluff;
if( name != "create" &&
name != "setup" &&
name != "init" &&
name != "reset" &&
name != "dest_me") {
if( help_map[name] )
help_map[name] += ({ fn });
else
help_map[name] = ({ fn });
}
if( docs[name] ) {
if( arrayp(docs[name]) ) {
/* Its a function! */
fluff = docs[name][AUTO_DOCS];
ret = ({ name, fname, query_short_args_def(docs[name][AUTO_ARGS]) });
} else {
ret = ({ name, fname, "" });
fluff = docs[name];
}
} else {
ret = ({ name, fname, "" });
if( mapp(docs) ) {
fluff = docs;
} else {
fluff = ([ ]);
}
}
if( fluff["main"] ) {
blue = implode( fluff["main"], "\n");
end = strlen(blue);
i = strsrch( blue, ".");
if( i > 0 )
end = i;
i = strsrch( blue, "!");
if( i > 0 && i < end )
end = i;
i = strsrch( blue, "?");
if( i > 0 && i < end )
end = i;
blue = blue[0..end];
}
ret += ({ blue });
if( fluff["index"] ) {
foreach( blue in fluff["index"] ) {
blue = replace( blue, ({ " ", "", "\n", "" }));
if( help_map[blue] ) {
help_map[blue] += ({ fn });
} else {
help_map[blue] = ({ fn });
}
extra_indexes += ({ ({ blue, fname, "", ret[AUTO_INDEX_SUMMARY] }) });
}
}
return ret;
} /* process_stuff() */
/** @ignore yes */
private void create_index() {
mixed index_stuff, rabbit;
string fname, fn, *bits, file;
object parse;
/* Build up the list... */
index_stuff = ({ });
help_map = ([ ]);
extra_indexes = ({ });
summary_map = ([ ]);
parse = clone_object(AUTODOC_FILE);
foreach( file in files ) {
/* Do not process it... Merely load it from disk */
parse->parse_file( file, 0, 1 );
fname = parse->query_file_name();
if( fname ) {
fn = replace( fname, "/", ".");
if( fn[0] == '.' )
fn = fn[1..];
fn = NROFF_DOC_DIR + fn;
bits = explode( fname[0..strlen(fname)-3], "/");
rabbit = process_stuff( bits[sizeof(bits) - 1], "", fn,
parse->query_main_docs() );
if( rabbit[AUTO_INDEX_SUMMARY] ) {
summary_map[fname] = rabbit[AUTO_INDEX_SUMMARY];
} else {
map_delete( summary_map, fname );
}
/* We loaded it... */
index_stuff += map( keys( parse->query_public_functions() ),
(: process_stuff( $1, $(fname), $(fn),
$(parse)->query_public_functions() ) :) );
index_stuff += map( keys( parse->query_protected_functions() ),
(: process_stuff( $1, $(fname), $(fn),
$(parse)->query_protected_functions() ) :) );
index_stuff += map( keys( parse->query_class_docs() ),
(: process_stuff( $1, $(fname), $(fn),
$(parse)->query_class_docs() ) :) );
if( sscanf( fname, "%*s.h" ) == 1 ) {
index_stuff += map( keys( parse->query_define_docs() ),
(: process_stuff( $1, $(fname), $(fn),
$(parse)->query_define_docs() ) :) );
}
}
reset_eval_cost();
}
index_stuff += extra_indexes;
extra_indexes = ({ });
/* Don't call this out. We would be copying huge arrays around. Eeek. */
catch( create_html_index( index_stuff ) );
summary_map = ([ ]);
last_created_index = time();
save();
event( users(), "inform", "Autodoc: Index generation successful",
"autodoc");
} /* create_index() */
/**
* Recreate documentation for one field immediately. This does not update
* the index, it merely creates the base files for this file. It does it
* at a random delay of up to 30 seconds.
* @param fname the file name to update
*/
int recreate_documentation( string fname ) {
object ob;
if( member_array( fname, files ) != -1 ) {
ob = clone_object(AUTODOC_FILE);
ob->parse_file( fname, (: after_thingy(1) :) );
return 1;
}
return 0;
} /* recreate_documentation() */
/**
* Regenerate the index files.
*/
void recreate_indexes() {
unguarded( (: create_index() :) );
} /* recreate_indexes() */
/**
* Adds a file into the list of files to process for autodocumentation.
* This file will be processed every time around in the autodocumentation
* loop to check to see if it has been changed and the results will be
* placed into the index. It automaticly calls recreate_documentation
* in random(60) seconds.
* @see recreate_documentation()
* @param fname the name of the file to add
* @return 1 if the add succeeds, 0 otherwise.
*/
int add_file( string fname ) {
fname = "/" + implode( explode( fname, "/" ) - ({ "" }), "/" );
if( fname[0..2] == "/w/" || fname[0..2] == "/d/" )
event( users(), "inform", "Autodoc: Adding \""+
fname+"\" to autodoc handler!", "autodoc");
if( member_array( fname, files ) == -1 ) {
if( sizeof( unguarded( (: stat($(fname)) :) ) ) ) {
files += ({ fname });
save();
if( sizeof(files) == 1 )
start_processing();
else
call_out( (: recreate_documentation($1) :), random(60), fname );
return 1;
}
}
return 0;
} /* add_file() */
/**
* Removes a file from the list of files to be processed
* for autodocumentation.
* @param fname the name of the file to remove
* @return 1 if the remove succeeds, 0 otherwise.
*/
int remove_file( string fname ) {
string fn, dfn, *our_files;
int i;
if( member_array( fname, files ) != -1 ) {
files -= ({ fname });
file_pos = 0;
fn = replace_string( fname, "/", ".");
dfn = ( fn[0] == '.' ? fn[1..] : fn );
if( fn[<2..] == ".c" )
fn = fn[0..<3];
fn = AUTODOC_SAVE_DIR+fn+".o";
// Delete the save file.
unguarded( (: rm($(fn)) :) );
// Delete the nroff files.
unguarded( (: rm( NROFF_DOC_DIR+$(dfn) ) :) );
// Delete the nroff single function files.
fn = NROFF_DOC_SINGLE+( fname[0] == '/' ? fname[1..<3] :
fname[0..<3] )+"/";
our_files = get_dir(fn);
if( sizeof(our_files) )
foreach( dfn in our_files )
unguarded( (: rm($(fn+dfn)) :) );
// Remove the directory if it exists.
if( dir_exists(fn) )
unguarded( (: rmdir($(fn)) :) );
// Try to remove the next upper directory as well, if it's empty.
if( ( i = strsrch( fn[0..<2], "/", -1 ) ) != -1 ) {
fn = fn[0..i];
if( dir_exists(fn) && !sizeof( get_dir(fn) ) )
unguarded( (: rmdir($(fn)) :) );
}
save();
if( !sizeof(files) )
remove_call_out(call_id);
event( users(), "inform", "Autodoc: removing \""+
fname+"\""+( !file_exists(fname) ? " (doesn't exist)" :
"")+" from the autodoc handler", "autodoc");
return 1;
}
if( TP )
tell_object( TP, "There is no such file in the autodoc "
"handler.\n");
return 0;
} /* remove_file() */
private void load() {
unguarded( (: restore_object( AUTODOC_SAVE_FILE ) :) );
} /* load() */
private void save() {
unguarded( (: save_object( AUTODOC_SAVE_FILE ) :) );
} /* save() */
/**
* Returns the list of files we are current processing.
* @return an array of strings being the file names
*/
string *query_files() { return files; }
/**
* Returns the help mapping. This is the mapping from function names to
* files. Each element in the mapping referes to an array of file
* names which contain the function or define.
* @return a mapping of arrays of files
*/
mapping query_help_map() { return help_map; }
/**
* Returns the number of functions in the help file mapping.
* @return the number of functions
*/
int query_help_map_size() { return sizeof(help_map); }
/**
* This method returns the array of help files with the
* specified function in it.
* @param func the name of the function to get the help files for
* @return array of file names
*/
string *query_help_on_func( string func ) { return help_map[func]; }
/**
* This method returns the file name of the help for the specified
* function in the specified file.
* @param file the file name to find the help in
* @param func the function to look for help on in the file
* @return the full path to the help file, 0 if it does not exist
*/
string query_help_on( string file, string func ) {
if( file[0] == '/' )
file = file[1..];
if( sscanf(file, "%*s.c") == 1 )
file = file[0..<3];
file = NROFF_DOC_SINGLE + file + "/" + func;
if( file_size(file) > 0 )
return file;
return 0;
} /* query_help_on() */
/**
* Returns the mapping of file names to a summary. This is only
* valid during the index creation cycle of the documentation generation
* system. It is used to create a more useful index page.
* @return the mapping of file names to summarys
*/
mapping query_summary_map() { return summary_map; }
/**
* This method tells us if the file is currently in the autodoc set.
* @return 1 if the file is found, 0 if not
*/
int is_autodoc_file(string name) {
return member_array( name, files ) != -1;
} /* is_autodoc_file() */