#!/usr/bin/perl # # Script name: headerDoc2HTML # Synopsis: Scans a file for headerDoc comments and generates an HTML # file from the comments it finds. # # Last Updated: $Date: 2014/02/14 17:55:29 $ # # ObjC additions by SKoT McDonald Aug 2001 # # Copyright (c) 1999-2004 Apple Computer, Inc. All rights reserved. # # @APPLE_LICENSE_HEADER_START@ # # This file contains Original Code and/or Modifications of Original Code # as defined in and that are subject to the Apple Public Source License # Version 2.0 (the 'License'). You may not use this file except in # compliance with the License. Please obtain a copy of the License at # http://www.opensource.apple.com/apsl/ and read it before using this # file. # # The Original Code and all software distributed under the License are # distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER # EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, # INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. # Please see the License for the specific language governing rights and # limitations under the License. # # @APPLE_LICENSE_HEADER_END@ # # $Revision: 1392429329 $ ##################################################################### # /*! # @header # The main HeaderDoc tool, headerDoc2HTML.pl # (headerdoc2html when installed), translates a # header file (or source code file) into HTML # (or XML). # # This document provides API-level documentation # on the tool's internals. For user documentation, see # {@linkdoc //apple_ref/doc/uid/TP40001215 HeaderDoc User Guide}. # @indexgroup HeaderDoc Tools # */ # /*! # @abstract # The version number of the HeaderDoc suite. # */ my $HeaderDoc_Version = "8.9"; # /*! # @abstract # The revision control revision number for this script. # @discussion # In the git repository, contains the number of seconds since # January 1, 1970. # */ my $VERSION = '$Revision: 1392429329 $'; # /*! # @abstract # The default comment in the right side if a header or # class lacks a discussion. # */ $HeaderDoc::defaultHeaderComment = "Use the links in the table of contents to the left to access the documentation.
\n"; # /*! # @abstract # Enables parsing of a bit more of the C programming # language to support nonstandard (external) uses of # the parser. # @discussion # HeaderDoc itself does not use this functionality. # */ $HeaderDoc::parseIfElse = 0; # /*! # @abstract # Storage for the bareNamesFromAPIRefs flag # @discussion # By default, if you use an \@link tag and # specify only an API reference marker (apple_ref), # as a link destination (with no link text), # HeaderDoc substitutes a class method call for # Objective-C methods and a name in the form # classname::methodName for other # methods. # # If you set this flag to 1, this behavior is # disabled for Objective-C methods. # # If you set this flag to 2, this behavior is # disabled for non-Objective-C methods. # # If you set this flag to 3, this behavior is # disabled for all methods. # */ $HeaderDoc::nameFromAPIRefReturnsOnlyName = 0; # /*! @abstract # The version of the test suite that matches this version # of HeaderDoc. # @discussion # Do not change this value. The actual value is automatically # populated by the Makefile during installation based on the # contents of the file # # [source_directory]/testsuite/version # # When the test suite changes in an incompatible way, the # version number in that file should be bumped before the # next version of HeaderDoc ships. # */ $HeaderDoc::testsuite_version="2"; ################ General Constants ################################### # /*! @abstract # If running in MacPerl in Mac OS 9, contains 1; normally 0. # */ my $isMacOS; # /*! @abstract # Normally "/", but ":" if running in MacPerl in Mac OS 9. # */ my $pathSeparator; ################ Flag Storage ################################### # /*! @abstract # Storage for the -o flag. # */ my $specifiedOutputDir; # /*! @abstract # Storage for the -d flag. # */ my $debugging = 0; # /*! @abstract # Storage for the -v flag. # */ my $printVersion; # /*! @abstract # Storage for the -q flag. # */ my $quietLevel; # /*! @abstract # Storage for the -X flag. Also set for the -m flag. # */ my $xml_output; # /*! @abstract # Storage for the -m flag. # */ my $man_output; # /*! @abstract # Storage for the -f flag. # */ my $function_list_output; # /*! @abstract # Storage for the -x flag. # */ my $doxytag_output; # /*! @abstract # Storage for the -s flag. # */ my $headerdoc_strip; # /*! @abstract # Storage for the -P flag. Also set for the -f flag. # */ my $use_stdout; # /*! @abstract # Storage for the -r flag (not yet implemented). # */ my $regenerate_headers; # # /*! @abstract # Storage for the -h flag. (Legacy cruft.) # */ # my $write_control_file; ################ Other Globals ################################### # /*! @abstract # Language currently being parsed. # */ my $lang = "C"; # Legacy cruft for "DB export" module. To be deleted in the future. # my $testingExport = 0; # my $export; # Look-up tables are used when exporting API and doc to tab-delimited # data files, which can be used for import to a database. # The look-up tables supply uniqueID-to-APIName mappings. # my $lookupTableDirName; # my $lookupTableDir; # my $functionFilename; # my $typesFilename; # my $enumsFilename; # /*! # @abstract # The array of input files to be procesed. # */ my @inputFiles; # /*! @abstract # Temp storage for list of temp files. # */ my @doxyTagFiles; # Described later. Defined inside the BEGIN block. # @HeaderDoc::ignorePrefixes = (); # @HeaderDoc::perHeaderIgnorePrefixes = (); # %HeaderDoc::perHeaderIncludes = (); # /*! # @abstract # Used to set the owning class when processing # embedded API symbols. # */ $HeaderDoc::currentClass = undef; # /*! # @abstract # Provides an out-of-band way for the block # parser to change the main API owner at the # top level of HeaderDoc. # @discussion # Used to work around the rather odd way # Perl's classes are designed. (They have no # end, and due to limitations in HeaderDoc, # they cannot be parsed as a single block # and pulled apart as other languages can.) # # Normally, this is empty. When set by the # parser, the top-level API owner is reset # to that object. # */ $HeaderDoc::perlClassChange = undef; # /*! # @abstract # Determines whether the macro filter code # should treat the != token as # a == token due to consistent # eccentricities in what is being parsed. # @discussion # HeaderDoc never uses this # feature. It is provided entirely for other # (internal) tools that use the same code for # very different purposes. # @seealso HeaderDoc::reverse_match # */ $HeaderDoc::enable_reverse_match = 0; # /*! # @abstract # The specific value that causes HeaderDoc # to treat the != token as # a == token due to consistent # eccentricities in what is being parsed. # @discussion # HeaderDoc never uses this # feature. It is provided entirely for other # (internal) tools that use the same code for # very different purposes. # # @seealso HeaderDoc::enable_reverse_match # */ $HeaderDoc::reverse_match = 0; # /*! # @abstract # Determines whether the macro filter code # attempts to handle switch and # case statements. # @discussion # HeaderDoc never uses this # feature. It is provided entirely for other # (internal) tools that use the same code for # very different purposes. # */ $HeaderDoc::interpret_case = 0; # /*! # @abstract # Set high to idicate that HeaderDoc needs to process a file again. # @abstract # In practice, this goes high whenever HeaderDoc encounters # an \@ignore or \@ignorefuncmacro # tag in an \@header comment. # */ my $reprocess_input = 0; # No longer used. Always zero. # $HeaderDoc::nodec = 0; # /*! # @abstract # The name of the current function group. # @discussion # Set by the \@functiongroup tag. Reset to empty upon # changing to a new header. (If empty, the value of # {@link HeaderDoc::globalGroup} # is used instead. # */ my $functionGroup = ""; # $HeaderDoc::outerNamesOnly = 0; # /*! # @abstract # The name of the current group. # @discussion # Set by the \@group tag. Reset to empty upon # changing to a new header. # */ $HeaderDoc::globalGroup = ""; # /*! # @abstract # Indicates that all token objects should be marked hidden. # # @discussion # The block parser uses this variable as an out-of-band way to # pass the "hide node" flag data to the parse tree package. # # Ideally, this should eventuallly go away and should be replaced # by changes to every place where a new node gets created, but # that isn't nearly as easy as it sounds. # */ $HeaderDoc::hidetokens = 0; # /*! # @abstract # The exit status. Changed to nonzero whenever an error occurs. # */ $HeaderDoc::exitstatus = 0; # /*! # @abstract # The skiplist file. # @discussion # If HeaderDoc is processing a single directory as its input, # it looks for a file at the top level of that directory called # skiplist. If it finds that file, it reads it, # and for each line that is not commented out (with a # sign), # it adds it into this newline-delimited list. # # HeaderDoc later uses this list to determine which files # to skip when processing the content. Note that the # preferred way to do this is with exclude lists (the # -e flag). # */ $HeaderDoc::skiplist = ""; # /*! # @abstract # The API reference language for IDL file content. # @discussion # An IDL file contains an interface description in an abstract # interface description language (hence the abbreviation). # Thus, the actual interfaces could be for any programming # language. # # By default, IDL file API references (apple_ref symbol markers) # are emitted using idl as the language name, but # you can override this by specifying a different value for # the IDLLanguage field in the HeaderDoc # config file. # */ $HeaderDoc::idl_language = "idl"; # /*! # @abstract # The default C compiler. # @discussion # By default, HeaderDoc uses /usr/bin/cc to calculate # the values of complex #define macros. This can be overridden with # the cCompiler field in the HeaderDoc config file. # */ $HeaderDoc::c_compiler = "/usr/bin/cc"; # /*! # @abstract # Turns on debug output when objects are allocated and released. # */ $HeaderDoc::debugAllocations = 0; # /*! # @abstract # New TOC style variable. # @discussion # Set by {@link setTOCFormat}. # */ $HeaderDoc::newTOC = 0; my @headerObjects; # holds finished objects, ready for printing # we defer printing until all header objects are ready # so that we can merge ObjC category methods into the # headerObject that holds the class, if it exists. my @categoryObjects; # holds finished objects that represent ObjC categories my %objCClassNameToObject = (); # makes it easy to find the class object to add category methods to my %headerIncluded = (); # /*! # @abstract # A hash of all API reference markers for minir API elements emitted so far. # @discussion # Used to prevent anyAPI reference markers from being # emitted when a documentation block that describes a declaration # with multiple names gets emitted a second (or subsequent) time. # */ %HeaderDoc::appleRefUsed = (); # /*! # @abstract # A hash table mapping availability macros # to the text that describes them. # @discussion # Used in the block parser to get a human-readable # string that describes the availability of the # current declaration. # */ %HeaderDoc::availability_defs = (); # /*! # @abstract # A hash table mapping availability macros # to whether or not the macro declaration takes # a parenthesized argument list. # @discussion # Used in the block parser to determine whether to # strip off the subsequent parenthetical # code or not. # */ %HeaderDoc::availability_has_args = (); # /*! # @abstract # The list of filename patterns to exclude when # producing documentation. # @discussion # Obtained from the file specified by the # -e flag. # */ @HeaderDoc::exclude_patterns = (); # $HeaderDoc::enable_custom_references = 0; my @classObjects; # /*! # @abstract # Set to 1 if you need to enable lots of debugging # for a single file. # @discussion # Most of the time, when debugging, you can get by # with processing a single file. When debugging # subtle C preprocessing bugs or other subtle # misbehavior, however, it is occasionally # necessary to process a lot of files to # reproduce the bug. # # This flag allows you to leave debugging off until # you get to a particular file, then turn it # on temporarily for the duration of that file. # # This flag must be used in conjunction with # {@link HeaderDoc::debugFile}. # */ $HeaderDoc::fileDebug = 0; # /*! # @abstract # Set to the name of a file to tell HeaderDoc # to enable debugging when processing that file. # @discussion # Only active if {@link HeaderDoc::fileDebug} is # nonzero. # */ $HeaderDoc::debugFile = ""; # /*! # @abstract # The current line number being parsed. # @discussion # Used as an out-of-band way to get this information # into the parse tree. Should ideally be an argument # when each parse tree node is being created, but that # isn't nearly as easy as it sounds. # */ $HeaderDoc::CurLine = 0; # $HeaderDoc::debugFile = "AAutoToolbar.h"; # $HeaderDoc::debugFile = "IOFWCommand.h"; # $HeaderDoc::debugFile = "IONetworkInterface.h"; # Turn on autoflushing of 'print' output. This is useful # when HeaderDoc is operating in support of a GUI front-end # which needs to get each line of log output as it is printed. $| = 1; # Check options in BEGIN block to avoid overhead of loading supporting # modules in error cases. my $uninstalledModulesPath; my $devtoolsModulesPath; BEGIN { use FindBin qw ($Bin); use Cwd; # use Getopt::Std; use Getopt::Long; use File::Find; # /*! # @abstract # Magic flag used when parsing manual page declarations. # # @discussion # Set to 1 if (in C) you want a function declaration # to end after the closing parenthesis even if there # is no trailing semicolon. Do NOT set this for # normal parsing; it will break many typedef # declarations and similar. This also enables # some broken man page detection for deleting lines # that say or and and. # # Note that HeaderDoc does not actually set this flag. # It is used by other tools that share this parser. # */ $HeaderDoc::parsing_man_pages = undef; # /*! # @abstract # Global header object for the header currently # being parsed. # @discussion # This should eventually go away. # */ $HeaderDoc::headerObject = undef; # /*! # @abstract # Storage for the -T flag. # */ $HeaderDoc::testmode = undef; # /*! # @abstract # Storage for the -M flag. # */ $HeaderDoc::man_section = "3"; # /*! # @abstract # Global variable where the language family info for the # header currently being parsed is stored. # @discussion # Historical versions of HeaderDoc used this in place # of passing this information around through the code. # This has been corrected in current versions, but # the {@link //apple_ref/perl/instm/HeaderDoc::BlockParse/blockParse//() blockParse} and {@link //apple_ref/perl/instm/HeaderDoc::BlockParse/blockParseOutside//() blockParseOutside} # functions still support falling back to this and # {@link HeaderDoc::sublang} for backwards # compatibility. (This backward compatibility is # temporary, however.) # */ $HeaderDoc::lang = undef; # /*! # @abstract # Global variable where the language info for the # header currently being parsed is stored. # @discussion # Historical versions of HeaderDoc used this in place # of passing this information around through the code. # This has been corrected in current versions, but # the {@link //apple_ref/perl/instm/HeaderDoc::BlockParse/blockParse//() blockParse} and {@link //apple_ref/perl/instm/HeaderDoc::BlockParse/blockParseOutside//() blockParseOutside} # functions still support falling back to this and # {@link HeaderDoc::lang} for backwards # compatibility. (This backward compatibility is # temporary, however.) # */ $HeaderDoc::sublang = undef; # /*! # @abstract # Storage for the -H flag. # */ $HeaderDoc::insert_header = 0; # /*! # @abstract # Storage for the introductionName configuration file field. # */ $HeaderDoc::introductionName = "Introduction"; # /*! # @abstract # Storage for the --auto-availability flag. # @discussion # By default, HeaderDoc ignores #if # directives with Apple-provided availability # macros. This flag enables that support by # setting this value to 1. # */ $HeaderDoc::auto_availability = 0; # /*! # @abstract # Enable internal documentation. # @discussion # Storage for the --document-internal flag. # */ $HeaderDoc::document_internal = 0; # /*! # @abstract # Flags certain XML tags as illegal due to # Apple-internal constraints. # */ $HeaderDoc::ExtraAppleWarnings = 0; # /*! # @abstract # Storage for the -C flag. # */ $HeaderDoc::ClassAsComposite = 0; # /*! # @abstract # The current access control state used and modified by the parser. # @discussion # This should probably be passed around through arguments, but # for now, it is't. # */ $HeaderDoc::AccessControlState = ""; # /*! # @abstract # Disables adding the next #definedeclaration to the # C preprocessor macro set. # @discussion # Triggered by a \@noparse directive in comment. # When this directive is encountered, the directive is parsed for # documentation purposes, but is not added to the set of # macros that affect parsing of other declarations. # */ $HeaderDoc::skipNextPDefine = 0; # /*! # @abstract # Deprecated. # @discussion # Storage for an old scheme to ignore junk CPP tokens at the start of line. # This holds the value of the ignorePrefixes field in the config file. # # The currently recommended way to do this is with the C preprocessor # on the command line (-D FOO="") or in code. # */ %HeaderDoc::ignorePrefixes = (); # /*! # @abstract # Deprecated. # @discussion # Storage for an old scheme to ignore junk CPP tokens at the start of line. # This holds the list of values from \@ignore tags. # # The currently recommended way to do this is with the C preprocessor # on the command line (-D FOO="") or in code. # */ %HeaderDoc::perHeaderIgnorePrefixes = (); # /*! # @abstract # Deprecated. # @discussion # Storage for an old scheme to ignore junk CPP tokens at the start of line. # This holds the list of values from \@ignorefuncmacro tags. # # The currently recommended way to do this is with the C preprocessor # in the code itself. # */ %HeaderDoc::perHeaderIgnoreFuncMacros = (); # /*! # @abstract # A hash of macro definition states passed in on the command # line that are interpreted by the macro filter engine. # @discussion # Legal values are: # #
#
1
Value we want defined to a particular value # (-D flag specified).
#
-1
Value we want to be explicitly undefined # (-U flag specified).
#
0
Don't care. (Default behavior for any token.)
#
# # This variable is used only if -D or -U is # passed on the command line (which, in turn, sets the variable # {@link HeaderDoc::filter_macro_definition_state} # to 1). # */ %HeaderDoc::filter_macro_definition_state = (); # /*! # @abstract # A hash of macro values passed in on the command # line that are interpreted by the macro filter engine. # @discussion # This hash contains the actual numerical values passed in on # the command line. If no value is specified, the value # defaults to 1. # # This variable is used only if -D or -U is # passed on the command line (which, in turn, sets the variable # {@link HeaderDoc::filter_macro_definition_state} # to 1). # # For explicitly undefined values (-U), this value is # unimportant and should be left blank. # */ %HeaderDoc::filter_macro_definition_value = (); # /*! # @abstract # A list of #include directives in the current header. # */ %HeaderDoc::perHeaderIncludes = (); # /*! # @abstract # A list of line numbers where CPP #include and # #if directives appear. # @discussion # Used for determining which C preprocessor directives should be # applied to a given block of code. # */ %HeaderDoc::perHeaderRanges = (); # /*! # @abstract # Enables/disables "outer name" type handling (-O flag). # @discussion # By default, HeaderDoc documents all names for type declarations. # This flag disables emission of documentation for inner # (structure, enumeration, or union) tag names declared inline. # # For example, typdef struct a { ... } b; has two # names by default: a and b. With the # -O flag, only the outer name (b) is # emitted in the documentation. # */ $HeaderDoc::outerNamesOnly = 0; # storage for -O flag. # /*! # @abstract # Used for quickly looking up names of # fields in data types by the data type name # and field name. # */ %HeaderDoc::namerefs = (); # /*! # @abstract # A counter used for generating UUIDs for inherited content. # */ $HeaderDoc::uniquenumber = 0; # $HeaderDoc::counter = 0; # Now unused. @@@ # /*! # @abstract # The configuration file path specified by the -cflag. # */ $HeaderDoc::specified_config_file = ""; # /*! # @abstract # Storage for the -F flag. # @discussion # Note that this variable may get removed eventually in favor # of a more flexible output style variable. # */ $HeaderDoc::use_iframes = 1; use lib '/Library/Perl/TechPubs'; # Apple configuration workaround use lib '/AppleInternal/Library/Perl'; # Apple configuration workaround my %options = (); # $lookupTableDirName = "LookupTables"; # Legacy cruft. # $functionFilename = "functions.tab";; # Legacy cruft. # $typesFilename = "types.tab"; # Legacy cruft. # $enumsFilename = "enumConstants.tab"; # Legacy cruft. # /*! # @abstract # Sorts content in the right side (content) pane by group. # */ $HeaderDoc::groupright = 0; # /*! # @abstract # Enables support for JavaDoc-style comments (comments with a second # asterisk at the start of the comment instead of an exclamation point). # */ $HeaderDoc::parse_javadoc = 0; # /*! # @abstract # Enables strict parameter taggint warnings (-t flag). # */ $HeaderDoc::force_parameter_tagging = 0; # /*! # @abstract # Controls whether to include the body of macros or not. # @discussion # By default, HeaderDoc omits the body of macro definitions # in the documentation. You can pass the -i # flag to include macro bodies in the output. Doing so # sets this variable to zero. Omitting the flag sets this # variable to 1. # */ $HeaderDoc::truncate_inline = 0; # /*! # @abstract # Deprecated. # @discussion # Storage for the -b flag. This flag uses # parser functionality that is no longer maintained. # It may or may not work, and you should not count on # it to continue working. It was primarily intended # as a way of checking parser behavior when the # colorizer code was nascent. # */ $HeaderDoc::dumb_as_dirt = 1; # /*! # @abstract # Disables link requests in declarations (-l flag). # @discussion # By default, HeaderDoc inserts link requests into the # declarations in HTML output. If you have no interest # in declaration linking, you can turn this off by # passing the -l flag on the command line. # Doing so clears this variable to 0. # */ $HeaderDoc::add_link_requests = 1; # /*! # @abstract # Tells HeaderDoc to suppress local variable documentation. # @discussion # By default, a local variable (an \@var tag # inside a function or method discussion) is emitted on the # output. This is desirable for internal documentation # purposes, but is undesirable for public documentation # purposes. # # Passing the -L flag on the command line sets this # flag and suppresses the emission of documentation on these # local variables. # */ $HeaderDoc::suppress_local_variables = 0; # /*! # @abstract # Enables emission of various stylesheet bits. # @discussion # Set if certain keys appear (with content) in the config file. # */ $HeaderDoc::use_styles = 0; # /*! # @abstract # Set to true when HeaderDoc should suppress warnings about # API reference conficts, object name changes, and so on. # @discussion # This is set to 1 when HeaderDoc is deliberately making a # change to an object that would normally trigger a warning. # Do not set this indiscriminately, as doing so can mask # actual errors in source content that result in incorrect # output. # */ $HeaderDoc::ignore_apiuid_errors = 0; # /*! # @abstract # Storage for the tocformat value from the config file. # */ $HeaderDoc::explicit_toc_format = undef; # /*! # @abstract # Storage for the contents of the file described in the # styleSheetExtrasFile field of the config file. # */ $HeaderDoc::styleSheetExtras = undef; # /*! # @abstract # Storage for the externalStyleSheets value from the config file. # */ $HeaderDoc::externalStyleSheets = undef; # /*! # @abstract # Storage for the externalTOCStyleSheets value from the config file. # */ $HeaderDoc::externalTOCStyleSheets = undef; # /*! # @abstract # Storage for the styleImports value from the config file. # */ $HeaderDoc::styleImports = undef; # /*! # @abstract # Storage for the tocStyleImports value from the config file. # */ $HeaderDoc::tocStyleImports = undef; # /*! # @abstract # Storage for the -C flag. Set to -1 if # flag is set. # @discussion # This flag is only supported when combined with the older # TOC formats (-F flag). # */ $HeaderDoc::flagDashC = undef; # /*! # @abstract # Storage for the -u (unsorted) flag. # @discussion # Set to 0 if flag is set, else 1. # */ $HeaderDoc::sort_entries = undef; # /*! # @abstract # Storage for the -F flag (legacy frames output mode). # */ $HeaderDoc::flagDashF = undef; # /*! # @abstract # Maximum line length for wrapping declarations. # @discussion # You can override this with the wrapAtColumn # field in the configuration file. # */ $HeaderDoc::maxDecLen = 60; # Wrap functions, etc. if declaration longer than this length # /*! # @abstract # Storage for the superclassName value from the config file. # */ $HeaderDoc::superclassName = "Superclass"; # /*! # @abstract # Storage for the suppressDefaultJavaScript value from the config file. # */ $HeaderDoc::suppressDefaultJavaScript = 0; # /*! # @abstract # Storage for the suppressDefaultStyles value from the config file. # */ $HeaderDoc::suppressDefaultStyles = 0; if ($^O =~ /MacOS/io) { $pathSeparator = ":"; $isMacOS = 1; #$Bin seems to return a colon after the path on certain versions of MacPerl #if it's there we take it out. If not, leave it be #WD-rpw 05/09/02 ($uninstalledModulesPath = $FindBin::Bin) =~ s/([^:]*):$/$1/o; } else { $pathSeparator = "/"; $isMacOS = 0; } $uninstalledModulesPath = "$FindBin::Bin"."$pathSeparator"."Modules"; $devtoolsModulesPath = "$FindBin::Bin"."$pathSeparator".".."."$pathSeparator"."share"."$pathSeparator"."headerdoc"."$pathSeparator"."Modules"; foreach (qw(Mac::Files)) { $MOD_AVAIL{$_} = eval "use $_; 1"; } # /*! # @abstract # Deprecated. # @discussion # Twig isn't installed by default in Mac OS X. This is used in a # feature that never got implemented---specifically, parsing # modified HeaderDoc XML output and updating the HeaderDoc # comments in the headers to allow round-tripping HeaderDoc markup # through XML editor tools. (The need for this functionality # never materialized. Also, in hindsight, a more lightweight, # and more commonly installed XML parser API would also be better.) # */ $HeaderDoc::twig_available = 0; foreach my $path (@INC) { # print STDERR "$path\n"; my $name = $path.$pathSeparator."XML".$pathSeparator."Twig.pm"; # print STDERR "NAME: $name\n"; if (-f $name) { $HeaderDoc::twig_available = 1; } } # /*! # @abstract # Indicates whether the FreezeThaw Perl module is available. # @discussion # Not all Mac OS X versions have this. It's in 10.5 and later. # For backwards compatibility, this code does a check here and # doesn't allow the test code (-T flag) to run if it isn't # available. # */ $HeaderDoc::FreezeThaw_available = 0; foreach my $path (@INC) { # print STDERR "$path\n"; my $name = $path.$pathSeparator."FreezeThaw.pm"; # print STDERR "NAME: $name\n"; if (-f $name) { $HeaderDoc::FreezeThaw_available = 1; } } } use lib $uninstalledModulesPath; use lib $devtoolsModulesPath; use HeaderDoc::Utilities qw(linesFromFile getLangAndSubLangFromFilename dumpCaches stripLeading); use HeaderDoc::Utilities qw(processTopLevel); use locale; # if ($HeaderDoc::twig_available) { # require HeaderDoc::Regen; # } # &getopts("CD@EFHLM:NOPQST:U@Xabc:de:fghijlmno:pqrstuvw:x", \%options); Getopt::Long::Configure ("bundling", "no_ignore_case_always"); my $result = GetOptions( "class-as-composite|C" => \$options{C}, "defined|D=s@" => \$options{D}, "process-everything|E" => \$options{E}, "old-style-frames|F" => \$options{F}, "insert-header|H" => \$options{H}, "suppress-local-variables|L" => \$options{L}, "man-section|M=s" => \$options{M}, "ignore-all-names|N" => \$options{N}, "outer-names-only|O" => \$options{O}, "pipe-output|P" => \$options{P}, "paranoid|Q" => \$options{Q}, "merge-superclass-docs|S" => \$options{S}, "test|T=s" => \$options{T}, "undefined|U=s@" => \$options{U}, "xml-output|X" => \$options{X}, "align-columns|a" => \$options{a}, "basic-processing-only|b" => \$options{b}, "config-file|c=s" => \$options{c}, "debugging|d" => \$options{d}, "exclude-list-file|e=s" => \$options{e}, "function-list-output|f" => \$options{f}, "group-right-side|g" => \$options{g}, # "write-control-file|h" => \$options{h}, "truncate-function-like-macros|i" => \$options{i}, "allow-javadoc-syntax|j" => \$options{j}, "no-link-requests|l" => \$options{l}, "man-page-output|m" => \$options{m}, "ignore-apiowner-names|n" => \$options{n}, "output-directory|o=s" => \$options{o}, "enable-cpp|p" => \$options{p}, "quiet|q" => \$options{q}, # "regenerate|r" => \$options{r}, "strip|s" => \$options{s}, "enforce-strict-tagging|t" => \$options{t}, "unsorted|u" => \$options{u}, "version|v" => \$options{v}, "python-tab-width|w=i" => \$options{w}, "doxytags|x" => \$options{x}, "tocformat=s" => \$options{tocformat}, "auto-availability" => \$options{auto_availability}, "document-internal" => \$HeaderDoc::document_internal, "apple" => \$options{apple} ); # print STDERR "-d: \"".$options{d}."\"\n"; # print STDERR "-D: \"".$options{D}."\"\n"; if (!$result) { die("Invalid options\n"); } # Moved to top so it can be used to get info about other flags.. if ($options{d}) { print STDERR "\tDebugging on...\n\n"; $debugging = 1; } if ($options{tocformat}) { # print "TF: $options{tocformat}\n"; $HeaderDoc::explicit_toc_format = $options{tocformat}; } if ($options{apple}) { $options{auto_availability} = 1; $options{H} = 1; $options{j} = 1; $options{n} = 1; $options{N} = 1; $options{p} = 1; $options{O} = 1; $options{Q} = 1; $HeaderDoc::ExtraAppleWarnings = 1; } if ($options{auto_availability}) { $HeaderDoc::auto_availability = 1; } if ($options{q}) { $quietLevel = "1"; } else { $quietLevel = "0"; } if ($options{Q}) { $HeaderDoc::enableParanoidWarnings = 1; } else { # /*! # @abstract # Storage for the -Q (disquiet) flag. # */ $HeaderDoc::enableParanoidWarnings = 0; } if ($options{v}) { # print STDERR "Getting version information for all modules. Please wait...\n"; $printVersion = 1; } if ($options{r}) { # print STDERR "TWIG? $HeaderDoc::twig_available\n"; if ($HeaderDoc::twig_available) { print STDERR "Regenerating headers.\n"; } else { warn "***********************************************************************\n"; warn "* Headerdoc comment regeneration from XML requires XML::Parser *\n"; warn "* and XML::Twig, available from CPAN. Visit *\n"; warn "* *\n"; warn "* http://www.cpan.org *\n"; warn "* *\n"; warn "* for more information. *\n"; warn "***********************************************************************\n"; exit -1; } $regenerate_headers = 1; } else { $regenerate_headers = 0; } if ($options{F}) { # Use old-style frames. $HeaderDoc::use_iframes = 0; $HeaderDoc::flagDashF = 1; # Set. } if ($options{S}) { # /*! # @abstract # Storage for the -S flag. # */ $HeaderDoc::IncludeSuper = 1; } else { $HeaderDoc::IncludeSuper = 0; } if ($options{C} || $HeaderDoc::use_iframes) { $HeaderDoc::ClassAsComposite = 1; } else { $HeaderDoc::flagDashC = -1; # Unset. $HeaderDoc::ClassAsComposite = 0; } if ($options{E}) { # /*! # @abstract # Storage for the -E flag. # */ $HeaderDoc::process_everything = 1; } else { $HeaderDoc::process_everything = 0; } if ($options{D}) { # print STDERR "OPTIONS D: ".$options{D}."\n"; # /*! # @abstract # Set if any -D or -U # flags are passed on the command line. # @discussion # This enables and disables the C preprocessor # filtering portions. # */ $HeaderDoc::enable_macro_filter = 1; foreach my $symbol (@{$options{D}}) { print STDERR "-D $symbol\n" if ($debugging); if ($symbol =~ /=/) { my @parts = split(/=/, $symbol); $HeaderDoc::filter_macro_definition_state{$parts[0]} = 1; $HeaderDoc::filter_macro_definition_value{$parts[0]} = $parts[1]; } else { $HeaderDoc::filter_macro_definition_state{$symbol} = 1; $HeaderDoc::filter_macro_definition_value{$symbol} = 1; } } } if ($options{U}) { # print STDERR "OPTIONS U: ".$options{U}."\n"; $HeaderDoc::enable_macro_filter = 1; foreach my $symbol (@{$options{U}}) { print STDERR "-U $symbol\n" if ($debugging); if ($symbol =~ /=/) { my @parts = split(/=/, $symbol); $HeaderDoc::filter_macro_definition_state{$parts[0]} = -1; $HeaderDoc::filter_macro_definition_value{$symbol} = ""; } else { $HeaderDoc::filter_macro_definition_state{$symbol} = -1; $HeaderDoc::filter_macro_definition_value{$symbol} = ""; } } } if ($options{a}) { # Align columns # /*! # @abstract # Storage for the -a flag. # */ $HeaderDoc::align_columns = 1; } else { $HeaderDoc::align_columns = 0; } if ($options{b}) { # "basic" mode - turn off some smart processing $HeaderDoc::dumb_as_dirt = 1; } else { $HeaderDoc::dumb_as_dirt = 0; } if ($options{c}) { # Use alternate config file. $HeaderDoc::specified_config_file = $options{c}; } if ($options{g}) { # Group right side content by group. $HeaderDoc::groupright = 1; } if ($options{j}) { # Allow JavaDoc syntax in C. $HeaderDoc::parse_javadoc = 1; } if ($options{p}) { $HeaderDoc::enable_cpp = 1; } else { # /*! # @abstract # Storage for the -p or # --enable-cpp flag. # */ $HeaderDoc::enable_cpp = 0; } # Ignore names specified in @header, @class, @category, and other # API owner tags if ($options{N}) { $HeaderDoc::ignore_apiowner_names = 2; } elsif ($options{n}) { $HeaderDoc::ignore_apiowner_names = 1; } else { # /*! # @abstract # Storage for the -n and -N flags. # */ $HeaderDoc::ignore_apiowner_names = 0; } if ($options{l}) { # "linkless" mode - don't add link requests $HeaderDoc::add_link_requests = 0; } else { $HeaderDoc::add_link_requests = 1; } if ($options{L}) { # Suppress local variables. $HeaderDoc::suppress_local_variables = 1; } else { $HeaderDoc::suppress_local_variables = 0; } if ($options{M}) { $HeaderDoc::man_section = $options{M}; } else { $HeaderDoc::man_section = 3; } if ($options{m}) { # man page output mode - implies xml $man_output = 1; $xml_output = 1; } else { $man_output = 0; } if ($options{e}) { my $exclude_list_file = $options{e}; print STDERR "EXCLUDE LIST FILE is \"$exclude_list_file\". CWD is ".cwd()."\n" if (!$quietLevel); my ($encoding, $linesref) = linesFromFile($exclude_list_file, 0); my @templines = @{$linesref}; @HeaderDoc::exclude_patterns = (); foreach my $line (@templines) { $line =~ s/\n//g; push(@HeaderDoc::exclude_patterns, $line); } } else { @HeaderDoc::exclude_patterns = (); } if ($options{s}) { $headerdoc_strip = 1; } else { $headerdoc_strip = 0; } if ($options{i}) { $HeaderDoc::truncate_inline = 0; } else { $HeaderDoc::truncate_inline = 1; } # if ($options{h}) { # $write_control_file = "1"; # } else { # $write_control_file = "0"; # } if ($options{u}) { $HeaderDoc::sort_entries = 0; } else { $HeaderDoc::sort_entries = 1; } if ($options{H}) { $HeaderDoc::insert_header = 1; } else { $HeaderDoc::insert_header = 0; } if ($options{w}) { $HeaderDoc::python_tab_spaces = $options{w}; if (!$quietLevel) { print STDERR "Python tab spaces set to ".$HeaderDoc::python_tab_spaces.".\n"; } } if ($options{t}) { if (!$quietLevel) { print STDERR "Forcing strict parameter tagging.\n"; } $HeaderDoc::force_parameter_tagging = 1; } if ($options{T}) { if (!$HeaderDoc::FreezeThaw_available) { warn "FreezeThaw Perl module not found in library path. Please\n"; warn "install FreezeThaw and try again.\n"; exit -1; } $HeaderDoc::testmode = $options{T}; } if ($options{O}) { $HeaderDoc::outerNamesOnly = 1; } else { $HeaderDoc::outerNamesOnly = 0; } if ($options{o}) { if ($use_stdout) { die("-o and -P are mutually exclusive."); } $specifiedOutputDir = $options{o}; if (! -e $specifiedOutputDir) { unless (mkdir ("$specifiedOutputDir", 0777)) { die "Error: $specifiedOutputDir does not exist. Exiting. \n$!\n"; } } elsif (! -d $specifiedOutputDir) { die "Error: $specifiedOutputDir is not a directory. Exiting.\n$!\n"; } elsif (! -w $specifiedOutputDir) { die "Error: Output directory $specifiedOutputDir is not writable. Exiting.\n$!\n"; } if ($quietLevel eq "0") { print STDERR "\nDocumentation will be written to $specifiedOutputDir\n"; } } my $scriptDir = cwd(); # $lookupTableDir = "$scriptDir$pathSeparator$lookupTableDirName"; # if (($options{x}) || ($testingExport)) { # if ((-e "$lookupTableDir$pathSeparator$functionFilename") && (-e "$lookupTableDir$pathSeparator$typesFilename")) { # print STDERR "\nWill write database files to an Export directory within each top-level HTML directory.\n\n"; # $export = 1; # } else { # print STDERR "\nLookup table files not available. Cannot export data.\n"; # $export = 0; # $testingExport = 0; # } # } $use_stdout = 0; if (!$headerdoc_strip && !$man_output) { if ($options{X}) { print STDERR "XML output mode.\n" if ($quietLevel eq "0"); $xml_output = 1; } elsif ($options{x}) { print STDERR "Doxygen tagfile output mode.\n" if ($quietLevel eq "0"); $doxytag_output = 1; if ($use_stdout) { die("-o and -x are mutually exclusive\n"); } } elsif ($options{f}) { print STDERR "FUNCTION LIST output mode.\n" if ($quietLevel eq "0"); $function_list_output = 1; $use_stdout = 1; } else { print STDERR "HTML output mode.\n" if ($quietLevel eq "0"); $xml_output = 0; } } # print STDERR "output mode is $xml_output\n"; # Pipe mode (single file only) if ($options{P} || $use_stdout) { $use_stdout = 1; if ($doxytag_output) { die("-P and -x are mutually exclusive\n"); } if (!$xml_output && !$function_list_output) { printf STDERR "XML output (-X) implicitly enabled by -P flag.\n"; $xml_output = 1; } if (!$HeaderDoc::ClassAsComposite) { printf STDERR "ClassAsComposite (-C) implicitly enabled by -P flag.\n"; $HeaderDoc::ClassAsComposite = 1; } } if (!$HeaderDoc::testmode && !$printVersion) { print STDERR "Will process one or more individual files.\n" if ($debugging); foreach my $singleFile (@ARGV) { if (-d $singleFile) { print STDERR "DIR $singleFile\n"; my $inputDir = $singleFile; if ($inputDir =~ /$pathSeparator$/) { $inputDir =~ s|(.*)$pathSeparator$|$1|; # get rid of trailing slash, if any } if ( -f $inputDir.$pathSeparator."skiplist") { my ($encoding, $arrayref) = linesFromFile($inputDir."/skiplist", 0); my @skiplist = @{$arrayref}; foreach my $skipfile (@skiplist) { if ($skipfile !~ /^\s*\#/ && $skipfile =~ /\S/) { $skipfile =~ s/^\s+//sg; $skipfile =~ s/\s+$//sg; $HeaderDoc::skiplist .= $skipfile."\n"; print STDERR "Will skip $skipfile\n" if ($debugging); } } # $HeaderDoc::skiplist =~ s/\s/\@/g; } if ($^O =~ /MacOS/io) { find(\&getHeaders, $inputDir); } else { &find({wanted => \&getHeaders, follow => 1, follow_skip => 2}, $inputDir); } } elsif (-f $singleFile) { if ($singleFile =~ /\.(cpp|c|C|h|m|M|i|hdoc|php|php\d|class|pas|p|java|j|jav|jsp|js|jscript|html|shtml|dhtml|htm|shtm|dhtm|pl|pm|bsh|csh|ksh|sh|defs|idl|conf|rb|rbx|rhtml|ruby|py|pyw|applescript|scpt|tcl)$/o) { push(@inputFiles, $singleFile); } else { warn "File $singleFile is not of a known header or source code file type\n"; } } else { warn "HeaderDoc: file/directory not found: $singleFile\n"; } } if ($debugging) { foreach my $if (@inputFiles) { print STDERR "FILE: $if\n"; } } unless (@inputFiles) { print STDERR "No valid input files specified. \n\n"; if ($isMacOS) { die "\tTo use HeaderDoc, drop a header file or folder of header files on this application.\n\n"; } else { die "\tUsage: headerdoc2html [-dq] [-o ] .\n\n"; } } } # /*! # @abstract # Search helper for Doxygen-style tag files. # @discussion # During processing, each source file results in a # corresponding Doxygen-style tag file (if the -x # flag is specified). This search helper is used # in tracking those files down for merging at the # end of processing. # */ sub getDoxyTagFiles { my $filePath = $File::Find::name; my $fileName = $_; if ($fileName =~ /\.doxytagtemp$/o) { push(@doxyTagFiles, $filePath); } } # /*! # @abstract # Search helper for header files. # @discussion # This is used for comparing names against the # list of valid file extensions to see if they # should be processed by HeaderDoc. # */ sub getHeaders { my $filePath = $File::Find::name; my $fileName = $_; if ($fileName =~ /\.(cpp|c|C|h|m|M|i|hdoc|php|php\d|class|pas|p|java|j|jav|jsp|js|jscript|html|shtml|dhtml|htm|shtm|dhtm|pl|pm|bsh|csh|ksh|sh|defs|idl|conf|rb|rbx|rhtml|ruby|py|pyw|applescript|scpt|tcl)$/o) { # Skip lists use exact filename matches and must be in the # the base directory being processed. Exclude list is # preferred, uses regular expressions, and can live in # any file. The filename of the exclude list is specified # with the -e command-line flag. if ($HeaderDoc::skiplist =~ /^\s*\Q$fileName\E\s*$/m) { print STDERR "skipped $filePath\n"; } elsif (in_exclude_list($filePath)) { print STDERR "skipped $filePath (found in exclude list)\n"; } else { push(@inputFiles, $filePath); # print STDERR "will process $filePath ($fileName)\n"; # print STDERR "SKIPLIST: ".$HeaderDoc::skiplist."\n"; } } } # /*! # @abstract # Checks the exclude list for filenames. # @discussion # When you specify an exclude list (the -e flag), # HeaderDoc reads that file for a list of expressions # to match. Every time HeaderDoc opens a header file, # source file, or other file to process, it checks it # against that list by calling this function. # */ sub in_exclude_list($) { my $filepath = shift; foreach my $pattern (@HeaderDoc::exclude_patterns) { if ($pattern =~ /\S/) { # print STDERR "Checking $filepath against pattern \"$pattern\".\n"; if ($filepath =~ $pattern) { return 1; } } } return 0; } # $HeaderDoc::curParserState = undef; # No longer used. use strict; use File::Copy; use File::Basename; use lib $uninstalledModulesPath; # use Devel::Peek; # Classes and other modules specific to HeaderDoc use HeaderDoc::Utilities qw(linesFromFile emptyHDok addAvailabilityMacro); use HeaderDoc::Utilities qw(findRelativePath safeName printArray linesFromFile printHash updateHashFromConfigFiles getHashFromConfigFile parseTokens stringToFields warnHDComment validTag filterHeaderDocComment processHeaderComment getLineArrays resolveLink objectForUID getAbsPath allow_everything getAvailabilityMacros); use HeaderDoc::BlockParse qw(blockParseOutside getAndClearCPPHash cpp_add_cl); use HeaderDoc::Header; use HeaderDoc::CPPClass; use HeaderDoc::ObjCClass; use HeaderDoc::ObjCProtocol; use HeaderDoc::ObjCCategory; use HeaderDoc::Function; use HeaderDoc::Method; use HeaderDoc::Typedef; use HeaderDoc::Struct; use HeaderDoc::Constant; use HeaderDoc::Var; use HeaderDoc::PDefine; use HeaderDoc::Enum; use HeaderDoc::Group; use HeaderDoc::MinorAPIElement; use HeaderDoc::HashObject; use HeaderDoc::ParseTree; use HeaderDoc::ParserState; use HeaderDoc::IncludeHash; use HeaderDoc::Dependency; use HeaderDoc::LineRange; use HeaderDoc::AvailHelper; use HeaderDoc::Test; use HeaderDoc::MacroFilter; # Determine where the modules really came from. # /*! # @abstract # The location from which the HeaderDoc modules # were loaded. # @discussion # This variable is populated automatically. # */ $HeaderDoc::modulesPath = $INC{'HeaderDoc/ParseTree.pm'}; $HeaderDoc::modulesPath =~ s/ParseTree.pm$//so; # Determine the location for test suite files. # If we're not in /System/Library/Perl or similar, then # we're loading from the source folder itself. # /*! # @abstract # The HeaderDoc test suite location. # @discussion # If HeaderDoc is being run out of a checked out copy. # this is the testsuite directory in that checked out # copy. Otherwise, this uses the installed path, # /usr/share/headerdoc/testsuite # */ $HeaderDoc::testdir = $HeaderDoc::modulesPath."/../../testsuite"; # /*! @abstract # Tells whether the test suite is being run from a checked-out # copy of the HeaderDoc sources or not. # */ $HeaderDoc::local_tests = 1; if ( ! -d $HeaderDoc::testdir ) { $HeaderDoc::local_tests = 0; $HeaderDoc::testdir = "/usr/share/headerdoc/testsuite"; } # print STDERR "Module path is ".$HeaderDoc::modulesPath."\n"; # foreach my $key (%INC) { # print STDERR "KEY: $key\nVALUE: ".$INC{$key}."\n"; # } # -v flag. if ($printVersion) { ################ Version Info ############################## &printVersionInfo(); exit $HeaderDoc::exitstatus; } # -T flag. if ($HeaderDoc::testmode) { print STDERR "Running tests.\n"; if ($HeaderDoc::testmode eq "create") { newtest(); } else { runtests($HeaderDoc::testmode, \@ARGV); } exit $HeaderDoc::exitstatus; } ################ Setup from Configuration File ####################### my $localConfigFileName = "headerDoc2HTML.config"; my $preferencesConfigFileName = "com.apple.headerDoc2HTML.config"; my $homeDir; my $usersPreferencesPath; my $systemPreferencesPath; my $usersAppSupportPath; my $systemAppSupportPath; # /*! # @abstract # A reference to a hash of non-standard tags to allow. # @discussion # Normally, HeaderDoc converts unknown tags to text. # You can add custom tags to the list of allowed tags # by listing them in the customTags field # in the HeaderDoc config file. The hash reference # in this variable lists those tags. # */ $HeaderDoc::custom_tags = undef; #added WD-rpw 07/30/01 to support running on MacPerl #modified WD-rpw 07/01/02 to support the MacPerl 5.8.0 if ($^O =~ /MacOS/io) { # Legacy support for MacPerl in Mac OS 9 eval { require "FindFolder.pl"; $homeDir = MacPerl::FindFolder("D"); #D = Desktop. Arbitrary place to put things $usersPreferencesPath = MacPerl::FindFolder("P"); #P = Preferences $usersAppSupportPath = MacPerl::FindFolder("P"); #P = Preferences }; if ($@) { import Mac::Files; $homeDir = Mac::Files::FindFolder(kOnSystemDisk(), kDesktopFolderType()); $usersPreferencesPath = Mac::Files::FindFolder(kOnSystemDisk(), kPreferencesFolderType()); $usersAppSupportPath = Mac::Files::FindFolder(kOnSystemDisk(), kPreferencesFolderType()); } $systemPreferencesPath = $usersPreferencesPath; $systemAppSupportPath = $usersAppSupportPath; } else { $homeDir = (getpwuid($<))[7]; $usersPreferencesPath = $homeDir.$pathSeparator."Library".$pathSeparator."Preferences"; $usersAppSupportPath = $homeDir.$pathSeparator."Library".$pathSeparator."Application Support".$pathSeparator."Apple".$pathSeparator."HeaderDoc"; $systemPreferencesPath = "/Library/Preferences"; $systemAppSupportPath = "/Library/Application Support/Apple/HeaderDoc"; } my $devtoolsPreferencesPath = "$FindBin::Bin"."$pathSeparator".".."."$pathSeparator"."share"."$pathSeparator"."headerdoc"."$pathSeparator"."conf"; # The order of files in this array determines the order that the config files will be read # If there are multiple config files that declare a value for the same key, the last one read wins my $CWD = cwd(); my @configFiles = ($devtoolsPreferencesPath.$pathSeparator.$preferencesConfigFileName, $systemPreferencesPath.$pathSeparator.$preferencesConfigFileName, $usersPreferencesPath.$pathSeparator.$preferencesConfigFileName, $Bin.$pathSeparator.$localConfigFileName, $CWD.$pathSeparator.$localConfigFileName); if (length($HeaderDoc::specified_config_file)) { @configFiles = (); push(@configFiles, $HeaderDoc::specified_config_file); } # default configuration, which will be modified by assignments found in config files. my %config = ( ignorePrefixes => "", externalStyleSheets => "", externalTOCStyleSheets => "", tocStyleImports => "", styleSheetExtrasFile => "", styleImports => "", TOCFormat => "default", # appleTOC intentionally not defined. classAsComposite => $HeaderDoc::ClassAsComposite, copyrightOwner => "", defaultFrameName => "index.html", compositePageName => "CompositePage.html", masterTOCName => "MasterTOC.html", apiUIDPrefix => "apple_ref", htmlHeader => "", htmlFooter => "", htmlHeaderFile => "", htmlFooterFile => "", dateFormat => "", textStyle => "", commentStyle => "", preprocessorStyle => "", funcNameStyle => "", stringStyle => "", charStyle => "", numberStyle => "", keywordStyle => "", typeStyle => "", paramStyle => "", varStyle => "", templateStyle => "", wrapAtColumn => "", introductionName => "Introduction", superclassName => "Superclass", IDLLanguage => "idl", cCompiler => "/usr/bin/cc" ); # print "CONFIG FILES: @{[ @configFiles ]}\n"; %config = &updateHashFromConfigFiles(\%config,\@configFiles); # Read the static availability macro text from the modules folder. if ( -f $HeaderDoc::modulesPath."../../Availability.list") { getAvailabilityMacros($HeaderDoc::modulesPath."../../Availability.list", 1); } else { getAvailabilityMacros($HeaderDoc::modulesPath."Availability.list", $quietLevel); } if ($config{"ignorePrefixes"}) { my $localDebug = 0; my @prefixlist = split(/\|/, $config{"ignorePrefixes"}); foreach my $prefix (@prefixlist) { print STDERR "ignoring $prefix\n" if ($localDebug); # push(@HeaderDoc::ignorePrefixes, $prefix); $prefix =~ s/^\s*//so; $prefix =~ s/\s*$//so; $HeaderDoc::ignorePrefixes{$prefix} = $prefix; } } if ($config{"customTags"}) { my %arr = (); foreach my $tag (split(/\s+/, $config{"customTags"})) { if (length($tag)) { $arr{$tag} = 1; } } $HeaderDoc::custom_tags = \%arr; } if (defined $config{bareNamesFromAPIRefs}) { $HeaderDoc::nameFromAPIRefReturnsOnlyName = $config{bareNamesFromAPIRefs}; } if ($config{"wrapAtColumn"}) { $HeaderDoc::maxDecLen = $config{"wrapAtColumn"}; } if ($config{"externalStyleSheets"}) { $HeaderDoc::externalStyleSheets = $config{"externalStyleSheets"}; $HeaderDoc::externalStyleSheets =~ s/[\n\r]/ /sgo; $HeaderDoc::use_styles = 1; } if ($config{"externalTOCStyleSheets"}) { $HeaderDoc::externalTOCStyleSheets = $config{"externalTOCStyleSheets"}; $HeaderDoc::externalTOCStyleSheets =~ s/[\n\r]/ /sgo; $HeaderDoc::use_styles = 1; } if ($config{"tocStyleImports"}) { $HeaderDoc::tocStyleImports = $config{"tocStyleImports"}; $HeaderDoc::tocStyleImports =~ s/[\n\r]/ /sgo; $HeaderDoc::use_styles = 1; } # 0, 1, true, false. if (defined $config{"suppressDefaultStyles"}) { if ($config{"suppressDefaultStyles"} eq "1" || $config{"suppressDefaultStyles"} eq "true") { $HeaderDoc::suppressDefaultStyles = 1; } else { $HeaderDoc::suppressDefaultStyles = 0; } } # 0, 1, true, false. if (defined $config{"suppressDefaultJavaScript"}) { if ($config{"suppressDefaultJavaScript"} eq "1" || $config{"suppressDefaultJavaScript"} eq "true") { $HeaderDoc::suppressDefaultJavaScript = 1; } else { $HeaderDoc::suppressDefaultJavaScript = 0; } } if ($config{"idlLanguage"}) { $HeaderDoc::idl_language = $config{"idlLanguage"}; } elsif ($config{"IDLLanguage"}) { $HeaderDoc::idl_language = $config{"IDLLanguage"}; } if ($config{"cCompiler"}) { $HeaderDoc::c_compiler = $config{"cCompiler"}; } if ($config{"styleSheetExtrasFile"} ne "") { my $found = 0; my $basename = $config{"styleSheetExtrasFile"}; my $oldRS = $/; $/ = undef; # my @extrasFiles = ($Bin.$pathSeparator.$basename, $usersPreferencesPath.$pathSeparator.$basename, $basename); my @extrasFiles = ($devtoolsPreferencesPath.$pathSeparator.$basename, $systemPreferencesPath.$pathSeparator.$basename, $usersPreferencesPath.$pathSeparator.$basename, $Bin.$pathSeparator.$basename, $CWD.$pathSeparator.$basename, $usersAppSupportPath.$pathSeparator.$basename, $systemAppSupportPath.$pathSeparator.$basename); foreach my $filename (@extrasFiles) { if (open(READFILE, "<$filename")) { $HeaderDoc::styleSheetExtras = ; close(READFILE); $found = 1; } } $/ = $oldRS; if (!$found) { die("Could not find file $basename in expected locations.\n"); } $HeaderDoc::styleSheetExtras =~ s/[\n\r]/ /sgo; $HeaderDoc::use_styles = 1; } if ($config{"styleImports"}) { $HeaderDoc::styleImports = $config{"styleImports"}; $HeaderDoc::styleImports =~ s/[\n\r]/ /sgo; $HeaderDoc::use_styles = 1; } # printHash(%config); if (defined $config{"appleTOC"}) { $HeaderDoc::newTOC = $config{"appleTOC"}; $HeaderDoc::newTOC =~ s/\s*//; # print STDERR "SET NEWTOC (appleTOC) TO ".$HeaderDoc::newTOC."\n"; } else { if (defined $config{"TOCFormat"}) { setTOCFormat($config{"TOCFormat"}); # print STDERR "SET NEWTOC (TF=".$config{"TOCFormat"}.") TO ".$HeaderDoc::newTOC."\n"; } else { $HeaderDoc::newTOC = 0; # print STDERR "SET NEWTOC (NoTF) TO 0\n"; } } if ($HeaderDoc::explicit_toc_format) { setTOCFormat($HeaderDoc::explicit_toc_format); } if ($config{"classAsComposite"}) { $HeaderDoc::ClassAsComposite = $config{"classAsComposite"}; $HeaderDoc::ClassAsComposite =~ s/\s*//; } else { $HeaderDoc::ClassAsComposite = 0; } if ($config{"copyrightOwner"}) { HeaderDoc::APIOwner->copyrightOwner($config{"copyrightOwner"}); } if ($config{"defaultFrameName"}) { HeaderDoc::APIOwner->defaultFrameName($config{"defaultFrameName"}); } if ($config{"compositePageName"}) { HeaderDoc::APIOwner->compositePageName($config{"compositePageName"}); } if ($config{"apiUIDPrefix"}) { HeaderDoc::APIOwner->apiUIDPrefix($config{"apiUIDPrefix"}); } if ($config{"htmlHeader"}) { HeaderDoc::APIOwner->htmlHeader($config{"htmlHeader"}); } if ($config{"htmlFooter"}) { HeaderDoc::APIOwner->htmlFooter($config{"htmlFooter"}); } my $oldRecSep = $/; undef $/; if ($config{"htmlHeaderFile"}) { my $basename = $config{"htmlHeaderFile"}; my @htmlHeaderFiles = ($Bin.$pathSeparator.$basename, $usersPreferencesPath.$pathSeparator.$basename, $basename); foreach my $filename (@htmlHeaderFiles) { if (open(HTMLHEADERFILE, "<$filename")) { my $headerString = ; close(HTMLHEADERFILE); # print STDERR "HEADER: $headerString"; HeaderDoc::APIOwner->htmlHeader($headerString); } } } if ($config{"htmlFooterFile"}) { my $basename = $config{"htmlFooterFile"}; my @htmlFooterFiles = ($Bin.$pathSeparator.$basename, $usersPreferencesPath.$pathSeparator.$basename, $basename); foreach my $filename (@htmlFooterFiles) { if (open(HTMLFOOTERFILE, "<$filename")) { my $headerString = ; close(HTMLFOOTERFILE); # print STDERR "FOOTER: $headerString"; HeaderDoc::APIOwner->htmlFooter($headerString); } } } $/ = $oldRecSep; if ($config{"dateFormat"}) { # /*! # @abstract # The date format used for date stamps. # @discussion # Value comes from the dateFormat # field in the config file. # */ $HeaderDoc::datefmt = $config{"dateFormat"}; if ($HeaderDoc::datefmt !~ /\S/) { $HeaderDoc::datefmt = "%B %d, %Y"; } } else { $HeaderDoc::datefmt = "%B %d, %Y"; } HeaderDoc::APIOwner::fix_date("UTF-8"); if ($config{"textStyle"}) { HeaderDoc::APIOwner->setStyle("text", $config{"textStyle"}); } if ($config{"commentStyle"}) { HeaderDoc::APIOwner->setStyle("comment", $config{"commentStyle"}); } if ($config{"preprocessorStyle"}) { HeaderDoc::APIOwner->setStyle("preprocessor", $config{"preprocessorStyle"}); } if ($config{"funcNameStyle"}) { HeaderDoc::APIOwner->setStyle("function", $config{"funcNameStyle"}); } if ($config{"stringStyle"}) { HeaderDoc::APIOwner->setStyle("string", $config{"stringStyle"}); } if ($config{"charStyle"}) { HeaderDoc::APIOwner->setStyle("char", $config{"charStyle"}); } if ($config{"numberStyle"}) { HeaderDoc::APIOwner->setStyle("number", $config{"numberStyle"}); } if ($config{"keywordStyle"}) { HeaderDoc::APIOwner->setStyle("keyword", $config{"keywordStyle"}); } if ($config{"typeStyle"}) { HeaderDoc::APIOwner->setStyle("type", $config{"typeStyle"}); } if ($config{"paramStyle"}) { HeaderDoc::APIOwner->setStyle("param", $config{"paramStyle"}); } if ($config{"varStyle"}) { HeaderDoc::APIOwner->setStyle("var", $config{"varStyle"}); } if ($config{"templateStyle"}) { HeaderDoc::APIOwner->setStyle("template", $config{"templateStyle"}); } if ($config{"addToCustomReferenceStyle"}) { HeaderDoc::APIOwner->setStyle("addToCustomReference", $config{"addToCustomReferenceStyle"}); } if ($config{"addToCustomReferenceLinkStyle"}) { HeaderDoc::APIOwner->setStyle("addToCustomReferenceLink", $config{"addToCustomReferenceLinkStyle"}); } if ($config{"introductionName"}) { $HeaderDoc::introductionName=$config{"introductionName"}; } if ($config{"superclassName"}) { $HeaderDoc::superclassName=$config{"superclassName"}; } ################### States ########################################### my $inHeader = 0; my $inJavaSource = 0; my $inShellScript = 0; my $inPerlScript = 0; my $inPHPScript = 0; my $inCPPHeader = 0; my $inOCCHeader = 0; my $inClass = 0; #includes CPPClass, ObjCClass ObjCProtocol my $inInterface = 0; my $inFunction = 0; my $inAvailabilityMacro = 0; my $inFunctionGroup = 0; my $inGroup = 0; my $inTypedef = 0; my $inUnknown = 0; my $inStruct = 0; my $inUnion = 0; my $inConstant = 0; my $inVar = 0; my $inPDefine = 0; my $inEnum = 0; my $inMethod = 0; ################ Processing starts here ############################## my $rootFileName; # my %HeaderFileProcessedThisRound = (); # /*! # @abstract # Hash of C preprocessor token hashes for files processed to date. # # @discussion # This hash is a copy of one of the C preprocessor # internal hashes. It is returned to the top-level # code by a call to getAndClearCPPHash(), and is # then merged with the hashes from any headers that # the current header includes. # # In addition to storing the values for future # merges, this copy of the hash is used to determine # whether a header file has been processed already for # the purposes of determining whether or not to merge # in C preprocessor macro definitions. # # @see //apple_ref/perl/data/HeaderDoc::BlockParse/CPP_HASH CPP_HASH # */ %HeaderDoc::HeaderFileCPPHashHash = (); # /*! # @abstract # Hash of argument lists for C preprocessor macros. # # @discussion # This hash is a copy of one of the C preprocessor # internal hashes. It is returned to the top-level # code by a call to getAndClearCPPHash(), and is # then merged with the hashes from any headers that # the current header includes. # # @see //apple_ref/perl/data/HeaderDoc::BlockParse/CPP_ARG_HASH CPP_ARG_HASH # */ %HeaderDoc::HeaderFileCPPArgHashHash = (); my $includeDebug = 0; if (!$quietLevel) { print STDERR "======= Parsing Input Files =======\n"; } if ($use_stdout && (scalar(@inputFiles) > 1)) { die("-P flag limits you to a single input file.\n"); } if ($debugging) { print STDERR "Processing includes.\n"; } foreach my $inputFile (@inputFiles) { my $fullpath=getAbsPath($inputFile); # Temp object. my ($rootFileName, $lang, $sublang) = getLangAndSubLangFromFilename($inputFile); my $headerObject = HeaderDoc::Header->new("LANG" => $lang, "SUBLANG" => $sublang); $HeaderDoc::headerObject = $headerObject; my ($encoding, $arrayref) = linesFromFile($inputFile, 1); my @rawInputLines = @{$arrayref}; if ($debugging) { print STDERR "Checking file $inputFile\n"; } # Grab any #include or #import directives. processIncludes(\@rawInputLines, $fullpath, $lang, $sublang); } if ($debugging) { print STDERR "Done processing includes. Fixing dependencies.\n"; } my @fileList = (); if (1 || $HeaderDoc::enable_cpp) { my $deplistref = fix_dependency_order(\@inputFiles); if ($deplistref) { @fileList = @{$deplistref}; } else { @fileList = @inputFiles; } } else { @fileList = @inputFiles } if ($debugging) { print STDERR "Done fixing dependencies. Filling fileList hash.\n"; } if ($#fileList != $#inputFiles) { die("File counts don't match: ".$#fileList." != ".$#inputFiles.".\n"); } # Remove duplicates. my %filelisthash = (); my @oldfileList = @fileList; @fileList = (); foreach my $inputFile (@oldfileList) { if (!$filelisthash{$inputFile}) { $filelisthash{$inputFile} = 1; push(@fileList, $inputFile); } } if ($debugging) { print STDERR "Done filling fileList hash\n"; } @oldfileList = (); # free memory %filelisthash = (); # free memory my $sort_entries = $HeaderDoc::sort_entries; my $tlhangDebug = 0; foreach my $inputFile (@fileList) { my $headerObject; # this is the Header object that will own the HeaderElement objects for this file. my $cppAccessControlState = "protected:"; # the default in C++ my $objcAccessControlState = "private:"; # the default in Objective C $HeaderDoc::AccessControlState = ""; my $hashtreecur = undef; my $hashtreeroot = undef; my @perHeaderClassObjects = (); my @perHeaderCategoryObjects = (); # $HeaderDoc::perHeaderObjectID = 0; # Restore this setting if it got changed on a per-header basis. $HeaderDoc::sort_entries = $sort_entries; my @path = split (/$pathSeparator/, $inputFile); my $filename = pop (@path); if ($HeaderDoc::HeaderFileCPPHashHash{$inputFile}) { print STDERR "Already procesed $inputFile. Skipping.\n" if ($includeDebug); next; } if (basename($filename) eq $HeaderDoc::debugFile) { $HeaderDoc::fileDebug = 1; print STDERR "Enabling debug mode for this file.\n"; } print STDERR "Top Level Point 100\n" if ($tlhangDebug); my $sublang = ""; if ($quietLevel eq "0") { if ($headerdoc_strip) { print STDERR "\nStripping $inputFile\n"; } elsif ($regenerate_headers) { print STDERR "\nRegenerating $inputFile\n"; } else { print STDERR "\nProcessing $inputFile\n"; } } # @@@ DAG WARNING: The next line doesn't do anything anymore. @@@ # %HeaderDoc::perHeaderIgnoreFuncMacros = ( "OSDeclareDefaultStructors" => "OSDeclareDefaultStructors", "OSDeclareAbstractStructors" => "OSDeclareAbstractStructors" ); ## if ($filename =~ /\.idl$/) { ## # print STDERR "IDL FILE\n"; ## # %HeaderDoc::perHeaderIgnoreFuncMacros = ( "cpp_quote" => "cpp_quote" ); ## HeaderDoc::BlockParse::cpp_add_string("#define cpp_quote(a) a", 0); ## Disabled because the syntax is cpp_quote(" content here "). Note the quotes. ## } %HeaderDoc::perHeaderIgnorePrefixes = (); foreach my $predefined_token (keys %HeaderDoc::filter_macro_definition_state) { if ($HeaderDoc::filter_macro_definition_state{$predefined_token} == 1) { cpp_add_cl($predefined_token, $HeaderDoc::filter_macro_definition_value{$predefined_token}); } } $HeaderDoc::globalGroup = ""; $reprocess_input = 0; my $headerDir = join("$pathSeparator", @path); print STDERR "Top Level Point 200\n" if ($tlhangDebug); ($rootFileName, $lang, $sublang) = getLangAndSubLangFromFilename($filename); if ($sublang eq "IDL") { $cppAccessControlState = "public:"; # IDLs have no notion of protection, typically. } $HeaderDoc::OptionalOrRequired = ""; my $rootOutputDir; if (length ($specifiedOutputDir)) { $rootOutputDir ="$specifiedOutputDir$pathSeparator$rootFileName"; } elsif (@path) { $rootOutputDir ="$headerDir$pathSeparator$rootFileName"; } else { $rootOutputDir = $rootFileName; } # my @cookedInputLines; my $localDebug = 0; $headerObject = HeaderDoc::Header->new("LANG" => $lang, "SUBLANG" => $sublang); $HeaderDoc::headerObject = $headerObject; my ($encoding, $arrayref) = linesFromFile($inputFile, 1); my @rawInputLines = @{$arrayref}; print STDERR "ENCODING GUESS: $encoding\n" if ($localDebug); # IS THIS STILL NEEDED? # foreach my $line (@rawInputLines) { # foreach my $prefix (keys %HeaderDoc::ignorePrefixes) { # if ($line =~ s/^\s*$prefix\s*//g) { # print STDERR "ignored $prefix\n" if ($localDebug); # } # } # push(@cookedInputLines, $line); # } # @rawInputLines = @cookedInputLines; # /*! # @abstract # A list of C preprocessor token hashes for the current header. # @discussion # This gets reset for each header. For more # info, see the documentation for # {@link //apple_ref/perl/data/HeaderDoc::BlockParse/CPP_HASH # HeaderDoc::BlockParse::CPP_HASH}. # */ @HeaderDoc::cppHashList = (); # /*! # @abstract # A list of C preprocessor argument hashes for the current header. # @discussion # This gets reset for each header. # {@link //apple_ref/perl/data/HeaderDoc::BlockParse/CPP_ARG_HASH # HeaderDoc::BlockParse::CPP_ARG_HASH}. # */ @HeaderDoc::cppArgHashList = (); REDO: print STDERR "Top Level Point 300\n" if ($tlhangDebug); print STDERR "REDO" if ($debugging); # print STDERR "LANG: $lang\n"; # check for HeaderDoc comments -- if none, move to next file my @headerDocCommentLines = grep(/^\s*\/\*\!/, @rawInputLines); if ((!@headerDocCommentLines) && ($lang eq "java" || $HeaderDoc::parse_javadoc)) { @headerDocCommentLines = grep(/^\s*\/\*\*[^\*]/, @rawInputLines); } if ((!@headerDocCommentLines) && ($lang eq "ruby" || $lang eq "python")) { @headerDocCommentLines = grep(/^\s*\!headerdoc\!\s*$/i, @rawInputLines); } if ((!@headerDocCommentLines) && ($lang eq "perl" || $lang eq "shell" || $lang eq "tcl")) { @headerDocCommentLines = grep(/^\s*\#\s*\/\*\!/, @rawInputLines); } if ((!@headerDocCommentLines) && ($lang eq "pascal")) { @headerDocCommentLines = grep(/^\s*\{\!/, @rawInputLines); } if ((!@headerDocCommentLines) && ($lang eq "applescript")) { @headerDocCommentLines = grep(/^\s*\(\*\!/, @rawInputLines); } my $skip_header_processing = 0; if (!@headerDocCommentLines && ((!$HeaderDoc::process_everything) || (!allow_everything($lang, $sublang)))) { if ($quietLevel eq "0") { print STDERR " Skipping. No HeaderDoc comments found.\n"; $skip_header_processing = 1; } } my $fullpath=getAbsPath($inputFile); my $basefilename = basename($inputFile); print STDERR "Top Level Point 400\n" if ($tlhangDebug); if (!$headerdoc_strip) { # Don't do this if we're stripping. It wastes memory and # creates unnecessary empty directories in the output path. $headerObject->encoding($encoding); # SDump($headerObject, "point1"); $headerObject->linenuminblock(0); $headerObject->blockoffset(0); # $headerObject->linenum(0); $headerObject->apiOwner($headerObject); $HeaderDoc::headerObject = $headerObject; # SDump($headerObject, "point2"); # if ($quietLevel eq "0") { # print STDERR "output mode is $xml_output\n"; # } if ($use_stdout) { $headerObject->use_stdout(1); } else { $headerObject->use_stdout(0); } if ($xml_output) { $headerObject->outputformat("hdxml"); } elsif ($function_list_output) { $headerObject->outputformat("functions"); } else { $headerObject->outputformat("html"); } $headerObject->outputDir($rootOutputDir); $headerObject->name($filename); $headerObject->filename($filename); $headerObject->fullpath($fullpath); } else { $HeaderDoc::headerObject = $headerObject; if ($use_stdout) { $headerObject->use_stdout(1); } else { $headerObject->use_stdout(0); } $headerObject->filename($filename); $headerObject->linenuminblock(0); $headerObject->blockoffset(0); # $headerObject->linenum(0); } # SDump($headerObject, "point3"); print STDERR "Top Level Point 500\n" if ($tlhangDebug); # scan input lines for class declarations # return an array of array refs, the first array being the header-wide lines # the others (if any) being the class-specific lines my @lineArrays = &getLineArrays(\@rawInputLines, $lang, $sublang); # print STDERR "NLA: " . scalar(@lineArrays) . "\n"; my $processEverythingDebug = 0; my $localDebug = 0 || $debugging; my $linenumdebug = 0; print STDERR "Top Level Point 600\n" if ($tlhangDebug); if ($headerdoc_strip) { # print STDERR "input file is $filename, output dir is $rootOutputDir\n"; my $outdir = ""; if (length ($specifiedOutputDir)) { $outdir ="$specifiedOutputDir"; } elsif (@path) { $outdir ="$headerDir"; } else { $outdir = "strip_output"; } strip($filename, $outdir, $rootOutputDir, $inputFile, \@rawInputLines); print STDERR "done.\n" if ($quietLevel eq "0"); next; } if ($regenerate_headers) { HeaderDoc::Regen->regenerate($inputFile, $rootOutputDir); print STDERR "done.\n" if ($quietLevel eq "0"); next; } print STDERR "Top Level Point 700\n" if ($tlhangDebug); # SDump($headerObject, "point4"); my $retainheader = 0; if (!$skip_header_processing) { foreach my $arrayRef (@lineArrays) { my $blockOffset = 0; my @inputLines = @$arrayRef; # $HeaderDoc::nodec = 0; $cppAccessControlState = "protected:"; # the default in C++ $objcAccessControlState = "private:"; # the default in Objective C if ($sublang eq "IDL") { $cppAccessControlState = "public:"; # IDLs have no notion of protection, typically. } $HeaderDoc::AccessControlState = ""; print STDERR "Top Level Point 705\n" if ($tlhangDebug); # look for /*! comments and collect all comment fields into the appropriate objects my $parseTokensRef = parseTokens($lang, $sublang); my $apiOwner = $headerObject; # switches to a class/protocol/category object, when within a those declarations my ($case_sensitive, $keywordhashref) = $apiOwner->keywords(); $HeaderDoc::currentClass = $apiOwner; print STDERR "Top Level Point 710\n" if ($tlhangDebug); print STDERR "inHeader\n" if ($localDebug); my $inputCounter = 0; my $ctdebug = 0; my $classType = "unknown"; print STDERR "CLASS TYPE CHANGED TO $classType\n" if ($ctdebug); my $nlines = $#inputLines; print STDERR "PROCESSING LINE ARRAY\n" if ($HeaderDoc::inputCounterDebug); my $inMLC = 0; print STDERR "Top Level Point 715\n" if ($tlhangDebug); while ($inputCounter <= $nlines) { my $line = ""; # print STDERR "inMLC: $inMLC\n"; print STDERR "LINE: \"".$inputLines[$inputCounter]."\"\n" if ($localDebug || $includeDebug); if ($inputLines[$inputCounter] =~ /^\s*#(?:include|import)[ \t]+(.*)$/) { print STDERR "Include line: $1\n" if ($includeDebug); my $rest = $1; $rest =~ s/^\s*//s; $rest =~ s/\s*$//s; if ($rest !~ s/^\<(.*)\>$/$1/s) { $rest =~ s/^\"(.*)\"$/$1/s; } my $filename = basename($rest); if ($HeaderDoc::HeaderFileCPPHashHash{$filename}) { my $includehash = HeaderDoc::IncludeHash->new(); $includehash->{FILENAME} = $filename; $includehash->{LINENUM} = $inputCounter + $blockOffset; $includehash->{HASHREF} = $HeaderDoc::HeaderFileCPPHashHash{$filename}; push(@HeaderDoc::cppHashList, $includehash); # print STDERR "PUSH HASH\n"; push(@HeaderDoc::cppArgHashList, $HeaderDoc::HeaderFileCPPArgHashHash{$filename}); print STDERR "Adding include for $filename.\n" if ($includeDebug); } } print STDERR "Input line number[1]: $inputCounter\n" if ($localDebug); print STDERR "last line ".$inputLines[$inputCounter-1]."\n" if ($localDebug); print STDERR "next line ".$inputLines[$inputCounter]."\n" if ($localDebug); if ($inputLines[$inputCounter] =~ /^\s*(public|private|protected)/o) { $cppAccessControlState = $&; if ($inputLines[$inputCounter] =~ /^\s*(public|private|protected)\s*:/o) { # trim leading whitespace and tabulation $cppAccessControlState =~ s/^\s+//o; # trim ending ':' and whitespace $cppAccessControlState =~ s/\s*:\s*$/$1/so; # set back the ':' $cppAccessControlState = "$cppAccessControlState:"; } } if ($inputLines[$inputCounter] =~ /^\s*(\@public|\@private|\@protected)/o) { $objcAccessControlState = $&; if ($inputLines[$inputCounter] =~ /^\s*(\@public|\@private|\@protected)\s+/o) { # trim leading whitespace and tabulation $objcAccessControlState =~ s/^\s+//o; # trim ending ':' and whitespace $objcAccessControlState =~ s/\s*:\s*$/$1/so; # set back the ':' $objcAccessControlState = "$objcAccessControlState:"; } } print STDERR "Top Level Point 750\n" if ($tlhangDebug); my @fields = (); # /*! # @abstract # Determines whether to treat commented # #if/#ifdef blocks # as blocks of related functions. # @discussion # Set to 1 by default. # Set to 0 if the -E # flag is passed (unless the language # does not support that flag). # */ $HeaderDoc::allow_multi = 0; if (($lang ne "pascal" && $lang ne "applescript" && $lang ne "ruby" && $lang ne "python" && ( ($lang ne "perl" && $lang ne "shell" && $lang ne "tcl" && $inputLines[$inputCounter] =~ /^\s*\/\*\!/o) || (($lang eq "perl" || $lang eq "shell" || $lang eq "tcl") && ($inputLines[$inputCounter] =~ /^\s*\#\s*\/\*\!/o)) || (($lang eq "java" || $HeaderDoc::parse_javadoc) && ($inputLines[$inputCounter] =~ /^\s*\/\*\*[^*]/o)))) || (($lang eq "applescript") && ($inputLines[$inputCounter] =~ s/^\s*\(\*!/\/\*!/so)) || (($lang eq "pascal") && ($inputLines[$inputCounter] =~ s/^\s*\{!/\/\*!/so)) || (($lang eq "ruby" || $lang eq "python") && $inMLC && ($inputLines[$inputCounter] =~ s/\s*\!headerdoc\!\s*$/\/\*\!/iso))) { # entering headerDoc comment my $newlinecount = 0; # slurp up comment as line print STDERR "Top Level Point 755\n" if ($tlhangDebug); if ($lang ne "ruby" && (($lang ne "pascal" && $lang ne "python" && $lang ne "applescript" && ($inputLines[$inputCounter] =~ /\s*\*\//o)) || ($lang eq "python" && ($inputLines[$inputCounter] =~ /\"\"\".*\"\"\"/)) || ($lang eq "applescript" && ($inputLines[$inputCounter] =~ s/\s*\*\)/\*\//so)) || ($lang eq "pascal" && ($inputLines[$inputCounter] =~ s/\s*\}/\*\//so)))) { # closing comment marker on same line # print STDERR "SINGLE-LINE\n"; my $linecopy = $inputLines[$inputCounter]; # print STDERR "LINE IS \"$linecopy\".\n" if ($linenumdebug); $newlinecount = ($linecopy =~ tr/\n//); $blockOffset += $newlinecount - 1; print STDERR "NEWLINECOUNT: $newlinecount\n" if ($linenumdebug); print STDERR "BLOCKOFFSET: $blockOffset\n" if ($linenumdebug); my $newline = $inputLines[$inputCounter++]; # print STDERR "PRE NEWLINE: $newline\n"; if ($lang eq "perl" || $lang eq "shell" || $lang eq "tcl") { # $newline =~ s/^#//; # print STDERR "POINT A: $newline\n"; $newline = stripLeading($newline, "#"); # s/^#//; # Strip off the leading '#' added by getLineArrays() } if ($lang eq "python") { $newline =~ s/\"\"\"/\/\*/s; $newline =~ s/\"\"\"/\*\//s; } $line .= $newline; print STDERR "INCREMENTED INPUTCOUNTER [M1]\n" if ($HeaderDoc::inputCounterDebug); # This is perfectly legal. Don't warn # necessarily. if (!emptyHDok($line)) { warnHDComment(\@inputLines, $inputCounter, $blockOffset, $lang, "HeaderDoc comment", "1", $parseTokensRef, $line); } print STDERR "Input line number[2]: $inputCounter\n" if ($localDebug); print STDERR "next line ".$inputLines[$inputCounter]."\n" if ($localDebug); } else { # multi-line comment # my $in_textblock = 0; my $in_pre = 0; # print STDERR "MULTI-LINE\n"; my $nInputLines = $nlines; print STDERR "MULTI-LINE LOOP START\n" if ($tlhangDebug); do { my $templine = $inputLines[$inputCounter]; # print STDERR "HERE: $templine\n"; # while ($templine =~ s/\@textblock//io) { $in_textblock++; } # while ($templine =~ s/\@\/textblock//io) { $in_textblock--; } # while ($templine =~ s/
//io) { $in_pre++; print STDERR "IN PRE\n" if ($localDebug);}
						# while ($templine =~ s/<\/pre>//io) { $in_pre--; print STDERR "OUT OF PRE\n" if ($localDebug);}
						# if (!$in_textblock && !$in_pre) {
							# $inputLines[$inputCounter] =~ s/^[\t ]*[*]?[\t ]+(.*)$/$1/o; # remove leading whitespace, and any leading asterisks
							# if ($line !~ /\S/) {
								# $line = "

