/* Function: AddAllFiles * Goes through all the files in the <FileSource> and calls <Files.Manager.AddOrUpdateFile()> on each one. */ override public void AddAllFiles(CancelDelegate cancelDelegate) { status.Reset(); bool forceReparse = EngineInstance.HasIssues(StartupIssues.NeedToStartFresh | StartupIssues.NeedToReparseAllFiles); Path path = (FileSource as FileSources.ImageFolder).Path; // Using a string stack instead of Path stack because the I/O functions will return strings and there's no need to normalize // them all or otherwise use Path functions on them. Stack <string> foldersToSearch = new Stack <string>(); foldersToSearch.Push(path); while (foldersToSearch.Count > 0) { string folder = foldersToSearch.Pop(); status.AddFolders(InputType.Image, 1); string[] subfolders = System.IO.Directory.GetDirectories(folder); if (cancelDelegate()) { return; } foreach (string subfolder in subfolders) { foldersToSearch.Push(subfolder); } string[] files = System.IO.Directory.GetFiles(folder); if (cancelDelegate()) { return; } foreach (string file in files) { Path filePath = file; string extension = filePath.Extension; if (Files.Manager.ImageExtensions.Contains(extension)) { status.AddFiles(FileType.Image, 1); Manager.AddOrUpdateFile(filePath, FileType.Image, System.IO.File.GetLastWriteTimeUtc(file), forceReparse); } if (cancelDelegate()) { return; } } } }
/* Function: Start * * Dependencies: * * - <Config.Manager> must be started before using the rest of the class. */ public bool Start(Errors.ErrorList errors) { EngineInstance.AddStartupWatcher(this); SQLite.API.Result sqliteResult = SQLite.API.Initialize(); if (sqliteResult != SQLite.API.Result.OK) { throw new SQLite.Exceptions.UnexpectedResult("Could not initialize SQLite.", sqliteResult); } Path databaseFile = EngineInstance.Config.WorkingDataFolder + "/CodeDB.nd"; connection = new SQLite.Connection(); bool success = false; if (!EngineInstance.HasIssues(StartupIssues.NeedToStartFresh | StartupIssues.FileIDsInvalidated | StartupIssues.CodeIDsInvalidated | StartupIssues.CommentIDsInvalidated)) { try { connection.Open(databaseFile, false); Version version = GetVersion(); if (BinaryFile.IsCompatible(version, Engine.Instance.Version, "2.0.2") == true) { LoadSystemVariables(); success = true; } } catch { } } if (!success) { connection.Dispose(); if (System.IO.File.Exists(databaseFile)) { System.IO.File.Delete(databaseFile); } connection.Open(databaseFile, true); CreateDatabase(); EngineInstance.AddStartupIssues(StartupIssues.CodeIDsInvalidated | StartupIssues.CommentIDsInvalidated | StartupIssues.NeedToReparseAllFiles, dontNotify: this); } started = true; return(true); }
public bool Start(ErrorList errorList) { bool reparsingEverything = EngineInstance.HasIssues(StartupIssues.NeedToStartFresh | StartupIssues.NeedToReparseAllFiles); unprocessedChanges = new UnprocessedChanges(reparsingEverything); // Watch for changes EngineInstance.CodeDB.AddChangeWatcher(this); EngineInstance.Files.AddChangeWatcher(this); return(true); }
/* Function: Start */ public bool Start(ErrorList errorList) { StartupIssues newStartupIssues = StartupIssues.None; // Load SearchIndex.nd bool hasBinaryFile; if (!EngineInstance.HasIssues(StartupIssues.NeedToStartFresh | StartupIssues.CommentIDsInvalidated | StartupIssues.CodeIDsInvalidated | StartupIssues.CommentIDsInvalidated)) { SearchIndex_nd binaryFileParser = new SearchIndex_nd(); hasBinaryFile = binaryFileParser.Load(Target.WorkingDataFolder + "/SearchIndex.nd", out prefixTopicIDs); } else { prefixTopicIDs = new StringTable <NumberSet>(KeySettingsForPrefixes); hasBinaryFile = false; } if (!hasBinaryFile) { // If we don't have the binary file we need to reparse all the source files to rebuild the index. However, setting NeedToReparseAllFiles // isn't enough because it will reparse those files, send them to CodeDB, and then CodeDB won't send topic updates if the underlying // content hasn't changed. So f**k it, blow it all up and start over. newStartupIssues |= StartupIssues.NeedToStartFresh; } // Add watchers EngineInstance.CodeDB.AddChangeWatcher(this); EngineInstance.AddStartupWatcher(this); if (newStartupIssues != StartupIssues.None) { EngineInstance.AddStartupIssues(newStartupIssues, dontNotify: this); } started = true; return(true); }
public override bool Start(Errors.ErrorList errorList) { int errors = errorList.Count; StartupIssues newStartupIssues = StartupIssues.None; // // Validate the output folder. // if (System.IO.Directory.Exists(config.Folder) == false) { errorList.Add(Locale.Get("NaturalDocs.Engine", "Error.FolderDoesntExist(type, name)", "output", config.Folder)); return(false); } // // Load and validate the styles, including any inherited styles. // string styleName = config.ProjectInfo.StyleName; Style_txt styleParser = new Style_txt(); if (styleName == null) { style = EngineInstance.Styles.LoadStyle("Default", errorList, Config.PropertySource.SystemDefault); } else if (EngineInstance.Styles.StyleExists(styleName)) { style = EngineInstance.Styles.LoadStyle(styleName, errorList, config.ProjectInfo.StyleNamePropertyLocation); } // Check if it's an empty folder we want to generate a default Style.txt for else if (System.IO.Directory.Exists(EngineInstance.Config.ProjectConfigFolder + '/' + styleName) && !System.IO.File.Exists(EngineInstance.Config.ProjectConfigFolder + '/' + styleName + "/Style.txt")) { style = new Styles.Advanced(EngineInstance.Config.ProjectConfigFolder + '/' + styleName + "/Style.txt"); // Inherit Default so everything still works before it's filled out. style.AddInheritedStyle("Default", Config.PropertySource.SystemGenerated); if (!styleParser.Save((Styles.Advanced)style, errorList, false)) { return(false); } // Now we have to reload it so it loads the inherited style as well. style = EngineInstance.Styles.LoadStyle(styleName, errorList, config.ProjectInfo.StyleNamePropertyLocation); } else { errorList.Add(Locale.Get("NaturalDocs.Engine", "Style.txt.CantFindStyle(name)", styleName), config.ProjectInfo.StyleNamePropertyLocation); return(false); } stylesWithInheritance = style.BuildInheritanceList(); // // Load Config.nd // Config_nd binaryConfigParser = new Config_nd(); List <Style> previousStyles; List <FileSourceInfo> previousFileSourceInfoList; bool hasBinaryConfigFile = false; if (!EngineInstance.HasIssues(StartupIssues.NeedToStartFresh)) { hasBinaryConfigFile = binaryConfigParser.Load(WorkingDataFolder + "/Config.nd", out previousStyles, out previousFileSourceInfoList); } else // start fresh { previousStyles = new List <Style>(); previousFileSourceInfoList = new List <FileSourceInfo>(); } // // Compare to the previous list of styles. // bool inPurgingOperation = false; if (!hasBinaryConfigFile) { // If we don't have the binary config file we have to purge every style folder because some of them may no longer be in // use and we won't know which. PurgeAllStyleFolders(ref inPurgingOperation); newStartupIssues |= StartupIssues.NeedToReparseStyleFiles; } else // (hasBinaryConfigFile) { // Purge the style folders for any no longer in use. foreach (var previousStyle in previousStyles) { bool stillExists = false; foreach (var currentStyle in stylesWithInheritance) { if (currentStyle.IsSameFundamentalStyle(previousStyle)) { stillExists = true; break; } } if (!stillExists) { PurgeStyleFolder(previousStyle.Name, ref inPurgingOperation); } } // Reparse styles on anything new. If a style is new we can't assume all its files are going to be sent to the // IChangeWatcher functions because another output target may have been using it, and thus they are already in // Files.Manager. foreach (var currentStyle in stylesWithInheritance) { bool foundMatch = false; foreach (var previousStyle in previousStyles) { if (previousStyle.IsSameFundamentalStyle(currentStyle)) { foundMatch = true; break; } } if (!foundMatch) { newStartupIssues |= StartupIssues.NeedToReparseStyleFiles; break; } } } // // Compare to the previous list of FileSources. // if (!hasBinaryConfigFile) { // If we don't have the binary config file we need to rebuild all the output because we don't know which FileSource was // previously set to which number, which determines which output folder they use, like /files vs /files2. newStartupIssues |= StartupIssues.NeedToRebuildAllOutput; // This also means we have to purge every source or image output folder because we won't know which changed or are // no longer in use. PurgeAllSourceAndImageFolders(ref inPurgingOperation); } else // (hasBinaryConfigFile) { bool hasDeletions = false; bool hasAdditions = false; // Purge the output folders of any deleted FileSources foreach (var previousFileSourceInfo in previousFileSourceInfoList) { bool stillExists = false; foreach (var fileSource in EngineInstance.Files.FileSources) { if (previousFileSourceInfo.IsSameFundamentalFileSource(fileSource)) { stillExists = true; break; } } if (!stillExists) { hasDeletions = true; Path outputFolder; if (previousFileSourceInfo.Type == InputType.Source) { outputFolder = Paths.SourceFile.OutputFolder(OutputFolder, previousFileSourceInfo.Number); } else if (previousFileSourceInfo.Type == InputType.Image) { outputFolder = Paths.Image.OutputFolder(OutputFolder, previousFileSourceInfo.Number, previousFileSourceInfo.Type); } else { throw new NotImplementedException(); } PurgeFolder(outputFolder, ref inPurgingOperation); } } // Check if any FileSources were added foreach (var fileSource in EngineInstance.Files.FileSources) { if (fileSource.Type == InputType.Source || fileSource.Type == InputType.Image) { bool foundMatch = false; foreach (var previousFileSourceInfo in previousFileSourceInfoList) { if (previousFileSourceInfo.IsSameFundamentalFileSource(fileSource)) { foundMatch = true; break; } } if (!foundMatch) { hasAdditions = true; break; } } } // If there were both additions and deletions, force a rebuild. This covers if a FileSource was simply moved from one // number to another, in which case the rebuild is required to populate the new folder. This also covers if a folder // FileSource is replaced by one for its parent folder, in which case a rebuild is required to recreate the output for the // files in the child folder. if (hasAdditions && hasDeletions) { newStartupIssues |= StartupIssues.NeedToRebuildAllOutput; } } // // Load BuildState.nd // BuildState_nd buildStateParser = new BuildState_nd(); bool hasBinaryBuildStateFile = false; if (!EngineInstance.HasIssues(StartupIssues.NeedToStartFresh | StartupIssues.FileIDsInvalidated | StartupIssues.CodeIDsInvalidated | StartupIssues.CommentIDsInvalidated)) { hasBinaryBuildStateFile = buildStateParser.Load(WorkingDataFolder + "/BuildState.nd", out buildState, out unprocessedChanges); } else // start fresh { buildState = new BuildState(); unprocessedChanges = new UnprocessedChanges(); } if (!hasBinaryBuildStateFile) { // If we don't have a build state file we need to reparse all the source files because we need to know sourceFilesWithContent // and classesWithContent. We also need to rebuild all the output because we don't know if there was anything left in // sourceFilesToRebuild from the last run. But those two flags actually aren't enough, because it will reparse those files, send // them to CodeDB, and then CodeDB won't send topic updates if the underlying content hasn't changed. We'd actually need // to add all files and classes to UnprocessedChanges, but the Files module isn't started yet. So f**k it, blow it all up and start // over. newStartupIssues |= StartupIssues.NeedToStartFresh; // Purge everything so no stray files are left behind from the previous build. PurgeAllSourceAndImageFolders(ref inPurgingOperation); PurgeAllClassFolders(ref inPurgingOperation); PurgeAllDatabaseFolders(ref inPurgingOperation); PurgeAllStyleFolders(ref inPurgingOperation); PurgeAllMenuFolders(ref inPurgingOperation); PurgeAllSearchIndexFolders(ref inPurgingOperation); } // // We're done with anything that could purge. // FinishedPurging(ref inPurgingOperation); // // Resave the Style.txt-based styles. // foreach (var style in stylesWithInheritance) { if (style is Styles.Advanced) { var advancedStyle = style as Styles.Advanced; bool isSystemStyle = EngineInstance.Config.SystemStyleFolder.Contains(advancedStyle.ConfigFile); // No error on save for system styles. styleParser.Save(advancedStyle, errorList, noErrorOnFail: isSystemStyle); } } // // Save Config.nd. // if (!System.IO.Directory.Exists(WorkingDataFolder)) { System.IO.Directory.CreateDirectory(WorkingDataFolder); } List <FileSourceInfo> fileSourceInfoList = new List <FileSourceInfo>(); foreach (var fileSource in EngineInstance.Files.FileSources) { if (fileSource.Type == Files.InputType.Source || fileSource.Type == Files.InputType.Image) { FileSourceInfo fileSourceInfo = new FileSourceInfo(); fileSourceInfo.CopyFrom(fileSource); fileSourceInfoList.Add(fileSourceInfo); } ; } binaryConfigParser.Save(WorkingDataFolder + "/Config.nd", stylesWithInheritance, fileSourceInfoList); // // Always rebuild the scaffolding since they're quick. If you ever make this differential, remember that FramePage depends // on the project name and other information. // unprocessedChanges.AddFramePage(); unprocessedChanges.AddMainStyleFiles(); // // Load up unprocessedChanges if we're rebuilding // if (EngineInstance.HasIssues(StartupIssues.NeedToRebuildAllOutput) || (newStartupIssues & StartupIssues.NeedToRebuildAllOutput) != 0) { unprocessedChanges.AddSourceFiles(buildState.sourceFilesWithContent); unprocessedChanges.AddClasses(buildState.classesWithContent); unprocessedChanges.AddImageFiles(buildState.usedImageFiles); newStartupIssues |= StartupIssues.NeedToReparseStyleFiles; unprocessedChanges.AddMainStyleFiles(); unprocessedChanges.AddMainSearchFiles(); unprocessedChanges.AddFramePage(); unprocessedChanges.AddMenu(); // We'll handle search prefixes after starting SearchIndex } // // Create the search index, watch other modules, and apply new StartupIssues // searchIndex = new SearchIndex.Manager(this); EngineInstance.CodeDB.AddChangeWatcher(this); EngineInstance.Files.AddChangeWatcher(this); searchIndex.AddChangeWatcher(this); EngineInstance.AddStartupWatcher(this); if (newStartupIssues != StartupIssues.None) { EngineInstance.AddStartupIssues(newStartupIssues, dontNotify: this); } searchIndex.Start(errorList); // // If we're rebuilding everything, add the search index prefixes now that that's started // if (EngineInstance.HasIssues(StartupIssues.NeedToRebuildAllOutput)) { var usedPrefixes = searchIndex.UsedPrefixes(); foreach (var searchPrefix in usedPrefixes) { unprocessedChanges.AddSearchPrefix(searchPrefix); } } return(errors == errorList.Count); }
/* Function: Start * * 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. * * Dependencies: * * - <Config.Manager> must be started before this class can start. */ public bool Start(Errors.ErrorList errorList) { StartupIssues newStartupIssues = StartupIssues.None; List <ConfigFileCommentType> systemCommentTypeList; List <ConfigFileCommentType> projectCommentTypeList; List <string> ignoredSystemKeywords; List <string> ignoredProjectKeywords; List <string> systemTags; List <string> projectTags; List <CommentType> binaryCommentTypes; List <Tag> binaryTags; List <KeyValuePair <string, int> > binarySingularKeywords; List <KeyValuePair <string, int> > binaryPluralKeywords; List <string> binaryIgnoredKeywords; // The return value, which is whether we were able to successfully load and parse the system Comments.txt, and if it exists, // the project Comments.txt. The project Comments.txt not existing is not a failure. bool success = true; Comments_nd commentsNDParser = new Comments_nd(); // We need the ID numbers to stay consistent between runs, so we need to create all the comment types and tags from the // binary file first. We'll worry about comparing their attributes and seeing if any were added or deleted later. if (EngineInstance.HasIssues(StartupIssues.NeedToStartFresh | StartupIssues.CommentIDsInvalidated)) { binaryCommentTypes = new List <CommentType>(); binaryTags = new List <Tag>(); binarySingularKeywords = new List <KeyValuePair <string, int> >(); binaryPluralKeywords = new List <KeyValuePair <string, int> >(); binaryIgnoredKeywords = new List <string>(); newStartupIssues |= StartupIssues.NeedToReparseAllFiles | StartupIssues.CommentIDsInvalidated; } else if (commentsNDParser.Load(EngineInstance.Config.WorkingDataFolder + "/Comments.nd", out binaryCommentTypes, out binaryTags, out binarySingularKeywords, out binaryPluralKeywords, out binaryIgnoredKeywords) == false) { // Even though it failed, LoadBinaryFile will still create valid empty objects for the variables. newStartupIssues |= StartupIssues.NeedToReparseAllFiles | StartupIssues.CommentIDsInvalidated; } else // Load binary file succeeded { // We use a try block so if anything screwy happens, like two things having the same ID number and thus causing // an exception when added, we can continue as if the binary file didn't parse at all. try { foreach (CommentType binaryCommentType in binaryCommentTypes) { // We don't add the binary comment type itself because we only want those for comparison purposes. We want // the types in commentTypes to be at their default values because the Comments.txt versions will only set some attributes, // not all, and we don't want the unset attributes influenced by the binary versions. CommentType newCommentType = new CommentType(binaryCommentType.Name); newCommentType.ID = binaryCommentType.ID; newCommentType.Flags.InBinaryFile = true; commentTypes.Add(newCommentType); } foreach (Tag binaryTag in binaryTags) { Tag newTag = new Tag(binaryTag.Name); newTag.ID = binaryTag.ID; newTag.InBinaryFile = true; tags.Add(newTag); } } catch { newStartupIssues |= StartupIssues.NeedToReparseAllFiles | StartupIssues.CommentIDsInvalidated; commentTypes.Clear(); tags.Clear(); // Clear them since they may be used later in this function. binaryCommentTypes.Clear(); binarySingularKeywords.Clear(); binaryPluralKeywords.Clear(); binaryIgnoredKeywords.Clear(); // Otherwise ignore the exception and continue. } } Path systemFile = EngineInstance.Config.SystemConfigFolder + "/Comments.txt"; Path projectFile = EngineInstance.Config.ProjectConfigFolder + "/Comments.txt"; Path oldProjectFile = EngineInstance.Config.ProjectConfigFolder + "/Topics.txt"; Comments_txt commentsTxtParser = new Comments_txt(); // Load the files. if (!commentsTxtParser.Load(systemFile, Config.PropertySource.SystemCommentsFile, out systemCommentTypeList, out ignoredSystemKeywords, out systemTags, errorList)) { success = false; // Continue anyway because we want to show errors from both files. } if (System.IO.File.Exists(projectFile)) { if (!commentsTxtParser.Load(projectFile, Config.PropertySource.ProjectCommentsFile, out projectCommentTypeList, out ignoredProjectKeywords, out projectTags, errorList)) { success = false; } } else if (System.IO.File.Exists(oldProjectFile)) { if (!commentsTxtParser.Load(oldProjectFile, Config.PropertySource.ProjectCommentsFile, out projectCommentTypeList, out ignoredProjectKeywords, out projectTags, errorList)) { success = false; } } else { // The project file not existing is not an error condition. Fill in the variables with empty structures. projectCommentTypeList = new List <ConfigFileCommentType>(); ignoredProjectKeywords = new List <string>(); projectTags = new List <string>(); } if (success == false) { return(false); } // Combine the ignored keywords. StringSet ignoredKeywords = new StringSet(KeySettingsForKeywords); foreach (string keyword in ignoredSystemKeywords) { if (keyword != null) { ignoredKeywords.Add(keyword); } } foreach (string keyword in ignoredProjectKeywords) { if (keyword != null) { ignoredKeywords.Add(keyword); } } // Combine the tags foreach (string tagName in systemTags) { Tag tag = tags[tagName]; if (tag == null) { tag = new Tag(tagName); tag.InSystemFile = true; tags.Add(tag); } else { tag.InSystemFile = true; // In case it changed since the binary version. tag.FixNameCapitalization(tagName); } } foreach (string tagName in projectTags) { Tag tag = tags[tagName]; if (tag == null) { tag = new Tag(tagName); tag.InProjectFile = true; tags.Add(tag); } else { tag.InProjectFile = true; tag.FixNameCapitalization(tagName); } } // All the comment types have to exist in IDObjects.Manager before the properties are set because Index With will need their // IDs. This pass only creates the types that were not already created by the binary file. // We don't need to do separate passes for standard entries and alter entries because alter entries should only appear // in the project file and only apply to types in the system file. Anything else is either an error (system file can't alter a // project entry) or would have been simplified out by LoadFile (a file with an alter entry applying to a type in the same // file.) foreach (ConfigFileCommentType commentType in systemCommentTypeList) { if (!Start_CreateType(commentType, systemFile, true, errorList)) { success = false; } } foreach (ConfigFileCommentType commentType in projectCommentTypeList) { if (!Start_CreateType(commentType, projectFile, false, errorList)) { success = false; } } // Need to exit early because Start_ApplyProperties assumes all the types were created correctly. if (success == false) { return(false); } // Now that everything's in commentTypes we can delete the ones that aren't in the text files, meaning they were in // the binary file from the last run but were deleted since then. We have to put them on a list and delete them in a // second pass because deleting them while iterating through would screw up the iterator. List <int> deletedIDs = new List <int>(); foreach (CommentType commentType in commentTypes) { if (commentType.Flags.InConfigFiles == false) { deletedIDs.Add(commentType.ID); newStartupIssues |= StartupIssues.NeedToReparseAllFiles; } } foreach (int deletedID in deletedIDs) { commentTypes.Remove(deletedID); } // Delete the tags that weren't in the text files as well. deletedIDs.Clear(); foreach (Tag tag in tags) { if (tag.InConfigFiles == false) { deletedIDs.Add(tag.ID); newStartupIssues |= StartupIssues.NeedToReparseAllFiles; } } foreach (int deletedID in deletedIDs) { tags.Remove(deletedID); } // Fill in the properties foreach (ConfigFileCommentType commentType in systemCommentTypeList) { if (!Start_ApplyProperties(commentType, systemFile, ignoredKeywords, errorList)) { success = false; } } foreach (ConfigFileCommentType commentType in projectCommentTypeList) { if (!Start_ApplyProperties(commentType, projectFile, ignoredKeywords, errorList)) { success = false; } } if (success == false) { return(false); } // Make sure there are no circular dependencies in Index With. foreach (CommentType commentType in commentTypes) { if (commentType.Index == CommentType.IndexValue.IndexWith) { IDObjects.NumberSet ids = new IDObjects.NumberSet(); CommentType currentType = commentType; do { ids.Add(currentType.ID); if (ids.Contains(currentType.IndexWith)) { // Start the dependency message on the repeated comment type, not on the one the loop started with because // it could go A > B > C > B, in which case reporting A is irrelevant. int repeatedID = currentType.IndexWith; CommentType iterator = commentTypes[repeatedID]; string repeatMessage = iterator.Name; // We want the error message to be on the repeated type only if that's the only one: A > A. Otherwise we // want it to be the second to last one: C in A > B > C > B. CommentType errorMessageTarget = currentType; for (;;) { iterator = commentTypes[iterator.IndexWith]; repeatMessage += " > " + iterator.Name; if (iterator.ID == repeatedID) { break; } errorMessageTarget = iterator; } Path errorMessageFile; List <ConfigFileCommentType> searchList; if (errorMessageTarget.Flags.InProjectFile) { errorMessageFile = projectFile; searchList = projectCommentTypeList; } else { errorMessageFile = systemFile; searchList = systemCommentTypeList; } int errorMessageLineNumber = 0; string lcErrorMessageTargetName = errorMessageTarget.Name.ToLower(); foreach (ConfigFileCommentType searchListType in searchList) { if (searchListType.Name.ToLower() == lcErrorMessageTargetName) { errorMessageLineNumber = searchListType.LineNumber; break; } } errorList.Add( Locale.Get("NaturalDocs.Engine", "Comments.txt.CircularDependencyInIndexWith(list)", repeatMessage), errorMessageFile, errorMessageLineNumber ); return(false); } currentType = commentTypes[currentType.IndexWith]; }while (currentType.Index == CommentType.IndexValue.IndexWith); } } // Simplify Index With. So A > B > C becomes A > C. Also A > B = no indexing becomes A = no indexing. foreach (CommentType commentType in commentTypes) { if (commentType.Index == CommentType.IndexValue.IndexWith) { CommentType targetCommentType = commentTypes[commentType.IndexWith]; while (targetCommentType.Index == CommentType.IndexValue.IndexWith) { targetCommentType = commentTypes[targetCommentType.IndexWith]; } if (targetCommentType.Index == CommentType.IndexValue.No) { commentType.Index = CommentType.IndexValue.No; } else { commentType.IndexWith = targetCommentType.ID; } } } // Everything is okay at this point. Save the files again to reformat them. If the project file didn't exist, saving it // with the empty structures we created will create it. Start_FixCapitalization(systemCommentTypeList); Start_FixCapitalization(projectCommentTypeList); if (!commentsTxtParser.Save(projectFile, projectCommentTypeList, ignoredProjectKeywords, projectTags, errorList, true, false)) { success = false; } ; if (!commentsTxtParser.Save(systemFile, systemCommentTypeList, ignoredSystemKeywords, systemTags, errorList, false, true)) { success = false; } ; // Compare the structures with the binary ones to see if anything changed. We only need to bother if we weren't already going to reparse // all the files. bool changed = EngineInstance.HasIssues(StartupIssues.NeedToReparseAllFiles) || (newStartupIssues & StartupIssues.NeedToReparseAllFiles) != 0; if (changed == false) { // First an easy comparison. if (binaryCommentTypes.Count != commentTypes.Count || binaryTags.Count != tags.Count || binaryIgnoredKeywords.Count != ignoredKeywords.Count || singularKeywords.Count != binarySingularKeywords.Count || pluralKeywords.Count != binaryPluralKeywords.Count) { changed = true; } } if (changed == false) { // Next a detailed comparison if necessary. foreach (CommentType binaryCommentType in binaryCommentTypes) { CommentType commentType = commentTypes[binaryCommentType.ID]; if (commentType == null || binaryCommentType != commentType) { changed = true; break; } } if (changed == false) { foreach (Tag binaryTag in binaryTags) { Tag tag = tags[binaryTag.ID]; if (tag == null || binaryTag != tag) { changed = true; break; } } } if (changed == false) { foreach (string binaryIgnoredKeyword in binaryIgnoredKeywords) { if (!ignoredKeywords.Contains(binaryIgnoredKeyword)) { changed = true; break; } } } if (changed == false) { foreach (KeyValuePair <string, int> binarySingularKeywordPair in binarySingularKeywords) { // We can use ID instead of Name because we know they match now. if (singularKeywords.ContainsKey(binarySingularKeywordPair.Key) == false || singularKeywords[binarySingularKeywordPair.Key].ID != binarySingularKeywordPair.Value) { changed = true; break; } } } if (changed == false) { foreach (KeyValuePair <string, int> binaryPluralKeywordPair in binaryPluralKeywords) { // We can use ID instead of Name because we know they match now. if (pluralKeywords.ContainsKey(binaryPluralKeywordPair.Key) == false || pluralKeywords[binaryPluralKeywordPair.Key].ID != binaryPluralKeywordPair.Value) { changed = true; break; } } } } if (changed) { newStartupIssues |= StartupIssues.NeedToReparseAllFiles; } commentsNDParser.Save(EngineInstance.Config.WorkingDataFolder + "/Comments.nd", commentTypes, tags, singularKeywords, pluralKeywords, ignoredKeywords); groupCommentTypeID = IDFromKeyword("group"); if (newStartupIssues != StartupIssues.None) { EngineInstance.AddStartupIssues(newStartupIssues); } return(success); }
/* Function: AddAllFiles * Goes through all the files in the <FileSource> and calls <Files.Manager.AddOrUpdateFile()> on each one. */ override public void AddAllFiles(CancelDelegate cancelDelegate) { status.Reset(); bool forceReparse = EngineInstance.HasIssues(StartupIssues.NeedToStartFresh | StartupIssues.NeedToReparseAllFiles); Path path = (FileSource as FileSources.SourceFolder).Path; // Using a string stack instead of Path stack because the I/O functions will return strings and there's no need to normalize // them all or otherwise use Path functions on them. Stack <string> foldersToSearch = new Stack <string>(); foldersToSearch.Push(path); while (foldersToSearch.Count > 0) { string folder = foldersToSearch.Pop(); if (Manager.SourceFolderIsIgnored(folder)) { continue; } status.AddFolders(InputType.Source, 1); string[] subfolders = System.IO.Directory.GetDirectories(folder); if (cancelDelegate()) { return; } foreach (string subfolder in subfolders) { foldersToSearch.Push(subfolder); } string[] files = System.IO.Directory.GetFiles(folder); if (cancelDelegate()) { return; } foreach (string file in files) { Path filePath = file; string extension = filePath.Extension; FileType?fileType = null; if (EngineInstance.Languages.FromFileExtension(extension) != null) { fileType = FileType.Source; } // We also look for images in the source folders because "(see image.jpg)" may be relative to the source // file instead of an image folder. else if (Files.Manager.ImageExtensions.Contains(extension)) { fileType = FileType.Image; } if (fileType != null) { status.AddFiles((FileType)fileType, 1); Manager.AddOrUpdateFile(filePath, (FileType)fileType, System.IO.File.GetLastWriteTimeUtc(file), forceReparse); } if (cancelDelegate()) { return; } } } }
// 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); }
// Group: Initialization Functions // __________________________________________________________________________ /* Function: Start_Stage1 * * Loads and combines the two versions of <Languages.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 <Comments.txt> will be loaded. Call <Start_Stage2()> after * <CommentTypes.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; // // Languages.txt // Path systemTextConfigPath = EngineInstance.Config.SystemConfigFolder + "/Languages.txt"; Path projectTextConfigPath = EngineInstance.Config.ProjectConfigFolder + "/Languages.txt"; ConfigFiles.TextFileParser textConfigFileParser = new ConfigFiles.TextFileParser(); // Load the system Languages.txt. if (!textConfigFileParser.Load(systemTextConfigPath, PropertySource.SystemLanguagesFile, errorList, out systemTextConfig)) { success = false; } // Load the project Languages.txt. We want to do this even if the system Languages.txt failed so we get the error messages // from both. if (System.IO.File.Exists(projectTextConfigPath)) { if (!textConfigFileParser.Load(projectTextConfigPath, PropertySource.ProjectLanguagesFile, errorList, out projectTextConfig)) { success = false; } } // If it doesn't exist it's not an error. Just create a blank config. else { projectTextConfig = new ConfigFiles.TextFile(); } if (!success) { return(false); } if (!ValidateLanguages(systemTextConfig, errorList)) { success = false; } if (!ValidateLanguages(projectTextConfig, errorList)) { success = false; } if (!success) { return(false); } // Merge them into one combined config. Note that this doesn't do file extensions, aliases, or shebang strings. Only the // languages and their other properties will exist in the merged config. if (!MergeLanguages(systemTextConfig, projectTextConfig, out mergedTextConfig, errorList)) { return(false); } if (!ValidateLanguages(mergedTextConfig, errorList)) { return(false); } // // Languages.nd // Path lastRunConfigPath = EngineInstance.Config.WorkingDataFolder + "/Languages.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.CodeIDsInvalidated; } // // Create the final config // config = new Config(); // We go through the languages 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 language IDs consistent from one run to the next if possible. IDObjects.NumberSet usedLanguageIDs = new IDObjects.NumberSet(); IDObjects.NumberSet lastRunUsedLanguageIDs = lastRunConfig.UsedLanguageIDs(); if (mergedTextConfig.HasLanguages) { foreach (var textLanguage in mergedTextConfig.Languages) { // First we need our base language object. If there's a predefined language matching the name we'll use that so // we get its settings and parser. If not we'll create a new object. We shouldn't have to worry about more than one // language using the same predefined language object because validation should have taken care of that. var finalLanguage = FindPredefinedLanguage(textLanguage.Name); if (finalLanguage == null) { finalLanguage = new Language(textLanguage.Name); } // Apply the properties. Keep going if there are errors since we want to find them all. if (!FinalizeLanguage(ref finalLanguage, textLanguage, errorList)) { success = false; } // We still need to set the ID. See if a language of the same name existed in the previous run. var lastRunLanguage = lastRunConfig.LanguageFromName(textLanguage.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 (lastRunLanguage == null) { int id = lastRunUsedLanguageIDs.LowestAvailable; if (usedLanguageIDs.Contains(id)) { id = Math.Max(usedLanguageIDs.Highest + 1, lastRunUsedLanguageIDs.Highest + 1); } finalLanguage.ID = id; config.AddLanguage(finalLanguage); usedLanguageIDs.Add(finalLanguage.ID); lastRunUsedLanguageIDs.Add(finalLanguage.ID); } // If the language did exist but we haven't used its ID yet, we can keep it. else if (!usedLanguageIDs.Contains(lastRunLanguage.ID)) { finalLanguage.ID = lastRunLanguage.ID; config.AddLanguage(finalLanguage); usedLanguageIDs.Add(finalLanguage.ID); } // However, if the language 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 { finalLanguage.ID = usedLanguageIDs.LowestAvailable; config.AddLanguage(finalLanguage); usedLanguageIDs.Add(finalLanguage.ID); newStartupIssues |= StartupIssues.CodeIDsInvalidated; } // 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 (finalLanguage.SimpleIdentifier == null) { finalLanguage.SimpleIdentifier = "LanguageID" + finalLanguage.ID; } // Also create a generic parser object if one wasn't inherited from a predefined language. if (!finalLanguage.HasParser) { finalLanguage.Parser = new Parser(EngineInstance, finalLanguage); } } } if (!success) { return(false); } // Apply file extensions, aliases, and shebang strings now. MergeLanguageIdentifiersInto(ref config, systemTextConfig, projectTextConfig); // That's it for stage one. Everything else is in stage 2. if (newStartupIssues != StartupIssues.None) { EngineInstance.AddStartupIssues(newStartupIssues); } return(true); }
/* Function: Start * * Validates the configuration and starts the module if successful. This can only be called once. If it fails, scrap the * entire <Engine.Instance> and start again. * * Dependencies: * * - <Config.Manager> and <Languages.Manager> must be started before using the rest of the class. Also * anything that could possibly set <Config.Manager.RebuildEverything>. */ public bool Start(Errors.ErrorList errors) { int startingErrorCount = errors.Count; StartupIssues newStartupIssues = StartupIssues.None; // Validate FileSources if (fileSources.Count == 0) { errors.Add( Locale.Get("NaturalDocs.Engine", "Error.NoFileSourcesDefined") ); } else { foreach (FileSource fileSource in fileSources) { fileSource.Validate(errors); } } // Make sure no source folders are completely ignored because of filters foreach (var fileSource in fileSources) { if (fileSource is FileSources.SourceFolder) { FileSources.SourceFolder folderFileSource = (FileSources.SourceFolder)fileSource; if (SourceFolderIsIgnored(folderFileSource.Path)) { errors.Add( Locale.Get("NaturalDocs.Engine", "Error.SourceFolderIsIgnored(sourceFolder)", folderFileSource.Path) ); } } } // Load Files.nd if (EngineInstance.HasIssues(StartupIssues.NeedToStartFresh)) { newStartupIssues |= StartupIssues.FileIDsInvalidated | StartupIssues.NeedToReparseAllFiles; } else { Files_nd filesParser = new Files_nd(); if (!filesParser.Load(EngineInstance.Config.WorkingDataFolder + "/Files.nd", out files)) { newStartupIssues |= StartupIssues.FileIDsInvalidated | StartupIssues.NeedToReparseAllFiles; } else // Files.nd loaded successfully { if (EngineInstance.HasIssues(StartupIssues.NeedToReparseAllFiles)) { unprocessedChanges.AddChangedFiles(files.usedIDs); } } } if (newStartupIssues != StartupIssues.None) { EngineInstance.AddStartupIssues(newStartupIssues); } bool success = (errors.Count == startingErrorCount); started = success; return(success); }
/* Function: Start * * Loads and combines the two versions of <Languages.txt>, returning whether it was successful. If there were any errors * they will be added to errorList. * * Dependencies: * * - <Config.Manager> and <CommentTypes.Manager> must be started before using the rest of the class. */ public bool Start(Errors.ErrorList errorList) { StartupIssues newStartupIssues = StartupIssues.None; List <ConfigFileLanguage> systemLanguageList; List <ConfigFileLanguage> projectLanguageList; List <string> ignoredSystemExtensions; List <string> ignoredProjectExtensions; List <Language> binaryLanguages; List <KeyValuePair <string, int> > binaryAliases; List <KeyValuePair <string, int> > binaryExtensions; List <KeyValuePair <string, int> > binaryShebangStrings; List <string> binaryIgnoredExtensions; // The return value, which is whether we were able to successfully load and parse the system Languages.txt, and if // it exists, the project Languages.txt. The project Languages.txt not existing is not a failure. bool success = true; // First add all the predefined languages, since they may be subclassed. foreach (Language language in predefinedLanguages) { languages.Add(language); } // We need the ID numbers to stay consistent between runs, so we create all the languages from the binary file // next. We'll worry about comparing their attributes with the text files and seeing if any were added or deleted later. Languages_nd languagesNDParser = new Languages_nd(this); // Don't bother going through the effort if we're rebuilding everything anyway. if (EngineInstance.HasIssues(StartupIssues.NeedToStartFresh | StartupIssues.CodeIDsInvalidated)) { binaryLanguages = new List <Language>(); binaryAliases = new List <KeyValuePair <string, int> >(); binaryExtensions = new List <KeyValuePair <string, int> >(); binaryShebangStrings = new List <KeyValuePair <string, int> >(); binaryIgnoredExtensions = new List <string>(); newStartupIssues |= StartupIssues.NeedToReparseAllFiles | StartupIssues.CodeIDsInvalidated; } else if (!languagesNDParser.Load(EngineInstance.Config.WorkingDataFolder + "/Languages.nd", out binaryLanguages, out binaryAliases, out binaryExtensions, out binaryShebangStrings, out binaryIgnoredExtensions)) { newStartupIssues |= StartupIssues.NeedToReparseAllFiles | StartupIssues.CodeIDsInvalidated; // Even though it failed, LoadBinaryFiles will still have created valid empty objects for them. } else // LoadBinaryFile succeeded { // We use a try block so if anything screwy happens, like two languages having the same ID number and thus // causing an exception when added, we can continue as if the binary file didn't parse at all. try { foreach (Language binaryLanguage in binaryLanguages) { // We don't add the binary language itself because we only want those for comparison purposes. We otherwise // want the languages to be at their default values because the Languages.txt versions will only set some // attributes, not all. // Check for predefined languages of the same name. If any of the binary languages' IDs collide with the // predefined languages' ones, it will be taken care of by the exception handler. Language existingLanguage = languages[binaryLanguage.Name]; if (existingLanguage == null) { Language newLanguage = new Language(this, binaryLanguage.Name); newLanguage.ID = binaryLanguage.ID; newLanguage.InBinaryFile = true; languages.Add(newLanguage); } else { existingLanguage.InBinaryFile = true; } } } catch { languages.Clear(); newStartupIssues |= StartupIssues.NeedToReparseAllFiles | StartupIssues.CodeIDsInvalidated; foreach (Language predefinedLanguage in predefinedLanguages) { languages.Add(predefinedLanguage); } // Clear them since they may be used later in this function. binaryLanguages.Clear(); binaryAliases.Clear(); binaryExtensions.Clear(); binaryShebangStrings.Clear(); binaryIgnoredExtensions.Clear(); // Otherwise ignore the exception and continue. } } Path systemFile = EngineInstance.Config.SystemConfigFolder + "/Languages.txt"; Path projectFile = EngineInstance.Config.ProjectConfigFolder + "/Languages.txt"; Languages_txt languagesTxtParser = new Languages_txt(); // Load the files. if (!languagesTxtParser.Load(systemFile, Config.PropertySource.SystemLanguageFile, out systemLanguageList, out ignoredSystemExtensions, errorList)) { success = false; // Continue anyway because we want to show errors from both files. } if (System.IO.File.Exists(projectFile)) { if (!languagesTxtParser.Load(projectFile, Config.PropertySource.ProjectLanguageFile, out projectLanguageList, out ignoredProjectExtensions, errorList)) { success = false; } } else { // The project file not existing is not an error condition. Fill in the variables with empty structures. projectLanguageList = new List <ConfigFileLanguage>(); ignoredProjectExtensions = new List <string>(); } if (success == false) { return(false); } // Combine the ignored extensions. StringSet ignoredExtensions = new StringSet(KeySettingsForExtensions); foreach (string extension in ignoredSystemExtensions) { ignoredExtensions.Add(extension); } foreach (string extension in ignoredProjectExtensions) { ignoredExtensions.Add(extension); } // Add the languages. We don't need to do separate passes for standard entries and alter entries because alter // entries should only appear in the project file and only apply to types in the system file. Anything else is either an // error (system file can't alter a project entry) or would have been simplified out by LoadFile (a file with an alter // entry applying to a language in the same file.) Start_AddLanguage() also prevents inappropriate properties from // being set on languages, like Line Comment on one with full language support. foreach (ConfigFileLanguage configFileLanguage in systemLanguageList) { if (!Start_AddLanguage(configFileLanguage, systemFile, true, ignoredExtensions, errorList)) { success = false; } } foreach (ConfigFileLanguage configFileLanguage in projectLanguageList) { if (!Start_AddLanguage(configFileLanguage, projectFile, false, ignoredExtensions, errorList)) { success = false; } } if (success == false) { return(false); } // Now that everything's in languages we can delete the ones that weren't in the config files, such as predefined // languages that were removed or languages that were in the binary file from the last run but were deleted. We // have to put them on a list and delete them in a second pass because deleting them while iterating through would // screw up the iterator. List <string> deletedLanguageNames = new List <string>(); foreach (Language language in languages) { if (language.InConfigFiles == false) { deletedLanguageNames.Add(language.Name); // Check this flag so we don't set it to changed if we're deleting a predefined language that wasn't in the binary // file. if (language.InBinaryFile == true) { newStartupIssues |= StartupIssues.NeedToReparseAllFiles; } } } foreach (string deletedLanguageName in deletedLanguageNames) { languages.Remove(deletedLanguageName); } // Everything is okay at this point. Save the files again to reformat them. If the project file didn't exist, saving it // with the empty structures will create it. Start_FixCapitalization(systemLanguageList); Start_FixCapitalization(projectLanguageList); if (!languagesTxtParser.Save(projectFile, projectLanguageList, ignoredProjectExtensions, errorList, true, false)) { success = false; } ; if (!languagesTxtParser.Save(systemFile, systemLanguageList, ignoredSystemExtensions, errorList, false, true)) { success = false; } ; // Generate alternate comment styles. We don't want these included in the config files but we do want them in the // binary files in case the generation method changes in a future version. foreach (Language language in languages) { if (language.Type == Language.LanguageType.BasicSupport) { language.GenerateJavadocCommentStrings(); language.GenerateXMLCommentStrings(); } } // Compare the structures with the binary ones to see if anything changed. if (binaryLanguages.Count != languages.Count || binaryAliases.Count != aliases.Count || binaryExtensions.Count != extensions.Count || binaryShebangStrings.Count != shebangStrings.Count || binaryIgnoredExtensions.Count != ignoredExtensions.Count) { newStartupIssues |= StartupIssues.NeedToReparseAllFiles; } // Only bother to do a detailed comparison if we're not already reparsing everything. else if (!EngineInstance.HasIssues(StartupIssues.NeedToReparseAllFiles) && (newStartupIssues & StartupIssues.NeedToReparseAllFiles) == 0) { bool changed = false; foreach (Language binaryLanguage in binaryLanguages) { Language language = languages[binaryLanguage.Name]; if (language == null || binaryLanguage != language) { changed = true; break; } } if (changed == false) { foreach (string binaryIgnoredExtension in binaryIgnoredExtensions) { if (ignoredExtensions.Contains(binaryIgnoredExtension) == false) { changed = true; break; } } } if (changed == false) { foreach (KeyValuePair <string, int> binaryAliasPair in binaryAliases) { // We can use ID instead of Name because we know they match now. if (aliases.ContainsKey(binaryAliasPair.Key) == false || aliases[binaryAliasPair.Key].ID != binaryAliasPair.Value) { changed = true; break; } } } if (changed == false) { foreach (KeyValuePair <string, int> binaryExtensionPair in binaryExtensions) { // We can use ID instead of Name because we know they match now. if (extensions.ContainsKey(binaryExtensionPair.Key) == false || extensions[binaryExtensionPair.Key].ID != binaryExtensionPair.Value) { changed = true; break; } } } if (changed == false) { foreach (KeyValuePair <string, int> binaryShebangStringPair in binaryShebangStrings) { // We can use ID instead of Name because we know they match now. if (shebangStrings.ContainsKey(binaryShebangStringPair.Key) == false || shebangStrings[binaryShebangStringPair.Key].ID != binaryShebangStringPair.Value) { changed = true; break; } } } if (changed) { newStartupIssues |= StartupIssues.NeedToReparseAllFiles; } } languagesNDParser.Save(EngineInstance.Config.WorkingDataFolder + "/Languages.nd", languages, aliases, extensions, shebangStrings, ignoredExtensions); if (newStartupIssues != StartupIssues.None) { EngineInstance.AddStartupIssues(newStartupIssues); } return(success); }
/* Function: AddAllFiles * Goes through all the files for all the loaded styles in <Styles.Manager> and calls <Files.Manager.AddOrUpdateFile()> * on each one. */ override public void AddAllFiles(CancelDelegate cancelDelegate) { status.Reset(); IList <Style> styles = EngineInstance.Styles.LoadedStyles; bool forceReparse = EngineInstance.HasIssues(StartupIssues.NeedToStartFresh | StartupIssues.NeedToReparseAllFiles | StartupIssues.NeedToReparseStyleFiles); // String stack instead of Path stack because the IO functions will return strings and there's no need to normalize // them all or otherwise use Path functions on them. Stack <string> foldersToSearch = new Stack <string>(); foreach (var style in styles) { if (style is Styles.CSSOnly) { Path cssFile = (style as Styles.CSSOnly).CSSFile; status.AddFiles(Files.FileType.Style, 1); EngineInstance.Files.AddOrUpdateFile(cssFile, Files.FileType.Style, System.IO.File.GetLastWriteTimeUtc(cssFile), forceReparse); } else if (style is Styles.Advanced) { foldersToSearch.Clear(); foldersToSearch.Push((style as Styles.Advanced).Folder); status.AddFolders(Files.InputType.Style, 1); while (foldersToSearch.Count > 0) { string folder = foldersToSearch.Pop(); string[] subfolders = System.IO.Directory.GetDirectories(folder); status.AddFolders(Files.InputType.Style, subfolders.Length); foreach (string subfolder in subfolders) { foldersToSearch.Push(subfolder); } if (cancelDelegate()) { return; } string[] files = System.IO.Directory.GetFiles(folder); foreach (string file in files) { if (style.Contains(file)) { status.AddFiles(Files.FileType.Style, 1); EngineInstance.Files.AddOrUpdateFile(file, Files.FileType.Style, System.IO.File.GetLastWriteTimeUtc(file), forceReparse); if (cancelDelegate()) { return; } } } } } else { throw new NotImplementedException(); } } }
private static bool BuildDocumentation(ErrorList errorList) { ShowConsoleHeader(); EngineInstance.AddStartupWatcher(new EngineStartupWatcher()); executionTimer.Start("Engine Startup"); if (EngineInstance.Start(errorList, commandLineConfig) == true) { executionTimer.End("Engine Startup"); // File Search executionTimer.Start("Finding Source Files"); var adderProcess = EngineInstance.Files.CreateAdderProcess(); using (StatusManagers.FileSearch statusManager = new StatusManagers.FileSearch(adderProcess)) { statusManager.Start(); Multithread("File Adder", adderProcess.WorkOnAddingAllFiles); statusManager.End(); } EngineInstance.Files.DeleteFilesNotReAdded(Engine.Delegates.NeverCancel); adderProcess.Dispose(); executionTimer.End("Finding Source Files"); // Rebuild notice string alternativeStartMessage = null; if (EngineInstance.Config.UserWantsEverythingRebuilt || EngineInstance.Config.UserWantsOutputRebuilt) { alternativeStartMessage = "Status.RebuildEverythingByRequest"; } else if (EngineInstance.HasIssues(StartupIssues.NeedToStartFresh | StartupIssues.NeedToReparseAllFiles | StartupIssues.NeedToRebuildAllOutput)) { alternativeStartMessage = "Status.RebuildEverythingAutomatically"; } // Parsing executionTimer.Start("Parsing Source Files"); var changeProcessor = EngineInstance.Files.CreateChangeProcessor(); using (StatusManagers.Parsing statusManager = new StatusManagers.Parsing(changeProcessor, alternativeStartMessage)) { statusManager.Start(); totalFileChanges = statusManager.TotalFilesToProcess; Multithread("Parser", changeProcessor.WorkOnProcessingChanges); statusManager.End(); } changeProcessor.Dispose(); executionTimer.End("Parsing Source Files"); // Resolving executionTimer.Start("Resolving Links"); var resolverProcess = EngineInstance.Links.CreateResolverProcess(); using (StatusManagers.ResolvingLinks statusManager = new StatusManagers.ResolvingLinks(resolverProcess)) { statusManager.Start(); Multithread("Resolver", resolverProcess.WorkOnResolvingLinks); statusManager.End(); } resolverProcess.Dispose(); executionTimer.End("Resolving Links"); // Building executionTimer.Start("Building Output"); var builderProcess = EngineInstance.Output.CreateBuilderProcess(); using (StatusManagers.Building statusManager = new StatusManagers.Building(builderProcess)) { statusManager.Start(); Multithread("Builder", builderProcess.WorkOnUpdatingOutput); Multithread("Finalizer", builderProcess.WorkOnFinalizingOutput); statusManager.End(); } builderProcess.Dispose(); executionTimer.End("Building Output"); // End EngineInstance.Cleanup(Delegates.NeverCancel); ShowConsoleFooter(true); return(true); } else // engine did not start correctly { executionTimer.End("Engine Startup"); ShowConsoleFooter(false); return(false); } }