1 // ****************************************************************
2 // Copyright 2002-2003, Charlie Poole
3 // This is free software licensed under the NUnit license. You may
4 // obtain a copy of the license at http://nunit.org/?p=license&r=2.4
5 // ****************************************************************
6 
7 using System;
8 using System.IO;
9 using System.Text;
10 using System.Reflection;
11 using System.Collections;
12 using System.Runtime.InteropServices;
13 
14 namespace NUnit.Util
15 {
16 	/// <summary>
17 	/// Static methods for manipulating project paths, including both directories
18 	/// and files. Some synonyms for System.Path methods are included as well.
19 	/// </summary>
20 	public class PathUtils
21 	{
22 		public const uint FILE_ATTRIBUTE_DIRECTORY  = 0x00000010;
23 		public const uint FILE_ATTRIBUTE_NORMAL     = 0x00000080;
24 		public const int MAX_PATH = 256;
25 
26 		protected static char DirectorySeparatorChar = Path.DirectorySeparatorChar;
27 		protected static char AltDirectorySeparatorChar = Path.AltDirectorySeparatorChar;
28 
29 		#region Public methods
30 
IsAssemblyFileType( string path )31 		public static bool IsAssemblyFileType( string path )
32 		{
33 			string extension = Path.GetExtension( path ).ToLower();
34 			return extension == ".dll" || extension == ".exe";
35 		}
36 
37 		/// <summary>
38 		/// Returns the relative path from a base directory to another
39 		/// directory or file.
40 		/// </summary>
RelativePath( string from, string to )41 		public static string RelativePath( string from, string to )
42 		{
43 			if (from == null)
44 				throw new ArgumentNullException (from);
45 			if (to == null)
46 				throw new ArgumentNullException (to);
47 			if (!Path.IsPathRooted (to))
48 				return to;
49 			if (Path.GetPathRoot (from) != Path.GetPathRoot (to))
50 				return null;
51 
52 			string[] _from = from.Split (PathUtils.DirectorySeparatorChar,
53 				PathUtils.AltDirectorySeparatorChar);
54 			string[] _to   =   to.Split (PathUtils.DirectorySeparatorChar,
55 				PathUtils.AltDirectorySeparatorChar);
56 
57 			StringBuilder sb = new StringBuilder (Math.Max (from.Length, to.Length));
58 
59 			int last_common, min = Math.Min (_from.Length, _to.Length);
60 			for (last_common = 0; last_common < min;  ++last_common)
61 			{
62 				if (!_from [last_common].Equals (_to [last_common]))
63 					break;
64 			}
65 
66 			if (last_common < _from.Length)
67 				sb.Append ("..");
68 			for (int i = last_common + 1; i < _from.Length; ++i)
69 			{
70 				sb.Append (PathUtils.DirectorySeparatorChar).Append ("..");
71 			}
72 
73 			if (sb.Length > 0)
74 				sb.Append (PathUtils.DirectorySeparatorChar);
75 			if (last_common < _to.Length)
76 				sb.Append (_to [last_common]);
77 			for (int i = last_common + 1; i < _to.Length; ++i)
78 			{
79 				sb.Append (PathUtils.DirectorySeparatorChar).Append (_to [i]);
80 			}
81 
82 			return sb.ToString ();
83 		}
84 
85 		/// <summary>
86 		/// Return the canonical form of a path.
87 		/// </summary>
Canonicalize( string path )88 		public static string Canonicalize( string path )
89 		{
90 			ArrayList parts = new ArrayList(
91 				path.Split( DirectorySeparatorChar, AltDirectorySeparatorChar ) );
92 
93 			for( int index = 0; index < parts.Count; )
94 			{
95 				string part = (string)parts[index];
96 
97 				switch( part )
98 				{
99 					case ".":
100 						parts.RemoveAt( index );
101 						break;
102 
103 					case "..":
104 						parts.RemoveAt( index );
105 						if ( index > 0 )
106 							parts.RemoveAt( --index );
107 						break;
108 					default:
109 						index++;
110 						break;
111 				}
112 			}
113 
114 			return String.Join( DirectorySeparatorChar.ToString(), (string[])parts.ToArray( typeof( string ) ) );
115 		}
116 
117 		/// <summary>
118 		/// True if the two paths are the same. However, two paths
119 		/// to the same file or directory using different network
120 		/// shares or drive letters are not treated as equal.
121 		/// </summary>
SamePath( string path1, string path2 )122 		public static bool SamePath( string path1, string path2 )
123 		{
124 			return string.Compare( Canonicalize(path1), Canonicalize(path2), PathUtils.IsWindows() ) == 0;
125 		}
126 
127 		/// <summary>
128 		/// True if the two paths are the same or if the second is
129 		/// directly or indirectly under the first. Note that paths
130 		/// using different network shares or drive letters are
131 		/// considered unrelated, even if they end up referencing
132 		/// the same subtrees in the file system.
133 		/// </summary>
SamePathOrUnder( string path1, string path2 )134 		public static bool SamePathOrUnder( string path1, string path2 )
135 		{
136 			path1 = Canonicalize( path1 );
137 			path2 = Canonicalize( path2 );
138 
139 			int length1 = path1.Length;
140 			int length2 = path2.Length;
141 
142 			// if path1 is longer, then path2 can't be under it
143 			if ( length1 > length2 )
144 				return false;
145 
146 			// if lengths are the same, check for equality
147 			if ( length1 == length2 )
148 				//return path1.ToLower() == path2.ToLower();
149 				return string.Compare( path1, path2, IsWindows() ) == 0;
150 
151 			// path 2 is longer than path 1: see if initial parts match
152 			//if ( path1.ToLower() != path2.Substring( 0, length1 ).ToLower() )
153 			if ( string.Compare( path1, path2.Substring( 0, length1 ), IsWindows() ) != 0 )
154 				return false;
155 
156 			// must match through or up to a directory separator boundary
157 			return	path2[length1-1] == DirectorySeparatorChar ||
158 				path2[length1] == DirectorySeparatorChar;
159 		}
160 
Combine( string path1, params string[] morePaths )161 		public static string Combine( string path1, params string[] morePaths )
162 		{
163 			string result = path1;
164 			foreach( string path in morePaths )
165 				result = Path.Combine( result, path );
166 			return result;
167 		}
168 
169 		// TODO: This logic should be in shared source
GetAssemblyPath( Assembly assembly )170 		public static string GetAssemblyPath( Assembly assembly )
171 		{
172 			string uri = assembly.CodeBase;
173 
174 			// If it wasn't loaded locally, use the Location
175 			if ( !uri.StartsWith( Uri.UriSchemeFile ) )
176 				return assembly.Location;
177 
178 			return GetAssemblyPathFromFileUri( uri );
179 		}
180 
181 		// Separate method for testability
GetAssemblyPathFromFileUri( string uri )182 		public static string GetAssemblyPathFromFileUri( string uri )
183 		{
184 			// Skip over the file://
185 			int start = Uri.UriSchemeFile.Length + Uri.SchemeDelimiter.Length;
186 
187 			if ( PathUtils.DirectorySeparatorChar == '\\' )
188 			{
189 				if ( uri[start] == '/' && uri[start+2] == ':' )
190 					++start;
191 			}
192 			else
193 			{
194 				if ( uri[start] != '/' )
195 					--start;
196 			}
197 
198 			return uri.Substring( start );
199 		}
200 		#endregion
201 
202 		#region Helper Methods
IsWindows()203 		private static bool IsWindows()
204 		{
205 			return PathUtils.DirectorySeparatorChar == '\\';
206 		}
207 		#endregion
208 	}
209 }
210