Browse Source

Merge pull request #2678 from kasoki/enhance_path

Add ability to normalize and join paths to haxe.io.Path
Simon Krajewski 11 years ago
parent
commit
54ebe3c7e3
2 changed files with 87 additions and 29 deletions
  1. 78 29
      std/haxe/io/Path.hx
  2. 9 0
      tests/unit/unitstd/haxe/io/Path.unit.hx

+ 78 - 29
std/haxe/io/Path.hx

@@ -24,7 +24,7 @@ package haxe.io;
 /**
 	This class provides a convenient way of working with paths. It supports the
 	common path formats:
-		
+
 	- directory1/directory2/filename.extension
 	- directory1\directory2\filename.excention
 **/
@@ -32,36 +32,36 @@ class Path {
 
 	/**
 		The directory.
-		
+
 		This is the leading part of the path that is not part of the file name
 		and the extension.
-		
+
 		Does not end with a `/` or `\` separator.
-		
+
 		If the path has no directory, the value is null.
 	**/
 	public var dir : String;
-	
+
 	/**
 		The file name.
-		
+
 		This is the part of the part between the directory and the extension.
-		
+
 		If there is no file name, e.g. for ".htaccess" or "/dir/", the value
 		is the empty String "".
 	**/
 	public var file : String;
-	
+
 	/**
 		The file extension.
-		
+
 		It is separated from the file name by a dot. This dot is not part of
 		the extension.
-		
+
 		If the path has no extension, the value is null.
 	**/
 	public var ext : String;
-		
+
 	/**
 		True if the last directory separator is a backslash, false otherwise.
 	**/
@@ -69,7 +69,7 @@ class Path {
 
 	/**
 		Creates a new Path instance by parsing `path`.
-		
+
 		Path information can be retrieved by accessing the dir, file and ext
 		properties.
 	**/
@@ -97,11 +97,11 @@ class Path {
 
 	/**
 		Returns a String representation of `this` path.
-		
+
 		If `this.backslash` is true, backslash is used as directory separator,
 		otherwise slash is used. This only affects the separator between
 		`this.dir` and `this.file`.
-		
+
 		If `this.directory` or `this.extension` is null, their representation
 		is the empty String "".
 	**/
@@ -111,7 +111,7 @@ class Path {
 
 	/**
 		Returns the String representation of `path` without the file extension.
-		
+
 		If `path` is null, the result is unspecified.
 	**/
 	public static function withoutExtension( path : String ) {
@@ -122,7 +122,7 @@ class Path {
 
 	/**
 		Returns the String representation of `path` without the directory.
-		
+
 		If `path` is null, the result is unspecified.
 	**/
 	public static function withoutDirectory( path ) {
@@ -133,9 +133,9 @@ class Path {
 
 	/**
 		Returns the directory of `path`.
-		
+
 		If the directory is null, the empty String "" is returned.
-		
+
 		If `path` is null, the result is unspecified.
 	**/
 	public static function directory( path ) {
@@ -147,9 +147,9 @@ class Path {
 
 	/**
 		Returns the extension of `path`.
-		
+
 		If the extension is null, the empty String "" is returned.
-		
+
 		If `path` is null, the result is unspecified.
 	**/
 	public static function extension( path ) {
@@ -161,9 +161,9 @@ class Path {
 
 	/**
 		Returns a String representation of `path` where the extension is `ext`.
-		
+
 		If `path` has no extension, `ext` is added as extension.
-		
+
 		If `path` or `ext` are null, the result is unspecified.
 	**/
 	public static function withExtension( path, ext ) {
@@ -172,16 +172,65 @@ class Path {
 		return s.toString();
 	}
 
+	/**
+		Join two paths together and normalize them
+
+		e.g. 'assets/maps' and '../textures/img.png' = 'assets/textures/img.png'
+	**/
+	public static function join( path1 : String, path2 : String ) : String {
+		path1 = Path.addTrailingSlash(path1);
+
+		return Path.normalize(path1 + path2);
+	}
+
+	/**
+		Normalize a given `path` (e.g. make '/usr/local/../lib' to '/usr/lib')
+	**/
+	public static function normalize( path : String) : String {
+		var slash = '/';
+
+		if( path == null || path == slash ) {
+			return slash;
+		}
+
+		var prependSlash = (path.charAt(0) == slash ||
+			path.charAt(0) == '.');
+		var target = [];
+		var src;
+		var parts;
+		var token;
+
+		src = path.split(slash);
+
+		for( i in 0...src.length ) {
+			token = src[i];
+
+			if(token == '..') {
+				target.pop();
+			} else if(token != '' && token != '.') {
+				target.push(token);
+			}
+		}
+
+		var regex = ~/[\/]{2,}/g;
+
+		var tmp = target.join(slash);
+
+		var result = regex.replace(tmp, slash);
+
+		return (prependSlash ? slash : '') + result;
+	}
+
 	/**
 		Adds a trailing slash to `path`, if it does not have one already.
-		
+
 		If the last slash in `path` is a backslash, a backslash is appended to
 		`path`.
-		
+
 		If the last slash in `path` is a slash, or if no slash is found, a slash
 		is appended to `path`. In particular, this applies to the empty String
 		"".
-		
+
 		If `path` is null, the result is unspecified.
 	**/
 	public static function addTrailingSlash( path : String ) : String {
@@ -197,15 +246,15 @@ class Path {
 			else path;
 		}
 	}
-	
+
 	/**
 		Removes trailing slashes from `path`.
-		
+
 		If `path` does not end with a `/` or `\`, `path` is returned unchanged.
-		
+
 		Otherwise the substring of `path` excluding the trailing slashes or
 		backslashes is returned.
-		
+
 		If `path` is null, the result is unspecified.
 	**/
 	@:require(haxe_ver >= 3.01)

+ 9 - 0
tests/unit/unitstd/haxe/io/Path.unit.hx

@@ -60,6 +60,15 @@ haxe.io.Path.withExtension(path2, "foo") == "/dir1/dir.with.dots\\file.foo";
 haxe.io.Path.withExtension(path3, "foo") == ".foo";
 haxe.io.Path.withExtension(path4, "foo") == "/dir/.foo";
 
+// normalize
+haxe.io.Path.normalize("dir1/dir2/../dir3") == "dir1/dir3";
+haxe.io.Path.normalize("/dir1/dir2/../../test.foo") == "/test.foo";
+haxe.io.Path.normalize("dir1/dir2/dir3/dir4/../../../dir5") == "dir1/dir5";
+
+// join
+haxe.io.Path.join("dir1/dir2", "dir3/dir4") == "dir1/dir2/dir3/dir4";
+haxe.io.Path.join("dir1/dir2/bad_dir/", "../dir3/dir4") == "dir1/dir2/dir3/dir4";
+
 // addTrailingSlash
 haxe.io.Path.addTrailingSlash("") == "/";
 haxe.io.Path.addTrailingSlash("a") == "a/";