diff --git a/System.IO.FileSystem.UnitTests/FileUnitTests.cs b/System.IO.FileSystem.UnitTests/FileUnitTests.cs index ac0c3ac..7992d5a 100644 --- a/System.IO.FileSystem.UnitTests/FileUnitTests.cs +++ b/System.IO.FileSystem.UnitTests/FileUnitTests.cs @@ -9,7 +9,7 @@ public class FileUnitTests [Setup] public void Setup() { - //Assert.SkipTest("These test will only run on real hardware. Comment out this line if you are testing on real hardware."); + Assert.SkipTest("These test will only run on real hardware. Comment out this line if you are testing on real hardware."); } private const string Root = @"I:\"; diff --git a/System.IO.FileSystem.UnitTests/PathInternalUnitTests.cs b/System.IO.FileSystem.UnitTests/PathInternalUnitTests.cs new file mode 100644 index 0000000..e7fda43 --- /dev/null +++ b/System.IO.FileSystem.UnitTests/PathInternalUnitTests.cs @@ -0,0 +1,19 @@ +using nanoFramework.TestFramework; + +namespace System.IO.FileSystem.UnitTests +{ + [TestClass] + public class PathInternalUnitTests + { + [TestMethod] + public void IsValidDriveChar_returns_true() + { + var tests = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray(); + + foreach (var test in tests) + { + Assert.IsTrue(PathInternal.IsValidDriveChar(test), $"Case: {test}"); + } + } + } +} diff --git a/System.IO.FileSystem.UnitTests/PathUnitTests.cs b/System.IO.FileSystem.UnitTests/PathUnitTests.cs new file mode 100644 index 0000000..903e32d --- /dev/null +++ b/System.IO.FileSystem.UnitTests/PathUnitTests.cs @@ -0,0 +1,404 @@ +using nanoFramework.TestFramework; + +namespace System.IO.FileSystem.UnitTests +{ + [TestClass] + internal class PathUnitTests + { + [TestMethod] + public void ChangeExtension_adds_extension() + { + const string path = @"I:\file"; + const string expect = @"I:\file.new"; + + Assert.AreEqual(expect, Path.ChangeExtension(path, "new")); + Assert.AreEqual(expect, Path.ChangeExtension(path, ".new")); + } + + [TestMethod] + public void ChangeExtension_changes_extension() + { + const string path = @"I:\file.old"; + const string expect = @"I:\file.new"; + + Assert.AreEqual(expect, Path.ChangeExtension(path, "new")); + Assert.AreEqual(expect, Path.ChangeExtension(path, ".new")); + } + + [TestMethod] + public void ChangeExtension_removes_extension() + { + const string path = @"I:\file.old"; + const string expect = @"I:\file"; + + Assert.AreEqual(expect, Path.ChangeExtension(path, null)); + } + + [TestMethod] + public void ChangeExtension_returns_empty_string_if_path_is_empty_string() + { + Assert.AreEqual(string.Empty, Path.ChangeExtension(string.Empty, ".new")); + } + + [TestMethod] + public void ChangeExtension_returns_null_if_path_is_null() + { + Assert.IsNull(Path.ChangeExtension(null, ".new")); + } + + [TestMethod] + public void Combine_returns_path1_if_path2_is_empty_string() + { + var path1 = "path1"; + var path2 = string.Empty; + + var actual = Path.Combine(path1, path2); + + Assert.AreEqual(path1, actual); + } + + [TestMethod] + public void Combine_combines_paths() + { + var expect = @"I:\Path1\Path2\File.ext"; + + Assert.AreEqual(expect, Path.Combine(@"I:\Path1", @"Path2\File.ext")); + Assert.AreEqual(expect, Path.Combine(@"I:\Path1\", @"Path2\File.ext")); + } + + [TestMethod] + public void Combine_returns_path2_if_it_is_an_absolute_path() + { + var path1 = @"I:\Directory"; + var path2 = @"I:\Absolute\Path"; + + var actual = Path.Combine(path1, path2); + + Assert.AreEqual(path2, actual); + } + + [TestMethod] + public void Combine_returns_path2_if_path1_is_empty_string() + { + var path1 = string.Empty; + var path2 = "path2"; + + var actual = Path.Combine(path1, path2); + + Assert.AreEqual(path2, actual); + } + + [TestMethod] + public void Combine_throws_if_path1_is_null() + { + Assert.ThrowsException(typeof(ArgumentNullException), () => { Path.Combine(null, "File.ext"); }); + } + + [TestMethod] + public void Combine_throws_if_path2_is_null() + { + Assert.ThrowsException(typeof(ArgumentNullException), () => { Path.Combine(@"I:\Directory", null); }); + } + + [TestMethod] + public void GetDirectoryName_returns_directory() + { + var tests = new[] { @"I:\directory", @"I:\directory\", @"I:\directory\file.ext" }; + var answers = new[] { @"I:\", @"I:\directory", @"I:\directory" }; + + for (var i = 0; i < tests.Length; i++) + { + var test = tests[i]; + var expected = answers[i]; + + Assert.AreEqual(expected, Path.GetDirectoryName(test), $"Case: {test}"); + } + } + + [TestMethod] + public void GetDirectoryName_returns_directory_UNC_paths() + { + var tests = new[] { @"\\server\share\", @"\\server\share\file.ext" }; + var answers = new[] { @"\\server\share", @"\\server\share" }; + + for (var i = 0; i < tests.Length; i++) + { + var test = tests[i]; + var expected = answers[i]; + + Assert.AreEqual(expected, Path.GetDirectoryName(test), $"Case: {test}"); + } + } + + [TestMethod] + public void GetDirectoryName_returns_null() + { + Assert.IsNull(Path.GetDirectoryName(null), $"Case: 'null'"); + + // TODO: Would like to add '(string) null' to these cases but for some reason this crashes. Investigate further and open defect + var tests = new[] { string.Empty, " ", @"\", "C:", @"C:\" }; + foreach (var test in tests) + { + var actual = Path.GetDirectoryName(test); + var message = $"Actual: '{actual}'. Case: '{test}'"; + + Assert.IsNull(Path.GetDirectoryName(test), message); + } + } + + [TestMethod] + public void GetDirectoryName_returns_null_UNC_paths() + { + Assert.SkipTest("UNC paths are not supported in the default build"); + + var tests = new[] { @"\\server\share" }; + foreach (var test in tests) + { + var actual = Path.GetDirectoryName(test); + var message = $"Actual: '{actual}'. Case: '{test}'"; + + Assert.IsNull(Path.GetDirectoryName(test), message); + } + } + [TestMethod] + public void GetExtension_returns_empty_string() + { + Assert.AreEqual(string.Empty, Path.GetExtension(string.Empty)); + Assert.AreEqual(string.Empty, Path.GetExtension("file")); + Assert.AreEqual(string.Empty, Path.GetExtension("file.")); + } + + [TestMethod] + public void GetExtension_returns_extension() + { + var file = "file.ext"; + var expect = ".ext"; + + Assert.AreEqual(expect, Path.GetExtension(file)); + Assert.AreEqual(expect, Path.GetExtension($"I:{file}")); + Assert.AreEqual(expect, Path.GetExtension(@$"I:\{file}")); + Assert.AreEqual(expect, Path.GetExtension(@$"I:\directory\{file}")); + Assert.AreEqual(expect, Path.GetExtension(@$"\{file}")); + } + + [TestMethod] + public void GetExtension_returns_extension_UNC_paths() + { + var file = "file.ext"; + var expect = ".ext"; + + Assert.AreEqual(expect, Path.GetExtension(@$"\\server\share\{file}")); + Assert.AreEqual(expect, Path.GetExtension(@$"\\server\share\directory\{file}")); + } + + [TestMethod] + public void GetExtension_returns_null() + { + Assert.IsNull(Path.GetExtension(null)); + } + + [TestMethod] + public void GetFilename_returns_empty_string() + { + Assert.AreEqual(string.Empty, Path.GetFileName("I:")); + Assert.AreEqual(string.Empty, Path.GetFileName(@"I:\")); + } + + [TestMethod] + public void GetFilename_returns_filename_without_extension() + { + Assert.AreEqual("file", Path.GetFileName(@"I:\directory\file")); + Assert.AreEqual("file.ext", Path.GetFileName(@"I:\directory\file.ext")); + Assert.AreEqual("file", Path.GetFileName(@"I:\file")); + Assert.AreEqual("file.ext", Path.GetFileName(@"I:\file.ext")); + } + + [TestMethod] + public void GetFilename_returns_filename_without_extension_UNC_paths() + { + Assert.AreEqual("file", Path.GetFileName(@"\\server\share\directory\file")); + Assert.AreEqual("file.ext", Path.GetFileName(@"\\server\share\directory\file.ext")); + } + + [TestMethod] + public void GetFilename_returns_null() + { + Assert.IsNull(Path.GetFileName(null)); + } + + [TestMethod] + public void GetFilenameWithoutExtension_returns_empty_string() + { + Assert.AreEqual(string.Empty, Path.GetFileNameWithoutExtension("I:")); + Assert.AreEqual(string.Empty, Path.GetFileNameWithoutExtension(@"I:\")); + } + + [TestMethod] + public void GetFilenameWithoutExtension_returns_filename_without_extension() + { + Assert.AreEqual("file", Path.GetFileNameWithoutExtension(@"I:\directory\file")); + Assert.AreEqual("file", Path.GetFileNameWithoutExtension(@"I:\directory\file.ext")); + Assert.AreEqual("file", Path.GetFileNameWithoutExtension(@"I:\file")); + Assert.AreEqual("file", Path.GetFileNameWithoutExtension(@"I:\file.ext")); + } + + [TestMethod] + public void GetFilenameWithoutExtension_returns_filename_without_extension_UNC_paths() + { + Assert.AreEqual("file", Path.GetFileNameWithoutExtension(@"\\server\share\directory\file")); + Assert.AreEqual("file", Path.GetFileNameWithoutExtension(@"\\server\share\directory\file.ext")); + } + + [TestMethod] + public void GetFilenameWithoutExtension_returns_null() + { + Assert.IsNull(Path.GetFileNameWithoutExtension(null)); + } + + [TestMethod] + public void GetPathRoot_returns_empty_string() + { + Assert.AreEqual(string.Empty, Path.GetPathRoot(@"directory\file")); + Assert.AreEqual(string.Empty, Path.GetPathRoot(@"directory\file.ext")); + Assert.AreEqual(string.Empty, Path.GetPathRoot("file")); + Assert.AreEqual(string.Empty, Path.GetPathRoot("file.ext")); + } + + [TestMethod] + public void GetPathRoot_returns_null() + { + Assert.IsNull(Path.GetPathRoot(null)); + Assert.IsNull(Path.GetPathRoot(" ")); + } + + [TestMethod] + public void GetPathRoot_returns_root() + { + var tests = new[] + { + "I:", @"I:\directory\file", @"I:\directory\file.ext", @"I:\file", @"I:\file.ext" + }; + + var answers = new[] { "I:", @"I:\", @"I:\", @"I:\", @"I:\" }; + + for (var i = 0; i < tests.Length; i++) + { + var test = tests[i]; + var expected = answers[i]; + + Assert.AreEqual(expected, Path.GetPathRoot(test), $"Case: {test}"); + } + } + + [TestMethod] + public void GetPathRoot_returns_root_UNC_paths() + { + Assert.SkipTest("UNC paths are not supported in the default build"); + + var tests = new[] + { + @"\\server\share\directory\file", @"\\server\share\directory\file.ext" + }; + + var answers = new[] { @"\\server\share", @"\\server\share" }; + + for (var i = 0; i < tests.Length; i++) + { + var test = tests[i]; + var expected = answers[i]; + + Assert.AreEqual(expected, Path.GetPathRoot(test), $"Case: {test}"); + } + } + + [TestMethod] + public void HasExtension_returns_false() + { + var tests = new[] + { + "file", @"\file.", @"\", "/", "I:", @"I:\", @"I:\directory\" + }; + + for (var i = 0; i < tests.Length; i++) + { + var test = tests[i]; + Assert.IsFalse(Path.HasExtension(test), $"Case: {test}"); + } + } + + [TestMethod] + public void HasExtension_returns_false_UNC_paths() + { + var tests = new[] + { + @"\\server\share\file.", @"\\server\share\directory\file" + }; + + for (var i = 0; i < tests.Length; i++) + { + var test = tests[i]; + Assert.IsFalse(Path.HasExtension(test), $"Case: {test}"); + } + } + + [TestMethod] + public void HasExtension_returns_true() + { + var tests = new[] + { + "file.ext", @"\file.ext", "/file.ext", "I:file.ext", @"I:\file.ext", @"I:\directory\file.ext" + }; + + for (var i = 0; i < tests.Length; i++) + { + var test = tests[i]; + Assert.IsTrue(Path.HasExtension(test), $"Case: {test}"); + } + } + + [TestMethod] + public void HasExtension_returns_true_UNC_paths() + { + var tests = new[] + { + @"\\server\share\file.ext", @"\\server\share\directory\file.ext" + }; + + for (var i = 0; i < tests.Length; i++) + { + var test = tests[i]; + Assert.IsTrue(Path.HasExtension(test), $"Case: {test}"); + } + } + + [TestMethod] + public void IsPathRooted_returns_true() + { + var tests = new[] + { + @"\", "/", "I:", @"I:\", @"I:\file.ext", @"I:\directory\file.ext" + }; + + for (var i = 0; i < tests.Length; i++) + { + var test = tests[i]; + Assert.IsTrue(Path.IsPathRooted(test), $"Case: {test}"); + } + } + + [TestMethod] + public void IsPathRooted_returns_true_UNC_paths() + { + var tests = new[] + { + @"\\server\share", @"\\server\share\file.ext", @"\\server\share\directory\file.ext" + }; + + for (var i = 0; i < tests.Length; i++) + { + var test = tests[i]; + Assert.IsTrue(Path.IsPathRooted(test), $"Case: {test}"); + } + } + } +} diff --git a/System.IO.FileSystem.UnitTests/System.IO.FileSystem.UnitTests.nfproj b/System.IO.FileSystem.UnitTests/System.IO.FileSystem.UnitTests.nfproj index 22e5a91..59d0ce1 100644 --- a/System.IO.FileSystem.UnitTests/System.IO.FileSystem.UnitTests.nfproj +++ b/System.IO.FileSystem.UnitTests/System.IO.FileSystem.UnitTests.nfproj @@ -23,12 +23,23 @@ v1.0 true + + true + + + key.snk + + + false + $(MSBuildProjectDirectory)\nano.runsettings + + diff --git a/System.IO.FileSystem.UnitTests/key.snk b/System.IO.FileSystem.UnitTests/key.snk new file mode 100644 index 0000000..67c9bb0 Binary files /dev/null and b/System.IO.FileSystem.UnitTests/key.snk differ diff --git a/System.IO.FileSystem.UnitTests/nano.runsettings b/System.IO.FileSystem.UnitTests/nano.runsettings index 93ce85e..438c21c 100644 --- a/System.IO.FileSystem.UnitTests/nano.runsettings +++ b/System.IO.FileSystem.UnitTests/nano.runsettings @@ -9,7 +9,7 @@ None - False + True COM3 diff --git a/System.IO.FileSystem/File.cs b/System.IO.FileSystem/File.cs index e9a8a28..b2ba13a 100644 --- a/System.IO.FileSystem/File.cs +++ b/System.IO.FileSystem/File.cs @@ -85,7 +85,6 @@ public static FileStream Create(string path) /// /// The name of the file to be deleted. Wildcard characters are not supported. /// is or empty. - /// Directory is not found or is read-only or a directory. public static void Delete(string path) { @@ -94,8 +93,6 @@ public static void Delete(string path) throw new ArgumentException(); } - Path.CheckInvalidPathChars(path); - try { byte attributes; diff --git a/System.IO.FileSystem/Path.cs b/System.IO.FileSystem/Path.cs index ceefc82..d04c4ba 100644 --- a/System.IO.FileSystem/Path.cs +++ b/System.IO.FileSystem/Path.cs @@ -3,261 +3,218 @@ // See LICENSE file in the project root for full license information. // -using System.Collections; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace System.IO { /// - /// Performs operations on String instances that contain file or directory path information. + /// Provides methods for processing file system strings in a cross-platform manner. + /// Most of the methods don't do a complete parsing (such as examining a UNC hostname), + /// but they will handle most string operations. /// - public sealed class Path + public static class Path { - #region Constants - - // From FS_decl.h - private const int FSMaxPathLength = 260 - 2; - private const int FSMaxFilenameLength = 256; - private const int FSNameMaxLength = 7 + 1; - - // Windows API definitions - internal const int MAX_PATH = 260; // From WinDef.h - - #endregion - - - #region Variables + // Public static readonly variant of the separators. The Path implementation itself is using + // internal const variant of the separators for better performance. /// /// Provides a platform-specific character used to separate directory levels in a path string that reflects a hierarchical file system organization. /// - public static readonly char DirectorySeparatorChar = '\\'; + public static readonly char DirectorySeparatorChar = PathInternal.DirectorySeparatorChar; /// - /// Provides a platform-specific array of characters that cannot be specified in path string arguments passed to members of the Path class. + /// Provides a platform-specific alternate character used to separate directory levels in a path string that reflects a hierarchical file system organization. /// - public static readonly char[] InvalidPathChars = { '/', '\"', '<', '>', '|', '\0', (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, (char)31 }; - - internal static readonly char[] m_illegalCharacters = { '?', '*' }; - - #endregion - - - #region Constructor + public static readonly char AltDirectorySeparatorChar = PathInternal.AltDirectorySeparatorChar; - private Path() - { - } - - #endregion + /// + /// Provides a platform-specific volume separator character. + /// + public static readonly char VolumeSeparatorChar = PathInternal.VolumeSeparatorChar; + /// + /// A platform-specific separator character used to separate path strings in environment variables. + /// + public static readonly char PathSeparator = PathInternal.PathSeparator; - #region Methods + // TODO: This is not needed when has an overload for + private const string ExtensionSeparatorString = "."; /// - /// Changes the extension of a file path. The path parameter - /// specifies a file path, and the extension parameter - /// specifies a file extension (with a leading period, such as - /// ".exe" or".cool"). - /// - /// The function returns a file path with the same root, directory, and base - /// name parts as path, but with the file extension changed to - /// the specified extension.Ifpath is null, the function - /// returns null. If path does not contain a file extension, - /// the new file extension is appended to the path.Ifextension - /// is null, any existing extension is removed from path. + /// Changes the extension of a path string. /// - /// The path for which to change file extension. - /// The new file extension (with a leading period), or null to remove the extension. - /// - public static string ChangeExtension( - string path, - string extension) + /// The path information to modify. + /// + /// The new extension (with or without a leading period). Specify to remove an existing extension from . + /// + /// + /// The modified path information. + /// + /// If is or an empty string (""), the path information is returned unmodified. + /// If is , the returned string contains the specified path with its extension removed. + /// If has no extension, and is not , the returned path string + /// contains appended to the end of . + /// + public static string ChangeExtension(string path, string extension) { - if (path != null) + if (path is null) { - CheckInvalidPathChars(path); - - string s = path; + return null; + } - for (int i = path.Length; --i >= 0;) - { - char ch = path[i]; + var subLength = path.Length; + if (subLength == 0) + { + return string.Empty; + } - if (ch == '.') - { - s = path.Substring(0, i); - break; - } + for (var i = path.Length - 1; i >= 0; i--) + { + var ch = path[i]; - if (ch == DirectorySeparatorChar) break; + if (ch == '.') + { + subLength = i; + break; } - if (extension != null && path.Length != 0) + if (PathInternal.IsDirectorySeparator(ch)) { - if (extension.Length == 0 || extension[0] != '.') - { - s += "."; - } - - s += extension; + break; } + } - return s; + if (extension is null) + { + return path.Substring(0, subLength); } - return null; + var subPath = path.Substring(0, subLength); + + return extension.StartsWith(ExtensionSeparatorString) ? + string.Concat(subPath, extension) : + string.Concat(subPath, ExtensionSeparatorString, extension); } /// - /// Returns the directory path of a file path. This method effectively - /// removes the last element of the given file path, i.e.it returns a - /// string consisting of all characters up to but not including the last - /// backslash("\") in the file path. The returned value is null if the file - /// path is null or if the file path denotes a root (such as "\", "C:", or - /// "\\server\share"). + /// Combines two strings into a path. /// - /// The path of a file or directory. - /// The directory path of the given path, or null if the given path denotes a root. - public static string GetDirectoryName(string path) + /// The first path to combine. + /// The second path to combine. + /// + /// The combined paths. If one of the specified paths is a zero-length string, this method returns the other path. + /// If contains an absolute path, this method returns . + /// + /// or is . + public static string Combine(string path1, string path2) { - if (path != null) + if (path1 is null || path2 is null) { - NormalizePath(path, false); - - int root = GetRootLength(path); - - int i = path.Length; - - if (i > root) - { - i = path.Length; - - if (i == root) - { - return null; - } - - var lastPathPostion = path.LastIndexOf(DirectorySeparatorChar); - - if (lastPathPostion == -1) - { - return string.Empty; - } - - return path.Substring(0, lastPathPostion); - } + throw new ArgumentNullException(); } - return null; + return CombineInternal(path1, path2); } - /// - /// Gets the length of the root DirectoryInfo or whatever DirectoryInfo markers - /// are specified for the first part of the DirectoryInfo name. - /// - /// - /// - internal static int GetRootLength(string path) + private static string CombineInternal(string first, string second) { - CheckInvalidPathChars(path); - - int i = 0; - int length = path.Length; - - if (length >= 1 - && IsDirectorySeparator(path[0])) + if (string.IsNullOrEmpty(first)) { - // Handles UNC names and directories off current drive's root. - i = 1; - - if (length >= 2 - && IsDirectorySeparator(path[1])) - { - i = 2; - int n = 2; + return second; + } - while (i < length && (path[i] != DirectorySeparatorChar || --n > 0)) - { - i++; - } - } + if (string.IsNullOrEmpty(second)) + { + return first; } - return i; - } + if (IsPathRooted(second)) + { + return second; + } - internal static bool IsDirectorySeparator(char c) - { - return c == DirectorySeparatorChar; + return JoinInternal(first, second); } /// - /// Gets an array containing the characters that are not allowed in path names. + /// Returns the directory portion of a file path. This method effectively + /// removes the last segment of the given file path, i.e. it returns a + /// string consisting of all characters up to but not including the last + /// backslash ("\") in the file path. The returned value is null if the + /// specified path is null, empty, or a root (such as "\", "C:", or + /// "\\server\share"). /// - /// An array containing the characters that are not allowed in path names. - public static char[] GetInvalidPathChars() + /// + /// Directory separators are normalized in the returned string. + /// + public static string GetDirectoryName(string path) { - return (char[])InvalidPathChars.Clone(); + if (path is null || PathInternal.IsEffectivelyEmpty(path)) + { + return null; + } + + var end = GetDirectoryNameOffset(path); + return end >= 0 ? PathInternal.NormalizeDirectorySeparators(path.Substring(0, end)) : null; } - /// - /// Returns the absolute path for the specified path string. - /// - /// The file or directory for which to obtain absolute path information. - /// - public static string GetFullPath(string path) + internal static int GetDirectoryNameOffset(string path) { - /* - ValidateNullOrEmpty(path); + var rootLength = PathInternal.GetRootLength(path); + var end = path.Length; + + if (end <= rootLength) + { + return -1; + } + + while (end > rootLength && !PathInternal.IsDirectorySeparator(path[--end])) + { + } - if (!Path.IsPathRooted(path)) + // Trim off any remaining separators (to deal with C:\foo\\bar) + while (end > rootLength && PathInternal.IsDirectorySeparator(path[end - 1])) { - string currDir = Directory.GetCurrentDirectory(); - path = Path.Combine(currDir, path); + end--; } - return NormalizePath(path, false); - */ - throw new NotImplementedException(); + return end; } /// - /// Returns the extension of the given path. The returned value includes the - /// period(".") character of the extension except when you have a terminal period when you get String.Empty, such as ".exe" or - /// ".cpp". The returned value is null if the given path is - /// null or if the given path does not include an extension. + /// Returns the extension (including the period ".") of the specified path string. /// - /// The path of a file or directory. - /// The extension of the given path, or null if the given path does not include an extension. - /// if path contains invalid characters. + /// The path string from which to get the extension. + /// + /// The extension of the specified path (including the period "."), or , or . + /// If is , returns . + /// If path does not have extension information, returns . + [return: NotNullIfNotNull("path")] public static string GetExtension(string path) { - if (path == null) + if (path is null) { return null; } - CheckInvalidPathChars(path); + var length = path.Length; - int length = path.Length; - - for (int i = length; --i >= 0;) + for (var i = length - 1; i >= 0; i--) { - char ch = path[i]; - + var ch = path[i]; + if (ch == '.') { if (i != length - 1) { return path.Substring(i, length - i); } - else - { - return string.Empty; - } + + return string.Empty; } - if (ch == DirectorySeparatorChar) + if (PathInternal.IsDirectorySeparator(ch)) { break; } @@ -267,32 +224,32 @@ public static string GetExtension(string path) } /// - /// Returns the name and extension parts of the given path. The resulting - /// string contains the characters of path that follow the last - /// backslash ("\"), slash ("/"), or colon (":") character in - /// path.The resulting string is the entire path if path - /// contains no backslash after removing trailing slashes, slash, or colon characters.The resulting - /// string is null if path is null. + /// Returns the file name and extension of the specified path string. /// - /// The path of a file or directory. - /// The name and extension parts of the given path. - /// if path contains invalid characters. + /// The path string from which to obtain the file name and extension. + /// + /// The characters after the last directory separator character in . + /// If the last character of is a directory or volume separator character, this method returns . + /// If is , this method returns . + /// + [return: NotNullIfNotNull("path")] public static string GetFileName(string path) { - if (path != null) + if (path is null) { - CheckInvalidPathChars(path); + return null; + } - int length = path.Length; + var root = GetPathRoot(path).Length; - for (int i = length; --i >= 0;) - { - char ch = path[i]; + // We don't want to cut off "C:\file.txt:stream" (i.e. should be "file.txt:stream") + // but we *do* want "C:Foo" => "Foo". This necessitates checking for the root. - if (ch == DirectorySeparatorChar) - { - return path.Substring(i + 1, length - i - 1); - } + for (var i = path.Length; --i >= 0;) + { + if (i < root || PathInternal.IsDirectorySeparator(path[i])) + { + return path.Substring(i + 1); } } @@ -303,372 +260,137 @@ public static string GetFileName(string path) /// Returns the file name of the specified path string without the extension. /// /// The path of the file. - /// + /// The string returned by , minus the last period (.) and all characters following it. + [return: NotNullIfNotNull("path")] public static string GetFileNameWithoutExtension(string path) { - path = GetFileName(path); - - if (path != null) + if (path is null) { - int i; - - if ((i = path.LastIndexOf('.')) == -1) - { - // No path extension found - return path; - } - else - { - return path.Substring(0, i); - } + return null; } - return null; - } + var fileName = GetFileName(path); + var lastPeriod = fileName.LastIndexOf('.'); - /// - /// Tests if a path includes a file extension. The result is - /// true if the characters that follow the last directory - /// separator('\\' or '/') or volume separator(':') in the path include - /// a period(".") other than a terminal period.The result is false otherwise. - /// - /// The path of a file or directory. - /// The root portion of the given path. - /// if path contains invalid characters. - public static string GetPathRoot(string path) - { - return path == null ? null : path.Substring(0, path.IndexOf(DirectorySeparatorChar)); + return lastPeriod < 0 ? + fileName : // No extension was found + fileName.Substring(0, lastPeriod); } /// - /// Tests if a path includes a file extension. The result is - /// true if the characters that follow the last directory - /// separator('\\' or '/') or volume separator(':') in the path include - /// a period(".") other than a terminal period.The result is false otherwise. + /// Gets an array containing the characters that are not allowed in file names. /// - /// The path to test. - /// Boolean indicating whether the path includes a file extension. - /// if path contains invalid characters. - public static bool HasExtension(string path) + /// An array containing the characters that are not allowed in file names. + public static char[] GetInvalidFileNameChars() => new char[] { - if (path != null) - { - CheckInvalidPathChars(path); - - for (int i = path.Length; --i >= 0;) - { - char ch = path[i]; - - if (ch == '.') - { - return i != path.Length - 1; - } - - if (ch == DirectorySeparatorChar) - { - break; - } - } - } - - return false; - } + '\"', '<', '>', '|', '\0', + (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, + (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, + (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, + (char)31, ':', '*', '?', '\\', '/' + }; /// - /// Tests if the given path contains a root. A path is considered rooted - /// if it starts with a backslash("\") or a drive letter and a colon (":"). + /// Gets an array containing the characters that are not allowed in path names. /// - /// The path to test. - /// Boolean indicating whether the path is rooted. - /// if path contains invalid characters. - public static bool IsPathRooted(string path) + /// An array containing the characters that are not allowed in path names. + public static char[] GetInvalidPathChars() => new[] { - if (path != null) - { - CheckInvalidPathChars(path); - - int length = path.Length; - - if (length >= 1 && (path[0] == DirectorySeparatorChar)) - { - return true; - } - } - - return false; - } + '|', '\0', + (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, + (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, + (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, + (char)31 + }; /// - /// Combines two strings into a path. + /// Gets the root directory information from the path contained in the specified string. /// - /// The first path to combine. - /// The second path to combine. - /// - public static string Combine(string path1, string path2) - { - if (path1 == null || path2 == null) - { -#pragma warning disable S3928 // Parameter names used into ArgumentException constructors should match an existing one - throw new ArgumentNullException(/*(path1==null) ? "path1" : "path2"*/); -#pragma warning restore S3928 // Parameter names used into ArgumentException constructors should match an existing one - } - - CheckInvalidPathChars(path1); - CheckInvalidPathChars(path2); - - if (path2.Length == 0) - { - return path1; - } - - if (path1.Length == 0) - { - return path2; - } - - if (IsPathRooted(path2)) - { - return path2; - } - - char ch = path1[path1.Length - 1]; - - return ch != DirectorySeparatorChar ? path1 + DirectorySeparatorChar + path2 : path1 + path2; - } - - //--// - - internal static void CheckInvalidPathChars(string path) + /// A string containing the path from which to obtain root directory information. + /// + /// The root directory of if it is rooted. + /// + /// -or- + /// + /// if does not contain root directory information. + /// + /// -or- + /// + /// if is or is effectively empty. + /// + public static string GetPathRoot(string path) { - if (-1 != path.IndexOfAny(InvalidPathChars)) + if (PathInternal.IsEffectivelyEmpty(path)) { -#pragma warning disable S3928 // Parameter names used into ArgumentException constructors should match an existing one - throw new ArgumentException(/*Environment.GetResourceString("Argument_InvalidPathChars")*/); -#pragma warning restore S3928 // Parameter names used into ArgumentException constructors should match an existing one + return null; } - } + var pathRootLength = PathInternal.GetRootLength(path); + var pathRoot = pathRootLength <= 0 ? string.Empty : path.Substring(0, pathRootLength); - internal static void ValidateNullOrEmpty(string str) - { - if (str == null) - { - throw new ArgumentNullException("Path is null."); - } - - if (str.Length == 0) - { - throw new ArgumentException("Path length is 0."); - } + return PathInternal.NormalizeDirectorySeparators(pathRoot); } - internal static string NormalizePath(string path, bool pattern) + /// + /// Determines whether a path includes a file name extension. + /// + /// The path to search for an extension. + /// + /// if the characters that follow the last directory separator (\ or /) or volume separator (:) + /// in the path include a period (.) followed by one or more characters; otherwise, . + /// + public static bool HasExtension([NotNullWhen(true)] string path) { - ValidateNullOrEmpty(path); - - int pathLength = path.Length; - - int i; - - for (i = 0; i < pathLength; i++) - { - if (path[i] != '\\') - { - break; - } - } - - bool rootedPath = false; - bool serverPath = false; - - // Handle some of the special cases. - // 1. Root (\) - // 2. Server (\\server). - // 3. InvalidPath (\\\, \\\\, etc). - if (i == 1) - { - rootedPath = true; - } - else if ((i == 2) && (pathLength > 2)) - { - serverPath = true; - } - else if (i > 2) - { - throw new ArgumentException("Path contains 3 and more successive backslashes."); - } - - if (rootedPath) - { - int limit = i + FSNameMaxLength; - - for (; i < limit && i < pathLength; i++) - { - if (path[i] == '\\') - { - break; - } - } - - if (i == limit) - { - // if the namespace is too long - throw new IOException("", (int)IOException.IOExceptionErrorCode.VolumeNotFound); - } - else if (pathLength - i >= FSMaxPathLength) - { - // if the "relative" path exceeds the limit - throw new IOException("", (int)IOException.IOExceptionErrorCode.PathTooLong); - } - } - else // For non-rooted paths (i.e. server paths or relative paths), we follow the MAX_PATH (260) limit from desktop + if (path is null) { - if (pathLength >= MAX_PATH) - { - throw new IOException("", (int)IOException.IOExceptionErrorCode.PathTooLong); - } + return false; } - string[] pathParts = path.Split(DirectorySeparatorChar); - - if (pattern && (pathParts.Length > 1)) - { - throw new ArgumentException("Path contains only a Directory/FileName"); - } - - ArrayList finalPathSegments = new ArrayList(); - int pathPartLen; - - for (int e = 0; e < pathParts.Length; e++) + for (var i = path.Length - 1; i >= 0; i--) { - pathPartLen = pathParts[e].Length; - - if (pathPartLen == 0) - { - // Do nothing. Apparently paths like c:\\folder\\\file.txt works fine in Windows. - continue; - } - else if (pathPartLen >= FSMaxFilenameLength) - { - throw new IOException("", (int)IOException.IOExceptionErrorCode.PathTooLong); - } - - if (pathParts[e].IndexOfAny(InvalidPathChars) != -1) - { - throw new ArgumentException("Path contains invalid characters: " + pathParts[e]); - } - - if (!pattern - && pathParts[e].IndexOfAny(m_illegalCharacters) != -1) + var ch = path[i]; + + if (ch == '.') { - throw new ArgumentException("Path contains illegal characters: " + pathParts[e]); + return i != path.Length - 1; } - // verify whether pathParts[e] is all '.'s. If it is - // we have some special cases. Also path with both dots - // and spaces only are invalid. - int length = pathParts[e].Length; - bool spaceFound = false; - - for (i = 0; i < length; i++) + if (PathInternal.IsDirectorySeparator(ch)) { - if (pathParts[e][i] == '.') - { - continue; - } - - if (pathParts[e][i] == ' ') - { - spaceFound = true; - continue; - } - break; } - - if (i >= length) - { - if (!spaceFound) - { - // Dots only. - if (i == 1) - { - // Stay in same directory. - } - else if (i == 2) - { - if (finalPathSegments.Count == 0) - { -#pragma warning disable S3928 // Parameter names used into ArgumentException constructors should match an existing one - throw new ArgumentException(); -#pragma warning restore S3928 // Parameter names used into ArgumentException constructors should match an existing one - } - - finalPathSegments.RemoveAt(finalPathSegments.Count - 1); - } - else - { -#pragma warning disable S3928 // Parameter names used into ArgumentException constructors should match an existing one - throw new ArgumentException(); -#pragma warning restore S3928 // Parameter names used into ArgumentException constructors should match an existing one - } - } - else - { - // Just dots and spaces doesn't make the cut. - throw new ArgumentException("Path only contains dots and spaces."); - } - } - else - { - int trim = length - 1; - - while (pathParts[e][trim] == ' ' || pathParts[e][trim] == '.') - { - trim--; - } - - finalPathSegments.Add(pathParts[e].Substring(0, trim + 1)); - } } - string normalizedPath = ""; + return false; + } - if (rootedPath) - { - normalizedPath += @"\"; - } - else if (serverPath) + /// + /// Returns a value indicating whether the specified path string contains a root. + /// + /// The path to test. + /// if contains a root; otherwise, . + public static bool IsPathRooted([NotNullWhen(true)] string path) + { + if (path is null) { - normalizedPath += @"\\"; - - // btw, server path must specify server name. - if (finalPathSegments.Count == 0) - { - throw new ArgumentException("Server Path is missing server name."); - } + return false; } - bool firstSegment = true; + var length = path.Length; + return (length >= 1 && PathInternal.IsDirectorySeparator(path[0])) + || (length >= 2 && PathInternal.IsValidDriveChar(path[0]) && path[1] == PathInternal.VolumeSeparatorChar); + } - for (int e = 0; e < finalPathSegments.Count; e++) - { - if (!firstSegment) - { - normalizedPath += "\\"; - } - else - { - firstSegment = false; - } + private static string JoinInternal(string first, string second) + { + Debug.Assert(first.Length > 0 && second.Length > 0, "should have dealt with empty paths"); - normalizedPath += (string)finalPathSegments[e]; - } + var hasSeparator = PathInternal.IsDirectorySeparator(first[first.Length - 1]) + || PathInternal.IsDirectorySeparator(second[0]); - return normalizedPath; + return hasSeparator ? + string.Concat(first, second) : + string.Concat(first, PathInternal.DirectorySeparatorCharAsString, second); } - - #endregion - } } diff --git a/System.IO.FileSystem/PathInternal.cs b/System.IO.FileSystem/PathInternal.cs new file mode 100644 index 0000000..92df7ed --- /dev/null +++ b/System.IO.FileSystem/PathInternal.cs @@ -0,0 +1,299 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text; + +namespace System.IO +{ + /// Contains internal path helpers that are shared between many projects. + internal static class PathInternal + { + internal const char DirectorySeparatorChar = '\\'; + internal const char AltDirectorySeparatorChar = '/'; + internal const char VolumeSeparatorChar = ':'; + internal const char PathSeparator = ';'; + + internal const string DirectorySeparatorCharAsString = "\\"; + + internal const int MaxShortPath = 260; + internal const int MaxShortDirectoryPath = 248; + +#if PATH_SUPPORTS_UNC + // \\?\, \\.\, \??\ + internal const int DevicePrefixLength = 4; + // \\ + internal const int UncPrefixLength = 2; + // \\?\UNC\, \\.\UNC\ + internal const int UncExtendedPrefixLength = 8; +#endif + + /// + /// Gets the length of the root of the path (drive, share, etc.). + /// + internal static int GetRootLength(string path) + { + var pathLength = path.Length; + var i = 0; + +#if PATH_SUPPORTS_UNC + var deviceSyntax = IsDevice(path); + var deviceUnc = deviceSyntax && IsDeviceUNC(path); + + if ((!deviceSyntax || deviceUnc) && pathLength > 0 && IsDirectorySeparator(path[0])) + { + // UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo") + if (deviceUnc || (pathLength > 1 && IsDirectorySeparator(path[1]))) + { + // UNC (\\?\UNC\ or \\), scan past server\share + + // Start past the prefix ("\\" or "\\?\UNC\") + i = deviceUnc ? UncExtendedPrefixLength : UncPrefixLength; + + // Skip two separators at most + var n = 2; + while (i < pathLength && (!IsDirectorySeparator(path[i]) || --n > 0)) + { + i++; + } + } + else + { + // Current drive rooted (e.g. "\foo") + i = 1; + } + } + else if (deviceSyntax) + { + // Device path (e.g. "\\?\.", "\\.\") + // Skip any characters following the prefix that aren't a separator + i = DevicePrefixLength; + while (i < pathLength && !IsDirectorySeparator(path[i])) + { + i++; + } + + // If there is another separator take it, as long as we have had at least one + // non-separator after the prefix (e.g. don't take "\\?\\", but take "\\?\a\") + if (i < pathLength && i > DevicePrefixLength && IsDirectorySeparator(path[i])) + { + i++; + } + } + else if (pathLength >= 2 && path[1] == VolumeSeparatorChar && IsValidDriveChar(path[0])) + { + // Valid drive specified path ("C:", "D:", etc.) + i = 2; + + // If the colon is followed by a directory separator, move past it (e.g "C:\") + if (pathLength > 2 && IsDirectorySeparator(path[2])) + { + i++; + } + } +#else + if (pathLength >= 2 && path[1] == VolumeSeparatorChar && IsValidDriveChar(path[0])) + { + // Valid drive specified path ("C:", "D:", etc.) + i = 2; + + // If the colon is followed by a directory separator, move past it (e.g "C:\") + if (pathLength > 2 && IsDirectorySeparator(path[2])) + { + i++; + } + } + else if (pathLength == 1 && IsDirectorySeparator(path[0])) + { + // Current drive rooted (e.g. "\foo") + i = 1; + } +#endif + + return i; + } + +#if PATH_SUPPORTS_UNC + /// + /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\") + /// + internal static bool IsDevice(string path) + { + // If the path begins with any two separators is will be recognized and normalized and prepped with + // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not. + return IsExtended(path) + || + ( + path.Length >= DevicePrefixLength + && IsDirectorySeparator(path[0]) + && IsDirectorySeparator(path[1]) + && (path[2] == '.' || path[2] == '?') + && IsDirectorySeparator(path[3]) + ); + } + + /// + /// Returns true if the path is a device UNC (\\?\UNC\, \\.\UNC\) + /// + internal static bool IsDeviceUNC(string path) + { + return path.Length >= UncExtendedPrefixLength + && IsDevice(path) + && IsDirectorySeparator(path[7]) + && path[4] == 'U' + && path[5] == 'N' + && path[6] == 'C'; + } +#endif + + /// + /// True if the given character is a directory separator. + /// + // [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsDirectorySeparator(char c) + { + return c is DirectorySeparatorChar or AltDirectorySeparatorChar; + } + + /// + /// Returns true if the path is effectively empty for the current OS. + /// For unix, this is empty or null. For Windows, this is empty, null, or + /// just spaces ((char)32). + /// + internal static bool IsEffectivelyEmpty(string path) + { + if (string.IsNullOrEmpty(path)) + { + return true; + } + + foreach (var c in path.ToCharArray()) + { + if (c != ' ') + { + return false; + } + } + + return true; + } + +#if PATH_SUPPORTS_UNC + /// + /// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the + /// path matches exactly (cannot use alternate directory separators) Windows will skip normalization + /// and path length checks. + /// + internal static bool IsExtended(string path) + { + // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths. + // Skipping of normalization will *only* occur if back slashes ('\') are used. + return path.Length >= DevicePrefixLength + && path[0] == '\\' + && (path[1] == '\\' || path[1] == '?') + && path[2] == '?' + && path[3] == '\\'; + } +#endif + + /// + /// Returns true if the given character is a valid drive letter + /// + internal static bool IsValidDriveChar(char value) + { + return (uint)((value | 0x20) - 'a') <= (uint)('z' - 'a'); + } + + /// + /// Normalize separators in the given path. Converts forward slashes into back slashes and compresses slash runs, keeping initial 2 if present. + /// Also trims initial whitespace in front of "rooted" paths (see PathStartSkip). + /// + /// This effectively replicates the behavior of the legacy NormalizePath when it was called with fullCheck=false and expandShortpaths=false. + /// The current NormalizePath gets directory separator normalization from Win32's GetFullPathName(), which will resolve relative paths and as + /// such can't be used here (and is overkill for our uses). + /// + /// Like the current NormalizePath this will not try and analyze periods/spaces within directory segments. + /// + /// + /// The only callers that used to use Path.Normalize(fullCheck=false) were Path.GetDirectoryName() and Path.GetPathRoot(). Both usages do + /// not need trimming of trailing whitespace here. + /// + /// GetPathRoot() could technically skip normalizing separators after the second segment- consider as a future optimization. + /// + /// For legacy .NET Framework behavior with ExpandShortPaths: + /// - It has no impact on GetPathRoot() so doesn't need consideration. + /// - It could impact GetDirectoryName(), but only if the path isn't relative (C:\ or \\Server\Share). + /// + /// In the case of GetDirectoryName() the ExpandShortPaths behavior was undocumented and provided inconsistent results if the path was + /// fixed/relative. For example: "C:\PROGRA~1\A.TXT" would return "C:\Program Files" while ".\PROGRA~1\A.TXT" would return ".\PROGRA~1". If you + /// ultimately call GetFullPath() this doesn't matter, but if you don't or have any intermediate string handling could easily be tripped up by + /// this undocumented behavior. + /// + /// We won't match this old behavior because: + /// + /// 1. It was undocumented + /// 2. It was costly (extremely so if it actually contained '~') + /// 3. Doesn't play nice with string logic + /// 4. Isn't a cross-plat friendly concept/behavior + /// + [return: NotNullIfNotNull("path")] + internal static string NormalizeDirectorySeparators(string path) + { + if (string.IsNullOrEmpty(path)) + { + return path; + } + + char current; + + // Make a pass to see if we need to normalize so we can potentially skip allocating + var normalized = true; + + for (var i = 0; i < path.Length; i++) + { + current = path[i]; + if (IsDirectorySeparator(current) + && (current != DirectorySeparatorChar + // Check for sequential separators past the first position (we need to keep initial two for UNC/extended) + || (i > 0 && i + 1 < path.Length && IsDirectorySeparator(path[i + 1])))) + { + normalized = false; + break; + } + } + + if (normalized) + { + return path; + } + + var builder = new StringBuilder(MaxShortPath); + var start = 0; + + if (IsDirectorySeparator(path[start])) + { + start++; + builder.Append(DirectorySeparatorChar); + } + + for (var i = start; i < path.Length; i++) + { + current = path[i]; + + // If we have a separator + if (IsDirectorySeparator(current)) + { + // If the next is a separator, skip adding this + if (i + 1 < path.Length && IsDirectorySeparator(path[i + 1])) + { + continue; + } + + // Ensure it is the primary separator + current = DirectorySeparatorChar; + } + + builder.Append(current); + } + + return builder.ToString(); + } + } +} diff --git a/System.IO.FileSystem/Properties/AssemblyInfo.cs b/System.IO.FileSystem/Properties/AssemblyInfo.cs index cc0af07..7b2665b 100644 --- a/System.IO.FileSystem/Properties/AssemblyInfo.cs +++ b/System.IO.FileSystem/Properties/AssemblyInfo.cs @@ -17,5 +17,7 @@ //////////////////////////////////////////////////////////////// // update this whenever the native assembly signature changes // -[assembly: AssemblyNativeVersion("1.0.0.1")] +[assembly: AssemblyNativeVersion("1.0.0.2")] //////////////////////////////////////////////////////////////// + +[assembly: InternalsVisibleTo("NFUnitTest, PublicKey=00240000048000009400000006020000002400005253413100040000010001001120aa3e809b3da4f65e1b1f65c0a3a1bf6335c39860ca41acb3c48de278c6b63c5df38239ec1f2e32d58cb897c8c174a5f8e78a9c0b6087d3aef373d7d0f3d9be67700fc2a5a38de1fb71b5b6f6046d841ff35abee2e0b0840a6291a312be184eb311baff5fef0ff6895b9a5f2253aed32fb06b819134f6bb9d531488a87ea2")] \ No newline at end of file diff --git a/System.IO.FileSystem/System.IO.FileSystem.nfproj b/System.IO.FileSystem/System.IO.FileSystem.nfproj index 2d3ab49..5192c03 100644 --- a/System.IO.FileSystem/System.IO.FileSystem.nfproj +++ b/System.IO.FileSystem/System.IO.FileSystem.nfproj @@ -13,7 +13,7 @@ Library Properties 512 - System.IO.FileSystem + System.IO System.IO.FileSystem v1.0 True @@ -53,6 +53,7 @@ + @@ -75,6 +76,9 @@ ..\packages\nanoFramework.Runtime.Events.1.11.15\lib\nanoFramework.Runtime.Events.dll True + + ..\packages\nanoFramework.System.Runtime.1.0.6\lib\nanoFramework.System.Runtime.dll + ..\packages\nanoFramework.System.Text.1.2.54\lib\nanoFramework.System.Text.dll True diff --git a/System.IO.FileSystem/packages.config b/System.IO.FileSystem/packages.config index 4ebce24..938a8c7 100644 --- a/System.IO.FileSystem/packages.config +++ b/System.IO.FileSystem/packages.config @@ -3,6 +3,7 @@ + \ No newline at end of file diff --git a/System.IO.FileSystem/packages.lock.json b/System.IO.FileSystem/packages.lock.json index 3f37d48..24c58f7 100644 --- a/System.IO.FileSystem/packages.lock.json +++ b/System.IO.FileSystem/packages.lock.json @@ -20,6 +20,12 @@ "resolved": "1.1.52", "contentHash": "gdExWfWNSl4dgaIoVHHFmhLiRSKAabHA8ueHuErGAWd97qaoN2wSHCtvKqfOu1zuzyccbFpm4HBxVsh6bWMyXw==" }, + "nanoFramework.System.Runtime": { + "type": "Direct", + "requested": "[1.0.6, 1.0.6]", + "resolved": "1.0.6", + "contentHash": "n87itPUMSsOJkUsdoXr0vhiBTggZBMgCtIIC7c+RsVAhF2u/0TU/h+ZLNyFL8Xhl0taPcTN4LiPPTkI+e95Q/g==" + }, "nanoFramework.System.Text": { "type": "Direct", "requested": "[1.2.54, 1.2.54]", diff --git a/nanoFramework.System.IO.FileSystem.nuspec b/nanoFramework.System.IO.FileSystem.nuspec index 30909a6..93f9823 100644 --- a/nanoFramework.System.IO.FileSystem.nuspec +++ b/nanoFramework.System.IO.FileSystem.nuspec @@ -20,8 +20,10 @@ This package requires a target with System.IO.FileSystem v$nativeVersion$ (check nanoFramework C# csharp netmf netnf System.IO.FileSystem - - + + + +