\n"; # } # } my $newline = $inputLines[$inputCounter++]; print STDERR "INCREMENTED INPUTCOUNTER [M2]\n" if ($HeaderDoc::inputCounterDebug); warnHDComment(\@inputLines, $inputCounter, $blockOffset, $lang, "HeaderDoc comment", "2", $parseTokensRef); $newline =~ s/^ \*//o; print STDERR "NEWLINE [A] IS $newline\n" if ($localDebug); if ($lang eq "perl" || $lang eq "shell" || $lang eq "tcl") { # $newline =~ s/^#//; # print STDERR "POINT B: $newline\n"; $newline = stripLeading($newline, "#"); # s/^#//; # Strip off the leading '#' added by getLineArrays() # $newline =~ s/^\s*\#//o; } $line .= $newline; print STDERR "Input line number[3]: $inputCounter\n" if ($localDebug); print STDERR "next line ".$inputLines[$inputCounter]."\n" if ($localDebug); } while ((($lang eq "pascal" && ($inputLines[$inputCounter] !~ /\}/o)) || ($lang eq "applescript" && ($inputLines[$inputCounter] !~ /\*\)/o)) || ($lang eq "python" && ($inputLines[$inputCounter] !~ s/\"\"\"$/\*\//so)) || ($lang eq "ruby" && ($inputLines[$inputCounter] !~ s/^=end\s*$/\*\//so)) || ($lang ne "pascal" && $lang ne "ruby" && $lang ne "python" && $lang ne "applescript" && ($inputLines[$inputCounter] !~ s/\*\//\*\//so))) && ($inputCounter <= $nInputLines)); print STDERR "MULTI-LINE LOOP END\n" if ($tlhangDebug); $inMLC = 0; my $newline = $inputLines[$inputCounter++]; print STDERR "INCREMENTED INPUTCOUNTER [M3]\n" if ($HeaderDoc::inputCounterDebug); # This is not inherently wrong. # print "NEWLINE: $newline\n"; if (!emptyHDok($line)) { # print STDERR "LINE WAS $line\n"; my $dectype = "HeaderDoc comment"; if ($line =~ /^\s*\/\*\!\s*\@define(d)?block\s+/s) { $dectype = "defineblock"; } warnHDComment(\@inputLines, $inputCounter, $blockOffset, $lang, $dectype, "3", $parseTokensRef); } if ($lang eq "perl" || $lang eq "shell" || $lang eq "tcl") { print STDERR "NEWLINE [B] IS $newline\n" if ($localDebug); # $newline =~ s/^\s*\#//o; # $newline =~ s/^#//; # print STDERR "POINT C: $newline\n"; $newline = stripLeading($newline, "#"); # s/^#//; # Strip off the leading '#' added by getLineArrays() } if ($newline !~ /^ \*\//o) { $newline =~ s/^ \*//o; } $line .= $newline; # get the closing comment marker print STDERR "Input line number[4]: $inputCounter\n" if ($localDebug); print STDERR "last line ".$inputLines[$inputCounter-1]."\n" if ($localDebug); print STDERR "next line ".$inputLines[$inputCounter]."\n" if ($localDebug); } # end of multi-line comment # print STDERR "ic=$inputCounter\n" if ($localDebug); # print STDERR "After slurp, comment was \"$line\"\n" if ($localDebug || 1); # warn("LINE: $line\n"); # HeaderDoc-ize JavaDoc/PerlDoc comments # This code never runs anyway because those have been stripped by now. # if (($lang eq "perl" || $lang eq "shell" || $lang eq "tcl") && ($line =~ /^\s*\#\s*\/\*\!/o)) { # $line =~ s/^\s*\#\s*\/\*\!/\/\*\!/o; # } if (($lang eq "java" || $HeaderDoc::parse_javadoc) && ($line =~ /^\s*\/\*\*[^*]/o)) { $line =~ s/^\s*\/\*\*/\/\*\!/o; } $line =~ s/^\s+//o; # trim leading whitespace $line =~ s/^(.*)\*\/\s*$/$1/so; # remove closing comment marker print STDERR "CURRENT line \"$line\"\n" if ($localDebug); ($inHeader, $inClass, $inInterface, $inCPPHeader, $inOCCHeader, $inPerlScript, $inShellScript, $inPHPScript, $inJavaSource, $inFunctionGroup, $inGroup, $inFunction, $inPDefine, $inTypedef, $inUnion, $inStruct, $inConstant, $inVar, $inEnum, $inMethod, $inAvailabilityMacro, $inUnknown, $classType, $line, $inputCounter, $blockOffset, $filename, $linenumdebug, $localDebug) = processTopLevel($inHeader, $inClass, $inInterface, $inCPPHeader, $inOCCHeader, $inPerlScript, $inShellScript, $inPHPScript, $inJavaSource, $inFunctionGroup, $inGroup, $inFunction, $inPDefine, $inTypedef, $inUnion, $inStruct, $inConstant, $inVar, $inEnum, $inMethod, $inAvailabilityMacro, $inUnknown, $classType, $line, $inputCounter, $blockOffset, $inputFile, $linenumdebug, $localDebug); # $inputCounter--; # inputCounter is current line. my $linenum = $inputCounter - 1; # $line =~ s/\n\n/\n

