############################################################################### # # 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 . It's format should be treated as opaque; use # to get them from topic names. However, they can be compared for equality with string functions. # # # Constants: Default TopicTypes # # Exported constants of the default , so you don't have to go through every time. # # TOPIC_GENERAL - The general , which has the special meaning of none in particular. # TOPIC_GENERIC - Generic . # TOPIC_GROUP - Group . # TOPIC_CLASS - Class . # TOPIC_INTERFACE - Interface . # TOPIC_FILE - File . # TOPIC_SECTION - Section . # TOPIC_FUNCTION - Function . # TOPIC_VARIABLE - Variable . # TOPIC_PROPERTY - Property . # TOPIC_TYPE - Type . # TOPIC_CONSTANT - Constant . # TOPIC_ENUMERATION - Enum . # TOPIC_DELEGATE - Delegate . # TOPIC_EVENT - Event . # 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 . # # # hash: types # # A hashref that maps to s. # my %types; # # hash: names # # A hashref that maps various forms of the all-lowercase type names to . 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 . Must not have any of the same keys as # . # my %keywords; # # hash: pluralKeywords # # A hashref that maps all-lowercase plural keywords to their . Must not have any of the same keys as # . # my %pluralKeywords; # # hash: indexable # # An existence hash of all the indexable . # my %indexable; # # array: requiredTypeNames # # An array of the 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 . # 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 names that are defined in the main . # 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 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 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 . # # # 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 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 . # 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 . # # 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 . # sub Save { my $self = shift; $self->SaveFile(1); # Main $self->SaveFile(0); # User }; # # Function: SaveFile # # Saves a particular version of . # # 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 of the keyword. # info - The 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 , this # does *not* tell you whether the name is plural or not. # # topicType - The of the name. # info - The 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 . # # Parameters: # # type - The . # # Returns: # # The 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 , or undef if it doesn't exist. # # Parameters: # # topicType - The . # 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 for the passed topic name. # # Parameters: # # topicName - The name of the topic, which can be plural and/or alphanumeric only. # # Returns: # # The . 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 is defined. # sub IsValidType #(type) { my ($self, $type) = @_; return exists $types{$type}; }; # # Function: TypeFromLegacy # # Returns a for the passed legacy topic type integer. 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 . # sub AllIndexableTypes { my ($self) = @_; return keys %indexable; }; ############################################################################### # Group: Support Functions # # Function: MakeTopicType # # Returns a 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;