#!/usr/bin/perl

###############################################################################
#
#   Script: Natural Docs
#
###############################################################################
#
#       Version 1.21
#
#       Copyright  2003-2004 Greg Valure
#
#       http://www.naturaldocs.org
#
#
#   About: License
#
#       Licensed under the GNU General Public License
#
#       This program is free software; you can redistribute it and/or modify
#       it under the terms of the GNU General Public License as published by
#       the Free Software Foundation; either version 2 of the License, or
#       (at your option) any later version.
#
#       This program is distributed in the hope that it will be useful,
#       but WITHOUT ANY WARRANTY; without even the implied warranty of
#       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#       GNU General Public License for more details.
#
#       You should have received a copy of the GNU General Public License
#       along with this program; if not, visit http://www.gnu.org/licenses/gpl.txt
#       or write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
#       Boston, MA  02111-1307  USA.
#
#
#   About: Syntax
#
#       > NaturalDocs -i [input (source) directory]
#       >            (-i [input (source) directory] ...)
#       >             -o [output format] [output directory]
#       >            (-o [output format] [output directory] ...)
#       >             -p [project directory]
#       >             [options]
#
#       Directory names with spaces are fine.  They don't need quotes on Windows.
#
#       Examples:
#
#           > NaturalDocs -i C:\My Project\Source
#           >             -o HTML C:\My Project\Docs
#           >             -p C:\My Project\Natural Docs
#
#           > NaturalDocs -i /src/project
#           >             -o HTML /doc/project
#           >             -p /etc/naturaldocs/project
#           >             -s Small -t 2 -q
#
#       Options:
#
#           -i [dir]            - The input (source) directory.  Required.  Can be specified multiple times.  Also --input, --source.
#
#           -o [fmt] [dir]   - The output format and directory.  Required.  See <Supported Output Formats> for a list of
#                                   options.  This can be specified multiple times, but only once per directory.  Also --output.
#
#           -p [dir]           - The project directory.  Required.  There should be a unique project directory for each
#                                   input directory.  Also --project.
#
#           -s [style]        - Sets the CSS style for HTML output.  If set to "Custom", it will not sync the project's CSS file
#                                   with one from Natural Docs' style directory.  If not specified, the style is "Default".  See
#                                   <HTML Styles> for options.  Also --style.
#
#           -do                - Prevents undocumented code aspects from being included in the output.  Also --documentedonly.
#
#           -t [len]           - Sets the number of spaces tabs should be expanded to.  This only needs to be set if you use tabs
#                                  in example code and text diagrams.  The default is 4.  Also --tablength.
#
#           -r                   - Rebuilds all output and data files from scratch.  Does not affect the menu file.  Also --rebuild.
#
#           -ro                 - Rebuilds all output files from scratch.  Use this whenever you add or change output formats.
#                                   Also --rebuildoutput.
#
#           -q                  - Suppresses all non-error output.  Also --quiet.
#
#           -ho                - For C and C++, only check the headers and not the source files.  Also --headersonly.
#
#           -h                  - Prints the syntax reference.  Also -?, --help.
#
#
#   About: Supported Languages
#
#       Adding additional languages is very easy, so don't worry if yours isn't here.  See <How to Add Languages>.
#
#       - C++
#       - C#
#       - Java
#       - JavaScript (.js files only)
#       - Perl (.pl and .pm files, plus .cgi and extensionless files if "perl" is in the #! line)
#       - Python (.py files, plus .cgi and extensionless files if "python" is in the #! line)
#       - PHP  (support isn't flawless because escapement isn't handled yet)
#       - PL/SQL
#       - Visual Basic
#       - Pascal (which includes Delphi)
#       - Assembly
#       - Tcl
#       - Ada
#       - Ruby
#       - Text files (.txt files only)
#
#
#   About: Supported Output Formats
#
#       HTML    - Creates HTML output.  A directory tree will be created to mirror the source tree, and each source file will have a
#                     corresponding HTML file of the same name, only with .html appended.  The files are self-contained and don't rely
#                     on frames.  index.html forwards to the first file on the menu.
#
#       FramedHTML - Creates framed HTML output.  A directory tree will be created to mirror the source tree, and each source file
#                             will have a corresponding HTML file of the same name, only with .html appended.  The menu has its own
#                             file, so changes to it will be done much quicker.
#
#       Natural Docs is designed to be extended with more output formats, but it requires familiarity with Perl.  See <How to Add
#       Output Formats> for details.
#
#
#   About: HTML Styles
#
#       Default  - The standard Natural Docs appearence.  You're looking at it right now.
#
#       Small    - For people who prefer 8pt Verdana, aka people with good eyes.  You get more on the screen at once, but some
#                     people hate it when web pages use tiny fonts.
#
#       Roman  - For people who prefer Roman fonts, aka people who use OS X, XP with ClearType, or some other decent
#                     anti-aliasing system.  Most people don't and thus prefer Verdana.  It makes the fancy quotes (66 and 99, as
#                     opposed to II) look especially nice, though.
#
#       Custom - Tells Natural Docs that the output directory is using its own custom CSS file.  Natural Docs will not sync the CSS
#                     file at all with this option set.
#
#       You can also use any CSS file present in Natural Docs' styles directory.  Just use its name without the .css extension.  For
#       example, if your style is Blue.css, use "Blue".
#
#       You can check for additional styles at http://www.naturaldocs.org.  If you would like to build your own, see
#       <How to Add HTML Styles>.
#
#
#   Topic: HTML Compatibility
#
#       Mozilla (Gecko)         - *Works on 1.0+.*  Tested on Mozilla 1.0.2 and 1.4.  1.0.2 has minor formatting issues with
#                                        prototypes in tooltips, but nothing that hurts usability.
#       Internet Explorer      - *Works on 4+.*  Tested on 4.0, 5.0 and 6.0.  I don't have a partition for 5.5 so I'm assuming it
#                                        works.  4.0 can't collapse the menu and makes the tooltips the full page width, although it positions
#                                        them correctly.  5.0 occasionally slightly misplaces the tooltips, but it's still usable and is usually fine.
#       Opera                      - *Works on 5+.*  Tested on 5.12, 6.05, and 7.02.  Only 7.02 can collapse the menu.  5.12 can't
#                                         size or position the tooltips very well, but they're usable most of the time.
#       Konqueror (KHTML)   - *Works on 3+.*  Tested on Konqueror 3.1.1.  Has some minor margin issues, but it's definitely their
#                                         renderer that's the problem.  Shows tooltips immediately because it has problems doing it on a timer.
#       Netscape 4.x            - *Nope.*  Crashes 4.08 and mangled on 4.8.  Why did they ever drop this for Gecko?  I've stopped
#                                        supporting NS4 in my projects and have found that my quality of life has improved considerably.
#
#       Note that a browser not being able to collapse the menu isn't a compatibility concern.  The menu defaults to completely open
#       if the browser can't handle collapsing it, so it's always usable.  It's just better on browsers that support it.
#
#       Also note that if a browser doesn't support frames, the user will automatically be forwarded to the menu file when using
#       framed HTML output so it will still be usable.
#
#
#   About: Maintainer
#
#       Greg Valure (gregvalure@naturaldocs.org)
#

