diff options
| author | Magnus Auvinen <magnus.auvinen@gmail.com> | 2008-08-02 08:21:29 +0000 |
|---|---|---|
| committer | Magnus Auvinen <magnus.auvinen@gmail.com> | 2008-08-02 08:21:29 +0000 |
| commit | 61bfe2d70cae6be8c4086a210a5451135ccca9ea (patch) | |
| tree | 62bf7808b1b2bfe5f56fe1e329871fb0991d0687 /docs/tool/Modules/NaturalDocs/Topics.pm | |
| parent | a13b94f9e0bca8ea892311d9d9e0c0bc48616ea7 (diff) | |
| download | zcatch-61bfe2d70cae6be8c4086a210a5451135ccca9ea.tar.gz zcatch-61bfe2d70cae6be8c4086a210a5451135ccca9ea.zip | |
added doc tool
Diffstat (limited to 'docs/tool/Modules/NaturalDocs/Topics.pm')
| -rw-r--r-- | docs/tool/Modules/NaturalDocs/Topics.pm | 1319 |
1 files changed, 1319 insertions, 0 deletions
diff --git a/docs/tool/Modules/NaturalDocs/Topics.pm b/docs/tool/Modules/NaturalDocs/Topics.pm new file mode 100644 index 00000000..ea42f57b --- /dev/null +++ b/docs/tool/Modules/NaturalDocs/Topics.pm @@ -0,0 +1,1319 @@ +############################################################################### +# +# 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-2008 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. +# +# > 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.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->MainConfigFile('Topics.txt')); + } + + + $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->UserConfigFile('Topics.txt')); + } + }; + + +# +# 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->MainConfigFile('Topics.txt'); + $status = NaturalDocs::Project->MainConfigFileStatus('Topics.txt'); + } + else + { + $file = NaturalDocs::Project->UserConfigFile('Topics.txt'); + $status = NaturalDocs::Project->UserConfigFileStatus('Topics.txt'); + }; + + 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 '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->MainConfigFileStatus('Topics.txt') == ::FILE_SAME()) + { return; }; + $file = NaturalDocs::Project->MainConfigFile('Topics.txt'); + } + else + { + # We have to check the main one two because this lists the topics defined in it. + if (NaturalDocs::Project->UserConfigFileStatus('Topics.txt') == ::FILE_SAME() && + NaturalDocs::Project->MainConfigFileStatus('Topics.txt') == ::FILE_SAME()) + { return; }; + $file = NaturalDocs::Project->UserConfigFile('Topics.txt'); + }; + + + # 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 '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" + . "# 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', '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; |