\n/go; # change newline pairs into HTML breaks, for para formatting my $fieldref = stringToFields($line, $fullpath, $linenum, $xml_output, $lang, $sublang); @fields = @{$fieldref}; $HeaderDoc::allow_multi = 1; # Treat any #if/#ifdef blocks we find as being blocks of similar functions. } elsif ($HeaderDoc::process_everything && allow_everything($lang, $sublang)) { # print STDERR "POINT\n"; print STDERR "Top Level Point 760\n" if ($tlhangDebug); # print STDERR "IC: $inputCounter\n"; my ($tempInputCounter, $dec, $type, $name, $pt, $value, $pplref, $returntype, $pridec, $parseTree, $simpleTDcontents, $bpavail) = &HeaderDoc::BlockParse::blockParse($fullpath, $blockOffset, \@inputLines, $inputCounter, 0, \%HeaderDoc::ignorePrefixes, \%HeaderDoc::perHeaderIgnorePrefixes, \%HeaderDoc::perHeaderIgnoreFuncMacros, $keywordhashref, $case_sensitive, $lang, $sublang); print STDERR "Top Level Point 765\n" if ($tlhangDebug); if ($dec !~ /^(\/\*.*?\*\/|\/\/.*?(\n|\r)|\n|\r)*(\/\*\!)/) { print STDERR "DECLARATION WITH NO MARKUP ENCOUNTERED.\n" if ($processEverythingDebug); $inUnknown = 1; # Only process declaration if we don't encounter a HeaderDoc comment on the way. } else { print STDERR "DECLARATION WITH MARKUP ENCOUNTERED.\n" if ($processEverythingDebug); } $HeaderDoc::allow_multi = 0; # Drop any #if/#ifdef blocks or partial blocks on the floor for safety. @fields = (); } elsif ($lang eq "python" && ($inputLines[$inputCounter] =~ /\"\"\"/o) && !$inMLC) { $inMLC = 1; } elsif ($lang eq "ruby" && ($inputLines[$inputCounter] =~ /^=begin\s*$/o)) { $inMLC = 1; } elsif ($lang eq "ruby" && ($inputLines[$inputCounter] =~ /^=end\s*$/o)) { $inMLC = 0; } # end slurping up print STDERR "Top Level Point 770\n" if ($tlhangDebug); # print "CHECKPOINT: INUNKNOWN: $inUnknown\n"; my $preAtPart = ""; if ($inCPPHeader) {print STDERR "inCPPHeader\n" if ($debugging); $sublang="cpp"; }; if ($inOCCHeader) {print STDERR "inCPPHeader\n" if ($debugging); $sublang="occ"; }; if ($inPerlScript) {print STDERR "inPerlScript\n" if ($debugging); $lang="php";}; if ($inPHPScript) {print STDERR "inPHPScript\n" if ($debugging); $lang="php";}; if ($inJavaSource) {print STDERR "inJavaSource\n" if ($debugging); $lang="java";}; if ($inHeader) { print STDERR "inHeader\n" if ($debugging); $functionGroup = ""; $HeaderDoc::globalGroup = ""; ($lang, $sublang) = processHeaderComment($apiOwner, $rootOutputDir, \@fields, $debugging, \$reprocess_input, $lang, $sublang); $HeaderDoc::currentClass = $apiOwner; $inputCounter--; print STDERR "DECREMENTED INPUTCOUNTER [M5]\n" if ($HeaderDoc::inputCounterDebug); if ($reprocess_input == 1) { # my @cookedInputLines; my $localDebug = 0; # foreach my $line (@rawInputLines) { # foreach my $prefix (keys %HeaderDoc::perHeaderIgnorePrefixes) { # if ($line =~ s/^\s*$prefix\s*//g) { # print STDERR "ignored $prefix\n" if ($localDebug); # } # } # push(@cookedInputLines, $line); # } # @rawInputLines = @cookedInputLines; $reprocess_input = 2; goto REDO; } }; if ($inGroup) { print STDERR "inGroup\n" if ($debugging); # my $rawname = $line; # $rawname =~ s/.*\/\*!\s*\@(group|name)\s+//sio; # $rawname =~ s/\s*\*\/.*//o; # my ($name, $desc, $is_nameline_disc) = getAPINameAndDisc($rawname); # $name =~ s/^\s+//smgo; # $name =~ s/\s+$//smgo; # if ($is_nameline_disc) { $name .= " ".$desc; $desc = ""; } # print STDERR "group name is $name\n" if ($debugging); my $group = HeaderDoc::Group->new("LANG" => $lang, "SUBLANG" => $sublang); $group->fullpath($fullpath); $group->filename($filename); $group->linenuminblock($inputCounter - 1); $group->blockoffset(0); $group->apiOwner($apiOwner); $group = $group->processComment(\@fields); $apiOwner->addGroup($group, 1); #(, $desc); $HeaderDoc::globalGroup = $group->name(); $inputCounter--; print STDERR "DECREMENTED INPUTCOUNTER [M6]\n" if ($HeaderDoc::inputCounterDebug); }; if ($inFunctionGroup) { print STDERR "inFunctionGroup\n" if ($debugging); # my $rawname = $line; # if (!($rawname =~ s/.*\/\*!\s+\@functiongroup\s+//io)) { # $rawname =~ s/.*\/\*!\s+\@methodgroup\s+//io; # print STDERR "inMethodGroup\n" if ($debugging); # } # $rawname =~ s/\s*\*\/.*//o; # my ($name, $desc, $is_nameline_disc) = getAPINameAndDisc($rawname); # $name =~ s/^\s+//smgo; # $name =~ s/\s+$//smgo; # if ($is_nameline_disc) { $name .= " ".$desc; $desc = ""; } my $group = HeaderDoc::Group->new("LANG" => $lang, "SUBLANG" => $sublang); $group->filename($filename); $group->linenuminblock($inputCounter - 1); $group->blockoffset(0); $group->apiOwner($apiOwner); $group = $group->processComment(\@fields); print STDERR "group name is ".$group->name()."\n" if ($debugging); $apiOwner->addGroup($group, 0); #(, $desc); # $HeaderDoc::globalGroup = $name; $functionGroup = $group->name(); $inputCounter--; print STDERR "DECREMENTED INPUTCOUNTER [M7]\n" if ($HeaderDoc::inputCounterDebug); }; print STDERR "Top Level Point 800\n" if ($tlhangDebug); if ($inUnknown || $inTypedef || $inStruct || $inEnum || $inUnion || $inConstant || $inVar || $inFunction || $inMethod || $inPDefine || $inClass || $inAvailabilityMacro) { # my $localDebug = 1; my $hangDebug = 0; my $parmDebug = 0; my $blockDebug = 0; # print STDERR "WRAPPER: FIELDS:\n"; # foreach my $field (@fields) { # print STDERR "FIELD: $field\n"; # } # print STDERR "ENDFIELDS\n"; # print STDERR "preAtPart: $preAtPart\n"; print STDERR "Top Level Point 810\n" if ($tlhangDebug); if ($inClass && $debugging) { print STDERR "INCLASS (MAIN)\n"; print STDERR "line is $line\n"; print STDERR "IC: $inputCounter\n"; print STDERR "CUR LINE: ".$inputLines[$inputCounter-1]."\n"; print STDERR "NEXT LINE: ".$inputLines[$inputCounter]."\n"; } # print STDERR "LINE_0: ".$inputLines[$inputCounter + 0]."\n"; # print STDERR "LINE_1: ".$inputLines[$inputCounter + 1]."\n"; # print STDERR "LINE_2: ".$inputLines[$inputCounter + 2]."\n"; # print STDERR "LINE_3: ".$inputLines[$inputCounter + 3]."\n"; # print STDERR "LINE_4: ".$inputLines[$inputCounter + 4]."\n"; my $subparse = 0; my $subparseTree = undef; my $classref = undef; my $catref = undef; my $newInputCounter; print STDERR "CALLING blockParseOutside WITH IC: $inputCounter (".$inputLines[$inputCounter].")\n" if ($debugging); my $junk = undef; print STDERR "BLOCKOFFSET IN LOOP: $blockOffset\n" if ($linenumdebug); my $bpPrintDebug = $localDebug || 0; print STDERR "Top Level Point 820\n" if ($tlhangDebug); my $nodec = 0; # HeaderDoc::nodec print STDERR "my (\$newInputCounter, \$cppAccessControlState, \$classType, \$classref, \$catref, \$blockOffset, \$numcurlybraces, \$foundMatch, \$newlang, \$newsublang, \$hashtreecur, \$hashtreeroot) = blockParseOutside($apiOwner, $inFunction, $inUnknown, $inTypedef, $inStruct, $inEnum, $inUnion, $inConstant, $inVar, $inMethod, $inPDefine, $inClass, $inInterface, $blockOffset, \@perHeaderCategoryObjects , \@perHeaderClassObjects, $classType, $cppAccessControlState, \@fields, $fullpath, $functionGroup, $headerObject, $inputCounter, \@inputLines, $lang, $nlines, $preAtPart, $xml_output, $localDebug, $hangDebug, $parmDebug, $blockDebug, $subparse, $subparseTree, $nodec, $HeaderDoc::allow_multi, undef, $sublang, $hashtreecur, $hashtreeroot);\n" if ($bpPrintDebug || $tlhangDebug); print STDERR "FIELDS:\n" if ($bpPrintDebug || $tlhangDebug); printArray(@fields) if ($bpPrintDebug || $tlhangDebug); print "FIRSTLINE: ".$inputLines[$inputCounter]."\n" if ($bpPrintDebug || $tlhangDebug); my $foundMatch; my $newlang; my $newsublang; ($newInputCounter, $cppAccessControlState, $classType, $classref, $catref, $blockOffset, $junk, $foundMatch, $newlang, $newsublang, $hashtreecur, $hashtreeroot) = blockParseOutside($apiOwner, $inFunction, $inUnknown, $inTypedef, $inStruct, $inEnum, $inUnion, $inConstant, $inVar, $inMethod, $inPDefine, $inClass, $inInterface, $blockOffset, \@perHeaderCategoryObjects, \@perHeaderClassObjects, $classType, $cppAccessControlState, \@fields, $fullpath, $functionGroup, $headerObject, $inputCounter, \@inputLines, $lang, $nlines, $preAtPart, $xml_output, $localDebug, $hangDebug, $parmDebug, $blockDebug, $subparse, $subparseTree, $nodec, $HeaderDoc::allow_multi, undef, $sublang, $hashtreecur, $hashtreeroot); print STDERR "BLOCKOFFSET RETURNED: $blockOffset\n" if ($linenumdebug); $lang = $newlang; $sublang = $newsublang; print STDERR "Top Level Point 830\n" if ($tlhangDebug); # $HeaderDoc::nodec = 0; @perHeaderClassObjects = @{$classref}; @perHeaderCategoryObjects = @{$catref}; # This fix for infinite loops is WRONG. # @@@ FIXME DAG @@@ print "IC: $inputCounter NIC: $newInputCounter\n" if ($HeaderDoc::inputCounterDebug); # if ($inputCounter > $newInputCounter) { # $inputCounter++; # print STDERR "INCREMENTED INPUTCOUNTER [M8]\n" if ($HeaderDoc::inputCounterDebug); # } else { $inputCounter = $newInputCounter; # } if ($lang eq "perl" && $HeaderDoc::perlClassChange) { print STDERR "CLASS: ".$HeaderDoc::perlClassChange."\n" if ($localDebug); print STDERR "OLDAPIO: $apiOwner\n" if ($localDebug); $apiOwner = $HeaderDoc::perlClassChange; $HeaderDoc::perlClassChange = undef; } } # end "inUnknown, etc." print STDERR "Top Level Point 890\n" if ($tlhangDebug); $inCPPHeader = $inOCCHeader = $inPerlScript = $inShellScript = $inPHPScript = $inJavaSource = $inInterface = $inHeader = $inUnknown = $inFunction = $inAvailabilityMacro = $inFunctionGroup = $inGroup = $inTypedef = $inUnion = $inStruct = $inConstant = $inVar = $inPDefine = $inEnum = $inMethod = $inClass = 0; $inputCounter++; print STDERR "INCREMENTED INPUTCOUNTER [M9] TO $inputCounter\n" if ($HeaderDoc::inputCounterDebug); print STDERR "Input line number[8]: $inputCounter\n" if ($localDebug); } # end processing individual line array print STDERR "Top Level Point 895\n" if ($tlhangDebug); # Put it back at the end. if ($lang eq "perl" && ($apiOwner != $headerObject)) { print STDERR "Resetting Header Object\n" if ($localDebug); $apiOwner = $headerObject; } print STDERR "DONE PROCESSING LINE ARRAY\n" if ($HeaderDoc::inputCounterDebug); print STDERR "Top Level Point 900\n" if ($tlhangDebug); if (ref($apiOwner) ne "HeaderDoc::Header") { # if we've been filling a class/protocol/category object, add it to the header my $name = $apiOwner->name(); my $refName = ref($apiOwner); # print STDERR "$classType : "; SWITCH: { ($classType eq "php" ) && do { push (@perHeaderClassObjects, $apiOwner); $headerObject->addToClasses($apiOwner); last SWITCH; }; ($classType eq "java" ) && do { push (@perHeaderClassObjects, $apiOwner); $headerObject->addToClasses($apiOwner); last SWITCH; }; ($classType eq "cpp" ) && do { push (@perHeaderClassObjects, $apiOwner); $headerObject->addToClasses($apiOwner); last SWITCH; }; ($classType eq "cppt" ) && do { push (@perHeaderClassObjects, $apiOwner); $headerObject->addToClasses($apiOwner); last SWITCH; }; ($classType eq "occ") && do { push (@perHeaderClassObjects, $apiOwner); if ($headerIncluded{$basefilename}) { $retainheader = 1; } $headerObject->addToClasses($apiOwner); $objCClassNameToObject{$apiOwner->name()} = $apiOwner; last SWITCH; }; ($classType eq "intf") && do { push (@perHeaderClassObjects, $apiOwner); $headerObject->addToProtocols($apiOwner); last SWITCH; }; ($classType eq "occCat") && do { push (@perHeaderCategoryObjects, $apiOwner); print STDERR "INSERTED CATEGORY into $headerObject\n" if ($ctdebug); $headerObject->addToCategories($apiOwner); last SWITCH; }; ($classType eq "C") && do { # $cppAccessControlState = "public:"; $cppAccessControlState = ""; push (@perHeaderClassObjects, $apiOwner); $headerObject->addToClasses($apiOwner); last SWITCH; }; foreach my $testclassref ( $headerObject->classes() ) { my $testclass = %{$testclassref}; bless($testclass, "HeaderDoc::APIOwner"); bless($testclass, $testclass->class()); print STDERR $testclass->name() . "\n"; } my $linenum = $inputCounter - 1; print STDERR $headerObject->fullpath().":$linenum: warning: Unknown class type '$classType' (known: cpp, objC, intf, occCat)\n"; } } } # end processing array of line arrays print STDERR "Top Level Point 1000\n" if ($tlhangDebug); $headerObject->reparentModuleMembers(); my @newobjs = (); foreach my $class (@perHeaderClassObjects) { if (!$class->isModule()) { push(@newobjs, $class); } } @perHeaderClassObjects = @newobjs; # SDump($headerObject, "point5"); if ($retainheader) { push (@headerObjects, $headerObject); # print STDERR "Retaining header\n"; } } # end !$skip_header_processing print STDERR "Top Level Point 1100\n" if ($tlhangDebug); my ($headercpphashref, $headercpparghashref) = getAndClearCPPHash(); my %headercpphash = %{$headercpphashref}; my %headercpparghash = %{$headercpparghashref}; my $includeListRef = $HeaderDoc::perHeaderIncludes{$fullpath}; if ($includeListRef) { my @includeList = @{$includeListRef}; print STDERR "LISTING PER HEADER INCLUDES\n" if ($includeDebug); foreach my $include (@includeList) { print STDERR "INCLUDE: $include\n" if ($includeDebug); my $pathname = $include; $pathname =~ s/^\s*//s; $pathname =~ s/\s*$//s; if ($pathname !~ s/^\<(.*)\>$/$1/s) { $pathname =~ s/^\"(.*)\"$/$1/s; } print STDERR "SANITIZED PATHNAME: $pathname\n" if ($includeDebug); my $includedfilename = basename($pathname); print STDERR "INCLUDED FILENAME: $includedfilename\n" if ($includeDebug); if ($HeaderDoc::HeaderFileCPPHashHash{$includedfilename}) { # Merge the hashes. print STDERR "FOUND. MERGING HASHES\n" if ($includeDebug); %headercpphash = (%headercpphash, %{$HeaderDoc::HeaderFileCPPHashHash{$includedfilename}}); %headercpparghash = (%headercpparghash, %{$HeaderDoc::HeaderFileCPPArgHashHash{$includedfilename}}); printCPPHashes(\%headercpphash, \%headercpparghash) if ($includeDebug) } print STDERR "\n" if ($includeDebug); } } else { print STDERR "NO PER HEADER INCLUDES (NO REF)\n" if ($includeDebug); } print STDERR "Top Level Point 1200\n" if ($tlhangDebug); # NOTE: These MUST not be modified to use the full filename or path. # If you do, C preprocessing interaction between headers will fail. $HeaderDoc::HeaderFileCPPHashHash{$basefilename} = \%headercpphash; $HeaderDoc::HeaderFileCPPArgHashHash{$basefilename} = \%headercpparghash; # This is safe to do on a per-header basis, as we've already forced # dependency ordering. foreach my $class (@perHeaderClassObjects) { if ($headerIncluded{$basefilename}) { # print STDERR "Retaining class\n"; push(@classObjects, $class); } } if (@perHeaderClassObjects && !$xml_output) { foreach my $class (@perHeaderClassObjects) { mergeClass($class); } } print STDERR "Top Level Point 1300\n" if ($tlhangDebug); # print STDERR "CLASSES: ".scalar(@perHeaderClassObjects)."\n"; # print STDERR "CATEGORIES: ".scalar(@perHeaderCategoryObjects)."\n"; # print STDERR "HEADERS: ".scalar(@headerObjects)."\n"; # foreach my $obj (@perHeaderCategoryObjects) { # print STDERR "CO: $obj\n"; # } # we merge ObjC methods declared in categories into the owning class, # if we've seen it during processing. Since we do dependency ordering, # we should have seen it by now if we're ever going to. if (!$skip_header_processing) { if (@perHeaderCategoryObjects && !$xml_output) { foreach my $obj (@perHeaderCategoryObjects) { my $nameOfAssociatedClass = $obj->className(); my $categoryName = $obj->categoryName(); my $localDebug = 0; # print STDERR "FOR CATEGORY: \"$categoryName\" CLASS IS \"$nameOfAssociatedClass\"\n"; if (exists $objCClassNameToObject{$nameOfAssociatedClass}) { my $associatedClass = $objCClassNameToObject{$nameOfAssociatedClass}; print STDERR "AC: $associatedClass\n" if ($localDebug); print STDERR "OBJ: $obj\n" if ($localDebug); my $methods = $obj->methods(); $associatedClass->addToMethods($obj->methods()); my $owner = $obj->headerObject(); print STDERR "Found category with name $categoryName and associated class $nameOfAssociatedClass\n" if ($localDebug); print STDERR "Associated class exists\n" if ($localDebug); print STDERR "Added methods to associated class\n" if ($localDebug); if (ref($owner)) { my $numCatsBefore = $owner->categories(); # $owner->printObject(); $owner->removeFromCategories($obj); my $numCatsAfter = $owner->categories(); print STDERR "Number of categories before: $numCatsBefore after:$numCatsAfter\n" if ($localDebug); } else { my $fullpath = $HeaderDoc::headerObject->fullpath(); my $linenum = $obj->linenum(); print STDERR "$fullpath:$linenum: warning: Couldn't find Header object that owns the category with name $categoryName.\n"; } my $assocapio = $associatedClass->APIOwner(); $assocapio->resetAppleRefUsed(); if ($man_output) { $assocapio->writeHeaderElementsToManPage(); } elsif ($function_list_output) { $assocapio->writeFunctionListToStdOut(); } elsif ($xml_output) { $assocapio->writeHeaderElementsToXMLPage(); } else { $assocapio->fixupTypeRequests(); $assocapio->setupAPIReferences(); $assocapio->createFramesetFile(); $assocapio->createTOCFile(); $assocapio->writeHeaderElements(); $assocapio->writeHeaderElementsToCompositePage(); $assocapio->createContentFile() if (!$HeaderDoc::ClassAsComposite); } if ($doxytag_output) { $assocapio->writeHeaderElementsToDoxyFile(); } } else { print STDERR "Found category with name $categoryName and associated class $nameOfAssociatedClass\n" if ($localDebug); print STDERR "Associated class doesn't exist\n" if ($localDebug); } } } print STDERR "Top Level Point 1400\n" if ($tlhangDebug); # SDump($headerObject, "point5a"); if ($doxytag_output) { $headerObject->writeHeaderElementsToDoxyFile(); } if ($man_output) { $headerObject->writeHeaderElementsToManPage(); } elsif ($function_list_output) { $headerObject->writeFunctionListToStdOut(); } elsif ($xml_output) { $headerObject->writeHeaderElementsToXMLPage(); } else { $headerObject->fixupTypeRequests(); $headerObject->setupAPIReferences(); # SDump($headerObject, "point5a1"); $headerObject->createFramesetFile(); # SDump($headerObject, "point5a2"); $headerObject->createTOCFile(); # SDump($headerObject, "point5a3"); $headerObject->writeHeaderElements(); # SDump($headerObject, "point5a4"); $headerObject->writeHeaderElementsToCompositePage(); # SDump($headerObject, "point5a5"); $headerObject->createContentFile() if (!$HeaderDoc::ClassAsComposite); # SDump($headerObject, "point5a6"); } } # !$skip_header_processing # SDump($headerObject, "point5b"); # if ("$write_control_file" eq "1") { # print STDERR "Writing doc server control file... "; # $headerObject->createMetaFile(); # print STDERR "done.\n"; # } # my $old_handle = select (STDOUT); # "select" STDOUT and save # previously selected handle # $| = 1; # perform flush after each write to STDOUT # print STDERR "Freeing data\n"; # print STDERR ""; # sleep(5); # SDump($headerObject, "point5c"); print STDERR "Top Level Point 1500\n" if ($tlhangDebug); foreach my $class (@perHeaderClassObjects) { if (!$headerIncluded{$basefilename}) { $class->free($retainheader ? 2 : 0); } } # SDump($headerObject, "point5d"); if (!$retainheader) { $headerObject->free($headerIncluded{$basefilename}); $HeaderDoc::headerObject = undef; $HeaderDoc::currentClass = undef; } # SDump($headerObject, "point6"); print "Object $headerObject should go away.\n" if ($HeaderDoc::debugAllocations); $headerObject = undef; print "Object should be gone.\n" if ($HeaderDoc::debugAllocations); # SDump($headerObject, "point7"); # select ($old_handle); # restore previously selected handle # print STDERR "freed.\n"; # sleep(5); print STDERR "Top Level Point 1600\n" if ($tlhangDebug); } # sleep(5); # if (!$quietLevel) { # print STDERR "======= Beginning post-processing =======\n"; # } if ($doxytag_output && $specifiedOutputDir) { mergeDoxyTags($specifiedOutputDir); } dumpCaches() if ($HeaderDoc::debugAllocations); if ($quietLevel eq "0") { print STDERR "...done\n"; } if ($HeaderDoc::exitstatus != 0) { print STDERR "WARNING: One or more input files could not be read. Be sure to check the\n"; print STDERR "output to make sure that all desired content was documented.\n"; } # print STDERR "COUNTER: ".$HeaderDoc::counter."\n"; exit $HeaderDoc::exitstatus; ############################# Subroutines ################################### # /*! # @abstract # Merges superclass bits into subclass upon request. # @discussion # The mergeClass function is used for merging bits of # a superclass into subclasses when the \@superclass # tag is specified. # # It is also always used for C psuedoclass classes # because any pseudo-superclass relationship isn't # really a superclass. # */ sub mergeClass { my $class = shift; my $superName = $class->checkShortLongAttributes("Superclass"); my $merge_content = 1; my $localDebug = 0; if ($class->isMerged()) { return; } # If superclass was not explicitly specified in the header and if # the 'S' (include all superclass documentation) flag was not # specified on the command line, don't include any superclass # documentation here. if (!$class->explicitSuper() && !$HeaderDoc::IncludeSuper) { $merge_content = 0; } if ($superName) { if (!($superName eq $class->name())) { my $super = 0; foreach my $mergeclass (@classObjects) { if ($mergeclass->name eq $superName) { $super = $mergeclass; } } if ($super) { if (!$super->isMerged()) { mergeClass($super); } my @methods = $super->methods(); my @functions = $super->functions(); my @vars = $super->vars(); my @structs = $super->structs(); my @enums = $super->enums(); my @pdefines = $super->pDefines(); my @typedefs = $super->typedefs(); my @constants = $super->constants(); my @classes = $super->classes(); my $name = $super->name(); my $discussion = $super->discussion(); $class->inheritDoc($discussion); if ($merge_content) { my @childfunctions = $class->functions(); my @childmethods = $class->methods(); my @childvars = $class->vars(); my @childstructs = $class->structs(); my @childenums = $class->enums(); my @childpdefines = $class->pDefines(); my @childtypedefs = $class->typedefs(); my @childconstants = $class->constants(); my @childclasses = $class->classes(); if (@methods) { foreach my $method (@methods) { if ($method->accessControl() eq "private") { next; } my $include = 1; foreach my $childmethod (@childmethods) { if ($method->name() eq $childmethod->name()) { if ($method->parsedParamCompare($childmethod)) { $include = 0; last; } } } if (!$include) { next; } my $newobj = $method->clone(); $class->addToMethods($method); $newobj->apiOwner($class); if ($newobj->origClass() eq "") { $newobj->origClass($name); } $HeaderDoc::ignore_apiuid_errors = 1; my $junk = $newobj->apirefSetup(1); $HeaderDoc::ignore_apiuid_errors = 0; } } if (@functions) { foreach my $function (@functions) { if ($function->accessControl() eq "private") { next; } my $include = 1; foreach my $childfunction (@childfunctions) { if ($function->name() eq $childfunction->name()) { if ($function->parsedParamCompare($childfunction)) { $include = 0; last; } } } if (!$include) { next; } my $newobj = $function->clone(); $class->addToFunctions($newobj); $newobj->apiOwner($class); if ($newobj->origClass() eq "") { $newobj->origClass($name); } $HeaderDoc::ignore_apiuid_errors = 1; my $junk = $newobj->apirefSetup(1); $HeaderDoc::ignore_apiuid_errors = 0; } } if (@vars) { foreach my $var (@vars) { if ($var->accessControl() eq "private") { next; } my $include = 1; foreach my $childvar (@childvars) { if ($var->name() eq $childvar->name()) { $include = 0; last; } } if (!$include) { next; } my $newobj = $var->clone(); $class->addToVars($newobj); $newobj->apiOwner($class); if ($newobj->origClass() eq "") { $newobj->origClass($name); } $HeaderDoc::ignore_apiuid_errors = 1; my $junk = $newobj->apirefSetup(1); $HeaderDoc::ignore_apiuid_errors = 0; } } if (@structs) { foreach my $struct (@structs) { if ($struct->accessControl() eq "private") { next; } my $include = 1; foreach my $childstruct (@childstructs) { if ($struct->name() eq $childstruct->name()) { $include = 0; last; } } my $newobj = $struct->clone(); $class->addToStructs($newobj); $newobj->apiOwner($class); if ($newobj->origClass() eq "") { $newobj->origClass($name); } $HeaderDoc::ignore_apiuid_errors = 1; my $junk = $newobj->apirefSetup(1); $HeaderDoc::ignore_apiuid_errors = 0; } } if (@enums) { foreach my $enum (@enums) { if ($enum->accessControl() eq "private") { next; } my $include = 1; foreach my $childenum (@childenums) { if ($enum->name() eq $childenum->name()) { $include = 0; last; } } my $newobj = $enum->clone(); $class->addToEnums($newobj); $newobj->apiOwner($class); if ($newobj->origClass() eq "") { $newobj->origClass($name); } $HeaderDoc::ignore_apiuid_errors = 1; my $junk = $newobj->apirefSetup(1); $HeaderDoc::ignore_apiuid_errors = 0; } } if (@pdefines) { foreach my $pdefine (@pdefines) { if ($pdefine->accessControl() eq "private") { next; } my $include = 1; foreach my $childpdefine (@childpdefines) { if ($pdefine->name() eq $childpdefine->name()) { $include = 0; last; } } my $newobj = $pdefine->clone(); $class->addToPDefines($newobj); $newobj->apiOwner($class); if ($newobj->origClass() eq "") { $newobj->origClass($name); } $HeaderDoc::ignore_apiuid_errors = 1; my $junk = $newobj->apirefSetup(1); $HeaderDoc::ignore_apiuid_errors = 0; } } if (@typedefs) { foreach my $typedef (@typedefs) { if ($typedef->accessControl() eq "private") { next; } my $include = 1; foreach my $childtypedef (@childtypedefs) { if ($typedef->name() eq $childtypedef->name()) { $include = 0; last; } } my $newobj = $typedef->clone(); $class->addToTypedefs($newobj); $newobj->apiOwner($class); if ($newobj->origClass() eq "") { $newobj->origClass($name); } $HeaderDoc::ignore_apiuid_errors = 1; my $junk = $newobj->apirefSetup(1); $HeaderDoc::ignore_apiuid_errors = 0; } } if (@constants) { foreach my $constant (@constants) { if ($constant->accessControl() eq "private") { next; } my $include = 1; foreach my $childconstant (@childconstants) { if ($constant->name() eq $childconstant->name()) { $include = 0; last; } } my $newobj = $constant->clone(); $class->addToConstants($newobj); $newobj->apiOwner($class); if ($newobj->origClass() eq "") { $newobj->origClass($name); } $HeaderDoc::ignore_apiuid_errors = 1; my $junk = $newobj->apirefSetup(1); $HeaderDoc::ignore_apiuid_errors = 0; } } if (@classes) { foreach my $classref (@classes) { my $class = %{$class}; bless($class, "HeaderDoc::APIOwner"); bless($class, $class->class()); if ($class->accessControl() eq "private") { next; } my $include = 1; foreach my $childclassref (@childclasses) { my $childclass = %{$childclassref}; bless($class, "HeaderDoc::APIOwner"); bless($class, $class->class()); if ($class->name() eq $childclass->name()) { $include = 0; last; } } my $newobj = $class->clone(); $class->addToClasses($newobj); $newobj->apiOwner($class); if ($newobj->origClass() eq "") { $newobj->origClass($name); } $HeaderDoc::ignore_apiuid_errors = 1; my $junk = $newobj->apirefSetup(1); $HeaderDoc::ignore_apiuid_errors = 0; } } } # if ($merge_content) } } } my $ai = $class->alsoInclude(); if ($ai) { my @aiarray = @{$ai}; foreach my $entry (@aiarray) { my $explicit = 0; my $uid = ""; $entry =~ s/^\s*//s; $entry =~ s/\s*$//s; if ($entry =~ /^\/\//) { $uid = $entry; $explicit = 1; } else { $uid = resolveLink($class, $entry, "included") } print STDERR "UID IS \"$uid\"\n" if ($localDebug); my $obj = objectForUID($uid); if (!$obj) { warn "Object for \"$uid\" could not be found.\n"; if (!$explicit) { warn " This should not happen. Please file a bug.\n"; } } else { my $objcl = ref($obj) || $obj; print STDERR "OBJ IS $obj\n" if ($localDebug); if ($objcl =~ /HeaderDoc::Function/) { $class->addToFunctions($obj); $obj->apiOwner()->removeFromFunctions($obj); } elsif ($objcl =~ /HeaderDoc::PDefine/) { $class->addToPDefines($obj); $obj->apiOwner()->removeFromPDefines($obj); } else { warn "Don't know how to add object of type $objcl to pseudoclass\n"; } } } } $class->isMerged(1); } # /*! # @abstract # Merge doxygen tag files and delete the partial files. # */ sub mergeDoxyTags { my $outputDir = shift; find(\&getDoxyTagFiles, $outputDir); my $tagfileoutput = ""; my $temp = $/; $/ = undef; foreach my $file (@doxyTagFiles) { open(MYFILE, "<$file"); my $temp = ; $temp =~ s/^\s*\n*//s; $temp =~ s/\n\s*<\/tagfile>.*$//s; $tagfileoutput .= "\n".$temp; close(MYFILE); if (!$debugging) { unlink($file); } } $/ = $temp; $tagfileoutput =~ s/^\n//; my $tagfile = "$outputDir".$pathSeparator."doxytags.xml"; open(MYFILE, ">$tagfile"); print MYFILE "\n"; print MYFILE $tagfileoutput; print MYFILE "\n\n"; close(MYFILE); } # /*! # @abstract # Creates directories recursively as needed. # @discussion # This is the Perl equivalent of "mkdir -p" # on the command line. # */ sub mkdir_recursive { my $path = shift; my $mask = shift; my @pathparts = split (/$pathSeparator/, $path); my $curpath = ""; my $first = 1; foreach my $pathpart (@pathparts) { if ($first) { $first = 0; $curpath = $pathpart; } elsif (! -e "$curpath$pathSeparator$pathpart") { if (!mkdir("$curpath$pathSeparator$pathpart", 0777)) { return 0; } $curpath .= "$pathSeparator$pathpart"; } else { $curpath .= "$pathSeparator$pathpart"; } } return 1; } # /*! # @abstract # Strips out HeaderDoc comments from a header. # */ sub strip { my $filename = shift; my $short_output_path = shift; my $long_output_path = shift; my $input_path_and_filename = shift; my $inputRef = shift; my @inputLines = @$inputRef; my $localDebug = 0; # for same layout as HTML files, do this: # my $output_file = "$long_output_path$pathSeparator$filename"; # my $output_path = "$long_output_path"; # to match the input file layout, do this: my $output_file = "$short_output_path$pathSeparator$input_path_and_filename"; my $output_path = "$short_output_path"; my @pathparts = split(/($pathSeparator)/, $input_path_and_filename); my $junk = pop(@pathparts); my $input_path = ""; foreach my $part (@pathparts) { $input_path .= $part; } if ($localDebug) { print STDERR "output path: $output_path\n"; print STDERR "short output path: $short_output_path\n"; print STDERR "long output path: $long_output_path\n"; print STDERR "input path and filename: $input_path_and_filename\n"; print STDERR "input path: $input_path\n"; print STDERR "filename: $filename\n"; print STDERR "output file: $output_file\n"; } if (-e $output_file) { # don't risk writing over original header $output_file .= "-stripped"; print STDERR "WARNING: output file exists. Saving as\n\n"; print STDERR " $output_file\n\n"; print STDERR "instead.\n"; } # mkdir -p $output_path if (! -e "$output_path$pathSeparator$input_path") { unless (mkdir_recursive ("$output_path$pathSeparator$input_path", 0777)) { die "Error: $output_path$pathSeparator$input_path does not exist. Exiting. \n$!\n"; } } open(OUTFILE, ">$output_file") || die "Can't write $output_file.\n"; if ($^O =~ /MacOS/io) {MacPerl::SetFileInfo('R*ch', 'TEXT', "$output_file");}; my $inComment = 0; my $text = ""; my $localDebug = 0; foreach my $line (@inputLines) { print STDERR "line $line\n" if ($localDebug); print STDERR "inComment $inComment\n" if ($localDebug); if (($line =~ /^\/\*\!/o) || (($lang eq "java" || $HeaderDoc::parse_javadoc) && ($line =~ /^\s*\/\*\*[^*]/o))) { # entering headerDoc comment # on entering a comment, set state to 1 (in comment) $inComment = 1; } if ($inComment && ($line =~ /\*\//o)) { # on leaving a comment, set state to 2 (leaving comment) $inComment = 2; } if (!$inComment) { $text .= $line; } if ($inComment == 2) { # state change back to 0 (we just skipped the last line of the comment) $inComment = 0; } } # print STDERR "text is $text\n"; print OUTFILE $text; close OUTFILE; } # /*! @abstract # Grabs any #include directives. # */ sub processIncludes($$$) { my $lineArrayRef = shift; my $pathname = shift; my $lang = shift; my $sublang = shift; my @lines = @{$lineArrayRef}; my $filename = basename($pathname); my $ah = HeaderDoc::AvailHelper->new(); my @includeList = (); my $availDebug = 0; # The next few lines were a bad idea. If you have two files # with the same name, the include lists got merged.... # my $includeListRef = $HeaderDoc::perHeaderIncludes{$fullpath}; # if ($includeListRef) { # @includeList = @{$includeListRef}; # } my @ranges = (); my @rangestack = (); my $linenum = 1; my $continuation = 0; my $contline = ""; my $inComment = 0; my $parseTokensRef = parseTokens($lang, $sublang); foreach my $line (@lines) { $inComment = prefilterCommentCheck($line, $parseTokensRef, $inComment); # print STDERR "IC: $inComment\n"; if ($continuation) { if ($line =~ /\\\s*$/) { $contline .= $line; } else { my $rangeref = pop(@rangestack); my $range = ${$rangeref}; $contline .= $line; $continuation = 0; if ($HeaderDoc::auto_availability) { $range->text($ah->parseString($contline, $pathname, $linenum)); } push(@rangestack, \$range); } } else { my $hackline = $line; if ($hackline =~ s/^\s*#(include|import)\s+//so && !$inComment) { my $incfile = ""; if ($hackline =~ /^(<.*?>)/o) { $incfile = $1; } elsif ($hackline =~ /^(\".*?\")/o) { $incfile = $1; } else { warn "$pathname:$linenum: warning: Unable to determine include file name for \"$line\".\n"; } if (length($incfile)) { push(@includeList, $incfile); } } if ($hackline =~ s/^\s*#ifdef\s+//so && !$inComment) { print STDERR "STARTRANGE ifdef: $hackline\n" if ($availDebug); my $range = HeaderDoc::LineRange->new(); $range->start($linenum); push(@rangestack, \$range); } if ($hackline =~ s/^\s*#ifndef\s+//so && !$inComment) { print STDERR "STARTRANGE ifndef: $hackline\n" if ($availDebug); my $range = HeaderDoc::LineRange->new(); $range->start($linenum); $range->text(""); push(@rangestack, \$range); } if ($hackline =~ s/^\s*#if\s+//so && !$inComment) { print STDERR "STARTRANGE if: $hackline\n" if ($availDebug); my $range = HeaderDoc::LineRange->new(); $range->start($linenum); if ($hackline =~ /\\\s*$/) { $continuation = 1; } else { if ($HeaderDoc::auto_availability) { $range->text($ah->parseString($hackline, $pathname, $linenum)); } } push(@rangestack, \$range); } if ($hackline =~ s/^\s*#endif\s+//so && !$inComment) { print STDERR "ENDRANGE: $hackline\n" if ($availDebug); my $rangeref = pop(@rangestack); if ($rangeref) { my $range = ${$rangeref}; bless($range, "HeaderDoc::LineRange"); $range->end($linenum); if (length($range->text())) { push(@ranges, \$range); } } else { warn "$pathname:$linenum: warning: Unbalanced #endif found in prescan.\n"; } } $linenum++; } } if (0) { print STDERR "Includes for \"$filename\":\n"; foreach my $name (@includeList) { print STDERR "$name\n"; } } if ($availDebug) { print STDERR "Ranges for \"$filename\":\n"; foreach my $rangeref (@ranges) { my $range = ${$rangeref}; bless($range, "HeaderDoc::LineRange"); print STDERR "-----\n"; print STDERR "START: ".$range->start()."\n"; print STDERR "END: ".$range->end()."\n"; print STDERR "TEXT: ".$range->text()."\n"; } } $HeaderDoc::perHeaderIncludes{$pathname} = \@includeList; $HeaderDoc::perHeaderRanges{$pathname} = \@ranges; } # /*! # @abstract # Checks for comment characters in a line and adjusts an # inComment variable accordingly, returning the new value. # @param line # The line to parse. # @param parseTokensRef # A set of parse tokens obtained with a call to {@link //apple_ref/perl/instm/HeaderDoc::Utilities/parseTokens//() parseTokens}. # @param inComment # The previous value of inComment. # @result # Returns the new value for inComment. # */ sub prefilterCommentCheck { my $line = shift; my $parseTokensRef = shift; my $inComment = shift; my $localDebug = 0; my %parseTokens = %{$parseTokensRef}; my $soc = $parseTokens{soc}; my $eoc = $parseTokens{eoc}; my $ilc = $parseTokens{ilc}; my $ilc_b = $parseTokens{ilc_b}; my @parts = (); if ($soc && $ilc && $ilc_b) { @parts = split(/(\Q$soc\E|\Q$eoc\E|\Q$ilc\E|\Q$ilc_b\E|[\r\n])/, $line); } elsif ($soc && $ilc) { @parts = split(/(\Q$soc\E|\Q$eoc\E|\Q$ilc\E|[\r\n])/, $line); } elsif ($ilc && $ilc_b) { @parts = split(/(\Q$ilc\E|\Q$ilc_b\E|[\r\n])/, $line); } elsif ($ilc) { @parts = split(/(\Q$ilc\E|[\r\n])/, $line); } else { return 0; } # This is processing a new line, so single-line comments from # the previous line are always ignored. if ($inComment == 2) { $inComment = 0; } foreach my $part (@parts) { print STDERR "checking $part (SOC: $soc ILC: $ilc ILC_B: $ilc_b EOC: $eoc)\n" if ($localDebug); if (($eoc) && ($part eq $eoc) && ($inComment == 1)) { $inComment = 0; print STDERR "GOT EOC\n" if ($localDebug); } elsif (($part =~ /[\r\n]/) && ($inComment == 2)) { # The "line" passed in may contain multiple lines, so we have to # do this check. print STDERR "GOT NEWLINE IN ILC\n" if ($localDebug); $inComment = 0; } elsif (!$inComment) { if (($soc) && ($part eq $soc)) { print STDERR "GOT SOC\n" if ($localDebug); $inComment = 1; } elsif (($ilc) && ($part eq $ilc)) { print STDERR "GOT ILC\n" if ($localDebug); $inComment = 2; } elsif (($ilc_b) && ($part eq $ilc_b)) { print STDERR "GOT ILC_B\n" if ($localDebug); $inComment = 2; } } } return $inComment; } my %pathForInclude; # /*! # @abstract # Reorders a list of C headers in dependency order. # @discussion # The dependency calculation is fairly straightforward. For # each header, HeaderDoc reads the list of #include # and #import directives. Then, for each input file: # #
    #
  • HeaderDoc looks to see if a dependency object with that filename exists. A # preexisting dependency object can occur for two reasons: because another # header has declared a dependency on it previously or because another header # with the same name has already been scanned.
  • #
  • If the dependency exists and the EXISTS flag is set on that # object, HeaderDoc knows that it has already processed a header with the # same name, and HeaderDoc emits a warning, then creates a new dependency # object.
  • #
  • If the dependency object exists and the EXISTS flag is not set. # HeaderDoc sets the EXISTS flag on the existing dependency object.
  • #
  • If the dependency object does not exist, it creates a new node and sets the # EXISTS flag.
  • #
  • HeaderDoc creates dependencies for each header that it includes, nested within # the dependency object for this input file.
  • #
