## @file # (Enter your file info here) # # @copy 2007 MailerMailer LLC # $Id: DocGenerator.pm 353 2007-08-14 17:46:43Z damjan $ ## @class RWDE::Doxy::DocGenerator # Note: This class is abstract. # It makes calls to fields that do not exist. # Please do not instantiate. package RWDE::Doxy::DocGenerator; use strict; use warnings; use Error qw(:try); use RWDE::Exceptions; use RWDE::Doxy::MethodData; use constant COMMENT => '#'; #Comments start with this character use constant COMMAND_PREFIX => '@'; #Prefix of the command use constant CHARACTER_SIZE => 1; #Size of one character use constant PARAM_SIZE => 9; #Size of '$$params{' use constant RETURN_SIZE => 6; #Size of 'return' ## @method void doxygenate() # Write/save the content to file. if you give a filename overwrites the original saves backup into the filename given # this function takes the filename gets the contents and creates all the tags we know how returns the marked up contents sub doxygenate() { my ($self, $params) = @_; #Process the file, placing all non_doxy lines into file_content_out #also populate MethodData and class data with the necessary info. $self->_create_intermediate_data(); #We now have intemediate data in file_content_out #We want to read from the file_content_out and write the complete doxygenated file #So place the file_content_out into the file_content_in. #Reset the file and class cursors. #Reset file content out $self->file_content_in($self->file_content_out); $self->reset_file_index(); $self->reset_file_content_out(); $self->reset_file_current_class(); #Insert initial info on top of page $self->_insert_file_data(); #Insert all other command and info $self->_insert_document_data(); return (); } ## @method protected void _create_intermediate_data() # Form an array of intermediate data. This will consist of all lines in file_content_in that are not doxy_lines # Also populate class and method data so we can preserve doxy comments. # (We can then add them back in somewhere else) sub _create_intermediate_data() { my ($self, $params) = @_; #Loop through every line of the file and parse/analyze it. while ($self->current_line()) { #if it is a doxy command tag #enter if the current line is a doxy tag command if ($self->current_line() =~ /^\s*## @(\S+)\s?(.*)(\n)/) { my $command_name = $1; my $command_info = $2; #Already saw the command. We will not need it again so increment the file index #We will regenerate this line when we remake the doxy tags so this will not be added to file_content_out $self->next_line(); if ($command_name eq 'file') { $self->_store_file_info(); } elsif ($command_name eq 'class') { #Command info consists of the class name #Ex: #@class #@class is the $command_name, whicle the class name is the $command_info, $self->_store_class_info({ command_info => $command_info }); #Methods will need to know what class they belong to #We will keep the name of this class handy so we can store the methods' method_data in the correct class_data $self->file_current_class($command_info); } elsif ($command_name eq 'method' or $command_name eq 'cmethod') { $self->_store_method_info({ command_info => $command_info }); } else { #This tag could be a todo, enum, etc. So just echo it to file_out $self->add_to_file_out({ content => $self->current_line() }); $self->next_line(); } } #This is not a tag so just add the line to the end of file_content_intermediate else { $self->add_to_file_out({ content => $self->current_line() }); $self->next_line(); } } } ## @method protected void _store_file_info() # Store all \@file-related doxy info lines (no command present) for later use. # All \@file info is placed at the top of the perl document file. sub _store_file_info() { my ($self, $params) = @_; $self->_store_info({ data => $self }); } ## @method protected void _store_class_info($command_info, $command_info}) # Store all \@class-related doxy info lines (no command present) for later use. # @param command_info (Enter explanation for param here) sub _store_class_info() { my ($self, $params) = @_; throw RWDE::DataMissingException({ info => 'No class_name specified' }) unless defined($$params{command_info}); $self->_store_info({ data => $self->get_class({ class_name => $$params{command_info} }) }); return (); } ## @method protected void _store_method_info($command_info) # Store all \@method-related doxy info lines (no command present) for later use. # @param command_info (Enter explanation for param here) sub _store_method_info() { my ($self, $params) = @_; throw RWDE::DataMissingException({ info => 'No class_name specified' }) unless defined($$params{command_info}); my $class_data = $self->get_class({ class_name => $self->file_current_class }); $$params{command_info} =~ /(\S+)\(.*\)\s*$/; my $method_data = $class_data->get_method({ method_name => $1 }); $self->_store_info({ data => $method_data, sub_info => 1 }); return (); } ## @method protected void _store_info($data) # Checks to see if a class info exists # If a ClassData entry exists then tell it to store the info lines # If it does not exist then create a new ClassData object and populate the info # @param data (Enter explanation for param here) sub _store_info() { my ($self, $params) = @_; throw RWDE::DataMissingException({ info => 'No data specified' }) unless defined($$params{data}); my $data = $$params{data}; #While we are looking at a "block" of doxy tags (every line until we do not start a line with "# ") while ($self->current_line() =~ /^\s*#\s(.*)\n/) { #@ implies that a tag exists so ignore anything that is not an info line #However, we will need to store params, etc. if ($self->current_line() !~ /^\s*#\s\@(\S+)\s*(.*)/) { $data->add_info({ info_line => $1 }); } else { #Files and classes do not have params and returns. #But methods do #This area will only be intered if sub_info param is defined. if (defined($$params{sub_info})) { if ($1 eq 'param') { my $param_info = $2; $param_info =~ /(\S+)\s*(.*)/; $data->add_param_info({ param_name => $1, param_info => $2 }); } elsif ($1 eq 'return') { $data->set_return_info({ return_info => $2 }); } } } $self->next_line(); } return (); } ## @method protected void _insert_file_data() #Insert \@file template info at the top of the file #Four lines, where filename is the file_name of the file. # ## \@file # # (Enter your file info here) # # # # \@copy 2007 MailerMailer LLC # # Subversion ID tag sub _insert_file_data() { my ($self, $params) = @_; my $info_lines = $self->file_info(); #Create the entire tag for this command $self->add_to_file_out({ content => $self->_command({ command => 'file' }) }); #Want to check if we store any @file lines if (scalar @{$info_lines} > 0) { #We have some lines stored. Add them in $self->_insert_info_lines({ info_lines => $info_lines }); } else { #No lines were found so generate the default stuff. $self->add_to_file_out({ content => $self->_info({ info => '(Enter your file info here)' }) }); $self->add_to_file_out({ content => $self->_info() }); $self->add_to_file_out({ content => $self->_sub_command({ command => 'copy', info => '2007 MailerMailer LLC' }) }); $self->add_to_file_out({ content => $self->_info({ info => '$Id$' }) }); $self->add_to_file_out({ content => "\n" }); } return (); } ## @method protected void _insert_document_data() #Insert method and sub tag data into new updated_file_data #The methods inside the 'if' will increment the line_index and return it. sub _insert_document_data() { my ($self, $params) = @_; #Loop through every line of the file and parse/analyze it. while ($self->current_line()) { #Does this line contain the start of a valid package/class? if ($self->current_line() =~ /^\s*package\s+(\S+);/) { #Update the current package name $self->file_current_class($1); #Insert class/package data before 'package ...' $self->_insert_class_data({ class_name => $self->file_current_class }); $self->add_to_file_out({ content => $self->current_line() }); $self->next_line(); } #Does this line contain a valid method. elsif ($self->current_line() =~ /^\s*sub\s+(\w+)/) { #Store current line index. we will need it later to copy lines to updated_file_content my $initial_line_index = $self->file_index; #Get the method data object or create a new one. my $class_data = $self->get_current_class(); my $method_data = $class_data->get_method({ method_name => $1 }); #Find method information (such as params, return info) #This method will increment $self->file_index so we will need it after the method is finished. $self->_determine_method_attributes({ method_data => $method_data }); #Insert method/sub data before 'sub ...' $self->_insert_method_data({ method_data => $method_data }); #Because the tag/comments occur on top of the method, we had to hold off until after that stuff was #taken care of. Now we can copy over the method, itself. #We subtract one off file_index because it is pointing to the line after the method, which #this loop will take care of on the next iteration. $self->add_range_to_file_out({ start_index => $initial_line_index, end_index => $self->file_index - 1 }); } #Nothing special about the line, just copy it over else { $self->add_to_file_out({ content => $self->current_line() }); $self->next_line(); } } return (); } ## @method protected void _insert_class_data($class_name}) # Insert \@class template info above the "package " # Two lines: # ## @class # # (Enter your class info here) # @param class_name (Enter explanation for param here) sub _insert_class_data() { my ($self, $params) = @_; throw RWDE::DataMissingException({ info => 'No class_name specified' }) unless defined($$params{class_name}); $self->add_to_file_out({ content => $self->_command({ command => 'class', info => $$params{class_name} }) }); my $info_lines = $self->get_class($params)->class_info(); #If we saved some info lines from before...add them in. if (scalar @{$info_lines} > 0) { $self->_insert_info_lines({ info_lines => $info_lines }); } else { #We have no lines to add so just generate an info line $self->add_to_file_out({ content => $self->_info({ info => '(Enter ' . $$params{class_name} . ' info here)' }) }); } return (); } ## @method protected object _insert_method_data($method_data) # Insert method or cmethod template info above the "sub ..." # @param method_data (Enter explanation for param here) sub _insert_method_data() { my ($self, $params) = @_; throw RWDE::DataMissingException({ info => 'No method_data specified' }) unless defined($$params{method_data}); my $method_data = $$params{method_data}; #Combine the method info $self->add_to_file_out({ content => $self->_command({ command => $method_data->method_type, info => $method_data->method_description() }) }); if (scalar @{ $method_data->method_info } > 0) { $self->_insert_info_lines({ info_lines => $method_data->method_info }); } else { #We have no lines to add so just generate an info line $self->add_to_file_out({ content => $self->_info({ info => '(Enter ' . $method_data->method_name . ' info here)' }) }); } foreach my $param_name (keys %{ $method_data->method_params() }) { #Get param info for this param name. It will return undef if one does not exist. my $param_info = $method_data->get_param_info({ param_name => $param_name }); #Use the param info if it exists or generate a generic info string if it does not $param_info = ($param_info ne undef) ? $param_info : '(Enter explanation for param here)'; $self->add_to_file_out({ content => $self->_sub_command({ command => 'param', info => "$param_name $param_info" }) }); } if ($method_data->method_return_type ne "void") { my $return_info = ($method_data->method_return_info ne undef and $method_data->method_return_info ne '') ? $method_data->method_return_info() : '(Enter explanation for return value here)'; $self->add_to_file_out({ content => $self->_sub_command({ command => 'return', info => $return_info }) }); } return (); } ## @method protected void _insert_info_lines($info_lines) # (Enter _insert_info_lines info here) sub _insert_info_lines() { my ($self, $params) = @_; throw RWDE::DataMissingException({ info => 'No info_lines specified' }) unless defined($$params{info_lines}); foreach my $info_line (@{ $$params{info_lines} }) { $self->add_to_file_out({ content => $self->_info({ info => $info_line }) }); } return (); } ## @method protected object _determine_method_attributes($method_data) # Add the new tag lines to the updated_file_content # @param method_data (Enter explanation for param here) sub _determine_method_attributes() { my ($self, $params) = @_; throw RWDE::DataMissingException({ info => 'No method_data specified' }) unless defined($$params{method_data}); my $method_data = $$params{method_data}; #We always need to go into the while loop once my $initial_loop = 1; #For every { we will increment the bracket count by 1. For every } we will decrement the bracket count by 1. #Once the bracker count reaches zero, then we have found the end of the method. #Start by incrementing here if necessary because one { could be on the same line as the method declaration: "sub my_name() {" #But we only want to do that if one is there. if ($self->current_line() =~ /\{/) { $method_data->increment_brackets_count(); } #Go to the next line and start pulling information from the method $self->next_line; #Loop through the entire line (which is a string) and look for certain properties. while (($self->current_line() and ($method_data->method_brackets_count != 0)) or $initial_loop) { $initial_loop = undef; #The character index for this line's string my $line = $self->current_line(); my $char_index = 0; my $no_comment_found = 1; #True as long as we do not find a comment while ($char_index < length($line) and $no_comment_found) { #If this character is a comment then the rest of the lines should not be analyzed. if (substr($line, $char_index, CHARACTER_SIZE) eq COMMENT) { $no_comment_found = 0; } #Is this character a {. If so then increment the bracket counter elsif (substr($line, $char_index, CHARACTER_SIZE) eq "{") { $method_data->increment_brackets_count(); $char_index++; } #If this character is a } then decrement the bracket counter elsif (substr($line, $char_index, CHARACTER_SIZE) eq "}") { $method_data->decrement_brackets_count(); $char_index++; } #Do the next 9 characters show '$$params{'. If so, then we have found a param (unless a compile problem exists). #There is one case where we do not want to count $$params{} as a parameter #-Lets say the we assign a value to a param key, such as $$params{something} = 4; #-This param was not passed in, so we do not want to count it #-However, if the param was passed in and it is reassigned (as above), it will still get picked up as a param #because it will appear at least twice. #-The regex takes care of this. elsif (substr($line, $char_index, PARAM_SIZE) eq '$$params{' and $line !~ /\$\$params\{\s*\S+\s*}\s*=/) { my $param_name; my $rest_of_line = substr($line, $char_index + PARAM_SIZE, length($line) - $char_index + PARAM_SIZE); if ($rest_of_line =~ /^\s*(\w+)/) { $param_name = $1; $method_data->add_param({ param_name => $param_name }); $char_index += (PARAM_SIZE + length($param_name) + 1); #The +1 is for the '}' } else { throw RWDE::DataBadException({ info => 'Malformed param File will probably not compile. On line ' . $self->file_index }); } } elsif (substr($line, $char_index, RETURN_SIZE) eq 'return') { my $rest_of_line = substr($line, $char_index + RETURN_SIZE, length($line) - $char_index + RETURN_SIZE); #If not "return;" and not "return();" then it is an object. Otherwise leave it at the default void. if ($rest_of_line !~ /^\s*\(?\s*\)?;/){ $method_data->method_return_type('object'); } $char_index += RETURN_SIZE; } else { $char_index++; } } $self->next_line(); } return (); } ## @method protected object _command($command) # This returns a one-line command string. # Example: command param is 'file', info param is "info" # will return '## @file info' # @param command (Enter explanation for param here) # @return (Enter explanation for return value here) sub _command() { my ($self, $params) = @_; throw RWDE::DataMissingException({ info => 'Command not specified' }) unless defined($$params{command}); #Use two comment characters for the major command. $$params{prefix} = COMMENT . COMMENT; return $self->_line($params); } ## @method protected object _sub_command($command) #This returns a one-line sub command string. #Example: command param is 'file', info param is 'info" # will return '# @file info' # @param command (Enter explanation for param here) # @return (Enter explanation for return value here) sub _sub_command() { my ($self, $params) = @_; throw RWDE::DataMissingException({ info => 'Command not specified' }) unless defined($$params{command}); $$params{prefix} = COMMENT; return $self->_line($params); } ## @method protected object _info($command) # This returns a one-line info string. # Example: info param is 'file' # will return '# file' # Note that commands are not allowed in info strings. Please see _command and _sub_command for that # @param command (Enter explanation for param here) # @return (Enter explanation for return value here) sub _info() { my ($self, $params) = @_; #info does not have a command. So passing in a command is illegal. throw RWDE::DataMissingException({ info => 'Command specified' }) unless not defined($$params{command}); $$params{prefix} = COMMENT; return $self->_line($params); } ## @method protected object _line($command, $info, $prefix) # This returns a one-line sub command string. # Example: info param is 'file' # will return '# file' # Note that commands are not allowed in info strings. Please see _command and _sub_command for that # @param info (Enter explanation for param here) # @param command (Enter explanation for param here) # @param prefix (Enter explanation for param here) # @return (Enter explanation for return value here) sub _line() { my ($self, $params) = @_; throw RWDE::DataMissingException({ info => 'No tag prefix specified' }) unless defined($$params{prefix}); my $tag = ''; #We just want an empty line for some reason.; if (defined($$params{info}) and defined($$params{command})) { $tag = COMMAND_PREFIX . $$params{command} . ' ' . $$params{info}; } elsif (defined($$params{command})) { $tag = COMMAND_PREFIX . $$params{command}; } elsif (defined($$params{info})) { $tag = $$params{info}; } return $$params{prefix} . ' ' . $tag . "\n"; } 1;