bixbite 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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;
|