# # After creating these child dependencies for every header, the result is a graph of # dependencies, with cycles. # # Finally, the code performs a depth-first walk of the graph (with cycle detection). # Because each object is guaranteed to be lower in the graph for at least one path # through the graph than any file on which it depends (ignoring circular dependencies), # this is sufficient to ensure that any file is processed prior to any file that it # includes. # # @result # Returns the result of a depth-first walk of the dependency graph in the form of a # dependency-ordered array. # # @var force # Set if the dependency object was found, but refers to a different header # (EXISTS is set). This forces a new object to be created in # spite of the existence of another object with the same name. # */ sub fix_dependency_order { my $inputlistref = shift; my @inputfiles = @{$inputlistref}; my $treetop = undef; # my %refhash = (); my $localDebug = 0; %pathForInclude = (); print STDERR "Scanning dependencies.\n" if ($localDebug || $debugging); my $foundcount = 0; foreach my $rawfilename (@inputfiles) { my $filename = basename($rawfilename); my $fullpath=getAbsPath($rawfilename); $pathForInclude{$filename} = $rawfilename; print STDERR "IN FILE: $filename:\n" if ($localDebug); # my $dep = HeaderDoc::Dependency->new(); my $curnoderef = HeaderDoc::Dependency->findname($filename); my $curnode = undef; my $force = 0; if ($curnoderef) { print STDERR "Node exists\n" if ($localDebug); $curnode = ${$curnoderef}; bless($curnode, "HeaderDoc::Dependency"); if ($curnode->{EXISTS}) { print STDERR "Node marked with EXISTS. Setting force -> 1\n" if ($localDebug); warn "WARNING: Multiple files named \"$filename\" found in argument\n". "list. Dependencies may not work as expected.\n"; $force = 1; } } if (!$curnoderef || $force) { print STDERR "CNR: $curnoderef FORCE: $force\n" if ($localDebug); $curnode = HeaderDoc::Dependency->new(); if (!$treetop) { $treetop = $curnode; $curnode = HeaderDoc::Dependency->new(); } $curnode->name($rawfilename); $curnode->depname($filename); $curnode->{EXISTS} = 1; } else { print STDERR "CNR: $curnoderef\n" if ($localDebug); $curnode = ${$curnoderef}; bless($curnode, "HeaderDoc::Dependency"); print STDERR " CN: $curnode\n" if ($localDebug); $curnode->name($rawfilename); $curnode->{EXISTS} = 1; $foundcount ++; } foreach my $include (@{$HeaderDoc::perHeaderIncludes{$fullpath}}) { print STDERR " COMPARE INCLUDE: $include\n" if ($localDebug); my $tempname = $include; # my @oldlist = (); $tempname =~ s/^\s*//s; $tempname =~ s/\s*$//s; if ($tempname !~ s/^\<(.*)\>$/$1/s) { $tempname =~ s/^\"(.*)\"$/$1/s; } my $rawincname = $tempname; $tempname = basename($tempname); print STDERR " TMPNM: $tempname\n" if ($localDebug); # if ($refhash{$tempname}) { # @oldlist = @{$refhash{$tempname}}; # } # push(@oldlist, $filename); # $refhash{$tempname} = \@oldlist; my $noderef = HeaderDoc::Dependency->findname($tempname); my $node = undef; if (!$noderef) { print STDERR "No existing reference found.\n" if ($localDebug); $node = HeaderDoc::Dependency->new(); $node->name($rawincname); $node->depname($tempname); } else { print STDERR "Existing reference found.\n" if ($localDebug); $node = HeaderDoc::Dependency->new(); $node = ${$noderef}; bless($node, "HeaderDoc::Dependency"); } $curnode->addchild($node); # print STDERR "$curnode -> $node\n"; } $treetop->addchild($curnode); } print STDERR "foundcount: $foundcount\n" if ($localDebug); # $treetop->dbprint() if ($localDebug); print STDERR "doing depth-first traversal.\n" if ($localDebug || $debugging); my $ret = depthfirst($treetop); if ($localDebug) { foreach my $entry (@{$ret}) { print STDERR "$entry "; } } $treetop = undef; print STDERR "\ndone.\n" if ($localDebug || $debugging); return $ret; } my @deplevels = (); # /*! # @abstract # Sets node depth information in the tree structure # as part of the depth-first tree walk code. # */ sub set_node_depths { my $node = shift; my $depth = shift; my $localDebug = 0; if ($depth <= 1) { print STDERR "Generating depth for ".$node->{NAME}."\n" if ($localDebug); } if ($node->{MARKED}) { # Avoid infinite recursion or reparenting nodes to deeper depth than things they include. return; } $node->{MARKED} = 1; if ($node->{DEPTH} <= $depth || !$node->{DEPTH}) { # print STDERR "NODE DEPTH NOW $depth\n" if ($localDebug); $node->{DEPTH} = $depth; } else { # Nothing to do. $node->{MARKED} = 0; return; } foreach my $childref (@{$node->{CHILDREN}}) { my $child = ${$childref}; bless($child, "HeaderDoc::Dependency"); set_node_depths($child, $depth + 1); } $node->{MARKED} = 0; } my $maxdependencydepth = 0; # /*! # @abstract # Lays out dependencies in a tree structure. # */ sub generate_depth_levels { my $node = shift; my $depth = shift; my $localDebug = 0; if ($node->{MARKED}) { # Avoid infinite recursion or reparenting nodes to deeper depth than things they include. return; } $node->{MARKED} = 1; print STDERR "NODE DEPTH: ".$node->{DEPTH}."\n" if ($localDebug);; my @levelarr = (); if ($deplevels[$node->{DEPTH}]) { @levelarr = @{$deplevels[$node->{DEPTH}]}; } push(@levelarr, \$node); $deplevels[$node->{DEPTH}] = \@levelarr; if ($node->{DEPTH} > $maxdependencydepth) { print STDERR "MAX DEPTH: $maxdependencydepth -> " if ($localDebug); $maxdependencydepth = $node->{DEPTH}; print STDERR "$maxdependencydepth\n" if ($localDebug); } foreach my $childref (@{$node->{CHILDREN}}) { my $child = ${$childref}; bless($child, "HeaderDoc::Dependency"); generate_depth_levels($child, $depth + 1); } } # /*! # @abstract # Depth-first tree walk used in determining dependency order. # */ sub depthfirst { my @rawfiles = (); my $treetop = shift; # my $debugging = 1; print STDERR "Doing recursive sort by depth\n" if ($debugging); # my $depth = depthfirst_rec(\$treetop, 0); set_node_depths($treetop, 0); print STDERR "Generating depth levels\n" if ($debugging); generate_depth_levels($treetop); my $depth = $maxdependencydepth; $treetop->dbprint() if ($debugging); print STDERR "Sweeping levels from depth $maxdependencydepth:\n" if ($debugging); my $level = $depth; while ($level >= 0) { print STDERR "Level $level\n" if ($debugging); my @array = (); if ($deplevels[$level]) { @array = @{$deplevels[$level]}; } else { print STDERR "No entries at level $level. How peculiar.\n" if ($debugging); } foreach my $dep (@array) { $dep = ${$dep}; bless($dep, "HeaderDoc::Dependency"); print STDERR "Adding ".$dep->name()."\n" if ($debugging); # if ($pathForInclude{$dep->depname}) { if ($dep->{EXISTS}) { # push(@rawfiles, $pathForInclude{$dep->depname}); push(@rawfiles, $dep->name()); } # } else { # warn("DNE: ".$dep->name()."\n"); # } } $level--; } print STDERR "done sweeping.\n" if ($debugging); my @files = (); my %namehash = (); my $filename; foreach $filename (@inputFiles) { # my $bn = basename($filename); print STDERR "File: $filename\n" if ($debugging); $namehash{$filename} = 1; } foreach my $filename (@rawfiles) { if (length($filename)) { # my $bn = basename($filename); if ($namehash{$filename}) { print STDERR "pushing $filename\n" if ($debugging); push(@files, $filename); $namehash{$filename} = 0; # include once. } else { print STDERR "skipping $filename\n" if ($debugging); } } } return \@files; } my %pathparts = (); # /*! # @abstract # Recursive portion of depth-first tree walk used # in determining dependency order. # */ sub depthfirst_rec { my $noderef = shift; my $level = shift; my $maxlevel = $level; my $norecurse = 0; # print STDERR "Depth: $level\n"; if (!$noderef) { return; } # print STDERR "NODEREF: $noderef\n"; my $node = ${$noderef}; bless($node, "HeaderDoc::Dependency"); # print STDERR "NAME: ".$node->name()."\n"; if ($node->{MARKED}) { $norecurse = 1; } if ($pathparts{$node->depname()}) { return; } if ($node->{MARKED} < $level+1) { $node->{MARKED} = $level + 1; } # print STDERR "NODE: $node\n"; if (!$norecurse && $node->{CHILDREN}) { my $opp = $pathparts{$node->depname()}; if ($opp == undef) { $opp = 0; } $pathparts{$node->depname()} = 1; foreach my $child (@{$node->{CHILDREN}}) { my $templevel = depthfirst_rec($child, $level + 1); if ($templevel > $maxlevel) { $maxlevel = $templevel; } } $pathparts{$node->depname()} = $opp; } my @oldarr = (); if ($deplevels[$level]) { @oldarr = @{$deplevels[$level]}; } push(@oldarr, \$node); $deplevels[$level] = \@oldarr; return $maxlevel; } # /*! # @abstract # Collects version info from HeaderDoc modules and prints. # */ sub printVersionInfo { my $bp = $HeaderDoc::BlockParse::VERSION; my $av = $HeaderDoc::APIOwner::VERSION; my $hev = $HeaderDoc::HeaderElement::VERSION; my $hv = $HeaderDoc::Header::VERSION; my $cppv = $HeaderDoc::CPPClass::VERSION; my $objcclassv = $HeaderDoc::ObjCClass::VERSION; my $objccnv = $HeaderDoc::ObjCContainer::VERSION; my $objccatv = $HeaderDoc::ObjCCategory::VERSION; my $objcprotocolv = $HeaderDoc::ObjCProtocol::VERSION; my $fv = $HeaderDoc::Function::VERSION; my $mv = $HeaderDoc::Method::VERSION; my $depv = $HeaderDoc::Dependency::VERSION; my $lr = $HeaderDoc::LineRange::VERSION; my $ah = $HeaderDoc::AvailHelper::VERSION; my $tv = $HeaderDoc::Typedef::VERSION; my $sv = $HeaderDoc::Struct::VERSION; my $cv = $HeaderDoc::Constant::VERSION; my $vv = $HeaderDoc::Var::VERSION; my $ev = $HeaderDoc::Enum::VERSION; my $uv = $HeaderDoc::Utilities::VERSION; my $me = $HeaderDoc::MinorAPIElement::VERSION; my $pd = $HeaderDoc::PDefine::VERSION; my $pt = $HeaderDoc::ParseTree::VERSION; my $ps = $HeaderDoc::ParserState::VERSION; my $ih = $HeaderDoc::IncludeHash::VERSION; my $rg = $HeaderDoc::Regen::VERSION; print STDERR "---------------------------------------------------------------------\n"; print STDERR "\tHeaderDoc Version: ".$HeaderDoc_Version."\n\n"; print STDERR "\theaderDoc2HTML - $VERSION\n"; print STDERR "\tModules:\n"; print STDERR "\t\tAPIOwner - $av\n"; print STDERR "\t\tAvailHelper - $ah\n"; print STDERR "\t\tBlockParse - $bp\n"; print STDERR "\t\tCPPClass - $cppv\n"; print STDERR "\t\tConstant - $cv\n"; print STDERR "\t\tDependency - $depv\n"; print STDERR "\t\tEnum - $ev\n"; print STDERR "\t\tFunction - $fv\n"; print STDERR "\t\tHeader - $hv\n"; print STDERR "\t\tHeaderElement - $hev\n"; print STDERR "\t\tIncludeHash - $ih\n"; print STDERR "\t\tLineRange - $lr\n"; print STDERR "\t\tMethod - $mv\n"; print STDERR "\t\tMinorAPIElement - $me\n"; print STDERR "\t\tObjCCategory - $objccatv\n"; print STDERR "\t\tObjCClass - $objcclassv\n"; print STDERR "\t\tObjCContainer - $objccnv\n"; print STDERR "\t\tObjCProtocol - $objcprotocolv\n"; print STDERR "\t\tPDefine - $pd\n"; print STDERR "\t\tParseTree - $pt\n"; print STDERR "\t\tParserState - $ps\n"; print STDERR "\t\tStruct - $sv\n"; print STDERR "\t\tTypedef - $tv\n"; print STDERR "\t\tUtilities - $uv\n"; print STDERR "\t\tVar - $vv\n"; print STDERR "---------------------------------------------------------------------\n"; } # /*! # @abstract # Test code for dumping notes in dependency tree. # */ sub SDump { my $arg = shift; my $text = shift; print STDERR "At position $text:\n"; Dump($arg); print STDERR "End dump\n"; } # /*! # @abstract # Creates a new test for the test framework. # @discussion See # {@linkdoc //apple_ref/doc/uid/TP40001215 HeaderDoc User Guide} # for info about creating tests. # */ sub newtest { $/ = "\n"; print STDERR "Enter name of test\n"; my $name = ; $name =~ s/\n$//s; my $lang = ""; my $sublang = ""; while ($lang !~ /^(C|java|javascript|pascal|php|perl|Csource|shell|csh|IDL|MIG|ruby|python|applescript|tcl)$/) { print STDERR "Enter language (C|java|javascript|pascal|php|perl|Csource|shell|csh|IDL|MIG|ruby|python|applescript|tcl)\n"; $lang = ; $lang =~ s/\n$//s; if ($lang eq "IDL") { $lang = "C"; $sublang = "IDL"; } elsif ($lang eq "MIG") { $lang = "C"; $sublang = "MIG"; } elsif ($lang eq "csh") { $lang = "shell"; $sublang = "csh"; } elsif ($lang eq "javascript") { $lang = "java"; $sublang = "javascript"; } else { $sublang = $lang; } } $HeaderDoc::lang = $lang; $HeaderDoc::sublang = $sublang; my $type = ""; if ($lang eq "C" || $lang eq "Csource") { while ($type !~ /(parser|cpp)/) { print STDERR "Enter type of test (parser|cpp)\n"; $type = ; $type =~ s/\n$//s; } } else { $type = "parser"; } $/ = undef; my $cppcode = ""; my $comment = ""; if ($type eq "parser") { print STDERR "Paste in HeaderDoc comment block.\nPress control-d on a new line when done.\n"; $comment = ; seek(STDIN,0,0); print STDERR "Paste in block of code.\nPress control-d on a new line when done.\n"; } else { print STDERR "Paste in initial macros.\nPress control-d on a new line when done.\n"; $cppcode = ; seek(STDIN,0,0); print STDERR "Paste in block of code to permute with this macro.\nPress control-d on a new line when done.\n"; } my $code = ; seek(STDIN,0,0); print STDERR "Optionally paste or type in a message to be displayed if the test fails.\nPress control-d on a new line when done.\n"; my $failmsg = ; seek(STDIN,0,0); my $test = HeaderDoc::Test->new( "NAME" => $name, "COMMENT" => $comment, "CODE" => $code, "LANG" => $lang, "SUBLANG" => $sublang, "TYPE" => $type, "CPPCODE" => $cppcode, "FAILMSG" => $failmsg ); # Don't check return value here. The "utilities" test will always fail # because the file has not yet been written to disk. $test->runTest(); $test->{EXPECTED_RESULT} = $test->{RESULT}; $test->{EXPECTED_RESULT_ALLDECS} = $test->{RESULT_ALLDECS}; my $filename = $test->{FILENAME}; # $filename =~ s/[^a-zA-Z0-9_.,-]/_/sg; my $testdir = $HeaderDoc::testdir; if (-d "$testdir") { if ($type eq "parser") { $filename = "$testdir/parser_tests/$filename"; } else { $filename = "$testdir/c_preprocessor_tests/$filename"; } } else { $filename = "/tmp/$filename.test"; } if (-f $filename) { print "You are about to overwrite an existing test case. Continue? (yes|no)\n"; $/ = "\n"; my $reply = ; $reply =~ s/\n$//s; if ($reply ne "yes") { print "Cancelled.\n"; exit(-1); } } $test->writeToFile($filename); $test->writeToPlist($filename); print "Wrote test data to \"$filename\"\n"; $test->dbprint(); } # /*! # @abstract # Runs a test using the test framework. # @discussion See # {@linkdoc //apple_ref/doc/uid/TP40001215 HeaderDoc User Guide} # for info about running tests. # */ sub runtests { my $mode = shift; my $argref = shift; my @args = @{$argref}; # my $testdir = $HeaderDoc::testdir; # my $filename = "$testdir/parser_tests/test.test"; my $ok_count = 0; my $fail_count = 0; my @testlist = undef; my $update = 0; if ($mode eq "update") { $update = 1; } my $force = 0; if ($mode eq "forceupdate") { $update = 1; $force = 1; } my $testdir = $HeaderDoc::testdir; my ($encoding, $linesref) = linesFromFile($testdir.$pathSeparator."version", 0); my $installedversion = ""; if ($linesref) { my @lines = @{$linesref}; $installedversion = $lines[0]; } $installedversion =~ s/^\s*//sg; $installedversion =~ s/\s*$//sg; if (!$HeaderDoc::local_tests) { if ($installedversion ne $HeaderDoc::testsuite_version) { print STDERR "Test suite not up-to-date. Please install the test suite that matches this\n". "version of HeaderDoc by downloading the source tarball from opensource.apple.com\n". "and running 'make realinstall' or 'make installsource'.\n"; $HeaderDoc::exitstatus = 1; return (0, 1); } } my %config = ( cCompiler => "/usr/bin/cc" ); my $localConfigFileName = "headerDoc2HTML.config"; my $preferencesConfigFileName = "com.apple.headerDoc2HTML.config"; my $CWD = cwd(); my @configFiles = ($devtoolsPreferencesPath.$pathSeparator.$preferencesConfigFileName, $systemPreferencesPath.$pathSeparator.$preferencesConfigFileName, $usersPreferencesPath.$pathSeparator.$preferencesConfigFileName, $Bin.$pathSeparator.$localConfigFileName, $CWD.$pathSeparator.$localConfigFileName); %config = &updateHashFromConfigFiles(\%config,\@configFiles); $HeaderDoc::c_compiler = $config{cCompiler}; print STDERR "Using C compiler: ".$HeaderDoc::c_compiler."\n"; if ($#args == -1) { my ($pyok, $pybad) = HeaderDoc::PythonParse::runPythonSpaceTests(); my ($macrook, $macrobad) = HeaderDoc::MacroFilter::run_macro_filter_tests(); my ($resolvelinksok, $resolvelinksbad) = runResolveLinksTests(); $ok_count += ($pyok + $macrook + $resolvelinksok); $fail_count += ($pybad + $macrobad + $resolvelinksbad); my $testdir = $HeaderDoc::testdir; print "-= Running parser tests =-\n\n"; # @testlist = <$testdir/parser_tests/*.test>; opendir(DIR, "$testdir/parser_tests"); @testlist = grep(/\.test$/,readdir(DIR)); closedir(DIR); map(s/^/$testdir\/parser_tests\//, @testlist); my $dump; if ($mode eq "dump" || $mode eq "dump_parser") { $dump = 1; } else { $dump = 0; } my ($newok, $newfail) = runtestlist(\@testlist, $dump, $update, $force); $ok_count += $newok; $fail_count += $newfail; print "-= Running C preprocessor tests =-\n\n"; # @testlist = <$testdir/c_preprocessor_tests/*.test>; opendir(DIR, "$testdir/c_preprocessor_tests"); @testlist = grep(/\.test$/,readdir(DIR)); closedir(DIR); map(s/^/$testdir\/c_preprocessor_tests\//, @testlist); if ($mode eq "dump" || $mode eq "dump_cpp") { $dump = 1; } else { $dump = 0; } ($newok, $newfail) = runtestlist(\@testlist, $dump, $update, $force); $ok_count += $newok; $fail_count += $newfail; } else { my $dump; if ($mode eq "dump") { $dump = 1; } else { $dump = 0; } my ($newok, $newfail) = runtestlist($argref, $dump, $update, $force); $ok_count += $newok; $fail_count += $newfail; } print "\n\n-= SUMMARY =-\n\n"; print "Tests passed: $ok_count\n"; print "Tests failed: $fail_count\n"; print "Percent passed: "; if ($fail_count != 0) { print "\e[31m"; } else { print "\e[32m"; } if ($ok_count || $fail_count) { print "".(($ok_count / ($fail_count + $ok_count)) * 100)."\%\n"; } else { print "NaN\n"; } print "\e[39m\n"; if ($fail_count) { $HeaderDoc::exitstatus = 1; } } # /*! # @abstract # Runs multiple tests using the test framework. # @discussion See # {@linkdoc //apple_ref/doc/uid/TP40001215 HeaderDoc User Guide} # for info about running tests. # */ sub runtestlist { my $testlistref = shift; my @testlist = @{$testlistref}; my $dump = shift; my $update = shift; my $force = shift; my $ok_count = 0; my $fail_count = 0; my @ignore_re = (); foreach my $filename (sort {uc($a) cmp uc($b)} @testlist) { if ($filename !~ /\.test$/) { print STDERR "$filename does not appear to be a test. Skipping.\n"; next; } my $test = HeaderDoc::Test->new(); $test->readFromFile($filename); my $plist = $filename; $plist =~ s/\.test$/\.plist/g; if (! -f $plist) { $test->writeToPlist($filename); } print "Test \"".$test->{NAME}."\": "; my $coretestfail = $test->runTest(\@ignore_re); if ($coretestfail) { die("\nTest suite aborted. Utilities tests failed.\n"); } if ($dump) { print "RESULTS DUMP:\n".$test->{RESULT}."\n"; } if ((($test->{RESULT} ne $test->{EXPECTED_RESULT}) || ($test->{RESULT_ALLDECS} ne $test->{EXPECTED_RESULT_ALLDECS})) && ($test->{FILTERED_RESULT} eq $test->{EXPECTED_FILTERED_RESULT}) && ($test->{FILTERED_RESULT_ALLDECS} eq $test->{EXPECTED_FILTERED_RESULT_ALLDECS})) { # Expected change caught by the filter. print STDERR "Passed after filtering. Updating test automatically.\n"; $test->{EXPECTED_RESULT} = $test->{RESULT}; $test->{EXPECTED_RESULT_ALLDECS} = $test->{RESULT_ALLDECS}; $test->writeToFile($filename); $test->writeToPlist($filename); $ok_count++; } elsif (($test->{FILTERED_RESULT} eq $test->{EXPECTED_FILTERED_RESULT}) && ($test->{FILTERED_RESULT_ALLDECS} eq $test->{EXPECTED_FILTERED_RESULT_ALLDECS})) { print "\e[32mOK\e[39m\n"; # if ($dump) { # $test->showresults(); # $test->dbprint(); # } $ok_count++; if ($force) { $test->writeToFile($filename); $test->writeToPlist($filename); } # $test->showresults(); } else { my $adonly = 0; if ($test->{FILTERED_RESULT} eq $test->{EXPECTED_FILTERED_RESULT}) { print "\e[31mFAILED (ALLDECS ONLY)\e[39m\n"; $adonly = 1; if ($debugging || 1) { if ($test->{RESULT_ALLDECS} eq $test->{EXPECTED_RESULT}) { print STDERR "Results same as with alldecs off\n"; } else { print STDERR "\@\@\@ ALLDECS RESULT:\@\@\@\n".$test->{RESULT_ALLDECS}."\n\n\@\@\@EXPECTED NON-ALLDECS RESULT:\@\@\@\n".$test->{EXPECTED_RESULT}."\n\n\@\@\@END OF RESULTS\@\@\@\n\n"; } } } else { print "\e[31mFAILED\e[39m\n"; } if ($dump || $update) { $test->showresults(); } if ($update) { my $continue_update = 1; while ($continue_update) { print "If these changes are expected, please type 'confirm' now.\n"; print "For more information, type 'more' now.\n"; print "To run 'diff' on the named objects, type 'less' now.\n"; print "To skip, type 'skip' now.\n"; print "To ignore a regexp, type 'ignore ' now.\n"; $/ = "\n"; my $temp = ; if ($temp =~ /^\s*less\s*$/) { my $addata = ""; my $adexpdata = ""; print "\nTest \"".$test->{NAME}."\":\n"; if ($adonly) { print "\e[31m"; print STDERR "******** DUMPING ALLDECS RESULT ********"; print "\e[39m\n"; $adexpdata = $test->{EXPECTED_RESULT}; $addata = $test->{RESULT}; $test->{EXPECTED_RESULT} = $test->{EXPECTED_RESULT_ALLDECS}; $test->{RESULT} = $test->{RESULT_ALLDECS}; } $test->showresults(-1); if ($adonly) { $test->{EXPECTED_RESULT} = $adexpdata; $test->{RESULT} = $addata; } } elsif ($temp =~ /^\s*more\s*$/) { print "\nTest \"".$test->{NAME}."\":\n"; $test->showresults(1); $test->dbprint(); } elsif ($temp =~ /^\s*confirm\s*$/) { $test->{EXPECTED_RESULT} = $test->{RESULT}; $test->{EXPECTED_RESULT_ALLDECS} = $test->{RESULT_ALLDECS}; $test->writeToFile($filename); $test->writeToPlist($filename); $ok_count++; $continue_update = 0; } elsif ($temp =~ /^\s*skip\s*$/) { $fail_count++; $continue_update = 0; } elsif ($temp =~ s/^\s*ignore\s*//s) { $temp =~ s/[\n\r]*$//s; push(@ignore_re, $temp); # FIXME DAG: Make this work. (Requires rolling back # the apple_ref lookups and stuff.) # my $coretestfail = $test->runTest(\@ignore_re); if (($test->{FILTERED_RESULT} eq $test->{EXPECTED_FILTERED_RESULT}) && ($test->{FILTERED_RESULT_ALLDECS} eq $test->{EXPECTED_FILTERED_RESULT_ALLDECS})) { print STDERR "It passes now.\n"; $test->{EXPECTED_RESULT} = $test->{RESULT}; $test->{EXPECTED_RESULT_ALLDECS} = $test->{RESULT_ALLDECS}; $test->writeToFile($filename); $test->writeToPlist($filename); $ok_count++; $continue_update = 0; } else { print STDERR "Still failed. (At some point, re-running a test will work, but not yet.)\n"; # print STDERR "EXPECTED: \n"; # print STDERR $test->{EXPECTED_FILTERED_RESULT}."\n"; # print STDERR "GOT: \n"; # print STDERR $test->{FILTERED_RESULT}."\n"; } } else { $temp =~ s/\n$//s; print "Unknown response \"$temp\"\n"; } } } else { $fail_count++; } } } return ($ok_count, $fail_count); } # /*! # @abstract # Sets the TOC format value based on its name. # @discussion # # Returns values via $HeaderDoc::newTOC. Possible values are: # # 0 Old TOC style (ugly). # 1 Apple old-style TOC. # 2 Apple interim TOC. # 3 Apple new-style TOC. # 4 Unused. # 5 Public new-style TOC. # */ sub setTOCFormat { my $format = shift; $format =~ s/\s*//sg; if ($HeaderDoc::flagDashF) { # Don't change anything; format specified on command line. return; } if ($format =~ /default/i || $format =~ /div/i) { $HeaderDoc::newTOC = 5; # print STDERR "DEFAULT\n"; } elsif ($format =~ /iframes/i) { $HeaderDoc::newTOC = 0; $HeaderDoc::use_iframes = 1; # print STDERR "IFRAMES\n"; } elsif ($format =~ /frames/i) { $HeaderDoc::newTOC = 0; $HeaderDoc::use_iframes = 0; # print STDERR "OLD-STYLE FRAMES\n"; } else { print STDERR "Unknown TOC format \"$format\"\n"; } if ($HeaderDoc::use_iframes || $HeaderDoc::newTOC) { $HeaderDoc::ClassAsComposite = 1; } elsif ($HeaderDoc::flagDashC == -1) { $HeaderDoc::ClassAsComposite = 0; } else { $HeaderDoc::ClassAsComposite = 1; } # $HeaderDoc::newTOC = 0; } # /*! # @abstract # Runs the resolveLinks test suite. # */ sub runResolveLinksTests { my $rltestdir = $HeaderDoc::testdir."/resolvelinks"; my $curdir = cwd(); my $okcount = 0; my $failcount = 0; print "-= Running resolveLinks tests =-\n\n"; chdir($rltestdir) || die("Could not change directories into resolveLinks test directory.\n");; # print "RLT: $rltestdir\n"; open(TESTRESULTS, "./runtests.sh --fromperl|") || die("Could not run resolveLinks tests.\n"); my $temp = $/; $/ = "\n"; while () { my $line = $_; if ($line =~ s/^PERLSTAT RESOLVELINKS:\s+//) { my @parts = split(/\s/, $line); $okcount = $parts[0]; $failcount = $parts[1]; } else { print $line; } } $/ = $temp; close(TESTRESULTS); chdir($curdir); print "\n"; return ($okcount, $failcount); } # /*! # @abstract Prints the CPP hashes for debugging purposes. # */ sub printCPPHashes { my $tokenhashref = shift; my $arghashref = shift; my %tokenhash = %{$tokenhashref}; my %arghash = %{$arghashref}; print STDERR "DUMPING CPP HASHES\n"; foreach my $token (keys %tokenhash) { print STDERR "CPP TOKEN $token -> ".$tokenhash{$token}."\n"; print STDERR " ARGS: ".$arghash{$token}."\n"; } } ################################################################################ # Version Notes # 1.61 (02/24/2000) Fixed getLineArrays to respect paragraph breaks in comments that # have an asterisk before each line. ################################################################################