A package handling the menu’s contents and state.
NaturalDocs:: | A package handling the menu’s contents and state. |
Constants | |
Variables | |
hasChanged | Whether the menu changed or not, regardless of why. |
menu | The parsed menu file. |
defaultTitlesChanged | An existence hash of default titles that have changed, since OnDefaultTitleChange() will be called before LoadAndUpdate(). |
title | The title of the menu. |
subTitle | The sub-title of the menu. |
footer | The footer for the documentation. |
timestampText | The timestamp for the documentation, stored as the final output text. |
timestampCode | The timestamp for the documentation, storted as the symbolic code. |
indexes | An existence hash of all the defined index TopicTypes appearing in the menu. |
previousIndexes | An existence hash of all the index TopicTypes that appeared in the menu last time. |
bannedIndexes | An existence hash of all the index TopicTypes that the user has manually deleted, and thus should not be added back to the menu automatically. |
Files | |
Menu.txt | The file used to generate the menu. |
PreviousMenuState.nd | The file used to store the previous state of the menu so as to detect changes. |
File Functions | |
LoadAndUpdate | Loads the menu file from disk and updates it. |
Save | Writes the changes to the menu files. |
Information Functions | |
HasChanged | Returns whether the menu has changed or not. |
Content | Returns the parsed menu as an arrayref of NaturalDocs::Menu::Entry objects. |
Title | Returns the title of the menu, or undef if none. |
SubTitle | Returns the sub-title of the menu, or undef if none. |
Footer | Returns the footer of the documentation, or undef if none. |
TimeStamp | Returns the timestamp text of the documentation, or undef if none. |
Indexes | Returns an existence hashref of all the index TopicTypes appearing in the menu. |
PreviousIndexes | Returns an existence hashref of all the index TopicTypes that previously appeared in the menu. |
FilesInMenu | Returns a hashref of all the files present in the menu. |
Event Handlers | These functions are called by NaturalDocs::Project only. |
OnDefaultTitleChange | Called by NaturalDocs::Project if the default menu title of a source file has changed. |
Support Functions | |
LoadMenuFile | Loads and parses the menu file Menu.txt. |
SaveMenuFile | Saves the current menu to Menu.txt. |
WriteMenuEntries | A recursive function to write the contents of an arrayref of NaturalDocs::Menu::Entry objects to disk. |
LoadPreviousMenuStateFile | Loads and parses the previous menu state file. |
SavePreviousMenuStateFile | Saves changes to PreviousMenuState.nd. |
WritePreviousMenuStateEntries | A recursive function to write the contents of an arrayref of NaturalDocs::Menu::Entry objects to disk. |
CheckForTrashedMenu | 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 menu and issues a warning. |
GenerateTimestampText | Generates timestampText from timestampCode with the current date. |
ConvertAmpChars | Replaces certain characters in the string with their entities and returns it. |
RestoreAmpChars | Replaces entity characters in the string with their original characters and returns it. |
Auto-Adjustment Functions | |
ResolveInputDirectories | Detects if the input directories in the menu file match those in the command line, and if not, tries to resolve them. |
ResolveRelativeInputDirectories | Resolves relative input directories to the input directories available. |
ResolveFile | Tests a relative path against a list of directories. |
LockUserTitleChanges | Detects if the user manually changed any file titles, and if so, automatically locks them with MENU_FILE_NOAUTOTITLE. |
FlagAutoTitleChanges | Finds which files have auto-titles that changed and flags their groups for updating with MENU_GROUP_UPDATETITLES and MENU_GROUP_UPDATEORDER. |
AutoPlaceNewFiles | Adds files to the menu that aren’t already on it, attempting to guess where they belong. |
MatchDirectoriesAndGroups | Determines which groups files in certain directories should be placed in. |
RemoveDeadFiles | Removes files from the menu that no longer exist or no longer have Natural Docs content. |
BanAndUnbanIndexes | Adjusts the indexes that are banned depending on if the user added or deleted any. |
AddAndRemoveIndexes | Automatically adds and removes index entries on the menu as necessary. |
RemoveDeadGroups | Removes groups with less than two entries. |
RemoveIfDead | Checks a group and all its sub-groups for life and remove any that are dead. |
DetectIndexGroups | Finds groups that are primarily used for indexes and gives them the MENU_GROUP_ISINDEXGROUP flag. |
CreateDirectorySubGroups | Where possible, creates sub-groups based on directories for any long groups that have MENU_GROUP_UPDATESTRUCTURE set. |
DetectOrder | Detects the order of the entries in all groups that have the MENU_GROUP_UPDATEORDER flag set. |
GenerateAutoFileTitles | Creates titles for the unlocked file entries in all groups that have the MENU_GROUP_UPDATETITLES flag set. |
ResortGroups | Resorts all groups that have MENU_GROUP_UPDATEORDER set. |
CompareEntries | A comparison function for use in sorting. |
SharedDirectoriesOf | Returns an array of all the directories shared by the files in the group. |
my $menu
The parsed menu file. Is stored as a MENU_GROUP NaturalDocs::Menu::Entry object, with the top-level entries being stored as the group’s content. This is done because it makes a number of functions simpler to implement, plus it allows group flags to be set on the top-level. However, it is exposed externally via Content() as an arrayref.
This structure will only contain objects for MENU_FILE, MENU_GROUP, MENU_TEXT, MENU_LINK, and MENU_INDEX entries. Other types, such as MENU_TITLE, are stored in variables such as title.
my %defaultTitlesChanged
An existence hash of default titles that have changed, since OnDefaultTitleChange() will be called before LoadAndUpdate(). Collects them to be applied later. The keys are the FileNames.
my %indexes
An existence hash of all the defined index TopicTypes appearing in the menu.
my %previousIndexes
An existence hash of all the index TopicTypes that appeared in the menu last time.
my %bannedIndexes
An existence hash of all the index TopicTypes that the user has manually deleted, and thus should not be added back to the menu automatically.
The file used to generate the menu.
The file is plain text. Blank lines can appear anywhere and are ignored. Tags and their content must be completely contained on one line with the exception of Group’s braces. All values in brackets below are encoded with entity characters.
# [comment]
The file supports single-line comments via #. They can appear alone on a line or after content.
Format: [version] Title: [title] SubTitle: [subtitle] Footer: [footer] Timestamp: [timestamp code]
The file format version, menu title, subtitle, footer, and timestamp are specified as above. Each can only be specified once, with subsequent ones being ignored. Subtitle is ignored if Title is not present. Format must be the first entry in the file. If it’s not present, it’s assumed the menu is from version 0.95 or earlier, since it was added with 1.0.
The timestamp code is as follows.
m | Single digit month, where applicable. January is “1”. |
mm | Always double digit month. January is “01”. |
mon | Short month word. January is “Jan”. |
month | Long month word. January is “January”. |
d | Single digit day, where applicable. 1 is “1”. |
dd | Always double digit day. 1 is “01”. |
day | Day with text extension. 1 is “1st”. |
yy | Double digit year. 2006 is “06”. |
yyyy | Four digit year. 2006 is “2006”. |
year | Four digit year. 2006 is “2006”. |
Anything else is left literal in the output.
File: [title] ([file name]) File: [title] (auto-title, [file name]) File: [title] (no auto-title, [file name])
Files are specified as above. If there is only one input directory, file names are relative. Otherwise they are absolute. If “no auto-title” is specified, the title on the line is used. If not, the title is ignored and the default file title is used instead. Auto-title defaults to on, so specifying “auto-title” is for compatibility only.
Group: [title] Group: [title] { ... }
Groups are specified as above. If no braces are specified, the group’s content is everything that follows until the end of the file, the next group (braced or unbraced), or the closing brace of a parent group. Group braces are the only things in this file that can span multiple lines.
There is no limitations on where the braces can appear. The opening brace can appear after the group tag, on its own line, or preceding another tag on a line. Similarly, the closing brace can appear after another tag or on its own line. Being bitchy here would just get in the way of quick and dirty editing; the package will clean it up automatically when it writes it back to disk.
Text: [text]
Arbitrary text is specified as above. As with other tags, everything must be contained on the same line.
Link: [URL] Link: [title] ([URL])
External links can be specified as above. If the titled form is not used, the URL is used as the title.
Index: [name] [topic type name] Index: [name]
Indexes are specified as above. The topic type names can be either singular or plural. General is assumed if not specified.
Don't Index: [topic type name] Don't Index: [topic type name], [topic type name], ...
The option above prevents indexes that exist but are not on the menu from being automatically added.
Data: [number]([obscured data])
Used to store non-user editable data.
Data: 1([obscured: [directory name]///[input directory]])
When there is more than one directory, these lines store the input directories used in the last run and their names. This allows menu files to be shared across machines since the names will be consistent and the directories can be used to convert filenames to the local machine’s paths. We don’t want this user-editable because they may think changing it changes the input directories, when it doesn’t. Also, changing it without changing all the paths screws up resolving.
Data: 2([obscured: [directory name])
When there is only one directory and its name is not “default”, this stores the name.
& | Ampersand. |
&lparen; | Left parenthesis. |
&rparen; | Right parenthesis. |
{ | Left brace. |
} | Right brace. |
This is also the point where indexes were automatically added and removed, so all index entries from prior revisions were manually added and are not guaranteed to contain anything.
This is also the point where auto-organization and better auto-titles were introduced. All groups in prior revisions were manually added, with the exception of a top-level Other group where new files were automatically added if there were groups defined.
Releases prior to 1.0 are no longer supported. Why?
The file used to store the previous state of the menu so as to detect changes.
[BINARY_FORMAT] [VersionInt: app version]
First is the standard BINARY_FORMAT VersionInt header.
[UInt8: 0 (end group)] [UInt8: MENU_FILE] [UInt8: noAutoTitle] [AString16: title] [AString16: target] [UInt8: MENU_GROUP] [AString16: title] [UInt8: MENU_INDEX] [AString16: title] [AString16: topic type] [UInt8: MENU_LINK] [AString16: title] [AString16: url] [UInt8: MENU_TEXT] [AString16: text]
The first UInt8 of each following line is either zero or one of the <Menu Entry Types>. What follows is contextual.
There are no entries for title, subtitle, or footer. Only the entries present in menu.
Pre-1.0 files are no longer supported. There was no significant number of downloads for pre-1.0 releases, and this eliminates a separate code path for them.
sub LoadAndUpdate
Loads the menu file from disk and updates it. Will add, remove, rearrange, and remove auto-titling from entries as necessary. Will also call NaturalDocs::Settings->GenerateDirectoryNames().
sub Content
Returns the parsed menu as an arrayref of NaturalDocs::Menu::Entry objects. Do not change the arrayref.
The arrayref will only contain MENU_FILE, MENU_GROUP, MENU_INDEX, MENU_TEXT, and MENU_LINK entries. Entries such as MENU_TITLE are parsed out and are only accessible via functions such as Title().
sub Indexes
Returns an existence hashref of all the index TopicTypes appearing in the menu. Do not change the hashref.
sub PreviousIndexes
Returns an existence hashref of all the index TopicTypes that previously appeared in the menu. Do not change the hashref.
sub FilesInMenu
Returns a hashref of all the files present in the menu. The keys are the FileNames, and the values are references to their NaturalDocs::Menu::Entry objects.
These functions are called by NaturalDocs::Project only. You don’t need to worry about calling them. For example, when changing the default menu title of a file, you only need to call NaturalDocs::Project->SetDefaultMenuTitle(). That function will handle calling OnDefaultTitleChange().
sub OnDefaultTitleChange #( file )
Called by NaturalDocs::Project if the default menu title of a source file has changed.
file | The source FileName that had its default menu title changed. |
sub LoadMenuFile
Loads and parses the menu file Menu.txt. This will fill menu, title, subTitle, footer, timestampText, timestampCode, indexes, and bannedIndexes. If there are any errors in the file, they will be recorded with NaturalDocs::ConfigFile->AddError().
The array ( inputDirectories, relativeFiles, onlyDirectoryName ) or an empty array if the file doesn’t exist.
inputDirectories | A hashref of all the input directories and their names stored in the menu file. The keys are the directories and the values are their names. Undef if none. |
relativeFiles | Whether the menu uses relative file names. |
onlyDirectoryName | The name of the input directory if there is only one. |
sub SaveMenuFile
Saves the current menu to Menu.txt.
sub WriteMenuEntries #( entries, fileHandle, indentChars, relativeFiles )
A recursive function to write the contents of an arrayref of NaturalDocs::Menu::Entry objects to disk.
entries | The arrayref of menu entries to write. |
fileHandle | The handle to the output file. |
indentChars | The indentation characters to add before each line. It is not the number of characters, it is the characters themselves. Use undef for none. |
relativeFiles | Whether to use relative file names. |
sub LoadPreviousMenuStateFile
Loads and parses the previous menu state file.
The array ( previousMenu, previousIndexes, previousFiles ) or an empty array if there was a problem with the file.
previousMenu | A MENU_GROUP NaturalDocs::Menu::Entry object, similar to menu, which contains the entire previous menu. |
previousIndexes | An existence hashref of the index TopicTypes present in the previous menu. |
previousFiles | A hashref of the files present in the previous menu. The keys are the FileNames, and the entries are references to its object in previousMenu. |
sub SavePreviousMenuStateFile
Saves changes to PreviousMenuState.nd.
sub WritePreviousMenuStateEntries #( entries, fileHandle )
A recursive function to write the contents of an arrayref of NaturalDocs::Menu::Entry objects to disk.
entries | The arrayref of menu entries to write. |
fileHandle | The handle to the output file. |
sub CheckForTrashedMenu #( numberOriginallyInMenu, numberRemoved )
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 menu and issues a warning.
numberOriginallyInMenu | A count of how many file entries were in the menu orignally. |
numberRemoved | A count of how many file entries were removed from the menu. |
sub GenerateTimestampText
Generates timestampText from timestampCode with the current date.
sub ConvertAmpChars #( string text, int flags ) => string
Replaces certain characters in the string with their entities and returns it.
text | The text to convert. |
flags | The flags of any additional characters to convert. |
The string with the amp chars converted.
sub RestoreAmpChars #( string text ) => string
Replaces entity characters in the string with their original characters and returns it. This will restore all amp chars regardless of the flags passed to ConvertAmpChars().
sub ResolveInputDirectories #( inputDirectoryNames )
Detects if the input directories in the menu file match those in the command line, and if not, tries to resolve them. This allows menu files to work across machines, since the absolute paths won’t be the same but the relative ones should be.
inputDirectoryNames | A hashref of the input directories appearing in the menu file, or undef if none. The keys are the directories, and the values are their names. May be undef. |
sub ResolveFile #( relativePath, possibleBases, possibleBaseScores )
Tests a relative path against a list of directories. Adds one to the score of each base where there is a match.
relativePath | The relative file name to test. |
possibleBases | An arrayref of bases to test it against. |
possibleBaseScores | An arrayref of scores to adjust. The score indexes should correspond to the base indexes. |
sub LockUserTitleChanges #( previousMenuFiles )
Detects if the user manually changed any file titles, and if so, automatically locks them with MENU_FILE_NOAUTOTITLE.
previousMenuFiles | A hashref of the files from the previous menu state. The keys are the FileNames, and the values are references to their NaturalDocs::Menu::Entry objects. |
sub FlagAutoTitleChanges
Finds which files have auto-titles that changed and flags their groups for updating with MENU_GROUP_UPDATETITLES and MENU_GROUP_UPDATEORDER.
sub AutoPlaceNewFiles #( fileInMenu )
Adds files to the menu that aren’t already on it, attempting to guess where they belong.
New files are placed after a dummy MENU_ENDOFORIGINAL entry so that they don’t affect the detected order. Also, the groups they’re placed in get MENU_GROUP_UPDATETITLES, MENU_GROUP_UPDATESTRUCTURE, and MENU_GROUP_UPDATEORDER flags.
filesInMenu | An existence hash of all the FileNames present in the menu. |
sub MatchDirectoriesAndGroups
Determines which groups files in certain directories should be placed in.
A hashref. The keys are the directory names, and the values are references to the group objects they should be placed in.
This only repreesents directories that currently have files on the menu, so it shouldn’t be assumed that every possible directory will exist. To match, you should first try to match the directory, and then strip the deepest directories one by one until there’s a match or there’s none left. If there’s none left, use the root group menu.
sub AddAndRemoveIndexes
Automatically adds and removes index entries on the menu as necessary. DetectIndexGroups() should be called beforehand.
sub RemoveDeadGroups
Removes groups with less than two entries. It will always remove empty groups, and it will remove groups with one entry if it has the MENU_GROUP_UPDATESTRUCTURE flag.
sub RemoveIfDead #( groupEntry, parentGroupEntry, parentGroupIndex )
Checks a group and all its sub-groups for life and remove any that are dead. Empty groups are removed, and groups with one entry and the MENU_GROUP_UPDATESTRUCTURE flag have their entry moved to the parent group.
groupEntry | The group to check for possible deletion. |
parentGroupEntry | The parent group to move the single entry to if necessary. |
parentGroupIndex | The index of the group in its parent. |
Whether the group was removed or not.
sub DetectIndexGroups
Finds groups that are primarily used for indexes and gives them the MENU_GROUP_ISINDEXGROUP flag.
sub CreateDirectorySubGroups
Where possible, creates sub-groups based on directories for any long groups that have MENU_GROUP_UPDATESTRUCTURE set. Clears the flag afterwards on groups that are short enough to not need any more sub-groups, but leaves it for the rest.
sub DetectOrder #( forceAll )
Detects the order of the entries in all groups that have the MENU_GROUP_UPDATEORDER flag set. Will set one of the MENU_GROUP_FILESSORTED, MENU_GROUP_FILESANDGROUPSSORTED, MENU_GROUP_EVERYTHINGSORTED, or MENU_GROUP_UNSORTED flags. It will always go for the most comprehensive sort possible, so if a group only has one entry, it will be flagged as MENU_GROUP_EVERYTHINGSORTED.
DetectIndexGroups() should be called beforehand, as the MENU_GROUP_ISINDEXGROUP flag affects how the order is detected.
The sort detection stops if it reaches a MENU_ENDOFORIGINAL entry, so new entries can be added to the end while still allowing the original sort to be detected.
forceAll | If set, the order will be detected for all groups regardless of whether MENU_GROUP_UPDATEORDER is set. |
sub GenerateAutoFileTitles #( forceAll )
Creates titles for the unlocked file entries in all groups that have the MENU_GROUP_UPDATETITLES flag set. It clears the flag afterwards so it can be used efficiently for multiple sweeps.
forceAll | If set, forces all the unlocked file titles to update regardless of whether the group has the MENU_GROUP_UPDATETITLES flag set. |
sub ResortGroups #( forceAll )
Resorts all groups that have MENU_GROUP_UPDATEORDER set. Assumes DetectOrder() and GenerateAutoFileTitles() have already been called. Will clear the flag and any MENU_ENDOFORIGINAL entries on reordered groups.
forceAll | If set, resorts all groups regardless of whether MENU_GROUP_UPDATEORDER is set. |
sub CompareEntries #( a, b )
A comparison function for use in sorting. Compares the two entries by their titles with StringCompare(), but in the case of a tie, puts MENU_FILE entries above MENU_GROUP entries.
Whether the menu changed or not, regardless of why.
my $hasChanged
The parsed menu file.
my $menu
An existence hash of default titles that have changed, since OnDefaultTitleChange() will be called before LoadAndUpdate().
my %defaultTitlesChanged
Called by NaturalDocs::Project if the default menu title of a source file has changed.
sub OnDefaultTitleChange #( file )
Loads the menu file from disk and updates it.
sub LoadAndUpdate
The title of the menu.
my $title
The sub-title of the menu.
my $subTitle
The footer for the documentation.
my $footer
The timestamp for the documentation, stored as the final output text.
my $timestampText
The timestamp for the documentation, storted as the symbolic code.
my $timestampCode
An existence hash of all the defined index TopicTypes appearing in the menu.
my %indexes
An existence hash of all the index TopicTypes that appeared in the menu last time.
my %previousIndexes
An existence hash of all the index TopicTypes that the user has manually deleted, and thus should not be added back to the menu automatically.
my %bannedIndexes
Writes the changes to the menu files.
sub Save
Returns whether the menu has changed or not.
sub HasChanged
Returns the parsed menu as an arrayref of NaturalDocs::Menu::Entry objects.
sub Content
Returns the title of the menu, or undef if none.
sub Title
Returns the sub-title of the menu, or undef if none.
sub SubTitle
Returns the footer of the documentation, or undef if none.
sub Footer
Returns the timestamp text of the documentation, or undef if none.
sub TimeStamp
Returns an existence hashref of all the index TopicTypes appearing in the menu.
sub Indexes
Returns an existence hashref of all the index TopicTypes that previously appeared in the menu.
sub PreviousIndexes
Returns a hashref of all the files present in the menu.
sub FilesInMenu
Loads and parses the menu file Menu.txt.
sub LoadMenuFile
Saves the current menu to Menu.txt.
sub SaveMenuFile
A recursive function to write the contents of an arrayref of NaturalDocs::Menu::Entry objects to disk.
sub WriteMenuEntries #( entries, fileHandle, indentChars, relativeFiles )
Loads and parses the previous menu state file.
sub LoadPreviousMenuStateFile
Saves changes to PreviousMenuState.nd.
sub SavePreviousMenuStateFile
A recursive function to write the contents of an arrayref of NaturalDocs::Menu::Entry objects to disk.
sub WritePreviousMenuStateEntries #( entries, fileHandle )
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 menu and issues a warning.
sub CheckForTrashedMenu #( numberOriginallyInMenu, numberRemoved )
Generates timestampText from timestampCode with the current date.
sub GenerateTimestampText
Replaces certain characters in the string with their entities and returns it.
sub ConvertAmpChars #( string text, int flags ) => string
Replaces entity characters in the string with their original characters and returns it.
sub RestoreAmpChars #( string text ) => string
Detects if the input directories in the menu file match those in the command line, and if not, tries to resolve them.
sub ResolveInputDirectories #( inputDirectoryNames )
Resolves relative input directories to the input directories available.
sub ResolveRelativeInputDirectories
Tests a relative path against a list of directories.
sub ResolveFile #( relativePath, possibleBases, possibleBaseScores )
Detects if the user manually changed any file titles, and if so, automatically locks them with MENU_FILE_NOAUTOTITLE.
sub LockUserTitleChanges #( previousMenuFiles )
Finds which files have auto-titles that changed and flags their groups for updating with MENU_GROUP_UPDATETITLES and MENU_GROUP_UPDATEORDER.
sub FlagAutoTitleChanges
Adds files to the menu that aren’t already on it, attempting to guess where they belong.
sub AutoPlaceNewFiles #( fileInMenu )
Determines which groups files in certain directories should be placed in.
sub MatchDirectoriesAndGroups
Removes files from the menu that no longer exist or no longer have Natural Docs content.
sub RemoveDeadFiles
Adjusts the indexes that are banned depending on if the user added or deleted any.
sub BanAndUnbanIndexes
Automatically adds and removes index entries on the menu as necessary.
sub AddAndRemoveIndexes
Removes groups with less than two entries.
sub RemoveDeadGroups
Checks a group and all its sub-groups for life and remove any that are dead.
sub RemoveIfDead #( groupEntry, parentGroupEntry, parentGroupIndex )
Finds groups that are primarily used for indexes and gives them the MENU_GROUP_ISINDEXGROUP flag.
sub DetectIndexGroups
Where possible, creates sub-groups based on directories for any long groups that have MENU_GROUP_UPDATESTRUCTURE set.
sub CreateDirectorySubGroups
Detects the order of the entries in all groups that have the MENU_GROUP_UPDATEORDER flag set.
sub DetectOrder #( forceAll )
Creates titles for the unlocked file entries in all groups that have the MENU_GROUP_UPDATETITLES flag set.
sub GenerateAutoFileTitles #( forceAll )
Resorts all groups that have MENU_GROUP_UPDATEORDER set.
sub ResortGroups #( forceAll )
A comparison function for use in sorting.
sub CompareEntries #( a, b )
Returns an array of all the directories shared by the files in the group.
sub SharedDirectoriesOf #( group )
Parses the input file for information.
sub ParseForInformation #( file )
Generates names for each of the input and image directories, which can later be retrieved with InputDirectoryNameOf() and ImageDirectoryNameOf().
sub GenerateDirectoryNames #( hashref inputHints, hashref imageHints )
Returns a TopicType for the passed legacy topic type integer.
sub TypeFromLegacy #( legacyInt )
Sets the FileName’s default menu title.
sub SetDefaultMenuTitle #( file, menuTitle )
Stores an error for the current configuration file.
sub AddError #( message, lineNumber )
Compares two strings so that the result is good for proper sorting.
sub StringCompare #( a, b )