diff options
Diffstat (limited to 'docs/tool/Modules/NaturalDocs/Settings.pm')
| -rw-r--r-- | docs/tool/Modules/NaturalDocs/Settings.pm | 1418 |
1 files changed, 1418 insertions, 0 deletions
diff --git a/docs/tool/Modules/NaturalDocs/Settings.pm b/docs/tool/Modules/NaturalDocs/Settings.pm new file mode 100644 index 00000000..8d1fc11b --- /dev/null +++ b/docs/tool/Modules/NaturalDocs/Settings.pm @@ -0,0 +1,1418 @@ +############################################################################### +# +# Package: NaturalDocs::Settings +# +############################################################################### +# +# A package to handle the command line and various other program settings. +# +############################################################################### + +# This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure +# Natural Docs is licensed under the GPL + +use Cwd (); + +use NaturalDocs::Settings::BuildTarget; + +use strict; +use integer; + +package NaturalDocs::Settings; + + +############################################################################### +# Group: Information + +=pod begin nd + + Topic: Usage and Dependencies + + - The <Constant Functions> can be called immediately. + + - Prior to initialization, <NaturalDocs::Builder> must have all its output packages registered. + + - To initialize, call <Load()>. All functions except <InputDirectoryNameOf()> will then be available. + + - <GenerateDirectoryNames()> must be called before <InputDirectoryNameOf()> will work. Currently it is called by + <NaturalDocs::Menu->LoadAndUpdate()>. + + + Architecture: Internal Overview + + - <Load()> first parses the command line, gathering all the settings and checking for errors. All <NaturalDocs::Builder> + packages must be registered before this is called because it needs their command line options. + <NaturalDocs::Project->ReparseEverything()> and <NaturalDocs::Project->RebuildEverything()> are called right away if -r + or -ro are used. + + - Output directories are *not* named at this point. See <Named Directories>. + + - The previous settings from the last time Natural Docs was run are loaded and compared to the current settings. + <NaturalDocs::Project->ReparseEverything()> and <NaturalDocs::Project->RebuildEverything()> are called if there are + any differences that warrant it. + + - It then waits for <GenerateDirectoryNames()> to be called by <NaturalDocs::Menu>. The reason for this is that the + previous directory names are stored as hints in the menu file, for reasons explained in <Named Directories>. Once that + happens all the unnamed directories have names generated for them so everything is named. The package is completely + set up. + + - The input directories are stored in an array instead of a hash because the order they were declared in matters. If two + people use multiple input directories on separate computers without sharing a menu file, they should at least get consistent + directory names by declaring them in the same order. + + + Architecture: Named Directories + + Ever since Natural Docs introduced multiple input directories in 1.16, they've had to be named. Since they don't necessarily + extend from the same root anymore, they can't share an output directory without the risk of file name conflicts. There was + an early attempt at giving them actual names, but now they're just numbered from 1. + + Directory names aren't generated right away. It waits for <Menu.txt> to load because that holds the obfuscated names from + the last run. <NaturalDocs::Menu> then calls <GenerateDirectoryNames()> and passes those along as hints. + <GenerateDirectoryNames()> then applies them to any matches and generates new ones for any remaining. This is done so + that output page locations can remain consistent when built on multiple computers, so long as the menu file is shared. I tend + to think the menu file is the most likely configuration file to be shared. + + + Architecture: Removed Directories + + Directories that were part of the previous run but aren't anymore are still stored in the package. The primary reason, though + there may be others, is file purging. If an input directory is removed, all the output files that were generated from anything + in it need to be removed. To find out what the output file name was for a removed source file, it needs to be able to split it + from it's original input directory and know what that directory was named. If this didn't happen those output files would be + orphaned, as was the case prior to 1.32. + +=cut + + + +############################################################################### +# Group: Variables + + +# handle: PREVIOUS_SETTINGS_FILEHANDLE +# The file handle used with <PreviousSettings.nd>. + +# array: inputDirectories +# An array of input directories. +my @inputDirectories; + +# array: inputDirectoryNames +# An array of the input directory names. Each name corresponds to the directory of the same index in <inputDirectories>. +my @inputDirectoryNames; + +# array: imageDirectories +# An array of image directories. +my @imageDirectories; + +# array: imageDirectoryNames +# An array of the image directory names. Each name corresponds to the directory of the same index in <imageDirectories>. +my @imageDirectoryNames; + +# array: relativeImageDirectories +# An array of the relative paths for images. The asterisks found in the command line are not present. +my @relativeImageDirectories; + +# array: excludedInputDirectories +# An array of input directories to exclude. +my @excludedInputDirectories; + +# array: removedInputDirectories +# An array of input directories that were once in the command line but are no longer. +my @removedInputDirectories; + +# array: removedInputDirectoryNames +# An array of the removed input directories' names. Each name corresponds to the directory of the same index in +# <removedInputDirectories>. +my @removedInputDirectoryNames; + +# array: removedImageDirectories +# An array of image directories that were once in the command line but are no longer. +my @removedImageDirectories; + +# array: removedImageDirectoryNames +# An array of the removed image directories' names. Each name corresponds to the directory of the same index in +# <removedImageDirectories>. +my @removedImageDirectoryNames; + +# var: projectDirectory +# The project directory. +my $projectDirectory; + +# array: buildTargets +# An array of <NaturalDocs::Settings::BuildTarget>s. +my @buildTargets; + +# var: documentedOnly +# Whether undocumented code aspects should be included in the output. +my $documentedOnly; + +# int: tabLength +# The number of spaces in tabs. +my $tabLength; + +# bool: noAutoGroup +# Whether auto-grouping is turned off. +my $noAutoGroup; + +# bool: onlyFileTitles +# Whether source files should always use the file name as the title. +my $onlyFileTitles; + +# bool: isQuiet +# Whether the script should be run in quiet mode or not. +my $isQuiet; + +# bool: rebuildData +# WHether most data files should be ignored and rebuilt. +my $rebuildData; + +# array: styles +# An array of style names to use, most important first. +my @styles; + +# var: charset +# The character encoding of the source files, and thus the output. +my $charset; + + +############################################################################### +# Group: Files + + +# +# File: PreviousSettings.nd +# +# Stores the previous command line settings. +# +# Format: +# +# > [BINARY_FORMAT] +# > [VersionInt: app version] +# +# The file starts with the standard <BINARY_FORMAT> <VersionInt> header. +# +# > [UInt8: tab length] +# > [UInt8: documented only (0 or 1)] +# > [UInt8: no auto-group (0 or 1)] +# > [UInt8: only file titles (0 or 1)] +# > [AString16: charset] +# > +# > [UInt8: number of input directories] +# > [AString16: input directory] [AString16: input directory name] ... +# +# A count of input directories, then that number of directory/name pairs. +# +# > [UInt8: number of output targets] +# > [AString16: output directory] [AString16: output format command line option] ... +# +# A count of output targets, then that number of directory/format pairs. +# +# +# Revisions: +# +# 1.4: +# +# - Added only file titles. +# +# 1.33: +# +# - Added charset. +# +# 1.3: +# +# - Removed headers-only, which was a 0/1 UInt8 after tab length. +# - Change auto-group level (1 = no, 2 = yes, 3 = full only) to no auto-group (0 or 1). +# +# 1.22: +# +# - Added auto-group level. +# +# 1.2: +# +# - File was added to the project. Prior to 1.2, it didn't exist. +# + + +############################################################################### +# Group: Action Functions + +# +# Function: Load +# +# Loads and parses all settings from the command line and configuration files. Will exit if the options are invalid or the syntax +# reference was requested. +# +sub Load + { + my ($self) = @_; + + $self->ParseCommandLine(); + $self->LoadAndComparePreviousSettings(); + }; + + +# +# Function: Save +# +# Saves all settings in configuration files to disk. +# +sub Save + { + my ($self) = @_; + + $self->SavePreviousSettings(); + }; + + +# +# Function: GenerateDirectoryNames +# +# Generates names for each of the input and image directories, which can later be retrieved with <InputDirectoryNameOf()> +# and <ImageDirectoryNameOf()>. +# +# Parameters: +# +# inputHints - A hashref of suggested input directory names, where the keys are the directories and the values are the names. +# These take precedence over anything generated. You should include names for directories that are no longer in +# the command line. This parameter may be undef. +# imageHints - Same as inputHints, only for the image directories. +# +sub GenerateDirectoryNames #(hashref inputHints, hashref imageHints) + { + my ($self, $inputHints, $imageHints) = @_; + + my %usedInputNames; + my %usedImageNames; + + + if (defined $inputHints) + { + # First, we have to convert all non-numeric names to numbers, since they may come from a pre-1.32 menu file. We do it + # here instead of in NaturalDocs::Menu to keep the naming scheme centralized. + + my @names = values %$inputHints; + my $hasNonNumeric; + + foreach my $name (@names) + { + if ($name !~ /^[0-9]+$/) + { + $hasNonNumeric = 1; + last; + }; + }; + + + if ($hasNonNumeric) + { + # Hash mapping old names to new names. + my %conversion; + + # The sequential number to use. Starts at two because we want 'default' to be one. + my $currentNumber = 2; + + # If there's only one name, we set it to one no matter what it was set to before. + if (scalar @names == 1) + { $conversion{$names[0]} = 1; } + else + { + # We sort the list first because we want the end result to be predictable. This conversion could be happening on many + # machines, and they may not all specify the input directories in the same order. They need to all come up with the + # same result. + @names = sort @names; + + foreach my $name (@names) + { + if ($name eq 'default') + { $conversion{$name} = 1; } + else + { + $conversion{$name} = $currentNumber; + $currentNumber++; + }; + }; + }; + + # Convert them to the new names. + foreach my $directory (keys %$inputHints) + { + $inputHints->{$directory} = $conversion{ $inputHints->{$directory} }; + }; + }; + + + # Now we apply all the names from the hints, and save any unused ones as removed directories. + + for (my $i = 0; $i < scalar @inputDirectories; $i++) + { + if (exists $inputHints->{$inputDirectories[$i]}) + { + $inputDirectoryNames[$i] = $inputHints->{$inputDirectories[$i]}; + $usedInputNames{ $inputDirectoryNames[$i] } = 1; + delete $inputHints->{$inputDirectories[$i]}; + }; + }; + + + # Any remaining hints are saved as removed directories. + + while (my ($directory, $name) = each %$inputHints) + { + push @removedInputDirectories, $directory; + push @removedInputDirectoryNames, $name; + }; + }; + + + if (defined $imageHints) + { + # Image directory names were never non-numeric, so there is no conversion. Apply all the names from the hints. + + for (my $i = 0; $i < scalar @imageDirectories; $i++) + { + if (exists $imageHints->{$imageDirectories[$i]}) + { + $imageDirectoryNames[$i] = $imageHints->{$imageDirectories[$i]}; + $usedImageNames{ $imageDirectoryNames[$i] } = 1; + delete $imageHints->{$imageDirectories[$i]}; + }; + }; + + + # Any remaining hints are saved as removed directories. + + while (my ($directory, $name) = each %$imageHints) + { + push @removedImageDirectories, $directory; + push @removedImageDirectoryNames, $name; + }; + }; + + + # Now we generate names for anything remaining. + + my $inputCounter = 1; + + for (my $i = 0; $i < scalar @inputDirectories; $i++) + { + if (!defined $inputDirectoryNames[$i]) + { + while (exists $usedInputNames{$inputCounter}) + { $inputCounter++; }; + + $inputDirectoryNames[$i] = $inputCounter; + $usedInputNames{$inputCounter} = 1; + + $inputCounter++; + }; + }; + + + my $imageCounter = 1; + + for (my $i = 0; $i < scalar @imageDirectories; $i++) + { + if (!defined $imageDirectoryNames[$i]) + { + while (exists $usedImageNames{$imageCounter}) + { $imageCounter++; }; + + $imageDirectoryNames[$i] = $imageCounter; + $usedImageNames{$imageCounter} = 1; + + $imageCounter++; + }; + }; + }; + + + +############################################################################### +# Group: Information Functions + + +# +# Function: InputDirectories +# +# Returns an arrayref of input directories. Do not change. +# +# This will not return any removed input directories. +# +sub InputDirectories + { return \@inputDirectories; }; + +# +# Function: InputDirectoryNameOf +# +# Returns the generated name of the passed input directory. <GenerateDirectoryNames()> must be called once before this +# function is available. +# +# If a name for a removed input directory is available, it will be returned as well. +# +sub InputDirectoryNameOf #(directory) + { + my ($self, $directory) = @_; + + for (my $i = 0; $i < scalar @inputDirectories; $i++) + { + if ($directory eq $inputDirectories[$i]) + { return $inputDirectoryNames[$i]; }; + }; + + for (my $i = 0; $i < scalar @removedInputDirectories; $i++) + { + if ($directory eq $removedInputDirectories[$i]) + { return $removedInputDirectoryNames[$i]; }; + }; + + return undef; + }; + + +# +# Function: SplitFromInputDirectory +# +# Takes an input file name and returns the array ( inputDirectory, relativePath ). +# +# If the file cannot be split from an input directory, it will try to do it with the removed input directories. +# +sub SplitFromInputDirectory #(file) + { + my ($self, $file) = @_; + + foreach my $directory (@inputDirectories, @removedInputDirectories) + { + if (NaturalDocs::File->IsSubPathOf($directory, $file)) + { return ( $directory, NaturalDocs::File->MakeRelativePath($directory, $file) ); }; + }; + + return ( ); + }; + + +# +# Function: ImageDirectories +# +# Returns an arrayref of image directories. Do not change. +# +# This will not return any removed image directories. +# +sub ImageDirectories + { return \@imageDirectories; }; + + +# +# Function: ImageDirectoryNameOf +# +# Returns the generated name of the passed image or input directory. <GenerateDirectoryNames()> must be called once before +# this function is available. +# +# If a name for a removed input or image directory is available, it will be returned as well. +# +sub ImageDirectoryNameOf #(directory) + { + my ($self, $directory) = @_; + + for (my $i = 0; $i < scalar @imageDirectories; $i++) + { + if ($directory eq $imageDirectories[$i]) + { return $imageDirectoryNames[$i]; }; + }; + + for (my $i = 0; $i < scalar @removedImageDirectories; $i++) + { + if ($directory eq $removedImageDirectories[$i]) + { return $removedImageDirectoryNames[$i]; }; + }; + + return undef; + }; + + +# +# Function: SplitFromImageDirectory +# +# Takes an input image file name and returns the array ( imageDirectory, relativePath ). +# +# If the file cannot be split from an image directory, it will try to do it with the removed image directories. +# +sub SplitFromImageDirectory #(file) + { + my ($self, $file) = @_; + + foreach my $directory (@imageDirectories, @removedImageDirectories) + { + if (NaturalDocs::File->IsSubPathOf($directory, $file)) + { return ( $directory, NaturalDocs::File->MakeRelativePath($directory, $file) ); }; + }; + + return ( ); + }; + + +# +# Function: RelativeImageDirectories +# +# Returns an arrayref of relative image directories. Do not change. +# +sub RelativeImageDirectories + { return \@relativeImageDirectories; }; + + +# Function: ExcludedInputDirectories +# Returns an arrayref of input directories to exclude. Do not change. +sub ExcludedInputDirectories + { return \@excludedInputDirectories; }; + + +# Function: BuildTargets +# Returns an arrayref of <NaturalDocs::Settings::BuildTarget>s. Do not change. +sub BuildTargets + { return \@buildTargets; }; + + +# +# Function: OutputDirectoryOf +# +# Returns the output directory of a builder object. +# +# Parameters: +# +# object - The builder object, whose class is derived from <NaturalDocs::Builder::Base>. +# +# Returns: +# +# The builder directory, or undef if the object wasn't found.. +# +sub OutputDirectoryOf #(object) + { + my ($self, $object) = @_; + + foreach my $buildTarget (@buildTargets) + { + if ($buildTarget->Builder() == $object) + { return $buildTarget->Directory(); }; + }; + + return undef; + }; + + +# Function: Styles +# Returns an arrayref of the styles associated with the output. +sub Styles + { return \@styles; }; + +# Function: ProjectDirectory +# Returns the project directory. +sub ProjectDirectory + { return $projectDirectory; }; + +# Function: ProjectDataDirectory +# Returns the project data directory. +sub ProjectDataDirectory + { return NaturalDocs::File->JoinPaths($projectDirectory, 'Data', 1); }; + +# Function: StyleDirectory +# Returns the main style directory. +sub StyleDirectory + { return NaturalDocs::File->JoinPaths($FindBin::RealBin, 'Styles', 1); }; + +# Function: JavaScriptDirectory +# Returns the main JavaScript directory. +sub JavaScriptDirectory + { return NaturalDocs::File->JoinPaths($FindBin::RealBin, 'JavaScript', 1); }; + +# Function: ConfigDirectory +# Returns the main configuration directory. +sub ConfigDirectory + { return NaturalDocs::File->JoinPaths($FindBin::RealBin, 'Config', 1); }; + +# Function: DocumentedOnly +# Returns whether undocumented code aspects should be included in the output. +sub DocumentedOnly + { return $documentedOnly; }; + +# Function: TabLength +# Returns the number of spaces tabs should be expanded to. +sub TabLength + { return $tabLength; }; + +# Function: NoAutoGroup +# Returns whether auto-grouping is turned off. +sub NoAutoGroup + { return $noAutoGroup; }; + +# Function: OnlyFileTitles +# Returns whether source files should always use the file name as the title. +sub OnlyFileTitles + { return $onlyFileTitles; }; + +# Function: IsQuiet +# Returns whether the script should be run in quiet mode or not. +sub IsQuiet + { return $isQuiet; }; + +# Function: RebuildData +# Returns whether all data files should be ignored and rebuilt. +sub RebuildData + { return $rebuildData; }; + +# Function: CharSet +# Returns the character set, or undef if none. +sub CharSet + { return $charset; }; + + +############################################################################### +# Group: Constant Functions + +# +# Function: AppVersion +# +# Returns Natural Docs' version number as an integer. Use <TextAppVersion()> to get a printable version. +# +sub AppVersion + { + my ($self) = @_; + return NaturalDocs::Version->FromString($self->TextAppVersion()); + }; + +# +# Function: TextAppVersion +# +# Returns Natural Docs' version number as plain text. +# +sub TextAppVersion + { + return '1.4'; + }; + +# +# Function: AppURL +# +# Returns a string of the project's current web address. +# +sub AppURL + { return 'http://www.naturaldocs.org'; }; + + + +############################################################################### +# Group: Support Functions + + +# +# Function: ParseCommandLine +# +# Parses and validates the command line. Will cause the script to exit if the options ask for the syntax reference or +# are invalid. +# +sub ParseCommandLine + { + my ($self) = @_; + + my %synonyms = ( 'input' => '-i', + 'source' => '-i', + 'excludeinput' => '-xi', + 'excludesource' => '-xi', + 'images' => '-img', + 'output' => '-o', + 'project' => '-p', + 'documentedonly' => '-do', + 'style' => '-s', + 'rebuild' => '-r', + 'rebuildoutput' => '-ro', + 'tablength' => '-t', + 'quiet' => '-q', + 'headersonly' => '-ho', + 'help' => '-h', + 'autogroup' => '-ag', + 'noautogroup' => '-nag', + 'onlyfiletitles' => '-oft', + 'onlyfiletitle' => '-oft', + 'charset' => '-cs', + 'characterset' => '-cs' ); + + + my @errorMessages; + + my $valueRef; + my $option; + + my @outputStrings; + my @imageStrings; + + + # Sometimes $valueRef is set to $ignored instead of undef because we don't want certain errors to cause other, + # unnecessary errors. For example, if they set the input directory twice, we want to show that error and swallow the + # specified directory without complaint. Otherwise it would complain about the directory too as if it were random crap + # inserted into the command line. + my $ignored; + + my $index = 0; + + while ($index < scalar @ARGV) + { + my $arg = $ARGV[$index]; + + if (substr($arg, 0, 1) eq '-') + { + $option = lc($arg); + + # Support options like -t2 as well as -t 2. + if ($option =~ /^([^0-9]+)([0-9]+)$/) + { + $option = $1; + splice(@ARGV, $index + 1, 0, $2); + }; + + # Convert long forms to short. + if (substr($option, 1, 1) eq '-') + { + # Strip all dashes. + my $newOption = $option; + $newOption =~ tr/-//d; + + if (exists $synonyms{$newOption}) + { $option = $synonyms{$newOption}; } + } + + if ($option eq '-i') + { + push @inputDirectories, undef; + $valueRef = \$inputDirectories[-1]; + } + elsif ($option eq '-xi') + { + push @excludedInputDirectories, undef; + $valueRef = \$excludedInputDirectories[-1]; + } + elsif ($option eq '-img') + { + push @imageStrings, undef; + $valueRef = \$imageStrings[-1]; + } + elsif ($option eq '-p') + { + if (defined $projectDirectory) + { + push @errorMessages, 'You cannot have more than one project directory.'; + $valueRef = \$ignored; + } + else + { $valueRef = \$projectDirectory; }; + } + elsif ($option eq '-o') + { + push @outputStrings, undef; + $valueRef = \$outputStrings[-1]; + } + elsif ($option eq '-s') + { + $valueRef = \$styles[0]; + } + elsif ($option eq '-t') + { + $valueRef = \$tabLength; + } + elsif ($option eq '-cs') + { + $valueRef = \$charset; + } + elsif ($option eq '-ag') + { + push @errorMessages, 'The -ag setting is no longer supported. You can use -nag (--no-auto-group) to turn off ' + . "auto-grouping, but there aren't multiple levels anymore."; + $valueRef = \$ignored; + } + + # Options that aren't followed by content. + else + { + $valueRef = undef; + + if ($option eq '-r') + { + NaturalDocs::Project->ReparseEverything(); + NaturalDocs::Project->RebuildEverything(); + $rebuildData = 1; + } + elsif ($option eq '-ro') + { + NaturalDocs::Project->RebuildEverything(); + } + elsif ($option eq '-do') + { $documentedOnly = 1; } + elsif ($option eq '-oft') + { $onlyFileTitles = 1; } + elsif ($option eq '-q') + { $isQuiet = 1; } + elsif ($option eq '-ho') + { + push @errorMessages, 'The -ho setting is no longer supported. You can have Natural Docs skip over the source file ' + . 'extensions by editing Languages.txt in your project directory.'; + } + elsif ($option eq '-nag') + { $noAutoGroup = 1; } + elsif ($option eq '-?' || $option eq '-h') + { + $self->PrintSyntax(); + exit; + } + else + { push @errorMessages, 'Unrecognized option ' . $option; }; + + }; + + } + + # Is a segment of text, not an option... + else + { + if (defined $valueRef) + { + # We want to preserve spaces in paths. + if (defined $$valueRef) + { $$valueRef .= ' '; }; + + $$valueRef .= $arg; + } + + else + { + push @errorMessages, 'Unrecognized element ' . $arg; + }; + }; + + $index++; + }; + + + # Validate the style, if specified. + + if ($styles[0]) + { + my @stylePieces = split(/ +/, $styles[0]); + @styles = ( ); + + while (scalar @stylePieces) + { + if (lc($stylePieces[0]) eq 'custom') + { + push @errorMessages, 'The "Custom" style setting is no longer supported. Copy your custom style sheet to your ' + . 'project directory and you can refer to it with -s.'; + shift @stylePieces; + } + else + { + # People may use styles with spaces in them. If a style doesn't exist, we need to join the pieces until we find one that + # does or we run out of pieces. + + my $extras = 0; + my $success; + + while ($extras < scalar @stylePieces) + { + my $style; + + if (!$extras) + { $style = $stylePieces[0]; } + else + { $style = join(' ', @stylePieces[0..$extras]); }; + + my $cssFile = NaturalDocs::File->JoinPaths( $self->StyleDirectory(), $style . '.css' ); + if (-e $cssFile) + { + push @styles, $style; + splice(@stylePieces, 0, 1 + $extras); + $success = 1; + last; + } + else + { + $cssFile = NaturalDocs::File->JoinPaths( $self->ProjectDirectory(), $style . '.css' ); + + if (-e $cssFile) + { + push @styles, $style; + splice(@stylePieces, 0, 1 + $extras); + $success = 1; + last; + } + else + { $extras++; }; + }; + }; + + if (!$success) + { + push @errorMessages, 'The style "' . $stylePieces[0] . '" does not exist.'; + shift @stylePieces; + }; + }; + }; + } + else + { @styles = ( 'Default' ); }; + + + # Decode and validate the output strings. + + my %outputDirectories; + + foreach my $outputString (@outputStrings) + { + my ($format, $directory) = split(/ /, $outputString, 2); + + if (!defined $directory) + { push @errorMessages, 'The -o option needs two parameters: -o [format] [directory]'; } + else + { + if (!NaturalDocs::File->PathIsAbsolute($directory)) + { $directory = NaturalDocs::File->JoinPaths(Cwd::cwd(), $directory, 1); }; + + $directory = NaturalDocs::File->CanonizePath($directory); + + if (! -e $directory || ! -d $directory) + { + # They may have forgotten the format portion and the directory name had a space in it. + if (-e ($format . ' ' . $directory) && -d ($format . ' ' . $directory)) + { + push @errorMessages, 'The -o option needs two parameters: -o [format] [directory]'; + $format = undef; + } + else + { push @errorMessages, 'The output directory ' . $directory . ' does not exist.'; } + } + elsif (exists $outputDirectories{$directory}) + { push @errorMessages, 'You cannot specify the output directory ' . $directory . ' more than once.'; } + else + { $outputDirectories{$directory} = 1; }; + + if (defined $format) + { + my $builderPackage = NaturalDocs::Builder->OutputPackageOf($format); + + if (defined $builderPackage) + { + push @buildTargets, + NaturalDocs::Settings::BuildTarget->New($builderPackage->New(), $directory); + } + else + { + push @errorMessages, 'The output format ' . $format . ' doesn\'t exist or is not installed.'; + $valueRef = \$ignored; + }; + }; + }; + }; + + if (!scalar @buildTargets) + { push @errorMessages, 'You did not specify an output directory.'; }; + + + # Decode and validate the image strings. + + foreach my $imageString (@imageStrings) + { + if ($imageString =~ /^ *\*/) + { + # The below NaturalDocs::File functions assume everything is canonized. + $imageString = NaturalDocs::File->CanonizePath($imageString); + + my ($volume, $directoryString) = NaturalDocs::File->SplitPath($imageString, 1); + my @directories = NaturalDocs::File->SplitDirectories($directoryString); + + shift @directories; + + $directoryString = NaturalDocs::File->JoinDirectories(@directories); + push @relativeImageDirectories, NaturalDocs::File->JoinPath($volume, $directoryString); + } + else + { + if (!NaturalDocs::File->PathIsAbsolute($imageString)) + { $imageString = NaturalDocs::File->JoinPaths(Cwd::cwd(), $imageString, 1); }; + + $imageString = NaturalDocs::File->CanonizePath($imageString); + + if (! -e $imageString || ! -d $imageString) + { push @errorMessages, 'The image directory ' . $imageString . ' does not exist.'; }; + + push @imageDirectories, $imageString; + }; + }; + + + # Make sure the input and project directories are specified, canonized, and exist. + + if (scalar @inputDirectories) + { + for (my $i = 0; $i < scalar @inputDirectories; $i++) + { + if (!NaturalDocs::File->PathIsAbsolute($inputDirectories[$i])) + { $inputDirectories[$i] = NaturalDocs::File->JoinPaths(Cwd::cwd(), $inputDirectories[$i], 1); }; + + $inputDirectories[$i] = NaturalDocs::File->CanonizePath($inputDirectories[$i]); + + if (! -e $inputDirectories[$i] || ! -d $inputDirectories[$i]) + { push @errorMessages, 'The input directory ' . $inputDirectories[$i] . ' does not exist.'; }; + }; + } + else + { push @errorMessages, 'You did not specify an input (source) directory.'; }; + + if (defined $projectDirectory) + { + if (!NaturalDocs::File->PathIsAbsolute($projectDirectory)) + { $projectDirectory = NaturalDocs::File->JoinPaths(Cwd::cwd(), $projectDirectory, 1); }; + + $projectDirectory = NaturalDocs::File->CanonizePath($projectDirectory); + + if (! -e $projectDirectory || ! -d $projectDirectory) + { push @errorMessages, 'The project directory ' . $projectDirectory . ' does not exist.'; }; + + # Create the Data subdirectory if it doesn't exist. + NaturalDocs::File->CreatePath( NaturalDocs::File->JoinPaths($projectDirectory, 'Data', 1) ); + } + else + { push @errorMessages, 'You did not specify a project directory.'; }; + + + # Make sure the excluded input directories are canonized, and add the project and output directories to the list. + + for (my $i = 0; $i < scalar @excludedInputDirectories; $i++) + { + if (!NaturalDocs::File->PathIsAbsolute($excludedInputDirectories[$i])) + { $excludedInputDirectories[$i] = NaturalDocs::File->JoinPaths(Cwd::cwd(), $excludedInputDirectories[$i], 1); }; + + $excludedInputDirectories[$i] = NaturalDocs::File->CanonizePath($excludedInputDirectories[$i]); + }; + + push @excludedInputDirectories, $projectDirectory; + + foreach my $buildTarget (@buildTargets) + { + push @excludedInputDirectories, $buildTarget->Directory(); + }; + + + # Determine the tab length, and default to four if not specified. + + if (defined $tabLength) + { + if ($tabLength !~ /^[0-9]+$/) + { push @errorMessages, 'The tab length must be a number.'; }; + } + else + { $tabLength = 4; }; + + + # Strip any quotes off of the charset. + $charset =~ tr/\"//d; + + + # Exit with the error message if there was one. + + if (scalar @errorMessages) + { + print join("\n", @errorMessages) . "\nType NaturalDocs -h to see the syntax reference.\n"; + exit; + }; + }; + +# +# Function: PrintSyntax +# +# Prints the syntax reference. +# +sub PrintSyntax + { + my ($self) = @_; + + # Make sure all line lengths are under 80 characters. + + print + + "Natural Docs, version " . $self->TextAppVersion() . "\n" + . $self->AppURL() . "\n" + . "This program is licensed under the GPL\n" + . "--------------------------------------\n" + . "\n" + . "Syntax:\n" + . "\n" + . " NaturalDocs -i [input (source) directory]\n" + . " (-i [input (source) directory] ...)\n" + . " -o [output format] [output directory]\n" + . " (-o [output format] [output directory] ...)\n" + . " -p [project directory]\n" + . " [options]\n" + . "\n" + . "Examples:\n" + . "\n" + . " NaturalDocs -i C:\\My Project\\Source -o HTML C:\\My Project\\Docs\n" + . " -p C:\\My Project\\Natural Docs\n" + . " NaturalDocs -i /src/project -o HTML /doc/project\n" + . " -p /etc/naturaldocs/project -s Small -q\n" + . "\n" + . "Required Parameters:\n" + . "\n" + . " -i [dir]\n--input [dir]\n--source [dir]\n" + . " Specifies an input (source) directory. Required.\n" + . " Can be specified multiple times.\n" + . "\n" + . " -o [fmt] [dir]\n--output [fmt] [dir]\n" + . " Specifies an output format and directory. Required.\n" + . " Can be specified multiple times, but only once per directory.\n" + . " Possible output formats:\n"; + + $self->PrintOutputFormats(' - '); + + print + "\n" + . " -p [dir]\n--project [dir]\n" + . " Specifies the project directory. Required.\n" + . " There needs to be a unique project directory for every source directory.\n" + . "\n" + . "Optional Parameters:\n" + . "\n" + . " -s [style] ([style] [style] ...)\n--style [style] ([style] [style] ...)\n" + . " Specifies the CSS style when building HTML output. If multiple styles are\n" + . " specified, they will all be included in the order given.\n" + . "\n" + . " -img [image directory]\n--image [image directory]" + . " Specifies an image directory. Can be specified multiple times.\n" + . " Start with * to specify a relative directory, as in -img */images.\n" + . "\n" + . " -do\n--documented-only\n" + . " Specifies only documented code aspects should be included in the output.\n" + . "\n" + . " -t [len]\n--tab-length [len]\n" + . " Specifies the number of spaces tabs should be expanded to. This only needs\n" + . " to be set if you use tabs in example code and text diagrams. Defaults to 4.\n" + . "\n" + . " -xi [dir]\n--exclude-input [dir]\n--exclude-source [dir]\n" + . " Excludes an input (source) directory from the documentation.\n" + . " Automatically done for the project and output directories. Can\n" + . " be specified multiple times.\n" + . "\n" + . " -nag\n--no-auto-group\n" + . " Turns off auto-grouping completely.\n" + . "\n" + . " -oft\n--only-file-titles\n" + . " Source files will only use the file name as the title.\n" + . "\n" + . " -r\n--rebuild\n" + . " Rebuilds all output and data files from scratch.\n" + . " Does not affect the menu file.\n" + . "\n" + . " -ro\n--rebuild-output\n" + . " Rebuilds all output files from scratch.\n" + . "\n" + . " -q\n--quiet\n" + . " Suppresses all non-error output.\n" + . "\n" + . " -?\n -h\n--help\n" + . " Displays this syntax reference.\n"; + }; + + +# +# Function: PrintOutputFormats +# +# Prints all the possible output formats that can be specified with -o. Each one will be placed on its own line. +# +# Parameters: +# +# prefix - Characters to prefix each one with, such as for indentation. +# +sub PrintOutputFormats #(prefix) + { + my ($self, $prefix) = @_; + + my $outputPackages = NaturalDocs::Builder::OutputPackages(); + + foreach my $outputPackage (@$outputPackages) + { + print $prefix . $outputPackage->CommandLineOption() . "\n"; + }; + }; + + +# +# Function: LoadAndComparePreviousSettings +# +# Loads <PreviousSettings.nd> and compares the values there with those in the command line. If differences require it, +# sets <rebuildData> and/or <rebuildOutput>. +# +sub LoadAndComparePreviousSettings + { + my ($self) = @_; + + my $fileIsOkay; + + if (!NaturalDocs::Settings->RebuildData()) + { + my $version; + + if (NaturalDocs::BinaryFile->OpenForReading( NaturalDocs::Project->DataFile('PreviousSettings.nd'), + NaturalDocs::Version->FromString('1.4') )) + { $fileIsOkay = 1; }; + }; + + if (!$fileIsOkay) + { + # We need to reparse everything because --documented-only may have changed. + # We need to rebuild everything because --tab-length may have changed. + NaturalDocs::Project->ReparseEverything(); + NaturalDocs::Project->RebuildEverything(); + } + else + { + my $raw; + + # [UInt8: tab expansion] + # [UInt8: documented only (0 or 1)] + # [UInt8: no auto-group (0 or 1)] + # [UInt8: only file titles (0 or 1)] + # [AString16: charset] + + my $prevTabLength = NaturalDocs::BinaryFile->GetUInt8(); + my $prevDocumentedOnly = NaturalDocs::BinaryFile->GetUInt8(); + my $prevNoAutoGroup = NaturalDocs::BinaryFile->GetUInt8(); + my $prevOnlyFileTitles = NaturalDocs::BinaryFile->GetUInt8(); + my $prevCharset = NaturalDocs::BinaryFile->GetAString16(); + + if ($prevTabLength != $self->TabLength()) + { + # We need to rebuild all output because this affects all text diagrams. + NaturalDocs::Project->RebuildEverything(); + }; + + if ($prevDocumentedOnly == 0) + { $prevDocumentedOnly = undef; }; + if ($prevNoAutoGroup == 0) + { $prevNoAutoGroup = undef; }; + if ($prevOnlyFileTitles == 0) + { $prevOnlyFileTitles = undef; }; + + if ($prevDocumentedOnly != $self->DocumentedOnly() || + $prevNoAutoGroup != $self->NoAutoGroup() || + $prevOnlyFileTitles != $self->OnlyFileTitles()) + { + NaturalDocs::Project->ReparseEverything(); + }; + + if ($prevCharset ne $charset) + { NaturalDocs::Project->RebuildEverything(); }; + + + # [UInt8: number of input directories] + + my $inputDirectoryCount = NaturalDocs::BinaryFile->GetUInt8(); + + while ($inputDirectoryCount) + { + # [AString16: input directory] [AString16: input directory name] ... + + my $inputDirectory = NaturalDocs::BinaryFile->GetAString16(); + my $inputDirectoryName = NaturalDocs::BinaryFile->GetAString16(); + + # Not doing anything with this for now. + + $inputDirectoryCount--; + }; + + + # [UInt8: number of output targets] + + my $outputTargetCount = NaturalDocs::BinaryFile->GetUInt8(); + + # Keys are the directories, values are the command line options. + my %previousOutputDirectories; + + while ($outputTargetCount) + { + # [AString16: output directory] [AString16: output format command line option] ... + + my $outputDirectory = NaturalDocs::BinaryFile->GetAString16(); + my $outputCommand = NaturalDocs::BinaryFile->GetAString16(); + + $previousOutputDirectories{$outputDirectory} = $outputCommand; + + $outputTargetCount--; + }; + + # Check if any targets were added to the command line, or if their formats changed. We don't care if targets were + # removed. + my $buildTargets = $self->BuildTargets(); + + foreach my $buildTarget (@$buildTargets) + { + if (!exists $previousOutputDirectories{$buildTarget->Directory()} || + $buildTarget->Builder()->CommandLineOption() ne $previousOutputDirectories{$buildTarget->Directory()}) + { + NaturalDocs::Project->RebuildEverything(); + last; + }; + }; + + NaturalDocs::BinaryFile->Close(); + }; + }; + + +# +# Function: SavePreviousSettings +# +# Saves the settings into <PreviousSettings.nd>. +# +sub SavePreviousSettings + { + my ($self) = @_; + + NaturalDocs::BinaryFile->OpenForWriting( NaturalDocs::Project->DataFile('PreviousSettings.nd') ); + + # [UInt8: tab length] + # [UInt8: documented only (0 or 1)] + # [UInt8: no auto-group (0 or 1)] + # [UInt8: only file titles (0 or 1)] + # [AString16: charset] + # [UInt8: number of input directories] + + my $inputDirectories = $self->InputDirectories(); + + NaturalDocs::BinaryFile->WriteUInt8($self->TabLength()); + NaturalDocs::BinaryFile->WriteUInt8($self->DocumentedOnly() ? 1 : 0); + NaturalDocs::BinaryFile->WriteUInt8($self->NoAutoGroup() ? 1 : 0); + NaturalDocs::BinaryFile->WriteUInt8($self->OnlyFileTitles() ? 1 : 0); + NaturalDocs::BinaryFile->WriteAString16($charset); + NaturalDocs::BinaryFile->WriteUInt8(scalar @$inputDirectories); + + foreach my $inputDirectory (@$inputDirectories) + { + my $inputDirectoryName = $self->InputDirectoryNameOf($inputDirectory); + + # [AString16: input directory] [AString16: input directory name] ... + NaturalDocs::BinaryFile->WriteAString16($inputDirectory); + NaturalDocs::BinaryFile->WriteAString16($inputDirectoryName); + }; + + # [UInt8: number of output targets] + + my $buildTargets = $self->BuildTargets(); + NaturalDocs::BinaryFile->WriteUInt8(scalar @$buildTargets); + + foreach my $buildTarget (@$buildTargets) + { + # [AString16: output directory] [AString16: output format command line option] ... + NaturalDocs::BinaryFile->WriteAString16( $buildTarget->Directory() ); + NaturalDocs::BinaryFile->WriteAString16( $buildTarget->Builder()->CommandLineOption() ); + }; + + NaturalDocs::BinaryFile->Close(); + }; + + +1; |