/* Function: FinishedPurging * Call this after you're done calling all the other purging functions. Pass the reference to the bool that tracked whether * we've registered a PossiblyLongOperation for purging with <Engine.Instance>. If it's set to true it will call * <Engine.Instance.EndPossiblyLongOperation()> and set it to false. */ protected void FinishedPurging(ref bool inPurgingOperation) { if (inPurgingOperation) { EngineInstance.EndPossiblyLongOperation(); inPurgingOperation = false; } }
/* Function: Start * * Initializes the configuration and returns whether all the settings are correct and that execution is ready to begin. * If there are problems they are added as <Errors> to the errorList parameter. This class is *not* designed to allow * multiple attempts. If this function fails scrap the entire <Engine.Instance> and start again. * * After <Start()> is called the properties of this class become read-only. This function will add all the input and filter * targets it has to <Files.Manager>, and all output targets to <Output.Manager>. */ public bool Start(ErrorList errorList, ProjectConfig commandLineConfig) { bool success = true; // Validate project config folder projectConfigFolder = commandLineConfig.ProjectConfigFolder; if (!commandLineConfig.ProjectConfigFolderPropertyLocation.IsDefined || String.IsNullOrEmpty(projectConfigFolder)) { errorList.Add( message: Locale.Get("NaturalDocs.Engine", "Error.NoProjectConfigFolder"), configSource: Source.CommandLine, property: "ProjectConfigFolder" ); success = false; } else if (!System.IO.Directory.Exists(projectConfigFolder)) { errorList.Add( message: Locale.Get("NaturalDocs.Engine", "Error.ProjectConfigFolderDoesntExist(name)", projectConfigFolder), propertyLocation: commandLineConfig.ProjectConfigFolderPropertyLocation, property: "ProjectConfigFolder" ); success = false; } else if (projectConfigFolder == SystemConfigFolder) { errorList.Add( message: Locale.Get("NaturalDocs.Engine", "Error.ProjectConfigFolderCannotEqualSystemConfigFolder"), propertyLocation: commandLineConfig.ProjectConfigFolderPropertyLocation, property: "ProjectConfigFolder" ); success = false; } if (success == false) { return(false); } // Load and merge configuration files ProjectConfig combinedConfig = new ProjectConfig(Source.Combined); MergeConfig(combinedConfig, commandLineConfig); var projectTxtParser = new Project_txt(); if (System.IO.File.Exists(projectConfigFolder + "/Project.txt")) { ProjectConfig projectTxtConfig; if (projectTxtParser.Load(projectConfigFolder + "/Project.txt", out projectTxtConfig, errorList)) { MergeConfig(combinedConfig, projectTxtConfig); } else { success = false; } } else if (System.IO.File.Exists(ProjectConfigFolder + "/Menu.txt")) { // Try to extract information from a pre-2.0 Menu.txt instead. ProjectConfig menuTxtConfig; var menuTxtParser = new Menu_txt(); if (menuTxtParser.Load(ProjectConfigFolder + "/Menu.txt", out menuTxtConfig)) { MergeConfig(combinedConfig, menuTxtConfig); } // No errors if this fails } // If neither file exists it's not an error condition. Just treat it as if nothing was defined. MergeConfig(combinedConfig, systemDefaultConfig); if (success == false) { return(false); } // Validate the working data folder, creating one if it doesn't exist. workingDataFolder = combinedConfig.WorkingDataFolder; if (!combinedConfig.WorkingDataFolderPropertyLocation.IsDefined || String.IsNullOrEmpty(workingDataFolder)) { workingDataFolder = projectConfigFolder + "/Working Data"; } if (!System.IO.Directory.Exists(workingDataFolder)) { try { System.IO.Directory.CreateDirectory(workingDataFolder); } catch { errorList.Add( message: Locale.Get("NaturalDocs.Engine", "Error.CantCreateWorkingDataFolder(name)", workingDataFolder), property: "WorkingDataFolder" ); success = false; } } if (success == false) { return(false); } // Load the previous configuration state. Remember that not every value in ProjectConfig is stored in Project.nd. ProjectConfig previousConfig = null; var projectNDParser = new Project_nd(); if (ReparseEverything == false && System.IO.File.Exists(workingDataFolder + "/Project.nd")) { if (!projectNDParser.Load(workingDataFolder + "/Project.nd", out previousConfig)) { previousConfig = null; } } // Merge output target numbers from Project.nd into the settings. These are not stored in Project.txt because they're // pretty expendible. if (previousConfig != null) { foreach (var target in combinedConfig.OutputTargets) { foreach (var previousTarget in previousConfig.OutputTargets) { if (target.IsSameTarget(previousTarget)) { target.Number = previousTarget.Number; break; } } } } // Target validation if (combinedConfig.InputTargets.Count < 1) { errorList.Add( message: Locale.Get("NaturalDocs.Engine", "Error.NoInputTargets"), property: "InputTargets" ); success = false; } if (combinedConfig.OutputTargets.Count < 1) { errorList.Add( message: Locale.Get("NaturalDocs.Engine", "Error.NoOutputTargets"), property: "OutputTargets" ); success = false; } for (int i = 0; i < combinedConfig.InputTargets.Count; i++) { if (combinedConfig.InputTargets[i].Validate(errorList, i) == false) { success = false; } } for (int i = 0; i < combinedConfig.FilterTargets.Count; i++) { if (combinedConfig.FilterTargets[i].Validate(errorList, i) == false) { success = false; } } for (int i = 0; i < combinedConfig.OutputTargets.Count; i++) { if (combinedConfig.OutputTargets[i].Validate(errorList, i) == false) { success = false; } } if (success == false) { return(false); } // Determine the target numbers that are already used and reset duplicates. IDObjects.NumberSet usedSourceNumbers = new IDObjects.NumberSet(); IDObjects.NumberSet usedImageNumbers = new IDObjects.NumberSet(); foreach (var target in combinedConfig.InputTargets) { if (target.Number != 0) { if (target.Type == Files.InputType.Source) { if (usedSourceNumbers.Contains(target.Number)) { target.Number = 0; target.NumberPropertyLocation = Source.NotDefined; } else { usedSourceNumbers.Add(target.Number); } } else if (target.Type == Files.InputType.Image) { if (usedImageNumbers.Contains(target.Number)) { target.Number = 0; target.NumberPropertyLocation = Source.NotDefined; } else { usedImageNumbers.Add(target.Number); } } } } IDObjects.NumberSet usedOutputNumbers = new IDObjects.NumberSet(); IDObjects.NumberSet outputNumbersToPurge = new IDObjects.NumberSet(); foreach (var target in combinedConfig.OutputTargets) { if (target.Number != 0) { if (usedOutputNumbers.Contains(target.Number)) { target.Number = 0; target.NumberPropertyLocation = Source.NotDefined; // Since we don't know which of the two entries generated working data under this number, purge it to be safe. outputNumbersToPurge.Add(target.Number); } else { usedOutputNumbers.Add(target.Number); } } } // Assign numbers to the entries that don't already have them and generate default input folder names. foreach (var target in combinedConfig.InputTargets) { if (target.Type == Files.InputType.Source) { if (target.Number == 0) { target.Number = usedSourceNumbers.LowestAvailable; target.NumberPropertyLocation = Source.SystemGenerated; usedSourceNumbers.Add(target.Number); } if (target.Name == null && combinedConfig.InputTargets.Count > 1) { target.GenerateDefaultName(); } } else if (target.Type == Files.InputType.Image) { if (target.Number == 0) { target.Number = usedImageNumbers.LowestAvailable; target.NumberPropertyLocation = Source.SystemGenerated; usedImageNumbers.Add(target.Number); } } } foreach (var target in combinedConfig.OutputTargets) { if (target.Number == 0) { target.Number = usedOutputNumbers.LowestAvailable; target.NumberPropertyLocation = Source.SystemGenerated; usedOutputNumbers.Add(target.Number); // If we're assigning it for the first time, purge it on the off chance that there's data left over from another // builder. outputNumbersToPurge.Add(target.Number); } } // Rebuild everything if there's an output target that didn't exist on the last run. foreach (var target in combinedConfig.OutputTargets) { bool foundMatch = false; if (previousConfig != null) { foreach (var previousTarget in previousConfig.OutputTargets) { if (previousTarget.IsSameTarget(target)) { foundMatch = true; break; } } } if (foundMatch == false) { RebuildAllOutput = true; break; } } // Apply global settings. // DEPENDENCY: We assume all these settings are set in systemDefaultConfig so we don't have to worry about them being // undefined. tabWidth = combinedConfig.TabWidth; documentedOnly = combinedConfig.DocumentedOnly; autoGroup = combinedConfig.AutoGroup; shrinkFiles = combinedConfig.ShrinkFiles; if (previousConfig == null || tabWidth != previousConfig.TabWidth || documentedOnly != previousConfig.DocumentedOnly || autoGroup != previousConfig.AutoGroup) { ReparseEverything = true; } else if (previousConfig != null && shrinkFiles != previousConfig.ShrinkFiles) { RebuildAllOutput = true; } // Resave the configuration. Project_txt will handle skipping all the default and command line properties. projectTxtParser.Save(projectConfigFolder + "/Project.txt", combinedConfig, errorList); projectNDParser.Save(workingDataFolder + "/Project.nd", combinedConfig); // Create file sources and filters for Files.Manager foreach (var target in combinedConfig.InputTargets) { EngineInstance.Files.AddFileSource(CreateFileSource(target)); } foreach (var target in combinedConfig.FilterTargets) { EngineInstance.Files.AddFilter(CreateFilter(target)); } // Some people may put the output folder in their source folder. Exclude it automatically. foreach (var target in combinedConfig.OutputTargets) { var filter = CreateOutputFilter(target); if (filter != null) { EngineInstance.Files.AddFilter(filter); } } // Create more default filters EngineInstance.Files.AddFilter(new Engine.Files.Filters.IgnoredSourceFolder(ProjectConfigFolder)); EngineInstance.Files.AddFilter(new Engine.Files.Filters.IgnoredSourceFolder(WorkingDataFolder)); EngineInstance.Files.AddFilter(new Engine.Files.Filters.IgnoredSourceFolder(SystemConfigFolder)); EngineInstance.Files.AddFilter(new Engine.Files.Filters.IgnoredSourceFolder(SystemStyleFolder)); EngineInstance.Files.AddFilter(new Engine.Files.Filters.IgnoredSourceFolderRegex(new Regex.Config.DefaultIgnoredSourceFolderRegex())); // Check all input folder entries against the filters. for (int i = 0; i < combinedConfig.InputTargets.Count; i++) { if (combinedConfig.InputTargets[i] is Targets.SourceFolder) { var sourceFolderTarget = (Targets.SourceFolder)combinedConfig.InputTargets[i]; if (sourceFolderTarget.Type == Files.InputType.Source && EngineInstance.Files.SourceFolderIsIgnored(sourceFolderTarget.Folder)) { errorList.Add( message: Locale.Get("NaturalDocs.Engine", "Error.SourceFolderIsIgnored(sourceFolder)", sourceFolderTarget.Folder), propertyLocation: sourceFolderTarget.FolderPropertyLocation, property: "InputTargets[" + i + "].Folder" ); success = false; } } else { throw new NotImplementedException(); } } // Create builders for Output.Manager foreach (var target in combinedConfig.OutputTargets) { // Merge the global project info so it has a complete configuration. The configuration files have already been saved without it. MergeProjectInfo(target.ProjectInfo, combinedConfig.ProjectInfo); EngineInstance.Output.AddBuilder(CreateBuilder(target)); } // Purge stray output working data, since otherwise it will be left behind if an output entry is removed. Regex.Config.OutputPathNumber outputPathNumberRegex = new Regex.Config.OutputPathNumber(); bool raisedPossiblyLongOperationEvent = false; string[] outputDataFolders = System.IO.Directory.GetDirectories(workingDataFolder, "Output*", System.IO.SearchOption.TopDirectoryOnly); foreach (string outputDataFolder in outputDataFolders) { System.Text.RegularExpressions.Match match = outputPathNumberRegex.Match(outputDataFolder); if (match.Success) { string numberString = match.Groups[1].ToString(); int number; if (String.IsNullOrEmpty(numberString)) { number = 1; } else { number = int.Parse(numberString); } if (outputNumbersToPurge.Contains(number) || !usedOutputNumbers.Contains(number)) { // Since we're deleting an entire folder, mark it as a possibly long operation. Some output formats may create many // files in there which could take a while to clear out. if (!raisedPossiblyLongOperationEvent) { EngineInstance.StartPossiblyLongOperation("PurgingOutputWorkingData"); raisedPossiblyLongOperationEvent = true; } try { System.IO.Directory.Delete(outputDataFolder, true); } catch (Exception e) { if (!(e is System.IO.IOException || e is System.IO.DirectoryNotFoundException)) { throw; } } } } } string[] outputDataFiles = System.IO.Directory.GetFiles(workingDataFolder, "Output*.nd", System.IO.SearchOption.TopDirectoryOnly); foreach (string outputDataFile in outputDataFiles) { System.Text.RegularExpressions.Match match = outputPathNumberRegex.Match(outputDataFile); if (match.Success) { string numberString = match.Groups[1].ToString(); int number; if (String.IsNullOrEmpty(numberString)) { number = 1; } else { number = int.Parse(numberString); } if (outputNumbersToPurge.Contains(number) || !usedOutputNumbers.Contains(number)) { // Since this should just be a few individual files we don't have to worry about it being a possibly long operation, // although this will piggyback on that event if it was already raised. System.IO.File.Delete(outputDataFile); } } } if (raisedPossiblyLongOperationEvent) { EngineInstance.EndPossiblyLongOperation(); } return(success); }