bixbite 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.markdown +49 -0
- data/VERSION +1 -0
- data/bin/bixbite +73 -0
- data/lib/bixbite.rb +13 -0
- data/lib/bixbite/command.rb +14 -0
- data/lib/bixbite/create.rb +76 -0
- data/template/Rakefile +25 -0
- data/template/assets/bixbite/Rakefile.rb +297 -0
- data/template/assets/naturaldocs/NaturalDocs/Config/Languages.txt +286 -0
- data/template/assets/naturaldocs/NaturalDocs/Config/Topics.txt +382 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/customizinglanguages.html +52 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/customizingtopics.html +74 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/documenting.html +58 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/documenting/reference.html +146 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/documenting/walkthrough.html +180 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/example/Default.css +528 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/example/NaturalDocs.js +204 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/examples.css +90 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/header/background.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/header/leftside.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/header/logo.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/header/overbody.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/header/overbodybg.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/header/overleftmargin.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/header/overmenu.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/header/overmenubg.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/header/rightside.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/logo.gif +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/menu/about.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/menu/background.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/menu/bottomleft.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/menu/bottomright.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/menu/community.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/menu/customizing.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/menu/using.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/index.html +9 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/javascript/BrowserStyles.js +77 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/javascript/PNGHandling.js +72 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/keywords.html +38 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/languages.html +32 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/menu.html +79 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/output.html +84 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/running.html +40 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/styles.css +290 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/styles.html +52 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/troubleshooting.html +18 -0
- data/template/assets/naturaldocs/NaturalDocs/Info/CSSGuide.txt +947 -0
- data/template/assets/naturaldocs/NaturalDocs/Info/File Parsing.txt +83 -0
- data/template/assets/naturaldocs/NaturalDocs/Info/HTMLTestCases.pm +269 -0
- data/template/assets/naturaldocs/NaturalDocs/Info/Languages.txt +107 -0
- data/template/assets/naturaldocs/NaturalDocs/Info/NDMarkup.txt +91 -0
- data/template/assets/naturaldocs/NaturalDocs/Info/Symbol Management.txt +59 -0
- data/template/assets/naturaldocs/NaturalDocs/Info/images/Logo.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/JavaScript/NaturalDocs.js +836 -0
- data/template/assets/naturaldocs/NaturalDocs/License-GPL.txt +341 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/BinaryFile.pm +294 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Builder.pm +280 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Builder/Base.pm +348 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Builder/FramedHTML.pm +345 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Builder/HTML.pm +398 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Builder/HTMLBase.pm +3693 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/ClassHierarchy.pm +860 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/ClassHierarchy/Class.pm +412 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/ClassHierarchy/File.pm +157 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/ConfigFile.pm +497 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Constants.pm +165 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/DefineMembers.pm +100 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Error.pm +305 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/File.pm +540 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/ImageReferenceTable.pm +383 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/ImageReferenceTable/Reference.pm +44 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/ImageReferenceTable/String.pm +110 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages.pm +1475 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/ActionScript.pm +1473 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Ada.pm +38 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Advanced.pm +828 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Advanced/Scope.pm +95 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Advanced/ScopeChange.pm +70 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Base.pm +832 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/CSharp.pm +1484 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/PLSQL.pm +319 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Pascal.pm +143 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Perl.pm +1370 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Prototype.pm +92 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Prototype/Parameter.pm +87 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Simple.pm +503 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Tcl.pm +219 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Menu.pm +3406 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Menu/Entry.pm +201 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/NDMarkup.pm +76 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Parser.pm +1331 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Parser/JavaDoc.pm +464 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Parser/Native.pm +1060 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Parser/ParsedTopic.pm +253 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Project.pm +1402 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Project/ImageFile.pm +160 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Project/SourceFile.pm +113 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/ReferenceString.pm +334 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Settings.pm +1418 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Settings/BuildTarget.pm +66 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SourceDB.pm +678 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SourceDB/Extension.pm +84 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SourceDB/File.pm +129 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SourceDB/Item.pm +201 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SourceDB/ItemDefinition.pm +45 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SourceDB/WatchedFileDefinitions.pm +159 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/StatusMessage.pm +102 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SymbolString.pm +212 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SymbolTable.pm +1984 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SymbolTable/File.pm +186 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SymbolTable/IndexElement.pm +522 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SymbolTable/Reference.pm +273 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SymbolTable/ReferenceTarget.pm +97 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SymbolTable/Symbol.pm +428 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SymbolTable/SymbolDefinition.pm +96 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Topics.pm +1319 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Topics/Type.pm +151 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Version.pm +384 -0
- data/template/assets/naturaldocs/NaturalDocs/NaturalDocs +400 -0
- data/template/assets/naturaldocs/NaturalDocs/NaturalDocs.bat +17 -0
- data/template/assets/naturaldocs/NaturalDocs/Styles/Default.css +767 -0
- data/template/assets/naturaldocs/NaturalDocs/Styles/Roman.css +765 -0
- data/template/assets/naturaldocs/NaturalDocs/Styles/Small.css +763 -0
- data/template/assets/utilities/pngout +0 -0
- data/template/deploy/public_html/.htaccess +0 -0
- data/template/documentation/js/.htaccess +0 -0
- data/template/src/html/.htaccess +76 -0
- data/template/src/html/css/cmn/global.css +96 -0
- data/template/src/html/css/cmn/ie.css +15 -0
- data/template/src/html/css/cmn/ie6.css +15 -0
- data/template/src/html/images/cmn/.htaccess +0 -0
- data/template/src/html/images/tmp/.htaccess +0 -0
- data/template/src/html/includes/debug.inc +5 -0
- data/template/src/html/includes/footer.inc +52 -0
- data/template/src/html/includes/header.inc +61 -0
- data/template/src/html/includes/html.inc +3 -0
- data/template/src/html/includes/namespace.inc +19 -0
- data/template/src/html/includes/page.inc +151 -0
- data/template/src/html/index.html +35 -0
- data/template/src/html/js/cmn/bootstrap.js +74 -0
- data/template/src/html/js/cmn/global.js +142 -0
- data/template/src/html/js/cmn/lib/LAB.js +348 -0
- data/template/src/html/min/.htaccess +4 -0
- data/template/src/html/min/MinifyCLI.php +19 -0
- data/template/src/html/min/README.txt +132 -0
- data/template/src/html/min/builder/_index.js +242 -0
- data/template/src/html/min/builder/bm.js +36 -0
- data/template/src/html/min/builder/index.php +182 -0
- data/template/src/html/min/builder/ocCheck.php +36 -0
- data/template/src/html/min/builder/rewriteTest.js +1 -0
- data/template/src/html/min/config.php +187 -0
- data/template/src/html/min/groupsConfig.php +34 -0
- data/template/src/html/min/index.php +66 -0
- data/template/src/html/min/lib/FirePHP.php +1370 -0
- data/template/src/html/min/lib/HTTP/ConditionalGet.php +348 -0
- data/template/src/html/min/lib/HTTP/Encoder.php +326 -0
- data/template/src/html/min/lib/JSMin.php +314 -0
- data/template/src/html/min/lib/JSMinPlus.php +1872 -0
- data/template/src/html/min/lib/Minify.php +532 -0
- data/template/src/html/min/lib/Minify/Build.php +103 -0
- data/template/src/html/min/lib/Minify/CSS.php +83 -0
- data/template/src/html/min/lib/Minify/CSS/Compressor.php +250 -0
- data/template/src/html/min/lib/Minify/CSS/UriRewriter.php +270 -0
- data/template/src/html/min/lib/Minify/Cache/APC.php +130 -0
- data/template/src/html/min/lib/Minify/Cache/File.php +125 -0
- data/template/src/html/min/lib/Minify/Cache/Memcache.php +137 -0
- data/template/src/html/min/lib/Minify/ClosureCompiler.php +85 -0
- data/template/src/html/min/lib/Minify/CommentPreserver.php +90 -0
- data/template/src/html/min/lib/Minify/Controller/Base.php +202 -0
- data/template/src/html/min/lib/Minify/Controller/Files.php +78 -0
- data/template/src/html/min/lib/Minify/Controller/Groups.php +94 -0
- data/template/src/html/min/lib/Minify/Controller/MinApp.php +132 -0
- data/template/src/html/min/lib/Minify/Controller/Page.php +82 -0
- data/template/src/html/min/lib/Minify/Controller/Version1.php +118 -0
- data/template/src/html/min/lib/Minify/HTML.php +245 -0
- data/template/src/html/min/lib/Minify/ImportProcessor.php +157 -0
- data/template/src/html/min/lib/Minify/Lines.php +131 -0
- data/template/src/html/min/lib/Minify/Logger.php +45 -0
- data/template/src/html/min/lib/Minify/Packer.php +37 -0
- data/template/src/html/min/lib/Minify/Source.php +187 -0
- data/template/src/html/min/lib/Minify/YUICompressor.php +139 -0
- data/template/src/html/min/lib/Solar/Dir.php +199 -0
- data/template/src/html/min/lib/closure-compiler.jar +0 -0
- data/template/src/html/min/lib/yuicompressor-2.4.2.jar +0 -0
- data/template/src/html/min/utils.php +90 -0
- data/template/src/templates/css/template.css +7 -0
- data/template/src/templates/js/template.js +72 -0
- data/template/src/templates/template.html +18 -0
- data/template/src/yaml/config.yml +46 -0
- data/template/src/yaml/deploy.yml +35 -0
- data/test/bixbite_test.rb +7 -0
- data/test/test_helper.rb +10 -0
- metadata +278 -0
@@ -0,0 +1,219 @@
|
|
1
|
+
###############################################################################
|
2
|
+
#
|
3
|
+
# Class: NaturalDocs::Languages::Tcl
|
4
|
+
#
|
5
|
+
###############################################################################
|
6
|
+
#
|
7
|
+
# A subclass to handle the language variations of Tcl.
|
8
|
+
#
|
9
|
+
###############################################################################
|
10
|
+
|
11
|
+
# This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure
|
12
|
+
# Natural Docs is licensed under the GPL
|
13
|
+
|
14
|
+
use strict;
|
15
|
+
use integer;
|
16
|
+
|
17
|
+
package NaturalDocs::Languages::Tcl;
|
18
|
+
|
19
|
+
use base 'NaturalDocs::Languages::Simple';
|
20
|
+
|
21
|
+
|
22
|
+
#
|
23
|
+
# bool: pastFirstBrace
|
24
|
+
#
|
25
|
+
# Whether we've past the first brace in a function prototype or not.
|
26
|
+
#
|
27
|
+
my $pastFirstBrace;
|
28
|
+
|
29
|
+
|
30
|
+
#
|
31
|
+
# Function: OnCode
|
32
|
+
#
|
33
|
+
# This is just overridden to reset <pastFirstBrace>.
|
34
|
+
#
|
35
|
+
sub OnCode #(...)
|
36
|
+
{
|
37
|
+
my ($self, @params) = @_;
|
38
|
+
|
39
|
+
$pastFirstBrace = 0;
|
40
|
+
|
41
|
+
return $self->SUPER::OnCode(@params);
|
42
|
+
};
|
43
|
+
|
44
|
+
|
45
|
+
#
|
46
|
+
# Function: OnPrototypeEnd
|
47
|
+
#
|
48
|
+
# Tcl's function syntax is shown below.
|
49
|
+
#
|
50
|
+
# > proc [name] { [params] } { [code] }
|
51
|
+
#
|
52
|
+
# The opening brace is one of the prototype enders. We need to allow the first opening brace because it contains the
|
53
|
+
# parameters.
|
54
|
+
#
|
55
|
+
# Also, the parameters may have braces within them. I've seen one that used { seconds 20 } as a parameter.
|
56
|
+
#
|
57
|
+
# Parameters:
|
58
|
+
#
|
59
|
+
# type - The <TopicType> of the prototype.
|
60
|
+
# prototypeRef - A reference to the prototype so far, minus the ender in dispute.
|
61
|
+
# ender - The ender symbol.
|
62
|
+
#
|
63
|
+
# Returns:
|
64
|
+
#
|
65
|
+
# ENDER_ACCEPT - The ender is accepted and the prototype is finished.
|
66
|
+
# ENDER_IGNORE - The ender is rejected and parsing should continue. Note that the prototype will be rejected as a whole
|
67
|
+
# if all enders are ignored before reaching the end of the code.
|
68
|
+
# ENDER_ACCEPT_AND_CONTINUE - The ender is accepted so the prototype may stand as is. However, the prototype might
|
69
|
+
# also continue on so continue parsing. If there is no accepted ender between here and
|
70
|
+
# the end of the code this version will be accepted instead.
|
71
|
+
# ENDER_REVERT_TO_ACCEPTED - The expedition from ENDER_ACCEPT_AND_CONTINUE failed. Use the last accepted
|
72
|
+
# version and end parsing.
|
73
|
+
#
|
74
|
+
sub OnPrototypeEnd #(type, prototypeRef, ender)
|
75
|
+
{
|
76
|
+
my ($self, $type, $prototypeRef, $ender) = @_;
|
77
|
+
|
78
|
+
if ($type eq ::TOPIC_FUNCTION() && $ender eq '{' && !$pastFirstBrace)
|
79
|
+
{
|
80
|
+
$pastFirstBrace = 1;
|
81
|
+
return ::ENDER_IGNORE();
|
82
|
+
}
|
83
|
+
else
|
84
|
+
{ return ::ENDER_ACCEPT(); };
|
85
|
+
};
|
86
|
+
|
87
|
+
|
88
|
+
#
|
89
|
+
# Function: ParsePrototype
|
90
|
+
#
|
91
|
+
# Parses the prototype and returns it as a <NaturalDocs::Languages::Prototype> object.
|
92
|
+
#
|
93
|
+
# Parameters:
|
94
|
+
#
|
95
|
+
# type - The <TopicType>.
|
96
|
+
# prototype - The text prototype.
|
97
|
+
#
|
98
|
+
# Returns:
|
99
|
+
#
|
100
|
+
# A <NaturalDocs::Languages::Prototype> object.
|
101
|
+
#
|
102
|
+
sub ParsePrototype #(type, prototype)
|
103
|
+
{
|
104
|
+
my ($self, $type, $prototype) = @_;
|
105
|
+
|
106
|
+
if ($type ne ::TOPIC_FUNCTION())
|
107
|
+
{
|
108
|
+
my $object = NaturalDocs::Languages::Prototype->New($prototype);
|
109
|
+
return $object;
|
110
|
+
};
|
111
|
+
|
112
|
+
|
113
|
+
# Parse the parameters out of the prototype.
|
114
|
+
|
115
|
+
my @tokens = $prototype =~ /([^\{\}\ ]+|.)/g;
|
116
|
+
|
117
|
+
my $parameter;
|
118
|
+
my @parameterLines;
|
119
|
+
|
120
|
+
my $braceLevel = 0;
|
121
|
+
|
122
|
+
my ($beforeParameters, $afterParameters, $finishedParameters);
|
123
|
+
|
124
|
+
foreach my $token (@tokens)
|
125
|
+
{
|
126
|
+
if ($finishedParameters)
|
127
|
+
{ $afterParameters .= $token; }
|
128
|
+
|
129
|
+
elsif ($token eq '{')
|
130
|
+
{
|
131
|
+
if ($braceLevel == 0)
|
132
|
+
{ $beforeParameters .= $token; }
|
133
|
+
|
134
|
+
else # braceLevel > 0
|
135
|
+
{ $parameter .= $token; };
|
136
|
+
|
137
|
+
$braceLevel++;
|
138
|
+
}
|
139
|
+
|
140
|
+
elsif ($token eq '}')
|
141
|
+
{
|
142
|
+
if ($braceLevel == 1)
|
143
|
+
{
|
144
|
+
if ($parameter && $parameter ne ' ')
|
145
|
+
{ push @parameterLines, $parameter; };
|
146
|
+
|
147
|
+
$finishedParameters = 1;
|
148
|
+
$afterParameters .= $token;
|
149
|
+
|
150
|
+
$braceLevel--;
|
151
|
+
}
|
152
|
+
elsif ($braceLevel > 1)
|
153
|
+
{
|
154
|
+
$parameter .= $token;
|
155
|
+
$braceLevel--;
|
156
|
+
};
|
157
|
+
}
|
158
|
+
|
159
|
+
elsif ($token eq ' ')
|
160
|
+
{
|
161
|
+
if ($braceLevel == 1)
|
162
|
+
{
|
163
|
+
if ($parameter)
|
164
|
+
{ push @parameterLines, $parameter; };
|
165
|
+
|
166
|
+
$parameter = undef;
|
167
|
+
}
|
168
|
+
elsif ($braceLevel > 1)
|
169
|
+
{
|
170
|
+
$parameter .= $token;
|
171
|
+
}
|
172
|
+
else
|
173
|
+
{
|
174
|
+
$beforeParameters .= $token;
|
175
|
+
};
|
176
|
+
}
|
177
|
+
|
178
|
+
else
|
179
|
+
{
|
180
|
+
if ($braceLevel > 0)
|
181
|
+
{ $parameter .= $token; }
|
182
|
+
else
|
183
|
+
{ $beforeParameters .= $token; };
|
184
|
+
};
|
185
|
+
};
|
186
|
+
|
187
|
+
foreach my $part (\$beforeParameters, \$afterParameters)
|
188
|
+
{
|
189
|
+
$$part =~ s/^ //;
|
190
|
+
$$part =~ s/ $//;
|
191
|
+
};
|
192
|
+
|
193
|
+
my $prototypeObject = NaturalDocs::Languages::Prototype->New($beforeParameters, $afterParameters);
|
194
|
+
|
195
|
+
|
196
|
+
# Parse the actual parameters.
|
197
|
+
|
198
|
+
foreach my $parameterLine (@parameterLines)
|
199
|
+
{
|
200
|
+
$prototypeObject->AddParameter( $self->ParseParameterLine($parameterLine) );
|
201
|
+
};
|
202
|
+
|
203
|
+
return $prototypeObject;
|
204
|
+
};
|
205
|
+
|
206
|
+
|
207
|
+
#
|
208
|
+
# Function: ParseParameterLine
|
209
|
+
#
|
210
|
+
# Parses a prototype parameter line and returns it as a <NaturalDocs::Languages::Prototype::Parameter> object.
|
211
|
+
#
|
212
|
+
sub ParseParameterLine #(line)
|
213
|
+
{
|
214
|
+
my ($self, $line) = @_;
|
215
|
+
return NaturalDocs::Languages::Prototype::Parameter->New(undef, undef, $line, undef, undef, undef);
|
216
|
+
};
|
217
|
+
|
218
|
+
|
219
|
+
1;
|
@@ -0,0 +1,3406 @@
|
|
1
|
+
###############################################################################
|
2
|
+
#
|
3
|
+
# Package: NaturalDocs::Menu
|
4
|
+
#
|
5
|
+
###############################################################################
|
6
|
+
#
|
7
|
+
# A package handling the menu's contents and state.
|
8
|
+
#
|
9
|
+
# Usage and Dependencies:
|
10
|
+
#
|
11
|
+
# - The <Event Handlers> can be called by <NaturalDocs::Project> immediately.
|
12
|
+
#
|
13
|
+
# - Prior to initialization, <NaturalDocs::Project> must be initialized, and all files that have been changed must be run
|
14
|
+
# through <NaturalDocs::Parser->ParseForInformation()>.
|
15
|
+
#
|
16
|
+
# - To initialize, call <LoadAndUpdate()>. Afterwards, all other functions are available. Also, <LoadAndUpdate()> will
|
17
|
+
# call <NaturalDocs::Settings->GenerateDirectoryNames()>.
|
18
|
+
#
|
19
|
+
# - To save the changes back to disk, call <Save()>.
|
20
|
+
#
|
21
|
+
###############################################################################
|
22
|
+
|
23
|
+
# This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure
|
24
|
+
# Natural Docs is licensed under the GPL
|
25
|
+
|
26
|
+
use Tie::RefHash;
|
27
|
+
|
28
|
+
use NaturalDocs::Menu::Entry;
|
29
|
+
|
30
|
+
use strict;
|
31
|
+
use integer;
|
32
|
+
|
33
|
+
package NaturalDocs::Menu;
|
34
|
+
|
35
|
+
|
36
|
+
#
|
37
|
+
# Constants: Constants
|
38
|
+
#
|
39
|
+
# MAXFILESINGROUP - The maximum number of file entries that can be present in a group before it becomes a candidate for
|
40
|
+
# sub-grouping.
|
41
|
+
# MINFILESINNEWGROUP - The minimum number of file entries that must be present in a group before it will be automatically
|
42
|
+
# created. This is *not* the number of files that must be in a group before it's deleted.
|
43
|
+
#
|
44
|
+
use constant MAXFILESINGROUP => 6;
|
45
|
+
use constant MINFILESINNEWGROUP => 3;
|
46
|
+
|
47
|
+
|
48
|
+
###############################################################################
|
49
|
+
# Group: Variables
|
50
|
+
|
51
|
+
|
52
|
+
#
|
53
|
+
# bool: hasChanged
|
54
|
+
#
|
55
|
+
# Whether the menu changed or not, regardless of why.
|
56
|
+
#
|
57
|
+
my $hasChanged;
|
58
|
+
|
59
|
+
|
60
|
+
#
|
61
|
+
# Object: menu
|
62
|
+
#
|
63
|
+
# The parsed menu file. Is stored as a <MENU_GROUP> <NaturalDocs::Menu::Entry> object, with the top-level entries being
|
64
|
+
# stored as the group's content. This is done because it makes a number of functions simpler to implement, plus it allows group
|
65
|
+
# flags to be set on the top-level. However, it is exposed externally via <Content()> as an arrayref.
|
66
|
+
#
|
67
|
+
# This structure will only contain objects for <MENU_FILE>, <MENU_GROUP>, <MENU_TEXT>, <MENU_LINK>, and
|
68
|
+
# <MENU_INDEX> entries. Other types, such as <MENU_TITLE>, are stored in variables such as <title>.
|
69
|
+
#
|
70
|
+
my $menu;
|
71
|
+
|
72
|
+
#
|
73
|
+
# hash: defaultTitlesChanged
|
74
|
+
#
|
75
|
+
# An existence hash of default titles that have changed, since <OnDefaultTitleChange()> will be called before
|
76
|
+
# <LoadAndUpdate()>. Collects them to be applied later. The keys are the <FileNames>.
|
77
|
+
#
|
78
|
+
my %defaultTitlesChanged;
|
79
|
+
|
80
|
+
#
|
81
|
+
# String: title
|
82
|
+
#
|
83
|
+
# The title of the menu.
|
84
|
+
#
|
85
|
+
my $title;
|
86
|
+
|
87
|
+
#
|
88
|
+
# String: subTitle
|
89
|
+
#
|
90
|
+
# The sub-title of the menu.
|
91
|
+
#
|
92
|
+
my $subTitle;
|
93
|
+
|
94
|
+
#
|
95
|
+
# String: footer
|
96
|
+
#
|
97
|
+
# The footer for the documentation.
|
98
|
+
#
|
99
|
+
my $footer;
|
100
|
+
|
101
|
+
#
|
102
|
+
# String: timestampText
|
103
|
+
#
|
104
|
+
# The timestamp for the documentation, stored as the final output text.
|
105
|
+
#
|
106
|
+
my $timestampText;
|
107
|
+
|
108
|
+
#
|
109
|
+
# String: timestampCode
|
110
|
+
#
|
111
|
+
# The timestamp for the documentation, storted as the symbolic code.
|
112
|
+
#
|
113
|
+
my $timestampCode;
|
114
|
+
|
115
|
+
#
|
116
|
+
# hash: indexes
|
117
|
+
#
|
118
|
+
# An existence hash of all the defined index <TopicTypes> appearing in the menu.
|
119
|
+
#
|
120
|
+
my %indexes;
|
121
|
+
|
122
|
+
#
|
123
|
+
# hash: previousIndexes
|
124
|
+
#
|
125
|
+
# An existence hash of all the index <TopicTypes> that appeared in the menu last time.
|
126
|
+
#
|
127
|
+
my %previousIndexes;
|
128
|
+
|
129
|
+
#
|
130
|
+
# hash: bannedIndexes
|
131
|
+
#
|
132
|
+
# An existence hash of all the index <TopicTypes> that the user has manually deleted, and thus should not be added back to
|
133
|
+
# the menu automatically.
|
134
|
+
#
|
135
|
+
my %bannedIndexes;
|
136
|
+
|
137
|
+
|
138
|
+
###############################################################################
|
139
|
+
# Group: Files
|
140
|
+
|
141
|
+
#
|
142
|
+
# File: Menu.txt
|
143
|
+
#
|
144
|
+
# The file used to generate the menu.
|
145
|
+
#
|
146
|
+
# Format:
|
147
|
+
#
|
148
|
+
# The file is plain text. Blank lines can appear anywhere and are ignored. Tags and their content must be completely
|
149
|
+
# contained on one line with the exception of Group's braces. All values in brackets below are encoded with entity characters.
|
150
|
+
#
|
151
|
+
# > # [comment]
|
152
|
+
#
|
153
|
+
# The file supports single-line comments via #. They can appear alone on a line or after content.
|
154
|
+
#
|
155
|
+
# > Format: [version]
|
156
|
+
# > Title: [title]
|
157
|
+
# > SubTitle: [subtitle]
|
158
|
+
# > Footer: [footer]
|
159
|
+
# > Timestamp: [timestamp code]
|
160
|
+
#
|
161
|
+
# The file format version, menu title, subtitle, footer, and timestamp are specified as above. Each can only be specified once,
|
162
|
+
# with subsequent ones being ignored. Subtitle is ignored if Title is not present. Format must be the first entry in the file. If
|
163
|
+
# it's not present, it's assumed the menu is from version 0.95 or earlier, since it was added with 1.0.
|
164
|
+
#
|
165
|
+
# The timestamp code is as follows.
|
166
|
+
#
|
167
|
+
# m - Single digit month, where applicable. January is "1".
|
168
|
+
# mm - Always double digit month. January is "01".
|
169
|
+
# mon - Short month word. January is "Jan".
|
170
|
+
# month - Long month word. January is "January".
|
171
|
+
# d - Single digit day, where applicable. 1 is "1".
|
172
|
+
# dd - Always double digit day. 1 is "01".
|
173
|
+
# day - Day with text extension. 1 is "1st".
|
174
|
+
# yy - Double digit year. 2006 is "06".
|
175
|
+
# yyyy - Four digit year. 2006 is "2006".
|
176
|
+
# year - Four digit year. 2006 is "2006".
|
177
|
+
#
|
178
|
+
# Anything else is left literal in the output.
|
179
|
+
#
|
180
|
+
# > File: [title] ([file name])
|
181
|
+
# > File: [title] (auto-title, [file name])
|
182
|
+
# > File: [title] (no auto-title, [file name])
|
183
|
+
#
|
184
|
+
# Files are specified as above. If there is only one input directory, file names are relative. Otherwise they are absolute.
|
185
|
+
# If "no auto-title" is specified, the title on the line is used. If not, the title is ignored and the
|
186
|
+
# default file title is used instead. Auto-title defaults to on, so specifying "auto-title" is for compatibility only.
|
187
|
+
#
|
188
|
+
# > Group: [title]
|
189
|
+
# > Group: [title] { ... }
|
190
|
+
#
|
191
|
+
# Groups are specified as above. If no braces are specified, the group's content is everything that follows until the end of the
|
192
|
+
# file, the next group (braced or unbraced), or the closing brace of a parent group. Group braces are the only things in this
|
193
|
+
# file that can span multiple lines.
|
194
|
+
#
|
195
|
+
# There is no limitations on where the braces can appear. The opening brace can appear after the group tag, on its own line,
|
196
|
+
# or preceding another tag on a line. Similarly, the closing brace can appear after another tag or on its own line. Being
|
197
|
+
# bitchy here would just get in the way of quick and dirty editing; the package will clean it up automatically when it writes it
|
198
|
+
# back to disk.
|
199
|
+
#
|
200
|
+
# > Text: [text]
|
201
|
+
#
|
202
|
+
# Arbitrary text is specified as above. As with other tags, everything must be contained on the same line.
|
203
|
+
#
|
204
|
+
# > Link: [URL]
|
205
|
+
# > Link: [title] ([URL])
|
206
|
+
#
|
207
|
+
# External links can be specified as above. If the titled form is not used, the URL is used as the title.
|
208
|
+
#
|
209
|
+
# > Index: [name]
|
210
|
+
# > [topic type name] Index: [name]
|
211
|
+
#
|
212
|
+
# Indexes are specified as above. The topic type names can be either singular or plural. General is assumed if not specified.
|
213
|
+
#
|
214
|
+
# > Don't Index: [topic type name]
|
215
|
+
# > Don't Index: [topic type name], [topic type name], ...
|
216
|
+
#
|
217
|
+
# The option above prevents indexes that exist but are not on the menu from being automatically added.
|
218
|
+
#
|
219
|
+
# > Data: [number]([obscured data])
|
220
|
+
#
|
221
|
+
# Used to store non-user editable data.
|
222
|
+
#
|
223
|
+
# > Data: 1([obscured: [directory name]///[input directory]])
|
224
|
+
#
|
225
|
+
# When there is more than one directory, these lines store the input directories used in the last run and their names. This
|
226
|
+
# allows menu files to be shared across machines since the names will be consistent and the directories can be used to convert
|
227
|
+
# filenames to the local machine's paths. We don't want this user-editable because they may think changing it changes the
|
228
|
+
# input directories, when it doesn't. Also, changing it without changing all the paths screws up resolving.
|
229
|
+
#
|
230
|
+
# > Data: 2([obscured: [directory name])
|
231
|
+
#
|
232
|
+
# When there is only one directory and its name is not "default", this stores the name.
|
233
|
+
#
|
234
|
+
#
|
235
|
+
# Entities:
|
236
|
+
#
|
237
|
+
# & - Ampersand.
|
238
|
+
# &lparen; - Left parenthesis.
|
239
|
+
# &rparen; - Right parenthesis.
|
240
|
+
# { - Left brace.
|
241
|
+
# } - Right brace.
|
242
|
+
#
|
243
|
+
#
|
244
|
+
# Revisions:
|
245
|
+
#
|
246
|
+
# 1.4:
|
247
|
+
#
|
248
|
+
# - Added Timestamp property.
|
249
|
+
# - Values are now encoded with entity characters.
|
250
|
+
#
|
251
|
+
# 1.3:
|
252
|
+
#
|
253
|
+
# - File names are now relative again if there is only one input directory.
|
254
|
+
# - Data: 2(...) added.
|
255
|
+
# - Can't use synonyms like "copyright" for "footer" or "sub-title" for "subtitle".
|
256
|
+
# - "Don't Index" line now requires commas to separate them, whereas it tolerated just spaces before.
|
257
|
+
#
|
258
|
+
# 1.16:
|
259
|
+
#
|
260
|
+
# - File names are now absolute instead of relative. Prior to 1.16 only one input directory was allowed, so they could be
|
261
|
+
# relative.
|
262
|
+
# - Data keywords introduced to store input directories and their names.
|
263
|
+
#
|
264
|
+
# 1.14:
|
265
|
+
#
|
266
|
+
# - Renamed this file from NaturalDocs_Menu.txt to Menu.txt.
|
267
|
+
#
|
268
|
+
# 1.1:
|
269
|
+
#
|
270
|
+
# - Added the "don't index" line.
|
271
|
+
#
|
272
|
+
# This is also the point where indexes were automatically added and removed, so all index entries from prior revisions
|
273
|
+
# were manually added and are not guaranteed to contain anything.
|
274
|
+
#
|
275
|
+
# 1.0:
|
276
|
+
#
|
277
|
+
# - Added the format line.
|
278
|
+
# - Added the "no auto-title" attribute.
|
279
|
+
# - Changed the file entry default to auto-title.
|
280
|
+
#
|
281
|
+
# This is also the point where auto-organization and better auto-titles were introduced. All groups in prior revisions were
|
282
|
+
# manually added, with the exception of a top-level Other group where new files were automatically added if there were
|
283
|
+
# groups defined.
|
284
|
+
#
|
285
|
+
# Break in support:
|
286
|
+
#
|
287
|
+
# Releases prior to 1.0 are no longer supported. Why?
|
288
|
+
#
|
289
|
+
# - They don't have a Format: line, which is required by <NaturalDocs::ConfigFile>, although I could work around this
|
290
|
+
# if I needed to.
|
291
|
+
# - No significant number of downloads for pre-1.0 releases.
|
292
|
+
# - Code simplification. I don't have to bridge the conversion from manual-only menu organization to automatic.
|
293
|
+
#
|
294
|
+
# 0.9:
|
295
|
+
#
|
296
|
+
# - Added index entries.
|
297
|
+
#
|
298
|
+
|
299
|
+
#
|
300
|
+
# File: PreviousMenuState.nd
|
301
|
+
#
|
302
|
+
# The file used to store the previous state of the menu so as to detect changes.
|
303
|
+
#
|
304
|
+
#
|
305
|
+
# Format:
|
306
|
+
#
|
307
|
+
# > [BINARY_FORMAT]
|
308
|
+
# > [VersionInt: app version]
|
309
|
+
#
|
310
|
+
# First is the standard <BINARY_FORMAT> <VersionInt> header.
|
311
|
+
#
|
312
|
+
# > [UInt8: 0 (end group)]
|
313
|
+
# > [UInt8: MENU_FILE] [UInt8: noAutoTitle] [AString16: title] [AString16: target]
|
314
|
+
# > [UInt8: MENU_GROUP] [AString16: title]
|
315
|
+
# > [UInt8: MENU_INDEX] [AString16: title] [AString16: topic type]
|
316
|
+
# > [UInt8: MENU_LINK] [AString16: title] [AString16: url]
|
317
|
+
# > [UInt8: MENU_TEXT] [AString16: text]
|
318
|
+
#
|
319
|
+
# The first UInt8 of each following line is either zero or one of the <Menu Entry Types>. What follows is contextual.
|
320
|
+
#
|
321
|
+
# There are no entries for title, subtitle, or footer. Only the entries present in <menu>.
|
322
|
+
#
|
323
|
+
# See Also:
|
324
|
+
#
|
325
|
+
# <File Format Conventions>
|
326
|
+
#
|
327
|
+
# Dependencies:
|
328
|
+
#
|
329
|
+
# - Because the type is represented by a UInt8, the <Menu Entry Types> must all be <= 255.
|
330
|
+
#
|
331
|
+
# Revisions:
|
332
|
+
#
|
333
|
+
# 1.3:
|
334
|
+
#
|
335
|
+
# - The topic type following the <MENU_INDEX> entries were changed from UInt8s to AString16s, since <TopicTypes>
|
336
|
+
# were switched from integer constants to strings. You can still convert the old to the new via
|
337
|
+
# <NaturalDocs::Topics->TypeFromLegacy()>.
|
338
|
+
#
|
339
|
+
# 1.16:
|
340
|
+
#
|
341
|
+
# - The file targets are now absolute. Prior to 1.16, they were relative to the input directory since only one was allowed.
|
342
|
+
#
|
343
|
+
# 1.14:
|
344
|
+
#
|
345
|
+
# - The file was renamed from NaturalDocs.m to PreviousMenuState.nd and moved into the Data subdirectory.
|
346
|
+
#
|
347
|
+
# 1.0:
|
348
|
+
#
|
349
|
+
# - The file's format was completely redone. Prior to 1.0, the file was a text file consisting of the app version and a line
|
350
|
+
# which was a tab-separated list of the indexes present in the menu. * meant the general index.
|
351
|
+
#
|
352
|
+
# Break in support:
|
353
|
+
#
|
354
|
+
# Pre-1.0 files are no longer supported. There was no significant number of downloads for pre-1.0 releases, and this
|
355
|
+
# eliminates a separate code path for them.
|
356
|
+
#
|
357
|
+
# 0.95:
|
358
|
+
#
|
359
|
+
# - Change the file version to match the app version. Prior to 0.95, the version line was 1. Test for "1" instead of "1.0" to
|
360
|
+
# distinguish.
|
361
|
+
#
|
362
|
+
# 0.9:
|
363
|
+
#
|
364
|
+
# - The file was added to the project. Prior to 0.9, it didn't exist.
|
365
|
+
#
|
366
|
+
|
367
|
+
|
368
|
+
###############################################################################
|
369
|
+
# Group: File Functions
|
370
|
+
|
371
|
+
#
|
372
|
+
# Function: LoadAndUpdate
|
373
|
+
#
|
374
|
+
# Loads the menu file from disk and updates it. Will add, remove, rearrange, and remove auto-titling from entries as
|
375
|
+
# necessary. Will also call <NaturalDocs::Settings->GenerateDirectoryNames()>.
|
376
|
+
#
|
377
|
+
sub LoadAndUpdate
|
378
|
+
{
|
379
|
+
my ($self) = @_;
|
380
|
+
|
381
|
+
my ($inputDirectoryNames, $relativeFiles, $onlyDirectoryName) = $self->LoadMenuFile();
|
382
|
+
|
383
|
+
my $errorCount = NaturalDocs::ConfigFile->ErrorCount();
|
384
|
+
if ($errorCount)
|
385
|
+
{
|
386
|
+
NaturalDocs::ConfigFile->PrintErrorsAndAnnotateFile();
|
387
|
+
NaturalDocs::Error->SoftDeath('There ' . ($errorCount == 1 ? 'is an error' : 'are ' . $errorCount . ' errors')
|
388
|
+
. ' in ' . NaturalDocs::Project->UserConfigFile('Menu.txt'));
|
389
|
+
};
|
390
|
+
|
391
|
+
# If the menu has a timestamp and today is a different day than the last time Natural Docs was run, we have to count it as the
|
392
|
+
# menu changing.
|
393
|
+
if (defined $timestampCode)
|
394
|
+
{
|
395
|
+
my (undef, undef, undef, $currentDay, $currentMonth, $currentYear) = localtime();
|
396
|
+
my (undef, undef, undef, $lastDay, $lastMonth, $lastYear) =
|
397
|
+
localtime( (stat( NaturalDocs::Project->DataFile('PreviousMenuState.nd') ))[9] );
|
398
|
+
# This should be okay if the previous menu state file doesn't exist.
|
399
|
+
|
400
|
+
if ($currentDay != $lastDay || $currentMonth != $lastMonth || $currentYear != $lastYear)
|
401
|
+
{ $hasChanged = 1; };
|
402
|
+
};
|
403
|
+
|
404
|
+
|
405
|
+
if ($relativeFiles)
|
406
|
+
{
|
407
|
+
my $inputDirectory = $self->ResolveRelativeInputDirectories($onlyDirectoryName);
|
408
|
+
|
409
|
+
if ($onlyDirectoryName)
|
410
|
+
{ $inputDirectoryNames = { $inputDirectory => $onlyDirectoryName }; };
|
411
|
+
}
|
412
|
+
else
|
413
|
+
{ $self->ResolveInputDirectories($inputDirectoryNames); };
|
414
|
+
|
415
|
+
NaturalDocs::Settings->GenerateDirectoryNames($inputDirectoryNames);
|
416
|
+
|
417
|
+
my $filesInMenu = $self->FilesInMenu();
|
418
|
+
|
419
|
+
my ($previousMenu, $previousIndexes, $previousFiles) = $self->LoadPreviousMenuStateFile();
|
420
|
+
|
421
|
+
if (defined $previousIndexes)
|
422
|
+
{ %previousIndexes = %$previousIndexes; };
|
423
|
+
|
424
|
+
if (defined $previousFiles)
|
425
|
+
{ $self->LockUserTitleChanges($previousFiles); };
|
426
|
+
|
427
|
+
# Don't need these anymore. We keep this level of detail because it may be used more in the future.
|
428
|
+
$previousMenu = undef;
|
429
|
+
$previousFiles = undef;
|
430
|
+
$previousIndexes = undef;
|
431
|
+
|
432
|
+
# We flag title changes instead of actually performing them at this point for two reasons. First, contents of groups are still
|
433
|
+
# subject to change, which would affect the generated titles. Second, we haven't detected the sort order yet. Changing titles
|
434
|
+
# could make groups appear unalphabetized when they were beforehand.
|
435
|
+
|
436
|
+
my $updateAllTitles;
|
437
|
+
|
438
|
+
# If the menu file changed, we can't be sure which groups changed and which didn't without a comparison, which really isn't
|
439
|
+
# worth the trouble. So we regenerate all the titles instead.
|
440
|
+
if (NaturalDocs::Project->UserConfigFileStatus('Menu.txt') == ::FILE_CHANGED())
|
441
|
+
{ $updateAllTitles = 1; }
|
442
|
+
else
|
443
|
+
{ $self->FlagAutoTitleChanges(); };
|
444
|
+
|
445
|
+
# We add new files before deleting old files so their presence still affects the grouping. If we deleted old files first, it could
|
446
|
+
# throw off where to place the new ones.
|
447
|
+
|
448
|
+
$self->AutoPlaceNewFiles($filesInMenu);
|
449
|
+
|
450
|
+
my $numberRemoved = $self->RemoveDeadFiles();
|
451
|
+
|
452
|
+
$self->CheckForTrashedMenu(scalar keys %$filesInMenu, $numberRemoved);
|
453
|
+
|
454
|
+
# Don't ban indexes if they deleted Menu.txt. They may have not deleted PreviousMenuState.nd and we don't want everything
|
455
|
+
# to be banned because of it.
|
456
|
+
if (NaturalDocs::Project->UserConfigFileStatus('Menu.txt') != ::FILE_DOESNTEXIST())
|
457
|
+
{ $self->BanAndUnbanIndexes(); };
|
458
|
+
|
459
|
+
# Index groups need to be detected before adding new ones.
|
460
|
+
|
461
|
+
$self->DetectIndexGroups();
|
462
|
+
|
463
|
+
$self->AddAndRemoveIndexes();
|
464
|
+
|
465
|
+
# We wait until after new files are placed to remove dead groups because a new file may save a group.
|
466
|
+
|
467
|
+
$self->RemoveDeadGroups();
|
468
|
+
|
469
|
+
$self->CreateDirectorySubGroups();
|
470
|
+
|
471
|
+
# We detect the sort before regenerating the titles so it doesn't get thrown off by changes. However, we do it after deleting
|
472
|
+
# dead entries and moving things into subgroups because their removal may bump it into a stronger sort category (i.e.
|
473
|
+
# SORTFILESANDGROUPS instead of just SORTFILES.) New additions don't factor into the sort.
|
474
|
+
|
475
|
+
$self->DetectOrder($updateAllTitles);
|
476
|
+
|
477
|
+
$self->GenerateAutoFileTitles($updateAllTitles);
|
478
|
+
|
479
|
+
$self->ResortGroups($updateAllTitles);
|
480
|
+
|
481
|
+
|
482
|
+
# Don't need this anymore.
|
483
|
+
%defaultTitlesChanged = ( );
|
484
|
+
};
|
485
|
+
|
486
|
+
|
487
|
+
#
|
488
|
+
# Function: Save
|
489
|
+
#
|
490
|
+
# Writes the changes to the menu files.
|
491
|
+
#
|
492
|
+
sub Save
|
493
|
+
{
|
494
|
+
my ($self) = @_;
|
495
|
+
|
496
|
+
if ($hasChanged)
|
497
|
+
{
|
498
|
+
$self->SaveMenuFile();
|
499
|
+
$self->SavePreviousMenuStateFile();
|
500
|
+
};
|
501
|
+
};
|
502
|
+
|
503
|
+
|
504
|
+
###############################################################################
|
505
|
+
# Group: Information Functions
|
506
|
+
|
507
|
+
#
|
508
|
+
# Function: HasChanged
|
509
|
+
#
|
510
|
+
# Returns whether the menu has changed or not.
|
511
|
+
#
|
512
|
+
sub HasChanged
|
513
|
+
{ return $hasChanged; };
|
514
|
+
|
515
|
+
#
|
516
|
+
# Function: Content
|
517
|
+
#
|
518
|
+
# Returns the parsed menu as an arrayref of <NaturalDocs::Menu::Entry> objects. Do not change the arrayref.
|
519
|
+
#
|
520
|
+
# The arrayref will only contain <MENU_FILE>, <MENU_GROUP>, <MENU_INDEX>, <MENU_TEXT>, and <MENU_LINK>
|
521
|
+
# entries. Entries such as <MENU_TITLE> are parsed out and are only accessible via functions such as <Title()>.
|
522
|
+
#
|
523
|
+
sub Content
|
524
|
+
{ return $menu->GroupContent(); };
|
525
|
+
|
526
|
+
#
|
527
|
+
# Function: Title
|
528
|
+
#
|
529
|
+
# Returns the title of the menu, or undef if none.
|
530
|
+
#
|
531
|
+
sub Title
|
532
|
+
{ return $title; };
|
533
|
+
|
534
|
+
#
|
535
|
+
# Function: SubTitle
|
536
|
+
#
|
537
|
+
# Returns the sub-title of the menu, or undef if none.
|
538
|
+
#
|
539
|
+
sub SubTitle
|
540
|
+
{ return $subTitle; };
|
541
|
+
|
542
|
+
#
|
543
|
+
# Function: Footer
|
544
|
+
#
|
545
|
+
# Returns the footer of the documentation, or undef if none.
|
546
|
+
#
|
547
|
+
sub Footer
|
548
|
+
{ return $footer; };
|
549
|
+
|
550
|
+
#
|
551
|
+
# Function: TimeStamp
|
552
|
+
#
|
553
|
+
# Returns the timestamp text of the documentation, or undef if none.
|
554
|
+
#
|
555
|
+
sub TimeStamp
|
556
|
+
{ return $timestampText; };
|
557
|
+
|
558
|
+
#
|
559
|
+
# Function: Indexes
|
560
|
+
#
|
561
|
+
# Returns an existence hashref of all the index <TopicTypes> appearing in the menu. Do not change the hashref.
|
562
|
+
#
|
563
|
+
sub Indexes
|
564
|
+
{ return \%indexes; };
|
565
|
+
|
566
|
+
#
|
567
|
+
# Function: PreviousIndexes
|
568
|
+
#
|
569
|
+
# Returns an existence hashref of all the index <TopicTypes> that previously appeared in the menu. Do not change the
|
570
|
+
# hashref.
|
571
|
+
#
|
572
|
+
sub PreviousIndexes
|
573
|
+
{ return \%previousIndexes; };
|
574
|
+
|
575
|
+
|
576
|
+
#
|
577
|
+
# Function: FilesInMenu
|
578
|
+
#
|
579
|
+
# Returns a hashref of all the files present in the menu. The keys are the <FileNames>, and the values are references to their
|
580
|
+
# <NaturalDocs::Menu::Entry> objects.
|
581
|
+
#
|
582
|
+
sub FilesInMenu
|
583
|
+
{
|
584
|
+
my ($self) = @_;
|
585
|
+
|
586
|
+
my @groupStack = ( $menu );
|
587
|
+
my $filesInMenu = { };
|
588
|
+
|
589
|
+
while (scalar @groupStack)
|
590
|
+
{
|
591
|
+
my $currentGroup = pop @groupStack;
|
592
|
+
my $currentGroupContent = $currentGroup->GroupContent();
|
593
|
+
|
594
|
+
foreach my $entry (@$currentGroupContent)
|
595
|
+
{
|
596
|
+
if ($entry->Type() == ::MENU_GROUP())
|
597
|
+
{ push @groupStack, $entry; }
|
598
|
+
elsif ($entry->Type() == ::MENU_FILE())
|
599
|
+
{ $filesInMenu->{ $entry->Target() } = $entry; };
|
600
|
+
};
|
601
|
+
};
|
602
|
+
|
603
|
+
return $filesInMenu;
|
604
|
+
};
|
605
|
+
|
606
|
+
|
607
|
+
|
608
|
+
###############################################################################
|
609
|
+
# Group: Event Handlers
|
610
|
+
#
|
611
|
+
# These functions are called by <NaturalDocs::Project> only. You don't need to worry about calling them. For example, when
|
612
|
+
# changing the default menu title of a file, you only need to call <NaturalDocs::Project->SetDefaultMenuTitle()>. That function
|
613
|
+
# will handle calling <OnDefaultTitleChange()>.
|
614
|
+
|
615
|
+
|
616
|
+
#
|
617
|
+
# Function: OnDefaultTitleChange
|
618
|
+
#
|
619
|
+
# Called by <NaturalDocs::Project> if the default menu title of a source file has changed.
|
620
|
+
#
|
621
|
+
# Parameters:
|
622
|
+
#
|
623
|
+
# file - The source <FileName> that had its default menu title changed.
|
624
|
+
#
|
625
|
+
sub OnDefaultTitleChange #(file)
|
626
|
+
{
|
627
|
+
my ($self, $file) = @_;
|
628
|
+
|
629
|
+
# Collect them for later. We'll deal with them in LoadAndUpdate().
|
630
|
+
|
631
|
+
$defaultTitlesChanged{$file} = 1;
|
632
|
+
};
|
633
|
+
|
634
|
+
|
635
|
+
|
636
|
+
###############################################################################
|
637
|
+
# Group: Support Functions
|
638
|
+
|
639
|
+
|
640
|
+
#
|
641
|
+
# Function: LoadMenuFile
|
642
|
+
#
|
643
|
+
# Loads and parses the menu file <Menu.txt>. This will fill <menu>, <title>, <subTitle>, <footer>, <timestampText>,
|
644
|
+
# <timestampCode>, <indexes>, and <bannedIndexes>. If there are any errors in the file, they will be recorded with
|
645
|
+
# <NaturalDocs::ConfigFile->AddError()>.
|
646
|
+
#
|
647
|
+
# Returns:
|
648
|
+
#
|
649
|
+
# The array ( inputDirectories, relativeFiles, onlyDirectoryName ) or an empty array if the file doesn't exist.
|
650
|
+
#
|
651
|
+
# inputDirectories - A hashref of all the input directories and their names stored in the menu file. The keys are the
|
652
|
+
# directories and the values are their names. Undef if none.
|
653
|
+
# relativeFiles - Whether the menu uses relative file names.
|
654
|
+
# onlyDirectoryName - The name of the input directory if there is only one.
|
655
|
+
#
|
656
|
+
sub LoadMenuFile
|
657
|
+
{
|
658
|
+
my ($self) = @_;
|
659
|
+
|
660
|
+
my $inputDirectories = { };
|
661
|
+
my $relativeFiles;
|
662
|
+
my $onlyDirectoryName;
|
663
|
+
|
664
|
+
# A stack of Menu::Entry object references as we move through the groups.
|
665
|
+
my @groupStack;
|
666
|
+
|
667
|
+
$menu = NaturalDocs::Menu::Entry->New(::MENU_GROUP(), undef, undef, undef);
|
668
|
+
my $currentGroup = $menu;
|
669
|
+
|
670
|
+
# Whether we're currently in a braceless group, since we'd have to find the implied end rather than an explicit one.
|
671
|
+
my $inBracelessGroup;
|
672
|
+
|
673
|
+
# Whether we're right after a group token, which is the only place there can be an opening brace.
|
674
|
+
my $afterGroupToken;
|
675
|
+
|
676
|
+
my $version;
|
677
|
+
|
678
|
+
if ($version = NaturalDocs::ConfigFile->Open(NaturalDocs::Project->UserConfigFile('Menu.txt'), 1))
|
679
|
+
{
|
680
|
+
# We don't check if the menu file is from a future version because we can't just throw it out and regenerate it like we can
|
681
|
+
# with other data files. So we just keep going regardless. Any syntactic differences will show up as errors.
|
682
|
+
|
683
|
+
while (my ($keyword, $value, $comment) = NaturalDocs::ConfigFile->GetLine())
|
684
|
+
{
|
685
|
+
# Check for an opening brace after a group token. This has to be separate from the rest of the code because the flag
|
686
|
+
# needs to be reset after every line.
|
687
|
+
if ($afterGroupToken)
|
688
|
+
{
|
689
|
+
$afterGroupToken = undef;
|
690
|
+
|
691
|
+
if ($keyword eq '{')
|
692
|
+
{
|
693
|
+
$inBracelessGroup = undef;
|
694
|
+
next;
|
695
|
+
}
|
696
|
+
else
|
697
|
+
{ $inBracelessGroup = 1; };
|
698
|
+
};
|
699
|
+
|
700
|
+
|
701
|
+
# Now on to the real code.
|
702
|
+
|
703
|
+
if ($keyword eq 'file')
|
704
|
+
{
|
705
|
+
my $flags = 0;
|
706
|
+
|
707
|
+
if ($value =~ /^(.+)\(([^\(]+)\)$/)
|
708
|
+
{
|
709
|
+
my ($title, $file) = ($1, $2);
|
710
|
+
|
711
|
+
$title =~ s/ +$//;
|
712
|
+
|
713
|
+
# Check for auto-title modifier.
|
714
|
+
if ($file =~ /^((?:no )?auto-title, ?)(.+)$/i)
|
715
|
+
{
|
716
|
+
my $modifier;
|
717
|
+
($modifier, $file) = ($1, $2);
|
718
|
+
|
719
|
+
if ($modifier =~ /^no/i)
|
720
|
+
{ $flags |= ::MENU_FILE_NOAUTOTITLE(); };
|
721
|
+
};
|
722
|
+
|
723
|
+
my $entry = NaturalDocs::Menu::Entry->New(::MENU_FILE(), $self->RestoreAmpChars($title),
|
724
|
+
$self->RestoreAmpChars($file), $flags);
|
725
|
+
|
726
|
+
$currentGroup->PushToGroup($entry);
|
727
|
+
}
|
728
|
+
else
|
729
|
+
{ NaturalDocs::ConfigFile->AddError('File lines must be in the format "File: [title] ([location])"'); };
|
730
|
+
}
|
731
|
+
|
732
|
+
|
733
|
+
elsif ($keyword eq 'group')
|
734
|
+
{
|
735
|
+
# End a braceless group, if we were in one.
|
736
|
+
if ($inBracelessGroup)
|
737
|
+
{
|
738
|
+
$currentGroup = pop @groupStack;
|
739
|
+
$inBracelessGroup = undef;
|
740
|
+
};
|
741
|
+
|
742
|
+
my $entry = NaturalDocs::Menu::Entry->New(::MENU_GROUP(), $self->RestoreAmpChars($value), undef, undef);
|
743
|
+
|
744
|
+
$currentGroup->PushToGroup($entry);
|
745
|
+
|
746
|
+
push @groupStack, $currentGroup;
|
747
|
+
$currentGroup = $entry;
|
748
|
+
|
749
|
+
$afterGroupToken = 1;
|
750
|
+
}
|
751
|
+
|
752
|
+
|
753
|
+
elsif ($keyword eq '{')
|
754
|
+
{
|
755
|
+
NaturalDocs::ConfigFile->AddError('Opening braces are only allowed after Group tags.');
|
756
|
+
}
|
757
|
+
|
758
|
+
|
759
|
+
elsif ($keyword eq '}')
|
760
|
+
{
|
761
|
+
# End a braceless group, if we were in one.
|
762
|
+
if ($inBracelessGroup)
|
763
|
+
{
|
764
|
+
$currentGroup = pop @groupStack;
|
765
|
+
$inBracelessGroup = undef;
|
766
|
+
};
|
767
|
+
|
768
|
+
# End a braced group too.
|
769
|
+
if (scalar @groupStack)
|
770
|
+
{ $currentGroup = pop @groupStack; }
|
771
|
+
else
|
772
|
+
{ NaturalDocs::ConfigFile->AddError('Unmatched closing brace.'); };
|
773
|
+
}
|
774
|
+
|
775
|
+
|
776
|
+
elsif ($keyword eq 'title')
|
777
|
+
{
|
778
|
+
if (!defined $title)
|
779
|
+
{ $title = $self->RestoreAmpChars($value); }
|
780
|
+
else
|
781
|
+
{ NaturalDocs::ConfigFile->AddError('Title can only be defined once.'); };
|
782
|
+
}
|
783
|
+
|
784
|
+
|
785
|
+
elsif ($keyword eq 'subtitle')
|
786
|
+
{
|
787
|
+
if (defined $title)
|
788
|
+
{
|
789
|
+
if (!defined $subTitle)
|
790
|
+
{ $subTitle = $self->RestoreAmpChars($value); }
|
791
|
+
else
|
792
|
+
{ NaturalDocs::ConfigFile->AddError('SubTitle can only be defined once.'); };
|
793
|
+
}
|
794
|
+
else
|
795
|
+
{ NaturalDocs::ConfigFile->AddError('Title must be defined before SubTitle.'); };
|
796
|
+
}
|
797
|
+
|
798
|
+
|
799
|
+
elsif ($keyword eq 'footer')
|
800
|
+
{
|
801
|
+
if (!defined $footer)
|
802
|
+
{ $footer = $self->RestoreAmpChars($value); }
|
803
|
+
else
|
804
|
+
{ NaturalDocs::ConfigFile->AddError('Footer can only be defined once.'); };
|
805
|
+
}
|
806
|
+
|
807
|
+
|
808
|
+
elsif ($keyword eq 'timestamp')
|
809
|
+
{
|
810
|
+
if (!defined $timestampCode)
|
811
|
+
{
|
812
|
+
$timestampCode = $self->RestoreAmpChars($value);
|
813
|
+
$self->GenerateTimestampText();
|
814
|
+
}
|
815
|
+
else
|
816
|
+
{ NaturalDocs::ConfigFile->AddError('Timestamp can only be defined once.'); };
|
817
|
+
}
|
818
|
+
|
819
|
+
|
820
|
+
elsif ($keyword eq 'text')
|
821
|
+
{
|
822
|
+
$currentGroup->PushToGroup( NaturalDocs::Menu::Entry->New(::MENU_TEXT(), $self->RestoreAmpChars($value),
|
823
|
+
undef, undef) );
|
824
|
+
}
|
825
|
+
|
826
|
+
|
827
|
+
elsif ($keyword eq 'link')
|
828
|
+
{
|
829
|
+
my ($title, $url);
|
830
|
+
|
831
|
+
if ($value =~ /^([^\(\)]+?) ?\(([^\)]+)\)$/)
|
832
|
+
{
|
833
|
+
($title, $url) = ($1, $2);
|
834
|
+
}
|
835
|
+
elsif (defined $comment)
|
836
|
+
{
|
837
|
+
$value .= $comment;
|
838
|
+
|
839
|
+
if ($value =~ /^([^\(\)]+?) ?\(([^\)]+)\) ?(?:#.*)?$/)
|
840
|
+
{
|
841
|
+
($title, $url) = ($1, $2);
|
842
|
+
};
|
843
|
+
};
|
844
|
+
|
845
|
+
if ($title)
|
846
|
+
{
|
847
|
+
$currentGroup->PushToGroup( NaturalDocs::Menu::Entry->New(::MENU_LINK(), $self->RestoreAmpChars($title),
|
848
|
+
$self->RestoreAmpChars($url), undef) );
|
849
|
+
}
|
850
|
+
else
|
851
|
+
{ NaturalDocs::ConfigFile->AddError('Link lines must be in the format "Link: [title] ([url])"'); };
|
852
|
+
}
|
853
|
+
|
854
|
+
|
855
|
+
elsif ($keyword eq 'data')
|
856
|
+
{
|
857
|
+
$value =~ /^(\d)\((.*)\)$/;
|
858
|
+
my ($number, $data) = ($1, $2);
|
859
|
+
|
860
|
+
$data = NaturalDocs::ConfigFile->Unobscure($data);
|
861
|
+
|
862
|
+
# The input directory naming convention changed with version 1.32, but NaturalDocs::Settings will handle that
|
863
|
+
# automatically.
|
864
|
+
|
865
|
+
if ($number == 1)
|
866
|
+
{
|
867
|
+
my ($dirName, $inputDir) = split(/\/\/\//, $data, 2);
|
868
|
+
$inputDirectories->{$inputDir} = $dirName;
|
869
|
+
}
|
870
|
+
elsif ($number == 2)
|
871
|
+
{ $onlyDirectoryName = $data; };
|
872
|
+
# Ignore other numbers because it may be from a future format and we don't want to make the user delete it
|
873
|
+
# manually.
|
874
|
+
}
|
875
|
+
|
876
|
+
elsif ($keyword eq "don't index")
|
877
|
+
{
|
878
|
+
my @indexes = split(/, ?/, $value);
|
879
|
+
|
880
|
+
foreach my $index (@indexes)
|
881
|
+
{
|
882
|
+
my $indexType = NaturalDocs::Topics->TypeFromName( $self->RestoreAmpChars($index) );
|
883
|
+
|
884
|
+
if (defined $indexType)
|
885
|
+
{ $bannedIndexes{$indexType} = 1; };
|
886
|
+
};
|
887
|
+
}
|
888
|
+
|
889
|
+
elsif ($keyword eq 'index')
|
890
|
+
{
|
891
|
+
my $entry = NaturalDocs::Menu::Entry->New(::MENU_INDEX(), $self->RestoreAmpChars($value),
|
892
|
+
::TOPIC_GENERAL(), undef);
|
893
|
+
$currentGroup->PushToGroup($entry);
|
894
|
+
|
895
|
+
$indexes{::TOPIC_GENERAL()} = 1;
|
896
|
+
}
|
897
|
+
|
898
|
+
elsif (substr($keyword, -6) eq ' index')
|
899
|
+
{
|
900
|
+
my $index = substr($keyword, 0, -6);
|
901
|
+
my ($indexType, $indexInfo) = NaturalDocs::Topics->NameInfo( $self->RestoreAmpChars($index) );
|
902
|
+
|
903
|
+
if (defined $indexType)
|
904
|
+
{
|
905
|
+
if ($indexInfo->Index())
|
906
|
+
{
|
907
|
+
$indexes{$indexType} = 1;
|
908
|
+
$currentGroup->PushToGroup(
|
909
|
+
NaturalDocs::Menu::Entry->New(::MENU_INDEX(), $self->RestoreAmpChars($value), $indexType, undef) );
|
910
|
+
}
|
911
|
+
else
|
912
|
+
{
|
913
|
+
# If it's on the menu but isn't indexable, the topic setting may have changed out from under it.
|
914
|
+
$hasChanged = 1;
|
915
|
+
};
|
916
|
+
}
|
917
|
+
else
|
918
|
+
{
|
919
|
+
NaturalDocs::ConfigFile->AddError($index . ' is not a valid index type.');
|
920
|
+
};
|
921
|
+
}
|
922
|
+
|
923
|
+
else
|
924
|
+
{
|
925
|
+
NaturalDocs::ConfigFile->AddError(ucfirst($keyword) . ' is not a valid keyword.');
|
926
|
+
};
|
927
|
+
};
|
928
|
+
|
929
|
+
|
930
|
+
# End a braceless group, if we were in one.
|
931
|
+
if ($inBracelessGroup)
|
932
|
+
{
|
933
|
+
$currentGroup = pop @groupStack;
|
934
|
+
$inBracelessGroup = undef;
|
935
|
+
};
|
936
|
+
|
937
|
+
# Close up all open groups.
|
938
|
+
my $openGroups = 0;
|
939
|
+
while (scalar @groupStack)
|
940
|
+
{
|
941
|
+
$currentGroup = pop @groupStack;
|
942
|
+
$openGroups++;
|
943
|
+
};
|
944
|
+
|
945
|
+
if ($openGroups == 1)
|
946
|
+
{ NaturalDocs::ConfigFile->AddError('There is an unclosed group.'); }
|
947
|
+
elsif ($openGroups > 1)
|
948
|
+
{ NaturalDocs::ConfigFile->AddError('There are ' . $openGroups . ' unclosed groups.'); };
|
949
|
+
|
950
|
+
|
951
|
+
if (!scalar keys %$inputDirectories)
|
952
|
+
{
|
953
|
+
$inputDirectories = undef;
|
954
|
+
$relativeFiles = 1;
|
955
|
+
};
|
956
|
+
|
957
|
+
NaturalDocs::ConfigFile->Close();
|
958
|
+
|
959
|
+
return ($inputDirectories, $relativeFiles, $onlyDirectoryName);
|
960
|
+
}
|
961
|
+
|
962
|
+
else
|
963
|
+
{ return ( ); };
|
964
|
+
};
|
965
|
+
|
966
|
+
|
967
|
+
#
|
968
|
+
# Function: SaveMenuFile
|
969
|
+
#
|
970
|
+
# Saves the current menu to <Menu.txt>.
|
971
|
+
#
|
972
|
+
sub SaveMenuFile
|
973
|
+
{
|
974
|
+
my ($self) = @_;
|
975
|
+
|
976
|
+
open(MENUFILEHANDLE, '>' . NaturalDocs::Project->UserConfigFile('Menu.txt'))
|
977
|
+
or die "Couldn't save menu file " . NaturalDocs::Project->UserConfigFile('Menu.txt') . "\n";
|
978
|
+
|
979
|
+
|
980
|
+
print MENUFILEHANDLE
|
981
|
+
"Format: " . NaturalDocs::Settings->TextAppVersion() . "\n\n\n";
|
982
|
+
|
983
|
+
my $inputDirs = NaturalDocs::Settings->InputDirectories();
|
984
|
+
|
985
|
+
|
986
|
+
if (defined $title)
|
987
|
+
{
|
988
|
+
print MENUFILEHANDLE 'Title: ' . $self->ConvertAmpChars($title) . "\n";
|
989
|
+
|
990
|
+
if (defined $subTitle)
|
991
|
+
{
|
992
|
+
print MENUFILEHANDLE 'SubTitle: ' . $self->ConvertAmpChars($subTitle) . "\n";
|
993
|
+
}
|
994
|
+
else
|
995
|
+
{
|
996
|
+
print MENUFILEHANDLE
|
997
|
+
"\n"
|
998
|
+
. "# You can also add a sub-title to your menu like this:\n"
|
999
|
+
. "# SubTitle: [subtitle]\n";
|
1000
|
+
};
|
1001
|
+
}
|
1002
|
+
else
|
1003
|
+
{
|
1004
|
+
print MENUFILEHANDLE
|
1005
|
+
"# You can add a title and sub-title to your menu like this:\n"
|
1006
|
+
. "# Title: [project name]\n"
|
1007
|
+
. "# SubTitle: [subtitle]\n";
|
1008
|
+
};
|
1009
|
+
|
1010
|
+
print MENUFILEHANDLE "\n";
|
1011
|
+
|
1012
|
+
if (defined $footer)
|
1013
|
+
{
|
1014
|
+
print MENUFILEHANDLE 'Footer: ' . $self->ConvertAmpChars($footer) . "\n";
|
1015
|
+
}
|
1016
|
+
else
|
1017
|
+
{
|
1018
|
+
print MENUFILEHANDLE
|
1019
|
+
"# You can add a footer to your documentation like this:\n"
|
1020
|
+
. "# Footer: [text]\n"
|
1021
|
+
. "# If you want to add a copyright notice, this would be the place to do it.\n";
|
1022
|
+
};
|
1023
|
+
|
1024
|
+
if (defined $timestampCode)
|
1025
|
+
{
|
1026
|
+
print MENUFILEHANDLE 'Timestamp: ' . $self->ConvertAmpChars($timestampCode) . "\n";
|
1027
|
+
}
|
1028
|
+
else
|
1029
|
+
{
|
1030
|
+
print MENUFILEHANDLE
|
1031
|
+
"\n"
|
1032
|
+
. "# You can add a timestamp to your documentation like one of these:\n"
|
1033
|
+
. "# Timestamp: Generated on month day, year\n"
|
1034
|
+
. "# Timestamp: Updated mm/dd/yyyy\n"
|
1035
|
+
. "# Timestamp: Last updated mon day\n"
|
1036
|
+
. "#\n";
|
1037
|
+
};
|
1038
|
+
|
1039
|
+
print MENUFILEHANDLE
|
1040
|
+
qq{# m - One or two digit month. January is "1"\n}
|
1041
|
+
. qq{# mm - Always two digit month. January is "01"\n}
|
1042
|
+
. qq{# mon - Short month word. January is "Jan"\n}
|
1043
|
+
. qq{# month - Long month word. January is "January"\n}
|
1044
|
+
. qq{# d - One or two digit day. 1 is "1"\n}
|
1045
|
+
. qq{# dd - Always two digit day. 1 is "01"\n}
|
1046
|
+
. qq{# day - Day with letter extension. 1 is "1st"\n}
|
1047
|
+
. qq{# yy - Two digit year. 2006 is "06"\n}
|
1048
|
+
. qq{# yyyy - Four digit year. 2006 is "2006"\n}
|
1049
|
+
. qq{# year - Four digit year. 2006 is "2006"\n}
|
1050
|
+
|
1051
|
+
. "\n";
|
1052
|
+
|
1053
|
+
if (scalar keys %bannedIndexes)
|
1054
|
+
{
|
1055
|
+
print MENUFILEHANDLE
|
1056
|
+
|
1057
|
+
"# These are indexes you deleted, so Natural Docs will not add them again\n"
|
1058
|
+
. "# unless you remove them from this line.\n"
|
1059
|
+
. "\n"
|
1060
|
+
. "Don't Index: ";
|
1061
|
+
|
1062
|
+
my $first = 1;
|
1063
|
+
|
1064
|
+
foreach my $index (keys %bannedIndexes)
|
1065
|
+
{
|
1066
|
+
if (!$first)
|
1067
|
+
{ print MENUFILEHANDLE ', '; }
|
1068
|
+
else
|
1069
|
+
{ $first = undef; };
|
1070
|
+
|
1071
|
+
print MENUFILEHANDLE $self->ConvertAmpChars( NaturalDocs::Topics->NameOfType($index, 1), CONVERT_COMMAS() );
|
1072
|
+
};
|
1073
|
+
|
1074
|
+
print MENUFILEHANDLE "\n\n";
|
1075
|
+
};
|
1076
|
+
|
1077
|
+
|
1078
|
+
# Remember to keep lines below eighty characters.
|
1079
|
+
|
1080
|
+
print MENUFILEHANDLE
|
1081
|
+
"\n"
|
1082
|
+
. "# --------------------------------------------------------------------------\n"
|
1083
|
+
. "# \n"
|
1084
|
+
. "# Cut and paste the lines below to change the order in which your files\n"
|
1085
|
+
. "# appear on the menu. Don't worry about adding or removing files, Natural\n"
|
1086
|
+
. "# Docs will take care of that.\n"
|
1087
|
+
. "# \n"
|
1088
|
+
. "# You can further organize the menu by grouping the entries. Add a\n"
|
1089
|
+
. "# \"Group: [name] {\" line to start a group, and add a \"}\" to end it.\n"
|
1090
|
+
. "# \n"
|
1091
|
+
. "# You can add text and web links to the menu by adding \"Text: [text]\" and\n"
|
1092
|
+
. "# \"Link: [name] ([URL])\" lines, respectively.\n"
|
1093
|
+
. "# \n"
|
1094
|
+
. "# The formatting and comments are auto-generated, so don't worry about\n"
|
1095
|
+
. "# neatness when editing the file. Natural Docs will clean it up the next\n"
|
1096
|
+
. "# time it is run. When working with groups, just deal with the braces and\n"
|
1097
|
+
. "# forget about the indentation and comments.\n"
|
1098
|
+
. "# \n";
|
1099
|
+
|
1100
|
+
if (scalar @$inputDirs > 1)
|
1101
|
+
{
|
1102
|
+
print MENUFILEHANDLE
|
1103
|
+
"# You can use this file on other computers even if they use different\n"
|
1104
|
+
. "# directories. As long as the command line points to the same source files,\n"
|
1105
|
+
. "# Natural Docs will be able to correct the locations automatically.\n"
|
1106
|
+
. "# \n";
|
1107
|
+
};
|
1108
|
+
|
1109
|
+
print MENUFILEHANDLE
|
1110
|
+
"# --------------------------------------------------------------------------\n"
|
1111
|
+
|
1112
|
+
. "\n\n";
|
1113
|
+
|
1114
|
+
|
1115
|
+
$self->WriteMenuEntries($menu->GroupContent(), \*MENUFILEHANDLE, undef, (scalar @$inputDirs == 1));
|
1116
|
+
|
1117
|
+
|
1118
|
+
if (scalar @$inputDirs > 1)
|
1119
|
+
{
|
1120
|
+
print MENUFILEHANDLE
|
1121
|
+
"\n\n##### Do not change or remove these lines. #####\n";
|
1122
|
+
|
1123
|
+
foreach my $inputDir (@$inputDirs)
|
1124
|
+
{
|
1125
|
+
print MENUFILEHANDLE
|
1126
|
+
'Data: 1(' . NaturalDocs::ConfigFile->Obscure( NaturalDocs::Settings->InputDirectoryNameOf($inputDir)
|
1127
|
+
. '///' . $inputDir ) . ")\n";
|
1128
|
+
};
|
1129
|
+
}
|
1130
|
+
elsif (lc(NaturalDocs::Settings->InputDirectoryNameOf($inputDirs->[0])) != 1)
|
1131
|
+
{
|
1132
|
+
print MENUFILEHANDLE
|
1133
|
+
"\n\n##### Do not change or remove this line. #####\n"
|
1134
|
+
. 'Data: 2(' . NaturalDocs::ConfigFile->Obscure( NaturalDocs::Settings->InputDirectoryNameOf($inputDirs->[0]) ) . ")\n";
|
1135
|
+
}
|
1136
|
+
|
1137
|
+
close(MENUFILEHANDLE);
|
1138
|
+
};
|
1139
|
+
|
1140
|
+
|
1141
|
+
#
|
1142
|
+
# Function: WriteMenuEntries
|
1143
|
+
#
|
1144
|
+
# A recursive function to write the contents of an arrayref of <NaturalDocs::Menu::Entry> objects to disk.
|
1145
|
+
#
|
1146
|
+
# Parameters:
|
1147
|
+
#
|
1148
|
+
# entries - The arrayref of menu entries to write.
|
1149
|
+
# fileHandle - The handle to the output file.
|
1150
|
+
# indentChars - The indentation _characters_ to add before each line. It is not the number of characters, it is the characters
|
1151
|
+
# themselves. Use undef for none.
|
1152
|
+
# relativeFiles - Whether to use relative file names.
|
1153
|
+
#
|
1154
|
+
sub WriteMenuEntries #(entries, fileHandle, indentChars, relativeFiles)
|
1155
|
+
{
|
1156
|
+
my ($self, $entries, $fileHandle, $indentChars, $relativeFiles) = @_;
|
1157
|
+
my $lastEntryType;
|
1158
|
+
|
1159
|
+
foreach my $entry (@$entries)
|
1160
|
+
{
|
1161
|
+
if ($entry->Type() == ::MENU_FILE())
|
1162
|
+
{
|
1163
|
+
my $fileName;
|
1164
|
+
|
1165
|
+
if ($relativeFiles)
|
1166
|
+
{ $fileName = (NaturalDocs::Settings->SplitFromInputDirectory($entry->Target()))[1]; }
|
1167
|
+
else
|
1168
|
+
{ $fileName = $entry->Target(); };
|
1169
|
+
|
1170
|
+
print $fileHandle $indentChars . 'File: ' . $self->ConvertAmpChars( $entry->Title(), CONVERT_PARENTHESIS() )
|
1171
|
+
. ' (' . ($entry->Flags() & ::MENU_FILE_NOAUTOTITLE() ? 'no auto-title, ' : '')
|
1172
|
+
. $self->ConvertAmpChars($fileName) . ")\n";
|
1173
|
+
}
|
1174
|
+
elsif ($entry->Type() == ::MENU_GROUP())
|
1175
|
+
{
|
1176
|
+
if (defined $lastEntryType && $lastEntryType != ::MENU_GROUP())
|
1177
|
+
{ print $fileHandle "\n"; };
|
1178
|
+
|
1179
|
+
print $fileHandle $indentChars . 'Group: ' . $self->ConvertAmpChars( $entry->Title() ) . " {\n\n";
|
1180
|
+
$self->WriteMenuEntries($entry->GroupContent(), $fileHandle, ' ' . $indentChars, $relativeFiles);
|
1181
|
+
print $fileHandle ' ' . $indentChars . '} # Group: ' . $self->ConvertAmpChars( $entry->Title() ) . "\n\n";
|
1182
|
+
}
|
1183
|
+
elsif ($entry->Type() == ::MENU_TEXT())
|
1184
|
+
{
|
1185
|
+
print $fileHandle $indentChars . 'Text: ' . $self->ConvertAmpChars( $entry->Title() ) . "\n";
|
1186
|
+
}
|
1187
|
+
elsif ($entry->Type() == ::MENU_LINK())
|
1188
|
+
{
|
1189
|
+
print $fileHandle $indentChars . 'Link: ' . $self->ConvertAmpChars( $entry->Title() ) . ' '
|
1190
|
+
. '(' . $self->ConvertAmpChars( $entry->Target(), CONVERT_PARENTHESIS() ) . ')' . "\n";
|
1191
|
+
}
|
1192
|
+
elsif ($entry->Type() == ::MENU_INDEX())
|
1193
|
+
{
|
1194
|
+
my $type;
|
1195
|
+
if ($entry->Target() ne ::TOPIC_GENERAL())
|
1196
|
+
{
|
1197
|
+
$type = NaturalDocs::Topics->NameOfType($entry->Target()) . ' ';
|
1198
|
+
};
|
1199
|
+
|
1200
|
+
print $fileHandle $indentChars . $self->ConvertAmpChars($type, CONVERT_COLONS()) . 'Index: '
|
1201
|
+
. $self->ConvertAmpChars( $entry->Title() ) . "\n";
|
1202
|
+
};
|
1203
|
+
|
1204
|
+
$lastEntryType = $entry->Type();
|
1205
|
+
};
|
1206
|
+
};
|
1207
|
+
|
1208
|
+
|
1209
|
+
#
|
1210
|
+
# Function: LoadPreviousMenuStateFile
|
1211
|
+
#
|
1212
|
+
# Loads and parses the previous menu state file.
|
1213
|
+
#
|
1214
|
+
# Returns:
|
1215
|
+
#
|
1216
|
+
# The array ( previousMenu, previousIndexes, previousFiles ) or an empty array if there was a problem with the file.
|
1217
|
+
#
|
1218
|
+
# previousMenu - A <MENU_GROUP> <NaturalDocs::Menu::Entry> object, similar to <menu>, which contains the entire
|
1219
|
+
# previous menu.
|
1220
|
+
# previousIndexes - An existence hashref of the index <TopicTypes> present in the previous menu.
|
1221
|
+
# previousFiles - A hashref of the files present in the previous menu. The keys are the <FileNames>, and the entries are
|
1222
|
+
# references to its object in previousMenu.
|
1223
|
+
#
|
1224
|
+
sub LoadPreviousMenuStateFile
|
1225
|
+
{
|
1226
|
+
my ($self) = @_;
|
1227
|
+
|
1228
|
+
my $fileIsOkay;
|
1229
|
+
my $version;
|
1230
|
+
my $previousStateFileName = NaturalDocs::Project->DataFile('PreviousMenuState.nd');
|
1231
|
+
|
1232
|
+
if (open(PREVIOUSSTATEFILEHANDLE, '<' . $previousStateFileName))
|
1233
|
+
{
|
1234
|
+
# See if it's binary.
|
1235
|
+
binmode(PREVIOUSSTATEFILEHANDLE);
|
1236
|
+
|
1237
|
+
my $firstChar;
|
1238
|
+
read(PREVIOUSSTATEFILEHANDLE, $firstChar, 1);
|
1239
|
+
|
1240
|
+
if ($firstChar == ::BINARY_FORMAT())
|
1241
|
+
{
|
1242
|
+
$version = NaturalDocs::Version->FromBinaryFile(\*PREVIOUSSTATEFILEHANDLE);
|
1243
|
+
|
1244
|
+
# Only the topic type format has changed since switching to binary, and we support both methods.
|
1245
|
+
|
1246
|
+
if (NaturalDocs::Version->CheckFileFormat($version))
|
1247
|
+
{ $fileIsOkay = 1; }
|
1248
|
+
else
|
1249
|
+
{ close(PREVIOUSSTATEFILEHANDLE); };
|
1250
|
+
}
|
1251
|
+
|
1252
|
+
else # it's not in binary
|
1253
|
+
{ close(PREVIOUSSTATEFILEHANDLE); };
|
1254
|
+
};
|
1255
|
+
|
1256
|
+
if ($fileIsOkay)
|
1257
|
+
{
|
1258
|
+
if (NaturalDocs::Project->UserConfigFileStatus('Menu.txt') == ::FILE_CHANGED())
|
1259
|
+
{ $hasChanged = 1; };
|
1260
|
+
|
1261
|
+
|
1262
|
+
my $menu = NaturalDocs::Menu::Entry->New(::MENU_GROUP(), undef, undef, undef);
|
1263
|
+
my $indexes = { };
|
1264
|
+
my $files = { };
|
1265
|
+
|
1266
|
+
my @groupStack;
|
1267
|
+
my $currentGroup = $menu;
|
1268
|
+
my $raw;
|
1269
|
+
|
1270
|
+
# [UInt8: type or 0 for end group]
|
1271
|
+
|
1272
|
+
while (read(PREVIOUSSTATEFILEHANDLE, $raw, 1))
|
1273
|
+
{
|
1274
|
+
my ($type, $flags, $title, $titleLength, $target, $targetLength);
|
1275
|
+
$type = unpack('C', $raw);
|
1276
|
+
|
1277
|
+
if ($type == 0)
|
1278
|
+
{ $currentGroup = pop @groupStack; }
|
1279
|
+
|
1280
|
+
elsif ($type == ::MENU_FILE())
|
1281
|
+
{
|
1282
|
+
# [UInt8: noAutoTitle] [AString16: title] [AString16: target]
|
1283
|
+
|
1284
|
+
read(PREVIOUSSTATEFILEHANDLE, $raw, 3);
|
1285
|
+
(my $noAutoTitle, $titleLength) = unpack('Cn', $raw);
|
1286
|
+
|
1287
|
+
if ($noAutoTitle)
|
1288
|
+
{ $flags = ::MENU_FILE_NOAUTOTITLE(); };
|
1289
|
+
|
1290
|
+
read(PREVIOUSSTATEFILEHANDLE, $title, $titleLength);
|
1291
|
+
read(PREVIOUSSTATEFILEHANDLE, $raw, 2);
|
1292
|
+
|
1293
|
+
$targetLength = unpack('n', $raw);
|
1294
|
+
|
1295
|
+
read(PREVIOUSSTATEFILEHANDLE, $target, $targetLength);
|
1296
|
+
}
|
1297
|
+
|
1298
|
+
elsif ($type == ::MENU_GROUP())
|
1299
|
+
{
|
1300
|
+
# [AString16: title]
|
1301
|
+
|
1302
|
+
read(PREVIOUSSTATEFILEHANDLE, $raw, 2);
|
1303
|
+
$titleLength = unpack('n', $raw);
|
1304
|
+
|
1305
|
+
read(PREVIOUSSTATEFILEHANDLE, $title, $titleLength);
|
1306
|
+
}
|
1307
|
+
|
1308
|
+
elsif ($type == ::MENU_INDEX())
|
1309
|
+
{
|
1310
|
+
# [AString16: title]
|
1311
|
+
|
1312
|
+
read(PREVIOUSSTATEFILEHANDLE, $raw, 2);
|
1313
|
+
$titleLength = unpack('n', $raw);
|
1314
|
+
|
1315
|
+
read(PREVIOUSSTATEFILEHANDLE, $title, $titleLength);
|
1316
|
+
|
1317
|
+
if ($version >= NaturalDocs::Version->FromString('1.3'))
|
1318
|
+
{
|
1319
|
+
# [AString16: topic type]
|
1320
|
+
read(PREVIOUSSTATEFILEHANDLE, $raw, 2);
|
1321
|
+
$targetLength = unpack('n', $raw);
|
1322
|
+
|
1323
|
+
read(PREVIOUSSTATEFILEHANDLE, $target, $targetLength);
|
1324
|
+
}
|
1325
|
+
else
|
1326
|
+
{
|
1327
|
+
# [UInt8: topic type (0 for general)]
|
1328
|
+
read(PREVIOUSSTATEFILEHANDLE, $raw, 1);
|
1329
|
+
$target = unpack('C', $raw);
|
1330
|
+
|
1331
|
+
$target = NaturalDocs::Topics->TypeFromLegacy($target);
|
1332
|
+
};
|
1333
|
+
}
|
1334
|
+
|
1335
|
+
elsif ($type == ::MENU_LINK())
|
1336
|
+
{
|
1337
|
+
# [AString16: title] [AString16: url]
|
1338
|
+
|
1339
|
+
read(PREVIOUSSTATEFILEHANDLE, $raw, 2);
|
1340
|
+
$titleLength = unpack('n', $raw);
|
1341
|
+
|
1342
|
+
read(PREVIOUSSTATEFILEHANDLE, $title, $titleLength);
|
1343
|
+
read(PREVIOUSSTATEFILEHANDLE, $raw, 2);
|
1344
|
+
$targetLength = unpack('n', $raw);
|
1345
|
+
|
1346
|
+
read(PREVIOUSSTATEFILEHANDLE, $target, $targetLength);
|
1347
|
+
}
|
1348
|
+
|
1349
|
+
elsif ($type == ::MENU_TEXT())
|
1350
|
+
{
|
1351
|
+
# [AString16: text]
|
1352
|
+
|
1353
|
+
read(PREVIOUSSTATEFILEHANDLE, $raw, 2);
|
1354
|
+
$titleLength = unpack('n', $raw);
|
1355
|
+
|
1356
|
+
read(PREVIOUSSTATEFILEHANDLE, $title, $titleLength);
|
1357
|
+
};
|
1358
|
+
|
1359
|
+
|
1360
|
+
# The topic type of the index may have been removed.
|
1361
|
+
|
1362
|
+
if ( !($type == ::MENU_INDEX() && !NaturalDocs::Topics->IsValidType($target)) )
|
1363
|
+
{
|
1364
|
+
my $entry = NaturalDocs::Menu::Entry->New($type, $title, $target, ($flags || 0));
|
1365
|
+
$currentGroup->PushToGroup($entry);
|
1366
|
+
|
1367
|
+
if ($type == ::MENU_FILE())
|
1368
|
+
{
|
1369
|
+
$files->{$target} = $entry;
|
1370
|
+
}
|
1371
|
+
elsif ($type == ::MENU_GROUP())
|
1372
|
+
{
|
1373
|
+
push @groupStack, $currentGroup;
|
1374
|
+
$currentGroup = $entry;
|
1375
|
+
}
|
1376
|
+
elsif ($type == ::MENU_INDEX())
|
1377
|
+
{
|
1378
|
+
$indexes->{$target} = 1;
|
1379
|
+
};
|
1380
|
+
};
|
1381
|
+
|
1382
|
+
};
|
1383
|
+
|
1384
|
+
close(PREVIOUSSTATEFILEHANDLE);
|
1385
|
+
|
1386
|
+
return ($menu, $indexes, $files);
|
1387
|
+
}
|
1388
|
+
else
|
1389
|
+
{
|
1390
|
+
$hasChanged = 1;
|
1391
|
+
return ( );
|
1392
|
+
};
|
1393
|
+
};
|
1394
|
+
|
1395
|
+
|
1396
|
+
#
|
1397
|
+
# Function: SavePreviousMenuStateFile
|
1398
|
+
#
|
1399
|
+
# Saves changes to <PreviousMenuState.nd>.
|
1400
|
+
#
|
1401
|
+
sub SavePreviousMenuStateFile
|
1402
|
+
{
|
1403
|
+
my ($self) = @_;
|
1404
|
+
|
1405
|
+
open (PREVIOUSSTATEFILEHANDLE, '>' . NaturalDocs::Project->DataFile('PreviousMenuState.nd'))
|
1406
|
+
or die "Couldn't save " . NaturalDocs::Project->DataFile('PreviousMenuState.nd') . ".\n";
|
1407
|
+
|
1408
|
+
binmode(PREVIOUSSTATEFILEHANDLE);
|
1409
|
+
|
1410
|
+
print PREVIOUSSTATEFILEHANDLE '' . ::BINARY_FORMAT();
|
1411
|
+
|
1412
|
+
NaturalDocs::Version->ToBinaryFile(\*PREVIOUSSTATEFILEHANDLE, NaturalDocs::Settings->AppVersion());
|
1413
|
+
|
1414
|
+
$self->WritePreviousMenuStateEntries($menu->GroupContent(), \*PREVIOUSSTATEFILEHANDLE);
|
1415
|
+
|
1416
|
+
close(PREVIOUSSTATEFILEHANDLE);
|
1417
|
+
};
|
1418
|
+
|
1419
|
+
|
1420
|
+
#
|
1421
|
+
# Function: WritePreviousMenuStateEntries
|
1422
|
+
#
|
1423
|
+
# A recursive function to write the contents of an arrayref of <NaturalDocs::Menu::Entry> objects to disk.
|
1424
|
+
#
|
1425
|
+
# Parameters:
|
1426
|
+
#
|
1427
|
+
# entries - The arrayref of menu entries to write.
|
1428
|
+
# fileHandle - The handle to the output file.
|
1429
|
+
#
|
1430
|
+
sub WritePreviousMenuStateEntries #(entries, fileHandle)
|
1431
|
+
{
|
1432
|
+
my ($self, $entries, $fileHandle) = @_;
|
1433
|
+
|
1434
|
+
foreach my $entry (@$entries)
|
1435
|
+
{
|
1436
|
+
if ($entry->Type() == ::MENU_FILE())
|
1437
|
+
{
|
1438
|
+
# We need to do length manually instead of using n/A in the template because it's not supported in earlier versions
|
1439
|
+
# of Perl.
|
1440
|
+
|
1441
|
+
# [UInt8: MENU_FILE] [UInt8: noAutoTitle] [AString16: title] [AString16: target]
|
1442
|
+
print $fileHandle pack('CCnA*nA*', ::MENU_FILE(), ($entry->Flags() & ::MENU_FILE_NOAUTOTITLE() ? 1 : 0),
|
1443
|
+
length($entry->Title()), $entry->Title(),
|
1444
|
+
length($entry->Target()), $entry->Target());
|
1445
|
+
}
|
1446
|
+
|
1447
|
+
elsif ($entry->Type() == ::MENU_GROUP())
|
1448
|
+
{
|
1449
|
+
# [UInt8: MENU_GROUP] [AString16: title]
|
1450
|
+
print $fileHandle pack('CnA*', ::MENU_GROUP(), length($entry->Title()), $entry->Title());
|
1451
|
+
$self->WritePreviousMenuStateEntries($entry->GroupContent(), $fileHandle);
|
1452
|
+
print $fileHandle pack('C', 0);
|
1453
|
+
}
|
1454
|
+
|
1455
|
+
elsif ($entry->Type() == ::MENU_INDEX())
|
1456
|
+
{
|
1457
|
+
# [UInt8: MENU_INDEX] [AString16: title] [AString16: topic type]
|
1458
|
+
print $fileHandle pack('CnA*nA*', ::MENU_INDEX(), length($entry->Title()), $entry->Title(),
|
1459
|
+
length($entry->Target()), $entry->Target());
|
1460
|
+
}
|
1461
|
+
|
1462
|
+
elsif ($entry->Type() == ::MENU_LINK())
|
1463
|
+
{
|
1464
|
+
# [UInt8: MENU_LINK] [AString16: title] [AString16: url]
|
1465
|
+
print $fileHandle pack('CnA*nA*', ::MENU_LINK(), length($entry->Title()), $entry->Title(),
|
1466
|
+
length($entry->Target()), $entry->Target());
|
1467
|
+
}
|
1468
|
+
|
1469
|
+
elsif ($entry->Type() == ::MENU_TEXT())
|
1470
|
+
{
|
1471
|
+
# [UInt8: MENU_TEXT] [AString16: hext]
|
1472
|
+
print $fileHandle pack('CnA*', ::MENU_TEXT(), length($entry->Title()), $entry->Title());
|
1473
|
+
};
|
1474
|
+
};
|
1475
|
+
|
1476
|
+
};
|
1477
|
+
|
1478
|
+
|
1479
|
+
#
|
1480
|
+
# Function: CheckForTrashedMenu
|
1481
|
+
#
|
1482
|
+
# Checks the menu to see if a significant number of file entries didn't resolve to actual files, and if so, saves a backup of the
|
1483
|
+
# menu and issues a warning.
|
1484
|
+
#
|
1485
|
+
# Parameters:
|
1486
|
+
#
|
1487
|
+
# numberOriginallyInMenu - A count of how many file entries were in the menu orignally.
|
1488
|
+
# numberRemoved - A count of how many file entries were removed from the menu.
|
1489
|
+
#
|
1490
|
+
sub CheckForTrashedMenu #(numberOriginallyInMenu, numberRemoved)
|
1491
|
+
{
|
1492
|
+
my ($self, $numberOriginallyInMenu, $numberRemoved) = @_;
|
1493
|
+
|
1494
|
+
no integer;
|
1495
|
+
|
1496
|
+
if ( ($numberOriginallyInMenu >= 6 && $numberRemoved == $numberOriginallyInMenu) ||
|
1497
|
+
($numberOriginallyInMenu >= 12 && ($numberRemoved / $numberOriginallyInMenu) >= 0.4) ||
|
1498
|
+
($numberRemoved >= 15) )
|
1499
|
+
{
|
1500
|
+
my $backupFile = NaturalDocs::Project->UserConfigFile('Menu_Backup.txt');
|
1501
|
+
my $backupFileNumber = 1;
|
1502
|
+
|
1503
|
+
while (-e $backupFile)
|
1504
|
+
{
|
1505
|
+
$backupFileNumber++;
|
1506
|
+
$backupFile = NaturalDocs::Project->UserConfigFile('Menu_Backup_' . $backupFileNumber . '.txt');
|
1507
|
+
};
|
1508
|
+
|
1509
|
+
NaturalDocs::File->Copy( NaturalDocs::Project->UserConfigFile('Menu.txt'), $backupFile );
|
1510
|
+
|
1511
|
+
print STDERR
|
1512
|
+
"\n"
|
1513
|
+
# GNU format. See http://www.gnu.org/prep/standards_15.html
|
1514
|
+
. "NaturalDocs: warning: possible trashed menu\n"
|
1515
|
+
. "\n"
|
1516
|
+
. " Natural Docs has detected that a significant number file entries in the\n"
|
1517
|
+
. " menu did not resolve to actual files. A backup of your original menu file\n"
|
1518
|
+
. " has been saved as\n"
|
1519
|
+
. "\n"
|
1520
|
+
. " " . $backupFile . "\n"
|
1521
|
+
. "\n"
|
1522
|
+
. " - If you recently deleted a lot of files from your project, you can safely\n"
|
1523
|
+
. " ignore this message. They have been deleted from the menu as well.\n"
|
1524
|
+
. " - If you recently rearranged your source tree, you may want to restore your\n"
|
1525
|
+
. " menu from the backup and do a search and replace to preserve your layout.\n"
|
1526
|
+
. " Otherwise the position of any moved files will be reset.\n"
|
1527
|
+
. " - If neither of these is the case, you may have gotten the -i parameter\n"
|
1528
|
+
. " wrong in the command line. You should definitely restore the backup and\n"
|
1529
|
+
. " try again, because otherwise every file in your menu will be reset.\n"
|
1530
|
+
. "\n";
|
1531
|
+
};
|
1532
|
+
|
1533
|
+
use integer;
|
1534
|
+
};
|
1535
|
+
|
1536
|
+
|
1537
|
+
#
|
1538
|
+
# Function: GenerateTimestampText
|
1539
|
+
#
|
1540
|
+
# Generates <timestampText> from <timestampCode> with the current date.
|
1541
|
+
#
|
1542
|
+
sub GenerateTimestampText
|
1543
|
+
{
|
1544
|
+
my $self = shift;
|
1545
|
+
|
1546
|
+
my @longMonths = ( 'January', 'February', 'March', 'April', 'May', 'June',
|
1547
|
+
'July', 'August', 'September', 'October', 'November', 'December' );
|
1548
|
+
my @shortMonths = ( 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec' );
|
1549
|
+
|
1550
|
+
my (undef, undef, undef, $day, $month, $year) = localtime();
|
1551
|
+
$year += 1900;
|
1552
|
+
|
1553
|
+
my $longDay;
|
1554
|
+
if ($day % 10 == 1 && $day != 11)
|
1555
|
+
{ $longDay = $day . 'st'; }
|
1556
|
+
elsif ($day % 10 == 2 && $day != 12)
|
1557
|
+
{ $longDay = $day . 'nd'; }
|
1558
|
+
elsif ($day % 10 == 3 && $day != 13)
|
1559
|
+
{ $longDay = $day . 'rd'; }
|
1560
|
+
else
|
1561
|
+
{ $longDay = $day . 'th'; };
|
1562
|
+
|
1563
|
+
|
1564
|
+
$timestampText = $timestampCode;
|
1565
|
+
|
1566
|
+
$timestampText =~ s/(?<![a-z])month(?![a-z])/$longMonths[$month]/i;
|
1567
|
+
$timestampText =~ s/(?<![a-z])mon(?![a-z])/$shortMonths[$month]/i;
|
1568
|
+
$timestampText =~ s/(?<![a-z])mm(?![a-z])/sprintf('%02d', $month + 1)/ie;
|
1569
|
+
$timestampText =~ s/(?<![a-z])m(?![a-z])/$month + 1/ie;
|
1570
|
+
|
1571
|
+
$timestampText =~ s/(?<![a-z])day(?![a-z])/$longDay/i;
|
1572
|
+
$timestampText =~ s/(?<![a-z])dd(?![a-z])/sprintf('%02d', $day)/ie;
|
1573
|
+
$timestampText =~ s/(?<![a-z])d(?![a-z])/$day/i;
|
1574
|
+
|
1575
|
+
$timestampText =~ s/(?<![a-z])(?:year|yyyy)(?![a-z])/$year/i;
|
1576
|
+
$timestampText =~ s/(?<![a-z])(?:year|yyyy)(?![a-z])/$year/i; #XXX
|
1577
|
+
$timestampText =~ s/(?<![a-z])yy(?![a-z])/sprintf('%02d', $year % 100)/ie;
|
1578
|
+
};
|
1579
|
+
|
1580
|
+
|
1581
|
+
use constant CONVERT_PARENTHESIS => 0x01;
|
1582
|
+
use constant CONVERT_COMMAS => 0x02;
|
1583
|
+
use constant CONVERT_COLONS => 0x04;
|
1584
|
+
|
1585
|
+
#
|
1586
|
+
# Function: ConvertAmpChars
|
1587
|
+
# Replaces certain characters in the string with their entities and returns it.
|
1588
|
+
#
|
1589
|
+
# Parameters:
|
1590
|
+
#
|
1591
|
+
# text - The text to convert.
|
1592
|
+
# flags - The flags of any additional characters to convert.
|
1593
|
+
#
|
1594
|
+
# Flags:
|
1595
|
+
#
|
1596
|
+
# - CONVERT_PARENTHESIS
|
1597
|
+
# - CONVERT_COMMAS
|
1598
|
+
# - CONVERT_COLONS
|
1599
|
+
#
|
1600
|
+
# Returns:
|
1601
|
+
#
|
1602
|
+
# The string with the amp chars converted.
|
1603
|
+
#
|
1604
|
+
sub ConvertAmpChars #(string text, int flags) => string
|
1605
|
+
{
|
1606
|
+
my ($self, $text, $flags) = @_;
|
1607
|
+
|
1608
|
+
$text =~ s/&/&/g;
|
1609
|
+
$text =~ s/\{/{/g;
|
1610
|
+
$text =~ s/\}/}/g;
|
1611
|
+
|
1612
|
+
if ($flags & CONVERT_PARENTHESIS())
|
1613
|
+
{
|
1614
|
+
$text =~ s/\(/&lparen;/g;
|
1615
|
+
$text =~ s/\)/&rparen;/g;
|
1616
|
+
};
|
1617
|
+
if ($flags & CONVERT_COMMAS())
|
1618
|
+
{
|
1619
|
+
$text =~ s/\,/,/g;
|
1620
|
+
};
|
1621
|
+
if ($flags & CONVERT_COLONS())
|
1622
|
+
{
|
1623
|
+
$text =~ s/\:/:/g;
|
1624
|
+
};
|
1625
|
+
|
1626
|
+
return $text;
|
1627
|
+
};
|
1628
|
+
|
1629
|
+
|
1630
|
+
#
|
1631
|
+
# Function: RestoreAmpChars
|
1632
|
+
# Replaces entity characters in the string with their original characters and returns it. This will restore all amp chars regardless
|
1633
|
+
# of the flags passed to <ConvertAmpChars()>.
|
1634
|
+
#
|
1635
|
+
sub RestoreAmpChars #(string text) => string
|
1636
|
+
{
|
1637
|
+
my ($self, $text) = @_;
|
1638
|
+
|
1639
|
+
$text =~ s/&lparen;/(/gi;
|
1640
|
+
$text =~ s/&rparen;/)/gi;
|
1641
|
+
$text =~ s/{/{/gi;
|
1642
|
+
$text =~ s/}/}/gi;
|
1643
|
+
$text =~ s/,/,/gi;
|
1644
|
+
$text =~ s/&/&/gi;
|
1645
|
+
$text =~ s/:/:/gi;
|
1646
|
+
|
1647
|
+
return $text;
|
1648
|
+
};
|
1649
|
+
|
1650
|
+
|
1651
|
+
|
1652
|
+
###############################################################################
|
1653
|
+
# Group: Auto-Adjustment Functions
|
1654
|
+
|
1655
|
+
|
1656
|
+
#
|
1657
|
+
# Function: ResolveInputDirectories
|
1658
|
+
#
|
1659
|
+
# Detects if the input directories in the menu file match those in the command line, and if not, tries to resolve them. This allows
|
1660
|
+
# menu files to work across machines, since the absolute paths won't be the same but the relative ones should be.
|
1661
|
+
#
|
1662
|
+
# Parameters:
|
1663
|
+
#
|
1664
|
+
# inputDirectoryNames - A hashref of the input directories appearing in the menu file, or undef if none. The keys are the
|
1665
|
+
# directories, and the values are their names. May be undef.
|
1666
|
+
#
|
1667
|
+
sub ResolveInputDirectories #(inputDirectoryNames)
|
1668
|
+
{
|
1669
|
+
my ($self, $menuDirectoryNames) = @_;
|
1670
|
+
|
1671
|
+
|
1672
|
+
# Determine which directories don't match the command line, if any.
|
1673
|
+
|
1674
|
+
my $inputDirectories = NaturalDocs::Settings->InputDirectories();
|
1675
|
+
my @unresolvedMenuDirectories;
|
1676
|
+
|
1677
|
+
foreach my $menuDirectory (keys %$menuDirectoryNames)
|
1678
|
+
{
|
1679
|
+
my $found;
|
1680
|
+
|
1681
|
+
foreach my $inputDirectory (@$inputDirectories)
|
1682
|
+
{
|
1683
|
+
if ($menuDirectory eq $inputDirectory)
|
1684
|
+
{
|
1685
|
+
$found = 1;
|
1686
|
+
last;
|
1687
|
+
};
|
1688
|
+
};
|
1689
|
+
|
1690
|
+
if (!$found)
|
1691
|
+
{ push @unresolvedMenuDirectories, $menuDirectory; };
|
1692
|
+
};
|
1693
|
+
|
1694
|
+
# Quit if everything matches up, which should be the most common case.
|
1695
|
+
if (!scalar @unresolvedMenuDirectories)
|
1696
|
+
{ return; };
|
1697
|
+
|
1698
|
+
# Poop. See which input directories are still available.
|
1699
|
+
|
1700
|
+
my @unresolvedInputDirectories;
|
1701
|
+
|
1702
|
+
foreach my $inputDirectory (@$inputDirectories)
|
1703
|
+
{
|
1704
|
+
if (!exists $menuDirectoryNames->{$inputDirectory})
|
1705
|
+
{ push @unresolvedInputDirectories, $inputDirectory; };
|
1706
|
+
};
|
1707
|
+
|
1708
|
+
# Quit if there are none. This means an input directory is in the menu that isn't in the command line. Natural Docs should
|
1709
|
+
# proceed normally and let the files be deleted.
|
1710
|
+
if (!scalar @unresolvedInputDirectories)
|
1711
|
+
{
|
1712
|
+
$hasChanged = 1;
|
1713
|
+
return;
|
1714
|
+
};
|
1715
|
+
|
1716
|
+
# The index into menuDirectoryScores is the same as in unresolvedMenuDirectories. The index into each arrayref within it is
|
1717
|
+
# the same as in unresolvedInputDirectories.
|
1718
|
+
my @menuDirectoryScores;
|
1719
|
+
for (my $i = 0; $i < scalar @unresolvedMenuDirectories; $i++)
|
1720
|
+
{ push @menuDirectoryScores, [ ]; };
|
1721
|
+
|
1722
|
+
|
1723
|
+
# Now plow through the menu, looking for files that have an unresolved base.
|
1724
|
+
|
1725
|
+
my @menuGroups = ( $menu );
|
1726
|
+
|
1727
|
+
while (scalar @menuGroups)
|
1728
|
+
{
|
1729
|
+
my $currentGroup = pop @menuGroups;
|
1730
|
+
my $currentGroupContent = $currentGroup->GroupContent();
|
1731
|
+
|
1732
|
+
foreach my $entry (@$currentGroupContent)
|
1733
|
+
{
|
1734
|
+
if ($entry->Type() == ::MENU_GROUP())
|
1735
|
+
{
|
1736
|
+
push @menuGroups, $entry;
|
1737
|
+
}
|
1738
|
+
elsif ($entry->Type() == ::MENU_FILE())
|
1739
|
+
{
|
1740
|
+
# Check if it uses an unresolved base.
|
1741
|
+
for (my $i = 0; $i < scalar @unresolvedMenuDirectories; $i++)
|
1742
|
+
{
|
1743
|
+
if (NaturalDocs::File->IsSubPathOf($unresolvedMenuDirectories[$i], $entry->Target()))
|
1744
|
+
{
|
1745
|
+
my $relativePath = NaturalDocs::File->MakeRelativePath($unresolvedMenuDirectories[$i], $entry->Target());
|
1746
|
+
$self->ResolveFile($relativePath, \@unresolvedInputDirectories, $menuDirectoryScores[$i]);
|
1747
|
+
last;
|
1748
|
+
};
|
1749
|
+
};
|
1750
|
+
};
|
1751
|
+
};
|
1752
|
+
};
|
1753
|
+
|
1754
|
+
|
1755
|
+
# Now, create an array of score objects. Each score object is the three value arrayref [ from, to, score ]. From and To are the
|
1756
|
+
# conversion options and are the indexes into unresolvedInput/MenuDirectories. We'll sort this array by score to get the best
|
1757
|
+
# possible conversions. Yes, really.
|
1758
|
+
my @scores;
|
1759
|
+
|
1760
|
+
for (my $menuIndex = 0; $menuIndex < scalar @unresolvedMenuDirectories; $menuIndex++)
|
1761
|
+
{
|
1762
|
+
for (my $inputIndex = 0; $inputIndex < scalar @unresolvedInputDirectories; $inputIndex++)
|
1763
|
+
{
|
1764
|
+
if ($menuDirectoryScores[$menuIndex]->[$inputIndex])
|
1765
|
+
{
|
1766
|
+
push @scores, [ $menuIndex, $inputIndex, $menuDirectoryScores[$menuIndex]->[$inputIndex] ];
|
1767
|
+
};
|
1768
|
+
};
|
1769
|
+
};
|
1770
|
+
|
1771
|
+
@scores = sort { $b->[2] <=> $a->[2] } @scores;
|
1772
|
+
|
1773
|
+
|
1774
|
+
# Now we determine what goes where.
|
1775
|
+
my @menuDirectoryConversions;
|
1776
|
+
|
1777
|
+
foreach my $scoreObject (@scores)
|
1778
|
+
{
|
1779
|
+
if (!defined $menuDirectoryConversions[ $scoreObject->[0] ])
|
1780
|
+
{
|
1781
|
+
$menuDirectoryConversions[ $scoreObject->[0] ] = $unresolvedInputDirectories[ $scoreObject->[1] ];
|
1782
|
+
};
|
1783
|
+
};
|
1784
|
+
|
1785
|
+
|
1786
|
+
# Now, FINALLY, we do the conversion. Note that not every menu directory may have a conversion defined.
|
1787
|
+
|
1788
|
+
@menuGroups = ( $menu );
|
1789
|
+
|
1790
|
+
while (scalar @menuGroups)
|
1791
|
+
{
|
1792
|
+
my $currentGroup = pop @menuGroups;
|
1793
|
+
my $currentGroupContent = $currentGroup->GroupContent();
|
1794
|
+
|
1795
|
+
foreach my $entry (@$currentGroupContent)
|
1796
|
+
{
|
1797
|
+
if ($entry->Type() == ::MENU_GROUP())
|
1798
|
+
{
|
1799
|
+
push @menuGroups, $entry;
|
1800
|
+
}
|
1801
|
+
elsif ($entry->Type() == ::MENU_FILE())
|
1802
|
+
{
|
1803
|
+
# Check if it uses an unresolved base.
|
1804
|
+
for (my $i = 0; $i < scalar @unresolvedMenuDirectories; $i++)
|
1805
|
+
{
|
1806
|
+
if (NaturalDocs::File->IsSubPathOf($unresolvedMenuDirectories[$i], $entry->Target()) &&
|
1807
|
+
defined $menuDirectoryConversions[$i])
|
1808
|
+
{
|
1809
|
+
my $relativePath = NaturalDocs::File->MakeRelativePath($unresolvedMenuDirectories[$i], $entry->Target());
|
1810
|
+
$entry->SetTarget( NaturalDocs::File->JoinPaths($menuDirectoryConversions[$i], $relativePath) );
|
1811
|
+
last;
|
1812
|
+
};
|
1813
|
+
};
|
1814
|
+
};
|
1815
|
+
};
|
1816
|
+
};
|
1817
|
+
|
1818
|
+
|
1819
|
+
# Whew.
|
1820
|
+
|
1821
|
+
$hasChanged = 1;
|
1822
|
+
};
|
1823
|
+
|
1824
|
+
|
1825
|
+
#
|
1826
|
+
# Function: ResolveRelativeInputDirectories
|
1827
|
+
#
|
1828
|
+
# Resolves relative input directories to the input directories available.
|
1829
|
+
#
|
1830
|
+
sub ResolveRelativeInputDirectories
|
1831
|
+
{
|
1832
|
+
my ($self) = @_;
|
1833
|
+
|
1834
|
+
my $inputDirectories = NaturalDocs::Settings->InputDirectories();
|
1835
|
+
my $resolvedInputDirectory;
|
1836
|
+
|
1837
|
+
if (scalar @$inputDirectories == 1)
|
1838
|
+
{ $resolvedInputDirectory = $inputDirectories->[0]; }
|
1839
|
+
else
|
1840
|
+
{
|
1841
|
+
my @score;
|
1842
|
+
|
1843
|
+
# Plow through the menu, looking for files and scoring them.
|
1844
|
+
|
1845
|
+
my @menuGroups = ( $menu );
|
1846
|
+
|
1847
|
+
while (scalar @menuGroups)
|
1848
|
+
{
|
1849
|
+
my $currentGroup = pop @menuGroups;
|
1850
|
+
my $currentGroupContent = $currentGroup->GroupContent();
|
1851
|
+
|
1852
|
+
foreach my $entry (@$currentGroupContent)
|
1853
|
+
{
|
1854
|
+
if ($entry->Type() == ::MENU_GROUP())
|
1855
|
+
{
|
1856
|
+
push @menuGroups, $entry;
|
1857
|
+
}
|
1858
|
+
elsif ($entry->Type() == ::MENU_FILE())
|
1859
|
+
{
|
1860
|
+
$self->ResolveFile($entry->Target(), $inputDirectories, \@score);
|
1861
|
+
};
|
1862
|
+
};
|
1863
|
+
};
|
1864
|
+
|
1865
|
+
# Determine the best match.
|
1866
|
+
|
1867
|
+
my $bestScore = 0;
|
1868
|
+
my $bestIndex = 0;
|
1869
|
+
|
1870
|
+
for (my $i = 0; $i < scalar @$inputDirectories; $i++)
|
1871
|
+
{
|
1872
|
+
if ($score[$i] > $bestScore)
|
1873
|
+
{
|
1874
|
+
$bestScore = $score[$i];
|
1875
|
+
$bestIndex = $i;
|
1876
|
+
};
|
1877
|
+
};
|
1878
|
+
|
1879
|
+
$resolvedInputDirectory = $inputDirectories->[$bestIndex];
|
1880
|
+
};
|
1881
|
+
|
1882
|
+
|
1883
|
+
# Okay, now that we have our resolved directory, update everything.
|
1884
|
+
|
1885
|
+
my @menuGroups = ( $menu );
|
1886
|
+
|
1887
|
+
while (scalar @menuGroups)
|
1888
|
+
{
|
1889
|
+
my $currentGroup = pop @menuGroups;
|
1890
|
+
my $currentGroupContent = $currentGroup->GroupContent();
|
1891
|
+
|
1892
|
+
foreach my $entry (@$currentGroupContent)
|
1893
|
+
{
|
1894
|
+
if ($entry->Type() == ::MENU_GROUP())
|
1895
|
+
{ push @menuGroups, $entry; }
|
1896
|
+
elsif ($entry->Type() == ::MENU_FILE())
|
1897
|
+
{
|
1898
|
+
$entry->SetTarget( NaturalDocs::File->JoinPaths($resolvedInputDirectory, $entry->Target()) );
|
1899
|
+
};
|
1900
|
+
};
|
1901
|
+
};
|
1902
|
+
|
1903
|
+
if (scalar @$inputDirectories > 1)
|
1904
|
+
{ $hasChanged = 1; };
|
1905
|
+
|
1906
|
+
return $resolvedInputDirectory;
|
1907
|
+
};
|
1908
|
+
|
1909
|
+
|
1910
|
+
#
|
1911
|
+
# Function: ResolveFile
|
1912
|
+
#
|
1913
|
+
# Tests a relative path against a list of directories. Adds one to the score of each base where there is a match.
|
1914
|
+
#
|
1915
|
+
# Parameters:
|
1916
|
+
#
|
1917
|
+
# relativePath - The relative file name to test.
|
1918
|
+
# possibleBases - An arrayref of bases to test it against.
|
1919
|
+
# possibleBaseScores - An arrayref of scores to adjust. The score indexes should correspond to the base indexes.
|
1920
|
+
#
|
1921
|
+
sub ResolveFile #(relativePath, possibleBases, possibleBaseScores)
|
1922
|
+
{
|
1923
|
+
my ($self, $relativePath, $possibleBases, $possibleBaseScores) = @_;
|
1924
|
+
|
1925
|
+
for (my $i = 0; $i < scalar @$possibleBases; $i++)
|
1926
|
+
{
|
1927
|
+
if (-e NaturalDocs::File->JoinPaths($possibleBases->[$i], $relativePath))
|
1928
|
+
{ $possibleBaseScores->[$i]++; };
|
1929
|
+
};
|
1930
|
+
};
|
1931
|
+
|
1932
|
+
|
1933
|
+
#
|
1934
|
+
# Function: LockUserTitleChanges
|
1935
|
+
#
|
1936
|
+
# Detects if the user manually changed any file titles, and if so, automatically locks them with <MENU_FILE_NOAUTOTITLE>.
|
1937
|
+
#
|
1938
|
+
# Parameters:
|
1939
|
+
#
|
1940
|
+
# previousMenuFiles - A hashref of the files from the previous menu state. The keys are the <FileNames>, and the values are
|
1941
|
+
# references to their <NaturalDocs::Menu::Entry> objects.
|
1942
|
+
#
|
1943
|
+
sub LockUserTitleChanges #(previousMenuFiles)
|
1944
|
+
{
|
1945
|
+
my ($self, $previousMenuFiles) = @_;
|
1946
|
+
|
1947
|
+
my @groupStack = ( $menu );
|
1948
|
+
my $groupEntry;
|
1949
|
+
|
1950
|
+
while (scalar @groupStack)
|
1951
|
+
{
|
1952
|
+
$groupEntry = pop @groupStack;
|
1953
|
+
|
1954
|
+
foreach my $entry (@{$groupEntry->GroupContent()})
|
1955
|
+
{
|
1956
|
+
|
1957
|
+
# If it's an unlocked file entry
|
1958
|
+
if ($entry->Type() == ::MENU_FILE() && ($entry->Flags() & ::MENU_FILE_NOAUTOTITLE()) == 0)
|
1959
|
+
{
|
1960
|
+
my $previousEntry = $previousMenuFiles->{$entry->Target()};
|
1961
|
+
|
1962
|
+
# If the previous entry was also unlocked and the titles are different, the user changed the title. Automatically lock it.
|
1963
|
+
if (defined $previousEntry && ($previousEntry->Flags() & ::MENU_FILE_NOAUTOTITLE()) == 0 &&
|
1964
|
+
$entry->Title() ne $previousEntry->Title())
|
1965
|
+
{
|
1966
|
+
$entry->SetFlags($entry->Flags() | ::MENU_FILE_NOAUTOTITLE());
|
1967
|
+
$hasChanged = 1;
|
1968
|
+
};
|
1969
|
+
}
|
1970
|
+
|
1971
|
+
elsif ($entry->Type() == ::MENU_GROUP())
|
1972
|
+
{
|
1973
|
+
push @groupStack, $entry;
|
1974
|
+
};
|
1975
|
+
|
1976
|
+
};
|
1977
|
+
};
|
1978
|
+
};
|
1979
|
+
|
1980
|
+
|
1981
|
+
#
|
1982
|
+
# Function: FlagAutoTitleChanges
|
1983
|
+
#
|
1984
|
+
# Finds which files have auto-titles that changed and flags their groups for updating with <MENU_GROUP_UPDATETITLES> and
|
1985
|
+
# <MENU_GROUP_UPDATEORDER>.
|
1986
|
+
#
|
1987
|
+
sub FlagAutoTitleChanges
|
1988
|
+
{
|
1989
|
+
my ($self) = @_;
|
1990
|
+
|
1991
|
+
my @groupStack = ( $menu );
|
1992
|
+
my $groupEntry;
|
1993
|
+
|
1994
|
+
while (scalar @groupStack)
|
1995
|
+
{
|
1996
|
+
$groupEntry = pop @groupStack;
|
1997
|
+
|
1998
|
+
foreach my $entry (@{$groupEntry->GroupContent()})
|
1999
|
+
{
|
2000
|
+
if ($entry->Type() == ::MENU_FILE() && ($entry->Flags() & ::MENU_FILE_NOAUTOTITLE()) == 0 &&
|
2001
|
+
exists $defaultTitlesChanged{$entry->Target()})
|
2002
|
+
{
|
2003
|
+
$groupEntry->SetFlags($groupEntry->Flags() | ::MENU_GROUP_UPDATETITLES() | ::MENU_GROUP_UPDATEORDER());
|
2004
|
+
$hasChanged = 1;
|
2005
|
+
}
|
2006
|
+
elsif ($entry->Type() == ::MENU_GROUP())
|
2007
|
+
{
|
2008
|
+
push @groupStack, $entry;
|
2009
|
+
};
|
2010
|
+
};
|
2011
|
+
};
|
2012
|
+
};
|
2013
|
+
|
2014
|
+
|
2015
|
+
#
|
2016
|
+
# Function: AutoPlaceNewFiles
|
2017
|
+
#
|
2018
|
+
# Adds files to the menu that aren't already on it, attempting to guess where they belong.
|
2019
|
+
#
|
2020
|
+
# New files are placed after a dummy <MENU_ENDOFORIGINAL> entry so that they don't affect the detected order. Also, the
|
2021
|
+
# groups they're placed in get <MENU_GROUP_UPDATETITLES>, <MENU_GROUP_UPDATESTRUCTURE>, and
|
2022
|
+
# <MENU_GROUP_UPDATEORDER> flags.
|
2023
|
+
#
|
2024
|
+
# Parameters:
|
2025
|
+
#
|
2026
|
+
# filesInMenu - An existence hash of all the <FileNames> present in the menu.
|
2027
|
+
#
|
2028
|
+
sub AutoPlaceNewFiles #(fileInMenu)
|
2029
|
+
{
|
2030
|
+
my ($self, $filesInMenu) = @_;
|
2031
|
+
|
2032
|
+
my $files = NaturalDocs::Project->FilesWithContent();
|
2033
|
+
|
2034
|
+
my $directories;
|
2035
|
+
|
2036
|
+
foreach my $file (keys %$files)
|
2037
|
+
{
|
2038
|
+
if (!exists $filesInMenu->{$file})
|
2039
|
+
{
|
2040
|
+
# This is done on demand because new files shouldn't be added very often, so this will save time.
|
2041
|
+
if (!defined $directories)
|
2042
|
+
{ $directories = $self->MatchDirectoriesAndGroups(); };
|
2043
|
+
|
2044
|
+
my $targetGroup;
|
2045
|
+
my $fileDirectoryString = (NaturalDocs::File->SplitPath($file))[1];
|
2046
|
+
|
2047
|
+
$targetGroup = $directories->{$fileDirectoryString};
|
2048
|
+
|
2049
|
+
if (!defined $targetGroup)
|
2050
|
+
{
|
2051
|
+
# Okay, if there's no exact match, work our way down.
|
2052
|
+
|
2053
|
+
my @fileDirectories = NaturalDocs::File->SplitDirectories($fileDirectoryString);
|
2054
|
+
|
2055
|
+
do
|
2056
|
+
{
|
2057
|
+
pop @fileDirectories;
|
2058
|
+
$targetGroup = $directories->{ NaturalDocs::File->JoinDirectories(@fileDirectories) };
|
2059
|
+
}
|
2060
|
+
while (!defined $targetGroup && scalar @fileDirectories);
|
2061
|
+
|
2062
|
+
if (!defined $targetGroup)
|
2063
|
+
{ $targetGroup = $menu; };
|
2064
|
+
};
|
2065
|
+
|
2066
|
+
$targetGroup->MarkEndOfOriginal();
|
2067
|
+
$targetGroup->PushToGroup( NaturalDocs::Menu::Entry->New(::MENU_FILE(), undef, $file, undef) );
|
2068
|
+
|
2069
|
+
$targetGroup->SetFlags( $targetGroup->Flags() | ::MENU_GROUP_UPDATETITLES() |
|
2070
|
+
::MENU_GROUP_UPDATESTRUCTURE() | ::MENU_GROUP_UPDATEORDER() );
|
2071
|
+
|
2072
|
+
$hasChanged = 1;
|
2073
|
+
};
|
2074
|
+
};
|
2075
|
+
};
|
2076
|
+
|
2077
|
+
|
2078
|
+
#
|
2079
|
+
# Function: MatchDirectoriesAndGroups
|
2080
|
+
#
|
2081
|
+
# Determines which groups files in certain directories should be placed in.
|
2082
|
+
#
|
2083
|
+
# Returns:
|
2084
|
+
#
|
2085
|
+
# A hashref. The keys are the directory names, and the values are references to the group objects they should be placed in.
|
2086
|
+
#
|
2087
|
+
# This only repreesents directories that currently have files on the menu, so it shouldn't be assumed that every possible
|
2088
|
+
# directory will exist. To match, you should first try to match the directory, and then strip the deepest directories one by
|
2089
|
+
# one until there's a match or there's none left. If there's none left, use the root group <menu>.
|
2090
|
+
#
|
2091
|
+
sub MatchDirectoriesAndGroups
|
2092
|
+
{
|
2093
|
+
my ($self) = @_;
|
2094
|
+
|
2095
|
+
# The keys are the directory names, and the values are hashrefs. For the hashrefs, the keys are the group objects, and the
|
2096
|
+
# values are the number of files in them from that directory. In other words,
|
2097
|
+
# $directories{$directory}->{$groupEntry} = $count;
|
2098
|
+
my %directories;
|
2099
|
+
# Note that we need to use Tie::RefHash to use references as keys. Won't work otherwise. Also, not every Perl distro comes
|
2100
|
+
# with Tie::RefHash::Nestable, so we can't rely on that.
|
2101
|
+
|
2102
|
+
# We're using an index instead of pushing and popping because we want to save a list of the groups in the order they appear
|
2103
|
+
# to break ties.
|
2104
|
+
my @groups = ( $menu );
|
2105
|
+
my $groupIndex = 0;
|
2106
|
+
|
2107
|
+
|
2108
|
+
# Count the number of files in each group that appear in each directory.
|
2109
|
+
|
2110
|
+
while ($groupIndex < scalar @groups)
|
2111
|
+
{
|
2112
|
+
my $groupEntry = $groups[$groupIndex];
|
2113
|
+
|
2114
|
+
foreach my $entry (@{$groupEntry->GroupContent()})
|
2115
|
+
{
|
2116
|
+
if ($entry->Type() == ::MENU_GROUP())
|
2117
|
+
{
|
2118
|
+
push @groups, $entry;
|
2119
|
+
}
|
2120
|
+
elsif ($entry->Type() == ::MENU_FILE())
|
2121
|
+
{
|
2122
|
+
my $directory = (NaturalDocs::File->SplitPath($entry->Target()))[1];
|
2123
|
+
|
2124
|
+
if (!exists $directories{$directory})
|
2125
|
+
{
|
2126
|
+
my $subHash = { };
|
2127
|
+
tie %$subHash, 'Tie::RefHash';
|
2128
|
+
$directories{$directory} = $subHash;
|
2129
|
+
};
|
2130
|
+
|
2131
|
+
if (!exists $directories{$directory}->{$groupEntry})
|
2132
|
+
{ $directories{$directory}->{$groupEntry} = 1; }
|
2133
|
+
else
|
2134
|
+
{ $directories{$directory}->{$groupEntry}++; };
|
2135
|
+
};
|
2136
|
+
};
|
2137
|
+
|
2138
|
+
$groupIndex++;
|
2139
|
+
};
|
2140
|
+
|
2141
|
+
|
2142
|
+
# Determine which group goes with which directory, breaking ties by using whichever group appears first.
|
2143
|
+
|
2144
|
+
my $finalDirectories = { };
|
2145
|
+
|
2146
|
+
while (my ($directory, $directoryGroups) = each %directories)
|
2147
|
+
{
|
2148
|
+
my $bestGroup;
|
2149
|
+
my $bestCount = 0;
|
2150
|
+
my %tiedGroups; # Existence hash
|
2151
|
+
|
2152
|
+
while (my ($group, $count) = each %$directoryGroups)
|
2153
|
+
{
|
2154
|
+
if ($count > $bestCount)
|
2155
|
+
{
|
2156
|
+
$bestGroup = $group;
|
2157
|
+
$bestCount = $count;
|
2158
|
+
%tiedGroups = ( );
|
2159
|
+
}
|
2160
|
+
elsif ($count == $bestCount)
|
2161
|
+
{
|
2162
|
+
$tiedGroups{$group} = 1;
|
2163
|
+
};
|
2164
|
+
};
|
2165
|
+
|
2166
|
+
# Break ties.
|
2167
|
+
if (scalar keys %tiedGroups)
|
2168
|
+
{
|
2169
|
+
$tiedGroups{$bestGroup} = 1;
|
2170
|
+
|
2171
|
+
foreach my $group (@groups)
|
2172
|
+
{
|
2173
|
+
if (exists $tiedGroups{$group})
|
2174
|
+
{
|
2175
|
+
$bestGroup = $group;
|
2176
|
+
last;
|
2177
|
+
};
|
2178
|
+
};
|
2179
|
+
};
|
2180
|
+
|
2181
|
+
|
2182
|
+
$finalDirectories->{$directory} = $bestGroup;
|
2183
|
+
};
|
2184
|
+
|
2185
|
+
|
2186
|
+
return $finalDirectories;
|
2187
|
+
};
|
2188
|
+
|
2189
|
+
|
2190
|
+
#
|
2191
|
+
# Function: RemoveDeadFiles
|
2192
|
+
#
|
2193
|
+
# Removes files from the menu that no longer exist or no longer have Natural Docs content.
|
2194
|
+
#
|
2195
|
+
# Returns:
|
2196
|
+
#
|
2197
|
+
# The number of file entries removed.
|
2198
|
+
#
|
2199
|
+
sub RemoveDeadFiles
|
2200
|
+
{
|
2201
|
+
my ($self) = @_;
|
2202
|
+
|
2203
|
+
my @groupStack = ( $menu );
|
2204
|
+
my $numberRemoved = 0;
|
2205
|
+
|
2206
|
+
my $filesWithContent = NaturalDocs::Project->FilesWithContent();
|
2207
|
+
|
2208
|
+
while (scalar @groupStack)
|
2209
|
+
{
|
2210
|
+
my $groupEntry = pop @groupStack;
|
2211
|
+
my $groupContent = $groupEntry->GroupContent();
|
2212
|
+
|
2213
|
+
my $index = 0;
|
2214
|
+
while ($index < scalar @$groupContent)
|
2215
|
+
{
|
2216
|
+
if ($groupContent->[$index]->Type() == ::MENU_FILE() &&
|
2217
|
+
!exists $filesWithContent->{ $groupContent->[$index]->Target() } )
|
2218
|
+
{
|
2219
|
+
$groupEntry->DeleteFromGroup($index);
|
2220
|
+
|
2221
|
+
$groupEntry->SetFlags( $groupEntry->Flags() | ::MENU_GROUP_UPDATETITLES() |
|
2222
|
+
::MENU_GROUP_UPDATESTRUCTURE() );
|
2223
|
+
$numberRemoved++;
|
2224
|
+
$hasChanged = 1;
|
2225
|
+
}
|
2226
|
+
|
2227
|
+
elsif ($groupContent->[$index]->Type() == ::MENU_GROUP())
|
2228
|
+
{
|
2229
|
+
push @groupStack, $groupContent->[$index];
|
2230
|
+
$index++;
|
2231
|
+
}
|
2232
|
+
|
2233
|
+
else
|
2234
|
+
{ $index++; };
|
2235
|
+
};
|
2236
|
+
};
|
2237
|
+
|
2238
|
+
return $numberRemoved;
|
2239
|
+
};
|
2240
|
+
|
2241
|
+
|
2242
|
+
#
|
2243
|
+
# Function: BanAndUnbanIndexes
|
2244
|
+
#
|
2245
|
+
# Adjusts the indexes that are banned depending on if the user added or deleted any.
|
2246
|
+
#
|
2247
|
+
sub BanAndUnbanIndexes
|
2248
|
+
{
|
2249
|
+
my ($self) = @_;
|
2250
|
+
|
2251
|
+
# Unban any indexes that are present, meaning the user added them back manually without deleting the ban.
|
2252
|
+
foreach my $index (keys %indexes)
|
2253
|
+
{ delete $bannedIndexes{$index}; };
|
2254
|
+
|
2255
|
+
# Ban any indexes that were in the previous menu but not the current, meaning the user manually deleted them. However,
|
2256
|
+
# don't do this if the topic isn't indexable, meaning they changed the topic type rather than the menu.
|
2257
|
+
foreach my $index (keys %previousIndexes)
|
2258
|
+
{
|
2259
|
+
if (!exists $indexes{$index} && NaturalDocs::Topics->TypeInfo($index)->Index())
|
2260
|
+
{ $bannedIndexes{$index} = 1; };
|
2261
|
+
};
|
2262
|
+
};
|
2263
|
+
|
2264
|
+
|
2265
|
+
#
|
2266
|
+
# Function: AddAndRemoveIndexes
|
2267
|
+
#
|
2268
|
+
# Automatically adds and removes index entries on the menu as necessary. <DetectIndexGroups()> should be called
|
2269
|
+
# beforehand.
|
2270
|
+
#
|
2271
|
+
sub AddAndRemoveIndexes
|
2272
|
+
{
|
2273
|
+
my ($self) = @_;
|
2274
|
+
|
2275
|
+
my %validIndexes;
|
2276
|
+
my @allIndexes = NaturalDocs::Topics->AllIndexableTypes();
|
2277
|
+
|
2278
|
+
foreach my $index (@allIndexes)
|
2279
|
+
{
|
2280
|
+
# Strip the banned indexes first so it's potentially less work for SymbolTable.
|
2281
|
+
if (!exists $bannedIndexes{$index})
|
2282
|
+
{ $validIndexes{$index} = 1; };
|
2283
|
+
};
|
2284
|
+
|
2285
|
+
%validIndexes = %{NaturalDocs::SymbolTable->HasIndexes(\%validIndexes)};
|
2286
|
+
|
2287
|
+
|
2288
|
+
# Delete dead indexes and find the best index group.
|
2289
|
+
|
2290
|
+
my @groupStack = ( $menu );
|
2291
|
+
|
2292
|
+
my $bestIndexGroup;
|
2293
|
+
my $bestIndexCount = 0;
|
2294
|
+
|
2295
|
+
while (scalar @groupStack)
|
2296
|
+
{
|
2297
|
+
my $currentGroup = pop @groupStack;
|
2298
|
+
my $index = 0;
|
2299
|
+
|
2300
|
+
my $currentIndexCount = 0;
|
2301
|
+
|
2302
|
+
while ($index < scalar @{$currentGroup->GroupContent()})
|
2303
|
+
{
|
2304
|
+
my $entry = $currentGroup->GroupContent()->[$index];
|
2305
|
+
|
2306
|
+
if ($entry->Type() == ::MENU_INDEX())
|
2307
|
+
{
|
2308
|
+
$currentIndexCount++;
|
2309
|
+
|
2310
|
+
if ($currentIndexCount > $bestIndexCount)
|
2311
|
+
{
|
2312
|
+
$bestIndexCount = $currentIndexCount;
|
2313
|
+
$bestIndexGroup = $currentGroup;
|
2314
|
+
};
|
2315
|
+
|
2316
|
+
# Remove it if it's dead.
|
2317
|
+
|
2318
|
+
if (!exists $validIndexes{ $entry->Target() })
|
2319
|
+
{
|
2320
|
+
$currentGroup->DeleteFromGroup($index);
|
2321
|
+
delete $indexes{ $entry->Target() };
|
2322
|
+
$hasChanged = 1;
|
2323
|
+
}
|
2324
|
+
else
|
2325
|
+
{ $index++; };
|
2326
|
+
}
|
2327
|
+
|
2328
|
+
else
|
2329
|
+
{
|
2330
|
+
if ($entry->Type() == ::MENU_GROUP())
|
2331
|
+
{ push @groupStack, $entry; };
|
2332
|
+
|
2333
|
+
$index++;
|
2334
|
+
};
|
2335
|
+
};
|
2336
|
+
};
|
2337
|
+
|
2338
|
+
|
2339
|
+
# Now add the new indexes.
|
2340
|
+
|
2341
|
+
foreach my $index (keys %indexes)
|
2342
|
+
{ delete $validIndexes{$index}; };
|
2343
|
+
|
2344
|
+
if (scalar keys %validIndexes)
|
2345
|
+
{
|
2346
|
+
# Add a group if there are no indexes at all.
|
2347
|
+
|
2348
|
+
if ($bestIndexCount == 0)
|
2349
|
+
{
|
2350
|
+
$menu->MarkEndOfOriginal();
|
2351
|
+
|
2352
|
+
my $newIndexGroup = NaturalDocs::Menu::Entry->New(::MENU_GROUP(), 'Index', undef,
|
2353
|
+
::MENU_GROUP_ISINDEXGROUP());
|
2354
|
+
$menu->PushToGroup($newIndexGroup);
|
2355
|
+
|
2356
|
+
$bestIndexGroup = $newIndexGroup;
|
2357
|
+
$menu->SetFlags( $menu->Flags() | ::MENU_GROUP_UPDATEORDER() | ::MENU_GROUP_UPDATESTRUCTURE() );
|
2358
|
+
};
|
2359
|
+
|
2360
|
+
# Add the new indexes.
|
2361
|
+
|
2362
|
+
$bestIndexGroup->MarkEndOfOriginal();
|
2363
|
+
my $isIndexGroup = $bestIndexGroup->Flags() & ::MENU_GROUP_ISINDEXGROUP();
|
2364
|
+
|
2365
|
+
foreach my $index (keys %validIndexes)
|
2366
|
+
{
|
2367
|
+
my $title;
|
2368
|
+
|
2369
|
+
if ($isIndexGroup)
|
2370
|
+
{
|
2371
|
+
if ($index eq ::TOPIC_GENERAL())
|
2372
|
+
{ $title = 'Everything'; }
|
2373
|
+
else
|
2374
|
+
{ $title = NaturalDocs::Topics->NameOfType($index, 1); };
|
2375
|
+
}
|
2376
|
+
else
|
2377
|
+
{
|
2378
|
+
$title = NaturalDocs::Topics->NameOfType($index) . ' Index';
|
2379
|
+
};
|
2380
|
+
|
2381
|
+
my $newEntry = NaturalDocs::Menu::Entry->New(::MENU_INDEX(), $title, $index, undef);
|
2382
|
+
$bestIndexGroup->PushToGroup($newEntry);
|
2383
|
+
|
2384
|
+
$indexes{$index} = 1;
|
2385
|
+
};
|
2386
|
+
|
2387
|
+
$bestIndexGroup->SetFlags( $bestIndexGroup->Flags() |
|
2388
|
+
::MENU_GROUP_UPDATEORDER() | ::MENU_GROUP_UPDATESTRUCTURE() );
|
2389
|
+
$hasChanged = 1;
|
2390
|
+
};
|
2391
|
+
};
|
2392
|
+
|
2393
|
+
|
2394
|
+
#
|
2395
|
+
# Function: RemoveDeadGroups
|
2396
|
+
#
|
2397
|
+
# Removes groups with less than two entries. It will always remove empty groups, and it will remove groups with one entry if it
|
2398
|
+
# has the <MENU_GROUP_UPDATESTRUCTURE> flag.
|
2399
|
+
#
|
2400
|
+
sub RemoveDeadGroups
|
2401
|
+
{
|
2402
|
+
my ($self) = @_;
|
2403
|
+
|
2404
|
+
my $index = 0;
|
2405
|
+
|
2406
|
+
while ($index < scalar @{$menu->GroupContent()})
|
2407
|
+
{
|
2408
|
+
my $entry = $menu->GroupContent()->[$index];
|
2409
|
+
|
2410
|
+
if ($entry->Type() == ::MENU_GROUP())
|
2411
|
+
{
|
2412
|
+
my $removed = $self->RemoveIfDead($entry, $menu, $index);
|
2413
|
+
|
2414
|
+
if (!$removed)
|
2415
|
+
{ $index++; };
|
2416
|
+
}
|
2417
|
+
else
|
2418
|
+
{ $index++; };
|
2419
|
+
};
|
2420
|
+
};
|
2421
|
+
|
2422
|
+
|
2423
|
+
#
|
2424
|
+
# Function: RemoveIfDead
|
2425
|
+
#
|
2426
|
+
# Checks a group and all its sub-groups for life and remove any that are dead. Empty groups are removed, and groups with one
|
2427
|
+
# entry and the <MENU_GROUP_UPDATESTRUCTURE> flag have their entry moved to the parent group.
|
2428
|
+
#
|
2429
|
+
# Parameters:
|
2430
|
+
#
|
2431
|
+
# groupEntry - The group to check for possible deletion.
|
2432
|
+
# parentGroupEntry - The parent group to move the single entry to if necessary.
|
2433
|
+
# parentGroupIndex - The index of the group in its parent.
|
2434
|
+
#
|
2435
|
+
# Returns:
|
2436
|
+
#
|
2437
|
+
# Whether the group was removed or not.
|
2438
|
+
#
|
2439
|
+
sub RemoveIfDead #(groupEntry, parentGroupEntry, parentGroupIndex)
|
2440
|
+
{
|
2441
|
+
my ($self, $groupEntry, $parentGroupEntry, $parentGroupIndex) = @_;
|
2442
|
+
|
2443
|
+
|
2444
|
+
# Do all sub-groups first, since their deletions will affect our UPDATESTRUCTURE flag and content count.
|
2445
|
+
|
2446
|
+
my $index = 0;
|
2447
|
+
while ($index < scalar @{$groupEntry->GroupContent()})
|
2448
|
+
{
|
2449
|
+
my $entry = $groupEntry->GroupContent()->[$index];
|
2450
|
+
|
2451
|
+
if ($entry->Type() == ::MENU_GROUP())
|
2452
|
+
{
|
2453
|
+
my $removed = $self->RemoveIfDead($entry, $groupEntry, $index);
|
2454
|
+
|
2455
|
+
if (!$removed)
|
2456
|
+
{ $index++; };
|
2457
|
+
}
|
2458
|
+
else
|
2459
|
+
{ $index++; };
|
2460
|
+
};
|
2461
|
+
|
2462
|
+
|
2463
|
+
# Now check ourself.
|
2464
|
+
|
2465
|
+
my $count = scalar @{$groupEntry->GroupContent()};
|
2466
|
+
if ($groupEntry->Flags() & ::MENU_GROUP_HASENDOFORIGINAL())
|
2467
|
+
{ $count--; };
|
2468
|
+
|
2469
|
+
if ($count == 0)
|
2470
|
+
{
|
2471
|
+
$parentGroupEntry->DeleteFromGroup($parentGroupIndex);
|
2472
|
+
|
2473
|
+
$parentGroupEntry->SetFlags( $parentGroupEntry->Flags() | ::MENU_GROUP_UPDATESTRUCTURE() );
|
2474
|
+
|
2475
|
+
$hasChanged = 1;
|
2476
|
+
return 1;
|
2477
|
+
}
|
2478
|
+
elsif ($count == 1 && ($groupEntry->Flags() & ::MENU_GROUP_UPDATESTRUCTURE()) )
|
2479
|
+
{
|
2480
|
+
my $onlyEntry = $groupEntry->GroupContent()->[0];
|
2481
|
+
if ($onlyEntry->Type() == ::MENU_ENDOFORIGINAL())
|
2482
|
+
{ $onlyEntry = $groupEntry->GroupContent()->[1]; };
|
2483
|
+
|
2484
|
+
$parentGroupEntry->DeleteFromGroup($parentGroupIndex);
|
2485
|
+
|
2486
|
+
$parentGroupEntry->MarkEndOfOriginal();
|
2487
|
+
$parentGroupEntry->PushToGroup($onlyEntry);
|
2488
|
+
|
2489
|
+
$parentGroupEntry->SetFlags( $parentGroupEntry->Flags() | ::MENU_GROUP_UPDATETITLES() |
|
2490
|
+
::MENU_GROUP_UPDATEORDER() | ::MENU_GROUP_UPDATESTRUCTURE() );
|
2491
|
+
|
2492
|
+
$hasChanged = 1;
|
2493
|
+
return 1;
|
2494
|
+
}
|
2495
|
+
else
|
2496
|
+
{ return undef; };
|
2497
|
+
};
|
2498
|
+
|
2499
|
+
|
2500
|
+
#
|
2501
|
+
# Function: DetectIndexGroups
|
2502
|
+
#
|
2503
|
+
# Finds groups that are primarily used for indexes and gives them the <MENU_GROUP_ISINDEXGROUP> flag.
|
2504
|
+
#
|
2505
|
+
sub DetectIndexGroups
|
2506
|
+
{
|
2507
|
+
my ($self) = @_;
|
2508
|
+
|
2509
|
+
my @groupStack = ( $menu );
|
2510
|
+
|
2511
|
+
while (scalar @groupStack)
|
2512
|
+
{
|
2513
|
+
my $groupEntry = pop @groupStack;
|
2514
|
+
|
2515
|
+
my $isIndexGroup = -1; # -1: Can't tell yet. 0: Can't be an index group. 1: Is an index group so far.
|
2516
|
+
|
2517
|
+
foreach my $entry (@{$groupEntry->GroupContent()})
|
2518
|
+
{
|
2519
|
+
if ($entry->Type() == ::MENU_INDEX())
|
2520
|
+
{
|
2521
|
+
if ($isIndexGroup == -1)
|
2522
|
+
{ $isIndexGroup = 1; };
|
2523
|
+
}
|
2524
|
+
|
2525
|
+
# Text is tolerated, but it still needs at least one index entry.
|
2526
|
+
elsif ($entry->Type() != ::MENU_TEXT())
|
2527
|
+
{
|
2528
|
+
$isIndexGroup = 0;
|
2529
|
+
|
2530
|
+
if ($entry->Type() == ::MENU_GROUP())
|
2531
|
+
{ push @groupStack, $entry; };
|
2532
|
+
};
|
2533
|
+
};
|
2534
|
+
|
2535
|
+
if ($isIndexGroup == 1)
|
2536
|
+
{
|
2537
|
+
$groupEntry->SetFlags( $groupEntry->Flags() | ::MENU_GROUP_ISINDEXGROUP() );
|
2538
|
+
};
|
2539
|
+
};
|
2540
|
+
};
|
2541
|
+
|
2542
|
+
|
2543
|
+
#
|
2544
|
+
# Function: CreateDirectorySubGroups
|
2545
|
+
#
|
2546
|
+
# Where possible, creates sub-groups based on directories for any long groups that have <MENU_GROUP_UPDATESTRUCTURE>
|
2547
|
+
# set. Clears the flag afterwards on groups that are short enough to not need any more sub-groups, but leaves it for the rest.
|
2548
|
+
#
|
2549
|
+
sub CreateDirectorySubGroups
|
2550
|
+
{
|
2551
|
+
my ($self) = @_;
|
2552
|
+
|
2553
|
+
my @groupStack = ( $menu );
|
2554
|
+
|
2555
|
+
foreach my $groupEntry (@groupStack)
|
2556
|
+
{
|
2557
|
+
if ($groupEntry->Flags() & ::MENU_GROUP_UPDATESTRUCTURE())
|
2558
|
+
{
|
2559
|
+
# Count the number of files.
|
2560
|
+
|
2561
|
+
my $fileCount = 0;
|
2562
|
+
|
2563
|
+
foreach my $entry (@{$groupEntry->GroupContent()})
|
2564
|
+
{
|
2565
|
+
if ($entry->Type() == ::MENU_FILE())
|
2566
|
+
{ $fileCount++; };
|
2567
|
+
};
|
2568
|
+
|
2569
|
+
|
2570
|
+
if ($fileCount > MAXFILESINGROUP)
|
2571
|
+
{
|
2572
|
+
my @sharedDirectories = $self->SharedDirectoriesOf($groupEntry);
|
2573
|
+
my $unsharedIndex = scalar @sharedDirectories;
|
2574
|
+
|
2575
|
+
# The keys are the first directory entries after the shared ones, and the values are the number of files that are in
|
2576
|
+
# that directory. Files that don't have subdirectories after the shared directories aren't included because they shouldn't
|
2577
|
+
# be put in a subgroup.
|
2578
|
+
my %directoryCounts;
|
2579
|
+
|
2580
|
+
foreach my $entry (@{$groupEntry->GroupContent()})
|
2581
|
+
{
|
2582
|
+
if ($entry->Type() == ::MENU_FILE())
|
2583
|
+
{
|
2584
|
+
my @entryDirectories = NaturalDocs::File->SplitDirectories( (NaturalDocs::File->SplitPath($entry->Target()))[1] );
|
2585
|
+
|
2586
|
+
if (scalar @entryDirectories > $unsharedIndex)
|
2587
|
+
{
|
2588
|
+
my $unsharedDirectory = $entryDirectories[$unsharedIndex];
|
2589
|
+
|
2590
|
+
if (!exists $directoryCounts{$unsharedDirectory})
|
2591
|
+
{ $directoryCounts{$unsharedDirectory} = 1; }
|
2592
|
+
else
|
2593
|
+
{ $directoryCounts{$unsharedDirectory}++; };
|
2594
|
+
};
|
2595
|
+
};
|
2596
|
+
};
|
2597
|
+
|
2598
|
+
|
2599
|
+
# Now create the subgroups.
|
2600
|
+
|
2601
|
+
# The keys are the first directory entries after the shared ones, and the values are the groups for those files to be
|
2602
|
+
# put in. There will only be entries for the groups with at least MINFILESINNEWGROUP files.
|
2603
|
+
my %directoryGroups;
|
2604
|
+
|
2605
|
+
while (my ($directory, $count) = each %directoryCounts)
|
2606
|
+
{
|
2607
|
+
if ($count >= MINFILESINNEWGROUP)
|
2608
|
+
{
|
2609
|
+
my $newGroup = NaturalDocs::Menu::Entry->New( ::MENU_GROUP(), ucfirst($directory), undef,
|
2610
|
+
::MENU_GROUP_UPDATETITLES() |
|
2611
|
+
::MENU_GROUP_UPDATEORDER() );
|
2612
|
+
|
2613
|
+
if ($count > MAXFILESINGROUP)
|
2614
|
+
{ $newGroup->SetFlags( $newGroup->Flags() | ::MENU_GROUP_UPDATESTRUCTURE()); };
|
2615
|
+
|
2616
|
+
$groupEntry->MarkEndOfOriginal();
|
2617
|
+
push @{$groupEntry->GroupContent()}, $newGroup;
|
2618
|
+
|
2619
|
+
$directoryGroups{$directory} = $newGroup;
|
2620
|
+
$fileCount -= $count;
|
2621
|
+
};
|
2622
|
+
};
|
2623
|
+
|
2624
|
+
|
2625
|
+
# Now fill the subgroups.
|
2626
|
+
|
2627
|
+
if (scalar keys %directoryGroups)
|
2628
|
+
{
|
2629
|
+
my $afterOriginal;
|
2630
|
+
my $index = 0;
|
2631
|
+
|
2632
|
+
while ($index < scalar @{$groupEntry->GroupContent()})
|
2633
|
+
{
|
2634
|
+
my $entry = $groupEntry->GroupContent()->[$index];
|
2635
|
+
|
2636
|
+
if ($entry->Type() == ::MENU_FILE())
|
2637
|
+
{
|
2638
|
+
my @entryDirectories =
|
2639
|
+
NaturalDocs::File->SplitDirectories( (NaturalDocs::File->SplitPath($entry->Target()))[1] );
|
2640
|
+
|
2641
|
+
my $unsharedDirectory = $entryDirectories[$unsharedIndex];
|
2642
|
+
|
2643
|
+
if (exists $directoryGroups{$unsharedDirectory})
|
2644
|
+
{
|
2645
|
+
my $targetGroup = $directoryGroups{$unsharedDirectory};
|
2646
|
+
|
2647
|
+
if ($afterOriginal)
|
2648
|
+
{ $targetGroup->MarkEndOfOriginal(); };
|
2649
|
+
$targetGroup->PushToGroup($entry);
|
2650
|
+
|
2651
|
+
$groupEntry->DeleteFromGroup($index);
|
2652
|
+
}
|
2653
|
+
else
|
2654
|
+
{ $index++; };
|
2655
|
+
}
|
2656
|
+
|
2657
|
+
elsif ($entry->Type() == ::MENU_ENDOFORIGINAL())
|
2658
|
+
{
|
2659
|
+
$afterOriginal = 1;
|
2660
|
+
$index++;
|
2661
|
+
}
|
2662
|
+
|
2663
|
+
elsif ($entry->Type() == ::MENU_GROUP())
|
2664
|
+
{
|
2665
|
+
# See if we need to relocate this group.
|
2666
|
+
|
2667
|
+
my @groupDirectories = $self->SharedDirectoriesOf($entry);
|
2668
|
+
|
2669
|
+
# The group's shared directories must be at least two levels deeper than the current. If the first level deeper
|
2670
|
+
# is a new group, move it there because it's a subdirectory of that one.
|
2671
|
+
if (scalar @groupDirectories - scalar @sharedDirectories >= 2)
|
2672
|
+
{
|
2673
|
+
my $unsharedDirectory = $groupDirectories[$unsharedIndex];
|
2674
|
+
|
2675
|
+
if (exists $directoryGroups{$unsharedDirectory} &&
|
2676
|
+
$directoryGroups{$unsharedDirectory} != $entry)
|
2677
|
+
{
|
2678
|
+
my $targetGroup = $directoryGroups{$unsharedDirectory};
|
2679
|
+
|
2680
|
+
if ($afterOriginal)
|
2681
|
+
{ $targetGroup->MarkEndOfOriginal(); };
|
2682
|
+
$targetGroup->PushToGroup($entry);
|
2683
|
+
|
2684
|
+
$groupEntry->DeleteFromGroup($index);
|
2685
|
+
|
2686
|
+
# We need to retitle the group if it has the name of the unshared directory.
|
2687
|
+
|
2688
|
+
my $oldTitle = $entry->Title();
|
2689
|
+
$oldTitle =~ s/ +//g;
|
2690
|
+
$unsharedDirectory =~ s/ +//g;
|
2691
|
+
|
2692
|
+
if (lc($oldTitle) eq lc($unsharedDirectory))
|
2693
|
+
{
|
2694
|
+
$entry->SetTitle($groupDirectories[$unsharedIndex + 1]);
|
2695
|
+
};
|
2696
|
+
}
|
2697
|
+
else
|
2698
|
+
{ $index++; };
|
2699
|
+
}
|
2700
|
+
else
|
2701
|
+
{ $index++; };
|
2702
|
+
}
|
2703
|
+
|
2704
|
+
else
|
2705
|
+
{ $index++; };
|
2706
|
+
};
|
2707
|
+
|
2708
|
+
$hasChanged = 1;
|
2709
|
+
|
2710
|
+
if ($fileCount <= MAXFILESINGROUP)
|
2711
|
+
{ $groupEntry->SetFlags( $groupEntry->Flags() & ~::MENU_GROUP_UPDATESTRUCTURE() ); };
|
2712
|
+
|
2713
|
+
$groupEntry->SetFlags( $groupEntry->Flags() | ::MENU_GROUP_UPDATETITLES() |
|
2714
|
+
::MENU_GROUP_UPDATEORDER() );
|
2715
|
+
};
|
2716
|
+
|
2717
|
+
}; # If group has >MAXFILESINGROUP files
|
2718
|
+
}; # If group has UPDATESTRUCTURE
|
2719
|
+
|
2720
|
+
|
2721
|
+
# Okay, now go through all the subgroups. We do this after the above so that newly created groups can get subgrouped
|
2722
|
+
# further.
|
2723
|
+
|
2724
|
+
foreach my $entry (@{$groupEntry->GroupContent()})
|
2725
|
+
{
|
2726
|
+
if ($entry->Type() == ::MENU_GROUP())
|
2727
|
+
{ push @groupStack, $entry; };
|
2728
|
+
};
|
2729
|
+
|
2730
|
+
}; # For each group entry
|
2731
|
+
};
|
2732
|
+
|
2733
|
+
|
2734
|
+
#
|
2735
|
+
# Function: DetectOrder
|
2736
|
+
#
|
2737
|
+
# Detects the order of the entries in all groups that have the <MENU_GROUP_UPDATEORDER> flag set. Will set one of the
|
2738
|
+
# <MENU_GROUP_FILESSORTED>, <MENU_GROUP_FILESANDGROUPSSORTED>, <MENU_GROUP_EVERYTHINGSORTED>, or
|
2739
|
+
# <MENU_GROUP_UNSORTED> flags. It will always go for the most comprehensive sort possible, so if a group only has one
|
2740
|
+
# entry, it will be flagged as <MENU_GROUP_EVERYTHINGSORTED>.
|
2741
|
+
#
|
2742
|
+
# <DetectIndexGroups()> should be called beforehand, as the <MENU_GROUP_ISINDEXGROUP> flag affects how the order is
|
2743
|
+
# detected.
|
2744
|
+
#
|
2745
|
+
# The sort detection stops if it reaches a <MENU_ENDOFORIGINAL> entry, so new entries can be added to the end while still
|
2746
|
+
# allowing the original sort to be detected.
|
2747
|
+
#
|
2748
|
+
# Parameters:
|
2749
|
+
#
|
2750
|
+
# forceAll - If set, the order will be detected for all groups regardless of whether <MENU_GROUP_UPDATEORDER> is set.
|
2751
|
+
#
|
2752
|
+
sub DetectOrder #(forceAll)
|
2753
|
+
{
|
2754
|
+
my ($self, $forceAll) = @_;
|
2755
|
+
my @groupStack = ( $menu );
|
2756
|
+
|
2757
|
+
while (scalar @groupStack)
|
2758
|
+
{
|
2759
|
+
my $groupEntry = pop @groupStack;
|
2760
|
+
my $index = 0;
|
2761
|
+
|
2762
|
+
|
2763
|
+
# First detect the sort.
|
2764
|
+
|
2765
|
+
if ($forceAll || ($groupEntry->Flags() & ::MENU_GROUP_UPDATEORDER()) )
|
2766
|
+
{
|
2767
|
+
my $order = ::MENU_GROUP_EVERYTHINGSORTED();
|
2768
|
+
|
2769
|
+
my $lastFile;
|
2770
|
+
my $lastFileOrGroup;
|
2771
|
+
|
2772
|
+
while ($index < scalar @{$groupEntry->GroupContent()} &&
|
2773
|
+
$groupEntry->GroupContent()->[$index]->Type() != ::MENU_ENDOFORIGINAL() &&
|
2774
|
+
$order != ::MENU_GROUP_UNSORTED())
|
2775
|
+
{
|
2776
|
+
my $entry = $groupEntry->GroupContent()->[$index];
|
2777
|
+
|
2778
|
+
|
2779
|
+
# Ignore the last entry if it's an index group. We don't want it to affect the sort.
|
2780
|
+
|
2781
|
+
if ($index + 1 == scalar @{$groupEntry->GroupContent()} &&
|
2782
|
+
$entry->Type() == ::MENU_GROUP() && ($entry->Flags() & ::MENU_GROUP_ISINDEXGROUP()) )
|
2783
|
+
{
|
2784
|
+
# Ignore.
|
2785
|
+
|
2786
|
+
# This is an awkward code construct, basically working towards an else instead of using an if, but the code just gets
|
2787
|
+
# too hard to read otherwise. The compiled code should work out to roughly the same thing anyway.
|
2788
|
+
}
|
2789
|
+
|
2790
|
+
|
2791
|
+
# Ignore the first entry if it's the general index in an index group. We don't want it to affect the sort.
|
2792
|
+
|
2793
|
+
elsif ($index == 0 && ($groupEntry->Flags() & ::MENU_GROUP_ISINDEXGROUP()) &&
|
2794
|
+
$entry->Type() == ::MENU_INDEX() && $entry->Target() eq ::TOPIC_GENERAL() )
|
2795
|
+
{
|
2796
|
+
# Ignore.
|
2797
|
+
}
|
2798
|
+
|
2799
|
+
|
2800
|
+
# Degenerate the sort.
|
2801
|
+
|
2802
|
+
else
|
2803
|
+
{
|
2804
|
+
|
2805
|
+
if ($order == ::MENU_GROUP_EVERYTHINGSORTED() && $index > 0 &&
|
2806
|
+
::StringCompare($entry->Title(), $groupEntry->GroupContent()->[$index - 1]->Title()) < 0)
|
2807
|
+
{ $order = ::MENU_GROUP_FILESANDGROUPSSORTED(); };
|
2808
|
+
|
2809
|
+
if ($order == ::MENU_GROUP_FILESANDGROUPSSORTED() &&
|
2810
|
+
($entry->Type() == ::MENU_FILE() || $entry->Type() == ::MENU_GROUP()) &&
|
2811
|
+
defined $lastFileOrGroup && ::StringCompare($entry->Title(), $lastFileOrGroup->Title()) < 0)
|
2812
|
+
{ $order = ::MENU_GROUP_FILESSORTED(); };
|
2813
|
+
|
2814
|
+
if ($order == ::MENU_GROUP_FILESSORTED() &&
|
2815
|
+
$entry->Type() == ::MENU_FILE() && defined $lastFile &&
|
2816
|
+
::StringCompare($entry->Title(), $lastFile->Title()) < 0)
|
2817
|
+
{ $order = ::MENU_GROUP_UNSORTED(); };
|
2818
|
+
|
2819
|
+
};
|
2820
|
+
|
2821
|
+
|
2822
|
+
# Set the lastX parameters for comparison and add sub-groups to the stack.
|
2823
|
+
|
2824
|
+
if ($entry->Type() == ::MENU_FILE())
|
2825
|
+
{
|
2826
|
+
$lastFile = $entry;
|
2827
|
+
$lastFileOrGroup = $entry;
|
2828
|
+
}
|
2829
|
+
elsif ($entry->Type() == ::MENU_GROUP())
|
2830
|
+
{
|
2831
|
+
$lastFileOrGroup = $entry;
|
2832
|
+
push @groupStack, $entry;
|
2833
|
+
};
|
2834
|
+
|
2835
|
+
$index++;
|
2836
|
+
};
|
2837
|
+
|
2838
|
+
$groupEntry->SetFlags($groupEntry->Flags() | $order);
|
2839
|
+
};
|
2840
|
+
|
2841
|
+
|
2842
|
+
# Find any subgroups in the remaining entries.
|
2843
|
+
|
2844
|
+
while ($index < scalar @{$groupEntry->GroupContent()})
|
2845
|
+
{
|
2846
|
+
my $entry = $groupEntry->GroupContent()->[$index];
|
2847
|
+
|
2848
|
+
if ($entry->Type() == ::MENU_GROUP())
|
2849
|
+
{ push @groupStack, $entry; };
|
2850
|
+
|
2851
|
+
$index++;
|
2852
|
+
};
|
2853
|
+
};
|
2854
|
+
};
|
2855
|
+
|
2856
|
+
|
2857
|
+
#
|
2858
|
+
# Function: GenerateAutoFileTitles
|
2859
|
+
#
|
2860
|
+
# Creates titles for the unlocked file entries in all groups that have the <MENU_GROUP_UPDATETITLES> flag set. It clears the
|
2861
|
+
# flag afterwards so it can be used efficiently for multiple sweeps.
|
2862
|
+
#
|
2863
|
+
# Parameters:
|
2864
|
+
#
|
2865
|
+
# forceAll - If set, forces all the unlocked file titles to update regardless of whether the group has the
|
2866
|
+
# <MENU_GROUP_UPDATETITLES> flag set.
|
2867
|
+
#
|
2868
|
+
sub GenerateAutoFileTitles #(forceAll)
|
2869
|
+
{
|
2870
|
+
my ($self, $forceAll) = @_;
|
2871
|
+
|
2872
|
+
my @groupStack = ( $menu );
|
2873
|
+
|
2874
|
+
while (scalar @groupStack)
|
2875
|
+
{
|
2876
|
+
my $groupEntry = pop @groupStack;
|
2877
|
+
|
2878
|
+
if ($forceAll || ($groupEntry->Flags() & ::MENU_GROUP_UPDATETITLES()) )
|
2879
|
+
{
|
2880
|
+
# Find common prefixes and paths to strip from the default menu titles.
|
2881
|
+
|
2882
|
+
my @sharedDirectories = $self->SharedDirectoriesOf($groupEntry);
|
2883
|
+
my $noSharedDirectories = (scalar @sharedDirectories == 0);
|
2884
|
+
|
2885
|
+
my @sharedPrefixes;
|
2886
|
+
my $noSharedPrefixes;
|
2887
|
+
|
2888
|
+
foreach my $entry (@{$groupEntry->GroupContent()})
|
2889
|
+
{
|
2890
|
+
if ($entry->Type() == ::MENU_FILE())
|
2891
|
+
{
|
2892
|
+
# Find the common prefixes among all file entries that are unlocked and don't use the file name as their default title.
|
2893
|
+
|
2894
|
+
my $defaultTitle = NaturalDocs::Project->DefaultMenuTitleOf($entry->Target());
|
2895
|
+
|
2896
|
+
if (!$noSharedPrefixes && ($entry->Flags() & ::MENU_FILE_NOAUTOTITLE()) == 0 &&
|
2897
|
+
$defaultTitle ne $entry->Target())
|
2898
|
+
{
|
2899
|
+
# If the filename is part of the title, separate it off so no part of it gets included as a common prefix. This would
|
2900
|
+
# happen if there's a group with only one file in it (Project.h => h) or only files that differ by extension
|
2901
|
+
# (Project.h, Project.cpp => h, cpp) and people labeled them manually (// File: Project.h).
|
2902
|
+
my $filename = (NaturalDocs::File->SplitPath($entry->Target()))[2];
|
2903
|
+
my $filenamePart;
|
2904
|
+
|
2905
|
+
if ( length $defaultTitle >= length $filename &&
|
2906
|
+
lc(substr($defaultTitle, 0 - length($filename))) eq lc($filename) )
|
2907
|
+
{
|
2908
|
+
$filenamePart = substr($defaultTitle, 0 - length($filename));
|
2909
|
+
$defaultTitle = substr($defaultTitle, 0, 0 - length($filename));
|
2910
|
+
};
|
2911
|
+
|
2912
|
+
|
2913
|
+
my @entryPrefixes = split(/(\.|::|->)/, $defaultTitle);
|
2914
|
+
|
2915
|
+
# Remove potential leading undef/empty string.
|
2916
|
+
if (!length $entryPrefixes[0])
|
2917
|
+
{ shift @entryPrefixes; };
|
2918
|
+
|
2919
|
+
# Remove last entry. Something has to exist for the title. If we already separated off the filename, that will be
|
2920
|
+
# it instead.
|
2921
|
+
if (!$filenamePart)
|
2922
|
+
{ pop @entryPrefixes; };
|
2923
|
+
|
2924
|
+
if (!scalar @entryPrefixes)
|
2925
|
+
{ $noSharedPrefixes = 1; }
|
2926
|
+
elsif (!scalar @sharedPrefixes)
|
2927
|
+
{ @sharedPrefixes = @entryPrefixes; }
|
2928
|
+
elsif ($entryPrefixes[0] ne $sharedPrefixes[0])
|
2929
|
+
{ $noSharedPrefixes = 1; }
|
2930
|
+
|
2931
|
+
# If both arrays have entries, and the first is shared...
|
2932
|
+
else
|
2933
|
+
{
|
2934
|
+
my $index = 1;
|
2935
|
+
|
2936
|
+
while ($index < scalar @sharedPrefixes && $entryPrefixes[$index] eq $sharedPrefixes[$index])
|
2937
|
+
{ $index++; };
|
2938
|
+
|
2939
|
+
if ($index < scalar @sharedPrefixes)
|
2940
|
+
{ splice(@sharedPrefixes, $index); };
|
2941
|
+
};
|
2942
|
+
};
|
2943
|
+
|
2944
|
+
}; # if entry is MENU_FILE
|
2945
|
+
}; # foreach entry in group content.
|
2946
|
+
|
2947
|
+
|
2948
|
+
if (!scalar @sharedPrefixes)
|
2949
|
+
{ $noSharedPrefixes = 1; };
|
2950
|
+
|
2951
|
+
|
2952
|
+
# Update all the menu titles of unlocked file entries.
|
2953
|
+
|
2954
|
+
foreach my $entry (@{$groupEntry->GroupContent()})
|
2955
|
+
{
|
2956
|
+
if ($entry->Type() == ::MENU_FILE() && ($entry->Flags() & ::MENU_FILE_NOAUTOTITLE()) == 0)
|
2957
|
+
{
|
2958
|
+
my $title = NaturalDocs::Project->DefaultMenuTitleOf($entry->Target());
|
2959
|
+
|
2960
|
+
if ($title eq $entry->Target())
|
2961
|
+
{
|
2962
|
+
my ($volume, $directoryString, $file) = NaturalDocs::File->SplitPath($entry->Target());
|
2963
|
+
my @directories = NaturalDocs::File->SplitDirectories($directoryString);
|
2964
|
+
|
2965
|
+
if (!$noSharedDirectories)
|
2966
|
+
{ splice(@directories, 0, scalar @sharedDirectories); };
|
2967
|
+
|
2968
|
+
# directory\...\directory\file.ext
|
2969
|
+
|
2970
|
+
if (scalar @directories > 2)
|
2971
|
+
{ @directories = ( $directories[0], '...', $directories[-1] ); };
|
2972
|
+
|
2973
|
+
$directoryString = NaturalDocs::File->JoinDirectories(@directories);
|
2974
|
+
$title = NaturalDocs::File->JoinPaths($directoryString, $file);
|
2975
|
+
}
|
2976
|
+
|
2977
|
+
else
|
2978
|
+
{
|
2979
|
+
my $filename = (NaturalDocs::File->SplitPath($entry->Target()))[2];
|
2980
|
+
my $filenamePart;
|
2981
|
+
|
2982
|
+
if ( length $title >= length $filename &&
|
2983
|
+
lc(substr($title, 0 - length($filename))) eq lc($filename) )
|
2984
|
+
{
|
2985
|
+
$filenamePart = substr($title, 0 - length($filename));
|
2986
|
+
$title = substr($title, 0, 0 - length($filename));
|
2987
|
+
};
|
2988
|
+
|
2989
|
+
my @segments = split(/(::|\.|->)/, $title);
|
2990
|
+
if (!length $segments[0])
|
2991
|
+
{ shift @segments; };
|
2992
|
+
|
2993
|
+
if ($filenamePart)
|
2994
|
+
{ push @segments, $filenamePart; };
|
2995
|
+
|
2996
|
+
if (!$noSharedPrefixes)
|
2997
|
+
{ splice(@segments, 0, scalar @sharedPrefixes); };
|
2998
|
+
|
2999
|
+
# package...package::target
|
3000
|
+
|
3001
|
+
if (scalar @segments > 5)
|
3002
|
+
{ splice(@segments, 1, scalar @segments - 4, '...'); };
|
3003
|
+
|
3004
|
+
$title = join('', @segments);
|
3005
|
+
};
|
3006
|
+
|
3007
|
+
$entry->SetTitle($title);
|
3008
|
+
}; # If entry is an unlocked file
|
3009
|
+
}; # Foreach entry
|
3010
|
+
|
3011
|
+
$groupEntry->SetFlags( $groupEntry->Flags() & ~::MENU_GROUP_UPDATETITLES() );
|
3012
|
+
|
3013
|
+
}; # If updating group titles
|
3014
|
+
|
3015
|
+
# Now find any subgroups.
|
3016
|
+
foreach my $entry (@{$groupEntry->GroupContent()})
|
3017
|
+
{
|
3018
|
+
if ($entry->Type() == ::MENU_GROUP())
|
3019
|
+
{ push @groupStack, $entry; };
|
3020
|
+
};
|
3021
|
+
};
|
3022
|
+
|
3023
|
+
};
|
3024
|
+
|
3025
|
+
|
3026
|
+
#
|
3027
|
+
# Function: ResortGroups
|
3028
|
+
#
|
3029
|
+
# Resorts all groups that have <MENU_GROUP_UPDATEORDER> set. Assumes <DetectOrder()> and <GenerateAutoFileTitles()>
|
3030
|
+
# have already been called. Will clear the flag and any <MENU_ENDOFORIGINAL> entries on reordered groups.
|
3031
|
+
#
|
3032
|
+
# Parameters:
|
3033
|
+
#
|
3034
|
+
# forceAll - If set, resorts all groups regardless of whether <MENU_GROUP_UPDATEORDER> is set.
|
3035
|
+
#
|
3036
|
+
sub ResortGroups #(forceAll)
|
3037
|
+
{
|
3038
|
+
my ($self, $forceAll) = @_;
|
3039
|
+
my @groupStack = ( $menu );
|
3040
|
+
|
3041
|
+
while (scalar @groupStack)
|
3042
|
+
{
|
3043
|
+
my $groupEntry = pop @groupStack;
|
3044
|
+
|
3045
|
+
if ($forceAll || ($groupEntry->Flags() & ::MENU_GROUP_UPDATEORDER()) )
|
3046
|
+
{
|
3047
|
+
my $newEntriesIndex;
|
3048
|
+
|
3049
|
+
|
3050
|
+
# Strip the ENDOFORIGINAL.
|
3051
|
+
|
3052
|
+
if ($groupEntry->Flags() & ::MENU_GROUP_HASENDOFORIGINAL())
|
3053
|
+
{
|
3054
|
+
$newEntriesIndex = 0;
|
3055
|
+
|
3056
|
+
while ($newEntriesIndex < scalar @{$groupEntry->GroupContent()} &&
|
3057
|
+
$groupEntry->GroupContent()->[$newEntriesIndex]->Type() != ::MENU_ENDOFORIGINAL() )
|
3058
|
+
{ $newEntriesIndex++; };
|
3059
|
+
|
3060
|
+
$groupEntry->DeleteFromGroup($newEntriesIndex);
|
3061
|
+
|
3062
|
+
$groupEntry->SetFlags( $groupEntry->Flags() & ~::MENU_GROUP_HASENDOFORIGINAL() );
|
3063
|
+
}
|
3064
|
+
else
|
3065
|
+
{ $newEntriesIndex = -1; };
|
3066
|
+
|
3067
|
+
|
3068
|
+
# Strip the exceptions.
|
3069
|
+
|
3070
|
+
my $trailingIndexGroup;
|
3071
|
+
my $leadingGeneralIndex;
|
3072
|
+
|
3073
|
+
if ( ($groupEntry->Flags() & ::MENU_GROUP_ISINDEXGROUP()) &&
|
3074
|
+
$groupEntry->GroupContent()->[0]->Type() == ::MENU_INDEX() &&
|
3075
|
+
$groupEntry->GroupContent()->[0]->Target() eq ::TOPIC_GENERAL() )
|
3076
|
+
{
|
3077
|
+
$leadingGeneralIndex = shift @{$groupEntry->GroupContent()};
|
3078
|
+
if ($newEntriesIndex != -1)
|
3079
|
+
{ $newEntriesIndex--; };
|
3080
|
+
}
|
3081
|
+
|
3082
|
+
elsif (scalar @{$groupEntry->GroupContent()} && $newEntriesIndex != 0)
|
3083
|
+
{
|
3084
|
+
my $lastIndex;
|
3085
|
+
|
3086
|
+
if ($newEntriesIndex != -1)
|
3087
|
+
{ $lastIndex = $newEntriesIndex - 1; }
|
3088
|
+
else
|
3089
|
+
{ $lastIndex = scalar @{$groupEntry->GroupContent()} - 1; };
|
3090
|
+
|
3091
|
+
if ($groupEntry->GroupContent()->[$lastIndex]->Type() == ::MENU_GROUP() &&
|
3092
|
+
( $groupEntry->GroupContent()->[$lastIndex]->Flags() & ::MENU_GROUP_ISINDEXGROUP() ) )
|
3093
|
+
{
|
3094
|
+
$trailingIndexGroup = $groupEntry->GroupContent()->[$lastIndex];
|
3095
|
+
$groupEntry->DeleteFromGroup($lastIndex);
|
3096
|
+
|
3097
|
+
if ($newEntriesIndex != -1)
|
3098
|
+
{ $newEntriesIndex++; };
|
3099
|
+
};
|
3100
|
+
};
|
3101
|
+
|
3102
|
+
|
3103
|
+
# If there weren't already exceptions, strip them from the new entries.
|
3104
|
+
|
3105
|
+
if ( (!defined $trailingIndexGroup || !defined $leadingGeneralIndex) && $newEntriesIndex != -1)
|
3106
|
+
{
|
3107
|
+
my $index = $newEntriesIndex;
|
3108
|
+
|
3109
|
+
while ($index < scalar @{$groupEntry->GroupContent()})
|
3110
|
+
{
|
3111
|
+
my $entry = $groupEntry->GroupContent()->[$index];
|
3112
|
+
|
3113
|
+
if (!defined $trailingIndexGroup &&
|
3114
|
+
$entry->Type() == ::MENU_GROUP() && ($entry->Flags() & ::MENU_GROUP_ISINDEXGROUP()) )
|
3115
|
+
{
|
3116
|
+
$trailingIndexGroup = $entry;
|
3117
|
+
$groupEntry->DeleteFromGroup($index);
|
3118
|
+
}
|
3119
|
+
elsif (!defined $leadingGeneralIndex && ($groupEntry->Flags() & ::MENU_GROUP_ISINDEXGROUP()) &&
|
3120
|
+
$entry->Type() == ::MENU_INDEX() && !defined $entry->Target())
|
3121
|
+
{
|
3122
|
+
$leadingGeneralIndex = $entry;
|
3123
|
+
$groupEntry->DeleteFromGroup($index);
|
3124
|
+
}
|
3125
|
+
else
|
3126
|
+
{ $index++; };
|
3127
|
+
};
|
3128
|
+
};
|
3129
|
+
|
3130
|
+
|
3131
|
+
# If there's no order, we still want to sort the new additions.
|
3132
|
+
|
3133
|
+
if ($groupEntry->Flags() & ::MENU_GROUP_UNSORTED())
|
3134
|
+
{
|
3135
|
+
if ($newEntriesIndex != -1)
|
3136
|
+
{
|
3137
|
+
my @newEntries =
|
3138
|
+
@{$groupEntry->GroupContent()}[$newEntriesIndex..scalar @{$groupEntry->GroupContent()} - 1];
|
3139
|
+
|
3140
|
+
@newEntries = sort { $self->CompareEntries($a, $b) } @newEntries;
|
3141
|
+
|
3142
|
+
foreach my $newEntry (@newEntries)
|
3143
|
+
{
|
3144
|
+
$groupEntry->GroupContent()->[$newEntriesIndex] = $newEntry;
|
3145
|
+
$newEntriesIndex++;
|
3146
|
+
};
|
3147
|
+
};
|
3148
|
+
}
|
3149
|
+
|
3150
|
+
elsif ($groupEntry->Flags() & ::MENU_GROUP_EVERYTHINGSORTED())
|
3151
|
+
{
|
3152
|
+
@{$groupEntry->GroupContent()} = sort { $self->CompareEntries($a, $b) } @{$groupEntry->GroupContent()};
|
3153
|
+
}
|
3154
|
+
|
3155
|
+
elsif ( ($groupEntry->Flags() & ::MENU_GROUP_FILESSORTED()) ||
|
3156
|
+
($groupEntry->Flags() & ::MENU_GROUP_FILESANDGROUPSSORTED()) )
|
3157
|
+
{
|
3158
|
+
my $groupContent = $groupEntry->GroupContent();
|
3159
|
+
my @newEntries;
|
3160
|
+
|
3161
|
+
if ($newEntriesIndex != -1)
|
3162
|
+
{ @newEntries = splice( @$groupContent, $newEntriesIndex ); };
|
3163
|
+
|
3164
|
+
|
3165
|
+
# First resort the existing entries.
|
3166
|
+
|
3167
|
+
# A couple of support functions. They're defined here instead of spun off into their own functions because they're only
|
3168
|
+
# used here and to make them general we would need to add support for the other sort options.
|
3169
|
+
|
3170
|
+
sub IsIncludedInSort #(groupEntry, entry)
|
3171
|
+
{
|
3172
|
+
my ($self, $groupEntry, $entry) = @_;
|
3173
|
+
|
3174
|
+
return ($entry->Type() == ::MENU_FILE() ||
|
3175
|
+
( $entry->Type() == ::MENU_GROUP() &&
|
3176
|
+
($groupEntry->Flags() & ::MENU_GROUP_FILESANDGROUPSSORTED()) ) );
|
3177
|
+
};
|
3178
|
+
|
3179
|
+
sub IsSorted #(groupEntry)
|
3180
|
+
{
|
3181
|
+
my ($self, $groupEntry) = @_;
|
3182
|
+
my $lastApplicable;
|
3183
|
+
|
3184
|
+
foreach my $entry (@{$groupEntry->GroupContent()})
|
3185
|
+
{
|
3186
|
+
# If the entry is applicable to the sort order...
|
3187
|
+
if ($self->IsIncludedInSort($groupEntry, $entry))
|
3188
|
+
{
|
3189
|
+
if (defined $lastApplicable)
|
3190
|
+
{
|
3191
|
+
if ($self->CompareEntries($entry, $lastApplicable) < 0)
|
3192
|
+
{ return undef; };
|
3193
|
+
};
|
3194
|
+
|
3195
|
+
$lastApplicable = $entry;
|
3196
|
+
};
|
3197
|
+
};
|
3198
|
+
|
3199
|
+
return 1;
|
3200
|
+
};
|
3201
|
+
|
3202
|
+
|
3203
|
+
# There's a good chance it's still sorted. They should only become unsorted if an auto-title changes.
|
3204
|
+
if (!$self->IsSorted($groupEntry))
|
3205
|
+
{
|
3206
|
+
# Crap. Okay, method one is to sort each group of continuous sortable elements. There's a possibility that doing
|
3207
|
+
# this will cause the whole to become sorted again. We try this first, even though it isn't guaranteed to succeed,
|
3208
|
+
# because it will restore the sort without moving any unsortable entries.
|
3209
|
+
|
3210
|
+
# Copy it because we'll need the original if this fails.
|
3211
|
+
my @originalGroupContent = @$groupContent;
|
3212
|
+
|
3213
|
+
my $index = 0;
|
3214
|
+
my $startSortable = 0;
|
3215
|
+
|
3216
|
+
while (1)
|
3217
|
+
{
|
3218
|
+
# If index is on an unsortable entry or the end of the array...
|
3219
|
+
if ($index == scalar @$groupContent || !$self->IsIncludedInSort($groupEntry, $groupContent->[$index]))
|
3220
|
+
{
|
3221
|
+
# If we have at least two sortable entries...
|
3222
|
+
if ($index - $startSortable >= 2)
|
3223
|
+
{
|
3224
|
+
# Sort them.
|
3225
|
+
my @sortableEntries = @{$groupContent}[$startSortable .. $index - 1];
|
3226
|
+
@sortableEntries = sort { $self->CompareEntries($a, $b) } @sortableEntries;
|
3227
|
+
foreach my $sortableEntry (@sortableEntries)
|
3228
|
+
{
|
3229
|
+
$groupContent->[$startSortable] = $sortableEntry;
|
3230
|
+
$startSortable++;
|
3231
|
+
};
|
3232
|
+
};
|
3233
|
+
|
3234
|
+
if ($index == scalar @$groupContent)
|
3235
|
+
{ last; };
|
3236
|
+
|
3237
|
+
$startSortable = $index + 1;
|
3238
|
+
};
|
3239
|
+
|
3240
|
+
$index++;
|
3241
|
+
};
|
3242
|
+
|
3243
|
+
if (!$self->IsSorted($groupEntry))
|
3244
|
+
{
|
3245
|
+
# Crap crap. Okay, now we do a full sort but with potential damage to the original structure. Each unsortable
|
3246
|
+
# element is locked to the next sortable element. We sort the sortable elements, bringing all the unsortable
|
3247
|
+
# pieces with them.
|
3248
|
+
|
3249
|
+
my @pieces = ( [ ] );
|
3250
|
+
my $currentPiece = $pieces[0];
|
3251
|
+
|
3252
|
+
foreach my $entry (@originalGroupContent)
|
3253
|
+
{
|
3254
|
+
push @$currentPiece, $entry;
|
3255
|
+
|
3256
|
+
# If the entry is sortable...
|
3257
|
+
if ($self->IsIncludedInSort($groupEntry, $entry))
|
3258
|
+
{
|
3259
|
+
$currentPiece = [ ];
|
3260
|
+
push @pieces, $currentPiece;
|
3261
|
+
};
|
3262
|
+
};
|
3263
|
+
|
3264
|
+
my $lastUnsortablePiece;
|
3265
|
+
|
3266
|
+
# If the last entry was sortable, we'll have an empty piece at the end. Drop it.
|
3267
|
+
if (scalar @{$pieces[-1]} == 0)
|
3268
|
+
{ pop @pieces; }
|
3269
|
+
|
3270
|
+
# If the last entry wasn't sortable, the last piece won't end with a sortable element. Save it, but remove it
|
3271
|
+
# from the list.
|
3272
|
+
else
|
3273
|
+
{ $lastUnsortablePiece = pop @pieces; };
|
3274
|
+
|
3275
|
+
# Sort the list.
|
3276
|
+
@pieces = sort { $self->CompareEntries( $a->[-1], $b->[-1] ) } @pieces;
|
3277
|
+
|
3278
|
+
# Copy it back to the original.
|
3279
|
+
if (defined $lastUnsortablePiece)
|
3280
|
+
{ push @pieces, $lastUnsortablePiece; };
|
3281
|
+
|
3282
|
+
my $index = 0;
|
3283
|
+
|
3284
|
+
foreach my $piece (@pieces)
|
3285
|
+
{
|
3286
|
+
foreach my $entry (@{$piece})
|
3287
|
+
{
|
3288
|
+
$groupEntry->GroupContent()->[$index] = $entry;
|
3289
|
+
$index++;
|
3290
|
+
};
|
3291
|
+
};
|
3292
|
+
};
|
3293
|
+
};
|
3294
|
+
|
3295
|
+
|
3296
|
+
# Okay, the orginal entries are sorted now. Sort the new entries and apply.
|
3297
|
+
|
3298
|
+
if (scalar @newEntries)
|
3299
|
+
{
|
3300
|
+
@newEntries = sort { $self->CompareEntries($a, $b) } @newEntries;
|
3301
|
+
my @originalEntries = @$groupContent;
|
3302
|
+
@$groupContent = ( );
|
3303
|
+
|
3304
|
+
while (1)
|
3305
|
+
{
|
3306
|
+
while (scalar @originalEntries && !$self->IsIncludedInSort($groupEntry, $originalEntries[0]))
|
3307
|
+
{ push @$groupContent, (shift @originalEntries); };
|
3308
|
+
|
3309
|
+
if (!scalar @originalEntries || !scalar @newEntries)
|
3310
|
+
{ last; };
|
3311
|
+
|
3312
|
+
while (scalar @newEntries && $self->CompareEntries($newEntries[0], $originalEntries[0]) < 0)
|
3313
|
+
{ push @$groupContent, (shift @newEntries); };
|
3314
|
+
|
3315
|
+
push @$groupContent, (shift @originalEntries);
|
3316
|
+
|
3317
|
+
if (!scalar @originalEntries || !scalar @newEntries)
|
3318
|
+
{ last; };
|
3319
|
+
};
|
3320
|
+
|
3321
|
+
if (scalar @originalEntries)
|
3322
|
+
{ push @$groupContent, @originalEntries; }
|
3323
|
+
elsif (scalar @newEntries)
|
3324
|
+
{ push @$groupContent, @newEntries; };
|
3325
|
+
};
|
3326
|
+
};
|
3327
|
+
|
3328
|
+
|
3329
|
+
# Now re-add the exceptions.
|
3330
|
+
|
3331
|
+
if (defined $leadingGeneralIndex)
|
3332
|
+
{
|
3333
|
+
unshift @{$groupEntry->GroupContent()}, $leadingGeneralIndex;
|
3334
|
+
};
|
3335
|
+
|
3336
|
+
if (defined $trailingIndexGroup)
|
3337
|
+
{
|
3338
|
+
$groupEntry->PushToGroup($trailingIndexGroup);
|
3339
|
+
};
|
3340
|
+
|
3341
|
+
};
|
3342
|
+
|
3343
|
+
foreach my $entry (@{$groupEntry->GroupContent()})
|
3344
|
+
{
|
3345
|
+
if ($entry->Type() == ::MENU_GROUP())
|
3346
|
+
{ push @groupStack, $entry; };
|
3347
|
+
};
|
3348
|
+
};
|
3349
|
+
};
|
3350
|
+
|
3351
|
+
|
3352
|
+
#
|
3353
|
+
# Function: CompareEntries
|
3354
|
+
#
|
3355
|
+
# A comparison function for use in sorting. Compares the two entries by their titles with <StringCompare()>, but in the case
|
3356
|
+
# of a tie, puts <MENU_FILE> entries above <MENU_GROUP> entries.
|
3357
|
+
#
|
3358
|
+
sub CompareEntries #(a, b)
|
3359
|
+
{
|
3360
|
+
my ($self, $a, $b) = @_;
|
3361
|
+
|
3362
|
+
my $result = ::StringCompare($a->Title(), $b->Title());
|
3363
|
+
|
3364
|
+
if ($result == 0)
|
3365
|
+
{
|
3366
|
+
if ($a->Type() == ::MENU_FILE() && $b->Type() == ::MENU_GROUP())
|
3367
|
+
{ $result = -1; }
|
3368
|
+
elsif ($a->Type() == ::MENU_GROUP() && $b->Type() == ::MENU_FILE())
|
3369
|
+
{ $result = 1; };
|
3370
|
+
};
|
3371
|
+
|
3372
|
+
return $result;
|
3373
|
+
};
|
3374
|
+
|
3375
|
+
|
3376
|
+
#
|
3377
|
+
# Function: SharedDirectoriesOf
|
3378
|
+
#
|
3379
|
+
# Returns an array of all the directories shared by the files in the group. If none, returns an empty array.
|
3380
|
+
#
|
3381
|
+
sub SharedDirectoriesOf #(group)
|
3382
|
+
{
|
3383
|
+
my ($self, $groupEntry) = @_;
|
3384
|
+
my @sharedDirectories;
|
3385
|
+
|
3386
|
+
foreach my $entry (@{$groupEntry->GroupContent()})
|
3387
|
+
{
|
3388
|
+
if ($entry->Type() == ::MENU_FILE())
|
3389
|
+
{
|
3390
|
+
my @entryDirectories = NaturalDocs::File->SplitDirectories( (NaturalDocs::File->SplitPath($entry->Target()))[1] );
|
3391
|
+
|
3392
|
+
if (!scalar @sharedDirectories)
|
3393
|
+
{ @sharedDirectories = @entryDirectories; }
|
3394
|
+
else
|
3395
|
+
{ ::ShortenToMatchStrings(\@sharedDirectories, \@entryDirectories); };
|
3396
|
+
|
3397
|
+
if (!scalar @sharedDirectories)
|
3398
|
+
{ last; };
|
3399
|
+
};
|
3400
|
+
};
|
3401
|
+
|
3402
|
+
return @sharedDirectories;
|
3403
|
+
};
|
3404
|
+
|
3405
|
+
|
3406
|
+
1;
|