We are all tremendously happy about back slashes in Windows file systems and do not think that their sole purpose is to make life complicated for smart people who are capable of using different OS. Upvote if disagree [ :) ].
Converting paths from/to Windows backward slashes is a pretty monotonous task. Here is a library class to help with it. GitHub
OSPath path = @"/foo\bar.txt";
// Windows output
WriteLine(path); // \foo\bar.txt
WriteLine(path.Windows); // \foo\bar.txt
WriteLine(path.Unix); // /foo/bar.txt
// MacOS output
WriteLine(path); // /foo/bar.txt
WriteLine(path.Windows); // \foo\bar.txt
WriteLine(path.Unix); // /foo/bar.txt
It also helps converting between relative and absolute paths:
OSPath ap = "/foo/bar";
WriteLine(ap.Relative.Unix); // foo/bar
WriteLine(ap.Absolute.Unix); // /foo/bar
OSPath rp = "foo/bar";
WriteLine(rp.Relative.Unix); // foo/bar
WriteLine(rp.Absolute.Unix); // /foo/bar
And to perform path arithmetic (Windows output):
OSPath root = @"/foo\bar";
WriteLine(root + "file.txt"); // \foo\bar\file.txt
WriteLine(root + "file.txt" - root); // file.txt
Library class is:
public class OSPath
{
public static readonly OSPath Empty = "";
public static bool IsWindows => DirectorySeparatorChar == '\\';
public OSPath(string path)
{
Path = path.Trim();
}
public static implicit operator OSPath(string path) => new OSPath(path);
public static implicit operator string(OSPath path) => path.Normalized;
public override string ToString() => Normalized;
protected string Path { get; }
public string Normalized => IsWindows ? Windows : Unix;
public string Windows => Path.Replace('/', '\\');
public string Unix => Volumeless.Path.Replace('\\', '/');
public OSPath Relative => Volumeless.Path.TrimStart('/', '\\');
public OSPath Absolute => IsAbsolute ? this : "/" + Relative;
public bool IsAbsolute => IsPathRooted(Path);
public bool HasVolume => IsAbsolute && Path[1] == ':';
public OSPath Volumeless => HasVolume
? (this - GetPathRoot(Path)).Absolute
: this;
public OSPath Parent => GetDirectoryName(Path);
public bool Contains(OSPath path) =>
Normalized.StartsWith(path);
public static OSPath operator +(OSPath left, OSPath right) =>
new OSPath(Combine(left, right.Relative));
public static OSPath operator -(OSPath left, OSPath right) =>
left.Contains(right)
? new OSPath(left.Normalized.Substring(right.Normalized.Length)).Relative
: left;
}
1 Answer 1
There are a couple of things that bother me:
1) I don't think you have properly implemented relative and absolute paths. At least in Windows: absolute path = working directory + relative path
. Your class only does this conversion correctly, if working directory is root directory, which is almost never the case in .Net. I don't know how things are in Linux, but from Windows perspective this is a surprising behavior. It should be either well documented or fixed.
2) You assume that 1:1 two-way conversion always exists. But does it? Windows is not case sensitive, Linux is. Windows has one letter volumes, Linux does not. Those are just a few differences off the top of my head. You can replace slashes all you want but that won't give you volume information.
3) How would you add macOS
to current implementation? AFAIR, it is very much like Linux, but not exactly the same, is it? To me it looks like you would have to pretty much rewrite the entire thing. This is a good indication that overall design can be improved.
-
\$\begingroup\$ OK, you got me guys :) Inheriting
UnixPath
andWindowsPath
from OSPath :) And it should probably beRooted
instead ofAbsolute
... Also, from a pragmatic point of view, 1:1 is not always necessary, so it does make sense to have something like that... To persist a relative path, or to append it to URL, etc. \$\endgroup\$Dmitry Nogin– Dmitry Nogin2018年06月13日 16:42:22 +00:00Commented Jun 13, 2018 at 16:42 -
\$\begingroup\$ Nope, I was right originally - splitting OSPath to multiple classes is just a useless over-complication with a lot of consequences and no practical gain. P.S. MacOS is the same as *nix, so this class will never ever be interested in modification. It does not make sense to convert damn simple and very useful small utility to anything bigger :) \$\endgroup\$Dmitry Nogin– Dmitry Nogin2018年06月13日 17:40:19 +00:00Commented Jun 13, 2018 at 17:40
-
\$\begingroup\$ @DmitryNogin anything having less than three classes is worthless :-P (just kidding) - but really, having everything in a single class is like having a book where on the left side there is one topic and on the right side another one :-] \$\endgroup\$t3chb0t– t3chb0t2018年06月13日 17:51:40 +00:00Commented Jun 13, 2018 at 17:51
-
\$\begingroup\$ @t3chb0t SRP is all about managing stability (giving one reason to change). This type will not change - it is common for small utilities. They just need to be useful by including everything used together. Look at
String
or that one I mentioned before -Uri
classLocalPath
andAbsolutePath
properties - they do map from Unix to Windows for the same purposes and implemented in the same class. Unfortunately,Uri
can not swallow/foo/bar
:) \$\endgroup\$Dmitry Nogin– Dmitry Nogin2018年06月13日 18:34:30 +00:00Commented Jun 13, 2018 at 18:34 -
\$\begingroup\$ @DmitryNogin, just to clarify: I don't think that you should have multiple classes to represent path. The general idea of having an entity to represent OS-agnostic path makes sense to me. I just think that the current implementation can be improved. In particular I think that you should pick one OS's path notation and use it internally as common denominator. Your implementation is too complex, because sometimes
Path
property holds windows path, and sometimes it holds linux path, and you have to account for both possibilities every step of the way. \$\endgroup\$Nikita B– Nikita B2018年06月13日 18:42:50 +00:00Commented Jun 13, 2018 at 18:42
Volumeless
should be callesOSLess
since this is an OS-free path, right? \$\endgroup\$Uri
or something like that. Pretty common for stable utility classes with no dependencies and not many reasons to change. 2) Yep, Volumeless sounds ugly, agree :) \$\endgroup\$Uri
isn't a good example becaue unlike your class, it's a generic representation or the URI concept. It does not hardcode e.g. schemas like yourOSPath
does with Windows & Unix etc. \$\endgroup\$Windows
orUnix
properties which are virtually hardcoded paths representations. \$\endgroup\$/
inAbsolutePath
and\` in
LocalPath`. I have general representation of OS Path. Actually, without anything asking for polymorphic behavior - I need Unix representation all the time for persistence, etc. \$\endgroup\$