###############################################################################
#
#   Group: Extending
#
###############################################################################
#
#   Topic: How to Add Languages
#
#       In the main script add a call to <NaturalDocs::Languages::Simple->New()> after all the rest.  See its documentation for
#       an explanation of the parameters.
#
#
#   Topic: How to Add Output Formats
#
#       - Create a new <NaturalDocs::Builder> sub-package derived from <NaturalDocs::Builder::Base>.
#       - See <NaturalDocs::Builder::Base>'s documentation for a list of the interface functions that should be implemented and
#         examples of how to do so.
#       - By placing the file in the Modules/NaturalDocs/Builder directory, it will automatically be included by Natural Docs.  You
#         do not have to add a 'use' statement anywhere in the code, which makes it easy to keep custom output formats whenever
#         you upgrade Natural Docs.
#
#
#   Topic: How to Add Topics
#
#       - If you'd like to add a synonym for an existing topic type, edit <NaturalDocs::Topics::constants>.  Note that it must be in
#         all lowercase.
#       - If you'd like to add a new topic type, read through <NaturalDocs::Topics'> source.  It should be obvious what needs to
#         be edited, just don't skip anything.  You can stop when you reach the functions.
#
#
#   Topic: How to Add Extensions
#
#       - Create a new <NaturalDocs::Extensions> sub-package derived from <NaturalDocs::Extensions::Base>.
#       - See <NaturalDocs::Extensions::Base's> documentation for a list of interface functions can be implemented.
#       - By placing the file in the Modules/NaturalDocs/Extensions directory, it will automatically be included by Natural Docs.  You
#         do not have to add a 'use' statement anywhere in the code, which makes it easy to keep custom extensions whenever
#         you upgrade Natural Docs.
#
#
#   Topic: How to Add HTML Styles
#
#       Read the <CSS Guide> to understand the styles Natural Docs uses and how they fit together.
#
#       After you've created your own CSS file, drop it in the Natural Docs styles directory.  Then you can reference it via -s in the
#       command line.  Its name is the CSS file name without the extension.  So if your file is Red.css, you use it by specifying
#       "-s Red" in your projects.
#
#       If you ever change the CSS file in the Natural Docs styles directory, each project that uses it will get an updated copy
#       automatically the next time Natural Docs is run on it.
#
#       Also make sure that whenever you upgrade Natural Docs, you check the Revisions section of the <CSS Guide> to see if
#       there have been any changes to the generated HTML.  It may require you to update your CSS file.
#
#
#   Topic: Code Conventions
#
#       - All NaturalDocs:: packages have their functions called in Package->Function() notation.  You call
#         <NaturalDocs::Project->FilesToBuild()>, *not* <NaturalDocs::Project::FilesToBuild()>.  Note that this was not the case
#         until version 1.14, so any custom modifications done before then probably need to be updated.
#
#       - If you're wondering about some of the line lengths or the strange indentation, it's because I work on Natural Docs using
#         8pt Verdana.
#
###############################################################################


