/* Function: Save * Saves the passed information in <Config.nd>. */ public void Save(Path filename, Config.ProjectInfo projectInfo, List <Style> styles, List <FileSourceInfo> fileSourceInfoList) { using (BinaryFile binaryFile = new BinaryFile()) { binaryFile.OpenForWriting(filename); // [String: Project Title or null] // [String: Project Subtitle or null] // [String: Project Copyright or null] // [String: Project Timestamp Code or null] binaryFile.WriteString(projectInfo.Title); binaryFile.WriteString(projectInfo.Subtitle); binaryFile.WriteString(projectInfo.Copyright); binaryFile.WriteString(projectInfo.TimestampCode); // [String: Style Path] // (properties) // [String: Style Path] // ... // [String: null] foreach (var style in styles) { if (style is Styles.CSSOnly) { binaryFile.WriteString((style as Styles.CSSOnly).CSSFile); } else if (style is Styles.Advanced) { binaryFile.WriteString((style as Styles.Advanced).ConfigFile); } else { throw new NotImplementedException(); } // [String: Inherit] ... [String: null] if (style.Inherits != null) { foreach (var inheritStatement in style.Inherits) { binaryFile.WriteString(inheritStatement.Name); } } binaryFile.WriteString(null); // [String: OnLoad] [Byte: Page Type] ... [String: null] if (style.OnLoad != null) { foreach (var onLoadStatement in style.OnLoad) { binaryFile.WriteString(onLoadStatement.Statement); binaryFile.WriteByte((byte)onLoadStatement.Type); } } binaryFile.WriteString(null); // [String: Link] [Byte: Page Type] ... [String: null] if (style.Links != null) { foreach (var linkStatement in style.Links) { binaryFile.WriteString(linkStatement.File); binaryFile.WriteByte((byte)linkStatement.Type); } } binaryFile.WriteString(null); // [String: Home Page or null] binaryFile.WriteString(style.HomePage); } // End of style paths binaryFile.WriteString(null); // [Int32: Source FileSource Number] [String: Source FileSource UniqueIDString] // [Int32: Source FileSource Number] [String: Source FileSource UniqueIDString] // ... // [Int32: 0] foreach (FileSourceInfo fileSourceInfo in fileSourceInfoList) { if (fileSourceInfo.Type == Files.InputType.Source) { binaryFile.WriteInt32(fileSourceInfo.Number); binaryFile.WriteString(fileSourceInfo.UniqueIDString); } } binaryFile.WriteInt32(0); // [Int32: Image FileSource Number] [String: Image FileSource UniqueIDString] // [Int32: Image FileSource Number] [String: Image FileSource UniqueIDString] // ... // [Int32: 0] foreach (FileSourceInfo fileSourceInfo in fileSourceInfoList) { if (fileSourceInfo.Type == Files.InputType.Image) { binaryFile.WriteInt32(fileSourceInfo.Number); binaryFile.WriteString(fileSourceInfo.UniqueIDString); } } binaryFile.WriteInt32(0); } }
/* Function: Load * Loads the information in <Config.nd> and returns whether it was successful. If not all the out parameters will still * return objects, they will just be empty. */ public bool Load(Path filename, out Config.ProjectInfo projectInfo, out List <Style> styles, out List <FileSourceInfo> fileSourceInfoList) { projectInfo = new Config.ProjectInfo(); styles = new List <Style>(); fileSourceInfoList = new List <FileSourceInfo>(); BinaryFile binaryFile = new BinaryFile(); bool result = true; try { if (binaryFile.OpenForReading(filename, "2.2") == false) { result = false; } else { // [String: Project Title or null] // [String: Project Subtitle or null] // [String: Project Copyright or null] // [String: Project Timestamp Code or null] projectInfo.Title = binaryFile.ReadString(); projectInfo.TitlePropertyLocation = Config.PropertySource.PreviousRun; projectInfo.Subtitle = binaryFile.ReadString(); projectInfo.SubtitlePropertyLocation = Config.PropertySource.PreviousRun; projectInfo.Copyright = binaryFile.ReadString(); projectInfo.CopyrightPropertyLocation = Config.PropertySource.PreviousRun; projectInfo.TimestampCode = binaryFile.ReadString(); projectInfo.TimestampCodePropertyLocation = Config.PropertySource.PreviousRun; // [String: Style Path] // (properties) // [String: Style Path] // ... // [String: null] string stylePath = binaryFile.ReadString(); while (stylePath != null) { Style style; if (stylePath.EndsWith(".css", StringComparison.OrdinalIgnoreCase)) { style = new Styles.CSSOnly(stylePath); } else { style = new Styles.Advanced(stylePath); } styles.Add(style); // [String: Inherit] ... [String: null] string inheritStatement = binaryFile.ReadString(); while (inheritStatement != null) { // Find the name in the list of styles so we can connect the objects together properly. There should only // be one style per name so we can just compare by name. Also, this list is stored in the order in which // they must be applied, which means inherited styles will appear before the ones that inherit from them, // so we can search the list we've built so far instead of waiting until they're all loaded. Style matchingStyle = null; for (int i = 0; i < styles.Count; i++) { if (string.Compare(inheritStatement, styles[i].Name, StringComparison.OrdinalIgnoreCase) == 0) { matchingStyle = styles[i]; break; } } // If there's no match just add it as null. style.AddInheritedStyle(inheritStatement, Config.PropertySource.PreviousRun, matchingStyle); inheritStatement = binaryFile.ReadString(); } // [String: OnLoad] [Byte: Page Type] ... [String: null] string onLoadStatement = binaryFile.ReadString(); while (onLoadStatement != null) { Engine.Styles.PageType pageType = (Engine.Styles.PageType)binaryFile.ReadByte(); style.AddOnLoad(onLoadStatement, Config.PropertySource.PreviousRun, pageType); onLoadStatement = binaryFile.ReadString(); } // [String: Link] [Byte: Page Type] ... [String: null] string linkStatement = binaryFile.ReadString(); while (linkStatement != null) { Engine.Styles.PageType pageType = (Engine.Styles.PageType)binaryFile.ReadByte(); style.AddLinkedFile(linkStatement, Config.PropertySource.PreviousRun, pageType); linkStatement = binaryFile.ReadString(); } // [String: Home Page or null] string homePage = binaryFile.ReadString(); if (homePage != null) { style.SetHomePage(homePage, Config.PropertySource.PreviousRun); } // Next style path stylePath = binaryFile.ReadString(); } projectInfo.StyleName = styles[styles.Count - 1].Name; projectInfo.StyleNamePropertyLocation = Config.PropertySource.PreviousRun; // [Int32: Source FileSource Number] [String: Source FileSource UniqueIDString] // [Int32: Source FileSource Number] [String: Source FileSource UniqueIDString] // ... // [Int32: 0] FileSourceInfo fileSourceInfo = new FileSourceInfo(); fileSourceInfo.Type = Files.InputType.Source; for (;;) { fileSourceInfo.Number = binaryFile.ReadInt32(); if (fileSourceInfo.Number == 0) { break; } fileSourceInfo.UniqueIDString = binaryFile.ReadString(); fileSourceInfoList.Add(fileSourceInfo); } // [Int32: Image FileSource Number] [String: Image FileSource UniqueIDString] // [Int32: Image FileSource Number] [String: Image FileSource UniqueIDString] // ... // [Int32: 0] fileSourceInfo.Type = Files.InputType.Image; for (;;) { fileSourceInfo.Number = binaryFile.ReadInt32(); if (fileSourceInfo.Number == 0) { break; } fileSourceInfo.UniqueIDString = binaryFile.ReadString(); fileSourceInfoList.Add(fileSourceInfo); } } } catch { result = false; } finally { binaryFile.Dispose(); } if (result == false) { projectInfo = new Config.ProjectInfo(); styles.Clear(); fileSourceInfoList.Clear(); } return(result); }
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); } if (style == null) { return(false); } stylesWithInheritance = style.BuildInheritanceList(); // // Load Config.nd // Config_nd binaryConfigParser = new Config_nd(); Config.ProjectInfo previousProjectInfo; List <Style> previousStyles; List <FileSourceInfo> previousFileSourceInfoList; bool hasBinaryConfigFile = false; if (!EngineInstance.HasIssues(StartupIssues.NeedToStartFresh)) { hasBinaryConfigFile = binaryConfigParser.Load(WorkingDataFolder + "/Config.nd", out previousProjectInfo, out previousStyles, out previousFileSourceInfoList); } else // start fresh { previousProjectInfo = new Config.ProjectInfo(); previousStyles = new List <Style>(); previousFileSourceInfoList = new List <FileSourceInfo>(); } // // Compare to the previous project info // bool projectInfoChanged = (!hasBinaryConfigFile || ProjectInfo.Title != previousProjectInfo.Title || ProjectInfo.Subtitle != previousProjectInfo.Subtitle || ProjectInfo.Copyright != previousProjectInfo.Copyright || ProjectInfo.TimestampCode != previousProjectInfo.TimestampCode); // // Compare to the previous list of styles. // bool inPurgingOperation = false; bool hasStyleChanges = 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; hasStyleChanges = true; } 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) { hasStyleChanges = true; 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) { hasStyleChanges = true; newStartupIssues |= StartupIssues.NeedToReparseStyleFiles; break; } } // Check if the list of styles is the same or there's any changes in the settings. if (stylesWithInheritance.Count != previousStyles.Count) { hasStyleChanges = true; } if (!hasStyleChanges) { for (int i = 0; i < stylesWithInheritance.Count; i++) { if (!stylesWithInheritance[i].IsSameStyleAndProperties(previousStyles[i], includeInheritedStyles: false)) { hasStyleChanges = true; 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", ProjectInfo, stylesWithInheritance, fileSourceInfoList); // // Determine our home page // Path newHomePage = Style.HomePageOf(stylesWithInheritance); bool homePageChanged = (newHomePage != buildState.HomePage); buildState.HomePage = newHomePage; // HomePageUsesTimestamp will be determined when it's built. // // Generate our timestamp // // We do it here instead of as needed because there are two places it could be used (the frame page and the home page) // and we want to avoid the admittedly unlikely possibility that Natural Docs can be building around midnight and use one // date for one and another for the other. string newTimestamp = ProjectInfo.MakeTimestamp(); bool timestampChanged = (newTimestamp != buildState.GeneratedTimestamp); buildState.GeneratedTimestamp = newTimestamp; // // Load up unprocessedChanges // 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.AddHomePage(); unprocessedChanges.AddMenu(); // We'll handle search prefixes after starting SearchIndex } else { if (!hasBinaryConfigFile || projectInfoChanged || timestampChanged) { unprocessedChanges.AddFramePage(); } if (!hasBinaryConfigFile || hasStyleChanges) { unprocessedChanges.AddMainStyleFiles(); } if (!hasBinaryConfigFile || homePageChanged || projectInfoChanged || (timestampChanged && buildState.HomePageUsesTimestamp)) { unprocessedChanges.AddHomePage(); } } // // 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); } } bool success = (errors == errorList.Count); started = success; return(success); }