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));
|
|
}
|
|
}
|