use strict;
use integer;

use 5.005;  # When File::Spec was included by default

use FindBin;
use lib "$FindBin::RealBin/Modules";

sub INIT
    {
    # This function is just here so that when I start the debugger, it doesn't open a new file.  Normally it would jump to an INIT
    # function in some other file since that's the first piece of code to execute.
    };

use NaturalDocs::Constants;
use NaturalDocs::Topics;
use NaturalDocs::Version;
use NaturalDocs::File;
use NaturalDocs::ConfigFile;
use NaturalDocs::NDMarkup;
use NaturalDocs::Languages;
use NaturalDocs::Settings;
use NaturalDocs::Project;
use NaturalDocs::Menu;
use NaturalDocs::SymbolTable;
use NaturalDocs::ClassHierarchy;
use NaturalDocs::Parser;
use NaturalDocs::Builder;
use NaturalDocs::Extensions;



###############################################################################
# Group: Support Functions
# General functions that are used throughout the program, and that don't really fit anywhere else.


#
#   Function: StringCompare
#
#   Compares two strings so that the result is good for proper sorting.  A proper sort orders the characters as
#   follows:
#
#   - End of string.
#   - Whitespace.  Line break-tab-space.
#   - Symbols, which is anything not included in the other entries.
#   - Numbers, 0-9.
#   - Letters, case insensitive except to break ties.
#
#   If you use cmp instead of this function, the result would go by ASCII/Unicode values which would place certain symbols
#   between letters and numbers instead of having them all grouped together.  Also, you would have to choose between case
#   sensitivity or complete case insensitivity, in which ties are broken arbitrarily.
#
#   Returns:
#
#   Like cmp, it returns zero if A and B are equal, a positive value if A is greater than B, and a negative value if A is less than B.
#
sub StringCompare #(a, b)
    {
    my ($a, $b) = @_;

    if (!defined $a)
        {
        if (!defined $b)
            {  return 0;  }
        else
            {  return -1;  };
        }
    elsif (!defined $b)
        {
        return 1;
        };

    my $translatedA = lc($a);
    my $translatedB = lc($b);

    $translatedA =~ tr/\n\r\t 0-9a-z/\x01\x02\x03\x04\xDB-\xFE/;
    $translatedB =~ tr/\n\r\t 0-9a-z/\x01\x02\x03\x04\xDB-\xFE/;

    my $result = $translatedA cmp $translatedB;

    if ($result == 0)
        {
        # Break the tie by comparing their case.  Lowercase before uppercase.

        # If statement just to keep everything theoretically kosher, even though in practice we don't need this.
        if (ord('A') > ord('a'))
            {  return ($a cmp $b);  }
        else
            {  return ($b cmp $a);  };
        }
    else
        {  return $result;  };
    };


#
#   Function: ShortenToMatchStrings
#
#   Compares two arrayrefs and shortens the first array to only contain shared entries.  Assumes all entries are strings.
#
#   Parameters:
#
#       sharedArrayRef - The arrayref that will be shortened to only contain common elements.
#       compareArrayRef - The arrayref to match.
#
sub ShortenToMatchStrings #(sharedArrayRef, compareArrayRef)
    {
    my ($sharedArrayRef, $compareArrayRef) = @_;

    my $index = 0;

    while ($index < scalar @$sharedArrayRef && $index < scalar @$compareArrayRef &&
             $sharedArrayRef->[$index] eq $compareArrayRef->[$index])
        {  $index++;  };

    if ($index < scalar @$sharedArrayRef)
        {  splice(@$sharedArrayRef, $index);  };
    };


