Exemple #1
0
        /* Function: MergeCommentTypes
         *
         * Merges two <ConfigFiles.TextFiles> into a new one, putting all the comment types into one list and applying any alter
         * entries.  This does NOT cover keywords, ignored keywords, or tags; those will be blank in the result.  Returns the new
         * list and whether it was successful.
         *
         * Any errors will be added to errorList, such as defining a duplicate entry that doesn't use alter, or an alter entry for a
         * non-existent comment type.  All alter entries will be applied, including any appearing in the base config, so there will
         * only be non-alter entries in the returned list.
         */
        protected bool MergeCommentTypes(ConfigFiles.TextFile baseConfig, ConfigFiles.TextFile overridingConfig,
                                         out ConfigFiles.TextFile combinedConfig, Errors.ErrorList errorList)
        {
            combinedConfig = new ConfigFiles.TextFile();

            // We merge the base config into the empty config instead of just copying it so any alter entries it has are applied
            if (!MergeCommentTypesInto(ref combinedConfig, baseConfig, errorList) ||
                !MergeCommentTypesInto(ref combinedConfig, overridingConfig, errorList))
            {
                combinedConfig = null;
                return(false);
            }

            return(true);
        }
Exemple #2
0
        /* Function: TouchUp_Stage2
         * Applies some minor improvements to the <ConfigFiles.TextFile>, such as making sure the capitalization of Alter Topic Type
         * and [Language] Keywords match the original definition.  Assumes everything is valid, meaning all Alter Topic Type entries
         * have corresponding entries in finalConfig and all [Language] Keyword entries have corresponding languages in
         * <Languages.Manager>.
         */
        protected void TouchUp_Stage2(ref ConfigFiles.TextFile textConfig, Config finalConfig)
        {
            if (textConfig.HasCommentTypes)
            {
                foreach (var commentType in textConfig.CommentTypes)
                {
                    // Fix "Alter Comment Type: [name]" capitalization

                    if (commentType.AlterType)
                    {
                        var originalType = finalConfig.CommentTypeFromName(commentType.Name);
                        commentType.FixNameCapitalization(originalType.Name);

                        // We don't also check to see if the comment type we're altering exists in the same file and merge their
                        // definitions into one.  Why?  Consider this:
                        //
                        // Comment Type: Comment Type A
                        //    Keyword: Keyword A
                        //
                        // Comment Type: Comment Type B
                        //    Keyword: Keyword B
                        //
                        // Alter Comment Type: Comment Type A
                        //    Keyword: Keyword B
                        //
                        // Keyword B should be part of Comment Type A.  However, if we merged the definitions it would appear
                        // first and be overridden by Comment Type B.  So we just leave the two comment type entries for A instead.
                    }


                    if (commentType.HasKeywordGroups)
                    {
                        foreach (var keywordGroup in commentType.KeywordGroups)
                        {
                            // Fix "[Language] Keywords" capitalization

                            if (keywordGroup.IsLanguageSpecific)
                            {
                                var originalLanguage = EngineInstance.Languages.FromName(keywordGroup.LanguageName);
                                keywordGroup.LanguageName = originalLanguage.Name;
                            }
                        }
                    }
                }
            }
        }
Exemple #3
0
        /* Function: MergeIgnoredKeywordsInto
         * Merges the ignored keywords from the <ConfigFiles.TextFile> into a <StringSet>.
         */
        protected void MergeIgnoredKeywordsInto(ref StringSet ignoredKeywords, ConfigFiles.TextFile textConfig)
        {
            if (textConfig.HasIgnoredKeywords)
            {
                foreach (var ignoredKeywordGroup in textConfig.IgnoredKeywordGroups)
                {
                    foreach (var ignoredKeywordDefinition in ignoredKeywordGroup.KeywordDefinitions)
                    {
                        ignoredKeywords.Add(ignoredKeywordDefinition.Keyword);

                        if (ignoredKeywordDefinition.HasPlural)
                        {
                            ignoredKeywords.Add(ignoredKeywordDefinition.Plural);
                        }
                    }
                }
            }
        }
Exemple #4
0
        // Group: Initialization Support Functions
        // __________________________________________________________________________
        //
        // These functions are only used by <Start_Stage1()> and <Start_Stage2()>.
        //


        /* Function: ValidateCommentTypes
         * Validates all the comment type settings in a <ConfigFiles.TextFile>.  Returns whether it is valid, and adds any errors it
         * finds to errorList.
         */
        protected bool ValidateCommentTypes(ConfigFiles.TextFile configFile, Errors.ErrorList errorList)
        {
            bool success = true;

            if (configFile.HasCommentTypes)
            {
                foreach (var commentType in configFile.CommentTypes)
                {
                    if (!ValidateCommentType(commentType, errorList))
                    {
                        success = false;
                        // Continue anyway so we can report the errors in all of them.
                    }
                }
            }

            return(success);
        }
