1############################################################################### 2# 3# Package: NaturalDocs::Settings 4# 5############################################################################### 6# 7# A package to handle the command line and various other program settings. 8# 9############################################################################### 10 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 14 15use Cwd (); 16 17use NaturalDocs::Settings::BuildTarget; 18 19use strict; 20use integer; 21 22package NaturalDocs::Settings; 23 24 25############################################################################### 26# Group: Information 27 28=pod begin nd 29 30 Topic: Usage and Dependencies 31 32 - The <Constant Functions> can be called immediately. 33 34 - Prior to initialization, <NaturalDocs::Builder> must have all its output packages registered. 35 36 - To initialize, call <Load()>. All functions except <InputDirectoryNameOf()> will then be available. 37 38 - <GenerateDirectoryNames()> must be called before <InputDirectoryNameOf()> will work. Currently it is called by 39 <NaturalDocs::Menu->LoadAndUpdate()>. 40 41 42 Architecture: Internal Overview 43 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 47 or -ro are used. 48 49 - Output directories are *not* named at this point. See <Named Directories>. 50 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. 54 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 58 set up. 59 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. 63 64 65 Architecture: Named Directories 66 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. 70 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. 76 77 78 Architecture: Removed Directories 79 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. 85 86=cut 87 88 89 90############################################################################### 91# Group: Variables 92 93 94# handle: PREVIOUS_SETTINGS_FILEHANDLE 95# The file handle used with <PreviousSettings.nd>. 96 97# array: inputDirectories 98# An array of input directories. 99my @inputDirectories; 100 101# array: inputDirectoryNames 102# An array of the input directory names. Each name corresponds to the directory of the same index in <inputDirectories>. 103my @inputDirectoryNames; 104 105# array: imageDirectories 106# An array of image directories. 107my @imageDirectories; 108 109# array: imageDirectoryNames 110# An array of the image directory names. Each name corresponds to the directory of the same index in <imageDirectories>. 111my @imageDirectoryNames; 112 113# array: relativeImageDirectories 114# An array of the relative paths for images. The asterisks found in the command line are not present. 115my @relativeImageDirectories; 116 117# array: excludedInputDirectories 118# An array of input directories to exclude. 119my @excludedInputDirectories; 120 121# array: removedInputDirectories 122# An array of input directories that were once in the command line but are no longer. 123my @removedInputDirectories; 124 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>. 128my @removedInputDirectoryNames; 129 130# array: removedImageDirectories 131# An array of image directories that were once in the command line but are no longer. 132my @removedImageDirectories; 133 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>. 137my @removedImageDirectoryNames; 138 139# var: projectDirectory 140# The project directory. 141my $projectDirectory; 142 143# array: buildTargets 144# An array of <NaturalDocs::Settings::BuildTarget>s. 145my @buildTargets; 146 147# var: documentedOnly 148# Whether undocumented code aspects should be included in the output. 149my $documentedOnly; 150 151# int: tabLength 152# The number of spaces in tabs. 153my $tabLength; 154 155# bool: noAutoGroup 156# Whether auto-grouping is turned off. 157my $noAutoGroup; 158 159# bool: onlyFileTitles 160# Whether source files should always use the file name as the title. 161my $onlyFileTitles; 162 163# bool: isQuiet 164# Whether the script should be run in quiet mode or not. 165my $isQuiet; 166 167# bool: rebuildData 168# WHether most data files should be ignored and rebuilt. 169my $rebuildData; 170 171# array: styles 172# An array of style names to use, most important first. 173my @styles; 174 175# var: highlightCode 176# Whether syntax highlighting should be applied to code tags. 177my $highlightCode; 178 179# var: highlightAnonymous 180# Whether syntax highlighting should be applied to anonymous code tags. 181my $highlightAnonymous; 182 183 184############################################################################### 185# Group: Files 186 187 188# 189# File: PreviousSettings.nd 190# 191# Stores the previous command line settings. 192# 193# Format: 194# 195# > [BINARY_FORMAT] 196# > [VersionInt: app version] 197# 198# The file starts with the standard <BINARY_FORMAT> <VersionInt> header. 199# 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)] 206# > 207# > [UInt8: number of input directories] 208# > [UString16: input directory] [UString16: input directory name] ... 209# 210# A count of input directories, then that number of directory/name pairs. 211# 212# > [UInt8: number of output targets] 213# > [UString16: output directory] [UString16: output format command line option] ... 214# 215# A count of output targets, then that number of directory/format pairs. 216# 217# 218# Revisions: 219# 220# 1.52: 221# 222# - Changed AString16s to UString16s. 223# 224# 1.51: 225# 226# - Removed charset. 227# 228# 1.5: 229# 230# - Added highlight code and highlight anonymous. 231# 232# 1.4: 233# 234# - Added only file titles. 235# 236# 1.33: 237# 238# - Added charset. 239# 240# 1.3: 241# 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). 244# 245# 1.22: 246# 247# - Added auto-group level. 248# 249# 1.2: 250# 251# - File was added to the project. Prior to 1.2, it didn't exist. 252# 253 254 255############################################################################### 256# Group: Action Functions 257 258# 259# Function: Load 260# 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. 263# 264sub Load 265 { 266 my ($self) = @_; 267 268 $self->ParseCommandLine(); 269 $self->LoadAndComparePreviousSettings(); 270 }; 271 272 273# 274# Function: Save 275# 276# Saves all settings in configuration files to disk. 277# 278sub Save 279 { 280 my ($self) = @_; 281 282 $self->SavePreviousSettings(); 283 }; 284 285 286# 287# Function: GenerateDirectoryNames 288# 289# Generates names for each of the input and image directories, which can later be retrieved with <InputDirectoryNameOf()> 290# and <ImageDirectoryNameOf()>. 291# 292# Parameters: 293# 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. 298# 299sub GenerateDirectoryNames #(hashref inputHints, hashref imageHints) 300 { 301 my ($self, $inputHints, $imageHints) = @_; 302 303 my %usedInputNames; 304 my %usedImageNames; 305 306 307 if (defined $inputHints) 308 { 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. 311 312 my @names = values %$inputHints; 313 my $hasNonNumeric; 314 315 foreach my $name (@names) 316 { 317 if ($name !~ /^[0-9]+$/) 318 { 319 $hasNonNumeric = 1; 320 last; 321 }; 322 }; 323 324 325 if ($hasNonNumeric) 326 { 327 # Hash mapping old names to new names. 328 my %conversion; 329 330 # The sequential number to use. Starts at two because we want 'default' to be one. 331 my $currentNumber = 2; 332 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; } 336 else 337 { 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 340 # same result. 341 @names = sort @names; 342 343 foreach my $name (@names) 344 { 345 if ($name eq 'default') 346 { $conversion{$name} = 1; } 347 else 348 { 349 $conversion{$name} = $currentNumber; 350 $currentNumber++; 351 }; 352 }; 353 }; 354 355 # Convert them to the new names. 356 foreach my $directory (keys %$inputHints) 357 { 358 $inputHints->{$directory} = $conversion{ $inputHints->{$directory} }; 359 }; 360 }; 361 362 363 # Now we apply all the names from the hints, and save any unused ones as removed directories. 364 365 for (my $i = 0; $i < scalar @inputDirectories; $i++) 366 { 367 if (exists $inputHints->{$inputDirectories[$i]}) 368 { 369 $inputDirectoryNames[$i] = $inputHints->{$inputDirectories[$i]}; 370 $usedInputNames{ $inputDirectoryNames[$i] } = 1; 371 delete $inputHints->{$inputDirectories[$i]}; 372 }; 373 }; 374 375 376 # Any remaining hints are saved as removed directories. 377 378 while (my ($directory, $name) = each %$inputHints) 379 { 380 push @removedInputDirectories, $directory; 381 push @removedInputDirectoryNames, $name; 382 }; 383 }; 384 385 386 if (defined $imageHints) 387 { 388 # Image directory names were never non-numeric, so there is no conversion. Apply all the names from the hints. 389 390 for (my $i = 0; $i < scalar @imageDirectories; $i++) 391 { 392 if (exists $imageHints->{$imageDirectories[$i]}) 393 { 394 $imageDirectoryNames[$i] = $imageHints->{$imageDirectories[$i]}; 395 $usedImageNames{ $imageDirectoryNames[$i] } = 1; 396 delete $imageHints->{$imageDirectories[$i]}; 397 }; 398 }; 399 400 401 # Any remaining hints are saved as removed directories. 402 403 while (my ($directory, $name) = each %$imageHints) 404 { 405 push @removedImageDirectories, $directory; 406 push @removedImageDirectoryNames, $name; 407 }; 408 }; 409 410 411 # Now we generate names for anything remaining. 412 413 my $inputCounter = 1; 414 415 for (my $i = 0; $i < scalar @inputDirectories; $i++) 416 { 417 if (!defined $inputDirectoryNames[$i]) 418 { 419 while (exists $usedInputNames{$inputCounter}) 420 { $inputCounter++; }; 421 422 $inputDirectoryNames[$i] = $inputCounter; 423 $usedInputNames{$inputCounter} = 1; 424 425 $inputCounter++; 426 }; 427 }; 428 429 430 my $imageCounter = 1; 431 432 for (my $i = 0; $i < scalar @imageDirectories; $i++) 433 { 434 if (!defined $imageDirectoryNames[$i]) 435 { 436 while (exists $usedImageNames{$imageCounter}) 437 { $imageCounter++; }; 438 439 $imageDirectoryNames[$i] = $imageCounter; 440 $usedImageNames{$imageCounter} = 1; 441 442 $imageCounter++; 443 }; 444 }; 445 }; 446 447 448 449############################################################################### 450# Group: Information Functions 451 452 453# 454# Function: InputDirectories 455# 456# Returns an arrayref of input directories. Do not change. 457# 458# This will not return any removed input directories. 459# 460sub InputDirectories 461 { return \@inputDirectories; }; 462 463# 464# Function: InputDirectoryNameOf 465# 466# Returns the generated name of the passed input directory. <GenerateDirectoryNames()> must be called once before this 467# function is available. 468# 469# If a name for a removed input directory is available, it will be returned as well. 470# 471sub InputDirectoryNameOf #(directory) 472 { 473 my ($self, $directory) = @_; 474 475 for (my $i = 0; $i < scalar @inputDirectories; $i++) 476 { 477 if ($directory eq $inputDirectories[$i]) 478 { return $inputDirectoryNames[$i]; }; 479 }; 480 481 for (my $i = 0; $i < scalar @removedInputDirectories; $i++) 482 { 483 if ($directory eq $removedInputDirectories[$i]) 484 { return $removedInputDirectoryNames[$i]; }; 485 }; 486 487 return undef; 488 }; 489 490 491# 492# Function: SplitFromInputDirectory 493# 494# Takes an input file name and returns the array ( inputDirectory, relativePath ). 495# 496# If the file cannot be split from an input directory, it will try to do it with the removed input directories. 497# 498sub SplitFromInputDirectory #(file) 499 { 500 my ($self, $file) = @_; 501 502 foreach my $directory (@inputDirectories, @removedInputDirectories) 503 { 504 if (NaturalDocs::File->IsSubPathOf($directory, $file)) 505 { return ( $directory, NaturalDocs::File->MakeRelativePath($directory, $file) ); }; 506 }; 507 508 return ( ); 509 }; 510 511 512# 513# Function: ImageDirectories 514# 515# Returns an arrayref of image directories. Do not change. 516# 517# This will not return any removed image directories. 518# 519sub ImageDirectories 520 { return \@imageDirectories; }; 521 522 523# 524# Function: ImageDirectoryNameOf 525# 526# Returns the generated name of the passed image or input directory. <GenerateDirectoryNames()> must be called once before 527# this function is available. 528# 529# If a name for a removed input or image directory is available, it will be returned as well. 530# 531sub ImageDirectoryNameOf #(directory) 532 { 533 my ($self, $directory) = @_; 534 535 for (my $i = 0; $i < scalar @imageDirectories; $i++) 536 { 537 if ($directory eq $imageDirectories[$i]) 538 { return $imageDirectoryNames[$i]; }; 539 }; 540 541 for (my $i = 0; $i < scalar @removedImageDirectories; $i++) 542 { 543 if ($directory eq $removedImageDirectories[$i]) 544 { return $removedImageDirectoryNames[$i]; }; 545 }; 546 547 return undef; 548 }; 549 550 551# 552# Function: SplitFromImageDirectory 553# 554# Takes an input image file name and returns the array ( imageDirectory, relativePath ). 555# 556# If the file cannot be split from an image directory, it will try to do it with the removed image directories. 557# 558sub SplitFromImageDirectory #(file) 559 { 560 my ($self, $file) = @_; 561 562 foreach my $directory (@imageDirectories, @removedImageDirectories) 563 { 564 if (NaturalDocs::File->IsSubPathOf($directory, $file)) 565 { return ( $directory, NaturalDocs::File->MakeRelativePath($directory, $file) ); }; 566 }; 567 568 return ( ); 569 }; 570 571 572# 573# Function: RelativeImageDirectories 574# 575# Returns an arrayref of relative image directories. Do not change. 576# 577sub RelativeImageDirectories 578 { return \@relativeImageDirectories; }; 579 580 581# Function: ExcludedInputDirectories 582# Returns an arrayref of input directories to exclude. Do not change. 583sub ExcludedInputDirectories 584 { return \@excludedInputDirectories; }; 585 586 587# Function: BuildTargets 588# Returns an arrayref of <NaturalDocs::Settings::BuildTarget>s. Do not change. 589sub BuildTargets 590 { return \@buildTargets; }; 591 592 593# 594# Function: OutputDirectoryOf 595# 596# Returns the output directory of a builder object. 597# 598# Parameters: 599# 600# object - The builder object, whose class is derived from <NaturalDocs::Builder::Base>. 601# 602# Returns: 603# 604# The builder directory, or undef if the object wasn't found.. 605# 606sub OutputDirectoryOf #(object) 607 { 608 my ($self, $object) = @_; 609 610 foreach my $buildTarget (@buildTargets) 611 { 612 if ($buildTarget->Builder() == $object) 613 { return $buildTarget->Directory(); }; 614 }; 615 616 return undef; 617 }; 618 619 620# Function: Styles 621# Returns an arrayref of the styles associated with the output. 622sub Styles 623 { return \@styles; }; 624 625# Function: ProjectDirectory 626# Returns the project directory. 627sub ProjectDirectory 628 { return $projectDirectory; }; 629 630# Function: ProjectDataDirectory 631# Returns the project data directory. 632sub ProjectDataDirectory 633 { return NaturalDocs::File->JoinPaths($projectDirectory, 'Data', 1); }; 634 635# Function: StyleDirectory 636# Returns the main style directory. 637sub StyleDirectory 638 { return NaturalDocs::File->JoinPaths($FindBin::RealBin, 'Styles', 1); }; 639 640# Function: JavaScriptDirectory 641# Returns the main JavaScript directory. 642sub JavaScriptDirectory 643 { return NaturalDocs::File->JoinPaths($FindBin::RealBin, 'JavaScript', 1); }; 644 645# Function: ConfigDirectory 646# Returns the main configuration directory. 647sub ConfigDirectory 648 { return NaturalDocs::File->JoinPaths($FindBin::RealBin, 'Config', 1); }; 649 650# Function: DocumentedOnly 651# Returns whether undocumented code aspects should be included in the output. 652sub DocumentedOnly 653 { return $documentedOnly; }; 654 655# Function: TabLength 656# Returns the number of spaces tabs should be expanded to. 657sub TabLength 658 { return $tabLength; }; 659 660# Function: NoAutoGroup 661# Returns whether auto-grouping is turned off. 662sub NoAutoGroup 663 { return $noAutoGroup; }; 664 665# Function: OnlyFileTitles 666# Returns whether source files should always use the file name as the title. 667sub OnlyFileTitles 668 { return $onlyFileTitles; }; 669 670# Function: IsQuiet 671# Returns whether the script should be run in quiet mode or not. 672sub IsQuiet 673 { return $isQuiet; }; 674 675# Function: RebuildData 676# Returns whether all data files should be ignored and rebuilt. 677sub RebuildData 678 { return $rebuildData; }; 679 680# Function: HighlightCode 681# Returns whether to apply syntax highlighting (start code) sections. 682sub HighlightCode 683 { return $highlightCode; } 684 685# Function: HighlightAnonymous 686# Returns whether to apply syntax highlighting to anonymous code sections designated with :, >, or |. 687sub HighlightAnonymous 688 { return $highlightAnonymous; } 689 690 691############################################################################### 692# Group: Constant Functions 693 694# 695# Function: AppVersion 696# 697# Returns Natural Docs' version number as an integer. Use <TextAppVersion()> to get a printable version. 698# 699sub AppVersion 700 { 701 my ($self) = @_; 702 return NaturalDocs::Version->FromString($self->TextAppVersion()); 703 }; 704 705# 706# Function: TextAppVersion 707# 708# Returns Natural Docs' version number as plain text. 709# 710sub TextAppVersion 711 { 712 return '1.52'; 713 }; 714 715# 716# Function: AppURL 717# 718# Returns a string of the project's current web address. 719# 720sub AppURL 721 { return 'http://www.naturaldocs.org'; }; 722 723 724 725############################################################################### 726# Group: Support Functions 727 728 729# 730# Function: ParseCommandLine 731# 732# Parses and validates the command line. Will cause the script to exit if the options ask for the syntax reference or 733# are invalid. 734# 735sub ParseCommandLine 736 { 737 my ($self) = @_; 738 739 my %synonyms = ( 'input' => '-i', 740 'source' => '-i', 741 'excludeinput' => '-xi', 742 'excludesource' => '-xi', 743 'images' => '-img', 744 'output' => '-o', 745 'project' => '-p', 746 'documentedonly' => '-do', 747 'style' => '-s', 748 'rebuild' => '-r', 749 'rebuildoutput' => '-ro', 750 'tablength' => '-t', 751 'quiet' => '-q', 752 'headersonly' => '-ho', 753 'help' => '-h', 754 'autogroup' => '-ag', 755 'noautogroup' => '-nag', 756 'onlyfiletitles' => '-oft', 757 'onlyfiletitle' => '-oft', 758 'highlight' => '-hl', 759 'highlighting' => '-hl' ); 760 761 762 my @errorMessages; 763 764 my $valueRef; 765 my $option; 766 767 my @outputStrings; 768 my @imageStrings; 769 my $highlightString; 770 771 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. 776 my $ignored; 777 778 my $index = 0; 779 780 while ($index < scalar @ARGV) 781 { 782 my $arg = $ARGV[$index]; 783 784 if (substr($arg, 0, 1) eq '-') 785 { 786 $option = lc($arg); 787 788 # Support options like -t2 as well as -t 2. 789 if ($option =~ /^([^0-9]+)([0-9]+)$/) 790 { 791 $option = $1; 792 splice(@ARGV, $index + 1, 0, $2); 793 }; 794 795 # Convert long forms to short. 796 if (substr($option, 1, 1) eq '-') 797 { 798 # Strip all dashes. 799 my $newOption = $option; 800 $newOption =~ tr/-//d; 801 802 if (exists $synonyms{$newOption}) 803 { $option = $synonyms{$newOption}; } 804 } 805 806 if ($option eq '-i') 807 { 808 push @inputDirectories, undef; 809 $valueRef = \$inputDirectories[-1]; 810 } 811 elsif ($option eq '-xi') 812 { 813 push @excludedInputDirectories, undef; 814 $valueRef = \$excludedInputDirectories[-1]; 815 } 816 elsif ($option eq '-img') 817 { 818 push @imageStrings, undef; 819 $valueRef = \$imageStrings[-1]; 820 } 821 elsif ($option eq '-p') 822 { 823 if (defined $projectDirectory) 824 { 825 push @errorMessages, 'You cannot have more than one project directory.'; 826 $valueRef = \$ignored; 827 } 828 else 829 { $valueRef = \$projectDirectory; }; 830 } 831 elsif ($option eq '-o') 832 { 833 push @outputStrings, undef; 834 $valueRef = \$outputStrings[-1]; 835 } 836 elsif ($option eq '-s') 837 { 838 $valueRef = \$styles[0]; 839 } 840 elsif ($option eq '-t') 841 { 842 $valueRef = \$tabLength; 843 } 844 elsif ($option eq '-hl') 845 { 846 $valueRef = \$highlightString; 847 } 848 elsif ($option eq '-ag') 849 { 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; 853 } 854 855 # Options that aren't followed by content. 856 else 857 { 858 $valueRef = undef; 859 860 if ($option eq '-r') 861 { 862 NaturalDocs::Project->ReparseEverything(); 863 NaturalDocs::Project->RebuildEverything(); 864 $rebuildData = 1; 865 } 866 elsif ($option eq '-ro') 867 { 868 NaturalDocs::Project->RebuildEverything(); 869 } 870 elsif ($option eq '-do') 871 { $documentedOnly = 1; } 872 elsif ($option eq '-oft') 873 { $onlyFileTitles = 1; } 874 elsif ($option eq '-q') 875 { $isQuiet = 1; } 876 elsif ($option eq '-ho') 877 { 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.'; 880 } 881 elsif ($option eq '-nag') 882 { $noAutoGroup = 1; } 883 elsif ($option eq '-?' || $option eq '-h') 884 { 885 $self->PrintSyntax(); 886 exit; 887 } 888 else 889 { push @errorMessages, 'Unrecognized option ' . $option; }; 890 891 }; 892 893 } 894 895 # Is a segment of text, not an option... 896 else 897 { 898 if (defined $valueRef) 899 { 900 # We want to preserve spaces in paths. 901 if (defined $$valueRef) 902 { $$valueRef .= ' '; }; 903 904 $$valueRef .= $arg; 905 } 906 907 else 908 { 909 push @errorMessages, 'Unrecognized element ' . $arg; 910 }; 911 }; 912 913 $index++; 914 }; 915 916 917 # Validate the style, if specified. 918 919 if ($styles[0]) 920 { 921 my @stylePieces = split(/ +/, $styles[0]); 922 @styles = ( ); 923 924 while (scalar @stylePieces) 925 { 926 if (lc($stylePieces[0]) eq 'custom') 927 { 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.'; 930 shift @stylePieces; 931 } 932 else 933 { 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. 936 937 my $extras = 0; 938 my $success; 939 940 while ($extras < scalar @stylePieces) 941 { 942 my $style; 943 944 if (!$extras) 945 { $style = $stylePieces[0]; } 946 else 947 { $style = join(' ', @stylePieces[0..$extras]); }; 948 949 my $cssFile = NaturalDocs::File->JoinPaths( $self->StyleDirectory(), $style . '.css' ); 950 if (-e $cssFile) 951 { 952 push @styles, $style; 953 splice(@stylePieces, 0, 1 + $extras); 954 $success = 1; 955 last; 956 } 957 else 958 { 959 $cssFile = NaturalDocs::File->JoinPaths( $self->ProjectDirectory(), $style . '.css' ); 960 961 if (-e $cssFile) 962 { 963 push @styles, $style; 964 splice(@stylePieces, 0, 1 + $extras); 965 $success = 1; 966 last; 967 } 968 else 969 { $extras++; }; 970 }; 971 }; 972 973 if (!$success) 974 { 975 push @errorMessages, 'The style "' . $stylePieces[0] . '" does not exist.'; 976 shift @stylePieces; 977 }; 978 }; 979 }; 980 } 981 else 982 { @styles = ( 'Default' ); }; 983 984 985 # Decode and validate the output strings. 986 987 my %outputDirectories; 988 989 foreach my $outputString (@outputStrings) 990 { 991 my ($format, $directory) = split(/ /, $outputString, 2); 992 993 if (!defined $directory) 994 { push @errorMessages, 'The -o option needs two parameters: -o [format] [directory]'; } 995 else 996 { 997 if (!NaturalDocs::File->PathIsAbsolute($directory)) 998 { $directory = NaturalDocs::File->JoinPaths(Cwd::cwd(), $directory, 1); }; 999 1000 $directory = NaturalDocs::File->CanonizePath($directory); 1001 1002 if (! -e $directory || ! -d $directory) 1003 { 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)) 1006 { 1007 push @errorMessages, 'The -o option needs two parameters: -o [format] [directory]'; 1008 $format = undef; 1009 } 1010 else 1011 { push @errorMessages, 'The output directory ' . $directory . ' does not exist.'; } 1012 } 1013 elsif (exists $outputDirectories{$directory}) 1014 { push @errorMessages, 'You cannot specify the output directory ' . $directory . ' more than once.'; } 1015 else 1016 { $outputDirectories{$directory} = 1; }; 1017 1018 if (defined $format) 1019 { 1020 my $builderPackage = NaturalDocs::Builder->OutputPackageOf($format); 1021 1022 if (defined $builderPackage) 1023 { 1024 push @buildTargets, 1025 NaturalDocs::Settings::BuildTarget->New($builderPackage->New(), $directory); 1026 } 1027 else 1028 { 1029 push @errorMessages, 'The output format ' . $format . ' doesn\'t exist or is not installed.'; 1030 $valueRef = \$ignored; 1031 }; 1032 }; 1033 }; 1034 }; 1035 1036 if (!scalar @buildTargets) 1037 { push @errorMessages, 'You did not specify an output directory.'; }; 1038 1039 1040 # Decode and validate the image strings. 1041 1042 foreach my $imageString (@imageStrings) 1043 { 1044 if ($imageString =~ /^ *\*/) 1045 { 1046 # The below NaturalDocs::File functions assume everything is canonized. 1047 $imageString = NaturalDocs::File->CanonizePath($imageString); 1048 1049 my ($volume, $directoryString) = NaturalDocs::File->SplitPath($imageString, 1); 1050 my @directories = NaturalDocs::File->SplitDirectories($directoryString); 1051 1052 shift @directories; 1053 1054 $directoryString = NaturalDocs::File->JoinDirectories(@directories); 1055 push @relativeImageDirectories, NaturalDocs::File->JoinPath($volume, $directoryString); 1056 } 1057 else 1058 { 1059 if (!NaturalDocs::File->PathIsAbsolute($imageString)) 1060 { $imageString = NaturalDocs::File->JoinPaths(Cwd::cwd(), $imageString, 1); }; 1061 1062 $imageString = NaturalDocs::File->CanonizePath($imageString); 1063 1064 if (! -e $imageString || ! -d $imageString) 1065 { push @errorMessages, 'The image directory ' . $imageString . ' does not exist.'; }; 1066 1067 push @imageDirectories, $imageString; 1068 }; 1069 }; 1070 1071 1072 # Make sure the input and project directories are specified, canonized, and exist. 1073 1074 if (scalar @inputDirectories) 1075 { 1076 for (my $i = 0; $i < scalar @inputDirectories; $i++) 1077 { 1078 if (!NaturalDocs::File->PathIsAbsolute($inputDirectories[$i])) 1079 { $inputDirectories[$i] = NaturalDocs::File->JoinPaths(Cwd::cwd(), $inputDirectories[$i], 1); }; 1080 1081 $inputDirectories[$i] = NaturalDocs::File->CanonizePath($inputDirectories[$i]); 1082 1083 if (! -e $inputDirectories[$i] || ! -d $inputDirectories[$i]) 1084 { push @errorMessages, 'The input directory ' . $inputDirectories[$i] . ' does not exist.'; }; 1085 }; 1086 } 1087 else 1088 { push @errorMessages, 'You did not specify an input (source) directory.'; }; 1089 1090 if (defined $projectDirectory) 1091 { 1092 if (!NaturalDocs::File->PathIsAbsolute($projectDirectory)) 1093 { $projectDirectory = NaturalDocs::File->JoinPaths(Cwd::cwd(), $projectDirectory, 1); }; 1094 1095 $projectDirectory = NaturalDocs::File->CanonizePath($projectDirectory); 1096 1097 if (! -e $projectDirectory || ! -d $projectDirectory) 1098 { push @errorMessages, 'The project directory ' . $projectDirectory . ' does not exist.'; }; 1099 1100 # Create the Data subdirectory if it doesn't exist. 1101 NaturalDocs::File->CreatePath( NaturalDocs::File->JoinPaths($projectDirectory, 'Data', 1) ); 1102 } 1103 else 1104 { push @errorMessages, 'You did not specify a project directory.'; }; 1105 1106 1107 # Make sure the excluded input directories are canonized, and add the project and output directories to the list. 1108 1109 for (my $i = 0; $i < scalar @excludedInputDirectories; $i++) 1110 { 1111 if (!NaturalDocs::File->PathIsAbsolute($excludedInputDirectories[$i])) 1112 { $excludedInputDirectories[$i] = NaturalDocs::File->JoinPaths(Cwd::cwd(), $excludedInputDirectories[$i], 1); }; 1113 1114 $excludedInputDirectories[$i] = NaturalDocs::File->CanonizePath($excludedInputDirectories[$i]); 1115 }; 1116 1117 push @excludedInputDirectories, $projectDirectory; 1118 1119 foreach my $buildTarget (@buildTargets) 1120 { 1121 push @excludedInputDirectories, $buildTarget->Directory(); 1122 }; 1123 1124 1125 # Determine the tab length, and default to four if not specified. 1126 1127 if (defined $tabLength) 1128 { 1129 if ($tabLength !~ /^[0-9]+$/) 1130 { push @errorMessages, 'The tab length must be a number.'; }; 1131 } 1132 else 1133 { $tabLength = 4; }; 1134 1135 1136 # Decode and validate the highlight setting. 1137 1138 if (defined $highlightString) 1139 { 1140 $highlightString = lc($highlightString); 1141 1142 if ($highlightString eq 'off') 1143 { 1144 $highlightCode = undef; 1145 $highlightAnonymous = undef; 1146 } 1147 elsif ($highlightString eq 'code') 1148 { 1149 $highlightCode = 1; 1150 $highlightAnonymous = undef; 1151 } 1152 elsif ($highlightString eq 'all') 1153 { 1154 $highlightCode = 1; 1155 $highlightAnonymous = 1; 1156 } 1157 else 1158 { push @errorMessages, $highlightString . ' is not a valid value for --highlight.'; } 1159 } 1160 else 1161 { 1162 $highlightCode = 1; 1163 $highlightAnonymous = undef; 1164 } 1165 1166 1167 # Exit with the error message if there was one. 1168 1169 if (scalar @errorMessages) 1170 { 1171 print join("\n", @errorMessages) . "\nType NaturalDocs -h to see the syntax reference.\n"; 1172 exit; 1173 }; 1174 }; 1175 1176# 1177# Function: PrintSyntax 1178# 1179# Prints the syntax reference. 1180# 1181sub PrintSyntax 1182 { 1183 my ($self) = @_; 1184 1185 # Make sure all line lengths are under 80 characters. 1186 1187 print 1188 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" 1194 . "\n" 1195 . "Syntax:\n" 1196 . "\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" 1202 . " [options]\n" 1203 . "\n" 1204 . "Examples:\n" 1205 . "\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" 1210 . "\n" 1211 . "Required Parameters:\n" 1212 . "\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" 1216 . "\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"; 1221 1222 $self->PrintOutputFormats(' - '); 1223 1224 print 1225 "\n" 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" 1229 . "\n" 1230 . "Optional Parameters:\n" 1231 . "\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" 1235 . "\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" 1239 . "\n" 1240 . " -do\n--documented-only\n" 1241 . " Specifies only documented code aspects should be included in the output.\n" 1242 . "\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" 1246 . "\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" 1251 . "\n" 1252 . " -nag\n--no-auto-group\n" 1253 . " Turns off auto-grouping completely.\n" 1254 . "\n" 1255 . " -oft\n--only-file-titles\n" 1256 . " Source files will only use the file name as the title.\n" 1257 . "\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" 1262 . " segments.\n" 1263 . " all - Systax highlighting is applied to prototypes, (start code) segments,\n" 1264 . " and lines starting with >, :, or |." 1265 . "\n" 1266 . " -r\n--rebuild\n" 1267 . " Rebuilds all output and data files from scratch.\n" 1268 . " Does not affect the menu file.\n" 1269 . "\n" 1270 . " -ro\n--rebuild-output\n" 1271 . " Rebuilds all output files from scratch.\n" 1272 . "\n" 1273 . " -q\n--quiet\n" 1274 . " Suppresses all non-error output.\n" 1275 . "\n" 1276 . " -?\n -h\n--help\n" 1277 . " Displays this syntax reference.\n"; 1278 }; 1279 1280 1281# 1282# Function: PrintOutputFormats 1283# 1284# Prints all the possible output formats that can be specified with -o. Each one will be placed on its own line. 1285# 1286# Parameters: 1287# 1288# prefix - Characters to prefix each one with, such as for indentation. 1289# 1290sub PrintOutputFormats #(prefix) 1291 { 1292 my ($self, $prefix) = @_; 1293 1294 my $outputPackages = NaturalDocs::Builder::OutputPackages(); 1295 1296 foreach my $outputPackage (@$outputPackages) 1297 { 1298 print $prefix . $outputPackage->CommandLineOption() . "\n"; 1299 }; 1300 }; 1301 1302 1303# 1304# Function: LoadAndComparePreviousSettings 1305# 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>. 1308# 1309sub LoadAndComparePreviousSettings 1310 { 1311 my ($self) = @_; 1312 1313 my $fileIsOkay; 1314 1315 if (!NaturalDocs::Settings->RebuildData()) 1316 { 1317 my $version; 1318 1319 if (NaturalDocs::BinaryFile->OpenForReading( NaturalDocs::Project->DataFile('PreviousSettings.nd'), 1320 NaturalDocs::Version->FromString('1.52') )) 1321 { $fileIsOkay = 1; }; 1322 }; 1323 1324 if (!$fileIsOkay) 1325 { 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(); 1330 } 1331 else 1332 { 1333 my $raw; 1334 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)] 1341 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(); 1348 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; }; 1359 1360 if ($prevTabLength != $self->TabLength() || 1361 $prevHighlightCode != $self->HighlightCode() || 1362 $prevHighlightAnonymous != $self->HighlightAnonymous()) 1363 { 1364 NaturalDocs::Project->RebuildEverything(); 1365 }; 1366 1367 if ($prevDocumentedOnly != $self->DocumentedOnly() || 1368 $prevNoAutoGroup != $self->NoAutoGroup() || 1369 $prevOnlyFileTitles != $self->OnlyFileTitles()) 1370 { 1371 NaturalDocs::Project->ReparseEverything(); 1372 }; 1373 1374 1375 # [UInt8: number of input directories] 1376 1377 my $inputDirectoryCount = NaturalDocs::BinaryFile->GetUInt8(); 1378 1379 while ($inputDirectoryCount) 1380 { 1381 # [UString16: input directory] [UString16: input directory name] ... 1382 1383 my $inputDirectory = NaturalDocs::BinaryFile->GetUString16(); 1384 my $inputDirectoryName = NaturalDocs::BinaryFile->GetUString16(); 1385 1386 # Not doing anything with this for now. 1387 1388 $inputDirectoryCount--; 1389 }; 1390 1391 1392 # [UInt8: number of output targets] 1393 1394 my $outputTargetCount = NaturalDocs::BinaryFile->GetUInt8(); 1395 1396 # Keys are the directories, values are the command line options. 1397 my %previousOutputDirectories; 1398 1399 while ($outputTargetCount) 1400 { 1401 # [UString16: output directory] [UString16: output format command line option] ... 1402 1403 my $outputDirectory = NaturalDocs::BinaryFile->GetUString16(); 1404 my $outputCommand = NaturalDocs::BinaryFile->GetUString16(); 1405 1406 $previousOutputDirectories{$outputDirectory} = $outputCommand; 1407 1408 $outputTargetCount--; 1409 }; 1410 1411 # Check if any targets were added to the command line, or if their formats changed. We don't care if targets were 1412 # removed. 1413 my $buildTargets = $self->BuildTargets(); 1414 1415 foreach my $buildTarget (@$buildTargets) 1416 { 1417 if (!exists $previousOutputDirectories{$buildTarget->Directory()} || 1418 $buildTarget->Builder()->CommandLineOption() ne $previousOutputDirectories{$buildTarget->Directory()}) 1419 { 1420 NaturalDocs::Project->RebuildEverything(); 1421 last; 1422 }; 1423 }; 1424 1425 NaturalDocs::BinaryFile->Close(); 1426 }; 1427 }; 1428 1429 1430# 1431# Function: SavePreviousSettings 1432# 1433# Saves the settings into <PreviousSettings.nd>. 1434# 1435sub SavePreviousSettings 1436 { 1437 my ($self) = @_; 1438 1439 NaturalDocs::BinaryFile->OpenForWriting( NaturalDocs::Project->DataFile('PreviousSettings.nd') ); 1440 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] 1448 1449 my $inputDirectories = $self->InputDirectories(); 1450 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); 1458 1459 foreach my $inputDirectory (@$inputDirectories) 1460 { 1461 my $inputDirectoryName = $self->InputDirectoryNameOf($inputDirectory); 1462 1463 # [UString16: input directory] [UString16: input directory name] ... 1464 NaturalDocs::BinaryFile->WriteUString16($inputDirectory); 1465 NaturalDocs::BinaryFile->WriteUString16($inputDirectoryName); 1466 }; 1467 1468 # [UInt8: number of output targets] 1469 1470 my $buildTargets = $self->BuildTargets(); 1471 NaturalDocs::BinaryFile->WriteUInt8(scalar @$buildTargets); 1472 1473 foreach my $buildTarget (@$buildTargets) 1474 { 1475 # [UString16: output directory] [UString16: output format command line option] ... 1476 NaturalDocs::BinaryFile->WriteUString16( $buildTarget->Directory() ); 1477 NaturalDocs::BinaryFile->WriteUString16( $buildTarget->Builder()->CommandLineOption() ); 1478 }; 1479 1480 NaturalDocs::BinaryFile->Close(); 1481 }; 1482 1483 14841; 1485