about summary refs log tree commit diff
path: root/docs/doctool/Modules/NaturalDocs/Topics.pm
diff options
context:
space:
mode:
Diffstat (limited to 'docs/doctool/Modules/NaturalDocs/Topics.pm')
-rw-r--r--docs/doctool/Modules/NaturalDocs/Topics.pm1351
1 files changed, 0 insertions, 1351 deletions
diff --git a/docs/doctool/Modules/NaturalDocs/Topics.pm b/docs/doctool/Modules/NaturalDocs/Topics.pm
deleted file mode 100644
index 24418bdd..00000000
--- a/docs/doctool/Modules/NaturalDocs/Topics.pm
+++ /dev/null
@@ -1,1351 +0,0 @@
-###############################################################################
-#
-#   Package: NaturalDocs::Topics
-#
-###############################################################################
-#
-#   The topic constants and functions to convert them to and from strings used throughout the script.  All constants are exported
-#   by default.
-#
-###############################################################################
-
-# This file is part of Natural Docs, which is Copyright (C) 2003-2005 Greg Valure
-# Natural Docs is licensed under the GPL
-
-use Text::Wrap ( );
-use Tie::RefHash ( );
-
-use strict;
-use integer;
-
-use NaturalDocs::Topics::Type;
-
-package NaturalDocs::Topics;
-
-use base 'Exporter';
-our @EXPORT = ( 'TOPIC_GENERAL', 'TOPIC_GENERIC', 'TOPIC_GROUP', 'TOPIC_CLASS', 'TOPIC_FILE', 'TOPIC_FUNCTION',
-                          'TOPIC_VARIABLE', 'TOPIC_PROPERTY', 'TOPIC_TYPE', 'TOPIC_ENUMERATION', 'TOPIC_CONSTANT',
-                          'TOPIC_INTERFACE', 'TOPIC_EVENT', 'TOPIC_DELEGATE', 'TOPIC_SECTION' );
-
-
-
-###############################################################################
-# Group: Types
-
-
-#
-#   Type: TopicType
-#
-#   A string representing a topic type as defined in <Topics.txt>.  It's format should be treated as opaque; use <MakeTopicType()>
-#   to get them from topic names.  However, they can be compared for equality with string functions.
-#
-
-
-#
-#   Constants: Default TopicTypes
-#
-#   Exported constants of the default <TopicTypes>, so you don't have to go through <TypeFromName()> every time.
-#
-#   TOPIC_GENERAL - The general <TopicType>, which has the special meaning of none in particular.
-#   TOPIC_GENERIC - Generic <TopicType>.
-#   TOPIC_GROUP - Group <TopicType>.
-#   TOPIC_CLASS - Class <TopicType>.
-#   TOPIC_INTERFACE - Interface <TopicType>.
-#   TOPIC_FILE - File <TopicType>.
-#   TOPIC_SECTION - Section <TopicType>.
-#   TOPIC_FUNCTION - Function <TopicType>.
-#   TOPIC_VARIABLE - Variable <TopicType>.
-#   TOPIC_PROPERTY - Property <TopicType>.
-#   TOPIC_TYPE - Type <TopicType>.
-#   TOPIC_CONSTANT - Constant <TopicType>.
-#   TOPIC_ENUMERATION - Enum <TopicType>.
-#   TOPIC_DELEGATE - Delegate <TopicType>.
-#   TOPIC_EVENT - Event <TopicType>.
-#
-use constant TOPIC_GENERAL => 'general';
-use constant TOPIC_GENERIC => 'generic';
-use constant TOPIC_GROUP => 'group';
-use constant TOPIC_CLASS => 'class';
-use constant TOPIC_INTERFACE => 'interface';
-use constant TOPIC_FILE => 'file';
-use constant TOPIC_SECTION => 'section';
-use constant TOPIC_FUNCTION => 'function';
-use constant TOPIC_VARIABLE => 'variable';
-use constant TOPIC_PROPERTY => 'property';
-use constant TOPIC_TYPE => 'type';
-use constant TOPIC_CONSTANT => 'constant';
-use constant TOPIC_ENUMERATION => 'enumeration';
-use constant TOPIC_DELEGATE => 'delegate';
-use constant TOPIC_EVENT => 'event';
-# Dependency: The values of these constants must match what is generated by MakeTopicType().
-# Dependency: These types must be added to requiredTypeNames so that they always exist.
-
-
-
-
-###############################################################################
-# Group: Variables
-
-
-#
-#   handle: FH_TOPICS
-#
-#   The file handle used when writing to <Topics.txt>.
-#
-
-
-#
-#   hash: types
-#
-#   A hashref that maps <TopicTypes> to <NaturalDocs::Topics::Type>s.
-#
-my %types;
-
-
-#
-#   hash: names
-#
-#   A hashref that maps various forms of the all-lowercase type names to <TopicTypes>.  All are in the same hash because
-#   two names that reduce to the same thing it would cause big problems, and we need to catch that.  Keys include
-#
-#   - Topic names
-#   - Plural topic names
-#   - Alphanumeric-only topic names
-#   - Alphanumeric-only plural topic names
-#
-my %names;
-
-
-#
-#   hash: keywords
-#
-#   A hashref that maps all-lowercase keywords to their <TopicTypes>.  Must not have any of the same keys as
-#   <pluralKeywords>.
-#
-my %keywords;
-
-
-#
-#   hash: pluralKeywords
-#
-#   A hashref that maps all-lowercase plural keywords to their <TopicTypes>.  Must not have any of the same keys as
-#   <keywords>.
-#
-my %pluralKeywords;
-
-
-#
-#   hash: indexable
-#
-#   An existence hash of all the indexable <TopicTypes>.
-#
-my %indexable;
-
-
-#
-#   array: requiredTypeNames
-#
-#   An array of the <TopicType> names which are required to be defined in the main file.  Are in the order they should appear
-#   when reformatting.
-#
-my @requiredTypeNames = ( 'Generic', 'Class', 'Interface', 'Section', 'File', 'Group', 'Function', 'Variable', 'Property', 'Type',
-                                           'Constant', 'Enumeration', 'Event', 'Delegate' );
-
-
-#
-#   array: legacyTypes
-#
-#   An array that converts the legacy topic types, which were numeric constants prior to 1.3, to the current <TopicTypes>.
-#   The legacy types are used as an index into the array.  Note that this does not support list type values.
-#
-my @legacyTypes = ( TOPIC_GENERAL, TOPIC_CLASS, TOPIC_SECTION, TOPIC_FILE, TOPIC_GROUP, TOPIC_FUNCTION,
-                                TOPIC_VARIABLE, TOPIC_GENERIC, TOPIC_TYPE, TOPIC_CONSTANT, TOPIC_PROPERTY );
-
-
-#
-#   array: mainTopicNames
-#
-#   An array of the <TopicType> names that are defined in the main <Topics.txt>.
-#
-my @mainTopicNames;
-
-
-
-###############################################################################
-# Group: Files
-
-
-#
-#   File: Topics.txt
-#
-#   The configuration file that defines or overrides the topic definitions for Natural Docs.  One version sits in Natural Docs'
-#   configuration directory, and another can be in a project directory to add to or override them.
-#
-#   > # [comments]
-#
-#   Everything after a # symbol is ignored.
-#
-#   Except when specifying topic names, everything below is case-insensitive.
-#
-#   > Format: [version]
-#
-#   Specifies the file format version of the file.
-#
-#
-#   Sections:
-#
-#       > Ignore[d] Keyword[s]: [keyword], [keyword] ...
-#       >    [keyword]
-#       >    [keyword], [keyword]
-#       >    ...
-#
-#       Ignores the keywords so that they're not recognized as Natural Docs topics anymore.  Can be specified as a list on the same
-#       line and/or following like a normal Keywords section.
-#
-#       > Topic Type: [name]
-#       > Alter Topic Type: [name]
-#
-#       Creates a new topic type or alters an existing one.  The name can only contain <CFChars> and isn't case sensitive, although
-#       the original case is remembered for presentation.
-#
-#       The name General is reserved.  There are a number of default types that must be defined in the main file as well, but those
-#       are governed by <NaturalDocs::Topics> and are not included here.  The default types can have their keywords or behaviors
-#       changed, though, either by editing the default file or by overriding them in the user file.
-#
-#       Enumeration is a special type.  It is indexed with Types and its definition list members are listed with Constants according
-#       to the rules in <Languages.txt>.
-#
-#
-#   Topic Type Sections:
-#
-#       > Plural: [name]
-#
-#       Specifies the plural name of the topic type.  Defaults to the singular name.  Has the same restrictions as the topic type
-#       name.
-#
-#       > Index: [yes|no]
-#
-#       Whether the topic type gets an index.  Defaults to yes.
-#
-#       > Scope: [normal|start|end|always global]
-#
-#       How the topic affects scope.  Defaults to normal.
-#
-#       normal - The topic stays within the current scope.
-#       start - The topic starts a new scope for all the topics beneath it, like class topics.
-#       end - The topic resets the scope back to global for all the topics beneath it, like section topics.
-#       always global - The topic is defined as a global symbol, but does not change the scope for any other topics.
-#
-#       > Class Hierarchy: [yes|no]
-#
-#       Whether the topic is part of the class hierarchy.  Defaults to no.
-#
-#       > Variable Type: [yes|no]
-#
-#       Whether the topic can be used as a variable type.  Defaults to no.
-#
-#       > Page Title if First: [yes|no]
-#
-#       Whether the title of this topic becomes the page title if it is the first topic in a file.  Defaults to no.
-#
-#       > Break Lists: [yes|no]
-#
-#       Whether list topics should be broken into individual topics in the output.  Defaults to no.
-#
-#       > Can Group With: [topic type], [topic type], ...
-#
-#       The list of <TopicTypes> the topic can possibly be grouped with.
-#
-#       > [Add] Keyword[s]:
-#       >    [keyword]
-#       >    [keyword], [plural keyword]
-#       >    ...
-#
-#       A list of the topic type's keywords.  Each line after the heading is the keyword and optionally its plural form.  This continues
-#       until the next line in "keyword: value" format.  "Add" isn't required.
-#
-#       - Keywords can only have letters and numbers.  No punctuation or spaces are allowed.
-#       - Keywords are not case sensitive.
-#       - Subsequent keyword sections add to the list.  They don't replace it.
-#       - Keywords can be redefined by other keyword sections.
-#
-#
-#   Revisions:
-#
-#       1.4:
-#
-#           Added Variable Type.
-#
-#       1.3:
-#
-#           The initial version of this file.
-#
-
-
-###############################################################################
-# Group: File Functions
-
-
-#
-#   Function: Load
-#
-#   Loads both the master and the project version of <Topics.txt>.
-#
-sub Load
-    {
-    my $self = shift;
-
-    # Add the special General topic type.
-
-    $types{::TOPIC_GENERAL()} = NaturalDocs::Topics::Type->New('General', 'General', 1, ::SCOPE_NORMAL(), undef);
-    $names{'general'} = ::TOPIC_GENERAL();
-    $indexable{::TOPIC_GENERAL()} = 1;
-    # There are no keywords for the general topic.
-
-
-    $self->LoadFile(1);  # Main
-
-    # Dependency: All the default topic types must be checked for existence.
-
-    # Check to see if the required types are defined.
-    foreach my $name (@requiredTypeNames)
-        {
-        if (!exists $names{lc($name)})
-            {  NaturalDocs::ConfigFile->AddError('The ' . $name . ' topic type must be defined in the main topics file.');  };
-        };
-
-    my $errorCount = NaturalDocs::ConfigFile->ErrorCount();
-
-    if ($errorCount)
-        {
-        NaturalDocs::ConfigFile->PrintErrorsAndAnnotateFile();
-        NaturalDocs::Error->SoftDeath('There ' . ($errorCount == 1 ? 'is an error' : 'are ' . $errorCount . ' errors')
-                                                    . ' in ' . NaturalDocs::Project->MainTopicsFile());
-        }
-
-
-    $self->LoadFile();  # User
-
-    $errorCount = NaturalDocs::ConfigFile->ErrorCount();
-
-    if ($errorCount)
-        {
-        NaturalDocs::ConfigFile->PrintErrorsAndAnnotateFile();
-        NaturalDocs::Error->SoftDeath('There ' . ($errorCount == 1 ? 'is an error' : 'are ' . $errorCount . ' errors')
-                                                    . ' in ' . NaturalDocs::Project->UserTopicsFile());
-        }
-    };
-
-
-#
-#   Function: LoadFile
-#
-#   Loads a particular version of <Topics.txt>.
-#
-#   Parameters:
-#
-#       isMain - Whether the file is the main file or not.
-#
-sub LoadFile #(isMain)
-    {
-    my ($self, $isMain) = @_;
-
-    my ($file, $status);
-
-    if ($isMain)
-        {
-        $file = NaturalDocs::Project->MainTopicsFile();
-        $status = NaturalDocs::Project->MainTopicsFileStatus();
-        }
-    else
-        {
-        $file = NaturalDocs::Project->UserTopicsFile();
-        $status = NaturalDocs::Project->UserTopicsFileStatus();
-        };
-
-    my $version;
-
-    if ($version = NaturalDocs::ConfigFile->Open($file))
-        {
-        # The format hasn't changed since the file was introduced.
-
-        if ($status == ::FILE_CHANGED())
-            {  NaturalDocs::Project->ReparseEverything();  };
-
-        my ($topicTypeKeyword, $topicTypeName, $topicType, $topicTypeObject, $inKeywords, $inIgnoredKeywords);
-
-        # Keys are topic type objects, values are unparsed strings.
-        my %canGroupWith;
-        tie %canGroupWith, 'Tie::RefHash';
-
-        while (my ($keyword, $value) = NaturalDocs::ConfigFile->GetLine())
-            {
-            if ($keyword)
-                {
-                $inKeywords = 0;
-                $inIgnoredKeywords = 0;
-                };
-
-            if ($keyword eq 'topic type')
-                {
-                $topicTypeKeyword = $keyword;
-                $topicTypeName = $value;
-
-                # Resolve conflicts and create the type if necessary.
-
-                $topicType = $self->MakeTopicType($topicTypeName);
-                my $lcTopicTypeName = lc($topicTypeName);
-
-                my $lcTopicTypeAName = $lcTopicTypeName;
-                $lcTopicTypeAName =~ tr/a-z0-9//cd;
-
-                if (!NaturalDocs::ConfigFile->HasOnlyCFChars($topicTypeName))
-                    {
-                    NaturalDocs::ConfigFile->AddError('Topic names can only have ' . NaturalDocs::ConfigFile->CFCharNames() . '.');
-                    }
-                elsif ($topicType eq ::TOPIC_GENERAL())
-                    {
-                    NaturalDocs::ConfigFile->AddError('You cannot define a General topic type.');
-                    }
-                elsif (defined $types{$topicType} || defined $names{$lcTopicTypeName} || defined $names{$lcTopicTypeAName})
-                    {
-                    NaturalDocs::ConfigFile->AddError('Topic type ' . $topicTypeName . ' is already defined or its name is too '
-                                                                     . 'similar to an existing name.  Use Alter Topic Type if you meant to override '
-                                                                     . 'its settings.');
-                    }
-                else
-                    {
-                    $topicTypeObject = NaturalDocs::Topics::Type->New($topicTypeName, $topicTypeName, 1, ::SCOPE_NORMAL(),
-                                                                                                  0, 0);
-
-                    $types{$topicType} = $topicTypeObject;
-                    $names{$lcTopicTypeName} = $topicType;
-                    $names{$lcTopicTypeAName} = $topicType;
-
-                    $indexable{$topicType} = 1;
-
-                    if ($isMain)
-                        {  push @mainTopicNames, $topicTypeName;  };
-                    };
-                }
-
-            elsif ($keyword eq 'alter topic type')
-                {
-                $topicTypeKeyword = $keyword;
-                $topicTypeName = $value;
-
-                # Resolve conflicts and create the type if necessary.
-
-                $topicType = $names{lc($topicTypeName)};
-
-                if (!defined $topicType)
-                    {  NaturalDocs::ConfigFile->AddError('Topic type ' . $topicTypeName . ' doesn\'t exist.');  }
-                elsif ($topicType eq ::TOPIC_GENERAL())
-                    {  NaturalDocs::ConfigFile->AddError('You cannot alter the General topic type.');  }
-                else
-                    {
-                    $topicTypeObject = $types{$topicType};
-                    };
-                }
-
-            elsif ($keyword =~ /^ignored? keywords?$/)
-                {
-                $inIgnoredKeywords = 1;
-
-                my @ignoredKeywords = split(/ ?, ?/, lc($value));
-
-                foreach my $ignoredKeyword (@ignoredKeywords)
-                    {
-                    delete $keywords{$ignoredKeyword};
-                    delete $pluralKeywords{$ignoredKeyword};
-                    };
-                }
-
-            # We continue even if there are errors in the topic type line so that we can find any other errors in the file as well.  We'd
-            # rather them all show up at once instead of them showing up one at a time between Natural Docs runs.  So we just ignore
-            # the settings if $topicTypeObject is undef.
-
-
-            elsif ($keyword eq 'plural')
-                {
-                my $pluralName = $value;
-                my $lcPluralName = lc($pluralName);
-
-                my $lcPluralAName = $lcPluralName;
-                $lcPluralAName =~ tr/a-zA-Z0-9//cd;
-
-                if (!NaturalDocs::ConfigFile->HasOnlyCFChars($pluralName))
-                    {
-                    NaturalDocs::ConfigFile->AddError('Plural names can only have '
-                                                                     . NaturalDocs::ConfigFile->CFCharNames() . '.');
-                    }
-                elsif ($lcPluralAName eq 'general')
-                    {
-                    NaturalDocs::ConfigFile->AddError('You cannot use General as a plural name for ' . $topicTypeName . '.');
-                    }
-                elsif ( (defined $names{$lcPluralName} && $names{$lcPluralName} ne $topicType) ||
-                         (defined $names{$lcPluralAName} && $names{$lcPluralAName} ne $topicType) )
-                    {
-                    NaturalDocs::ConfigFile->AddError($topicTypeName . "'s plural name, " . $pluralName
-                                                                     . ', is already defined or is too similar to an existing name.');
-                    }
-
-                elsif (defined $topicTypeObject)
-                    {
-                    $topicTypeObject->SetPluralName($pluralName);
-
-                    $names{$lcPluralName} = $topicType;
-                    $names{$lcPluralAName} = $topicType;
-                    };
-                }
-
-            elsif ($keyword eq 'index')
-                {
-                $value = lc($value);
-
-                if ($value eq 'yes')
-                    {
-                    if (defined $topicTypeObject)
-                        {
-                        $topicTypeObject->SetIndex(1);
-                        $indexable{$topicType} = 1;
-                        };
-                    }
-                elsif ($value eq 'no')
-                    {
-                    if (defined $topicTypeObject)
-                        {
-                        $topicTypeObject->SetIndex(0);
-                        delete $indexable{$topicType};
-                        };
-                    }
-                else
-                    {
-                    NaturalDocs::ConfigFile->AddError('Index lines can only be "yes" or "no".');
-                    };
-                }
-
-            elsif ($keyword eq 'class hierarchy')
-                {
-                $value = lc($value);
-
-                if ($value eq 'yes')
-                    {
-                    if (defined $topicTypeObject)
-                        {  $topicTypeObject->SetClassHierarchy(1);  };
-                    }
-                elsif ($value eq 'no')
-                    {
-                    if (defined $topicTypeObject)
-                        {  $topicTypeObject->SetClassHierarchy(0);  };
-                    }
-                else
-                    {
-                    NaturalDocs::ConfigFile->AddError('Class Hierarchy lines can only be "yes" or "no".');
-                    };
-                }
-
-            elsif ($keyword eq 'variable type')
-                {
-                $value = lc($value);
-
-                if ($value eq 'yes')
-                    {
-                    if (defined $topicTypeObject)
-                        {  $topicTypeObject->SetVariableType(1);  };
-                    }
-                elsif ($value eq 'no')
-                    {
-                    if (defined $topicTypeObject)
-                        {  $topicTypeObject->SetVariableType(0);  };
-                    }
-                else
-                    {
-                    NaturalDocs::ConfigFile->AddError('Variable Type lines can only be "yes" or "no".');
-                    };
-                }
-
-            elsif ($keyword eq 'scope')
-                {
-                $value = lc($value);
-
-                if ($value eq 'normal')
-                    {
-                    if (defined $topicTypeObject)
-                        {  $topicTypeObject->SetScope(::SCOPE_NORMAL());  };
-                    }
-                elsif ($value eq 'start')
-                    {
-                    if (defined $topicTypeObject)
-                        {  $topicTypeObject->SetScope(::SCOPE_START());  };
-                    }
-                elsif ($value eq 'end')
-                    {
-                    if (defined $topicTypeObject)
-                        {  $topicTypeObject->SetScope(::SCOPE_END());  };
-                    }
-                elsif ($value eq 'always global')
-                    {
-                    if (defined $topicTypeObject)
-                        {  $topicTypeObject->SetScope(::SCOPE_ALWAYS_GLOBAL());  };
-                    }
-                else
-                    {
-                    NaturalDocs::ConfigFile->AddError('Scope lines can only be "normal", "start", "end", or "always global".');
-                    };
-                }
-
-            elsif ($keyword eq 'page title if first')
-                {
-                $value = lc($value);
-
-                if ($value eq 'yes')
-                    {
-                    if (defined $topicTypeObject)
-                        {  $topicTypeObject->SetPageTitleIfFirst(1);  };
-                    }
-                elsif ($value eq 'no')
-                    {
-                    if (defined $topicTypeObject)
-                        {  $topicTypeObject->SetPageTitleIfFirst(undef);  };
-                    }
-                else
-                    {
-                    NaturalDocs::ConfigFile->AddError('Page Title if First lines can only be "yes" or "no".');
-                    };
-                }
-
-            elsif ($keyword eq 'break lists')
-                {
-                $value = lc($value);
-
-                if ($value eq 'yes')
-                    {
-                    if (defined $topicTypeObject)
-                        {  $topicTypeObject->SetBreakLists(1);  };
-                    }
-                elsif ($value eq 'no')
-                    {
-                    if (defined $topicTypeObject)
-                        {  $topicTypeObject->SetBreakLists(undef);  };
-                    }
-                else
-                    {
-                    NaturalDocs::ConfigFile->AddError('Break Lists lines can only be "yes" or "no".');
-                    };
-                }
-
-            elsif ($keyword eq 'can group with')
-                {
-                if (defined $topicTypeObject)
-                    {  $canGroupWith{$topicTypeObject} = lc($value);  };
-                }
-
-            elsif ($keyword =~ /^(?:add )?keywords?$/)
-                {
-                $inKeywords = 1;
-                }
-
-            elsif (defined $keyword)
-                {  NaturalDocs::ConfigFile->AddError($keyword . ' is not a valid keyword.');  }
-
-            elsif (!$inKeywords && !$inIgnoredKeywords)
-                {
-                NaturalDocs::ConfigFile->AddError('All lines in ' . $topicTypeKeyword . ' sections must begin with a keyword.');
-                }
-
-            else # No keyword but in keyword section.
-                {
-                $value = lc($value);
-
-                if ($value =~ /^([a-z0-9 ]*[a-z0-9]) ?, ?([a-z0-9 ]+)$/)
-                    {
-                    my ($singular, $plural) = ($1, $2);
-
-                    if ($inIgnoredKeywords)
-                        {
-                        delete $keywords{$singular};
-                        delete $keywords{$plural};
-                        delete $pluralKeywords{$singular};
-                        delete $pluralKeywords{$plural};
-                        }
-                    elsif (defined $topicTypeObject)
-                        {
-                        $keywords{$singular} = $topicType;
-                        delete $pluralKeywords{$singular};
-
-                        $pluralKeywords{$plural} = $topicType;
-                        delete $keywords{$plural};
-                        };
-                    }
-                elsif ($value =~ /^[a-z0-9 ]+$/)
-                    {
-                    if ($inIgnoredKeywords)
-                        {
-                        delete $keywords{$value};
-                        delete $pluralKeywords{$value};
-                        }
-                    elsif (defined $topicType)
-                        {
-                        $keywords{$value} = $topicType;
-                        delete $pluralKeywords{$value};
-                        };
-                    }
-                else
-                    {
-                    NaturalDocs::ConfigFile->AddError('Keywords can only have letters, numbers, and spaces.  '
-                                                                     . 'Plurals must be separated by a comma.');
-                    };
-                };
-            };
-
-        NaturalDocs::ConfigFile->Close();
-
-
-        # Parse out the Can Group With lines now that everything's defined.
-
-        while (my ($typeObject, $value) = each %canGroupWith)
-            {
-            my @values = split(/ ?, ?/, $value);
-            my @types;
-
-            foreach my $value (@values)
-                {
-                # We're just going to ignore invalid items.
-                if (exists $names{$value})
-                    {  push @types, $names{$value};  };
-                };
-
-            if (scalar @types)
-                {  $typeObject->SetCanGroupWith(\@types);  };
-            };
-        }
-
-    else # couldn't open file
-        {
-        if ($isMain)
-            {  die "Couldn't open topics file " . $file . "\n";  }
-        else
-            {  NaturalDocs::Project->ReparseEverything();  };
-        };
-    };
-
-
-#
-#   Function: Save
-#
-#   Saves the main and user versions of <Topics.txt>.
-#
-sub Save
-    {
-    my $self = shift;
-
-    $self->SaveFile(1); # Main
-    $self->SaveFile(0); # User
-    };
-
-
-#
-#   Function: SaveFile
-#
-#   Saves a particular version of <Topics.txt>.
-#
-#   Parameters:
-#
-#       isMain - Whether the file is the main file or not.
-#
-sub SaveFile #(isMain)
-    {
-    my ($self, $isMain) = @_;
-
-    my $file;
-
-    if ($isMain)
-        {
-        if (NaturalDocs::Project->MainTopicsFileStatus() == ::FILE_SAME())
-            {  return;  };
-        $file = NaturalDocs::Project->MainTopicsFile();
-        }
-    else
-        {
-        # We have to check the main one two because this lists the topics defined in it.
-        if (NaturalDocs::Project->UserTopicsFileStatus() == ::FILE_SAME() &&
-            NaturalDocs::Project->MainTopicsFileStatus() == ::FILE_SAME())
-            {  return;  };
-        $file = NaturalDocs::Project->UserTopicsFile();
-        };
-
-
-    # Array of topic type names in the order they appear in the file.  If Alter Topic Type is used, the name will end with an asterisk.
-    my @topicTypeOrder;
-
-    # Keys are topic type names, values are property hashrefs.  Hashref keys are the property names, values the value.
-    # For keywords, the key is Keywords and the values are arrayrefs of singular and plural pairs.  If no plural is defined, the entry
-    # will be undef.
-    my %properties;
-
-    # List of ignored keywords specified as Ignore Keywords: [keyword], [keyword], ...
-    my @inlineIgnoredKeywords;
-
-    # List of ignored keywords specified in [keyword], [plural keyword] lines.  Done in pairs, like for regular keywords.
-    my @separateIgnoredKeywords;
-
-    my $inIgnoredKeywords;
-
-    if (NaturalDocs::ConfigFile->Open($file))
-        {
-        # We can assume the file is valid.
-
-        my ($keyword, $value, $topicTypeName);
-
-        while (($keyword, $value) = NaturalDocs::ConfigFile->GetLine())
-            {
-            $keyword = lc($keyword);
-
-            if ($keyword eq 'topic type' || $keyword eq 'alter topic type')
-                {
-                $topicTypeName = $types{ $names{lc($value)} }->Name();
-
-                if ($keyword eq 'alter topic type')
-                    {  $topicTypeName .= '*';  };
-
-                push @topicTypeOrder, $topicTypeName;
-
-                if (!exists $properties{$topicTypeName})
-                    {  $properties{$topicTypeName} = { 'keywords' => [ ] };  };
-                }
-
-            elsif ($keyword eq 'plural')
-                {
-                $properties{$topicTypeName}->{$keyword} = $value;
-                }
-
-            elsif ($keyword eq 'index' ||
-                    $keyword eq 'scope' ||
-                    $keyword eq 'page title if first' ||
-                    $keyword eq 'class hierarchy' ||
-                    $keyword eq 'variable type' ||
-                    $keyword eq 'break lists' ||
-                    $keyword eq 'can group with')
-                {
-                $properties{$topicTypeName}->{$keyword} = lc($value);
-                }
-
-            elsif ($keyword =~ /^(?:add )?keywords?$/)
-                {
-                $inIgnoredKeywords = 0;
-                }
-
-            elsif ($keyword =~ /^ignored? keywords?$/)
-                {
-                $inIgnoredKeywords = 1;
-                if ($value)
-                    {  push @inlineIgnoredKeywords, split(/ ?, ?/, $value);  };
-                }
-
-            elsif (!$keyword)
-                {
-                my ($singular, $plural) = split(/ ?, ?/, lc($value));
-
-                if ($inIgnoredKeywords)
-                    {  push @separateIgnoredKeywords, $singular, $plural;  }
-                else
-                    {  push @{$properties{$topicTypeName}->{'keywords'}}, $singular, $plural;  };
-                };
-            };
-
-        NaturalDocs::ConfigFile->Close();
-        };
-
-
-    if (!open(FH_TOPICS, '>' . $file))
-        {
-        # The main file may be on a shared volume or some other place the user doesn't have write access to.  Since this is only to
-        # reformat the file, we can ignore the failure.
-        if ($isMain)
-            {  return;  }
-        else
-            {  die "Couldn't save " . $file;  };
-        };
-
-    print FH_TOPICS 'Format: ' . NaturalDocs::Settings->TextAppVersion() . "\n\n";
-
-    # Remember the 80 character limit.
-
-    if ($isMain)
-        {
-        print FH_TOPICS
-        "# This is the main Natural Docs topics file.  If you change anything here, it\n"
-        . "# will apply to EVERY PROJECT you use Natural Docs on.  If you'd like to\n"
-        . "# change something for just one project, edit the Topics.txt in its project\n"
-        . "# directory instead.\n";
-        }
-    else
-        {
-        print FH_TOPICS
-        "# This is the Natural Docs topics file for this project.  If you change anything\n"
-        . "# here, it will apply to THIS PROJECT ONLY.  If you'd like to change something\n"
-        . "# for all your projects, edit the Topics.txt in Natural Docs' Config directory\n"
-        . "# instead.\n\n\n";
-
-        if (scalar @inlineIgnoredKeywords || scalar @separateIgnoredKeywords)
-            {
-            if (scalar @inlineIgnoredKeywords == 1 && !scalar @separateIgnoredKeywords)
-                {
-                print FH_TOPICS 'Ignore Keyword: ' . $inlineIgnoredKeywords[0] . "\n";
-                }
-            else
-                {
-                print FH_TOPICS
-                'Ignore Keywords: ' . join(', ', @inlineIgnoredKeywords) . "\n";
-
-                for (my $i = 0; $i < scalar @separateIgnoredKeywords; $i += 2)
-                    {
-                    print FH_TOPICS '   ' . $separateIgnoredKeywords[$i];
-
-                    if (defined $separateIgnoredKeywords[$i + 1])
-                        {  print FH_TOPICS ', ' . $separateIgnoredKeywords[$i + 1];  };
-
-                    print FH_TOPICS "\n";
-                    };
-                };
-            }
-        else
-            {
-            print FH_TOPICS
-            "# If you'd like to prevent keywords from being recognized by Natural Docs, you\n"
-            . "# can do it like this:\n"
-            . "# Ignore Keywords: [keyword], [keyword], ...\n"
-            . "#\n"
-            . "# Or you can use the list syntax like how they are defined:\n"
-            . "# Ignore Keywords:\n"
-            . "#    [keyword]\n"
-            . "#    [keyword], [plural keyword]\n"
-            . "#    ...\n";
-            };
-        };
-
-    print FH_TOPICS # [CFChars]
-    "\n\n"
-    . "#-------------------------------------------------------------------------------\n"
-    . "# SYNTAX:\n"
-    . "#\n";
-
-    if ($isMain)
-        {
-        print FH_TOPICS
-        "# Topic Type: [name]\n"
-        . "#    Creates a new topic type.  Each type gets its own index and behavior\n"
-        . "#    settings.  Its name can have letters, numbers, spaces, and these\n"
-        . "#    charaters: - / . '\n"
-        . "#\n"
-        . "#    The Enumeration type is special.  It's indexed with Types but its members\n"
-        . "#    are indexed with Constants according to the rules in Languages.txt.\n"
-        . "#\n"
-        }
-    else
-        {
-        print FH_TOPICS
-        "# Topic Type: [name]\n"
-        . "# Alter Topic Type: [name]\n"
-        . "#    Creates a new topic type or alters one from the main file.  Each type gets\n"
-        . "#    its own index and behavior settings.  Its name can have letters, numbers,\n"
-        . "#    spaces, and these charaters: - / . '\n"
-        . "#\n";
-        };
-
-    print FH_TOPICS
-    "# Plural: [name]\n"
-    . "#    Sets the plural name of the topic type, if different.\n"
-    . "#\n"
-    . "# Keywords:\n"
-    . "#    [keyword]\n"
-    . "#    [keyword], [plural keyword]\n"
-    . "#    ...\n";
-
-    if ($isMain)
-        {
-        print FH_TOPICS
-        "#    Defines a list of keywords for the topic type.  They may only contain\n"
-        . "#    letters, numbers, and spaces and are not case sensitive.  Plural keywords\n"
-        . "#    are used for list topics.\n";
-        }
-    else
-        {
-        print FH_TOPICS
-        "#    Defines or adds to the list of keywords for the topic type.  They may only\n"
-        . "#    contain letters, numbers, and spaces and are not case sensitive.  Plural\n"
-        . "#    keywords are used for list topics.  You can redefine keywords found in the\n"
-        . "#    main topics file.\n";
-        }
-
-    print FH_TOPICS
-    "#\n"
-    . "# Index: [yes|no]\n"
-    . "#    Whether the topics get their own index.  Defaults to yes.  Everything is\n"
-    . "#    included in the general index regardless of this setting.\n"
-    . "#\n"
-    . "# Scope: [normal|start|end|always global]\n"
-    . "#    How the topics affects scope.  Defaults to normal.\n"
-    . "#    normal        - Topics stay within the current scope.\n"
-    . "#    start         - Topics start a new scope for all the topics beneath it,\n"
-    . "#                    like class topics.\n"
-    . "#    end           - Topics reset the scope back to global for all the topics\n"
-    . "#                    beneath it.\n"
-    . "#    always global - Topics are defined as global, but do not change the scope\n"
-    . "#                    for any other topics.\n"
-    . "#\n"
-    . "# Class Hierarchy: [yes|no]\n"
-    . "#    Whether the topics are part of the class hierarchy.  Defaults to no.\n"
-    . "#\n"
-    . "# Variable Type: [yes|no]\n"
-    . "#    Whether the topics can be a variable type.  Defaults to no.\n"
-    . "#\n"
-    . "# Page Title If First: [yes|no]\n"
-    . "#    Whether the topic's title becomes the page title if it's the first one in\n"
-    . "#    a file.  Defaults to no.\n"
-    . "#\n"
-    . "# Break Lists: [yes|no]\n"
-    . "#    Whether list topics should be broken into individual topics in the output.\n"
-    . "#    Defaults to no.\n"
-    . "#\n"
-    . "# Can Group With: [type], [type], ...\n"
-    . "#    Defines a list of topic types that this one can possibly be grouped with.\n"
-    . "#    Defaults to none.\n"
-    . "#-------------------------------------------------------------------------------\n\n";
-
-    my $listToPrint;
-
-    if ($isMain)
-        {
-        print FH_TOPICS
-        "# The following topics MUST be defined in this file:\n"
-        . "#\n";
-        $listToPrint = \@requiredTypeNames;
-        }
-    else
-        {
-        print FH_TOPICS
-        "# The following topics are defined in the main file, if you'd like to alter\n"
-        . "# their behavior or add keywords:\n"
-        . "#\n";
-        $listToPrint = \@mainTopicNames;
-        }
-
-    print FH_TOPICS
-    Text::Wrap::wrap('#    ', '#    ', join(', ', @$listToPrint)) . "\n"
-    . "\n"
-    . "# If you add something that you think would be useful to other developers\n"
-    . "# and should be included in Natural Docs by default, please e-mail it to\n"
-    . "# topics [at] naturaldocs [dot] org.\n";
-
-    # Existence hash.  We do this because we want the required ones to go first by adding them to @topicTypeOrder, but we don't
-    # want them to appear twice.
-    my %doneTopicTypes;
-    my ($altering, $numberOfProperties);
-
-    if ($isMain)
-        {  unshift @topicTypeOrder, @requiredTypeNames;  };
-
-    my @propertyOrder = ('Plural', 'Index', 'Scope', 'Class Hierarchy', 'Variable Type', 'Page Title If First', 'Break Lists');
-
-    foreach my $topicType (@topicTypeOrder)
-        {
-        if (!exists $doneTopicTypes{$topicType})
-            {
-            if (substr($topicType, -1) eq '*')
-                {
-                print FH_TOPICS "\n\n"
-                . 'Alter Topic Type: ' . substr($topicType, 0, -1) . "\n\n";
-
-                $altering = 1;
-                $numberOfProperties = 0;
-                }
-            else
-                {
-                print FH_TOPICS "\n\n"
-                . 'Topic Type: ' . $topicType . "\n\n";
-
-                $altering = 0;
-                $numberOfProperties = 0;
-                };
-
-            foreach my $property (@propertyOrder)
-                {
-                if (exists $properties{$topicType}->{lc($property)})
-                    {
-                    print FH_TOPICS
-                    '   ' . $property . ': ' . ucfirst( $properties{$topicType}->{lc($property)} ) . "\n";
-
-                    $numberOfProperties++;
-                    };
-                };
-
-            if (exists $properties{$topicType}->{'can group with'})
-                {
-                my @typeStrings = split(/ ?, ?/, lc($properties{$topicType}->{'can group with'}));
-                my @types;
-
-                foreach my $typeString (@typeStrings)
-                    {
-                    if (exists $names{$typeString})
-                        {  push @types, $names{$typeString};  };
-                    };
-
-                if (scalar @types)
-                    {
-                    for (my $i = 0; $i < scalar @types; $i++)
-                        {
-                        my $name = NaturalDocs::Topics->NameOfType($types[$i], 1);
-
-                        if ($i == 0)
-                            {  print FH_TOPICS '   Can Group With: ' . $name;  }
-                        else
-                            {  print FH_TOPICS ', ' . $name;  };
-                        };
-
-                    print FH_TOPICS "\n";
-                    $numberOfProperties++;
-                    };
-                };
-
-            if (scalar @{$properties{$topicType}->{'keywords'}})
-                {
-                if ($numberOfProperties > 1)
-                    {  print FH_TOPICS "\n";  };
-
-                print FH_TOPICS
-                '   ' . ($altering ? 'Add ' : '') . 'Keywords:' . "\n";
-
-                my $keywords = $properties{$topicType}->{'keywords'};
-
-                for (my $i = 0; $i < scalar @$keywords; $i += 2)
-                    {
-                    print FH_TOPICS '      ' . $keywords->[$i];
-
-                    if (defined $keywords->[$i + 1])
-                        {  print FH_TOPICS ', ' . $keywords->[$i + 1];  };
-
-                    print FH_TOPICS "\n";
-                    };
-                };
-
-            $doneTopicTypes{$topicType} = 1;
-            };
-        };
-
-    close(FH_TOPICS);
-    };
-
-
-
-###############################################################################
-# Group: Functions
-
-
-#
-#   Function: KeywordInfo
-#
-#   Returns information about a topic keyword.
-#
-#   Parameters:
-#
-#       keyword - The keyword, which may be plural.
-#
-#   Returns:
-#
-#       The array ( topicType, info, isPlural ), or an empty array if the keyword doesn't exist.
-#
-#       topicType - The <TopicType> of the keyword.
-#       info - The <NaturalDocs::Topics::Type> of its type.
-#       isPlural - Whether the keyword was plural or not.
-#
-sub KeywordInfo #(keyword)
-    {
-    my ($self, $keyword) = @_;
-
-    $keyword = lc($keyword);
-
-    my $type = $keywords{$keyword};
-
-    if (defined $type)
-        {  return ( $type, $types{$type}, undef );  };
-
-    $type = $pluralKeywords{$keyword};
-
-    if (defined $type)
-        {  return ( $type, $types{$type}, 1 );  };
-
-    return ( );
-    };
-
-
-#
-#   Function: NameInfo
-#
-#   Returns information about a topic name.
-#
-#   Parameters:
-#
-#      name - The topic type name, which can be plural and/or alphanumeric only.
-#
-#   Returns:
-#
-#       The array ( topicType, info ), or an empty array if the name doesn't exist.  Note that unlike <KeywordInfo()>, this
-#       does *not* tell you whether the name is plural or not.
-#
-#       topicType - The <TopicType> of the name.
-#       info - The <NaturalDocs::Topics::Type> of the type.
-#
-sub NameInfo #(name)
-    {
-    my ($self, $name) = @_;
-
-    my $type = $names{lc($name)};
-
-    if (defined $type)
-        {  return ( $type, $types{$type} );  }
-    else
-        {  return ( );  };
-    };
-
-
-#
-#   Function: TypeInfo
-#
-#   Returns information about a <TopicType>.
-#
-#   Parameters:
-#
-#      type - The <TopicType>.
-#
-#   Returns:
-#
-#       The <NaturalDocs::Topics::Type> of the type, or undef if it didn't exist.
-#
-sub TypeInfo #(type)
-    {
-    my ($self, $type) = @_;
-    return $types{$type};
-    };
-
-
-#
-#   Function: NameOfType
-#
-#   Returns the name of the passed <TopicType>, or undef if it doesn't exist.
-#
-#   Parameters:
-#
-#       topicType - The <TopicType>.
-#       plural - Whether to return the plural instead of the singular.
-#       alphanumericOnly - Whether to strips everything but alphanumeric characters out.  Case isn't modified.
-#
-#   Returns:
-#
-#       The topic type name, according to what was specified in the parameters, or undef if it doesn't exist.
-#
-sub NameOfType #(topicType, plural, alphanumericOnly)
-    {
-    my ($self, $topicType, $plural, $alphanumericOnly) = @_;
-
-    my $topicObject = $types{$topicType};
-
-    if (!defined $topicObject)
-        {  return undef;  };
-
-    my $topicName = ($plural ? $topicObject->PluralName() : $topicObject->Name());
-
-    if ($alphanumericOnly)
-        {  $topicName =~ tr/a-zA-Z0-9//cd;  };
-
-    return $topicName;
-    };
-
-
-#
-#   Function: TypeFromName
-#
-#   Returns a <TopicType> for the passed topic name.
-#
-#   Parameters:
-#
-#       topicName - The name of the topic, which can be plural and/or alphanumeric only.
-#
-#   Returns:
-#
-#       The <TopicType>.  It does not specify whether the name was plural or not.
-#
-sub TypeFromName #(topicName)
-    {
-    my ($self, $topicName) = @_;
-
-    return $names{lc($topicName)};
-    };
-
-
-#
-#   Function: IsValidType
-#
-#   Returns whether the passed <TopicType> is defined.
-#
-sub IsValidType #(type)
-    {
-    my ($self, $type) = @_;
-    return exists $types{$type};
-    };
-
-
-#
-#   Function: TypeFromLegacy
-#
-#   Returns a <TopicType> for the passed legacy topic type integer.  <TopicTypes> were changed from integer constants to
-#   strings in 1.3.
-#
-sub TypeFromLegacy #(legacyInt)
-    {
-    my ($self, $int) = @_;
-    return $legacyTypes[$int];
-    };
-
-
-#
-#   Function: AllIndexableTypes
-#
-#   Returns an array of all possible indexable <TopicTypes>.
-#
-sub AllIndexableTypes
-    {
-    my ($self) = @_;
-    return keys %indexable;
-    };
-
-
-
-###############################################################################
-# Group: Support Functions
-
-
-#
-#   Function: MakeTopicType
-#
-#   Returns a <TopicType> for the passed topic name.  It does not check to see if it exists already.
-#
-#   Parameters:
-#
-sub MakeTopicType #(topicName)
-    {
-    my ($self, $topicName) = @_;
-
-    # Dependency: The values of the default topic type constants must match what is generated here.
-
-    # Turn everything to lowercase and strip non-alphanumeric characters.
-    $topicName = lc($topicName);
-    $topicName =~ tr/a-z0-9//cd;
-
-    return $topicName;
-    };
-
-
-
-1;