context: // This program is free software; you can redistribute it and/or modify
  // it under the terms of the GNU General Public License as published by
  // the Free Software Foundation; either version 2 of the License, or
  // (at your option) any later version.
  //
  // This program is distributed in the hope that it will be useful,
  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  // GNU General Public License for more details.
  //
  // You should have received a copy of the GNU General Public License
  // along with this program; if not, write to the Free Software
  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  
  using System;
  using System.IO;
  using System.Diagnostics;
  
  namespace NDoc3.Core {
  	/// <summary>
  	/// Handler for content events
  	/// </summary>
  	public delegate void ContentEventHandler(string path);
  
  	/// <summary>
  	/// The Workspace class manages the Output directory and its subfolders
  	/// where help file content and project files are used to compile the 
  	/// final help collection
  	/// </summary>
  	public class Workspace : IDisposable {
  		/// <summary>
  		/// Event raised when a content directory is added
  		/// </summary>
  		public event ContentEventHandler ContentDirectoryAdded;
  
  		/// <summary>
  		/// Event raised when a content file is added
  		/// </summary>
  		public event ContentEventHandler ContentFileAdded;
  
  		/// <summary>
  		/// The name of the directory where the html file are created
  		/// </summary>
  		private readonly string contentDir = "content";
  
  		/// <summary>
  		/// The location of the workspace and files
  		/// </summary>
  		private readonly string rootDir = string.Empty;
  
  		/// <summary>
  		/// These are the output file type extensions that will be cleaned
  		/// </summary>
  		private readonly string cleanableFileTypes = string.Empty;
  
  		private readonly string buildDir;
  
  		/// <summary>
  		/// Creates a new instance of the <see cref="Workspace"/> class.
  		/// </summary>
  		/// <param name="root">The location to create the workspace</param>
  		/// <param name="type">The type of workspace</param>
  		/// <param name="contentDirName">The name of the sub folder where content will be placed</param>
  		/// <param name="cleanableExtensions">A semi-colon delimited list of file extensions that can be deleted when cleaning
  		/// the root folder of the workspace (*.ex1;*.ex2)</param>
  		public Workspace(string root, string type, string contentDirName, string cleanableExtensions) {
  			if (!Path.IsPathRooted(root))
  				throw new ArgumentException("A relative path cannot be used for a worksapce");
  
  			if (type.Length == 0)
  				throw new ArgumentException("The workspace type cannot be zero length", type);
  
  			rootDir = root;
  
  			buildDir = string.Format("ndoc3_{0}_temp", type);
  			contentDir = contentDirName;
  			cleanableFileTypes = cleanableExtensions;
  
  			if (!Directory.Exists(RootDirectory))
  				Directory.CreateDirectory(RootDirectory);
  
  			CleanIntermediates();
  		}
  
  		/// <summary>
  		/// The name of the directory where the compilation takes place
  		/// </summary>
  		public string WorkingDirectoryName {
  			get {
  				return buildDir;
  			}
  		}
  
  		/// <summary>
  		/// The name of the content directory
  		/// </summary>
  		public string ContentDirectoryName {
  			get {
  				return contentDir;
  			}
  		}
  
  		/// <summary>
  		/// The full path to the worksapce root.
  		/// This is where project outputs will be saved when compilation
  		/// is complete
  		/// </summary>
  		public string RootDirectory {
  			get {
  				return rootDir;
  			}
  		}
  
  		/// <summary>
  		/// Prepares the workspace, by creating working and content directories
  		/// </summary>
  		public void Prepare() {
  			if (!Directory.Exists(WorkingDirectory))
  				Directory.CreateDirectory(WorkingDirectory);
  
  			if (!Directory.Exists(ContentDirectory))
  				Directory.CreateDirectory(ContentDirectory);
  
  			if (!Directory.Exists(ResourceDirectory))
  				Directory.CreateDirectory(ResourceDirectory);
  		}
  
  		/// <summary>
  		/// The full path of the help content files
  		/// </summary>
  		public string ContentDirectory {
  			get {
  				return Path.Combine(WorkingDirectory, ContentDirectoryName);
  			}
  		}
  
  		/// <summary>
  		/// The the full path to the directory where the compilation will run
  		/// </summary>
  		public string WorkingDirectory {
  			get {
  				return Path.Combine(RootDirectory, buildDir);
  			}
  		}
  
  		/// <summary>
  		/// The location where the xslt stylesheets will be unpacked
  		/// </summary>
  		public string ResourceDirectory {
  			get {
  				return Path.Combine(WorkingDirectory, "ndoc3_resources");
  			}
  		}
  
  		/// <summary>
  		/// Adds a sub directory to the resource directory
  		/// </summary>
  		/// <param name="dirName">The name of the sub directory</param>
  		public void AddResourceDirectory(string dirName) {
  			if (!Directory.Exists(Path.Combine(ResourceDirectory, dirName)))
  				Directory.CreateDirectory(Path.Combine(ResourceDirectory, dirName));
  		}
  
  		/// <summary>
  		/// Recursively deletes the ResourceDirectory
  		/// </summary>
  		public void RemoveResourceDirectory() {
  			if (Directory.Exists(ResourceDirectory))
  				Directory.Delete(ResourceDirectory, true);
  		}
  
  		/// <summary>
  		/// Saves files mathing the specified filter from the build directory to the root directory
  		/// </summary>
  		/// <param name="filter">File filter to search for</param>
  		public void SaveOutputs(string filter) {
  			DirectoryInfo dir = new DirectoryInfo(WorkingDirectory);
  			foreach (FileInfo f in dir.GetFiles(filter)) {
  				string newFile = Path.Combine(RootDirectory, f.Name);
  				if (File.Exists(newFile))
  					File.Delete(newFile);
  				f.MoveTo(newFile);
  			}
  		}
  
  		/// <summary>
  		/// Copies project resources into the workspace.
  		/// Project files are files needed to compile the help file, but
  		/// are not directly part of its content
  		/// </summary>
  		/// <param name="sourceDirectory">The path to the resources</param>
  		public void ImportProjectFiles(string sourceDirectory) {
  			ImportProjectFiles(sourceDirectory, "*.*");
  		}
  
  		/// <summary>
  		/// Copies project resources into the workspace
  		/// Project files are files needed to compile the help file, but
  		/// are not directly part of its content
  		/// </summary>
  		/// <param name="sourceDirectory">The path to the resources</param>
  		/// <param name="filter">File filter to use when selecting files to import</param>
  		public void ImportProjectFiles(string sourceDirectory, string filter) {
  			if (!Directory.Exists(sourceDirectory))
  				throw new ArgumentException(string.Format("The source location {0} does not exist", sourceDirectory));
  
  			DirectoryInfo dir = new DirectoryInfo(sourceDirectory);
  			foreach (FileInfo file in dir.GetFiles(filter))
  				ImportFile(file, WorkingDirectory);
  		}
  
  		private static void ImportFile(FileInfo file, string targetDir) {
  			Debug.Assert(file != null);
  
  			string targetName = Path.Combine(targetDir, file.Name);
  			file.CopyTo(targetName, true);
  			// clear any possible readonly flag so we can delete this file later during clean-up
  			File.SetAttributes(targetName, FileAttributes.Normal);
  		}
  
  		/// <summary>
  		/// Recursively copies the contents of sourceDirectory into the workspace content,
  		/// maintainng the same directory structure
  		/// </summary>
  		/// <param name="sourceDirectory">The directory to import</param>
  		public void ImportContentDirectory(DirectoryInfo sourceDirectory) {
  			if (!sourceDirectory.Exists)
  				throw new ArgumentException(string.Format("The source location {0} does not exist", sourceDirectory));
  
  			// first import all the files in the directory directly to content
  			foreach (FileInfo file in sourceDirectory.GetFiles())
  				ImportFile(file, ContentDirectory);
  
  			// then recursively import any foldes in sourceDirectory
  			foreach (DirectoryInfo dir in sourceDirectory.GetDirectories())
  				ImportDirectory(dir, new DirectoryInfo(ContentDirectory));
  		}
  
  		/// <summary>
  		/// Raises the <see cref="Workspace.ContentDirectoryAdded"/> event
  		/// </summary>
  		/// <param name="relativePath">Path relative to the workspace root</param>
  		protected virtual void OnContentDirectoryAdded(string relativePath) {
  			if (ContentDirectoryAdded != null)
  				ContentDirectoryAdded(relativePath);
  		}
  
  		private void ImportDirectory(DirectoryInfo sourceDir, DirectoryInfo targetParent) {
  			DirectoryInfo targetDir = new DirectoryInfo(Path.Combine(targetParent.FullName, sourceDir.Name));
  
  			if (!targetDir.Exists)
  				targetDir.Create();
  
  			OnContentDirectoryAdded(GetRelativePath(new DirectoryInfo(WorkingDirectory), targetDir));
  
  			foreach (FileInfo file in sourceDir.GetFiles())
  				ImportFile(file, targetDir.FullName);
  
  			foreach (DirectoryInfo childDir in sourceDir.GetDirectories())
  				ImportDirectory(childDir, targetDir);
  		}
  
  		/// <summary>
  		/// Return the relative path between two directories
  		/// </summary>
  		/// <param name="ancestor">The folder closest to the drive root</param>
  		/// <param name="child">A folder that is a child of ancestor</param>
  		/// <returns></returns>
  		public static string GetRelativePath(DirectoryInfo ancestor, DirectoryInfo child) {
  			if (ancestor.Root.FullName != child.Root.FullName)
  				return "";
  
  			string tmp = child.FullName.Replace(ancestor.FullName, "");
  
  			// strip off the leading backslash if present
  			if (tmp.IndexOf(Path.DirectorySeparatorChar, 0) == 0)
  
gt: 				tmp = tmp.Substring(1);