Exemple #5
0
        /* Function: MergeCommentTypesInto
         * Merges the comment types of the second <ConfigFiles.TextFile> into the first, adding new types and applying any
         * alter entries.  This does NOT merge keywords, ignored keywords, or tags.  The base config will be changed, even if
         * there are errors.  Returns false if there were any errors and adds them to errorList.
         */
        protected bool MergeCommentTypesInto(ref ConfigFiles.TextFile baseConfig, ConfigFiles.TextFile overridingConfig,
                                             Errors.ErrorList errorList)
        {
            bool success = true;

            if (overridingConfig.HasCommentTypes)
            {
                foreach (var overridingCommentType in overridingConfig.CommentTypes)
                {
                    var matchingCommentType = baseConfig.FindCommentType(overridingCommentType.Name);

                    if (matchingCommentType != null)
                    {
                        if (overridingCommentType.AlterType == false)
                        {
                            errorList.Add(Locale.Get("NaturalDocs.Engine", "Comments.txt.CommentTypeAlreadyExists(name)", overridingCommentType.Name),
                                          overridingCommentType.PropertyLocation);
                            success = false;
                        }
                        else
                        {
                            MergeCommentTypeInto(ref matchingCommentType, overridingCommentType);
                        }
                    }

                    else                     // no match
                    {
                        if (overridingCommentType.AlterType == true)
                        {
                            errorList.Add(Locale.Get("NaturalDocs.Engine", "Comments.txt.AlteredCommentTypeDoesntExist(name)", overridingCommentType.Name),
                                          overridingCommentType.NamePropertyLocation);
                            success = false;
                        }
                        else
                        {
                            baseConfig.AddCommentType(overridingCommentType.Duplicate());
                        }
                    }
                }
            }

            return(success);
        }
Exemple #6
0
        /* Function: MergeKeywordsInto_Stage2
         * Merges the keywords from the <ConfigFiles.TextFile> into the <Config>, returning whether it was successful.  It
         * assumes all <ConfigFiles.TextCommentTypes> in textConfig have corresponding <CommentTypes> in outputConfig.
         * Any errors will be added to errorList, such as having a language-specific keyword that doesn't match a name in
         * <Languages.Manager>.
         */
        protected bool MergeKeywordsInto_Stage2(ref Config outputConfig, ConfigFiles.TextFile textConfig,
                                                StringSet ignoredKeywords, Errors.ErrorList errorList)
        {
            bool success = true;

            if (textConfig.HasCommentTypes)
            {
                foreach (var commentType in textConfig.CommentTypes)
                {
                    int commentTypeID = outputConfig.CommentTypeFromName(commentType.Name).ID;

                                        #if DEBUG
                    if (commentTypeID == 0)
                    {
                        throw new InvalidOperationException();
                    }
                                        #endif

                    if (commentType.HasKeywordGroups)
                    {
                        foreach (var keywordGroup in commentType.KeywordGroups)
                        {
                            int languageID = 0;

                            if (keywordGroup.IsLanguageSpecific)
                            {
                                var language = EngineInstance.Languages.FromName(keywordGroup.LanguageName);

                                if (language == null)
                                {
                                    errorList.Add(
                                        Locale.Get("NaturalDocs.Engine", "Comments.txt.UnrecognizedKeywordLanguage(name)", keywordGroup.LanguageName),
                                        keywordGroup.PropertyLocation
                                        );

                                    success = false;
                                }
                                else
                                {
                                    languageID = language.ID;
                                }
                            }

                            foreach (var keywordDefinition in keywordGroup.KeywordDefinitions)
                            {
                                if (!ignoredKeywords.Contains(keywordDefinition.Keyword))
                                {
                                    var outputKeywordDefinition = new KeywordDefinition(keywordDefinition.Keyword);
                                    outputKeywordDefinition.CommentTypeID = commentTypeID;

                                    if (languageID != 0)
                                    {
                                        outputKeywordDefinition.LanguageID = languageID;
                                    }

                                    // AddKeywordDefinition will handle overwriting definitions with the same keyword and language
                                    outputConfig.AddKeywordDefinition(outputKeywordDefinition);
                                }

                                if (keywordDefinition.HasPlural && !ignoredKeywords.Contains(keywordDefinition.Plural))
                                {
                                    var outputKeywordDefinition = new KeywordDefinition(keywordDefinition.Plural);
                                    outputKeywordDefinition.CommentTypeID = commentTypeID;
                                    outputKeywordDefinition.Plural        = true;

                                    if (languageID != 0)
                                    {
                                        outputKeywordDefinition.LanguageID = languageID;
                                    }

                                    outputConfig.AddKeywordDefinition(outputKeywordDefinition);
                                }
                            }
                        }
                    }
                }
            }

            return(success);
        }
