Clean up sln reader/writer (#5005)
This commit is contained in:
parent
d335b5de40
commit
2b7e9b6524
6 changed files with 316 additions and 639 deletions
|
@ -1,400 +0,0 @@
|
|||
//
|
||||
// FilePath.cs
|
||||
//
|
||||
// Author:
|
||||
// Lluis Sanchez Gual <lluis@novell.com>
|
||||
//
|
||||
// Copyright (c) 2011 Novell, Inc (http://www.novell.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.DotNet.Cli.Sln.Internal.FileManipulation
|
||||
{
|
||||
public struct FilePath : IComparable<FilePath>, IComparable, IEquatable<FilePath>
|
||||
{
|
||||
public static readonly StringComparer PathComparer = (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal;
|
||||
public static readonly StringComparison PathComparison = (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
|
||||
|
||||
readonly string fileName;
|
||||
|
||||
public static readonly FilePath Null = new FilePath(null);
|
||||
public static readonly FilePath Empty = new FilePath(string.Empty);
|
||||
|
||||
public FilePath(string name)
|
||||
{
|
||||
if (name != null && name.Length > 6 && name[0] == 'f' && name.StartsWith("file://", StringComparison.Ordinal))
|
||||
name = new Uri(name).LocalPath;
|
||||
|
||||
fileName = name;
|
||||
}
|
||||
|
||||
public bool IsNull
|
||||
{
|
||||
get { return fileName == null; }
|
||||
}
|
||||
|
||||
public bool IsNullOrEmpty
|
||||
{
|
||||
get { return string.IsNullOrEmpty(fileName); }
|
||||
}
|
||||
|
||||
public bool IsNotNull
|
||||
{
|
||||
get { return fileName != null; }
|
||||
}
|
||||
|
||||
public bool IsEmpty
|
||||
{
|
||||
get { return fileName != null && fileName.Length == 0; }
|
||||
}
|
||||
|
||||
const int PATHMAX = 4096 + 1;
|
||||
|
||||
static readonly char[] invalidPathChars = Path.GetInvalidPathChars();
|
||||
public static char[] GetInvalidPathChars()
|
||||
{
|
||||
return (char[])invalidPathChars.Clone();
|
||||
}
|
||||
|
||||
static readonly char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
|
||||
public static char[] GetInvalidFileNameChars()
|
||||
{
|
||||
return (char[])invalidFileNameChars.Clone();
|
||||
}
|
||||
|
||||
public FilePath FullPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return new FilePath(!string.IsNullOrEmpty(fileName) ? Path.GetFullPath(fileName) : "");
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
return Directory.Exists(FullPath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a path in standard form, which can be used to be compared
|
||||
/// for equality with other canonical paths. It is similar to FullPath,
|
||||
/// but unlike FullPath, the directory "/a/b" is considered equal to "/a/b/"
|
||||
/// </summary>
|
||||
public FilePath CanonicalPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (fileName == null)
|
||||
return FilePath.Null;
|
||||
if (fileName.Length == 0)
|
||||
return FilePath.Empty;
|
||||
string fp = Path.GetFullPath(fileName);
|
||||
if (fp.Length > 0)
|
||||
{
|
||||
if (fp[fp.Length - 1] == Path.DirectorySeparatorChar)
|
||||
return fp.TrimEnd(Path.DirectorySeparatorChar);
|
||||
if (fp[fp.Length - 1] == Path.AltDirectorySeparatorChar)
|
||||
return fp.TrimEnd(Path.AltDirectorySeparatorChar);
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
}
|
||||
|
||||
public string FileName
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.GetFileName(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
public string Extension
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.GetExtension(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasExtension(string extension)
|
||||
{
|
||||
return fileName.Length > extension.Length
|
||||
&& fileName.EndsWith(extension, StringComparison.OrdinalIgnoreCase)
|
||||
&& fileName[fileName.Length - extension.Length - 1] != Path.PathSeparator;
|
||||
}
|
||||
|
||||
public string FileNameWithoutExtension
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.GetFileNameWithoutExtension(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
public FilePath ParentDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
return new FilePath(Path.GetDirectoryName(fileName));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAbsolute
|
||||
{
|
||||
get { return Path.IsPathRooted(fileName); }
|
||||
}
|
||||
|
||||
public bool IsChildPathOf(FilePath basePath)
|
||||
{
|
||||
if (basePath.fileName[basePath.fileName.Length - 1] != Path.DirectorySeparatorChar)
|
||||
return fileName.StartsWith(basePath.fileName + Path.DirectorySeparatorChar, PathComparison);
|
||||
else
|
||||
return fileName.StartsWith(basePath.fileName, PathComparison);
|
||||
}
|
||||
|
||||
public FilePath ChangeExtension(string ext)
|
||||
{
|
||||
return Path.ChangeExtension(fileName, ext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a file path with the name changed to the provided name, but keeping the extension
|
||||
/// </summary>
|
||||
/// <returns>The new file path</returns>
|
||||
/// <param name="newName">New file name</param>
|
||||
public FilePath ChangeName(string newName)
|
||||
{
|
||||
return ParentDirectory.Combine(newName) + Extension;
|
||||
}
|
||||
|
||||
public FilePath Combine(params FilePath[] paths)
|
||||
{
|
||||
string path = fileName;
|
||||
foreach (FilePath p in paths)
|
||||
path = Path.Combine(path, p.fileName);
|
||||
return new FilePath(path);
|
||||
}
|
||||
|
||||
public FilePath Combine(params string[] paths)
|
||||
{
|
||||
return new FilePath(Path.Combine(fileName, Path.Combine(paths)));
|
||||
}
|
||||
|
||||
public Task DeleteAsync()
|
||||
{
|
||||
return Task.Run((System.Action)Delete);
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
// Ensure that this file/directory and all children are writable
|
||||
MakeWritable(true);
|
||||
|
||||
// Also ensure the directory containing this file/directory is writable,
|
||||
// otherwise we will not be able to delete it
|
||||
ParentDirectory.MakeWritable(false);
|
||||
|
||||
if (Directory.Exists(this))
|
||||
{
|
||||
Directory.Delete(this, true);
|
||||
}
|
||||
else if (File.Exists(this))
|
||||
{
|
||||
File.Delete(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void MakeWritable()
|
||||
{
|
||||
MakeWritable(false);
|
||||
}
|
||||
|
||||
public void MakeWritable(bool recurse)
|
||||
{
|
||||
if (Directory.Exists(this))
|
||||
{
|
||||
try
|
||||
{
|
||||
var info = new DirectoryInfo(this);
|
||||
info.Attributes &= ~FileAttributes.ReadOnly;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
if (recurse)
|
||||
{
|
||||
foreach (var sub in Directory.GetFileSystemEntries(this))
|
||||
{
|
||||
((FilePath)sub).MakeWritable(recurse);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (File.Exists(this))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Try/catch is to work around a mono bug where dangling symlinks
|
||||
// blow up when you call SetFileAttributes. Just ignore this case
|
||||
// until mono 2.10.7/8 is released which fixes it.
|
||||
var info = new FileInfo(this);
|
||||
info.Attributes &= ~FileAttributes.ReadOnly;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a path by combining all provided path sections
|
||||
/// </summary>
|
||||
public static FilePath Build(params string[] paths)
|
||||
{
|
||||
return Empty.Combine(paths);
|
||||
}
|
||||
|
||||
public static FilePath GetCommonRootPath(IEnumerable<FilePath> paths)
|
||||
{
|
||||
FilePath root = FilePath.Null;
|
||||
foreach (FilePath p in paths)
|
||||
{
|
||||
if (root.IsNull)
|
||||
root = p;
|
||||
else if (root == p)
|
||||
continue;
|
||||
else if (root.IsChildPathOf(p))
|
||||
root = p;
|
||||
else
|
||||
{
|
||||
while (!root.IsNullOrEmpty && !p.IsChildPathOf(root))
|
||||
root = root.ParentDirectory;
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
public FilePath ToAbsolute(FilePath basePath)
|
||||
{
|
||||
if (IsAbsolute)
|
||||
return FullPath;
|
||||
else
|
||||
return Combine(basePath, this).FullPath;
|
||||
}
|
||||
|
||||
public static implicit operator FilePath(string name)
|
||||
{
|
||||
return new FilePath(name);
|
||||
}
|
||||
|
||||
public static implicit operator string(FilePath filePath)
|
||||
{
|
||||
return filePath.fileName;
|
||||
}
|
||||
|
||||
public static bool operator ==(FilePath name1, FilePath name2)
|
||||
{
|
||||
return PathComparer.Equals(name1.fileName, name2.fileName);
|
||||
}
|
||||
|
||||
public static bool operator !=(FilePath name1, FilePath name2)
|
||||
{
|
||||
return !(name1 == name2);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (!(obj is FilePath))
|
||||
return false;
|
||||
|
||||
FilePath fn = (FilePath)obj;
|
||||
return this == fn;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
if (fileName == null)
|
||||
return 0;
|
||||
return PathComparer.GetHashCode(fileName);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public int CompareTo(FilePath filePath)
|
||||
{
|
||||
return PathComparer.Compare(fileName, filePath.fileName);
|
||||
}
|
||||
|
||||
int IComparable.CompareTo(object obj)
|
||||
{
|
||||
if (!(obj is FilePath))
|
||||
return -1;
|
||||
return CompareTo((FilePath)obj);
|
||||
}
|
||||
|
||||
#region IEquatable<FilePath> Members
|
||||
|
||||
bool IEquatable<FilePath>.Equals(FilePath other)
|
||||
{
|
||||
return this == other;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public static class FilePathUtil
|
||||
{
|
||||
public static string[] ToStringArray(this FilePath[] paths)
|
||||
{
|
||||
string[] array = new string[paths.Length];
|
||||
for (int n = 0; n < paths.Length; n++)
|
||||
array[n] = paths[n].ToString();
|
||||
return array;
|
||||
}
|
||||
|
||||
public static FilePath[] ToFilePathArray(this string[] paths)
|
||||
{
|
||||
var array = new FilePath[paths.Length];
|
||||
for (int n = 0; n < paths.Length; n++)
|
||||
array[n] = paths[n];
|
||||
return array;
|
||||
}
|
||||
|
||||
public static IEnumerable<string> ToPathStrings(this IEnumerable<FilePath> paths)
|
||||
{
|
||||
foreach (FilePath p in paths)
|
||||
yield return p.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,9 +30,9 @@ using System.Text;
|
|||
|
||||
namespace Microsoft.DotNet.Cli.Sln.Internal.FileManipulation
|
||||
{
|
||||
static class FileUtil
|
||||
static internal class FileUtil
|
||||
{
|
||||
public static TextFormatInfo GetTextFormatInfo(string file)
|
||||
internal static TextFormatInfo GetTextFormatInfo(string file)
|
||||
{
|
||||
var info = new TextFormatInfo();
|
||||
|
||||
|
@ -45,16 +45,22 @@ namespace Microsoft.DotNet.Cli.Sln.Internal.FileManipulation
|
|||
int nread, i;
|
||||
|
||||
if ((nread = fs.Read(buf, 0, buf.Length)) <= 0)
|
||||
{
|
||||
return info;
|
||||
}
|
||||
|
||||
if (TryParse(buf, nread, out encoding))
|
||||
{
|
||||
i = encoding.GetPreamble().Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
encoding = null;
|
||||
i = 0;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
// Read to the first newline to figure out which line endings this file is using
|
||||
while (i < nread)
|
||||
{
|
||||
if (buf[i] == '\r')
|
||||
|
@ -83,7 +89,6 @@ namespace Microsoft.DotNet.Cli.Sln.Internal.FileManipulation
|
|||
}
|
||||
} while (newLine == null);
|
||||
|
||||
// Check for a blank line at the end
|
||||
info.EndsWithEmptyLine = fs.Seek(-1, SeekOrigin.End) > 0 && fs.ReadByte() == (int)'\n';
|
||||
info.NewLine = newLine;
|
||||
info.Encoding = encoding;
|
||||
|
@ -100,7 +105,9 @@ namespace Microsoft.DotNet.Cli.Sln.Internal.FileManipulation
|
|||
bool matched = true;
|
||||
|
||||
if (available < table[i].GetPreamble().Length)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < table[i].GetPreamble().Length; j++)
|
||||
{
|
||||
|
@ -132,11 +139,13 @@ namespace Microsoft.DotNet.Cli.Sln.Internal.FileManipulation
|
|||
};
|
||||
}
|
||||
|
||||
class TextFormatInfo
|
||||
internal class TextFormatInfo
|
||||
{
|
||||
public TextFormatInfo()
|
||||
{
|
||||
NewLine = Environment.NewLine;
|
||||
Encoding = null;
|
||||
EndsWithEmptyLine = true;
|
||||
}
|
||||
|
||||
public string NewLine { get; set; }
|
||||
|
@ -144,4 +153,3 @@ namespace Microsoft.DotNet.Cli.Sln.Internal.FileManipulation
|
|||
public bool EndsWithEmptyLine { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -372,7 +372,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration
|
|||
return (solutionFile == null)
|
||||
? new List<string>()
|
||||
: new List<string>(solutionFile.Projects.Select(p =>
|
||||
Path.Combine(solutionFile.BaseDirectory.FullPath, Path.GetDirectoryName(p.FilePath))));
|
||||
Path.Combine(solutionFile.BaseDirectory, Path.GetDirectoryName(p.FilePath))));
|
||||
}
|
||||
|
||||
private static string ResolveRootDirectory(string projectPath)
|
||||
|
|
|
@ -115,7 +115,7 @@ namespace Microsoft.DotNet.Tools.Migrate
|
|||
foreach (var project in _slnFile.Projects)
|
||||
{
|
||||
var projectDirectory = Path.Combine(
|
||||
_slnFile.BaseDirectory.FullPath,
|
||||
_slnFile.BaseDirectory,
|
||||
Path.GetDirectoryName(project.FilePath));
|
||||
|
||||
var csprojFiles = new DirectoryInfo(projectDirectory)
|
||||
|
@ -129,8 +129,7 @@ namespace Microsoft.DotNet.Tools.Migrate
|
|||
}
|
||||
}
|
||||
|
||||
_slnFile.Write(Path.Combine(_slnFile.BaseDirectory.FullPath,
|
||||
Path.GetFileName(_slnFile.FileName)));
|
||||
_slnFile.Write();
|
||||
}
|
||||
|
||||
private void MoveProjectJsonArtifactsToBackup(MigrationReport migrationReport)
|
||||
|
@ -404,13 +403,14 @@ namespace Microsoft.DotNet.Tools.Migrate
|
|||
throw new Exception($"Unable to find the solution file at {slnPath}");
|
||||
}
|
||||
|
||||
_slnFile = new SlnFile();
|
||||
_slnFile.Read(slnPath);
|
||||
_slnFile = SlnFile.Read(slnPath);
|
||||
|
||||
foreach (var project in _slnFile.Projects)
|
||||
{
|
||||
var projectFilePath = Path.Combine(_slnFile.BaseDirectory.FullPath,
|
||||
Path.Combine(Path.GetDirectoryName(project.FilePath), Project.FileName));
|
||||
var projectFilePath = Path.Combine(
|
||||
_slnFile.BaseDirectory,
|
||||
Path.GetDirectoryName(project.FilePath),
|
||||
Project.FileName);
|
||||
|
||||
if (File.Exists(projectFilePath))
|
||||
{
|
||||
|
|
|
@ -21,17 +21,14 @@ namespace Microsoft.DotNet.Cli.Sln.Internal.Tests
|
|||
|
||||
var solutionFullPath = Path.Combine(solutionDirectory, "TestAppWithSln.sln");
|
||||
|
||||
var slnFile = new SlnFile();
|
||||
slnFile.Read(solutionFullPath);
|
||||
var slnFile = SlnFile.Read(solutionFullPath);
|
||||
|
||||
slnFile.FormatVersion.Should().Be("12.00");
|
||||
slnFile.ProductDescription.Should().Be("Visual Studio 14");
|
||||
slnFile.VisualStudioVersion.Should().Be("14.0.25420.1");
|
||||
slnFile.MinimumVisualStudioVersion.Should().Be("10.0.40219.1");
|
||||
slnFile.BaseDirectory.Should().Be(solutionDirectory);
|
||||
slnFile.FileName.FileName.Should().Be("TestAppWithSln.sln");
|
||||
|
||||
SlnFile.GetFileVersion(solutionFullPath).Should().Be("12.00");
|
||||
slnFile.FullPath.Should().Be(solutionFullPath);
|
||||
|
||||
slnFile.Projects.Count.Should().Be(1);
|
||||
var project = slnFile.Projects[0];
|
||||
|
@ -49,8 +46,8 @@ namespace Microsoft.DotNet.Cli.Sln.Internal.Tests
|
|||
|
||||
var solutionFullPath = Path.Combine(solutionDirectory, "TestAppWithSln.sln");
|
||||
|
||||
var slnFile = new SlnFile();
|
||||
slnFile.Read(solutionFullPath);
|
||||
var slnFile = SlnFile.Read(solutionFullPath);
|
||||
slnFile.FullPath.Should().Be(solutionFullPath);
|
||||
|
||||
slnFile.Projects.Count.Should().Be(1);
|
||||
var project = slnFile.Projects[0];
|
||||
|
@ -62,23 +59,19 @@ namespace Microsoft.DotNet.Cli.Sln.Internal.Tests
|
|||
var newSolutionFullPath = Path.Combine(solutionDirectory, "TestAppWithSln_modified.sln");
|
||||
slnFile.Write(newSolutionFullPath);
|
||||
|
||||
slnFile = new SlnFile();
|
||||
slnFile.Read(newSolutionFullPath);
|
||||
slnFile = SlnFile.Read(newSolutionFullPath);
|
||||
slnFile.FormatVersion.Should().Be("12.00");
|
||||
slnFile.ProductDescription.Should().Be("Visual Studio 14");
|
||||
slnFile.VisualStudioVersion.Should().Be("14.0.25420.1");
|
||||
slnFile.MinimumVisualStudioVersion.Should().Be("10.0.40219.1");
|
||||
slnFile.BaseDirectory.Should().Be(solutionDirectory);
|
||||
slnFile.FileName.FileName.Should().Be("TestAppWithSln_modified.sln");
|
||||
SlnFile.GetFileVersion(solutionFullPath).Should().Be("12.00");
|
||||
slnFile.FullPath.Should().Be(newSolutionFullPath);
|
||||
slnFile.Projects.Count.Should().Be(1);
|
||||
project = slnFile.Projects[0];
|
||||
project.Id.Should().Be("{0138CB8F-4AA9-4029-A21E-C07C30F425BA}");
|
||||
project.TypeGuid.Should().Be("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}");
|
||||
project.Name.Should().Be("New Project Name");
|
||||
project.FilePath.Should().Be("New File Path");
|
||||
slnFile.Projects.Count.Should().Be(1);
|
||||
project = slnFile.Projects[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue