mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-14 20:18:19 +00:00
1320 lines
42 KiB
Perl
1320 lines
42 KiB
Perl
###############################################################################
|
|
#
|
|
# 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;
|