1 ###############################################################################
3 # Package: NaturalDocs::Topics
5 ###############################################################################
7 # The topic constants and functions to convert them to and from strings used throughout the script. All constants are exported
10 ###############################################################################
12 # This file is part of Natural Docs, which is Copyright © 2003-2010 Greg Valure
13 # Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
14 # Refer to License.txt for the complete details
22 use NaturalDocs::Topics::Type;
24 package NaturalDocs::Topics;
27 our @EXPORT = ( 'TOPIC_GENERAL', 'TOPIC_GENERIC', 'TOPIC_GROUP', 'TOPIC_CLASS', 'TOPIC_FILE', 'TOPIC_FUNCTION',
28 'TOPIC_VARIABLE', 'TOPIC_PROPERTY', 'TOPIC_TYPE', 'TOPIC_ENUMERATION', 'TOPIC_CONSTANT',
29 'TOPIC_INTERFACE', 'TOPIC_EVENT', 'TOPIC_DELEGATE', 'TOPIC_SECTION' );
33 ###############################################################################
40 # A string representing a topic type as defined in <Topics.txt>. It's format should be treated as opaque; use <MakeTopicType()>
41 # to get them from topic names. However, they can be compared for equality with string functions.
46 # Constants: Default TopicTypes
48 # Exported constants of the default <TopicTypes>, so you don't have to go through <TypeFromName()> every time.
50 # TOPIC_GENERAL - The general <TopicType>, which has the special meaning of none in particular.
51 # TOPIC_GENERIC - Generic <TopicType>.
52 # TOPIC_GROUP - Group <TopicType>.
53 # TOPIC_CLASS - Class <TopicType>.
54 # TOPIC_INTERFACE - Interface <TopicType>.
55 # TOPIC_FILE - File <TopicType>.
56 # TOPIC_SECTION - Section <TopicType>.
57 # TOPIC_FUNCTION - Function <TopicType>.
58 # TOPIC_VARIABLE - Variable <TopicType>.
59 # TOPIC_PROPERTY - Property <TopicType>.
60 # TOPIC_TYPE - Type <TopicType>.
61 # TOPIC_CONSTANT - Constant <TopicType>.
62 # TOPIC_ENUMERATION - Enum <TopicType>.
63 # TOPIC_DELEGATE - Delegate <TopicType>.
64 # TOPIC_EVENT - Event <TopicType>.
66 use constant TOPIC_GENERAL => 'general';
67 use constant TOPIC_GENERIC => 'generic';
68 use constant TOPIC_GROUP => 'group';
69 use constant TOPIC_CLASS => 'class';
70 use constant TOPIC_INTERFACE => 'interface';
71 use constant TOPIC_FILE => 'file';
72 use constant TOPIC_SECTION => 'section';
73 use constant TOPIC_FUNCTION => 'function';
74 use constant TOPIC_VARIABLE => 'variable';
75 use constant TOPIC_PROPERTY => 'property';
76 use constant TOPIC_TYPE => 'type';
77 use constant TOPIC_CONSTANT => 'constant';
78 use constant TOPIC_ENUMERATION => 'enumeration';
79 use constant TOPIC_DELEGATE => 'delegate';
80 use constant TOPIC_EVENT => 'event';
81 # Dependency: The values of these constants must match what is generated by MakeTopicType().
82 # Dependency: These types must be added to requiredTypeNames so that they always exist.
87 ###############################################################################
94 # The file handle used when writing to <Topics.txt>.
101 # A hashref that maps <TopicTypes> to <NaturalDocs::Topics::Type>s.
109 # A hashref that maps various forms of the all-lowercase type names to <TopicTypes>. All are in the same hash because
110 # two names that reduce to the same thing it would cause big problems, and we need to catch that. Keys include
113 # - Plural topic names
114 # - Alphanumeric-only topic names
115 # - Alphanumeric-only plural topic names
123 # A hashref that maps all-lowercase keywords to their <TopicTypes>. Must not have any of the same keys as
130 # hash: pluralKeywords
132 # A hashref that maps all-lowercase plural keywords to their <TopicTypes>. Must not have any of the same keys as
141 # An existence hash of all the indexable <TopicTypes>.
147 # array: requiredTypeNames
149 # An array of the <TopicType> names which are required to be defined in the main file. Are in the order they should appear
152 my @requiredTypeNames = ( 'Generic', 'Class', 'Interface', 'Section', 'File', 'Group', 'Function', 'Variable', 'Property', 'Type',
153 'Constant', 'Enumeration', 'Event', 'Delegate' );
159 # An array that converts the legacy topic types, which were numeric constants prior to 1.3, to the current <TopicTypes>.
160 # The legacy types are used as an index into the array. Note that this does not support list type values.
162 my @legacyTypes = ( TOPIC_GENERAL, TOPIC_CLASS, TOPIC_SECTION, TOPIC_FILE, TOPIC_GROUP, TOPIC_FUNCTION,
163 TOPIC_VARIABLE, TOPIC_GENERIC, TOPIC_TYPE, TOPIC_CONSTANT, TOPIC_PROPERTY );
167 # array: mainTopicNames
169 # An array of the <TopicType> names that are defined in the main <Topics.txt>.
175 ###############################################################################
182 # The configuration file that defines or overrides the topic definitions for Natural Docs. One version sits in Natural Docs'
183 # configuration directory, and another can be in a project directory to add to or override them.
187 # Everything after a # symbol is ignored.
189 # Except when specifying topic names, everything below is case-insensitive.
191 # > Format: [version]
193 # Specifies the file format version of the file.
198 # > Ignore[d] Keyword[s]: [keyword], [keyword] ...
200 # > [keyword], [keyword]
203 # Ignores the keywords so that they're not recognized as Natural Docs topics anymore. Can be specified as a list on the same
204 # line and/or following like a normal Keywords section.
206 # > Topic Type: [name]
207 # > Alter Topic Type: [name]
209 # Creates a new topic type or alters an existing one. The name can only contain <CFChars> and isn't case sensitive, although
210 # the original case is remembered for presentation.
212 # The name General is reserved. There are a number of default types that must be defined in the main file as well, but those
213 # are governed by <NaturalDocs::Topics> and are not included here. The default types can have their keywords or behaviors
214 # changed, though, either by editing the default file or by overriding them in the user file.
216 # Enumeration is a special type. It is indexed with Types and its definition list members are listed with Constants according
217 # to the rules in <Languages.txt>.
220 # Topic Type Sections:
224 # Specifies the plural name of the topic type. Defaults to the singular name. Has the same restrictions as the topic type
229 # Whether the topic type gets an index. Defaults to yes.
231 # > Scope: [normal|start|end|always global]
233 # How the topic affects scope. Defaults to normal.
235 # normal - The topic stays within the current scope.
236 # start - The topic starts a new scope for all the topics beneath it, like class topics.
237 # end - The topic resets the scope back to global for all the topics beneath it, like section topics.
238 # always global - The topic is defined as a global symbol, but does not change the scope for any other topics.
240 # > Class Hierarchy: [yes|no]
242 # Whether the topic is part of the class hierarchy. Defaults to no.
244 # > Page Title if First: [yes|no]
246 # Whether the title of this topic becomes the page title if it is the first topic in a file. Defaults to no.
248 # > Break Lists: [yes|no]
250 # Whether list topics should be broken into individual topics in the output. Defaults to no.
252 # > Can Group With: [topic type], [topic type], ...
254 # The list of <TopicTypes> the topic can possibly be grouped with.
256 # > [Add] Keyword[s]:
258 # > [keyword], [plural keyword]
261 # A list of the topic type's keywords. Each line after the heading is the keyword and optionally its plural form. This continues
262 # until the next line in "keyword: value" format. "Add" isn't required.
264 # - Keywords can only have letters and numbers. No punctuation or spaces are allowed.
265 # - Keywords are not case sensitive.
266 # - Subsequent keyword sections add to the list. They don't replace it.
267 # - Keywords can be redefined by other keyword sections.
274 # The initial version of this file.
278 ###############################################################################
279 # Group: File Functions
285 # Loads both the master and the project version of <Topics.txt>.
291 # Add the special General topic type.
293 $types{::TOPIC_GENERAL()} = NaturalDocs::Topics::Type->New('General', 'General', 1, ::SCOPE_NORMAL(), undef);
294 $names{'general'} = ::TOPIC_GENERAL();
295 $indexable{::TOPIC_GENERAL()} = 1;
296 # There are no keywords for the general topic.
299 $self->LoadFile(1); # Main
301 # Dependency: All the default topic types must be checked for existence.
303 # Check to see if the required types are defined.
304 foreach my $name (@requiredTypeNames)
306 if (!exists $names{lc($name)})
307 { NaturalDocs::ConfigFile->AddError('The ' . $name . ' topic type must be defined in the main topics file.'); };
310 my $errorCount = NaturalDocs::ConfigFile->ErrorCount();
314 NaturalDocs::ConfigFile->PrintErrorsAndAnnotateFile();
315 NaturalDocs::Error->SoftDeath('There ' . ($errorCount == 1 ? 'is an error' : 'are ' . $errorCount . ' errors')
316 . ' in ' . NaturalDocs::Project->MainConfigFile('Topics.txt'));
320 $self->LoadFile(); # User
322 $errorCount = NaturalDocs::ConfigFile->ErrorCount();
326 NaturalDocs::ConfigFile->PrintErrorsAndAnnotateFile();
327 NaturalDocs::Error->SoftDeath('There ' . ($errorCount == 1 ? 'is an error' : 'are ' . $errorCount . ' errors')
328 . ' in ' . NaturalDocs::Project->UserConfigFile('Topics.txt'));
336 # Loads a particular version of <Topics.txt>.
340 # isMain - Whether the file is the main file or not.
342 sub LoadFile #(isMain)
344 my ($self, $isMain) = @_;
350 $file = NaturalDocs::Project->MainConfigFile('Topics.txt');
351 $status = NaturalDocs::Project->MainConfigFileStatus('Topics.txt');
355 $file = NaturalDocs::Project->UserConfigFile('Topics.txt');
356 $status = NaturalDocs::Project->UserConfigFileStatus('Topics.txt');
361 if ($version = NaturalDocs::ConfigFile->Open($file))
363 # The format hasn't changed since the file was introduced.
365 if ($status == ::FILE_CHANGED())
366 { NaturalDocs::Project->ReparseEverything(); };
368 my ($topicTypeKeyword, $topicTypeName, $topicType, $topicTypeObject, $inKeywords, $inIgnoredKeywords);
370 # Keys are topic type objects, values are unparsed strings.
372 tie %canGroupWith, 'Tie::RefHash';
374 while (my ($keyword, $value) = NaturalDocs::ConfigFile->GetLine())
379 $inIgnoredKeywords = 0;
382 if ($keyword eq 'topic type')
384 $topicTypeKeyword = $keyword;
385 $topicTypeName = $value;
387 # Resolve conflicts and create the type if necessary.
389 $topicType = $self->MakeTopicType($topicTypeName);
390 my $lcTopicTypeName = lc($topicTypeName);
392 my $lcTopicTypeAName = $lcTopicTypeName;
393 $lcTopicTypeAName =~ tr/a-z0-9//cd;
395 if (!NaturalDocs::ConfigFile->HasOnlyCFChars($topicTypeName))
397 NaturalDocs::ConfigFile->AddError('Topic names can only have ' . NaturalDocs::ConfigFile->CFCharNames() . '.');
399 elsif ($topicType eq ::TOPIC_GENERAL())
401 NaturalDocs::ConfigFile->AddError('You cannot define a General topic type.');
403 elsif (defined $types{$topicType} || defined $names{$lcTopicTypeName} || defined $names{$lcTopicTypeAName})
405 NaturalDocs::ConfigFile->AddError('Topic type ' . $topicTypeName . ' is already defined or its name is too '
406 . 'similar to an existing name. Use Alter Topic Type if you meant to override '
411 $topicTypeObject = NaturalDocs::Topics::Type->New($topicTypeName, $topicTypeName, 1, ::SCOPE_NORMAL(),
414 $types{$topicType} = $topicTypeObject;
415 $names{$lcTopicTypeName} = $topicType;
416 $names{$lcTopicTypeAName} = $topicType;
418 $indexable{$topicType} = 1;
421 { push @mainTopicNames, $topicTypeName; };
425 elsif ($keyword eq 'alter topic type')
427 $topicTypeKeyword = $keyword;
428 $topicTypeName = $value;
430 # Resolve conflicts and create the type if necessary.
432 $topicType = $names{lc($topicTypeName)};
434 if (!defined $topicType)
435 { NaturalDocs::ConfigFile->AddError('Topic type ' . $topicTypeName . ' doesn\'t exist.'); }
436 elsif ($topicType eq ::TOPIC_GENERAL())
437 { NaturalDocs::ConfigFile->AddError('You cannot alter the General topic type.'); }
440 $topicTypeObject = $types{$topicType};
444 elsif ($keyword =~ /^ignored? keywords?$/)
446 $inIgnoredKeywords = 1;
448 my @ignoredKeywords = split(/ ?, ?/, lc($value));
450 foreach my $ignoredKeyword (@ignoredKeywords)
452 delete $keywords{$ignoredKeyword};
453 delete $pluralKeywords{$ignoredKeyword};
457 # 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
458 # rather them all show up at once instead of them showing up one at a time between Natural Docs runs. So we just ignore
459 # the settings if $topicTypeObject is undef.
462 elsif ($keyword eq 'plural')
464 my $pluralName = $value;
465 my $lcPluralName = lc($pluralName);
467 my $lcPluralAName = $lcPluralName;
468 $lcPluralAName =~ tr/a-zA-Z0-9//cd;
470 if (!NaturalDocs::ConfigFile->HasOnlyCFChars($pluralName))
472 NaturalDocs::ConfigFile->AddError('Plural names can only have '
473 . NaturalDocs::ConfigFile->CFCharNames() . '.');
475 elsif ($lcPluralAName eq 'general')
477 NaturalDocs::ConfigFile->AddError('You cannot use General as a plural name for ' . $topicTypeName . '.');
479 elsif ( (defined $names{$lcPluralName} && $names{$lcPluralName} ne $topicType) ||
480 (defined $names{$lcPluralAName} && $names{$lcPluralAName} ne $topicType) )
482 NaturalDocs::ConfigFile->AddError($topicTypeName . "'s plural name, " . $pluralName
483 . ', is already defined or is too similar to an existing name.');
486 elsif (defined $topicTypeObject)
488 $topicTypeObject->SetPluralName($pluralName);
490 $names{$lcPluralName} = $topicType;
491 $names{$lcPluralAName} = $topicType;
495 elsif ($keyword eq 'index')
501 if (defined $topicTypeObject)
503 $topicTypeObject->SetIndex(1);
504 $indexable{$topicType} = 1;
507 elsif ($value eq 'no')
509 if (defined $topicTypeObject)
511 $topicTypeObject->SetIndex(0);
512 delete $indexable{$topicType};
517 NaturalDocs::ConfigFile->AddError('Index lines can only be "yes" or "no".');
521 elsif ($keyword eq 'class hierarchy')
527 if (defined $topicTypeObject)
528 { $topicTypeObject->SetClassHierarchy(1); };
530 elsif ($value eq 'no')
532 if (defined $topicTypeObject)
533 { $topicTypeObject->SetClassHierarchy(0); };
537 NaturalDocs::ConfigFile->AddError('Class Hierarchy lines can only be "yes" or "no".');
541 elsif ($keyword eq 'scope')
545 if ($value eq 'normal')
547 if (defined $topicTypeObject)
548 { $topicTypeObject->SetScope(::SCOPE_NORMAL()); };
550 elsif ($value eq 'start')
552 if (defined $topicTypeObject)
553 { $topicTypeObject->SetScope(::SCOPE_START()); };
555 elsif ($value eq 'end')
557 if (defined $topicTypeObject)
558 { $topicTypeObject->SetScope(::SCOPE_END()); };
560 elsif ($value eq 'always global')
562 if (defined $topicTypeObject)
563 { $topicTypeObject->SetScope(::SCOPE_ALWAYS_GLOBAL()); };
567 NaturalDocs::ConfigFile->AddError('Scope lines can only be "normal", "start", "end", or "always global".');
571 elsif ($keyword eq 'page title if first')
577 if (defined $topicTypeObject)
578 { $topicTypeObject->SetPageTitleIfFirst(1); };
580 elsif ($value eq 'no')
582 if (defined $topicTypeObject)
583 { $topicTypeObject->SetPageTitleIfFirst(undef); };
587 NaturalDocs::ConfigFile->AddError('Page Title if First lines can only be "yes" or "no".');
591 elsif ($keyword eq 'break lists')
597 if (defined $topicTypeObject)
598 { $topicTypeObject->SetBreakLists(1); };
600 elsif ($value eq 'no')
602 if (defined $topicTypeObject)
603 { $topicTypeObject->SetBreakLists(undef); };
607 NaturalDocs::ConfigFile->AddError('Break Lists lines can only be "yes" or "no".');
611 elsif ($keyword eq 'can group with')
613 if (defined $topicTypeObject)
614 { $canGroupWith{$topicTypeObject} = lc($value); };
617 elsif ($keyword =~ /^(?:add )?keywords?$/)
622 elsif (defined $keyword)
623 { NaturalDocs::ConfigFile->AddError($keyword . ' is not a valid keyword.'); }
625 elsif (!$inKeywords && !$inIgnoredKeywords)
627 NaturalDocs::ConfigFile->AddError('All lines in ' . $topicTypeKeyword . ' sections must begin with a keyword.');
630 else # No keyword but in keyword section.
634 if ($value =~ /^([a-z0-9 ]*[a-z0-9]) ?, ?([a-z0-9 ]+)$/)
636 my ($singular, $plural) = ($1, $2);
638 if ($inIgnoredKeywords)
640 delete $keywords{$singular};
641 delete $keywords{$plural};
642 delete $pluralKeywords{$singular};
643 delete $pluralKeywords{$plural};
645 elsif (defined $topicTypeObject)
647 $keywords{$singular} = $topicType;
648 delete $pluralKeywords{$singular};
650 $pluralKeywords{$plural} = $topicType;
651 delete $keywords{$plural};
654 elsif ($value =~ /^[a-z0-9 ]+$/)
656 if ($inIgnoredKeywords)
658 delete $keywords{$value};
659 delete $pluralKeywords{$value};
661 elsif (defined $topicType)
663 $keywords{$value} = $topicType;
664 delete $pluralKeywords{$value};
669 NaturalDocs::ConfigFile->AddError('Keywords can only have letters, numbers, and spaces. '
670 . 'Plurals must be separated by a comma.');
675 NaturalDocs::ConfigFile->Close();
678 # Parse out the Can Group With lines now that everything's defined.
680 while (my ($typeObject, $value) = each %canGroupWith)
682 my @values = split(/ ?, ?/, $value);
685 foreach my $value (@values)
687 # We're just going to ignore invalid items.
688 if (exists $names{$value})
689 { push @types, $names{$value}; };
693 { $typeObject->SetCanGroupWith(\@types); };
697 else # couldn't open file
700 { die "Couldn't open topics file " . $file . "\n"; }
702 { NaturalDocs::Project->ReparseEverything(); };
710 # Saves the main and user versions of <Topics.txt>.
716 $self->SaveFile(1); # Main
717 $self->SaveFile(0); # User
724 # Saves a particular version of <Topics.txt>.
728 # isMain - Whether the file is the main file or not.
730 sub SaveFile #(isMain)
732 my ($self, $isMain) = @_;
738 if (NaturalDocs::Project->MainConfigFileStatus('Topics.txt') == ::FILE_SAME())
740 $file = NaturalDocs::Project->MainConfigFile('Topics.txt');
744 # We have to check the main one two because this lists the topics defined in it.
745 if (NaturalDocs::Project->UserConfigFileStatus('Topics.txt') == ::FILE_SAME() &&
746 NaturalDocs::Project->MainConfigFileStatus('Topics.txt') == ::FILE_SAME())
748 $file = NaturalDocs::Project->UserConfigFile('Topics.txt');
752 # 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.
755 # Keys are topic type names, values are property hashrefs. Hashref keys are the property names, values the value.
756 # For keywords, the key is Keywords and the values are arrayrefs of singular and plural pairs. If no plural is defined, the entry
760 # List of ignored keywords specified as Ignore Keywords: [keyword], [keyword], ...
761 my @inlineIgnoredKeywords;
763 # List of ignored keywords specified in [keyword], [plural keyword] lines. Done in pairs, like for regular keywords.
764 my @separateIgnoredKeywords;
766 my $inIgnoredKeywords;
768 if (NaturalDocs::ConfigFile->Open($file))
770 # We can assume the file is valid.
772 my ($keyword, $value, $topicTypeName);
774 while (($keyword, $value) = NaturalDocs::ConfigFile->GetLine())
776 $keyword = lc($keyword);
778 if ($keyword eq 'topic type' || $keyword eq 'alter topic type')
780 $topicTypeName = $types{ $names{lc($value)} }->Name();
782 if ($keyword eq 'alter topic type')
783 { $topicTypeName .= '*'; };
785 push @topicTypeOrder, $topicTypeName;
787 if (!exists $properties{$topicTypeName})
788 { $properties{$topicTypeName} = { 'keywords' => [ ] }; };
791 elsif ($keyword eq 'plural')
793 $properties{$topicTypeName}->{$keyword} = $value;
796 elsif ($keyword eq 'index' ||
797 $keyword eq 'scope' ||
798 $keyword eq 'page title if first' ||
799 $keyword eq 'class hierarchy' ||
800 $keyword eq 'break lists' ||
801 $keyword eq 'can group with')
803 $properties{$topicTypeName}->{$keyword} = lc($value);
806 elsif ($keyword =~ /^(?:add )?keywords?$/)
808 $inIgnoredKeywords = 0;
811 elsif ($keyword =~ /^ignored? keywords?$/)
813 $inIgnoredKeywords = 1;
815 { push @inlineIgnoredKeywords, split(/ ?, ?/, $value); };
820 my ($singular, $plural) = split(/ ?, ?/, lc($value));
822 if ($inIgnoredKeywords)
823 { push @separateIgnoredKeywords, $singular, $plural; }
825 { push @{$properties{$topicTypeName}->{'keywords'}}, $singular, $plural; };
829 NaturalDocs::ConfigFile->Close();
833 if (!open(FH_TOPICS, '>' . $file))
835 # 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
836 # reformat the file, we can ignore the failure.
840 { die "Couldn't save " . $file; };
843 binmode(FH_TOPICS, ':encoding(UTF-8)');
844 print FH_TOPICS 'Format: ' . NaturalDocs::Settings->TextAppVersion() . "\n\n";
846 # Remember the 80 character limit.
851 "# This is the main Natural Docs topics file. If you change anything here, it\n"
852 . "# will apply to EVERY PROJECT you use Natural Docs on. If you'd like to\n"
853 . "# change something for just one project, edit the Topics.txt in its project\n"
854 . "# directory instead.\n";
859 "# This is the Natural Docs topics file for this project. If you change anything\n"
860 . "# here, it will apply to THIS PROJECT ONLY. If you'd like to change something\n"
861 . "# for all your projects, edit the Topics.txt in Natural Docs' Config directory\n"
862 . "# instead.\n\n\n";
864 if (scalar @inlineIgnoredKeywords || scalar @separateIgnoredKeywords)
866 if (scalar @inlineIgnoredKeywords == 1 && !scalar @separateIgnoredKeywords)
868 print FH_TOPICS 'Ignore Keyword: ' . $inlineIgnoredKeywords[0] . "\n";
873 'Ignore Keywords: ' . join(', ', @inlineIgnoredKeywords) . "\n";
875 for (my $i = 0; $i < scalar @separateIgnoredKeywords; $i += 2)
877 print FH_TOPICS ' ' . $separateIgnoredKeywords[$i];
879 if (defined $separateIgnoredKeywords[$i + 1])
880 { print FH_TOPICS ', ' . $separateIgnoredKeywords[$i + 1]; };
882 print FH_TOPICS "\n";
889 "# If you'd like to prevent keywords from being recognized by Natural Docs, you\n"
890 . "# can do it like this:\n"
891 . "# Ignore Keywords: [keyword], [keyword], ...\n"
893 . "# Or you can use the list syntax like how they are defined:\n"
894 . "# Ignore Keywords:\n"
896 . "# [keyword], [plural keyword]\n"
901 print FH_TOPICS # [CFChars]
903 . "#-------------------------------------------------------------------------------\n"
910 "# Topic Type: [name]\n"
911 . "# Creates a new topic type. Each type gets its own index and behavior\n"
912 . "# settings. Its name can have letters, numbers, spaces, and these\n"
913 . "# charaters: - / . '\n"
915 . "# The Enumeration type is special. It's indexed with Types but its members\n"
916 . "# are indexed with Constants according to the rules in Languages.txt.\n"
922 "# Topic Type: [name]\n"
923 . "# Alter Topic Type: [name]\n"
924 . "# Creates a new topic type or alters one from the main file. Each type gets\n"
925 . "# its own index and behavior settings. Its name can have letters, numbers,\n"
926 . "# spaces, and these charaters: - / . '\n"
932 . "# Sets the plural name of the topic type, if different.\n"
936 . "# [keyword], [plural keyword]\n"
942 "# Defines a list of keywords for the topic type. They may only contain\n"
943 . "# letters, numbers, and spaces and are not case sensitive. Plural keywords\n"
944 . "# are used for list topics.\n";
949 "# Defines or adds to the list of keywords for the topic type. They may only\n"
950 . "# contain letters, numbers, and spaces and are not case sensitive. Plural\n"
951 . "# keywords are used for list topics. You can redefine keywords found in the\n"
952 . "# main topics file.\n";
957 . "# Index: [yes|no]\n"
958 . "# Whether the topics get their own index. Defaults to yes. Everything is\n"
959 . "# included in the general index regardless of this setting.\n"
961 . "# Scope: [normal|start|end|always global]\n"
962 . "# How the topics affects scope. Defaults to normal.\n"
963 . "# normal - Topics stay within the current scope.\n"
964 . "# start - Topics start a new scope for all the topics beneath it,\n"
965 . "# like class topics.\n"
966 . "# end - Topics reset the scope back to global for all the topics\n"
968 . "# always global - Topics are defined as global, but do not change the scope\n"
969 . "# for any other topics.\n"
971 . "# Class Hierarchy: [yes|no]\n"
972 . "# Whether the topics are part of the class hierarchy. Defaults to no.\n"
974 . "# Page Title If First: [yes|no]\n"
975 . "# Whether the topic's title becomes the page title if it's the first one in\n"
976 . "# a file. Defaults to no.\n"
978 . "# Break Lists: [yes|no]\n"
979 . "# Whether list topics should be broken into individual topics in the output.\n"
980 . "# Defaults to no.\n"
982 . "# Can Group With: [type], [type], ...\n"
983 . "# Defines a list of topic types that this one can possibly be grouped with.\n"
984 . "# Defaults to none.\n"
985 . "#-------------------------------------------------------------------------------\n\n";
992 "# The following topics MUST be defined in this file:\n"
994 $listToPrint = \@requiredTypeNames;
999 "# The following topics are defined in the main file, if you'd like to alter\n"
1000 . "# their behavior or add keywords:\n"
1002 $listToPrint = \@mainTopicNames;
1006 Text::Wrap::wrap('# ', '# ', join(', ', @$listToPrint)) . "\n"
1008 . "# If you add something that you think would be useful to other developers\n"
1009 . "# and should be included in Natural Docs by default, please e-mail it to\n"
1010 . "# topics [at] naturaldocs [dot] org.\n";
1012 # Existence hash. We do this because we want the required ones to go first by adding them to @topicTypeOrder, but we don't
1013 # want them to appear twice.
1015 my ($altering, $numberOfProperties);
1018 { unshift @topicTypeOrder, @requiredTypeNames; };
1020 my @propertyOrder = ('Plural', 'Index', 'Scope', 'Class Hierarchy', 'Page Title If First', 'Break Lists');
1022 foreach my $topicType (@topicTypeOrder)
1024 if (!exists $doneTopicTypes{$topicType})
1026 if (substr($topicType, -1) eq '*')
1028 print FH_TOPICS "\n\n"
1029 . 'Alter Topic Type: ' . substr($topicType, 0, -1) . "\n\n";
1032 $numberOfProperties = 0;
1036 print FH_TOPICS "\n\n"
1037 . 'Topic Type: ' . $topicType . "\n\n";
1040 $numberOfProperties = 0;
1043 foreach my $property (@propertyOrder)
1045 if (exists $properties{$topicType}->{lc($property)})
1048 ' ' . $property . ': ' . ucfirst( $properties{$topicType}->{lc($property)} ) . "\n";
1050 $numberOfProperties++;
1054 if (exists $properties{$topicType}->{'can group with'})
1056 my @typeStrings = split(/ ?, ?/, lc($properties{$topicType}->{'can group with'}));
1059 foreach my $typeString (@typeStrings)
1061 if (exists $names{$typeString})
1062 { push @types, $names{$typeString}; };
1067 for (my $i = 0; $i < scalar @types; $i++)
1069 my $name = NaturalDocs::Topics->NameOfType($types[$i], 1);
1072 { print FH_TOPICS ' Can Group With: ' . $name; }
1074 { print FH_TOPICS ', ' . $name; };
1077 print FH_TOPICS "\n";
1078 $numberOfProperties++;
1082 if (scalar @{$properties{$topicType}->{'keywords'}})
1084 if ($numberOfProperties > 1)
1085 { print FH_TOPICS "\n"; };
1088 ' ' . ($altering ? 'Add ' : '') . 'Keywords:' . "\n";
1090 my $keywords = $properties{$topicType}->{'keywords'};
1092 for (my $i = 0; $i < scalar @$keywords; $i += 2)
1094 print FH_TOPICS ' ' . $keywords->[$i];
1096 if (defined $keywords->[$i + 1])
1097 { print FH_TOPICS ', ' . $keywords->[$i + 1]; };
1099 print FH_TOPICS "\n";
1103 $doneTopicTypes{$topicType} = 1;
1112 ###############################################################################
1117 # Function: KeywordInfo
1119 # Returns information about a topic keyword.
1123 # keyword - The keyword, which may be plural.
1127 # The array ( topicType, info, isPlural ), or an empty array if the keyword doesn't exist.
1129 # topicType - The <TopicType> of the keyword.
1130 # info - The <NaturalDocs::Topics::Type> of its type.
1131 # isPlural - Whether the keyword was plural or not.
1133 sub KeywordInfo #(keyword)
1135 my ($self, $keyword) = @_;
1137 $keyword = lc($keyword);
1139 my $type = $keywords{$keyword};
1142 { return ( $type, $types{$type}, undef ); };
1144 $type = $pluralKeywords{$keyword};
1147 { return ( $type, $types{$type}, 1 ); };
1154 # Function: NameInfo
1156 # Returns information about a topic name.
1160 # name - The topic type name, which can be plural and/or alphanumeric only.
1164 # The array ( topicType, info ), or an empty array if the name doesn't exist. Note that unlike <KeywordInfo()>, this
1165 # does *not* tell you whether the name is plural or not.
1167 # topicType - The <TopicType> of the name.
1168 # info - The <NaturalDocs::Topics::Type> of the type.
1170 sub NameInfo #(name)
1172 my ($self, $name) = @_;
1174 my $type = $names{lc($name)};
1177 { return ( $type, $types{$type} ); }
1184 # Function: TypeInfo
1186 # Returns information about a <TopicType>.
1190 # type - The <TopicType>.
1194 # The <NaturalDocs::Topics::Type> of the type, or undef if it didn't exist.
1196 sub TypeInfo #(type)
1198 my ($self, $type) = @_;
1199 return $types{$type};
1204 # Function: NameOfType
1206 # Returns the name of the passed <TopicType>, or undef if it doesn't exist.
1210 # topicType - The <TopicType>.
1211 # plural - Whether to return the plural instead of the singular.
1212 # alphanumericOnly - Whether to strips everything but alphanumeric characters out. Case isn't modified.
1216 # The topic type name, according to what was specified in the parameters, or undef if it doesn't exist.
1218 sub NameOfType #(topicType, plural, alphanumericOnly)
1220 my ($self, $topicType, $plural, $alphanumericOnly) = @_;
1222 my $topicObject = $types{$topicType};
1224 if (!defined $topicObject)
1227 my $topicName = ($plural ? $topicObject->PluralName() : $topicObject->Name());
1229 if ($alphanumericOnly)
1230 { $topicName =~ tr/a-zA-Z0-9//cd; };
1237 # Function: TypeFromName
1239 # Returns a <TopicType> for the passed topic name.
1243 # topicName - The name of the topic, which can be plural and/or alphanumeric only.
1247 # The <TopicType>. It does not specify whether the name was plural or not.
1249 sub TypeFromName #(topicName)
1251 my ($self, $topicName) = @_;
1253 return $names{lc($topicName)};
1258 # Function: IsValidType
1260 # Returns whether the passed <TopicType> is defined.
1262 sub IsValidType #(type)
1264 my ($self, $type) = @_;
1265 return exists $types{$type};
1270 # Function: TypeFromLegacy
1272 # Returns a <TopicType> for the passed legacy topic type integer. <TopicTypes> were changed from integer constants to
1275 sub TypeFromLegacy #(legacyInt)
1277 my ($self, $int) = @_;
1278 return $legacyTypes[$int];
1283 # Function: AllIndexableTypes
1285 # Returns an array of all possible indexable <TopicTypes>.
1287 sub AllIndexableTypes
1290 return keys %indexable;
1295 ###############################################################################
1296 # Group: Support Functions
1300 # Function: MakeTopicType
1302 # Returns a <TopicType> for the passed topic name. It does not check to see if it exists already.
1306 sub MakeTopicType #(topicName)
1308 my ($self, $topicName) = @_;
1310 # Dependency: The values of the default topic type constants must match what is generated here.
1312 # Turn everything to lowercase and strip non-alphanumeric characters.
1313 $topicName = lc($topicName);
1314 $topicName =~ tr/a-z0-9//cd;