#
#   Function: UseDirectory
#
#   Does the equivalent of "use [module]" for every .pm file in a directory and all its subdirectories.  You *must* call this function
#   from within a BEGIN block if you want the INIT functions of the modules to be executed.
#
#   Parameters:
#
#       directory  - A single subdirectory.  Specify this as many times as necessary to make the path from the Natural Docs
#                        directory.
#
#   Example:
#
#       > BEGIN {
#       >    IncludeDirectory('Modules', 'NaturalDocs', 'Extensions');
#       > };
#
#       This code will include every .pm file in [Natural Docs' directory]/Modules/NaturalDocs/Extensions and all its subdirectories.
#
sub UseDirectory #(directory, directory, directory ...)
    {
    my $extraPath = NaturalDocs::File->JoinDirectories(@_);
    my $fullPath = NaturalDocs::File->JoinPaths($FindBin::RealBin, $extraPath, 1);

    my @directories = ( $fullPath );

    while (scalar @directories)
        {
        my $directory = pop @directories;

        opendir DIRECTORYHANDLE, $directory;
        my @entries = readdir DIRECTORYHANDLE;
        closedir DIRECTORYHANDLE;

        @entries = NaturalDocs::File->NoUpwards(@entries);

        foreach my $entry (@entries)
            {
            my $fullEntry = NaturalDocs::File->JoinPaths($directory, $entry);

            # If an entry is a directory, recurse.
            if (-d $fullEntry)
                {
                # Join again with the noFile flag set in case the platform handles them differently.
                push @directories, NaturalDocs::File->JoinPaths($directory, $entry, 1);
                }

            # Otherwise add it if it's a supported extension.  We need to explicitly ignore the menu files because they're text files and
            # their syntax is similar to Natural Docs content.
            elsif ($entry =~ /\.pm$/i)
                {
                require $fullEntry;
                };
            };
        };
    };


#
#   Function: XChomp
#
#   A cross-platform chomp function.  Regular chomp fails when parsing Windows-format line breaks on a Unix platform.  It
#   leaves the /r on, which screws everything up.  This does not.
#
#   Parameters:
#
#       lineRef - A *reference* to the line to chomp.
#
sub XChomp #(lineRef)
    {
    my $lineRef = shift;
    $$lineRef =~ s/[\n\r]+$//;
    };


###############################################################################
#
#   Main Code
#
#   The order in which functions are called here is critically important.  Read the "Usage and Dependencies" sections of all the
#   packages before even thinking about rearranging these.
#


# The Builder and Extension directories are live.  Any .pm files in them are automatically included.

BEGIN {

    UseDirectory( 'Modules', 'NaturalDocs', 'Builder' );
    UseDirectory( 'Modules', 'NaturalDocs', 'Extensions' );

    };