Exemple #7
0
        // Group: Initialization Functions
        // __________________________________________________________________________


        /* Function: Start_Stage1
         *
         * Loads and combines the two versions of <Comments.txt>, returning whether it was successful.  If there were any
         * errors they will be added to errorList.
         *
         * Only the settings which don't depend on <Languages.txt> will be loaded.  Call <Start_Stage2()> after
         * <Languages.Manager.Start_Stage1()> has been called to complete the process.
         *
         * Dependencies:
         *
         *		- <Config.Manager> must be started before this class can start.
         */
        public bool Start_Stage1(Errors.ErrorList errorList)
        {
            StartupIssues newStartupIssues = StartupIssues.None;
            bool          success          = true;


            //
            // Comments.txt
            //

            Path systemTextConfigPath  = EngineInstance.Config.SystemConfigFolder + "/Comments.txt";
            Path projectTextConfigPath = EngineInstance.Config.ProjectConfigFolder + "/Comments.txt";
            Path oldTopicsFilePath     = EngineInstance.Config.ProjectConfigFolder + "/Topics.txt";

            ConfigFiles.TextFileParser textConfigFileParser = new ConfigFiles.TextFileParser();

            // Load the system Comments.txt.
            if (!textConfigFileParser.Load(systemTextConfigPath, PropertySource.SystemCommentsFile, errorList, out systemTextConfig))
            {
                success = false;
            }

            // Load the project Comments.txt.  We want to do this even if the system Comments.txt failed so we get the error messages
            // from both.
            if (System.IO.File.Exists(projectTextConfigPath))
            {
                if (!textConfigFileParser.Load(projectTextConfigPath, PropertySource.ProjectCommentsFile, errorList, out projectTextConfig))
                {
                    success = false;
                }
            }
            // If the project Comments.txt doesn't exist, try loading Topics.txt, which is what the file was called prior to 2.0.
            else if (System.IO.File.Exists(oldTopicsFilePath))
            {
                if (!textConfigFileParser.Load(oldTopicsFilePath, PropertySource.ProjectCommentsFile, errorList, out projectTextConfig))
                {
                    success = false;
                }
            }
            // If neither file exists just create a blank config.  The project Comments.txt not existing is not an error.
            else
            {
                projectTextConfig = new ConfigFiles.TextFile();
            }

            if (!success)
            {
                return(false);
            }

            if (!ValidateCommentTypes(systemTextConfig, errorList))
            {
                success = false;
            }
            if (!ValidateCommentTypes(projectTextConfig, errorList))
            {
                success = false;
            }

            if (!success)
            {
                return(false);
            }

            // Merge them into one combined config.  Note that this doesn't do keywords, ignored keywords, or tags.  Only the comment
            // types and their non-keyword properties will exist in the merged config.
            if (!MergeCommentTypes(systemTextConfig, projectTextConfig, out mergedTextConfig, errorList))
            {
                return(false);
            }
            if (!ValidateCommentTypes(mergedTextConfig, errorList))
            {
                return(false);
            }


            //
            // Comments.nd
            //

            Path lastRunConfigPath = EngineInstance.Config.WorkingDataFolder + "/Comments.nd";

            ConfigFiles.BinaryFileParser binaryConfigFileParser = new ConfigFiles.BinaryFileParser();

            // If we need to start fresh anyway we can skip loading the file and create a blank config
            if (EngineInstance.HasIssues(StartupIssues.NeedToStartFresh |
                                         StartupIssues.CommentIDsInvalidated |
                                         StartupIssues.CodeIDsInvalidated) ||

                // though if that wasn't the case but we failed at loading the file, the result is the same
                !binaryConfigFileParser.Load(lastRunConfigPath, out lastRunConfig))
            {
                lastRunConfig = new Config();

                newStartupIssues |= StartupIssues.NeedToReparseAllFiles |
                                    StartupIssues.CommentIDsInvalidated;
            }


            //
            // Create the final config
            //

            config = new Config();


            // We go through the comment types present in the merged text config files and convert them into the final config.  We
            // need the contents of Comments.nd so we can keep the comment type IDs consistent from one run to the next if possible.

            IDObjects.NumberSet usedCommentTypeIDs        = new IDObjects.NumberSet();
            IDObjects.NumberSet lastRunUsedCommentTypeIDs = lastRunConfig.UsedCommentTypeIDs();

            if (mergedTextConfig.HasCommentTypes)
            {
                foreach (var textCommentType in mergedTextConfig.CommentTypes)
                {
                    var finalCommentType = FinalizeCommentType(textCommentType);

                    // We still need to set the ID.  See if a comment type of the same name existed in the previous run.
                    var lastRunCommentType = lastRunConfig.CommentTypeFromName(textCommentType.Name);

                    // If there wasn't one we can assign a new ID, but pick one that isn't used in this run or the last run so there's no
                    // conflicts.
                    if (lastRunCommentType == null)
                    {
                        int id = lastRunUsedCommentTypeIDs.LowestAvailable;

                        if (usedCommentTypeIDs.Contains(id))
                        {
                            id = Math.Max(usedCommentTypeIDs.Highest + 1, lastRunUsedCommentTypeIDs.Highest + 1);
                        }

                        finalCommentType.ID = id;
                        config.AddCommentType(finalCommentType);

                        usedCommentTypeIDs.Add(finalCommentType.ID);
                        lastRunUsedCommentTypeIDs.Add(finalCommentType.ID);
                    }
                    // If the type did exist but we haven't used its ID yet, we can keep it.
                    else if (!usedCommentTypeIDs.Contains(lastRunCommentType.ID))
                    {
                        finalCommentType.ID = lastRunCommentType.ID;
                        config.AddCommentType(finalCommentType);

                        usedCommentTypeIDs.Add(finalCommentType.ID);
                    }
                    // However, if the type did exist and we assigned that ID already, then we have a conflict and have to tell the engine
                    // that the IDs from the last run were invalidated.  We can just assign anything unused at this point since it no longer
                    // matters.
                    else
                    {
                        finalCommentType.ID = usedCommentTypeIDs.LowestAvailable;
                        config.AddCommentType(finalCommentType);

                        usedCommentTypeIDs.Add(finalCommentType.ID);
                        newStartupIssues |= StartupIssues.CommentIDsInvalidated;
                    }

                    // Now that we have a final ID, set the simple identifier for any type that still needs it.  Some types may have it null if
                    // it wasn't manually defined and the name didn't contain A-Z characters.

                    if (finalCommentType.SimpleIdentifier == null)
                    {
                        finalCommentType.SimpleIdentifier = "CommentTypeID" + finalCommentType.ID;
                    }
                }
            }


            // Now do the same thing for tags.

            List <string> mergedTagList  = null;
            int           mergedTagCount = (systemTextConfig.HasTags ? systemTextConfig.Tags.Count : 0) +
                                           (projectTextConfig.HasTags ? projectTextConfig.Tags.Count : 0);

            if (mergedTagCount > 0)
            {
                mergedTagList = new List <string>(mergedTagCount);

                if (systemTextConfig.HasTags)
                {
                    mergedTagList.AddRange(systemTextConfig.Tags);
                }
                if (projectTextConfig.HasTags)
                {
                    mergedTagList.AddRange(projectTextConfig.Tags);
                }
            }

            IDObjects.NumberSet usedTagIDs        = new IDObjects.NumberSet();
            IDObjects.NumberSet lastRunUsedTagIDs = lastRunConfig.UsedTagIDs();

            if (mergedTagList != null)
            {
                foreach (var tagString in mergedTagList)
                {
                    // Just skip it if it already exists
                    if (config.TagFromName(tagString) != null)
                    {
                        continue;
                    }

                    var tag = new Tag(tagString);

                    // We still need to set the ID.  See if a tag of the same name existed in the previous run.
                    var lastRunTag = lastRunConfig.TagFromName(tagString);

                    // If there wasn't one we can assign a new ID, but pick one that isn't used in this run or the last run so there's no
                    // conflicts.
                    if (lastRunTag == null)
                    {
                        int id = lastRunUsedTagIDs.LowestAvailable;

                        if (usedTagIDs.Contains(id))
                        {
                            id = Math.Max(usedTagIDs.Highest + 1, lastRunUsedTagIDs.Highest + 1);
                        }

                        tag.ID = id;
                        config.AddTag(tag);

                        usedTagIDs.Add(tag.ID);
                        lastRunUsedTagIDs.Add(tag.ID);
                    }
                    // If the tag did exist but we haven't used its ID yet, we can keep it.
                    else if (!usedTagIDs.Contains(lastRunTag.ID))
                    {
                        tag.ID = lastRunTag.ID;
                        config.AddTag(tag);

                        usedTagIDs.Add(tag.ID);
                    }
                    // However, if the tag did exist and we assigned that ID already, then we have a conflict and have to tell the engine
                    // that the IDs from the last run were invalidated.  We can just assign anything unused at this point since it no longer
                    // matters.
                    else
                    {
                        tag.ID = usedTagIDs.LowestAvailable;
                        config.AddTag(tag);

                        usedTagIDs.Add(tag.ID);
                        newStartupIssues |= StartupIssues.CommentIDsInvalidated;
                    }
                }
            }


            // That's it for stage one.  Everything else is in stage 2.

            if (newStartupIssues != StartupIssues.None)
            {
                EngineInstance.AddStartupIssues(newStartupIssues);
            }

            return(true);
        }