/* -*- LPC -*- */ /* * $Locker: $ * $Id: autodoc_handler.c,v 1.19 1999/10/26 00:57:49 pinkfish Exp $ * */ /** * 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 dw nroff 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 * sentance only. There needs to be one of these for every * parameter, the first word of the sentance is the name of the * paramater and the rest is the descrption. 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 * sentance only. Theres should only be one of these in every * function. There should be no full stop on the end of the sentance. * <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>@see help::effects * </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 classifiy the effect. So a tree * of the classifications can be derived. * <dt>@index * <dd>Adds in a new index refernece 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 /obj/handlers/autodoc/autodoc_file * @see /obj/handlers/autodoc/autodoc_nroff * @see /obj/handlers/autodoc/autodoc_html * @author Pinkfish * @started Tue Oct 28 13:25:09 EST 1997 * */ #include <autodoc.h> #define MAIN_FILE 0 #define INDEX_FILE 1 #define SAVE_FILE (SAVE_DIR "main_rubbish") string *files; int file_pos; int something_changed; int last_created_index; nosave function *filters; nosave int call_id; nosave mapping summary_map; nosave mixed *extra_indexes; mapping help_map; private void create_nroff_file(object ob, int type); private void create_html_file(object ob, int type); 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 create() { seteuid(getuid()); filters = ({ (: create_nroff_file($1, $2) :), (: create_html_file($1, $2) :) }); file_pos = 0; something_changed = 0; files = ({ }); load(); do_parse_next_file(); /* For testing... */ //create_index(); } /* create() */ private void create_nroff_file(mixed ob, int type) { string fname; mixed *stuff; mixed *index_stuff; int i; if (type == MAIN_FILE) { /* Now we create the nroff file... */ fname = replace(ob->query_file_name(), "/", "."); if (fname[0] == '.') { fname = fname[1..]; } rm(NROFF_DOC_DIR + fname); AUTODOC_NROFF->create_nroff_file(ob, NROFF_DOC_DIR + fname); } else if (type == INDEX_FILE) { /* Create an index file... */ /* Divide into letters... */ index_stuff = ob; stuff = map(query_files(), function (string name) { string *bits; bits = explode(name, "/"); return ({ bits[sizeof(bits)-1], name }); } ); stuff = unique_array(index_stuff + stuff, (: lower_case($1[0])[0] :)); for (i = 0; i < sizeof(stuff); i++) { fname = NROFF_DOC_DIR + "index_" + lower_case(stuff[i][0][0][0..0]); AUTODOC_NROFF->create_nroff_index_file(stuff[i], fname); } } } /* create_nroff_file() */ private void create_html_file(mixed ob, int type) { string fname; mixed *stuff; mixed *index_stuff; int i; mapping chars; if (type == MAIN_FILE) { /* Now we create the nroff file... */ fname = replace(ob->query_file_name(), "/", "."); if (fname[0] == '.') { fname = fname[1..]; } fname += ".html"; rm(HTML_DOC_DIR + fname); AUTODOC_HTML->create_html_file(ob, HTML_DOC_DIR + fname); } else if (type == INDEX_FILE) { /* Create an index file... */ index_stuff = ob; stuff = map(query_files(), function (string name) { string *bits; bits = explode(name, "/"); return ({ bits[sizeof(bits)-1], name, "", summary_map[name] }); } ); stuff = unique_array(index_stuff + stuff, (: lower_case($1[0])[0] :)); chars = ([ ]); for (i = 0; i < sizeof(stuff); i++) { fname = "index_" + lower_case(stuff[i][0][0][0..0]) + ".html"; rm(HTML_DOC_DIR + fname); AUTODOC_HTML->create_html_index_file(stuff[i], stuff[i][0][0][0..0], HTML_DOC_DIR + fname); reset_eval_cost(); chars[capitalize(stuff[i][0][0][0..0])] = fname; } /* The differnt sorts of index will be genertated in the html file */ fname = HTML_DOC_DIR; // + "index.html"; //rm(fname); AUTODOC_HTML->create_main_index(chars, fname); } } /* create_html_file() */ private void after_thingy(int no_index) { int i; if (no_index) { printf("Finished recreating the documentation for %O\n", previous_object()->query_file_name(), previous_object()); } if (previous_object()->query_changed()) { something_changed = 1; } if (previous_object()->query_num_failed_tries() > 1) { /* * This means that the file has been deleted... Argh! Auto remove * it and log it. */ remove_file(files[file_pos - 1]); //log_file("AUTODOC", ctime(time()) + ": Removing: " + files[file_pos - 1] + " \n"); } else if (previous_object()->query_changed() || no_index) { //log_file("AUTODOC", ctime(time()) + ": Parsing: " + files[file_pos - 1] + " \n"); for (i = 0; i < sizeof(filters); i++) { call_out((: evaluate($1, $2, $3) :), 0, filters[i], previous_object(), MAIN_FILE); } } call_out((: $1->dest_me() :), 20, previous_object()); save(); } /* after_thingy() */ 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; } new(AUTODOC_FILE)->parse_file(files[file_pos++], (: after_thingy(0) :)); } /* do_parse_next_file() */ private void start_processing() { if (sizeof(files) == 0) { 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() :), 360); } /* start_processing() */ 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() */ /* * 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; int end; mixed *ret; mapping fluff; if (name != "create" && name != "setup" && name != "init") { 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() */ private void create_index() { int i; mixed *index_stuff; mixed *rabbit; string fname; string fn; string *bits; object parse; //log_file("AUTODOC", ctime(time()) + ": Created index.\n"); /* Build up the list... */ index_stuff = ({ }); help_map = ([ ]); extra_indexes = ({ }); summary_map = ([ ]); parse = clone_object(AUTODOC_FILE); for (i = 0; i < sizeof(files); i++) { /* Do not process it... Merely load it from disk */ parse->parse_file(files[i], 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. */ for (i = 0; i < sizeof(filters); i++) { reset_eval_cost(); catch(evaluate(filters[i], index_stuff, INDEX_FILE)); } summary_map = ([ ]); last_created_index = time(); save(); } /* create_index() */ /** * Recreate documentation for one fiel 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 (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; string dfn; string *our_files; if (member_array(fname, files) != -1) { files -= ({ fname }); file_pos = 0; /* Delete the files associated with it, start with the html files. */ fn = HTML_DOC_DIR + replace(fname, "/", "."); unguarded((: rm($(fn)) :)); /* Delete the nroff files. */ fn = NROFF_DOC_DIR + replace(fname, "/", "."); unguarded((: rm($(fn)) :)); /* Delete the nroff single function files. */ fn = NROFF_DOC_SINGLE + fname[0..<3] + "/"; our_files = get_dir(fn); if (sizeof(our_files)) { foreach (dfn in our_files) { dfn = fn + dfn; unguarded( (: rm($(dfn)) :) ); } } /* Remove the directory if it exists. */ if (file_size(fn[0..<2]) != -1) { unguarded((: rm($(fn[0..<2])) :)); } save(); if (!sizeof(files)) { remove_call_out(call_id); } return 1; } return 0; } /* remove_file() */ private void load() { unguarded( (: restore_object( SAVE_FILE ) :) ); } /* load() */ private void save() { unguarded( (: save_object( 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; } /** * 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() */ /** * Retuirns 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() */