334 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
		
		
			
		
	
	
			334 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
|  | /* | ||
|  |  * Copyright (C)2005-2019 Haxe Foundation | ||
|  |  * | ||
|  |  * 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. | ||
|  |  */ | ||
|  | 
 | ||
|  | 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.extension` | ||
|  | **/ | ||
|  | 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:Null<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:Null<String>; | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		`true` if the last directory separator is a backslash, `false` otherwise. | ||
|  | 	**/ | ||
|  | 	public var backslash:Bool; | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Creates a new `Path` instance by parsing `path`. | ||
|  | 
 | ||
|  | 		Path information can be retrieved by accessing the `dir`, `file` and `ext` | ||
|  | 		properties. | ||
|  | 	**/ | ||
|  | 	public function new(path:String) { | ||
|  | 		switch (path) { | ||
|  | 			case "." | "..": | ||
|  | 				dir = path; | ||
|  | 				file = ""; | ||
|  | 				return; | ||
|  | 		} | ||
|  | 		var c1 = path.lastIndexOf("/"); | ||
|  | 		var c2 = path.lastIndexOf("\\"); | ||
|  | 		if (c1 < c2) { | ||
|  | 			dir = path.substr(0, c2); | ||
|  | 			path = path.substr(c2 + 1); | ||
|  | 			backslash = true; | ||
|  | 		} else if (c2 < c1) { | ||
|  | 			dir = path.substr(0, c1); | ||
|  | 			path = path.substr(c1 + 1); | ||
|  | 		} else | ||
|  | 			dir = null; | ||
|  | 		var cp = path.lastIndexOf("."); | ||
|  | 		if (cp != -1) { | ||
|  | 			ext = path.substr(cp + 1); | ||
|  | 			file = path.substr(0, cp); | ||
|  | 		} else { | ||
|  | 			ext = null; | ||
|  | 			file = 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 `""`. | ||
|  | 	**/ | ||
|  | 	public function toString():String { | ||
|  | 		return (if (dir == null) "" else dir + if (backslash) "\\" else "/") + file + (if (ext == null) "" else "." + ext); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Returns the String representation of `path` without the file extension. | ||
|  | 
 | ||
|  | 		If `path` is `null`, the result is unspecified. | ||
|  | 	**/ | ||
|  | 	public static function withoutExtension(path:String):String { | ||
|  | 		var s = new Path(path); | ||
|  | 		s.ext = null; | ||
|  | 		return s.toString(); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Returns the String representation of `path` without the directory. | ||
|  | 
 | ||
|  | 		If `path` is `null`, the result is unspecified. | ||
|  | 	**/ | ||
|  | 	public static function withoutDirectory(path):String { | ||
|  | 		var s = new Path(path); | ||
|  | 		s.dir = null; | ||
|  | 		return s.toString(); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		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):String { | ||
|  | 		var s = new Path(path); | ||
|  | 		if (s.dir == null) | ||
|  | 			return ""; | ||
|  | 		return s.dir; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Returns the extension of `path`. | ||
|  | 
 | ||
|  | 		If `path` has no extension, the empty String `""` is returned. | ||
|  | 
 | ||
|  | 		If `path` is `null`, the result is unspecified. | ||
|  | 	**/ | ||
|  | 	public static function extension(path):String { | ||
|  | 		var s = new Path(path); | ||
|  | 		if (s.ext == null) | ||
|  | 			return ""; | ||
|  | 		return s.ext; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		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):String { | ||
|  | 		var s = new Path(path); | ||
|  | 		s.ext = ext; | ||
|  | 		return s.toString(); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Joins all paths in `paths` together. | ||
|  | 
 | ||
|  | 		If `paths` is empty, the empty String `""` is returned. Otherwise the | ||
|  | 		paths are joined with a slash between them. | ||
|  | 
 | ||
|  | 		If `paths` is `null`, the result is unspecified. | ||
|  | 	**/ | ||
|  | 	public static function join(paths:Array<String>):String { | ||
|  | 		var paths = paths.filter(function(s) return s != null && s != ""); | ||
|  | 		if (paths.length == 0) { | ||
|  | 			return ""; | ||
|  | 		} | ||
|  | 		var path = paths[0]; | ||
|  | 		for (i in 1...paths.length) { | ||
|  | 			path = addTrailingSlash(path); | ||
|  | 			path += paths[i]; | ||
|  | 		} | ||
|  | 		return normalize(path); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Normalize a given `path` (e.g. turn `'/usr/local/../lib'` into `'/usr/lib'`). | ||
|  | 
 | ||
|  | 		Also replaces backslashes `\` with slashes `/` and afterwards turns | ||
|  | 		multiple slashes into a single one. | ||
|  | 
 | ||
|  | 		If `path` is `null`, the result is unspecified. | ||
|  | 	**/ | ||
|  | 	public static function normalize(path:String):String { | ||
|  | 		var slash = "/"; | ||
|  | 		path = path.split("\\").join(slash); | ||
|  | 		if (path == slash) | ||
|  | 			return slash; | ||
|  | 
 | ||
|  | 		var target = []; | ||
|  | 
 | ||
|  | 		for (token in path.split(slash)) { | ||
|  | 			if (token == '..' && target.length > 0 && target[target.length - 1] != "..") { | ||
|  | 				target.pop(); | ||
|  | 			} else if (token == '') { | ||
|  | 				if (target.length > 0 || path.charCodeAt(0) == '/'.code) { | ||
|  | 					target.push(token); | ||
|  | 				} | ||
|  | 			} else if (token != '.') { | ||
|  | 				target.push(token); | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		var tmp = target.join(slash); | ||
|  | 		var acc = new StringBuf(); | ||
|  | 		var colon = false; | ||
|  | 		var slashes = false; | ||
|  | 		#if utf16 | ||
|  | 		for (c in haxe.iterators.StringIteratorUnicode.unicodeIterator(tmp)) { | ||
|  | 			switch (c) { | ||
|  | 		#else | ||
|  | 		for (i in 0...tmp.length) { | ||
|  | 			switch (StringTools.fastCodeAt(tmp, i)) { | ||
|  | 		#end | ||
|  | 				case ":".code: | ||
|  | 					acc.add(":"); | ||
|  | 					colon = true; | ||
|  | 				case "/".code if (!colon): | ||
|  | 					slashes = true; | ||
|  | 				case var i: | ||
|  | 					colon = false; | ||
|  | 					if (slashes) { | ||
|  | 						acc.add("/"); | ||
|  | 						slashes = false; | ||
|  | 					} | ||
|  | 					acc.addChar(i); | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		return acc.toString(); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		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 { | ||
|  | 		if (path.length == 0) | ||
|  | 			return "/"; | ||
|  | 		var c1 = path.lastIndexOf("/"); | ||
|  | 		var c2 = path.lastIndexOf("\\"); | ||
|  | 		return if (c1 < c2) { | ||
|  | 			if (c2 != path.length - 1) | ||
|  | 				path + "\\"; | ||
|  | 			else | ||
|  | 				path; | ||
|  | 		} else { | ||
|  | 			if (c1 != path.length - 1) | ||
|  | 				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. | ||
|  | 	**/ | ||
|  | 	public static function removeTrailingSlashes(path:String):String { | ||
|  | 		while (true) { | ||
|  | 			switch (path.charCodeAt(path.length - 1)) { | ||
|  | 				case '/'.code | '\\'.code: | ||
|  | 					path = path.substr(0, -1); | ||
|  | 				case _: | ||
|  | 					break; | ||
|  | 			} | ||
|  | 		} | ||
|  | 		return path; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Returns `true` if the path is an absolute path, and `false` otherwise. | ||
|  | 	**/ | ||
|  | 	public static function isAbsolute(path:String):Bool { | ||
|  | 		if (StringTools.startsWith(path, '/')) | ||
|  | 			return true; | ||
|  | 		if (path.charAt(1) == ':') | ||
|  | 			return true; | ||
|  | 		if (StringTools.startsWith(path, '\\\\')) | ||
|  | 			return true; | ||
|  | 		return false; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	private static function unescape(path:String):String { | ||
|  | 		var regex = ~/-x([0-9][0-9])/g; | ||
|  | 		return regex.map(path, function(regex) return String.fromCharCode(Std.parseInt(regex.matched(1)))); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	private static function escape(path:String, allowSlashes:Bool = false):String { | ||
|  | 		var regex = allowSlashes ? ~/[^A-Za-z0-9_\/\\\.]/g : ~/[^A-Za-z0-9_\.]/g; | ||
|  | 		return regex.map(path, function(v) return '-x' + v.matched(0).charCodeAt(0)); | ||
|  | 	} | ||
|  | } |