Skip to content

Commit

Permalink
Added stringByAppendingPathComponent: and stringByAppendingPathExtens…
Browse files Browse the repository at this point in the history
…ion: to CPString. Also, changed CPString path behaviour to correspond to Cocoa behaviour.
  • Loading branch information
mrcarlberg committed May 11, 2012
1 parent c437537 commit c35ccde
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 18 deletions.
120 changes: 106 additions & 14 deletions Foundation/CPString.j
Expand Up @@ -706,14 +706,67 @@ var CPStringRegexSpecialCharacters = [
Returns an the path components of this string. This
method assumes that the string's content is a '/'
separated file system path.
Multiple '/' separators between components are truncated to a single one.
*/
- (CPArray)pathComponents
{
if (length === 0) return [""];
if (self === "/") return ["/"];
var result = split('/');
if (result[0] === "")
result[0] = "/";
if (result[result.length - 1] === "")
result.pop();
var index = result.length - 1;
if (index > 0)
{
if (result[index] === "")
result[index] = "/";
while (index--)
{
while (result[index] === "")
{
result.splice(index--, 1);
}
}
}
return result;
}

/*!
Returns a string built from the strings in a given array by
concatenating them with a path separator between each pair.
This method assumes that the string's content is a '/'
separated file system path.
Multiple '/' separators between components are truncated to a single one.
*/
+ (CPString)pathWithComponents:(CPArray)components
{
var size = components.length,
result = "",
i = -1,
firstRound = true,
firstIsSlash = false;

while (++i < size)
{
var component = components[i],
lenMinusOne = component.length - 1;
if (lenMinusOne >= 0 && (component !== "/" || firstRound)) // Skip "" and "/" (not first time)
{
if (lenMinusOne > 0 && component.indexOf("/",lenMinusOne) === lenMinusOne) // Ends with "/"
component = component.substring(0, lenMinusOne);
if (firstRound)
{
if (component === "/")
firstIsSlash = true;
firstRound = false;
}
else if (!firstIsSlash)
result += "/";
else
firstIsSlash = false;
result += component;
}
}
return result;
}

Expand All @@ -737,33 +790,72 @@ var CPStringRegexSpecialCharacters = [
*/
- (CPString)lastPathComponent
{
var components = [self pathComponents];
return components[components.length - 1];
var components = [self pathComponents],
lastIndex = components.length - 1,
lastComponent = components[lastIndex];
return lastIndex > 0 && lastComponent === "/" ? components[lastIndex - 1] : lastComponent;
}

/*!
Deletes the last path component of a string.
Returns a new string made by appending to the receiver a given string
This method assumes that the string's content is a '/'
separated file system path.
Multiple '/' separators between components are truncated to a single one.
*/
- (CPString)stringByDeletingLastPathComponent
- (CPString)stringByAppendingPathComponent:(CPString)aString
{
var components = [self pathComponents],
addComponents = aString && aString !== "/" ? [aString pathComponents] : [];
return [CPString pathWithComponents:components.concat(addComponents)];
}

/*!
Returns a new string made by appending to the receiver an extension separator followed by a given extension
This method assumes that the extension separator is a '.'
Extension can't include a '/' character, receiver can't be empty or be just a '/'. If so the
result will be the receiver itself.
Multiple '/' separators between components are truncated to a single one.
*/
- (CPString)stringByAppendingPathExtension:(CPString)ext
{
var path = self,
start = length - 1;
if (ext.indexOf('/') >= 0 || length === 0 || self === "/") // Can't handle these
return self;
var components = [self pathComponents],
last = components.length - 1;

if (last > 0 && components[last] === "/")
components.splice(last--, 1);

components[last] = components[last] + "." + ext;

while (path.charAt(start) === '/')
start--;
return [CPString pathWithComponents:components];
}

path = path.substr(0, path.lastIndexOf('/', start));
/*!
Deletes the last path component of a string.
This method assumes that the string's content is a '/'
separated file system path.
Multiple '/' separators between components are truncated to a single one.
*/
- (CPString)stringByDeletingLastPathComponent
{
if (length === 0) return "";
if (self === "/") return "/";
var components = [self pathComponents],
last = components.length - 1;

if (path === "" && charAt(0) === '/')
return '/';
if (components[last] === "/")
last--;
components.splice(last, components.length - last);

return path;
return [CPString pathWithComponents:components];
}

/*!
Deletes the extension of a string.
This method assumes that the string's content is a '/'
separated file system path.
Multiple '/' separators between components are truncated to a single one.
*/
- (CPString)stringByDeletingPathExtension
{
Expand Down
91 changes: 87 additions & 4 deletions Tests/Foundation/CPStringTest.j
Expand Up @@ -225,6 +225,52 @@
[self assert:"ffffff" equals:[CPString stringWithHash:16777215]];
}

- (void)testStringByAppendingPathComponent
{
var testStrings = [
["/tmp/", "scratch.tiff", "/tmp/scratch.tiff"],
["/tmp///", "scratch.tiff", "/tmp/scratch.tiff"],
["/tmp///", "///scratch.tiff", "/tmp/scratch.tiff"],
["/tmp", "scratch.tiff", "/tmp/scratch.tiff"],
["/tmp///", "scratch.tiff", "/tmp/scratch.tiff"],
["/tmp///", "///scratch.tiff", "/tmp/scratch.tiff"],
["/", "scratch.tiff", "/scratch.tiff"],
["", "scratch.tiff", "scratch.tiff"],
["", "", ""],
["", "/", ""],
["/", "/", "/"],
["/tmp", nil, "/tmp"],
["/tmp", "/", "/tmp"],
["/tmp/", "", "/tmp"]
];

for (var i = 0; i < testStrings.length; i++)
{
var result = [testStrings[i][0] stringByAppendingPathComponent:testStrings[i][1]];

[self assertTrue:result === testStrings[i][2] message:"Value <" + testStrings[i][0] + "> Adding <" + testStrings[i][1] + "> Expected <" + testStrings[i][2] + "> was <" + result + ">"];
}
}

- (void)testStringByAppendingPathExtension
{
var testStrings = [
["/tmp/scratch.old", "tiff", "/tmp/scratch.old.tiff"],
["/tmp/scratch.", "tiff", "/tmp/scratch..tiff"],
["/tmp///", "tiff", "/tmp.tiff"],
["scratch", "tiff", "scratch.tiff"],
["/", "tiff", "/"],
["", "tiff", ""]
];

for (var i = 0; i < testStrings.length; i++)
{
var result = [testStrings[i][0] stringByAppendingPathExtension:testStrings[i][1]];

[self assertTrue:result === testStrings[i][2] message:"Value <" + testStrings[i][0] + "> Adding <" + testStrings[i][1] + "> Expected <" + testStrings[i][2] + "> was <" + result + ">"];
}
}

- (void)testStringByDeletingLastPathComponent
{
var testStrings = [
Expand All @@ -235,22 +281,58 @@
["/", "/"],
["scratch.tiff", ""],
["a/b/c/d//////", "a/b/c"],
["a/b/////////c/d//////", "a/b/c"],
["a/b/././././c/d/./././", "a/b/././././c/d/./."],
[@"a/b/././././d////", "a/b/./././."],
[@"~/a", "~"],
[@"~/a/", "~"],
[@"../../", ".."]
[@"../../", ".."],
[@"", ""]
];

for (var i = 0; i < testStrings.length; i++)
[self assert:[testStrings[i][0] stringByDeletingLastPathComponent] equals:testStrings[i][1]];
{
var result = [testStrings[i][0] stringByDeletingLastPathComponent];

[self assertTrue:result === testStrings[i][1] message:"Value <" + testStrings[i][0] + "> Expected <" + testStrings[i][1] + "> was <" + result + ">"];
}
}

- (void)testPathWithComponents
{
var testStrings = [
[["tmp", "scratch"], "tmp/scratch"],
[["/", "tmp", "scratch"], "/tmp/scratch"],
[["/", "tmp", "/", "scratch"], "/tmp/scratch"],
[["/", "tmp", "scratch", "/"], "/tmp/scratch"],
[["/", "tmp", "scratch", ""], "/tmp/scratch"],
[["", "/tmp", "scratch", ""], "/tmp/scratch"],
[["", "tmp", "scratch", ""], "tmp/scratch"],
[["/"], "/"],
[["/", "/", "/"], "/"],
[["", "", ""], ""],
[[""], ""]
];

for (var i = 0; i < testStrings.length; i++)
{
var result = [CPString pathWithComponents:testStrings[i][0]];

[self assertTrue:result === testStrings[i][1] message:"Value <" + testStrings[i][0] + "> Expected [" + testStrings[i][1] + "] was [" + result + "]"];
}
}

- (void)testPathComponents
{
var testStrings = [
["tmp/scratch", ["tmp", "scratch"]],
["/tmp/scratch", ["/", "tmp", "scratch"]]
["/tmp/scratch", ["/", "tmp", "scratch"]],
["/tmp/scratch/", ["/", "tmp", "scratch", "/"]],
["/tmp/", ["/", "tmp", "/"]],
["/////tmp/////scratch///", ["/", "tmp", "scratch", "/"]],
["scratch.tiff", ["scratch.tiff"]],
["/", ["/"]],
["", [""]]
];

for (var i = 0; i < testStrings.length; i++)
Expand All @@ -268,7 +350,8 @@
["/tmp/scratch", "scratch"],
["/tmp/", "tmp"],
["scratch", "scratch"],
["/", "/"]
["/", "/"],
["", ""]
];

for (var i = 0; i < testStrings.length; i++)
Expand Down

0 comments on commit c35ccde

Please sign in to comment.