eval {

    # Check that our required packages are okay.

    NaturalDocs::File->CheckCompatibility();


    # Almost everything requires Settings to be initialized.

    NaturalDocs::Settings->Load();


    # Add the supported languages.

    my $cExtensions = [ 'h', 'hpp' ];

    if (!NaturalDocs::Settings->HeadersOnly())
        {  push @$cExtensions, 'c', 'cpp';  };


    # Notes for people unfamiliar with Perl:
    # - Use single values as follows: 'value'
    # - Use multiple values as follows: [  'value1', 'value2', 'value3' ].  You MUST include the brackets.
    # - Specify line breaks as follows: "\n".  You MUST use quotes instead of apostrophes.  You don't have to worry about \r.
    # - Specify backslashes as follows: "\\".  You MUST use quotes instead of apostrophes.
    # - Follow the documentation on the parameters closely.  You don't want to skip one or put them in the wrong order.
    # - Don't worry that some lines use something like PLSQL->New() instead of Simple->New().  This is only necessary
    #   when the language's syntax needs special handling.  Simple->New() should be fine in most cases.

    NaturalDocs::Languages::Simple->New( 'C++', $cExtensions, undef, '//', '/*', '*/', [ ';', '{' ], [ ';', '=' ], undef );
    NaturalDocs::Languages::Simple->New( 'C#', 'cs', undef, '//', '/*', '*/', [ ';', '{' ], [ ';', '=' ], undef );
    NaturalDocs::Languages::Simple->New( 'Java', 'java', undef, '//', '/*', '*/', '{', [ ';', '=' ], undef );
    NaturalDocs::Languages::Simple->New( 'JavaScript', 'js', undef, '//', '/*', '*/', '{', [ ';', '=' ], undef );
    NaturalDocs::Languages::Simple->New( 'Python', 'py', 'python', '#', undef, undef, ':', '=', "\\" );

    # Doesn't detect escapement yet.
    NaturalDocs::Languages::PHP->New( 'PHP', [ 'inc', 'php', 'php3', 'php4', 'phtml' ], 'php',
                                                          [ '//', '#' ], '/*', '*/', [ '{', ';' ], [ ';', '=' ], undef );

    NaturalDocs::Languages::PLSQL->New( 'PL/SQL', 'sql', undef, '--', '/*', '*/', [ ';', 'as', 'AS', 'is', 'IS' ],
                                                              [ ';', ':=', 'default', 'DEFAULT' ], undef );
    NaturalDocs::Languages::Simple->New( 'VB', [ 'vb', 'bas', 'cls', 'frm' ], undef, '\'', undef, undef, "\n", [ "\n", '=' ], '_' );
    NaturalDocs::Languages::Pascal->New( 'Pascal', 'pas', undef, '//', [ '{', '(*' ], [ '}', '*)' ], [ ';' ], [ ';', '=' ], undef );
    NaturalDocs::Languages::Simple->New( 'Assembly', 'asm', undef, ';', undef, undef, undef, "\n", "\\" );
    NaturalDocs::Languages::Ada->New( 'Ada', 'ada', undef, '--', undef, undef, [ 'is', 'IS', ';' ], [ ':=', ';' ], undef );
    NaturalDocs::Languages::Tcl->New( 'Tcl', 'tcl', [ 'tclsh', 'wish' ], '#', undef, undef, [ '{', ';' ], [ ';', "\n" ], undef );
    NaturalDocs::Languages::Ruby->New( 'Ruby', 'rb', 'ruby', '#', undef, undef, [ ';', "\n" ], [ ';', '=', "\n" ], "\\" );
    NaturalDocs::Languages::Simple->New( 'Text', 'txt', undef, undef, undef, undef, undef, undef, undef );

    # This one has full language support, hence no parameters.
    NaturalDocs::Languages::Perl->New();


    # Migrate from the old file names that were used prior to 1.14.

    NaturalDocs::Project->MigrateOldFiles();


    if (!NaturalDocs::Settings->IsQuiet())
        {  print "Finding files and detecting changes...\n";  };

    NaturalDocs::Project->LoadAndDetectChanges();

    NaturalDocs::SymbolTable->Load();
    NaturalDocs::ClassHierarchy->Load();

    NaturalDocs::SymbolTable->Purge();
    NaturalDocs::ClassHierarchy->Purge();


    # Parse any supported files that have changed.

    my $filesToParse = NaturalDocs::Project->FilesToParse();
    my $amount = scalar keys %$filesToParse;

    if ($amount > 0)
      {
      if (!NaturalDocs::Settings->IsQuiet())
          {  print 'Parsing ' . $amount . ' file' . ($amount > 1 ? 's' : '') . "...\n";  };

      foreach my $file (keys %$filesToParse)
          {  NaturalDocs::Parser->ParseForInformation($file);  };
      };


    # The symbol table is now fully resolved, so we can reduce its memory footprint.

    NaturalDocs::SymbolTable->PurgeResolvingInfo();


    # Load and update the menu file.  We need to do this after parsing so when it is updated, it will detect files where the
    # default menu title has changed and files that have added or deleted Natural Docs content.

    NaturalDocs::Menu->LoadAndUpdate();


    # Build any files that need it.  This needs to be run regardless of whether there are any files to build.  It will handle its own
    # output messages.

    NaturalDocs::Builder->Run();


    # Write the changes back to disk.  The menu must be saved before the project because the project file stores when the
    # menu file was last changed.

    NaturalDocs::Menu->Save();
    NaturalDocs::Project->Save();
    NaturalDocs::SymbolTable->Save();
    NaturalDocs::ClassHierarchy->Save();
    NaturalDocs::Settings->Save();

    if (!NaturalDocs::Settings->IsQuiet())
        {  print "Done.\n";  };

};

if ($@)  # Oops.
    {
    my $perlVersion;

    if ($^V)
        {  $perlVersion = sprintf('%vd', $^V);  }
    if (!$perlVersion || substr($perlVersion, 0, 1) eq '%')
        {  $perlVersion = $];  };

    die
         "\n"
         . "Natural Docs encountered the following error and was stopped:\n"
         . "\n"
         . "   " . $@
         . "\n"

         . "Also include the following information when sending an error report:\n"
         . "\n"
         . "   Natural Docs version: " . NaturalDocs::Settings->TextAppVersion() . "\n"
         . "   Perl version: " .$perlVersion . " on " . $^O . "\n"
         . "\n"

         . "You can get help at the following web site:\n"
         . "\n"
         . "   " . NaturalDocs::Settings->AppURL() . "\n"
         . "\n";
    };
