1 ###############################################################################
3 # Package: NaturalDocs::Settings
5 ###############################################################################
7 # A package to handle the command line and various other program settings.
9 ###############################################################################
11 # This file is part of Natural Docs, which is Copyright © 2003-2010 Greg Valure
12 # Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
13 # Refer to License.txt for the complete details
17 use NaturalDocs::Settings::BuildTarget;
22 package NaturalDocs::Settings;
25 ###############################################################################
30 Topic: Usage and Dependencies
32 - The <Constant Functions> can be called immediately.
34 - Prior to initialization, <NaturalDocs::Builder> must have all its output packages registered.
36 - To initialize, call <Load()>. All functions except <InputDirectoryNameOf()> will then be available.
38 - <GenerateDirectoryNames()> must be called before <InputDirectoryNameOf()> will work. Currently it is called by
39 <NaturalDocs::Menu->LoadAndUpdate()>.
42 Architecture: Internal Overview
44 - <Load()> first parses the command line, gathering all the settings and checking for errors. All <NaturalDocs::Builder>
45 packages must be registered before this is called because it needs their command line options.
46 <NaturalDocs::Project->ReparseEverything()> and <NaturalDocs::Project->RebuildEverything()> are called right away if -r
49 - Output directories are *not* named at this point. See <Named Directories>.
51 - The previous settings from the last time Natural Docs was run are loaded and compared to the current settings.
52 <NaturalDocs::Project->ReparseEverything()> and <NaturalDocs::Project->RebuildEverything()> are called if there are
53 any differences that warrant it.
55 - It then waits for <GenerateDirectoryNames()> to be called by <NaturalDocs::Menu>. The reason for this is that the
56 previous directory names are stored as hints in the menu file, for reasons explained in <Named Directories>. Once that
57 happens all the unnamed directories have names generated for them so everything is named. The package is completely
60 - The input directories are stored in an array instead of a hash because the order they were declared in matters. If two
61 people use multiple input directories on separate computers without sharing a menu file, they should at least get consistent
62 directory names by declaring them in the same order.
65 Architecture: Named Directories
67 Ever since Natural Docs introduced multiple input directories in 1.16, they've had to be named. Since they don't necessarily
68 extend from the same root anymore, they can't share an output directory without the risk of file name conflicts. There was
69 an early attempt at giving them actual names, but now they're just numbered from 1.
71 Directory names aren't generated right away. It waits for <Menu.txt> to load because that holds the obfuscated names from
72 the last run. <NaturalDocs::Menu> then calls <GenerateDirectoryNames()> and passes those along as hints.
73 <GenerateDirectoryNames()> then applies them to any matches and generates new ones for any remaining. This is done so
74 that output page locations can remain consistent when built on multiple computers, so long as the menu file is shared. I tend
75 to think the menu file is the most likely configuration file to be shared.
78 Architecture: Removed Directories
80 Directories that were part of the previous run but aren't anymore are still stored in the package. The primary reason, though
81 there may be others, is file purging. If an input directory is removed, all the output files that were generated from anything
82 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
83 from it's original input directory and know what that directory was named. If this didn't happen those output files would be
84 orphaned, as was the case prior to 1.32.
90 ###############################################################################
94 # handle: PREVIOUS_SETTINGS_FILEHANDLE
95 # The file handle used with <PreviousSettings.nd>.
97 # array: inputDirectories
98 # An array of input directories.
101 # array: inputDirectoryNames
102 # An array of the input directory names. Each name corresponds to the directory of the same index in <inputDirectories>.
103 my @inputDirectoryNames;
105 # array: imageDirectories
106 # An array of image directories.
107 my @imageDirectories;
109 # array: imageDirectoryNames
110 # An array of the image directory names. Each name corresponds to the directory of the same index in <imageDirectories>.
111 my @imageDirectoryNames;
113 # array: relativeImageDirectories
114 # An array of the relative paths for images. The asterisks found in the command line are not present.
115 my @relativeImageDirectories;
117 # array: excludedInputDirectories
118 # An array of input directories to exclude.
119 my @excludedInputDirectories;
121 # array: removedInputDirectories
122 # An array of input directories that were once in the command line but are no longer.
123 my @removedInputDirectories;
125 # array: removedInputDirectoryNames
126 # An array of the removed input directories' names. Each name corresponds to the directory of the same index in
127 # <removedInputDirectories>.
128 my @removedInputDirectoryNames;
130 # array: removedImageDirectories
131 # An array of image directories that were once in the command line but are no longer.
132 my @removedImageDirectories;
134 # array: removedImageDirectoryNames
135 # An array of the removed image directories' names. Each name corresponds to the directory of the same index in
136 # <removedImageDirectories>.
137 my @removedImageDirectoryNames;
139 # var: projectDirectory
140 # The project directory.
141 my $projectDirectory;
143 # array: buildTargets
144 # An array of <NaturalDocs::Settings::BuildTarget>s.
147 # var: documentedOnly
148 # Whether undocumented code aspects should be included in the output.
152 # The number of spaces in tabs.
156 # Whether auto-grouping is turned off.
159 # bool: onlyFileTitles
160 # Whether source files should always use the file name as the title.
164 # Whether the script should be run in quiet mode or not.
168 # WHether most data files should be ignored and rebuilt.
172 # An array of style names to use, most important first.
176 # Whether syntax highlighting should be applied to code tags.
179 # var: highlightAnonymous
180 # Whether syntax highlighting should be applied to anonymous code tags.
181 my $highlightAnonymous;
184 ###############################################################################
189 # File: PreviousSettings.nd
191 # Stores the previous command line settings.
196 # > [VersionInt: app version]
198 # The file starts with the standard <BINARY_FORMAT> <VersionInt> header.
200 # > [UInt8: tab length]
201 # > [UInt8: documented only (0 or 1)]
202 # > [UInt8: no auto-group (0 or 1)]
203 # > [UInt8: only file titles (0 or 1)]
204 # > [UInt8: highlight code (0 or 1)]
205 # > [UInt8: highlight anonymous (0 or 1)]
207 # > [UInt8: number of input directories]
208 # > [UString16: input directory] [UString16: input directory name] ...
210 # A count of input directories, then that number of directory/name pairs.
212 # > [UInt8: number of output targets]
213 # > [UString16: output directory] [UString16: output format command line option] ...
215 # A count of output targets, then that number of directory/format pairs.
222 # - Changed AString16s to UString16s.
230 # - Added highlight code and highlight anonymous.
234 # - Added only file titles.
242 # - Removed headers-only, which was a 0/1 UInt8 after tab length.
243 # - Change auto-group level (1 = no, 2 = yes, 3 = full only) to no auto-group (0 or 1).
247 # - Added auto-group level.
251 # - File was added to the project. Prior to 1.2, it didn't exist.
255 ###############################################################################
256 # Group: Action Functions
261 # Loads and parses all settings from the command line and configuration files. Will exit if the options are invalid or the syntax
262 # reference was requested.
268 $self->ParseCommandLine();
269 $self->LoadAndComparePreviousSettings();
276 # Saves all settings in configuration files to disk.
282 $self->SavePreviousSettings();
287 # Function: GenerateDirectoryNames
289 # Generates names for each of the input and image directories, which can later be retrieved with <InputDirectoryNameOf()>
290 # and <ImageDirectoryNameOf()>.
294 # inputHints - A hashref of suggested input directory names, where the keys are the directories and the values are the names.
295 # These take precedence over anything generated. You should include names for directories that are no longer in
296 # the command line. This parameter may be undef.
297 # imageHints - Same as inputHints, only for the image directories.
299 sub GenerateDirectoryNames #(hashref inputHints, hashref imageHints)
301 my ($self, $inputHints, $imageHints) = @_;
307 if (defined $inputHints)
309 # 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
310 # here instead of in NaturalDocs::Menu to keep the naming scheme centralized.
312 my @names = values %$inputHints;
315 foreach my $name (@names)
317 if ($name !~ /^[0-9]+$/)
327 # Hash mapping old names to new names.
330 # The sequential number to use. Starts at two because we want 'default' to be one.
331 my $currentNumber = 2;
333 # If there's only one name, we set it to one no matter what it was set to before.
334 if (scalar @names == 1)
335 { $conversion{$names[0]} = 1; }
338 # We sort the list first because we want the end result to be predictable. This conversion could be happening on many
339 # machines, and they may not all specify the input directories in the same order. They need to all come up with the
341 @names = sort @names;
343 foreach my $name (@names)
345 if ($name eq 'default')
346 { $conversion{$name} = 1; }
349 $conversion{$name} = $currentNumber;
355 # Convert them to the new names.
356 foreach my $directory (keys %$inputHints)
358 $inputHints->{$directory} = $conversion{ $inputHints->{$directory} };
363 # Now we apply all the names from the hints, and save any unused ones as removed directories.
365 for (my $i = 0; $i < scalar @inputDirectories; $i++)
367 if (exists $inputHints->{$inputDirectories[$i]})
369 $inputDirectoryNames[$i] = $inputHints->{$inputDirectories[$i]};
370 $usedInputNames{ $inputDirectoryNames[$i] } = 1;
371 delete $inputHints->{$inputDirectories[$i]};
376 # Any remaining hints are saved as removed directories.
378 while (my ($directory, $name) = each %$inputHints)
380 push @removedInputDirectories, $directory;
381 push @removedInputDirectoryNames, $name;
386 if (defined $imageHints)
388 # Image directory names were never non-numeric, so there is no conversion. Apply all the names from the hints.
390 for (my $i = 0; $i < scalar @imageDirectories; $i++)
392 if (exists $imageHints->{$imageDirectories[$i]})
394 $imageDirectoryNames[$i] = $imageHints->{$imageDirectories[$i]};
395 $usedImageNames{ $imageDirectoryNames[$i] } = 1;
396 delete $imageHints->{$imageDirectories[$i]};
401 # Any remaining hints are saved as removed directories.
403 while (my ($directory, $name) = each %$imageHints)
405 push @removedImageDirectories, $directory;
406 push @removedImageDirectoryNames, $name;
411 # Now we generate names for anything remaining.
413 my $inputCounter = 1;
415 for (my $i = 0; $i < scalar @inputDirectories; $i++)
417 if (!defined $inputDirectoryNames[$i])
419 while (exists $usedInputNames{$inputCounter})
420 { $inputCounter++; };
422 $inputDirectoryNames[$i] = $inputCounter;
423 $usedInputNames{$inputCounter} = 1;
430 my $imageCounter = 1;
432 for (my $i = 0; $i < scalar @imageDirectories; $i++)
434 if (!defined $imageDirectoryNames[$i])
436 while (exists $usedImageNames{$imageCounter})
437 { $imageCounter++; };
439 $imageDirectoryNames[$i] = $imageCounter;
440 $usedImageNames{$imageCounter} = 1;
449 ###############################################################################
450 # Group: Information Functions
454 # Function: InputDirectories
456 # Returns an arrayref of input directories. Do not change.
458 # This will not return any removed input directories.
461 { return \@inputDirectories; };
464 # Function: InputDirectoryNameOf
466 # Returns the generated name of the passed input directory. <GenerateDirectoryNames()> must be called once before this
467 # function is available.
469 # If a name for a removed input directory is available, it will be returned as well.
471 sub InputDirectoryNameOf #(directory)
473 my ($self, $directory) = @_;
475 for (my $i = 0; $i < scalar @inputDirectories; $i++)
477 if ($directory eq $inputDirectories[$i])
478 { return $inputDirectoryNames[$i]; };
481 for (my $i = 0; $i < scalar @removedInputDirectories; $i++)
483 if ($directory eq $removedInputDirectories[$i])
484 { return $removedInputDirectoryNames[$i]; };
492 # Function: SplitFromInputDirectory
494 # Takes an input file name and returns the array ( inputDirectory, relativePath ).
496 # If the file cannot be split from an input directory, it will try to do it with the removed input directories.
498 sub SplitFromInputDirectory #(file)
500 my ($self, $file) = @_;
502 foreach my $directory (@inputDirectories, @removedInputDirectories)
504 if (NaturalDocs::File->IsSubPathOf($directory, $file))
505 { return ( $directory, NaturalDocs::File->MakeRelativePath($directory, $file) ); };
513 # Function: ImageDirectories
515 # Returns an arrayref of image directories. Do not change.
517 # This will not return any removed image directories.
520 { return \@imageDirectories; };
524 # Function: ImageDirectoryNameOf
526 # Returns the generated name of the passed image or input directory. <GenerateDirectoryNames()> must be called once before
527 # this function is available.
529 # If a name for a removed input or image directory is available, it will be returned as well.
531 sub ImageDirectoryNameOf #(directory)
533 my ($self, $directory) = @_;
535 for (my $i = 0; $i < scalar @imageDirectories; $i++)
537 if ($directory eq $imageDirectories[$i])
538 { return $imageDirectoryNames[$i]; };
541 for (my $i = 0; $i < scalar @removedImageDirectories; $i++)
543 if ($directory eq $removedImageDirectories[$i])
544 { return $removedImageDirectoryNames[$i]; };
552 # Function: SplitFromImageDirectory
554 # Takes an input image file name and returns the array ( imageDirectory, relativePath ).
556 # If the file cannot be split from an image directory, it will try to do it with the removed image directories.
558 sub SplitFromImageDirectory #(file)
560 my ($self, $file) = @_;
562 foreach my $directory (@imageDirectories, @removedImageDirectories)
564 if (NaturalDocs::File->IsSubPathOf($directory, $file))
565 { return ( $directory, NaturalDocs::File->MakeRelativePath($directory, $file) ); };
573 # Function: RelativeImageDirectories
575 # Returns an arrayref of relative image directories. Do not change.
577 sub RelativeImageDirectories
578 { return \@relativeImageDirectories; };
581 # Function: ExcludedInputDirectories
582 # Returns an arrayref of input directories to exclude. Do not change.
583 sub ExcludedInputDirectories
584 { return \@excludedInputDirectories; };
587 # Function: BuildTargets
588 # Returns an arrayref of <NaturalDocs::Settings::BuildTarget>s. Do not change.
590 { return \@buildTargets; };
594 # Function: OutputDirectoryOf
596 # Returns the output directory of a builder object.
600 # object - The builder object, whose class is derived from <NaturalDocs::Builder::Base>.
604 # The builder directory, or undef if the object wasn't found..
606 sub OutputDirectoryOf #(object)
608 my ($self, $object) = @_;
610 foreach my $buildTarget (@buildTargets)
612 if ($buildTarget->Builder() == $object)
613 { return $buildTarget->Directory(); };
621 # Returns an arrayref of the styles associated with the output.
623 { return \@styles; };
625 # Function: ProjectDirectory
626 # Returns the project directory.
628 { return $projectDirectory; };
630 # Function: ProjectDataDirectory
631 # Returns the project data directory.
632 sub ProjectDataDirectory
633 { return NaturalDocs::File->JoinPaths($projectDirectory, 'Data', 1); };
635 # Function: StyleDirectory
636 # Returns the main style directory.
638 { return NaturalDocs::File->JoinPaths($FindBin::RealBin, 'Styles', 1); };
640 # Function: JavaScriptDirectory
641 # Returns the main JavaScript directory.
642 sub JavaScriptDirectory
643 { return NaturalDocs::File->JoinPaths($FindBin::RealBin, 'JavaScript', 1); };
645 # Function: ConfigDirectory
646 # Returns the main configuration directory.
648 { return NaturalDocs::File->JoinPaths($FindBin::RealBin, 'Config', 1); };
650 # Function: DocumentedOnly
651 # Returns whether undocumented code aspects should be included in the output.
653 { return $documentedOnly; };
655 # Function: TabLength
656 # Returns the number of spaces tabs should be expanded to.
658 { return $tabLength; };
660 # Function: NoAutoGroup
661 # Returns whether auto-grouping is turned off.
663 { return $noAutoGroup; };
665 # Function: OnlyFileTitles
666 # Returns whether source files should always use the file name as the title.
668 { return $onlyFileTitles; };
671 # Returns whether the script should be run in quiet mode or not.
673 { return $isQuiet; };
675 # Function: RebuildData
676 # Returns whether all data files should be ignored and rebuilt.
678 { return $rebuildData; };
680 # Function: HighlightCode
681 # Returns whether to apply syntax highlighting (start code) sections.
683 { return $highlightCode; }
685 # Function: HighlightAnonymous
686 # Returns whether to apply syntax highlighting to anonymous code sections designated with :, >, or |.
687 sub HighlightAnonymous
688 { return $highlightAnonymous; }
691 ###############################################################################
692 # Group: Constant Functions
695 # Function: AppVersion
697 # Returns Natural Docs' version number as an integer. Use <TextAppVersion()> to get a printable version.
702 return NaturalDocs::Version->FromString($self->TextAppVersion());
706 # Function: TextAppVersion
708 # Returns Natural Docs' version number as plain text.
718 # Returns a string of the project's current web address.
721 { return 'http://www.naturaldocs.org'; };
725 ###############################################################################
726 # Group: Support Functions
730 # Function: ParseCommandLine
732 # Parses and validates the command line. Will cause the script to exit if the options ask for the syntax reference or
739 my %synonyms = ( 'input' => '-i',
741 'excludeinput' => '-xi',
742 'excludesource' => '-xi',
746 'documentedonly' => '-do',
749 'rebuildoutput' => '-ro',
752 'headersonly' => '-ho',
754 'autogroup' => '-ag',
755 'noautogroup' => '-nag',
756 'onlyfiletitles' => '-oft',
757 'onlyfiletitle' => '-oft',
758 'highlight' => '-hl',
759 'highlighting' => '-hl' );
772 # Sometimes $valueRef is set to $ignored instead of undef because we don't want certain errors to cause other,
773 # unnecessary errors. For example, if they set the input directory twice, we want to show that error and swallow the
774 # specified directory without complaint. Otherwise it would complain about the directory too as if it were random crap
775 # inserted into the command line.
780 while ($index < scalar @ARGV)
782 my $arg = $ARGV[$index];
784 if (substr($arg, 0, 1) eq '-')
788 # Support options like -t2 as well as -t 2.
789 if ($option =~ /^([^0-9]+)([0-9]+)$/)
792 splice(@ARGV, $index + 1, 0, $2);
795 # Convert long forms to short.
796 if (substr($option, 1, 1) eq '-')
799 my $newOption = $option;
800 $newOption =~ tr/-//d;
802 if (exists $synonyms{$newOption})
803 { $option = $synonyms{$newOption}; }
808 push @inputDirectories, undef;
809 $valueRef = \$inputDirectories[-1];
811 elsif ($option eq '-xi')
813 push @excludedInputDirectories, undef;
814 $valueRef = \$excludedInputDirectories[-1];
816 elsif ($option eq '-img')
818 push @imageStrings, undef;
819 $valueRef = \$imageStrings[-1];
821 elsif ($option eq '-p')
823 if (defined $projectDirectory)
825 push @errorMessages, 'You cannot have more than one project directory.';
826 $valueRef = \$ignored;
829 { $valueRef = \$projectDirectory; };
831 elsif ($option eq '-o')
833 push @outputStrings, undef;
834 $valueRef = \$outputStrings[-1];
836 elsif ($option eq '-s')
838 $valueRef = \$styles[0];
840 elsif ($option eq '-t')
842 $valueRef = \$tabLength;
844 elsif ($option eq '-hl')
846 $valueRef = \$highlightString;
848 elsif ($option eq '-ag')
850 push @errorMessages, 'The -ag setting is no longer supported. You can use -nag (--no-auto-group) to turn off '
851 . "auto-grouping, but there aren't multiple levels anymore.";
852 $valueRef = \$ignored;
855 # Options that aren't followed by content.
862 NaturalDocs::Project->ReparseEverything();
863 NaturalDocs::Project->RebuildEverything();
866 elsif ($option eq '-ro')
868 NaturalDocs::Project->RebuildEverything();
870 elsif ($option eq '-do')
871 { $documentedOnly = 1; }
872 elsif ($option eq '-oft')
873 { $onlyFileTitles = 1; }
874 elsif ($option eq '-q')
876 elsif ($option eq '-ho')
878 push @errorMessages, 'The -ho setting is no longer supported. You can have Natural Docs skip over the source file '
879 . 'extensions by editing Languages.txt in your project directory.';
881 elsif ($option eq '-nag')
882 { $noAutoGroup = 1; }
883 elsif ($option eq '-?' || $option eq '-h')
885 $self->PrintSyntax();
889 { push @errorMessages, 'Unrecognized option ' . $option; };
895 # Is a segment of text, not an option...
898 if (defined $valueRef)
900 # We want to preserve spaces in paths.
901 if (defined $$valueRef)
902 { $$valueRef .= ' '; };
909 push @errorMessages, 'Unrecognized element ' . $arg;
917 # Validate the style, if specified.
921 my @stylePieces = split(/ +/, $styles[0]);
924 while (scalar @stylePieces)
926 if (lc($stylePieces[0]) eq 'custom')
928 push @errorMessages, 'The "Custom" style setting is no longer supported. Copy your custom style sheet to your '
929 . 'project directory and you can refer to it with -s.';
934 # 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
935 # does or we run out of pieces.
940 while ($extras < scalar @stylePieces)
945 { $style = $stylePieces[0]; }
947 { $style = join(' ', @stylePieces[0..$extras]); };
949 my $cssFile = NaturalDocs::File->JoinPaths( $self->StyleDirectory(), $style . '.css' );
952 push @styles, $style;
953 splice(@stylePieces, 0, 1 + $extras);
959 $cssFile = NaturalDocs::File->JoinPaths( $self->ProjectDirectory(), $style . '.css' );
963 push @styles, $style;
964 splice(@stylePieces, 0, 1 + $extras);
975 push @errorMessages, 'The style "' . $stylePieces[0] . '" does not exist.';
982 { @styles = ( 'Default' ); };
985 # Decode and validate the output strings.
987 my %outputDirectories;
989 foreach my $outputString (@outputStrings)
991 my ($format, $directory) = split(/ /, $outputString, 2);
993 if (!defined $directory)
994 { push @errorMessages, 'The -o option needs two parameters: -o [format] [directory]'; }
997 if (!NaturalDocs::File->PathIsAbsolute($directory))
998 { $directory = NaturalDocs::File->JoinPaths(Cwd::cwd(), $directory, 1); };
1000 $directory = NaturalDocs::File->CanonizePath($directory);
1002 if (! -e $directory || ! -d $directory)
1004 # They may have forgotten the format portion and the directory name had a space in it.
1005 if (-e ($format . ' ' . $directory) && -d ($format . ' ' . $directory))
1007 push @errorMessages, 'The -o option needs two parameters: -o [format] [directory]';
1011 { push @errorMessages, 'The output directory ' . $directory . ' does not exist.'; }
1013 elsif (exists $outputDirectories{$directory})
1014 { push @errorMessages, 'You cannot specify the output directory ' . $directory . ' more than once.'; }
1016 { $outputDirectories{$directory} = 1; };
1018 if (defined $format)
1020 my $builderPackage = NaturalDocs::Builder->OutputPackageOf($format);
1022 if (defined $builderPackage)
1025 NaturalDocs::Settings::BuildTarget->New($builderPackage->New(), $directory);
1029 push @errorMessages, 'The output format ' . $format . ' doesn\'t exist or is not installed.';
1030 $valueRef = \$ignored;
1036 if (!scalar @buildTargets)
1037 { push @errorMessages, 'You did not specify an output directory.'; };
1040 # Decode and validate the image strings.
1042 foreach my $imageString (@imageStrings)
1044 if ($imageString =~ /^ *\*/)
1046 # The below NaturalDocs::File functions assume everything is canonized.
1047 $imageString = NaturalDocs::File->CanonizePath($imageString);
1049 my ($volume, $directoryString) = NaturalDocs::File->SplitPath($imageString, 1);
1050 my @directories = NaturalDocs::File->SplitDirectories($directoryString);
1054 $directoryString = NaturalDocs::File->JoinDirectories(@directories);
1055 push @relativeImageDirectories, NaturalDocs::File->JoinPath($volume, $directoryString);
1059 if (!NaturalDocs::File->PathIsAbsolute($imageString))
1060 { $imageString = NaturalDocs::File->JoinPaths(Cwd::cwd(), $imageString, 1); };
1062 $imageString = NaturalDocs::File->CanonizePath($imageString);
1064 if (! -e $imageString || ! -d $imageString)
1065 { push @errorMessages, 'The image directory ' . $imageString . ' does not exist.'; };
1067 push @imageDirectories, $imageString;
1072 # Make sure the input and project directories are specified, canonized, and exist.
1074 if (scalar @inputDirectories)
1076 for (my $i = 0; $i < scalar @inputDirectories; $i++)
1078 if (!NaturalDocs::File->PathIsAbsolute($inputDirectories[$i]))
1079 { $inputDirectories[$i] = NaturalDocs::File->JoinPaths(Cwd::cwd(), $inputDirectories[$i], 1); };
1081 $inputDirectories[$i] = NaturalDocs::File->CanonizePath($inputDirectories[$i]);
1083 if (! -e $inputDirectories[$i] || ! -d $inputDirectories[$i])
1084 { push @errorMessages, 'The input directory ' . $inputDirectories[$i] . ' does not exist.'; };
1088 { push @errorMessages, 'You did not specify an input (source) directory.'; };
1090 if (defined $projectDirectory)
1092 if (!NaturalDocs::File->PathIsAbsolute($projectDirectory))
1093 { $projectDirectory = NaturalDocs::File->JoinPaths(Cwd::cwd(), $projectDirectory, 1); };
1095 $projectDirectory = NaturalDocs::File->CanonizePath($projectDirectory);
1097 if (! -e $projectDirectory || ! -d $projectDirectory)
1098 { push @errorMessages, 'The project directory ' . $projectDirectory . ' does not exist.'; };
1100 # Create the Data subdirectory if it doesn't exist.
1101 NaturalDocs::File->CreatePath( NaturalDocs::File->JoinPaths($projectDirectory, 'Data', 1) );
1104 { push @errorMessages, 'You did not specify a project directory.'; };
1107 # Make sure the excluded input directories are canonized, and add the project and output directories to the list.
1109 for (my $i = 0; $i < scalar @excludedInputDirectories; $i++)
1111 if (!NaturalDocs::File->PathIsAbsolute($excludedInputDirectories[$i]))
1112 { $excludedInputDirectories[$i] = NaturalDocs::File->JoinPaths(Cwd::cwd(), $excludedInputDirectories[$i], 1); };
1114 $excludedInputDirectories[$i] = NaturalDocs::File->CanonizePath($excludedInputDirectories[$i]);
1117 push @excludedInputDirectories, $projectDirectory;
1119 foreach my $buildTarget (@buildTargets)
1121 push @excludedInputDirectories, $buildTarget->Directory();
1125 # Determine the tab length, and default to four if not specified.
1127 if (defined $tabLength)
1129 if ($tabLength !~ /^[0-9]+$/)
1130 { push @errorMessages, 'The tab length must be a number.'; };
1133 { $tabLength = 4; };
1136 # Decode and validate the highlight setting.
1138 if (defined $highlightString)
1140 $highlightString = lc($highlightString);
1142 if ($highlightString eq 'off')
1144 $highlightCode = undef;
1145 $highlightAnonymous = undef;
1147 elsif ($highlightString eq 'code')
1150 $highlightAnonymous = undef;
1152 elsif ($highlightString eq 'all')
1155 $highlightAnonymous = 1;
1158 { push @errorMessages, $highlightString . ' is not a valid value for --highlight.'; }
1163 $highlightAnonymous = undef;
1167 # Exit with the error message if there was one.
1169 if (scalar @errorMessages)
1171 print join("\n", @errorMessages) . "\nType NaturalDocs -h to see the syntax reference.\n";
1177 # Function: PrintSyntax
1179 # Prints the syntax reference.
1185 # Make sure all line lengths are under 80 characters.
1189 "Natural Docs, version " . $self->TextAppVersion() . "\n"
1190 . $self->AppURL() . "\n"
1191 . "This program is licensed under version 3 of the AGPL\n"
1192 . "Refer to License.txt for the complete details\n"
1193 . "--------------------------------------\n"
1197 . " NaturalDocs -i [input (source) directory]\n"
1198 . " (-i [input (source) directory] ...)\n"
1199 . " -o [output format] [output directory]\n"
1200 . " (-o [output format] [output directory] ...)\n"
1201 . " -p [project directory]\n"
1206 . " NaturalDocs -i C:\\My Project\\Source -o HTML C:\\My Project\\Docs\n"
1207 . " -p C:\\My Project\\Natural Docs\n"
1208 . " NaturalDocs -i /src/project -o HTML /doc/project\n"
1209 . " -p /etc/naturaldocs/project -s Small -q\n"
1211 . "Required Parameters:\n"
1213 . " -i [dir]\n--input [dir]\n--source [dir]\n"
1214 . " Specifies an input (source) directory. Required.\n"
1215 . " Can be specified multiple times.\n"
1217 . " -o [fmt] [dir]\n--output [fmt] [dir]\n"
1218 . " Specifies an output format and directory. Required.\n"
1219 . " Can be specified multiple times, but only once per directory.\n"
1220 . " Possible output formats:\n";
1222 $self->PrintOutputFormats(' - ');
1226 . " -p [dir]\n--project [dir]\n"
1227 . " Specifies the project directory. Required.\n"
1228 . " There needs to be a unique project directory for every source directory.\n"
1230 . "Optional Parameters:\n"
1232 . " -s [style] ([style] [style] ...)\n--style [style] ([style] [style] ...)\n"
1233 . " Specifies the CSS style when building HTML output. If multiple styles are\n"
1234 . " specified, they will all be included in the order given.\n"
1236 . " -img [image directory]\n--image [image directory]\n"
1237 . " Specifies an image directory. Can be specified multiple times.\n"
1238 . " Start with * to specify a relative directory, as in -img */images.\n"
1240 . " -do\n--documented-only\n"
1241 . " Specifies only documented code aspects should be included in the output.\n"
1243 . " -t [len]\n--tab-length [len]\n"
1244 . " Specifies the number of spaces tabs should be expanded to. This only needs\n"
1245 . " to be set if you use tabs in example code and text diagrams. Defaults to 4.\n"
1247 . " -xi [dir]\n--exclude-input [dir]\n--exclude-source [dir]\n"
1248 . " Excludes an input (source) directory from the documentation.\n"
1249 . " Automatically done for the project and output directories. Can\n"
1250 . " be specified multiple times.\n"
1252 . " -nag\n--no-auto-group\n"
1253 . " Turns off auto-grouping completely.\n"
1255 . " -oft\n--only-file-titles\n"
1256 . " Source files will only use the file name as the title.\n"
1258 . " -hl [option]\n--highlight [option]\n"
1259 . " Specifies when syntax highlighting should be applied. Defaults to code.\n"
1260 . " off - No syntax highlighting is applied.\n"
1261 . " code - Syntax highlighting is only applied to prototypes and (start code)\n"
1263 . " all - Systax highlighting is applied to prototypes, (start code) segments,\n"
1264 . " and lines starting with >, :, or |."
1266 . " -r\n--rebuild\n"
1267 . " Rebuilds all output and data files from scratch.\n"
1268 . " Does not affect the menu file.\n"
1270 . " -ro\n--rebuild-output\n"
1271 . " Rebuilds all output files from scratch.\n"
1274 . " Suppresses all non-error output.\n"
1276 . " -?\n -h\n--help\n"
1277 . " Displays this syntax reference.\n";
1282 # Function: PrintOutputFormats
1284 # Prints all the possible output formats that can be specified with -o. Each one will be placed on its own line.
1288 # prefix - Characters to prefix each one with, such as for indentation.
1290 sub PrintOutputFormats #(prefix)
1292 my ($self, $prefix) = @_;
1294 my $outputPackages = NaturalDocs::Builder::OutputPackages();
1296 foreach my $outputPackage (@$outputPackages)
1298 print $prefix . $outputPackage->CommandLineOption() . "\n";
1304 # Function: LoadAndComparePreviousSettings
1306 # Loads <PreviousSettings.nd> and compares the values there with those in the command line. If differences require it,
1307 # sets <rebuildData> and/or <rebuildOutput>.
1309 sub LoadAndComparePreviousSettings
1315 if (!NaturalDocs::Settings->RebuildData())
1319 if (NaturalDocs::BinaryFile->OpenForReading( NaturalDocs::Project->DataFile('PreviousSettings.nd'),
1320 NaturalDocs::Version->FromString('1.52') ))
1321 { $fileIsOkay = 1; };
1326 # We need to reparse everything because --documented-only may have changed.
1327 # We need to rebuild everything because --tab-length may have changed.
1328 NaturalDocs::Project->ReparseEverything();
1329 NaturalDocs::Project->RebuildEverything();
1335 # [UInt8: tab expansion]
1336 # [UInt8: documented only (0 or 1)]
1337 # [UInt8: no auto-group (0 or 1)]
1338 # [UInt8: only file titles (0 or 1)]
1339 # [UInt8: highlight code (0 or 1)]
1340 # [UInt8: highlight anonymous (0 or 1)]
1342 my $prevTabLength = NaturalDocs::BinaryFile->GetUInt8();
1343 my $prevDocumentedOnly = NaturalDocs::BinaryFile->GetUInt8();
1344 my $prevNoAutoGroup = NaturalDocs::BinaryFile->GetUInt8();
1345 my $prevOnlyFileTitles = NaturalDocs::BinaryFile->GetUInt8();
1346 my $prevHighlightCode = NaturalDocs::BinaryFile->GetUInt8();
1347 my $prevHighlightAnonymous = NaturalDocs::BinaryFile->GetUInt8();
1349 if ($prevDocumentedOnly == 0)
1350 { $prevDocumentedOnly = undef; };
1351 if ($prevNoAutoGroup == 0)
1352 { $prevNoAutoGroup = undef; };
1353 if ($prevOnlyFileTitles == 0)
1354 { $prevOnlyFileTitles = undef; };
1355 if ($prevHighlightCode == 0)
1356 { $prevHighlightCode = undef; };
1357 if ($prevHighlightAnonymous == 0)
1358 { $prevHighlightAnonymous = undef; };
1360 if ($prevTabLength != $self->TabLength() ||
1361 $prevHighlightCode != $self->HighlightCode() ||
1362 $prevHighlightAnonymous != $self->HighlightAnonymous())
1364 NaturalDocs::Project->RebuildEverything();
1367 if ($prevDocumentedOnly != $self->DocumentedOnly() ||
1368 $prevNoAutoGroup != $self->NoAutoGroup() ||
1369 $prevOnlyFileTitles != $self->OnlyFileTitles())
1371 NaturalDocs::Project->ReparseEverything();
1375 # [UInt8: number of input directories]
1377 my $inputDirectoryCount = NaturalDocs::BinaryFile->GetUInt8();
1379 while ($inputDirectoryCount)
1381 # [UString16: input directory] [UString16: input directory name] ...
1383 my $inputDirectory = NaturalDocs::BinaryFile->GetUString16();
1384 my $inputDirectoryName = NaturalDocs::BinaryFile->GetUString16();
1386 # Not doing anything with this for now.
1388 $inputDirectoryCount--;
1392 # [UInt8: number of output targets]
1394 my $outputTargetCount = NaturalDocs::BinaryFile->GetUInt8();
1396 # Keys are the directories, values are the command line options.
1397 my %previousOutputDirectories;
1399 while ($outputTargetCount)
1401 # [UString16: output directory] [UString16: output format command line option] ...
1403 my $outputDirectory = NaturalDocs::BinaryFile->GetUString16();
1404 my $outputCommand = NaturalDocs::BinaryFile->GetUString16();
1406 $previousOutputDirectories{$outputDirectory} = $outputCommand;
1408 $outputTargetCount--;
1411 # Check if any targets were added to the command line, or if their formats changed. We don't care if targets were
1413 my $buildTargets = $self->BuildTargets();
1415 foreach my $buildTarget (@$buildTargets)
1417 if (!exists $previousOutputDirectories{$buildTarget->Directory()} ||
1418 $buildTarget->Builder()->CommandLineOption() ne $previousOutputDirectories{$buildTarget->Directory()})
1420 NaturalDocs::Project->RebuildEverything();
1425 NaturalDocs::BinaryFile->Close();
1431 # Function: SavePreviousSettings
1433 # Saves the settings into <PreviousSettings.nd>.
1435 sub SavePreviousSettings
1439 NaturalDocs::BinaryFile->OpenForWriting( NaturalDocs::Project->DataFile('PreviousSettings.nd') );
1441 # [UInt8: tab length]
1442 # [UInt8: documented only (0 or 1)]
1443 # [UInt8: no auto-group (0 or 1)]
1444 # [UInt8: only file titles (0 or 1)]
1445 # [UInt8: highlight code (0 or 1)]
1446 # [UInt8: highlight anonymous (0 or 1)]
1447 # [UInt8: number of input directories]
1449 my $inputDirectories = $self->InputDirectories();
1451 NaturalDocs::BinaryFile->WriteUInt8($self->TabLength());
1452 NaturalDocs::BinaryFile->WriteUInt8($self->DocumentedOnly() ? 1 : 0);
1453 NaturalDocs::BinaryFile->WriteUInt8($self->NoAutoGroup() ? 1 : 0);
1454 NaturalDocs::BinaryFile->WriteUInt8($self->OnlyFileTitles() ? 1 : 0);
1455 NaturalDocs::BinaryFile->WriteUInt8($self->HighlightCode() ? 1 : 0);
1456 NaturalDocs::BinaryFile->WriteUInt8($self->HighlightAnonymous() ? 1 : 0);
1457 NaturalDocs::BinaryFile->WriteUInt8(scalar @$inputDirectories);
1459 foreach my $inputDirectory (@$inputDirectories)
1461 my $inputDirectoryName = $self->InputDirectoryNameOf($inputDirectory);
1463 # [UString16: input directory] [UString16: input directory name] ...
1464 NaturalDocs::BinaryFile->WriteUString16($inputDirectory);
1465 NaturalDocs::BinaryFile->WriteUString16($inputDirectoryName);
1468 # [UInt8: number of output targets]
1470 my $buildTargets = $self->BuildTargets();
1471 NaturalDocs::BinaryFile->WriteUInt8(scalar @$buildTargets);
1473 foreach my $buildTarget (@$buildTargets)
1475 # [UString16: output directory] [UString16: output format command line option] ...
1476 NaturalDocs::BinaryFile->WriteUString16( $buildTarget->Directory() );
1477 NaturalDocs::BinaryFile->WriteUString16( $buildTarget->Builder()->CommandLineOption() );
1480 NaturalDocs::BinaryFile->Close();