// 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); }
/* Function: Start_Stage2 * * Finishes loading and and combing the two versions of <Comments.txt>, returning whether it was successful. If there * were any errors they will be added to errorList. * * This must be called after <Start_Stage1()> has been called, and also <Languages.Manager.Start_Stage1()>. This * finalizes any settings which also depend on <Languages.txt>. * * Dependencies: * * - <Config.Manager> must be started before this class can start. * - <Start_Stage1()> must be called and return true before this function can be called. * - <Languages.Manager.Start_Stage1()> must be called and return true before this function can be called. */ public bool Start_Stage2(Errors.ErrorList errorList) { // First we collect all the ignored keywords. StringSet ignoredKeywords = new StringSet(Config.KeySettingsForKeywords); // Remember that mergedTextConfig only has comment types, not keywords, ignored keywords, or tags. So we have to // go back to the system and project configs. MergeIgnoredKeywordsInto(ref ignoredKeywords, systemTextConfig); MergeIgnoredKeywordsInto(ref ignoredKeywords, projectTextConfig); // Now add all keywords that aren't ignored, validating the language names along the way. if (!MergeKeywordsInto_Stage2(ref config, systemTextConfig, ignoredKeywords, errorList) || !MergeKeywordsInto_Stage2(ref config, projectTextConfig, ignoredKeywords, errorList)) { return(false); } // Now we have our final configuration and everything is okay. Save the text files again to reformat them. TouchUp_Stage2(ref systemTextConfig, config); TouchUp_Stage2(ref projectTextConfig, config); Path systemTextConfigPath = EngineInstance.Config.SystemConfigFolder + "/Comments.txt"; Path projectTextConfigPath = EngineInstance.Config.ProjectConfigFolder + "/Comments.txt"; ConfigFiles.TextFileParser textConfigFileParser = new ConfigFiles.TextFileParser(); // If the project Comments.txt didn't exist, saving the blank structure that was created will create a default one. if (!textConfigFileParser.Save(projectTextConfigPath, PropertySource.ProjectCommentsFile, projectTextConfig, errorList)) { return(false); } ; // We don't care if we're not able to resave the system Comments.txt since it may be in a protected location. textConfigFileParser.Save(systemTextConfigPath, PropertySource.SystemCommentsFile, systemTextConfig, errorList: null); // Save Comments.nd as well. Path lastRunConfigPath = EngineInstance.Config.WorkingDataFolder + "/Comments.nd"; ConfigFiles.BinaryFileParser binaryConfigFileParser = new ConfigFiles.BinaryFileParser(); binaryConfigFileParser.Save(lastRunConfigPath, config); // Look up our Group comment type ID since it's used often and we want to cache it. groupCommentTypeID = IDFromKeyword("group", 0); // Compare the config against the previous one and reparse everything if there are changes. Changes that would invalidate // IDs have already been handled. if (config != lastRunConfig) { EngineInstance.AddStartupIssues(StartupIssues.NeedToReparseAllFiles); } // We're done with these variables which were only needed between start stages 1 and 2. systemTextConfig = null; projectTextConfig = null; mergedTextConfig = null; lastRunConfig = null; started = true; return(true); }